mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
permissions: make SessionConfigured profile-only
This commit is contained in:
@@ -4674,9 +4674,9 @@ impl CodexMessageProcessor {
|
||||
thread_status,
|
||||
/*has_live_in_progress_turn*/ false,
|
||||
);
|
||||
let permission_profile = thread_response_permission_profile(
|
||||
codex_thread.config_snapshot().await.permission_profile,
|
||||
);
|
||||
let config_snapshot = codex_thread.config_snapshot().await;
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile.clone());
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
thread,
|
||||
@@ -4687,7 +4687,7 @@ impl CodexMessageProcessor {
|
||||
instruction_sources,
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
approvals_reviewer: session_configured.approvals_reviewer.into(),
|
||||
sandbox: session_configured.sandbox_policy.into(),
|
||||
sandbox: config_snapshot.sandbox_policy.into(),
|
||||
permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
};
|
||||
@@ -5405,9 +5405,9 @@ impl CodexMessageProcessor {
|
||||
.await,
|
||||
/*has_in_progress_turn*/ false,
|
||||
);
|
||||
let permission_profile = thread_response_permission_profile(
|
||||
forked_thread.config_snapshot().await.permission_profile,
|
||||
);
|
||||
let config_snapshot = forked_thread.config_snapshot().await;
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile.clone());
|
||||
|
||||
let response = ThreadForkResponse {
|
||||
thread: thread.clone(),
|
||||
@@ -5418,7 +5418,7 @@ impl CodexMessageProcessor {
|
||||
instruction_sources,
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
approvals_reviewer: session_configured.approvals_reviewer.into(),
|
||||
sandbox: session_configured.sandbox_policy.into(),
|
||||
sandbox: config_snapshot.sandbox_policy.into(),
|
||||
permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
};
|
||||
|
||||
@@ -867,7 +867,6 @@ impl Session {
|
||||
// Dispatch the SessionConfiguredEvent first and then report any errors.
|
||||
// If resuming, include converted initial messages in the payload so UIs can render them immediately.
|
||||
let initial_messages = initial_history.get_event_msgs();
|
||||
let session_sandbox_policy = session_configuration.sandbox_policy();
|
||||
let events = std::iter::once(Event {
|
||||
id: INITIAL_SUBMIT_ID.to_owned(),
|
||||
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
|
||||
@@ -879,8 +878,7 @@ impl Session {
|
||||
service_tier: session_configuration.service_tier,
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
approvals_reviewer: session_configuration.approvals_reviewer,
|
||||
sandbox_policy: session_sandbox_policy.clone(),
|
||||
permission_profile: Some(session_configuration.permission_profile()),
|
||||
permission_profile: session_configuration.permission_profile(),
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
|
||||
history_log_id,
|
||||
|
||||
@@ -1527,17 +1527,12 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() ->
|
||||
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
assert_eq!(
|
||||
test.session_configured.sandbox_policy,
|
||||
expected_sandbox_policy
|
||||
);
|
||||
let expected_permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
|
||||
&expected_sandbox_policy,
|
||||
);
|
||||
assert_eq!(
|
||||
test.session_configured.permission_profile,
|
||||
Some(expected_permission_profile),
|
||||
test.session_configured.permission_profile, expected_permission_profile,
|
||||
"ExternalSandbox is represented explicitly instead of as a lossy root-write profile"
|
||||
);
|
||||
Ok(())
|
||||
|
||||
@@ -1043,8 +1043,9 @@ fn session_configured_from_thread_response(
|
||||
service_tier,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
permission_profile: permission_profile.unwrap_or_else(|| {
|
||||
PermissionProfile::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, cwd.as_path())
|
||||
}),
|
||||
cwd,
|
||||
reasoning_effort,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -3538,7 +3538,7 @@ pub struct SessionNetworkProxyRuntime {
|
||||
pub socks_addr: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
#[derive(Debug, Clone, Serialize, JsonSchema, TS)]
|
||||
pub struct SessionConfiguredEvent {
|
||||
pub session_id: ThreadId,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -3566,16 +3566,8 @@ pub struct SessionConfiguredEvent {
|
||||
#[serde(default)]
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
|
||||
/// Legacy sandbox projection for commands executed in the system.
|
||||
///
|
||||
/// Consumers should prefer `permission_profile` when it is present. This
|
||||
/// field remains available as a compatibility fallback for older emitters
|
||||
/// and sessions that only expose legacy sandbox state.
|
||||
pub sandbox_policy: SandboxPolicy,
|
||||
|
||||
/// Canonical effective permissions for commands executed in the session.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub permission_profile: PermissionProfile,
|
||||
|
||||
/// Working directory that should be treated as the *root* of the
|
||||
/// session.
|
||||
@@ -3606,6 +3598,67 @@ pub struct SessionConfiguredEvent {
|
||||
pub rollout_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SessionConfiguredEvent {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Wire {
|
||||
session_id: ThreadId,
|
||||
forked_from_id: Option<ThreadId>,
|
||||
#[serde(default)]
|
||||
thread_name: Option<String>,
|
||||
model: String,
|
||||
model_provider_id: String,
|
||||
service_tier: Option<ServiceTier>,
|
||||
approval_policy: AskForApproval,
|
||||
#[serde(default)]
|
||||
approvals_reviewer: ApprovalsReviewer,
|
||||
sandbox_policy: Option<SandboxPolicy>,
|
||||
permission_profile: Option<PermissionProfile>,
|
||||
cwd: AbsolutePathBuf,
|
||||
reasoning_effort: Option<ReasoningEffortConfig>,
|
||||
history_log_id: u64,
|
||||
history_entry_count: usize,
|
||||
initial_messages: Option<Vec<EventMsg>>,
|
||||
network_proxy: Option<SessionNetworkProxyRuntime>,
|
||||
rollout_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
let wire = Wire::deserialize(deserializer)?;
|
||||
let permission_profile = match (wire.permission_profile, wire.sandbox_policy) {
|
||||
(Some(permission_profile), _) => permission_profile,
|
||||
(None, Some(sandbox_policy)) => PermissionProfile::from_legacy_sandbox_policy_for_cwd(
|
||||
&sandbox_policy,
|
||||
wire.cwd.as_path(),
|
||||
),
|
||||
(None, None) => {
|
||||
return Err(serde::de::Error::missing_field("permission_profile"));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
session_id: wire.session_id,
|
||||
forked_from_id: wire.forked_from_id,
|
||||
thread_name: wire.thread_name,
|
||||
model: wire.model,
|
||||
model_provider_id: wire.model_provider_id,
|
||||
service_tier: wire.service_tier,
|
||||
approval_policy: wire.approval_policy,
|
||||
approvals_reviewer: wire.approvals_reviewer,
|
||||
permission_profile,
|
||||
cwd: wire.cwd,
|
||||
reasoning_effort: wire.reasoning_effort,
|
||||
history_log_id: wire.history_log_id,
|
||||
history_entry_count: wire.history_entry_count,
|
||||
initial_messages: wire.initial_messages,
|
||||
network_proxy: wire.network_proxy,
|
||||
rollout_path: wire.rollout_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
|
||||
pub struct ThreadNameUpdatedEvent {
|
||||
pub thread_id: ThreadId,
|
||||
@@ -5085,6 +5138,7 @@ mod tests {
|
||||
fn serialize_event() -> Result<()> {
|
||||
let conversation_id = ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
|
||||
let rollout_file = NamedTempFile::new()?;
|
||||
let permission_profile = PermissionProfile::read_only();
|
||||
let event = Event {
|
||||
id: "1234".to_string(),
|
||||
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
|
||||
@@ -5096,8 +5150,7 @@ mod tests {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: permission_profile.clone(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -5117,9 +5170,7 @@ mod tests {
|
||||
"model_provider_id": "openai",
|
||||
"approval_policy": "never",
|
||||
"approvals_reviewer": "user",
|
||||
"sandbox_policy": {
|
||||
"type": "read-only"
|
||||
},
|
||||
"permission_profile": permission_profile,
|
||||
"cwd": test_path_buf("/home/user/project"),
|
||||
"reasoning_effort": "medium",
|
||||
"history_log_id": 0,
|
||||
@@ -5131,6 +5182,28 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_legacy_session_configured_event_uses_sandbox_policy() -> Result<()> {
|
||||
let cwd = test_path_buf("/home/user/project");
|
||||
let value = json!({
|
||||
"session_id": "67e55044-10b1-426f-9247-bb680e5fe0c8",
|
||||
"model": "codex-mini-latest",
|
||||
"model_provider_id": "openai",
|
||||
"approval_policy": "never",
|
||||
"approvals_reviewer": "user",
|
||||
"sandbox_policy": {
|
||||
"type": "read-only"
|
||||
},
|
||||
"cwd": cwd,
|
||||
"history_log_id": 0,
|
||||
"history_entry_count": 0,
|
||||
});
|
||||
|
||||
let event: SessionConfiguredEvent = serde_json::from_value(value)?;
|
||||
assert_eq!(event.permission_profile, PermissionProfile::read_only());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_u8_as_base64_serialization_and_deserialization() -> Result<()> {
|
||||
let event = ExecCommandOutputDeltaEvent {
|
||||
|
||||
@@ -556,6 +556,7 @@ mod tests {
|
||||
use crate::app::test_support::app_enabled_in_effective_config;
|
||||
use crate::app::test_support::make_test_app;
|
||||
use crate::test_support::PathBufExt;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::Event;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
@@ -653,8 +654,7 @@ mod tests {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: next_cwd.clone().abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -3495,8 +3495,7 @@ async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::High),
|
||||
history_log_id: 0,
|
||||
@@ -4246,8 +4245,7 @@ async fn backtrack_selection_with_duplicate_history_targets_unique_turn() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -4310,8 +4308,7 @@ async fn backtrack_selection_with_duplicate_history_targets_unique_turn() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -4404,8 +4401,7 @@ async fn backtrack_resubmit_preserves_data_image_urls_in_user_turn() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -4788,8 +4784,7 @@ async fn new_session_requests_shutdown_for_previous_conversation() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -4901,8 +4896,7 @@ async fn clear_only_ui_reset_preserves_chat_session_state() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -141,12 +141,8 @@ use codex_protocol::items::AgentMessageContent;
|
||||
use codex_protocol::items::AgentMessageItem;
|
||||
use codex_protocol::items::UserMessageItem;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::models::local_image_label_text;
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::plan_tool::PlanItemArg as UpdatePlanItemArg;
|
||||
use codex_protocol::plan_tool::StepStatus as UpdatePlanItemStatus;
|
||||
#[cfg(test)]
|
||||
@@ -1617,8 +1613,7 @@ fn thread_session_state_to_legacy_event(
|
||||
service_tier: session.service_tier,
|
||||
approval_policy: session.approval_policy,
|
||||
approvals_reviewer: session.approvals_reviewer,
|
||||
sandbox_policy: session.sandbox_policy,
|
||||
permission_profile: Some(session.permission_profile),
|
||||
permission_profile: session.permission_profile,
|
||||
cwd: session.cwd,
|
||||
reasoning_effort: session.reasoning_effort,
|
||||
history_log_id: session.history_log_id,
|
||||
@@ -2376,32 +2371,14 @@ impl ChatWidget {
|
||||
self.config.permissions.approval_policy =
|
||||
Constrained::allow_only(event.approval_policy);
|
||||
}
|
||||
let permission_sync = match event.permission_profile.clone() {
|
||||
Some(permission_profile) => self
|
||||
.config
|
||||
.permissions
|
||||
.set_permission_profile(permission_profile),
|
||||
None => self
|
||||
.config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(event.sandbox_policy.clone(), event.cwd.as_path()),
|
||||
};
|
||||
let permission_sync = self
|
||||
.config
|
||||
.permissions
|
||||
.set_permission_profile(event.permission_profile.clone());
|
||||
if let Err(err) = permission_sync {
|
||||
tracing::warn!(%err, "failed to sync permissions from SessionConfigured");
|
||||
let permission_profile = event.permission_profile.clone().unwrap_or_else(|| {
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
&event.sandbox_policy,
|
||||
event.cwd.as_path(),
|
||||
);
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&event.sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
NetworkSandboxPolicy::from(&event.sandbox_policy),
|
||||
)
|
||||
});
|
||||
self.config.permissions.permission_profile =
|
||||
Constrained::allow_only(permission_profile);
|
||||
Constrained::allow_only(event.permission_profile.clone());
|
||||
}
|
||||
self.config.approvals_reviewer = event.approvals_reviewer;
|
||||
self.status_line_project_root_name_cache = None;
|
||||
|
||||
@@ -16,8 +16,7 @@ async fn submission_preserves_text_elements_and_local_images() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -121,8 +120,7 @@ async fn submission_includes_configured_permission_profile() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: Some(expected_permission_profile.clone()),
|
||||
permission_profile: expected_permission_profile.clone(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -172,10 +170,7 @@ async fn submission_keeps_profile_when_legacy_projection_is_external() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::ExternalSandbox {
|
||||
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
|
||||
},
|
||||
permission_profile: Some(expected_permission_profile.clone()),
|
||||
permission_profile: expected_permission_profile.clone(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -218,8 +213,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -314,8 +308,7 @@ async fn enter_with_only_remote_images_submits_user_turn() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -380,8 +373,7 @@ async fn shift_enter_with_only_remote_images_does_not_submit_user_turn() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -421,8 +413,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_modal_is_active() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -462,8 +453,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_input_disabled() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -506,8 +496,7 @@ async fn submission_prefers_selected_duplicate_skill_path() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -1004,8 +1004,7 @@ async fn bang_shell_enter_while_task_running_submits_run_user_shell_command() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -24,8 +24,7 @@ async fn resumed_initial_messages_render_history() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -138,8 +137,7 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -200,8 +198,7 @@ async fn replayed_user_message_preserves_remote_image_urls() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -256,7 +253,6 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
.expect("set sandbox policy");
|
||||
chat.config.cwd = test_path_buf("/home/user/main").abs();
|
||||
|
||||
let legacy_fallback_sandbox = SandboxPolicy::new_read_only_policy();
|
||||
let expected_cwd = test_path_buf("/home/user/sub-agent").abs();
|
||||
let expected_file_system_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
@@ -289,8 +285,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: legacy_fallback_sandbox,
|
||||
permission_profile: Some(expected_permission_profile.clone()),
|
||||
permission_profile: expected_permission_profile.clone(),
|
||||
cwd: expected_cwd.clone(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -355,8 +350,9 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: expected_sandbox.clone(),
|
||||
permission_profile: None,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::External {
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
},
|
||||
cwd: test_path_buf("/home/user/external").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -405,8 +401,7 @@ async fn replayed_user_message_with_only_remote_images_renders_history_cell() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -459,8 +454,7 @@ async fn replayed_user_message_with_only_local_images_does_not_render_history_ce
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -781,8 +775,7 @@ async fn replayed_reasoning_item_hides_raw_reasoning_when_disabled() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
cwd: test_project_path().abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -829,8 +822,7 @@ async fn replayed_reasoning_item_shows_raw_reasoning_when_enabled() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::read_only(),
|
||||
cwd: test_project_path().abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -483,8 +483,7 @@ async fn permissions_selection_marks_auto_review_current_after_session_configure
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: ApprovalsReviewer::AutoReview,
|
||||
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::workspace_write(),
|
||||
cwd: test_project_path().abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
@@ -519,6 +518,16 @@ async fn permissions_selection_marks_auto_review_current_with_custom_workspace_w
|
||||
.set_enabled(Feature::GuardianApproval, /*enabled*/ true);
|
||||
|
||||
let extra_root = test_path_buf("/tmp/guardian-approvals-extra").abs();
|
||||
let cwd = test_project_path().abs();
|
||||
let permission_profile = PermissionProfile::from_legacy_sandbox_policy_for_cwd(
|
||||
&SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![extra_root],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
},
|
||||
cwd.as_path(),
|
||||
);
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "session-configured-custom-workspace".to_string(),
|
||||
@@ -531,14 +540,8 @@ async fn permissions_selection_marks_auto_review_current_with_custom_workspace_w
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: ApprovalsReviewer::AutoReview,
|
||||
sandbox_policy: SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![extra_root],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
},
|
||||
permission_profile: None,
|
||||
cwd: test_project_path().abs(),
|
||||
permission_profile,
|
||||
cwd,
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
history_entry_count: 0,
|
||||
|
||||
@@ -1059,8 +1059,7 @@ async fn submit_user_message_emits_structured_plugin_mentions_from_bindings() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
@@ -1305,8 +1304,7 @@ async fn plan_slash_command_with_args_submits_prompt_in_plan_mode() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -1741,8 +1741,7 @@ async fn session_configured_clears_goal_status_footer() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
reasoning_effort: Some(ReasoningEffortConfig::default()),
|
||||
history_log_id: 0,
|
||||
|
||||
@@ -54,6 +54,7 @@ use codex_protocol::account::PlanType;
|
||||
use codex_protocol::mcp::Resource;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::mcp::ResourceTemplate;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::WebSearchAction;
|
||||
use codex_protocol::models::local_image_label_text;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
@@ -64,7 +65,6 @@ use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::FileChange;
|
||||
use codex_protocol::protocol::McpAuthStatus;
|
||||
use codex_protocol::protocol::McpInvocation;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
use codex_protocol::request_user_input::RequestUserInputAnswer;
|
||||
use codex_protocol::request_user_input::RequestUserInputQuestion;
|
||||
@@ -1240,7 +1240,7 @@ pub(crate) fn new_session_info(
|
||||
model,
|
||||
reasoning_effort,
|
||||
approval_policy,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
..
|
||||
} = event;
|
||||
// Header box rendered as history (so it appears at the very top)
|
||||
@@ -1251,7 +1251,7 @@ pub(crate) fn new_session_info(
|
||||
config.cwd.to_path_buf(),
|
||||
CODEX_CLI_VERSION,
|
||||
)
|
||||
.with_yolo_mode(has_yolo_permissions(approval_policy, &sandbox_policy));
|
||||
.with_yolo_mode(has_yolo_permissions(approval_policy, &permission_profile));
|
||||
let mut parts: Vec<Box<dyn HistoryCell>> = vec![Box::new(header)];
|
||||
|
||||
if is_first_event {
|
||||
@@ -1313,14 +1313,16 @@ pub(crate) fn new_session_info(
|
||||
pub(crate) fn is_yolo_mode(config: &Config) -> bool {
|
||||
has_yolo_permissions(
|
||||
config.permissions.approval_policy.value(),
|
||||
&config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
&config.permissions.permission_profile(),
|
||||
)
|
||||
}
|
||||
|
||||
fn has_yolo_permissions(approval_policy: AskForApproval, sandbox_policy: &SandboxPolicy) -> bool {
|
||||
approval_policy == AskForApproval::Never && *sandbox_policy == SandboxPolicy::DangerFullAccess
|
||||
fn has_yolo_permissions(
|
||||
approval_policy: AskForApproval,
|
||||
permission_profile: &PermissionProfile,
|
||||
) -> bool {
|
||||
approval_policy == AskForApproval::Never
|
||||
&& matches!(permission_profile, PermissionProfile::Disabled)
|
||||
}
|
||||
|
||||
pub(crate) fn new_user_prompt(
|
||||
@@ -2993,7 +2995,6 @@ mod tests {
|
||||
use codex_protocol::parse_command::ParsedCommand;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::McpAuthStatus;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionConfiguredEvent;
|
||||
use dirs::home_dir;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -3184,8 +3185,7 @@ mod tests {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
permission_profile: None,
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
cwd: test_path_buf("/tmp/project").abs(),
|
||||
reasoning_effort: None,
|
||||
history_log_id: 0,
|
||||
|
||||
Reference in New Issue
Block a user