Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 77 additions & 6 deletions crates/tui/src/prompts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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."
)
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions crates/tui/src/prompts/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 `<codewhale:subagent.done>`.
a section has no entries. If blocked, name the missing fact. Then stop.
"#;
2 changes: 1 addition & 1 deletion crates/tui/src/tools/apply_patch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions crates/tui/src/tools/canonical_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/tools/fetch_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/tools/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/tools/file_search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion crates/tui/src/tools/fim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/tools/git_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions crates/tui/src/tools/github/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
8 changes: 4 additions & 4 deletions crates/tui/src/tools/goal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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"],
Expand Down
5 changes: 3 additions & 2 deletions crates/tui/src/tools/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/tools/image_ocr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/tools/mcp_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions crates/tui/src/tools/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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" }
}
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion crates/tui/src/tools/remember.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
Expand Down
Loading
Loading