diff --git a/tests/test_tui_gateway_server.py b/tests/test_tui_gateway_server.py index 7bfff003670..ccab5b17ad9 100644 --- a/tests/test_tui_gateway_server.py +++ b/tests/test_tui_gateway_server.py @@ -722,9 +722,9 @@ def test_load_enabled_toolsets_rejects_disabled_mcp_env(monkeypatch, capsys): config_mod, "load_config", lambda: {"platform_toolsets": {"cli": ["memory"]}} ) - # Sorted: ["kanban", "memory", "project"]. `kanban` and `project` are - # auto-recovered by _get_platform_tools — both are non-configurable platform - # toolsets whose tools live in hermes-cli's universe (see toolsets.py). + # Sorted: ["kanban", "memory", "project"]. `kanban` is auto-recovered by + # _get_platform_tools (a non-configurable platform toolset in hermes-cli's + # universe); `project` is GUI-only, folded in by _load_enabled_toolsets. assert server._load_enabled_toolsets() == ["kanban", "memory", "project"] err = capsys.readouterr().err assert "ignoring disabled MCP servers" in err diff --git a/tools/project_tools.py b/tools/project_tools.py index 84d50e46f74..008e0068285 100644 --- a/tools/project_tools.py +++ b/tools/project_tools.py @@ -5,10 +5,11 @@ Projects (per-profile ``projects.db``) are the named workspaces the desktop sidebar groups sessions into. Creating / switching a project is a deliberate act expressed as explicit tools — never a side effect of a terminal ``cd``. -Available everywhere (CLI, messaging, desktop) so projects can be triaged from -any surface. The DB write is the durable part; when a GUI gateway has wired the -workspace callback, a create/switch also re-anchors the live session's cwd so -the sidebar follows the move. Elsewhere that step simply no-ops. +Exposed only on GUI sessions: the tools live in the `project` toolset (kept off +``_HERMES_CORE_TOOLS``) which the desktop/TUI gateway folds into its resolved +toolsets, so no CLI/messaging/cron schema carries them. The GUI also wires +``set_project_workspace_callback`` so a create/switch re-anchors the live +session's cwd and the sidebar follows the move; the DB write is the durable part. """ import json diff --git a/toolsets.py b/toolsets.py index cace42c495a..0c3beb364c7 100644 --- a/toolsets.py +++ b/toolsets.py @@ -51,9 +51,11 @@ _HERMES_CORE_TOOLS = [ "text_to_speech", # Planning & memory "todo", "memory", - # Desktop Projects (gateway-gated; the agent's intentional handle on the - # sidebar's named workspaces) - "project_list", "project_create", "project_switch", + # NOTE: the desktop Project tools (project_list/create/switch) are + # deliberately NOT here. They only make sense where a GUI can follow the + # move, so they live in the `project` toolset and are enabled solely by the + # GUI gateway (tui_gateway/server.py::_load_enabled_toolsets) — keeping them + # off every CLI/messaging/cron schema (narrow waist). # Session history search "session_search", # Clarifying questions @@ -227,7 +229,7 @@ TOOLSETS = { }, "project": { - "description": "Desktop Projects — create/switch named workspaces (gateway only)", + "description": "Desktop Projects — create/switch named workspaces (GUI sessions only)", "tools": ["project_list", "project_create", "project_switch"], "includes": [] }, diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 2b79d515499..190f84e8d5c 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -2297,12 +2297,18 @@ def _load_enabled_toolsets() -> list[str] | None: # list without baking in implicit MCP defaults. Using the wrong # variant at agent creation time makes MCP tools silently missing # from the TUI. See PR #3252 for the original design split. - enabled = sorted( - _get_platform_tools(cfg, "cli", include_default_mcp_servers=True) - ) + enabled = _get_platform_tools(cfg, "cli", include_default_mcp_servers=True) if fallback_notice is not None: print(fallback_notice, file=sys.stderr, flush=True) - return enabled or None + if not enabled: + return None + # The desktop Project tools are off _HERMES_CORE_TOOLS (every other + # platform would carry their schema for nothing), so the platform + # recovery above — which keys off hermes-cli's tool universe — can't + # surface them. This resolver runs ONLY in the desktop/TUI gateway, so + # folding in the `project` toolset here is the gate that exposes them on + # exactly the surface that can follow a project move. + return sorted(enabled | {"project"}) except Exception: if fallback_notice is not None: print(