mirror of
https://github.com/openai/codex.git
synced 2026-11-05 16:29:31 +00:00
Constrain Windows sandbox requirements (#23766)
# Why Managed requirements can already constrain sandbox policy choices, but Windows sandbox implementation selection was still resolved independently from those requirements. That left the TUI able to continue through the unelevated fallback even when an organization wants to require the elevated Windows sandbox implementation. # What - Add `[windows].allowed_sandbox_implementations` requirements support for the Windows `elevated` and `unelevated` implementations. - Apply that allowlist during core config resolution so disallowed configured or feature-selected Windows sandbox implementations fall back to an allowed implementation with the existing requirements warning path. - Reuse the existing TUI Windows setup prompts to block disallowed unelevated continuation, keep required elevated setup in front of the user, and refuse to persist a TUI-selected Windows sandbox mode that requirements disallow. # Semantics | Allowed | Selected | Effective | | --- | --- | --- | | `["elevated"]` | `unelevated` / unset | `elevated` | | `["unelevated"]` | `elevated` / unset | `unelevated` | | `["elevated", "unelevated"]` | `elevated` | `elevated` | | `["elevated", "unelevated"]` | `unelevated` | `unelevated` | | `["elevated", "unelevated"]` | unset | `elevated` | Availability is handled by interactive setup surfaces after allowlist resolution. If the effective elevated implementation is not ready, elevated-only requirements block on setup. When unelevated is also allowed, the UI may offer the existing unelevated fallback. ## TUI Screens If elevated setup is not already complete: ``` Your organization requires the default Codex agent sandbox to continue. Set it up to protect your files and control network access. Learn more <https://developers.openai.com/codex/windows> › 1. Set up default sandbox (requires Administrator permissions) 2. Quit ``` If admin setup fails under `["elevated"]`: ``` Couldn't set up your sandbox with Administrator permissions Your organization requires the default sandbox before Codex can continue. Learn more <https://developers.openai.com/codex/windows> › 1. Try setting up admin sandbox again 2. Quit ``` # Next Steps - extend the requirements/readout surface, such as `configRequirements/read`, so clients can inspect the loaded `[windows].allowed_sandbox_implementations` requirement instead of inferring it from Windows setup state - consider extending `windowsSandbox/readiness` as well - update the App startup guide, setup flow, and banner surfaces so an elevated-only requirement omits any continue-unelevated escape hatch and blocks startup until a permitted implementation is ready; - preserve the existing unelevated fallback path when requirements allow it, including the `["unelevated"]` case where elevated is disallowed
This commit is contained in:
1 parent
8e5f561697
commit
a5a94ee5a7
25 files changed
+821
-143
No files matched your search
@@ -8,6 +8,13 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
pub(crate) fn submit_initial_user_message_if_pending(&mut self) {
|
||||
if self.suppress_initial_user_message_submit {
|
||||
return;
|
||||
}
|
||||
#[cfg(any(target_os = "windows", test))]
|
||||
if self.elevated_windows_sandbox_setup_required() {
|
||||
return;
|
||||
}
|
||||
if let Some(user_message) = self.initial_user_message.take() {
|
||||
self.submit_user_message(user_message);
|
||||
}
|
||||
|
||||
@@ -135,13 +135,7 @@ impl ChatWidget {
|
||||
if self.connectors_enabled() {
|
||||
self.prefetch_connectors();
|
||||
}
|
||||
if let Some(user_message) = self.initial_user_message.take() {
|
||||
if self.suppress_initial_user_message_submit {
|
||||
self.initial_user_message = Some(user_message);
|
||||
} else {
|
||||
self.submit_user_message(user_message);
|
||||
}
|
||||
}
|
||||
self.submit_initial_user_message_if_pending();
|
||||
if display == SessionConfiguredDisplay::Normal
|
||||
&& let Some(forked_from_id) = forked_from_id
|
||||
{
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/permissions.rs
|
||||
expression: "render_bottom_popup(&chat, 120)"
|
||||
---
|
||||
Your organization requires the default Codex agent sandbox to continue. Set it up to protect your files and control
|
||||
network access.
|
||||
Learn more <https://developers.openai.com/codex/windows>
|
||||
|
||||
› 1. Set up default sandbox (requires Administrator permissions)
|
||||
2. Quit
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/permissions.rs
|
||||
expression: popup
|
||||
---
|
||||
Couldn't set up your sandbox with Administrator permissions
|
||||
|
||||
Your organization requires the default sandbox before Codex can continue.
|
||||
Learn more <https://developers.openai.com/codex/windows>
|
||||
|
||||
› 1. Try setting up admin sandbox again
|
||||
2. Quit
|
||||
|
||||
Press enter to confirm or esc to go back
|
||||
@@ -123,7 +123,6 @@ pub(super) use codex_config::ConfigLayerStack;
|
||||
pub(super) use codex_config::RequirementSource;
|
||||
pub(super) use codex_config::types::ApprovalsReviewer;
|
||||
pub(super) use codex_config::types::Notifications;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(super) use codex_config::types::WindowsSandboxModeToml;
|
||||
pub(super) use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME;
|
||||
pub(super) use codex_core_skills::model::SkillMetadata;
|
||||
|
||||
@@ -48,6 +48,25 @@ fn app_server_workspace_write_profile(extra_root: AbsolutePathBuf) -> Permission
|
||||
}
|
||||
}
|
||||
|
||||
fn windows_sandbox_requirements_stack(
|
||||
allowed_sandbox_implementations: Vec<WindowsSandboxModeToml>,
|
||||
) -> ConfigLayerStack {
|
||||
let requirements_toml = codex_config::ConfigRequirementsToml {
|
||||
windows: Some(codex_config::WindowsRequirementsToml {
|
||||
allowed_sandbox_implementations: Some(allowed_sandbox_implementations),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let mut requirements_with_sources = codex_config::ConfigRequirementsWithSources::default();
|
||||
requirements_with_sources
|
||||
.merge_unset_fields(RequirementSource::Unknown, requirements_toml.clone());
|
||||
let requirements = codex_config::ConfigRequirements::try_from(requirements_with_sources)
|
||||
.expect("windows sandbox requirements");
|
||||
|
||||
ConfigLayerStack::new(Vec::new(), requirements, requirements_toml)
|
||||
.expect("test config layer stack")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn approvals_selection_popup_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
@@ -404,6 +423,148 @@ async fn startup_prompts_for_windows_sandbox_when_agent_requested() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tokio::test]
|
||||
async fn startup_windows_sandbox_prompt_blocks_disallowed_unelevated_fallback() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
chat.set_feature_enabled(Feature::WindowsSandbox, /*enabled*/ false);
|
||||
chat.set_feature_enabled(Feature::WindowsSandboxElevated, /*enabled*/ false);
|
||||
chat.config.config_layer_stack =
|
||||
windows_sandbox_requirements_stack(vec![WindowsSandboxModeToml::Elevated]);
|
||||
|
||||
chat.maybe_prompt_windows_sandbox_enable(/*show_now*/ true);
|
||||
|
||||
let popup = render_bottom_popup(&chat, /*width*/ 120);
|
||||
assert!(
|
||||
popup.contains("Your organization requires the default Codex agent sandbox"),
|
||||
"expected required sandbox prompt copy: {popup}"
|
||||
);
|
||||
assert!(
|
||||
!popup.contains("Use non-admin sandbox"),
|
||||
"expected required sandbox prompt to hide non-admin fallback: {popup}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn windows_sandbox_required_enable_prompt_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
chat.config.config_layer_stack =
|
||||
windows_sandbox_requirements_stack(vec![WindowsSandboxModeToml::Elevated]);
|
||||
let preset = builtin_approval_presets()
|
||||
.into_iter()
|
||||
.find(|preset| preset.id == "auto")
|
||||
.expect("auto preset");
|
||||
|
||||
chat.open_windows_sandbox_enable_prompt(preset, /*profile_selection*/ None);
|
||||
|
||||
assert_chatwidget_snapshot!(
|
||||
"windows_sandbox_required_enable_prompt",
|
||||
render_bottom_popup(&chat, /*width*/ 120)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn windows_sandbox_required_enable_prompt_reopens_on_cancel_when_unelevated_allowed() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
chat.config.permissions.windows_sandbox_mode = Some(WindowsSandboxModeToml::Elevated);
|
||||
chat.config.config_layer_stack = windows_sandbox_requirements_stack(vec![
|
||||
WindowsSandboxModeToml::Elevated,
|
||||
WindowsSandboxModeToml::Unelevated,
|
||||
]);
|
||||
let preset = builtin_approval_presets()
|
||||
.into_iter()
|
||||
.find(|preset| preset.id == "auto")
|
||||
.expect("auto preset");
|
||||
|
||||
chat.open_windows_sandbox_enable_prompt(preset, /*profile_selection*/ None);
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
|
||||
assert!(matches!(
|
||||
rx.try_recv(),
|
||||
Ok(AppEvent::OpenWindowsSandboxEnablePrompt { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn required_windows_sandbox_setup_defers_configured_initial_prompt() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let initial_prompt = "fix required sandbox startup".to_string();
|
||||
|
||||
chat.config.permissions.windows_sandbox_mode = Some(WindowsSandboxModeToml::Elevated);
|
||||
chat.config.config_layer_stack = windows_sandbox_requirements_stack(vec![
|
||||
WindowsSandboxModeToml::Elevated,
|
||||
WindowsSandboxModeToml::Unelevated,
|
||||
]);
|
||||
chat.initial_user_message =
|
||||
create_initial_user_message(Some(initial_prompt.clone()), Vec::new(), Vec::new());
|
||||
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
thread_id: ThreadId::new(),
|
||||
forked_from_id: None,
|
||||
fork_parent_title: None,
|
||||
thread_name: None,
|
||||
model: "gpt-test".to_string(),
|
||||
model_provider_id: "test-provider".to_string(),
|
||||
service_tier: None,
|
||||
approval_policy: AskForApproval::OnRequest,
|
||||
approvals_reviewer: ApprovalsReviewer::User,
|
||||
permission_profile: PermissionProfile::workspace_write(),
|
||||
active_permission_profile: None,
|
||||
cwd: test_project_path().abs(),
|
||||
runtime_workspace_roots: Vec::new(),
|
||||
instruction_source_paths: Vec::new(),
|
||||
reasoning_effort: None,
|
||||
collaboration_mode: None,
|
||||
personality: None,
|
||||
message_history: None,
|
||||
network_proxy: None,
|
||||
rollout_path: Some(PathBuf::new()),
|
||||
});
|
||||
drain_insert_history(&mut rx);
|
||||
|
||||
assert!(chat.initial_user_message.is_some());
|
||||
while let Ok(op) = op_rx.try_recv() {
|
||||
assert!(
|
||||
!matches!(op, Op::UserTurn { .. }),
|
||||
"required sandbox setup should hold the configured initial prompt"
|
||||
);
|
||||
}
|
||||
|
||||
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
|
||||
chat.submit_initial_user_message_if_pending();
|
||||
|
||||
let Op::UserTurn { items, .. } = next_submit_op(&mut op_rx) else {
|
||||
panic!("expected initial prompt submission after setup is no longer required");
|
||||
};
|
||||
assert_eq!(
|
||||
items,
|
||||
vec![UserInput::Text {
|
||||
text: initial_prompt,
|
||||
text_elements: Vec::new(),
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn windows_sandbox_required_fallback_prompt_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
chat.config.config_layer_stack =
|
||||
windows_sandbox_requirements_stack(vec![WindowsSandboxModeToml::Elevated]);
|
||||
let preset = builtin_approval_presets()
|
||||
.into_iter()
|
||||
.find(|preset| preset.id == "auto")
|
||||
.expect("auto preset");
|
||||
|
||||
chat.open_windows_sandbox_fallback_prompt(preset, /*profile_selection*/ None);
|
||||
|
||||
let popup = render_bottom_popup(&chat, /*width*/ 120);
|
||||
assert_chatwidget_snapshot!("windows_sandbox_required_fallback_prompt", popup);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tokio::test]
|
||||
async fn startup_does_not_prompt_for_windows_sandbox_when_not_requested() {
|
||||
|
||||
@@ -3,6 +3,31 @@
|
||||
use super::*;
|
||||
|
||||
impl ChatWidget {
|
||||
#[cfg(any(target_os = "windows", test))]
|
||||
pub(crate) fn windows_sandbox_mode_allowed(&self, mode: WindowsSandboxModeToml) -> bool {
|
||||
self.config
|
||||
.config_layer_stack
|
||||
.requirements()
|
||||
.windows_sandbox_mode
|
||||
.can_set(&Some(mode))
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "windows", test))]
|
||||
pub(super) fn elevated_windows_sandbox_setup_required(&self) -> bool {
|
||||
WindowsSandboxLevel::from_config(&self.config) == WindowsSandboxLevel::Elevated
|
||||
&& self
|
||||
.config
|
||||
.config_layer_stack
|
||||
.requirements()
|
||||
.windows_sandbox_mode
|
||||
.source
|
||||
.is_some()
|
||||
&& !crate::legacy_core::windows_sandbox::sandbox_setup_is_complete(
|
||||
self.config.codex_home.as_path(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn world_writable_warning_details(&self) -> Option<(Vec<String>, usize, bool)> {
|
||||
if self
|
||||
@@ -193,7 +218,7 @@ impl ChatWidget {
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", test))]
|
||||
pub(crate) fn open_windows_sandbox_enable_prompt(
|
||||
&mut self,
|
||||
preset: ApprovalPreset,
|
||||
@@ -255,11 +280,22 @@ impl ChatWidget {
|
||||
&[],
|
||||
);
|
||||
|
||||
let allow_unelevated =
|
||||
self.windows_sandbox_mode_allowed(WindowsSandboxModeToml::Unelevated);
|
||||
let setup_choice_is_required =
|
||||
!allow_unelevated || self.elevated_windows_sandbox_setup_required();
|
||||
let mut header = ColumnRenderable::new();
|
||||
header.push(*Box::new(
|
||||
Paragraph::new(vec![
|
||||
line!["Set up the Codex agent sandbox to protect your files and control network access. Learn more <https://developers.openai.com/codex/windows>"],
|
||||
])
|
||||
Paragraph::new(if allow_unelevated {
|
||||
vec![
|
||||
line!["Set up the Codex agent sandbox to protect your files and control network access. Learn more <https://developers.openai.com/codex/windows>"],
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
line!["Your organization requires the default Codex agent sandbox to continue. Set it up to protect your files and control network access."],
|
||||
line!["Learn more <https://developers.openai.com/codex/windows>"],
|
||||
]
|
||||
})
|
||||
.wrap(Wrap { trim: false }),
|
||||
));
|
||||
|
||||
@@ -268,25 +304,27 @@ impl ChatWidget {
|
||||
let legacy_preset = preset.clone();
|
||||
let legacy_profile_selection = profile_selection.clone();
|
||||
let quit_otel = self.session_telemetry.clone();
|
||||
let items = vec![
|
||||
SelectionItem {
|
||||
name: "Set up default sandbox (requires Administrator permissions)".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
accept_otel.counter(
|
||||
"codex.windows_sandbox.elevated_prompt_accept",
|
||||
/*inc*/ 1,
|
||||
&[],
|
||||
);
|
||||
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
||||
preset: preset.clone(),
|
||||
profile_selection: profile_selection.clone(),
|
||||
});
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
let retry_preset = preset.clone();
|
||||
let retry_profile_selection = profile_selection.clone();
|
||||
let mut items = vec![SelectionItem {
|
||||
name: "Set up default sandbox (requires Administrator permissions)".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
accept_otel.counter(
|
||||
"codex.windows_sandbox.elevated_prompt_accept",
|
||||
/*inc*/ 1,
|
||||
&[],
|
||||
);
|
||||
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
||||
preset: preset.clone(),
|
||||
profile_selection: profile_selection.clone(),
|
||||
});
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
}];
|
||||
if allow_unelevated {
|
||||
items.push(SelectionItem {
|
||||
name: "Use non-admin sandbox (higher risk if prompt injected)".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
@@ -302,33 +340,41 @@ impl ChatWidget {
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
name: "Quit".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
quit_otel.counter(
|
||||
"codex.windows_sandbox.elevated_prompt_quit",
|
||||
/*inc*/ 1,
|
||||
&[],
|
||||
);
|
||||
tx.send(AppEvent::Exit(ExitMode::ShutdownFirst));
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
items.push(SelectionItem {
|
||||
name: "Quit".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
quit_otel.counter(
|
||||
"codex.windows_sandbox.elevated_prompt_quit",
|
||||
/*inc*/ 1,
|
||||
&[],
|
||||
);
|
||||
tx.send(AppEvent::Exit(ExitMode::ShutdownFirst));
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: None,
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(header),
|
||||
on_cancel: setup_choice_is_required.then(|| {
|
||||
Box::new(move |tx: &AppEventSender| {
|
||||
tx.send(AppEvent::OpenWindowsSandboxEnablePrompt {
|
||||
preset: retry_preset.clone(),
|
||||
profile_selection: retry_profile_selection.clone(),
|
||||
});
|
||||
}) as _
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(all(not(target_os = "windows"), not(test)))]
|
||||
pub(crate) fn open_windows_sandbox_enable_prompt(
|
||||
&mut self,
|
||||
_preset: ApprovalPreset,
|
||||
@@ -336,7 +382,7 @@ impl ChatWidget {
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", test))]
|
||||
pub(crate) fn open_windows_sandbox_fallback_prompt(
|
||||
&mut self,
|
||||
preset: ApprovalPreset,
|
||||
@@ -344,14 +390,24 @@ impl ChatWidget {
|
||||
) {
|
||||
use ratatui_macros::line;
|
||||
|
||||
let allow_unelevated =
|
||||
self.windows_sandbox_mode_allowed(WindowsSandboxModeToml::Unelevated);
|
||||
let setup_choice_is_required =
|
||||
!allow_unelevated || self.elevated_windows_sandbox_setup_required();
|
||||
let mut lines = Vec::new();
|
||||
lines.push(line![
|
||||
"Couldn't set up your sandbox with Administrator permissions".bold()
|
||||
]);
|
||||
lines.push(line![""]);
|
||||
lines.push(line![
|
||||
"You can still use Codex in a non-admin sandbox. It carries greater risk if prompt injected."
|
||||
]);
|
||||
if allow_unelevated {
|
||||
lines.push(line![
|
||||
"You can still use Codex in a non-admin sandbox. It carries greater risk if prompt injected."
|
||||
]);
|
||||
} else {
|
||||
lines.push(line![
|
||||
"Your organization requires the default sandbox before Codex can continue."
|
||||
]);
|
||||
}
|
||||
lines.push(line![
|
||||
"Learn more <https://developers.openai.com/codex/windows>"
|
||||
]);
|
||||
@@ -361,32 +417,34 @@ impl ChatWidget {
|
||||
|
||||
let elevated_preset = preset.clone();
|
||||
let legacy_preset = preset;
|
||||
let retry_preset = elevated_preset.clone();
|
||||
let retry_profile_selection = profile_selection.clone();
|
||||
let elevated_profile_selection = profile_selection.clone();
|
||||
let legacy_profile_selection = profile_selection;
|
||||
let quit_otel = self.session_telemetry.clone();
|
||||
let items = vec![
|
||||
SelectionItem {
|
||||
name: "Try setting up admin sandbox again".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new({
|
||||
let otel = self.session_telemetry.clone();
|
||||
let preset = elevated_preset;
|
||||
move |tx| {
|
||||
otel.counter(
|
||||
"codex.windows_sandbox.fallback_retry_elevated",
|
||||
/*inc*/ 1,
|
||||
&[],
|
||||
);
|
||||
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
||||
preset: preset.clone(),
|
||||
profile_selection: elevated_profile_selection.clone(),
|
||||
});
|
||||
}
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
let mut items = vec![SelectionItem {
|
||||
name: "Try setting up admin sandbox again".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new({
|
||||
let otel = self.session_telemetry.clone();
|
||||
let preset = elevated_preset;
|
||||
move |tx| {
|
||||
otel.counter(
|
||||
"codex.windows_sandbox.fallback_retry_elevated",
|
||||
/*inc*/ 1,
|
||||
&[],
|
||||
);
|
||||
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
||||
preset: preset.clone(),
|
||||
profile_selection: elevated_profile_selection.clone(),
|
||||
});
|
||||
}
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
}];
|
||||
if allow_unelevated {
|
||||
items.push(SelectionItem {
|
||||
name: "Use Codex with non-admin sandbox".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new({
|
||||
@@ -406,33 +464,41 @@ impl ChatWidget {
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
name: "Quit".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
quit_otel.counter(
|
||||
"codex.windows_sandbox.fallback_prompt_quit",
|
||||
/*inc*/ 1,
|
||||
&[],
|
||||
);
|
||||
tx.send(AppEvent::Exit(ExitMode::ShutdownFirst));
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
items.push(SelectionItem {
|
||||
name: "Quit".to_string(),
|
||||
description: None,
|
||||
actions: vec![Box::new(move |tx| {
|
||||
quit_otel.counter(
|
||||
"codex.windows_sandbox.fallback_prompt_quit",
|
||||
/*inc*/ 1,
|
||||
&[],
|
||||
);
|
||||
tx.send(AppEvent::Exit(ExitMode::ShutdownFirst));
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
title: None,
|
||||
footer_hint: Some(standard_popup_hint_line()),
|
||||
items,
|
||||
header: Box::new(header),
|
||||
on_cancel: setup_choice_is_required.then(|| {
|
||||
Box::new(move |tx: &AppEventSender| {
|
||||
tx.send(AppEvent::OpenWindowsSandboxFallbackPrompt {
|
||||
preset: retry_preset.clone(),
|
||||
profile_selection: retry_profile_selection.clone(),
|
||||
});
|
||||
}) as _
|
||||
}),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(all(not(target_os = "windows"), not(test)))]
|
||||
pub(crate) fn open_windows_sandbox_fallback_prompt(
|
||||
&mut self,
|
||||
_preset: ApprovalPreset,
|
||||
@@ -442,8 +508,11 @@ impl ChatWidget {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self, show_now: bool) {
|
||||
let windows_sandbox_level = WindowsSandboxLevel::from_config(&self.config);
|
||||
let setup_is_required = windows_sandbox_level == WindowsSandboxLevel::Disabled
|
||||
|| self.elevated_windows_sandbox_setup_required();
|
||||
if show_now
|
||||
&& WindowsSandboxLevel::from_config(&self.config) == WindowsSandboxLevel::Disabled
|
||||
&& setup_is_required
|
||||
&& let Some(preset) = builtin_approval_presets()
|
||||
.into_iter()
|
||||
.find(|preset| preset.id == "auto")
|
||||
|
||||
Reference in new issue
Block a user