mirror of
https://github.com/openai/codex.git
synced 2026-11-02 16:20:35 +00:00
Queue nudges while plan generating (#10457)
## Summary
This PR fixes a UI/streaming race when nudged or steer-enabled messages
are queued during an active Plan stream.
Previously, `submit_user_message_with_mode` switched collaboration mode
immediately (via `set_collaboration_mask`) even when the message was
queued. If that happened mid-Plan stream, `active_mode_kind` could flip
away from Plan before the turn finished, causing subsequent
`on_plan_delta` updates to be ignored in the UI.
Now, mode switching is deferred until the queued message is actually
submitted.
## What changed
- Added a per-message deferred mode override on `UserMessage`:
- `collaboration_mode_override: Option<CollaborationModeMask>`
- Updated `submit_user_message_with_mode` to:
- create a `UserMessage` carrying the mode override
- queue or submit that message without mutating global mode immediately
- Updated `submit_user_message` to:
- apply `collaboration_mode_override` just before constructing/sending
`Op::UserTurn`
- Kept queueing condition scoped to active Plan stream rendering:
- queue only while plan output is actively streaming in TUI
(`plan_stream_controller.is_some()`)
## Why
This preserves Plan mode for the remainder of the in-flight Plan turn,
so streamed plan deltas continue rendering correctly, while still
ensuring the follow-up queued message is sent with the intended
collaboration mode.
## Behavior after this change
- If a nudged/steer submission happens while Plan output is actively
streaming:
- message is queued
- UI stays in Plan mode for the running turn
- once dequeued/submitted, mode override is applied and the message is
sent in the intended mode
- If no Plan stream is active:
- submission proceeds immediately and mode override is applied as before
## Tests
Added/updated coverage in `tui/src/chatwidget/tests.rs`:
- `submit_user_message_with_mode_queues_while_plan_stream_is_active`
- asserts mode remains Plan while queued
- asserts mode switches to Code when queued message is actually
submitted
- `submit_user_message_with_mode_submits_when_plan_stream_is_not_active`
- `steer_enter_queues_while_plan_stream_is_active`
- `steer_enter_submits_when_plan_stream_is_not_active`
Also updated existing `UserMessage { ... }` test fixtures to include the
new field.
## Codex author
`codex fork 019c1047-d5d5-7c92-a357-6009604dc7e8`
This commit is contained in:
1 parent
4521a6e852
commit
b7ecd166a6
2 files changed
+228
-23
No files matched your search
@@ -1643,16 +1643,7 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
if let Some(combined) = self.drain_queued_messages_for_restore() {
|
||||
let combined_local_image_paths = combined
|
||||
.local_images
|
||||
.iter()
|
||||
.map(|img| img.path.clone())
|
||||
.collect();
|
||||
self.bottom_pane.set_composer_text(
|
||||
combined.text,
|
||||
combined.text_elements,
|
||||
combined_local_image_paths,
|
||||
);
|
||||
self.restore_user_message_to_composer(combined);
|
||||
self.refresh_queued_user_messages();
|
||||
}
|
||||
|
||||
@@ -1715,6 +1706,18 @@ impl ChatWidget {
|
||||
Some(combined)
|
||||
}
|
||||
|
||||
fn restore_user_message_to_composer(&mut self, user_message: UserMessage) {
|
||||
let UserMessage {
|
||||
text,
|
||||
local_images,
|
||||
text_elements,
|
||||
mention_paths: _,
|
||||
} = user_message;
|
||||
let local_image_paths = local_images.into_iter().map(|img| img.path).collect();
|
||||
self.bottom_pane
|
||||
.set_composer_text(text, text_elements, local_image_paths);
|
||||
}
|
||||
|
||||
fn on_plan_update(&mut self, update: UpdatePlanArgs) {
|
||||
self.saw_plan_update_this_turn = true;
|
||||
self.add_to_history(history_cell::new_plan_update(update));
|
||||
@@ -3003,16 +3006,7 @@ impl ChatWidget {
|
||||
} if !self.queued_user_messages.is_empty() => {
|
||||
// Prefer the most recently queued item.
|
||||
if let Some(user_message) = self.queued_user_messages.pop_back() {
|
||||
let local_image_paths = user_message
|
||||
.local_images
|
||||
.iter()
|
||||
.map(|img| img.path.clone())
|
||||
.collect();
|
||||
self.bottom_pane.set_composer_text(
|
||||
user_message.text,
|
||||
user_message.text_elements,
|
||||
local_image_paths,
|
||||
);
|
||||
self.restore_user_message_to_composer(user_message);
|
||||
self.refresh_queued_user_messages();
|
||||
self.request_redraw();
|
||||
}
|
||||
@@ -3030,8 +3024,8 @@ impl ChatWidget {
|
||||
text_elements,
|
||||
mention_paths: self.bottom_pane.take_mention_paths(),
|
||||
};
|
||||
if self.is_session_configured() {
|
||||
// Submitted is only emitted when steer is enabled (Enter sends immediately).
|
||||
if self.is_session_configured() && !self.is_plan_streaming_in_tui() {
|
||||
// Submitted is only emitted when steer is enabled.
|
||||
// Reset any reasoning header only when we are actually submitting a turn.
|
||||
self.reasoning_buffer.clear();
|
||||
self.full_reasoning_buffer.clear();
|
||||
@@ -6415,6 +6409,10 @@ impl ChatWidget {
|
||||
self.bottom_pane.is_task_running() || self.is_review_mode
|
||||
}
|
||||
|
||||
fn is_plan_streaming_in_tui(&self) -> bool {
|
||||
self.plan_stream_controller.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn composer_is_empty(&self) -> bool {
|
||||
self.bottom_pane.composer_is_empty()
|
||||
}
|
||||
@@ -6424,8 +6422,27 @@ impl ChatWidget {
|
||||
text: String,
|
||||
collaboration_mode: CollaborationModeMask,
|
||||
) {
|
||||
if self.agent_turn_running
|
||||
&& self.active_collaboration_mask.as_ref() != Some(&collaboration_mode)
|
||||
{
|
||||
self.add_error_message(
|
||||
"Cannot switch collaboration mode while a turn is running.".to_string(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.set_collaboration_mask(collaboration_mode);
|
||||
self.submit_user_message(text.into());
|
||||
let should_queue = self.is_plan_streaming_in_tui();
|
||||
let user_message = UserMessage {
|
||||
text,
|
||||
local_images: Vec::new(),
|
||||
text_elements: Vec::new(),
|
||||
mention_paths: HashMap::new(),
|
||||
};
|
||||
if should_queue {
|
||||
self.queue_user_message(user_message);
|
||||
} else {
|
||||
self.submit_user_message(user_message);
|
||||
}
|
||||
}
|
||||
|
||||
/// True when the UI is in the regular composer state with no running task,
|
||||
|
||||
Reference in new issue
Block a user