From 8f872c042a01bccc8d8f5cb0a5c8f748d09e478d Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Thu, 14 May 2026 23:00:23 -0700 Subject: [PATCH] tui/exec: show effective workspace roots in summaries --- .../src/event_processor_with_human_output.rs | 5 +- ...event_processor_with_human_output_tests.rs | 71 +++++++++++++++++++ .../tui/src/chatwidget/status_surfaces.rs | 8 +-- codex-rs/tui/src/status/card.rs | 13 ++-- codex-rs/tui/src/status/tests.rs | 34 +++++++++ 5 files changed, 120 insertions(+), 11 deletions(-) diff --git a/codex-rs/exec/src/event_processor_with_human_output.rs b/codex-rs/exec/src/event_processor_with_human_output.rs index 755d754f08f..c3190b87406 100644 --- a/codex-rs/exec/src/event_processor_with_human_output.rs +++ b/codex-rs/exec/src/event_processor_with_human_output.rs @@ -420,6 +420,7 @@ fn config_summary_entries( config: &Config, session_configured_event: &SessionConfiguredEvent, ) -> Vec<(&'static str, String)> { + let permission_profile = config.permissions.effective_permission_profile(); let mut entries = vec![ ("workdir", config.cwd.display().to_string()), ("model", session_configured_event.model.clone()), @@ -434,9 +435,9 @@ fn config_summary_entries( ( "sandbox", summarize_permission_profile( - &config.permissions.effective_permission_profile(), + &permission_profile, &config.cwd, - config.permissions.user_visible_workspace_roots(), + config.effective_workspace_roots().as_slice(), ), ), ]; diff --git a/codex-rs/exec/src/event_processor_with_human_output_tests.rs b/codex-rs/exec/src/event_processor_with_human_output_tests.rs index 17cfda4550e..1e900f2b61b 100644 --- a/codex-rs/exec/src/event_processor_with_human_output_tests.rs +++ b/codex-rs/exec/src/event_processor_with_human_output_tests.rs @@ -2,12 +2,17 @@ use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::Turn; use codex_app_server_protocol::TurnStatus; +use codex_core::config::ConfigBuilder; +use codex_protocol::SessionId; +use codex_protocol::ThreadId; use codex_protocol::models::PermissionProfile; use codex_protocol::permissions::FileSystemAccessMode; use codex_protocol::permissions::FileSystemPath; use codex_protocol::permissions::FileSystemSandboxEntry; use codex_protocol::permissions::FileSystemSandboxPolicy; use codex_protocol::permissions::NetworkSandboxPolicy; +use codex_protocol::protocol::AskForApproval; +use codex_protocol::protocol::SessionConfiguredEvent; use codex_utils_absolute_path::test_support::PathBufExt; use codex_utils_absolute_path::test_support::test_path_buf; use codex_utils_sandbox_summary::summarize_permission_profile; @@ -15,6 +20,7 @@ use owo_colors::Style; use pretty_assertions::assert_eq; use super::EventProcessorWithHumanOutput; +use super::config_summary_entries; use super::final_message_from_turn_items; use super::reasoning_text; use super::should_print_final_message_to_stdout; @@ -168,6 +174,71 @@ fn summarizes_managed_read_only_permission_profile() { ); } +#[tokio::test] +async fn config_summary_entries_include_runtime_workspace_roots() { + let codex_home = tempfile::tempdir().expect("create codex home"); + let cwd = tempfile::tempdir().expect("create cwd"); + let extra_root = tempfile::tempdir().expect("create extra root"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(cwd.path().to_path_buf())) + .build() + .await + .expect("build default config"); + let cwd = cwd.path().to_path_buf().abs(); + let extra_root = extra_root.path().to_path_buf().abs(); + let expected_extra_root_name = extra_root + .file_name() + .expect("extra root should have file name") + .to_string_lossy() + .to_string(); + config.cwd = cwd.clone(); + config.workspace_roots = vec![cwd.clone(), extra_root]; + config + .permissions + .set_workspace_roots(config.workspace_roots.clone()); + config + .permissions + .set_permission_profile(PermissionProfile::workspace_write_with( + &[], + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ true, + /*exclude_slash_tmp*/ true, + )) + .expect("set permission profile"); + + let session_configured_event = SessionConfiguredEvent { + session_id: SessionId::new(), + thread_id: ThreadId::new(), + forked_from_id: None, + thread_source: None, + thread_name: None, + model: "gpt-5.4".to_string(), + model_provider_id: config.model_provider_id.clone(), + service_tier: None, + approval_policy: AskForApproval::Never, + approvals_reviewer: config.approvals_reviewer, + permission_profile: config.permissions.effective_permission_profile(), + active_permission_profile: None, + cwd, + reasoning_effort: None, + initial_messages: None, + network_proxy: None, + rollout_path: None, + }; + + let summary_entries = config_summary_entries(&config, &session_configured_event); + let sandbox_summary = summary_entries + .iter() + .find_map(|(key, value)| (*key == "sandbox").then_some(value)) + .expect("sandbox summary entry"); + assert!( + sandbox_summary.starts_with("workspace-write [workdir, ") + && sandbox_summary.contains(&expected_extra_root_name), + "expected runtime workspace root in sandbox summary: {summary_entries:?}" + ); +} + #[test] fn final_message_from_turn_items_uses_latest_agent_message() { let message = final_message_from_turn_items(&[ diff --git a/codex-rs/tui/src/chatwidget/status_surfaces.rs b/codex-rs/tui/src/chatwidget/status_surfaces.rs index 8dd5f327d3d..e34fe4688a9 100644 --- a/codex-rs/tui/src/chatwidget/status_surfaces.rs +++ b/codex-rs/tui/src/chatwidget/status_surfaces.rs @@ -902,11 +902,9 @@ fn permissions_display(config: &Config) -> String { } let permission_profile = config.permissions.effective_permission_profile(); - let summary = summarize_permission_profile( - &permission_profile, - &config.cwd, - config.permissions.workspace_roots(), - ); + let workspace_roots = config.effective_workspace_roots(); + let summary = + summarize_permission_profile(&permission_profile, &config.cwd, workspace_roots.as_slice()); if let Some(details) = summary.strip_prefix("read-only") && !details.contains("(network access enabled)") { diff --git a/codex-rs/tui/src/status/card.rs b/codex-rs/tui/src/status/card.rs index 9a7c23606f4..3d30f800b6a 100644 --- a/codex-rs/tui/src/status/card.rs +++ b/codex-rs/tui/src/status/card.rs @@ -256,7 +256,7 @@ impl StatusHistoryCell { ) -> (Self, StatusHistoryHandle) { let approval_policy = AskForApproval::from(config.permissions.approval_policy.value()); let permission_profile = config.permissions.effective_permission_profile(); - let workspace_roots = config.permissions.user_visible_workspace_roots(); + let workspace_roots = config.effective_workspace_roots(); let mut config_entries = vec![ ("workdir", config.cwd.display().to_string()), ("model", model_name.to_string()), @@ -267,7 +267,11 @@ impl StatusHistoryCell { ), ( "sandbox", - summarize_permission_profile(&permission_profile, &config.cwd, workspace_roots), + summarize_permission_profile( + &permission_profile, + &config.cwd, + workspace_roots.as_slice(), + ), ), ]; if config.model_provider.wire_api == WireApi::Responses { @@ -291,8 +295,9 @@ impl StatusHistoryCell { .map(|(_, v)| v.clone()) .unwrap_or_else(|| "".to_string()); let active_permission_profile = config.permissions.active_permission_profile(); - let sandbox = status_permission_summary(&permission_profile, &config.cwd, workspace_roots); - let workspace_root_suffix = workspace_root_suffix(workspace_roots, &config.cwd); + let sandbox = + status_permission_summary(&permission_profile, &config.cwd, workspace_roots.as_slice()); + let workspace_root_suffix = workspace_root_suffix(workspace_roots.as_slice(), &config.cwd); let approval = status_approval_label(approval_policy, config.approvals_reviewer, &approval); let permissions = status_permissions_label( active_permission_profile.as_ref(), diff --git a/codex-rs/tui/src/status/tests.rs b/codex-rs/tui/src/status/tests.rs index 65a390a0070..93807181966 100644 --- a/codex-rs/tui/src/status/tests.rs +++ b/codex-rs/tui/src/status/tests.rs @@ -463,6 +463,40 @@ async fn status_permissions_workspace_roots_show_additional_directories() { ); } +#[tokio::test] +async fn status_permissions_workspace_roots_include_profile_defined_directories() { + let temp_home = TempDir::new().expect("temp home"); + let mut config = test_config(&temp_home).await; + set_workspace_cwd(&mut config, test_path_buf("/workspace/tests").abs()); + config + .permissions + .approval_policy + .set(AskForApproval::OnRequest.to_core()) + .expect("set approval policy"); + let profile_root = test_path_buf("/workspace/shared").abs(); + config + .permissions + .set_permission_profile_from_session_snapshot_with_profile_workspace_roots( + PermissionProfile::workspace_write_with( + std::slice::from_ref(&profile_root), + NetworkSandboxPolicy::Restricted, + /*exclude_tmpdir_env_var*/ false, + /*exclude_slash_tmp*/ false, + ), + Some(ActivePermissionProfile::new(":workspace")), + vec![profile_root.clone()], + ) + .expect("set permission profile"); + + assert_eq!( + permissions_text_for(&config), + Some(format!( + "Workspace [{}] (on-request)", + profile_root.display() + )) + ); +} + #[tokio::test] async fn status_permissions_broadened_workspace_profile_shows_builtin_label() { let temp_home = TempDir::new().expect("temp home");