From 6b1267c2e44887ad6fd72e9008ede4855ec242eb Mon Sep 17 00:00:00 2001 From: Jonny Kovacs Date: Sat, 11 Jul 2026 11:53:53 -0500 Subject: [PATCH] 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 --- gateway/slash_commands.py | 35 ++++++++++++++++++---------- tests/gateway/test_status_command.py | 32 +++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index 6649848a3401..845f44aa3663 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -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//`` 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 = [ diff --git a/tests/gateway/test_status_command.py b/tests/gateway/test_status_command.py index 3d5e15d079e5..2da89d16dc8f 100644 --- a/tests/gateway/test_status_command.py +++ b/tests/gateway/test_status_command.py @@ -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