matrixcode_core/tools/
plan_mode.rs1use std::sync::Arc;
2use anyhow::Result;
3use async_trait::async_trait;
4use serde_json::{Value, json};
5use tokio::sync::Mutex;
6
7use super::{Tool, ToolDefinition};
8use crate::approval::RiskLevel;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum PlanState {
13 None,
14 Active,
15 Committed,
16}
17
18#[derive(Debug, Clone)]
20pub struct PlanInfo {
21 pub state: PlanState,
22 pub plan_content: String,
23 pub files_to_modify: Vec<String>,
24 pub created_at: Option<std::time::Instant>,
25}
26
27static PLAN_STATE: std::sync::OnceLock<Arc<Mutex<PlanInfo>>> = std::sync::OnceLock::new();
28
29fn get_plan_state() -> Arc<Mutex<PlanInfo>> {
30 PLAN_STATE.get_or_init(|| {
31 Arc::new(Mutex::new(PlanInfo {
32 state: PlanState::None,
33 plan_content: String::new(),
34 files_to_modify: Vec::new(),
35 created_at: None,
36 }))
37 }).clone()
38}
39
40pub struct EnterPlanModeTool;
42
43#[async_trait]
44impl Tool for EnterPlanModeTool {
45 fn definition(&self) -> ToolDefinition {
46 ToolDefinition {
47 name: "enter_plan_mode".to_string(),
48 description: "Enter planning mode to design an implementation approach before executing. Use for: (1) Non-trivial implementation tasks that need planning; (2) Tasks that could benefit from architectural consideration; (3) Changes that might have significant impact. Returns step-by-step plan with critical files identified.".to_string(),
49 parameters: json!({
50 "type": "object",
51 "properties": {}
52 }),
53 }
54 }
55
56 fn risk_level(&self) -> RiskLevel {
57 RiskLevel::Safe }
59
60 async fn execute(&self, _params: Value) -> Result<String> {
61 let plan = get_plan_state();
62 let mut state = plan.lock().await;
63
64 if state.state == PlanState::Active {
65 return Ok("Already in plan mode. Continue planning or use exit_plan_mode to finish.".to_string());
66 }
67
68 state.state = PlanState::Active;
69 state.plan_content = String::new();
70 state.files_to_modify = Vec::new();
71 state.created_at = Some(std::time::Instant::now());
72
73 Ok("Entered plan mode. Design your implementation approach:\n\n1. Analyze the task requirements\n2. Identify key files and components\n3. Consider architectural trade-offs\n4. Create step-by-step implementation plan\n5. Use exit_plan_mode to commit and execute\n\nNote: In plan mode, focus on analysis and design. Tool executions will be limited to read-only operations.".to_string())
74 }
75}
76
77pub struct ExitPlanModeTool;
79
80#[async_trait]
81impl Tool for ExitPlanModeTool {
82 fn definition(&self) -> ToolDefinition {
83 ToolDefinition {
84 name: "exit_plan_mode".to_string(),
85 description: "Exit planning mode. If plan is approved, the agent will execute the planned changes. If plan is rejected, the plan is discarded and no changes are made.".to_string(),
86 parameters: json!({
87 "type": "object",
88 "properties": {
89 "plan": {
90 "type": "string",
91 "description": "The implementation plan to commit (optional if already documented)"
92 },
93 "files_to_modify": {
94 "type": "array",
95 "items": {"type": "string"},
96 "description": "List of files that will be modified (optional)"
97 },
98 "approved": {
99 "type": "boolean",
100 "default": true,
101 "description": "Whether the plan is approved for execution"
102 }
103 }
104 }),
105 }
106 }
107
108 fn risk_level(&self) -> RiskLevel {
109 RiskLevel::Mutating
110 }
111
112 async fn execute(&self, params: Value) -> Result<String> {
113 let plan_content = params["plan"].as_str();
114 let files_to_modify = params["files_to_modify"].as_array()
115 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect::<Vec<_>>());
116 let approved = params["approved"].as_bool().unwrap_or(true);
117
118 let plan = get_plan_state();
119 let mut state = plan.lock().await;
120
121 if state.state != PlanState::Active {
122 return Ok("Not in plan mode. Use enter_plan_mode first.".to_string());
123 }
124
125 if let Some(content) = plan_content {
127 state.plan_content = content.to_string();
128 }
129 if let Some(files) = files_to_modify {
130 state.files_to_modify = files;
131 }
132
133 if approved {
134 state.state = PlanState::Committed;
135
136 let files_str = if state.files_to_modify.is_empty() {
137 "No specific files identified".to_string()
138 } else {
139 state.files_to_modify.join(", ")
140 };
141
142 Ok(format!(
143 "Plan committed. Ready to execute.\n\nPlan: {}\nFiles to modify: {}\n\nNow proceeding with implementation...",
144 state.plan_content, files_str
145 ))
146 } else {
147 state.state = PlanState::None;
148 state.plan_content.clear();
149 state.files_to_modify.clear();
150
151 Ok("Plan rejected and discarded. Returning to normal mode without making changes.".to_string())
152 }
153 }
154}
155
156pub fn is_in_plan_mode() -> bool {
158 false }
161
162pub async fn get_current_plan_state() -> PlanState {
164 let plan = get_plan_state();
165 let state = plan.lock().await;
166 state.state.clone()
167}