mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Queue follow-up input during user shell commands (#18820)
Fixes #17954. ## Why When a manual shell command like `!sleep 10` is running, submitting plain text such as `hi` currently sends that text as a steer for the active shell turn. User shell turns are not steerable like model turns, so the TUI can remain stuck in `Working` after the shell command finishes. ## What Changed - Detect when the only active work is one or more `ExecCommandSource::UserShell` commands. - Queue plain submitted input in that state so it drains after the shell command and shell turn complete. - Preserve `!cmd` submissions during running work so explicit shell commands keep their existing behavior. - Add regression coverage for the `!sleep 10` plus `hi` flow in `chatwidget::tests::exec_flow::user_message_during_user_shell_command_is_queued_not_steered`. ## Verification - Manually confirmed hang before the fix and no hang after the fix
This commit is contained in:
committed by
GitHub
Unverified
parent
41652665f5
commit
b7fec54354
@@ -5411,6 +5411,12 @@ impl ChatWidget {
|
||||
let should_submit_now =
|
||||
self.is_session_configured() && !self.is_plan_streaming_in_tui();
|
||||
if should_submit_now {
|
||||
if self.only_user_shell_commands_running()
|
||||
&& !user_message.text.starts_with('!')
|
||||
{
|
||||
self.queue_user_message(user_message);
|
||||
return;
|
||||
}
|
||||
// Submitted is emitted when user submits.
|
||||
// Reset any reasoning header only when we are actually submitting a turn.
|
||||
self.reasoning_buffer.clear();
|
||||
@@ -7476,6 +7482,15 @@ impl ChatWidget {
|
||||
self.user_turn_pending_start || self.bottom_pane.is_task_running()
|
||||
}
|
||||
|
||||
fn only_user_shell_commands_running(&self) -> bool {
|
||||
self.agent_turn_running
|
||||
&& !self.running_commands.is_empty()
|
||||
&& self
|
||||
.running_commands
|
||||
.values()
|
||||
.all(|command| command.source == ExecCommandSource::UserShell)
|
||||
}
|
||||
|
||||
/// Rebuild and update the bottom-pane pending-input preview.
|
||||
fn refresh_pending_input_preview(&mut self) {
|
||||
let queued_messages: Vec<String> = self
|
||||
|
||||
@@ -1036,6 +1036,58 @@ async fn bang_shell_enter_while_task_running_submits_run_user_shell_command() {
|
||||
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_message_during_user_shell_command_is_queued_not_steered() {
|
||||
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
chat.handle_codex_event(Event {
|
||||
id: "turn-start".into(),
|
||||
msg: EventMsg::TurnStarted(TurnStartedEvent {
|
||||
turn_id: "turn-1".to_string(),
|
||||
started_at: None,
|
||||
model_context_window: None,
|
||||
collaboration_mode_kind: ModeKind::Default,
|
||||
}),
|
||||
});
|
||||
let begin = begin_exec_with_source(
|
||||
&mut chat,
|
||||
"user-shell-sleep",
|
||||
"sleep 10",
|
||||
ExecCommandSource::UserShell,
|
||||
);
|
||||
|
||||
assert!(chat.only_user_shell_commands_running());
|
||||
chat.bottom_pane
|
||||
.set_composer_text("hi".to_string(), Vec::new(), Vec::new());
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
assert_matches!(op_rx.try_recv(), Err(TryRecvError::Empty));
|
||||
assert_eq!(chat.queued_user_message_texts(), vec!["hi".to_string()]);
|
||||
|
||||
end_exec(&mut chat, begin, "", "", /*exit_code*/ 0);
|
||||
chat.handle_codex_event(Event {
|
||||
id: "turn-complete".into(),
|
||||
msg: EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
turn_id: "turn-1".to_string(),
|
||||
last_agent_message: Some("done".to_string()),
|
||||
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 user message after shell completion, got {other:?}"),
|
||||
}
|
||||
assert!(chat.queued_user_messages.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn disabled_slash_command_while_task_running_snapshot() {
|
||||
// Build a chat widget and simulate an active task
|
||||
|
||||
Reference in New Issue
Block a user