vtcode_core/tools/
error_messages.rs1pub mod agent_execution {
9 pub const PLANNING_DENIED_CONTEXT: &str = "tool denied by planning workflow";
11 pub const LOOP_DETECTION_PREFIX: &str = "Loop detection";
15 const LOOP_BLOCK_TOOL_OPEN: &str = ": Tool '";
16 const LOOP_BLOCK_CALLED: &str = "' has been called ";
17 const LOOP_BLOCK_BLOCKED: &str = " times with identical parameters and is now blocked.";
18 pub const LOOP_RETRY_BLOCKED_LINE: &str =
20 "The call was not executed, and repeating it with the same parameters will be blocked again.";
21
22 pub fn planning_workflow_denial_message(tool_name: &str) -> String {
24 format!(
25 "Tool '{tool_name}' execution failed: tool denied by planning workflow\n\n\
26 This call is not a recognized read-only planning action, so it is blocked during planning.\n\n\
27 Available during planning:\n\
28 - Read files: exec_command with readonly shell inspection commands such as sed, rg, ls, find, and git show\n\
29 - Run recognized commands: cargo check, cargo nextest run, git status, command -v, ls, grep, find, diff\n\
30 - Search code: exec_command with rg or other readonly search commands\n\
31 - Use task_tracker\n\n\
32 Planning shell shapes: use `&&` chains or separate calls; `;`, `||`, redirections (`>`, `2>`), and `*` globs are not recognized. Prefer `rg --files src | rg '\\.md$'` over `ls */*.md`.\n\n\
33 To start implementation:\n\
34 1. Wait for the user to review and approve the plan in the UI\n\
35 2. After approval, use apply_patch for file edits\n\n\
36 Fallback if automatic planning handoff keeps failing: type `implement` to present the plan again."
37 )
38 }
39
40 pub fn loop_detection_block_message(tool_name: &str, repeat_count: u64, original_error: Option<&str>) -> String {
42 let mut message = format!(
43 "{LOOP_DETECTION_PREFIX}{LOOP_BLOCK_TOOL_OPEN}{tool_name}{LOOP_BLOCK_CALLED}{repeat_count}{LOOP_BLOCK_BLOCKED}\n\n\
44 {LOOP_RETRY_BLOCKED_LINE}\n\n\
45 If you need the result from this tool:\n\
46 1. Check if you already have the result from a previous successful call in your conversation history\n\
47 2. If not available, use a different approach or modify your request"
48 );
49
50 if let Some(error) = original_error {
51 message.push_str("\n\nOriginal error: ");
52 message.push_str(error);
53 }
54
55 message
56 }
57
58 pub fn is_loop_detection_block_message(message: &str) -> bool {
64 let Some(rest) = message
65 .strip_prefix(LOOP_DETECTION_PREFIX)
66 .and_then(|rest| rest.strip_prefix(LOOP_BLOCK_TOOL_OPEN))
67 else {
68 return false;
69 };
70 let Some((tool_name, rest)) = rest.split_once(LOOP_BLOCK_CALLED) else {
71 return false;
72 };
73 let count_len = rest.len() - rest.trim_start_matches(|c: char| c.is_ascii_digit()).len();
74 !tool_name.is_empty() && count_len > 0 && rest[count_len..].starts_with(LOOP_BLOCK_BLOCKED)
75 }
76
77 pub fn is_planning_active_denial(error: &str) -> bool {
79 error.contains(PLANNING_DENIED_CONTEXT)
80 }
81
82 pub fn preflight_policy_correction(error: &str) -> Option<&'static str> {
84 if is_planning_active_denial(error) {
85 return Some(
86 "Planning permits only recognized read-only command shapes. Do not retry the same denied command. Use sed, rg, git show, or command -v for inspection; defer unknown scripts until plan approval. This denial does not establish that the script itself mutates files.",
87 );
88 }
89 let lowered = error.to_ascii_lowercase();
90 if lowered.contains("command security check failed") || lowered.contains("command injection pattern detected") {
91 return Some(
92 "The shell command was rejected by security policy, not JSON validation. Do not retry it unchanged or bypass the policy. Use a simpler literal command without dynamic expansion, or an approved dedicated tool; report the blocker if no permitted form satisfies the task.",
93 );
94 }
95 None
96 }
97}
98
99pub mod skill_ops {
101 pub const SKILL_NOT_FOUND: &str = "Skill not found";
102 pub const SKILL_ALREADY_EXISTS: &str = "Skill already exists";
103 pub const INVALID_SKILL_FORMAT: &str = "Invalid skill format";
104 pub const SKILL_SAVE_FAILED: &str = "Failed to save skill";
105 pub const SKILL_LOAD_FAILED: &str = "Failed to load skill";
106
107 pub fn skill_not_found_error(name: &str) -> anyhow::Error {
109 anyhow::anyhow!("Skill '{name}' not found")
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 fn internal_unified_tool_name(suffix: &str) -> String {
118 format!("unified_{suffix}")
119 }
120
121 #[test]
122 fn test_agent_execution_message_helpers() {
123 let planning_msg = agent_execution::planning_workflow_denial_message("write_file");
124 assert!(agent_execution::is_planning_active_denial(&planning_msg));
125 assert!(planning_msg.contains("blocked during planning"));
126 assert!(planning_msg.contains("cargo check"));
127 assert!(planning_msg.contains("exec_command"));
128 assert!(planning_msg.contains("apply_patch"));
129 assert!(planning_msg.contains("Planning shell shapes"));
130 assert!(planning_msg.contains("rg --files"));
131 assert!(!planning_msg.contains(&internal_unified_tool_name("file")));
132 assert!(!planning_msg.contains(&internal_unified_tool_name("exec")));
133 assert!(!planning_msg.contains(&internal_unified_tool_name("search")));
134 assert!(!planning_msg.contains(&format!("/{}", "mode")));
135 assert!(!planning_msg.contains("DO NOT retry this tool or use /plan off"));
136
137 let loop_msg = agent_execution::loop_detection_block_message("read_file", 3, Some("base error"));
138 assert!(loop_msg.starts_with(agent_execution::LOOP_DETECTION_PREFIX));
139 assert!(loop_msg.contains(agent_execution::LOOP_RETRY_BLOCKED_LINE));
140 assert!(loop_msg.contains("Original error: base error"));
141 assert!(agent_execution::is_loop_detection_block_message(&loop_msg));
142 }
143
144 #[test]
145 fn loop_detection_classification_ignores_prose_mentions() {
146 use agent_execution::is_loop_detection_block_message;
147
148 let generated = agent_execution::loop_detection_block_message("mcp::fs::read", 12, None);
149 assert!(is_loop_detection_block_message(&generated));
150
151 for message in [
152 "warning: loop detection disabled for this target",
153 "Loop detection is off; running tests",
154 "stderr: Loop detection: Tool 'x' has been called 3 times with identical parameters and is now blocked.",
155 "Loop detection: Tool 'x' has been called many times with identical parameters and is now blocked.",
156 "Loop detection: Tool '' has been called 3 times with identical parameters and is now blocked.",
157 "LOOP DETECTION: Tool 'x' has been called 3 times with identical parameters and is now blocked.",
158 ] {
159 assert!(!is_loop_detection_block_message(message), "{message:?}");
160 }
161 }
162}