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