use std::collections::VecDeque;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::time::{Duration, Instant};
use hashbrown::{HashMap, HashSet};
use tokio::sync::RwLock;
use vtcode_config::core::permissions::AgentPermissionsConfig;
use vtcode_core::acp::ToolPermissionCache;
use vtcode_core::config::PermissionsConfig;
use vtcode_core::config::loader::VTCodeConfig;
use vtcode_core::config::types::AgentConfig as CoreAgentConfig;
use vtcode_core::core::decision_tracker::DecisionTracker;
use vtcode_core::core::trajectory::TrajectoryLogger;
use vtcode_core::llm::provider as uni;
use vtcode_core::tools::{ApprovalRecorder, ToolRegistry, ToolResultCache};
use vtcode_core::types::CompactStr;
use vtcode_core::utils::ansi::AnsiRenderer;
use vtcode_ui::tui::app::{InlineHandle, InlineSession};
use crate::agent::runloop::mcp_events::McpPanelState;
use crate::agent::runloop::unified::inline_events::harness::HarnessEventEmitter;
use crate::agent::runloop::unified::planning_workflow_state::PlanningWorkflowSessionState;
use crate::agent::runloop::unified::state::SessionStats;
use crate::agent::runloop::unified::tool_call_safety::ToolCallSafetyValidator;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TurnRunId(pub String);
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TurnId(pub String);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum TurnPhase {
Preparing,
Requesting,
ExecutingTools,
Finalizing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RecoveryPhase {
Inactive,
Pending,
InPass,
Completed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RecoveryMode {
ToolEnabledRetry,
ToolFreeSynthesis,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ToolBudgetWarning {
pub used: usize,
pub max: usize,
pub remaining: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ToolBudgetExhaustion {
pub used: usize,
pub max: usize,
pub remaining: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ToolBudgetExhaustionNotice {
pub exhaustion: ToolBudgetExhaustion,
pub first_notice: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ToolWallClockExhaustion {
pub max_secs: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ToolWallClockExhaustionNotice {
pub exhaustion: ToolWallClockExhaustion,
pub first_notice: bool,
}
pub(crate) const TOOL_BUDGET_WARNING_THRESHOLD: f64 = 0.75;
pub(crate) const SESSION_LIMIT_GRANT_DIRECTIVE: &str = "Session tool-call limit increased by the user. Continue this same turn with the current agent, retry the pending call, and reuse existing tool outputs instead of repeating exploration.";
pub(crate) const SESSION_LIMIT_AUTO_GRANT_INCREMENT: usize = 100;
#[inline]
pub(crate) fn full_auto_loop_grants_enabled(full_auto: bool, vt_cfg: Option<&VTCodeConfig>) -> bool {
full_auto
&& vt_cfg.is_some_and(|cfg| cfg.automation.full_auto.enabled && cfg.automation.full_auto.auto_grant_tool_limits)
}
const TOOL_PREVIEW_METADATA_MAX_DEPTH: usize = 8;
const TOOL_PREVIEW_METADATA_STRING_LIMIT: usize = 512;
const TOOL_PREVIEW_METADATA_PARSE_LIMIT_BYTES: usize = 128 * 1024;
const AUX_MODEL_CALL_BUDGET: u32 = 3;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TurnExecutionSnapshot {
pub run_id: String,
pub turn_id: String,
pub phase: TurnPhase,
pub max_tool_calls: usize,
pub max_tool_wall_clock_secs: u64,
pub max_tool_retries: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct BlockedToolRecoveryTelemetry {
pub(crate) last_tool: String,
pub(crate) consecutive_cap: usize,
pub(crate) total_cap: usize,
pub(crate) blocked_streak: usize,
pub(crate) blocked_total: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct StreamedToolCallItem {
pub(crate) item_id: String,
pub(crate) tool_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct DiagnosisMemoKey {
pub tool: CompactStr,
pub evidence: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DiagnosisMemoEntry {
pub observed: CompactStr,
pub likely_cause: CompactStr,
pub next_action: CompactStr,
}
pub(crate) struct HarnessTurnState {
pub run_id: TurnRunId,
pub turn_id: TurnId,
pub phase: TurnPhase,
pub turn_started_at: Instant,
wait_started_at: Option<Instant>,
excluded_wait_duration: Duration,
pub tool_calls: usize,
requested_tool_calls: u32,
admitted_tool_calls: u32,
failed_tool_calls: u32,
denied_tool_calls: u32,
preflight_failures: u32,
reused_results: u32,
spooled_results: u32,
raw_spooled_bytes: u64,
model_visible_output_bytes: u64,
model_visible_tool_preview_budget_exhausted: bool,
suppressed_tool_previews: u32,
suppressed_tool_call_ids: HashSet<String>,
recovery_activations: u32,
pub blocked_tool_calls: usize,
pub consecutive_blocked_tool_calls: usize,
pub consecutive_preflight_failures: usize,
pub consecutive_assistant_text_responses: u32,
out_of_band_tool_progress: bool,
final_response_rendered: bool,
final_response_event_emitted: bool,
streamed_response_event_emitted: bool,
final_response_was_fallback: bool,
turn_refused: bool,
pub consecutive_spool_chunk_reads: usize,
pub consecutive_same_shell_command_runs: usize,
pub last_shell_command_signature: Option<String>,
pub last_admitted_shell_command_signature: Option<String>,
last_failed_shell_key: Option<String>,
pub consecutive_same_file_read_family_calls: usize,
last_file_read_family_signature: Option<String>,
file_read_path_counts: HashMap<String, usize>,
claimed_patch_recovery_paths: HashSet<std::path::PathBuf>,
pub(crate) seen_successful_readonly_signatures: HashSet<String>,
streamed_tool_call_item_ids: HashMap<String, StreamedToolCallItem>,
failure_diagnosis_memo: HashMap<DiagnosisMemoKey, DiagnosisMemoEntry>,
failure_diagnosis_model_calls: u32,
auto_permission_probe_model_calls: u32,
pub stop_hook_active: bool,
pub seen_task_tracker_create_signatures: HashSet<String>,
pub recently_written_files: HashSet<String>,
pub tool_budget_warning_emitted: bool,
pub tool_budget_exhausted_emitted: bool,
pub wall_clock_exhausted_emitted: bool,
tool_budget_rejection_pending: bool,
pub wall_clock_directive_pending: bool,
pub tool_budget_directive_pending: bool,
session_limit_grant_directive_pending: bool,
session_limit_granted: bool,
pending_auto_permission_probe_warning: Option<String>,
pub recovery_reason: Option<String>,
recovery_prompt_reason: Option<String>,
recovery_phase: RecoveryPhase,
recovery_mode: Option<RecoveryMode>,
recovery_retry_count: u8,
post_tool_compaction_pending: bool,
post_tool_context_capacity_failure: bool,
post_tool_context_compaction_failed: bool,
post_tool_tool_enabled_retry_used: bool,
post_tool_recovery_cycles: u8,
recovery_rejected_synthesis: Option<String>,
approved_plan_execution: bool,
approved_plan_recovery_retries: u8,
interview_denial_recovery_pending: bool,
preflight_circuit_recovery_pending: bool,
blocked_tool_recovery_pending: bool,
blocked_tool_recovery_reason: Option<String>,
blocked_tool_recovery_telemetry: Option<BlockedToolRecoveryTelemetry>,
pub max_tool_calls: usize,
pub max_tool_wall_clock: Duration,
pub max_tool_retries: u32,
pub consecutive_relaxed_continuations: u32,
incomplete_tracker_items_cache: Option<Vec<String>>,
}
pub(crate) struct RunLoopContext<'a> {
pub renderer: &'a mut AnsiRenderer,
pub handle: &'a InlineHandle,
pub tool_registry: &'a mut ToolRegistry,
pub tool_result_cache: &'a Arc<RwLock<ToolResultCache>>,
pub tool_permission_cache: &'a Arc<RwLock<ToolPermissionCache>>,
pub permissions_state: &'a Arc<RwLock<PermissionsConfig>>,
pub decision_ledger: &'a Arc<RwLock<DecisionTracker>>,
pub session_stats: &'a mut SessionStats,
pub plan_session: &'a mut PlanningWorkflowSessionState,
pub mcp_panel_state: &'a mut McpPanelState,
pub approval_recorder: &'a ApprovalRecorder,
pub session: &'a mut InlineSession,
pub safety_validator: Option<&'a Arc<ToolCallSafetyValidator>>,
pub traj: &'a TrajectoryLogger,
pub harness_state: &'a mut HarnessTurnState,
pub harness_emitter: Option<&'a HarnessEventEmitter>,
pub auto_permission: Option<AutoPermissionRuntimeContext<'a>>,
pub skip_confirmations: bool,
pub full_auto: bool,
pub active_agent_permissions: Option<&'a AgentPermissionsConfig>,
pub agent_name: Option<String>,
pub default_primary_agent: Option<String>,
pub is_subagent: bool,
}
pub(crate) struct AutoPermissionRuntimeContext<'a> {
pub config: &'a CoreAgentConfig,
pub vt_cfg: Option<&'a VTCodeConfig>,
pub provider_client: &'a mut dyn uni::LLMProvider,
pub working_history: &'a [uni::Message],
}
impl<'a> RunLoopContext<'a> {
#[expect(
clippy::too_many_arguments,
reason = "Intentional compatibility, platform, test, or API-shape suppression."
)]
pub(crate) fn new(
renderer: &'a mut AnsiRenderer,
handle: &'a InlineHandle,
tool_registry: &'a mut ToolRegistry,
_tools: &'a Arc<RwLock<Vec<uni::ToolDefinition>>>,
tool_result_cache: &'a Arc<RwLock<ToolResultCache>>,
tool_permission_cache: &'a Arc<RwLock<ToolPermissionCache>>,
permissions_state: &'a Arc<RwLock<PermissionsConfig>>,
decision_ledger: &'a Arc<RwLock<DecisionTracker>>,
session_stats: &'a mut SessionStats,
plan_session: &'a mut PlanningWorkflowSessionState,
mcp_panel_state: &'a mut McpPanelState,
approval_recorder: &'a ApprovalRecorder,
session: &'a mut InlineSession,
safety_validator: Option<&'a Arc<ToolCallSafetyValidator>>,
traj: &'a TrajectoryLogger,
harness_state: &'a mut HarnessTurnState,
harness_emitter: Option<&'a HarnessEventEmitter>,
) -> Self {
Self::new_with_auto_permission_context(
renderer,
handle,
tool_registry,
_tools,
tool_result_cache,
tool_permission_cache,
permissions_state,
decision_ledger,
session_stats,
plan_session,
mcp_panel_state,
approval_recorder,
session,
safety_validator,
traj,
harness_state,
harness_emitter,
None,
false,
false,
)
}
#[expect(
clippy::too_many_arguments,
reason = "Intentional compatibility, platform, test, or API-shape suppression."
)]
pub(crate) fn new_with_auto_permission_context(
renderer: &'a mut AnsiRenderer,
handle: &'a InlineHandle,
tool_registry: &'a mut ToolRegistry,
_tools: &'a Arc<RwLock<Vec<uni::ToolDefinition>>>,
tool_result_cache: &'a Arc<RwLock<ToolResultCache>>,
tool_permission_cache: &'a Arc<RwLock<ToolPermissionCache>>,
permissions_state: &'a Arc<RwLock<PermissionsConfig>>,
decision_ledger: &'a Arc<RwLock<DecisionTracker>>,
session_stats: &'a mut SessionStats,
plan_session: &'a mut PlanningWorkflowSessionState,
mcp_panel_state: &'a mut McpPanelState,
approval_recorder: &'a ApprovalRecorder,
session: &'a mut InlineSession,
safety_validator: Option<&'a Arc<ToolCallSafetyValidator>>,
traj: &'a TrajectoryLogger,
harness_state: &'a mut HarnessTurnState,
harness_emitter: Option<&'a HarnessEventEmitter>,
auto_permission: Option<AutoPermissionRuntimeContext<'a>>,
skip_confirmations: bool,
full_auto: bool,
) -> Self {
Self {
renderer,
handle,
tool_registry,
tool_result_cache,
tool_permission_cache,
permissions_state,
decision_ledger,
session_stats,
plan_session,
mcp_panel_state,
approval_recorder,
session,
safety_validator,
traj,
harness_state,
harness_emitter,
auto_permission,
skip_confirmations,
full_auto,
active_agent_permissions: None,
agent_name: None,
default_primary_agent: None,
is_subagent: false,
}
}
}
pub(crate) use budget::*;
pub(crate) use cross_turn_tracker::*;
mod budget;
mod cross_turn_tracker;
mod harness_state;
use preview_bounds::*;
mod preview_bounds;
#[cfg(test)]
mod tests;