Skip to content

Tool Registry

nick3 edited this page May 28, 2026 · 1 revision

Tool Registry

The in-process registry that holds every AI tool the model can call. Self-describing tools register once; AIManager.getToolDefinitions() lists them for the LLM; toolRegistry.dispatch() runs them.

Source: src/main/ai-tools/registry.ts.


Types

export interface ToolRuntimeState {
  currentStep: { number: number; title: string; action: string; successCriteria: string } | null
}

export interface VisionHelpers {
  verify: (input: { imagePath: string; question: string }) => Promise<{
    verdict: 'yes' | 'no' | 'unclear'
    explanation: string
  }>
  describe: (input: { imagePath: string; prompt?: string }) => Promise<{
    description: string
  }>
}

export interface ToolContext {
  window: BrowserWindow
  ptyManager: PtyManager
  workspaceStore: WorkspaceStore
  agentStore: AgentStore
  orchestrationStore: OrchestrationStore
  configLoader: ConfigLoader
  state: ToolRuntimeState
  vision?: VisionHelpers   // undefined when no provider is configured
}

export interface ToolDef<TArgs = Record<string, unknown>, TResult = unknown> {
  name: string
  description: string
  parameters: AIToolDefinition['function']['parameters']  // OpenAI tool-schema
  run: (args: TArgs, ctx: ToolContext) => TResult | Promise<TResult>
}

Registry API

class Registry {
  register<TArgs, TResult>(def: ToolDef<TArgs, TResult>): void
  unregister(name: string): boolean
  has(name: string): boolean
  listDefinitions(): AIToolDefinition[]   // OpenAI-format
  dispatch(name, args, ctx): Promise<
    | { ok: true; result: unknown; durationMs: number }
    | { ok: false; error: string; durationMs: number }
  >
}

export const toolRegistry = new Registry()

register warns (but allows) overwriting an existing tool — supports hot-reloading plugin tools without restart.

dispatch never throws — errors are wrapped and returned as { ok: false, error, durationMs }. The caller (AIManager.executeTool) renders these as tool results so the model sees the error and can adjust.


Defining a tool

Example: a tool that returns the current time.

import { toolRegistry } from './registry'

toolRegistry.register<{ timezone?: string }, { now: string; timezone: string }>({
  name: 'get_current_time',
  description: 'Returns the current ISO timestamp in the specified timezone (default UTC).',
  parameters: {
    type: 'object',
    properties: {
      timezone: {
        type: 'string',
        description: 'IANA timezone name (e.g., "America/New_York"). Defaults to UTC.'
      }
    }
  },
  run: async ({ timezone }) => {
    const tz = timezone ?? 'UTC'
    return {
      now: new Date().toLocaleString('en-US', { timeZone: tz, hour12: false }),
      timezone: tz
    }
  }
})

What this gives you:

  • The model sees the tool in getToolDefinitions() output
  • The model can call it with {timezone: 'America/Los_Angeles'}
  • The result is returned to the model as a tool message
  • Errors thrown inside run() are caught and surfaced as an error tool result

Tool naming conventions

  • <category>_<verb>_<object> — e.g., browser_click, read_terminal_output, goal_assign_task
  • Lowercase, snake_case, no spaces
  • Categories already in use: browser_*, read_*, write_*, wait_*, poll_*, list_*, capture_*, focus_*, maximize_*, restart_*, convert_*, agent verbs (set_agent_role, assign_task, etc.), declare_step, verify_step, claim_complete, abort_with_report
  • Avoid conflicts with the built-in catalog (see AI-Tools-Reference)

If your tool collides with a built-in, the last registration wins — so user plugin tools can intentionally override built-ins. The registry logs a warning to console.


Tool context — what's available in run

Field Use
window The main BrowserWindow. Use for window.webContents.send(...) to push events to the renderer, or window.webContents.capturePage() for screenshots.
ptyManager PTY pool. getPtyIdForPane(paneId), getScrollbackBuffer(ptyId), write(ptyId, data).
workspaceStore Workspaces + settings. getSettings().activeWorkspaceId, get(workspaceId).
agentStore Per-pane agent state. setRole, assignTask, completeTask, etc.
orchestrationStore Multi-pane orchestration goals + events.
configLoader Loaded personas, skills, task templates.
state Per-AIManager ToolRuntimeState — currently just currentStep for the step protocol. Add fields here for tool-to-tool coordination.
vision Optional vision helpers. undefined when no AI provider configured. Tools must guard before using.

Pagination via PagedTextResult

For tools returning large text payloads, use the standard envelope:

{
  success: true,
  content: string,           // chunk for this page
  hasMore: boolean,
  nextCursor?: string,       // pass back to get next chunk
  totalBytes: number,
  truncated?: boolean
}

The model is taught about pagination in the default system prompt. Tools already using this envelope: read_terminal_output, browser_get_content, browser_get_axtree.

For your own tool, store the full content somewhere keyed by an opaque cursor, return chunks slice-by-slice.


Where built-in tools live

src/main/ai-tools/
├── registry.ts                 — the Registry class
├── index.ts                    — registerAllTools() + plugin loader bootstrap
├── plugin-loader.ts            — fs.watch + require-cache busting for hot reload
├── step-protocol.ts            — declare_step, verify_step
├── pane.ts                     — list_panes, capture_screenshot, focus_pane, ...
├── terminal.ts                 — write_to_terminal, read_terminal_output, ...
├── orchestration.ts            — get_fleet_status, assign_task, ...
└── browser/
    ├── _helpers.ts             — saveScreenshotToDisk, cdpClickAt
    ├── navigation.ts           — navigate, get_content, screenshot, execute_js, back/forward/reload
    ├── interaction-t1.ts       — click, type, wait_for_*, keypress, scroll, select, check
    ├── interaction-t2.ts       — query, get_axtree, set_files, click_at, hover, drag, full_page, annotated
    ├── advanced.ts             — smart_click, run_recipe, cookies, save_pdf/html, convert_pane_*
    └── vision.ts               — verify_visual_state, describe_screen

The bootstrap at index.ts:registerAllTools() is called once from AIManager's constructor.


Plugin tools

User-authored tools dropped into <userData>/clusterspace-data/config/tools/*.js are loaded automatically by plugin-loader.ts (CommonJS, hot-reloaded on file change). Full guide: Plugin-Authoring.


See also

Clone this wiki locally