Restore TUI working status after steer message is set (#19939)

Fix for #19925

Restore the `Working` indicator after a streamed final answer finishes
when a user steer message is sent.
Add regression coverage for long output plus a mid-stream steer:
`cargo test -p codex-tui
final_answer_completion_restores_status_indicator_for_pending_steer`

Duplication/testing steps:
1. Start a new thread and ask for a long response.
2. While the response is streaming, submit a steer message.
3. When the first response finishes, observe whether `Working...` is
shown while waiting for the steer message response.
This commit is contained in:
canvrno-oai
2026-04-28 18:10:40 -07:00
committed by GitHub
Unverified
parent c9f7c88f3d
commit 24be9ac0a4
2 changed files with 60 additions and 1 deletions
+1 -1
View File
@@ -4974,7 +4974,7 @@ impl ChatWidget {
}
self.pending_status_indicator_restore = match item.phase {
// Models that don't support preambles only output AgentMessageItems on turn completion.
Some(MessagePhase::FinalAnswer) | None => false,
Some(MessagePhase::FinalAnswer) | None => !self.pending_steers.is_empty(),
Some(MessagePhase::Commentary) => true,
};
self.maybe_restore_status_indicator_after_stream_idle();
@@ -947,6 +947,65 @@ async fn idle_commit_ticks_do_not_restore_status_without_commentary_completion()
assert_eq!(chat.bottom_pane.status_indicator_visible(), false);
}
#[tokio::test]
async fn final_answer_completion_restores_status_indicator_for_pending_steer() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.thread_id = Some(ThreadId::new());
chat.on_task_started();
assert_eq!(chat.bottom_pane.status_indicator_visible(), true);
chat.on_agent_message_delta("Long output line 1\n".to_string());
chat.on_commit_tick();
drain_insert_history(&mut rx);
chat.on_agent_message_delta("Long output line 2\n".to_string());
chat.on_commit_tick();
drain_insert_history(&mut rx);
assert_eq!(chat.bottom_pane.status_indicator_visible(), false);
assert_eq!(chat.bottom_pane.is_task_running(), true);
chat.bottom_pane.set_composer_text(
"Please summarize the rest more briefly.".to_string(),
Vec::new(),
Vec::new(),
);
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert_eq!(chat.pending_steers.len(), 1);
let items = match next_submit_op(&mut op_rx) {
Op::UserTurn { items, .. } => items,
other => panic!("expected Op::UserTurn, got {other:?}"),
};
assert_eq!(
items,
vec![UserInput::Text {
text: "Please summarize the rest more briefly.".to_string(),
text_elements: Vec::new(),
}]
);
complete_assistant_message(
&mut chat,
"msg-final",
"Long output line 1\nLong output line 2\n",
Some(MessagePhase::FinalAnswer),
);
assert_eq!(chat.bottom_pane.status_indicator_visible(), true);
assert_eq!(chat.bottom_pane.is_task_running(), true);
complete_user_message(
&mut chat,
"user-steer",
"Please summarize the rest more briefly.",
);
assert!(chat.pending_steers.is_empty());
assert_eq!(chat.bottom_pane.status_indicator_visible(), true);
assert_eq!(chat.bottom_pane.is_task_running(), true);
}
#[tokio::test]
async fn commentary_completion_restores_status_indicator_before_exec_begin() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;