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
16pub 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 make_tool(
89 "map_project",
90 "Compact architecture-aware map of the project structure, key configuration files, \
91 likely entrypoints, and core owner files. Use this at the start of a task to gain \
92 spatial awareness before deeper file reads or LSP inspection.",
93 serde_json::json!({
94 "type": "object",
95 "properties": {
96 "focus": {
97 "type": "string",
98 "description": "Optional relative subpath to focus the map on instead of the whole workspace."
99 },
100 "include_symbols": {
101 "type": "boolean",
102 "description": "Whether to extract a small set of top symbols from core files. Defaults to true."
103 },
104 "max_depth": {
105 "type": "integer",
106 "description": "Optional tree depth cap for the directory section. Defaults to 4 and is capped internally."
107 }
108 }
109 }),
110 ),
111 make_tool(
112 "trace_runtime_flow",
113 "Return an authoritative read-only trace of Hematite runtime flow. \
114 Use this for architecture questions about keyboard input to final output, \
115 reasoning/specular separation, startup wiring, runtime subsystems, \
116 voice synthesis and Ctrl+T toggle, or \
117 session reset commands like /clear, /new, and /forget. Prefer this over guessing.",
118 serde_json::json!({
119 "type": "object",
120 "properties": {
121 "topic": {
122 "type": "string",
123 "enum": ["user_turn", "session_reset", "reasoning_split", "runtime_subsystems", "startup", "voice"],
124 "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."
125 },
126 "input": {
127 "type": "string",
128 "description": "Optional user input to label a normal user-turn trace"
129 },
130 "command": {
131 "type": "string",
132 "enum": ["/clear", "/new", "/forget", "all"],
133 "description": "Optional reset command when topic=session_reset"
134 }
135 },
136 "required": ["topic"]
137 }),
138 ),
139 make_tool(
140 "describe_toolchain",
141 "Return an authoritative read-only description of Hematite's actual tool surface and investigation strategy. \
142 Use this for tooling-discipline questions, best-tool selection, or read-only plans for tracing runtime behavior. \
143 Prefer this over improvising tool names or investigation steps from memory.",
144 serde_json::json!({
145 "type": "object",
146 "properties": {
147 "topic": {
148 "type": "string",
149 "enum": ["read_only_codebase", "user_turn_plan", "voice_latency_plan", "host_inspection_plan", "all"],
150 "description": "Which authoritative toolchain report to return"
151 },
152 "question": {
153 "type": "string",
154 "description": "Optional user question to label or tailor the read-only investigation plan"
155 }
156 }
157 }),
158 ),
159 make_tool(
160 "inspect_host",
161 "Return a structured read-only inspection of the current machine and environment. \
162 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. \
163 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. \
164 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, \
165 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, \
166 and topic=directory or topic=disk for arbitrary paths.",
167 serde_json::json!({
168 "type": "object",
169 "properties": {
170 "topic": {
171 "type": "string",
172 "enum": ["summary", "toolchains", "path", "env_doctor", "fix_plan", "network", "services", "processes", "desktop", "downloads", "directory", "disk", "ports", "repo_doctor"],
173 "description": "Which structured host inspection to run."
174 },
175 "name": {
176 "type": "string",
177 "description": "Optional when topic=processes or topic=services. Case-insensitive substring filter for process or service names."
178 },
179 "issue": {
180 "type": "string",
181 "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'."
182 },
183 "path": {
184 "type": "string",
185 "description": "Required when topic=directory. Optional for topic=disk or topic=repo_doctor. Absolute or relative path to inspect."
186 },
187 "port": {
188 "type": "integer",
189 "description": "Optional when topic=ports or topic=fix_plan. Filter the result to one listening TCP port or anchor a port-conflict fix plan."
190 },
191 "max_entries": {
192 "type": "integer",
193 "description": "Optional cap for listed entries. Defaults to 10 and is capped internally."
194 }
195 }
196 }),
197 ),
198 make_tool(
199 "read_file",
200 "Read the contents of a file. For large files, use 'offset' and 'limit' to navigate.",
201 serde_json::json!({
202 "type": "object",
203 "properties": {
204 "path": {
205 "type": "string",
206 "description": "Path to the file, relative to the project root"
207 },
208 "offset": {
209 "type": "integer",
210 "description": "Starting line number (0-indexed)"
211 },
212 "limit": {
213 "type": "integer",
214 "description": "Number of lines to read"
215 }
216 },
217 "required": ["path"]
218 }),
219 ),
220 make_tool(
221 "lsp_definitions",
222 "Get the precise definition location (file:line:char) for a symbol at a specific position. \
223 Use this to jump to function/struct source code accurately.",
224 serde_json::json!({
225 "type": "object",
226 "properties": {
227 "path": { "type": "string", "description": "File path" },
228 "line": { "type": "integer", "description": "0-indexed line" },
229 "character": { "type": "integer", "description": "0-indexed character" }
230 },
231 "required": ["path", "line", "character"]
232 }),
233 ),
234 make_tool(
235 "lsp_references",
236 "Find all locations where a symbol is used across the entire workspace. \
237 Use this to understand the impact of a refactor or discover internal API users.",
238 serde_json::json!({
239 "type": "object",
240 "properties": {
241 "path": { "type": "string", "description": "File path" },
242 "line": { "type": "integer", "description": "0-indexed line" },
243 "character": { "type": "integer", "description": "0-indexed character" }
244 },
245 "required": ["path", "line", "character"]
246 }),
247 ),
248 make_tool(
249 "lsp_hover",
250 "Get hover information (documentation, function signature, type details) for a symbol. \
251 Use this for rapid spatial awareness without opening every file.",
252 serde_json::json!({
253 "type": "object",
254 "properties": {
255 "path": { "type": "string", "description": "File path" },
256 "line": { "type": "integer", "description": "0-indexed line" },
257 "character": { "type": "integer", "description": "0-indexed character" }
258 },
259 "required": ["path", "line", "character"]
260 }),
261 ),
262 make_tool(
263 "lsp_rename_symbol",
264 "Rename a symbol project-wide using the Language Server. Ensures all references are updated safely.",
265 serde_json::json!({
266 "type": "object",
267 "properties": {
268 "path": { "type": "string", "description": "File path" },
269 "line": { "type": "integer", "description": "0-indexed line" },
270 "character": { "type": "integer", "description": "0-indexed character" },
271 "new_name": { "type": "string", "description": "The new name for the symbol" }
272 },
273 "required": ["path", "line", "character", "new_name"]
274 }),
275 ),
276 make_tool(
277 "lsp_get_diagnostics",
278 "Get a list of current compiler errors and warnings for a specific file. \
279 Use this to verify your code compiles and and to find exactly where errors are located.",
280 serde_json::json!({
281 "type": "object",
282 "properties": {
283 "path": { "type": "string", "description": "File path" }
284 },
285 "required": ["path"]
286 }),
287 ),
288 make_tool(
289 "vision_analyze",
290 "Send an image file (screenshot, diagram, or UI mockup) to the multimodal vision model for technical analysis. \
291 Use this to identify UI bugs, confirm visual states, or understand architectural diagrams.",
292 serde_json::json!({
293 "type": "object",
294 "properties": {
295 "path": { "type": "string", "description": "Absolute or relative path to the image file." },
296 "prompt": { "type": "string", "description": "The specific question or analysis request for the vision model." }
297 },
298 "required": ["path", "prompt"]
299 }),
300 ),
301 make_tool(
302 "patch_hunk",
303 "Replace a specific line range [start_line, end_line] with new content. \
304 This is the most precise way to edit code and avoids search string failures.",
305 serde_json::json!({
306 "type": "object",
307 "properties": {
308 "path": { "type": "string", "description": "File path" },
309 "start_line": { "type": "integer", "description": "Starting line (1-indexed)" },
310 "end_line": { "type": "integer", "description": "Ending line (inclusive)" },
311 "replacement": { "type": "string", "description": "The new content for this range" }
312 },
313 "required": ["path", "start_line", "end_line", "replacement"]
314 }),
315 ),
316 make_tool(
317 "multi_search_replace",
318 "Replace multiple existing code blocks in a single file with new content. \
319 Each hunk specifies an EXACT 'search' string and a 'replace' string. \
320 The 'search' string MUST exactly match the existing file contents (including whitespace). \
321 This is the safest and most reliable way to make multiple structural edits.",
322 serde_json::json!({
323 "type": "object",
324 "properties": {
325 "path": { "type": "string", "description": "File path" },
326 "hunks": {
327 "type": "array",
328 "items": {
329 "type": "object",
330 "properties": {
331 "search": { "type": "string", "description": "Exact existing text to find and replace" },
332 "replace": { "type": "string", "description": "The new replacement text" }
333 },
334 "required": ["search", "replace"]
335 }
336 }
337 },
338 "required": ["path", "hunks"]
339 }),
340 ),
341 make_tool(
342 "write_file",
343 "Write content to a file, creating it (and any parent dirs) if needed. \
344 Overwrites existing files.",
345 serde_json::json!({
346 "type": "object",
347 "properties": {
348 "path": { "type": "string", "description": "File path" },
349 "content": { "type": "string", "description": "Full file content to write" }
350 },
351 "required": ["path", "content"]
352 }),
353 ),
354 make_tool(
355 "research_web",
356 "Perform a zero-cost technical search using DuckDuckGo. \
357 Use this to find documentation, latest API changes, or solutions to complex errors \
358 when your internal knowledge is insufficient. Returns snippets and URLs.",
359 serde_json::json!({
360 "type": "object",
361 "properties": {
362 "query": { "type": "string", "description": "The technical search query" }
363 },
364 "required": ["query"]
365 }),
366 ),
367 make_tool(
368 "fetch_docs",
369 "Fetch a URL and convert it to clean Markdown. Use this to 'read' the documentation \
370 links found via research_web. This tool uses a proxy to bypass IP blocks.",
371 serde_json::json!({
372 "type": "object",
373 "properties": {
374 "url": { "type": "string", "description": "The URL of the documentation to fetch" }
375 },
376 "required": ["url"]
377 }),
378 ),
379 make_tool(
380 "edit_file",
381 "Edit a file by replacing an exact string with another. \
382 The 'search' string does NOT need perfectly matching indentation (it is fuzzy), \
383 but the non-whitespace text must match exactly. Use this for targeted edits.",
384 serde_json::json!({
385 "type": "object",
386 "properties": {
387 "path": { "type": "string", "description": "File path" },
388 "search": {
389 "type": "string",
390 "description": "The exact text to find (must match whitespace/indentation precisely)"
391 },
392 "replace": {
393 "type": "string",
394 "description": "The replacement text"
395 }
396 },
397 "required": ["path", "search", "replace"]
398 }),
399 ),
400 make_tool(
401 "auto_pin_context",
402 "Select 1-3 core files to 'Lock' into high-fidelity memory. \
403 Use this after map_project to ensure the most important architecture files \
404 are always visible during complex refactorings.",
405 serde_json::json!({
406 "type": "object",
407 "properties": {
408 "paths": {
409 "type": "array",
410 "items": { "type": "string" }
411 },
412 "reason": { "type": "string" }
413 },
414 "required": ["paths", "reason"]
415 }),
416 ),
417 make_tool(
418 "list_pinned",
419 "List all files currently pinned in the model's active context.",
420 serde_json::json!({
421 "type": "object",
422 "properties": {}
423 }),
424 ),
425 make_tool(
426 "list_files",
427 "List files in a directory, optionally filtered by extension.",
428 serde_json::json!({
429 "type": "object",
430 "properties": {
431 "path": {
432 "type": "string",
433 "description": "Directory to list (default: current dir)"
434 },
435 "extension": {
436 "type": "string",
437 "description": "Only return files with this extension, e.g. 'rs', 'toml' (no dot)"
438 }
439 },
440 "required": []
441 }),
442 ),
443 make_tool(
444 "grep_files",
445 "Search file contents for a regex pattern. Supports context lines, files-only mode, \
446 and pagination. Returns file:line:content format by default.",
447 serde_json::json!({
448 "type": "object",
449 "properties": {
450 "pattern": {
451 "type": "string",
452 "description": "Regex pattern to search for (case-insensitive by default)"
453 },
454 "path": {
455 "type": "string",
456 "description": "Directory to search (default: current dir)"
457 },
458 "extension": {
459 "type": "string",
460 "description": "Only search files with this extension, e.g. 'rs'"
461 },
462 "mode": {
463 "type": "string",
464 "enum": ["content", "files_only"],
465 "description": "'content' (default) returns matching lines; 'files_only' returns only filenames"
466 },
467 "context": {
468 "type": "integer",
469 "description": "Lines of context before AND after each match (like rg -C)"
470 },
471 "before": {
472 "type": "integer",
473 "description": "Lines of context before each match (overrides context)"
474 },
475 "after": {
476 "type": "integer",
477 "description": "Lines of context after each match (overrides context)"
478 },
479 "head_limit": {
480 "type": "integer",
481 "description": "Max hunks (or files in files_only) to return (default: 50)"
482 },
483 "offset": {
484 "type": "integer",
485 "description": "Skip first N hunks/files - for pagination (default: 0)"
486 }
487 },
488 "required": ["pattern"]
489 }),
490 ),
491 make_tool(
492 "git_commit",
493 "Stage all changes (git add -A) and create a commit. You MUST use 'Conventional Commits' (e.g. 'feat: description').",
494 serde_json::json!({
495 "type": "object",
496 "properties": {
497 "message": { "type": "string", "description": "Commit message (Conventional Commit style)" }
498 },
499 "required": ["message"]
500 }),
501 ),
502 make_tool(
503 "git_push",
504 "Push current branched changes to the remote origin. Requires an existing remote connection.",
505 serde_json::json!({
506 "type": "object",
507 "properties": {},
508 "required": []
509 }),
510 ),
511 make_tool(
512 "git_remote",
513 "View or manage git remotes. Use this for onboarding to GitHub/GitLab services.",
514 serde_json::json!({
515 "type": "object",
516 "properties": {
517 "action": {
518 "type": "string",
519 "enum": ["list", "add", "remove"],
520 "description": "Operation to perform"
521 },
522 "name": { "type": "string", "description": "Remote name (e.g. origin)" },
523 "url": { "type": "string", "description": "Remote URL (for 'add' action)" }
524 },
525 "required": ["action"]
526 }),
527 ),
528 make_tool(
529 "git_onboarding",
530 "High-level wizard to connect this repository to a remote host (GitHub/GitLab). \
531 Handles adding the remote and performing the initial tracking push in one step.",
532 serde_json::json!({
533 "type": "object",
534 "properties": {
535 "url": { "type": "string", "description": "The remote repository URL (HTTPS or SSH)" },
536 "name": { "type": "string", "description": "The remote name (default: origin)" },
537 "push": { "type": "boolean", "description": "Whether to perform an initial push to establish tracking (default: false)" }
538 },
539 "required": ["url"]
540 }),
541 ),
542 make_tool(
543 "verify_build",
544 "Run project verification for build, test, lint, or fix workflows. \
545 Prefer per-project verify profiles from `.hematite/settings.json`, and fall back to \
546 auto-detected defaults when no profile is configured. Returns BUILD OK or BUILD FAILED \
547 with command output. ALWAYS call this after scaffolding a new project or making structural changes.",
548 serde_json::json!({
549 "type": "object",
550 "properties": {
551 "action": {
552 "type": "string",
553 "enum": ["build", "test", "lint", "fix"],
554 "description": "Which verification action to run. Defaults to build."
555 },
556 "profile": {
557 "type": "string",
558 "description": "Optional named verify profile from `.hematite/settings.json`."
559 },
560 "timeout_secs": {
561 "type": "integer",
562 "description": "Optional timeout override for this verification run."
563 }
564 }
565 }),
566 ),
567 make_tool(
568 "git_worktree",
569 "Manage Git worktrees - isolated working directories on separate branches. \
570 Use 'add' to create a safe sandbox for risky/experimental work, \
571 'list' to see all worktrees, 'remove' to clean up, 'prune' to remove stale entries.",
572 serde_json::json!({
573 "type": "object",
574 "properties": {
575 "action": {
576 "type": "string",
577 "enum": ["list", "add", "remove", "prune"],
578 "description": "Worktree operation to perform"
579 },
580 "path": {
581 "type": "string",
582 "description": "Directory path for the new worktree (required for add/remove)"
583 },
584 "branch": {
585 "type": "string",
586 "description": "Branch name for the worktree (add only; defaults to path basename)"
587 }
588 },
589 "required": ["action"]
590 }),
591 ),
592 make_tool(
593 "clarify",
594 "Ask the user a clarifying question when you genuinely cannot proceed without \
595 more information. Use this ONLY when you are blocked and cannot make a \
596 reasonable assumption. Do NOT use it to ask permission - just act.",
597 serde_json::json!({
598 "type": "object",
599 "properties": {
600 "question": {
601 "type": "string",
602 "description": "The specific question to ask the user"
603 }
604 },
605 "required": ["question"]
606 }),
607 ),
608 make_tool(
609 "manage_tasks",
610 "Manage the persistent task ledger in .hematite/TASK.md. Use this to track long-term goals across restarts.",
611 crate::tools::tasks::get_tasks_params(),
612 ),
613 make_tool(
614 "maintain_plan",
615 "Document the architectural strategy and session blueprint in .hematite/PLAN.md. Use this to maintain context across restarts.",
616 crate::tools::plan::get_plan_params(),
617 ),
618 make_tool(
619 "generate_walkthrough",
620 "Generate a final session report in .hematite/WALKTHROUGH.md including achievements and verification results.",
621 crate::tools::plan::get_walkthrough_params(),
622 ),
623 make_tool(
624 "swarm",
625 "Delegate high-volume parallel tasks to a swarm of background workers. \
626 Use this for large-scale refactors, multi-file research, or parallel documentation updates. \
627 You must provide a 'tasks' array where each task has an 'id', 'target' (file), and 'instruction'.",
628 serde_json::json!({
629 "type": "object",
630 "properties": {
631 "tasks": {
632 "type": "array",
633 "items": {
634 "type": "object",
635 "properties": {
636 "id": { "type": "string" },
637 "target": { "type": "string", "description": "Target file or directory" },
638 "instruction": { "type": "string", "description": "Specific task for this worker" }
639 },
640 "required": ["id", "target", "instruction"]
641 }
642 },
643 "max_workers": {
644 "type": "integer",
645 "description": "Max parallel workers (default 3, auto-throttled by hardware)",
646 "default": 3
647 }
648 },
649 "required": ["tasks"]
650 }),
651 ),
652 ];
653
654 let lsp_defs = crate::tools::lsp_tools::get_lsp_definitions();
655 tools.push(make_tool(
656 "lsp_search_symbol",
657 "Find the location (file/line) of any function, struct, or variable in the entire project workspace. \
658 This is the fastest 'Golden Path' for navigating to a symbol by name.",
659 serde_json::json!({
660 "type": "object",
661 "properties": {
662 "query": { "type": "string", "description": "The name of the symbol to find (e.g. 'initialize_mcp')" }
663 },
664 "required": ["query"]
665 }),
666 ));
667 for def in lsp_defs {
668 let name = def["name"].as_str().unwrap();
669 tools.push(ToolDefinition {
670 tool_type: "function".into(),
671 function: ToolFunction {
672 name: name.into(),
673 description: def["description"].as_str().unwrap().into(),
674 parameters: def["parameters"].clone(),
675 },
676 metadata: tool_metadata_for_name(name),
677 });
678 }
679
680 tools
681}
682
683pub async fn dispatch_builtin_tool(name: &str, args: &Value) -> Result<String, String> {
684 match name {
685 "shell" => crate::tools::shell::execute(args).await,
686 "run_code" => crate::tools::code_sandbox::execute(args).await,
687 "map_project" => crate::tools::project_map::map_project(args).await,
688 "trace_runtime_flow" => crate::tools::runtime_trace::trace_runtime_flow(args).await,
689 "describe_toolchain" => crate::tools::toolchain::describe_toolchain(args).await,
690 "inspect_host" => crate::tools::host_inspect::inspect_host(args).await,
691 "read_file" => crate::tools::file_ops::read_file(args).await,
692 "inspect_lines" => crate::tools::file_ops::inspect_lines(args).await,
693 "write_file" => crate::tools::file_ops::write_file(args).await,
694 "edit_file" => crate::tools::file_ops::edit_file(args).await,
695 "patch_hunk" => crate::tools::file_ops::patch_hunk(args).await,
696 "multi_search_replace" => crate::tools::file_ops::multi_search_replace(args).await,
697 "list_files" => crate::tools::file_ops::list_files(args).await,
698 "grep_files" => crate::tools::file_ops::grep_files(args).await,
699 "git_commit" => crate::tools::git::execute(args).await,
700 "git_push" => crate::tools::git::execute_push(args).await,
701 "git_remote" => crate::tools::git::execute_remote(args).await,
702 "git_onboarding" => crate::tools::git_onboarding::execute(args).await,
703 "verify_build" => crate::tools::verify_build::execute(args).await,
704 "git_worktree" => crate::tools::git::execute_worktree(args).await,
705 "health" => crate::tools::health::execute(args).await,
706 "research_web" => crate::tools::research::execute_search(args).await,
707 "fetch_docs" => crate::tools::research::execute_fetch(args).await,
708 "manage_tasks" => crate::tools::tasks::manage_tasks(args).await,
709 "maintain_plan" => crate::tools::plan::maintain_plan(args).await,
710 "generate_walkthrough" => crate::tools::plan::generate_walkthrough(args).await,
711 "clarify" => {
712 let q = args.get("question").and_then(|v| v.as_str()).unwrap_or("?");
713 Ok(format!("[clarify] {q}"))
714 }
715 "vision_analyze" => Err(
716 "Tool 'vision_analyze' must be dispatched by ConversationManager (it requires hardware engine access)."
717 .into(),
718 ),
719 other => {
720 if other.contains('.') || other.contains('/') || other.contains('\\') {
721 Err(format!(
722 "'{}' 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.",
723 other
724 ))
725 } else if matches!(other.to_lowercase().as_str(), "hematite" | "assistant" | "ai") {
726 Err(format!(
727 "'{}' is YOUR IDENTITY, not a tool. Use list_files or read_file to explore the codebase.",
728 other
729 ))
730 } else if matches!(
731 other.to_lowercase().as_str(),
732 "thought" | "think" | "reasoning" | "thinking" | "internal"
733 ) {
734 Err(format!(
735 "'{}' is NOT a tool - it is a reasoning tag. Output your answer as plain text after your <think> block.",
736 other
737 ))
738 } else {
739 Err(format!("Unknown tool: '{}'", other))
740 }
741 }
742 }
743}