Skip to main content

matrixcode_core/tools/
plan_mode.rs

1use 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/// 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.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
40/// EnterPlanMode tool - enter planning mode for designing implementation
41pub 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  // Read-only mode
58    }
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
77/// ExitPlanMode tool - exit planning mode and commit plan
78pub 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        // Update plan content if provided
126        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
156/// Check if currently in plan mode
157pub fn is_in_plan_mode() -> bool {
158    // This is a synchronous check - for async use get_plan_state().lock().await
159    false  // Placeholder - real check would need async context
160}
161
162/// Get current plan state (async)
163pub async fn get_current_plan_state() -> PlanState {
164    let plan = get_plan_state();
165    let state = plan.lock().await;
166    state.state.clone()
167}