diff --git a/crates/tui/src/prompts.rs b/crates/tui/src/prompts.rs index 561c05f221..c17d033b6c 100644 --- a/crates/tui/src/prompts.rs +++ b/crates/tui/src/prompts.rs @@ -112,7 +112,8 @@ Only output English for:\n\ - Technical terms that lack a standard translation in {target_language}\n\ - Code blocks the user explicitly requests in English\n\n\ This is a hard display requirement: the user does not read English, \ -so any English prose in your response will block their decision-making." +so any English prose in your response will block their decision-making. \ +This overrides the ## Language mirroring rule for this session." ) } @@ -455,6 +456,54 @@ pub fn set_base_prompt_override(s: String) -> Result<(), String> { set_prompt_override(&BASE_PROMPT_OVERRIDE, s) } +/// Replace the Simplified Chinese locale preamble. First call wins; later +/// calls return the rejected string. Set before spawning any engine. +pub fn set_locale_preamble_zh_hans_override(s: String) -> Result<(), String> { + set_prompt_override(&LOCALE_PREAMBLE_ZH_HANS_OVERRIDE, s) +} + +/// Replace the Japanese locale preamble. First call wins; later calls return +/// the rejected string. Set before spawning any engine. +pub fn set_locale_preamble_ja_override(s: String) -> Result<(), String> { + set_prompt_override(&LOCALE_PREAMBLE_JA_OVERRIDE, s) +} + +/// Replace the Brazilian Portuguese locale preamble. First call wins; later +/// calls return the rejected string. Set before spawning any engine. +pub fn set_locale_preamble_pt_br_override(s: String) -> Result<(), String> { + set_prompt_override(&LOCALE_PREAMBLE_PT_BR_OVERRIDE, s) +} + +/// Replace the Vietnamese locale preamble. First call wins; later calls +/// return the rejected string. Set before spawning any engine. +pub fn set_locale_preamble_vi_override(s: String) -> Result<(), String> { + set_prompt_override(&LOCALE_PREAMBLE_VI_OVERRIDE, s) +} + +/// Replace the Simplified Chinese locale closer. First call wins; later calls +/// return the rejected string. Set before spawning any engine. +pub fn set_locale_closer_zh_hans_override(s: String) -> Result<(), String> { + set_prompt_override(&LOCALE_CLOSER_ZH_HANS_OVERRIDE, s) +} + +/// Replace the Japanese locale closer. First call wins; later calls return +/// the rejected string. Set before spawning any engine. +pub fn set_locale_closer_ja_override(s: String) -> Result<(), String> { + set_prompt_override(&LOCALE_CLOSER_JA_OVERRIDE, s) +} + +/// Replace the Brazilian Portuguese locale closer. First call wins; later +/// calls return the rejected string. Set before spawning any engine. +pub fn set_locale_closer_pt_br_override(s: String) -> Result<(), String> { + set_prompt_override(&LOCALE_CLOSER_PT_BR_OVERRIDE, s) +} + +/// Replace the Vietnamese locale closer. First call wins; later calls return +/// the rejected string. Set before spawning any engine. +pub fn set_locale_closer_vi_override(s: String) -> Result<(), String> { + set_prompt_override(&LOCALE_CLOSER_VI_OVERRIDE, s) +} + // ── Config-directory prompt overrides (issue #3638) ── // Bridge the embedder override hooks above to a user-facing source: an // optional file in the Codewhale config directory. This lets users repurpose @@ -705,6 +754,14 @@ pub(crate) fn effective_authority_recap() -> &'static str { effective_prompt_override(&AUTHORITY_RECAP_OVERRIDE, AUTHORITY_RECAP) } +/// Whether the authority-recap trailer is appended after WorldState. When an +/// embedder composer owns the static prompt prefix, the bundled +/// `### Whose word wins` section the recap points at no longer exists, so +/// appending the recap would leave a dangling cross-reference. +fn authority_recap_trailer_appended(composer_installed: bool) -> bool { + !composer_installed +} + /// Optional locale-native reinforcement preamble prepended to the system /// prompt when the user's UI locale is non-English. /// @@ -1265,11 +1322,16 @@ pub fn system_prompt_for_mode_with_context_skills_session_and_approval( .to_system_blocks(); // Trailers keep recency bias after WorldState: authority, then locale. - blocks.push(SystemBlock { - block_type: "text".to_string(), - text: effective_authority_recap().trim().to_string(), - cache_control: None, - }); + // When an embedder composer owns the static prefix, the bundled + // `### Whose word wins` section the recap points at no longer exists, + // so appending the recap would leave a dangling cross-reference. + if authority_recap_trailer_appended(static_prompt_composer_installed()) { + blocks.push(SystemBlock { + block_type: "text".to_string(), + text: effective_authority_recap().trim().to_string(), + cache_control: None, + }); + } if let Some(closer) = locale_reinforcement_closer(session_context.locale_tag) { blocks.push(SystemBlock { block_type: "text".to_string(), @@ -1365,6 +1427,15 @@ mod tests { /// agent prompt's own discussion of the convention). const HANDOFF_BLOCK_MARKER: &str = "left a relay artifact at `.codewhale/handoff.md`"; + /// The recap points at the bundled `### Whose word wins` section; an + /// embedder composer that owns the static prefix retires that section, + /// so the trailer must be skipped instead of dangling in the blocks. + #[test] + fn authority_recap_trailer_skipped_when_static_composer_owns_prefix() { + assert!(authority_recap_trailer_appended(false)); + assert!(!authority_recap_trailer_appended(true)); + } + // Config-directory prompt override resolution (#3638). These exercise the // pure file resolver only; the global install path is intentionally not // unit-tested here because `set_base_prompt_override` writes a process-wide diff --git a/crates/tui/src/prompts/text.rs b/crates/tui/src/prompts/text.rs index a4b5070b99..5a6f72426c 100644 --- a/crates/tui/src/prompts/text.rs +++ b/crates/tui/src/prompts/text.rs @@ -185,7 +185,7 @@ improves throughput. Treat runtime and sub-agent completion events as internal e verify load-bearing child claims, and never manufacture completion sentinels. Prefer notify/join tools to polling. -For substantial work, emit session-persistent `repl` blocks: ```repl runs; use ```python (or prose) to illustrate without running. retain source/transcript +For substantial work, emit session-persistent `repl` blocks: ` ```repl ` runs; use ` ```python ` (or prose) to illustrate without running. Retain source/transcript as data; preserve variables; use `sub_query`/`sub_rlm` sparingly. Use `workflow`, `agent`, goals, `harness`; retain evidence-backed lessons. @@ -350,6 +350,5 @@ pub const SUBAGENT_SCOUT_OUTPUT_FORMAT: &str = r#"## Output contract (scout) End with these exact Markdown headings: `### SUMMARY` and `### EVIDENCE`. Keep each section compact. Cite only files you actually inspected and distinguish child reports from evidence you verified. Write `None.` where -a section has no entries. If blocked, name the missing fact. Then stop -with ``. +a section has no entries. If blocked, name the missing fact. Then stop. "#; diff --git a/crates/tui/src/tools/apply_patch.rs b/crates/tui/src/tools/apply_patch.rs index be78d8fa7e..3597467f6c 100644 --- a/crates/tui/src/tools/apply_patch.rs +++ b/crates/tui/src/tools/apply_patch.rs @@ -317,7 +317,7 @@ impl ToolSpec for ApplyPatchTool { } fn description(&self) -> &'static str { - "Apply a unified-diff patch (multi-hunk, multi-file). Use this instead of `git apply`, `patch`, or repeated `edit_file` calls in `Bash` — single transactional change with fuzzy matching and a rendered diff." + "Apply a unified-diff patch (multi-hunk, multi-file) or full-file replacements via `replace`. Use this instead of `git apply`, `patch`, or repeated `edit_file` calls in `Bash` — single transactional change with fuzzy matching and a rendered diff." } fn input_schema(&self) -> Value { diff --git a/crates/tui/src/tools/canonical_action.rs b/crates/tui/src/tools/canonical_action.rs index 25086b3340..42fa58854c 100644 --- a/crates/tui/src/tools/canonical_action.rs +++ b/crates/tui/src/tools/canonical_action.rs @@ -235,6 +235,14 @@ mod tests { .with_test_runner_tool() .with_web_tools() .with_patch_tools() + .with_verify_tool(None, "test-model".to_string()) + .with_registry_mcp_sync_tool() + .with_runtime_mcp_tool(std::sync::Arc::new(tokio::sync::Mutex::new( + crate::mcp::McpPool::new(crate::mcp::McpConfig::default()), + ))) + .with_registry_mcp_start_tool(std::sync::Arc::new(tokio::sync::Mutex::new( + crate::mcp::McpPool::new(crate::mcp::McpConfig::default()), + ))) .build(ToolContext::new(tmp.path().to_path_buf())); for tool in registry.to_api_tools() { diff --git a/crates/tui/src/tools/fetch_url.rs b/crates/tui/src/tools/fetch_url.rs index 84594bff4f..fd1e480c63 100644 --- a/crates/tui/src/tools/fetch_url.rs +++ b/crates/tui/src/tools/fetch_url.rs @@ -97,7 +97,7 @@ impl ToolSpec for FetchUrlTool { } fn description(&self) -> &'static str { - "Fetch a known URL directly (HTTP GET) and return its content with a session-scoped citation ref_id. Use this instead of `curl` in `exec_shell` — sandboxed, network-policy aware, and properly decoded. Plain-text endpoints (`.md`, `.txt`, `.json`, `.yaml`, `raw.githubusercontent.com`, public APIs) prefer this over the browser/automation stack. For unknown queries, use `web_search` first. If a login or authorization wall is returned, treat the wall as the result; do not claim the protected page was read." + "Fetch a known URL directly (HTTP GET) and return its content with a session-scoped citation ref_id. Use this instead of `curl` in `Bash` — sandboxed, network-policy aware, and properly decoded. Plain-text endpoints (`.md`, `.txt`, `.json`, `.yaml`, `raw.githubusercontent.com`, public APIs) prefer this over the browser/automation stack. For unknown queries, use the Web tool with action=search first. If a login or authorization wall is returned, treat the wall as the result; do not claim the protected page was read." } fn input_schema(&self) -> Value { diff --git a/crates/tui/src/tools/file.rs b/crates/tui/src/tools/file.rs index 9d5f545ff2..bea362031a 100644 --- a/crates/tui/src/tools/file.rs +++ b/crates/tui/src/tools/file.rs @@ -1028,7 +1028,7 @@ impl ToolSpec for EditFileTool { } fn description(&self) -> &'static str { - "Replace text in a single file via exact search/replace after the file has been read with File `read` in this session. Use this instead of `sed -i` in `Bash` for one unambiguous in-place edit. `search` must match exactly one location by default; when no exact match is found the tool retries with leading-whitespace-tolerant fuzzy matching automatically. Returns a compact unified diff, not the full file. For structural, multi-block, or cross-file changes, use File `patch` or `write` instead." + "Replace text in a single file via exact search/replace after the file has been read with File `read` in this session. Use this instead of `sed -i` in `Bash` for one unambiguous in-place edit. `search` must match exactly one location by default; when no exact match is found the tool retries with leading-whitespace-tolerant fuzzy matching plus punctuation/line-ending normalization fallbacks automatically. Returns a compact unified diff, not the full file. For structural, multi-block, or cross-file changes, use File `patch` or `write` instead." } fn input_schema(&self) -> Value { diff --git a/crates/tui/src/tools/file_search.rs b/crates/tui/src/tools/file_search.rs index d83596a917..40b8432b5b 100644 --- a/crates/tui/src/tools/file_search.rs +++ b/crates/tui/src/tools/file_search.rs @@ -40,7 +40,7 @@ impl ToolSpec for FileSearchTool { } fn description(&self) -> &'static str { - "Find files by name using fuzzy matching with score-based ranking. Use this instead of `find -name` or `fd` in `exec_shell` for filename search. Pass `extensions` to filter by suffix." + "Find files by name using fuzzy matching with score-based ranking. Use this instead of `find -name` or `fd` in `Bash` for filename search. Respects .gitignore; by default skips target/**, node_modules/**, lock files, and similar generated artifacts unless `exclude` overrides them. `limit` accepts at most 200. Pass `extensions` to filter by suffix." } fn input_schema(&self) -> Value { diff --git a/crates/tui/src/tools/fim.rs b/crates/tui/src/tools/fim.rs index 1c43164553..ac4ad38813 100644 --- a/crates/tui/src/tools/fim.rs +++ b/crates/tui/src/tools/fim.rs @@ -70,7 +70,9 @@ impl ToolSpec for FimEditTool { prefix_anchor (text that appears before the section to replace), and \ suffix_anchor (text that appears after the section to replace). The tool \ calls the active route's fill-in-the-middle completion endpoint to \ - generate replacement content." + generate replacement content; this requires the active provider to \ + expose a fill-in-the-middle completions endpoint, and the call fails \ + otherwise." } fn input_schema(&self) -> Value { diff --git a/crates/tui/src/tools/git_tool.rs b/crates/tui/src/tools/git_tool.rs index 975034e1fd..3f44d06303 100644 --- a/crates/tui/src/tools/git_tool.rs +++ b/crates/tui/src/tools/git_tool.rs @@ -82,7 +82,7 @@ impl ToolSpec for GitTool { }, "unified": { "type": "integer", - "description": "Number of context lines for diff or show output" + "description": "Number of context lines for diff or show output (default 3, max 50)" }, "max_count": { "type": "integer", diff --git a/crates/tui/src/tools/github/mod.rs b/crates/tui/src/tools/github/mod.rs index b83c02750e..0983f888ca 100644 --- a/crates/tui/src/tools/github/mod.rs +++ b/crates/tui/src/tools/github/mod.rs @@ -138,10 +138,10 @@ impl ToolSpec for GithubTool { "Post an evidence-backed GitHub issue/PR comment with gh. Requires approval. Use blocker comments for partial work; do not claim closure without evidence." } Some("close_issue") => { - "Close a GitHub issue only when structured acceptance evidence is present and approved. For pull requests use github_close_pr; do not call PRs issues in user-facing output. Never close merely because the agent is stopping." + "Close a GitHub issue only when structured acceptance evidence is present and approved. Rejected when the worktree is dirty unless allow_dirty=true. For pull requests use github_close_pr; do not call PRs issues in user-facing output. Never close merely because the agent is stopping." } Some("close_pr") => { - "Close a GitHub pull request only when structured acceptance evidence is present and approved. Use this for PRs instead of github_close_issue so the UI, audit trail, and comments keep PR wording clear." + "Close a GitHub pull request only when structured acceptance evidence is present and approved. Rejected when the worktree is dirty unless allow_dirty=true. Use this for PRs instead of github_close_issue so the UI, audit trail, and comments keep PR wording clear." } _ if self.read_only => { "Read GitHub issue/PR context using gh. Actions: \"issue_context\" and \"pr_context\"; bodies/comments/labels/state are summarized and large bodies become task artifacts when a durable task is active." diff --git a/crates/tui/src/tools/goal.rs b/crates/tui/src/tools/goal.rs index 40980b493d..c48a68bfae 100644 --- a/crates/tui/src/tools/goal.rs +++ b/crates/tui/src/tools/goal.rs @@ -218,7 +218,7 @@ impl GoalState { ) -> Result<(), &'static str> { if self.objective.is_some() && self.status != Some(GoalStatus::Complete) { return Err( - "An unfinished goal already exists. Complete or clear it before creating another.", + "An unfinished goal already exists. Complete it before creating another (blocked/paused goals are cleared by the user/host).", ); } self.objective = Some(objective); @@ -657,7 +657,7 @@ impl ToolSpec for CreateGoalTool { } fn description(&self) -> &'static str { - "Create the current runtime goal. Use this only when the user explicitly asks to pursue a persistent objective and no unfinished goal exists; complete or clear an unfinished goal before creating another." + "Create the current runtime goal. Use this only when the user explicitly asks to pursue a persistent objective and no unfinished goal exists; complete it before creating another (blocked/paused goals are cleared by the user/host). Root agent only; sub-agents inspect with get_goal." } fn input_schema(&self) -> Value { @@ -777,7 +777,7 @@ impl ToolSpec for UpdateGoalTool { } fn description(&self) -> &'static str { - "Update the runtime goal completion gate. Critical verification may seal one immutable completion contract. Advisory review is append-only context and never completes, blocks, or pauses the goal. Mark blocked when progress requires user input." + "Update the runtime goal completion gate. Critical verification may seal one immutable completion contract. Advisory review is append-only context and never completes, blocks, or pauses the goal. Mark blocked when progress requires user input. Root agent only; sub-agents inspect with get_goal." } fn input_schema(&self) -> Value { @@ -818,7 +818,7 @@ impl ToolSpec for UpdateGoalTool { "gaps": { "type": "array", "items": {"type": "string"}, - "description": "Concrete remaining gaps. Required for critical not_achieved reviews; order and duplicate wording do not affect the stall fingerprint." + "description": "Concrete remaining gaps. Required for critical not_achieved reviews; order and duplicate wording do not affect the stall fingerprint. Three identical critical gap sets auto-pause the goal (no_progress)." } }, "required": ["status", "check", "summary"], diff --git a/crates/tui/src/tools/handle.rs b/crates/tui/src/tools/handle.rs index 829d8b25ba..1b050e7819 100644 --- a/crates/tui/src/tools/handle.rs +++ b/crates/tui/src/tools/handle.rs @@ -191,8 +191,9 @@ impl ToolSpec for HandleReadTool { retrieve_tool_result for spilled tool results/artifacts and \ File action=\"read\" for workspace files. Provide \ exactly one projection: `slice` for char/line slices, `range` for \ - one-based line ranges, `count` for metadata counts, or `jsonpath` \ - for a small JSON-path projection. This retrieves from the handle's \ + one-based line ranges, `count` for metadata counts, `jsonpath` \ + for a small JSON-path projection, or `introspect` for the \ + handle's supported projections, size hints, and examples. This retrieves from the handle's \ backing environment instead of asking the parent transcript to hold \ the full payload." } diff --git a/crates/tui/src/tools/image_ocr.rs b/crates/tui/src/tools/image_ocr.rs index 14e7f8fe7f..8ef42471c6 100644 --- a/crates/tui/src/tools/image_ocr.rs +++ b/crates/tui/src/tools/image_ocr.rs @@ -28,7 +28,7 @@ impl ToolSpec for ImageOcrTool { } fn description(&self) -> &'static str { - "Extract text from an image (PNG, JPEG, or TIFF) via local OCR. On macOS this uses the built-in Vision framework; otherwise it uses local tesseract when available. Use this for screenshots, scanned receipts/whiteboards, image-only PDFs, or any visual that contains text the model needs to read. Returns the extracted text inline; no file is written." + "Extract text from an image (PNG, JPEG, or TIFF) via local OCR. On macOS this uses the built-in Vision framework; otherwise it uses local tesseract when available. Use this for screenshots, scanned receipts/whiteboards, or any visual that contains text the model needs to read. Returns the extracted text inline; no file is written." } fn input_schema(&self) -> Value { diff --git a/crates/tui/src/tools/mcp_registry.rs b/crates/tui/src/tools/mcp_registry.rs index dc43825844..a09f63c068 100644 --- a/crates/tui/src/tools/mcp_registry.rs +++ b/crates/tui/src/tools/mcp_registry.rs @@ -827,7 +827,7 @@ impl ToolSpec for McpSyncRegistry { plausibly covers the task's core specialized capability, call \ start_registry_mcp_server with its exact name and inspect its tools \ before choosing a local alternative; do not run its package command \ - through exec_shell." + through Bash." } fn input_schema(&self) -> Value { diff --git a/crates/tui/src/tools/plan.rs b/crates/tui/src/tools/plan.rs index 8c8f365e84..8799d7e027 100644 --- a/crates/tui/src/tools/plan.rs +++ b/crates/tui/src/tools/plan.rs @@ -402,7 +402,7 @@ impl ToolSpec for UpdatePlanTool { } fn description(&self) -> &'static str { - "Legacy compatibility tool for loading older Plan artifacts. New work uses the canonical work_update list and a normal Plan-mode response." + "Legacy compatibility tool for loading older Plan artifacts. New work uses the canonical todo_write list and a normal Plan-mode response." } fn model_visible(&self) -> bool { @@ -465,7 +465,7 @@ impl ToolSpec for UpdatePlanTool { }, "plan": { "type": "array", - "description": "Legacy replay field; new work must use work_update", + "description": "Legacy replay field; new work must use todo_write", "deprecated": true, "items": { "type": "object" } } @@ -601,7 +601,7 @@ mod tests { assert!(!tool.model_visible()); assert!(description.contains("Legacy compatibility")); - assert!(description.contains("canonical work_update list")); + assert!(description.contains("canonical todo_write list")); } #[tokio::test] diff --git a/crates/tui/src/tools/remember.rs b/crates/tui/src/tools/remember.rs index 6933b78167..91821557da 100644 --- a/crates/tui/src/tools/remember.rs +++ b/crates/tui/src/tools/remember.rs @@ -72,7 +72,7 @@ impl ToolSpec for RememberTool { "scope": { "type": "string", "enum": ["global", "workspace"], - "description": "Native backend scope; defaults to global." + "description": "Native backend scope; defaults to global. workspace requires a git repository with an origin." } }, "required": [] diff --git a/crates/tui/src/tools/runtime_mcp.rs b/crates/tui/src/tools/runtime_mcp.rs index d602021f4c..926891caee 100644 --- a/crates/tui/src/tools/runtime_mcp.rs +++ b/crates/tui/src/tools/runtime_mcp.rs @@ -218,6 +218,7 @@ impl ToolSpec for StartRuntimeMcpServer { (like 'https://...'), call this tool immediately to start the server \ and register its tools. Do NOT suggest editing config files. \ Accepts a local command (stdio) or a remote URL (HTTP/SSE). \ + Local commands must be a known runtime (npx/npm/pnpm/yarn/bunx/bun/node/python/python3/uvx/uv/deno/ruby/cargo); shell wrappers are rejected. \ After the server starts, the response lists each tool's callable name. \ You MUST copy those exact names when calling the tools. \ Do NOT construct or guess tool names yourself." diff --git a/crates/tui/src/tools/search.rs b/crates/tui/src/tools/search.rs index cc53d1624f..2099d86a30 100644 --- a/crates/tui/src/tools/search.rs +++ b/crates/tui/src/tools/search.rs @@ -56,7 +56,7 @@ impl ToolSpec for GrepFilesTool { } fn description(&self) -> &'static str { - "Search for a regex pattern in workspace files. Use this instead of `grep -r`, `rg`, or `find ... -exec grep` in `exec_shell` — pure-Rust, faster, and skips common non-code directories (node_modules, .git, target, ...) by default. Returns matching lines with context (default: 2 lines before/after each match)." + "Search for a regex pattern in workspace files. Use this instead of `grep -r`, `rg`, or `find ... -exec grep` in `Bash` — pure-Rust, faster, and skips common non-code directories (node_modules, .git, target, ...) by default; it does not apply .gitignore (built-in default exclusions only). Returns matching lines with context (default: 2 lines before/after each match)." } fn input_schema(&self) -> Value { @@ -83,7 +83,7 @@ impl ToolSpec for GrepFilesTool { }, "context_lines": { "type": "integer", - "description": "Number of context lines before and after each match (default: 2)" + "description": "Number of context lines before and after each match (default: 2). context_lines=1 returns single strings instead of arrays." }, "case_insensitive": { "type": "boolean", diff --git a/crates/tui/src/tools/send_later.rs b/crates/tui/src/tools/send_later.rs index fed7b97a07..53a93248ad 100644 --- a/crates/tui/src/tools/send_later.rs +++ b/crates/tui/src/tools/send_later.rs @@ -104,6 +104,7 @@ Actions: \"schedule\" (create a pending trigger; requires approval), \ \"list\" (recent triggers), \"read\" (one trigger by trigger_id), \ \"cancel\" (cancel a pending trigger before it fires; requires approval). \ Use delay_minutes or fire_at (ISO 8601 UTC) — not both. \ +message is required; fire_at must be strictly in the future. \ Returns trigger_id and resolved fire_at." } } @@ -185,7 +186,7 @@ Returns trigger_id and resolved fire_at." json!({ "type": "object", "properties": properties, - "required": ["action"] + "required": ["action", "message"] }) } diff --git a/crates/tui/src/tools/shell.rs b/crates/tui/src/tools/shell.rs index ba315cfaf8..39d2dce454 100644 --- a/crates/tui/src/tools/shell.rs +++ b/crates/tui/src/tools/shell.rs @@ -3405,7 +3405,7 @@ impl ToolSpec for BashTool { if self.read_only { "Inspect the workspace with the bounded read-only command subset. Commands run directly as argv, never through a shell; only action=run plus command, cwd, and timeout_ms are accepted." } else { - "Execute a shell command in the workspace. Action \"run\" (default) executes a command; \"wait\" polls a background task; \"interact\" sends stdin to a background task; \"cancel\" kills a background task. Foreground mode is for bounded commands; use background=true for work expected to take >5 seconds. Commands run via the user's login shell ($SHELL); when that shell is zsh, a bare word starting with `=` undergoes `=command` PATH expansion (e.g. `echo ===` fails) — quote such arguments, e.g. `echo '==='`." + "Execute a shell command in the workspace. Action \"run\" (default) executes a command; \"wait\" polls a background task; \"interact\" sends stdin to a background task; \"cancel\" kills a background task. Foreground mode is for bounded commands; use background=true for work expected to take >5 seconds. Output is truncated per stream (~30KB: head/tail kept, middle summarized; see metadata flags). Commands run via the user's login shell ($SHELL); when that shell is zsh, a bare word starting with `=` undergoes `=command` PATH expansion (e.g. `echo ===` fails) — quote such arguments, e.g. `echo '==='`." } } @@ -3427,7 +3427,7 @@ impl ToolSpec for BashTool { }, "timeout_ms": { "type": "integer", - "description": "Timeout in milliseconds. The default depends on the action: action=run 120000 (capped at 600000), action=wait 30000, action=interact 1000. For action=wait, `timeout_secs` (seconds) and `timeout` (milliseconds) are accepted aliases." + "description": "Timeout in milliseconds. The default depends on the action: action=run 120000 (capped at 600000), action=wait 30000, action=interact 1000. For action=wait, `timeout_secs` (seconds) and `timeout` (milliseconds) are accepted aliases. Applies to the foreground wait; background=true tasks are not bounded by it — stop them with action=cancel." }, "background": { "type": "boolean", diff --git a/crates/tui/src/tools/subagent/mod.rs b/crates/tui/src/tools/subagent/mod.rs index ec16708819..a54f9afdc0 100644 --- a/crates/tui/src/tools/subagent/mod.rs +++ b/crates/tui/src/tools/subagent/mod.rs @@ -7132,7 +7132,7 @@ impl ToolSpec for AgentTool { "For parallel write work use worktree=true so children do not collide in the parent checkout. ", "Add a Fleet profile, role, or explicit limits only when they improve the task. ", "Coordinate through this same tool: action=message queues a note without waking the child; action=followup delivers queued notes and wakes a running child for its next user-provenance turn; action=interrupt stops the current child turn while preserving its checkpoint; action=wait blocks without changing child state, and until=\"all\" joins a whole fan-out in one call. ", - "The narrow agents/list, agents/message, agents/followup, agents/interrupt, and agents/wait tools expose the same semantics directly; there is no second transport. ", + "The narrow agents/list, agents/message, agents/followup, agents/interrupt, agents/coordinate, and agents/wait tools expose the same semantics directly; there is no second transport. ", "In Operate, background workers are the default for independent or long work; a write-capable root start defaults write scope to the parent workspace unless narrowed with write_roots, exact_files, or coordination_contracts; arbitrary shell remains gated. ", "Legacy action=status|peek|cancel remain for compatibility." ) @@ -7158,7 +7158,7 @@ impl ToolSpec for AgentTool { }, "timeout_secs": { "type": "integer", - "minimum": 5, + "minimum": 1, "maximum": 120, "description": "For action=wait: maximum seconds to block (default 30). Prefer ending the turn and staying reachable — results arrive automatically as sentinels — only wait when you must join before continuing." }, @@ -7197,6 +7197,16 @@ impl ToolSpec for AgentTool { "enum": FLEET_ROLE_SCHEMA_VALUES, "description": SUBAGENT_TYPE_DESCRIPTION }, + "allowed_tools": { + "type": "array", + "items": { "type": "string" }, + "description": "For type=custom: exact tool names this child may call — the child gets exactly the tools listed (advanced)." + }, + "disallowed_tools": { + "type": "array", + "items": { "type": "string" }, + "description": "Tool names removed from this child's toolset (deny list) (advanced)." + }, "profile": { "type": "string", "description": "Optional Fleet roster member to run this child as (e.g. reviewer, scout, builder, verifier, synthesizer, manager, or a custom member from project .codewhale/agents/, personal $CODEWHALE_HOME/agents/, or [fleet.profiles] config). The member supplies role posture, model routing, instruction overlay, and delegation bounds. Named profiles bind 1:1 to their configured route — 'model' is not accepted when a named profile is set. Only 'general' (no profile) permits the model option. See /fleet. For fast exploration use the scout role." @@ -13901,8 +13911,7 @@ const EXPLORE_AGENT_INTRO: &str = concat!( "Use `File` for bounded reads and `Bash` action `run` for the advertised direct-argv evidence subset: navigation/rg, safe Git reads (for example `git log -n 5`), and read-only GitHub views such as `gh issue view`. Builds, tests, writes, unknown flags, and shell control actions are unavailable.\n", "Use your private `todo_write` list as editable working notes when useful; it is agent-owned state, not permission to write project files. Those tool calls remain in the complete transcript artifact returned to the parent.\n", "Honor QUESTION, SCOPE, ALREADY_KNOWN, and STOP_CONDITION. Do not repeat ALREADY_KNOWN work unless evidence contradicts it; do not broaden once QUESTION is answered.\n", - "Your value is compressed reconnaissance: cite `path:line-range` for each finding and stop once evidence is sufficient. Return partial findings if the next step would be speculative or duplicative.\n", - "CHANGES will almost always be \"None.\" for a scout.\n\n" + "Your value is compressed reconnaissance: cite `path:line-range` for each finding and stop once evidence is sufficient. Return partial findings if the next step would be speculative or duplicative.\n\n" ); const PLAN_AGENT_INTRO: &str = concat!( diff --git a/crates/tui/src/tools/tasks.rs b/crates/tui/src/tools/tasks.rs index 1286d3288a..785ca97ce0 100644 --- a/crates/tui/src/tools/tasks.rs +++ b/crates/tui/src/tools/tasks.rs @@ -174,7 +174,7 @@ impl ToolSpec for TasksTool { "Cancel a queued or running durable task through TaskManager. Requires approval because it changes work state." } Some("gate_run") => { - "Run an approved verification gate command and return structured evidence. When inside a durable task, the gate result and log artifact are attached to that task." + "Run an approved verification gate command and return structured evidence. When inside a durable task, the gate result and log artifact are attached to that task. Dangerous commands are BLOCKED; default timeout 120s." } Some("pr_attempt_record") => { "Capture current git diff as a durable PR work attempt with patch artifact, changed files, and verification notes." @@ -190,7 +190,7 @@ impl ToolSpec for TasksTool { "Inspect durable tasks and their PR attempts. Actions: \"list\", \"read\", \"pr_attempt_list\", \"pr_attempt_read\"." } _ => { - "Manage durable background tasks through TaskManager. Durable tasks are restart-aware executable work, distinct from sub-agents. Actions: \"create\" (enqueue; approval), \"list\", \"read\", \"cancel\" (approval), \"gate_run\" (run an approved verification gate command and return structured evidence; approval), \"pr_attempt_record\", \"pr_attempt_list\", \"pr_attempt_read\", \"pr_attempt_preflight\". Use task_shell_start for long-running shell work." + "Manage durable background tasks through TaskManager. Durable tasks are restart-aware executable work, distinct from sub-agents. Actions: \"create\" (enqueue; approval), \"list\", \"read\", \"cancel\" (approval), \"gate_run\" (run an approved verification gate command and return structured evidence; approval), \"pr_attempt_record\" (approval), \"pr_attempt_list\", \"pr_attempt_read\", \"pr_attempt_preflight\" (approval). Use task_shell_start for long-running shell work." } } } @@ -861,7 +861,7 @@ impl ToolSpec for TaskShellStartTool { "properties": { "command": { "type": "string" }, "cwd": { "type": "string", "description": "Optional working directory within the workspace." }, - "timeout_ms": { "type": "integer", "minimum": 1000, "maximum": 600000 }, + "timeout_ms": { "type": "integer", "minimum": 1000, "maximum": 600000, "description": "Applies to the foreground wait; the background task itself is not bounded by it — stop it with action=cancel." }, "stdin": { "type": "string" }, "tty": { "type": "boolean" } }, diff --git a/crates/tui/src/tools/terminal_session.rs b/crates/tui/src/tools/terminal_session.rs index 658b59ef0b..71c4f559f8 100644 --- a/crates/tui/src/tools/terminal_session.rs +++ b/crates/tui/src/tools/terminal_session.rs @@ -636,7 +636,7 @@ pub struct TerminalRunTool; impl ToolSpec for TerminalRunTool { terminal_tool_common!( "terminal/run", - "Run a command in a persistent PTY shell session. cd, exports, shell functions, and activated environments persist across calls in this process. Identity and a non-secret last-known summary persist across restarts; prior shells are surfaced as stale/lost and are never reattached." + "Run a command in a persistent PTY shell session. cd, exports, shell functions, and activated environments persist across calls in this process. Identity and a non-secret last-known summary persist across restarts; prior shells are surfaced as stale/lost and are never reattached. On timeout the wait is abandoned but the command keeps running in the session (use terminal/wait or cancel). (Unix only)" ); fn input_schema(&self) -> serde_json::Value { json!({"type":"object","properties":{"command":{"type":"string"},"session":{"type":"string","default":"term-1"},"timeout_secs":{"type":"integer","default":120}},"required":["command"]}) @@ -683,7 +683,7 @@ pub struct TerminalSendTool; impl ToolSpec for TerminalSendTool { terminal_tool_common!( "terminal/send", - "Send raw input to a live persistent terminal session. Use a literal ETX control byte to interrupt an interactive process. A prior-process shell is reported as stale/lost rather than reattached." + "Send raw input to a live persistent terminal session. Use a literal ETX control byte to interrupt an interactive process. A prior-process shell is reported as stale/lost rather than reattached. (Unix only)" ); fn input_schema(&self) -> serde_json::Value { json!({"type":"object","properties":{"session":{"type":"string"},"text":{"type":"string"},"wait_ms":{"type":"integer","default":250}},"required":["session","text"]}) @@ -725,7 +725,7 @@ pub struct TerminalWaitTool; impl ToolSpec for TerminalWaitTool { terminal_tool_common!( "terminal/wait", - "Wait for the current foreground command in a live persistent terminal session and return buffered output. A prior-process shell is reported as stale/lost rather than reattached." + "Wait for the current foreground command in a live persistent terminal session and return buffered output. A prior-process shell is reported as stale/lost rather than reattached. Output buffer holds at most 512KiB; older bytes are dropped silently. (Unix only)" ); fn input_schema(&self) -> serde_json::Value { json!({"type":"object","properties":{"session":{"type":"string"},"timeout_secs":{"type":"integer","default":120}},"required":["session"]}) @@ -764,7 +764,7 @@ pub struct TerminalCancelTool; impl ToolSpec for TerminalCancelTool { terminal_tool_common!( "terminal/cancel", - "Interrupt the running foreground command with ETX. The live terminal session survives and can be reused; its non-secret summary persists." + "Interrupt the running foreground command with ETX. The live terminal session survives and can be reused; its non-secret summary persists. (Unix only)" ); fn input_schema(&self) -> serde_json::Value { json!({"type":"object","properties":{"session":{"type":"string"}},"required":["session"]}) @@ -808,7 +808,7 @@ pub struct TerminalResetTool; impl ToolSpec for TerminalResetTool { terminal_tool_common!( "terminal/reset", - "Kill and recreate a persistent terminal session with a fresh environment. This loses live cd, exports, functions, activated environments, and running work while retaining the prior historical summary." + "Kill and recreate a persistent terminal session with a fresh environment. This loses live cd, exports, functions, activated environments, and running work while retaining the prior historical summary. (Unix only)" ); fn input_schema(&self) -> serde_json::Value { json!({"type":"object","properties":{"session":{"type":"string"}},"required":["session"]}) diff --git a/crates/tui/src/tools/verify.rs b/crates/tui/src/tools/verify.rs index c5da50a57b..e423737e7d 100644 --- a/crates/tui/src/tools/verify.rs +++ b/crates/tui/src/tools/verify.rs @@ -316,7 +316,8 @@ elevated reasoning and tries to REFUTE it, returning structured findings (issue, suggested fix). Call this when it is worth spending extra thinking: before claiming a non-trivial \ change complete, after a risky or subtle edit, or when you are unsure the change fully satisfies \ the requirement and handles edge cases. Skip it for trivial or mechanical changes. This is not a \ -test runner (use run_verifiers) or a code review of an arbitrary target (use review) — it is a \ +test runner (use the Run tool with action=\"verifiers\") or a code review of an arbitrary \ +target (use review) — it is a \ self-check of whether what you just did is actually correct and complete." } diff --git a/crates/tui/src/tools/web_run.rs b/crates/tui/src/tools/web_run.rs index 6ad217b742..036d805876 100644 --- a/crates/tui/src/tools/web_run.rs +++ b/crates/tui/src/tools/web_run.rs @@ -356,7 +356,7 @@ impl ToolSpec for WebRunTool { } fn description(&self) -> &'static str { - "Browse the web (search/open/click/find/screenshot/image_query) and return structured results with ref_ids for citations." + "Browse the web (search/open/click/find/screenshot/image_query) and return structured results with ref_ids for citations. ref_ids are session-cache references (≈30min TTL, ~256 pages); reopen by URL when evicted." } fn input_schema(&self) -> Value { @@ -383,7 +383,7 @@ impl ToolSpec for WebRunTool { "type": "object", "properties": { "q": { "type": "string" }, - "recency": { "type": "integer" }, + "recency": { "type": "integer", "description": "Freshness window in days (accepted but not enforced for image search)" }, "max_results": { "type": "integer" }, "timeout_ms": { "type": "integer" }, "domains": { "type": "array", "items": { "type": "string" } } @@ -396,7 +396,7 @@ impl ToolSpec for WebRunTool { "items": { "type": "object", "properties": { - "ref_id": { "type": "string" }, + "ref_id": { "type": "string", "description": "accepts a raw http(s) URL as ref_id" }, "lineno": { "type": "integer" } }, "required": ["ref_id"] @@ -426,11 +426,12 @@ impl ToolSpec for WebRunTool { }, "screenshot": { "type": "array", + "description": "Screenshot a PDF page. PDF refs only; returns the page's text lines (not an image), pageno is 0-based.", "items": { "type": "object", "properties": { "ref_id": { "type": "string" }, - "pageno": { "type": "integer" } + "pageno": { "type": "integer", "description": "0-based page number" } }, "required": ["ref_id", "pageno"] } diff --git a/crates/tui/src/tools/web_search.rs b/crates/tui/src/tools/web_search.rs index b2f5c0ed60..13fd7a94a1 100644 --- a/crates/tui/src/tools/web_search.rs +++ b/crates/tui/src/tools/web_search.rs @@ -103,7 +103,7 @@ impl ToolSpec for WebSearchTool { } fn description(&self) -> &'static str { - "Search the web and return ranked results with URLs, snippets, session-scoped ref_ids, and an execution receipt. Open a result ref_id with `web.run` when the short summary is not enough; fetch only the few sources needed. When the exact active route reports a documented first-party server-side search tool, it is tried first; otherwise the default backend is DuckDuckGo with Bing fallback. Configured API backends visibly degrade through DuckDuckGo then Bing when unavailable, and every hop is recorded. Configuration and network-policy errors fail closed. Explicit Bing and private DuckDuckGo-compatible routes do not cross providers. Set `[search] provider = \"bing\" | \"tavily\" | \"bocha\" | \"metaso\" | \"searxng\" | \"baidu\" | \"volcengine\" | \"sofya\"` in config.toml, or `[search] base_url` for a private DuckDuckGo-compatible endpoint or trusted SearXNG instance. For a known canonical URL, prefer `fetch_url` directly." + "Search the web and return ranked results with URLs, snippets, session-scoped ref_ids, and an execution receipt. Open a result ref_id with `web.run` when the short summary is not enough; fetch only the few sources needed. When the exact active route reports a documented first-party server-side search tool, it is tried first; otherwise the default backend is DuckDuckGo with Bing fallback. Configured API backends visibly degrade to Bing directly (single-hop) when unavailable, and every hop is recorded. Configuration and network-policy errors fail closed. Explicit Bing and private DuckDuckGo-compatible routes do not cross providers. Set `[search] provider = \"bing\" | \"tavily\" | \"bocha\" | \"metaso\" | \"searxng\" | \"baidu\" | \"volcengine\" | \"sofya\"` in config.toml, or `[search] base_url` for a private DuckDuckGo-compatible endpoint or trusted SearXNG instance. For a known canonical URL, prefer the Web tool with action=fetch." } fn input_schema(&self) -> Value { diff --git a/crates/tui/src/tools/web_tool.rs b/crates/tui/src/tools/web_tool.rs index 62513a4701..618efaf41c 100644 --- a/crates/tui/src/tools/web_tool.rs +++ b/crates/tui/src/tools/web_tool.rs @@ -71,7 +71,7 @@ impl ToolSpec for WebTool { } fn description(&self) -> &'static str { - "Search the web, fetch a known URL, or wait for a local dev server. Prefer fetch for a canonical URL and search when the source is unknown. Web actions are read-only and network-policy aware." + "Search the web, fetch a known URL, or wait for a local dev server. Prefer fetch for a canonical URL and search when the source is unknown. search/fetch are network-policy aware; wait only reaches loopback and does not evaluate network policy." } fn input_schema(&self) -> Value { @@ -159,7 +159,7 @@ impl ToolSpec for WebTool { }, "port": { "type": "integer", - "description": "TCP port to wait for (action=wait)" + "description": "TCP port to wait for (action=wait). action=wait requires `port`; the `url` port must match and the host must be loopback." }, "poll_interval_ms": { "type": "integer", diff --git a/crates/tui/src/tools/workflow.rs b/crates/tui/src/tools/workflow.rs index 6fb267ae48..a40c220753 100644 --- a/crates/tui/src/tools/workflow.rs +++ b/crates/tui/src/tools/workflow.rs @@ -792,19 +792,19 @@ impl ToolSpec for WorkflowTool { }, "script": { "type": "string", - "description": "Workflow JS source. The runtime provides args, task(...), parallel(thunks), pipeline(thunks), log(...), phase(...), and budget. Fan-out syntax: await parallel([() => task({...}), () => task({...})]). parallel() requires one array of zero-argument thunks, not variadic task promises." + "description": "Workflow JS source. The runtime provides args, task(...), parallel(thunks), pipeline(items, ...stages), log(...), phase(...), and budget. Fan-out syntax: await parallel([() => task({...}), () => task({...})]). parallel() requires one array of zero-argument thunks, not variadic task promises. Date and Math.random are unavailable (deterministic replay)." }, "source_path": { "type": "string", - "description": "Path to a .workflow.js script inside the workspace. Use instead of script for checked-in workflows." + "description": "Path to a workflow script (.workflow.js/.ts) inside the workspace or ~/.codewhale/workflows. Use instead of script for checked-in workflows." }, "fleet": { "type": "string", - "description": "Named Fleet to resolve task({ role }) declarations, loaded from $CODEWHALE_HOME/fleets/ or workspace fleets/. Accepts a qualified origin/name. A legacy roster maps roles to profiles. An exact Fleet (schema = \"exact\") is frozen at start: each member's provider, model, reasoning, and permission ceiling are fixed, and per-task model/thinking overrides are rejected." + "description": "Named Fleet to resolve task({ role }) declarations, loaded from $CODEWHALE_HOME/fleets/ or workspace fleets/. Accepts a qualified origin/name. A legacy roster maps roles to profiles. An exact Fleet (schema = \"exact\") is frozen at start: each member's provider, model, reasoning, and permission ceiling are fixed, and any per-task routing/stance override (model, strength, thinking, type, allowed_tools, write_authority) is rejected; write-role members must declare write scope." }, "plan": { "type": "object", - "description": "Structured planner plan JSON (#4124). Alternative to script/source_path. Accepts goal, risk, max_children, token_budget, phases[], and/or children[] (or IR nodes). risk must be exactly read_only, writes, or elevated. For a child, prefer role/profile without an explicit type; do not combine a role/profile with a conflicting type. Lowered to Workflow JS with parallel() partial-success semantics." + "description": "Structured planner plan JSON (#4124). Alternative to script/source_path. Accepts goal, risk, max_children, token_budget, phases[], children[], gates[] (or IR nodes). gates[] are Workflow-owned lane gates that can pause roles pending APPROVE/PASS verdicts. risk: read_only | writes | elevated (common aliases accepted). For a child, prefer role/profile without an explicit type; do not combine a role/profile with a conflicting type. Lowered to Workflow JS with parallel() partial-success semantics." }, "args": { "anyOf": [ @@ -833,7 +833,7 @@ impl ToolSpec for WorkflowTool { "verify": { "type": "boolean", "default": false, - "description": "After a successful workflow completion, run quick workspace verifier gates (auto/quick profile)." + "description": "After a successful workflow completion, run quick workspace verifier gates (auto/quick profile); any failed or skipped gate flips the run's final status to Failed." } }, "required": [], diff --git a/crates/tui/src/tools/workflow_trigger.rs b/crates/tui/src/tools/workflow_trigger.rs index a2421b3f53..03fa649f17 100644 --- a/crates/tui/src/tools/workflow_trigger.rs +++ b/crates/tui/src/tools/workflow_trigger.rs @@ -4,11 +4,12 @@ //! saying the word "workflow". Policy here answers "should we orchestrate?" — //! the parent prompt still **tells the operator** the intended shape and may //! ask setup questions via `request_user_input` (TUI modal) before calling -//! `workflow` / `plan`. +//! `workflow` / emitting a Plan-mode response. //! -//! This remains Act/Agent guidance rather than a prose classifier at the host -//! boundary. Operate sends ordinary work to direct background workers and -//! reaches for Workflow only when its stronger orchestration properties help. +//! This is an offline policy probe / reserved classifier; the Act prompt layer +//! intentionally does not mention Workflow. Operate sends ordinary work to +//! direct background workers and reaches for Workflow only when its stronger +//! orchestration properties help. /// Signals the parent can supply without full conversation replay. #[derive(Debug, Clone, Default, PartialEq, Eq)]