Skip to main content

hematite/agent/
tool_registry.rs

1use crate::agent::inference::{tool_metadata_for_name, ToolDefinition, ToolFunction};
2use serde_json::Value;
3
4fn make_tool(name: &str, description: &str, parameters: Value) -> ToolDefinition {
5    ToolDefinition {
6        tool_type: "function".into(),
7        function: ToolFunction {
8            name: name.into(),
9            description: description.into(),
10            parameters,
11        },
12        metadata: tool_metadata_for_name(name),
13    }
14}
15
16/// Returns the full set of tools exposed to the model.
17pub fn get_tools() -> Vec<ToolDefinition> {
18    let os = std::env::consts::OS;
19    let mut tools = vec![
20        make_tool(
21            "shell",
22            &format!(
23                "Execute a command in the host shell ({os}). \
24                     Use this for building, testing, or system operations. \
25                     Output is capped at 64KB. Prefer non-interactive commands."
26            ),
27            serde_json::json!({
28                "type": "object",
29                "properties": {
30                    "command": {
31                        "type": "string",
32                        "description": "The command to run"
33                    },
34                    "reason": {
35                        "type": "string",
36                        "description": "For risky shell calls, explain what this command is verifying or changing."
37                    },
38                    "timeout_secs": {
39                        "type": "integer",
40                        "description": "Optional timeout in seconds (default 60)"
41                    }
42                },
43                "required": ["command"]
44            }),
45        ),
46        make_tool(
47            "run_code",
48            "Execute a short JavaScript/TypeScript or Python snippet in a sandboxed subprocess. \
49             No network access, no filesystem escape, hard 10-second timeout. \
50             Use this to verify logic, test algorithms, compute values, or test functions \
51             when you need real output rather than a guess. \
52             ALWAYS include the `language` field — there is no default. \
53             \
54             JAVASCRIPT/TYPESCRIPT (language: \"javascript\"): \
55             Runs via Deno, NOT Node.js. `require()` does not exist — never use it. \
56             URL imports (e.g. from 'https://deno.land/...') are blocked — network is off. \
57             Use built-in Web APIs only: `crypto.subtle`, `TextEncoder`, `URL`, `atob`/`btoa`, etc. \
58             SHA-256 example: \
59               const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('hello')); \
60               console.log([...new Uint8Array(buf)].map(b=>b.toString(16).padStart(2,'0')).join('')); \
61             \
62             PYTHON (language: \"python\"): \
63             Standard library is available. `hashlib`, `json`, `math`, `datetime`, `re`, `itertools` all work. \
64             `subprocess`, `socket`, `urllib`, `requests` are blocked. \
65             SHA-256 example: import hashlib; print(hashlib.sha256(b'hello').hexdigest()) \
66             \
67             Do NOT fall back to shell to run deno, python, or node — use this tool directly.",
68            serde_json::json!({
69                "type": "object",
70                "properties": {
71                    "language": {
72                        "type": "string",
73                        "enum": ["javascript", "typescript", "python"],
74                        "description": "The language to run. javascript/typescript requires Deno; python requires Python 3."
75                    },
76                    "code": {
77                        "type": "string",
78                        "description": "The code to execute. Keep it short and self-contained. Print results to stdout."
79                    },
80                    "timeout_seconds": {
81                        "type": "integer",
82                        "description": "Max execution time in seconds (default 10, max 60). Use higher values for longer computations."
83                    }
84                },
85                "required": ["language", "code"]
86            }),
87        ),
88
89        make_tool(
90            "trace_runtime_flow",
91            "Return an authoritative read-only trace of Hematite runtime flow. \
92             Use this for architecture questions about keyboard input to final output, \
93             reasoning/specular separation, startup wiring, runtime subsystems, \
94             voice synthesis and Ctrl+T toggle, or \
95             session reset commands like /clear, /new, and /forget. Prefer this over guessing.",
96            serde_json::json!({
97                "type": "object",
98                "properties": {
99                    "topic": {
100                        "type": "string",
101                        "enum": ["user_turn", "session_reset", "reasoning_split", "runtime_subsystems", "startup", "voice"],
102                        "description": "Which verified runtime report to return. Use 'voice' for any question about Ctrl+T, voice toggle, or TTS pipeline. Use 'user_turn' for keyboard-to-output flow. Use 'session_reset' for /clear, /forget, /new. Use 'startup' for startup wiring. Use 'reasoning_split' for specular/thought routing. Use 'runtime_subsystems' for background subsystem overview."
103                    },
104                    "input": {
105                        "type": "string",
106                        "description": "Optional user input to label a normal user-turn trace"
107                    },
108                    "command": {
109                        "type": "string",
110                        "enum": ["/clear", "/new", "/forget", "all"],
111                        "description": "Optional reset command when topic=session_reset"
112                    }
113                },
114                "required": ["topic"]
115            }),
116        ),
117        make_tool(
118            "describe_toolchain",
119            "Return an authoritative read-only description of Hematite's actual tool surface and investigation strategy. \
120             Use this for tooling-discipline questions, best-tool selection, or read-only plans for tracing runtime behavior. \
121             Prefer this over improvising tool names or investigation steps from memory.",
122            serde_json::json!({
123                "type": "object",
124                "properties": {
125                    "topic": {
126                        "type": "string",
127                        "enum": ["read_only_codebase", "user_turn_plan", "voice_latency_plan", "host_inspection_plan", "all"],
128                        "description": "Which authoritative toolchain report to return"
129                    },
130                    "question": {
131                        "type": "string",
132                        "description": "Optional user question to label or tailor the read-only investigation plan"
133                    }
134                }
135            }),
136        ),
137        make_tool(
138            "inspect_host",
139            "Return a structured read-only inspection of the current machine and environment. \
140             Prefer this over raw shell for questions about installed developer tools, PATH issues, package-manager and environment health, network state, service state, running processes, desktop items, Downloads size, open listening ports, repo health, or directory/disk summaries. \
141             For remediation questions phrased like 'how do I fix cargo not found', 'how do I fix port 3000 already in use', or 'how do I fix LM Studio not reachable', use topic=fix_plan instead of diagnosis-only topics like env_doctor, path, or ports. \
142             Use topic=summary for a compact host snapshot, topic=toolchains for common dev tool versions, topic=path for PATH analysis, topic=env_doctor for package-manager and PATH health, topic=fix_plan for structured remediation plans, topic=network for adapters/IPs/gateways/DNS, topic=services for service status and startup mode, \
143             topic=processes for running-process snapshots, topic=desktop or topic=downloads for known folders, topic=ports for listening endpoints, topic=repo_doctor for a structured workspace health report, \
144             and topic=directory or topic=disk for arbitrary paths.",
145            serde_json::json!({
146                "type": "object",
147                "properties": {
148                    "topic": {
149                        "type": "string",
150                        "enum": ["summary", "toolchains", "path", "env_doctor", "fix_plan", "network", "services", "processes", "desktop", "downloads", "directory", "disk", "ports", "repo_doctor"],
151                        "description": "Which structured host inspection to run."
152                    },
153                    "name": {
154                        "type": "string",
155                        "description": "Optional when topic=processes or topic=services. Case-insensitive substring filter for process or service names."
156                    },
157                    "issue": {
158                        "type": "string",
159                        "description": "Optional when topic=fix_plan. Plain-English issue description such as 'cargo not found', 'port 3000 already in use', or 'LM Studio not reachable on localhost:1234'."
160                    },
161                    "path": {
162                        "type": "string",
163                        "description": "Required when topic=directory. Optional for topic=disk or topic=repo_doctor. Absolute or relative path to inspect."
164                    },
165                    "port": {
166                        "type": "integer",
167                        "description": "Optional when topic=ports or topic=fix_plan. Filter the result to one listening TCP port or anchor a port-conflict fix plan."
168                    },
169                    "max_entries": {
170                        "type": "integer",
171                        "description": "Optional cap for listed entries. Defaults to 10 and is capped internally."
172                    }
173                }
174            }),
175        ),
176        make_tool(
177            "run_hematite_maintainer_workflow",
178            "Run one of Hematite's known maintainer or release workflows with explicit approval. \
179             Prefer this over raw shell when the user explicitly asks to run one of Hematite's own scripts such as `clean.ps1`, `scripts/package-windows.ps1`, or `release.ps1`. \
180             Use workflow=clean for cleanup, workflow=package_windows for rebuilding the local Windows portable or installer, and workflow=release for the normal version bump/tag/push/publish flow. \
181             Keep this tool constrained to Hematite's own known workflows instead of inventing ad hoc shell commands or pretending to run arbitrary project scripts.",
182            serde_json::json!({
183                "type": "object",
184                "properties": {
185                    "workflow": {
186                        "type": "string",
187                        "enum": ["clean", "package_windows", "release"],
188                        "description": "Which known Hematite maintainer workflow to run."
189                    },
190                    "deep": {
191                        "type": "boolean",
192                        "description": "For workflow=clean. Also remove heavy build/runtime artifacts such as target/ and vein.db."
193                    },
194                    "reset": {
195                        "type": "boolean",
196                        "description": "For workflow=clean. Reset PLAN/TASK state in addition to normal cleanup."
197                    },
198                    "prune_dist": {
199                        "type": "boolean",
200                        "description": "For workflow=clean. Keep only the current Cargo.toml version under dist/."
201                    },
202                    "installer": {
203                        "type": "boolean",
204                        "description": "For workflow=package_windows. Also build the Windows installer."
205                    },
206                    "add_to_path": {
207                        "type": "boolean",
208                        "description": "For workflow=package_windows or workflow=release. Update the user PATH to the rebuilt portable."
209                    },
210                    "version": {
211                        "type": "string",
212                        "description": "For workflow=release. Exact semantic version such as 0.4.5."
213                    },
214                    "bump": {
215                        "type": "string",
216                        "enum": ["patch", "minor", "major"],
217                        "description": "For workflow=release. Ask release.ps1 to calculate the next version."
218                    },
219                    "push": {
220                        "type": "boolean",
221                        "description": "For workflow=release. Push main and the new tag."
222                    },
223                    "skip_installer": {
224                        "type": "boolean",
225                        "description": "For workflow=release. Skip the Windows installer build."
226                    },
227                    "publish_crates": {
228                        "type": "boolean",
229                        "description": "For workflow=release. Publish hematite-cli to crates.io after a successful push."
230                    },
231                    "publish_voice_crate": {
232                        "type": "boolean",
233                        "description": "For workflow=release. Publish hematite-kokoros first, then hematite-cli."
234                    }
235                },
236                "required": ["workflow"]
237            }),
238        ),
239        make_tool(
240            "run_workspace_workflow",
241            "Run an approval-gated workflow or script in the locked project workspace root. \
242             Use this for the current project's build, test, lint, fix, package.json scripts, just/task/make targets, explicit local script paths, or an exact workspace command. \
243             This tool is for the active workspace, not for Hematite's own maintainer scripts.",
244            serde_json::json!({
245                "type": "object",
246                "properties": {
247                    "workflow": {
248                        "type": "string",
249                        "enum": ["build", "test", "lint", "fix", "package_script", "task", "just", "make", "script_path", "command"],
250                        "description": "Which workspace workflow to run."
251                    },
252                    "name": {
253                        "type": "string",
254                        "description": "Required for workflow=package_script, task, just, or make. The script or target name."
255                    },
256                    "path": {
257                        "type": "string",
258                        "description": "Required for workflow=script_path. Relative path to a script inside the locked workspace root."
259                    },
260                    "command": {
261                        "type": "string",
262                        "description": "Required for workflow=command. Exact command to execute from the locked workspace root."
263                    },
264                    "timeout_ms": {
265                        "type": "integer",
266                        "description": "Optional timeout override in milliseconds."
267                    }
268                },
269                "required": ["workflow"]
270            }),
271        ),
272        make_tool(
273            "read_file",
274            "Read the contents of a file. For large files, use 'offset' and 'limit' to navigate.",
275            serde_json::json!({
276                "type": "object",
277                "properties": {
278                    "path": {
279                        "type": "string",
280                        "description": "Path to the file, relative to the project root"
281                    },
282                    "offset": {
283                        "type": "integer",
284                        "description": "Starting line number (0-indexed)"
285                    },
286                    "limit": {
287                        "type": "integer",
288                        "description": "Number of lines to read"
289                    }
290                },
291                "required": ["path"]
292            }),
293        ),
294        make_tool(
295            "lsp_definitions",
296            "Get the precise definition location (file:line:char) for a symbol at a specific position. \
297             Use this to jump to function/struct source code accurately.",
298            serde_json::json!({
299                "type": "object",
300                "properties": {
301                    "path": { "type": "string", "description": "File path" },
302                    "line": { "type": "integer", "description": "0-indexed line" },
303                    "character": { "type": "integer", "description": "0-indexed character" }
304                },
305                "required": ["path", "line", "character"]
306            }),
307        ),
308        make_tool(
309            "lsp_references",
310            "Find all locations where a symbol is used across the entire workspace. \
311             Use this to understand the impact of a refactor or discover internal API users.",
312            serde_json::json!({
313                "type": "object",
314                "properties": {
315                    "path": { "type": "string", "description": "File path" },
316                    "line": { "type": "integer", "description": "0-indexed line" },
317                    "character": { "type": "integer", "description": "0-indexed character" }
318                },
319                "required": ["path", "line", "character"]
320            }),
321        ),
322        make_tool(
323            "lsp_hover",
324            "Get hover information (documentation, function signature, type details) for a symbol. \
325             Use this for rapid spatial awareness without opening every file.",
326            serde_json::json!({
327                "type": "object",
328                "properties": {
329                    "path": { "type": "string", "description": "File path" },
330                    "line": { "type": "integer", "description": "0-indexed line" },
331                    "character": { "type": "integer", "description": "0-indexed character" }
332                },
333                "required": ["path", "line", "character"]
334            }),
335        ),
336        make_tool(
337            "lsp_rename_symbol",
338            "Rename a symbol project-wide using the Language Server. Ensures all references are updated safely.",
339            serde_json::json!({
340                "type": "object",
341                "properties": {
342                    "path": { "type": "string", "description": "File path" },
343                    "line": { "type": "integer", "description": "0-indexed line" },
344                    "character": { "type": "integer", "description": "0-indexed character" },
345                    "new_name": { "type": "string", "description": "The new name for the symbol" }
346                },
347                "required": ["path", "line", "character", "new_name"]
348            }),
349        ),
350        make_tool(
351            "lsp_get_diagnostics",
352            "Get a list of current compiler errors and warnings for a specific file. \
353             Use this to verify your code compiles and and to find exactly where errors are located.",
354            serde_json::json!({
355                "type": "object",
356                "properties": {
357                    "path": { "type": "string", "description": "File path" }
358                },
359                "required": ["path"]
360            }),
361        ),
362        make_tool(
363            "vision_analyze",
364            "Send an image file (screenshot, diagram, or UI mockup) to the multimodal vision model for technical analysis. \
365             Use this to identify UI bugs, confirm visual states, or understand architectural diagrams.",
366            serde_json::json!({
367                "type": "object",
368                "properties": {
369                    "path": { "type": "string", "description": "Absolute or relative path to the image file." },
370                    "prompt": { "type": "string", "description": "The specific question or analysis request for the vision model." }
371                },
372                "required": ["path", "prompt"]
373            }),
374        ),
375        make_tool(
376            "patch_hunk",
377            "Replace a specific line range [start_line, end_line] with new content. \
378             This is the most precise way to edit code and avoids search string failures.",
379            serde_json::json!({
380                "type": "object",
381                "properties": {
382                    "path": { "type": "string", "description": "File path" },
383                    "start_line": { "type": "integer", "description": "Starting line (1-indexed)" },
384                    "end_line": { "type": "integer", "description": "Ending line (inclusive)" },
385                    "replacement": { "type": "string", "description": "The new content for this range" }
386                },
387                "required": ["path", "start_line", "end_line", "replacement"]
388            }),
389        ),
390        make_tool(
391            "multi_search_replace",
392            "Replace multiple existing code blocks in a single file with new content. \
393             Each hunk specifies an EXACT 'search' string and a 'replace' string. \
394             The 'search' string MUST exactly match the existing file contents (including whitespace). \
395             This is the safest and most reliable way to make multiple structural edits.",
396            serde_json::json!({
397                "type": "object",
398                "properties": {
399                    "path": { "type": "string", "description": "File path" },
400                    "hunks": {
401                        "type": "array",
402                        "items": {
403                            "type": "object",
404                            "properties": {
405                                "search": { "type": "string", "description": "Exact existing text to find and replace" },
406                                "replace": { "type": "string", "description": "The new replacement text" }
407                            },
408                            "required": ["search", "replace"]
409                        }
410                    }
411                },
412                "required": ["path", "hunks"]
413            }),
414        ),
415        make_tool(
416            "write_file",
417            "Write content to a file, creating it (and any parent dirs) if needed. \
418             Overwrites existing files.",
419            serde_json::json!({
420                "type": "object",
421                "properties": {
422                    "path": { "type": "string", "description": "File path" },
423                    "content": { "type": "string", "description": "Full file content to write" }
424                },
425                "required": ["path", "content"]
426            }),
427        ),
428        make_tool(
429            "research_web",
430            "Perform a zero-cost technical search using DuckDuckGo. \
431             Use this to find documentation, latest API changes, or solutions to complex errors \
432             when your internal knowledge is insufficient. Returns snippets and URLs.",
433            serde_json::json!({
434                "type": "object",
435                "properties": {
436                    "query": { "type": "string", "description": "The technical search query" }
437                },
438                "required": ["query"]
439            }),
440        ),
441        make_tool(
442            "fetch_docs",
443            "Fetch a URL and convert it to clean Markdown. Use this to 'read' the documentation \
444             links found via research_web. This tool uses a proxy to bypass IP blocks.",
445            serde_json::json!({
446                "type": "object",
447                "properties": {
448                    "url": { "type": "string", "description": "The URL of the documentation to fetch" }
449                },
450                "required": ["url"]
451            }),
452        ),
453        make_tool(
454            "edit_file",
455            "Edit a file by replacing an exact string with another. \
456             The 'search' string does NOT need perfectly matching indentation (it is fuzzy), \
457             but the non-whitespace text must match exactly. Use this for targeted edits.",
458            serde_json::json!({
459                "type": "object",
460                "properties": {
461                    "path": { "type": "string", "description": "File path" },
462                    "search": {
463                        "type": "string",
464                        "description": "The exact text to find (must match whitespace/indentation precisely)"
465                    },
466                    "replace": {
467                        "type": "string",
468                        "description": "The replacement text"
469                    }
470                },
471                "required": ["path", "search", "replace"]
472            }),
473        ),
474        make_tool(
475            "auto_pin_context",
476            "Select 1-3 core files to 'Lock' into high-fidelity memory. \
477             Use this to ensure the most important architecture files \
478             are always visible during complex refactorings.",
479            serde_json::json!({
480                "type": "object",
481                "properties": {
482                    "paths": {
483                        "type": "array",
484                        "items": { "type": "string" }
485                    },
486                    "reason": { "type": "string" }
487                },
488                "required": ["paths", "reason"]
489            }),
490        ),
491        make_tool(
492            "list_pinned",
493            "List all files currently pinned in the model's active context.",
494            serde_json::json!({
495                "type": "object",
496                "properties": {}
497            }),
498        ),
499        make_tool(
500            "list_files",
501            "List files in a directory, optionally filtered by extension.",
502            serde_json::json!({
503                "type": "object",
504                "properties": {
505                    "path": {
506                        "type": "string",
507                        "description": "Directory to list (default: current dir)"
508                    },
509                    "extension": {
510                        "type": "string",
511                        "description": "Only return files with this extension, e.g. 'rs', 'toml' (no dot)"
512                    }
513                },
514                "required": []
515            }),
516        ),
517        make_tool(
518            "grep_files",
519            "Search file contents for a regex pattern. Supports context lines, files-only mode, \
520             and pagination. Returns file:line:content format by default.",
521            serde_json::json!({
522                "type": "object",
523                "properties": {
524                    "pattern": {
525                        "type": "string",
526                        "description": "Regex pattern to search for (case-insensitive by default)"
527                    },
528                    "path": {
529                        "type": "string",
530                        "description": "Directory to search (default: current dir)"
531                    },
532                    "extension": {
533                        "type": "string",
534                        "description": "Only search files with this extension, e.g. 'rs'"
535                    },
536                    "mode": {
537                        "type": "string",
538                        "enum": ["content", "files_only"],
539                        "description": "'content' (default) returns matching lines; 'files_only' returns only filenames"
540                    },
541                    "context": {
542                        "type": "integer",
543                        "description": "Lines of context before AND after each match (like rg -C)"
544                    },
545                    "before": {
546                        "type": "integer",
547                        "description": "Lines of context before each match (overrides context)"
548                    },
549                    "after": {
550                        "type": "integer",
551                        "description": "Lines of context after each match (overrides context)"
552                    },
553                    "head_limit": {
554                        "type": "integer",
555                        "description": "Max hunks (or files in files_only) to return (default: 50)"
556                    },
557                    "offset": {
558                        "type": "integer",
559                        "description": "Skip first N hunks/files - for pagination (default: 0)"
560                    }
561                },
562                "required": ["pattern"]
563            }),
564        ),
565        make_tool(
566            "git_commit",
567            "Stage all changes (git add -A) and create a commit. You MUST use 'Conventional Commits' (e.g. 'feat: description').",
568            serde_json::json!({
569                "type": "object",
570                "properties": {
571                    "message": { "type": "string", "description": "Commit message (Conventional Commit style)" }
572                },
573                "required": ["message"]
574            }),
575        ),
576        make_tool(
577            "git_push",
578            "Push current branched changes to the remote origin. Requires an existing remote connection.",
579            serde_json::json!({
580                "type": "object",
581                "properties": {},
582                "required": []
583            }),
584        ),
585        make_tool(
586            "git_remote",
587            "View or manage git remotes. Use this for onboarding to GitHub/GitLab services.",
588            serde_json::json!({
589                "type": "object",
590                "properties": {
591                    "action": {
592                        "type": "string",
593                        "enum": ["list", "add", "remove"],
594                        "description": "Operation to perform"
595                    },
596                    "name": { "type": "string", "description": "Remote name (e.g. origin)" },
597                    "url": { "type": "string", "description": "Remote URL (for 'add' action)" }
598                },
599                "required": ["action"]
600            }),
601        ),
602        make_tool(
603            "git_onboarding",
604            "High-level wizard to connect this repository to a remote host (GitHub/GitLab). \
605             Handles adding the remote and performing the initial tracking push in one step.",
606            serde_json::json!({
607                "type": "object",
608                "properties": {
609                    "url": { "type": "string", "description": "The remote repository URL (HTTPS or SSH)" },
610                    "name": { "type": "string", "description": "The remote name (default: origin)" },
611                    "push": { "type": "boolean", "description": "Whether to perform an initial push to establish tracking (default: false)" }
612                },
613                "required": ["url"]
614            }),
615        ),
616        make_tool(
617            "verify_build",
618            "Run project verification for build, test, lint, or fix workflows. \
619             Prefer per-project verify profiles from `.hematite/settings.json`, and fall back to \
620             auto-detected defaults when no profile is configured. Returns BUILD OK or BUILD FAILED \
621             with command output. ALWAYS call this after scaffolding a new project or making structural changes.",
622            serde_json::json!({
623                "type": "object",
624                "properties": {
625                    "action": {
626                        "type": "string",
627                        "enum": ["build", "test", "lint", "fix"],
628                        "description": "Which verification action to run. Defaults to build."
629                    },
630                    "profile": {
631                        "type": "string",
632                        "description": "Optional named verify profile from `.hematite/settings.json`."
633                    },
634                    "timeout_secs": {
635                        "type": "integer",
636                        "description": "Optional timeout override for this verification run."
637                    }
638                }
639            }),
640        ),
641        make_tool(
642            "git_worktree",
643            "Manage Git worktrees - isolated working directories on separate branches. \
644             Use 'add' to create a safe sandbox for risky/experimental work, \
645             'list' to see all worktrees, 'remove' to clean up, 'prune' to remove stale entries.",
646            serde_json::json!({
647                "type": "object",
648                "properties": {
649                    "action": {
650                        "type": "string",
651                        "enum": ["list", "add", "remove", "prune"],
652                        "description": "Worktree operation to perform"
653                    },
654                    "path": {
655                        "type": "string",
656                        "description": "Directory path for the new worktree (required for add/remove)"
657                    },
658                    "branch": {
659                        "type": "string",
660                        "description": "Branch name for the worktree (add only; defaults to path basename)"
661                    }
662                },
663                "required": ["action"]
664            }),
665        ),
666        make_tool(
667            "clarify",
668            "Ask the user a clarifying question when you genuinely cannot proceed without \
669             more information. Use this ONLY when you are blocked and cannot make a \
670             reasonable assumption. Do NOT use it to ask permission - just act.",
671            serde_json::json!({
672                "type": "object",
673                "properties": {
674                    "question": {
675                        "type": "string",
676                        "description": "The specific question to ask the user"
677                    }
678                },
679                "required": ["question"]
680            }),
681        ),
682        make_tool(
683            "manage_tasks",
684            "Manage the persistent task ledger in .hematite/TASK.md. Use this to track long-term goals across restarts.",
685            crate::tools::tasks::get_tasks_params(),
686        ),
687        make_tool(
688            "maintain_plan",
689            "Document the architectural strategy and session blueprint in .hematite/PLAN.md. Use this to maintain context across restarts.",
690            crate::tools::plan::get_plan_params(),
691        ),
692        make_tool(
693            "generate_walkthrough",
694            "Generate a final session report in .hematite/WALKTHROUGH.md including achievements and verification results.",
695            crate::tools::plan::get_walkthrough_params(),
696        ),
697        make_tool(
698            "swarm",
699            "Delegate high-volume parallel tasks to a swarm of background workers. \
700             Use this for large-scale refactors, multi-file research, or parallel documentation updates. \
701             You must provide a 'tasks' array where each task has an 'id', 'target' (file), and 'instruction'.",
702            serde_json::json!({
703                "type": "object",
704                "properties": {
705                    "tasks": {
706                        "type": "array",
707                        "items": {
708                            "type": "object",
709                            "properties": {
710                                "id": { "type": "string" },
711                                "target": { "type": "string", "description": "Target file or directory" },
712                                "instruction": { "type": "string", "description": "Specific task for this worker" }
713                            },
714                            "required": ["id", "target", "instruction"]
715                        }
716                    },
717                    "max_workers": {
718                        "type": "integer",
719                        "description": "Max parallel workers (default 3, auto-throttled by hardware)",
720                        "default": 3
721                    }
722                },
723                "required": ["tasks"]
724            }),
725        ),
726    ];
727
728    let lsp_defs = crate::tools::lsp_tools::get_lsp_definitions();
729    tools.push(make_tool(
730        "lsp_search_symbol",
731        "Find the location (file/line) of any function, struct, or variable in the entire project workspace. \
732         This is the fastest 'Golden Path' for navigating to a symbol by name.",
733        serde_json::json!({
734            "type": "object",
735            "properties": {
736                "query": { "type": "string", "description": "The name of the symbol to find (e.g. 'initialize_mcp')" }
737            },
738            "required": ["query"]
739        }),
740    ));
741    for def in lsp_defs {
742        let name = def["name"].as_str().unwrap();
743        tools.push(ToolDefinition {
744            tool_type: "function".into(),
745            function: ToolFunction {
746                name: name.into(),
747                description: def["description"].as_str().unwrap().into(),
748                parameters: def["parameters"].clone(),
749            },
750            metadata: tool_metadata_for_name(name),
751        });
752    }
753
754    tools
755}
756
757pub async fn dispatch_builtin_tool(name: &str, args: &Value) -> Result<String, String> {
758    match name {
759        "shell" => crate::tools::shell::execute(args).await,
760        "run_code" => crate::tools::code_sandbox::execute(args).await,
761        "trace_runtime_flow" => crate::tools::runtime_trace::trace_runtime_flow(args).await,
762        "describe_toolchain" => crate::tools::toolchain::describe_toolchain(args).await,
763        "inspect_host" => crate::tools::host_inspect::inspect_host(args).await,
764        "run_hematite_maintainer_workflow" => {
765            crate::tools::repo_script::run_hematite_maintainer_workflow(args).await
766        }
767        "run_workspace_workflow" => crate::tools::workspace_workflow::run_workspace_workflow(args).await,
768        "read_file" => crate::tools::file_ops::read_file(args).await,
769        "inspect_lines" => crate::tools::file_ops::inspect_lines(args).await,
770        "write_file" => crate::tools::file_ops::write_file(args).await,
771        "edit_file" => crate::tools::file_ops::edit_file(args).await,
772        "patch_hunk" => crate::tools::file_ops::patch_hunk(args).await,
773        "multi_search_replace" => crate::tools::file_ops::multi_search_replace(args).await,
774        "list_files" => crate::tools::file_ops::list_files(args).await,
775        "grep_files" => crate::tools::file_ops::grep_files(args).await,
776        "git_commit" => crate::tools::git::execute(args).await,
777        "git_push" => crate::tools::git::execute_push(args).await,
778        "git_remote" => crate::tools::git::execute_remote(args).await,
779        "git_onboarding" => crate::tools::git_onboarding::execute(args).await,
780        "verify_build" => crate::tools::verify_build::execute(args).await,
781        "git_worktree" => crate::tools::git::execute_worktree(args).await,
782        "health" => crate::tools::health::execute(args).await,
783        "research_web" => crate::tools::research::execute_search(args).await,
784        "fetch_docs" => crate::tools::research::execute_fetch(args).await,
785        "manage_tasks" => crate::tools::tasks::manage_tasks(args).await,
786        "maintain_plan" => crate::tools::plan::maintain_plan(args).await,
787        "generate_walkthrough" => crate::tools::plan::generate_walkthrough(args).await,
788        "clarify" => {
789            let q = args.get("question").and_then(|v| v.as_str()).unwrap_or("?");
790            Ok(format!("[clarify] {q}"))
791        }
792        "vision_analyze" => Err(
793            "Tool 'vision_analyze' must be dispatched by ConversationManager (it requires hardware engine access)."
794                .into(),
795        ),
796        other => {
797            if other.contains('.') || other.contains('/') || other.contains('\\') {
798                Err(format!(
799                    "'{}' is a PATH, not a tool. You correctly identified the location, but you MUST use `read_file` or `list_files` (internal) or `powershell` (external) to access it.",
800                    other
801                ))
802            } else if matches!(other.to_lowercase().as_str(), "hematite" | "assistant" | "ai") {
803                Err(format!(
804                    "'{}' is YOUR IDENTITY, not a tool. Use list_files or read_file to explore the codebase.",
805                    other
806                ))
807            } else if matches!(
808                other.to_lowercase().as_str(),
809                "thought" | "think" | "reasoning" | "thinking" | "internal"
810            ) {
811                Err(format!(
812                    "'{}' is NOT a tool - it is a reasoning tag. Output your answer as plain text after your <think> block.",
813                    other
814                ))
815            } else {
816                Err(format!("Unknown tool: '{}'", other))
817            }
818        }
819    }
820}