Allow idle threads to prewarm and repair WebSocket connections (#47701)

## Why

A previously used WebSocket can close while a thread is idle. Hosts need a way to prepare the connection before the next turn without repeating warmup on a healthy connection.

## What changed

- Expose `CodexThread::prewarm()` using the existing startup warmup and next-turn handoff.
- Reuse healthy connections and pending warmups, retry failed warmups, and repair closed connections. Treat a finished WebSocket reader task as a closed connection.
- Skip scheduling during an active turn or shutdown, and stop warmup admission before shutdown takes the pending task.
- Expose `active_turn_environment_selections()` so hosts can inspect the environments captured by the active turn, including those still starting or failed, when authorizing steering.

## Testing

Add regression coverage for repeated idle prewarm calls, healthy connection reuse with preserved response continuation, reconnection with `generate: false`, and warmup rejection after shutdown. Update delayed MCP discovery coverage to verify prewarm repairs an eagerly closed socket and the first turn reuses it.

GitOrigin-RevId: aa164b91a399de2375eda3bf28397725439fce93
This commit is contained in:
vkg-oai
2026-09-24 00:37:56 +00:00
committed by copyberry
parent 4b3b569f39
commit d838c2346d
14 changed files with 310 additions and 91 deletions
+18
View File
@@ -239,6 +239,13 @@ impl CodexThread {
self.session.services.session_telemetry.clone()
}
/// Schedule the same background model warmup used at startup for an idle thread.
/// The next turn consumes the warmup through the existing startup handoff.
/// Call after installing host services such as the thread's attestation routing.
pub async fn prewarm(&self) {
self.session.schedule_startup_prewarm().await;
}
/// Whether analytics is enabled for this thread after configuration and host overrides.
pub fn analytics_enabled(&self) -> bool {
self.session.services.analytics_events_client.is_enabled()
@@ -525,6 +532,17 @@ impl CodexThread {
self.session.inject_if_running(items).await
}
/// Environment selections captured by the active turn, before later settings updates.
/// Includes environments that are still starting or have failed. Hosts use this snapshot
/// to authorize steering against every executor that the active turn selected.
pub async fn active_turn_environment_selections(
&self,
) -> Option<Vec<TurnEnvironmentSelection>> {
let active = self.session.active_turn.lock().await;
let task = active.as_ref()?.task.as_ref()?;
Some(task.turn_context.initial_environments.all_selections())
}
/// Captures a regular turn only after its input is recorded. The caller must flush the rollout.
pub async fn interrupted_turn(
&self,