mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
## 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
120 lines
4.7 KiB
Rust
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())
|
|
})
|
|
}
|