Expose current environment selections for a running turn (#47970)

## Why

`active_turn_environment_selections()` returns the selections captured at turn start, so it does not reflect later environment settings updates.

## What changed

Add `CodexThread::current_turn_environment_selections(expected_turn_id)` to read the running turn's current selections, including environments that are still starting or have failed. Return `None` when the requested turn is no longer active or has been cancelled.

## Testing

Add a regression test covering selection updates, mismatched turn IDs, and completed turns, while verifying that the initial selection snapshot remains unchanged.

GitOrigin-RevId: bb526a686d549209142150ffde99ad1444add205
This commit is contained in:
sayan-oai
2026-09-25 00:07:35 +00:00
committed by copyberry
parent a708fc6839
commit 97da50c1aa
2 changed files with 72 additions and 0 deletions
+14
View File
@@ -546,6 +546,20 @@ impl CodexThread {
Some(task.turn_context.initial_environments.all_selections())
}
/// Returns the named running turn's current selections, including environments that are
/// still starting or have failed. Returns `None` if that turn is no longer running.
pub async fn current_turn_environment_selections(
&self,
expected_turn_id: &str,
) -> Option<Vec<TurnEnvironmentSelection>> {
let active = self.session.active_turn.lock().await;
let task = active.as_ref()?.task.as_ref()?;
if task.turn_context.sub_id != expected_turn_id || task.cancellation_token.is_cancelled() {
return None;
}
Some(self.session.services.turn_environments.selections())
}
/// Captures a regular turn only after its input is recorded. The caller must flush the rollout.
pub async fn interrupted_turn(
&self,
@@ -10,6 +10,64 @@ use core_test_support::submit_thread_settings;
use core_test_support::test_codex::environment_config_for_selection;
use pretty_assertions::assert_eq;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn current_turn_environment_selections_follow_active_updates() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
mount_sse_sequence(
&server,
vec![paused_response("first", "pause"), sse_completed("second")],
)
.await;
let test = step_settings_test().build_with_auto_env(&server).await?;
let thread = &test.codex;
let turn_id = start_paused_turn(thread).await?.turn_id;
let initial = thread
.active_turn_environment_selections()
.await
.expect("running turn");
assert_eq!(initial.len(), 1);
assert_eq!(
thread.current_turn_environment_selections(&turn_id).await,
Some(initial.clone())
);
assert_eq!(
thread
.current_turn_environment_selections("other-turn")
.await,
None
);
for environments in [vec![], initial.clone()] {
apply_turn_settings(
thread,
&turn_id,
TurnSettingsUpdate {
environments: Some(environments.clone()),
..Default::default()
},
)
.await?;
assert_eq!(
thread.current_turn_environment_selections(&turn_id).await,
Some(environments)
);
assert_eq!(
thread.active_turn_environment_selections().await,
Some(initial.clone())
);
}
answer_paused_turn(thread, &turn_id).await?;
wait_for_event(thread, |event| matches!(event, EventMsg::TurnComplete(_))).await;
assert_eq!(
thread.current_turn_environment_selections(&turn_id).await,
None
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn model_update_preserves_active_environment_and_next_turn_uses_new_selection() -> Result<()>
{