From f5b69436b5b71b6c22198d51d5363fef8f6e73e6 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Wed, 8 Jul 2026 14:55:10 -0700 Subject: [PATCH] Add compaction counts to TUI status line and title --- codex-rs/cli/src/doctor/title.rs | 1 + codex-rs/tui/src/app/tests.rs | 20 +++++++-- .../tui/src/bottom_pane/status_line_setup.rs | 6 +++ .../tui/src/bottom_pane/status_line_style.rs | 3 +- .../src/bottom_pane/status_surface_preview.rs | 3 ++ codex-rs/tui/src/bottom_pane/title_setup.rs | 4 ++ codex-rs/tui/src/chatwidget.rs | 1 - ...ts__status_line_setup_popup_live_only.snap | 4 +- ...ts__status_surface_previews_live_only.snap | 4 +- ...erminal_title_setup_popup_compactions.snap | 20 +++++++++ .../tui/src/chatwidget/status_surfaces.rs | 5 +++ .../tests/status_surface_previews.rs | 41 +++++++++++++++++++ 12 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__terminal_title_setup_popup_compactions.snap diff --git a/codex-rs/cli/src/doctor/title.rs b/codex-rs/cli/src/doctor/title.rs index 09dc80c0a2..c060e42725 100644 --- a/codex-rs/cli/src/doctor/title.rs +++ b/codex-rs/cli/src/doctor/title.rs @@ -139,6 +139,7 @@ fn terminal_title_item_id(item: &str) -> Option<&'static str> { "used-tokens" => Some("used-tokens"), "total-input-tokens" => Some("total-input-tokens"), "total-output-tokens" => Some("total-output-tokens"), + "compactions" => Some("compactions"), "thread-id" | "session-id" => Some("thread-id"), "fast-mode" => Some("fast-mode"), "model" | "model-name" => Some("model"), diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index ce25a9fb5b..7f13868753 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -1125,22 +1125,34 @@ async fn replayed_interrupted_turn_restores_queued_input_to_composer() { async fn token_usage_update_refreshes_status_line_with_runtime_context_window() { let mut app = make_test_app().await; app.chat_widget.setup_status_line( - vec![crate::bottom_pane::StatusLineItem::ContextWindowSize], + vec![ + crate::bottom_pane::StatusLineItem::ContextWindowSize, + crate::bottom_pane::StatusLineItem::Compactions, + ], /*use_theme_colors*/ true, ); + app.chat_widget + .setup_terminal_title(vec![crate::bottom_pane::TerminalTitleItem::Compactions]); - assert_eq!(app.chat_widget.status_line_text(), None); + assert_eq!( + app.chat_widget.status_line_text(), + Some("0 compactions".into()) + ); app.handle_thread_event_now(ThreadBufferedEvent::Notification(token_usage_notification( ThreadId::new(), "turn-1", Some(950_000), - /*window_number*/ 0, + /*window_number*/ 3, ))); assert_eq!( app.chat_widget.status_line_text(), - Some("950K window".into()) + Some("950K window · 3 compactions".into()) + ); + assert_eq!( + app.chat_widget.last_terminal_title, + Some("3 compactions".to_string()) ); } diff --git a/codex-rs/tui/src/bottom_pane/status_line_setup.rs b/codex-rs/tui/src/bottom_pane/status_line_setup.rs index 63ce4237db..70bef36a0a 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_setup.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_setup.rs @@ -17,6 +17,7 @@ //! - Context usage (remaining %, used %, window size) //! - Usage limits (primary, secondary) //! - Session info (thread title, thread ID, tokens used) +//! - Context compaction count //! - Application version use ratatui::buffer::Buffer; @@ -124,6 +125,9 @@ pub(crate) enum StatusLineItem { /// Total output tokens generated. TotalOutputTokens, + /// Number of context compactions in the current thread. + Compactions, + /// Full thread UUID. #[strum(to_string = "thread-id", serialize = "session-id")] SessionId, @@ -182,6 +186,7 @@ impl StatusLineItem { StatusLineItem::UsedTokens => "Total tokens used in session (omitted when zero)", StatusLineItem::TotalInputTokens => "Total input tokens used in session", StatusLineItem::TotalOutputTokens => "Total output tokens used in session", + StatusLineItem::Compactions => "Number of context compactions in the current thread", StatusLineItem::SessionId => "Current thread identifier (omitted until thread starts)", StatusLineItem::FastMode => "Whether Fast mode is currently active", StatusLineItem::RawOutput => "Whether raw scrollback mode is active", @@ -219,6 +224,7 @@ impl StatusLineItem { StatusLineItem::UsedTokens => StatusSurfacePreviewItem::UsedTokens, StatusLineItem::TotalInputTokens => StatusSurfacePreviewItem::TotalInputTokens, StatusLineItem::TotalOutputTokens => StatusSurfacePreviewItem::TotalOutputTokens, + StatusLineItem::Compactions => StatusSurfacePreviewItem::Compactions, StatusLineItem::SessionId => StatusSurfacePreviewItem::SessionId, StatusLineItem::FastMode => StatusSurfacePreviewItem::FastMode, StatusLineItem::RawOutput => StatusSurfacePreviewItem::RawOutput, diff --git a/codex-rs/tui/src/bottom_pane/status_line_style.rs b/codex-rs/tui/src/bottom_pane/status_line_style.rs index 4198943a4f..173cf4351b 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_style.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_style.rs @@ -43,7 +43,8 @@ impl StatusLineAccent { | StatusLineItem::ContextWindowSize | StatusLineItem::UsedTokens | StatusLineItem::TotalInputTokens - | StatusLineItem::TotalOutputTokens => Self::Usage, + | StatusLineItem::TotalOutputTokens + | StatusLineItem::Compactions => Self::Usage, StatusLineItem::FiveHourLimit | StatusLineItem::WeeklyLimit => Self::Limit, StatusLineItem::CodexVersion | StatusLineItem::SessionId => Self::Metadata, StatusLineItem::FastMode | StatusLineItem::RawOutput => Self::Mode, diff --git a/codex-rs/tui/src/bottom_pane/status_surface_preview.rs b/codex-rs/tui/src/bottom_pane/status_surface_preview.rs index b8b7a6fbd4..187953fc6f 100644 --- a/codex-rs/tui/src/bottom_pane/status_surface_preview.rs +++ b/codex-rs/tui/src/bottom_pane/status_surface_preview.rs @@ -27,6 +27,7 @@ pub(crate) enum StatusSurfacePreviewItem { UsedTokens, TotalInputTokens, TotalOutputTokens, + Compactions, SessionId, FastMode, RawOutput, @@ -60,6 +61,7 @@ impl StatusSurfacePreviewItem { StatusSurfacePreviewItem::UsedTokens => "0 used", StatusSurfacePreviewItem::TotalInputTokens => "0 in", StatusSurfacePreviewItem::TotalOutputTokens => "0 out", + StatusSurfacePreviewItem::Compactions => "0 compactions", StatusSurfacePreviewItem::SessionId => "550e8400-e29b-41d4", StatusSurfacePreviewItem::FastMode => "Fast on", StatusSurfacePreviewItem::RawOutput => "raw output", @@ -93,6 +95,7 @@ impl StatusSurfacePreviewItem { Self::UsedTokens, Self::TotalInputTokens, Self::TotalOutputTokens, + Self::Compactions, Self::SessionId, Self::FastMode, Self::RawOutput, diff --git a/codex-rs/tui/src/bottom_pane/title_setup.rs b/codex-rs/tui/src/bottom_pane/title_setup.rs index 1a3993c1cd..fbd8d10e5c 100644 --- a/codex-rs/tui/src/bottom_pane/title_setup.rs +++ b/codex-rs/tui/src/bottom_pane/title_setup.rs @@ -72,6 +72,8 @@ pub(crate) enum TerminalTitleItem { TotalInputTokens, /// Total output tokens generated. TotalOutputTokens, + /// Number of context compactions in the current thread. + Compactions, /// Full thread UUID. #[strum(to_string = "thread-id", serialize = "session-id")] SessionId, @@ -118,6 +120,7 @@ impl TerminalTitleItem { TerminalTitleItem::UsedTokens => "Total tokens used in session (omitted when zero)", TerminalTitleItem::TotalInputTokens => "Total input tokens used in session", TerminalTitleItem::TotalOutputTokens => "Total output tokens used in session", + TerminalTitleItem::Compactions => "Number of context compactions in the current thread", TerminalTitleItem::SessionId => { "Current thread identifier (omitted until thread starts)" } @@ -150,6 +153,7 @@ impl TerminalTitleItem { TerminalTitleItem::TotalOutputTokens => { Some(StatusSurfacePreviewItem::TotalOutputTokens) } + TerminalTitleItem::Compactions => Some(StatusSurfacePreviewItem::Compactions), TerminalTitleItem::SessionId => Some(StatusSurfacePreviewItem::SessionId), TerminalTitleItem::FastMode => Some(StatusSurfacePreviewItem::FastMode), TerminalTitleItem::Model => Some(StatusSurfacePreviewItem::Model), diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index c1f758f33c..4383450aff 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -551,7 +551,6 @@ pub(crate) struct ChatWidget { runtime_model_provider_base_url: Option, pub(crate) remote_connection: Option, token_info: Option, - #[cfg_attr(not(test), allow(dead_code))] window_number: u64, rate_limit_snapshots_by_limit_id: BTreeMap, refreshing_status_outputs: Vec<(u64, StatusHistoryHandle)>, diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap index 709f7734df..5408237288 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap @@ -12,9 +12,9 @@ expression: status_line_popup_snapshot(&mut chat) [x] project-name Project name (omitted when unavailable) [x] git-branch Current Git branch (omitted when unavailable) [x] thread-title Current thread title, or thread identifier when unnamed + [x] compactions Number of context compactions in the current thread [ ] model Current model name [ ] model-with-reasoning Current model name with reasoning level - [ ] reasoning Current reasoning level - preview-live-root · feature/live-preview-branch · Live preview thread + preview-live-root · feature/live-preview-branch · Live preview thread · 3 compactions Press space to toggle; ←/→ to move; enter to confirm and close; esc to close diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_surface_previews_live_only.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_surface_previews_live_only.snap index 4cf1871af6..ab7b88b9e7 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_surface_previews_live_only.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_surface_previews_live_only.snap @@ -2,5 +2,5 @@ source: tui/src/chatwidget/tests/status_surface_previews.rs expression: snapshot --- -status line: preview-live-root · feature/live-preview-branch · Live preview thread -terminal title: preview-live-root | Live preview thread | feature/live-preview-branch | Tasks 2/5 +status line: preview-live-root · feature/live-preview-branch · Live preview thread · 3 compactions +terminal title: preview-live-root | Live preview thread | feature/live-preview-branch | Tasks 2/5 | 3 compactions diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__terminal_title_setup_popup_compactions.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__terminal_title_setup_popup_compactions.snap new file mode 100644 index 0000000000..b6c002b166 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__terminal_title_setup_popup_compactions.snap @@ -0,0 +1,20 @@ +--- +source: tui/src/chatwidget/tests/status_surface_previews.rs +expression: terminal_title_popup_snapshot(&mut chat) +--- + Configure Terminal Title + Select which items to display in the terminal title. + + Type to search + > +› [x] app-name Codex app name + [x] model Current model name + [x] reasoning Current reasoning level + [x] fast-mode Whether Fast mode is currently active + [x] compactions Number of context compactions in the current thread + [ ] project-name Project name (falls back to current directory name) + [ ] current-dir Current working directory + [ ] activity Spinner while working, action-required message while blocked. + + codex | gpt-5.5 | default | Fast off | 3 compactions + Press space to toggle; ←/→ to move; enter to confirm and close; esc to close diff --git a/codex-rs/tui/src/chatwidget/status_surfaces.rs b/codex-rs/tui/src/chatwidget/status_surfaces.rs index 7120462330..4cf4eb3cb4 100644 --- a/codex-rs/tui/src/chatwidget/status_surfaces.rs +++ b/codex-rs/tui/src/chatwidget/status_surfaces.rs @@ -727,6 +727,7 @@ impl ChatWidget { "{} out", format_tokens_compact(self.status_line_total_usage().output_tokens) )), + StatusLineItem::Compactions => Some(format!("{} compactions", self.window_number)), StatusLineItem::SessionId => self.thread_id.map(|id| id.to_string()), StatusLineItem::FastMode => Some( if self.current_service_tier() == Some(ServiceTier::Fast.request_value()) { @@ -785,6 +786,7 @@ impl ChatWidget { StatusSurfacePreviewItem::UsedTokens => StatusLineItem::UsedTokens, StatusSurfacePreviewItem::TotalInputTokens => StatusLineItem::TotalInputTokens, StatusSurfacePreviewItem::TotalOutputTokens => StatusLineItem::TotalOutputTokens, + StatusSurfacePreviewItem::Compactions => StatusLineItem::Compactions, StatusSurfacePreviewItem::SessionId => StatusLineItem::SessionId, StatusSurfacePreviewItem::FastMode => StatusLineItem::FastMode, StatusSurfacePreviewItem::RawOutput => StatusLineItem::RawOutput, @@ -843,6 +845,9 @@ impl ChatWidget { TerminalTitleItem::TotalOutputTokens => self .status_line_value_for_item(StatusLineItem::TotalOutputTokens) .map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)), + TerminalTitleItem::Compactions => self + .status_line_value_for_item(StatusLineItem::Compactions) + .map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)), TerminalTitleItem::SessionId => self .status_line_value_for_item(StatusLineItem::SessionId) .map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)), diff --git a/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs b/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs index 63005cb957..980722dd76 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs @@ -89,6 +89,13 @@ async fn status_surface_preview_lines_live_only_snapshot() { chat.status_line_branch = Some("feature/live-preview-branch".to_string()); chat.thread_name = Some("Live preview thread".to_string()); chat.transcript.last_plan_progress = Some((2, 5)); + handle_token_count_with_window_number( + &mut chat, + Some(make_token_info( + /*total_tokens*/ 1, /*context_window*/ 100, + )), + /*window_number*/ 3, + ); let snapshot = combined_preview_snapshot( &mut chat, @@ -96,12 +103,14 @@ async fn status_surface_preview_lines_live_only_snapshot() { StatusLineItem::ProjectRoot, StatusLineItem::GitBranch, StatusLineItem::ThreadTitle, + StatusLineItem::Compactions, ], &[ TerminalTitleItem::Project, TerminalTitleItem::Thread, TerminalTitleItem::GitBranch, TerminalTitleItem::TaskProgress, + TerminalTitleItem::Compactions, ], ); @@ -114,10 +123,18 @@ async fn status_line_setup_popup_live_only_snapshot() { cache_project_root(&mut chat, "preview-live-root"); chat.status_line_branch = Some("feature/live-preview-branch".to_string()); chat.thread_name = Some("Live preview thread".to_string()); + handle_token_count_with_window_number( + &mut chat, + Some(make_token_info( + /*total_tokens*/ 1, /*context_window*/ 100, + )), + /*window_number*/ 3, + ); chat.config.tui_status_line = Some(vec![ "project-name".to_string(), "git-branch".to_string(), "thread-title".to_string(), + "compactions".to_string(), ]); assert_chatwidget_snapshot!( @@ -326,6 +343,30 @@ async fn terminal_title_setup_popup_live_only_snapshot() { ); } +#[tokio::test] +async fn terminal_title_setup_popup_compactions_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + handle_token_count_with_window_number( + &mut chat, + Some(make_token_info( + /*total_tokens*/ 1, /*context_window*/ 100, + )), + /*window_number*/ 3, + ); + chat.config.tui_terminal_title = Some(vec![ + "app-name".to_string(), + "model".to_string(), + "reasoning".to_string(), + "fast-mode".to_string(), + "compactions".to_string(), + ]); + + assert_chatwidget_snapshot!( + "terminal_title_setup_popup_compactions", + terminal_title_popup_snapshot(&mut chat) + ); +} + #[tokio::test] async fn terminal_title_setup_popup_hardcoded_only_snapshot() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;