diff --git a/hermes_cli/web_routers/messaging.py b/hermes_cli/web_routers/messaging.py index 6c38c5c9dc..ccdbd85c65 100644 --- a/hermes_cli/web_routers/messaging.py +++ b/hermes_cli/web_routers/messaging.py @@ -170,7 +170,18 @@ def _platform_enablement( plat_cfg = (load_config().get("platforms") or {}).get(platform_id) plat_cfg = plat_cfg if isinstance(plat_cfg, dict) else {} hc = plat_cfg.get("home_channel") - enabled, home_channel = bool(plat_cfg.get("enabled")), (hc if isinstance(hc, dict) else None) + # Credential fallback mirrors _enable_from_env (gateway/config_env.py): env + # credentials alone enable a platform — `hermes gateway setup` only writes + # .env and never a platforms: entry (#104614) — while an explicit + # enabled: false still wins, exactly like the real gateway config. Only the + # profile's own .env (env_on_disk) is consulted; os.environ would leak the + # root install's credentials into this profile's state. + raw_enabled = plat_cfg.get("enabled") + if raw_enabled is False: + enabled = False + else: + enabled = bool(raw_enabled) or all(env_on_disk.get(key) for key in required) + home_channel = hc if isinstance(hc, dict) else None except Exception: enabled, home_channel = False, None return enabled, all(env_on_disk.get(key) for key in required), home_channel diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index 46c89c37ad..34146a7fc2 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -773,6 +773,48 @@ class TestWebServerEndpoints: assert seen["status_path"] == worker_home / "gateway_state.json" assert seen["expected_home"] == worker_home + def test_messaging_platforms_profile_scoped_env_credentials_enable(self, monkeypatch): + """Env credentials alone must enable a platform on the profile-scoped path. + + ``hermes gateway setup`` writes the token to .env and never a ``platforms:`` + entry, but the desktop always sends ``?profile=default``; the scoped branch + consulted only config.yaml's ``platforms:`` section, so the Messaging page + showed "Disabled" for a connected bot (#104614). An explicit + ``enabled: false`` must still win, mirroring ``_enable_from_env`` in + gateway/config_env.py. + """ + import hermes_cli.web_server as web_server + from hermes_cli import profiles as profiles_mod + + worker_home = profiles_mod.get_profile_dir("worker") + worker_home.mkdir(parents=True) + (worker_home / ".env").write_text("DISCORD_BOT_TOKEN=tok\n", encoding="utf-8") + + monkeypatch.setattr(_gw_status, "get_running_pid_cached", lambda pid_path=None, **kw: None) + monkeypatch.setattr(_gw_status, "get_running_pid", lambda pid_path=None, **kw: None) + monkeypatch.setattr(_gw_status, "read_runtime_status", lambda path=None: None) + monkeypatch.setattr(_gw_status, "get_runtime_status_running_pid", lambda runtime=None, **kw: None) + monkeypatch.setattr(web_server, "_GATEWAY_HEALTH_URL", None) + + resp = self.client.get("/api/messaging/platforms?profile=worker") + + assert resp.status_code == 200 + platforms = {p["id"]: p for p in resp.json()["platforms"]} + assert platforms["discord"]["enabled"] is True + assert platforms["discord"]["configured"] is True + assert platforms["discord"]["state"] != "disabled" + + # Explicit platforms.discord.enabled: false wins over the .env credentials. + (worker_home / "config.yaml").write_text( + "platforms:\n discord:\n enabled: false\n", encoding="utf-8" + ) + resp = self.client.get("/api/messaging/platforms?profile=worker") + + assert resp.status_code == 200 + platforms = {p["id"]: p for p in resp.json()["platforms"]} + assert platforms["discord"]["enabled"] is False + assert platforms["discord"]["state"] == "disabled" +