vtcode 0.137.0

A Rust-based terminal coding agent with modular architecture supporting multiple LLM providers
use anyhow::{Context, Result};
use std::path::Path;
use vtcode_config::{SubagentDiscoveryInput, SubagentSpec, discover_subagents};
use vtcode_core::config::VTCodeConfig;
use vtcode_core::{ActivePrimaryAgent, build_primary_agent_runtime_config};

use crate::agent::runloop::unified::session_setup::active_primary_agent_from_specs_for_mode;

#[derive(Debug)]
pub(crate) struct FullAutoPrimaryAgentRuntime {
    pub(crate) vt_cfg: VTCodeConfig,
    pub(crate) active_primary_agent: ActivePrimaryAgent,
}

pub(crate) fn resolve_full_auto_primary_agent_runtime(
    workspace: &Path,
    vt_cfg: &VTCodeConfig,
    primary_agent_explicitly_configured: bool,
) -> Result<FullAutoPrimaryAgentRuntime> {
    let discovered = discover_subagents(&SubagentDiscoveryInput::new(workspace.to_path_buf()))
        .with_context(|| format!("Failed to discover primary agents in {}", workspace.display()))?;

    resolve_full_auto_primary_agent_runtime_from_specs(
        &discovered.effective,
        vt_cfg,
        primary_agent_explicitly_configured,
    )
}

fn resolve_full_auto_primary_agent_runtime_from_specs(
    specs: &[SubagentSpec],
    vt_cfg: &VTCodeConfig,
    primary_agent_explicitly_configured: bool,
) -> Result<FullAutoPrimaryAgentRuntime> {
    let active =
        active_primary_agent_from_specs_for_mode(specs, Some(vt_cfg), true, primary_agent_explicitly_configured, None)?;
    let active_primary_agent = active.active().clone();
    let mut runtime_vt_cfg = build_primary_agent_runtime_config(vt_cfg, &active_primary_agent);
    runtime_vt_cfg.runtime_agent_permissions = Some(active_primary_agent.permissions.clone());

    Ok(FullAutoPrimaryAgentRuntime { vt_cfg: runtime_vt_cfg, active_primary_agent })
}

#[cfg(test)]
mod tests {
    use super::*;
    use vtcode_config::{
        builtin_primary_auto_agent, builtin_primary_build_agent,
        core::permissions::{AgentPermissionsConfig, PermissionDefault},
    };
    use vtcode_core::config::types::ReasoningEffortLevel;

    #[test]
    fn defaulted_full_auto_uses_effective_custom_auto() {
        let mut auto = builtin_primary_auto_agent();
        auto.prompt = "Custom auto instructions".to_string();
        auto.model = Some("gpt-5".to_string());
        auto.reasoning_effort = Some(ReasoningEffortLevel::High);
        auto.permissions = AgentPermissionsConfig::new(PermissionDefault::Deny);

        let resolved = resolve_full_auto_primary_agent_runtime_from_specs(&[auto], &VTCodeConfig::default(), false)
            .expect("defaulted auto should resolve");

        assert_eq!(resolved.active_primary_agent.identity.name, "auto");
        assert_eq!(resolved.active_primary_agent.instructions, "Custom auto instructions");
        assert_eq!(resolved.vt_cfg.agent.default_model, "gpt-5");
        assert_eq!(resolved.vt_cfg.agent.reasoning_effort, ReasoningEffortLevel::High);
        assert_eq!(
            resolved
                .vt_cfg
                .runtime_agent_permissions
                .as_ref()
                .expect("agent permissions")
                .default,
            PermissionDefault::Deny
        );
    }

    #[test]
    fn explicit_missing_primary_for_full_auto_falls_back_to_build_with_notice() {
        let cfg = VTCodeConfig {
            default_primary_agent: "duck".to_string(),
            ..VTCodeConfig::default()
        };

        let resolved = resolve_full_auto_primary_agent_runtime_from_specs(&[builtin_primary_auto_agent()], &cfg, true)
            .expect("explicit duck should resolve");

        // "duck" is not in the provided specs, so the resolver falls back to
        // the built-in "build" agent. Unlike the previously silent behaviour,
        // `from_specs_with_default` now emits a `tracing::warn!` fallback
        // notice (see `fallback_notice` in `vtcode-core::primary_agent`) so the
        // operator learns the configured "duck" agent was dropped under
        // full-auto. The behaviour here is unchanged: build is selected.
        assert_eq!(resolved.active_primary_agent.identity.name, "build");
        assert_eq!(
            resolved
                .vt_cfg
                .runtime_agent_permissions
                .as_ref()
                .expect("agent permissions")
                .default,
            PermissionDefault::Ask
        );
    }

    #[test]
    fn missing_defaulted_auto_fails_fast() {
        let err = resolve_full_auto_primary_agent_runtime_from_specs(
            &[builtin_primary_build_agent()],
            &VTCodeConfig::default(),
            false,
        )
        .expect_err("missing auto should fail");

        assert!(
            err.to_string()
                .contains("no effective primary agent named 'auto' was discovered")
        );
    }
}