vtcode 0.125.1

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};

use anyhow::{Context, Result, anyhow, bail};
use vtcode_core::config::VTCodeConfig;
use vtcode_core::config::models::ModelId;
use vtcode_core::config::types::AgentConfig as CoreAgentConfig;
use vtcode_core::core::agent::runner::{AgentRunner, RunnerSettings};
use vtcode_core::core::agent::task::{ContextItem, Task};
use vtcode_core::core::agent::types::AgentType;
use vtcode_core::core::threads::ThreadBootstrap;
use vtcode_core::utils::colors::style;

use crate::startup::require_full_auto_workspace_trust;

const AUTO_SESSION_PREFIX: &str = "auto-task";
const AUTO_TASK_ID: &str = "auto-task";
const AUTO_TASK_TITLE: &str = "Autonomous Task";

pub async fn handle_auto_task_command(
    config: &CoreAgentConfig,
    vt_cfg: &VTCodeConfig,
    prompt: &str,
    primary_agent_explicitly_configured: bool,
) -> Result<()> {
    let trimmed = prompt.trim();
    if trimmed.is_empty() {
        bail!("Automation prompt is empty. Provide instructions after --auto/--full-auto.");
    }

    require_full_auto_workspace_trust(&config.workspace, "autonomous runs", "--auto/--full-auto")
        .await?;

    let automation_cfg = &vt_cfg.automation.full_auto;
    if !automation_cfg.enabled {
        bail!(
            "Automation is disabled in configuration. Enable [automation.full_auto] to continue."
        );
    }

    let primary_agent_runtime =
        crate::cli::full_auto_primary_agent::resolve_full_auto_primary_agent_runtime(
            &config.workspace,
            vt_cfg,
            primary_agent_explicitly_configured,
        )?;
    let run_vt_cfg = primary_agent_runtime.vt_cfg;
    let mut run_config = config.clone();
    run_config.model = run_vt_cfg.agent.default_model.clone();
    run_config.reasoning_effort = run_vt_cfg.agent.reasoning_effort;

    if run_config
        .provider
        .eq_ignore_ascii_case(crate::codex_app_server::CODEX_PROVIDER)
    {
        let completed = crate::codex_app_server::run_codex_noninteractive_with_instructions(
            &run_config,
            Some(&run_vt_cfg),
            crate::codex_app_server::CodexNonInteractiveRun {
                prompt: trimmed.to_string(),
                read_only: false,
                skip_confirmations: true,
                ephemeral: true,
                resume_thread_id: None,
                seed_messages: Vec::new(),
                review_target: None,
            },
            primary_agent_turn_instructions(&primary_agent_runtime.active_primary_agent),
        )
        .await?;
        if !completed.output.trim().is_empty() {
            println!("{}", completed.output.trim());
        }
        return Ok(());
    }

    let model_id = ModelId::from_str(&run_config.model).with_context(|| {
        format!(
            "Model '{}' is not recognized for autonomous execution. Update vtcode.toml to a \
             supported identifier.",
            run_config.model
        )
    })?;

    let session_id = format!(
        "{AUTO_SESSION_PREFIX}-{}",
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_err(|err| anyhow!("Failed to derive session identifier timestamp: {}", err))?
            .as_secs()
    );

    let mut runner = AgentRunner::new_with_bootstrap(
        AgentType::Single,
        model_id,
        run_config.api_key.clone(),
        run_config.workspace.clone(),
        session_id,
        RunnerSettings {
            reasoning_effort: Some(run_config.reasoning_effort),
            verbosity: None,
        },
        None,
        ThreadBootstrap::new(None),
        Some(run_vt_cfg),
        run_config.openai_chatgpt_auth.clone(),
    )
    .await?;
    runner.set_active_primary_agent(primary_agent_runtime.active_primary_agent);

    runner.enable_full_auto(&automation_cfg.allowed_tools).await;

    let task = Task {
        id: AUTO_TASK_ID.to_string(),
        title: AUTO_TASK_TITLE.to_string(),
        description: trimmed.to_string(),
        instructions: None,
    };

    let max_retries = vt_cfg.agent.max_task_retries;
    let result = runner
        .execute_task_with_retry(&task, &[] as &[ContextItem], max_retries)
        .await
        .context("Failed to execute autonomous task after retries")?;

    if !result.summary.trim().is_empty() {
        println!(
            "{} {}",
            style("[SUMMARY]").green().bold(),
            result.summary.trim()
        );
    }

    if !result.modified_files.is_empty() {
        println!(
            "{} {}",
            style("[FILES]").cyan().bold(),
            result.modified_files.join(", ")
        );
    }

    if !result.executed_commands.is_empty() {
        println!(
            "{} {}",
            style("[COMMANDS]").cyan().bold(),
            result.executed_commands.join(", ")
        );
    }

    if !result.warnings.is_empty() {
        for warning in result.warnings {
            println!("{} {}", style("[WARNING]").red().bold(), warning);
        }
    }

    Ok(())
}

fn primary_agent_turn_instructions(
    active_primary_agent: &vtcode_core::ActivePrimaryAgent,
) -> Option<String> {
    Some(active_primary_agent.instructions.trim().to_string())
        .filter(|instructions| !instructions.is_empty())
}