diff --git a/codex-rs/tui_app_server/src/chatwidget.rs b/codex-rs/tui_app_server/src/chatwidget.rs index 7a636eded..f3c272d62 100644 --- a/codex-rs/tui_app_server/src/chatwidget.rs +++ b/codex-rs/tui_app_server/src/chatwidget.rs @@ -5675,13 +5675,15 @@ impl ChatWidget { ThreadItem::Reasoning { summary, content, .. } => { - for delta in summary { - self.on_agent_reasoning_delta(delta); - } - if self.config.show_raw_agent_reasoning { - for delta in content { + if from_replay { + for delta in summary { self.on_agent_reasoning_delta(delta); } + if self.config.show_raw_agent_reasoning { + for delta in content { + self.on_agent_reasoning_delta(delta); + } + } } self.on_agent_reasoning_final(); } diff --git a/codex-rs/tui_app_server/src/chatwidget/tests.rs b/codex-rs/tui_app_server/src/chatwidget/tests.rs index 6eefc52a8..98509303e 100644 --- a/codex-rs/tui_app_server/src/chatwidget/tests.rs +++ b/codex-rs/tui_app_server/src/chatwidget/tests.rs @@ -47,6 +47,7 @@ use codex_app_server_protocol::PluginMarketplaceEntry; use codex_app_server_protocol::PluginReadResponse; use codex_app_server_protocol::PluginSource; use codex_app_server_protocol::PluginSummary; +use codex_app_server_protocol::ReasoningSummaryTextDeltaNotification; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::SkillSummary; use codex_app_server_protocol::ThreadClosedNotification; @@ -5135,6 +5136,56 @@ async fn replayed_reasoning_item_shows_raw_reasoning_when_enabled() { assert!(rendered.contains("Raw reasoning")); } +#[tokio::test] +async fn live_reasoning_summary_is_not_rendered_twice_when_item_completes() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + chat.show_welcome_banner = false; + + chat.handle_server_notification( + ServerNotification::TurnStarted(TurnStartedNotification { + thread_id: "thread-1".to_string(), + turn: AppServerTurn { + id: "turn-1".to_string(), + items: Vec::new(), + status: AppServerTurnStatus::InProgress, + error: None, + }, + }), + None, + ); + let _ = drain_insert_history(&mut rx); + + chat.handle_server_notification( + ServerNotification::ReasoningSummaryTextDelta(ReasoningSummaryTextDeltaNotification { + thread_id: "thread-1".to_string(), + turn_id: "turn-1".to_string(), + item_id: "reasoning-1".to_string(), + delta: "Summary only".to_string(), + summary_index: 0, + }), + None, + ); + + chat.handle_server_notification( + ServerNotification::ItemCompleted(ItemCompletedNotification { + thread_id: "thread-1".to_string(), + turn_id: "turn-1".to_string(), + item: AppServerThreadItem::Reasoning { + id: "reasoning-1".to_string(), + summary: vec!["Summary only".to_string()], + content: Vec::new(), + }, + }), + None, + ); + + let rendered = match rx.try_recv() { + Ok(AppEvent::InsertHistoryCell(cell)) => lines_to_single_string(&cell.transcript_lines(80)), + other => panic!("expected InsertHistoryCell, got {other:?}"), + }; + assert_eq!(rendered.matches("Summary only").count(), 1); +} + #[test] fn rendered_user_message_event_from_inputs_matches_flattened_user_message_shape() { let local_image = PathBuf::from("/tmp/local.png");