vtcode 0.99.1

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use std::sync::Arc;

use anyhow::Result;
use tokio::sync::{Notify, RwLock};

use vtcode_core::config::loader::VTCodeConfig;
use vtcode_core::config::types::AgentConfig as CoreAgentConfig;
use vtcode_core::core::decision_tracker::DecisionTracker;
use vtcode_core::hooks::{LifecycleHookEngine, SessionEndReason};
use vtcode_core::llm::provider as uni;
use vtcode_core::tools::ToolRegistry;
use vtcode_tui::app::{InlineHandle, InlineHeaderContext, InlineSession};

use crate::agent::runloop::mcp_events;
use crate::agent::runloop::model_picker::ModelPickerState;
pub(crate) use crate::agent::runloop::slash_commands::SlashCommandOutcome;
use crate::agent::runloop::unified::async_mcp_manager::AsyncMcpManager;
use crate::agent::runloop::unified::context_manager::ContextManager;
use crate::agent::runloop::unified::inline_events::harness::HarnessEventEmitter;
use crate::agent::runloop::unified::palettes::ActivePalette;
use crate::agent::runloop::unified::session_setup::IdeContextBridge;
use crate::agent::runloop::unified::state::{CtrlCState, SessionStats};
use crate::agent::runloop::unified::status_line::InputStatusState;
use crate::agent::runloop::unified::tool_catalog::ToolCatalogState;
use crate::agent::runloop::unified::workspace_links::LinkedDirectory;
use crate::agent::runloop::welcome::SessionBootstrap;
use vtcode_core::utils::ansi::AnsiRenderer;

mod handlers;

pub(crate) enum SlashCommandControl {
    Continue,
    SubmitPrompt(String),
    ReplaceInput(String),
    BreakWithReason(SessionEndReason),
}

pub(crate) struct SlashCommandContext<'a> {
    pub(crate) thread_id: &'a str,
    pub(crate) active_thread_label: &'a str,
    pub(crate) thread_handle: &'a vtcode_core::core::threads::ThreadRuntimeHandle,
    pub(crate) renderer: &'a mut AnsiRenderer,
    pub(crate) handle: &'a InlineHandle,
    pub(crate) session: &'a mut InlineSession,
    pub(crate) header_context: &'a mut InlineHeaderContext,
    pub(crate) ide_context_bridge: &'a mut Option<IdeContextBridge>,
    pub(crate) config: &'a mut CoreAgentConfig,
    pub(crate) vt_cfg: &'a mut Option<VTCodeConfig>,
    pub(crate) provider_client: &'a mut Box<dyn uni::LLMProvider>,
    pub(crate) session_bootstrap: &'a SessionBootstrap,
    pub(crate) model_picker_state: &'a mut Option<ModelPickerState>,
    pub(crate) palette_state: &'a mut Option<ActivePalette>,
    pub(crate) tool_registry: &'a mut ToolRegistry,
    pub(crate) conversation_history: &'a mut Vec<uni::Message>,
    pub(crate) decision_ledger: &'a Arc<RwLock<DecisionTracker>>,
    pub(crate) context_manager: &'a mut ContextManager,
    pub(crate) session_stats: &'a mut SessionStats,
    pub(crate) input_status_state: &'a mut InputStatusState,
    pub(crate) tools: &'a Arc<RwLock<Vec<uni::ToolDefinition>>>,
    pub(crate) tool_catalog: &'a Arc<ToolCatalogState>,
    pub(crate) async_mcp_manager: Option<&'a Arc<AsyncMcpManager>>,
    pub(crate) mcp_panel_state: &'a mut mcp_events::McpPanelState,
    pub(crate) linked_directories: &'a mut Vec<LinkedDirectory>,
    pub(crate) ctrl_c_state: &'a Arc<CtrlCState>,
    pub(crate) ctrl_c_notify: &'a Arc<Notify>,
    pub(crate) full_auto: bool,
    pub(crate) loaded_skills:
        &'a Arc<RwLock<hashbrown::HashMap<String, vtcode_core::skills::types::Skill>>>,
    pub(crate) checkpoint_manager: Option<&'a vtcode_core::core::agent::snapshots::SnapshotManager>,
    pub(crate) lifecycle_hooks: Option<&'a LifecycleHookEngine>,
    pub(crate) harness_emitter: Option<&'a HarnessEventEmitter>,
}

