| name | maintain-local-tools |
| description | Maintain the opt-in native list_directory/read_file/web_search/http_request/fetch_text/fetch_json tools, private workspace boundary, bounded arguments/results, call budget, cancellation, and visible tool messages. Use when changing local tool settings, execution, or tool UI. |
Maintain local tools
Role and boundary
Keep tools native-owned, bounded, and opt-in. The Local route may use all six
tools; ChatGPT and OpenRouter may use only the explicitly enabled
list_directory/read_file subset. Filesystem tools remain read-only. HTTP
tools may call only natively allowlisted origins under strict method, body,
response, timeout, redirect, and header bounds. The model and WebView may
request typed tool names/arguments, but neither may choose a filesystem root,
allowlist, proxy, credential, or arbitrary network target.
Source map
src/settings.ts: AssistantToolsConfig, default-off toggles, toolCallLimitRange, defaultToolCallLimit, migration/recovery, and route snapshot.
src/main.ts: wireAssistantPreferences toggles/select; src/chat.ts: expandable tool-message rendering; src/chat-turn.ts: correlated onToolCall/onToolResult projection; src/chat-store.ts: ChatToolMessage, append/complete/toggle and persistence.
src/chat-message-view.ts: expandable request/result/status rendering; src/assistant-coordinator.ts/src/llm.ts: correlated tool callbacks and provider-history filtering.
src-tauri/src/tools/catalog.rs: one exhaustive macro catalog whose records generate the closed ToolName enum and definition match while binding enablement, local/external availability, strict argument decoding, schemas, event redaction, and blocking/async execution.
src-tauri/src/tools.rs: WorkspaceRoot, ToolState, ToolCall, ToolResult, bounded execution/budget outcomes, path containment/symlink checks, list/read limits, SearXNG search, allowlisted HTTP requests, UTF-8/JSON validation, five-second timeout, and cancellation.
src-tauri/src/llm/mod.rs and src-tauri/src/llm/provider_execution.rs: local/external loop-specific parsing and native event emission around the shared bounded tool execution contract; tests tests/chat-store.test.ts, tests/assistant-coordinator.test.ts, tests/settings.test.ts, and tests/llm.test.ts.
Cover
- Keep
list_directory (immediate, sorted, at most 100 entries), read_file (relative bounded UTF-8 text, line/byte limits, regular-file/size checks), web_search (bounded query/results through one explicitly configured native SearXNG endpoint), http_request (allowlisted API methods and bounded UTF-8 responses), fetch_text (GET-only bounded UTF-8), and fetch_json (GET-only bounded JSON). Reject absolute paths, traversal, symlinks escaping the selected root, binary/invalid UTF-8, invalid limits, directories-as-files, unsafe search endpoints, arbitrary search URLs, non-allowlisted HTTP origins, remote plain HTTP, credentials/fragments, redirects, oversized request/response data, invalid JSON, and sensitive response headers.
- Resolve one canonical app-data
workspace by default, or one explicitly configured absolute workspace for development/package verification. Reject symlinked roots, enforce native five-second timeout and cancellation, and return typed structured errors without leaking host paths or secrets.
- Keep tools disabled by default, independently toggleable, and capped at 1–5
calls per turn. Local exposes all enabled tools; external providers expose
only enabled tools whose catalog definition explicitly allows an external
provider. HTTP uses the built-in approved HTTPS origins plus any valid origins
supplied through native launch configuration
VOICE_LAYER_HTTP_ALLOWED_ORIGINS; remote plain HTTP remains unavailable.
Disable further calls after the budget and continue with an answer instead
of looping.
- Persist and render tool requests/results as expandable UI records, but exclude tool messages from provider history and do not speak hidden/tool text. Preserve request correlation and local privacy.
- Inspect current native tests and settings schema before adding any operation. A generic shell/filesystem endpoint is out of scope.
Workspace persistence incident
The native tools once passed the real repository only through the
VOICE_LAYER_WORKSPACE launch environment. The first ChatGPT request could
therefore list real files, while a later normal relaunch silently fell back to
the empty app-data workspace and returned entries: []. A temporary file in
the private workspace is not sufficient verification; it hides this launch
lifecycle bug.
WorkspaceRoot::from_app now validates an explicit absolute, non-symlink
VOICE_LAYER_WORKSPACE, persists it at <app_data_dir>/workspace-root with a
temporary-file rename, and reloads that path on later launches. If neither
source exists, the app-owned private workspace remains the safe empty default.
just open supplies the repository path for the first development/package
launch, after which ordinary launches retain it. Relative tool arguments are
still resolved and contained within the selected canonical root; the WebView
cannot choose or mutate the root.
For every workspace/tool change, perform this packaged regression check:
- Build the exact release bundle with
just bundle.
- Launch once with
VOICE_LAYER_WORKSPACE set to a real test workspace, then
relaunch the exact bundle without that environment variable.
- In a fresh ChatGPT session with External API Tool Calling enabled, call
list_directory with { "path": ".", "filetype": "file" } and require
real entries.
- Call
read_file on a known text file from that same workspace and require
an ok result.
- Confirm both expandable tool records show success and that the assistant
reports the returned data. Do not accept a fixture-only result or a
pre-relaunch environment-only result.
Validate and accept
- Run
bun test tests/chat-turn.test.ts tests/chat-store.test.ts tests/assistant-coordinator.test.ts tests/settings.test.ts tests/llm.test.ts and focused Rust tools/LLM tests. The catalog parity tests must prove every definition has identity, schema, decode, and dispatch coverage and that the external subset remains exactly list_directory/read_file. Web search and HTTP tests must use in-process HTTP fixtures; they must not depend on a live provider or external API.
- Run
git diff --check against the actual files changed; run just check when the change crosses TypeScript/Rust/build boundaries. Exercise toggle, limit, malformed/path/binary/oversize/timeout/cancel, and expandable-message flows.
- Treat real app-data containment and provider privacy as native/package
acceptance; use
$test-voice-layer-app for visible UI behavior and
$maintain-assistant-providers for external-provider tool boundaries.
Relationships
Coordinate with $maintain-settings, $maintain-chat, $maintain-sessions, $maintain-assistant-orchestration, and $maintain-debug (diagnostic references only).