//! Plan management tools — enter and mark ready. //! //! These tools implement a two-phase planning workflow: `PlanEnter` presents a //! structured plan to the user for approval, and `PlanReady` signals that the //! plan is finalised and execution may begin. use crate::tools::{Tool, ToolCtx}; use anyhow::Result; use serde_json::{json, Value}; use tracing::{info, instrument, warn}; /// Enter a planning phase — persist a structured plan and notify the user. /// /// Flow: extract plan text → write to `{session_dir}/PLAN.md` → push a /// `PlanUpdate` turn event → return plan length summary. pub struct PlanEnter; impl Tool for PlanEnter { fn name(&self) -> &'static str { "plan_enter" } fn description(&self) -> &'static str { "Enter a planning phase — present a structured plan for approval" } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "plan": { "type": "string", "description": "The structured plan text" } }, "required": ["plan"] }) } #[instrument(skip(self, ctx, args))] fn run(&self, ctx: &ToolCtx, args: &Value) -> Result { let plan_text = crate::tools::arg_str(args, "plan")?; info!(plan_len = plan_text.len(), "plan_enter called"); let plan_path = ctx.session_dir.join("PLAN.md"); let _ = std::fs::write(&plan_path, &plan_text); if let Some(events) = &ctx.turn_events { if let Ok(mut q) = events.lock() { q.push_back(crate::TurnEvent::PlanUpdate(plan_text.clone())); } } Ok(format!( "Plan entered (length: {} chars). Waiting for approval...", plan_text.len() )) } } /// Signal that the plan is ready and execution can begin. /// /// Flow: extract plan content → persist to a timestamped file in /// `{session_dir}/plans/` → overwrite `PLAN.md` → push `PlanUpdate` event. pub struct PlanReady; impl Tool for PlanReady { fn name(&self) -> &'static str { "plan_ready" } fn description(&self) -> &'static str { "Signal that the plan is ready and execution can begin" } fn parameters(&self) -> Value { json!({ "type": "object", "properties": { "plan": { "type": "string", "description": "The final plan content" } }, "required": ["plan"] }) } #[instrument(skip(self, ctx, args))] fn run(&self, ctx: &ToolCtx, args: &Value) -> Result { let plan_content = crate::tools::arg_str(args, "plan")?; info!(plan_len = plan_content.len(), "plan_ready called"); // Persist the plan to session directory for reference let plan_dir = ctx.session_dir.join("plans"); if std::fs::create_dir_all(&plan_dir).is_ok() { let filename = format!("plan-{}.md", chrono::Utc::now().format("%Y%m%d_%H%M%S")); let path = plan_dir.join(&filename); std::fs::write(&path, &plan_content) .map_err(|e| anyhow::anyhow!("failed to save plan: {e}"))?; info!(filename = %filename, "plan persisted to disk"); // Also save the latest plan let plan_path = ctx.session_dir.join("PLAN.md"); let _ = std::fs::write(&plan_path, &plan_content); if let Some(events) = &ctx.turn_events { if let Ok(mut q) = events.lock() { q.push_back(crate::TurnEvent::PlanUpdate(plan_content.clone())); } } Ok(format!("Plan saved to {filename}. Starting execution.")) } else { warn!("failed to create plans directory"); Ok("Plan is ready. Starting execution.".to_string()) } } }