use vtcode_core::llm::provider as uni;
use vtcode_core::tools::registry::ToolRegistry;
use vtcode_core::utils::ansi::AnsiRenderer;
use vtcode_ui::tui::app::InlineHandle;
use crate::agent::runloop::unified::planning_workflow::{
PlanningIntent, assistant_recently_prompted_implementation, detect_planning_intent,
};
use crate::agent::runloop::unified::planning_workflow_state::{
PlanningWorkflowSessionState, finish_planning_workflow, short_confirmation_hint_with_fallback,
};
use crate::agent::runloop::unified::turn::context::TurnLoopResult;
use vtcode_config::builtin_primary_build_agent;
const PLANNING_WORKFLOW_EXIT_TRIGGER_STATUS: &str = "Planning workflow: implementation intent detected from your message. Exiting planning mode and proceeding with execution.";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PlanningTransition {
None,
ExitAndImplement,
StayInPlanning,
}
impl PlanningTransition {
#[inline]
pub(crate) fn into_result_and_agent(self) -> (TurnLoopResult, Option<String>) {
match self {
PlanningTransition::None => (TurnLoopResult::Completed { plan_approved_execution_pending: false }, None),
PlanningTransition::ExitAndImplement => (
TurnLoopResult::Completed { plan_approved_execution_pending: true },
Some(builtin_primary_build_agent().name),
),
PlanningTransition::StayInPlanning => {
(TurnLoopResult::Completed { plan_approved_execution_pending: false }, None)
}
}
}
#[inline]
pub(crate) fn should_break(self) -> bool {
!matches!(self, PlanningTransition::None)
}
}
pub(crate) async fn maybe_handle_planning_exit_trigger(
renderer: &mut AnsiRenderer,
tool_registry: &mut ToolRegistry,
plan_session: &mut PlanningWorkflowSessionState,
handle: &InlineHandle,
working_history: &[uni::Message],
auto_finish_planning_attempted: &mut bool,
) -> anyhow::Result<PlanningTransition> {
if !tool_registry.is_planning_active() {
return Ok(PlanningTransition::None);
}
if *auto_finish_planning_attempted {
return Ok(PlanningTransition::None);
}
let Some(last_user_msg) = working_history.iter().rev().find(|msg| msg.role == uni::MessageRole::User) else {
return Ok(PlanningTransition::None);
};
let text = last_user_msg.content.as_text();
let assistant_prompted = assistant_recently_prompted_implementation(working_history);
let intent = detect_planning_intent(&text, assistant_prompted);
let transition = match intent {
PlanningIntent::ExitAndImplement => {
*auto_finish_planning_attempted = true;
display_status(renderer, PLANNING_WORKFLOW_EXIT_TRIGGER_STATUS)?;
finish_planning_workflow(tool_registry, plan_session, handle, true).await;
PlanningTransition::ExitAndImplement
}
PlanningIntent::StayInPlanning => {
display_status(renderer, &short_confirmation_hint_with_fallback())?;
PlanningTransition::StayInPlanning
}
PlanningIntent::None => PlanningTransition::None,
};
Ok(transition)
}
fn display_status(renderer: &mut AnsiRenderer, message: &str) -> anyhow::Result<()> {
renderer.line(vtcode_core::utils::ansi::MessageStyle::Status, message)
}