From 9e6edbb1bc5b5dbdf1f16f8c216eeb90f37667b5 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 30 Mar 2026 19:20:22 +0200 Subject: [PATCH] simplify further --- .../src/protocol/thread_history.rs | 64 ++++++++++---- .../app-server/src/bespoke_event_handling.rs | 27 +++--- codex-rs/core/src/agent/control_tests.rs | 12 ++- codex-rs/core/src/codex.rs | 48 +++++++---- .../src/codex/rollout_reconstruction_tests.rs | 76 ++++++++++++----- codex-rs/core/src/codex_tests.rs | 62 +++++++++----- codex-rs/core/src/tasks/compact.rs | 8 +- codex-rs/core/src/tasks/ghost_snapshot.rs | 8 +- codex-rs/core/src/tasks/mod.rs | 26 ++---- codex-rs/core/src/tasks/regular.rs | 8 +- codex-rs/core/src/tasks/review.rs | 7 +- codex-rs/core/src/tasks/undo.rs | 16 ++-- codex-rs/core/src/tasks/user_shell.rs | 8 +- .../src/tools/handlers/multi_agents_tests.rs | 36 +++++--- codex-rs/core/tests/suite/resume.rs | 4 +- codex-rs/protocol/src/protocol.rs | 30 ------- codex-rs/tui/src/app/app_server_adapter.rs | 26 +++--- codex-rs/tui/src/chatwidget.rs | 14 ++-- codex-rs/tui/src/chatwidget/tests.rs | 84 ++++++++++++++----- 19 files changed, 361 insertions(+), 203 deletions(-) diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index 6d77e52c77..7a167af1d4 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -1386,7 +1386,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: turn_id.to_string(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), ]; @@ -1457,7 +1459,9 @@ mod tests { })), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-image".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), ]; @@ -1772,7 +1776,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), ]; @@ -2071,7 +2077,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), EventMsg::TurnStarted(TurnStartedEvent { turn_id: "turn-b".into(), @@ -2105,7 +2113,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-b".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), ]; @@ -2154,7 +2164,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), EventMsg::TurnStarted(TurnStartedEvent { turn_id: "turn-b".into(), @@ -2188,7 +2200,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-b".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), ]; @@ -2357,7 +2371,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), EventMsg::TurnStarted(TurnStartedEvent { turn_id: "turn-b".into(), @@ -2372,7 +2388,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), EventMsg::AgentMessage(AgentMessageEvent { message: "still in b".into(), @@ -2381,7 +2399,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-b".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), ]; @@ -2412,7 +2432,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), EventMsg::TurnStarted(TurnStartedEvent { turn_id: "turn-b".into(), @@ -2462,7 +2484,9 @@ mod tests { }), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-compact".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), ]; @@ -2701,7 +2725,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), EventMsg::Error(ErrorEvent { message: "request-level failure".into(), @@ -2754,7 +2780,9 @@ mod tests { }), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }), ]; @@ -2802,7 +2830,9 @@ mod tests { RolloutItem::ResponseItem(hook_prompt), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), ]; @@ -2847,7 +2877,9 @@ mod tests { }), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-a".into(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), ]; diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index db317c3e09..70bc0c0c63 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -300,8 +300,9 @@ pub(crate) async fn apply_bespoke_event_handling( outgoing.abort_pending_server_requests().await; let turn_failed = thread_state.lock().await.turn_summary.last_error.is_some() || matches!( - _ev.error(), - Some(error) if error.affects_turn_status() + &_ev.outcome, + codex_protocol::protocol::TurnOutcome::Failed { error } + if error.affects_turn_status() ); thread_watch_manager .note_turn_completed(&conversation_id.to_string(), turn_failed) @@ -2120,15 +2121,19 @@ async fn handle_turn_complete( let (status, error) = match turn_summary.last_error { Some(error) => (TurnStatus::Failed, Some(error)), - None => match event.error() { - Some(error) if error.affects_turn_status() => ( - TurnStatus::Failed, - Some(TurnError { - message: error.message.clone(), - codex_error_info: error.codex_error_info.clone().map(Into::into), - additional_details: None, - }), - ), + None => match &event.outcome { + codex_protocol::protocol::TurnOutcome::Failed { error } + if error.affects_turn_status() => + { + ( + TurnStatus::Failed, + Some(TurnError { + message: error.message.clone(), + codex_error_info: error.codex_error_info.clone().map(Into::into), + additional_details: None, + }), + ) + } _ => (TurnStatus::Completed, None), }, }; diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index 53c277f20a..59185f8659 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -250,7 +250,9 @@ async fn on_event_updates_status_from_task_started() { async fn on_event_updates_status_from_task_complete() { let status = agent_status_from_event(&EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-1".to_string(), - last_agent_message: Some("done".to_string()), + outcome: TurnOutcome::Succeeded { + last_agent_message: Some("done".to_string()), + }, })); let expected = AgentStatus::Completed(Some("done".to_string())); assert_eq!(status, Some(expected)); @@ -1133,7 +1135,9 @@ async fn multi_agent_v2_completion_ignores_dead_direct_parent() { tester_turn.as_ref(), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: tester_turn.sub_id.clone(), - last_agent_message: Some("done".to_string()), + outcome: TurnOutcome::Succeeded { + last_agent_message: Some("done".to_string()), + }, }), ) .await; @@ -1228,7 +1232,9 @@ async fn multi_agent_v2_completion_queues_message_for_direct_parent() { tester_turn.as_ref(), EventMsg::TurnComplete(TurnCompleteEvent { turn_id: tester_turn.sub_id.clone(), - last_agent_message: Some("done".to_string()), + outcome: TurnOutcome::Succeeded { + last_agent_message: Some("done".to_string()), + }, }), ) .await; diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index a1fd2ff5e9..22e389e33a 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -315,6 +315,7 @@ use crate::protocol::TokenCountEvent; use crate::protocol::TokenUsage; use crate::protocol::TokenUsageInfo; use crate::protocol::TurnDiffEvent; +use crate::protocol::TurnOutcome; use crate::protocol::WarningEvent; use crate::resolve_skill_dependencies_for_turn; use crate::rollout::RolloutRecorder; @@ -335,7 +336,6 @@ use crate::tasks::GhostSnapshotTask; use crate::tasks::ReviewTask; use crate::tasks::SessionTask; use crate::tasks::SessionTaskContext; -use crate::tasks::TaskCompletion; use crate::tools::ToolRouter; use crate::tools::context::SharedTurnDiffTracker; use crate::tools::js_repl::JsReplHandle; @@ -5597,9 +5597,11 @@ pub(crate) async fn run_turn( input: Vec, prewarmed_client_session: Option, cancellation_token: CancellationToken, -) -> TaskCompletion { +) -> TurnOutcome { if input.is_empty() && !sess.has_pending_input().await { - return TaskCompletion::Completed(None); + return TurnOutcome::Succeeded { + last_agent_message: None, + }; } let model_info = turn_context.model_info.clone(); @@ -5613,7 +5615,9 @@ pub(crate) async fn run_turn( .is_err() { error!("Failed to run pre-sampling compact"); - return TaskCompletion::Completed(None); + return TurnOutcome::Succeeded { + last_agent_message: None, + }; } let skills_outcome = Some(turn_context.turn_skills.outcome.as_ref()); @@ -5643,7 +5647,11 @@ pub(crate) async fn run_turn( .await { Ok(mcp_tools) => mcp_tools, - Err(_) if turn_context.apps_enabled() => return TaskCompletion::Completed(None), + Err(_) if turn_context.apps_enabled() => { + return TurnOutcome::Succeeded { + last_agent_message: None, + }; + } Err(_) => HashMap::new(), } } else { @@ -5741,7 +5749,9 @@ pub(crate) async fn run_turn( .collect::>(); if run_pending_session_start_hooks(&sess, &turn_context).await { - return TaskCompletion::Completed(None); + return TurnOutcome::Succeeded { + last_agent_message: None, + }; } let additional_contexts = if input.is_empty() { Vec::new() @@ -5761,7 +5771,9 @@ pub(crate) async fn run_turn( user_prompt_submit_outcome.additional_contexts, ) .await; - return TaskCompletion::Completed(None); + return TurnOutcome::Succeeded { + last_agent_message: None, + }; } sess.record_user_prompt_and_emit_turn_item(turn_context.as_ref(), &input, response_item) .await; @@ -5924,7 +5936,9 @@ pub(crate) async fn run_turn( .await .is_err() { - return TaskCompletion::Completed(None); + return TurnOutcome::Succeeded { + last_agent_message: None, + }; } continue; } @@ -6036,10 +6050,12 @@ pub(crate) async fn run_turn( } } if let Some(message) = abort_message { - return TaskCompletion::Failed(ErrorEvent { - message, - codex_error_info: None, - }); + return TurnOutcome::Failed { + error: ErrorEvent { + message, + codex_error_info: None, + }, + }; } break; } @@ -6062,16 +6078,18 @@ pub(crate) async fn run_turn( .to_string(), codex_error_info: Some(CodexErrorInfo::BadRequest), }; - return TaskCompletion::Failed(error); + return TurnOutcome::Failed { error }; } Err(e) => { info!("Turn error: {e:#}"); - return TaskCompletion::Failed(e.to_error_event(/*message_prefix*/ None)); + return TurnOutcome::Failed { + error: e.to_error_event(/*message_prefix*/ None), + }; } } } - TaskCompletion::Completed(last_agent_message) + TurnOutcome::Succeeded { last_agent_message } } async fn run_pre_sampling_compact( diff --git a/codex-rs/core/src/codex/rollout_reconstruction_tests.rs b/codex-rs/core/src/codex/rollout_reconstruction_tests.rs index 86abfa6756..6da5c00d90 100644 --- a/codex-rs/core/src/codex/rollout_reconstruction_tests.rs +++ b/codex-rs/core/src/codex/rollout_reconstruction_tests.rs @@ -208,7 +208,9 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: first_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -232,7 +234,9 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_com RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: rolled_back_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::ThreadRolledBack( @@ -298,7 +302,9 @@ async fn reconstruct_history_rollback_keeps_history_and_metadata_in_sync_for_inc RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: first_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -383,7 +389,9 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: first_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -406,7 +414,9 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: second_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -420,7 +430,9 @@ async fn reconstruct_history_rollback_skips_non_user_turns_for_history_and_metad RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: standalone_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::ThreadRolledBack( @@ -489,7 +501,9 @@ async fn reconstruct_history_rollback_counts_inter_agent_assistant_turns() { RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: first_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -505,7 +519,9 @@ async fn reconstruct_history_rollback_counts_inter_agent_assistant_turns() { RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: assistant_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::ThreadRolledBack( @@ -569,7 +585,9 @@ async fn reconstruct_history_rollback_clears_history_and_metadata_when_exceeding RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: only_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::ThreadRolledBack( @@ -615,7 +633,9 @@ async fn record_initial_history_resumed_rollback_skips_only_user_turns() { RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: user_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), // Standalone task turn (no UserMessage) should not consume rollback skips. @@ -629,7 +649,9 @@ async fn record_initial_history_resumed_rollback_skips_only_user_turns() { RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: standalone_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::ThreadRolledBack( @@ -679,7 +701,9 @@ async fn record_initial_history_resumed_rollback_drops_incomplete_user_turn_comp RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: previous_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -831,7 +855,9 @@ async fn reconstruct_history_legacy_compaction_without_replacement_history_clear RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: current_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), ]; @@ -897,7 +923,9 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: previous_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), ]; @@ -995,7 +1023,9 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: previous_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -1096,7 +1126,9 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: previous_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -1124,7 +1156,9 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: current_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), ]; @@ -1203,7 +1237,9 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: previous_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( @@ -1348,7 +1384,9 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear RolloutItem::EventMsg(EventMsg::TurnComplete( codex_protocol::protocol::TurnCompleteEvent { turn_id: previous_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, }, )), RolloutItem::EventMsg(EventMsg::TurnStarted( diff --git a/codex-rs/core/src/codex_tests.rs b/codex-rs/core/src/codex_tests.rs index 4bb3a0e9e8..7a1ceda48a 100644 --- a/codex-rs/core/src/codex_tests.rs +++ b/codex-rs/core/src/codex_tests.rs @@ -46,6 +46,7 @@ use crate::protocol::TokenCountEvent; use crate::protocol::TokenUsage; use crate::protocol::TokenUsageInfo; use crate::protocol::TurnCompleteEvent; +use crate::protocol::TurnOutcome; use crate::protocol::TurnStartedEvent; use crate::protocol::UserMessageEvent; use crate::rollout::policy::EventPersistenceMode; @@ -54,7 +55,6 @@ use crate::rollout::recorder::RolloutRecorderParams; use crate::state::TaskKind; use crate::tasks::SessionTask; use crate::tasks::SessionTaskContext; -use crate::tasks::TaskCompletion; use crate::tools::ToolRouter; use crate::tools::context::FunctionToolOutput; use crate::tools::context::ToolInvocation; @@ -1486,7 +1486,9 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context RolloutItem::ResponseItem(turn_one_assistant.clone()), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: first_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), RolloutItem::EventMsg(EventMsg::TurnStarted( codex_protocol::protocol::TurnStartedEvent { @@ -1508,7 +1510,9 @@ async fn thread_rollback_recomputes_previous_turn_settings_and_reference_context RolloutItem::ResponseItem(turn_two_assistant), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: rolled_back_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), ]) .await; @@ -1582,7 +1586,9 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio RolloutItem::ResponseItem(assistant_message("turn 1 assistant")), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: first_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), RolloutItem::EventMsg(EventMsg::TurnStarted( codex_protocol::protocol::TurnStartedEvent { @@ -1597,7 +1603,9 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio }), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: compact_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), RolloutItem::EventMsg(EventMsg::TurnStarted( codex_protocol::protocol::TurnStartedEvent { @@ -1621,7 +1629,9 @@ async fn thread_rollback_restores_cleared_reference_context_item_after_compactio RolloutItem::ResponseItem(assistant_message("turn 2 assistant")), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: rolled_back_turn_id, - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), ]) .await; @@ -1664,7 +1674,9 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() { RolloutItem::ResponseItem(assistant_message("turn 1 assistant")), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-1".to_string(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), RolloutItem::EventMsg(EventMsg::TurnStarted( codex_protocol::protocol::TurnStartedEvent { @@ -1684,7 +1696,9 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() { RolloutItem::ResponseItem(assistant_message("turn 2 assistant")), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-2".to_string(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), RolloutItem::EventMsg(EventMsg::TurnStarted( codex_protocol::protocol::TurnStartedEvent { @@ -1704,7 +1718,9 @@ async fn thread_rollback_persists_marker_and_replays_cumulatively() { RolloutItem::ResponseItem(assistant_message("turn 3 assistant")), RolloutItem::EventMsg(EventMsg::TurnComplete(TurnCompleteEvent { turn_id: "turn-3".to_string(), - last_agent_message: None, + outcome: TurnOutcome::Succeeded { + last_agent_message: None, + }, })), ]) .await; @@ -3140,13 +3156,15 @@ async fn spawn_task_turn_span_inherits_dispatch_trace_context() { _ctx: Arc, _input: Vec, _cancellation_token: CancellationToken, - ) -> TaskCompletion { + ) -> TurnOutcome { let mut trace = self .captured_trace .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); *trace = current_span_w3c_trace_context(); - TaskCompletion::Completed(None) + TurnOutcome::Succeeded { + last_agent_message: None, + } } } @@ -4401,10 +4419,12 @@ impl SessionTask for NeverEndingTask { _ctx: Arc, _input: Vec, cancellation_token: CancellationToken, - ) -> TaskCompletion { + ) -> TurnOutcome { if self.listen_to_cancellation_token { cancellation_token.cancelled().await; - return TaskCompletion::Completed(None); + return TurnOutcome::Succeeded { + last_agent_message: None, + }; } loop { sleep(Duration::from_secs(60)).await; @@ -4428,11 +4448,13 @@ impl SessionTask for FailingTask { _ctx: Arc, _input: Vec, _cancellation_token: CancellationToken, - ) -> TaskCompletion { - TaskCompletion::Failed(ErrorEvent { - message: "boom".to_string(), - codex_error_info: None, - }) + ) -> TurnOutcome { + TurnOutcome::Failed { + error: ErrorEvent { + message: "boom".to_string(), + codex_error_info: None, + }, + } } } @@ -4557,7 +4579,9 @@ async fn task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input() sess.on_task_finished( Arc::clone(&tc), - TaskCompletion::Completed(/*last_agent_message*/ None), + TurnOutcome::Succeeded { + last_agent_message: None, + }, ) .await; diff --git a/codex-rs/core/src/tasks/compact.rs b/codex-rs/core/src/tasks/compact.rs index c15cef1284..cf96e844cb 100644 --- a/codex-rs/core/src/tasks/compact.rs +++ b/codex-rs/core/src/tasks/compact.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use super::SessionTask; use super::SessionTaskContext; -use super::TaskCompletion; use crate::codex::TurnContext; +use crate::protocol::TurnOutcome; use crate::state::TaskKind; use async_trait::async_trait; use codex_protocol::user_input::UserInput; @@ -28,7 +28,7 @@ impl SessionTask for CompactTask { ctx: Arc, input: Vec, _cancellation_token: CancellationToken, - ) -> TaskCompletion { + ) -> TurnOutcome { let session = session.clone_session(); let _ = if crate::compact::should_use_remote_compact_task(&ctx.provider) { let _ = session.services.session_telemetry.counter( @@ -45,6 +45,8 @@ impl SessionTask for CompactTask { ); crate::compact::run_compact_task(session.clone(), ctx, input).await }; - TaskCompletion::Completed(None) + TurnOutcome::Succeeded { + last_agent_message: None, + } } } diff --git a/codex-rs/core/src/tasks/ghost_snapshot.rs b/codex-rs/core/src/tasks/ghost_snapshot.rs index d8d165076c..551c09c4e8 100644 --- a/codex-rs/core/src/tasks/ghost_snapshot.rs +++ b/codex-rs/core/src/tasks/ghost_snapshot.rs @@ -1,10 +1,10 @@ use crate::codex::TurnContext; use crate::protocol::EventMsg; +use crate::protocol::TurnOutcome; use crate::protocol::WarningEvent; use crate::state::TaskKind; use crate::tasks::SessionTask; use crate::tasks::SessionTaskContext; -use crate::tasks::TaskCompletion; use async_trait::async_trait; use codex_git_utils::CreateGhostCommitOptions; use codex_git_utils::GhostSnapshotReport; @@ -43,7 +43,7 @@ impl SessionTask for GhostSnapshotTask { ctx: Arc, _input: Vec, cancellation_token: CancellationToken, - ) -> TaskCompletion { + ) -> TurnOutcome { tokio::task::spawn(async move { let token = self.token; let warnings_enabled = !ctx.ghost_snapshot.disable_warnings; @@ -157,7 +157,9 @@ impl SessionTask for GhostSnapshotTask { Err(err) => warn!("failed to mark ghost snapshot ready: {err}"), } }); - TaskCompletion::Completed(None) + TurnOutcome::Succeeded { + last_agent_message: None, + } } } diff --git a/codex-rs/core/src/tasks/mod.rs b/codex-rs/core/src/tasks/mod.rs index e32d9c0d2e..a3ee5de85c 100644 --- a/codex-rs/core/src/tasks/mod.rs +++ b/codex-rs/core/src/tasks/mod.rs @@ -29,11 +29,11 @@ use crate::hook_runtime::inspect_pending_input; use crate::hook_runtime::record_additional_contexts; use crate::hook_runtime::record_pending_input; use crate::models_manager::manager::ModelsManager; -use crate::protocol::ErrorEvent; use crate::protocol::EventMsg; use crate::protocol::TurnAbortReason; use crate::protocol::TurnAbortedEvent; use crate::protocol::TurnCompleteEvent; +use crate::protocol::TurnOutcome; use crate::state::ActiveTurn; use crate::state::RunningTask; use crate::state::TaskKind; @@ -100,11 +100,6 @@ pub(crate) struct SessionTaskContext { session: Arc, } -pub(crate) enum TaskCompletion { - Completed(Option), - Failed(ErrorEvent), -} - impl SessionTaskContext { pub(crate) fn new(session: Arc) -> Self { Self { session } @@ -146,7 +141,7 @@ pub(crate) trait SessionTask: Send + Sync + 'static { /// `ctx`, returning an optional final agent message when finished. The /// provided `cancellation_token` is cancelled when the session requests an /// abort; implementers should watch for it and terminate quickly once it - /// fires. The returned [`TaskCompletion`] determines the single terminal + /// fires. The returned [`TurnOutcome`] determines the single terminal /// event emitted by [`Session::on_task_finished`]. async fn run( self: Arc, @@ -154,7 +149,7 @@ pub(crate) trait SessionTask: Send + Sync + 'static { ctx: Arc, input: Vec, cancellation_token: CancellationToken, - ) -> TaskCompletion; + ) -> TurnOutcome; /// Gives the task a chance to perform cleanup after an abort. /// @@ -320,7 +315,7 @@ impl Session { pub async fn on_task_finished( self: &Arc, turn_context: Arc, - completion: TaskCompletion, + outcome: TurnOutcome, ) { turn_context .turn_metadata_state @@ -437,15 +432,10 @@ impl Session { &[("token_type", "reasoning_output"), tmp_mem], ); } - let event = match completion { - TaskCompletion::Completed(last_agent_message) => EventMsg::TurnComplete( - TurnCompleteEvent::succeeded(turn_context.sub_id.clone(), last_agent_message), - ), - TaskCompletion::Failed(error) => EventMsg::TurnComplete(TurnCompleteEvent::failed( - turn_context.sub_id.clone(), - error, - )), - }; + let event = EventMsg::TurnComplete(TurnCompleteEvent { + turn_id: turn_context.sub_id.clone(), + outcome, + }); self.send_event(turn_context.as_ref(), event).await; } diff --git a/codex-rs/core/src/tasks/regular.rs b/codex-rs/core/src/tasks/regular.rs index 5ad1ea835f..b409f24104 100644 --- a/codex-rs/core/src/tasks/regular.rs +++ b/codex-rs/core/src/tasks/regular.rs @@ -7,6 +7,7 @@ use crate::codex::TurnContext; use crate::codex::run_turn; use crate::protocol::EventMsg; use crate::protocol::TurnStartedEvent; +use crate::protocol::TurnOutcome; use crate::session_startup_prewarm::SessionStartupPrewarmResolution; use crate::state::TaskKind; use codex_protocol::user_input::UserInput; @@ -15,7 +16,6 @@ use tracing::trace_span; use super::SessionTask; use super::SessionTaskContext; -use super::TaskCompletion; #[derive(Default)] pub(crate) struct RegularTask; @@ -42,7 +42,7 @@ impl SessionTask for RegularTask { ctx: Arc, input: Vec, cancellation_token: CancellationToken, - ) -> TaskCompletion { + ) -> TurnOutcome { let sess = session.clone_session(); let run_turn_span = trace_span!("run_turn"); // Regular turns emit `TurnStarted` inline so first-turn lifecycle does @@ -59,7 +59,9 @@ impl SessionTask for RegularTask { .await { SessionStartupPrewarmResolution::Cancelled => { - return TaskCompletion::Completed(None); + return TurnOutcome::Succeeded { + last_agent_message: None, + }; } SessionStartupPrewarmResolution::Unavailable { .. } => None, SessionStartupPrewarmResolution::Ready(prewarmed_client_session) => { diff --git a/codex-rs/core/src/tasks/review.rs b/codex-rs/core/src/tasks/review.rs index bb357cfd02..e9dec6b407 100644 --- a/codex-rs/core/src/tasks/review.rs +++ b/codex-rs/core/src/tasks/review.rs @@ -32,7 +32,6 @@ use std::sync::LazyLock; use super::SessionTask; use super::SessionTaskContext; -use super::TaskCompletion; static REVIEW_EXIT_SUCCESS_TEMPLATE: LazyLock