mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
fix(gateway): a profile rescan drops status verdicts about removed credentials
Deleting a duplicated bot token from a secondary profile's .env triggers a rescan, but the old '<profile>:<platform>' fatal entry stayed in gateway_state.json until restart, so Desktop kept painting 'Backend telegram down'. Before rebuilding a changed profile, drop its entries for platforms with no live or queued adapter; the rescan re-publishes every verdict that still holds (unserved-WhatsApp notes are un-deduped for it).
This commit is contained in:
1 parent
f92db3b007
commit
61ba1f684e
3 files changed
+60
-3
No files matched your search
@@ -273,6 +273,8 @@ class GatewayProfileReconcileMixin:
|
||||
# Only acknowledge the configuration observed before connecting;
|
||||
# a setup save during an awaited handshake needs another scan.
|
||||
scan_signature = profile_serve_signature(current[name])
|
||||
if name in changed:
|
||||
self._drop_stale_platform_verdicts(name)
|
||||
try:
|
||||
connected = await self._start_one_profile_adapters(name, current[name], claimed)
|
||||
except MultiplexConfigError as exc:
|
||||
@@ -328,6 +330,19 @@ class GatewayProfileReconcileMixin:
|
||||
result["served_profiles"] = self.served_profile_names()
|
||||
return result
|
||||
|
||||
def _drop_stale_platform_verdicts(self, name: str) -> None:
|
||||
"""Before a changed profile's rescan, drop its ``<name>:<platform>`` status entries for
|
||||
platforms with no live or queued adapter: the rescan re-publishes every verdict that still
|
||||
holds, so a fatal/disabled entry about a credential the user just removed can't outlive it."""
|
||||
from gateway.run import _write_runtime_status_quiet
|
||||
live = set((getattr(self, "_profile_adapters", None) or {}).get(name) or {})
|
||||
live |= set((getattr(self, "_profile_failed_platforms", None) or {}).get(name) or {})
|
||||
noted = getattr(self, "_unserved_secondary_platforms", None)
|
||||
if isinstance(noted, set):
|
||||
noted -= {entry for entry in noted if entry[0] == name}
|
||||
_write_runtime_status_quiet(drop_profile_platforms=name,
|
||||
keep_platforms={f"{name}:{getattr(p, 'value', p)}" for p in live})
|
||||
|
||||
def _parked_profile_names(self) -> list:
|
||||
"""Profiles that exist but could not be served (unusable store, home owned elsewhere); boot's
|
||||
``initialize_gateway_runtime`` parks into the same published ``name -> reason`` map."""
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ import time
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone, UTC
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, NamedTuple, Optional
|
||||
from typing import Any, Callable, Iterable, NamedTuple, Optional
|
||||
from hermes_dir_acl import make_user_dir
|
||||
|
||||
from gateway.status_inline_source import (
|
||||
@@ -1093,7 +1093,7 @@ def _prepare_runtime_status_update(
|
||||
parked_profiles: Any = _UNSET, multiplex_standalone_reason: Any = _UNSET,
|
||||
platform_metrics: Any = _UNSET,
|
||||
ingress_url: Any = _UNSET, listener_base: Any = _UNSET, clear_profile_platforms: bool = False,
|
||||
drop_profile_platforms: Optional[str] = None,
|
||||
drop_profile_platforms: Optional[str] = None, keep_platforms: Iterable[str] = (),
|
||||
load_existing: bool = True, reload_existing: bool = False,
|
||||
) -> tuple[Path, dict[str, Any], dict[str, Any]]:
|
||||
"""Merge one update into the process-wide canonical status snapshot."""
|
||||
@@ -1117,7 +1117,7 @@ def _prepare_runtime_status_update(
|
||||
payload["platforms"] = {
|
||||
k: v for k, v in payload["platforms"].items()
|
||||
if not isinstance(k, str) or ":" not in k
|
||||
or (drop_prefix is not None and not k.startswith(drop_prefix))
|
||||
or (drop_prefix is not None and not k.startswith(drop_prefix)) or k in keep_platforms
|
||||
}
|
||||
payload.update({key: current_record[key] for key in ("kind", "pid", "argv", "start_time")})
|
||||
payload["updated_at"] = _utc_now_iso()
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"""A changed profile's rescan drops status verdicts about credentials the user removed.
|
||||
|
||||
Removing a duplicated bot token from a secondary profile's .env must clear its
|
||||
``<profile>:<platform>`` fatal entry (Desktop: "Backend telegram down"), while a
|
||||
platform that is still live keeps its entry.
|
||||
"""
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from gateway.config import Platform
|
||||
from gateway.status import flush_runtime_status, publish_runtime_status
|
||||
from tests.gateway.test_multiplex_hot_serve import _mkprofile, _runner
|
||||
|
||||
|
||||
def _platforms(home):
|
||||
flush_runtime_status()
|
||||
return json.loads((home / "gateway_state.json").read_text(encoding="utf-8")).get("platforms") or {}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rescan_drops_fatal_for_removed_credential_keeps_live(tmp_path, monkeypatch):
|
||||
runner, home = _runner(tmp_path, monkeypatch)
|
||||
alpha = _mkprofile(home, "alpha", "DISCORD_BOT_TOKEN=alpha-token\n")
|
||||
with patch("hermes_cli.profiles.get_active_profile_name", return_value="default"):
|
||||
await runner._start_secondary_profile_adapters()
|
||||
assert Platform.DISCORD in runner._profile_adapters["alpha"]
|
||||
publish_runtime_status(platform="alpha:discord", platform_state="connected")
|
||||
publish_runtime_status(platform="alpha:telegram", platform_state="fatal",
|
||||
error_code="duplicate_credential", error_message="dup")
|
||||
publish_runtime_status(platform="telegram", platform_state="connected")
|
||||
assert _platforms(home)["alpha:telegram"]["state"] == "fatal"
|
||||
|
||||
# User deletes the copied TELEGRAM_BOT_TOKEN line: the .env change triggers a rescan.
|
||||
(alpha / ".env").write_text("DISCORD_BOT_TOKEN=alpha-token\n# edited\n", encoding="utf-8")
|
||||
assert (await runner.reconcile_served_profiles())["rescanned"] == ["alpha"]
|
||||
|
||||
platforms = _platforms(home)
|
||||
assert "alpha:telegram" not in platforms
|
||||
assert platforms["alpha:discord"]["state"] == "connected"
|
||||
assert platforms["telegram"]["state"] == "connected"
|
||||
Reference in new issue
Block a user