session: convert legacy sandbox updates before settings apply

This commit is contained in:
Michael Bolin
2026-04-30 06:45:12 -07:00
parent 0cdfe2544a
commit 2bc3c954e3
4 changed files with 110 additions and 36 deletions

View File

@@ -234,14 +234,26 @@ impl CodexThread {
.await
.with_updates(model, effort, /*developer_instructions*/ None)
};
let clear_active_permission_profile =
permission_profile.is_none() && sandbox_policy.is_some();
let permission_profile = match (permission_profile, sandbox_policy.as_ref()) {
(Some(permission_profile), _) => Some(permission_profile),
(None, Some(sandbox_policy)) => Some(
self.codex
.session
.permission_profile_from_legacy_sandbox_update(sandbox_policy, cwd.as_deref())
.await,
),
(None, None) => None,
};
let updates = SessionSettingsUpdate {
cwd,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
active_permission_profile,
clear_active_permission_profile,
windows_sandbox_level,
collaboration_mode: Some(collaboration_mode),
reasoning_summary: summary,

View File

@@ -34,6 +34,7 @@ use crate::tasks::execute_user_shell_command;
use codex_mcp::collect_mcp_snapshot_from_manager;
use codex_mcp::compute_auth_statuses;
use codex_protocol::models::ContentItem;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::protocol::CodexErrorInfo;
use codex_protocol::protocol::ErrorEvent;
@@ -50,6 +51,7 @@ use codex_protocol::protocol::RealtimeVoicesList;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::ReviewRequest;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SkillErrorInfo;
use codex_protocol::protocol::SkillsListEntry;
use codex_protocol::protocol::ThreadMemoryMode;
@@ -71,6 +73,7 @@ use codex_protocol::user_input::UserInput;
use codex_rmcp_client::ElicitationAction;
use codex_rmcp_client::ElicitationResponse;
use serde_json::Value;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use tracing::debug;
@@ -153,15 +156,23 @@ pub(super) async fn user_input_or_turn_inner(
},
})
});
let clear_active_permission_profile = permission_profile.is_none();
let permission_profile = permission_profile_with_legacy_fallback(
sess,
Some(&sandbox_policy),
permission_profile,
Some(cwd.as_path()),
)
.await;
(
items,
SessionSettingsUpdate {
cwd: Some(cwd),
approval_policy: Some(approval_policy),
approvals_reviewer,
sandbox_policy: Some(sandbox_policy),
permission_profile,
active_permission_profile: None,
clear_active_permission_profile,
windows_sandbox_level: None,
collaboration_mode,
reasoning_summary: summary,
@@ -205,15 +216,24 @@ pub(super) async fn user_input_or_turn_inner(
.with_updates(model, effort, /*developer_instructions*/ None),
)
};
let clear_active_permission_profile =
permission_profile.is_none() && sandbox_policy.is_some();
let permission_profile = permission_profile_with_legacy_fallback(
sess,
sandbox_policy.as_ref(),
permission_profile,
cwd.as_deref(),
)
.await;
(
items,
SessionSettingsUpdate {
cwd,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
active_permission_profile,
clear_active_permission_profile,
windows_sandbox_level,
collaboration_mode,
reasoning_summary: summary,
@@ -294,6 +314,22 @@ pub(super) async fn user_input_or_turn_inner(
}
}
async fn permission_profile_with_legacy_fallback(
sess: &Session,
sandbox_policy: Option<&SandboxPolicy>,
permission_profile: Option<PermissionProfile>,
cwd: Option<&Path>,
) -> Option<PermissionProfile> {
match (permission_profile, sandbox_policy) {
(Some(permission_profile), _) => Some(permission_profile),
(None, Some(sandbox_policy)) => Some(
sess.permission_profile_from_legacy_sandbox_update(sandbox_policy, cwd)
.await,
),
(None, None) => None,
}
}
async fn mirror_user_text_to_realtime(sess: &Arc<Session>, items: &[UserInput]) {
let text = UserMessageItem::new(items).message();
if text.is_empty() {
@@ -1039,6 +1075,15 @@ pub(super) async fn submission_loop(
/*developer_instructions*/ None,
)
};
let clear_active_permission_profile =
permission_profile.is_none() && sandbox_policy.is_some();
let permission_profile = permission_profile_with_legacy_fallback(
&sess,
sandbox_policy.as_ref(),
permission_profile,
cwd.as_deref(),
)
.await;
override_turn_context(
&sess,
sub.id.clone(),
@@ -1046,8 +1091,8 @@ pub(super) async fn submission_loop(
cwd,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
clear_active_permission_profile,
windows_sandbox_level,
collaboration_mode: Some(collaboration_mode),
reasoning_summary: summary,

View File

@@ -1354,6 +1354,17 @@ impl Session {
state.session_configuration.apply(updates).map(|_| ())
}
pub(crate) async fn permission_profile_from_legacy_sandbox_update(
&self,
sandbox_policy: &SandboxPolicy,
cwd: Option<&Path>,
) -> PermissionProfile {
let state = self.state.lock().await;
state
.session_configuration
.permission_profile_from_legacy_sandbox_update(sandbox_policy, cwd)
}
pub(crate) async fn set_session_startup_prewarm(
&self,
startup_prewarm: SessionStartupPrewarmHandle,

View File

@@ -126,6 +126,24 @@ impl SessionConfiguration {
self.permission_profile.get().network_sandbox_policy()
}
pub(super) fn permission_profile_from_legacy_sandbox_update(
&self,
sandbox_policy: &SandboxPolicy,
cwd: Option<&Path>,
) -> PermissionProfile {
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
sandbox_policy,
self.resolved_update_cwd(cwd).as_path(),
&self.file_system_sandbox_policy(),
);
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(sandbox_policy),
)
}
pub(super) fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
ThreadConfigSnapshot {
model: self.collaboration_mode.model().to_string(),
@@ -191,19 +209,7 @@ impl SessionConfiguration {
next_configuration.windows_sandbox_level = windows_sandbox_level;
}
let absolute_cwd = updates
.cwd
.as_ref()
.map(|cwd| {
AbsolutePathBuf::relative_to_current_dir(normalize_for_native_workdir(
cwd.as_path(),
))
.unwrap_or_else(|e| {
warn!("failed to normalize update cwd: {cwd:?}: {e}");
self.cwd.clone()
})
})
.unwrap_or_else(|| self.cwd.clone());
let absolute_cwd = self.resolved_update_cwd(updates.cwd.as_deref());
let cwd_changed = absolute_cwd.as_path() != self.cwd.as_path();
next_configuration.cwd = absolute_cwd.clone();
@@ -214,35 +220,22 @@ impl SessionConfiguration {
}
if let Some(permission_profile) = updates.permission_profile.clone() {
let active_permission_profile =
let active_permission_profile = if updates.clear_active_permission_profile {
None
} else {
updates.active_permission_profile.clone().or_else(|| {
if permission_profile == self.permission_profile() {
self.active_permission_profile.clone()
} else {
None
}
});
})
};
next_configuration.set_permission_profile_projection(
permission_profile,
Some(&current_file_system_sandbox_policy),
)?;
next_configuration.active_permission_profile = active_permission_profile;
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&sandbox_policy,
&next_configuration.cwd,
&current_file_system_sandbox_policy,
);
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
next_configuration.permission_profile.set(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
network_sandbox_policy,
),
)?;
next_configuration.active_permission_profile = None;
} else if cwd_changed
&& file_system_policy_matches_legacy
&& file_system_policy_has_rebindable_project_root_write
@@ -273,6 +266,17 @@ impl SessionConfiguration {
Ok(next_configuration)
}
fn resolved_update_cwd(&self, cwd: Option<&Path>) -> AbsolutePathBuf {
cwd.map(|cwd| {
AbsolutePathBuf::relative_to_current_dir(normalize_for_native_workdir(cwd))
.unwrap_or_else(|e| {
warn!("failed to normalize update cwd: {cwd:?}: {e}");
self.cwd.clone()
})
})
.unwrap_or_else(|| self.cwd.clone())
}
fn set_permission_profile_projection(
&mut self,
permission_profile: PermissionProfile,
@@ -301,9 +305,11 @@ pub(crate) struct SessionSettingsUpdate {
pub(crate) cwd: Option<PathBuf>,
pub(crate) approval_policy: Option<AskForApproval>,
pub(crate) approvals_reviewer: Option<ApprovalsReviewer>,
pub(crate) sandbox_policy: Option<SandboxPolicy>,
pub(crate) permission_profile: Option<PermissionProfile>,
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
/// Legacy sandbox updates are represented as permission profiles before
/// reaching this layer, but they should still clear any named profile.
pub(crate) clear_active_permission_profile: bool,
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
pub(crate) collaboration_mode: Option<CollaborationMode>,
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,