From 266c6920d9b82fe4d68959529565256b12a9be99 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sat, 8 Aug 2026 20:51:57 +0000 Subject: [PATCH] Include buffered turns when editing prompts (#37622) ## Why Prompt editing looks up the selected user message in the thread's turns, but new live turns may still exist only in the replay buffer. ## What changed Reconstruct buffered turns from turn and item notifications before locating the prompt to edit. Preserve completion metadata and avoid duplicating turns or items already present in the snapshot. ## Testing Extend the prompt-edit fork test to cover a selected turn supplied through the replay buffer and verify that the source thread remains unchanged. GitOrigin-RevId: e357098085a4def871f3df65b6721a31e07b737f --- codex-rs/tui/src/app/event_dispatch.rs | 52 +++++++++++++++++++++++++- codex-rs/tui/src/app/tests.rs | 30 +++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) 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()?;