mirror of
https://github.com/openai/codex.git
synced 2026-11-05 16:29:31 +00:00
tui: plumb permission profile selection (#23708)
## Why The named-profile `/permissions` picker needs a small TUI action path that can select permission profiles without folding the menu UI and profile metadata into the same review. ## What changed - Carry permission-profile selections through the TUI app event flow. - Persist selected profiles while preserving the existing approval settings and guardrail prompts. - Keep the legacy `/permissions` picker behavior in this layer; the profile-mode menu stays in the follow-up PR. ## Stack 1. [#22931](https://github.com/openai/codex/pull/22931): runtime/session/network propagation for active permission profiles. 2. **This PR**: TUI selection plumbing and guardrail flow. 3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware `/permissions` menu and custom profile display. <img width="1632" height="1186" alt="image" src="https://github.com/user-attachments/assets/69ddcd5e-b57c-468d-8c1d-246916323c15" /> ## Validation - `git diff --cached --check` before commit. - Full test run skipped at the user request while pushing the split stack.
This commit is contained in:
1 parent
e0e304b123
commit
fcff0d6c52
13 files changed
+627
-144
No files matched your search
+19
-1
@@ -10,6 +10,7 @@ use crate::app_event::AppEvent;
|
|||||||
use crate::app_event::ExitMode;
|
use crate::app_event::ExitMode;
|
||||||
use crate::app_event::FeedbackCategory;
|
use crate::app_event::FeedbackCategory;
|
||||||
use crate::app_event::HistoryLookupResponse;
|
use crate::app_event::HistoryLookupResponse;
|
||||||
|
use crate::app_event::PermissionProfileSelection;
|
||||||
use crate::app_event::RateLimitRefreshOrigin;
|
use crate::app_event::RateLimitRefreshOrigin;
|
||||||
use crate::app_event::RealtimeAudioDeviceKind;
|
use crate::app_event::RealtimeAudioDeviceKind;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@@ -484,7 +485,7 @@ pub(crate) struct App {
|
|||||||
harness_overrides: ConfigOverrides,
|
harness_overrides: ConfigOverrides,
|
||||||
loader_overrides: LoaderOverrides,
|
loader_overrides: LoaderOverrides,
|
||||||
runtime_approval_policy_override: Option<AskForApproval>,
|
runtime_approval_policy_override: Option<AskForApproval>,
|
||||||
runtime_permission_profile_override: Option<PermissionProfile>,
|
runtime_permission_profile_override: Option<RuntimePermissionProfileOverride>,
|
||||||
|
|
||||||
pub(crate) file_search: FileSearchManager,
|
pub(crate) file_search: FileSearchManager,
|
||||||
|
|
||||||
@@ -554,6 +555,23 @@ pub(crate) struct App {
|
|||||||
pending_hook_enabled_writes: HashMap<String, Option<bool>>,
|
pending_hook_enabled_writes: HashMap<String, Option<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
struct RuntimePermissionProfileOverride {
|
||||||
|
permission_profile: PermissionProfile,
|
||||||
|
active_permission_profile: Option<ActivePermissionProfile>,
|
||||||
|
network: Option<crate::legacy_core::config::NetworkProxySpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimePermissionProfileOverride {
|
||||||
|
fn from_config(config: &Config) -> Self {
|
||||||
|
Self {
|
||||||
|
permission_profile: config.permissions.permission_profile().clone(),
|
||||||
|
active_permission_profile: config.permissions.active_permission_profile(),
|
||||||
|
network: config.permissions.network.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn active_turn_not_steerable_turn_error(error: &TypedRequestError) -> Option<AppServerTurnError> {
|
fn active_turn_not_steerable_turn_error(error: &TypedRequestError) -> Option<AppServerTurnError> {
|
||||||
let TypedRequestError::Server { source, .. } = error else {
|
let TypedRequestError::Server { source, .. } = error else {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
//! loop.
|
//! loop.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use codex_utils_approval_presets::ApprovalPreset;
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub(super) async fn rebuild_config_for_cwd(&self, cwd: PathBuf) -> Result<Config> {
|
pub(super) async fn rebuild_config_for_cwd(&self, cwd: PathBuf) -> Result<Config> {
|
||||||
@@ -21,6 +23,161 @@ impl App {
|
|||||||
.wrap_err_with(|| format!("Failed to rebuild config for cwd {cwd_display}"))
|
.wrap_err_with(|| format!("Failed to rebuild config for cwd {cwd_display}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn rebuild_config_for_permission_profile(
|
||||||
|
&self,
|
||||||
|
profile_id: &str,
|
||||||
|
) -> Result<Config> {
|
||||||
|
let mut overrides = self.harness_overrides.clone();
|
||||||
|
overrides.cwd = Some(self.chat_widget.config_ref().cwd.to_path_buf());
|
||||||
|
overrides.sandbox_mode = None;
|
||||||
|
overrides.permission_profile = None;
|
||||||
|
overrides.default_permissions = Some(profile_id.to_string());
|
||||||
|
ConfigBuilder::default()
|
||||||
|
.codex_home(self.config.codex_home.to_path_buf())
|
||||||
|
.cli_overrides(self.cli_kv_overrides.clone())
|
||||||
|
.harness_overrides(overrides)
|
||||||
|
.loader_overrides(self.loader_overrides.clone())
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.wrap_err_with(|| {
|
||||||
|
format!("Failed to rebuild config for permission profile {profile_id}")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub(super) async fn permission_profile_for_windows_setup(
|
||||||
|
&self,
|
||||||
|
preset: &ApprovalPreset,
|
||||||
|
profile_selection: Option<&PermissionProfileSelection>,
|
||||||
|
) -> Result<PermissionProfile> {
|
||||||
|
match profile_selection {
|
||||||
|
Some(selection) => Ok(self
|
||||||
|
.rebuild_config_for_permission_profile(selection.profile_id.as_str())
|
||||||
|
.await?
|
||||||
|
.permissions
|
||||||
|
.permission_profile()
|
||||||
|
.clone()),
|
||||||
|
None => Ok(preset.permission_profile.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn apply_permission_profile_selection(
|
||||||
|
&mut self,
|
||||||
|
selection: PermissionProfileSelection,
|
||||||
|
) -> bool {
|
||||||
|
let PermissionProfileSelection {
|
||||||
|
profile_id,
|
||||||
|
approval_policy,
|
||||||
|
approvals_reviewer,
|
||||||
|
display_label,
|
||||||
|
} = selection;
|
||||||
|
let selected_config = match self
|
||||||
|
.rebuild_config_for_permission_profile(profile_id.as_str())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
error = %err,
|
||||||
|
profile_id,
|
||||||
|
"failed to resolve selected permission profile"
|
||||||
|
);
|
||||||
|
self.chat_widget.add_error_message(format!(
|
||||||
|
"Failed to set permission profile `{profile_id}`: {err}"
|
||||||
|
));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let permission_profile = selected_config.permissions.permission_profile();
|
||||||
|
let active_permission_profile = selected_config.permissions.active_permission_profile();
|
||||||
|
let network = selected_config.permissions.network.clone();
|
||||||
|
|
||||||
|
let mut config = self.config.clone();
|
||||||
|
if let Some(policy) = approval_policy
|
||||||
|
&& !self.try_set_approval_policy_on_config(
|
||||||
|
&mut config,
|
||||||
|
policy,
|
||||||
|
"Failed to set approval policy",
|
||||||
|
"failed to set selected permission profile approval policy on app config",
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Err(err) = config
|
||||||
|
.permissions
|
||||||
|
.set_permission_profile_from_session_snapshot(
|
||||||
|
PermissionProfileSnapshot::from_session_snapshot(
|
||||||
|
permission_profile.clone(),
|
||||||
|
active_permission_profile.clone(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
tracing::warn!(
|
||||||
|
error = %err,
|
||||||
|
profile_id,
|
||||||
|
"failed to set selected permission profile on app config"
|
||||||
|
);
|
||||||
|
self.chat_widget.add_error_message(format!(
|
||||||
|
"Failed to set permission profile `{profile_id}`: {err}"
|
||||||
|
));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(reviewer) = approvals_reviewer {
|
||||||
|
config.approvals_reviewer = reviewer;
|
||||||
|
}
|
||||||
|
config.permissions.network = network.clone();
|
||||||
|
self.config = config;
|
||||||
|
|
||||||
|
if let Some(policy) = approval_policy {
|
||||||
|
self.runtime_approval_policy_override = Some(policy);
|
||||||
|
self.chat_widget.set_approval_policy(policy);
|
||||||
|
}
|
||||||
|
if let Err(err) = self.chat_widget.set_permission_profile_with_active_profile(
|
||||||
|
permission_profile.clone(),
|
||||||
|
active_permission_profile.clone(),
|
||||||
|
) {
|
||||||
|
tracing::warn!(
|
||||||
|
error = %err,
|
||||||
|
profile_id,
|
||||||
|
"failed to set selected permission profile on chat config"
|
||||||
|
);
|
||||||
|
self.chat_widget.add_error_message(format!(
|
||||||
|
"Failed to set permission profile `{profile_id}`: {err}"
|
||||||
|
));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(reviewer) = approvals_reviewer {
|
||||||
|
self.chat_widget.set_approvals_reviewer(reviewer);
|
||||||
|
}
|
||||||
|
self.chat_widget.set_permission_network(network);
|
||||||
|
self.runtime_permission_profile_override =
|
||||||
|
Some(RuntimePermissionProfileOverride::from_config(&self.config));
|
||||||
|
self.sync_active_thread_permission_settings_to_cached_session()
|
||||||
|
.await;
|
||||||
|
self.app_event_tx
|
||||||
|
.send(AppEvent::CodexOp(AppCommand::override_turn_context(
|
||||||
|
/*cwd*/ None,
|
||||||
|
approval_policy,
|
||||||
|
approvals_reviewer,
|
||||||
|
Some(permission_profile.clone()),
|
||||||
|
active_permission_profile,
|
||||||
|
/*windows_sandbox_level*/ None,
|
||||||
|
/*model*/ None,
|
||||||
|
/*effort*/ None,
|
||||||
|
/*summary*/ None,
|
||||||
|
/*service_tier*/ None,
|
||||||
|
/*collaboration_mode*/ None,
|
||||||
|
/*personality*/ None,
|
||||||
|
)));
|
||||||
|
self.app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||||
|
history_cell::new_info_event(
|
||||||
|
format!("Permissions updated to {display_label}"),
|
||||||
|
/*hint*/ None,
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) async fn refresh_in_memory_config_from_disk(&mut self) -> Result<()> {
|
pub(super) async fn refresh_in_memory_config_from_disk(&mut self) -> Result<()> {
|
||||||
let mut config = self
|
let mut config = self
|
||||||
.rebuild_config_for_cwd(self.chat_widget.config_ref().cwd.to_path_buf())
|
.rebuild_config_for_cwd(self.chat_widget.config_ref().cwd.to_path_buf())
|
||||||
@@ -73,13 +230,25 @@ impl App {
|
|||||||
"Failed to carry forward approval policy override: {err}"
|
"Failed to carry forward approval policy override: {err}"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(profile) = self.runtime_permission_profile_override.as_ref()
|
if let Some(profile_override) = self.runtime_permission_profile_override.as_ref() {
|
||||||
&& let Err(err) = config.permissions.set_permission_profile(profile.clone())
|
match config
|
||||||
{
|
.permissions
|
||||||
tracing::warn!(%err, "failed to carry forward permission profile override");
|
.set_permission_profile_from_session_snapshot(
|
||||||
self.chat_widget.add_error_message(format!(
|
PermissionProfileSnapshot::from_session_snapshot(
|
||||||
"Failed to carry forward permission profile override: {err}"
|
profile_override.permission_profile.clone(),
|
||||||
));
|
profile_override.active_permission_profile.clone(),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Ok(()) => {
|
||||||
|
config.permissions.network = profile_override.network.clone();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(%err, "failed to carry forward permission profile override");
|
||||||
|
self.chat_widget.add_error_message(format!(
|
||||||
|
"Failed to carry forward permission profile override: {err}"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,8 +510,9 @@ impl App {
|
|||||||
self.chat_widget
|
self.chat_widget
|
||||||
.add_error_message(format!("Failed to enable Auto-review: {err}"));
|
.add_error_message(format!("Failed to enable Auto-review: {err}"));
|
||||||
}
|
}
|
||||||
if let Some(permission_profile) = permission_profile_override_value {
|
if permission_profile_override.is_some() {
|
||||||
self.runtime_permission_profile_override = Some(permission_profile);
|
self.runtime_permission_profile_override =
|
||||||
|
Some(RuntimePermissionProfileOverride::from_config(&self.config));
|
||||||
}
|
}
|
||||||
|
|
||||||
if approval_policy_override.is_some()
|
if approval_policy_override.is_some()
|
||||||
|
|||||||
@@ -800,18 +800,24 @@ impl App {
|
|||||||
AppEvent::OpenFullAccessConfirmation {
|
AppEvent::OpenFullAccessConfirmation {
|
||||||
preset,
|
preset,
|
||||||
return_to_permissions,
|
return_to_permissions,
|
||||||
|
profile_selection,
|
||||||
} => {
|
} => {
|
||||||
self.chat_widget
|
self.chat_widget.open_full_access_confirmation(
|
||||||
.open_full_access_confirmation(preset, return_to_permissions);
|
preset,
|
||||||
|
return_to_permissions,
|
||||||
|
profile_selection,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
AppEvent::OpenWorldWritableWarningConfirmation {
|
AppEvent::OpenWorldWritableWarningConfirmation {
|
||||||
preset,
|
preset,
|
||||||
|
profile_selection,
|
||||||
sample_paths,
|
sample_paths,
|
||||||
extra_count,
|
extra_count,
|
||||||
failed_scan,
|
failed_scan,
|
||||||
} => {
|
} => {
|
||||||
self.chat_widget.open_world_writable_warning_confirmation(
|
self.chat_widget.open_world_writable_warning_confirmation(
|
||||||
preset,
|
preset,
|
||||||
|
profile_selection,
|
||||||
sample_paths,
|
sample_paths,
|
||||||
extra_count,
|
extra_count,
|
||||||
failed_scan,
|
failed_scan,
|
||||||
@@ -848,10 +854,17 @@ impl App {
|
|||||||
self.launch_external_editor(tui).await;
|
self.launch_external_editor(tui).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppEvent::OpenWindowsSandboxEnablePrompt { preset } => {
|
AppEvent::OpenWindowsSandboxEnablePrompt {
|
||||||
self.chat_widget.open_windows_sandbox_enable_prompt(preset);
|
preset,
|
||||||
|
profile_selection,
|
||||||
|
} => {
|
||||||
|
self.chat_widget
|
||||||
|
.open_windows_sandbox_enable_prompt(preset, profile_selection);
|
||||||
}
|
}
|
||||||
AppEvent::OpenWindowsSandboxFallbackPrompt { preset } => {
|
AppEvent::OpenWindowsSandboxFallbackPrompt {
|
||||||
|
preset,
|
||||||
|
profile_selection,
|
||||||
|
} => {
|
||||||
self.session_telemetry.counter(
|
self.session_telemetry.counter(
|
||||||
"codex.windows_sandbox.fallback_prompt_shown",
|
"codex.windows_sandbox.fallback_prompt_shown",
|
||||||
/*inc*/ 1,
|
/*inc*/ 1,
|
||||||
@@ -866,12 +879,30 @@ impl App {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.chat_widget
|
self.chat_widget
|
||||||
.open_windows_sandbox_fallback_prompt(preset);
|
.open_windows_sandbox_fallback_prompt(preset, profile_selection);
|
||||||
}
|
}
|
||||||
AppEvent::BeginWindowsSandboxElevatedSetup { preset } => {
|
AppEvent::BeginWindowsSandboxElevatedSetup {
|
||||||
|
preset,
|
||||||
|
profile_selection,
|
||||||
|
} => {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
let permission_profile = preset.permission_profile.clone();
|
let permission_profile = match self
|
||||||
|
.permission_profile_for_windows_setup(&preset, profile_selection.as_ref())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(permission_profile) => permission_profile,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
error = %err,
|
||||||
|
"failed to resolve permission profile for elevated Windows sandbox setup"
|
||||||
|
);
|
||||||
|
self.chat_widget.add_error_message(format!(
|
||||||
|
"Failed to prepare Windows sandbox for the selected permission profile: {err}"
|
||||||
|
));
|
||||||
|
return Ok(AppRunControl::Continue);
|
||||||
|
}
|
||||||
|
};
|
||||||
let policy_cwd = self.config.cwd.clone();
|
let policy_cwd = self.config.cwd.clone();
|
||||||
let command_cwd = policy_cwd.clone();
|
let command_cwd = policy_cwd.clone();
|
||||||
let env_map: std::collections::HashMap<String, String> =
|
let env_map: std::collections::HashMap<String, String> =
|
||||||
@@ -887,6 +918,7 @@ impl App {
|
|||||||
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
||||||
preset,
|
preset,
|
||||||
mode: WindowsSandboxEnableMode::Elevated,
|
mode: WindowsSandboxEnableMode::Elevated,
|
||||||
|
profile_selection,
|
||||||
});
|
});
|
||||||
return Ok(AppRunControl::Continue);
|
return Ok(AppRunControl::Continue);
|
||||||
}
|
}
|
||||||
@@ -903,7 +935,10 @@ impl App {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
else {
|
else {
|
||||||
tx.send(AppEvent::OpenWindowsSandboxFallbackPrompt { preset });
|
tx.send(AppEvent::OpenWindowsSandboxFallbackPrompt {
|
||||||
|
preset,
|
||||||
|
profile_selection,
|
||||||
|
});
|
||||||
return Ok(AppRunControl::Continue);
|
return Ok(AppRunControl::Continue);
|
||||||
};
|
};
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
@@ -924,6 +959,7 @@ impl App {
|
|||||||
AppEvent::EnableWindowsSandboxForAgentMode {
|
AppEvent::EnableWindowsSandboxForAgentMode {
|
||||||
preset: preset.clone(),
|
preset: preset.clone(),
|
||||||
mode: WindowsSandboxEnableMode::Elevated,
|
mode: WindowsSandboxEnableMode::Elevated,
|
||||||
|
profile_selection: profile_selection.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -955,7 +991,10 @@ impl App {
|
|||||||
error = %err,
|
error = %err,
|
||||||
"failed to run elevated Windows sandbox setup"
|
"failed to run elevated Windows sandbox setup"
|
||||||
);
|
);
|
||||||
AppEvent::OpenWindowsSandboxFallbackPrompt { preset }
|
AppEvent::OpenWindowsSandboxFallbackPrompt {
|
||||||
|
preset,
|
||||||
|
profile_selection,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
tx.send(event);
|
tx.send(event);
|
||||||
@@ -963,13 +1002,31 @@ impl App {
|
|||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
let _ = preset;
|
let _ = (preset, profile_selection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppEvent::BeginWindowsSandboxLegacySetup { preset } => {
|
AppEvent::BeginWindowsSandboxLegacySetup {
|
||||||
|
preset,
|
||||||
|
profile_selection,
|
||||||
|
} => {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
let permission_profile = preset.permission_profile.clone();
|
let permission_profile = match self
|
||||||
|
.permission_profile_for_windows_setup(&preset, profile_selection.as_ref())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(permission_profile) => permission_profile,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
error = %err,
|
||||||
|
"failed to resolve permission profile for legacy Windows sandbox setup"
|
||||||
|
);
|
||||||
|
self.chat_widget.add_error_message(format!(
|
||||||
|
"Failed to prepare Windows sandbox for the selected permission profile: {err}"
|
||||||
|
));
|
||||||
|
return Ok(AppRunControl::Continue);
|
||||||
|
}
|
||||||
|
};
|
||||||
let policy_cwd = self.config.cwd.clone();
|
let policy_cwd = self.config.cwd.clone();
|
||||||
let command_cwd = policy_cwd.clone();
|
let command_cwd = policy_cwd.clone();
|
||||||
let env_map: std::collections::HashMap<String, String> =
|
let env_map: std::collections::HashMap<String, String> =
|
||||||
@@ -988,7 +1045,10 @@ impl App {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
else {
|
else {
|
||||||
tx.send(AppEvent::OpenWindowsSandboxFallbackPrompt { preset });
|
tx.send(AppEvent::OpenWindowsSandboxFallbackPrompt {
|
||||||
|
preset,
|
||||||
|
profile_selection,
|
||||||
|
});
|
||||||
return Ok(AppRunControl::Continue);
|
return Ok(AppRunControl::Continue);
|
||||||
};
|
};
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
@@ -1014,12 +1074,13 @@ impl App {
|
|||||||
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
||||||
preset,
|
preset,
|
||||||
mode: WindowsSandboxEnableMode::Legacy,
|
mode: WindowsSandboxEnableMode::Legacy,
|
||||||
|
profile_selection,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
let _ = preset;
|
let _ = (preset, profile_selection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppEvent::BeginWindowsSandboxGrantReadRoot { path } => {
|
AppEvent::BeginWindowsSandboxGrantReadRoot { path } => {
|
||||||
@@ -1082,7 +1143,11 @@ impl App {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AppEvent::EnableWindowsSandboxForAgentMode { preset, mode } => {
|
AppEvent::EnableWindowsSandboxForAgentMode {
|
||||||
|
preset,
|
||||||
|
mode,
|
||||||
|
profile_selection,
|
||||||
|
} => {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
self.chat_widget.clear_windows_sandbox_setup_status();
|
self.chat_widget.clear_windows_sandbox_setup_status();
|
||||||
@@ -1142,11 +1207,40 @@ impl App {
|
|||||||
self.app_event_tx.send(
|
self.app_event_tx.send(
|
||||||
AppEvent::OpenWorldWritableWarningConfirmation {
|
AppEvent::OpenWorldWritableWarningConfirmation {
|
||||||
preset: Some(preset.clone()),
|
preset: Some(preset.clone()),
|
||||||
|
profile_selection: profile_selection.clone(),
|
||||||
sample_paths,
|
sample_paths,
|
||||||
extra_count,
|
extra_count,
|
||||||
failed_scan,
|
failed_scan,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
} else if let Some(selection) = profile_selection {
|
||||||
|
self.app_event_tx.send(AppEvent::CodexOp(
|
||||||
|
AppCommand::override_turn_context(
|
||||||
|
/*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,
|
||||||
|
/*effort*/ None,
|
||||||
|
/*summary*/ None,
|
||||||
|
/*service_tier*/ None,
|
||||||
|
/*collaboration_mode*/ None,
|
||||||
|
/*personality*/ None,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
self.apply_permission_profile_selection(selection).await;
|
||||||
|
let _ = mode;
|
||||||
|
self.chat_widget.add_plain_history_lines(vec![
|
||||||
|
Line::from(vec!["• ".dim(), "Sandbox ready".into()]),
|
||||||
|
Line::from(vec![
|
||||||
|
" ".into(),
|
||||||
|
"Codex can now safely edit files and execute commands in your computer"
|
||||||
|
.dark_gray(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
self.app_event_tx.send(AppEvent::CodexOp(
|
self.app_event_tx.send(AppEvent::CodexOp(
|
||||||
AppCommand::override_turn_context(
|
AppCommand::override_turn_context(
|
||||||
@@ -1196,7 +1290,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
let _ = (preset, mode);
|
let _ = (preset, mode, profile_selection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppEvent::PersistModelSelection { model, effort } => {
|
AppEvent::PersistModelSelection { model, effort } => {
|
||||||
@@ -1460,7 +1554,7 @@ impl App {
|
|||||||
return Ok(AppRunControl::Continue);
|
return Ok(AppRunControl::Continue);
|
||||||
}
|
}
|
||||||
self.runtime_permission_profile_override =
|
self.runtime_permission_profile_override =
|
||||||
Some(self.config.permissions.permission_profile().clone());
|
Some(RuntimePermissionProfileOverride::from_config(&self.config));
|
||||||
self.sync_active_thread_permission_settings_to_cached_session()
|
self.sync_active_thread_permission_settings_to_cached_session()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -1496,6 +1590,9 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AppEvent::SelectPermissionProfile(selection) => {
|
||||||
|
self.apply_permission_profile_selection(selection).await;
|
||||||
|
}
|
||||||
AppEvent::UpdateApprovalsReviewer(policy) => {
|
AppEvent::UpdateApprovalsReviewer(policy) => {
|
||||||
self.config.approvals_reviewer = policy;
|
self.config.approvals_reviewer = policy;
|
||||||
self.chat_widget.set_approvals_reviewer(policy);
|
self.chat_widget.set_approvals_reviewer(policy);
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ impl App {
|
|||||||
fn send_world_writable_scan_failed(tx: &AppEventSender) {
|
fn send_world_writable_scan_failed(tx: &AppEventSender) {
|
||||||
tx.send(AppEvent::OpenWorldWritableWarningConfirmation {
|
tx.send(AppEvent::OpenWorldWritableWarningConfirmation {
|
||||||
preset: None,
|
preset: None,
|
||||||
|
profile_selection: None,
|
||||||
sample_paths: Vec::new(),
|
sample_paths: Vec::new(),
|
||||||
extra_count: 0usize,
|
extra_count: 0usize,
|
||||||
failed_scan: true,
|
failed_scan: true,
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ use codex_protocol::config_types::CollaborationMode;
|
|||||||
use codex_protocol::config_types::CollaborationModeMask;
|
use codex_protocol::config_types::CollaborationModeMask;
|
||||||
use codex_protocol::config_types::ModeKind;
|
use codex_protocol::config_types::ModeKind;
|
||||||
use codex_protocol::config_types::Personality;
|
use codex_protocol::config_types::Personality;
|
||||||
|
use codex_protocol::config_types::SandboxMode;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
use codex_protocol::config_types::Settings;
|
use codex_protocol::config_types::Settings;
|
||||||
use codex_protocol::models::ActivePermissionProfile;
|
use codex_protocol::models::ActivePermissionProfile;
|
||||||
@@ -1627,6 +1628,91 @@ async fn reset_memories_clears_local_memory_directories() -> Result<()> {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn apply_permission_profile_selection_preserves_loader_overrides() -> Result<()> {
|
||||||
|
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
|
||||||
|
let codex_home = tempdir()?;
|
||||||
|
let selected_config = codex_home.path().join("work.config.toml");
|
||||||
|
std::fs::write(
|
||||||
|
&selected_config,
|
||||||
|
r#"
|
||||||
|
default_permissions = "locked-down"
|
||||||
|
|
||||||
|
[permissions.locked-down.filesystem]
|
||||||
|
":minimal" = "read"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
app.config.codex_home = codex_home.path().to_path_buf().abs();
|
||||||
|
app.loader_overrides.user_config_path = Some(selected_config.abs());
|
||||||
|
app.harness_overrides.sandbox_mode = Some(SandboxMode::WorkspaceWrite);
|
||||||
|
app.harness_overrides.permission_profile = Some(PermissionProfile::workspace_write());
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
app.apply_permission_profile_selection(PermissionProfileSelection {
|
||||||
|
profile_id: "locked-down".to_string(),
|
||||||
|
approval_policy: None,
|
||||||
|
approvals_reviewer: None,
|
||||||
|
display_label: "locked-down".to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.config
|
||||||
|
.permissions
|
||||||
|
.active_permission_profile()
|
||||||
|
.as_ref()
|
||||||
|
.map(|profile| profile.id.as_str()),
|
||||||
|
Some("locked-down")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.chat_widget
|
||||||
|
.config_ref()
|
||||||
|
.permissions
|
||||||
|
.active_permission_profile()
|
||||||
|
.as_ref()
|
||||||
|
.map(|profile| profile.id.as_str()),
|
||||||
|
Some("locked-down")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.runtime_permission_profile_override,
|
||||||
|
Some(RuntimePermissionProfileOverride::from_config(&app.config))
|
||||||
|
);
|
||||||
|
let op = match app_event_rx.try_recv() {
|
||||||
|
Ok(AppEvent::CodexOp(op)) => op,
|
||||||
|
other => panic!("expected CodexOp event, got {other:?}"),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
op,
|
||||||
|
Op::OverrideTurnContext {
|
||||||
|
cwd: None,
|
||||||
|
approval_policy: None,
|
||||||
|
approvals_reviewer: None,
|
||||||
|
permission_profile: Some(app.config.permissions.permission_profile().clone()),
|
||||||
|
active_permission_profile: app.config.permissions.active_permission_profile(),
|
||||||
|
windows_sandbox_level: None,
|
||||||
|
model: None,
|
||||||
|
effort: None,
|
||||||
|
summary: None,
|
||||||
|
service_tier: None,
|
||||||
|
collaboration_mode: None,
|
||||||
|
personality: None,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let cell = match app_event_rx.try_recv() {
|
||||||
|
Ok(AppEvent::InsertHistoryCell(cell)) => cell,
|
||||||
|
other => panic!("expected InsertHistoryCell event, got {other:?}"),
|
||||||
|
};
|
||||||
|
let rendered = cell
|
||||||
|
.display_lines(/*width*/ 120)
|
||||||
|
.into_iter()
|
||||||
|
.map(|line| line.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
assert!(rendered.contains("Permissions updated to locked-down"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn update_feature_flags_enabling_guardian_selects_auto_review() -> Result<()> {
|
async fn update_feature_flags_enabling_guardian_selects_auto_review() -> Result<()> {
|
||||||
let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels().await;
|
let (mut app, mut app_event_rx, mut op_rx) = make_test_app_with_channels().await;
|
||||||
@@ -1687,7 +1773,7 @@ async fn update_feature_flags_enabling_guardian_selects_auto_review() -> Result<
|
|||||||
assert_eq!(app.runtime_approval_policy_override, None);
|
assert_eq!(app.runtime_approval_policy_override, None);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
app.runtime_permission_profile_override,
|
app.runtime_permission_profile_override,
|
||||||
Some(auto_review.permission_profile())
|
Some(RuntimePermissionProfileOverride::from_config(&app.config))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
op_rx.try_recv(),
|
op_rx.try_recv(),
|
||||||
|
|||||||
@@ -591,7 +591,9 @@ impl App {
|
|||||||
let permissions_override = Self::turn_permissions_override_from_config(
|
let permissions_override = Self::turn_permissions_override_from_config(
|
||||||
config,
|
config,
|
||||||
active_permission_profile.as_ref(),
|
active_permission_profile.as_ref(),
|
||||||
self.runtime_permission_profile_override.as_ref(),
|
self.runtime_permission_profile_override
|
||||||
|
.as_ref()
|
||||||
|
.map(|profile| &profile.permission_profile),
|
||||||
);
|
);
|
||||||
app_server
|
app_server
|
||||||
.turn_start(
|
.turn_start(
|
||||||
|
|||||||
@@ -686,6 +686,7 @@ pub(crate) enum AppEvent {
|
|||||||
OpenFullAccessConfirmation {
|
OpenFullAccessConfirmation {
|
||||||
preset: ApprovalPreset,
|
preset: ApprovalPreset,
|
||||||
return_to_permissions: bool,
|
return_to_permissions: bool,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Open the Windows world-writable directories warning.
|
/// Open the Windows world-writable directories warning.
|
||||||
@@ -695,6 +696,7 @@ pub(crate) enum AppEvent {
|
|||||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||||
OpenWorldWritableWarningConfirmation {
|
OpenWorldWritableWarningConfirmation {
|
||||||
preset: Option<ApprovalPreset>,
|
preset: Option<ApprovalPreset>,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
/// Up to 3 sample world-writable directories to display in the warning.
|
/// Up to 3 sample world-writable directories to display in the warning.
|
||||||
sample_paths: Vec<String>,
|
sample_paths: Vec<String>,
|
||||||
/// If there are more than `sample_paths`, this carries the remaining count.
|
/// If there are more than `sample_paths`, this carries the remaining count.
|
||||||
@@ -707,24 +709,28 @@ pub(crate) enum AppEvent {
|
|||||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||||
OpenWindowsSandboxEnablePrompt {
|
OpenWindowsSandboxEnablePrompt {
|
||||||
preset: ApprovalPreset,
|
preset: ApprovalPreset,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Open the Windows sandbox fallback prompt after declining or failing elevation.
|
/// Open the Windows sandbox fallback prompt after declining or failing elevation.
|
||||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||||
OpenWindowsSandboxFallbackPrompt {
|
OpenWindowsSandboxFallbackPrompt {
|
||||||
preset: ApprovalPreset,
|
preset: ApprovalPreset,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Begin the elevated Windows sandbox setup flow.
|
/// Begin the elevated Windows sandbox setup flow.
|
||||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||||
BeginWindowsSandboxElevatedSetup {
|
BeginWindowsSandboxElevatedSetup {
|
||||||
preset: ApprovalPreset,
|
preset: ApprovalPreset,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Begin the non-elevated Windows sandbox setup flow.
|
/// Begin the non-elevated Windows sandbox setup flow.
|
||||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||||
BeginWindowsSandboxLegacySetup {
|
BeginWindowsSandboxLegacySetup {
|
||||||
preset: ApprovalPreset,
|
preset: ApprovalPreset,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Begin a non-elevated grant of read access for an additional directory.
|
/// Begin a non-elevated grant of read access for an additional directory.
|
||||||
@@ -745,6 +751,7 @@ pub(crate) enum AppEvent {
|
|||||||
EnableWindowsSandboxForAgentMode {
|
EnableWindowsSandboxForAgentMode {
|
||||||
preset: ApprovalPreset,
|
preset: ApprovalPreset,
|
||||||
mode: WindowsSandboxEnableMode,
|
mode: WindowsSandboxEnableMode,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Update the Windows sandbox feature mode without changing approval presets.
|
/// Update the Windows sandbox feature mode without changing approval presets.
|
||||||
@@ -756,6 +763,9 @@ pub(crate) enum AppEvent {
|
|||||||
/// Update the current built-in active permission profile in the running app and widget.
|
/// Update the current built-in active permission profile in the running app and widget.
|
||||||
UpdateActivePermissionProfile(ActivePermissionProfile),
|
UpdateActivePermissionProfile(ActivePermissionProfile),
|
||||||
|
|
||||||
|
/// Select a named permission profile, optionally applying built-in mode settings too.
|
||||||
|
SelectPermissionProfile(PermissionProfileSelection),
|
||||||
|
|
||||||
/// Update the current approvals reviewer in the running app and widget.
|
/// Update the current approvals reviewer in the running app and widget.
|
||||||
UpdateApprovalsReviewer(ApprovalsReviewer),
|
UpdateApprovalsReviewer(ApprovalsReviewer),
|
||||||
|
|
||||||
@@ -995,6 +1005,15 @@ pub(crate) enum AppEvent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Named profile selection to apply after any required UI guardrails complete.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct PermissionProfileSelection {
|
||||||
|
pub profile_id: String,
|
||||||
|
pub approval_policy: Option<AskForApproval>,
|
||||||
|
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||||
|
pub display_label: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct RealtimeWebrtcOffer {
|
pub(crate) struct RealtimeWebrtcOffer {
|
||||||
pub(crate) offer_sdp: String,
|
pub(crate) offer_sdp: String,
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ fn queued_message_edit_hint_binding(
|
|||||||
|
|
||||||
use crate::app_event::AppEvent;
|
use crate::app_event::AppEvent;
|
||||||
use crate::app_event::ExitMode;
|
use crate::app_event::ExitMode;
|
||||||
|
use crate::app_event::PermissionProfileSelection;
|
||||||
use crate::app_event::RateLimitRefreshOrigin;
|
use crate::app_event::RateLimitRefreshOrigin;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use crate::app_event::WindowsSandboxEnableMode;
|
use crate::app_event::WindowsSandboxEnableMode;
|
||||||
@@ -459,6 +460,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
|
|
||||||
const USER_SHELL_COMMAND_HELP_TITLE: &str = "Prefix a command with ! to run it locally";
|
const USER_SHELL_COMMAND_HELP_TITLE: &str = "Prefix a command with ! to run it locally";
|
||||||
const USER_SHELL_COMMAND_HELP_HINT: &str = "Example: !ls";
|
const USER_SHELL_COMMAND_HELP_HINT: &str = "Example: !ls";
|
||||||
|
const AUTO_REVIEW_DESCRIPTION: &str = "Same workspace-write permissions as Default, but eligible `on-request` approvals are routed through the auto-reviewer subagent.";
|
||||||
const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
|
const DEFAULT_OPENAI_BASE_URL: &str = "https://api.openai.com/v1";
|
||||||
const DEFAULT_STATUS_LINE_ITEMS: [&str; 2] = ["model-with-reasoning", "current-dir"];
|
const DEFAULT_STATUS_LINE_ITEMS: [&str; 2] = ["model-with-reasoning", "current-dir"];
|
||||||
const MAX_AGENT_COPY_HISTORY: usize = 32;
|
const MAX_AGENT_COPY_HISTORY: usize = 32;
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ impl ChatWidget {
|
|||||||
} else {
|
} else {
|
||||||
preset.label.to_string()
|
preset.label.to_string()
|
||||||
};
|
};
|
||||||
let preset_approval = AskForApproval::from(preset.approval);
|
|
||||||
let base_description =
|
let base_description =
|
||||||
Some(preset.description.replace(" (Identical to Agent mode)", ""));
|
Some(preset.description.replace(" (Identical to Agent mode)", ""));
|
||||||
let approval_disabled_reason = match self
|
let approval_disabled_reason = match self
|
||||||
@@ -70,86 +69,13 @@ impl ChatWidget {
|
|||||||
let default_disabled_reason = approval_disabled_reason
|
let default_disabled_reason = approval_disabled_reason
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| guardian_disabled_reason(false));
|
.or_else(|| guardian_disabled_reason(false));
|
||||||
let requires_confirmation = preset.id == "full-access"
|
let default_actions = self.permission_mode_actions(
|
||||||
&& !self
|
&preset,
|
||||||
.config
|
base_name.clone(),
|
||||||
.notices
|
ApprovalsReviewer::User,
|
||||||
.hide_full_access_warning
|
/*profile_selection*/ None,
|
||||||
.unwrap_or(false);
|
/*return_to_permissions*/ !include_read_only,
|
||||||
let default_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 crate::legacy_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
|
||||||
&& crate::legacy_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.permission_profile.clone(),
|
|
||||||
preset.active_permission_profile.clone(),
|
|
||||||
base_name.clone(),
|
|
||||||
ApprovalsReviewer::User,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
{
|
|
||||||
Self::approval_preset_actions(
|
|
||||||
preset_approval,
|
|
||||||
preset.permission_profile.clone(),
|
|
||||||
preset.active_permission_profile.clone(),
|
|
||||||
base_name.clone(),
|
|
||||||
ApprovalsReviewer::User,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self::approval_preset_actions(
|
|
||||||
preset_approval,
|
|
||||||
preset.permission_profile.clone(),
|
|
||||||
preset.active_permission_profile.clone(),
|
|
||||||
base_name.clone(),
|
|
||||||
ApprovalsReviewer::User,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
if preset.id == "auto" {
|
if preset.id == "auto" {
|
||||||
items.push(SelectionItem {
|
items.push(SelectionItem {
|
||||||
name: base_name.clone(),
|
name: base_name.clone(),
|
||||||
@@ -170,10 +96,7 @@ impl ChatWidget {
|
|||||||
if guardian_approval_enabled {
|
if guardian_approval_enabled {
|
||||||
items.push(SelectionItem {
|
items.push(SelectionItem {
|
||||||
name: "Auto-review".to_string(),
|
name: "Auto-review".to_string(),
|
||||||
description: Some(
|
description: Some(AUTO_REVIEW_DESCRIPTION.to_string()),
|
||||||
"Same workspace-write permissions as Default, but eligible `on-request` approvals are routed through the auto-reviewer subagent."
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
is_current: current_review_policy == ApprovalsReviewer::AutoReview
|
is_current: current_review_policy == ApprovalsReviewer::AutoReview
|
||||||
&& Self::preset_matches_current(
|
&& Self::preset_matches_current(
|
||||||
current_approval,
|
current_approval,
|
||||||
@@ -181,12 +104,12 @@ impl ChatWidget {
|
|||||||
self.config.cwd.as_path(),
|
self.config.cwd.as_path(),
|
||||||
&preset,
|
&preset,
|
||||||
),
|
),
|
||||||
actions: Self::approval_preset_actions(
|
actions: self.permission_mode_actions(
|
||||||
preset_approval,
|
&preset,
|
||||||
preset.permission_profile.clone(),
|
|
||||||
preset.active_permission_profile.clone(),
|
|
||||||
"Auto-review".to_string(),
|
"Auto-review".to_string(),
|
||||||
ApprovalsReviewer::AutoReview,
|
ApprovalsReviewer::AutoReview,
|
||||||
|
/*profile_selection*/ None,
|
||||||
|
/*return_to_permissions*/ !include_read_only,
|
||||||
),
|
),
|
||||||
dismiss_on_select: true,
|
dismiss_on_select: true,
|
||||||
disabled_reason: approval_disabled_reason
|
disabled_reason: approval_disabled_reason
|
||||||
@@ -346,6 +269,97 @@ impl ChatWidget {
|
|||||||
})]
|
})]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn permission_profile_selection_actions(
|
||||||
|
selection: PermissionProfileSelection,
|
||||||
|
) -> Vec<SelectionAction> {
|
||||||
|
vec![Box::new(move |tx| {
|
||||||
|
tx.send(AppEvent::SelectPermissionProfile(selection.clone()));
|
||||||
|
})]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn permission_mode_actions(
|
||||||
|
&self,
|
||||||
|
preset: &ApprovalPreset,
|
||||||
|
label: String,
|
||||||
|
approvals_reviewer: ApprovalsReviewer,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
|
return_to_permissions: bool,
|
||||||
|
) -> Vec<SelectionAction> {
|
||||||
|
let apply_actions = || {
|
||||||
|
profile_selection.clone().map_or_else(
|
||||||
|
|| {
|
||||||
|
Self::approval_preset_actions(
|
||||||
|
AskForApproval::from(preset.approval),
|
||||||
|
preset.permission_profile.clone(),
|
||||||
|
preset.active_permission_profile.clone(),
|
||||||
|
label.clone(),
|
||||||
|
approvals_reviewer,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Self::permission_profile_selection_actions,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let requires_confirmation = approvals_reviewer == ApprovalsReviewer::User
|
||||||
|
&& preset.id == "full-access"
|
||||||
|
&& !self
|
||||||
|
.config
|
||||||
|
.notices
|
||||||
|
.hide_full_access_warning
|
||||||
|
.unwrap_or(false);
|
||||||
|
if requires_confirmation {
|
||||||
|
let preset = preset.clone();
|
||||||
|
return vec![Box::new(move |tx| {
|
||||||
|
tx.send(AppEvent::OpenFullAccessConfirmation {
|
||||||
|
preset: preset.clone(),
|
||||||
|
return_to_permissions,
|
||||||
|
profile_selection: profile_selection.clone(),
|
||||||
|
});
|
||||||
|
})];
|
||||||
|
}
|
||||||
|
if approvals_reviewer == ApprovalsReviewer::User && preset.id == "auto" {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
if WindowsSandboxLevel::from_config(&self.config) == WindowsSandboxLevel::Disabled {
|
||||||
|
let preset = preset.clone();
|
||||||
|
if crate::legacy_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED
|
||||||
|
&& crate::legacy_core::windows_sandbox::sandbox_setup_is_complete(
|
||||||
|
self.config.codex_home.as_path(),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return vec![Box::new(move |tx| {
|
||||||
|
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
||||||
|
preset: preset.clone(),
|
||||||
|
mode: WindowsSandboxEnableMode::Elevated,
|
||||||
|
profile_selection: profile_selection.clone(),
|
||||||
|
});
|
||||||
|
})];
|
||||||
|
}
|
||||||
|
return vec![Box::new(move |tx| {
|
||||||
|
tx.send(AppEvent::OpenWindowsSandboxEnablePrompt {
|
||||||
|
preset: preset.clone(),
|
||||||
|
profile_selection: profile_selection.clone(),
|
||||||
|
});
|
||||||
|
})];
|
||||||
|
}
|
||||||
|
if let Some((sample_paths, extra_count, failed_scan)) =
|
||||||
|
self.world_writable_warning_details()
|
||||||
|
{
|
||||||
|
let preset = preset.clone();
|
||||||
|
return vec![Box::new(move |tx| {
|
||||||
|
tx.send(AppEvent::OpenWorldWritableWarningConfirmation {
|
||||||
|
preset: Some(preset.clone()),
|
||||||
|
profile_selection: profile_selection.clone(),
|
||||||
|
sample_paths: sample_paths.clone(),
|
||||||
|
extra_count,
|
||||||
|
failed_scan,
|
||||||
|
});
|
||||||
|
})];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apply_actions()
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn preset_matches_current(
|
pub(super) fn preset_matches_current(
|
||||||
current_approval: AskForApproval,
|
current_approval: AskForApproval,
|
||||||
current_permission_profile: &PermissionProfile,
|
current_permission_profile: &PermissionProfile,
|
||||||
@@ -389,6 +403,7 @@ impl ChatWidget {
|
|||||||
&mut self,
|
&mut self,
|
||||||
preset: ApprovalPreset,
|
preset: ApprovalPreset,
|
||||||
return_to_permissions: bool,
|
return_to_permissions: bool,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
) {
|
) {
|
||||||
let selected_name = preset.label.to_string();
|
let selected_name = preset.label.to_string();
|
||||||
let approval = AskForApproval::from(preset.approval);
|
let approval = AskForApproval::from(preset.approval);
|
||||||
@@ -406,23 +421,33 @@ impl ChatWidget {
|
|||||||
));
|
));
|
||||||
let header = ColumnRenderable::with(header_children);
|
let header = ColumnRenderable::with(header_children);
|
||||||
|
|
||||||
let mut accept_actions = Self::approval_preset_actions(
|
let mut accept_actions = profile_selection.clone().map_or_else(
|
||||||
approval,
|
|| {
|
||||||
preset.permission_profile.clone(),
|
Self::approval_preset_actions(
|
||||||
preset.active_permission_profile.clone(),
|
approval,
|
||||||
selected_name.clone(),
|
preset.permission_profile.clone(),
|
||||||
ApprovalsReviewer::User,
|
preset.active_permission_profile.clone(),
|
||||||
|
selected_name.clone(),
|
||||||
|
ApprovalsReviewer::User,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Self::permission_profile_selection_actions,
|
||||||
);
|
);
|
||||||
accept_actions.push(Box::new(|tx| {
|
accept_actions.push(Box::new(|tx| {
|
||||||
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
|
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut accept_and_remember_actions = Self::approval_preset_actions(
|
let mut accept_and_remember_actions = profile_selection.map_or_else(
|
||||||
approval,
|
|| {
|
||||||
preset.permission_profile,
|
Self::approval_preset_actions(
|
||||||
preset.active_permission_profile,
|
approval,
|
||||||
selected_name,
|
preset.permission_profile,
|
||||||
ApprovalsReviewer::User,
|
preset.active_permission_profile,
|
||||||
|
selected_name,
|
||||||
|
ApprovalsReviewer::User,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Self::permission_profile_selection_actions,
|
||||||
);
|
);
|
||||||
accept_and_remember_actions.push(Box::new(|tx| {
|
accept_and_remember_actions.push(Box::new(|tx| {
|
||||||
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
|
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
|
||||||
|
|||||||
@@ -30,6 +30,30 @@ impl ChatWidget {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_permission_profile_with_active_profile(
|
||||||
|
&mut self,
|
||||||
|
profile: PermissionProfile,
|
||||||
|
active_permission_profile: Option<ActivePermissionProfile>,
|
||||||
|
) -> ConstraintResult<()> {
|
||||||
|
self.config
|
||||||
|
.permissions
|
||||||
|
.set_permission_profile_from_session_snapshot(
|
||||||
|
PermissionProfileSnapshot::from_session_snapshot(
|
||||||
|
profile,
|
||||||
|
active_permission_profile,
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
self.refresh_status_surfaces();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_permission_network(
|
||||||
|
&mut self,
|
||||||
|
network: Option<crate::legacy_core::config::NetworkProxySpec>,
|
||||||
|
) {
|
||||||
|
self.config.permissions.network = network;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
||||||
pub(crate) fn set_windows_sandbox_mode(&mut self, mode: Option<WindowsSandboxModeToml>) {
|
pub(crate) fn set_windows_sandbox_mode(&mut self, mode: Option<WindowsSandboxModeToml>) {
|
||||||
self.config.permissions.windows_sandbox_mode = mode;
|
self.config.permissions.windows_sandbox_mode = mode;
|
||||||
|
|||||||
@@ -297,7 +297,10 @@ impl ChatWidget {
|
|||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
self.app_event_tx
|
self.app_event_tx
|
||||||
.send(AppEvent::BeginWindowsSandboxElevatedSetup { preset });
|
.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
||||||
|
preset,
|
||||||
|
profile_selection: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -166,7 +166,9 @@ async fn full_access_confirmation_popup_snapshot() {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|preset| preset.id == "full-access")
|
.find(|preset| preset.id == "full-access")
|
||||||
.expect("full access preset");
|
.expect("full access preset");
|
||||||
chat.open_full_access_confirmation(preset, /*return_to_permissions*/ false);
|
chat.open_full_access_confirmation(
|
||||||
|
preset, /*return_to_permissions*/ false, /*profile_selection*/ None,
|
||||||
|
);
|
||||||
|
|
||||||
let popup = render_bottom_popup(&chat, /*width*/ 80);
|
let popup = render_bottom_popup(&chat, /*width*/ 80);
|
||||||
assert_chatwidget_snapshot!("full_access_confirmation_popup", popup);
|
assert_chatwidget_snapshot!("full_access_confirmation_popup", popup);
|
||||||
@@ -181,7 +183,7 @@ async fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|preset| preset.id == "auto")
|
.find(|preset| preset.id == "auto")
|
||||||
.expect("auto preset");
|
.expect("auto preset");
|
||||||
chat.open_windows_sandbox_enable_prompt(preset);
|
chat.open_windows_sandbox_enable_prompt(preset, /*profile_selection*/ None);
|
||||||
|
|
||||||
let popup = render_bottom_popup(&chat, /*width*/ 120);
|
let popup = render_bottom_popup(&chat, /*width*/ 120);
|
||||||
assert!(
|
assert!(
|
||||||
@@ -799,8 +801,9 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
|
|||||||
AppEvent::OpenFullAccessConfirmation {
|
AppEvent::OpenFullAccessConfirmation {
|
||||||
preset,
|
preset,
|
||||||
return_to_permissions,
|
return_to_permissions,
|
||||||
|
profile_selection,
|
||||||
} => {
|
} => {
|
||||||
open_confirmation_event = Some((preset, return_to_permissions));
|
open_confirmation_event = Some((preset, return_to_permissions, profile_selection));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -811,9 +814,9 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
|
|||||||
"did not expect history cell before confirming full access"
|
"did not expect history cell before confirming full access"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let (preset, return_to_permissions) =
|
let (preset, return_to_permissions, profile_selection) =
|
||||||
open_confirmation_event.expect("expected full access confirmation event");
|
open_confirmation_event.expect("expected full access confirmation event");
|
||||||
chat.open_full_access_confirmation(preset, return_to_permissions);
|
chat.open_full_access_confirmation(preset, return_to_permissions, profile_selection);
|
||||||
|
|
||||||
let popup = render_bottom_popup(&chat, /*width*/ 80);
|
let popup = render_bottom_popup(&chat, /*width*/ 80);
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ impl ChatWidget {
|
|||||||
pub(crate) fn open_world_writable_warning_confirmation(
|
pub(crate) fn open_world_writable_warning_confirmation(
|
||||||
&mut self,
|
&mut self,
|
||||||
preset: Option<ApprovalPreset>,
|
preset: Option<ApprovalPreset>,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
sample_paths: Vec<String>,
|
sample_paths: Vec<String>,
|
||||||
extra_count: usize,
|
extra_count: usize,
|
||||||
failed_scan: bool,
|
failed_scan: bool,
|
||||||
@@ -111,7 +112,9 @@ impl ChatWidget {
|
|||||||
tx.send(AppEvent::SkipNextWorldWritableScan);
|
tx.send(AppEvent::SkipNextWorldWritableScan);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if let (Some(approval), Some(permission_profile), Some(active_permission_profile)) = (
|
if let Some(selection) = profile_selection.clone() {
|
||||||
|
accept_actions.extend(Self::permission_profile_selection_actions(selection));
|
||||||
|
} else if let (Some(approval), Some(permission_profile), Some(active_permission_profile)) = (
|
||||||
approval,
|
approval,
|
||||||
permission_profile.clone(),
|
permission_profile.clone(),
|
||||||
active_permission_profile.clone(),
|
active_permission_profile.clone(),
|
||||||
@@ -130,7 +133,10 @@ impl ChatWidget {
|
|||||||
tx.send(AppEvent::UpdateWorldWritableWarningAcknowledged(true));
|
tx.send(AppEvent::UpdateWorldWritableWarningAcknowledged(true));
|
||||||
tx.send(AppEvent::PersistWorldWritableWarningAcknowledged);
|
tx.send(AppEvent::PersistWorldWritableWarningAcknowledged);
|
||||||
}));
|
}));
|
||||||
if let (Some(approval), Some(permission_profile), Some(active_permission_profile)) =
|
if let Some(selection) = profile_selection {
|
||||||
|
accept_and_remember_actions
|
||||||
|
.extend(Self::permission_profile_selection_actions(selection));
|
||||||
|
} else if let (Some(approval), Some(permission_profile), Some(active_permission_profile)) =
|
||||||
(approval, permission_profile, active_permission_profile)
|
(approval, permission_profile, active_permission_profile)
|
||||||
{
|
{
|
||||||
accept_and_remember_actions.extend(Self::approval_preset_actions(
|
accept_and_remember_actions.extend(Self::approval_preset_actions(
|
||||||
@@ -171,6 +177,7 @@ impl ChatWidget {
|
|||||||
pub(crate) fn open_world_writable_warning_confirmation(
|
pub(crate) fn open_world_writable_warning_confirmation(
|
||||||
&mut self,
|
&mut self,
|
||||||
_preset: Option<ApprovalPreset>,
|
_preset: Option<ApprovalPreset>,
|
||||||
|
_profile_selection: Option<PermissionProfileSelection>,
|
||||||
_sample_paths: Vec<String>,
|
_sample_paths: Vec<String>,
|
||||||
_extra_count: usize,
|
_extra_count: usize,
|
||||||
_failed_scan: bool,
|
_failed_scan: bool,
|
||||||
@@ -178,7 +185,11 @@ impl ChatWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[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: ApprovalPreset,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
|
) {
|
||||||
use ratatui_macros::line;
|
use ratatui_macros::line;
|
||||||
|
|
||||||
if !crate::legacy_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED {
|
if !crate::legacy_core::windows_sandbox::ELEVATED_SANDBOX_NUX_ENABLED {
|
||||||
@@ -202,6 +213,7 @@ impl ChatWidget {
|
|||||||
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
tx.send(AppEvent::EnableWindowsSandboxForAgentMode {
|
||||||
preset: preset_clone.clone(),
|
preset: preset_clone.clone(),
|
||||||
mode: WindowsSandboxEnableMode::Legacy,
|
mode: WindowsSandboxEnableMode::Legacy,
|
||||||
|
profile_selection: profile_selection.clone(),
|
||||||
});
|
});
|
||||||
})],
|
})],
|
||||||
dismiss_on_select: true,
|
dismiss_on_select: true,
|
||||||
@@ -245,6 +257,7 @@ impl ChatWidget {
|
|||||||
let accept_otel = self.session_telemetry.clone();
|
let accept_otel = self.session_telemetry.clone();
|
||||||
let legacy_otel = self.session_telemetry.clone();
|
let legacy_otel = self.session_telemetry.clone();
|
||||||
let legacy_preset = preset.clone();
|
let legacy_preset = preset.clone();
|
||||||
|
let legacy_profile_selection = profile_selection.clone();
|
||||||
let quit_otel = self.session_telemetry.clone();
|
let quit_otel = self.session_telemetry.clone();
|
||||||
let items = vec![
|
let items = vec![
|
||||||
SelectionItem {
|
SelectionItem {
|
||||||
@@ -258,6 +271,7 @@ impl ChatWidget {
|
|||||||
);
|
);
|
||||||
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
||||||
preset: preset.clone(),
|
preset: preset.clone(),
|
||||||
|
profile_selection: profile_selection.clone(),
|
||||||
});
|
});
|
||||||
})],
|
})],
|
||||||
dismiss_on_select: true,
|
dismiss_on_select: true,
|
||||||
@@ -274,6 +288,7 @@ impl ChatWidget {
|
|||||||
);
|
);
|
||||||
tx.send(AppEvent::BeginWindowsSandboxLegacySetup {
|
tx.send(AppEvent::BeginWindowsSandboxLegacySetup {
|
||||||
preset: legacy_preset.clone(),
|
preset: legacy_preset.clone(),
|
||||||
|
profile_selection: legacy_profile_selection.clone(),
|
||||||
});
|
});
|
||||||
})],
|
})],
|
||||||
dismiss_on_select: true,
|
dismiss_on_select: true,
|
||||||
@@ -305,10 +320,19 @@ impl ChatWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[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: ApprovalPreset,
|
||||||
|
_profile_selection: Option<PermissionProfileSelection>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[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: ApprovalPreset,
|
||||||
|
profile_selection: Option<PermissionProfileSelection>,
|
||||||
|
) {
|
||||||
use ratatui_macros::line;
|
use ratatui_macros::line;
|
||||||
|
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
@@ -328,6 +352,8 @@ impl ChatWidget {
|
|||||||
|
|
||||||
let elevated_preset = preset.clone();
|
let elevated_preset = preset.clone();
|
||||||
let legacy_preset = preset;
|
let legacy_preset = preset;
|
||||||
|
let elevated_profile_selection = profile_selection.clone();
|
||||||
|
let legacy_profile_selection = profile_selection;
|
||||||
let quit_otel = self.session_telemetry.clone();
|
let quit_otel = self.session_telemetry.clone();
|
||||||
let items = vec![
|
let items = vec![
|
||||||
SelectionItem {
|
SelectionItem {
|
||||||
@@ -344,6 +370,7 @@ impl ChatWidget {
|
|||||||
);
|
);
|
||||||
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
|
||||||
preset: preset.clone(),
|
preset: preset.clone(),
|
||||||
|
profile_selection: elevated_profile_selection.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})],
|
})],
|
||||||
@@ -364,6 +391,7 @@ impl ChatWidget {
|
|||||||
);
|
);
|
||||||
tx.send(AppEvent::BeginWindowsSandboxLegacySetup {
|
tx.send(AppEvent::BeginWindowsSandboxLegacySetup {
|
||||||
preset: preset.clone(),
|
preset: preset.clone(),
|
||||||
|
profile_selection: legacy_profile_selection.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})],
|
})],
|
||||||
@@ -396,7 +424,12 @@ impl ChatWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[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: ApprovalPreset,
|
||||||
|
_profile_selection: Option<PermissionProfileSelection>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self, show_now: bool) {
|
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self, show_now: bool) {
|
||||||
@@ -406,7 +439,7 @@ impl ChatWidget {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|preset| preset.id == "auto")
|
.find(|preset| preset.id == "auto")
|
||||||
{
|
{
|
||||||
self.open_windows_sandbox_enable_prompt(preset);
|
self.open_windows_sandbox_enable_prompt(preset, /*profile_selection*/ None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user