feat(tui): add pluggable tool registry (#2420)

Thanks @aboimpinto.

Adds a self-describing local plugin/override tool registry, keeps explicit [tools.overrides] above auto-discovered scripts, makes plugin discovery deterministic, hardens child-process stdin/stdout behavior, and updates the local plugin examples.

Validation:
- cargo fmt --all -- --check
- git diff --check
- CARGO_TARGET_DIR=/Volumes/VIXinSSD/codewhale-target/fix-2420-rebase cargo test -p codewhale-tui tools::plugin --all-features
- CARGO_TARGET_DIR=/Volumes/VIXinSSD/codewhale-target/fix-2420-rebase cargo test -p codewhale-tui tools::registry --all-features
This commit is contained in:
Paulo Aboim Pinto
2026-05-31 12:34:54 +02:00
committed by GitHub
parent cef1632d6a
commit f488cd8e00
11 changed files with 1186 additions and 10 deletions
+53
View File
@@ -652,6 +652,59 @@ default_text_model = "deepseek-ai/deepseek-v4-pro"
# [runtime_api]
# cors_origins = ["http://localhost:5173", "http://127.0.0.1:5173"]
# ─────────────────────────────────────────────────────────────────────────────────
# Tool Overrides & Plugins ([tools])
# ─────────────────────────────────────────────────────────────────────────────────
# The `[tools]` table lets you replace any built-in tool with a custom
# implementation (script or command) or disable it entirely — without
# forking or recompiling the binary.
#
# Plugin scripts dropped in the plugin directory are auto-discovered and
# registered as model-visible tools alongside the built-in ones.
#
# Scripts receive the tool's JSON input on **stdin** and must return a
# JSON `ToolResult` (`{"content": "...", "success": true}`) on **stdout**.
#
# [tools]
# # Custom plugin directory (defaults to `~/.codewhale/tools/`)
# plugin_dir = "~/.codewhale/tools"
#
# [tools.overrides]
# # Disable a tool entirely — removes it from the model-visible catalog.
# "code_execution" = { type = "disabled" }
#
# # Replace a tool with a script. Relative paths resolve against plugin_dir.
# "exec_shell" = { type = "script", path = "audit-exec-shell.sh" }
#
# # Replace a tool with a command (binary on PATH or absolute path).
# "read_file" = { type = "command", command = "bat", args = ["--paging=never"] }
#
# # Scripts can also accept static arguments before the JSON input:
# "fetch_url" = { type = "script", path = "cached-fetch.sh", args = ["--ttl", "300"] }
# ──────────── Enterprise example: audit-logging exec_shell wrapper ──────────────
# Drop `audit-exec-shell.sh` in `~/.codewhale/tools/` and enable with:
#
# [tools.overrides]
# "exec_shell" = { type = "script", path = "audit-exec-shell.sh" }
#
# The wrapper logs every request to `~/.codewhale/audit/exec_shell.log`, then
# delegates to your own approved shell executor. Do not pipe the raw JSON
# request into `sh -s`; parse the command field and enforce your policy first.
#
# ```sh
# #!/usr/bin/env sh
# # name: exec_shell
# # description: Audit-logging wrapper for exec_shell
# # approval: required
# LOGDIR="${HOME}/.codewhale/audit"
# mkdir -p "$LOGDIR"
# LOGFILE="$LOGDIR/exec_shell.log"
# input=$(cat)
# echo "[$(date -Iseconds)] $input" >> "$LOGFILE"
# printf '%s\n' '{"content":"audit wrapper placeholder: configure an executor","success":false}'
# ```
# ─────────────────────────────────────────────────────────────────────────────────
# Requirements (admin constraints) example file
# ─────────────────────────────────────────────────────────────────────────────────