mirror of
https://github.com/openai/codex.git
synced 2026-04-26 09:21:02 +03:00
Compare commits
4 Commits
dev/friel/
...
dh--tui-cu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db3b2b286a | ||
|
|
4dd25dda04 | ||
|
|
3399b9645c | ||
|
|
f45a4bfce1 |
8
codex-rs/Cargo.lock
generated
8
codex-rs/Cargo.lock
generated
@@ -2290,7 +2290,6 @@ dependencies = [
|
||||
"codex-protocol",
|
||||
"codex-state",
|
||||
"codex-utils-absolute-path",
|
||||
"codex-utils-approval-presets",
|
||||
"codex-utils-cargo-bin",
|
||||
"codex-utils-cli",
|
||||
"codex-utils-elapsed",
|
||||
@@ -2364,13 +2363,6 @@ dependencies = [
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-utils-approval-presets"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-utils-cache"
|
||||
version = "0.0.0"
|
||||
|
||||
@@ -55,7 +55,6 @@ members = [
|
||||
"utils/sandbox-summary",
|
||||
"utils/sanitizer",
|
||||
"utils/sleep-inhibitor",
|
||||
"utils/approval-presets",
|
||||
"utils/oss",
|
||||
"utils/fuzzy-match",
|
||||
"codex-client",
|
||||
@@ -117,7 +116,6 @@ codex-state = { path = "state" }
|
||||
codex-stdio-to-uds = { path = "stdio-to-uds" }
|
||||
codex-tui = { path = "tui" }
|
||||
codex-utils-absolute-path = { path = "utils/absolute-path" }
|
||||
codex-utils-approval-presets = { path = "utils/approval-presets" }
|
||||
codex-utils-cache = { path = "utils/cache" }
|
||||
codex-utils-cargo-bin = { path = "utils/cargo-bin" }
|
||||
codex-utils-cli = { path = "utils/cli" }
|
||||
|
||||
@@ -181,10 +181,6 @@ pub struct Config {
|
||||
/// using backend-specific headers or URLs to enforce this.
|
||||
pub enforce_residency: Constrained<Option<ResidencyRequirement>>,
|
||||
|
||||
/// True if the user passed in an override or set a value in config.toml
|
||||
/// for either of approval_policy or sandbox_mode.
|
||||
pub did_user_set_custom_approval_policy_or_sandbox_mode: bool,
|
||||
|
||||
/// When `true`, `AgentReasoning` events emitted by the backend will be
|
||||
/// suppressed from the frontend output. This can reduce visual noise when
|
||||
/// users are only interested in the final agent responses.
|
||||
@@ -1543,9 +1539,6 @@ impl Config {
|
||||
let active_project = cfg
|
||||
.get_active_project(&resolved_cwd)
|
||||
.unwrap_or(ProjectConfig { trust_level: None });
|
||||
let sandbox_mode_was_explicit = sandbox_mode.is_some()
|
||||
|| config_profile.sandbox_mode.is_some()
|
||||
|| cfg.sandbox_mode.is_some();
|
||||
|
||||
let windows_sandbox_level = match windows_sandbox_mode {
|
||||
Some(WindowsSandboxModeToml::Elevated) => WindowsSandboxLevel::Elevated,
|
||||
@@ -1566,9 +1559,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
}
|
||||
let approval_policy_was_explicit = approval_policy_override.is_some()
|
||||
|| config_profile.approval_policy.is_some()
|
||||
|| cfg.approval_policy.is_some();
|
||||
let mut approval_policy = approval_policy_override
|
||||
.or(config_profile.approval_policy)
|
||||
.or(cfg.approval_policy)
|
||||
@@ -1581,9 +1571,7 @@ impl Config {
|
||||
AskForApproval::default()
|
||||
}
|
||||
});
|
||||
if !approval_policy_was_explicit
|
||||
&& let Err(err) = requirements.approval_policy.can_set(&approval_policy)
|
||||
{
|
||||
if let Err(err) = requirements.approval_policy.can_set(&approval_policy) {
|
||||
tracing::warn!(
|
||||
error = %err,
|
||||
"default approval policy is disallowed by requirements; falling back to required default"
|
||||
@@ -1592,10 +1580,6 @@ impl Config {
|
||||
}
|
||||
let web_search_mode = resolve_web_search_mode(&cfg, &config_profile, &features)
|
||||
.unwrap_or(WebSearchMode::Cached);
|
||||
// TODO(dylan): We should be able to leverage ConfigLayerStack so that
|
||||
// we can reliably check this at every config level.
|
||||
let did_user_set_custom_approval_policy_or_sandbox_mode =
|
||||
approval_policy_was_explicit || sandbox_mode_was_explicit;
|
||||
|
||||
let mut model_providers = built_in_model_providers();
|
||||
// Merge user-defined providers into the built-in list.
|
||||
@@ -1823,7 +1807,6 @@ impl Config {
|
||||
macos_seatbelt_profile_extensions: None,
|
||||
},
|
||||
enforce_residency: enforce_residency.value,
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode,
|
||||
notify: cfg.notify,
|
||||
user_instructions,
|
||||
base_instructions,
|
||||
@@ -2807,7 +2790,6 @@ profile = "project"
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::DangerFullAccess
|
||||
));
|
||||
assert!(config.did_user_set_custom_approval_policy_or_sandbox_mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -4172,7 +4154,6 @@ model_verbosity = "high"
|
||||
macos_seatbelt_profile_extensions: None,
|
||||
},
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
@@ -4285,7 +4266,6 @@ model_verbosity = "high"
|
||||
macos_seatbelt_profile_extensions: None,
|
||||
},
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
@@ -4396,7 +4376,6 @@ model_verbosity = "high"
|
||||
macos_seatbelt_profile_extensions: None,
|
||||
},
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
@@ -4493,7 +4472,6 @@ model_verbosity = "high"
|
||||
macos_seatbelt_profile_extensions: None,
|
||||
},
|
||||
enforce_residency: Constrained::allow_any(None),
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
user_instructions: None,
|
||||
notify: None,
|
||||
cwd: fixture.cwd(),
|
||||
@@ -4560,24 +4538,6 @@ model_verbosity = "high"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_did_user_set_custom_approval_policy_or_sandbox_mode_defaults_no() -> anyhow::Result<()>
|
||||
{
|
||||
let fixture = create_test_fixture()?;
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
fixture.cfg.clone(),
|
||||
ConfigOverrides {
|
||||
..Default::default()
|
||||
},
|
||||
fixture.codex_home(),
|
||||
)?;
|
||||
|
||||
assert!(config.did_user_set_custom_approval_policy_or_sandbox_mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset() -> anyhow::Result<()>
|
||||
{
|
||||
|
||||
@@ -39,7 +39,6 @@ codex-login = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-state = { workspace = true }
|
||||
codex-utils-approval-presets = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-cli = { workspace = true }
|
||||
codex-utils-elapsed = { workspace = true }
|
||||
|
||||
@@ -1747,12 +1747,8 @@ impl App {
|
||||
AppEvent::OpenAllModelsPopup { models } => {
|
||||
self.chat_widget.open_all_models_popup(models);
|
||||
}
|
||||
AppEvent::OpenFullAccessConfirmation {
|
||||
preset,
|
||||
return_to_permissions,
|
||||
} => {
|
||||
self.chat_widget
|
||||
.open_full_access_confirmation(preset, return_to_permissions);
|
||||
AppEvent::OpenFullAccessConfirmation { preset } => {
|
||||
self.chat_widget.open_full_access_confirmation(preset);
|
||||
}
|
||||
AppEvent::OpenWorldWritableWarningConfirmation {
|
||||
preset,
|
||||
@@ -2358,9 +2354,6 @@ impl App {
|
||||
));
|
||||
}
|
||||
}
|
||||
AppEvent::OpenApprovalsPopup => {
|
||||
self.chat_widget.open_approvals_popup();
|
||||
}
|
||||
AppEvent::OpenAgentPicker => {
|
||||
self.open_agent_picker().await;
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ use codex_core::protocol::RateLimitSnapshot;
|
||||
use codex_file_search::FileMatch;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_utils_approval_presets::ApprovalPreset;
|
||||
|
||||
use crate::bottom_pane::ApprovalRequest;
|
||||
use crate::bottom_pane::StatusLineItem;
|
||||
use crate::history_cell::HistoryCell;
|
||||
use crate::permissions::PermissionsPreset;
|
||||
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
@@ -171,8 +171,7 @@ pub(crate) enum AppEvent {
|
||||
|
||||
/// Open the confirmation prompt before enabling full access mode.
|
||||
OpenFullAccessConfirmation {
|
||||
preset: ApprovalPreset,
|
||||
return_to_permissions: bool,
|
||||
preset: PermissionsPreset,
|
||||
},
|
||||
|
||||
/// Open the Windows world-writable directories warning.
|
||||
@@ -181,7 +180,7 @@ pub(crate) enum AppEvent {
|
||||
/// policy change and only acknowledges/dismisses the warning.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
OpenWorldWritableWarningConfirmation {
|
||||
preset: Option<ApprovalPreset>,
|
||||
preset: Option<PermissionsPreset>,
|
||||
/// Up to 3 sample world-writable directories to display in the warning.
|
||||
sample_paths: Vec<String>,
|
||||
/// If there are more than `sample_paths`, this carries the remaining count.
|
||||
@@ -193,25 +192,25 @@ pub(crate) enum AppEvent {
|
||||
/// Prompt to enable the Windows sandbox feature before using Agent mode.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
OpenWindowsSandboxEnablePrompt {
|
||||
preset: ApprovalPreset,
|
||||
preset: PermissionsPreset,
|
||||
},
|
||||
|
||||
/// Open the Windows sandbox fallback prompt after declining or failing elevation.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
OpenWindowsSandboxFallbackPrompt {
|
||||
preset: ApprovalPreset,
|
||||
preset: PermissionsPreset,
|
||||
},
|
||||
|
||||
/// Begin the elevated Windows sandbox setup flow.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
BeginWindowsSandboxElevatedSetup {
|
||||
preset: ApprovalPreset,
|
||||
preset: PermissionsPreset,
|
||||
},
|
||||
|
||||
/// Begin the non-elevated Windows sandbox setup flow.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
BeginWindowsSandboxLegacySetup {
|
||||
preset: ApprovalPreset,
|
||||
preset: PermissionsPreset,
|
||||
},
|
||||
|
||||
/// Begin a non-elevated grant of read access for an additional directory.
|
||||
@@ -230,7 +229,7 @@ pub(crate) enum AppEvent {
|
||||
/// Enable the Windows sandbox feature and switch to Agent mode.
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
EnableWindowsSandboxForAgentMode {
|
||||
preset: ApprovalPreset,
|
||||
preset: PermissionsPreset,
|
||||
mode: WindowsSandboxEnableMode,
|
||||
},
|
||||
|
||||
@@ -278,9 +277,6 @@ pub(crate) enum AppEvent {
|
||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||
SkipNextWorldWritableScan,
|
||||
|
||||
/// Re-open the approval presets popup.
|
||||
OpenApprovalsPopup,
|
||||
|
||||
/// Open the skills list popup.
|
||||
OpenSkillsList,
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ use std::time::Instant;
|
||||
|
||||
use crate::bottom_pane::StatusLineItem;
|
||||
use crate::bottom_pane::StatusLineSetupView;
|
||||
use crate::permissions::PermissionsPreset;
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::permissions::builtin_permissions_presets;
|
||||
use crate::permissions::visible_permissions_options;
|
||||
use crate::permissions::windows_degraded_sandbox_enabled;
|
||||
use crate::status::RateLimitWindowDisplay;
|
||||
use crate::status::format_directory_display;
|
||||
use crate::status::format_tokens_compact;
|
||||
@@ -242,8 +247,6 @@ use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
use codex_utils_approval_presets::ApprovalPreset;
|
||||
use codex_utils_approval_presets::builtin_approval_presets;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
const USER_SHELL_COMMAND_HELP_TITLE: &str = "Prefix a command with ! to run it locally";
|
||||
@@ -3338,7 +3341,7 @@ impl ChatWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(preset) = builtin_approval_presets()
|
||||
let Some(preset) = builtin_permissions_presets()
|
||||
.into_iter()
|
||||
.find(|preset| preset.id == "auto")
|
||||
else {
|
||||
@@ -5328,132 +5331,16 @@ impl ChatWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Open the permissions popup (alias for /permissions).
|
||||
pub(crate) fn open_approvals_popup(&mut self) {
|
||||
self.open_permissions_popup();
|
||||
}
|
||||
|
||||
/// Open a popup to choose the permissions mode (approval policy + sandbox policy).
|
||||
pub(crate) fn open_permissions_popup(&mut self) {
|
||||
let include_read_only = cfg!(target_os = "windows");
|
||||
let current_approval = self.config.permissions.approval_policy.value();
|
||||
let current_sandbox = self.config.permissions.sandbox_policy.get();
|
||||
let mut items: Vec<SelectionItem> = Vec::new();
|
||||
let presets: Vec<ApprovalPreset> = builtin_approval_presets();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let windows_sandbox_level = WindowsSandboxLevel::from_config(&self.config);
|
||||
#[cfg(target_os = "windows")]
|
||||
let windows_degraded_sandbox_enabled =
|
||||
matches!(windows_sandbox_level, WindowsSandboxLevel::RestrictedToken);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let windows_degraded_sandbox_enabled = false;
|
||||
let items: Vec<SelectionItem> = visible_permissions_options(&self.config);
|
||||
|
||||
let windows_degraded_sandbox_enabled = windows_degraded_sandbox_enabled(&self.config);
|
||||
let show_elevate_sandbox_hint = codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
||||
&& windows_degraded_sandbox_enabled
|
||||
&& presets.iter().any(|preset| preset.id == "auto");
|
||||
|
||||
for preset in presets.into_iter() {
|
||||
if !include_read_only && preset.id == "read-only" {
|
||||
continue;
|
||||
}
|
||||
let is_current =
|
||||
Self::preset_matches_current(current_approval, current_sandbox, &preset);
|
||||
let name = if preset.id == "auto" && windows_degraded_sandbox_enabled {
|
||||
"Default (non-admin sandbox)".to_string()
|
||||
} else {
|
||||
preset.label.to_string()
|
||||
};
|
||||
let description = Some(preset.description.replace(" (Identical to Agent mode)", ""));
|
||||
let disabled_reason = match self
|
||||
.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.can_set(&preset.approval)
|
||||
{
|
||||
Ok(()) => None,
|
||||
Err(err) => Some(err.to_string()),
|
||||
};
|
||||
let requires_confirmation = preset.id == "full-access"
|
||||
&& !self
|
||||
.config
|
||||
.notices
|
||||
.hide_full_access_warning
|
||||
.unwrap_or(false);
|
||||
let actions: Vec<SelectionAction> = if requires_confirmation {
|
||||
let preset_clone = preset.clone();
|
||||
vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::OpenFullAccessConfirmation {
|
||||
preset: preset_clone.clone(),
|
||||
return_to_permissions: !include_read_only,
|
||||
});
|
||||
})]
|
||||
} else if preset.id == "auto" {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if WindowsSandboxLevel::from_config(&self.config)
|
||||
== WindowsSandboxLevel::Disabled
|
||||
{
|
||||
let preset_clone = preset.clone();
|
||||
if codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
||||
&& codex_core::windows_sandbox::sandbox_setup_is_complete(
|
||||
self.config.codex_home.as_path(),
|
||||
)
|
||||
{
|
||||
vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
||||
preset: preset_clone.clone(),
|
||||
mode: WindowsSandboxEnableMode::Elevated,
|
||||
});
|
||||
})]
|
||||
} else {
|
||||
vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::OpenWindowsSandboxEnablePrompt {
|
||||
preset: preset_clone.clone(),
|
||||
});
|
||||
})]
|
||||
}
|
||||
} else if let Some((sample_paths, extra_count, failed_scan)) =
|
||||
self.world_writable_warning_details()
|
||||
{
|
||||
let preset_clone = preset.clone();
|
||||
vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::OpenWorldWritableWarningConfirmation {
|
||||
preset: Some(preset_clone.clone()),
|
||||
sample_paths: sample_paths.clone(),
|
||||
extra_count,
|
||||
failed_scan,
|
||||
});
|
||||
})]
|
||||
} else {
|
||||
Self::approval_preset_actions(
|
||||
preset.approval,
|
||||
preset.sandbox.clone(),
|
||||
name.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Self::approval_preset_actions(
|
||||
preset.approval,
|
||||
preset.sandbox.clone(),
|
||||
name.clone(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Self::approval_preset_actions(preset.approval, preset.sandbox.clone(), name.clone())
|
||||
};
|
||||
items.push(SelectionItem {
|
||||
name,
|
||||
description,
|
||||
is_current,
|
||||
actions,
|
||||
dismiss_on_select: true,
|
||||
disabled_reason,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
&& items
|
||||
.iter()
|
||||
.any(|item| item.name == "Default (non-admin sandbox)");
|
||||
|
||||
let footer_note = show_elevate_sandbox_hint.then(|| {
|
||||
vec![
|
||||
@@ -5519,14 +5406,6 @@ impl ChatWidget {
|
||||
})]
|
||||
}
|
||||
|
||||
fn preset_matches_current(
|
||||
current_approval: AskForApproval,
|
||||
current_sandbox: &SandboxPolicy,
|
||||
preset: &ApprovalPreset,
|
||||
) -> bool {
|
||||
current_approval == preset.approval && *current_sandbox == preset.sandbox
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn world_writable_warning_details(&self) -> Option<(Vec<String>, usize, bool)> {
|
||||
if self
|
||||
@@ -5557,14 +5436,7 @@ impl ChatWidget {
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn open_full_access_confirmation(
|
||||
&mut self,
|
||||
preset: ApprovalPreset,
|
||||
return_to_permissions: bool,
|
||||
) {
|
||||
let selected_name = preset.label.to_string();
|
||||
let approval = preset.approval;
|
||||
let sandbox = preset.sandbox;
|
||||
pub(crate) fn open_full_access_confirmation(&mut self, preset: PermissionsPreset) {
|
||||
let mut header_children: Vec<Box<dyn Renderable>> = Vec::new();
|
||||
let title_line = Line::from("Enable full access?").bold();
|
||||
let info_line = Line::from(vec![
|
||||
@@ -5579,25 +5451,27 @@ impl ChatWidget {
|
||||
));
|
||||
let header = ColumnRenderable::with(header_children);
|
||||
|
||||
let mut accept_actions =
|
||||
Self::approval_preset_actions(approval, sandbox.clone(), selected_name.clone());
|
||||
let mut accept_actions = Self::approval_preset_actions(
|
||||
preset.approval,
|
||||
preset.sandbox.clone(),
|
||||
preset.label.to_string(),
|
||||
);
|
||||
accept_actions.push(Box::new(|tx| {
|
||||
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
|
||||
}));
|
||||
|
||||
let mut accept_and_remember_actions =
|
||||
Self::approval_preset_actions(approval, sandbox, selected_name);
|
||||
let mut accept_and_remember_actions = Self::approval_preset_actions(
|
||||
preset.approval,
|
||||
preset.sandbox.clone(),
|
||||
preset.label.to_string(),
|
||||
);
|
||||
accept_and_remember_actions.push(Box::new(|tx| {
|
||||
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
|
||||
tx.send(AppEvent::PersistFullAccessWarningAcknowledged);
|
||||
}));
|
||||
|
||||
let deny_actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
|
||||
if return_to_permissions {
|
||||
tx.send(AppEvent::OpenPermissionsPopup);
|
||||
} else {
|
||||
tx.send(AppEvent::OpenApprovalsPopup);
|
||||
}
|
||||
tx.send(AppEvent::OpenPermissionsPopup);
|
||||
})];
|
||||
|
||||
let items = vec![
|
||||
@@ -5635,7 +5509,7 @@ impl ChatWidget {
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn open_world_writable_warning_confirmation(
|
||||
&mut self,
|
||||
preset: Option<ApprovalPreset>,
|
||||
preset: Option<PermissionsPreset>,
|
||||
sample_paths: Vec<String>,
|
||||
extra_count: usize,
|
||||
failed_scan: bool,
|
||||
@@ -5744,7 +5618,7 @@ impl ChatWidget {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn open_world_writable_warning_confirmation(
|
||||
&mut self,
|
||||
_preset: Option<ApprovalPreset>,
|
||||
_preset: Option<PermissionsPreset>,
|
||||
_sample_paths: Vec<String>,
|
||||
_extra_count: usize,
|
||||
_failed_scan: bool,
|
||||
@@ -5752,7 +5626,7 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn open_windows_sandbox_enable_prompt(&mut self, preset: ApprovalPreset) {
|
||||
pub(crate) fn open_windows_sandbox_enable_prompt(&mut self, preset: PermissionsPreset) {
|
||||
use ratatui_macros::line;
|
||||
|
||||
if !codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED {
|
||||
@@ -5785,7 +5659,7 @@ impl ChatWidget {
|
||||
name: "Go back".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(|tx| {
|
||||
tx.send(AppEvent::OpenApprovalsPopup);
|
||||
tx.send(AppEvent::OpenPermissionsPopup);
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
@@ -5864,10 +5738,10 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn open_windows_sandbox_enable_prompt(&mut self, _preset: ApprovalPreset) {}
|
||||
pub(crate) fn open_windows_sandbox_enable_prompt(&mut self, _preset: PermissionsPreset) {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn open_windows_sandbox_fallback_prompt(&mut self, preset: ApprovalPreset) {
|
||||
pub(crate) fn open_windows_sandbox_fallback_prompt(&mut self, preset: PermissionsPreset) {
|
||||
use ratatui_macros::line;
|
||||
|
||||
let mut lines = Vec::new();
|
||||
@@ -5943,13 +5817,13 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn open_windows_sandbox_fallback_prompt(&mut self, _preset: ApprovalPreset) {}
|
||||
pub(crate) fn open_windows_sandbox_fallback_prompt(&mut self, _preset: PermissionsPreset) {}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self, show_now: bool) {
|
||||
if show_now
|
||||
&& WindowsSandboxLevel::from_config(&self.config) == WindowsSandboxLevel::Disabled
|
||||
&& let Some(preset) = builtin_approval_presets()
|
||||
&& let Some(preset) = builtin_permissions_presets()
|
||||
.into_iter()
|
||||
.find(|preset| preset.id == "auto")
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
assertion_line: 3092
|
||||
expression: popup
|
||||
---
|
||||
Update Model Permissions
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::bottom_pane::FeedbackAudience;
|
||||
use crate::bottom_pane::LocalImageAttachment;
|
||||
use crate::bottom_pane::MentionBinding;
|
||||
use crate::history_cell::UserHistoryCell;
|
||||
use crate::permissions::builtin_permissions_presets;
|
||||
use crate::test_backend::VT100Backend;
|
||||
use crate::tui::FrameRequester;
|
||||
use assert_matches::assert_matches;
|
||||
@@ -91,7 +92,6 @@ use codex_protocol::protocol::SkillScope;
|
||||
use codex_protocol::user_input::TextElement;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_approval_presets::builtin_approval_presets;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyModifiers;
|
||||
@@ -3076,7 +3076,7 @@ async fn ctrl_d_quits_without_prompt() {
|
||||
async fn ctrl_d_with_modal_open_does_not_quit() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
chat.open_approvals_popup();
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::CONTROL));
|
||||
|
||||
assert_matches!(rx.try_recv(), Err(TryRecvError::Empty));
|
||||
@@ -5024,7 +5024,7 @@ async fn approvals_selection_popup_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
chat.config.notices.hide_full_access_warning = None;
|
||||
chat.open_approvals_popup();
|
||||
chat.open_permissions_popup();
|
||||
|
||||
let popup = render_bottom_popup(&chat, 80);
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -5045,7 +5045,7 @@ async fn approvals_selection_popup_snapshot_windows_degraded_sandbox() {
|
||||
chat.set_feature_enabled(Feature::WindowsSandbox, true);
|
||||
chat.set_feature_enabled(Feature::WindowsSandboxElevated, false);
|
||||
|
||||
chat.open_approvals_popup();
|
||||
chat.open_permissions_popup();
|
||||
|
||||
let popup = render_bottom_popup(&chat, 80);
|
||||
assert!(
|
||||
@@ -5064,10 +5064,11 @@ async fn approvals_selection_popup_snapshot_windows_degraded_sandbox() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn preset_matching_requires_exact_workspace_write_settings() {
|
||||
let preset = builtin_approval_presets()
|
||||
let preset = builtin_permissions_presets()
|
||||
.into_iter()
|
||||
.find(|p| p.id == "auto")
|
||||
.expect("auto preset exists");
|
||||
let mut config = test_config().await;
|
||||
let current_sandbox = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![AbsolutePathBuf::try_from("C:\\extra").unwrap()],
|
||||
read_only_access: Default::default(),
|
||||
@@ -5075,13 +5076,28 @@ async fn preset_matching_requires_exact_workspace_write_settings() {
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.unwrap();
|
||||
config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(current_sandbox)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
!ChatWidget::preset_matches_current(AskForApproval::OnRequest, ¤t_sandbox, &preset),
|
||||
!preset.to_selection_item(&config).is_current,
|
||||
"WorkspaceWrite with extra roots should not match the Default preset"
|
||||
);
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::Never)
|
||||
.unwrap();
|
||||
assert!(
|
||||
!ChatWidget::preset_matches_current(AskForApproval::Never, ¤t_sandbox, &preset),
|
||||
!preset.to_selection_item(&config).is_current,
|
||||
"approval mismatch should prevent matching the preset"
|
||||
);
|
||||
}
|
||||
@@ -5090,11 +5106,11 @@ async fn preset_matching_requires_exact_workspace_write_settings() {
|
||||
async fn full_access_confirmation_popup_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
let preset = builtin_approval_presets()
|
||||
let preset = builtin_permissions_presets()
|
||||
.into_iter()
|
||||
.find(|preset| preset.id == "full-access")
|
||||
.find(|selection_item| selection_item.id == "full-access")
|
||||
.expect("full access preset");
|
||||
chat.open_full_access_confirmation(preset, false);
|
||||
chat.open_full_access_confirmation(preset);
|
||||
|
||||
let popup = render_bottom_popup(&chat, 80);
|
||||
assert_snapshot!("full_access_confirmation_popup", popup);
|
||||
@@ -5105,7 +5121,7 @@ async fn full_access_confirmation_popup_snapshot() {
|
||||
async fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
|
||||
let preset = builtin_approval_presets()
|
||||
let preset = builtin_permissions_presets()
|
||||
.into_iter()
|
||||
.find(|preset| preset.id == "auto")
|
||||
.expect("auto preset");
|
||||
@@ -5383,7 +5399,7 @@ async fn approvals_popup_shows_disabled_presets() {
|
||||
)),
|
||||
})
|
||||
.expect("construct constrained approval policy");
|
||||
chat.open_approvals_popup();
|
||||
chat.open_permissions_popup();
|
||||
|
||||
let width = 80;
|
||||
let height = chat.desired_height(width);
|
||||
@@ -5416,7 +5432,7 @@ async fn approvals_popup_navigation_skips_disabled() {
|
||||
_ => Err(invalid_value(candidate.to_string(), "[on-request]")),
|
||||
})
|
||||
.expect("construct constrained approval policy");
|
||||
chat.open_approvals_popup();
|
||||
chat.open_permissions_popup();
|
||||
|
||||
// The approvals popup is the active bottom-pane view; drive navigation via chat handle_key_event.
|
||||
// Start selected at idx 0 (enabled), move down twice; the disabled option should be skipped
|
||||
@@ -5624,11 +5640,8 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
|
||||
AppEvent::InsertHistoryCell(cell) => {
|
||||
cells_before_confirmation.push(cell.display_lines(80));
|
||||
}
|
||||
AppEvent::OpenFullAccessConfirmation {
|
||||
preset,
|
||||
return_to_permissions,
|
||||
} => {
|
||||
open_confirmation_event = Some((preset, return_to_permissions));
|
||||
AppEvent::OpenFullAccessConfirmation { preset } => {
|
||||
open_confirmation_event = Some(preset);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -5639,9 +5652,8 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
|
||||
"did not expect history cell before confirming full access"
|
||||
);
|
||||
}
|
||||
let (preset, return_to_permissions) =
|
||||
open_confirmation_event.expect("expected full access confirmation event");
|
||||
chat.open_full_access_confirmation(preset, return_to_permissions);
|
||||
let preset = open_confirmation_event.expect("expected full access confirmation event");
|
||||
chat.open_full_access_confirmation(preset);
|
||||
|
||||
let popup = render_bottom_popup(&chat, 80);
|
||||
assert!(
|
||||
|
||||
@@ -91,6 +91,7 @@ mod notifications;
|
||||
pub mod onboarding;
|
||||
mod oss_selection;
|
||||
mod pager_overlay;
|
||||
mod permissions;
|
||||
pub mod public_widgets;
|
||||
mod render;
|
||||
mod resume_picker;
|
||||
@@ -925,15 +926,8 @@ async fn load_config_or_exit_with_fallback_cwd(
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if user has configured a sandbox / approval policy,
|
||||
/// or if the current cwd project is already trusted. If not, we need to
|
||||
/// show the trust screen.
|
||||
/// Determine if the user has decided whether to trust the current directory.
|
||||
fn should_show_trust_screen(config: &Config) -> bool {
|
||||
if config.did_user_set_custom_approval_policy_or_sandbox_mode {
|
||||
// Respect explicit approval/sandbox overrides made by the user.
|
||||
return false;
|
||||
}
|
||||
// otherwise, show only if no trust decision has been made
|
||||
config.active_project.trust_level.is_none()
|
||||
}
|
||||
|
||||
@@ -986,7 +980,6 @@ mod tests {
|
||||
async fn windows_shows_trust_prompt_without_sandbox() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let mut config = build_config(&temp_dir).await?;
|
||||
config.did_user_set_custom_approval_policy_or_sandbox_mode = false;
|
||||
config.active_project = ProjectConfig { trust_level: None };
|
||||
config.set_windows_sandbox_enabled(false);
|
||||
|
||||
@@ -1002,7 +995,6 @@ mod tests {
|
||||
async fn windows_shows_trust_prompt_with_sandbox() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let mut config = build_config(&temp_dir).await?;
|
||||
config.did_user_set_custom_approval_policy_or_sandbox_mode = false;
|
||||
config.active_project = ProjectConfig { trust_level: None };
|
||||
config.set_windows_sandbox_enabled(true);
|
||||
|
||||
@@ -1025,7 +1017,6 @@ mod tests {
|
||||
use codex_protocol::config_types::TrustLevel;
|
||||
let temp_dir = TempDir::new()?;
|
||||
let mut config = build_config(&temp_dir).await?;
|
||||
config.did_user_set_custom_approval_policy_or_sandbox_mode = false;
|
||||
config.active_project = ProjectConfig {
|
||||
trust_level: Some(TrustLevel::Untrusted),
|
||||
};
|
||||
|
||||
240
codex-rs/tui/src/permissions/mod.rs
Normal file
240
codex-rs/tui/src/permissions/mod.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
use crate::app_event::AppEvent;
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::app_event::WindowsSandboxEnableMode;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use crate::bottom_pane::SelectionAction;
|
||||
use crate::bottom_pane::SelectionItem;
|
||||
use crate::history_cell;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::Op;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_core::protocol_config_types::WindowsSandboxLevel;
|
||||
|
||||
/// A simple preset pairing an approval policy with a sandbox policy.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PermissionsPreset {
|
||||
/// Stable identifier for the preset.
|
||||
pub id: &'static str,
|
||||
/// Display label shown in UIs.
|
||||
pub label: &'static str,
|
||||
/// Short human description shown next to the label in UIs.
|
||||
pub description: &'static str,
|
||||
/// Approval policy to apply.
|
||||
pub approval: AskForApproval,
|
||||
/// Sandbox policy to apply.
|
||||
pub sandbox: SandboxPolicy,
|
||||
}
|
||||
|
||||
/// Built-in list of approval presets that pair approval and sandbox policy.
|
||||
pub fn builtin_permissions_presets() -> Vec<PermissionsPreset> {
|
||||
vec![
|
||||
PermissionsPreset {
|
||||
id: "read-only",
|
||||
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,
|
||||
sandbox: SandboxPolicy::new_read_only_policy(),
|
||||
},
|
||||
PermissionsPreset {
|
||||
id: "auto",
|
||||
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.",
|
||||
approval: AskForApproval::OnRequest,
|
||||
sandbox: SandboxPolicy::new_workspace_write_policy(),
|
||||
},
|
||||
PermissionsPreset {
|
||||
id: "full-access",
|
||||
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,
|
||||
sandbox: SandboxPolicy::DangerFullAccess,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub(crate) fn visible_permissions_options(config: &Config) -> Vec<SelectionItem> {
|
||||
builtin_permissions_presets()
|
||||
.into_iter()
|
||||
.filter(PermissionsPreset::is_visible)
|
||||
.map(|preset| preset.to_selection_item(config))
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl PermissionsPreset {
|
||||
fn is_visible(&self) -> bool {
|
||||
matches!(self.id, "auto" | "full-access")
|
||||
|| (cfg!(target_os = "windows") && matches!(self.id, "read-only"))
|
||||
}
|
||||
|
||||
pub(crate) fn to_selection_item(&self, config: &Config) -> SelectionItem {
|
||||
let name = if self.id == "auto" && windows_degraded_sandbox_enabled(config) {
|
||||
"Default (non-admin sandbox)".to_string()
|
||||
} else {
|
||||
self.label.to_string()
|
||||
};
|
||||
|
||||
SelectionItem {
|
||||
name,
|
||||
description: Some(self.description.to_string()),
|
||||
is_current: self.is_current(config),
|
||||
actions: self.actions(config),
|
||||
dismiss_on_select: true,
|
||||
disabled_reason: self.disabled_reason(config),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_current(&self, config: &Config) -> bool {
|
||||
self.approval == config.permissions.approval_policy.value()
|
||||
&& self.sandbox == *config.permissions.sandbox_policy.get()
|
||||
}
|
||||
|
||||
fn disabled_reason(&self, config: &Config) -> Option<String> {
|
||||
let disabled_sandbox_reason = match config.permissions.sandbox_policy.can_set(&self.sandbox)
|
||||
{
|
||||
Ok(()) => None,
|
||||
Err(err) => Some(err.to_string()),
|
||||
};
|
||||
if disabled_sandbox_reason.is_some() {
|
||||
return disabled_sandbox_reason;
|
||||
}
|
||||
|
||||
let disabled_approval_reason =
|
||||
match config.permissions.approval_policy.can_set(&self.approval) {
|
||||
Ok(()) => None,
|
||||
Err(err) => Some(err.to_string()),
|
||||
};
|
||||
if disabled_approval_reason.is_some() {
|
||||
return disabled_approval_reason;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn actions(&self, config: &Config) -> Vec<SelectionAction> {
|
||||
let requires_full_access_confirmation =
|
||||
self.id == "full-access" && !config.notices.hide_full_access_warning.unwrap_or(false);
|
||||
if requires_full_access_confirmation {
|
||||
let preset = self.clone();
|
||||
return vec![Box::new(move |tx: &AppEventSender| {
|
||||
tx.send(AppEvent::OpenFullAccessConfirmation {
|
||||
preset: preset.clone(),
|
||||
});
|
||||
})];
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if let Some(actions) = windows_permissions_actions(self, config) {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
let approval = self.approval;
|
||||
let sandbox = self.sandbox.clone();
|
||||
let label = if self.id == "auto" && windows_degraded_sandbox_enabled(config) {
|
||||
"Default (non-admin sandbox)".to_string()
|
||||
} else {
|
||||
self.label.to_string()
|
||||
};
|
||||
vec![Box::new(move |tx: &AppEventSender| {
|
||||
let sandbox_clone = sandbox.clone();
|
||||
tx.send(AppEvent::CodexOp(Op::OverrideTurnContext {
|
||||
cwd: None,
|
||||
approval_policy: Some(approval),
|
||||
sandbox_policy: Some(sandbox_clone.clone()),
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
summary: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
}));
|
||||
tx.send(AppEvent::UpdateAskForApprovalPolicy(approval));
|
||||
tx.send(AppEvent::UpdateSandboxPolicy(sandbox_clone));
|
||||
tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
history_cell::new_info_event(format!("Permissions updated to {label}"), None),
|
||||
)));
|
||||
})]
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle windows-specific actions for auto preset. Returns Some when it should take precedence over the approval preset actions.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn windows_permissions_actions(
|
||||
preset: &PermissionsPreset,
|
||||
config: &Config,
|
||||
) -> Option<Vec<SelectionAction>> {
|
||||
if preset.id != "auto" {
|
||||
return None;
|
||||
}
|
||||
|
||||
if codex_core::windows_sandbox::windows_sandbox_level_from_config(config)
|
||||
== WindowsSandboxLevel::Disabled
|
||||
{
|
||||
let preset_clone = preset.clone();
|
||||
if codex_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
||||
&& codex_core::windows_sandbox::sandbox_setup_is_complete(config.codex_home.as_path())
|
||||
{
|
||||
Some(vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
||||
preset: preset_clone.clone(),
|
||||
mode: WindowsSandboxEnableMode::Elevated,
|
||||
});
|
||||
})])
|
||||
} else {
|
||||
Some(vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::OpenWindowsSandboxEnablePrompt {
|
||||
preset: preset_clone.clone(),
|
||||
});
|
||||
})])
|
||||
}
|
||||
} else if let Some((sample_paths, extra_count, failed_scan)) =
|
||||
world_writable_warning_details(config)
|
||||
{
|
||||
let preset_clone = preset.clone();
|
||||
Some(vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::OpenWorldWritableWarningConfirmation {
|
||||
preset: Some(preset_clone.clone()),
|
||||
sample_paths: sample_paths.clone(),
|
||||
extra_count,
|
||||
failed_scan,
|
||||
});
|
||||
})])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn windows_degraded_sandbox_enabled(config: &Config) -> bool {
|
||||
let windows_sandbox_level =
|
||||
codex_core::windows_sandbox::windows_sandbox_level_from_config(config);
|
||||
matches!(windows_sandbox_level, WindowsSandboxLevel::RestrictedToken)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn world_writable_warning_details(config: &Config) -> Option<(Vec<String>, usize, bool)> {
|
||||
if config.notices.hide_world_writable_warning.unwrap_or(false) {
|
||||
return None;
|
||||
}
|
||||
let cwd = config.cwd.clone();
|
||||
let env_map: std::collections::HashMap<String, String> = std::env::vars().collect();
|
||||
match codex_windows_sandbox::apply_world_writable_scan_and_denies(
|
||||
config.codex_home.as_path(),
|
||||
cwd.as_path(),
|
||||
&env_map,
|
||||
config.permissions.sandbox_policy.get(),
|
||||
Some(config.codex_home.as_path()),
|
||||
) {
|
||||
Ok(_) => None,
|
||||
Err(_) => Some((Vec::new(), 0, true)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn windows_degraded_sandbox_enabled(_config: &Config) -> bool {
|
||||
false
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "approval-presets",
|
||||
crate_name = "codex_utils_approval_presets",
|
||||
)
|
||||
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "codex-utils-approval-presets"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-core = { workspace = true }
|
||||
@@ -1,46 +0,0 @@
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
|
||||
/// A simple preset pairing an approval policy with a sandbox policy.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApprovalPreset {
|
||||
/// Stable identifier for the preset.
|
||||
pub id: &'static str,
|
||||
/// Display label shown in UIs.
|
||||
pub label: &'static str,
|
||||
/// Short human description shown next to the label in UIs.
|
||||
pub description: &'static str,
|
||||
/// Approval policy to apply.
|
||||
pub approval: AskForApproval,
|
||||
/// Sandbox policy to apply.
|
||||
pub sandbox: SandboxPolicy,
|
||||
}
|
||||
|
||||
/// Built-in list of approval presets that pair approval and sandbox policy.
|
||||
///
|
||||
/// Keep this UI-agnostic so it can be reused by both TUI and MCP server.
|
||||
pub fn builtin_approval_presets() -> Vec<ApprovalPreset> {
|
||||
vec![
|
||||
ApprovalPreset {
|
||||
id: "read-only",
|
||||
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,
|
||||
sandbox: SandboxPolicy::new_read_only_policy(),
|
||||
},
|
||||
ApprovalPreset {
|
||||
id: "auto",
|
||||
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,
|
||||
sandbox: SandboxPolicy::new_workspace_write_policy(),
|
||||
},
|
||||
ApprovalPreset {
|
||||
id: "full-access",
|
||||
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,
|
||||
sandbox: SandboxPolicy::DangerFullAccess,
|
||||
},
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user