diff --git a/hermes_cli/web_routers/messaging.py b/hermes_cli/web_routers/messaging.py index 468a3e6037..d30375aa59 100644 --- a/hermes_cli/web_routers/messaging.py +++ b/hermes_cli/web_routers/messaging.py @@ -22,8 +22,8 @@ from typing import Any, Optional from fastapi import APIRouter, HTTPException from gateway.status import ( - multiplexer_liveness_for_profile, profile_platforms_from_multiplexer, resolve_gateway_liveness, - retained_gateway_state) + multiplexer_liveness_for_profile, profile_name_for_home, profile_platforms_from_multiplexer, + resolve_gateway_liveness, retained_gateway_state) from hermes_cli._subprocess_compat import windows_hide_flags from hermes_cli.config import OPTIONAL_ENV_VARS, get_env_path from hermes_constants import get_process_hermes_home @@ -294,7 +294,7 @@ def _platform_payloads(scoped_dir: Optional[Path], entries) -> list[dict[str, An # profile's standalone days outranks nothing: only a record proving a live own gateway does — # the same rung order ``resolve_gateway_liveness`` uses (own runtime PID before the multiplexer), # so the two surfaces cannot disagree. Unscoped, the profile is the process's own home (a pooled - # ``hermes --profile X serve``); the default home resolves to a name the multiplexer never serves. + # ``hermes --profile X serve``). own_home = scoped_dir if scoped_dir is not None else get_process_hermes_home() if ( runtime is None @@ -302,7 +302,16 @@ def _platform_payloads(scoped_dir: Optional[Path], entries) -> list[dict[str, An ): served = multiplexer_liveness_for_profile(own_home) if served is not None: - runtime = {**served[1], "platforms": profile_platforms_from_multiplexer(served[1], own_home.name)} + # The fold keys on the profile NAME rung 4 just proved served; ``own_home.name`` is only + # the directory basename (".hermes" or any custom HERMES_HOME name for the default root, + # the profile id only for secondaries under profiles/) — the default's flat keys + # then never matched and the card read "Restart needed" forever (#123088). + runtime = { + **served[1], + "platforms": profile_platforms_from_multiplexer( + served[1], profile_name_for_home(own_home) or "default" + ), + } return [_messaging_platform_payload(entry, env_on_disk, runtime, scoped=scoped_dir is not None, profile_home=scoped_dir) for entry in entries] diff --git a/tests/hermes_cli/test_served_profile_mirror_platforms.py b/tests/hermes_cli/test_served_profile_mirror_platforms.py index 174e06e7f4..4b926ae4c6 100644 --- a/tests/hermes_cli/test_served_profile_mirror_platforms.py +++ b/tests/hermes_cli/test_served_profile_mirror_platforms.py @@ -89,6 +89,38 @@ def test_default_profile_keeps_its_flat_adapters_when_rekeyed(served_root): assert "telegram" not in plats # that entry belongs to alpha +def test_messaging_card_for_the_default_home_rekeys_by_the_profile_not_the_dirname(tmp_path, monkeypatch): + """The multiplexer fold keys on the profile NAME; ``_platform_payloads`` used to pass + ``own_home.name`` — the directory basename, ``.hermes`` for the shipped default root or any + custom ``HERMES_HOME`` name, equal to the profile id only for secondaries under + ``profiles/``. On the default home the fold came back empty and the Channels card read + "Restart needed" forever while the multiplexer served its flat-keyed adapters (#123088).""" + import gateway.status as status + from hermes_cli.web_routers import messaging + root = tmp_path / ".hermes" # the shipped default root's literal name — never equal to "default" + root.mkdir() + (root / "config.yaml").write_text("gateway: {multiplex_profiles: true}\n", encoding="utf-8") + record = { + "pid": os.getpid(), "hermes_home": str(root), "gateway_state": "running", + "served_profiles": ["default", "route-runner"], + "platforms": {"telegram": {"state": "connected"}}, + } + monkeypatch.setenv("HERMES_HOME", str(root)) + import hermes_constants + monkeypatch.setattr(hermes_constants, "_default_hermes_root_memo", None) + # A launch-service gateway's record fails the own-rung argv check (inline ``-c``), so the card + # falls through to the multiplexer rung — mirrored here by an absent own record. + monkeypatch.setattr(messaging, "read_runtime_status", lambda *a, **k: None) + monkeypatch.setattr(messaging, "multiplexer_liveness_for_profile", lambda home: (os.getpid(), record)) + monkeypatch.setattr(status, "_read_process_cmdline", lambda pid: "hermes gateway run") + monkeypatch.setattr(messaging, "_platform_enablement", lambda *a, **k: (True, True, None)) + entry = {"id": "telegram", "name": "Telegram", "description": "", "docs_url": "", "env_vars": [], + "required_env": []} + [payload] = messaging._platform_payloads(None, [entry]) # unscoped: the dashboard's own home + assert payload["gateway_running"] is True + assert payload["state"] == "connected", payload + + def test_messaging_card_for_a_served_profile_reads_connected_not_restart_needed(served_root, monkeypatch): from hermes_cli.web_routers import messaging monkeypatch.setattr(messaging, "_platform_enablement", lambda *a, **k: (True, True, None))