impl<'a> SlashCommandContext<'a> {
    pub(crate) fn reborrow(&mut self) -> SlashCommandContext<'_> {
        SlashCommandContext {
            thread_id: self.thread_id,
            active_thread_label: self.active_thread_label,
            thread_handle: self.thread_handle,
            renderer: self.renderer,
            handle: self.handle,
            session: self.session,
            header_context: self.header_context,
            ide_context_bridge: self.ide_context_bridge,
            config: self.config,
            vt_cfg: self.vt_cfg,
            provider_client: self.provider_client,
            session_bootstrap: self.session_bootstrap,
            model_picker_state: self.model_picker_state,
            palette_state: self.palette_state,
            tool_registry: self.tool_registry,
            conversation_history: self.conversation_history,
            decision_ledger: self.decision_ledger,
            context_manager: self.context_manager,
            session_stats: self.session_stats,
            input_status_state: self.input_status_state,
            tools: self.tools,
            tool_catalog: self.tool_catalog,
            async_mcp_manager: self.async_mcp_manager,
            mcp_panel_state: self.mcp_panel_state,
            linked_directories: self.linked_directories,
            ctrl_c_state: self.ctrl_c_state,
            ctrl_c_notify: self.ctrl_c_notify,
            full_auto: self.full_auto,
            loaded_skills: self.loaded_skills,
            checkpoint_manager: self.checkpoint_manager,
            lifecycle_hooks: self.lifecycle_hooks,
            harness_emitter: self.harness_emitter,
        }
    }
}

pub(crate) async fn run_with_event_loop_suspended<T, F>(
    handle: &InlineHandle,
    suspend_tui: bool,
    launch: F,
) -> Result<T>
where
    F: FnOnce() -> Result<T>,
{
    handlers::run_with_event_loop_suspended(handle, suspend_tui, launch).await
}

