Skip to main content

matrixcode_core/tools/
plan_mode.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4use std::sync::Arc;
5use tokio::sync::Mutex;
6
7use super::{Tool, ToolDefinition};
8use crate::approval::RiskLevel;
9
10/// Planning mode state
11#[derive(Debug, Clone, PartialEq)]
12pub enum PlanState {
13    None,
14    Active,
15    Committed,
16}
17
18/// Plan info
19#[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
31        .get_or_init(|| {
32            Arc::new(Mutex::new(PlanInfo {
33                state: PlanState::None,
34                plan_content: String::new(),
35                files_to_modify: Vec::new(),
36                created_at: None,
37            }))
38        })
39        .clone()
40}
41
42/// EnterPlanMode tool - enter planning mode for designing implementation
43pub struct EnterPlanModeTool;
44
45#[async_trait]
46impl Tool for EnterPlanModeTool {
47    fn definition(&self) -> ToolDefinition {
48        ToolDefinition {
49            name: "enter_plan_mode".to_string(),
50            description: "进入规划模式,在执行前设计实现方案。适用于:(1) 需规划的非 trivial 实现任务;(2) 可受益于架构考量的问题;(3) 可能产生重大影响的变更。返回分步计划并识别关键文件。".to_string(),
51            parameters: json!({
52                "type": "object",
53                "properties": {}
54            }),
55            ..Default::default()
56        }
57    }
58
59    fn risk_level(&self) -> RiskLevel {
60        RiskLevel::Safe // Read-only mode
61    }
62
63    async fn execute(&self, _params: Value) -> Result<String> {
64        let plan = get_plan_state();
65        let mut state = plan.lock().await;
66
67        if state.state == PlanState::Active {
68            return Ok(
69                "Already in plan mode. Continue planning or use exit_plan_mode to finish."
70                    .to_string(),
71            );
72        }
73
74        state.state = PlanState::Active;
75        state.plan_content = String::new();
76        state.files_to_modify = Vec::new();
77        state.created_at = Some(std::time::Instant::now());
78
79        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())
80    }
81}
82
83/// ExitPlanMode tool - exit planning mode and commit plan
84pub struct ExitPlanModeTool;
85
86#[async_trait]
87impl Tool for ExitPlanModeTool {
88    fn definition(&self) -> ToolDefinition {
89        ToolDefinition {
90            name: "exit_plan_mode".to_string(),
91            description: "退出规划模式。若计划被批准,代理将执行计划的变更;若被拒绝,计划将被丢弃且不做任何修改。注意:用户批准工具执行即视为批准计划。".to_string(),
92            parameters: json!({
93                "type": "object",
94                "properties": {
95                    "plan": {
96                        "type": "string",
97                        "description": "要提交的实现计划(可选,若已记录)"
98                    },
99                    "files_to_modify": {
100                        "type": "array",
101                        "items": {"type": "string"},
102                        "description": "将要修改的文件列表(可选)"
103                    }
104                    // NOTE: 'approved' parameter removed - user approval of tool execution = plan approval
105                    // This prevents AI from setting approved=false while user approves execution
106                }
107            }),
108            ..Default::default()
109        }
110    }
111
112    fn risk_level(&self) -> RiskLevel {
113        RiskLevel::Mutating
114    }
115
116    async fn execute(&self, params: Value) -> Result<String> {
117        let plan_content = params["plan"].as_str();
118        let files_to_modify = params["files_to_modify"].as_array().map(|arr| {
119            arr.iter()
120                .filter_map(|v| v.as_str().map(|s| s.to_string()))
121                .collect::<Vec<_>>()
122        });
123        // User approval of tool execution = plan approval
124        // If this tool is executed, it means user approved the plan
125        let approved = true;
126
127        let plan = get_plan_state();
128        let mut state = plan.lock().await;
129
130        if state.state != PlanState::Active {
131            return Ok("Not in plan mode. Use enter_plan_mode first.".to_string());
132        }
133
134        // Update plan content if provided
135        if let Some(content) = plan_content {
136            state.plan_content = content.to_string();
137        }
138        if let Some(files) = files_to_modify {
139            state.files_to_modify = files;
140        }
141
142        if approved {
143            state.state = PlanState::Committed;
144
145            let files_str = if state.files_to_modify.is_empty() {
146                "No specific files identified".to_string()
147            } else {
148                state.files_to_modify.join(", ")
149            };
150
151            Ok(format!(
152                "Plan committed. Ready to execute.\n\nPlan: {}\nFiles to modify: {}\n\nNow proceeding with implementation...",
153                state.plan_content, files_str
154            ))
155        } else {
156            state.state = PlanState::None;
157            state.plan_content.clear();
158            state.files_to_modify.clear();
159
160            Ok(
161                "Plan rejected and discarded. Returning to normal mode without making changes."
162                    .to_string(),
163            )
164        }
165    }
166}
167
168/// Check if currently in plan mode
169pub fn is_in_plan_mode() -> bool {
170    // This is a synchronous check - for async use get_plan_state().lock().await
171    false // Placeholder - real check would need async context
172}
173
174/// Get current plan state (async)
175pub async fn get_current_plan_state() -> PlanState {
176    let plan = get_plan_state();
177    let state = plan.lock().await;
178    state.state.clone()
179}