Codex Handbook
core/src/session/mod.rs 3589 lines
use std::borrow::Cow;use std::collections::BTreeMap;use std::collections::HashMap;use std::collections::HashSet;use std::fmt::Debug;use std::path::Path;use std::path::PathBuf;use std::sync::Arc;use std::sync::atomic::AtomicU64;use std::time::SystemTime;use std::time::UNIX_EPOCH;use crate::agent::AgentControl;use crate::agent::AgentStatus;use crate::agent::agent_status_from_event;use crate::agent::status::is_final;use crate::agents_md::LoadedAgentsMd;use crate::attestation::AttestationProvider;use crate::build_available_skills;use crate::compact;use crate::config::ManagedFeatures;use crate::config::resolve_tool_suggest_config_from_layer_stack;use crate::connectors;use crate::context::ApprovedCommandPrefixSaved;use crate::context::AppsInstructions;use crate::context::AvailablePluginsInstructions;use crate::context::AvailableSkillsInstructions;use crate::context::CollaborationModeInstructions;use crate::context::ContextualUserFragment;use crate::context::NetworkRuleSaved;use crate::context::PermissionsInstructions;use crate::context::PersonalitySpecInstructions;use crate::default_skill_metadata_budget;use crate::environment_selection::TurnEnvironmentSnapshot;use crate::exec_policy::ExecPolicyManager;use crate::image_preparation::prepare_response_items;use crate::parse_turn_item;use crate::realtime_conversation::RealtimeConversationManager;use crate::session::turn_context::TurnEnvironment;use crate::session_prefix::format_inter_agent_completion_message;use crate::skills::SkillRenderSideEffects;use crate::skills_load_input_from_config;use crate::turn_metadata::TurnMetadataState;use crate::turn_timing::now_unix_timestamp_ms;use async_channel::Receiver;use async_channel::Sender;use chrono::Local;use chrono::Utc;use codex_analytics::AnalyticsEventsClient;use codex_analytics::SubAgentThreadStartedInput;use codex_analytics::TurnCodexErrorFact;use codex_app_server_protocol::McpServerElicitationRequest;use codex_app_server_protocol::McpServerElicitationRequestParams;use codex_config::types::AuthKeyringBackendKind;use codex_config::types::OAuthCredentialsStoreMode;use codex_exec_server::Environment;use codex_exec_server::EnvironmentManager;use codex_extension_api::ExtensionDataInit;use codex_extension_api::LoadedUserInstructions;use codex_extension_api::PromptSlot;use codex_features::FEATURES;use codex_features::Feature;use codex_features::unstable_features_warning_event;use codex_hooks::Hooks;use codex_hooks::HooksConfig;use codex_login::AuthManager;use codex_login::CodexAuth;use codex_login::auth_env_telemetry::collect_auth_env_telemetry;use codex_login::default_client::originator;use codex_mcp::McpConnectionManager;use codex_mcp::McpResourceClient;use codex_mcp::McpRuntimeContext;use codex_mcp::codex_apps_tools_cache_key;use codex_models_manager::manager::RefreshStrategy;use codex_models_manager::manager::SharedModelsManager;use codex_network_proxy::NetworkProxy;use codex_network_proxy::NetworkProxyAuditMetadata;use codex_network_proxy::normalize_host;use codex_otel::current_span_trace_id;use codex_otel::current_span_w3c_trace_context;use codex_otel::set_parent_from_w3c_trace_context;use codex_protocol::SessionId;use codex_protocol::ThreadId;use codex_protocol::approvals::ElicitationRequestEvent;use codex_protocol::approvals::ExecPolicyAmendment;use codex_protocol::approvals::NetworkPolicyAmendment;use codex_protocol::approvals::NetworkPolicyRuleAction;use codex_protocol::config_types::ApprovalsReviewer;use codex_protocol::config_types::AutoCompactTokenLimitScope;use codex_protocol::config_types::ModeKind;use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;use codex_protocol::config_types::Settings;use codex_protocol::config_types::WebSearchMode;use codex_protocol::dynamic_tools::DynamicToolResponse;use codex_protocol::dynamic_tools::DynamicToolSpec;use codex_protocol::items::TurnItem;use codex_protocol::items::UserMessageItem;use codex_protocol::mcp::CallToolResult;use codex_protocol::models::ActivePermissionProfile;use codex_protocol::models::AdditionalPermissionProfile;use codex_protocol::models::BaseInstructions;use codex_protocol::models::PermissionProfile;use codex_protocol::models::SandboxEnforcement;use codex_protocol::models::format_allow_prefixes;use codex_protocol::openai_models::ModelInfo;use codex_protocol::openai_models::ModelPreset;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::protocol::AdditionalContextEntry;use codex_protocol::protocol::FileChange;use codex_protocol::protocol::HasLegacyEvent;use codex_protocol::protocol::InterAgentCommunication;use codex_protocol::protocol::ItemCompletedEvent;use codex_protocol::protocol::ItemStartedEvent;use codex_protocol::protocol::MultiAgentVersion;use codex_protocol::protocol::RawResponseItemEvent;use codex_protocol::protocol::ReviewRequest;use codex_protocol::protocol::RolloutItem;use codex_protocol::protocol::SessionSource;use codex_protocol::protocol::SubAgentSource;use codex_protocol::protocol::ThreadSource;use codex_protocol::protocol::TurnAbortReason;use codex_protocol::protocol::TurnContextItem;use codex_protocol::protocol::TurnContextNetworkItem;use codex_protocol::protocol::TurnEnvironmentSelection;use codex_protocol::protocol::TurnEnvironmentSelections;use codex_protocol::protocol::W3cTraceContext;use codex_protocol::request_permissions::PermissionGrantScope;use codex_protocol::request_permissions::RequestPermissionProfile;use codex_protocol::request_permissions::RequestPermissionsArgs;use codex_protocol::request_permissions::RequestPermissionsEvent;use codex_protocol::request_permissions::RequestPermissionsResponse;use codex_protocol::request_user_input::RequestUserInputArgs;use codex_protocol::request_user_input::RequestUserInputResponse;use codex_rmcp_client::ElicitationResponse;use codex_rollout::state_db;use codex_rollout_trace::AgentResultTracePayload;use codex_rollout_trace::ThreadStartedTraceMetadata;use codex_rollout_trace::ThreadTraceContext;use codex_sandboxing::policy_transforms::intersect_permission_profiles;use codex_shell_command::parse_command::parse_command;use codex_terminal_detection::user_agent;use codex_thread_store::CreateThreadParams;use codex_thread_store::LiveThread;use codex_thread_store::LiveThreadInitGuard;use codex_thread_store::LocalThreadStore;use codex_thread_store::ReadThreadParams;use codex_thread_store::ResumeThreadParams;use codex_thread_store::ThreadPersistenceMetadata;use codex_thread_store::ThreadStore;use codex_utils_output_truncation::TruncationPolicy;use codex_utils_path_uri::PathUri;use futures::future::BoxFuture;use futures::future::Shared;use futures::prelude::*;use rmcp::model::ElicitationCapability;use rmcp::model::FormElicitationCapability;use rmcp::model::ListResourceTemplatesResult;use rmcp::model::ListResourcesResult;use rmcp::model::PaginatedRequestParams;use rmcp::model::ReadResourceRequestParams;use rmcp::model::ReadResourceResult;use rmcp::model::RequestId;use rmcp::model::UrlElicitationCapability;use serde_json::Value;use tokio::sync::Mutex;use tokio::sync::RwLock;use tokio::sync::oneshot;use tokio::sync::watch;use tokio::task::JoinHandle;use tokio_util::sync::CancellationToken;use toml::Value as TomlValue;use tracing::Instrument;use tracing::debug;use tracing::error;use tracing::info;use tracing::info_span;use tracing::instrument;use tracing::warn;use uuid::Uuid;use crate::client::ModelClient;use crate::codex_thread::ThreadConfigSnapshot;#[cfg(test)]use crate::compact::collect_user_messages;use crate::config::Config;use crate::config::Constrained;use crate::config::ConstraintResult;use crate::config::PermissionProfileSnapshot;use crate::config::PermissionProfileState;use crate::config::StartedNetworkProxy;use crate::config::resolve_web_search_mode_for_turn;use crate::context_manager::ContextManager;use crate::thread_rollout_truncation::initial_history_has_prior_user_turns;use codex_config::CONFIG_TOML_FILE;use codex_config::ConfigLayerSource;use codex_config::ConfigLayerStackOrdering;use codex_config::types::McpServerConfig;use codex_model_provider_info::ModelProviderInfo;use codex_protocol::config_types::ShellEnvironmentPolicy;use codex_protocol::error::CodexErr;use codex_protocol::error::Result as CodexResult;#[cfg(test)]use codex_protocol::exec_output::StreamOutput;mod config_lock;mod handlers;mod inject;mod input_queue;mod mcp;mod multi_agents;mod review;mod rollout_reconstruction;#[allow(clippy::module_inception)]pub(crate) mod session;mod token_budget;pub(crate) mod turn;pub(crate) mod turn_context;use self::config_lock::export_config_lock_if_configured;use self::config_lock::validate_config_lock_if_configured;#[cfg(test)]use self::handlers::submission_dispatch_span;use self::handlers::submission_loop;pub(crate) use self::input_queue::InputQueueActivity;pub(crate) use self::input_queue::TurnInput;pub(crate) use self::input_queue::TurnInputQueue;use self::review::spawn_review_thread;use self::session::AppServerClientMetadata;use self::session::Session;use self::session::SessionConfiguration;pub(crate) use self::session::SessionSettingsUpdate;#[cfg(test)]use self::turn::AssistantMessageStreamParsers;#[cfg(test)]use self::turn::collect_explicit_app_ids_from_skill_items;use self::turn::realtime_text_for_event;use self::turn_context::TurnContext;use self::turn_context::TurnSkillsContext;#[cfg(test)]mod rollout_reconstruction_tests;#[derive(Debug, PartialEq)]pub enum SteerInputError {    NoActiveTurn(Vec<UserInput>),    ExpectedTurnMismatch { expected: String, actual: String },    ActiveTurnNotSteerable { turn_kind: NonSteerableTurnKind },    EmptyInput,}impl SteerInputError {    fn to_error_event(&self) -> ErrorEvent {        match self {            Self::NoActiveTurn(_) => ErrorEvent {                message: "no active turn to steer".to_string(),                codex_error_info: Some(CodexErrorInfo::BadRequest),            },            Self::ExpectedTurnMismatch { expected, actual } => ErrorEvent {                message: format!("expected active turn id `{expected}` but found `{actual}`"),                codex_error_info: Some(CodexErrorInfo::BadRequest),            },            Self::ActiveTurnNotSteerable { turn_kind } => {                let turn_kind_label = match turn_kind {                    NonSteerableTurnKind::Review => "review",                    NonSteerableTurnKind::Compact => "compact",                };                ErrorEvent {                    message: format!("cannot steer a {turn_kind_label} turn"),                    codex_error_info: Some(CodexErrorInfo::ActiveTurnNotSteerable {                        turn_kind: *turn_kind,                    }),                }            }            Self::EmptyInput => ErrorEvent {                message: "input must not be empty".to_string(),                codex_error_info: Some(CodexErrorInfo::BadRequest),            },        }    }}/// Notes from the previous real user turn.////// Conceptually this is the same role that `previous_model` used to fill, but/// it can carry other prior-turn settings that matter when constructing/// sensible state-change diffs or full-context reinjection, such as model/// switches, compaction compatibility, or detecting a prior/// `realtime_active -> false` transition.#[derive(Clone, Debug, PartialEq, Eq)]pub(crate) struct PreviousTurnSettings {    pub(crate) model: String,    pub(crate) comp_hash: Option<String>,    pub(crate) realtime_active: Option<bool>,}#[cfg(test)]use crate::SkillLoadOutcome;#[cfg(test)]use crate::SkillMetadata;use crate::SkillsManager;use crate::agents_md::load_project_instructions;use crate::exec_policy::ExecPolicyUpdateError;use crate::guardian::GuardianReviewSessionManager;use crate::mcp::McpManager;use crate::network_policy_decision::execpolicy_network_rule_amendment;use crate::rollout::map_session_init_error;use crate::session_startup_prewarm::SessionStartupPrewarmHandle;use crate::shell;use crate::state::AutoCompactWindowSnapshot;use crate::state::PendingRequestPermissions;use crate::state::SessionServices;use crate::state::SessionState;#[cfg(test)]use crate::stream_events_utils::HandleOutputCtx;#[cfg(test)]use crate::stream_events_utils::handle_output_item_done;use crate::tasks::ReviewTask;use crate::tools::network_approval::NetworkApprovalService;use crate::tools::network_approval::build_blocked_request_observer;use crate::tools::network_approval::build_network_policy_decider;#[cfg(test)]use crate::tools::parallel::ToolCallRuntime;use crate::tools::sandboxing::ApprovalStore;use crate::turn_timing::TurnTimingState;use crate::turn_timing::record_turn_ttfm_metric;use crate::unified_exec::UnifiedExecProcessManager;use crate::windows_sandbox::WindowsSandboxLevelExt;use codex_core_plugins::PluginsManager;use codex_git_utils::get_git_repo_root;use codex_mcp::McpConfig;use codex_mcp::compute_auth_statuses;use codex_mcp::effective_mcp_servers_from_configured;use codex_mcp::host_owned_codex_apps_enabled;use codex_otel::SessionTelemetry;use codex_otel::THREAD_STARTED_METRIC;use codex_otel::TelemetryAuthMode;use codex_protocol::config_types::CollaborationMode;use codex_protocol::config_types::Personality;use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;use codex_protocol::config_types::WindowsSandboxLevel;use codex_protocol::models::LocalImagePreparation;use codex_protocol::models::ResponseInputItem;use codex_protocol::models::ResponseItem;use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;use codex_protocol::protocol::ApplyPatchApprovalRequestEvent;use codex_protocol::protocol::AskForApproval;use codex_protocol::protocol::CodexErrorInfo;use codex_protocol::protocol::CompactedItem;use codex_protocol::protocol::DeprecationNoticeEvent;use codex_protocol::protocol::ErrorEvent;use codex_protocol::protocol::Event;use codex_protocol::protocol::EventMsg;use codex_protocol::protocol::ExecApprovalRequestEvent;use codex_protocol::protocol::InitialHistory;use codex_protocol::protocol::McpServerRefreshConfig;use codex_protocol::protocol::ModelRerouteEvent;use codex_protocol::protocol::ModelRerouteReason;use codex_protocol::protocol::ModelVerification;use codex_protocol::protocol::ModelVerificationEvent;use codex_protocol::protocol::NetworkApprovalContext;use codex_protocol::protocol::NonSteerableTurnKind;use codex_protocol::protocol::Op;use codex_protocol::protocol::RateLimitSnapshot;use codex_protocol::protocol::RequestUserInputEvent;use codex_protocol::protocol::ReviewDecision;use codex_protocol::protocol::SandboxPolicy;use codex_protocol::protocol::SessionConfiguredEvent;use codex_protocol::protocol::SessionNetworkProxyRuntime;use codex_protocol::protocol::StreamErrorEvent;use codex_protocol::protocol::Submission;use codex_protocol::protocol::ThreadMemoryMode;use codex_protocol::protocol::TokenCountEvent;use codex_protocol::protocol::TokenUsage;use codex_protocol::protocol::TokenUsageInfo;use codex_protocol::protocol::TurnModerationMetadataEvent;use codex_protocol::protocol::WarningEvent;use codex_protocol::user_input::UserInput;use codex_tools::ToolEnvironmentMode;use codex_tools::UnifiedExecShellMode;use codex_utils_absolute_path::AbsolutePathBuf;#[cfg(test)]use codex_utils_stream_parser::ProposedPlanSegment;/// The high-level interface to the Codex system./// It operates as a queue pair where you send submissions and receive events.pub struct Codex {    pub(crate) tx_sub: Sender<Submission>,    pub(crate) rx_event: Receiver<Event>,    // Last known status of the agent.    pub(crate) agent_status: watch::Receiver<AgentStatus>,    pub(crate) session: Arc<Session>,    // Shared future for the background submission loop completion so multiple    // callers can wait for shutdown.    pub(crate) session_loop_termination: SessionLoopTermination,}pub(crate) type SessionLoopTermination = Shared<BoxFuture<'static, ()>>;/// Wrapper returned by [`Codex::spawn`] containing the spawned [`Codex`] and/// the unique session id.pub struct CodexSpawnOk {    pub codex: Codex,    pub thread_id: ThreadId,}pub(crate) struct CodexSpawnArgs {    pub(crate) config: Config,    pub(crate) user_instructions: LoadedUserInstructions,    pub(crate) installation_id: String,    pub(crate) auth_manager: Arc<AuthManager>,    pub(crate) models_manager: SharedModelsManager,    pub(crate) environment_manager: Arc<EnvironmentManager>,    pub(crate) skills_manager: Arc<SkillsManager>,    pub(crate) plugins_manager: Arc<PluginsManager>,    pub(crate) mcp_manager: Arc<McpManager>,    pub(crate) extensions: Arc<codex_extension_api::ExtensionRegistry<crate::config::Config>>,    pub(crate) conversation_history: InitialHistory,    pub(crate) session_source: SessionSource,    pub(crate) forked_from_thread_id: Option<ThreadId>,    pub(crate) parent_thread_id: Option<ThreadId>,    pub(crate) thread_source: Option<ThreadSource>,    pub(crate) agent_control: AgentControl,    pub(crate) dynamic_tools: Vec<DynamicToolSpec>,    pub(crate) metrics_service_name: Option<String>,    pub(crate) inherited_exec_policy: Option<Arc<ExecPolicyManager>>,    pub(crate) inherited_environments: Option<TurnEnvironmentSnapshot>,    /// Parent rollout trace used only to derive fresh spawned child traces.    ///    /// Root sessions and non-thread-spawn subagents pass a disabled context;    /// `Session::new` creates the root trace itself when rollout tracing is enabled.    pub(crate) parent_rollout_thread_trace: ThreadTraceContext,    pub(crate) user_shell_override: Option<shell::Shell>,    pub(crate) parent_trace: Option<W3cTraceContext>,    pub(crate) environment_selections: Vec<TurnEnvironmentSelection>,    pub(crate) thread_extension_init: ExtensionDataInit,    pub(crate) analytics_events_client: Option<AnalyticsEventsClient>,    pub(crate) thread_store: Arc<dyn ThreadStore>,    pub(crate) attestation_provider: Option<Arc<dyn AttestationProvider>>,    pub(crate) inherited_multi_agent_version: Option<MultiAgentVersion>,}pub(crate) fn resolve_multi_agent_version(    conversation_history: &InitialHistory,    inherited_multi_agent_version: Option<MultiAgentVersion>,) -> Option<MultiAgentVersion> {    if inherited_multi_agent_version == Some(MultiAgentVersion::Disabled) {        return Some(MultiAgentVersion::Disabled);    }    conversation_history        .get_multi_agent_version()        .or(inherited_multi_agent_version)        .or(match conversation_history {            InitialHistory::New | InitialHistory::Cleared => None,            // Threads created before runtime metadata existed keep the legacy V1 tool surface.            InitialHistory::Resumed(_) | InitialHistory::Forked(_) => Some(MultiAgentVersion::V1),        })}pub(crate) const INITIAL_SUBMIT_ID: &str = "";pub(crate) const SUBMISSION_CHANNEL_CAPACITY: usize = 512;const CYBER_VERIFY_URL: &str = "https://chatgpt.com/cyber";const CYBER_SAFETY_URL: &str = "https://developers.openai.com/codex/concepts/cyber-safety";impl Codex {    /// Spawn a new [`Codex`] and initialize the session.    pub(crate) async fn spawn(args: CodexSpawnArgs) -> CodexResult<CodexSpawnOk> {        let parent_trace = match args.parent_trace {            Some(trace) => {                if codex_otel::context_from_w3c_trace_context(&trace).is_some() {                    Some(trace)                } else {                    warn!("ignoring invalid thread spawn trace carrier");                    None                }            }            None => None,        };        let thread_spawn_span = info_span!("thread_spawn", otel.name = "thread_spawn");        if let Some(trace) = parent_trace.as_ref() {            let _ = set_parent_from_w3c_trace_context(&thread_spawn_span, trace);        }        Self::spawn_internal(CodexSpawnArgs {            parent_trace,            ..args        })        .instrument(thread_spawn_span)        .await    }    async fn spawn_internal(args: CodexSpawnArgs) -> CodexResult<CodexSpawnOk> {        let CodexSpawnArgs {            mut config,            user_instructions,            installation_id,            auth_manager,            models_manager,            environment_manager,            skills_manager,            plugins_manager,            mcp_manager,            extensions,            conversation_history,            session_source,            forked_from_thread_id,            parent_thread_id,            thread_source,            agent_control,            dynamic_tools,            metrics_service_name,            user_shell_override,            inherited_exec_policy,            inherited_environments,            parent_rollout_thread_trace,            parent_trace: _,            environment_selections,            thread_extension_init,            analytics_events_client,            thread_store,            attestation_provider,            inherited_multi_agent_version,        } = args;        let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);        let (tx_event, rx_event) = async_channel::unbounded();        let LoadedUserInstructions {            instructions: user_instructions,            warnings: user_instruction_provider_warnings,        } = user_instructions;        // TODO(anp) pull startup_warnings out of Config        config            .startup_warnings            .extend(user_instruction_provider_warnings);        let exec_policy = if crate::guardian::is_guardian_reviewer_source(&session_source) {            // Guardian review should rely on the built-in shell safety checks,            // not on caller-provided exec-policy rules that could shape the            // reviewer or silently auto-approve commands.            Arc::new(ExecPolicyManager::default())        } else if let Some(exec_policy) = &inherited_exec_policy {            Arc::clone(exec_policy)        } else {            Arc::new(                ExecPolicyManager::load(&config.config_layer_stack)                    .await                    .map_err(|err| CodexErr::Fatal(format!("failed to load rules: {err}")))?,            )        };        let config = Arc::new(config);        let refresh_strategy = if session_source.is_non_root_agent() {            codex_models_manager::manager::RefreshStrategy::Offline        } else {            codex_models_manager::manager::RefreshStrategy::OnlineIfUncached        };        if config.model.is_none()            || !matches!(                refresh_strategy,                codex_models_manager::manager::RefreshStrategy::Offline            )        {            let _ = models_manager.list_models(refresh_strategy).await;        }        let model = models_manager            .get_default_model(&config.model, refresh_strategy)            .await;        // Resolve base instructions for the session. Priority order:        // 1. config.base_instructions override        // 2. conversation history => session_meta.base_instructions        // 3. base_instructions for current model        let model_info = models_manager            .get_model_info(model.as_str(), &config.to_models_manager_config())            .await;        let multi_agent_version =            resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version);        config            .validate_multi_agent_v2_config()            .map_err(|err| CodexErr::InvalidRequest(err.to_string()))?;        let base_instructions = config            .base_instructions            .clone()            .or_else(|| conversation_history.get_base_instructions().map(|s| s.text))            .unwrap_or_else(|| model_info.get_model_instructions(config.personality));        // Dynamic tools are defined at thread start and persisted in rollout session metadata.        let dynamic_tools = if dynamic_tools.is_empty() {            conversation_history.get_dynamic_tools().unwrap_or_default()        } else {            dynamic_tools        };        // TODO (aibrahim): Consolidate config.model and config.model_reasoning_effort into config.collaboration_mode        // to avoid extracting these fields separately and constructing CollaborationMode here.        let collaboration_mode = CollaborationMode {            mode: ModeKind::Default,            settings: Settings {                model: model.clone(),                reasoning_effort: config.model_reasoning_effort.clone(),                developer_instructions: None,            },        };        let service_tier = get_service_tier(            config.service_tier.clone(),            config.features.enabled(Feature::FastMode),            &model_info,        );        let session_configuration = SessionConfiguration {            provider: config.model_provider.clone(),            collaboration_mode,            model_reasoning_summary: config.model_reasoning_summary,            service_tier,            developer_instructions: config.developer_instructions.clone(),            loaded_agents_md: None,            personality: config.personality,            base_instructions,            compact_prompt: config.compact_prompt.clone(),            approval_policy: config.permissions.approval_policy.clone(),            approvals_reviewer: config.approvals_reviewer,            permission_profile_state: session_permission_profile_state_from_config(&config)?,            windows_sandbox_level: WindowsSandboxLevel::from_config(&config),            environments: TurnEnvironmentSelections::new(                config.cwd.clone(),                environment_selections,            ),            workspace_roots: config.workspace_roots.clone(),            codex_home: config.codex_home.clone(),            thread_name: None,            original_config_do_not_use: Arc::clone(&config),            metrics_service_name,            app_server_client_name: None,            app_server_client_version: None,            session_source,            forked_from_thread_id,            parent_thread_id,            thread_source,            dynamic_tools,            user_shell_override,        };        // Generate a unique ID for the lifetime of this Codex session.        let session_source_clone = session_configuration.session_source.clone();        let (agent_status_tx, agent_status_rx) = watch::channel(AgentStatus::PendingInit);        let session = Box::pin(Session::new(            session_configuration,            config.clone(),            user_instructions,            installation_id,            auth_manager.clone(),            models_manager.clone(),            exec_policy,            tx_event.clone(),            agent_status_tx.clone(),            conversation_history,            session_source_clone,            skills_manager,            plugins_manager,            mcp_manager.clone(),            extensions,            thread_extension_init,            agent_control,            environment_manager,            inherited_environments,            analytics_events_client,            thread_store,            parent_rollout_thread_trace,            attestation_provider,            multi_agent_version,        ))        .await        .map_err(|e| {            error!("Failed to create session: {e:#}");            map_session_init_error(&e, &config.codex_home)        })?;        let thread_id = session.thread_id;        // This task will run until Op::Shutdown is received.        let session_for_loop = Arc::clone(&session);        let session_loop_handle = tokio::spawn(async move {            submission_loop(session_for_loop, config, rx_sub)                .instrument(info_span!("session_loop", thread_id = %thread_id))                .await;        });        let codex = Codex {            tx_sub,            rx_event,            agent_status: agent_status_rx,            session,            session_loop_termination: session_loop_termination_from_handle(session_loop_handle),        };        Ok(CodexSpawnOk { codex, thread_id })    }    /// Submit the `op` wrapped in a `Submission` with a unique ID.    pub async fn submit(&self, op: Op) -> CodexResult<String> {        self.submit_with_trace(op, /*trace*/ None).await    }    pub async fn submit_with_trace(        &self,        op: Op,        trace: Option<W3cTraceContext>,    ) -> CodexResult<String> {        let id = Uuid::now_v7().to_string();        let sub = Submission {            id: id.clone(),            op,            client_user_message_id: None,            trace,        };        self.submit_with_id(sub).await?;        Ok(id)    }    pub async fn submit_user_input_with_client_user_message_id(        &self,        op: Op,        trace: Option<W3cTraceContext>,        client_user_message_id: Option<String>,    ) -> CodexResult<String> {        debug_assert!(matches!(op, Op::UserInput { .. }));        let id = Uuid::now_v7().to_string();        let sub = Submission {            id: id.clone(),            op,            client_user_message_id,            trace,        };        self.submit_with_id(sub).await?;        Ok(id)    }    /// Use sparingly: prefer `submit()` so Codex is responsible for generating    /// unique IDs for each submission.    pub async fn submit_with_id(&self, mut sub: Submission) -> CodexResult<()> {        if sub.trace.is_none() {            sub.trace = current_span_w3c_trace_context();        }        self.tx_sub            .send(sub)            .await            .map_err(|_| CodexErr::InternalAgentDied)?;        Ok(())    }    /// Persist a thread-level memory mode update for the active session.    ///    /// This is a local-only operation that updates rollout metadata directly    /// and does not involve the model.    pub async fn set_thread_memory_mode(        &self,        mode: codex_protocol::protocol::ThreadMemoryMode,    ) -> anyhow::Result<()> {        handlers::persist_thread_memory_mode_update(&self.session, mode).await    }    pub async fn shutdown_and_wait(&self) -> CodexResult<()> {        let session_loop_termination = self.session_loop_termination.clone();        match self.submit(Op::Shutdown).await {            Ok(_) => {}            Err(CodexErr::InternalAgentDied) => {}            Err(err) => return Err(err),        }        session_loop_termination.await;        Ok(())    }    pub async fn next_event(&self) -> CodexResult<Event> {        let event = self            .rx_event            .recv()            .await            .map_err(|_| CodexErr::InternalAgentDied)?;        Ok(event)    }    pub async fn steer_input(        &self,        input: Vec<UserInput>,        additional_context: BTreeMap<String, AdditionalContextEntry>,        expected_turn_id: Option<&str>,        client_user_message_id: Option<String>,        responsesapi_client_metadata: Option<HashMap<String, String>>,    ) -> Result<String, SteerInputError> {        self.session            .steer_input(                input,                additional_context,                expected_turn_id,                client_user_message_id,                responsesapi_client_metadata,            )            .await    }    pub(crate) async fn set_app_server_client_info(        &self,        app_server_client_name: Option<String>,        app_server_client_version: Option<String>,        mcp_elicitations_auto_deny: bool,    ) -> ConstraintResult<()> {        self.session            .update_settings(SessionSettingsUpdate {                app_server_client_name,                app_server_client_version,                ..Default::default()            })            .await?;        let mcp_connection_manager = self.session.services.mcp_connection_manager.load_full();        mcp_connection_manager.set_elicitations_auto_deny(mcp_elicitations_auto_deny);        Ok(())    }    pub(crate) async fn agent_status(&self) -> AgentStatus {        self.agent_status.borrow().clone()    }    pub(crate) async fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {        let state = self.session.state.lock().await;        state.session_configuration.thread_config_snapshot()    }    pub(crate) async fn instruction_sources(&self) -> Vec<AbsolutePathBuf> {        let state = self.session.state.lock().await;        state            .session_configuration            .loaded_agents_md            .as_ref()            .map_or_else(Vec::new, |instructions| {                instructions.sources().cloned().collect()            })    }    pub(crate) async fn thread_environment_selections(&self) -> Vec<TurnEnvironmentSelection> {        let state = self.session.state.lock().await;        state            .session_configuration            .environment_selections()            .to_vec()    }    pub(crate) fn state_db(&self) -> Option<state_db::StateDbHandle> {        self.session.state_db()    }    pub(crate) fn enabled(&self, feature: Feature) -> bool {        self.session.enabled(feature)    }}fn get_service_tier(    configured_service_tier: Option<String>,    fast_mode_enabled: bool,    model_info: &ModelInfo,) -> Option<String> {    if !fast_mode_enabled {        return None;    }    configured_service_tier.filter(|service_tier| {        service_tier == SERVICE_TIER_DEFAULT_REQUEST_VALUE            || model_info.supports_service_tier(service_tier)    })}fn session_permission_profile_state_from_config(    config: &Config,) -> CodexResult<PermissionProfileState> {    Ok(config.permissions.permission_profile_state().clone())}#[cfg(test)]pub(crate) fn completed_session_loop_termination() -> SessionLoopTermination {    futures::future::ready(()).boxed().shared()}pub(crate) fn session_loop_termination_from_handle(    handle: JoinHandle<()>,) -> SessionLoopTermination {    async move {        let _ = handle.await;    }    .boxed()    .shared()}async fn thread_title_from_thread_store(    live_thread: Option<&LiveThread>,    thread_store: &Arc<dyn ThreadStore>,    conversation_id: ThreadId,) -> Option<String> {    let thread = match live_thread {        Some(live_thread) => {            live_thread                .read_thread(                    /*include_archived*/ true, /*include_history*/ false,                )                .await        }        None => {            thread_store                .read_thread(ReadThreadParams {                    thread_id: conversation_id,                    include_archived: true,                    include_history: false,                })                .await        }    }    .ok()?;    let title = thread.name.as_deref()?.trim();    (!title.is_empty() && thread.preview.trim() != title).then(|| title.to_string())}impl Session {    pub(crate) async fn app_server_client_metadata(&self) -> AppServerClientMetadata {        let state = self.state.lock().await;        AppServerClientMetadata {            client_name: state.session_configuration.app_server_client_name.clone(),            client_version: state                .session_configuration                .app_server_client_version                .clone(),        }    }    fn managed_network_proxy_active_for_permission_profile(        permission_profile: &PermissionProfile,    ) -> bool {        !matches!(permission_profile, PermissionProfile::Disabled)    }    /// Builds the `x-codex-beta-features` header value for this session.    ///    /// `ModelClient` is session-scoped and intentionally does not depend on the full `Config`, so    /// we precompute the comma-separated list of enabled experimental feature keys at session    /// creation time and thread it into the client.    fn build_model_client_beta_features_header(config: &Config) -> Option<String> {        let beta_features_header = FEATURES            .iter()            .filter_map(|spec| {                let advertise_in_model_client_header =                    spec.stage.experimental_menu_description().is_some()                        || spec.id == Feature::RemoteCompactionV2;                if advertise_in_model_client_header && config.features.enabled(spec.id) {                    Some(spec.key)                } else {                    None                }            })            .collect::<Vec<_>>()            .join(",");        if beta_features_header.is_empty() {            None        } else {            Some(beta_features_header)        }    }    async fn start_managed_network_proxy(        spec: &crate::config::NetworkProxySpec,        exec_policy: &codex_execpolicy::Policy,        permission_profile: &PermissionProfile,        network_policy_decider: Option<Arc<dyn codex_network_proxy::NetworkPolicyDecider>>,        blocked_request_observer: Option<Arc<dyn codex_network_proxy::BlockedRequestObserver>>,        managed_network_requirements_enabled: bool,        audit_metadata: NetworkProxyAuditMetadata,    ) -> anyhow::Result<(StartedNetworkProxy, SessionNetworkProxyRuntime)> {        let spec = spec            .with_exec_policy_network_rules(exec_policy)            .map_err(|err| {                tracing::warn!(                    "failed to apply execpolicy network rules to managed proxy; continuing with configured network policy: {err}"                );                err            })            .unwrap_or_else(|_| spec.clone());        let network_proxy = spec            .start_proxy(                permission_profile,                network_policy_decider,                blocked_request_observer,                managed_network_requirements_enabled,                audit_metadata,            )            .await            .map_err(|err| anyhow::anyhow!("failed to start managed network proxy: {err}"))?;        let session_network_proxy = {            let proxy = network_proxy.proxy();            SessionNetworkProxyRuntime {                http_addr: proxy.http_addr().to_string(),                socks_addr: proxy.socks_addr().to_string(),            }        };        Ok((network_proxy, session_network_proxy))    }    async fn refresh_managed_network_proxy_for_current_permission_profile(&self) {        let Ok(_refresh_guard) = self.managed_network_proxy_refresh_lock.acquire().await else {            error!("managed network proxy refresh semaphore closed");            return;        };        let session_configuration = {            let state = self.state.lock().await;            state.session_configuration.clone()        };        let Some(spec) = session_configuration            .original_config_do_not_use            .permissions            .network            .as_ref()            .cloned()        else {            self.services.network_proxy.store(None);            return;        };        let spec = match spec            .recompute_for_permission_profile(&session_configuration.permission_profile())        {            Ok(spec) => spec,            Err(err) => {                warn!("failed to rebuild managed network proxy policy for sandbox change: {err}");                return;            }        };        let current_exec_policy = self.services.exec_policy.current();        let spec = match spec.with_exec_policy_network_rules(current_exec_policy.as_ref()) {            Ok(spec) => spec,            Err(err) => {                warn!(                    "failed to apply execpolicy network rules while refreshing managed network proxy: {err}"                );                spec            }        };        if let Some(started_proxy) = self.services.network_proxy.load_full() {            if let Err(err) = spec.apply_to_started_proxy(started_proxy.as_ref()).await {                warn!("failed to refresh managed network proxy for sandbox change: {err}");            }            return;        }        match Self::start_managed_network_proxy(            &spec,            current_exec_policy.as_ref(),            &session_configuration.permission_profile(),            /*network_policy_decider*/ None,            self.services                .managed_network_requirements_configured                .then(|| {                    build_blocked_request_observer(Arc::clone(&self.services.network_approval))                }),            self.services.managed_network_requirements_configured,            self.services.network_proxy_audit_metadata.clone(),        )        .await        {            Ok((started_proxy, _session_network_proxy)) => {                self.services                    .network_proxy                    .store(Some(Arc::new(started_proxy)));            }            Err(err) => {                warn!("failed to start managed network proxy for sandbox change: {err}");            }        }    }    #[cfg(test)]    pub(crate) async fn codex_home(&self) -> AbsolutePathBuf {        let state = self.state.lock().await;        state.session_configuration.codex_home().clone()    }    pub(crate) fn subscribe_out_of_band_elicitation_pause_state(&self) -> watch::Receiver<bool> {        self.out_of_band_elicitation_paused.subscribe()    }    pub(crate) fn set_out_of_band_elicitation_pause_state(&self, paused: bool) {        self.out_of_band_elicitation_paused.send_replace(paused);    }    pub(crate) fn get_tx_event(&self) -> Sender<Event> {        self.tx_event.clone()    }    pub(crate) fn state_db(&self) -> Option<state_db::StateDbHandle> {        self.services.state_db.clone()    }    pub(crate) fn live_thread_for_persistence(        &self,        operation: &str,    ) -> anyhow::Result<&LiveThread> {        self.live_thread()            .ok_or_else(|| anyhow::anyhow!("Session persistence is disabled; cannot {operation}."))    }    pub(crate) fn live_thread(&self) -> Option<&LiveThread> {        self.services.live_thread.as_ref()    }    /// Flush rollout writes and return the final durability-barrier result.    #[instrument(name = "session.flush_rollout", level = "trace", skip_all)]    pub(crate) async fn flush_rollout(&self) -> std::io::Result<()> {        if let Some(live_thread) = self.live_thread() {            live_thread.flush().await.map_err(std::io::Error::other)        } else {            Ok(())        }    }    pub(crate) async fn try_ensure_rollout_materialized(&self) -> std::io::Result<()> {        if let Some(live_thread) = self.live_thread() {            live_thread.persist().await.map_err(std::io::Error::other)?;        }        Ok(())    }    pub(crate) async fn ensure_rollout_materialized(&self) {        if let Err(e) = self.try_ensure_rollout_materialized().await {            warn!("failed to materialize thread persistence: {e}");        }    }    fn next_internal_sub_id(&self) -> String {        let id = self            .next_internal_sub_id            .fetch_add(1, std::sync::atomic::Ordering::SeqCst);        format!("auto-compact-{id}")    }    pub(crate) async fn route_realtime_text_input(self: &Arc<Self>, text: String) {        handlers::user_input_or_turn_inner(            self,            self.next_internal_sub_id(),            Op::UserInput {                items: vec![UserInput::Text {                    text,                    text_elements: Vec::new(),                }],                final_output_json_schema: None,                responsesapi_client_metadata: None,                additional_context: Default::default(),                thread_settings: Default::default(),            },            /*client_user_message_id*/ None,        )        .await;    }    pub(crate) async fn get_total_token_usage(&self) -> i64 {        let state = self.state.lock().await;        state.get_total_token_usage(state.server_reasoning_included())    }    pub(crate) async fn auto_compact_window_snapshot(&self) -> AutoCompactWindowSnapshot {        let state = self.state.lock().await;        state.auto_compact_window_snapshot()    }    pub(crate) async fn estimated_tokens_after_last_model_generated_item(&self) -> i64 {        let state = self.state.lock().await;        state            .history            .estimated_tokens_after_last_model_generated_item()    }    pub(crate) async fn total_token_usage(&self) -> Option<TokenUsage> {        let state = self.state.lock().await;        state.token_info().map(|info| info.total_token_usage)    }    /// Returns the complete token usage snapshot currently cached for this session.    ///    /// Resume and fork reconstruction seed this state from the last persisted rollout    /// `TokenCount` event. Callers that need to replay restored usage to a client    /// should use this accessor instead of `total_token_usage`, because the app-server    /// notification includes both total and last-turn usage.    pub(crate) async fn token_usage_info(&self) -> Option<TokenUsageInfo> {        let state = self.state.lock().await;        state.token_info()    }    pub(crate) async fn get_estimated_token_count(        &self,        turn_context: &TurnContext,    ) -> Option<i64> {        let state = self.state.lock().await;        state.history.estimate_token_count(turn_context)    }    pub(crate) async fn get_base_instructions(&self) -> BaseInstructions {        let state = self.state.lock().await;        BaseInstructions {            text: state.session_configuration.base_instructions.clone(),        }    }    // Merges connector IDs into the session-level explicit connector selection.    pub(crate) async fn merge_connector_selection(        &self,        connector_ids: HashSet<String>,    ) -> HashSet<String> {        let mut state = self.state.lock().await;        state.merge_connector_selection(connector_ids)    }    // Returns the connector IDs currently selected for this session.    pub(crate) async fn get_connector_selection(&self) -> HashSet<String> {        let state = self.state.lock().await;        state.get_connector_selection()    }    // Clears connector IDs that were accumulated for explicit selection.    pub(crate) async fn clear_connector_selection(&self) {        let mut state = self.state.lock().await;        state.clear_connector_selection();    }    async fn record_initial_history(&self, conversation_history: InitialHistory) {        let is_subagent = {            let state = self.state.lock().await;            state                .session_configuration                .session_source                .is_non_root_agent()        };        let has_prior_user_turns = initial_history_has_prior_user_turns(&conversation_history);        {            let mut state = self.state.lock().await;            state.set_next_turn_is_first(!has_prior_user_turns);        }        match conversation_history {            InitialHistory::New | InitialHistory::Cleared => {                // Defer initial context insertion until the first real turn starts so                // turn/start overrides can be merged before we write model-visible context.                self.set_previous_turn_settings(/*previous_turn_settings*/ None)                    .await;            }            InitialHistory::Resumed(resumed_history) => {                let turn_context = self.new_default_turn().await;                let rollout_items = resumed_history.history;                let previous_turn_settings = self                    .apply_rollout_reconstruction(&turn_context, &rollout_items)                    .await;                // If resuming, warn when the last recorded model differs from the current one.                let curr: &str = turn_context.model_info.slug.as_str();                if let Some(prev) = previous_turn_settings                    .as_ref()                    .map(|settings| settings.model.as_str())                    .filter(|model| *model != curr)                {                    warn!("resuming session with different model: previous={prev}, current={curr}");                    self.send_event(                        &turn_context,                        EventMsg::Warning(WarningEvent {                            message: format!(                                "This session was recorded with model `{prev}` but is resuming with `{curr}`. \                         Consider switching back to `{prev}` as it may affect Codex performance."                            ),                        }),                    )                    .await;                }                // Seed usage info from the recorded rollout so UIs can show token counts                // immediately on resume/fork.                if let Some(info) = Self::last_token_info_from_rollout(&rollout_items) {                    let mut state = self.state.lock().await;                    state.set_token_info(Some(info));                }                // Defer seeding the session's initial context until the first turn starts so                // turn/start overrides can be merged before we write to the rollout.                if !is_subagent {                    let _ = self.flush_rollout().await;                }            }            InitialHistory::Forked(rollout_items) => {                let turn_context = self.new_default_turn().await;                self.apply_rollout_reconstruction(&turn_context, &rollout_items)                    .await;                // Seed usage info from the recorded rollout so UIs can show token counts                // immediately on resume/fork.                if let Some(info) = Self::last_token_info_from_rollout(&rollout_items) {                    let mut state = self.state.lock().await;                    state.set_token_info(Some(info));                }                // If persisting, persist all rollout items as-is (the store filters).                if !rollout_items.is_empty() {                    self.persist_rollout_items(&rollout_items).await;                }                // Forked threads should remain file-backed immediately after startup.                self.ensure_rollout_materialized().await;                // Flush after seeding history and any persisted rollout copy.                if !is_subagent {                    let _ = self.flush_rollout().await;                }            }        }    }    #[instrument(        level = "trace",        skip_all,        fields(            thread_id = %self.thread_id(),            rollout_item_count = rollout_items.len()        )    )]    async fn apply_rollout_reconstruction(        &self,        turn_context: &TurnContext,        rollout_items: &[RolloutItem],    ) -> Option<PreviousTurnSettings> {        let rollout_reconstruction::RolloutReconstruction {            mut history,            previous_turn_settings,            reference_context_item,            window_id,        } = self            .reconstruct_history_from_rollout(turn_context, rollout_items)            .await;        if turn_context.features.enabled(Feature::ResizeAllImages) {            // Keep the recorded rollout unchanged. Prepare its reconstructed history before            // installing it, so legacy images are processed once for this resume or fork and            // will be processed again if the rollout is reconstructed in a future session.            // This meets image resizing requirements without modifying persisted rollouts.            prepare_response_items(&mut history);        }        {            let mut state = self.state.lock().await;            state.replace_history(history, reference_context_item);            state.set_auto_compact_window_id(window_id);            state.set_previous_turn_settings(previous_turn_settings.clone());        }        let prefix_tokens = if matches!(            turn_context.config.model_auto_compact_token_limit_scope,            AutoCompactTokenLimitScope::BodyAfterPrefix        ) {            let history = self.clone_history().await;            let base_instructions = self.get_base_instructions().await;            history.estimate_token_count_with_base_instructions(&base_instructions)        } else {            None        };        if let Some(prefix_tokens) = prefix_tokens {            self.set_auto_compact_window_estimated_prefill_for_scope(turn_context, prefix_tokens)                .await;        }        previous_turn_settings    }    async fn set_auto_compact_window_estimated_prefill_for_scope(        &self,        turn_context: &TurnContext,        tokens: i64,    ) {        if !matches!(            turn_context.config.model_auto_compact_token_limit_scope,            AutoCompactTokenLimitScope::BodyAfterPrefix        ) {            return;        }        let mut state = self.state.lock().await;        state.set_auto_compact_window_estimated_prefill(tokens);    }    fn last_token_info_from_rollout(rollout_items: &[RolloutItem]) -> Option<TokenUsageInfo> {        rollout_items.iter().rev().find_map(|item| match item {            RolloutItem::EventMsg(EventMsg::TokenCount(ev)) => ev.info.clone(),            _ => None,        })    }    async fn previous_turn_settings(&self) -> Option<PreviousTurnSettings> {        let state = self.state.lock().await;        state.previous_turn_settings()    }    pub(crate) async fn set_previous_turn_settings(        &self,        previous_turn_settings: Option<PreviousTurnSettings>,    ) {        let mut state = self.state.lock().await;        state.set_previous_turn_settings(previous_turn_settings);    }    pub(crate) async fn update_settings(        &self,        updates: SessionSettingsUpdate,    ) -> ConstraintResult<()> {        let notify_config_contributors = !self.services.extensions.config_contributors().is_empty();        let (previous_config, new_config, permission_profile_changed) = {            let mut state = self.state.lock().await;            let updated = match state.session_configuration.apply(&updates) {                Ok(updated) => updated,                Err(err) => {                    warn!("rejected session settings update: {err}");                    return Err(err);                }            };            let previous_config = notify_config_contributors                .then(|| Self::build_effective_session_config(&state.session_configuration));            let new_config =                notify_config_contributors.then(|| Self::build_effective_session_config(&updated));            let previous_permission_profile = state.session_configuration.permission_profile();            let updated_permission_profile = updated.permission_profile();            let permission_profile_changed =                previous_permission_profile != updated_permission_profile;            if updates.environments.is_some() {                self.services                    .turn_environments                    .update_selections(updated.environment_selections());            }            state.session_configuration = updated;            (previous_config, new_config, permission_profile_changed)        };        self.emit_config_changed_contributors(previous_config.as_ref(), new_config.as_ref());        if permission_profile_changed {            self.refresh_managed_network_proxy_for_current_permission_profile()                .await;        }        Ok(())    }    pub(crate) async fn preview_settings(        &self,        updates: &SessionSettingsUpdate,    ) -> ConstraintResult<ThreadConfigSnapshot> {        let state = self.state.lock().await;        state            .session_configuration            .apply(updates)            .map(|configuration| configuration.thread_config_snapshot())    }    pub(crate) async fn set_session_startup_prewarm(        &self,        startup_prewarm: SessionStartupPrewarmHandle,    ) {        let mut state = self.state.lock().await;        state.set_session_startup_prewarm(startup_prewarm);    }    pub(crate) async fn take_session_startup_prewarm(&self) -> Option<SessionStartupPrewarmHandle> {        let mut state = self.state.lock().await;        state.take_session_startup_prewarm()    }    pub(crate) async fn get_config(&self) -> std::sync::Arc<Config> {        let state = self.state.lock().await;        state            .session_configuration            .original_config_do_not_use            .clone()    }    pub(crate) async fn user_instructions(&self) -> Option<codex_extension_api::UserInstructions> {        let state = self.state.lock().await;        state            .session_configuration            .loaded_agents_md            .as_ref()            .and_then(LoadedAgentsMd::user_instructions)            .cloned()    }    pub(crate) async fn provider(&self) -> ModelProviderInfo {        let state = self.state.lock().await;        state.session_configuration.provider.clone()    }    pub(crate) async fn refresh_runtime_config(&self, next_config: Config) {        // Refresh only the user layer from the incoming snapshot. Preserve thread-local        // layers such as request/session overrides that were present when this session        // was created.        let notify_config_contributors = !self.services.extensions.config_contributors().is_empty();        let (previous_config, new_config, config) = {            let mut state = self.state.lock().await;            let previous_config = notify_config_contributors                .then(|| Self::build_effective_session_config(&state.session_configuration));            let mut config = (*state.session_configuration.original_config_do_not_use).clone();            config.config_layer_stack = config                .config_layer_stack                .with_user_layer_from(&next_config.config_layer_stack);            config.tool_suggest =                resolve_tool_suggest_config_from_layer_stack(&config.config_layer_stack);            let config = Arc::new(config);            state.session_configuration.original_config_do_not_use = Arc::clone(&config);            let new_config = notify_config_contributors                .then(|| Self::build_effective_session_config(&state.session_configuration));            (previous_config, new_config, config)        };        self.emit_config_changed_contributors(previous_config.as_ref(), new_config.as_ref());        self.services.skills_manager.clear_cache();        self.services.plugins_manager.clear_cache();        let environments = self.services.turn_environments.snapshot().await;        let hooks = build_hooks_for_config(            config.as_ref(),            self.services.plugins_manager.as_ref(),            environments.single_local_environment(),        )        .await;        let state = self.state.lock().await;        // A newer refresh may have updated the config while this hook build was in flight.        // Only publish hooks derived from the current config snapshot.        if Arc::ptr_eq(            &state.session_configuration.original_config_do_not_use,            &config,        ) {            self.services.hooks.store(Arc::new(hooks));        }    }    fn emit_config_changed_contributors(        &self,        previous_config: Option<&Config>,        new_config: Option<&Config>,    ) {        let (Some(previous_config), Some(new_config)) = (previous_config, new_config) else {            return;        };        if previous_config == new_config {            return;        }        for contributor in self.services.extensions.config_contributors() {            contributor.on_config_changed(                &self.services.session_extension_data,                &self.services.thread_extension_data,                previous_config,                new_config,            );        }    }    pub(crate) async fn reload_user_config_layer(&self) {        // Refresh layer-backed runtime state for an existing session, including enabled plugin,        // skill, and hook state. Derived config fields such as feature gates and legacy notify        // settings remain session-static.        //        // Prefer `refresh_runtime_config()` when the host can already provide a materialized        // config snapshot. This file-based path exists for legacy local reload flows.        let config_toml_paths = {            let state = self.state.lock().await;            let config = &state.session_configuration.original_config_do_not_use;            let user_config_paths = config                .config_layer_stack                .get_user_layers(                    ConfigLayerStackOrdering::LowestPrecedenceFirst,                    /*include_disabled*/ true,                )                .into_iter()                .filter_map(|layer| match &layer.name {                    ConfigLayerSource::User { file, .. } => Some(file.clone()),                    _ => None,                })                .collect::<Vec<_>>();            if user_config_paths.is_empty() {                vec![                    state                        .session_configuration                        .codex_home                        .join(CONFIG_TOML_FILE),                ]            } else {                user_config_paths            }        };        let mut reloaded_user_configs = Vec::with_capacity(config_toml_paths.len());        for config_toml_path in config_toml_paths {            let user_config = match std::fs::read_to_string(&config_toml_path) {                Ok(contents) => match toml::from_str::<toml::Value>(&contents) {                    Ok(config) => config,                    Err(err) => {                        warn!("failed to parse user config while reloading layer: {err}");                        return;                    }                },                Err(err) if err.kind() == std::io::ErrorKind::NotFound => {                    toml::Value::Table(Default::default())                }                Err(err) => {                    warn!("failed to read user config while reloading layer: {err}");                    return;                }            };            reloaded_user_configs.push((config_toml_path, user_config));        }        let next_config = {            let state = self.state.lock().await;            let mut config = (*state.session_configuration.original_config_do_not_use).clone();            for (config_toml_path, user_config) in reloaded_user_configs {                config.config_layer_stack = config                    .config_layer_stack                    .with_user_config(&config_toml_path, user_config);            }            config.tool_suggest =                resolve_tool_suggest_config_from_layer_stack(&config.config_layer_stack);            config        };        self.refresh_runtime_config(next_config).await;    }    async fn build_settings_update_items(        &self,        reference_context_item: Option<&TurnContextItem>,        current_context: &TurnContext,    ) -> Vec<ResponseItem> {        // TODO: Make context updates a pure diff of persisted previous/current TurnContextItem        // state so replay/backtracking is deterministic. Runtime inputs that affect model-visible        // context (shell, exec policy, feature gates, previous-turn bridge) should be persisted        // state or explicit non-state replay events.        let previous_turn_settings = {            let state = self.state.lock().await;            state.previous_turn_settings()        };        let shell = self.user_shell();        let exec_policy = self.services.exec_policy.current();        crate::context_manager::updates::build_settings_update_items(            reference_context_item,            previous_turn_settings.as_ref(),            current_context,            shell.as_ref(),            exec_policy.as_ref(),            self.features.enabled(Feature::Personality),        )    }    /// Record a terminal CodexErr before the app-server completion notification is reduced.    pub(crate) fn track_turn_codex_error(&self, turn_context: &TurnContext, error: &CodexErr) {        self.services            .analytics_events_client            .track_turn_codex_error(TurnCodexErrorFact::from_codex_err(                self.thread_id.to_string(),                turn_context.sub_id.clone(),                error,            ));    }    /// Persist the event to rollout and send it to clients.    pub(crate) async fn send_event(&self, turn_context: &TurnContext, msg: EventMsg) {        let legacy_source = msg.clone();        if let EventMsg::Error(error) = &legacy_source            && error                .codex_error_info                .as_ref()                .is_some_and(CodexErrorInfo::affects_turn_status)        {            turn_context                .terminal_error                .lock()                .await                .replace(error.message.clone());        }        self.services            .rollout_thread_trace            .record_codex_turn_event(&turn_context.sub_id, &legacy_source);        self.services            .rollout_thread_trace            .record_tool_call_event(turn_context.sub_id.clone(), &legacy_source);        let event = Event {            id: turn_context.sub_id.clone(),            msg,        };        self.send_event_raw(event).await;        self.maybe_notify_parent_of_terminal_turn(turn_context, &legacy_source)            .await;        self.maybe_mirror_event_text_to_realtime(&legacy_source)            .await;        self.maybe_clear_realtime_handoff_for_event(&legacy_source)            .await;        let show_raw_agent_reasoning = self.show_raw_agent_reasoning();        for legacy in legacy_source.as_legacy_events(show_raw_agent_reasoning) {            let legacy_event = Event {                id: turn_context.sub_id.clone(),                msg: legacy,            };            self.send_event_raw(legacy_event).await;        }    }    /// Forwards terminal turn events from spawned MultiAgentV2 children to their direct parent.    async fn maybe_notify_parent_of_terminal_turn(        &self,        turn_context: &TurnContext,        msg: &EventMsg,    ) {        if turn_context.multi_agent_version != MultiAgentVersion::V2 {            return;        }        if !matches!(msg, EventMsg::TurnComplete(_) | EventMsg::TurnAborted(_)) {            return;        }        let SessionSource::SubAgent(SubAgentSource::ThreadSpawn {            parent_thread_id,            agent_path: Some(child_agent_path),            ..        }) = &turn_context.session_source        else {            return;        };        let status = match turn_context.terminal_error.lock().await.take() {            Some(error) => {                let status = AgentStatus::Errored(error);                self.agent_status.send_replace(status.clone());                status            }            None => {                let Some(status) = agent_status_from_event(msg) else {                    return;                };                status            }        };        if !is_final(&status) {            return;        }        self.forward_child_completion_to_parent(            turn_context,            *parent_thread_id,            child_agent_path,            status,        )        .await;    }    /// Sends the standard completion envelope from a spawned MultiAgentV2 child to its parent.    async fn forward_child_completion_to_parent(        &self,        turn_context: &TurnContext,        parent_thread_id: ThreadId,        child_agent_path: &codex_protocol::AgentPath,        status: AgentStatus,    ) {        let Some(parent_agent_path) = child_agent_path            .as_str()            .rsplit_once('/')            .and_then(|(parent, _)| codex_protocol::AgentPath::try_from(parent).ok())        else {            return;        };        let Some(message) = format_inter_agent_completion_message(            parent_agent_path.clone(),            child_agent_path.clone(),            &status,        ) else {            return;        };        // `communication` owns the message. Keep a second copy only when the        // recorder will actually need it after parent delivery succeeds.        let trace_message = self            .services            .rollout_thread_trace            .is_enabled()            .then(|| message.clone());        let communication = InterAgentCommunication::new(            child_agent_path.clone(),            parent_agent_path,            Vec::new(),            message,            /*trigger_turn*/ false,        );        if let Err(err) = self            .services            .agent_control            .send_inter_agent_communication(parent_thread_id, communication)            .await        {            debug!("failed to notify parent thread {parent_thread_id}: {err}");            return;        }        if let Some(message) = trace_message {            self.services                .rollout_thread_trace                .record_agent_result_interaction(                    turn_context.sub_id.as_str(),                    parent_thread_id,                    &AgentResultTracePayload {                        child_agent_path: child_agent_path.as_str(),                        message: &message,                        status: &status,                    },                );        }    }    async fn maybe_mirror_event_text_to_realtime(&self, msg: &EventMsg) {        let Some(text) = realtime_text_for_event(msg) else {            return;        };        if self.conversation.running_state().await.is_none() {            return;        }        if let Err(err) = self.conversation.handoff_out(text).await {            debug!("failed to mirror event text to realtime conversation: {err}");        }    }    async fn maybe_clear_realtime_handoff_for_event(&self, msg: &EventMsg) {        if !matches!(msg, EventMsg::TurnComplete(_)) {            return;        }        if let Err(err) = self.conversation.handoff_complete().await {            debug!("failed to finalize realtime handoff output: {err}");        }        self.conversation.clear_active_handoff().await;    }    pub(crate) async fn send_event_raw(&self, event: Event) {        // Persist the event into rollout storage (the store filters as needed).        let rollout_items = vec![RolloutItem::EventMsg(event.msg.clone())];        self.persist_rollout_items(&rollout_items).await;        self.services            .rollout_thread_trace            .record_protocol_event(&event.msg);        self.deliver_event_raw(event).await;    }    async fn deliver_event_raw(&self, event: Event) {        // Record the last known agent status.        if let Some(status) = agent_status_from_event(&event.msg) {            self.agent_status.send_replace(status);        }        if let Err(e) = self.tx_event.send(event).await {            debug!("dropping event because channel is closed: {e}");        }    }    pub(crate) async fn emit_turn_item_started(&self, turn_context: &TurnContext, item: &TurnItem) {        self.send_event(            turn_context,            EventMsg::ItemStarted(ItemStartedEvent {                thread_id: self.thread_id,                turn_id: turn_context.sub_id.clone(),                item: item.clone(),                started_at_ms: now_unix_timestamp_ms(),            }),        )        .await;    }    pub(crate) async fn emit_turn_item_completed(        &self,        turn_context: &TurnContext,        item: TurnItem,    ) {        record_turn_ttfm_metric(turn_context, &item).await;        self.send_event(            turn_context,            EventMsg::ItemCompleted(ItemCompletedEvent {                thread_id: self.thread_id,                turn_id: turn_context.sub_id.clone(),                item,                completed_at_ms: now_unix_timestamp_ms(),            }),        )        .await;    }    /// Adds an execpolicy amendment to both the in-memory and on-disk policies so future    /// commands can use the newly approved prefix.    pub(crate) async fn persist_execpolicy_amendment(        &self,        amendment: &ExecPolicyAmendment,    ) -> Result<(), ExecPolicyUpdateError> {        let codex_home = self            .state            .lock()            .await            .session_configuration            .codex_home()            .clone();        self.services            .exec_policy            .append_amendment_and_update(&codex_home, amendment)            .await?;        Ok(())    }    pub(crate) async fn turn_context_for_sub_id(&self, sub_id: &str) -> Option<Arc<TurnContext>> {        let active = self.active_turn.lock().await;        active            .as_ref()            .and_then(|turn| turn.task.as_ref())            .filter(|task| task.turn_context.sub_id == sub_id)            .map(|task| Arc::clone(&task.turn_context))    }    async fn active_turn_context_and_cancellation_token(        &self,    ) -> Option<(Arc<TurnContext>, CancellationToken)> {        let active = self.active_turn.lock().await;        let task = active.as_ref()?.task.as_ref()?;        Some((            Arc::clone(&task.turn_context),            task.cancellation_token.child_token(),        ))    }    pub(crate) async fn record_execpolicy_amendment_message(        &self,        sub_id: &str,        amendment: &ExecPolicyAmendment,    ) {        let Some(prefixes) = format_allow_prefixes(vec![amendment.command.clone()]) else {            warn!("execpolicy amendment for {sub_id} had no command prefix");            return;        };        let message: ResponseItem =            ContextualUserFragment::into(ApprovedCommandPrefixSaved::new(prefixes));        let turn_context = self.turn_context_for_sub_id(sub_id).await;        self.inject_no_new_turn(vec![message], turn_context.as_deref())            .await;    }    pub(crate) async fn persist_network_policy_amendment(        &self,        amendment: &NetworkPolicyAmendment,        network_approval_context: &NetworkApprovalContext,    ) -> anyhow::Result<()> {        let _refresh_guard = self            .managed_network_proxy_refresh_lock            .acquire()            .await            .map_err(|_| anyhow::anyhow!("managed network proxy refresh semaphore closed"))?;        let host =            Self::validated_network_policy_amendment_host(amendment, network_approval_context)?;        let codex_home = self            .state            .lock()            .await            .session_configuration            .codex_home()            .clone();        let execpolicy_amendment =            execpolicy_network_rule_amendment(amendment, network_approval_context, &host);        if let Some(started_network_proxy) = self.services.network_proxy.load_full() {            let proxy = started_network_proxy.proxy();            match amendment.action {                NetworkPolicyRuleAction::Allow => proxy                    .add_allowed_domain(&host)                    .await                    .map_err(|err| anyhow::anyhow!("failed to update runtime allowlist: {err}"))?,                NetworkPolicyRuleAction::Deny => proxy                    .add_denied_domain(&host)                    .await                    .map_err(|err| anyhow::anyhow!("failed to update runtime denylist: {err}"))?,            }        }        self.services            .exec_policy            .append_network_rule_and_update(                &codex_home,                &host,                execpolicy_amendment.protocol,                execpolicy_amendment.decision,                Some(execpolicy_amendment.justification),            )            .await            .map_err(|err| {                anyhow::anyhow!("failed to persist network policy amendment to execpolicy: {err}")            })?;        Ok(())    }    fn validated_network_policy_amendment_host(        amendment: &NetworkPolicyAmendment,        network_approval_context: &NetworkApprovalContext,    ) -> anyhow::Result<String> {        let approved_host = normalize_host(&network_approval_context.host);        let amendment_host = normalize_host(&amendment.host);        if amendment_host != approved_host {            return Err(anyhow::anyhow!(                "network policy amendment host '{}' does not match approved host '{}'",                amendment.host,                network_approval_context.host            ));        }        Ok(approved_host)    }    pub(crate) async fn record_network_policy_amendment_message(        &self,        sub_id: &str,        amendment: &NetworkPolicyAmendment,    ) {        let message: ResponseItem = ContextualUserFragment::into(NetworkRuleSaved::new(amendment));        let turn_context = self.turn_context_for_sub_id(sub_id).await;        self.inject_no_new_turn(vec![message], turn_context.as_deref())            .await;    }    /// Emit an exec approval request event and await the user's decision.    ///    /// The request is keyed by `call_id` + `approval_id` so matching responses    /// are delivered to the correct in-flight turn. If the pending approval is    /// cleared before a response arrives, treat it as an abort so interrupted    /// turns do not continue on a synthetic denial.    ///    /// Note that if `available_decisions` is `None`, then the other fields will    /// be used to derive the available decisions via    /// [ExecApprovalRequestEvent::default_available_decisions].    #[allow(clippy::too_many_arguments)]    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub async fn request_command_approval(        &self,        turn_context: &TurnContext,        call_id: String,        approval_id: Option<String>,        command: Vec<String>,        cwd: AbsolutePathBuf,        reason: Option<String>,        network_approval_context: Option<NetworkApprovalContext>,        proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,        additional_permissions: Option<AdditionalPermissionProfile>,        available_decisions: Option<Vec<ReviewDecision>>,    ) -> ReviewDecision {        //  command-level approvals use `call_id`.        // `approval_id` is only present for subcommand callbacks (execve intercept)        let effective_approval_id = approval_id.clone().unwrap_or_else(|| call_id.clone());        // Add the tx_approve callback to the map before sending the request.        let (tx_approve, rx_approve) = oneshot::channel();        let prev_entry = {            let mut active = self.active_turn.lock().await;            match active.as_mut() {                Some(at) => {                    let mut ts = at.turn_state.lock().await;                    ts.insert_pending_approval(effective_approval_id.clone(), tx_approve)                }                None => None,            }        };        if prev_entry.is_some() {            warn!("Overwriting existing pending approval for call_id: {effective_approval_id}");        }        let parsed_cmd = parse_command(&command);        let proposed_network_policy_amendments = network_approval_context.as_ref().map(|context| {            vec![                NetworkPolicyAmendment {                    host: context.host.clone(),                    action: NetworkPolicyRuleAction::Allow,                },                NetworkPolicyAmendment {                    host: context.host.clone(),                    action: NetworkPolicyRuleAction::Deny,                },            ]        });        let available_decisions = available_decisions.unwrap_or_else(|| {            ExecApprovalRequestEvent::default_available_decisions(                network_approval_context.as_ref(),                proposed_execpolicy_amendment.as_ref(),                proposed_network_policy_amendments.as_deref(),                additional_permissions.as_ref(),            )        });        let event = EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {            call_id,            approval_id,            turn_id: turn_context.sub_id.clone(),            started_at_ms: now_unix_timestamp_ms(),            command,            cwd,            reason,            network_approval_context,            proposed_execpolicy_amendment,            proposed_network_policy_amendments,            additional_permissions,            available_decisions: Some(available_decisions),            parsed_cmd,        });        self.send_event(turn_context, event).await;        rx_approve.await.unwrap_or(ReviewDecision::Abort)    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub async fn request_patch_approval(        &self,        turn_context: &TurnContext,        call_id: String,        changes: HashMap<PathBuf, FileChange>,        reason: Option<String>,        grant_root: Option<PathBuf>,    ) -> oneshot::Receiver<ReviewDecision> {        // Add the tx_approve callback to the map before sending the request.        let (tx_approve, rx_approve) = oneshot::channel();        let approval_id = call_id.clone();        let prev_entry = {            let mut active = self.active_turn.lock().await;            match active.as_mut() {                Some(at) => {                    let mut ts = at.turn_state.lock().await;                    ts.insert_pending_approval(approval_id.clone(), tx_approve)                }                None => None,            }        };        if prev_entry.is_some() {            warn!("Overwriting existing pending approval for call_id: {approval_id}");        }        let event = EventMsg::ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent {            call_id,            turn_id: turn_context.sub_id.clone(),            started_at_ms: now_unix_timestamp_ms(),            changes,            reason,            grant_root,        });        self.send_event(turn_context, event).await;        rx_approve    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub(crate) async fn request_permissions_for_environment(        self: &Arc<Self>,        turn_context: &Arc<TurnContext>,        call_id: String,        args: RequestPermissionsArgs,        environment: TurnEnvironmentSelection,        cancellation_token: CancellationToken,    ) -> Option<RequestPermissionsResponse> {        match turn_context.as_ref().approval_policy.value() {            AskForApproval::Never => {                return Some(RequestPermissionsResponse {                    permissions: RequestPermissionProfile::default(),                    scope: PermissionGrantScope::Turn,                    strict_auto_review: false,                });            }            AskForApproval::Granular(granular_config)                if !granular_config.allows_request_permissions() =>            {                return Some(RequestPermissionsResponse {                    permissions: RequestPermissionProfile::default(),                    scope: PermissionGrantScope::Turn,                    strict_auto_review: false,                });            }            AskForApproval::OnFailure            | AskForApproval::OnRequest            | AskForApproval::UnlessTrusted            | AskForApproval::Granular(_) => {}        }        let requested_permissions = args.permissions;        // TODO(anp): Migrate request_permissions to support paths from foreign environments.        let Ok(native_environment_cwd) = environment.cwd.to_abs_path() else {            warn!(                cwd = %environment.cwd,                "request_permissions requires a cwd native to the Codex host"            );            return Some(RequestPermissionsResponse {                permissions: RequestPermissionProfile::default(),                scope: PermissionGrantScope::Turn,                strict_auto_review: false,            });        };        if crate::guardian::routes_approval_to_guardian(turn_context.as_ref()) {            let originating_turn_state = {                let active = self.active_turn.lock().await;                active.as_ref().map(|active| Arc::clone(&active.turn_state))            };            let review_id = crate::guardian::new_guardian_review_id();            let session = Arc::clone(self);            let turn = Arc::clone(turn_context);            let request = crate::guardian::GuardianApprovalRequest::RequestPermissions {                id: call_id,                turn_id: turn_context.sub_id.clone(),                reason: args.reason,                permissions: requested_permissions.clone(),            };            let review_rx = crate::guardian::spawn_approval_request_review(                session,                turn,                review_id,                request,                /*retry_reason*/ None,                codex_analytics::GuardianApprovalRequestSource::MainTurn,                cancellation_token.clone(),            );            let decision = tokio::select! {                biased;                _ = cancellation_token.cancelled() => return None,                decision = review_rx => decision.unwrap_or(ReviewDecision::Denied),            };            let response = match decision {                ReviewDecision::Approved | ReviewDecision::ApprovedExecpolicyAmendment { .. } => {                    RequestPermissionsResponse {                        permissions: requested_permissions.clone(),                        scope: PermissionGrantScope::Turn,                        strict_auto_review: false,                    }                }                ReviewDecision::ApprovedForSession => RequestPermissionsResponse {                    permissions: requested_permissions.clone(),                    scope: PermissionGrantScope::Session,                    strict_auto_review: false,                },                ReviewDecision::NetworkPolicyAmendment {                    network_policy_amendment,                } => match network_policy_amendment.action {                    NetworkPolicyRuleAction::Allow => RequestPermissionsResponse {                        permissions: requested_permissions.clone(),                        scope: PermissionGrantScope::Turn,                        strict_auto_review: false,                    },                    NetworkPolicyRuleAction::Deny => RequestPermissionsResponse {                        permissions: RequestPermissionProfile::default(),                        scope: PermissionGrantScope::Turn,                        strict_auto_review: false,                    },                },                ReviewDecision::Abort | ReviewDecision::Denied | ReviewDecision::TimedOut => {                    RequestPermissionsResponse {                        permissions: RequestPermissionProfile::default(),                        scope: PermissionGrantScope::Turn,                        strict_auto_review: false,                    }                }            };            let response = Self::normalize_request_permissions_response(                requested_permissions,                response,                native_environment_cwd.as_path(),            );            self.record_granted_request_permissions_for_turn(                &response,                &environment.environment_id,                originating_turn_state.as_ref(),            )            .await;            return Some(response);        }        let (tx_response, rx_response) = oneshot::channel();        let prev_entry = {            let mut active = self.active_turn.lock().await;            match active.as_mut() {                Some(at) => {                    let mut ts = at.turn_state.lock().await;                    ts.insert_pending_request_permissions(                        call_id.clone(),                        PendingRequestPermissions {                            tx_response,                            requested_permissions: requested_permissions.clone(),                            environment: environment.clone(),                        },                    )                }                None => None,            }        };        if prev_entry.is_some() {            warn!("Overwriting existing pending request_permissions for call_id: {call_id}");        }        let event = EventMsg::RequestPermissions(RequestPermissionsEvent {            call_id: call_id.clone(),            turn_id: turn_context.sub_id.clone(),            environment_id: Some(environment.environment_id.clone()),            started_at_ms: now_unix_timestamp_ms(),            reason: args.reason,            permissions: requested_permissions,            cwd: Some(native_environment_cwd),        });        self.send_event(turn_context.as_ref(), event).await;        tokio::select! {            biased;            _ = cancellation_token.cancelled() => {                let mut active = self.active_turn.lock().await;                if let Some(at) = active.as_mut() {                    let mut ts = at.turn_state.lock().await;                    let _ = ts.remove_pending_request_permissions(&call_id);                }                None            }            response = rx_response => response.ok(),        }    }    pub(crate) async fn request_permissions_for_cwd(        self: &Arc<Self>,        turn_context: &Arc<TurnContext>,        call_id: String,        args: RequestPermissionsArgs,        cwd: AbsolutePathBuf,        cancellation_token: CancellationToken,    ) -> Option<RequestPermissionsResponse> {        let turn_environment = match args.environment_id.as_deref() {            Some(environment_id) => turn_context                .environments                .turn_environments                .iter()                .find(|environment| environment.environment_id == environment_id),            None => turn_context.environments.primary(),        };        let Some(turn_environment) = turn_environment else {            return Some(RequestPermissionsResponse {                permissions: RequestPermissionProfile::default(),                scope: PermissionGrantScope::Turn,                strict_auto_review: false,            });        };        let mut environment = turn_environment.selection();        environment.cwd = PathUri::from_abs_path(&cwd);        self.request_permissions_for_environment(            turn_context,            call_id,            args,            environment,            cancellation_token,        )        .await    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub async fn request_user_input(        &self,        turn_context: &TurnContext,        call_id: String,        args: RequestUserInputArgs,    ) -> Option<RequestUserInputResponse> {        let sub_id = turn_context.sub_id.clone();        let (tx_response, rx_response) = oneshot::channel();        let event_id = sub_id.clone();        let prev_entry = {            let mut active = self.active_turn.lock().await;            match active.as_mut() {                Some(at) => {                    let mut ts = at.turn_state.lock().await;                    ts.insert_pending_user_input(sub_id, tx_response)                }                None => None,            }        };        if prev_entry.is_some() {            warn!("Overwriting existing pending user input for sub_id: {event_id}");        }        let event = EventMsg::RequestUserInput(RequestUserInputEvent {            call_id,            turn_id: turn_context.sub_id.clone(),            questions: args.questions,            auto_resolution_ms: args.auto_resolution_ms,        });        turn_context            .turn_metadata_state            .mark_user_input_requested_during_turn();        self.send_event(turn_context, event).await;        rx_response.await.ok()    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub async fn notify_user_input_response(        &self,        sub_id: &str,        response: RequestUserInputResponse,    ) {        let entry = {            let mut active = self.active_turn.lock().await;            match active.as_mut() {                Some(at) => {                    let mut ts = at.turn_state.lock().await;                    ts.remove_pending_user_input(sub_id)                }                None => None,            }        };        match entry {            Some(tx_response) => {                tx_response.send(response).ok();            }            None => {                warn!("No pending user input found for sub_id: {sub_id}");            }        }    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub async fn notify_request_permissions_response(        &self,        call_id: &str,        response: RequestPermissionsResponse,    ) {        let (entry, originating_turn_state) = {            let mut active = self.active_turn.lock().await;            match active.as_mut() {                Some(at) => {                    let mut ts = at.turn_state.lock().await;                    let entry = ts.remove_pending_request_permissions(call_id);                    let originating_turn_state = entry.as_ref().map(|_| Arc::clone(&at.turn_state));                    (entry, originating_turn_state)                }                None => (None, None),            }        };        match entry {            Some(entry) => {                // TODO(anp): Migrate request_permissions to support paths from foreign environments.                let response = match entry.environment.cwd.to_abs_path() {                    Ok(native_environment_cwd) => Self::normalize_request_permissions_response(                        entry.requested_permissions,                        response,                        native_environment_cwd.as_path(),                    ),                    Err(err) => {                        warn!(                            cwd = %entry.environment.cwd,                            %err,                            "request_permissions requires a cwd native to the Codex host"                        );                        RequestPermissionsResponse {                            permissions: RequestPermissionProfile::default(),                            scope: PermissionGrantScope::Turn,                            strict_auto_review: false,                        }                    }                };                self.record_granted_request_permissions_for_turn(                    &response,                    &entry.environment.environment_id,                    originating_turn_state.as_ref(),                )                .await;                entry.tx_response.send(response).ok();            }            None => {                warn!("No pending request_permissions found for call_id: {call_id}");            }        }    }    fn normalize_request_permissions_response(        requested_permissions: RequestPermissionProfile,        response: RequestPermissionsResponse,        cwd: &Path,    ) -> RequestPermissionsResponse {        if response.strict_auto_review && matches!(response.scope, PermissionGrantScope::Session) {            return RequestPermissionsResponse {                permissions: RequestPermissionProfile::default(),                scope: PermissionGrantScope::Turn,                strict_auto_review: false,            };        }        if response.permissions.is_empty() {            return response;        }        RequestPermissionsResponse {            permissions: intersect_permission_profiles(                requested_permissions.into(),                response.permissions.into(),                cwd,            )            .into(),            scope: response.scope,            strict_auto_review: response.strict_auto_review,        }    }    async fn record_granted_request_permissions_for_turn(        &self,        response: &RequestPermissionsResponse,        environment_id: &str,        originating_turn_state: Option<&Arc<Mutex<crate::state::TurnState>>>,    ) {        if response.permissions.is_empty() {            return;        }        match response.scope {            PermissionGrantScope::Turn => {                if let Some(turn_state) = originating_turn_state {                    let mut ts = turn_state.lock().await;                    let permissions: AdditionalPermissionProfile =                        response.permissions.clone().into();                    ts.record_granted_permissions(environment_id, permissions);                    if response.strict_auto_review {                        ts.enable_strict_auto_review();                    }                }            }            PermissionGrantScope::Session => {                let mut state = self.state.lock().await;                state.record_granted_permissions(                    environment_id,                    response.permissions.clone().into(),                );            }        }    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn reads must stay consistent with the matching turn state"    )]    pub(crate) async fn granted_turn_permissions(        &self,        environment_id: &str,    ) -> Option<AdditionalPermissionProfile> {        let active = self.active_turn.lock().await;        let active = active.as_ref()?;        let ts = active.turn_state.lock().await;        ts.granted_permissions(environment_id)    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn reads must stay consistent with the matching turn state"    )]    pub(crate) async fn strict_auto_review_enabled_for_turn(&self) -> bool {        let active = self.active_turn.lock().await;        let Some(active) = active.as_ref() else {            return false;        };        let ts = active.turn_state.lock().await;        ts.strict_auto_review_enabled()    }    pub(crate) async fn granted_session_permissions(        &self,        environment_id: &str,    ) -> Option<AdditionalPermissionProfile> {        let state = self.state.lock().await;        state.granted_permissions(environment_id)    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub async fn notify_dynamic_tool_response(&self, call_id: &str, response: DynamicToolResponse) {        let entry = {            let mut active = self.active_turn.lock().await;            match active.as_mut() {                Some(at) => {                    let mut ts = at.turn_state.lock().await;                    ts.remove_pending_dynamic_tool(call_id)                }                None => None,            }        };        match entry {            Some(tx_response) => {                tx_response.send(response).ok();            }            None => {                warn!("No pending dynamic tool call found for call_id: {call_id}");            }        }    }    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub async fn notify_approval(&self, approval_id: &str, decision: ReviewDecision) {        let entry = {            let mut active = self.active_turn.lock().await;            match active.as_mut() {                Some(at) => {                    let mut ts = at.turn_state.lock().await;                    ts.remove_pending_approval(approval_id)                }                None => None,            }        };        match entry {            Some(tx_approve) => {                tx_approve.send(decision).ok();            }            None => {                warn!("No pending approval found for call_id: {approval_id}");            }        }    }    /// Records conversation items: append to history, persist to rollout, and    /// notify clients observing raw response items.    pub(crate) fn prepare_conversation_items_for_history<'a>(        &self,        turn_context: &TurnContext,        items: &'a [ResponseItem],    ) -> Cow<'a, [ResponseItem]> {        if !turn_context.features.enabled(Feature::ResizeAllImages) {            return Cow::Borrowed(items);        }        let mut prepared_items = items.to_vec();        prepare_response_items(&mut prepared_items);        Cow::Owned(prepared_items)    }    pub(crate) fn response_item_from_user_input(        &self,        turn_context: &TurnContext,        input: Vec<UserInput>,    ) -> ResponseItem {        let local_image_preparation = if turn_context.features.enabled(Feature::ResizeAllImages) {            LocalImagePreparation::Defer        } else {            LocalImagePreparation::Process        };        ResponseItem::from(ResponseInputItem::from_user_input(            input,            local_image_preparation,        ))    }    pub(crate) async fn record_conversation_items(        &self,        turn_context: &TurnContext,        items: &[ResponseItem],    ) {        let items = self.prepare_conversation_items_for_history(turn_context, items);        let items = items.as_ref();        {            let mut state = self.state.lock().await;            state.record_items(items.iter(), turn_context.truncation_policy);        }        self.persist_rollout_response_items(items).await;        self.send_raw_response_items(turn_context, items).await;    }    pub(crate) async fn record_inter_agent_communication(        &self,        turn_context: &TurnContext,        communication: InterAgentCommunication,    ) {        let response_item = communication.to_model_input_item();        let items = self.prepare_conversation_items_for_history(            turn_context,            std::slice::from_ref(&response_item),        );        let items = items.as_ref();        {            let mut state = self.state.lock().await;            state.record_items(items.iter(), turn_context.truncation_policy);        }        self.persist_rollout_items(&[RolloutItem::InterAgentCommunication(communication)])            .await;        self.send_raw_response_items(turn_context, items).await;    }    async fn maybe_warn_on_server_model_mismatch(        self: &Arc<Self>,        turn_context: &Arc<TurnContext>,        server_model: String,    ) -> bool {        let requested_model = turn_context.model_info.slug.clone();        let server_model_normalized = server_model.to_ascii_lowercase();        let requested_model_normalized = requested_model.to_ascii_lowercase();        if server_model_normalized == requested_model_normalized {            info!("server reported model {server_model} (matches requested model)");            return false;        }        warn!("server reported model {server_model} while requested model was {requested_model}");        let warning_message = format!(            "Your account was flagged for potentially high-risk cyber activity and this request was routed to gpt-5.2 as a fallback. To regain access to gpt-5.3-codex, apply for trusted access: {CYBER_VERIFY_URL} or learn more: {CYBER_SAFETY_URL}"        );        self.send_event(            turn_context,            EventMsg::ModelReroute(ModelRerouteEvent {                from_model: requested_model.clone(),                to_model: server_model.clone(),                reason: ModelRerouteReason::HighRiskCyberActivity,            }),        )        .await;        self.send_event(            turn_context,            EventMsg::Warning(WarningEvent {                message: warning_message.clone(),            }),        )        .await;        true    }    pub(crate) async fn emit_model_verification(        self: &Arc<Self>,        turn_context: &Arc<TurnContext>,        verifications: Vec<ModelVerification>,    ) {        self.send_event(            turn_context,            EventMsg::ModelVerification(ModelVerificationEvent { verifications }),        )        .await;    }    pub(crate) async fn emit_turn_moderation_metadata(        self: &Arc<Self>,        turn_context: &Arc<TurnContext>,        metadata: TurnModerationMetadataEvent,    ) {        self.send_event(turn_context, EventMsg::TurnModerationMetadata(metadata))            .await;    }    #[cfg(test)]    pub(crate) async fn replace_history(        &self,        items: Vec<ResponseItem>,        reference_context_item: Option<TurnContextItem>,    ) {        let mut state = self.state.lock().await;        state.replace_history(items, reference_context_item);    }    pub(crate) async fn replace_compacted_history(        &self,        items: Vec<ResponseItem>,        reference_context_item: Option<TurnContextItem>,        compacted_item: CompactedItem,    ) {        {            let mut state = self.state.lock().await;            state.replace_history(items, reference_context_item.clone());        }        self.persist_rollout_items(&[RolloutItem::Compacted(compacted_item)])            .await;        if let Some(turn_context_item) = reference_context_item {            self.persist_rollout_items(&[RolloutItem::TurnContext(turn_context_item)])                .await;        }        {            let mut state = self.state.lock().await;            state.queue_pending_session_start_source(codex_hooks::SessionStartSource::Compact);        }    }    async fn persist_rollout_response_items(&self, items: &[ResponseItem]) {        let rollout_items: Vec<RolloutItem> = items            .iter()            .cloned()            .map(RolloutItem::ResponseItem)            .collect();        self.persist_rollout_items(&rollout_items).await;    }    pub fn enabled(&self, feature: Feature) -> bool {        self.features.enabled(feature)    }    pub(crate) fn features(&self) -> ManagedFeatures {        self.features.clone()    }    pub(crate) async fn collaboration_mode(&self) -> CollaborationMode {        let state = self.state.lock().await;        state.session_configuration.collaboration_mode.clone()    }    pub(crate) fn multi_agent_version(&self) -> Option<MultiAgentVersion> {        self.multi_agent_version.get().copied()    }    pub(crate) fn set_multi_agent_version_if_unset(        &self,        multi_agent_version: MultiAgentVersion,    ) -> MultiAgentVersion {        *self.multi_agent_version.get_or_init(|| multi_agent_version)    }    pub(crate) fn resolve_multi_agent_version_for_model(        &self,        model_info: &ModelInfo,        config: &Config,    ) -> MultiAgentVersion {        if let Some(multi_agent_version) = self.multi_agent_version() {            return multi_agent_version;        }        let selected = model_info            .multi_agent_version            .unwrap_or_else(|| config.multi_agent_version_from_features());        self.set_multi_agent_version_if_unset(selected)    }    async fn send_raw_response_items(&self, turn_context: &TurnContext, items: &[ResponseItem]) {        for item in items {            self.send_event(                turn_context,                EventMsg::RawResponseItem(RawResponseItemEvent { item: item.clone() }),            )            .await;        }    }    pub(crate) async fn build_initial_context(        &self,        turn_context: &TurnContext,    ) -> Vec<ResponseItem> {        let mut developer_sections = Vec::<String>::with_capacity(8);        let mut contextual_user_sections = Vec::<String>::with_capacity(2);        let mut separate_developer_sections = Vec::<String>::new();        let (            reference_context_item,            previous_turn_settings,            collaboration_mode,            base_instructions,            session_source,            auto_compact_window_id,        ) = {            let state = self.state.lock().await;            (                state.reference_context_item(),                state.previous_turn_settings(),                state.session_configuration.collaboration_mode.clone(),                state.session_configuration.base_instructions.clone(),                state.session_configuration.session_source.clone(),                state.auto_compact_window_id(),            )        };        if let Some(model_switch_message) =            crate::context_manager::updates::build_model_instructions_update_item(                previous_turn_settings.as_ref(),                turn_context,            )        {            developer_sections.push(model_switch_message);        }        if turn_context.config.include_permissions_instructions {            developer_sections.push(                PermissionsInstructions::from_permission_profile(                    &turn_context.permission_profile,                    turn_context.approval_policy.value(),                    turn_context.config.approvals_reviewer,                    self.services.exec_policy.current().as_ref(),                    #[allow(deprecated)]                    &turn_context.cwd,                    turn_context                        .features                        .enabled(Feature::ExecPermissionApprovals),                    turn_context                        .features                        .enabled(Feature::RequestPermissionsTool),                )                .render(),            );        }        let separate_guardian_developer_message =            crate::guardian::is_guardian_reviewer_source(&session_source);        // Keep the guardian policy prompt out of the aggregated developer bundle so it        // stays isolated as its own top-level developer message for guardian subagents.        if !separate_guardian_developer_message            && let Some(developer_instructions) = turn_context.developer_instructions.as_deref()            && !developer_instructions.is_empty()        {            developer_sections.push(developer_instructions.to_string());        }        // Add developer instructions from collaboration_mode if they exist and are non-empty        if turn_context.config.include_collaboration_mode_instructions            && let Some(collab_instructions) =                CollaborationModeInstructions::from_collaboration_mode(&collaboration_mode)        {            developer_sections.push(collab_instructions.render());        }        if let Some(realtime_update) = crate::context_manager::updates::build_initial_realtime_item(            reference_context_item.as_ref(),            previous_turn_settings.as_ref(),            turn_context,        ) {            developer_sections.push(realtime_update);        }        if self.features.enabled(Feature::Personality)            && let Some(personality) = turn_context.personality        {            let model_info = turn_context.model_info.clone();            let has_baked_personality = model_info.supports_personality()                && base_instructions == model_info.get_model_instructions(Some(personality));            if !has_baked_personality                && let Some(personality_message) =                    crate::context_manager::updates::personality_message_for(                        &model_info,                        personality,                    )            {                developer_sections                    .push(PersonalitySpecInstructions::new(personality_message).render());            }        }        if turn_context.config.include_apps_instructions && turn_context.apps_enabled() {            let mcp_connection_manager = self.services.mcp_connection_manager.load_full();            let accessible_and_enabled_connectors =                connectors::list_accessible_and_enabled_connectors_from_manager(                    &mcp_connection_manager,                    &turn_context.config,                )                .await;            if let Some(apps_instructions) =                AppsInstructions::from_connectors(&accessible_and_enabled_connectors)            {                developer_sections.push(apps_instructions.render());            }        }        if turn_context.config.include_skill_instructions {            let available_skills = build_available_skills(                &turn_context.turn_skills.outcome,                default_skill_metadata_budget(turn_context.model_info.context_window),                SkillRenderSideEffects::ThreadStart {                    session_telemetry: &self.services.session_telemetry,                },            );            if let Some(available_skills) = available_skills {                let warning_message = available_skills.warning_message.clone();                let skills_instructions = AvailableSkillsInstructions::from(available_skills);                if let Some(warning_message) = warning_message {                    self.send_event_raw(Event {                        id: String::new(),                        msg: EventMsg::Warning(WarningEvent {                            message: warning_message,                        }),                    })                    .await;                }                developer_sections.push(skills_instructions.render());            }        }        let loaded_plugins = self            .services            .plugins_manager            .plugins_for_config(&turn_context.config.plugins_config_input())            .await;        if let Some(plugin_instructions) =            AvailablePluginsInstructions::from_plugins(loaded_plugins.capability_summaries())        {            developer_sections.push(plugin_instructions.render());        }        let context_contributors = self.services.extensions.context_contributors().to_vec();        for contributor in context_contributors {            for fragment in contributor                .contribute(                    &self.services.session_extension_data,                    &self.services.thread_extension_data,                )                .await            {                match fragment.slot() {                    PromptSlot::DeveloperPolicy | PromptSlot::DeveloperCapabilities => {                        developer_sections.push(fragment.text().to_string());                    }                    PromptSlot::ContextualUser => {                        contextual_user_sections.push(fragment.text().to_string());                    }                    PromptSlot::SeparateDeveloper => {                        separate_developer_sections.push(fragment.text().to_string());                    }                }            }        }        if let Some(user_instructions) = turn_context.user_instructions.as_deref() {            contextual_user_sections.push(user_instructions.to_string());        }        // This is full-context metadata. Steady-state context diffs should not re-emit it.        if turn_context.features.enabled(Feature::TokenBudget)            && let Some(model_context_window) = turn_context.model_context_window()        {            developer_sections.push(                crate::context::TokenBudgetContext::new(                    self.thread_id(),                    auto_compact_window_id,                    model_context_window,                )                .render(),            );        }        if turn_context.config.include_environment_context {            let shell = self.user_shell();            let subagents = self                .services                .agent_control                .format_environment_context_subagents(self.thread_id)                .await;            contextual_user_sections.push(                crate::context::EnvironmentContext::from_turn_context(turn_context, shell.as_ref())                    .with_subagents(subagents)                    .render(),            );        }        let multi_agent_v2_usage_hint_text =            multi_agents::usage_hint_text(turn_context, &session_source);        let mut items = Vec::with_capacity(4);        if let Some(developer_message) =            crate::context_manager::updates::build_developer_update_item(developer_sections)        {            items.push(developer_message);        }        for section in separate_developer_sections {            if let Some(developer_message) =                crate::context_manager::updates::build_developer_update_item(vec![section])            {                items.push(developer_message);            }        }        if let Some(usage_hint_text) = multi_agent_v2_usage_hint_text            && let Some(usage_hint_message) =                crate::context_manager::updates::build_developer_update_item(vec![                    usage_hint_text.to_string(),                ])        {            items.push(usage_hint_message);        }        if let Some(contextual_user_message) =            crate::context_manager::updates::build_contextual_user_message(contextual_user_sections)        {            items.push(contextual_user_message);        }        // Emit the guardian policy prompt as a separate developer item so the guardian        // subagent sees a distinct, easy-to-audit instruction block.        if separate_guardian_developer_message            && let Some(developer_instructions) = turn_context.developer_instructions.as_deref()            && !developer_instructions.is_empty()            && let Some(guardian_developer_message) =                crate::context_manager::updates::build_developer_update_item(vec![                    developer_instructions.to_string(),                ])        {            items.push(guardian_developer_message);        }        items    }    pub(crate) async fn persist_rollout_items(&self, items: &[RolloutItem]) {        if let Some(live_thread) = self.live_thread()            && let Err(e) = live_thread.append_items(items).await        {            error!("failed to record rollout items: {e:#}");        }    }    pub(crate) async fn clone_history(&self) -> ContextManager {        let state = self.state.lock().await;        state.clone_history()    }    pub(crate) async fn current_window_id(&self) -> String {        let state = self.state.lock().await;        let thread_id = self.thread_id;        let window_id = state.auto_compact_window_id();        format!("{thread_id}:{window_id}")    }    pub(crate) async fn advance_auto_compact_window_id(&self) -> u64 {        let mut state = self.state.lock().await;        state.advance_auto_compact_window_id()    }    pub(crate) async fn request_new_context_window(&self) {        let mut state = self.state.lock().await;        state.request_new_context_window();    }    pub(crate) async fn maybe_start_new_context_window(        &self,        turn_context: &TurnContext,    ) -> Option<u64> {        let window_id = {            let mut state = self.state.lock().await;            state.start_new_context_window_if_requested()        };        let window_id = window_id?;        let context_items = self.build_initial_context(turn_context).await;        let turn_context_item = turn_context.to_turn_context_item();        let replacement_history = context_items;        {            let mut state = self.state.lock().await;            state.replace_history(replacement_history.clone(), Some(turn_context_item.clone()));        };        self.persist_rollout_items(&[            RolloutItem::Compacted(CompactedItem {                message: String::new(),                replacement_history: Some(replacement_history),                window_id: Some(window_id),            }),            RolloutItem::TurnContext(turn_context_item),        ])        .await;        {            let mut state = self.state.lock().await;            state.queue_pending_session_start_source(codex_hooks::SessionStartSource::Compact);        }        self.recompute_token_usage(turn_context).await;        Some(window_id)    }    pub(crate) async fn reference_context_item(&self) -> Option<TurnContextItem> {        let state = self.state.lock().await;        state.reference_context_item()    }    /// Persist the latest turn context snapshot for the first real user turn and for    /// steady-state turns that emit model-visible context updates.    ///    /// When the reference snapshot is missing, this injects full initial context. Otherwise, it    /// emits only settings diff items.    ///    /// If full context is injected and a model switch occurred, this prepends the    /// `<model_switch>` developer message so model-specific instructions are not lost.    ///    /// This is the normal runtime path that establishes a new `reference_context_item`.    /// Mid-turn compaction is the other path that can re-establish that baseline when it    /// reinjects full initial context into replacement history. Other non-regular tasks    /// intentionally do not update the baseline.    #[instrument(level = "trace", skip_all)]    pub(crate) async fn record_context_updates_and_set_reference_context_item(        &self,        turn_context: &TurnContext,    ) {        let reference_context_item = {            let state = self.state.lock().await;            state.reference_context_item()        };        let should_inject_full_context = reference_context_item.is_none();        let context_items = if should_inject_full_context {            self.build_initial_context(turn_context).await        } else {            // Steady-state path: append only context diffs to minimize token overhead.            self.build_settings_update_items(reference_context_item.as_ref(), turn_context)                .await        };        let turn_context_item = turn_context.to_turn_context_item();        if !context_items.is_empty() {            self.record_conversation_items(turn_context, &context_items)                .await;        }        // Persist one `TurnContextItem` per real user turn so resume/lazy replay can recover the        // latest durable baseline even when this turn emitted no model-visible context diffs.        self.persist_rollout_items(&[RolloutItem::TurnContext(turn_context_item.clone())])            .await;        // Advance the in-memory diff baseline even when this turn emitted no model-visible        // context items. This keeps later runtime diffing aligned with the current turn state.        let mut state = self.state.lock().await;        state.set_reference_context_item(Some(turn_context_item));    }    pub(crate) async fn update_token_usage_info(        &self,        turn_context: &TurnContext,        token_usage: Option<&TokenUsage>,    ) {        self.record_token_usage_info(turn_context, token_usage)            .await;        self.send_token_count_event(turn_context).await;    }    pub(crate) async fn record_token_usage_info(        &self,        turn_context: &TurnContext,        token_usage: Option<&TokenUsage>,    ) {        if let Some(token_usage) = token_usage {            let token_info = {                let mut state = self.state.lock().await;                state                    .update_token_info_from_usage(token_usage, turn_context.model_context_window());                if matches!(                    turn_context.config.model_auto_compact_token_limit_scope,                    AutoCompactTokenLimitScope::BodyAfterPrefix                ) {                    state.ensure_auto_compact_window_server_prefill_from_usage(token_usage);                }                state.token_info()            };            if let Some(token_info) = token_info.as_ref() {                for contributor in self.services.extensions.token_usage_contributors() {                    contributor                        .on_token_usage(                            &self.services.session_extension_data,                            &self.services.thread_extension_data,                            turn_context.extension_data.as_ref(),                            token_info,                        )                        .await;                }            }        }    }    pub(crate) async fn recompute_token_usage(&self, turn_context: &TurnContext) {        let history = self.clone_history().await;        let base_instructions = self.get_base_instructions().await;        let Some(estimated_total_tokens) =            history.estimate_token_count_with_base_instructions(&base_instructions)        else {            return;        };        {            let mut state = self.state.lock().await;            let mut info = state.token_info().unwrap_or(TokenUsageInfo {                total_token_usage: TokenUsage::default(),                last_token_usage: TokenUsage::default(),                model_context_window: None,            });            info.last_token_usage = TokenUsage {                input_tokens: 0,                cached_input_tokens: 0,                output_tokens: 0,                reasoning_output_tokens: 0,                total_tokens: estimated_total_tokens.max(0),            };            if let Some(model_context_window) = turn_context.model_context_window() {                info.model_context_window = Some(model_context_window);            }            state.set_token_info(Some(info));        }        self.set_auto_compact_window_estimated_prefill_for_scope(            turn_context,            estimated_total_tokens,        )        .await;        self.send_token_count_event(turn_context).await;    }    pub(crate) async fn update_rate_limits(        &self,        turn_context: &TurnContext,        new_rate_limits: RateLimitSnapshot,    ) {        self.record_rate_limits_info(new_rate_limits).await;        self.send_token_count_event(turn_context).await;    }    pub(crate) async fn record_rate_limits_info(&self, new_rate_limits: RateLimitSnapshot) {        {            let mut state = self.state.lock().await;            state.set_rate_limits(new_rate_limits);        }    }    pub(crate) async fn mcp_dependency_prompted(&self) -> HashSet<String> {        let state = self.state.lock().await;        state.mcp_dependency_prompted()    }    pub(crate) async fn record_mcp_dependency_prompted<I>(&self, names: I)    where        I: IntoIterator<Item = String>,    {        let mut state = self.state.lock().await;        state.record_mcp_dependency_prompted(names);    }    pub(crate) async fn set_server_reasoning_included(&self, included: bool) {        let mut state = self.state.lock().await;        state.set_server_reasoning_included(included);    }    pub(crate) async fn send_token_count_event(&self, turn_context: &TurnContext) {        let (info, rate_limits) = {            let state = self.state.lock().await;            state.token_info_and_rate_limits()        };        let event = EventMsg::TokenCount(TokenCountEvent { info, rate_limits });        self.send_event(turn_context, event).await;    }    pub(crate) async fn set_total_tokens_full(&self, turn_context: &TurnContext) {        if let Some(context_window) = turn_context.model_context_window() {            let mut state = self.state.lock().await;            state.set_token_usage_full(context_window);        }        self.send_token_count_event(turn_context).await;    }    pub(crate) async fn record_response_item_and_emit_turn_item(        &self,        turn_context: &TurnContext,        response_item: ResponseItem,    ) {        // Add to conversation history and persist response item to rollout.        self.record_conversation_items(turn_context, std::slice::from_ref(&response_item))            .await;        // Derive a turn item and emit lifecycle events if applicable.        if let Some(item) = parse_turn_item(&response_item) {            self.emit_turn_item_started(turn_context, &item).await;            self.emit_turn_item_completed(turn_context, item).await;        }    }    pub(crate) async fn record_user_prompt_and_emit_turn_item(        &self,        turn_context: &TurnContext,        input: &[UserInput],        client_id: Option<String>,    ) {        // Persist the user message to history, but emit the turn item from `UserInput` so        // UI-only `text_elements` are preserved. `ResponseItem::Message` does not carry        // those spans, and `record_response_item_and_emit_turn_item` would drop them.        let response_item = self.response_item_from_user_input(turn_context, input.to_vec());        self.record_conversation_items(turn_context, std::slice::from_ref(&response_item))            .await;        let mut user_message_item = UserMessageItem::new(input);        user_message_item.client_id = client_id;        let turn_item = TurnItem::UserMessage(user_message_item);        self.emit_turn_item_started(turn_context, &turn_item).await;        self.emit_turn_item_completed(turn_context, turn_item).await;        self.ensure_rollout_materialized().await;    }    pub(crate) async fn notify_stream_error(        &self,        turn_context: &TurnContext,        message: impl Into<String>,        codex_error: CodexErr,    ) {        let additional_details = codex_error.to_string();        let codex_error_info = CodexErrorInfo::ResponseStreamDisconnected {            http_status_code: codex_error.http_status_code_value(),        };        let event = EventMsg::StreamError(StreamErrorEvent {            message: message.into(),            codex_error_info: Some(codex_error_info),            additional_details: Some(additional_details),        });        self.send_event(turn_context, event).await;    }    /// Inject additional user input into the currently active turn.    ///    /// Returns the active turn id when accepted.    #[expect(        clippy::await_holding_invalid_type,        reason = "active turn checks and turn state updates must remain atomic"    )]    pub async fn steer_input(        &self,        input: Vec<UserInput>,        additional_context: BTreeMap<String, AdditionalContextEntry>,        expected_turn_id: Option<&str>,        client_user_message_id: Option<String>,        responsesapi_client_metadata: Option<HashMap<String, String>>,    ) -> Result<String, SteerInputError> {        let mut active = self.active_turn.lock().await;        let Some(active_turn) = active.as_mut() else {            return Err(SteerInputError::NoActiveTurn(input));        };        let Some(active_task) = active_turn.task.as_ref() else {            return Err(SteerInputError::NoActiveTurn(input));        };        let active_turn_id = &active_task.turn_context.sub_id;        if let Some(expected_turn_id) = expected_turn_id            && expected_turn_id != active_turn_id        {            return Err(SteerInputError::ExpectedTurnMismatch {                expected: expected_turn_id.to_string(),                actual: active_turn_id.clone(),            });        }        match active_task.kind {            crate::state::TaskKind::Regular => {}            crate::state::TaskKind::Review => {                return Err(SteerInputError::ActiveTurnNotSteerable {                    turn_kind: NonSteerableTurnKind::Review,                });            }            crate::state::TaskKind::Compact => {                return Err(SteerInputError::ActiveTurnNotSteerable {                    turn_kind: NonSteerableTurnKind::Compact,                });            }        }        if input.is_empty() {            return Err(SteerInputError::EmptyInput);        }        let additional_context_input = {            let mut state = self.state.lock().await;            state.additional_context.merge(additional_context)        };        if let Some(responsesapi_client_metadata) = responsesapi_client_metadata {            active_task                .turn_context                .turn_metadata_state                .set_responsesapi_client_metadata(responsesapi_client_metadata);        }        let mut pending_input = additional_context_input            .into_iter()            .map(ResponseItem::from)            .map(TurnInput::ResponseItem)            .collect::<Vec<_>>();        pending_input.push(TurnInput::UserInput {            content: input,            client_id: client_user_message_id,        });        self.input_queue            .extend_pending_input_and_accept_mailbox_delivery_for_turn_state(                active_turn.turn_state.as_ref(),                pending_input,            )            .await;        Ok(active_turn_id.clone())    }    pub(crate) async fn record_memory_citation_for_turn(&self, sub_id: &str) {        let turn_state = self            .input_queue            .turn_state_for_sub_id(&self.active_turn, sub_id)            .await;        let Some(turn_state) = turn_state else {            return;        };        turn_state.lock().await.has_memory_citation = true;    }    pub async fn interrupt_task(self: &Arc<Self>) {        info!("interrupt received: abort current task, if any");        let had_active_turn = self.active_turn.lock().await.is_some();        self.abort_all_tasks(TurnAbortReason::Interrupted).await;        if !had_active_turn {            self.cancel_mcp_startup().await;        }    }    pub(crate) fn hooks(&self) -> Arc<Hooks> {        self.services.hooks.load_full()    }    pub(crate) fn user_shell(&self) -> Arc<shell::Shell> {        Arc::clone(&self.services.user_shell)    }    pub(crate) async fn current_rollout_path(&self) -> anyhow::Result<Option<PathBuf>> {        let Some(live_thread) = self.live_thread() else {            return Ok(None);        };        live_thread.local_rollout_path().await.map_err(Into::into)    }    pub(crate) async fn hook_transcript_path(&self) -> Option<PathBuf> {        self.ensure_rollout_materialized().await;        match self.current_rollout_path().await {            Ok(path) => path,            Err(err) => {                warn!("{err}");                None            }        }    }    pub(crate) async fn take_pending_session_start_source(        &self,    ) -> Option<codex_hooks::SessionStartSource> {        let mut state = self.state.lock().await;        state.take_pending_session_start_source()    }    fn show_raw_agent_reasoning(&self) -> bool {        self.services.show_raw_agent_reasoning    }}pub(crate) fn emit_subagent_session_started(    analytics_events_client: &AnalyticsEventsClient,    client_metadata: AppServerClientMetadata,    session_id: SessionId,    thread_id: ThreadId,    parent_thread_id: Option<ThreadId>,    thread_config: ThreadConfigSnapshot,    subagent_source: SubAgentSource,) {    let AppServerClientMetadata {        client_name,        client_version,    } = client_metadata;    let (Some(client_name), Some(client_version)) = (client_name, client_version) else {        tracing::warn!("skipping subagent thread analytics: missing inherited client metadata");        return;    };    let created_at = SystemTime::now()        .duration_since(UNIX_EPOCH)        .unwrap_or_default()        .as_secs();    analytics_events_client.track_subagent_thread_started(SubAgentThreadStartedInput {        session_id: session_id.to_string(),        thread_id: thread_id.to_string(),        parent_thread_id: parent_thread_id.map(|thread_id| thread_id.to_string()),        forked_from_thread_id: thread_config            .forked_from_thread_id            .map(|thread_id| thread_id.to_string()),        product_client_id: client_name.clone(),        client_name,        client_version,        model: thread_config.model,        ephemeral: thread_config.ephemeral,        subagent_source,        created_at,    });}/// Builds the hook engine for one config snapshot, including any enabled plugin hooks.async fn build_hooks_for_config(    config: &Config,    plugins_manager: &PluginsManager,    environment: Option<&TurnEnvironment>,) -> Hooks {    let (hook_shell_program, hook_shell_argv) = environment        .and_then(|environment| environment.shell.as_ref())        .map(|shell| {            let mut argv = shell.derive_exec_args("", /*use_login_shell*/ false);            let program = argv.remove(0);            let _ = argv.pop();            (Some(program), argv)        })        .unwrap_or_default();    let plugins_input = config.plugins_config_input();    let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await;    let plugin_hook_sources = plugin_outcome.effective_plugin_hook_sources();    let plugin_hook_load_warnings = plugin_outcome.effective_plugin_hook_warnings();    Hooks::new(HooksConfig {        legacy_notify_argv: config.notify.clone(),        feature_enabled: config.features.enabled(Feature::CodexHooks),        bypass_hook_trust: config.bypass_hook_trust,        config_layer_stack: Some(config.config_layer_stack.clone()),        plugin_hook_sources,        plugin_hook_load_warnings,        shell_program: hook_shell_program,        shell_args: hook_shell_argv,    })}#[cfg(test)]pub(crate) mod tests;