fix(tui): clarify shell tool availability errors (#2412)

Harvested from #2402 with thanks to @axobase001.

Keeps `allow_shell` guidance visible for gated shell tools even when missing-tool suggestions exist, removes the nonexistent task_shell_cancel matcher, and broadens regression coverage.

Partially addresses #2328.
This commit is contained in:
Hunter Bown
2026-05-31 02:52:24 -07:00
committed by GitHub
parent 0dd7f0b802
commit ce72b0cbc4
2 changed files with 75 additions and 2 deletions
+38
View File
@@ -2359,6 +2359,44 @@ fn missing_tool_error_message_includes_discovery_guidance_when_no_match() {
assert!(message.contains(TOOL_SEARCH_BM25_NAME));
}
#[test]
fn missing_shell_tool_error_message_names_allow_shell_gate() {
let catalog = vec![api_tool("read_file")];
for tool_name in [
"exec_shell",
"exec_shell_wait",
"exec_shell_interact",
"task_shell_start",
"task_shell_wait",
] {
let message = missing_tool_error_message(tool_name, &catalog);
assert!(message.contains("not available in the current tool catalog"));
assert!(message.contains("allow_shell"), "{tool_name}: {message}");
assert!(
message.contains("trusted workspaces"),
"{tool_name}: {message}"
);
assert!(
message.contains(TOOL_SEARCH_BM25_NAME),
"{tool_name}: {message}"
);
}
}
#[test]
fn missing_shell_tool_error_message_keeps_allow_shell_hint_with_suggestions() {
let catalog = vec![api_tool("exec")];
let message = missing_tool_error_message("exec_shell", &catalog);
assert!(message.contains("Did you mean:"));
assert!(message.contains("exec"));
assert!(message.contains("allow_shell"));
assert!(message.contains("trusted workspaces"));
assert!(message.contains(TOOL_SEARCH_BM25_NAME));
}
#[test]
fn filter_tool_call_delta_strips_bracket_marker() {
let mut in_block = false;
+37 -2
View File
@@ -438,17 +438,52 @@ fn suggest_tool_names(catalog: &[Tool], requested: &str, limit: usize) -> Vec<St
pub(super) fn missing_tool_error_message(tool_name: &str, catalog: &[Tool]) -> String {
let suggestions = suggest_tool_names(catalog, tool_name, 3);
let shell_hint = if is_shell_tool_name(tool_name) {
Some(shell_tool_allow_shell_hint())
} else {
None
};
if suggestions.is_empty() {
if let Some(shell_hint) = shell_hint {
return format!(
"Tool '{tool_name}' is not available in the current tool catalog. \
{shell_hint}, or use {TOOL_SEARCH_BM25_NAME} with a short query."
);
}
return format!(
"Tool '{tool_name}' is not available in the current tool catalog. \
Verify mode/feature flags, or use {TOOL_SEARCH_BM25_NAME} with a short query."
);
}
let suggestion_text = format!("Did you mean: {}?", suggestions.join(", "));
if let Some(shell_hint) = shell_hint {
return format!(
"Tool '{tool_name}' is not available in the current tool catalog. \
{suggestion_text} {shell_hint}. \
You can also use {TOOL_SEARCH_BM25_NAME} to discover tools."
);
}
format!(
"Tool '{tool_name}' is not available in the current tool catalog. \
Did you mean: {}? You can also use {TOOL_SEARCH_BM25_NAME} to discover tools.",
suggestions.join(", ")
{suggestion_text} You can also use {TOOL_SEARCH_BM25_NAME} to discover tools."
)
}
fn shell_tool_allow_shell_hint() -> &'static str {
"Shell tools are gated by `allow_shell`; enable `allow_shell = true` for trusted workspaces, \
or switch to an auto-approve mode that permits shell access"
}
fn is_shell_tool_name(tool_name: &str) -> bool {
matches!(
tool_name,
"exec_shell"
| "exec_shell_wait"
| "exec_shell_interact"
| "task_shell_start"
| "task_shell_wait"
)
}