From 6ae4cb88d52f881d889ab720790036fc1ef4b5f8 Mon Sep 17 00:00:00 2001 From: treatux <290971332+treatux@users.noreply.github.com> Date: Sat, 19 Sep 2026 05:24:03 +0000 Subject: [PATCH] fix(docs): reference pages name tools the registry never registered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shipped tool-surface references still document the pre-consolidation surface: tools-reference.md lists cronjob/todo/process/project_create/ project_list/project_switch/open_preview/close_preview/read_preview/tour/tip (6 uncallable, 5 hidden dispatch-only aliases), and toolsets-reference.md still claims web_search is a member of the browser toolset — membership decacbac3 deliberately removed (#64503) with a regression test. Both rename commits (e16ad33a9, 217ab2f8d) left website/ untouched. Pin the pages to the live registry with a contract test (real discover_builtin_tools()/resolve_toolset queries over the shipped .md data); rename the rows to the registered surface (cronjob_manage, todo_list, process_manage, desktop_project enum, desktop_preview, gui_tour, show_tip) and add the browser row's actual members (browser_vault_*, browser_exec, apply_layout) that the docs never mentioned. --- .../test_reference_docs_registry_contract.py | 107 ++++++++++++++++++ website/docs/reference/tools-reference.md | 27 ++--- website/docs/reference/toolsets-reference.md | 12 +- 3 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 tests/tools/test_reference_docs_registry_contract.py diff --git a/tests/tools/test_reference_docs_registry_contract.py b/tests/tools/test_reference_docs_registry_contract.py new file mode 100644 index 0000000000..cd12399412 --- /dev/null +++ b/tests/tools/test_reference_docs_registry_contract.py @@ -0,0 +1,107 @@ +"""Contract: the shipped tool reference docs name only tools the shipped code registers. + +``website/docs/reference/tools-reference.md`` and ``toolsets-reference.md`` are the +authoritative built-in-tool surface published with the package. This pins them to the +live registry: every tool name the docs claim must (a) be a registered tool and (b) +resolve inside the toolset that claims it — the registry/queryable half of the +"documented surface == registered surface" contract. A rename in ``tools/`` or +``toolsets.py`` that skips these pages fails here instead of shipping stale tool +names to readers (they shipped stale once: the project/preview/tour/tip +consolidations left ``project_create``/``open_preview``/``tour``/``tip`` behind, and +the browser row still claimed the ``web_search`` membership #64503 removed). + +Reads only the two shipped .md docs (data, like cli-config.yaml.example contracts) +and the live registry/toolset resolver — never source text. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] +TOOLS_REF = REPO_ROOT / "website" / "docs" / "reference" / "tools-reference.md" +TOOLSETS_REF = REPO_ROOT / "website" / "docs" / "reference" / "toolsets-reference.md" + +_SECTION_RE = re.compile(r"^## `([a-z0-9_-]+)` toolset", re.MULTILINE) +_ROW_RE = re.compile(r"^\| `([a-z0-9_]+)` \|", re.MULTILINE) + + +@pytest.fixture(scope="module") +def registered_names(): + """Every tool name the shipped surface registers: built-ins plus bundled + backend plugins (the spotify plugin owns the doc's spotify rows). The same + discovery path production runs at startup.""" + import model_tools # noqa: F401 — triggers built-in tool discovery + from hermes_cli.plugins import discover_plugins + + discover_plugins() + from tools.registry import registry + + return {entry.name for entry in registry.get_all_entries()} + + +def _tool_sections(path) -> list[tuple[str, list[str]]]: + """(toolset, claimed tool names) per ``## ``toolset`` toolset`` section.""" + text = path.read_text(encoding="utf-8") + headers = [(m.start(), m.group(1)) for m in _SECTION_RE.finditer(text)] + return [ + (toolset, _ROW_RE.findall(text[start:next_start])) + for (start, toolset), (next_start, _) in zip(headers, [*headers[1:], (len(text), None)]) + ] + + +def _core_toolset_rows(path) -> list[tuple[str, str]]: + """(toolset, Tools cell) rows of the Core Toolsets table.""" + text = path.read_text(encoding="utf-8") + match = re.search(r"^## Core Toolsets$.*?^(?=\| `)", text, re.MULTILINE | re.DOTALL) + assert match, "Core Toolsets table vanished from toolsets-reference.md" + section = text[match.start() : text.index("## Platform Toolsets", match.start())] + return [ + (cells[0].strip("` "), cells[1]) + for line in section.splitlines() + if line.startswith("| `") and len(cells := [c.strip() for c in line.strip().strip("|").split("|")]) > 1 + ] + + +def test_tools_reference_rows_resolve_in_their_toolset(registered_names): + from toolsets import resolve_toolset + + stale = [ + (toolset, name) + for toolset, rows in _tool_sections(TOOLS_REF) + for name in rows + if name not in registered_names or name not in set(resolve_toolset(toolset)) + ] + assert not stale, ( + "tools-reference.md documents tool names the registry does not register " + "(or that the claimed toolset does not resolve): " + + ", ".join(f"{ts}:{n}" for ts, n in stale) + ) + + +def test_toolsets_reference_core_table_resolves(registered_names): + """Core Toolsets table: every claimed member resolves within that toolset; + composite rows name real toolsets.""" + from toolsets import TOOLSETS, resolve_toolset, validate_toolset + + stale, unknown = [], [] + for toolset, tools_cell in _core_toolset_rows(TOOLSETS_REF): + if "composite" in tools_cell: + unknown += [(toolset, n) for n in re.findall(r"`([a-z0-9_]+)`", tools_cell) if not validate_toolset(n)] + continue + cell = re.sub(r"\([^)]*\)", " ", tools_cell) # drop "(via `includes`)" + resolved = set(resolve_toolset(toolset)) + for name in re.findall(r"`([a-z0-9_]+)`", cell): + if name not in registered_names or name not in resolved: + stale.append((toolset, name)) + assert not stale, ( + "toolsets-reference.md claims tool membership the toolsets.py/registry resolver " + "does not deliver: " + ", ".join(f"{ts}:{n}" for ts, n in stale) + ) + assert not unknown, ( + "toolsets-reference.md composite rows name unknown toolsets: " + + ", ".join(f"{ts}:{n}" for ts, n in unknown) + ) diff --git a/website/docs/reference/tools-reference.md b/website/docs/reference/tools-reference.md index f95a419a74..b65bc8b5f3 100644 --- a/website/docs/reference/tools-reference.md +++ b/website/docs/reference/tools-reference.md @@ -8,7 +8,7 @@ description: "Authoritative reference for Hermes built-in tools, grouped by tool This page documents Hermes' built-in tools, grouped by toolset. Availability varies by platform, credentials, and enabled toolsets. -**Quick counts (current registry):** ~86 tools — 10 browser tools (core) + 2 CDP-gated browser tools, 4 file tools, 4 Home Assistant tools, 2 terminal tools (`terminal`, `process`), 12 desktop-GUI tools (`read_terminal`, `close_terminal`, `open_preview`, `close_preview`, `read_preview`, `drive_preview`, `annotate_preview`, `read_window_below`, `focus_pane`, `react_to_message`, `tour`, `tip` — desktop-app sessions only), 2 web tools, 5 Feishu tools, 7 Spotify tools (registered by the bundled `spotify` plugin), 5 Yuanbao tools, 12 kanban tools (registered when the kanban dispatcher spawns the agent), 3 project tools (desktop/GUI sessions), 2 Discord tools, 3 video tools (`video_generate`, `xai_video_edit`, `xai_video_extend`), and a handful of standalone tools (`memory`, `clarify`, `delegate_task`, `execute_code`, `cronjob`, `session_search`, `skill_view`/`skill_manage`/`skills_list`, `text_to_speech`, `image_generate`, `vision_analyze`, `video_analyze`, `todo`, `computer_use`, `x_search`). +**Quick counts (current registry):** ~100 tools — 10 browser tools (core) + 2 CDP-gated browser tools + 5 browser-vault tools + `browser_exec`, 4 file tools, 4 Home Assistant tools, 2 terminal tools (`terminal`, `process_manage`), 11 desktop-GUI tools (`read_terminal`, `close_terminal`, `desktop_preview`, `drive_preview`, `annotate_preview`, `read_window_below`, `focus_pane`, `react_to_message`, `gui_tour`, `show_tip`, `apply_layout` — desktop-app sessions only), 2 web tools, 5 Feishu tools, 7 Spotify tools (registered by the bundled `spotify` plugin), 5 Yuanbao tools, 14 kanban tools (registered when the kanban dispatcher spawns the agent), 1 project tool (`desktop_project`; desktop/GUI sessions), 2 Discord tools, 3 video tools (`video_generate`, `xai_video_edit`, `xai_video_extend`), and a handful of standalone tools (`memory`, `clarify`, `delegate_task`, `execute_code`, `cronjob_manage`, `session_search`, `skill_view`/`skill_manage`/`skills_list`, `text_to_speech`, `image_generate`, `vision_analyze`, `video_analyze`, `todo_list`, `computer_use`, `x_search`). :::tip MCP Tools In addition to built-in tools, Hermes can load tools dynamically from MCP servers. MCP tools appear with the prefix `mcp____` (e.g., `mcp__github__create_issue` for the `github` MCP server). See [MCP Integration](../user-guide/features/mcp.md) for configuration. @@ -81,7 +81,7 @@ token). Other sessions do not see it. | Tool | Description | Requires environment | |------|-------------|----------------------| -| `cronjob` | Unified scheduled-task manager. Use `action="create"`, `"list"`, `"update"`, `"pause"`, `"resume"`, `"run"`, or `"remove"` to manage jobs. Supports skill-backed jobs with one or more attached skills, and `skills=[]` on update clears attached skills. Cron runs happen in fresh sessions with no current-chat context. | — | +| `cronjob_manage` | Unified scheduled-task manager. Use `action="create"`, `"list"`, `"update"`, `"pause"`, `"resume"`, `"run"`, or `"remove"` to manage jobs. Supports skill-backed jobs with one or more attached skills, and `skills=[]` on update clears attached skills. Cron runs happen in fresh sessions with no current-chat context. | — | ## `delegation` toolset @@ -170,9 +170,7 @@ Tools for driving desktop [Projects](../user-guide/cli.md) — named, multi-fold | Tool | Description | Requires environment | |------|-------------|----------------------| -| `project_create` | Create a desktop Project (a named workspace) and switch this chat into it. Pass `path` to anchor it to a repo/folder. | — | -| `project_list` | List the desktop Projects and which one is active. | — | -| `project_switch` | Switch this chat into an existing Project (by name, slug, or id); moves the session workspace to the project's primary folder. | — | +| `desktop_project` | One action enum for the three Project verbs: `create` makes a desktop Project (a named workspace) and switches this chat into it — pass `path` to anchor it to a repo/folder; `list` shows the desktop Projects and which one is active; `switch` moves this chat into an existing Project (by name, slug, or id), moving the session workspace to the project's primary folder. | — | ## `memory` toolset @@ -198,7 +196,7 @@ Tools for driving desktop [Projects](../user-guide/cli.md) — named, multi-fold | Tool | Description | Requires environment | |------|-------------|----------------------| -| `process` | Manage background processes started with terminal(background=true). Actions: 'list' (show all), 'poll' (check status + new output), 'log' (full output with pagination), 'wait' (block until done or timeout), 'kill' (terminate), 'write' (sen… | — | +| `process_manage` | Manage background processes started with terminal(background=true). Actions: 'list' (show all), 'poll' (check status + new output), 'log' (full output with pagination), 'wait' (block until done or timeout), 'kill' (terminate), 'write' (sen… | — | | `terminal` | Execute shell commands on a Linux environment. Filesystem persists between calls. Set `background=true` for long-running servers. Set `notify_on_complete=true` (with `background=true`) to get an automatic notification when the process finishes — no polling needed. Do NOT use cat/head/tail — use read_file. Do NOT use grep/rg/find — use search_files. | — | ## `desktop_ui` toolset @@ -210,21 +208,20 @@ messaging, and cron sessions. | Tool | Description | Requires environment | |------|-------------|----------------------| | `read_terminal` | Read what's currently shown in the in-app terminal pane of the Hermes desktop GUI (the embedded shell beside this chat). | — | -| `close_terminal` | Close the read-only terminal tab for a background process in the Hermes desktop GUI. Does NOT kill the process — only drops the tab/view; use process(action='kill') to stop it. | — | -| `open_preview` | Open a web URL, localhost dev-server URL, or file path in the preview pane beside the chat in the Hermes desktop app. | — | -| `close_preview` | Close the preview pane beside the chat, or one tab inside it. Omit `url` to close the whole pane; pass a URL or file path to close that tab. | — | -| `read_preview` | Read what's currently shown in the preview pane of the Hermes desktop GUI — the in-app Browser's page text (URL + title + rendered text, pageable with `start`/`count`), or a file/artifact tab's identity. | — | +| `close_terminal` | Close the read-only terminal tab for a background process in the Hermes desktop GUI. Does NOT kill the process — only drops the tab/view; use process_manage(action='kill') to stop it. | — | +| `desktop_preview` | Drive the preview pane beside the chat in the Hermes desktop app: `open` a web URL, localhost dev-server URL, or file path (HTML renders live); `close` the whole pane (omit `url`) or one tab inside it (pass the URL or file path); `read` what the pane currently shows — the in-app Browser's page text (URL + title + rendered text, pageable with `start`/`count`) or a file/artifact tab's identity. | — | | `drive_preview` | Interact with the page open in the in-app browser: `elements` inventories what's clickable and typable (each with a ref that names it, like `btn-sign-in` or `inp-email`, plus role, label, and value), then `click`, `hover`, `type`, `scroll`, and `press` act on a ref, and `back`/`forward`/`reload` drive the pane's history. The pointer and keyboard are real input, so hover menus open. A ref lasts until the page navigates, including across a re-render that rebuilds the element, so after the first inventory every action answers with just a delta — what was added, removed, changed, or rebound — instead of the whole page again. | — | | `annotate_preview` | Outline an element in the in-app browser and leave the mark up until it's removed — the deliberate counterpart to the transient cues `drive_preview` draws as it works. `add` marks a ref with an optional short label, `remove` takes one down, `clear` takes them all. Marks follow their element and vanish with it, so a navigation clears them. | — | | `read_window_below` | Identify the OS window directly underneath the Hermes desktop window — app name, title, bounds (metadata only, never pixels). On macOS, other apps' titles appear only when Screen Recording is already granted; the tool never prompts for it. | — | | `focus_pane` | Reveal and focus a pane in the Hermes desktop app (chat, files, terminal, review, sessions). | — | | `react_to_message` | React to a message with a single emoji, iMessage-tapback style. Opt-in via Settings → Appearance (`display.message_reactions`). | — | -| `tour` | Give a live guided tour: dim the screen, highlight an element, and attach a narrated popover (driver.js). Works on the Hermes app's own UI and on any page open in the preview pane; `targets` discovers what's on screen, `show` narrates step-by-step, `start` hands the user Next/Prev controls. | — | -| `tip` | Point at one element with a small accent bubble and an arrow — the quiet sibling of `tour`, with no dimming, no spotlight, and no Next/Prev. Same `data-tour` handles and the same `tour(action='targets')` discovery call. | — | +| `gui_tour` | Give a live guided tour: dim the screen, highlight an element, and attach a narrated popover (driver.js). Works on the Hermes app's own UI and on any page open in the preview pane; `targets` discovers what's on screen, `show` narrates step-by-step, `start` hands the user Next/Prev controls. | — | +| `show_tip` | Point at one element with a small accent bubble and an arrow — the quiet sibling of `gui_tour`, with no dimming, no spotlight, and no Next/Prev. Same `data-tour` handles and the same `tour(action='targets')` discovery call. | — | +| `apply_layout` | Apply a saved layout preset to the Hermes desktop app when the user asks to rearrange the workspace. Built-ins: default (chat + sidebars), focus (chat only), terminal-deck, quad; plugin/user presets by id. To reveal ONE pane, use `focus_pane` instead. | — | ### Tours -The `tour` tool discovers its own targets — call `action='targets'` and it returns every addressable element on screen with a selector, a label, and a `stable` flag. Stable selectors key off identity (`data-tour`, `id`, `data-testid`, `aria-label`) and survive a re-render; positional `nth-child` paths don't, so stable ones sort first and should be preferred. +The `gui_tour` tool discovers its own targets — call `action='targets'` and it returns every addressable element on screen with a selector, a label, and a `stable` flag. Stable selectors key off identity (`data-tour`, `id`, `data-testid`, `aria-label`) and survive a re-render; positional `nth-child` paths don't, so stable ones sort first and should be preferred. To give an element a durable handle of your own, mark it up: @@ -275,7 +272,7 @@ nothing to page through. It's the right weight for a sentence that would be clearer with a finger on the thing it's about — "the model name is a button" — where dimming the whole app would not be. -The `tip` tool takes the same selectors `tour(action='targets')` reports, so +The `show_tip` tool takes the same selectors `gui_tour(action='targets')` reports, so discovery is one call for both, and the durable `data-tour` handles above name targets for either. One tip is on screen at a time; a new one replaces the last. @@ -300,7 +297,7 @@ the meantime. | Tool | Description | Requires environment | |------|-------------|----------------------| -| `todo` | Manage your task list for the current session. Use for complex tasks with 3+ steps or when the user provides multiple tasks. Call with no parameters to read the current list. Items may nest: an item's optional `parent` field points at another item's id, making it a subtask — surfaces render the tree indented. | — | +| `todo_list` | Manage your task list for the current session. Use for complex tasks with 3+ steps or when the user provides multiple tasks. Call with no parameters to read the current list. Items may nest: an item's optional `parent` field points at another item's id, making it a subtask — surfaces render the tree indented. | — | ## `vision` toolset diff --git a/website/docs/reference/toolsets-reference.md b/website/docs/reference/toolsets-reference.md index ba3051dac4..279c6a4d6c 100644 --- a/website/docs/reference/toolsets-reference.md +++ b/website/docs/reference/toolsets-reference.md @@ -52,12 +52,12 @@ Or in-session: | Toolset | Tools | Purpose | |---------|-------|---------| -| `browser` | `browser_back`, `browser_cdp`, `browser_click`, `browser_console`, `browser_dialog`, `browser_get_images`, `browser_navigate`, `browser_press`, `browser_scroll`, `browser_snapshot`, `browser_type`, `browser_vision`, `web_search` | Core browser automation. Includes `web_search` as a fallback for quick lookups. `browser_cdp` and `browser_dialog` are gated at runtime — registered only when a CDP endpoint is reachable at session start (via `/browser connect`, `browser.cdp_url` config, Browserbase, or Camofox). `browser_dialog` works together with the `pending_dialogs` and `frame_tree` fields that `browser_snapshot` adds when a CDP supervisor is attached. | +| `browser` | `browser_back`, `browser_cdp`, `browser_click`, `browser_console`, `browser_dialog`, `browser_exec`, `browser_get_images`, `browser_navigate`, `browser_press`, `browser_scroll`, `browser_snapshot`, `browser_type`, `browser_vault_enter_code`, `browser_vault_fill`, `browser_vault_list`, `browser_vault_save_login`, `browser_vault_unlock`, `browser_vision` | Core browser automation. `browser_cdp` and `browser_dialog` are gated at runtime — registered only when a CDP endpoint is reachable at session start (via `/browser connect`, `browser.cdp_url` config, Browserbase, or Camofox). `browser_dialog` works together with the `pending_dialogs` and `frame_tree` fields that `browser_snapshot` adds when a CDP supervisor is attached. `browser_exec` replaces the other browser tools when `browser.backend` is `browser-use`; the `browser_vault_*` tools ride with the browser for credential forms. Quick lookups belong to the `web`/`search` toolsets — `web_search` is deliberately NOT a member, so `disabled_toolsets: [browser]` cannot strip it (#64503). | | `clarify` | `clarify` | Ask the user a question when the agent needs clarification. | | `code_execution` | `execute_code` | Run Python scripts that call Hermes tools programmatically. | | `connections` | `manage_connections` | Connect the user to apps: managed connector accounts through the Nous gateway, and local MCP servers from the catalog. On the desktop every action is a card that blocks until the user settles it; off the desktop managed actions return a connect link per app. | | `coding` | composite (`file` + `terminal` + `search` + `web` + `skills` + `browser` + `todo` + `memory` + `session_search` + `clarify` + `code_execution` + `delegation` + `vision`) | Coding-focused bundle for software work: file editing, terminal, search, web docs, skills, browser, delegate, and code execution. | -| `cronjob` | `cronjob` | Schedule and manage recurring tasks. | +| `cronjob` | `cronjob_manage` | Schedule and manage recurring tasks. | | `debugging` | composite (`file` + `terminal` + `web`) | Debug bundle — file, process/terminal, web extract/search. | | `delegation` | `delegate_task` | Spawn isolated subagent instances for parallel work. | | `discord` | `discord` | Core Discord text/embed/DM actions (gateway-only). Active on the `hermes-discord` toolset. | @@ -72,15 +72,15 @@ Or in-session: | `video_gen` | `video_generate`, `xai_video_edit`, `xai_video_extend` | Text-to-video and image-to-video via plugin-registered backends (xAI Grok-Imagine, FAL.ai Veo 3.1 / Pixverse v6 / Kling 3.0 / Kling O3). Pass `image_url` to animate an image; omit it for text-to-video. `xai_video_edit` / `xai_video_extend` are provider-specific edit/extend tools, gated on xAI Imagine credentials. | | `kanban` | `kanban_attach`, `kanban_attach_url`, `kanban_attachments`, `kanban_block`, `kanban_comment`, `kanban_complete`, `kanban_create`, `kanban_heartbeat`, `kanban_link`, `kanban_list`, `kanban_request_changes`, `kanban_request_review`, `kanban_show`, `kanban_unblock` | Multi-agent coordination tools. Registered for dispatcher-spawned task workers (`HERMES_KANBAN_TASK`) and for platforms whose saved selection lists `kanban` (`hermes tools enable kanban --platform

`; the `all`/`*` wildcard does **not** enable it). Workers mark tasks done, request first-class review, block, heartbeat, comment, and create/link follow-up tasks; orchestrator profiles additionally get board-routing tools like list/unblock. `delegate_task` children are not Kanban run owners: their schema strips/disables this toolset and runtime guards reject direct board mutations, even if parent `HERMES_KANBAN_*` env vars are present. | | `memory` | `memory` | Persistent cross-session memory management. | -| `desktop_ui` | `annotate_preview`, `close_preview`, `close_terminal`, `drive_preview`, `focus_pane`, `open_preview`, `react_to_message`, `read_preview`, `read_terminal`, `read_window_below`, `tour` | Affordances that act on the Hermes desktop app itself — read/close the embedded terminal pane, open, read, close, interact with, and annotate the in-app browser, identify the OS window behind the app, reveal a pane, react to a message, run a guided tour (highlight + narrate UI elements in the app or the preview pane). Enabled for sessions whose source is the desktop app, whichever backend it's connected to (local, SSH, URL, or Hermes Cloud). Never present on CLI, TUI, messaging, or cron sessions. | -| `project` | `project_create`, `project_list`, `project_switch` | Create and switch desktop [Projects](../user-guide/cli.md) (named, multi-folder workspaces). GUI / desktop sessions only. | +| `desktop_ui` | `annotate_preview`, `apply_layout`, `close_terminal`, `desktop_preview`, `drive_preview`, `focus_pane`, `gui_tour`, `react_to_message`, `read_terminal`, `read_window_below`, `show_tip` | Affordances that act on the Hermes desktop app itself — read/close the embedded terminal pane, open, read, close, interact with, and annotate the in-app browser, identify the OS window behind the app, reveal a pane, react to a message, run a guided tour (highlight + narrate UI elements in the app or the preview pane), and apply a layout preset. Enabled for sessions whose source is the desktop app, whichever backend it's connected to (local, SSH, URL, or Hermes Cloud). Never present on CLI, TUI, messaging, or cron sessions. | +| `project` | `desktop_project` | Create and switch desktop [Projects](../user-guide/cli.md) (named, multi-folder workspaces) via one `create`/`switch`/`list` action enum. GUI / desktop sessions only. | | `safe` | `image_generate`, `vision_analyze`, `web_extract`, `web_search` (via `includes`) | Read-only research + media generation. No file writes, no terminal, no code execution. | | `search` | `web_search` | Web search only (without extract). | | `session_search` | `session_search` | Search past conversation sessions. | | `skills` | `skill_manage`, `skill_view`, `skills_list` | Skill CRUD and browsing. | | `spotify` | `spotify_albums`, `spotify_devices`, `spotify_library`, `spotify_playback`, `spotify_playlists`, `spotify_queue`, `spotify_search` | Native Spotify control (playback, queue, search, playlists, albums, library). Registered by the bundled `spotify` plugin. | -| `terminal` | `process`, `terminal` | Shell command execution and background process management. | -| `todo` | `todo` | Task list management within a session. | +| `terminal` | `process_manage`, `terminal` | Shell command execution and background process management. | +| `todo` | `todo_list` | Task list management within a session. | | `tts` | `text_to_speech` | Text-to-speech audio generation. | | `vision` | `vision_analyze` | Image analysis via vision-capable models. | | `video` | `video_analyze` | Video analysis and understanding tools (opt-in, not in the default toolset — add explicitly via `--toolsets`). |