agent_code_lib/tools/
plan_mode.rs1use async_trait::async_trait;
8use serde_json::json;
9
10use super::{Tool, ToolContext, ToolResult};
11use crate::error::ToolError;
12
13pub struct EnterPlanModeTool;
15
16#[async_trait]
17impl Tool for EnterPlanModeTool {
18 fn name(&self) -> &'static str {
19 "EnterPlanMode"
20 }
21
22 fn description(&self) -> &'static str {
23 "Switch to plan mode for safe exploration before making changes."
24 }
25
26 fn prompt(&self) -> String {
27 "Use this tool when you need to plan an approach before making changes. \
28 In plan mode, only read-only tools are available (FileRead, Grep, Glob, Bash). \
29 Write tools are blocked until ExitPlanMode is called.\n\n\
30 When to enter plan mode:\n\
31 - Complex tasks requiring multiple file changes\n\
32 - Unclear requirements that need investigation first\n\
33 - Multiple possible approaches to evaluate\n\
34 - Large refactors where the plan should be reviewed\n\
35 - When the user asks to \"plan\", \"think through\", or \"design\"\n\n\
36 You should write your plan to a file before exiting plan mode."
37 .to_string()
38 }
39
40 fn input_schema(&self) -> serde_json::Value {
41 json!({
42 "type": "object",
43 "properties": {}
44 })
45 }
46
47 fn is_read_only(&self) -> bool {
48 true
49 }
50
51 fn is_concurrency_safe(&self) -> bool {
52 true
53 }
54
55 async fn call(
56 &self,
57 _input: serde_json::Value,
58 _ctx: &ToolContext,
59 ) -> Result<ToolResult, ToolError> {
60 let plan_dir = dirs::data_dir()
62 .unwrap_or_else(|| std::path::PathBuf::from("."))
63 .join("agent-code")
64 .join("plans");
65 let _ = std::fs::create_dir_all(&plan_dir);
66
67 let slug = generate_slug();
68 let plan_path = plan_dir.join(format!("{slug}.md"));
69
70 let template = format!(
72 "# Plan\n\n\
73 Created: {}\n\n\
74 ## Goal\n\n\
75 (describe what needs to be accomplished)\n\n\
76 ## Approach\n\n\
77 (outline the steps)\n\n\
78 ## Files to modify\n\n\
79 (list files and what changes each needs)\n\n\
80 ## Risks / open questions\n\n\
81 (anything uncertain)\n",
82 chrono::Utc::now().format("%Y-%m-%d %H:%M UTC"),
83 );
84 let _ = std::fs::write(&plan_path, &template);
85
86 Ok(ToolResult::success(format!(
87 "Entered plan mode. Only read-only tools are available.\n\
88 Plan file created: {}\n\
89 Write your plan to this file, then call ExitPlanMode when ready.",
90 plan_path.display()
91 )))
92 }
93}
94
95pub struct ExitPlanModeTool;
97
98#[async_trait]
99impl Tool for ExitPlanModeTool {
100 fn name(&self) -> &'static str {
101 "ExitPlanMode"
102 }
103
104 fn description(&self) -> &'static str {
105 "Exit plan mode and re-enable all tools for execution. \
106 Call this after your plan is complete and ready to implement."
107 }
108
109 fn input_schema(&self) -> serde_json::Value {
110 json!({
111 "type": "object",
112 "properties": {}
113 })
114 }
115
116 fn is_read_only(&self) -> bool {
117 true
118 }
119
120 fn is_concurrency_safe(&self) -> bool {
121 true
122 }
123
124 async fn call(
125 &self,
126 _input: serde_json::Value,
127 _ctx: &ToolContext,
128 ) -> Result<ToolResult, ToolError> {
129 Ok(ToolResult::success(
130 "Exited plan mode. All tools are now available for execution.",
131 ))
132 }
133}
134
135fn generate_slug() -> String {
137 let adjectives = [
138 "brave", "calm", "dark", "eager", "fair", "golden", "hidden", "iron", "jade", "keen",
139 "light", "mystic", "noble", "ocean", "proud", "quick", "rapid", "silent", "true", "vivid",
140 ];
141 let nouns = [
142 "anchor", "beacon", "cedar", "dawn", "ember", "falcon", "grove", "harbor", "island",
143 "jewel", "kernel", "lantern", "meadow", "nexus", "orbit", "peak", "quill", "river",
144 "spark", "tower",
145 ];
146
147 let now = std::time::SystemTime::now()
148 .duration_since(std::time::UNIX_EPOCH)
149 .unwrap_or_default()
150 .subsec_nanos();
151
152 let adj = adjectives[(now as usize) % adjectives.len()];
153 let noun = nouns[((now as usize) / adjectives.len()) % nouns.len()];
154
155 format!("{adj}-{noun}")
156}