mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
tui: pass active permission profiles through app commands
This commit is contained in:
@@ -140,6 +140,8 @@ use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::Personality;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ModelAvailabilityNux;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
@@ -327,6 +329,7 @@ fn default_exec_approval_decisions(
|
||||
struct AutoReviewMode {
|
||||
approval_policy: AskForApproval,
|
||||
approvals_reviewer: ApprovalsReviewer,
|
||||
active_permission_profile: ActivePermissionProfile,
|
||||
permission_profile: PermissionProfile,
|
||||
}
|
||||
|
||||
@@ -338,6 +341,9 @@ fn auto_review_mode() -> AutoReviewMode {
|
||||
AutoReviewMode {
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: ApprovalsReviewer::AutoReview,
|
||||
active_permission_profile: ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
),
|
||||
permission_profile: PermissionProfile::workspace_write(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,16 +105,20 @@ impl App {
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn try_set_permission_profile_on_config(
|
||||
pub(super) fn try_set_permission_profile_snapshot_on_config(
|
||||
&mut self,
|
||||
config: &mut Config,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
user_message_prefix: &str,
|
||||
log_message: &str,
|
||||
) -> bool {
|
||||
if let Err(err) = config
|
||||
.permissions
|
||||
.set_permission_profile(permission_profile)
|
||||
.set_permission_profile_from_session_snapshot(
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
)
|
||||
{
|
||||
tracing::warn!(error = %err, "{log_message}");
|
||||
self.chat_widget
|
||||
@@ -149,6 +153,7 @@ impl App {
|
||||
let mut approval_policy_override = None;
|
||||
let mut approvals_reviewer_override = None;
|
||||
let mut permission_profile_override = None;
|
||||
let mut active_permission_profile_override = None;
|
||||
let mut feature_updates_to_apply = Vec::with_capacity(updates.len());
|
||||
// Auto-Review owns `approvals_reviewer`, but disabling the feature
|
||||
// from inside a profile should not silently clear a value configured at
|
||||
@@ -240,9 +245,10 @@ impl App {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if !self.try_set_permission_profile_on_config(
|
||||
if !self.try_set_permission_profile_snapshot_on_config(
|
||||
&mut feature_config,
|
||||
auto_review_preset.permission_profile.clone(),
|
||||
Some(auto_review_preset.active_permission_profile.clone()),
|
||||
"Failed to enable Auto-review",
|
||||
"failed to set auto-review permission profile on staged config",
|
||||
) {
|
||||
@@ -260,6 +266,8 @@ impl App {
|
||||
]);
|
||||
approval_policy_override = Some(auto_review_preset.approval_policy);
|
||||
permission_profile_override = Some(auto_review_preset.permission_profile.clone());
|
||||
active_permission_profile_override =
|
||||
Some(auto_review_preset.active_permission_profile.clone());
|
||||
}
|
||||
next_config = feature_config;
|
||||
feature_updates_to_apply.push((feature, effective_enabled));
|
||||
@@ -305,7 +313,10 @@ impl App {
|
||||
if let Some(permission_profile) = permission_profile_override_value.as_ref()
|
||||
&& let Err(err) = self
|
||||
.chat_widget
|
||||
.set_permission_profile(permission_profile.clone())
|
||||
.set_permission_profile_from_session_snapshot(
|
||||
permission_profile.clone(),
|
||||
active_permission_profile_override.clone(),
|
||||
)
|
||||
{
|
||||
tracing::error!(
|
||||
error = %err,
|
||||
@@ -333,7 +344,7 @@ impl App {
|
||||
/*cwd*/ None,
|
||||
approval_policy_override,
|
||||
approvals_reviewer_override,
|
||||
permission_profile_override,
|
||||
active_permission_profile_override,
|
||||
/*windows_sandbox_level*/ None,
|
||||
/*model*/ None,
|
||||
/*effort*/ None,
|
||||
@@ -360,7 +371,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),
|
||||
/*model*/ None,
|
||||
|
||||
@@ -1111,7 +1111,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),
|
||||
/*model*/ None,
|
||||
@@ -1136,7 +1136,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),
|
||||
/*model*/ None,
|
||||
@@ -1150,9 +1150,12 @@ impl App {
|
||||
self.app_event_tx.send(AppEvent::UpdateAskForApprovalPolicy(
|
||||
AskForApproval::from(preset.approval),
|
||||
));
|
||||
self.app_event_tx.send(AppEvent::UpdatePermissionProfile(
|
||||
preset.permission_profile.clone(),
|
||||
));
|
||||
self.app_event_tx.send(AppEvent::UpdatePermissionProfile {
|
||||
permission_profile: preset.permission_profile.clone(),
|
||||
active_permission_profile: Some(
|
||||
preset.active_permission_profile.clone(),
|
||||
),
|
||||
});
|
||||
let _ = mode;
|
||||
self.chat_widget.add_plain_history_lines(vec![
|
||||
Line::from(vec!["• ".dim(), "Sandbox ready".into()]),
|
||||
@@ -1400,16 +1403,20 @@ impl App {
|
||||
self.sync_active_thread_permission_settings_to_cached_session()
|
||||
.await;
|
||||
}
|
||||
AppEvent::UpdatePermissionProfile(permission_profile) => {
|
||||
AppEvent::UpdatePermissionProfile {
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
} => {
|
||||
#[cfg(target_os = "windows")]
|
||||
let permission_profile_is_managed_restricted =
|
||||
managed_filesystem_sandbox_is_restricted(&permission_profile);
|
||||
let permission_profile_for_chat = permission_profile.clone();
|
||||
|
||||
let mut config = self.config.clone();
|
||||
if !self.try_set_permission_profile_on_config(
|
||||
if !self.try_set_permission_profile_snapshot_on_config(
|
||||
&mut config,
|
||||
permission_profile,
|
||||
active_permission_profile.clone(),
|
||||
"Failed to set permission profile",
|
||||
"failed to set permission profile on app config",
|
||||
) {
|
||||
@@ -1418,7 +1425,10 @@ impl App {
|
||||
self.config = config;
|
||||
if let Err(err) = self
|
||||
.chat_widget
|
||||
.set_permission_profile(permission_profile_for_chat)
|
||||
.set_permission_profile_from_session_snapshot(
|
||||
permission_profile_for_chat,
|
||||
active_permission_profile,
|
||||
)
|
||||
{
|
||||
tracing::warn!(%err, "failed to set permission profile on chat config");
|
||||
self.chat_widget
|
||||
|
||||
@@ -1642,6 +1642,17 @@ async fn update_feature_flags_enabling_guardian_selects_auto_review() -> Result<
|
||||
.permission_profile(),
|
||||
&auto_review.permission_profile
|
||||
);
|
||||
assert_eq!(
|
||||
app.config.permissions.active_permission_profile(),
|
||||
Some(auto_review.active_permission_profile.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
app.chat_widget
|
||||
.config_ref()
|
||||
.permissions
|
||||
.active_permission_profile(),
|
||||
Some(auto_review.active_permission_profile.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
app.chat_widget.config_ref().approvals_reviewer,
|
||||
auto_review.approvals_reviewer
|
||||
@@ -1657,7 +1668,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.clone()),
|
||||
active_permission_profile: Some(auto_review.active_permission_profile.clone()),
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
@@ -1747,7 +1758,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,
|
||||
effort: None,
|
||||
@@ -1825,7 +1836,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.clone()),
|
||||
active_permission_profile: Some(auto_review.active_permission_profile.clone()),
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
@@ -1882,7 +1893,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,
|
||||
effort: None,
|
||||
@@ -1941,7 +1952,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.clone()),
|
||||
active_permission_profile: Some(auto_review.active_permission_profile.clone()),
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
@@ -2028,7 +2039,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,
|
||||
effort: None,
|
||||
|
||||
@@ -510,7 +510,7 @@ impl App {
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
model,
|
||||
effort,
|
||||
summary,
|
||||
@@ -590,7 +590,7 @@ impl App {
|
||||
approvals_reviewer.unwrap_or(config.approvals_reviewer);
|
||||
let permissions_override = Self::turn_permissions_override_from_config(
|
||||
config,
|
||||
permission_profile,
|
||||
active_permission_profile.as_ref(),
|
||||
self.runtime_permission_profile_override.as_ref(),
|
||||
);
|
||||
app_server
|
||||
@@ -698,16 +698,14 @@ impl App {
|
||||
|
||||
fn turn_permissions_override_from_config(
|
||||
config: &Config,
|
||||
permission_profile: &PermissionProfile,
|
||||
active_permission_profile: Option<&ActivePermissionProfile>,
|
||||
runtime_permission_profile_override: Option<&PermissionProfile>,
|
||||
) -> TurnPermissionsOverride {
|
||||
let effective_permission_profile = config.permissions.effective_permission_profile();
|
||||
if &effective_permission_profile == permission_profile
|
||||
&& let Some(active_permission_profile) = config.permissions.active_permission_profile()
|
||||
{
|
||||
return TurnPermissionsOverride::ActiveProfile(active_permission_profile);
|
||||
if let Some(active_permission_profile) = active_permission_profile {
|
||||
return TurnPermissionsOverride::ActiveProfile(active_permission_profile.clone());
|
||||
}
|
||||
|
||||
let effective_permission_profile = config.permissions.effective_permission_profile();
|
||||
let runtime_permission_profile_override =
|
||||
runtime_permission_profile_override.map(|profile| {
|
||||
profile
|
||||
@@ -718,9 +716,9 @@ impl App {
|
||||
});
|
||||
if runtime_permission_profile_override
|
||||
.as_ref()
|
||||
.is_some_and(|profile| profile == permission_profile)
|
||||
.is_some_and(|profile| profile == &effective_permission_profile)
|
||||
{
|
||||
return TurnPermissionsOverride::LegacySandbox(permission_profile.clone());
|
||||
return TurnPermissionsOverride::LegacySandbox(effective_permission_profile);
|
||||
}
|
||||
|
||||
TurnPermissionsOverride::Preserve
|
||||
@@ -1506,12 +1504,12 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn turn_permissions_use_active_profile_when_available() {
|
||||
let config = config_with_workspace_profile().await;
|
||||
let permission_profile = config.permissions.effective_permission_profile();
|
||||
let active_permission_profile = config.permissions.active_permission_profile();
|
||||
|
||||
assert_eq!(
|
||||
App::turn_permissions_override_from_config(
|
||||
&config,
|
||||
&permission_profile,
|
||||
active_permission_profile.as_ref(),
|
||||
/*runtime_permission_profile_override*/ None,
|
||||
),
|
||||
TurnPermissionsOverride::ActiveProfile(ActivePermissionProfile::new(
|
||||
@@ -1527,12 +1525,10 @@ mod tests {
|
||||
.permissions
|
||||
.set_permission_profile(PermissionProfile::read_only())
|
||||
.expect("read-only profile should be allowed");
|
||||
let permission_profile = config.permissions.effective_permission_profile();
|
||||
|
||||
assert_eq!(
|
||||
App::turn_permissions_override_from_config(
|
||||
&config,
|
||||
&permission_profile,
|
||||
&config, /*active_permission_profile*/ None,
|
||||
/*runtime_permission_profile_override*/ None,
|
||||
),
|
||||
TurnPermissionsOverride::Preserve
|
||||
@@ -1552,7 +1548,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
App::turn_permissions_override_from_config(
|
||||
&config,
|
||||
&effective_permission_profile,
|
||||
/*active_permission_profile*/ None,
|
||||
Some(&permission_profile),
|
||||
),
|
||||
TurnPermissionsOverride::LegacySandbox(effective_permission_profile)
|
||||
|
||||
@@ -16,7 +16,7 @@ use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use serde::Serialize;
|
||||
@@ -41,7 +41,7 @@ pub(crate) enum AppCommand {
|
||||
cwd: PathBuf,
|
||||
approval_policy: AskForApproval,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
model: String,
|
||||
effort: Option<ReasoningEffortConfig>,
|
||||
summary: Option<ReasoningSummaryConfig>,
|
||||
@@ -54,7 +54,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>,
|
||||
effort: Option<Option<ReasoningEffortConfig>>,
|
||||
@@ -142,7 +142,7 @@ impl AppCommand {
|
||||
items: Vec<UserInput>,
|
||||
cwd: PathBuf,
|
||||
approval_policy: AskForApproval,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
model: String,
|
||||
effort: Option<ReasoningEffortConfig>,
|
||||
summary: Option<ReasoningSummaryConfig>,
|
||||
@@ -156,7 +156,7 @@ impl AppCommand {
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer: None,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
model,
|
||||
effort,
|
||||
summary,
|
||||
@@ -172,7 +172,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>,
|
||||
effort: Option<Option<ReasoningEffortConfig>>,
|
||||
@@ -185,7 +185,7 @@ impl AppCommand {
|
||||
cwd,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
model,
|
||||
effort,
|
||||
|
||||
@@ -43,6 +43,7 @@ use codex_features::Feature;
|
||||
use codex_plugin::PluginCapabilitySummary;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_realtime_webrtc::RealtimeWebrtcEvent;
|
||||
@@ -743,7 +744,10 @@ pub(crate) enum AppEvent {
|
||||
UpdateAskForApprovalPolicy(AskForApproval),
|
||||
|
||||
/// Update the current permission profile in the running app and widget.
|
||||
UpdatePermissionProfile(PermissionProfile),
|
||||
UpdatePermissionProfile {
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
},
|
||||
|
||||
/// Update the current approvals reviewer in the running app and widget.
|
||||
UpdateApprovalsReviewer(ApprovalsReviewer),
|
||||
|
||||
@@ -442,6 +442,7 @@ use crate::workspace_command::WorkspaceCommandRunner;
|
||||
use chrono::Local;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_file_search::FileMatch;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
|
||||
@@ -334,12 +334,12 @@ impl ChatWidget {
|
||||
None if self.config.notices.fast_default_opt_out == Some(true) => Some(None),
|
||||
None => None,
|
||||
};
|
||||
let permission_profile = self.config.permissions.effective_permission_profile();
|
||||
let active_permission_profile = self.config.permissions.active_permission_profile();
|
||||
let op = AppCommand::user_turn(
|
||||
items,
|
||||
self.config.cwd.to_path_buf(),
|
||||
AskForApproval::from(self.config.permissions.approval_policy.value()),
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
effective_mode.model().to_string(),
|
||||
effective_mode.reasoning_effort(),
|
||||
/*summary*/ None,
|
||||
|
||||
@@ -125,6 +125,7 @@ impl ChatWidget {
|
||||
Self::approval_preset_actions(
|
||||
preset_approval,
|
||||
preset.permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
base_name.clone(),
|
||||
ApprovalsReviewer::User,
|
||||
)
|
||||
@@ -135,6 +136,7 @@ impl ChatWidget {
|
||||
Self::approval_preset_actions(
|
||||
preset_approval,
|
||||
preset.permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
base_name.clone(),
|
||||
ApprovalsReviewer::User,
|
||||
)
|
||||
@@ -143,6 +145,7 @@ impl ChatWidget {
|
||||
Self::approval_preset_actions(
|
||||
preset_approval,
|
||||
preset.permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
base_name.clone(),
|
||||
ApprovalsReviewer::User,
|
||||
)
|
||||
@@ -181,6 +184,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,
|
||||
),
|
||||
@@ -309,6 +313,7 @@ impl ChatWidget {
|
||||
pub(super) fn approval_preset_actions(
|
||||
approval: AskForApproval,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: ActivePermissionProfile,
|
||||
label: String,
|
||||
approvals_reviewer: ApprovalsReviewer,
|
||||
) -> Vec<SelectionAction> {
|
||||
@@ -318,7 +323,7 @@ impl ChatWidget {
|
||||
/*cwd*/ None,
|
||||
Some(approval),
|
||||
Some(approvals_reviewer),
|
||||
Some(permission_profile_clone.clone()),
|
||||
Some(active_permission_profile.clone()),
|
||||
/*windows_sandbox_level*/ None,
|
||||
/*model*/ None,
|
||||
/*effort*/ None,
|
||||
@@ -328,7 +333,10 @@ impl ChatWidget {
|
||||
/*personality*/ None,
|
||||
)));
|
||||
tx.send(AppEvent::UpdateAskForApprovalPolicy(approval));
|
||||
tx.send(AppEvent::UpdatePermissionProfile(permission_profile_clone));
|
||||
tx.send(AppEvent::UpdatePermissionProfile {
|
||||
permission_profile: permission_profile_clone,
|
||||
active_permission_profile: Some(active_permission_profile.clone()),
|
||||
});
|
||||
tx.send(AppEvent::UpdateApprovalsReviewer(approvals_reviewer));
|
||||
tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
history_cell::new_info_event(
|
||||
@@ -403,6 +411,7 @@ impl ChatWidget {
|
||||
let mut accept_actions = Self::approval_preset_actions(
|
||||
approval,
|
||||
permission_profile.clone(),
|
||||
preset.active_permission_profile.clone(),
|
||||
selected_name.clone(),
|
||||
ApprovalsReviewer::User,
|
||||
);
|
||||
@@ -413,6 +422,7 @@ impl ChatWidget {
|
||||
let mut accept_and_remember_actions = Self::approval_preset_actions(
|
||||
approval,
|
||||
permission_profile,
|
||||
preset.active_permission_profile,
|
||||
selected_name,
|
||||
ApprovalsReviewer::User,
|
||||
);
|
||||
|
||||
@@ -285,7 +285,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()),
|
||||
Some(Some(default_effort)),
|
||||
|
||||
@@ -107,7 +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,
|
||||
/*effort*/ None,
|
||||
|
||||
@@ -23,7 +23,18 @@ impl ChatWidget {
|
||||
&mut self,
|
||||
profile: PermissionProfile,
|
||||
) -> ConstraintResult<()> {
|
||||
self.config.permissions.set_permission_profile(profile)?;
|
||||
self.set_permission_profile_from_session_snapshot(profile, /*active_profile*/ None)
|
||||
}
|
||||
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
pub(crate) fn set_permission_profile_from_session_snapshot(
|
||||
&mut self,
|
||||
profile: PermissionProfile,
|
||||
active_profile: Option<ActivePermissionProfile>,
|
||||
) -> ConstraintResult<()> {
|
||||
self.config
|
||||
.permissions
|
||||
.set_permission_profile_from_session_snapshot(profile, active_profile)?;
|
||||
self.refresh_status_surfaces();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -53,7 +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,
|
||||
/*effort*/ None,
|
||||
|
||||
@@ -149,6 +149,8 @@ pub(super) use codex_protocol::config_types::CollaborationMode;
|
||||
pub(super) use codex_protocol::config_types::ModeKind;
|
||||
pub(super) use codex_protocol::config_types::Personality;
|
||||
pub(super) use codex_protocol::config_types::ServiceTier;
|
||||
pub(super) use codex_protocol::models::ActivePermissionProfile;
|
||||
pub(super) use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
pub(super) use codex_protocol::models::FileSystemPermissions;
|
||||
pub(super) use codex_protocol::models::MessagePhase;
|
||||
pub(super) use codex_protocol::models::NetworkPermissions;
|
||||
|
||||
@@ -93,7 +93,7 @@ async fn submission_preserves_text_elements_and_local_images() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn submission_includes_configured_permission_profile() {
|
||||
async fn submission_includes_configured_active_permission_profile() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
let thread_id = ThreadId::new();
|
||||
@@ -119,6 +119,7 @@ async fn submission_includes_configured_permission_profile() {
|
||||
},
|
||||
}
|
||||
.into();
|
||||
let expected_active_permission_profile = ActivePermissionProfile::new("custom");
|
||||
let configured = crate::session_state::ThreadSessionState {
|
||||
thread_id,
|
||||
forked_from_id: None,
|
||||
@@ -129,8 +130,8 @@ async fn submission_includes_configured_permission_profile() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: expected_permission_profile.clone(),
|
||||
active_permission_profile: None,
|
||||
permission_profile: expected_permission_profile,
|
||||
active_permission_profile: Some(expected_active_permission_profile.clone()),
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
runtime_workspace_roots: Vec::new(),
|
||||
instruction_source_paths: Vec::new(),
|
||||
@@ -149,17 +150,21 @@ async fn submission_includes_configured_permission_profile() {
|
||||
);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
let permission_profile = match next_submit_op(&mut op_rx) {
|
||||
let active_permission_profile = match next_submit_op(&mut op_rx) {
|
||||
Op::UserTurn {
|
||||
permission_profile, ..
|
||||
} => permission_profile,
|
||||
active_permission_profile,
|
||||
..
|
||||
} => active_permission_profile,
|
||||
other => panic!("expected Op::UserTurn, got {other:?}"),
|
||||
};
|
||||
assert_eq!(permission_profile, expected_permission_profile);
|
||||
assert_eq!(
|
||||
active_permission_profile,
|
||||
Some(expected_active_permission_profile)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn submission_keeps_profile_when_legacy_projection_is_external() {
|
||||
async fn submission_omits_active_permission_profile_for_legacy_snapshot() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
let thread_id = ThreadId::new();
|
||||
@@ -179,7 +184,7 @@ async fn submission_keeps_profile_when_legacy_projection_is_external() {
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::Never,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: expected_permission_profile.clone(),
|
||||
permission_profile: expected_permission_profile,
|
||||
active_permission_profile: None,
|
||||
cwd: test_path_buf("/home/user/project").abs(),
|
||||
runtime_workspace_roots: Vec::new(),
|
||||
@@ -196,13 +201,14 @@ async fn submission_keeps_profile_when_legacy_projection_is_external() {
|
||||
.set_composer_text("submit".to_string(), Vec::new(), Vec::new());
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
|
||||
let permission_profile = match next_submit_op(&mut op_rx) {
|
||||
let active_permission_profile = match next_submit_op(&mut op_rx) {
|
||||
Op::UserTurn {
|
||||
permission_profile, ..
|
||||
} => permission_profile,
|
||||
active_permission_profile,
|
||||
..
|
||||
} => active_permission_profile,
|
||||
other => panic!("expected Op::UserTurn, got {other:?}"),
|
||||
};
|
||||
assert_eq!(permission_profile, expected_permission_profile);
|
||||
assert_eq!(active_permission_profile, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -742,7 +742,9 @@ 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,
|
||||
)),
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
@@ -752,6 +754,26 @@ async fn permissions_selection_sends_approvals_reviewer_in_override_turn_context
|
||||
personality: None,
|
||||
}
|
||||
);
|
||||
|
||||
let permission_profile_update = std::iter::from_fn(|| rx.try_recv().ok())
|
||||
.find_map(|event| match event {
|
||||
AppEvent::UpdatePermissionProfile {
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
} => Some((permission_profile, active_permission_profile)),
|
||||
_ => None,
|
||||
})
|
||||
.expect("expected UpdatePermissionProfile event");
|
||||
|
||||
assert_eq!(
|
||||
permission_profile_update,
|
||||
(
|
||||
PermissionProfile::workspace_write(),
|
||||
Some(ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -42,12 +42,13 @@ impl ChatWidget {
|
||||
extra_count: usize,
|
||||
failed_scan: bool,
|
||||
) {
|
||||
let (approval, 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,10 +111,15 @@ impl ChatWidget {
|
||||
tx.send(AppEvent::SkipNextWorldWritableScan);
|
||||
}));
|
||||
}
|
||||
if let (Some(approval), Some(permission_profile)) = (approval, 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,
|
||||
));
|
||||
@@ -124,10 +130,13 @@ impl ChatWidget {
|
||||
tx.send(AppEvent::UpdateWorldWritableWarningAcknowledged(true));
|
||||
tx.send(AppEvent::PersistWorldWritableWarningAcknowledged);
|
||||
}));
|
||||
if let (Some(approval), Some(permission_profile)) = (approval, 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,
|
||||
));
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY;
|
||||
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
|
||||
@@ -12,6 +16,8 @@ pub struct ApprovalPreset {
|
||||
pub description: &'static str,
|
||||
/// Approval policy to apply.
|
||||
pub approval: AskForApproval,
|
||||
/// Built-in permission profile selected by this preset.
|
||||
pub active_permission_profile: ActivePermissionProfile,
|
||||
/// Permission profile to apply.
|
||||
pub permission_profile: PermissionProfile,
|
||||
}
|
||||
@@ -26,6 +32,9 @@ pub fn builtin_approval_presets() -> Vec<ApprovalPreset> {
|
||||
label: "Read Only",
|
||||
description: "Codex can read files in the current workspace. Approval is required to edit files or access the internet.",
|
||||
approval: AskForApproval::OnRequest,
|
||||
active_permission_profile: ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_READ_ONLY,
|
||||
),
|
||||
permission_profile: PermissionProfile::read_only(),
|
||||
},
|
||||
ApprovalPreset {
|
||||
@@ -33,6 +42,9 @@ pub fn builtin_approval_presets() -> Vec<ApprovalPreset> {
|
||||
label: "Default",
|
||||
description: "Codex can read and edit files in the current workspace, and run commands. Approval is required to access the internet or edit other files. (Identical to Agent mode)",
|
||||
approval: AskForApproval::OnRequest,
|
||||
active_permission_profile: ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_WORKSPACE,
|
||||
),
|
||||
permission_profile: PermissionProfile::workspace_write(),
|
||||
},
|
||||
ApprovalPreset {
|
||||
@@ -40,6 +52,9 @@ pub fn builtin_approval_presets() -> Vec<ApprovalPreset> {
|
||||
label: "Full Access",
|
||||
description: "Codex can edit files outside this workspace and access the internet without asking for approval. Exercise caution when using.",
|
||||
approval: AskForApproval::Never,
|
||||
active_permission_profile: ActivePermissionProfile::new(
|
||||
BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS,
|
||||
),
|
||||
permission_profile: PermissionProfile::Disabled,
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user