fix(toolsets): scope project tools to GUI sessions (off core)

project_list/create/switch were in _HERMES_CORE_TOOLS, so every platform
(cli, cron, telegram, discord, …) shipped their schema on every API call —
even though off the desktop the workspace callback no-ops and the DB write
has no consumer (narrow waist).

Drop them from _HERMES_CORE_TOOLS; they stay in the `project` toolset, which
the desktop/TUI gateway folds into its resolved toolsets. _load_enabled_toolsets
runs only in that gateway, so it's the gate that exposes them on exactly the
surface that can follow a project move.
This commit is contained in:
Brooklyn Nicholson
2026-06-23 13:17:52 -05:00
parent 3f2e41eb10
commit cc9b33499f
4 changed files with 24 additions and 15 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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": []
},

View File

@@ -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(