vtcode 0.138.0

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use anyhow::Result;
use std::sync::Arc;
use tracing::warn;
use vtcode_core::utils::ansi::MessageStyle;

use crate::agent::runloop::unified::async_mcp_manager::McpInitStatus;
use crate::agent::runloop::unified::session_setup::{active_deferred_tool_policy, refresh_tool_snapshot};

use super::SlashCommandContext;

pub(super) async fn ensure_skills_context_activated(ctx: &SlashCommandContext<'_>) -> Result<()> {
    let Some(vt_cfg) = ctx.vt_cfg.as_ref() else {
        return Ok(());
    };

    vtcode_core::context::ensure_skills_dynamic_context(&ctx.config.workspace, &vt_cfg.context.dynamic).await
}

pub(super) async fn ensure_mcp_activated(ctx: &mut SlashCommandContext<'_>) -> Result<()> {
    let Some(manager) = ctx.async_mcp_manager else {
        return Ok(());
    };

    if let Err(err) = manager.start_initialization() {
        warn!("Failed to start MCP initialization: {}", err);
        ctx.renderer
            .line(MessageStyle::Error, &format!("Failed to start MCP runtime: {err}"))?;
        return Ok(());
    }
    // Non-blocking: start initialization and return. Tool attachment happens in
    // async lifecycle updates, or immediately if already ready.
    let _ = try_attach_ready_mcp(ctx).await?;
    Ok(())
}

pub(super) async fn try_attach_ready_mcp(ctx: &mut SlashCommandContext<'_>) -> Result<bool> {
    let Some(manager) = ctx.async_mcp_manager else {
        return Ok(false);
    };

    match manager.get_status().await {
        McpInitStatus::Ready { client } => {
            if ctx.tool_registry.mcp_client().is_none() {
                ctx.tool_registry.set_mcp_client(Arc::clone(&client)).await;
                if let Err(err) = ctx.tool_registry.refresh_mcp_tools().await {
                    warn!("Failed to refresh MCP tools after activation: {}", err);
                } else {
                    let tool_documentation_mode = ctx
                        .vt_cfg
                        .as_ref()
                        .as_ref()
                        .map(|cfg| cfg.agent.tool_documentation_mode)
                        .unwrap_or_default();
                    let deferred_tool_policy =
                        active_deferred_tool_policy(ctx.config, ctx.vt_cfg.as_ref(), &**ctx.provider_client);
                    refresh_tool_snapshot(
                        ctx.tool_registry,
                        ctx.tools,
                        ctx.tool_catalog,
                        ctx.config,
                        ctx.vt_cfg.as_ref(),
                        tool_documentation_mode,
                        &deferred_tool_policy,
                    )
                    .await;
                    ctx.tool_catalog.mark_pending_refresh("mcp_background_refresh");
                }
                sync_mcp_context_files(ctx, &client).await?;
            }
            Ok(true)
        }
        McpInitStatus::Error { message } => {
            ctx.renderer
                .line(MessageStyle::Error, &format!("MCP activation failed: {message}"))?;
            Ok(false)
        }
        McpInitStatus::Disabled | McpInitStatus::Initializing { .. } => Ok(false),
    }
}

pub(super) async fn sync_mcp_context_files(
    ctx: &SlashCommandContext<'_>,
    client: &Arc<vtcode_core::mcp::McpClient>,
) -> Result<()> {
    let Some(vt_cfg) = ctx.vt_cfg.as_ref() else {
        return Ok(());
    };

    let dynamic_cfg = &vt_cfg.context.dynamic;
    if !dynamic_cfg.enabled || !dynamic_cfg.sync_mcp_tools {
        return Ok(());
    }

    vtcode_core::context::ensure_mcp_dynamic_context(&ctx.config.workspace, dynamic_cfg).await?;
    if let Err(err) = client.sync_tools_to_files(&ctx.config.workspace).await {
        warn!("Failed to sync MCP tools to dynamic context files: {}", err);
    }

    Ok(())
}