fix(gateway): gate /profile source scoping on multiplex_profiles

Review follow-up: honor source.profile and enter _profile_runtime_scope
only when gateway.multiplex_profiles is on, mirroring the gating in
_run_agent, _reset_notice_session_info, and _resolve_profile_for_key.
When multiplexing is off (the default) a stamped source is ignored and
/profile reports the active profile and default home, byte-identical to
before this PR.

The stamped-source test now enables multiplexing (it previously
exercised the ungated path under the default config), and a new
regression asserts the stamp is ignored when multiplexing is off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jonny Kovacs 2026-07-11 11:53:53 -05:00 committed by Teknium
parent f7d6f099db
commit 6b1267c2e4
2 changed files with 55 additions and 12 deletions

View file

@ -337,25 +337,36 @@ class GatewaySlashCommandsMixin:
answer "default" in every chat regardless of which profile actually
serves the room/channel (``source.profile`` stamped by the
``/p/<profile>/`` URL prefix, a per-credential adapter, or a room
profile map). Report the stamped profile and, like the scoped /reset
banner (#59003), resolve the displayed home under that profile's
runtime scope. Single-profile gateways are unchanged: an unstamped
source falls back to the active profile and the default home.
profile map). When ``multiplex_profiles`` is on, report the stamped
profile and, like the scoped /reset banner (#59003), resolve the
displayed home under that profile's runtime scope. When multiplexing
is off (the default) the stamp is ignored mirroring the gating in
``_run_agent`` and ``_reset_notice_session_info`` and the command
reports the active profile and default home, byte-identical to before.
"""
from hermes_constants import display_hermes_home
from hermes_cli.profiles import get_active_profile_name
multiplexed = getattr(
getattr(self, "config", None), "multiplex_profiles", False
)
source = getattr(event, "source", None)
profile_name = (
getattr(source, "profile", "") or ""
).strip() or get_active_profile_name()
try:
from gateway.run import _profile_runtime_scope
profile_home = self._resolve_profile_home_for_source(source)
with _profile_runtime_scope(profile_home):
profile_name = ""
if multiplexed:
profile_name = (getattr(source, "profile", "") or "").strip()
profile_name = profile_name or get_active_profile_name()
if multiplexed:
try:
from gateway.run import _profile_runtime_scope
profile_home = self._resolve_profile_home_for_source(source)
with _profile_runtime_scope(profile_home):
display = display_hermes_home()
except Exception:
display = display_hermes_home()
except Exception:
else:
display = display_hermes_home()
lines = [

View file

@ -691,6 +691,7 @@ async def test_profile_command_reports_source_stamped_profile(monkeypatch, tmp_p
chat_type="dm",
)
runner = _make_runner(session_entry)
runner.config.multiplex_profiles = True
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
event = _make_event("/profile")
@ -702,6 +703,37 @@ async def test_profile_command_reports_source_stamped_profile(monkeypatch, tmp_p
assert f"**Home:** `{profile_home}`" in result
@pytest.mark.asyncio
async def test_profile_command_ignores_stamp_when_multiplexing_off(monkeypatch, tmp_path):
"""Without ``gateway.multiplex_profiles`` a stamped source is ignored:
/profile keeps reporting the active profile and the default home,
mirroring the multiplex gating in ``_run_agent`` and
``_reset_notice_session_info``."""
hermes_home = tmp_path / ".hermes"
profile_home = hermes_home / "profiles" / "milo"
profile_home.mkdir(parents=True)
session_entry = SessionEntry(
session_key=build_session_key(_make_source()),
session_id="sess-1",
created_at=datetime.now(),
updated_at=datetime.now(),
platform=Platform.TELEGRAM,
chat_type="dm",
)
runner = _make_runner(session_entry)
assert runner.config.multiplex_profiles is False
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
event = _make_event("/profile")
event.source.profile = "milo"
result = await runner._handle_profile_command(event)
assert "**Profile:** `default`" in result
assert f"**Home:** `{hermes_home}`" in result
@pytest.mark.asyncio
async def test_profile_command_unstamped_source_unchanged(monkeypatch, tmp_path):
"""Single-profile behavior is untouched: an unstamped source reports the