mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
core: refresh active permission profiles at runtime (#22931)
## Why Once a named permission profile is selected, runtime state has to keep that profile identity intact instead of collapsing back to anonymous effective permissions. The session refresh path also needs to rebuild profile-derived network proxy state so active profile switches take effect consistently. ## What changed - Preserve the active permission profile through session updates. - Rebuild profile-derived runtime/network configuration when the active profile changes. - Keep the runtime path aligned with the current session configuration APIs. - Tighten the affected tests, including the Windows delete-pending memory-file case that was intermittently tripping CI. ## Stack 1. **This PR**: runtime/session/network propagation for active permission profiles. 2. [#23708](https://github.com/openai/codex/pull/23708): TUI selection plumbing and guardrail flow. 3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware `/permissions` menu and custom profile display. <img width="1296" height="906" alt="image" src="https://github.com/user-attachments/assets/077fa3a7-80cb-4925-80b1-d2395018d90a" />
This commit is contained in:
committed by
GitHub
Unverified
parent
896ee672cc
commit
40ad7be2b5
@@ -495,6 +495,39 @@ fn profile_allows_configured_network_proxy(permission_profile: &PermissionProfil
|
||||
}
|
||||
}
|
||||
|
||||
fn build_network_proxy_spec(
|
||||
configured_network_proxy_config: NetworkProxyConfig,
|
||||
network_requirements: Option<Sourced<codex_config::NetworkConstraints>>,
|
||||
permission_profile: &PermissionProfile,
|
||||
) -> std::io::Result<Option<NetworkProxySpec>> {
|
||||
let (network_requirements, network_requirements_source) = match network_requirements {
|
||||
Some(Sourced { value, source }) => (Some(value), Some(source)),
|
||||
None => (None, None),
|
||||
};
|
||||
let has_network_requirements = network_requirements.is_some();
|
||||
let network = NetworkProxySpec::from_config_and_constraints(
|
||||
configured_network_proxy_config,
|
||||
network_requirements,
|
||||
permission_profile,
|
||||
)
|
||||
.map_err(|err| {
|
||||
if let Some(source) = network_requirements_source.as_ref() {
|
||||
std::io::Error::new(
|
||||
err.kind(),
|
||||
format!("failed to build managed network proxy from {source}: {err}"),
|
||||
)
|
||||
} else {
|
||||
err
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(if has_network_requirements {
|
||||
Some(network)
|
||||
} else {
|
||||
network.enabled().then_some(network)
|
||||
})
|
||||
}
|
||||
|
||||
/// Configured thread persistence backend.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub enum ThreadStoreConfig {
|
||||
@@ -3320,32 +3353,12 @@ impl Config {
|
||||
let mcp_servers = constrain_mcp_servers(cfg.mcp_servers.clone(), mcp_servers.as_ref())
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{e}")))?;
|
||||
|
||||
let (network_requirements, network_requirements_source) = match network_requirements {
|
||||
Some(Sourced { value, source }) => (Some(value), Some(source)),
|
||||
None => (None, None),
|
||||
};
|
||||
let has_network_requirements = network_requirements.is_some();
|
||||
let network_permission_profile = constrained_permission_profile.get().clone();
|
||||
let network = NetworkProxySpec::from_config_and_constraints(
|
||||
let network = build_network_proxy_spec(
|
||||
configured_network_proxy_config,
|
||||
network_requirements,
|
||||
&network_permission_profile,
|
||||
)
|
||||
.map_err(|err| {
|
||||
if let Some(source) = network_requirements_source.as_ref() {
|
||||
std::io::Error::new(
|
||||
err.kind(),
|
||||
format!("failed to build managed network proxy from {source}: {err}"),
|
||||
)
|
||||
} else {
|
||||
err
|
||||
}
|
||||
})?;
|
||||
let network = if has_network_requirements {
|
||||
Some(network)
|
||||
} else {
|
||||
network.enabled().then_some(network)
|
||||
};
|
||||
)?;
|
||||
let helper_readable_roots = get_readable_roots_required_for_codex_runtime(
|
||||
&codex_home,
|
||||
zsh_path.as_ref(),
|
||||
@@ -3700,6 +3713,53 @@ impl Config {
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub(crate) fn network_proxy_spec_for_active_permission_profile(
|
||||
&self,
|
||||
active_permission_profile: &ActivePermissionProfile,
|
||||
permission_profile: &PermissionProfile,
|
||||
) -> std::io::Result<Option<NetworkProxySpec>> {
|
||||
let profile_allows_network_proxy =
|
||||
profile_allows_configured_network_proxy(permission_profile);
|
||||
let configured_network_proxy_config = if profile_allows_network_proxy {
|
||||
let cfg: ConfigToml = self
|
||||
.config_layer_stack
|
||||
.effective_config()
|
||||
.try_into()
|
||||
.map_err(|err| {
|
||||
std::io::Error::new(
|
||||
ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"failed to read effective config for selected permission profile: {err}"
|
||||
),
|
||||
)
|
||||
})?;
|
||||
let mut configured_network_proxy_config = network_proxy_config_for_profile_selection(
|
||||
cfg.permissions.as_ref(),
|
||||
active_permission_profile.id.as_str(),
|
||||
)?;
|
||||
if self.features.enabled(Feature::NetworkProxy)
|
||||
&& permission_profile.network_sandbox_policy().is_enabled()
|
||||
{
|
||||
if let Some(network_proxy) = network_proxy_toml_config(cfg.features.as_ref()) {
|
||||
apply_network_proxy_feature_config(
|
||||
&mut configured_network_proxy_config,
|
||||
network_proxy,
|
||||
);
|
||||
}
|
||||
configured_network_proxy_config.network.enabled = true;
|
||||
}
|
||||
configured_network_proxy_config
|
||||
} else {
|
||||
NetworkProxyConfig::default()
|
||||
};
|
||||
|
||||
build_network_proxy_spec(
|
||||
configured_network_proxy_config,
|
||||
self.config_layer_stack.requirements().network.clone(),
|
||||
permission_profile,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bundled_skills_enabled(&self) -> bool {
|
||||
crate::manager::bundled_skills_enabled_from_stack(&self.config_layer_stack)
|
||||
}
|
||||
|
||||
@@ -635,7 +635,8 @@ pub(super) async fn run_guardian_review_session(
|
||||
schema: serde_json::Value,
|
||||
external_cancel: Option<CancellationToken>,
|
||||
) -> (GuardianReviewOutcome, GuardianReviewAnalyticsResult) {
|
||||
let live_network_config = match session.services.network_proxy.as_ref() {
|
||||
let network_proxy = session.services.network_proxy.load_full();
|
||||
let live_network_config = match network_proxy.as_ref() {
|
||||
Some(network_proxy) => match network_proxy.proxy().current_cfg().await {
|
||||
Ok(config) => Some(config),
|
||||
Err(err) => {
|
||||
|
||||
@@ -952,9 +952,6 @@ impl Session {
|
||||
}
|
||||
|
||||
async fn refresh_managed_network_proxy_for_current_permission_profile(&self) {
|
||||
let Some(started_proxy) = self.services.network_proxy.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Ok(_refresh_guard) = self.managed_network_proxy_refresh_lock.acquire().await else {
|
||||
error!("managed network proxy refresh semaphore closed");
|
||||
return;
|
||||
@@ -968,7 +965,9 @@ impl Session {
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.cloned()
|
||||
else {
|
||||
self.services.network_proxy.store(None);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -991,8 +990,36 @@ impl Session {
|
||||
spec
|
||||
}
|
||||
};
|
||||
if let Err(err) = spec.apply_to_started_proxy(started_proxy).await {
|
||||
warn!("failed to refresh managed network proxy for sandbox change: {err}");
|
||||
if let Some(started_proxy) = self.services.network_proxy.load_full() {
|
||||
if let Err(err) = spec.apply_to_started_proxy(started_proxy.as_ref()).await {
|
||||
warn!("failed to refresh managed network proxy for sandbox change: {err}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
match Self::start_managed_network_proxy(
|
||||
&spec,
|
||||
current_exec_policy.as_ref(),
|
||||
&session_configuration.permission_profile(),
|
||||
/*network_policy_decider*/ None,
|
||||
self.services
|
||||
.managed_network_requirements_configured
|
||||
.then(|| {
|
||||
build_blocked_request_observer(Arc::clone(&self.services.network_approval))
|
||||
}),
|
||||
self.services.managed_network_requirements_configured,
|
||||
self.services.network_proxy_audit_metadata.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok((started_proxy, _session_network_proxy)) => {
|
||||
self.services
|
||||
.network_proxy
|
||||
.store(Some(Arc::new(started_proxy)));
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("failed to start managed network proxy for sandbox change: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1886,7 +1913,7 @@ impl Session {
|
||||
let execpolicy_amendment =
|
||||
execpolicy_network_rule_amendment(amendment, network_approval_context, &host);
|
||||
|
||||
if let Some(started_network_proxy) = self.services.network_proxy.as_ref() {
|
||||
if let Some(started_network_proxy) = self.services.network_proxy.load_full() {
|
||||
let proxy = started_network_proxy.proxy();
|
||||
match amendment.action {
|
||||
NetworkPolicyRuleAction::Allow => proxy
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::input_queue::InputQueue;
|
||||
use super::*;
|
||||
use crate::config::ConstraintError;
|
||||
use crate::goals::GoalRuntimeState;
|
||||
use crate::skills::SkillError;
|
||||
use crate::state::ActiveTurn;
|
||||
@@ -291,6 +292,33 @@ impl SessionConfiguration {
|
||||
updates.profile_workspace_roots.clone().unwrap_or_default(),
|
||||
Some(¤t_file_system_sandbox_policy),
|
||||
)?;
|
||||
if let Some(active_permission_profile) = next_configuration.active_permission_profile()
|
||||
{
|
||||
let mut config = (*next_configuration.original_config_do_not_use).clone();
|
||||
let permission_profile = next_configuration.permission_profile();
|
||||
config.permissions.network = config
|
||||
.network_proxy_spec_for_active_permission_profile(
|
||||
&active_permission_profile,
|
||||
&permission_profile,
|
||||
)
|
||||
.map_err(|err| ConstraintError::InvalidValue {
|
||||
field_name: "default_permissions",
|
||||
candidate: active_permission_profile.id.clone(),
|
||||
allowed: format!(
|
||||
"configured permission profile with valid network policy ({err})"
|
||||
),
|
||||
requirement_source: codex_config::RequirementSource::Unknown,
|
||||
})?;
|
||||
config
|
||||
.permissions
|
||||
.set_permission_profile_from_session_snapshot(
|
||||
PermissionProfileSnapshot::active(
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
),
|
||||
)?;
|
||||
next_configuration.original_config_do_not_use = Arc::new(config);
|
||||
}
|
||||
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
@@ -885,7 +913,7 @@ impl Session {
|
||||
network_policy_decider.as_ref().map(Arc::clone),
|
||||
blocked_request_observer.as_ref().map(Arc::clone),
|
||||
managed_network_requirements_configured,
|
||||
network_proxy_audit_metadata,
|
||||
network_proxy_audit_metadata.clone(),
|
||||
)
|
||||
.instrument(info_span!(
|
||||
"session_init.network_proxy",
|
||||
@@ -977,7 +1005,9 @@ impl Session {
|
||||
session_extension_data,
|
||||
thread_extension_data,
|
||||
agent_control,
|
||||
network_proxy,
|
||||
network_proxy: arc_swap::ArcSwapOption::from(network_proxy.map(Arc::new)),
|
||||
network_proxy_audit_metadata,
|
||||
managed_network_requirements_configured,
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
state_db: state_db_ctx.clone(),
|
||||
live_thread: live_thread_init.as_ref().cloned(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::turn_context::TurnEnvironment;
|
||||
use super::*;
|
||||
use crate::config::ConfigBuilder;
|
||||
use crate::config::ConfigOverrides;
|
||||
use crate::config::test_config;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::TurnAborted;
|
||||
@@ -80,6 +81,11 @@ use codex_app_server_protocol::AppInfo;
|
||||
use codex_app_server_protocol::McpElicitationSchema;
|
||||
use codex_config::config_toml::ConfigToml;
|
||||
use codex_config::config_toml::ProjectConfig;
|
||||
use codex_config::permissions_toml::FilesystemPermissionToml;
|
||||
use codex_config::permissions_toml::FilesystemPermissionsToml;
|
||||
use codex_config::permissions_toml::NetworkToml;
|
||||
use codex_config::permissions_toml::PermissionProfileToml;
|
||||
use codex_config::permissions_toml::PermissionsToml;
|
||||
use codex_execpolicy::Decision;
|
||||
use codex_execpolicy::NetworkRuleProtocol;
|
||||
use codex_execpolicy::Policy;
|
||||
@@ -818,7 +824,7 @@ async fn managed_network_proxy_decider_survives_full_access_start() -> anyhow::R
|
||||
|
||||
#[tokio::test]
|
||||
async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow::Result<()> {
|
||||
let (mut session, _turn_context) = make_session_and_context().await;
|
||||
let (session, _turn_context) = make_session_and_context().await;
|
||||
let initial_permission_profile = PermissionProfile::workspace_write();
|
||||
|
||||
let mut network_config = NetworkProxyConfig::default();
|
||||
@@ -873,7 +879,10 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
|
||||
.set_permission_profile_for_tests(initial_permission_profile)
|
||||
.expect("test setup should allow permission profile");
|
||||
}
|
||||
session.services.network_proxy = Some(started_proxy);
|
||||
session
|
||||
.services
|
||||
.network_proxy
|
||||
.store(Some(Arc::new(started_proxy)));
|
||||
|
||||
session
|
||||
.new_turn_with_sub_id(
|
||||
@@ -888,7 +897,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
|
||||
let started_proxy = session
|
||||
.services
|
||||
.network_proxy
|
||||
.as_ref()
|
||||
.load_full()
|
||||
.expect("managed network proxy should be present");
|
||||
assert_eq!(
|
||||
started_proxy
|
||||
@@ -3805,6 +3814,114 @@ async fn session_configuration_apply_retargets_implicit_workspace_root_on_cwd_up
|
||||
assert!(!updated_policy.can_write_path_with_cwd(old_root.as_path(), updated.cwd.as_path()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn active_profile_update_rebuilds_network_proxy_config() -> std::io::Result<()> {
|
||||
let codex_home = tempfile::tempdir().expect("create codex home");
|
||||
let cwd = tempfile::tempdir().expect("create cwd");
|
||||
let permissions = PermissionsToml {
|
||||
entries: std::collections::BTreeMap::from([
|
||||
(
|
||||
"locked-down".to_string(),
|
||||
PermissionProfileToml {
|
||||
description: None,
|
||||
extends: None,
|
||||
workspace_roots: None,
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: std::collections::BTreeMap::from([(
|
||||
":minimal".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
|
||||
)]),
|
||||
}),
|
||||
network: None,
|
||||
},
|
||||
),
|
||||
(
|
||||
"web-enabled".to_string(),
|
||||
PermissionProfileToml {
|
||||
description: None,
|
||||
extends: None,
|
||||
workspace_roots: None,
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
glob_scan_max_depth: None,
|
||||
entries: std::collections::BTreeMap::from([(
|
||||
":minimal".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Read),
|
||||
)]),
|
||||
}),
|
||||
network: Some(NetworkToml {
|
||||
enabled: Some(true),
|
||||
proxy_url: Some("http://127.0.0.1:43128".to_string()),
|
||||
enable_socks5: Some(false),
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
),
|
||||
]),
|
||||
};
|
||||
let base_config = ConfigToml {
|
||||
features: Some(toml::from_str("network_proxy = true").expect("valid features")),
|
||||
default_permissions: Some("locked-down".to_string()),
|
||||
permissions: Some(permissions),
|
||||
..Default::default()
|
||||
};
|
||||
std::fs::write(
|
||||
codex_home.path().join(codex_config::CONFIG_TOML_FILE),
|
||||
toml::to_string(&base_config).expect("serialize config"),
|
||||
)?;
|
||||
let locked_config = Arc::new(
|
||||
ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.harness_overrides(ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
})
|
||||
.build()
|
||||
.await?,
|
||||
);
|
||||
assert_ne!(
|
||||
locked_config
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.map(crate::config::NetworkProxySpec::proxy_host_and_port)
|
||||
.as_deref(),
|
||||
Some("127.0.0.1:43128")
|
||||
);
|
||||
let selected_config = ConfigBuilder::default()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.harness_overrides(ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
default_permissions: Some("web-enabled".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let mut session_configuration = make_session_configuration_for_tests().await;
|
||||
session_configuration.permission_profile_state =
|
||||
locked_config.permissions.permission_profile_state().clone();
|
||||
session_configuration.original_config_do_not_use = Arc::clone(&locked_config);
|
||||
|
||||
let updated = session_configuration
|
||||
.apply(&SessionSettingsUpdate {
|
||||
permission_profile: Some(selected_config.permissions.permission_profile().clone()),
|
||||
active_permission_profile: selected_config.permissions.active_permission_profile(),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("active profile update should apply");
|
||||
|
||||
let network = updated
|
||||
.original_config_do_not_use
|
||||
.permissions
|
||||
.network
|
||||
.as_ref()
|
||||
.expect("selected profile proxy should become the session proxy config");
|
||||
assert_eq!(network.proxy_host_and_port(), "127.0.0.1:43128");
|
||||
assert!(!network.socks_enabled());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, ignore)]
|
||||
#[tokio::test]
|
||||
async fn new_default_turn_uses_config_aware_skills_for_role_overrides() {
|
||||
@@ -4359,7 +4476,9 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
),
|
||||
thread_extension_data: codex_extension_api::ExtensionData::new(thread_id.to_string()),
|
||||
agent_control,
|
||||
network_proxy: None,
|
||||
network_proxy: arc_swap::ArcSwapOption::from(None),
|
||||
network_proxy_audit_metadata: crate::config::NetworkProxyAuditMetadata::default(),
|
||||
managed_network_requirements_configured: false,
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
state_db: None,
|
||||
live_thread: None,
|
||||
@@ -6186,7 +6305,9 @@ where
|
||||
),
|
||||
thread_extension_data: codex_extension_api::ExtensionData::new(thread_id.to_string()),
|
||||
agent_control,
|
||||
network_proxy: None,
|
||||
network_proxy: arc_swap::ArcSwapOption::from(None),
|
||||
network_proxy_audit_metadata: crate::config::NetworkProxyAuditMetadata::default(),
|
||||
managed_network_requirements_configured: false,
|
||||
network_approval: Arc::clone(&network_approval),
|
||||
state_db: state_db.clone(),
|
||||
live_thread: None,
|
||||
|
||||
@@ -704,6 +704,7 @@ impl Session {
|
||||
&self.services.models_manager,
|
||||
self.services
|
||||
.network_proxy
|
||||
.load_full()
|
||||
.as_ref()
|
||||
.and_then(|started_proxy| {
|
||||
Self::managed_network_proxy_active_for_permission_profile(
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::SkillsManager;
|
||||
use crate::agent::AgentControl;
|
||||
use crate::attestation::AttestationProvider;
|
||||
use crate::client::ModelClient;
|
||||
use crate::config::NetworkProxyAuditMetadata;
|
||||
use crate::config::StartedNetworkProxy;
|
||||
use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::guardian::GuardianRejection;
|
||||
@@ -15,6 +16,7 @@ use crate::tools::network_approval::NetworkApprovalService;
|
||||
use crate::tools::sandboxing::ApprovalStore;
|
||||
use crate::unified_exec::UnifiedExecProcessManager;
|
||||
use arc_swap::ArcSwap;
|
||||
use arc_swap::ArcSwapOption;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_core_plugins::PluginsManager;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
@@ -65,7 +67,9 @@ pub(crate) struct SessionServices {
|
||||
pub(crate) session_extension_data: ExtensionData,
|
||||
pub(crate) thread_extension_data: ExtensionData,
|
||||
pub(crate) agent_control: AgentControl,
|
||||
pub(crate) network_proxy: Option<StartedNetworkProxy>,
|
||||
pub(crate) network_proxy: ArcSwapOption<StartedNetworkProxy>,
|
||||
pub(crate) network_proxy_audit_metadata: NetworkProxyAuditMetadata,
|
||||
pub(crate) managed_network_requirements_configured: bool,
|
||||
pub(crate) network_approval: Arc<NetworkApprovalService>,
|
||||
pub(crate) state_db: Option<StateDbHandle>,
|
||||
pub(crate) live_thread: Option<LiveThread>,
|
||||
|
||||
@@ -655,7 +655,8 @@ impl Session {
|
||||
"false"
|
||||
},
|
||||
);
|
||||
let network_proxy_active = match self.services.network_proxy.as_ref() {
|
||||
let network_proxy = self.services.network_proxy.load_full();
|
||||
let network_proxy_active = match network_proxy.as_ref() {
|
||||
Some(started_network_proxy) => {
|
||||
match started_network_proxy.proxy().current_cfg().await {
|
||||
Ok(config) => config.network.enabled,
|
||||
|
||||
@@ -360,6 +360,7 @@ impl App {
|
||||
/*cwd*/ None,
|
||||
approval_policy_override,
|
||||
approvals_reviewer_override,
|
||||
permission_profile_override,
|
||||
active_permission_profile_override,
|
||||
/*windows_sandbox_level*/ None,
|
||||
/*model*/ None,
|
||||
@@ -387,6 +388,7 @@ impl App {
|
||||
/*cwd*/ None,
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*active_permission_profile*/ None,
|
||||
#[cfg(target_os = "windows")]
|
||||
Some(windows_sandbox_level),
|
||||
|
||||
@@ -1125,6 +1125,7 @@ impl App {
|
||||
/*cwd*/ None,
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*active_permission_profile*/ None,
|
||||
#[cfg(target_os = "windows")]
|
||||
Some(windows_sandbox_level),
|
||||
@@ -1150,6 +1151,7 @@ impl App {
|
||||
/*cwd*/ None,
|
||||
Some(AskForApproval::from(preset.approval)),
|
||||
Some(self.config.approvals_reviewer),
|
||||
Some(preset.permission_profile.clone()),
|
||||
Some(preset.active_permission_profile.clone()),
|
||||
#[cfg(target_os = "windows")]
|
||||
Some(windows_sandbox_level),
|
||||
|
||||
@@ -1695,6 +1695,7 @@ async fn update_feature_flags_enabling_guardian_selects_auto_review() -> Result<
|
||||
cwd: None,
|
||||
approval_policy: Some(auto_review.approval_policy),
|
||||
approvals_reviewer: Some(auto_review.approvals_reviewer),
|
||||
permission_profile: Some(auto_review.permission_profile()),
|
||||
active_permission_profile: Some(auto_review.active_permission_profile.clone()),
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
@@ -1787,6 +1788,7 @@ async fn update_feature_flags_disabling_guardian_clears_review_policy_and_restor
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
@@ -1865,6 +1867,7 @@ async fn update_feature_flags_enabling_guardian_overrides_explicit_manual_review
|
||||
cwd: None,
|
||||
approval_policy: Some(auto_review.approval_policy),
|
||||
approvals_reviewer: Some(auto_review.approvals_reviewer),
|
||||
permission_profile: Some(auto_review.permission_profile()),
|
||||
active_permission_profile: Some(auto_review.active_permission_profile.clone()),
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
@@ -1922,6 +1925,7 @@ async fn update_feature_flags_disabling_guardian_clears_manual_review_policy_wit
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
@@ -1981,6 +1985,7 @@ async fn update_feature_flags_enabling_guardian_in_profile_sets_profile_auto_rev
|
||||
cwd: None,
|
||||
approval_policy: Some(auto_review.approval_policy),
|
||||
approvals_reviewer: Some(auto_review.approvals_reviewer),
|
||||
permission_profile: Some(auto_review.permission_profile()),
|
||||
active_permission_profile: Some(auto_review.active_permission_profile.clone()),
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
@@ -2068,6 +2073,7 @@ guardian_approval = true
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
approvals_reviewer: Some(ApprovalsReviewer::User),
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
@@ -5276,6 +5282,7 @@ async fn override_turn_context_sends_thread_settings_update() {
|
||||
/*cwd*/ None,
|
||||
Some(AskForApproval::OnRequest),
|
||||
Some(ApprovalsReviewer::AutoReview),
|
||||
/*permission_profile*/ None,
|
||||
Some(ActivePermissionProfile::new(
|
||||
codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
)),
|
||||
|
||||
@@ -101,6 +101,7 @@ impl App {
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile: _,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level: _,
|
||||
model,
|
||||
|
||||
@@ -17,6 +17,7 @@ use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use serde::Serialize;
|
||||
@@ -54,6 +55,7 @@ pub(crate) enum AppCommand {
|
||||
cwd: Option<PathBuf>,
|
||||
approval_policy: Option<AskForApproval>,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
permission_profile: Option<PermissionProfile>,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||
model: Option<String>,
|
||||
@@ -172,6 +174,7 @@ impl AppCommand {
|
||||
cwd: Option<PathBuf>,
|
||||
approval_policy: Option<AskForApproval>,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
permission_profile: Option<PermissionProfile>,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||
model: Option<String>,
|
||||
@@ -185,6 +188,7 @@ impl AppCommand {
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
model,
|
||||
|
||||
@@ -124,6 +124,7 @@ impl ChatWidget {
|
||||
} else {
|
||||
Self::approval_preset_actions(
|
||||
preset_approval,
|
||||
preset.permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
base_name.clone(),
|
||||
ApprovalsReviewer::User,
|
||||
@@ -134,6 +135,7 @@ impl ChatWidget {
|
||||
{
|
||||
Self::approval_preset_actions(
|
||||
preset_approval,
|
||||
preset.permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
base_name.clone(),
|
||||
ApprovalsReviewer::User,
|
||||
@@ -142,6 +144,7 @@ impl ChatWidget {
|
||||
} else {
|
||||
Self::approval_preset_actions(
|
||||
preset_approval,
|
||||
preset.permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
base_name.clone(),
|
||||
ApprovalsReviewer::User,
|
||||
@@ -180,6 +183,7 @@ impl ChatWidget {
|
||||
),
|
||||
actions: Self::approval_preset_actions(
|
||||
preset_approval,
|
||||
preset.permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
"Auto-review".to_string(),
|
||||
ApprovalsReviewer::AutoReview,
|
||||
@@ -308,6 +312,7 @@ impl ChatWidget {
|
||||
|
||||
pub(super) fn approval_preset_actions(
|
||||
approval: AskForApproval,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: ActivePermissionProfile,
|
||||
label: String,
|
||||
approvals_reviewer: ApprovalsReviewer,
|
||||
@@ -317,6 +322,7 @@ impl ChatWidget {
|
||||
/*cwd*/ None,
|
||||
Some(approval),
|
||||
Some(approvals_reviewer),
|
||||
Some(permission_profile.clone()),
|
||||
Some(active_permission_profile.clone()),
|
||||
/*windows_sandbox_level*/ None,
|
||||
/*model*/ None,
|
||||
@@ -402,6 +408,7 @@ impl ChatWidget {
|
||||
|
||||
let mut accept_actions = Self::approval_preset_actions(
|
||||
approval,
|
||||
preset.permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
selected_name.clone(),
|
||||
ApprovalsReviewer::User,
|
||||
@@ -412,6 +419,7 @@ impl ChatWidget {
|
||||
|
||||
let mut accept_and_remember_actions = Self::approval_preset_actions(
|
||||
approval,
|
||||
preset.permission_profile,
|
||||
preset.active_permission_profile,
|
||||
selected_name,
|
||||
ApprovalsReviewer::User,
|
||||
|
||||
@@ -308,6 +308,7 @@ impl ChatWidget {
|
||||
/*cwd*/ None,
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*active_permission_profile*/ None,
|
||||
/*windows_sandbox_level*/ None,
|
||||
Some(switch_model_for_events.clone()),
|
||||
|
||||
@@ -107,6 +107,7 @@ impl ChatWidget {
|
||||
/*cwd*/ None,
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*active_permission_profile*/ None,
|
||||
/*windows_sandbox_level*/ None,
|
||||
/*model*/ None,
|
||||
|
||||
@@ -734,6 +734,7 @@ impl ChatWidget {
|
||||
/*cwd*/ None,
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*active_permission_profile*/ None,
|
||||
/*windows_sandbox_level*/ None,
|
||||
/*model*/ None,
|
||||
|
||||
@@ -53,6 +53,7 @@ impl ChatWidget {
|
||||
/*cwd*/ None,
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*active_permission_profile*/ None,
|
||||
/*windows_sandbox_level*/ None,
|
||||
/*model*/ None,
|
||||
|
||||
@@ -743,6 +743,7 @@ async fn permissions_selection_sends_approvals_reviewer_in_override_turn_context
|
||||
cwd: None,
|
||||
approval_policy: Some(AskForApproval::OnRequest),
|
||||
approvals_reviewer: Some(ApprovalsReviewer::AutoReview),
|
||||
permission_profile: Some(PermissionProfile::workspace_write()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
)),
|
||||
|
||||
@@ -42,12 +42,13 @@ impl ChatWidget {
|
||||
extra_count: usize,
|
||||
failed_scan: bool,
|
||||
) {
|
||||
let (approval, active_permission_profile) = match &preset {
|
||||
let (approval, permission_profile, active_permission_profile) = match &preset {
|
||||
Some(p) => (
|
||||
Some(AskForApproval::from(p.approval)),
|
||||
Some(p.permission_profile.clone()),
|
||||
Some(p.active_permission_profile.clone()),
|
||||
),
|
||||
None => (None, None),
|
||||
None => (None, None, None),
|
||||
};
|
||||
let mut header_children: Vec<Box<dyn Renderable>> = Vec::new();
|
||||
let describe_profile = |profile: &PermissionProfile| {
|
||||
@@ -110,11 +111,14 @@ impl ChatWidget {
|
||||
tx.send(AppEvent::SkipNextWorldWritableScan);
|
||||
}));
|
||||
}
|
||||
if let (Some(approval), Some(active_permission_profile)) =
|
||||
(approval, active_permission_profile.clone())
|
||||
{
|
||||
if let (Some(approval), Some(permission_profile), Some(active_permission_profile)) = (
|
||||
approval,
|
||||
permission_profile.clone(),
|
||||
active_permission_profile.clone(),
|
||||
) {
|
||||
accept_actions.extend(Self::approval_preset_actions(
|
||||
approval,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
mode_label.to_string(),
|
||||
ApprovalsReviewer::User,
|
||||
@@ -126,11 +130,12 @@ impl ChatWidget {
|
||||
tx.send(AppEvent::UpdateWorldWritableWarningAcknowledged(true));
|
||||
tx.send(AppEvent::PersistWorldWritableWarningAcknowledged);
|
||||
}));
|
||||
if let (Some(approval), Some(active_permission_profile)) =
|
||||
(approval, active_permission_profile)
|
||||
if let (Some(approval), Some(permission_profile), Some(active_permission_profile)) =
|
||||
(approval, permission_profile, active_permission_profile)
|
||||
{
|
||||
accept_and_remember_actions.extend(Self::approval_preset_actions(
|
||||
approval,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
mode_label.to_string(),
|
||||
ApprovalsReviewer::User,
|
||||
|
||||
Reference in New Issue
Block a user