Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions tests/tui_gateway/test_make_agent_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,96 @@ def test_probe_config_health_flags_null_personalities_with_active_personality():
assert "agent.personalities" in msg


def test_make_agent_discovers_profile_mcp_before_tool_snapshot():
"""Dashboard profile sessions must discover that profile's MCP tools before
constructing AIAgent.

The dashboard process can be launched from one HERMES_HOME while the user
opens a Desktop chat under another profile. Startup MCP discovery only covers
the launch profile; without this per-session discovery, AIAgent snapshots an
empty/non-MCP registry for the selected profile and the model never receives
those MCP tool schemas.
"""

fake_runtime = {
"provider": "openrouter",
"base_url": "https://api.synthetic.new/v1",
"api_key": "sk-test",
"api_mode": "chat_completions",
"command": None,
"args": None,
"credential_pool": None,
}
fake_cfg = {
"agent": {"system_prompt": ""},
"model": {"default": "glm-5"},
"mcp_servers": {"profile-mcp": {"enabled": True, "url": "http://localhost/mcp"}},
}

call_order = []

def record_discover():
call_order.append("discover")
return ["mcp_profile_mcp_ping"]

def record_agent(*args, **kwargs):
call_order.append("agent")
return MagicMock()

with (
patch("tui_gateway.server._load_cfg", return_value=fake_cfg),
patch("tui_gateway.server._get_db", return_value=MagicMock()),
patch("tui_gateway.server._load_reasoning_config", return_value=None),
patch("tui_gateway.server._load_service_tier", return_value=None),
patch("tui_gateway.server._load_enabled_toolsets", return_value=["profile-mcp"]),
patch(
"hermes_cli.runtime_provider.resolve_runtime_provider",
return_value=fake_runtime,
),
patch("tools.mcp_tool.discover_mcp_tools", side_effect=record_discover) as mock_discover,
patch("run_agent.AIAgent", side_effect=record_agent) as mock_agent,
):
from tui_gateway.server import _make_agent

_make_agent("sid-profile-mcp", "key-profile-mcp")

mock_discover.assert_called_once()
assert call_order == ["discover", "agent"]
assert mock_agent.call_args.kwargs["enabled_toolsets"] == ["profile-mcp"]


def test_make_agent_skips_profile_mcp_discovery_when_unconfigured():
fake_runtime = {
"provider": "openrouter",
"base_url": "https://api.synthetic.new/v1",
"api_key": "sk-test",
"api_mode": "chat_completions",
"command": None,
"args": None,
"credential_pool": None,
}
fake_cfg = {"agent": {"system_prompt": ""}, "model": {"default": "glm-5"}}

with (
patch("tui_gateway.server._load_cfg", return_value=fake_cfg),
patch("tui_gateway.server._get_db", return_value=MagicMock()),
patch("tui_gateway.server._load_reasoning_config", return_value=None),
patch("tui_gateway.server._load_service_tier", return_value=None),
patch(
"hermes_cli.runtime_provider.resolve_runtime_provider",
return_value=fake_runtime,
),
patch("tools.mcp_tool.discover_mcp_tools") as mock_discover,
patch("run_agent.AIAgent") as mock_agent,
):
from tui_gateway.server import _make_agent

_make_agent("sid-no-mcp", "key-no-mcp")

mock_discover.assert_not_called()
assert mock_agent.called


def test_make_agent_tolerates_null_config_sections():
"""Bare `agent:` / `display:` keys in ~/.hermes/config.yaml parse as
None. cfg.get("agent", {}) returns None (default only fires on missing
Expand Down
21 changes: 21 additions & 0 deletions tui_gateway/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3853,6 +3853,27 @@ def _make_agent(
pass

cfg = _load_cfg()

# Desktop dashboard sessions can be created for a profile that is different
# from the dashboard process' launch HERMES_HOME. The dashboard startup MCP
# discovery thread only sees the launch profile, so a newly-created profile
# session (for example `epam`) can reach AIAgent construction before that
# profile's MCP servers have ever been discovered in this process. AIAgent
# snapshots tools once in __init__, so waiting for the launch-profile thread
# is not sufficient: discover the *currently bound* profile's MCP servers
# here, while the caller's profile HERMES_HOME override is active, before the
# tool snapshot is built.
try:
raw_mcp = cfg.get("mcp_servers") if isinstance(cfg, dict) else None
if isinstance(raw_mcp, dict) and raw_mcp:
from tools.mcp_tool import discover_mcp_tools

discover_mcp_tools()
except Exception:
logger.warning(
"Profile MCP tool discovery failed before agent snapshot",
exc_info=True,
)
agent_cfg = cfg.get("agent") or {}
system_prompt = _prompt_text(agent_cfg.get("system_prompt", ""))
startup_skills = _parse_tui_skills_env()
Expand Down