pub(crate) async fn handle_outcome(
    outcome: SlashCommandOutcome,
    ctx: SlashCommandContext<'_>,
) -> Result<SlashCommandControl> {
    match outcome {
        SlashCommandOutcome::SubmitPrompt { prompt } => {
            Ok(SlashCommandControl::SubmitPrompt(prompt))
        }
        SlashCommandOutcome::ReplaceInput { content } => {
            Ok(SlashCommandControl::ReplaceInput(content))
        }
        SlashCommandOutcome::Handled => Ok(SlashCommandControl::Continue),
        SlashCommandOutcome::ThemeChanged(theme_id) => {
            handlers::handle_theme_changed(ctx, theme_id).await
        }
        SlashCommandOutcome::StartThemePalette { mode } => {
            handlers::handle_start_theme_palette(ctx, mode).await
        }
        SlashCommandOutcome::StartSessionPalette {
            mode,
            limit,
            show_all,
        } => handlers::handle_start_session_palette(ctx, mode, limit, show_all).await,
        SlashCommandOutcome::StartHistoryPicker => handlers::handle_start_history_picker(ctx).await,
        SlashCommandOutcome::StartFileBrowser { initial_filter } => {
            handlers::handle_start_file_browser(ctx, initial_filter).await
        }
        SlashCommandOutcome::StartStatuslineSetup { instructions } => {
            handlers::handle_start_statusline_setup(ctx, instructions).await
        }
        SlashCommandOutcome::StartTerminalTitleSetup => {
            handlers::handle_start_terminal_title_setup(ctx).await
        }
        SlashCommandOutcome::StartModelSelection => {
            handlers::handle_start_model_selection(ctx).await
        }
        SlashCommandOutcome::SetEffort { level, persist } => {
            handlers::handle_set_effort(ctx, level, persist).await
        }
        SlashCommandOutcome::ToggleIdeContext => handlers::handle_toggle_ide_context(ctx).await,
        SlashCommandOutcome::InitializeWorkspace { force } => {
            handlers::handle_initialize_workspace(ctx, force).await
        }
        SlashCommandOutcome::ShowSettings => handlers::handle_show_settings(ctx).await,
        SlashCommandOutcome::ShowSettingsAtPath { path } => {
            handlers::handle_show_settings_at_path(ctx, Some(&path)).await
        }
        SlashCommandOutcome::ShowHooks => handlers::handle_show_hooks(ctx).await,
        SlashCommandOutcome::ShowMemoryConfig => handlers::handle_show_memory_config(ctx).await,
        SlashCommandOutcome::ShowPermissions => handlers::handle_show_permissions(ctx).await,
        SlashCommandOutcome::ShowMemory => handlers::handle_show_memory(ctx).await,
        SlashCommandOutcome::ClearScreen => handlers::handle_clear_screen(ctx).await,
        SlashCommandOutcome::ClearConversation => handlers::handle_clear_conversation(ctx).await,
        SlashCommandOutcome::CompactConversation { command } => {
            handlers::handle_compact_conversation(ctx, command).await
        }
        SlashCommandOutcome::CopyLatestAssistantReply => {
            handlers::handle_copy_latest_assistant_reply(ctx).await
        }
        SlashCommandOutcome::TriggerPromptSuggestions => {
            handlers::handle_trigger_prompt_suggestions(ctx).await
        }
        SlashCommandOutcome::ToggleTasksPanel => handlers::handle_toggle_tasks_panel(ctx).await,
        SlashCommandOutcome::ShowJobsPanel => handlers::handle_show_jobs_panel(ctx).await,
        SlashCommandOutcome::ShowStatus => handlers::handle_show_status(ctx).await,
        SlashCommandOutcome::Notify { message } => handlers::handle_notify(ctx, message).await,
        SlashCommandOutcome::StopAgent => handlers::handle_stop_agent(ctx).await,
        SlashCommandOutcome::ManageMcp { action } => handlers::handle_manage_mcp(ctx, action).await,

        SlashCommandOutcome::StartDoctorInteractive => {
            handlers::handle_start_doctor_interactive(ctx).await
        }
        SlashCommandOutcome::RunDoctor { quick } => handlers::handle_run_doctor(ctx, quick).await,
        SlashCommandOutcome::Update {
            check_only,
            install,
            force,
        } => handlers::handle_update(ctx, check_only, install, force).await,
        SlashCommandOutcome::StartTerminalSetup => handlers::handle_start_terminal_setup(ctx).await,
        SlashCommandOutcome::ManageLoop { command } => {
            handlers::handle_manage_loop(ctx, command).await
        }
        SlashCommandOutcome::ManageSchedule { action } => {
            handlers::handle_manage_schedule(ctx, action).await
        }
        SlashCommandOutcome::NewSession => handlers::handle_new_session(ctx).await,
        SlashCommandOutcome::OpenDocs => handlers::handle_open_docs(ctx).await,
        SlashCommandOutcome::LaunchEditor { file } => {
            handlers::handle_launch_editor(ctx, file).await
        }
        SlashCommandOutcome::LaunchGit => handlers::handle_launch_git(ctx).await,
        SlashCommandOutcome::ManageSkills { action } => {
            handlers::handle_manage_skills(ctx, action).await
        }
        SlashCommandOutcome::ManageAgents { action } => {
            handlers::handle_manage_agents(ctx, action).await
        }
        SlashCommandOutcome::ManageSubprocesses { action } => {
            handlers::handle_manage_subprocesses(ctx, action).await
        }
        SlashCommandOutcome::OpenRewindPicker => handlers::handle_open_rewind_picker(ctx).await,
        SlashCommandOutcome::RewindToTurn { turn, scope } => {
            handlers::handle_rewind_to_turn(ctx, turn, scope).await
        }
        SlashCommandOutcome::RewindLatest { scope } => {
            handlers::handle_rewind_latest(ctx, scope).await
        }
        SlashCommandOutcome::TogglePlanMode { enable, prompt } => {
            let control = handlers::handle_toggle_plan_mode(ctx, enable).await?;
            if matches!(control, SlashCommandControl::Continue)
                && let Some(prompt) = prompt
            {
                return Ok(SlashCommandControl::SubmitPrompt(prompt));
            }
            Ok(control)
        }
        SlashCommandOutcome::StartModeSelection => handlers::handle_start_mode_selection(ctx).await,
        SlashCommandOutcome::SetMode { mode } => handlers::handle_set_mode(ctx, mode).await,
        SlashCommandOutcome::CycleMode => handlers::handle_cycle_mode(ctx).await,
        SlashCommandOutcome::OAuthLogin { provider } => {
            handlers::handle_oauth_login(ctx, provider).await
        }
        SlashCommandOutcome::StartOAuthProviderPicker { action } => {
            handlers::handle_start_oauth_provider_picker(ctx, action).await
        }
        SlashCommandOutcome::OAuthLogout { provider } => {
            handlers::handle_oauth_logout(ctx, provider).await
        }
        SlashCommandOutcome::RefreshOAuth { provider } => {
            handlers::handle_refresh_oauth(ctx, provider).await
        }
        SlashCommandOutcome::ShowAuthStatus { provider } => {
            handlers::handle_show_auth_status(ctx, provider).await
        }
        SlashCommandOutcome::ShareLog { format } => handlers::handle_share_log(ctx, format).await,
        SlashCommandOutcome::Exit => handlers::handle_exit(ctx).await,
    }
}