Add interrupted turn recovery (#38303)

## What changed

- Add `RecoverTurnRequest` and `CodexThread::recover_turn_if_idle` to resume an interrupted regular turn with its existing turn ID and updated thread settings.
- Treat recovery separately from automatic idle work so it can resume in Plan mode without injecting an empty user message.
- Reject recovery while another turn is active without applying settings or queuing input.

## Testing

- Cover successful Plan-mode recovery, turn ID preservation, and the absence of an empty user message.
- Cover active-turn rejection and verify that it leaves settings and pending input unchanged.

GitOrigin-RevId: 2376fdc78b5d48b571633981960741ba269ab1ef
This commit is contained in:
Owen Lin authored and copyberry committed 2026-08-13 03:27:19 +00:00
1 parent 357696c5e7
commit 363427b5e3
10 files changed
+228 -4

No files matched your search

+22
View File
@@ -120,6 +120,7 @@ use codex_protocol::protocol::RawResponseItemEvent;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::ThreadHistoryMode;
use codex_protocol::protocol::ThreadSettingsOverrides;
use codex_protocol::protocol::ThreadSource;
use codex_protocol::protocol::TurnAbortReason;
use codex_protocol::protocol::TurnContextItem;
@@ -856,6 +857,27 @@ impl SessionIo {
reply_rx.await.unwrap_or(Err(CodexErr::InternalAgentDied))
}
pub(crate) async fn submit_recover_turn(
&self,
thread_settings: ThreadSettingsOverrides,
trace: Option<W3cTraceContext>,
turn_id: String,
) -> CodexResult<TurnInputSubmission> {
let (reply_tx, reply_rx) = oneshot::channel();
self.submit_with_id(Submission {
id: turn_id,
op: Op::RecoverTurn {
thread_settings,
reply: reply_tx,
},
trace,
parent_turn_id: None,
root_turn_id: None,
})
.await?;
reply_rx.await.unwrap_or(Err(CodexErr::InternalAgentDied))
}
pub(crate) async fn shutdown_and_wait(&self) -> CodexResult<()> {
let session_loop_termination = self.session_loop_termination.clone();
match self.submit(Op::Shutdown).await {