diff --git a/codex-rs/tui/src/app/event_dispatch.rs b/codex-rs/tui/src/app/event_dispatch.rs index 4726c76856..49049aeada 100644 --- a/codex-rs/tui/src/app/event_dispatch.rs +++ b/codex-rs/tui/src/app/event_dispatch.rs @@ -321,7 +321,57 @@ impl App { .await; let config = self.fresh_session_config(); let turns = match self.thread_event_channels.get(&thread_id) { - Some(channel) => Some(channel.store.lock().await.turns.clone()), + Some(channel) => { + let store = channel.store.lock().await; + let mut turns = store.turns.clone(); + // Snapshot turns contain loaded history; newer live turns remain in + // the replay buffer and must also be visible to prompt-edit lookups. + for event in &store.buffer { + let ThreadBufferedEvent::Notification(notification) = event else { + continue; + }; + match notification.as_ref() { + ServerNotification::TurnStarted(notification) + if !turns + .iter() + .any(|turn| turn.id == notification.turn.id) => + { + turns.push(notification.turn.clone()); + } + ServerNotification::ItemCompleted(notification) => { + if matches!( + notification.item, + ThreadItem::UserMessage { .. } + | ThreadItem::EnteredReviewMode { .. } + | ThreadItem::ExitedReviewMode { .. } + ) && let Some(turn) = turns + .iter_mut() + .find(|turn| turn.id == notification.turn_id) + && !turn + .items + .iter() + .any(|item| item.id() == notification.item.id()) + { + turn.items.push(notification.item.clone()); + } + } + ServerNotification::TurnCompleted(notification) => { + if let Some(turn) = turns + .iter_mut() + .find(|turn| turn.id == notification.turn.id) + { + turn.status = notification.turn.status.clone(); + turn.error = notification.turn.error.clone(); + turn.started_at = notification.turn.started_at; + turn.completed_at = notification.turn.completed_at; + turn.duration_ms = notification.turn.duration_ms; + } + } + _ => {} + } + } + Some(turns) + } None => None, }; let started = match turns { diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index 3c84a7491b..1057fb8267 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -6590,8 +6590,38 @@ async fn prompt_edit_forks_before_selected_prompt_and_preserves_source() -> Resu crate::app_server_session::ResumeModelSettings::OverrideFromCurrentConfig, ) .await?; + let selected_turn = started.turns[1].clone(); app.enqueue_primary_thread_session(started.session, started.turns) .await?; + { + let mut store = app + .thread_event_channels + .get(&source_thread_id) + .expect("source thread event channel") + .store + .lock() + .await; + store.turns.pop(); + store.push_notification(turn_started_notification( + source_thread_id, + &selected_turn.id, + )); + for item in selected_turn.items { + store.push_notification(ServerNotification::ItemCompleted( + codex_app_server_protocol::ItemCompletedNotification { + thread_id: source_thread_id.to_string(), + turn_id: selected_turn.id.clone(), + completed_at_ms: 0, + item, + }, + )); + } + store.push_notification(turn_completed_notification( + source_thread_id, + &selected_turn.id, + TurnStatus::Interrupted, + )); + } while app_event_rx.try_recv().is_ok() {} let source_before = std::fs::read_to_string(&source_path)?; let mut tui = crate::tui::test_support::make_test_tui()?;