From 4503c2458beec8aacd0516c7d9496dec4fec122a Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 24 Sep 2026 23:10:29 -0500 Subject: [PATCH] fix(tui_gateway): honor prefill_messages_file on the Desktop agent path Desktop and TUI agents are built by tui_gateway.server._make_agent, which never runs the classic CLI bootstrap and passed no prefill_messages to AIAgent, so a configured prefill_messages_file was silently ignored there. Resolve it with the CLI's own resolver/loader (env > prefill_messages_file > legacy agent.prefill_messages_file). The loader takes an optional base_dir so relative paths resolve against the profile building the agent, not the launch home. Co-authored-by: leakedd <66937184+leakedd@users.noreply.github.com> --- hermes_cli/cli_config_load.py | 10 ++-- tests/tui_gateway/test_make_agent_prefill.py | 55 ++++++++++++++++++++ tui_gateway/agent_callbacks.py | 9 ++++ tui_gateway/server.py | 2 +- 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 tests/tui_gateway/test_make_agent_prefill.py diff --git a/hermes_cli/cli_config_load.py b/hermes_cli/cli_config_load.py index f7af2b875e..78fa2ba350 100644 --- a/hermes_cli/cli_config_load.py +++ b/hermes_cli/cli_config_load.py @@ -24,14 +24,16 @@ def _cli(): return cli -def _load_prefill_messages(file_path: str) -> List[Dict[str, Any]]: - """Load prefill messages (JSON array) from *file_path*; relative to ~/.hermes/; missing/empty -> [].""" - from cli import _hermes_home +def _load_prefill_messages(file_path: str, base_dir: Path | None = None) -> List[Dict[str, Any]]: + """Load prefill messages (JSON array) from *file_path*; missing/empty -> []. + + Relative paths resolve against *base_dir*, defaulting to the CLI's hermes home. + """ if not file_path: return [] path = Path(file_path).expanduser() if not path.is_absolute(): - path = _hermes_home / path + path = (base_dir or _cli()._hermes_home) / path if not path.exists(): logger.warning("Prefill messages file not found: %s", path) return [] diff --git a/tests/tui_gateway/test_make_agent_prefill.py b/tests/tui_gateway/test_make_agent_prefill.py new file mode 100644 index 0000000000..43a5a6062d --- /dev/null +++ b/tests/tui_gateway/test_make_agent_prefill.py @@ -0,0 +1,55 @@ +"""Desktop/TUI agents honor ``prefill_messages_file`` from the active profile. + +Regression for #60456: ``tui_gateway.server._make_agent`` (the Desktop / ``hermes serve`` agent +factory) never passed ``prefill_messages``, so a configured prefill file only worked in the +classic CLI and the messaging gateway. +""" + +from __future__ import annotations + +import json +import types + +import yaml + +from hermes_constants import reset_hermes_home_override, set_hermes_home_override +from tui_gateway import server + + +def _write_home(root, name: str, content: str): + home = root / name + home.mkdir() + (home / "config.yaml").write_text(yaml.safe_dump({"prefill_messages_file": "prefill.json"}), encoding="utf-8") + (home / "prefill.json").write_text(json.dumps([{"role": "user", "content": content}]), encoding="utf-8") + return home + + +def _built_prefill(monkeypatch, home): + captured = {} + + def fake_agent(**kwargs): + captured.update(kwargs) + return types.SimpleNamespace(model=kwargs.get("model")) + + monkeypatch.setattr("run_agent.AIAgent", fake_agent) + token = set_hermes_home_override(home) + try: + server._make_agent("sid", "session-key") + finally: + reset_hermes_home_override(token) + return captured["prefill_messages"] + + +def test_make_agent_injects_the_active_profiles_prefill(monkeypatch, tmp_path): + monkeypatch.delenv("HERMES_PREFILL_MESSAGES_FILE", raising=False) + monkeypatch.setattr(server, "_resolve_agent_model_runtime", lambda *_a: ("test-model", {})) + monkeypatch.setattr(server, "_load_enabled_toolsets", lambda *_a, **_kw: None) + monkeypatch.setattr(server, "_get_db", lambda: None) + monkeypatch.setattr(server, "_agent_cbs", lambda sid: {}) + home_a = _write_home(tmp_path, "a", "prefill from A") + home_b = _write_home(tmp_path, "b", "prefill from B") + + # A -> B -> A: the relative path resolves against whichever profile builds the agent. + assert _built_prefill(monkeypatch, home_a) == [{"role": "user", "content": "prefill from A"}] + assert _built_prefill(monkeypatch, home_b) == [{"role": "user", "content": "prefill from B"}] + assert _built_prefill(monkeypatch, home_a) == [{"role": "user", "content": "prefill from A"}] diff --git a/tui_gateway/agent_callbacks.py b/tui_gateway/agent_callbacks.py index a56356d02a..1507c5202a 100644 --- a/tui_gateway/agent_callbacks.py +++ b/tui_gateway/agent_callbacks.py @@ -330,6 +330,15 @@ def _load_fallback_model(): return get_fallback_chain(_load_cfg()) +def _load_prefill_messages() -> list: + """Configured prefill messages, resolved like the CLI (env > ``prefill_messages_file`` > legacy + ``agent.*``). Desktop/TUI agents never run the CLI bootstrap, so without this the setting was + ignored there (#60456). Relative paths resolve against the active profile home, per call.""" + from hermes_cli.cli_config_load import _load_prefill_messages as _load, _resolve_prefill_messages_file + from hermes_constants import get_hermes_home + return _load(_resolve_prefill_messages_file(_load_cfg()), get_hermes_home()) + + def _sync_agent_fallback_with_config(sid: str, session: dict) -> None: """Adopt ``fallback_providers`` edits into the cached agent at turn start. diff --git a/tui_gateway/server.py b/tui_gateway/server.py index a565df92c4..13a0c94211 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -2548,7 +2548,7 @@ def _make_agent( checkpoints_enabled=is_truthy_value(os.environ.get("HERMES_TUI_CHECKPOINTS")), pass_session_id=is_truthy_value(os.environ.get("HERMES_TUI_PASS_SESSION_ID")), skip_context_files=ignore_rules, skip_memory=ignore_rules, fallback_model=_load_fallback_model(), - **_agent_cbs(sid)) + prefill_messages=_load_prefill_messages() or None, **_agent_cbs(sid)) if context_cwd_is_launch_artifact is None: context_cwd_is_launch_artifact = _context_cwd_is_launch_artifact(session) agent._context_cwd_is_launch_artifact = bool(context_cwd_is_launch_artifact)