mirror of
https://github.com/openai/codex.git
synced 2026-04-30 17:36:40 +00:00
Prevent replayed runtime events from forcing active status (#12420)
Fixes #11852 Resume replay was applying transient runtime events (`TurnStarted`, `StreamError`) as if they were live, which could leave the TUI stuck in a stale `Working` / `Reconnecting...` state after resuming an interrupted reconnect. This change makes replay transcript-oriented for these events by: - skipping retry-status restoration for replayed non-stream events - ignoring replayed `TurnStarted` for task-running state - ignoring replayed `StreamError` for reconnect/status UI Also adds TUI regression tests and snapshot coverage for the interrupted reconnect replay case.
This commit is contained in:
@@ -832,6 +832,12 @@ fn remap_placeholders_for_message(message: UserMessage, next_label: &mut usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ReplayKind {
|
||||
ResumeInitialMessages,
|
||||
ThreadSnapshot,
|
||||
}
|
||||
|
||||
impl ChatWidget {
|
||||
/// Synchronize the bottom-pane "task running" indicator with the current lifecycles.
|
||||
///
|
||||
@@ -4002,13 +4008,13 @@ impl ChatWidget {
|
||||
continue;
|
||||
}
|
||||
// `id: None` indicates a synthetic/fake id coming from replay.
|
||||
self.dispatch_event_msg(None, msg, true);
|
||||
self.dispatch_event_msg(None, msg, Some(ReplayKind::ResumeInitialMessages));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_codex_event(&mut self, event: Event) {
|
||||
let Event { id, msg } = event;
|
||||
self.dispatch_event_msg(Some(id), msg, false);
|
||||
self.dispatch_event_msg(Some(id), msg, None);
|
||||
}
|
||||
|
||||
pub(crate) fn handle_codex_event_replay(&mut self, event: Event) {
|
||||
@@ -4016,7 +4022,7 @@ impl ChatWidget {
|
||||
if matches!(msg, EventMsg::ShutdownComplete) {
|
||||
return;
|
||||
}
|
||||
self.dispatch_event_msg(None, msg, true);
|
||||
self.dispatch_event_msg(None, msg, Some(ReplayKind::ThreadSnapshot));
|
||||
}
|
||||
|
||||
/// Dispatch a protocol `EventMsg` to the appropriate handler.
|
||||
@@ -4024,9 +4030,17 @@ impl ChatWidget {
|
||||
/// `id` is `Some` for live events and `None` for replayed events from
|
||||
/// `replay_initial_messages()`. Callers should treat `None` as a "fake" id
|
||||
/// that must not be used to correlate follow-up actions.
|
||||
fn dispatch_event_msg(&mut self, id: Option<String>, msg: EventMsg, from_replay: bool) {
|
||||
fn dispatch_event_msg(
|
||||
&mut self,
|
||||
id: Option<String>,
|
||||
msg: EventMsg,
|
||||
replay_kind: Option<ReplayKind>,
|
||||
) {
|
||||
let from_replay = replay_kind.is_some();
|
||||
let is_resume_initial_replay =
|
||||
matches!(replay_kind, Some(ReplayKind::ResumeInitialMessages));
|
||||
let is_stream_error = matches!(&msg, EventMsg::StreamError(_));
|
||||
if !is_stream_error {
|
||||
if !is_resume_initial_replay && !is_stream_error {
|
||||
self.restore_retry_status_header_if_present();
|
||||
}
|
||||
|
||||
@@ -4061,7 +4075,11 @@ impl ChatWidget {
|
||||
self.on_agent_reasoning_final();
|
||||
}
|
||||
EventMsg::AgentReasoningSectionBreak(_) => self.on_reasoning_section_break(),
|
||||
EventMsg::TurnStarted(_) => self.on_task_started(),
|
||||
EventMsg::TurnStarted(_) => {
|
||||
if !is_resume_initial_replay {
|
||||
self.on_task_started();
|
||||
}
|
||||
}
|
||||
EventMsg::TurnComplete(TurnCompleteEvent {
|
||||
last_agent_message, ..
|
||||
}) => self.on_task_complete(last_agent_message, from_replay),
|
||||
@@ -4151,7 +4169,11 @@ impl ChatWidget {
|
||||
message,
|
||||
additional_details,
|
||||
..
|
||||
}) => self.on_stream_error(message, additional_details),
|
||||
}) => {
|
||||
if !is_resume_initial_replay {
|
||||
self.on_stream_error(message, additional_details);
|
||||
}
|
||||
}
|
||||
EventMsg::UserMessage(ev) => {
|
||||
if from_replay {
|
||||
self.on_user_message_event(ev);
|
||||
|
||||
Reference in New Issue
Block a user