diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 8f0d2f1c01..646528cf97 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -480,20 +480,6 @@ impl Codex { } = args; let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY); let (tx_event, rx_event) = async_channel::unbounded(); - let fs = environment_selections.primary_filesystem(); - let plugins_input = config.plugins_config_input(); - let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await; - let effective_skill_roots = plugin_outcome.effective_plugin_skill_roots(); - let skills_input = skills_load_input_from_config(&config, effective_skill_roots); - let loaded_skills = skills_manager.skills_for_config(&skills_input, fs).await; - - for err in &loaded_skills.errors { - error!( - "failed to load skill {}: {}", - err.path.display(), - err.message - ); - } if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) = session_source && depth >= config.agent_max_depth diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 884c440f28..8945880c33 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -1,6 +1,7 @@ use super::input_queue::InputQueue; use super::*; use crate::goals::GoalRuntimeState; +use crate::skills::SkillError; use crate::state::ActiveTurn; use codex_protocol::SessionId; use codex_protocol::config_types::ServiceTier; @@ -405,6 +406,29 @@ pub(crate) struct AppServerClientMetadata { pub(crate) client_version: Option, } +async fn warm_plugins_and_skills_for_session_init( + config: Arc, + environment_manager: Arc, + plugins_manager: Arc, + skills_manager: Arc, + environments: Vec, +) -> Vec { + let fs = crate::environment_selection::resolve_environment_selections( + environment_manager.as_ref(), + &environments, + ) + .ok() + .and_then(|resolved| resolved.primary_filesystem()); + let plugins_input = config.plugins_config_input(); + let plugin_outcome = plugins_manager.plugins_for_config(&plugins_input).await; + let effective_skill_roots = plugin_outcome.effective_plugin_skill_roots(); + let skills_input = skills_load_input_from_config(config.as_ref(), effective_skill_roots); + skills_manager + .skills_for_config(&skills_input, fs) + .await + .errors +} + impl Session { /// Returns the concrete identity for this thread. pub(crate) fn thread_id(&self) -> ThreadId { @@ -578,9 +602,38 @@ impl Session { otel.name = "session_init.auth_mcp", )); + let plugin_and_skill_warmup_fut = warm_plugins_and_skills_for_session_init( + Arc::clone(&config), + Arc::clone(&environment_manager), + Arc::clone(&plugins_manager), + Arc::clone(&skills_manager), + session_configuration.environments.clone(), + ) + .instrument(info_span!( + "session_init.plugin_skill_warmup", + otel.name = "session_init.plugin_skill_warmup", + )); + // Join all independent futures. - let (thread_persistence_result, state_db_ctx, (auth, mcp_servers, auth_statuses)) = - tokio::join!(thread_persistence_fut, state_db_fut, auth_and_mcp_fut); + let ( + thread_persistence_result, + state_db_ctx, + (auth, mcp_servers, auth_statuses), + plugin_skill_errors, + ) = tokio::join!( + thread_persistence_fut, + state_db_fut, + auth_and_mcp_fut, + plugin_and_skill_warmup_fut + ); + + for err in &plugin_skill_errors { + error!( + "failed to load skill {}: {}", + err.path.display(), + err.message + ); + } let mut live_thread_init = LiveThreadInitGuard::new(thread_persistence_result.map_err(|e| {