From 62884f4400ed7f7a239aa26755e26262356bdb02 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Sat, 7 Mar 2026 11:33:56 +0100 Subject: [PATCH 1/8] feat: make interrupt state not final for multi-agents --- .../schema/json/ClientRequest.json | 2 +- .../schema/json/EventMsg.json | 7 +++ .../schema/json/ServerNotification.json | 1 + .../codex_app_server_protocol.schemas.json | 10 ++- .../codex_app_server_protocol.v2.schemas.json | 10 ++- .../json/v2/ItemCompletedNotification.json | 1 + .../json/v2/ItemStartedNotification.json | 1 + .../schema/json/v2/ReviewStartResponse.json | 1 + .../schema/json/v2/ThreadForkResponse.json | 1 + .../schema/json/v2/ThreadListResponse.json | 1 + .../json/v2/ThreadMetadataUpdateResponse.json | 1 + .../schema/json/v2/ThreadReadResponse.json | 1 + .../schema/json/v2/ThreadResumeResponse.json | 1 + .../json/v2/ThreadRollbackResponse.json | 1 + .../schema/json/v2/ThreadStartResponse.json | 1 + .../json/v2/ThreadStartedNotification.json | 1 + .../json/v2/ThreadUnarchiveResponse.json | 1 + .../json/v2/TurnCompletedNotification.json | 1 + .../schema/json/v2/TurnStartResponse.json | 1 + .../json/v2/TurnStartedNotification.json | 1 + .../v2/WindowsSandboxSetupStartParams.json | 2 +- .../schema/typescript/AgentStatus.ts | 2 +- .../schema/typescript/v2/CollabAgentStatus.ts | 2 +- .../src/protocol/thread_history.rs | 63 +++++++++++++++++++ .../app-server-protocol/src/protocol/v2.rs | 16 +++++ codex-rs/core/src/agent/control.rs | 7 ++- codex-rs/core/src/agent/status.rs | 12 +++- codex-rs/protocol/src/protocol.rs | 2 + 28 files changed, 142 insertions(+), 9 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index a790874de0e..f90ed006e6d 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -4217,4 +4217,4 @@ } ], "title": "ClientRequest" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json index 70ddde458ed..48d76c4d4c2 100644 --- a/codex-rs/app-server-protocol/schema/json/EventMsg.json +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -46,6 +46,13 @@ ], "type": "string" }, + { + "description": "Agent's current turn was interrupted and it may receive more input.", + "enum": [ + "interrupted" + ], + "type": "string" + }, { "additionalProperties": false, "description": "Agent is done. Contains the final assistant message.", diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index fcb205db041..a326135bbe3 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -535,6 +535,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index c26cea19a40..e0bd629a72b 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -134,6 +134,13 @@ ], "type": "string" }, + { + "description": "Agent's current turn was interrupted and it may receive more input.", + "enum": [ + "interrupted" + ], + "type": "string" + }, { "additionalProperties": false, "description": "Agent is done. Contains the final assistant message.", @@ -9219,6 +9226,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", @@ -17071,4 +17079,4 @@ }, "title": "CodexAppServerProtocol", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index bbf05bfed0d..490181ecfa4 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -180,6 +180,13 @@ ], "type": "string" }, + { + "description": "Agent's current turn was interrupted and it may receive more input.", + "enum": [ + "interrupted" + ], + "type": "string" + }, { "additionalProperties": false, "description": "Agent is done. Contains the final assistant message.", @@ -2199,6 +2206,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", @@ -15326,4 +15334,4 @@ }, "title": "CodexAppServerProtocolV2", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index 8f5fcc3674d..f6e5b08ab74 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -41,6 +41,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index 0108ff5d7c6..319b9d6f38a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -41,6 +41,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index 3c5d11cad47..26cb7514246 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index fec4e20e44b..802c480831a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -201,6 +201,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index 3965739d1ad..fd80b909d53 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index a74ee5d63ce..cdb740bdac1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index f00275398fd..1937e9b373b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index e8addc11013..9f7ce2d1718 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -201,6 +201,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index e95b2d850fb..364dfbb3fc7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 52d9e295151..50327ddc74d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -201,6 +201,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 698793bbdc5..97d7eb2c139 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 30d1e2841f1..a94eb611e5e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index 89a9e580de8..3eeae09d4ad 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index dbe082c3b01..f6a62eb34e7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 07323f20267..aad29522a1a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -155,6 +155,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/v2/WindowsSandboxSetupStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/WindowsSandboxSetupStartParams.json index 6e2e8baebff..ed93913cb39 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/WindowsSandboxSetupStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/WindowsSandboxSetupStartParams.json @@ -33,4 +33,4 @@ ], "title": "WindowsSandboxSetupStartParams", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts b/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts index ddf6789c78d..484f15da7e6 100644 --- a/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts +++ b/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts @@ -5,4 +5,4 @@ /** * Agent lifecycle status, derived from emitted events. */ -export type AgentStatus = "pending_init" | "running" | { "completed": string | null } | { "errored": string } | "shutdown" | "not_found"; +export type AgentStatus = "pending_init" | "running" | "interrupted" | { "completed": string | null } | { "errored": string } | "shutdown" | "not_found"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentStatus.ts b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentStatus.ts index 3672d19dac0..66d3119ba68 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentStatus.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/CollabAgentStatus.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type CollabAgentStatus = "pendingInit" | "running" | "completed" | "errored" | "shutdown" | "notFound"; +export type CollabAgentStatus = "pendingInit" | "running" | "interrupted" | "completed" | "errored" | "shutdown" | "notFound"; 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 1ad1591292f..ca80b5c1fc6 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -2337,6 +2337,69 @@ mod tests { ); } + #[test] + fn reconstructs_interrupted_send_input_as_completed_collab_call() { + let sender = ThreadId::try_from("00000000-0000-0000-0000-000000000001") + .expect("valid sender thread id"); + let receiver = ThreadId::try_from("00000000-0000-0000-0000-000000000002") + .expect("valid receiver thread id"); + let events = vec![ + EventMsg::UserMessage(UserMessageEvent { + message: "redirect".into(), + images: None, + text_elements: Vec::new(), + local_images: Vec::new(), + }), + EventMsg::CollabAgentInteractionBegin( + codex_protocol::protocol::CollabAgentInteractionBeginEvent { + call_id: "send-1".into(), + sender_thread_id: sender, + receiver_thread_id: receiver, + prompt: "new task".into(), + }, + ), + EventMsg::CollabAgentInteractionEnd( + codex_protocol::protocol::CollabAgentInteractionEndEvent { + call_id: "send-1".into(), + sender_thread_id: sender, + receiver_thread_id: receiver, + receiver_agent_nickname: None, + receiver_agent_role: None, + prompt: "new task".into(), + status: AgentStatus::Interrupted, + }, + ), + ]; + + let items = events + .into_iter() + .map(RolloutItem::EventMsg) + .collect::>(); + let turns = build_turns_from_rollout_items(&items); + assert_eq!(turns.len(), 1); + assert_eq!(turns[0].items.len(), 2); + assert_eq!( + turns[0].items[1], + ThreadItem::CollabAgentToolCall { + id: "send-1".into(), + tool: CollabAgentTool::SendInput, + status: CollabAgentToolCallStatus::Completed, + sender_thread_id: sender.to_string(), + receiver_thread_ids: vec![receiver.to_string()], + prompt: Some("new task".into()), + agents_states: [( + receiver.to_string(), + CollabAgentState { + status: crate::protocol::v2::CollabAgentStatus::Interrupted, + message: None, + }, + )] + .into_iter() + .collect(), + } + ); + } + #[test] fn rollback_failed_error_does_not_mark_turn_failed() { let events = vec![ diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index e24bc46d244..2bf5e02a1d6 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -3834,6 +3834,7 @@ pub enum CollabAgentToolCallStatus { pub enum CollabAgentStatus { PendingInit, Running, + Interrupted, Completed, Errored, Shutdown, @@ -3859,6 +3860,10 @@ impl From for CollabAgentState { status: CollabAgentStatus::Running, message: None, }, + CoreAgentStatus::Interrupted => Self { + status: CollabAgentStatus::Interrupted, + message: None, + }, CoreAgentStatus::Completed(message) => Self { status: CollabAgentStatus::Completed, message, @@ -5075,6 +5080,17 @@ mod tests { AbsolutePathBuf::from_absolute_path(path).expect("path must be absolute") } + #[test] + fn collab_agent_state_maps_interrupted_status() { + assert_eq!( + CollabAgentState::from(CoreAgentStatus::Interrupted), + CollabAgentState { + status: CollabAgentStatus::Interrupted, + message: None, + } + ); + } + #[test] fn command_execution_request_approval_rejects_relative_additional_permission_paths() { let err = serde_json::from_value::(json!({ diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index e7547f9c9d3..9b565d87f81 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -697,10 +697,15 @@ mod tests { reason: TurnAbortReason::Interrupted, })); - let expected = AgentStatus::Errored("Interrupted".to_string()); + let expected = AgentStatus::Interrupted; assert_eq!(status, Some(expected)); } + #[tokio::test] + async fn interrupted_status_is_not_final() { + assert_eq!(is_final(&AgentStatus::Interrupted), false); + } + #[tokio::test] async fn on_event_updates_status_from_shutdown_complete() { let status = agent_status_from_event(&EventMsg::ShutdownComplete); diff --git a/codex-rs/core/src/agent/status.rs b/codex-rs/core/src/agent/status.rs index 74981513fd7..c343e195031 100644 --- a/codex-rs/core/src/agent/status.rs +++ b/codex-rs/core/src/agent/status.rs @@ -7,7 +7,12 @@ pub(crate) fn agent_status_from_event(msg: &EventMsg) -> Option { match msg { EventMsg::TurnStarted(_) => Some(AgentStatus::Running), EventMsg::TurnComplete(ev) => Some(AgentStatus::Completed(ev.last_agent_message.clone())), - EventMsg::TurnAborted(ev) => Some(AgentStatus::Errored(format!("{:?}", ev.reason))), + EventMsg::TurnAborted(ev) => match ev.reason { + codex_protocol::protocol::TurnAbortReason::Interrupted => { + Some(AgentStatus::Interrupted) + } + _ => Some(AgentStatus::Errored(format!("{:?}", ev.reason))), + }, EventMsg::Error(ev) => Some(AgentStatus::Errored(ev.message.clone())), EventMsg::ShutdownComplete => Some(AgentStatus::Shutdown), _ => None, @@ -15,5 +20,8 @@ pub(crate) fn agent_status_from_event(msg: &EventMsg) -> Option { } pub(crate) fn is_final(status: &AgentStatus) -> bool { - !matches!(status, AgentStatus::PendingInit | AgentStatus::Running) + !matches!( + status, + AgentStatus::PendingInit | AgentStatus::Running | AgentStatus::Interrupted + ) } diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 420345c21c0..bc1ff0f56a1 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -1312,6 +1312,8 @@ pub enum AgentStatus { PendingInit, /// Agent is currently running. Running, + /// Agent's current turn was interrupted and it may receive more input. + Interrupted, /// Agent is done. Contains the final assistant message. Completed(Option), /// Agent encountered an error. From 6f214efedf9d420a51828ab1fd2b8760493cd0f4 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Sat, 7 Mar 2026 11:37:57 +0100 Subject: [PATCH 2/8] nit --- .../core/src/tools/handlers/multi_agents.rs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/tools/handlers/multi_agents.rs b/codex-rs/core/src/tools/handlers/multi_agents.rs index 1d3805b3220..3d51765c207 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents.rs @@ -651,7 +651,8 @@ pub(crate) mod wait { }) } - async fn wait_for_final_status( + // Pub only for tests. Do not use. + pub(super) async fn wait_for_final_status( session: Arc, thread_id: ThreadId, mut status_rx: Receiver, @@ -1007,6 +1008,7 @@ mod tests { use std::sync::Arc; use std::time::Duration; use tokio::sync::Mutex; + use tokio::sync::watch; use tokio::time::timeout; fn invocation( @@ -1986,6 +1988,26 @@ mod tests { assert_eq!(success, None); } + #[tokio::test] + async fn wait_for_final_status_ignores_interrupted_status() { + let (session, _turn) = make_session_and_context().await; + let agent_id = ThreadId::new(); + let (status_tx, status_rx) = watch::channel(AgentStatus::Interrupted); + + tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(10)).await; + status_tx.send_replace(AgentStatus::Shutdown); + }); + + let result = timeout( + Duration::from_secs(1), + wait::wait_for_final_status(Arc::new(session), agent_id, status_rx), + ) + .await + .expect("wait should complete once a truly final status arrives"); + assert_eq!(result, Some((agent_id, AgentStatus::Shutdown))); + } + #[tokio::test] async fn close_agent_submits_shutdown_and_returns_status() { let (mut session, turn) = make_session_and_context().await; From 220979af844c08cc56051b8e3b55f3a02ee1ee28 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Sat, 7 Mar 2026 11:45:39 +0100 Subject: [PATCH 3/8] More testing and nit fixes --- .../src/event_processor_with_human_output.rs | 2 ++ .../src/event_processor_with_jsonl_output.rs | 4 ++++ codex-rs/exec/src/exec_events.rs | 1 + codex-rs/tui/src/multi_agents.rs | 20 +++++++++++++++++++ ...nts__tests__collab_resume_interrupted.snap | 6 ++++++ 5 files changed, 33 insertions(+) create mode 100644 codex-rs/tui/src/snapshots/codex_tui__multi_agents__tests__collab_resume_interrupted.snap diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index ae58f3c9fd3..cd4f04126cb 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -1163,6 +1163,7 @@ fn format_collab_status(status: &AgentStatus) -> String { match status { AgentStatus::PendingInit => "pending init".to_string(), AgentStatus::Running => "running".to_string(), + AgentStatus::Interrupted => "interrupted".to_string(), AgentStatus::Completed(Some(message)) => { let preview = truncate_preview(message.trim(), 120); if preview.is_empty() { @@ -1192,6 +1193,7 @@ fn style_for_agent_status( match status { AgentStatus::PendingInit | AgentStatus::Shutdown => processor.dimmed, AgentStatus::Running => processor.cyan, + AgentStatus::Interrupted => processor.yellow, AgentStatus::Completed(_) => processor.green, AgentStatus::Errored(_) | AgentStatus::NotFound => processor.red, } diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output.rs b/codex-rs/exec/src/event_processor_with_jsonl_output.rs index 8bec82b15dc..6eba6eaf6a7 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output.rs @@ -815,6 +815,10 @@ impl From for CollabAgentState { status: CollabAgentStatus::Running, message: None, }, + CoreAgentStatus::Interrupted => Self { + status: CollabAgentStatus::Interrupted, + message: None, + }, CoreAgentStatus::Completed(message) => Self { status: CollabAgentStatus::Completed, message, diff --git a/codex-rs/exec/src/exec_events.rs b/codex-rs/exec/src/exec_events.rs index 368098f16be..d356a6a70b7 100644 --- a/codex-rs/exec/src/exec_events.rs +++ b/codex-rs/exec/src/exec_events.rs @@ -228,6 +228,7 @@ pub enum CollabTool { pub enum CollabAgentStatus { PendingInit, Running, + Interrupted, Completed, Errored, Shutdown, diff --git a/codex-rs/tui/src/multi_agents.rs b/codex-rs/tui/src/multi_agents.rs index 7161e8880d4..7979ac77eed 100644 --- a/codex-rs/tui/src/multi_agents.rs +++ b/codex-rs/tui/src/multi_agents.rs @@ -411,6 +411,7 @@ fn status_summary_spans(status: &AgentStatus) -> Vec> { match status { AgentStatus::PendingInit => vec![Span::from("Pending init").cyan()], AgentStatus::Running => vec![Span::from("Running").cyan().bold()], + AgentStatus::Interrupted => vec![Span::from("Interrupted").yellow()], AgentStatus::Completed(message) => { let mut spans = vec![Span::from("Completed").green()]; if let Some(message) = message.as_ref() { @@ -560,6 +561,25 @@ mod tests { assert!(!title.spans[4].style.add_modifier.contains(Modifier::DIM)); } + #[test] + fn collab_resume_interrupted_snapshot() { + let sender_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000001") + .expect("valid sender thread id"); + let robie_id = ThreadId::from_string("00000000-0000-0000-0000-000000000002") + .expect("valid robie thread id"); + + let cell = resume_end(CollabResumeEndEvent { + call_id: "call-resume".to_string(), + sender_thread_id, + receiver_thread_id: robie_id, + receiver_agent_nickname: Some("Robie".to_string()), + receiver_agent_role: Some("explorer".to_string()), + status: AgentStatus::Interrupted, + }); + + assert_snapshot!("collab_resume_interrupted", cell_to_text(&cell)); + } + fn cell_to_text(cell: &PlainHistoryCell) -> String { cell.display_lines(200) .iter() diff --git a/codex-rs/tui/src/snapshots/codex_tui__multi_agents__tests__collab_resume_interrupted.snap b/codex-rs/tui/src/snapshots/codex_tui__multi_agents__tests__collab_resume_interrupted.snap new file mode 100644 index 00000000000..17401ba3e0e --- /dev/null +++ b/codex-rs/tui/src/snapshots/codex_tui__multi_agents__tests__collab_resume_interrupted.snap @@ -0,0 +1,6 @@ +--- +source: tui/src/multi_agents.rs +expression: cell_to_text(&cell) +--- +• Resumed Robie [explorer] + └ Interrupted From caa5f511905cb593f91c7d06adecde865faf2c56 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Sat, 7 Mar 2026 11:47:42 +0100 Subject: [PATCH 4/8] doc test --- codex-rs/app-server-protocol/src/protocol/thread_history.rs | 3 +++ 1 file changed, 3 insertions(+) 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 ca80b5c1fc6..810c900ac93 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -2339,6 +2339,9 @@ mod tests { #[test] fn reconstructs_interrupted_send_input_as_completed_collab_call() { + // `send_input(interrupt=true)` first stops the child's active turn, then redirects it with + // new input. The transient interrupted status should remain visible in agent state, but the + // collab tool call itself is still a successful redirect rather than a failed operation. let sender = ThreadId::try_from("00000000-0000-0000-0000-000000000001") .expect("valid sender thread id"); let receiver = ThreadId::try_from("00000000-0000-0000-0000-000000000002") From bd747afe62b1740b2f324f71896288212e4fde79 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Sat, 7 Mar 2026 11:55:05 +0100 Subject: [PATCH 5/8] clippy --- codex-rs/tui/src/multi_agents.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codex-rs/tui/src/multi_agents.rs b/codex-rs/tui/src/multi_agents.rs index 7979ac77eed..5b5915c675f 100644 --- a/codex-rs/tui/src/multi_agents.rs +++ b/codex-rs/tui/src/multi_agents.rs @@ -407,6 +407,8 @@ fn status_summary_line(status: &AgentStatus) -> Line<'static> { status_summary_spans(status).into() } +// Allow `.yellow()` +#[allow(clippy::disallowed_methods)] fn status_summary_spans(status: &AgentStatus) -> Vec> { match status { AgentStatus::PendingInit => vec![Span::from("Pending init").cyan()], From 75a6f5dfbe618cb7bf9d71cf35ceb68643405d5e Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 16 Mar 2026 14:08:18 +0000 Subject: [PATCH 6/8] nit --- codex-rs/app-server-protocol/src/protocol/thread_history.rs | 2 ++ 1 file changed, 2 insertions(+) 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 1e002909013..6a570b7fa5b 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -2470,6 +2470,8 @@ mod tests { sender_thread_id: sender.to_string(), receiver_thread_ids: vec![receiver.to_string()], prompt: Some("new task".into()), + model: None, + reasoning_effort: None, agents_states: [( receiver.to_string(), CollabAgentState { From 965afd86abd7e45c989b74a0c865d8b34a6133ed Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 16 Mar 2026 15:10:12 +0000 Subject: [PATCH 7/8] nit --- codex-rs/app-server-protocol/schema/json/EventMsg.json | 0 .../schema/json/codex_app_server_protocol.schemas.json | 1 + .../schema/json/codex_app_server_protocol.v2.schemas.json | 1 + .../app-server-protocol/schema/typescript/AgentStatus.ts | 8 -------- 4 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 codex-rs/app-server-protocol/schema/json/EventMsg.json delete mode 100644 codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 720f7b0e704..124f21a149e 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -5691,6 +5691,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 25d688373ed..b726a187647 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -2292,6 +2292,7 @@ "enum": [ "pendingInit", "running", + "interrupted", "completed", "errored", "shutdown", diff --git a/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts b/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts deleted file mode 100644 index 484f15da7e6..00000000000 --- a/codex-rs/app-server-protocol/schema/typescript/AgentStatus.ts +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE! DO NOT MODIFY BY HAND! - -// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. - -/** - * Agent lifecycle status, derived from emitted events. - */ -export type AgentStatus = "pending_init" | "running" | "interrupted" | { "completed": string | null } | { "errored": string } | "shutdown" | "not_found"; From beceb5e3b38755621eb193c9e6448fe3f50c0c6e Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 16 Mar 2026 15:40:50 +0000 Subject: [PATCH 8/8] nit fix --- codex-rs/core/src/agent/control_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index d78c448b292..26819309a7d 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -207,7 +207,7 @@ async fn on_event_updates_status_from_turn_aborted() { reason: TurnAbortReason::Interrupted, })); - let expected = AgentStatus::Errored("Interrupted".to_string()); + let expected = AgentStatus::Interrupted; assert_eq!(status, Some(expected)); }