diff --git a/codex-rs/analytics/src/facts.rs b/codex-rs/analytics/src/facts.rs index 1d371acb1c..a7a64cac58 100644 --- a/codex-rs/analytics/src/facts.rs +++ b/codex-rs/analytics/src/facts.rs @@ -111,6 +111,7 @@ pub enum TurnSteerRejectionReason { ExpectedTurnMismatch, NonSteerableReview, NonSteerableCompact, + NonSteerableUserShell, EmptyInput, InputTooLarge, } @@ -137,6 +138,7 @@ pub enum TurnSteerRequestError { ExpectedTurnMismatch, NonSteerableReview, NonSteerableCompact, + NonSteerableUserShell, } #[derive(Clone, Copy, Debug)] @@ -152,6 +154,7 @@ impl From for TurnSteerRejectionReason { TurnSteerRequestError::ExpectedTurnMismatch => Self::ExpectedTurnMismatch, TurnSteerRequestError::NonSteerableReview => Self::NonSteerableReview, TurnSteerRequestError::NonSteerableCompact => Self::NonSteerableCompact, + TurnSteerRequestError::NonSteerableUserShell => Self::NonSteerableUserShell, } } } diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index d02a28e370..090311da4a 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -528,7 +528,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -1930,7 +1930,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 e87fa17928..566b29d66f 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 @@ -5813,7 +5813,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -9963,7 +9963,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 8254ad0127..c8432632e9 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 @@ -2430,7 +2430,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -6735,7 +6735,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json index 032743d55d..e207e85fed 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ErrorNotification.json @@ -115,7 +115,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -140,7 +140,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 05dfaa4b72..1110c9fa1a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -138,7 +138,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -524,7 +524,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 909c2af4d8..9ce17ae9a5 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -199,7 +199,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -615,7 +615,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 8ebebd39f9..637c6b7864 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -141,7 +141,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -550,7 +550,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 40da3390ec..9349dd542c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -141,7 +141,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -550,7 +550,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 400c0b661a..551105dab7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -141,7 +141,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -550,7 +550,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 aef0cd737c..1c506d255e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -199,7 +199,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -615,7 +615,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 6a50d6d978..b2d177107d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -141,7 +141,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -550,7 +550,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 a3d20555f1..64df15ca4d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -199,7 +199,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -615,7 +615,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 c4b1e54490..98ac1c60bc 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -141,7 +141,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -550,7 +550,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 e55c9651fa..72f9413244 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -141,7 +141,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -550,7 +550,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 2848940085..4f29c2f856 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -138,7 +138,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -524,7 +524,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 c3f31e5322..bf4a16d1a9 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -138,7 +138,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -524,7 +524,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, 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 7e8ca749c0..bf4270789f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -138,7 +138,7 @@ }, { "additionalProperties": false, - "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command.", "properties": { "activeTurnNotSteerable": { "properties": { @@ -524,7 +524,8 @@ "NonSteerableTurnKind": { "enum": [ "review", - "compact" + "compact", + "userShell" ], "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/NonSteerableTurnKind.ts b/codex-rs/app-server-protocol/schema/typescript/v2/NonSteerableTurnKind.ts index 2624df2ba0..8e544c4b0e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/NonSteerableTurnKind.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/NonSteerableTurnKind.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 NonSteerableTurnKind = "review" | "compact"; +export type NonSteerableTurnKind = "review" | "compact" | "userShell"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 5173549326..410435873e 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -139,6 +139,7 @@ macro_rules! v2_enum_from_core { pub enum NonSteerableTurnKind { Review, Compact, + UserShell, } /// This translation layer make sure that we expose codex error code in camel case. @@ -181,7 +182,7 @@ pub enum CodexErrorInfo { http_status_code: Option, }, /// Returned when `turn/start` or `turn/steer` is submitted while the current active turn - /// cannot accept same-turn steering, for example `/review` or manual `/compact`. + /// cannot accept same-turn steering, for example `/review`, manual `/compact`, or a user shell command. ActiveTurnNotSteerable { #[serde(rename = "turnKind")] #[ts(rename = "turnKind")] @@ -228,6 +229,7 @@ impl From for NonSteerableTurnKind { match value { CoreNonSteerableTurnKind::Review => Self::Review, CoreNonSteerableTurnKind::Compact => Self::Compact, + CoreNonSteerableTurnKind::UserShell => Self::UserShell, } } } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 2fece44544..4dff9ce56d 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1058,7 +1058,7 @@ There are additional item-specific events: - `ResponseStreamDisconnected { httpStatusCode? }`: disconnect of the response SSE stream in the middle of a turn before completion - `ResponseTooManyFailedAttempts { httpStatusCode? }` - `ActiveTurnNotSteerable { turnKind }`: `turn/start` or `turn/steer` was submitted while the - current active turn was not steerable, for example `/review` or manual `/compact` + current active turn was not steerable, for example `/review`, manual `/compact`, or a user shell command - `BadRequest` - `Unauthorized` - `SandboxError` diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 6a9dfb4042..8147c1dbcd 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -7061,6 +7061,10 @@ impl CodexMessageProcessor { "cannot steer a compact turn".to_string(), TurnSteerRequestError::NonSteerableCompact, ), + codex_protocol::protocol::NonSteerableTurnKind::UserShell => ( + "cannot steer a user shell turn".to_string(), + TurnSteerRequestError::NonSteerableUserShell, + ), }; let error = TurnError { message: message.clone(), diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 1b81af7058..852e5a0ea3 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -216,6 +216,7 @@ impl SteerInputError { let turn_kind_label = match turn_kind { NonSteerableTurnKind::Review => "review", NonSteerableTurnKind::Compact => "compact", + NonSteerableTurnKind::UserShell => "user shell", }; ErrorEvent { message: format!("cannot steer a {turn_kind_label} turn"), @@ -2787,6 +2788,11 @@ impl Session { turn_kind: NonSteerableTurnKind::Compact, }); } + Some(crate::state::TaskKind::UserShell) => { + return Err(SteerInputError::ActiveTurnNotSteerable { + turn_kind: NonSteerableTurnKind::UserShell, + }); + } None => return Err(SteerInputError::NoActiveTurn(input)), } diff --git a/codex-rs/core/src/codex_tests.rs b/codex-rs/core/src/codex_tests.rs index aa5583405e..5cd52a7a26 100644 --- a/codex-rs/core/src/codex_tests.rs +++ b/codex-rs/core/src/codex_tests.rs @@ -5391,6 +5391,7 @@ async fn steer_input_rejects_non_regular_turns() { for (task_kind, turn_kind) in [ (TaskKind::Review, NonSteerableTurnKind::Review), (TaskKind::Compact, NonSteerableTurnKind::Compact), + (TaskKind::UserShell, NonSteerableTurnKind::UserShell), ] { let (sess, _tc, _rx) = make_session_and_context_with_rx().await; let input = vec![UserInput::Text { diff --git a/codex-rs/core/src/state/turn.rs b/codex-rs/core/src/state/turn.rs index 214fd8be15..8713c565d2 100644 --- a/codex-rs/core/src/state/turn.rs +++ b/codex-rs/core/src/state/turn.rs @@ -64,6 +64,7 @@ pub(crate) enum TaskKind { Regular, Review, Compact, + UserShell, } pub(crate) struct RunningTask { diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index 8b108eee06..df353d1270 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -63,7 +63,7 @@ impl UserShellCommandTask { impl SessionTask for UserShellCommandTask { fn kind(&self) -> TaskKind { - TaskKind::Regular + TaskKind::UserShell } fn span_name(&self) -> &'static str { diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index b777b4d320..5d23072b54 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -1845,6 +1845,7 @@ pub enum AgentStatus { pub enum NonSteerableTurnKind { Review, Compact, + UserShell, } /// Codex errors that we expose to clients. diff --git a/codex-rs/tui/src/chatwidget/tests/exec_flow.rs b/codex-rs/tui/src/chatwidget/tests/exec_flow.rs index 53f16a39d1..d0c0c2c49a 100644 --- a/codex-rs/tui/src/chatwidget/tests/exec_flow.rs +++ b/codex-rs/tui/src/chatwidget/tests/exec_flow.rs @@ -1027,6 +1027,71 @@ async fn bang_shell_command_submits_run_user_shell_command_in_app_server_tui() { assert_matches!(rx.try_recv(), Err(TryRecvError::Empty)); } +#[tokio::test] +async fn user_shell_steer_rejection_queues_follow_up_until_shell_turn_completes() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.thread_id = Some(ThreadId::new()); + + chat.handle_codex_event(Event { + id: "shell-turn-start".into(), + msg: EventMsg::TurnStarted(TurnStartedEvent { + turn_id: "shell-turn".to_string(), + started_at: None, + model_context_window: None, + collaboration_mode_kind: ModeKind::Default, + }), + }); + + chat.submit_user_message(UserMessage::from("hi")); + + assert_eq!(chat.pending_steers.len(), 1); + match next_submit_op(&mut op_rx) { + Op::UserTurn { items, .. } => assert_eq!( + items, + vec![UserInput::Text { + text: "hi".to_string(), + text_elements: Vec::new(), + }] + ), + other => panic!("expected running-turn steer submit, got {other:?}"), + } + + chat.handle_codex_event(Event { + id: "shell-steer-rejected".into(), + msg: EventMsg::Error(ErrorEvent { + message: "cannot steer a user shell turn".to_string(), + codex_error_info: Some(CodexErrorInfo::ActiveTurnNotSteerable { + turn_kind: NonSteerableTurnKind::UserShell, + }), + }), + }); + + assert!(chat.pending_steers.is_empty()); + assert_eq!(chat.queued_user_message_texts(), vec!["hi"]); + assert!(drain_insert_history(&mut rx).is_empty()); + + chat.handle_codex_event(Event { + id: "shell-turn-complete".into(), + msg: EventMsg::TurnComplete(TurnCompleteEvent { + turn_id: "shell-turn".to_string(), + last_agent_message: None, + completed_at: None, + duration_ms: None, + }), + }); + + match next_submit_op(&mut op_rx) { + Op::UserTurn { items, .. } => assert_eq!( + items, + vec![UserInput::Text { + text: "hi".to_string(), + text_elements: Vec::new(), + }] + ), + other => panic!("expected queued follow-up submit, got {other:?}"), + } +} + #[tokio::test] async fn disabled_slash_command_while_task_running_snapshot() { // Build a chat widget and simulate an active task