diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index bd1014b2f4..a28059fb46 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1576,7 +1576,10 @@ impl ChatWidget { fn on_undo_started(&mut self, event: UndoStartedEvent) { self.bottom_pane.ensure_status_indicator(); - self.bottom_pane.set_interrupt_hint_visible(false); + self.agent_turn_running = true; + self.update_task_running_state(); + self.bottom_pane.set_interrupt_hint_visible(true); + self.request_redraw(); let message = event .message .unwrap_or_else(|| "Undo in progress...".to_string()); @@ -1585,6 +1588,8 @@ impl ChatWidget { fn on_undo_completed(&mut self, event: UndoCompletedEvent) { let UndoCompletedEvent { success, message } = event; + self.agent_turn_running = false; + self.update_task_running_state(); self.bottom_pane.hide_status_indicator(); let message = message.unwrap_or_else(|| { if success { @@ -1598,6 +1603,7 @@ impl ChatWidget { } else { self.add_error_message(message); } + self.maybe_send_next_queued_input(); } fn on_stream_error(&mut self, message: String, additional_details: Option) { diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 76d5600010..86234ce3df 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -2536,6 +2536,45 @@ async fn undo_success_events_render_info_messages() { ); } +#[tokio::test] +async fn undo_completed_clears_running_and_sends_next_queued_input() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await; + chat.thread_id = Some(ThreadId::new()); + + chat.agent_turn_running = true; + chat.update_task_running_state(); + assert!(chat.bottom_pane.is_task_running()); + + chat.queue_user_message(UserMessage { + text: "next".to_string(), + local_images: Vec::new(), + text_elements: Vec::new(), + }); + assert_eq!(chat.queued_user_messages.len(), 1); + + chat.handle_codex_event(Event { + id: "undo-complete".to_string(), + msg: EventMsg::UndoCompleted(UndoCompletedEvent { + success: true, + message: None, + }), + }); + + assert!(!chat.bottom_pane.is_task_running()); + assert!(chat.queued_user_messages.is_empty()); + + let op = next_submit_op(&mut op_rx); + let actual_items = match op { + Op::UserTurn { items, .. } => items, + other => panic!("expected Op::UserTurn, got {other:?}"), + }; + let expected_items = vec![UserInput::Text { + text: "next".to_string(), + text_elements: Vec::new(), + }]; + assert_eq!(actual_items, expected_items); +} + #[tokio::test] async fn undo_failure_events_render_error_message() { let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; @@ -2572,7 +2611,7 @@ async fn undo_failure_events_render_error_message() { } #[tokio::test] -async fn undo_started_hides_interrupt_hint() { +async fn undo_started_shows_interrupt_hint() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; chat.handle_codex_event(Event { @@ -2585,8 +2624,8 @@ async fn undo_started_hides_interrupt_hint() { .status_widget() .expect("status indicator should be active"); assert!( - !status.interrupt_hint_visible(), - "undo should hide the interrupt hint because the operation cannot be cancelled" + status.interrupt_hint_visible(), + "undo should show the interrupt hint while work is in progress" ); }