use super::App;
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use crate::agent::r#loop::AgentEvent;
use crate::tui::state::{ChatMessage, MessageRole, PendingPlan};
impl App {
pub(super) fn save_plan_to_file(&self, plan: &PendingPlan) -> Result<String, String> {
let plan_dir = std::path::Path::new(&self.working_dir)
.join("docs")
.join("plan");
if let Err(e) = std::fs::create_dir_all(&plan_dir) {
return Err(format!("Failed to create docs/plan/: {e}"));
}
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
let filename = format!("plan_{timestamp}.md");
let path = plan_dir.join(&filename);
let content = format!(
"# Implementation Plan\n\n\
> Task: {}\n\n\
---\n\n\
{}\n",
plan.user_msg, plan.plan,
);
std::fs::write(&path, &content).map_err(|e| format!("Failed to save plan: {e}"))?;
Ok(format!("docs/plan/{filename}"))
}
pub(super) fn handle_save_plan(&mut self) {
if let Some(ref plan) = self.state.pending_plan {
match self.save_plan_to_file(plan) {
Ok(rel_path) => {
self.state.messages.push(ChatMessage::text(
MessageRole::System,
format!(
"Plan saved to `{rel_path}`.\n\n\
`/proceed` — start code agent now\n\
`/cancel-plan` — discard the plan"
),
));
}
Err(e) => {
self.state
.messages
.push(ChatMessage::text(MessageRole::System, e));
}
}
} else {
self.state.messages.push(ChatMessage::text(
MessageRole::System,
"No pending plan. Run a task in `/architect` mode first.",
));
}
}
pub(super) fn handle_proceed_plan(&mut self, event_tx: mpsc::UnboundedSender<AgentEvent>) {
if let Some(plan) = self.state.pending_plan.take() {
if let Err(e) = self.save_plan_to_file(&plan) {
self.state
.messages
.push(ChatMessage::text(MessageRole::System, e));
}
self.state
.messages
.push(ChatMessage::text(MessageRole::User, "/proceed"));
self.state.agent_busy = true;
self.agent_busy_since = Some(std::time::Instant::now());
self.state.elapsed_secs = 0;
self.state.status_msg = "Thinking (code)...".to_string();
self.state.agent_mode = "code".to_string();
self.state.scroll_offset = 0;
self.scroll_target = 0;
self.checkpoint_mgr.begin(&plan.user_msg);
if let Some(ref ctx) = self.context {
self.last_context_backup = Some(ctx.clone());
}
let client = self.client.clone();
let config = self.config.clone();
let working_dir = self.working_dir.clone();
let system_prompt = plan.system_prompt.clone();
let plan_text = plan.plan.clone();
let user_msg = plan.user_msg.clone();
let lsp_manager = self.lsp_manager.clone();
let arch_context = self.context.clone();
let cancel = CancellationToken::new();
self.cancel_token = Some(cancel.clone());
let (approval_req_tx, approval_req_rx) = tokio::sync::mpsc::unbounded_channel();
self.approval_req_rx = Some(approval_req_rx);
let approval_gate = crate::agent::approval::ApprovalGate::new(
self.approve_mode.clone(),
approval_req_tx,
)
.with_session_approvals(self.session_approvals.clone());
tokio::spawn(async move {
crate::agent::r#loop::execute_plan(crate::agent::r#loop::ExecutePlanParams {
client,
config,
system_prompt,
plan: plan_text,
user_msg,
working_dir,
event_tx,
cancel,
lsp_manager,
arch_context,
approval_gate,
})
.await;
});
} else {
self.state.messages.push(ChatMessage::text(
MessageRole::System,
"No pending plan. Run a task in `/architect` mode first.",
));
}
}
pub(super) fn handle_cancel_plan(&mut self) {
if self.state.pending_plan.take().is_some() {
self.state
.messages
.push(ChatMessage::text(MessageRole::System, "Plan discarded."));
} else {
self.state.messages.push(ChatMessage::text(
MessageRole::System,
"No pending plan to cancel.",
));
}
}
}