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 db789a11f..28107eea9 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_setup.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_setup.rs @@ -12,6 +12,8 @@ //! - Model information (name, reasoning level) //! - Directory paths (current dir, project root) //! - Git information (branch name) +//! - Permissions profile +//! - Approval mode //! - Context usage (remaining %, used %, window size) //! - Usage limits (5-hour, weekly) //! - Session info (thread title, ID, tokens used) @@ -81,6 +83,13 @@ pub(crate) enum StatusLineItem { #[strum(to_string = "run-state", serialize = "status")] Status, + /// Active permission profile or sandbox summary. + Permissions, + + /// Active command approval mode. + #[strum(to_string = "approval-mode", serialize = "approval")] + ApprovalMode, + /// Percentage of context window remaining. ContextRemaining, @@ -143,6 +152,8 @@ impl StatusLineItem { "Committed branch changes against the default branch (omitted when unavailable)" } StatusLineItem::Status => "Compact session run-state text (Ready, Working, Thinking)", + StatusLineItem::Permissions => "Active permission profile or sandbox mode", + StatusLineItem::ApprovalMode => "Active command approval mode", StatusLineItem::ContextRemaining => { "Percentage of context window remaining (omitted when unknown)" } @@ -184,6 +195,8 @@ impl StatusLineItem { StatusLineItem::PullRequestNumber => StatusSurfacePreviewItem::PullRequestNumber, StatusLineItem::BranchChanges => StatusSurfacePreviewItem::BranchChanges, StatusLineItem::Status => StatusSurfacePreviewItem::Status, + StatusLineItem::Permissions => StatusSurfacePreviewItem::Permissions, + StatusLineItem::ApprovalMode => StatusSurfacePreviewItem::ApprovalMode, StatusLineItem::ContextRemaining => StatusSurfacePreviewItem::ContextRemaining, StatusLineItem::ContextUsed => StatusSurfacePreviewItem::ContextUsed, StatusLineItem::FiveHourLimit => StatusSurfacePreviewItem::FiveHourLimit, 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 07018bff0..ffb2a289e 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_style.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_style.rs @@ -45,6 +45,8 @@ impl StatusLineAccent { StatusLineItem::FiveHourLimit | StatusLineItem::WeeklyLimit => Self::Limit, StatusLineItem::CodexVersion | StatusLineItem::SessionId => Self::Metadata, StatusLineItem::FastMode | StatusLineItem::RawOutput => Self::Mode, + StatusLineItem::Permissions => Self::Mode, + StatusLineItem::ApprovalMode => Self::Mode, StatusLineItem::ThreadTitle => Self::Thread, StatusLineItem::TaskProgress => Self::Progress, } 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 1f23742a5..9f0f8ed5f 100644 --- a/codex-rs/tui/src/bottom_pane/status_surface_preview.rs +++ b/codex-rs/tui/src/bottom_pane/status_surface_preview.rs @@ -16,6 +16,8 @@ pub(crate) enum StatusSurfacePreviewItem { GitBranch, PullRequestNumber, BranchChanges, + Permissions, + ApprovalMode, ContextRemaining, ContextUsed, FiveHourLimit, @@ -45,6 +47,8 @@ impl StatusSurfacePreviewItem { StatusSurfacePreviewItem::GitBranch => "feat/awesome-feature", StatusSurfacePreviewItem::PullRequestNumber => "PR #123", StatusSurfacePreviewItem::BranchChanges => "+12 -3", + StatusSurfacePreviewItem::Permissions => "Workspace", + StatusSurfacePreviewItem::ApprovalMode => "on-request", StatusSurfacePreviewItem::ContextRemaining => "Context 0% left", StatusSurfacePreviewItem::ContextUsed => "Context 0% used", StatusSurfacePreviewItem::FiveHourLimit => "5h 0%", @@ -74,6 +78,8 @@ impl StatusSurfacePreviewItem { Self::GitBranch, Self::PullRequestNumber, Self::BranchChanges, + Self::Permissions, + Self::ApprovalMode, Self::ContextRemaining, Self::ContextUsed, Self::FiveHourLimit, diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 35d523aa5..b65f8b1c4 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -9141,6 +9141,8 @@ impl ChatWidget { .set(policy.to_core()) { tracing::warn!(%err, "failed to set approval_policy on chat config"); + } else { + self.refresh_status_surfaces(); } } @@ -9150,7 +9152,9 @@ impl ChatWidget { &mut self, profile: PermissionProfile, ) -> ConstraintResult<()> { - self.config.permissions.set_permission_profile(profile) + self.config.permissions.set_permission_profile(profile)?; + self.refresh_status_surfaces(); + Ok(()) } #[cfg_attr(not(target_os = "windows"), allow(dead_code))] @@ -9231,6 +9235,7 @@ impl ChatWidget { pub(crate) fn set_approvals_reviewer(&mut self, policy: ApprovalsReviewer) { self.config.approvals_reviewer = policy; + self.refresh_status_surfaces(); } pub(crate) fn set_full_access_warning_acknowledged(&mut self, acknowledged: bool) { diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_surface_previews_hardcoded_only.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_surface_previews_hardcoded_only.snap index abf3df3c2..250d56ff4 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_surface_previews_hardcoded_only.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_surface_previews_hardcoded_only.snap @@ -2,5 +2,5 @@ source: tui/src/chatwidget/tests/status_surface_previews.rs expression: snapshot --- -status line: my-project · feat/awesome-feature · thread title +status line: my-project · feat/awesome-feature · thread title · Read Only · on-request terminal title: thread title | feat/awesome-feature | Tasks 0/0 diff --git a/codex-rs/tui/src/chatwidget/status_surfaces.rs b/codex-rs/tui/src/chatwidget/status_surfaces.rs index fe010de48..a862bcabe 100644 --- a/codex-rs/tui/src/chatwidget/status_surfaces.rs +++ b/codex-rs/tui/src/chatwidget/status_surfaces.rs @@ -6,7 +6,12 @@ use super::*; use crate::bottom_pane::status_line_from_segments; use crate::branch_summary; +use crate::legacy_core::config::Config; use crate::status::format_tokens_compact; +use codex_app_server_protocol::AskForApproval; +use codex_protocol::config_types::ApprovalsReviewer; +use codex_protocol::models::PermissionProfile; +use codex_utils_sandbox_summary::summarize_permission_profile; /// Items shown in the terminal title when the user has not configured a /// custom selection. Intentionally minimal: activity indicator + project name. @@ -590,6 +595,8 @@ impl ChatWidget { } }), StatusLineItem::Status => Some(self.run_state_status_text()), + StatusLineItem::Permissions => Some(permissions_display(&self.config)), + StatusLineItem::ApprovalMode => Some(approval_mode_display(&self.config)), StatusLineItem::UsedTokens => { let usage = self.status_line_total_usage(); let total = usage.blended_total(); @@ -678,6 +685,8 @@ impl ChatWidget { StatusSurfacePreviewItem::GitBranch => StatusLineItem::GitBranch, StatusSurfacePreviewItem::PullRequestNumber => StatusLineItem::PullRequestNumber, StatusSurfacePreviewItem::BranchChanges => StatusLineItem::BranchChanges, + StatusSurfacePreviewItem::Permissions => StatusLineItem::Permissions, + StatusSurfacePreviewItem::ApprovalMode => StatusLineItem::ApprovalMode, StatusSurfacePreviewItem::ContextRemaining => StatusLineItem::ContextRemaining, StatusSurfacePreviewItem::ContextUsed => StatusLineItem::ContextUsed, StatusSurfacePreviewItem::FiveHourLimit => StatusLineItem::FiveHourLimit, @@ -890,6 +899,44 @@ impl ChatWidget { } } +fn permissions_display(config: &Config) -> String { + let active_permission_profile = config.permissions.active_permission_profile(); + if let Some(active_permission_profile) = active_permission_profile.as_ref() + && !active_permission_profile.id.starts_with(':') + { + return active_permission_profile.id.clone(); + } + + let permission_profile = config.permissions.permission_profile(); + let summary = summarize_permission_profile(&permission_profile, config.cwd.as_path()); + if let Some(details) = summary.strip_prefix("read-only") + && !details.contains("(network access enabled)") + { + return "Read Only".to_string(); + } + if let Some(details) = summary.strip_prefix("workspace-write") + && !details.contains("(network access enabled)") + { + return "Workspace".to_string(); + } + if permission_profile == PermissionProfile::Disabled { + return "Full Access".to_string(); + } + + "Custom permissions".to_string() +} + +fn approval_mode_display(config: &Config) -> String { + let approval_policy = AskForApproval::from(config.permissions.approval_policy.value()); + if approval_policy == AskForApproval::OnRequest + && config.approvals_reviewer == ApprovalsReviewer::AutoReview + { + "auto-review".to_string() + } else { + config.permissions.approval_policy.value().to_string() + } +} + fn parse_items_with_invalids(ids: impl IntoIterator) -> (Vec, Vec) where T: std::str::FromStr, 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 4b080794b..a4fdf62f0 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs @@ -112,6 +112,8 @@ async fn status_surface_preview_lines_hardcoded_only_snapshot() { StatusLineItem::ProjectRoot, StatusLineItem::GitBranch, StatusLineItem::ThreadTitle, + StatusLineItem::Permissions, + StatusLineItem::ApprovalMode, ], &[ TerminalTitleItem::Thread,