diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index 42af749bb7..a01ffb498f 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -103,6 +103,10 @@ impl BottomPane { } } + pub fn status_widget(&self) -> Option<&StatusIndicatorWidget> { + self.status.as_ref() + } + fn active_view(&self) -> Option<&dyn BottomPaneView> { self.view_stack.last().map(std::convert::AsRef::as_ref) } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 0bf77f9994..724feb29f2 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -254,6 +254,8 @@ pub(crate) struct ChatWidget { // List of ghost commits corresponding to each turn. ghost_snapshots: Vec, ghost_snapshots_disabled: bool, + // Whether to add a final message separator after the last message + needs_final_message_separator: bool, } struct UserMessage { @@ -649,6 +651,14 @@ impl ChatWidget { self.flush_active_cell(); if self.stream_controller.is_none() { + if self.needs_final_message_separator { + let elapsed_seconds = self + .bottom_pane + .status_widget() + .map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds); + self.add_to_history(history_cell::FinalMessageSeparator::new(elapsed_seconds)); + self.needs_final_message_separator = false; + } self.stream_controller = Some(StreamController::new(self.config.clone())); } if let Some(controller) = self.stream_controller.as_mut() @@ -902,6 +912,7 @@ impl ChatWidget { is_review_mode: false, ghost_snapshots: Vec::new(), ghost_snapshots_disabled: true, + needs_final_message_separator: false, } } @@ -963,6 +974,7 @@ impl ChatWidget { is_review_mode: false, ghost_snapshots: Vec::new(), ghost_snapshots_disabled: true, + needs_final_message_separator: false, } } @@ -1189,6 +1201,7 @@ impl ChatWidget { fn flush_active_cell(&mut self) { if let Some(active) = self.active_cell.take() { + self.needs_final_message_separator = true; self.app_event_tx.send(AppEvent::InsertHistoryCell(active)); } } @@ -1201,6 +1214,7 @@ impl ChatWidget { if !cell.display_lines(u16::MAX).is_empty() { // Only break exec grouping if the cell renders visible lines. self.flush_active_cell(); + self.needs_final_message_separator = true; } self.app_event_tx.send(AppEvent::InsertHistoryCell(cell)); } @@ -1242,6 +1256,7 @@ impl ChatWidget { if !text.is_empty() { self.add_to_history(history_cell::new_user_prompt(text)); } + self.needs_final_message_separator = false; } fn capture_ghost_snapshot(&mut self) { diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap index 97709b613c..e3121774f4 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__binary_size_ideal_response.snap @@ -1,5 +1,6 @@ --- source: tui/src/chatwidget/tests.rs +assertion_line: 1152 expression: "lines[start_idx..].join(\"\\n\")" --- • I need to check the codex-rs repository to explain why the project's binaries @@ -9,6 +10,8 @@ expression: "lines[start_idx..].join(\"\\n\")" is set up. I should look into the Cargo.toml file to confirm features and profiles without needing to edit any code. Let's get started on this! +─ Worked for 0s ──────────────────────────────────────────────────────────────── + • I’m going to scan the workspace and Cargo manifests to see build profiles and dependencies that impact binary size. Then I’ll summarize the main causes. @@ -110,6 +113,8 @@ expression: "lines[start_idx..].join(\"\\n\")" "Main Causes" and "Build-Mode Notes." I can also include brief suggestions for reducing size, but I want to stay focused on answering the user's question. +─ Worked for 0s ──────────────────────────────────────────────────────────────── + • Here’s what’s driving size in this workspace’s binaries. Main Causes diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 0c9ebc73aa..f3799ad336 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -340,6 +340,7 @@ fn make_chatwidget_manual() -> ( is_review_mode: false, ghost_snapshots: Vec::new(), ghost_snapshots_disabled: false, + needs_final_message_separator: false, }; (widget, rx, op_rx) } diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 941a7fbdda..faf405d197 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -1080,6 +1080,40 @@ pub(crate) fn new_reasoning_summary_block( Box::new(new_reasoning_block(full_reasoning_buffer, config)) } +#[derive(Debug)] +pub struct FinalMessageSeparator { + elapsed_seconds: Option, +} +impl FinalMessageSeparator { + pub(crate) fn new(elapsed_seconds: Option) -> Self { + Self { elapsed_seconds } + } +} +impl HistoryCell for FinalMessageSeparator { + fn display_lines(&self, width: u16) -> Vec> { + let elapsed_seconds = self + .elapsed_seconds + .map(super::status_indicator_widget::fmt_elapsed_compact); + if let Some(elapsed_seconds) = elapsed_seconds { + let worked_for = format!("─ Worked for {elapsed_seconds} ─"); + let worked_for_width = worked_for.width(); + vec![ + Line::from_iter([ + worked_for, + "─".repeat((width as usize).saturating_sub(worked_for_width)), + ]) + .dim(), + ] + } else { + vec![Line::from_iter(["─".repeat(width as usize).dim()])] + } + } + + fn transcript_lines(&self) -> Vec> { + vec![] + } +} + fn format_mcp_invocation<'a>(invocation: McpInvocation) -> Line<'a> { let args_str = invocation .arguments diff --git a/codex-rs/tui/src/status_indicator_widget.rs b/codex-rs/tui/src/status_indicator_widget.rs index 96f2c49fee..b6fa2fd530 100644 --- a/codex-rs/tui/src/status_indicator_widget.rs +++ b/codex-rs/tui/src/status_indicator_widget.rs @@ -34,7 +34,7 @@ pub(crate) struct StatusIndicatorWidget { // Format elapsed seconds into a compact human-friendly form used by the status line. // Examples: 0s, 59s, 1m 00s, 59m 59s, 1h 00m 00s, 2h 03m 09s -fn fmt_elapsed_compact(elapsed_secs: u64) -> String { +pub fn fmt_elapsed_compact(elapsed_secs: u64) -> String { if elapsed_secs < 60 { return format!("{elapsed_secs}s"); } @@ -142,7 +142,7 @@ impl StatusIndicatorWidget { elapsed.as_secs() } - fn elapsed_seconds(&self) -> u64 { + pub fn elapsed_seconds(&self) -> u64 { self.elapsed_seconds_at(Instant::now()) } }