diff --git a/crates/g3-cli/src/interactive.rs b/crates/g3-cli/src/interactive.rs index addea60..4ac01a6 100644 --- a/crates/g3-cli/src/interactive.rs +++ b/crates/g3-cli/src/interactive.rs @@ -143,6 +143,22 @@ async fn execute_user_input( } } +/// Check if plan is terminal and exit plan mode if so. +/// +/// Returns true if plan mode was exited (plan is complete or all blocked). +fn check_and_exit_plan_mode_if_terminal( + agent: &mut Agent, + in_plan_mode: &mut bool, + output: &SimpleOutput, +) -> bool { + if *in_plan_mode && agent.is_plan_terminal() { + output.print("\nšŸ“‹ Plan complete - exiting plan mode"); + *in_plan_mode = false; + agent.set_plan_mode(false); + return true; + } + false +} /// Run interactive mode with console output. /// If `agent_name` is Some, we're in agent+chat mode: skip session resume/verbose welcome, @@ -298,6 +314,9 @@ pub async fn run_interactive( execute_user_input( &mut agent, &final_input, show_prompt, show_code, &output, from_agent_mode ).await; + + // Check if plan completed and exit plan mode if so + check_and_exit_plan_mode_if_terminal(&mut agent, &mut in_plan_mode, &output); } else { // Single line input let input = line.trim().to_string(); @@ -365,6 +384,9 @@ pub async fn run_interactive( execute_user_input( &mut agent, &final_input, show_prompt, show_code, &output, from_agent_mode ).await; + + // Check if plan completed and exit plan mode if so + check_and_exit_plan_mode_if_terminal(&mut agent, &mut in_plan_mode, &output); } } Err(ReadlineError::Interrupted) => { diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 8d59a12..f47afc5 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -50,7 +50,7 @@ pub use skills::{Skill, discover_skills, generate_skills_prompt}; #[cfg(test)] mod task_result_comprehensive_tests; use crate::ui_writer::UiWriter; -use tools::plan::{check_plan_approval_gate, ApprovalGateResult}; +use tools::plan::{check_plan_approval_gate, read_plan, ApprovalGateResult}; #[cfg(test)] mod tilde_expansion_tests; @@ -1548,6 +1548,22 @@ impl Agent { self.in_plan_mode } + /// Check if the current plan is in a terminal state (all items done or blocked). + /// + /// Returns true if: + /// - A plan exists AND all items are in terminal state (done or blocked) + /// + /// Returns false if: + /// - No session_id is set + /// - No plan exists for the session + /// - Plan has items that are not terminal (todo or doing) + pub fn is_plan_terminal(&self) -> bool { + let Some(session_id) = &self.session_id else { + return false; + }; + read_plan(session_id).ok().flatten().map_or(false, |plan| plan.is_complete()) + } + // ========================================================================= // STREAMING & LLM INTERACTION // =========================================================================