Files
codex/codex-rs/tui/src/projectless.rs
T
Eric Traut 3985001067 Respect server approval policy defaults during TUI startup (#56516)
## Why

Fresh TUI sessions sent the locally resolved approval policy in `thread/start`, overriding the app server's configured default. Local constraints could also prevent the TUI from adopting the policy returned by the server.

## What changed

- Send explicit approval policy overrides at startup and otherwise let the server resolve the policy. Preserve the resolved-config behavior for selected profiles.
- Forward granular `approval_policy` overrides with their replacement and dotted-key merge semantics.
- Forward eligible projectless approval defaults only when allowed by the server's approval policy requirements.
- Accept the server's startup policy before submitting initial or queued input, including when local constraints would reject it.

## Testing

Extend startup tests to cover explicit override precedence, granular policy merging, projectless defaults, and use of the server's approval policy on the first turn.

GitOrigin-RevId: 995ce5f21b04b058d60c2ea80a139c0cf48c0e98
2026-10-23 16:18:43 +00:00

120 lines
4.7 KiB
Rust

//! Select desktop-like execution defaults for positively discovered local projectless folders.
//! Saved trust, explicit choices, managed policy, and remote execution retain their own defaults.
use crate::app_server_session::AppServerSession;
use crate::legacy_core::config::Config;
use crate::legacy_core::config::ConfigOverrides;
use codex_exec_server::EnvironmentManager;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::PermissionProfileSnapshot;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::GranularApprovalConfig;
#[derive(Default)]
pub(crate) struct AppliedDefaults {
pub(crate) prompt_windows_sandbox: bool,
pub(crate) approval_policy: Option<AskForApproval>,
}
/// Returns the selected product approval policy and whether Windows sandbox setup is required.
pub(crate) fn apply_defaults(
config: &mut Config,
overrides: &ConfigOverrides,
app_server: &AppServerSession,
environments: &EnvironmentManager,
response: &crate::config_update::EffectiveConfig,
) -> AppliedDefaults {
let defaults = &response.config;
let effective = config.config_layer_stack.effective_config();
if response.layers.as_ref().is_none_or(|layers| layers.iter().any(|layer| {
layer["name"]["type"] == "project"
}))
|| app_server.uses_remote_workspace()
|| defaults.sandbox_mode.is_some()
|| defaults.sandbox_workspace_write.is_some()
|| ["default_permissions", "permissions", "network"].iter()
.any(|key| defaults.additional.get(*key).is_some_and(|value| !value.is_null()))
// Reuse local discovery only when server markers and trust decisions agree.
|| ["project_root_markers", "projects"].iter().any(|key|
serde_json::to_value(effective.get(*key)).ok().as_ref()
!= Some(defaults.additional.get(*key).unwrap_or(&serde_json::Value::Null)))
|| !config.config_layer_stack.is_projectless()
|| config.active_project.trust_level.is_some()
|| config.workspace_roots.len() != 1
|| config.workspace_roots.first() != Some(&config.cwd)
|| !has_only_local_environments(environments)
|| overrides.sandbox_mode.is_some()
|| overrides.permission_profile.is_some()
|| overrides.default_permissions.is_some()
|| [
"sandbox_mode",
"default_permissions",
"permissions",
"sandbox_workspace_write",
"network",
]
.iter()
.any(|key| effective.get(*key).is_some())
|| config
.config_layer_stack
.requirements_toml()
.default_permissions
.is_some()
|| !config.is_permission_profile_allowed(
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
&PermissionProfile::workspace_write(),
)
{
return AppliedDefaults::default();
}
let granular = AskForApproval::Granular(GranularApprovalConfig {
sandbox_approval: false,
rules: false,
skill_approval: false,
request_permissions: true,
mcp_elicitations: true,
});
let mut applied = AppliedDefaults::default();
if overrides.approval_policy.is_none() && effective.get("approval_policy").is_none() {
applied.approval_policy = defaults.approval_policy.is_none().then_some(granular);
let granular = defaults
.approval_policy
.as_ref()
.map_or(granular, |policy| policy.to_core());
let _ = config.permissions.approval_policy.set(granular);
}
#[cfg(target_os = "windows")]
if config.effective_local_windows_sandbox_type() == codex_sandboxing::SandboxType::None {
applied.prompt_windows_sandbox = true;
return applied;
}
if config
.permissions
.set_permission_profile(PermissionProfile::workspace_write())
.is_err()
{
return applied;
}
let snapshot = PermissionProfileSnapshot::active(
config.permissions.permission_profile().clone(),
ActivePermissionProfile::new(BUILT_IN_PERMISSION_PROFILE_WORKSPACE.to_string()),
);
let _ = config
.permissions
.set_permission_profile_from_session_snapshot(snapshot);
applied
}
/// Local filesystem discovery is authoritative only for positively local execution.
pub(crate) fn has_only_local_environments(environments: &EnvironmentManager) -> bool {
let ids = environments.default_environment_ids();
!ids.is_empty()
&& ids.iter().all(|id| {
environments
.get_environment(id)
.is_some_and(|environment| !environment.is_remote())
})
}