Add unfinished root turn suspension (#40038)

## Why

An active root turn needs to be stopped without marking it complete or aborted
before another runtime can recover the same turn ID.

## What changed

- Add `CodexThread::suspend_turn_and_shutdown` and `SuspendTurnOutcome`.
- Flush history, stop the active regular task, close the history writer, and
  shut down the session without recording a terminal turn event.
- Reject suspension when no supported turn is active or the loaded agent
  subtree still contains a live descendant.

## Testing

- Verify that suspension preserves unfinished history and allows the turn to be
  recovered under its original ID after the descendant guard is cleared.

GitOrigin-RevId: 1f9b019d07c51474ec2d991d263bc15cdd4f89ad
This commit is contained in:
richardopenai
2026-08-22 00:37:07 +00:00
committed by copyberry
parent 970b7f2ff4
commit 4f39251a01
10 changed files with 345 additions and 3 deletions

View File

@@ -56,6 +56,7 @@ use crate::plan_tool::UpdatePlanArgs;
use crate::request_permissions::RequestPermissionsEvent;
use crate::request_permissions::RequestPermissionsResponse;
use crate::request_user_input::RequestUserInputResponse;
use crate::turn_input::SuspendTurnOutcome;
use crate::turn_input::TurnInputMode;
use crate::turn_input::TurnInputRequest;
use crate::turn_input::TurnInputSubmission;
@@ -581,6 +582,11 @@ pub enum Op {
reply: oneshot::Sender<CodexResult<TurnInputSubmission>>,
},
/// Stop the active root turn without recording a terminal turn event.
SuspendTurnAndShutdown {
reply: oneshot::Sender<CodexResult<SuspendTurnOutcome>>,
},
/// Apply persistent thread-settings overrides without starting a turn.
///
/// This uses the same submission queue as turn starts so app-server can
@@ -875,6 +881,7 @@ impl Op {
Self::RealtimeConversationListVoices => "realtime_conversation_list_voices",
Self::TurnInput { .. } => "turn_input",
Self::RecoverTurn { .. } => "recover_turn",
Self::SuspendTurnAndShutdown { .. } => "suspend_turn_and_shutdown",
Self::ThreadSettings { .. } => "thread_settings",
Self::InterAgentCommunication { .. } => "inter_agent_communication",
Self::ExecApproval { .. } => "exec_approval",

View File

@@ -13,6 +13,18 @@ use serde_json::Value;
use std::collections::BTreeMap;
use std::collections::HashMap;
/// Result of stopping an unfinished root turn so another worker can recover it.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SuspendTurnOutcome {
Suspended {
turn_id: String,
},
NotActive,
/// A currently loaded descendant would remain running after root handoff.
HasLiveDescendants,
UnsupportedTask,
}
/// Input consumed by a regular turn.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum TurnInput {