Skip to main content

claude_rust_tools/infrastructure/
plan_mode_tool.rs

1use std::io::Write;
2use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
3
4use claude_rust_errors::{AppError, AppResult};
5use claude_rust_types::{PermissionLevel, Tool};
6use serde_json::{Value, json};
7
8pub struct EnterPlanModeTool;
9
10#[async_trait::async_trait]
11impl Tool for EnterPlanModeTool {
12    fn name(&self) -> &str { "enter_plan_mode" }
13    fn description(&self) -> &str { "Enter plan mode." }
14    fn input_schema(&self) -> Value { json!({"type":"object","properties":{},"required":[]}) }
15    fn permission_level(&self) -> PermissionLevel { PermissionLevel::ReadOnly }
16    fn is_read_only(&self, _input: &Value) -> bool { true }
17    async fn execute(&self, _input: Value) -> AppResult<String> {
18        Ok("Plan mode activated.".into())
19    }
20}
21
22pub struct ExitPlanModeTool {
23    paused: Arc<AtomicBool>,
24}
25
26impl ExitPlanModeTool {
27    pub fn new(paused: Arc<AtomicBool>) -> Self {
28        Self { paused }
29    }
30}
31
32#[async_trait::async_trait]
33impl Tool for ExitPlanModeTool {
34    fn name(&self) -> &str { "exit_plan_mode" }
35
36    fn description(&self) -> &str {
37        "Signal that you have finished planning. Call this after presenting your complete plan as text to the user. The user will review and approve your plan before any changes are made."
38    }
39
40    fn input_schema(&self) -> Value {
41        json!({"type":"object","properties":{},"required":[]})
42    }
43
44    fn permission_level(&self) -> PermissionLevel {
45        PermissionLevel::ReadOnly
46    }
47
48    fn is_read_only(&self, _input: &Value) -> bool { true }
49    fn requires_user_interaction(&self) -> bool { true }
50
51    async fn execute(&self, _input: Value) -> AppResult<String> {
52        let paused = self.paused.clone();
53        tokio::task::spawn_blocking(move || {
54            paused.store(true, Ordering::Relaxed);
55            let result = confirm_exit_plan();
56            paused.store(false, Ordering::Relaxed);
57            result
58        })
59        .await
60        .map_err(|e| AppError::Tool(format!("exit plan task failed: {e}")))??;
61
62        Ok("User approved the plan. Proceeding with implementation.".into())
63    }
64}
65
66fn confirm_exit_plan() -> AppResult<String> {
67    use crossterm::{
68        event::{self, KeyCode, KeyModifiers},
69        terminal,
70    };
71
72    let mut out = std::io::stdout();
73    let _ = writeln!(out, "\n  \x1b[35m\x1b[1mâ—†  Exit plan mode?\x1b[0m  \x1b[2m[y/n]\x1b[0m");
74    let _ = out.flush();
75
76    terminal::enable_raw_mode().map_err(|e| AppError::Tool(e.to_string()))?;
77
78    let result = loop {
79        if !event::poll(std::time::Duration::from_millis(100)).unwrap_or(false) {
80            continue;
81        }
82        if let event::Event::Key(k) = event::read().map_err(|e| AppError::Tool(e.to_string()))? { match (k.code, k.modifiers) {
83            (KeyCode::Char('y'), _) | (KeyCode::Char('Y'), _) | (KeyCode::Enter, _) => {
84                let _ = writeln!(out, "\r  \x1b[35m\x1b[1mâ—†  Plan approved\x1b[0m\n");
85                let _ = out.flush();
86                break Ok(String::new());
87            }
88            (KeyCode::Char('n'), _) | (KeyCode::Char('N'), _) | (KeyCode::Esc, _) => {
89                let _ = writeln!(out, "\r  \x1b[2m◆  Plan rejected — continue refining\x1b[0m\n");
90                let _ = out.flush();
91                break Err(AppError::Tool(
92                    "Plan rejected by user. Refine your plan and call exit_plan_mode again when ready.".into(),
93                ));
94            }
95            (KeyCode::Char('c'), KeyModifiers::CONTROL) => {
96                break Err(AppError::Interrupted);
97            }
98            _ => {}
99        } }
100    };
101
102    terminal::disable_raw_mode().ok();
103    result
104}