From 19162b1fe63f6e644bd456cd40b6c37f1c1b9bea Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Thu, 5 Feb 2026 20:31:24 +1100 Subject: [PATCH] Exit plan mode when plan is completed or blocked When a plan reaches a terminal state (all items done or blocked) in interactive mode, automatically exit plan mode and return to normal prompt. Changes: - Add Agent::is_plan_terminal() method to check if plan is complete - Add check_and_exit_plan_mode_if_terminal() helper in interactive.rs - Call the helper after each execute_user_input() to detect completion Fixes issue where plan mode prompt ' >> ' persisted after plan completion. --- crates/g3-cli/src/interactive.rs | 22 ++++++++++++++++++++++ crates/g3-core/src/lib.rs | 18 +++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) 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 // =========================================================================