From 0af3f1f66e1777e8d9db3fa36de9ae57f7d4bfa9 Mon Sep 17 00:00:00 2001 From: Friel Date: Thu, 5 Mar 2026 08:22:43 -0800 Subject: [PATCH] fix(tui): keep subagent panel out of transcript history --- codex-rs/tui/src/chatwidget.rs | 8 ++++++ codex-rs/tui/src/chatwidget/tests.rs | 40 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index b6c2402043..581990073e 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -5495,6 +5495,14 @@ impl ChatWidget { fn flush_active_cell(&mut self) { if let Some(active) = self.active_cell.take() { + // Subagent status is a transient panel, not transcript history. If we + // flush it into history every time another cell is inserted, the + // transcript gets spammed with repeated identical "Subagents ..." blocks. + // Keep the panel mounted so later transcript cells do not make it disappear. + if active.as_any().is::() { + self.active_cell = Some(active); + return; + } self.needs_final_message_separator = true; self.app_event_tx.send(AppEvent::InsertHistoryCell(active)); } diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 863b43386f..e4670ab2b5 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -14,6 +14,9 @@ use crate::bottom_pane::FeedbackAudience; use crate::bottom_pane::LocalImageAttachment; use crate::bottom_pane::MentionBinding; use crate::chatwidget::realtime::RealtimeConversationPhase; +use crate::history_cell::SubagentPanelAgent; +use crate::history_cell::SubagentPanelState; +use crate::history_cell::SubagentStatusCell; use crate::history_cell::UserHistoryCell; use crate::model_catalog::ModelCatalog; use crate::test_backend::VT100Backend; @@ -197,6 +200,7 @@ use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::path::PathBuf; +use std::sync::Mutex as StdMutex; use tempfile::NamedTempFile; use tempfile::tempdir; use tokio::sync::mpsc::error::TryRecvError; @@ -2431,6 +2435,42 @@ fn status_line_text(chat: &ChatWidget) -> Option { chat.status_line_text() } +async fn subagent_panel_is_not_flushed_into_transcript_history() { + let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await; + + let state = Arc::new(StdMutex::new(SubagentPanelState { + started_at: Instant::now(), + total_agents: 1, + running_count: 0, + running_agents: vec![SubagentPanelAgent { + ordinal: 1, + name: "user-request-derisk-implement".to_string(), + status: AgentStatus::PendingInit, + is_watchdog: true, + preview: "watchdog idle".to_string(), + latest_update_at: Instant::now(), + }], + })); + chat.on_subagent_panel_updated(Arc::new(SubagentStatusCell::new(state, true))); + + chat.add_to_history(history_cell::new_error_event("follow-up cell".to_string())); + + let inserted = drain_insert_history(&mut rx); + assert_eq!( + inserted.len(), + 1, + "subagent panel should remain transient and not be inserted into transcript history" + ); + let rendered = lines_to_single_string(&inserted[0]); + assert!(rendered.contains("follow-up cell")); + assert!(!rendered.contains("Subagents")); + assert!( + chat.active_cell + .as_ref() + .is_some_and(|cell| cell.as_any().is::()), + "subagent panel should stay mounted after other history cells are inserted" + ); +} fn make_token_info(total_tokens: i64, context_window: i64) -> TokenUsageInfo { fn usage(total_tokens: i64) -> TokenUsage { TokenUsage {