From 7c797c6544ae97d3ebfac52d31a00d72f5dba5a3 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 13 Apr 2026 08:59:19 -0700 Subject: [PATCH] Suppress duplicate compaction and terminal wait events (#17601) Addresses #17514 Problem: PR #16966 made the TUI render the deprecated context-compaction notification, while v2 could also receive legacy unified-exec interaction items alongside terminal-interaction notifications, causing duplicate "Context compacted" and "Waited for background terminal" messages. Solution: Suppress deprecated context-compaction notifications and legacy unified-exec interaction command items from the app-server v2 projection, and render canonical context-compaction items through the existing TUI info-event path. --- .../app-server/src/bespoke_event_handling.rs | 27 +++++++++++++++++++ codex-rs/tui/src/chatwidget.rs | 3 ++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 08a51108a..9e4e603b6 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -1303,6 +1303,11 @@ pub(crate) async fn apply_bespoke_event_handling( .await; } EventMsg::ContextCompacted(..) => { + // Core still fans out this deprecated event for legacy clients; + // v2 clients receive the canonical ContextCompaction item instead. + if matches!(api_version, ApiVersion::V2) { + return; + } let notification = ContextCompactedNotification { thread_id: conversation_id.to_string(), turn_id: event_turn_id.clone(), @@ -1599,6 +1604,17 @@ pub(crate) async fn apply_bespoke_event_handling( .await; } EventMsg::ExecCommandBegin(exec_command_begin_event) => { + if matches!(api_version, ApiVersion::V2) + && matches!( + exec_command_begin_event.source, + codex_protocol::protocol::ExecCommandSource::UnifiedExecInteraction + ) + { + // TerminalInteraction is the v2 surface for unified exec + // stdin/poll events. Suppress the legacy CommandExecution + // item so clients do not render the same wait twice. + return; + } let item_id = exec_command_begin_event.call_id.clone(); let command_actions = exec_command_begin_event .parsed_cmd @@ -1702,6 +1718,17 @@ pub(crate) async fn apply_bespoke_event_handling( .command_execution_started .remove(&call_id); } + if matches!(api_version, ApiVersion::V2) + && matches!( + exec_command_end_event.source, + codex_protocol::protocol::ExecCommandSource::UnifiedExecInteraction + ) + { + // The paired begin event is suppressed above; keep the + // completion out of v2 as well so no orphan legacy item is + // emitted for unified exec interactions. + return; + } let item = build_command_execution_end_item(&exec_command_end_event); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 5f1196c94..189371ffd 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -2193,6 +2193,7 @@ impl ChatWidget { self.request_redraw(); } + #[cfg(test)] fn on_agent_message(&mut self, message: String) { self.finalize_completed_assistant_message(Some(&message)); } @@ -5924,7 +5925,7 @@ impl ChatWidget { self.exit_review_mode_after_item(); } ThreadItem::ContextCompaction { .. } => { - self.on_agent_message("Context compacted".to_owned()); + self.on_context_compacted(); } ThreadItem::HookPrompt { .. } => {} ThreadItem::CollabAgentToolCall {