diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 034844bf02..b1cbef7a8c 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2761,7 +2761,7 @@ impl Session { }) } - async fn current_active_turn_context(&self) -> Option> { + pub(crate) async fn current_active_turn_context(&self) -> Option> { let active = self.active_turn.lock().await; let turn = active.as_ref()?; turn.current_turn_context.clone().or_else(|| { diff --git a/codex-rs/core/src/tools/network_approval.rs b/codex-rs/core/src/tools/network_approval.rs index aeef59833d..fdc69a9876 100644 --- a/codex-rs/core/src/tools/network_approval.rs +++ b/codex-rs/core/src/tools/network_approval.rs @@ -249,11 +249,7 @@ impl NetworkApprovalService { } async fn active_turn_context(session: &Session) -> Option> { - let active_turn = session.active_turn.lock().await; - active_turn - .as_ref() - .and_then(|turn| turn.tasks.first()) - .map(|(_, task)| Arc::clone(&task.turn_context)) + session.current_active_turn_context().await } fn format_network_target(protocol: &str, host: &str, port: u16) -> String { @@ -555,9 +551,39 @@ pub(crate) async fn finish_deferred_network_approval( #[cfg(test)] mod tests { use super::*; + use crate::codex::make_session_and_context_with_rx; + use crate::state::ActiveTurn; + use crate::state::RunningTask; + use crate::state::TaskKind; + use crate::tasks::SessionTask; + use crate::tasks::SessionTaskContext; use codex_network_proxy::BlockedRequestArgs; use codex_protocol::protocol::AskForApproval; + use codex_protocol::user_input::UserInput; use pretty_assertions::assert_eq; + use tokio::sync::Notify; + use tokio_util::sync::CancellationToken; + use tokio_util::task::AbortOnDropHandle; + + #[derive(Clone, Copy)] + struct NoopTask; + + #[async_trait::async_trait] + impl SessionTask for NoopTask { + fn kind(&self) -> TaskKind { + TaskKind::Regular + } + + async fn run( + self: Arc, + _session: Arc, + _ctx: Arc, + _input: Vec, + _cancellation_token: CancellationToken, + ) -> Option { + None + } + } #[tokio::test] async fn pending_approvals_are_deduped_per_host_protocol_and_port() { @@ -697,4 +723,43 @@ mod tests { assert_eq!(service.take_call_outcome("registration-1").await, None); assert_eq!(service.take_call_outcome("registration-2").await, None); } + + #[tokio::test] + async fn active_turn_context_prefers_refreshed_current_turn_context_over_task_snapshot() { + let (session, original_turn_context, _rx) = make_session_and_context_with_rx().await; + let next_model = if original_turn_context.model_info.slug == "gpt-5.1" { + "gpt-5" + } else { + "gpt-5.1" + }; + let refreshed_turn_context = Arc::new( + original_turn_context + .with_model(next_model.to_string(), &session.services.models_manager) + .await, + ); + let handle = tokio::spawn(async {}); + + *session.active_turn.lock().await = Some(ActiveTurn { + current_turn_context: Some(Arc::clone(&refreshed_turn_context)), + tasks: IndexMap::from([( + original_turn_context.sub_id.clone(), + RunningTask { + done: Arc::new(Notify::new()), + kind: TaskKind::Regular, + task: Arc::new(NoopTask), + cancellation_token: CancellationToken::new(), + handle: Arc::new(AbortOnDropHandle::new(handle)), + turn_context: Arc::clone(&original_turn_context), + _timer: None, + }, + )]), + ..Default::default() + }); + + let active_turn_context = NetworkApprovalService::active_turn_context(&session) + .await + .expect("active turn context"); + + assert!(Arc::ptr_eq(&active_turn_context, &refreshed_turn_context)); + } }