mirror of
https://github.com/openai/codex.git
synced 2026-05-02 04:11:39 +03:00
tui: align pending steers with core acceptance (#12868)
## Summary - submit `Enter` steers immediately while a turn is already running instead of routing them through `queued_user_messages` - keep those submitted steers visible in the footer as `pending_steers` until core records them as a user message or aborts the turn - reconcile pending steers on `ItemCompleted(UserMessage)`, not `RawResponseItem` - emit user-message item lifecycle for leftover pending input at task finish, then remove the TUI `TurnComplete` fallback - keep `queued_user_messages` for actual queued drafts, rendered below pending steers ## Problem While the assistant was generating, pressing `Enter` could send the input into `queued_user_messages`. That queue only drains after the turn ends, so ordinary steers behaved like queued drafts instead of landing at the next core sampling boundary. The first version of this fix also used `RawResponseItem` to decide when a steer had landed. Review feedback was that this is the wrong abstraction for client behavior. There was also a late edge case in core: if pending steer input was accepted after the final sampling decision but before `TurnComplete`, core would record that user message into history at task finish without emitting `ItemStarted(UserMessage)` / `ItemCompleted(UserMessage)`. TUI had a fallback to paper over that gap locally. ## Approach - `Enter` during an active turn now submits a normal `Op::UserTurn` immediately - TUI keeps a local pending-steer preview instead of rendering that user message into history immediately - when core records the steer as `ItemCompleted(UserMessage)`, TUI matches and removes the corresponding pending preview, then renders the committed user message - core now emits the same user-message lifecycle when `on_task_finished(...)` drains leftover pending user input, before `TurnComplete` - with that lifecycle gap closed in core, TUI no longer needs to flush pending steers into history on `TurnComplete` - if the turn is interrupted, pending steers and queued drafts are both restored into the composer, with pending steers first ## Notes - `Tab` still uses the real queued-message path - `queued_user_messages` and `pending_steers` are separate state with separate semantics - the pending-steer matching key is built directly from `UserInput` - this removes the new TUI dependency on `RawResponseItem` ## Validation - `just fmt` - `cargo test -p codex-core task_finish_emits_turn_item_lifecycle_for_leftover_pending_user_input -- --nocapture` - `cargo test -p codex-tui`
This commit is contained in:
committed by
GitHub
parent
24a2d0c696
commit
299b8ac445
@@ -17,8 +17,8 @@ use std::path::PathBuf;
|
||||
|
||||
use crate::app_event::ConnectorsSnapshot;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use crate::bottom_pane::pending_input_preview::PendingInputPreview;
|
||||
use crate::bottom_pane::pending_thread_approvals::PendingThreadApprovals;
|
||||
use crate::bottom_pane::queued_user_messages::QueuedUserMessages;
|
||||
use crate::bottom_pane::unified_exec_footer::UnifiedExecFooter;
|
||||
use crate::key_hint;
|
||||
use crate::key_hint::KeyBinding;
|
||||
@@ -93,9 +93,9 @@ pub(crate) use skills_toggle_view::SkillsToggleView;
|
||||
pub(crate) use status_line_setup::StatusLineItem;
|
||||
pub(crate) use status_line_setup::StatusLineSetupView;
|
||||
mod paste_burst;
|
||||
mod pending_input_preview;
|
||||
mod pending_thread_approvals;
|
||||
pub mod popup_consts;
|
||||
mod queued_user_messages;
|
||||
mod scroll_state;
|
||||
mod selection_popup_common;
|
||||
mod textarea;
|
||||
@@ -172,8 +172,8 @@ pub(crate) struct BottomPane {
|
||||
/// When a status row exists, this summary is mirrored inline in that row;
|
||||
/// when no status row exists, it renders as its own footer row.
|
||||
unified_exec_footer: UnifiedExecFooter,
|
||||
/// Queued user messages to show above the composer while a turn is running.
|
||||
queued_user_messages: QueuedUserMessages,
|
||||
/// Preview of pending steers and queued drafts shown above the composer.
|
||||
pending_input_preview: PendingInputPreview,
|
||||
/// Inactive threads with pending approval requests.
|
||||
pending_thread_approvals: PendingThreadApprovals,
|
||||
context_window_percent: Option<i64>,
|
||||
@@ -223,7 +223,7 @@ impl BottomPane {
|
||||
is_task_running: false,
|
||||
status: None,
|
||||
unified_exec_footer: UnifiedExecFooter::new(),
|
||||
queued_user_messages: QueuedUserMessages::new(),
|
||||
pending_input_preview: PendingInputPreview::new(),
|
||||
pending_thread_approvals: PendingThreadApprovals::new(),
|
||||
esc_backtrack_hint: false,
|
||||
animations_enabled,
|
||||
@@ -317,7 +317,7 @@ impl BottomPane {
|
||||
/// Update the key hint shown next to queued messages so it matches the
|
||||
/// binding that `ChatWidget` actually listens for.
|
||||
pub(crate) fn set_queued_message_edit_binding(&mut self, binding: KeyBinding) {
|
||||
self.queued_user_messages.set_edit_binding(binding);
|
||||
self.pending_input_preview.set_edit_binding(binding);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
@@ -774,9 +774,14 @@ impl BottomPane {
|
||||
true
|
||||
}
|
||||
|
||||
/// Update the queued messages preview shown above the composer.
|
||||
pub(crate) fn set_queued_user_messages(&mut self, queued: Vec<String>) {
|
||||
self.queued_user_messages.messages = queued;
|
||||
/// Update the pending-input preview shown above the composer.
|
||||
pub(crate) fn set_pending_input_preview(
|
||||
&mut self,
|
||||
queued: Vec<String>,
|
||||
pending_steers: Vec<String>,
|
||||
) {
|
||||
self.pending_input_preview.pending_steers = pending_steers;
|
||||
self.pending_input_preview.queued_messages = queued;
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
@@ -1019,18 +1024,19 @@ impl BottomPane {
|
||||
flex.push(0, RenderableItem::Borrowed(&self.unified_exec_footer));
|
||||
}
|
||||
let has_pending_thread_approvals = !self.pending_thread_approvals.is_empty();
|
||||
let has_queued_messages = !self.queued_user_messages.messages.is_empty();
|
||||
let has_pending_input = !self.pending_input_preview.queued_messages.is_empty()
|
||||
|| !self.pending_input_preview.pending_steers.is_empty();
|
||||
let has_status_or_footer =
|
||||
self.status.is_some() || !self.unified_exec_footer.is_empty();
|
||||
let has_inline_previews = has_pending_thread_approvals || has_queued_messages;
|
||||
let has_inline_previews = has_pending_thread_approvals || has_pending_input;
|
||||
if has_inline_previews && has_status_or_footer {
|
||||
flex.push(0, RenderableItem::Owned("".into()));
|
||||
}
|
||||
flex.push(1, RenderableItem::Borrowed(&self.pending_thread_approvals));
|
||||
if has_pending_thread_approvals && has_queued_messages {
|
||||
if has_pending_thread_approvals && has_pending_input {
|
||||
flex.push(0, RenderableItem::Owned("".into()));
|
||||
}
|
||||
flex.push(1, RenderableItem::Borrowed(&self.queued_user_messages));
|
||||
flex.push(1, RenderableItem::Borrowed(&self.pending_input_preview));
|
||||
if !has_inline_previews && has_status_or_footer {
|
||||
flex.push(0, RenderableItem::Owned("".into()));
|
||||
}
|
||||
@@ -1406,7 +1412,7 @@ mod tests {
|
||||
StatusDetailsCapitalization::CapitalizeFirst,
|
||||
STATUS_DETAILS_DEFAULT_MAX_LINES,
|
||||
);
|
||||
pane.set_queued_user_messages(vec!["Queued follow-up question".to_string()]);
|
||||
pane.set_pending_input_preview(vec!["Queued follow-up question".to_string()], Vec::new());
|
||||
|
||||
let width = 48;
|
||||
let height = pane.desired_height(width);
|
||||
@@ -1433,7 +1439,7 @@ mod tests {
|
||||
});
|
||||
|
||||
pane.set_task_running(true);
|
||||
pane.set_queued_user_messages(vec!["Queued follow-up question".to_string()]);
|
||||
pane.set_pending_input_preview(vec!["Queued follow-up question".to_string()], Vec::new());
|
||||
pane.hide_status_indicator();
|
||||
|
||||
let width = 48;
|
||||
@@ -1461,7 +1467,7 @@ mod tests {
|
||||
});
|
||||
|
||||
pane.set_task_running(true);
|
||||
pane.set_queued_user_messages(vec!["Queued follow-up question".to_string()]);
|
||||
pane.set_pending_input_preview(vec!["Queued follow-up question".to_string()], Vec::new());
|
||||
|
||||
let width = 48;
|
||||
let height = pane.desired_height(width);
|
||||
|
||||
Reference in New Issue
Block a user