mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Fix queued messages during /review (#9122)
Sending a message during /review interrupts the review, whereas during normal operation, sending a message while the agent is running will queue the message. This is unexpected behavior, and since /review usually takes a while, it takes away a potentially useful operation. Summary - Treat review mode as an active task for message queuing so inputs don’t inject into the running review turn. - Prevents user submissions from rendering immediately in the transcript while the review continues streaming. - Keeps review UX consistent with normal “task running” behavior and avoids accidental interrupt/replacement. Notes - This change only affects UI queuing logic; core review flow and task lifecycle remain unchanged.
This commit is contained in:
@@ -2077,7 +2077,7 @@ impl ChatWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn queue_user_message(&mut self, user_message: UserMessage) {
|
fn queue_user_message(&mut self, user_message: UserMessage) {
|
||||||
if self.bottom_pane.is_task_running() {
|
if self.bottom_pane.is_task_running() || self.is_review_mode {
|
||||||
self.queued_user_messages.push_back(user_message);
|
self.queued_user_messages.push_back(user_message);
|
||||||
self.refresh_queued_user_messages();
|
self.refresh_queued_user_messages();
|
||||||
} else {
|
} else {
|
||||||
@@ -2286,7 +2286,7 @@ impl ChatWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventMsg::EnteredReviewMode(review_request) => {
|
EventMsg::EnteredReviewMode(review_request) => {
|
||||||
self.on_entered_review_mode(review_request)
|
self.on_entered_review_mode(review_request, from_replay)
|
||||||
}
|
}
|
||||||
EventMsg::ExitedReviewMode(review) => self.on_exited_review_mode(review),
|
EventMsg::ExitedReviewMode(review) => self.on_exited_review_mode(review),
|
||||||
EventMsg::ContextCompacted(_) => self.on_agent_message("Context compacted".to_owned()),
|
EventMsg::ContextCompacted(_) => self.on_agent_message("Context compacted".to_owned()),
|
||||||
@@ -2300,11 +2300,15 @@ impl ChatWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_entered_review_mode(&mut self, review: ReviewRequest) {
|
fn on_entered_review_mode(&mut self, review: ReviewRequest, from_replay: bool) {
|
||||||
// Enter review mode and emit a concise banner
|
// Enter review mode and emit a concise banner
|
||||||
if self.pre_review_token_info.is_none() {
|
if self.pre_review_token_info.is_none() {
|
||||||
self.pre_review_token_info = Some(self.token_info.clone());
|
self.pre_review_token_info = Some(self.token_info.clone());
|
||||||
}
|
}
|
||||||
|
// Avoid toggling running state for replayed history events on resume.
|
||||||
|
if !from_replay && !self.bottom_pane.is_task_running() {
|
||||||
|
self.bottom_pane.set_task_running(true);
|
||||||
|
}
|
||||||
self.is_review_mode = true;
|
self.is_review_mode = true;
|
||||||
let hint = review
|
let hint = review
|
||||||
.user_facing_hint
|
.user_facing_hint
|
||||||
@@ -3787,9 +3791,12 @@ impl ChatWidget {
|
|||||||
self.bottom_pane.clear_esc_backtrack_hint();
|
self.bottom_pane.clear_esc_backtrack_hint();
|
||||||
}
|
}
|
||||||
/// Forward an `Op` directly to codex.
|
/// Forward an `Op` directly to codex.
|
||||||
pub(crate) fn submit_op(&self, op: Op) {
|
pub(crate) fn submit_op(&mut self, op: Op) {
|
||||||
// Record outbound operation for session replay fidelity.
|
// Record outbound operation for session replay fidelity.
|
||||||
crate::session_log::log_outbound_op(&op);
|
crate::session_log::log_outbound_op(&op);
|
||||||
|
if matches!(&op, Op::Review { .. }) && !self.bottom_pane.is_task_running() {
|
||||||
|
self.bottom_pane.set_task_running(true);
|
||||||
|
}
|
||||||
if let Err(e) = self.codex_op_tx.send(op) {
|
if let Err(e) = self.codex_op_tx.send(op) {
|
||||||
tracing::error!("failed to submit op: {e}");
|
tracing::error!("failed to submit op: {e}");
|
||||||
}
|
}
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
source: tui/src/chatwidget/tests.rs
|
||||||
|
assertion_line: 3840
|
||||||
|
expression: term.backend().vt100().screen().contents()
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
• Working (0s • esc to interrupt)
|
||||||
|
↳ Queued while /review is running.
|
||||||
|
⌥ + ↑ edit
|
||||||
|
|
||||||
|
|
||||||
|
› Ask Codex to do anything
|
||||||
|
|
||||||
|
100% context left · ? for shortcuts
|
||||||
@@ -3871,3 +3871,33 @@ async fn chatwidget_tall() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert_snapshot!(term.backend().vt100().screen().contents());
|
assert_snapshot!(term.backend().vt100().screen().contents());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn review_queues_user_messages_snapshot() {
|
||||||
|
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||||
|
|
||||||
|
chat.handle_codex_event(Event {
|
||||||
|
id: "review-1".into(),
|
||||||
|
msg: EventMsg::EnteredReviewMode(ReviewRequest {
|
||||||
|
target: ReviewTarget::UncommittedChanges,
|
||||||
|
user_facing_hint: Some("current changes".to_string()),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
let _ = drain_insert_history(&mut rx);
|
||||||
|
|
||||||
|
chat.queue_user_message(UserMessage::from(
|
||||||
|
"Queued while /review is running.".to_string(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let width: u16 = 80;
|
||||||
|
let height: u16 = 18;
|
||||||
|
let backend = VT100Backend::new(width, height);
|
||||||
|
let mut term = crate::custom_terminal::Terminal::with_options(backend).expect("terminal");
|
||||||
|
let desired_height = chat.desired_height(width).min(height);
|
||||||
|
term.set_viewport_area(Rect::new(0, height - desired_height, width, desired_height));
|
||||||
|
term.draw(|f| {
|
||||||
|
chat.render(f.area(), f.buffer_mut());
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert_snapshot!(term.backend().vt100().screen().contents());
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user