From cca21126266c92fa2e428d50facf7e3982319714 Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Thu, 26 Feb 2026 12:15:45 -0800 Subject: [PATCH] tui: flush pending nudges after manual interrupt --- codex-rs/tui/src/chatwidget.rs | 8 +++++-- codex-rs/tui/src/chatwidget/tests.rs | 32 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 1d9db6f076..45bd82259b 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1885,6 +1885,7 @@ impl ChatWidget { fn on_interrupted_turn(&mut self, reason: TurnAbortReason) { let interrupted_for_pending_nudge = std::mem::take(&mut self.pending_nudge_interrupt_requested); + let had_pending_nudges = !self.pending_nudges.is_empty(); // Finalize, log a gentle prompt, and clear running state. self.finalize_turn(); if reason == TurnAbortReason::Interrupted { @@ -1897,9 +1898,12 @@ impl ChatWidget { )); } - if interrupted_for_pending_nudge { + if had_pending_nudges { self.maybe_send_pending_nudges(); - } else if let Some(combined) = self.drain_queued_messages_for_restore() { + } + if !interrupted_for_pending_nudge + && let Some(combined) = self.drain_queued_messages_for_restore() + { self.restore_user_message_to_composer(combined); self.refresh_pending_input_preview(); } diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 5c02c23f4b..8a2677cb84 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -3616,6 +3616,38 @@ async fn steer_enter_during_final_stream_preserves_follow_up_prompts_in_order() assert!(chat.pending_nudges.is_empty()); } +#[tokio::test] +async fn manual_interrupt_flushes_pending_nudges() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; + chat.thread_id = Some(ThreadId::new()); + chat.on_task_started(); + chat.on_agent_message_delta("Final answer line\n".to_string()); + + chat.bottom_pane.set_composer_text( + "queued while streaming".to_string(), + Vec::new(), + Vec::new(), + ); + chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + assert_eq!(chat.pending_nudges.len(), 1); + assert_no_submit_op(&mut op_rx); + + chat.on_interrupted_turn(TurnAbortReason::Interrupted); + + assert!(chat.pending_nudges.is_empty()); + match next_submit_op(&mut op_rx) { + Op::UserTurn { items, .. } => assert_eq!( + items, + vec![UserInput::Text { + text: "queued while streaming".to_string(), + text_elements: Vec::new(), + }] + ), + other => panic!("expected Op::UserTurn after manual interrupt, got {other:?}"), + } +} + #[tokio::test] async fn enter_submits_when_plan_stream_is_not_active() { let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await;