mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
Merge remote-tracking branch 'openai/main'
This commit is contained in:
@@ -945,6 +945,9 @@ impl Session {
|
||||
cwd.clone(),
|
||||
session_configuration.sandbox_policy.get(),
|
||||
session_configuration.windows_sandbox_level,
|
||||
per_turn_config
|
||||
.features
|
||||
.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
));
|
||||
TurnContext {
|
||||
sub_id,
|
||||
@@ -4072,6 +4075,9 @@ async fn spawn_review_thread(
|
||||
parent_turn_context.cwd.clone(),
|
||||
&parent_turn_context.sandbox_policy,
|
||||
parent_turn_context.windows_sandbox_level,
|
||||
parent_turn_context
|
||||
.features
|
||||
.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
));
|
||||
|
||||
let review_turn_context = TurnContext {
|
||||
|
||||
@@ -6,6 +6,7 @@ use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
pub(crate) fn sandbox_tag(
|
||||
policy: &SandboxPolicy,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
use_linux_sandbox_bwrap: bool,
|
||||
) -> &'static str {
|
||||
if matches!(policy, SandboxPolicy::DangerFullAccess) {
|
||||
return "none";
|
||||
@@ -17,8 +18,61 @@ pub(crate) fn sandbox_tag(
|
||||
{
|
||||
return "windows_elevated";
|
||||
}
|
||||
if cfg!(target_os = "linux") && use_linux_sandbox_bwrap {
|
||||
return "linux_bubblewrap";
|
||||
}
|
||||
|
||||
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled)
|
||||
.map(SandboxType::as_metric_tag)
|
||||
.unwrap_or("none")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::sandbox_tag;
|
||||
use crate::exec::SandboxType;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::safety::get_platform_sandbox;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::protocol::NetworkAccess;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn danger_full_access_is_untagged_even_when_bubblewrap_is_enabled() {
|
||||
let actual = sandbox_tag(
|
||||
&SandboxPolicy::DangerFullAccess,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
true,
|
||||
);
|
||||
assert_eq!(actual, "none");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_sandbox_keeps_external_tag_when_bubblewrap_is_enabled() {
|
||||
let actual = sandbox_tag(
|
||||
&SandboxPolicy::ExternalSandbox {
|
||||
network_access: NetworkAccess::Enabled,
|
||||
},
|
||||
WindowsSandboxLevel::Disabled,
|
||||
true,
|
||||
);
|
||||
assert_eq!(actual, "external");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bubblewrap_feature_sets_distinct_linux_tag() {
|
||||
let actual = sandbox_tag(
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
WindowsSandboxLevel::Disabled,
|
||||
true,
|
||||
);
|
||||
let expected = if cfg!(target_os = "linux") {
|
||||
"linux_bubblewrap"
|
||||
} else {
|
||||
get_platform_sandbox(false)
|
||||
.map(SandboxType::as_metric_tag)
|
||||
.unwrap_or("none")
|
||||
};
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::client_common::tools::ToolSpec;
|
||||
use crate::features::Feature;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::sandbox_tags::sandbox_tag;
|
||||
@@ -88,6 +89,10 @@ impl ToolRegistry {
|
||||
sandbox_tag(
|
||||
&invocation.turn.sandbox_policy,
|
||||
invocation.turn.windows_sandbox_level,
|
||||
invocation
|
||||
.turn
|
||||
.features
|
||||
.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
),
|
||||
),
|
||||
(
|
||||
@@ -356,8 +361,12 @@ async fn dispatch_after_tool_use_hook(dispatch: AfterToolUseHookDispatch<'_>) {
|
||||
success: dispatch.success,
|
||||
duration_ms: u64::try_from(dispatch.duration.as_millis()).unwrap_or(u64::MAX),
|
||||
mutating: dispatch.mutating,
|
||||
sandbox: sandbox_tag(&turn.sandbox_policy, turn.windows_sandbox_level)
|
||||
.to_string(),
|
||||
sandbox: sandbox_tag(
|
||||
&turn.sandbox_policy,
|
||||
turn.windows_sandbox_level,
|
||||
turn.features.enabled(Feature::UseLinuxSandboxBwrap),
|
||||
)
|
||||
.to_string(),
|
||||
sandbox_policy: sandbox_policy_tag(&turn.sandbox_policy).to_string(),
|
||||
output_preview: dispatch.output_preview.clone(),
|
||||
},
|
||||
|
||||
@@ -132,9 +132,17 @@ impl TurnMetadataState {
|
||||
cwd: PathBuf,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
use_linux_sandbox_bwrap: bool,
|
||||
) -> Self {
|
||||
let repo_root = get_git_repo_root(&cwd).map(|root| root.to_string_lossy().into_owned());
|
||||
let sandbox = Some(sandbox_tag(sandbox_policy, windows_sandbox_level).to_string());
|
||||
let sandbox = Some(
|
||||
sandbox_tag(
|
||||
sandbox_policy,
|
||||
windows_sandbox_level,
|
||||
use_linux_sandbox_bwrap,
|
||||
)
|
||||
.to_string(),
|
||||
);
|
||||
let base_metadata = build_turn_metadata_bag(Some(turn_id), sandbox, None, None);
|
||||
let base_header = base_metadata
|
||||
.to_header_value()
|
||||
@@ -290,4 +298,52 @@ mod tests {
|
||||
Some(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_metadata_state_respects_linux_bubblewrap_toggle() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let cwd = temp_dir.path().to_path_buf();
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
let without_bubblewrap = TurnMetadataState::new(
|
||||
"turn-a".to_string(),
|
||||
cwd.clone(),
|
||||
&sandbox_policy,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
false,
|
||||
);
|
||||
let with_bubblewrap = TurnMetadataState::new(
|
||||
"turn-b".to_string(),
|
||||
cwd,
|
||||
&sandbox_policy,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
true,
|
||||
);
|
||||
|
||||
let without_bubblewrap_header = without_bubblewrap
|
||||
.current_header_value()
|
||||
.expect("without_bubblewrap_header");
|
||||
let with_bubblewrap_header = with_bubblewrap
|
||||
.current_header_value()
|
||||
.expect("with_bubblewrap_header");
|
||||
|
||||
let without_bubblewrap_json: Value =
|
||||
serde_json::from_str(&without_bubblewrap_header).expect("without_bubblewrap_json");
|
||||
let with_bubblewrap_json: Value =
|
||||
serde_json::from_str(&with_bubblewrap_header).expect("with_bubblewrap_json");
|
||||
|
||||
let without_bubblewrap_sandbox = without_bubblewrap_json
|
||||
.get("sandbox")
|
||||
.and_then(Value::as_str);
|
||||
let with_bubblewrap_sandbox = with_bubblewrap_json.get("sandbox").and_then(Value::as_str);
|
||||
|
||||
let expected_with_bubblewrap =
|
||||
sandbox_tag(&sandbox_policy, WindowsSandboxLevel::Disabled, true);
|
||||
assert_eq!(with_bubblewrap_sandbox, Some(expected_with_bubblewrap));
|
||||
|
||||
if cfg!(target_os = "linux") {
|
||||
assert_eq!(with_bubblewrap_sandbox, Some("linux_bubblewrap"));
|
||||
assert_ne!(with_bubblewrap_sandbox, without_bubblewrap_sandbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use codex_core::features::Features;
|
||||
use codex_core::protocol::ElicitationAction;
|
||||
use codex_core::protocol::ExecPolicyAmendment;
|
||||
use codex_core::protocol::FileChange;
|
||||
use codex_core::protocol::NetworkApprovalContext;
|
||||
use codex_core::protocol::Op;
|
||||
use codex_core::protocol::ReviewDecision;
|
||||
use codex_protocol::mcp::RequestId;
|
||||
@@ -42,6 +43,7 @@ pub(crate) enum ApprovalRequest {
|
||||
id: String,
|
||||
command: Vec<String>,
|
||||
reason: Option<String>,
|
||||
network_approval_context: Option<NetworkApprovalContext>,
|
||||
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
|
||||
},
|
||||
ApplyPatch {
|
||||
@@ -108,11 +110,23 @@ impl ApprovalOverlay {
|
||||
) -> (Vec<ApprovalOption>, SelectionViewParams) {
|
||||
let (options, title) = match &variant {
|
||||
ApprovalVariant::Exec {
|
||||
network_approval_context,
|
||||
proposed_execpolicy_amendment,
|
||||
..
|
||||
} => (
|
||||
exec_options(proposed_execpolicy_amendment.clone()),
|
||||
"Would you like to run the following command?".to_string(),
|
||||
exec_options(
|
||||
proposed_execpolicy_amendment.clone(),
|
||||
network_approval_context.as_ref(),
|
||||
),
|
||||
network_approval_context.as_ref().map_or_else(
|
||||
|| "Would you like to run the following command?".to_string(),
|
||||
|network_approval_context| {
|
||||
format!(
|
||||
"Do you want to approve access to \"{}\"?",
|
||||
network_approval_context.host
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
ApprovalVariant::ApplyPatch { .. } => (
|
||||
patch_options(),
|
||||
@@ -342,6 +356,7 @@ impl From<ApprovalRequest> for ApprovalRequestState {
|
||||
id,
|
||||
command,
|
||||
reason,
|
||||
network_approval_context,
|
||||
proposed_execpolicy_amendment,
|
||||
} => {
|
||||
let mut header: Vec<Line<'static>> = Vec::new();
|
||||
@@ -359,6 +374,7 @@ impl From<ApprovalRequest> for ApprovalRequestState {
|
||||
variant: ApprovalVariant::Exec {
|
||||
id,
|
||||
command,
|
||||
network_approval_context,
|
||||
proposed_execpolicy_amendment,
|
||||
},
|
||||
header: Box::new(Paragraph::new(header).wrap(Wrap { trim: false })),
|
||||
@@ -414,6 +430,7 @@ enum ApprovalVariant {
|
||||
Exec {
|
||||
id: String,
|
||||
command: Vec<String>,
|
||||
network_approval_context: Option<NetworkApprovalContext>,
|
||||
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
|
||||
},
|
||||
ApplyPatch {
|
||||
@@ -447,7 +464,33 @@ impl ApprovalOption {
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_options(proposed_execpolicy_amendment: Option<ExecPolicyAmendment>) -> Vec<ApprovalOption> {
|
||||
fn exec_options(
|
||||
proposed_execpolicy_amendment: Option<ExecPolicyAmendment>,
|
||||
network_approval_context: Option<&NetworkApprovalContext>,
|
||||
) -> Vec<ApprovalOption> {
|
||||
if network_approval_context.is_some() {
|
||||
return vec![
|
||||
ApprovalOption {
|
||||
label: "Yes, just this once".to_string(),
|
||||
decision: ApprovalDecision::Review(ReviewDecision::Approved),
|
||||
display_shortcut: None,
|
||||
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('y'))],
|
||||
},
|
||||
ApprovalOption {
|
||||
label: "Yes, and allow this host for this session".to_string(),
|
||||
decision: ApprovalDecision::Review(ReviewDecision::ApprovedForSession),
|
||||
display_shortcut: None,
|
||||
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('a'))],
|
||||
},
|
||||
ApprovalOption {
|
||||
label: "No, and tell Codex what to do differently".to_string(),
|
||||
decision: ApprovalDecision::Review(ReviewDecision::Abort),
|
||||
display_shortcut: Some(key_hint::plain(KeyCode::Esc)),
|
||||
additional_shortcuts: vec![key_hint::plain(KeyCode::Char('n'))],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
vec![ApprovalOption {
|
||||
label: "Yes, proceed".to_string(),
|
||||
decision: ApprovalDecision::Review(ReviewDecision::Approved),
|
||||
@@ -531,6 +574,7 @@ fn elicitation_options() -> Vec<ApprovalOption> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::app_event::AppEvent;
|
||||
use codex_core::protocol::NetworkApprovalProtocol;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
|
||||
@@ -539,6 +583,7 @@ mod tests {
|
||||
id: "test".to_string(),
|
||||
command: vec!["echo".to_string(), "hi".to_string()],
|
||||
reason: Some("reason".to_string()),
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
}
|
||||
}
|
||||
@@ -581,6 +626,7 @@ mod tests {
|
||||
id: "test".to_string(),
|
||||
command: vec!["echo".to_string()],
|
||||
reason: None,
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(vec![
|
||||
"echo".to_string(),
|
||||
])),
|
||||
@@ -619,6 +665,7 @@ mod tests {
|
||||
id: "test".into(),
|
||||
command,
|
||||
reason: None,
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
};
|
||||
|
||||
@@ -641,6 +688,67 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_exec_options_use_expected_labels_and_hide_execpolicy_amendment() {
|
||||
let network_context = NetworkApprovalContext {
|
||||
host: "example.com".to_string(),
|
||||
protocol: NetworkApprovalProtocol::Https,
|
||||
};
|
||||
let options = exec_options(
|
||||
Some(ExecPolicyAmendment::new(vec!["curl".to_string()])),
|
||||
Some(&network_context),
|
||||
);
|
||||
|
||||
let labels: Vec<String> = options.into_iter().map(|option| option.label).collect();
|
||||
assert_eq!(
|
||||
labels,
|
||||
vec![
|
||||
"Yes, just this once".to_string(),
|
||||
"Yes, and allow this host for this session".to_string(),
|
||||
"No, and tell Codex what to do differently".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_exec_prompt_title_includes_host() {
|
||||
let (tx, _rx) = unbounded_channel::<AppEvent>();
|
||||
let tx = AppEventSender::new(tx);
|
||||
let exec_request = ApprovalRequest::Exec {
|
||||
id: "test".into(),
|
||||
command: vec!["curl".into(), "https://example.com".into()],
|
||||
reason: Some("network request blocked".into()),
|
||||
network_approval_context: Some(NetworkApprovalContext {
|
||||
host: "example.com".to_string(),
|
||||
protocol: NetworkApprovalProtocol::Https,
|
||||
}),
|
||||
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(vec!["curl".into()])),
|
||||
};
|
||||
|
||||
let view = ApprovalOverlay::new(exec_request, tx, Features::with_defaults());
|
||||
let mut buf = Buffer::empty(Rect::new(0, 0, 100, view.desired_height(100)));
|
||||
view.render(Rect::new(0, 0, 100, view.desired_height(100)), &mut buf);
|
||||
|
||||
let rendered: Vec<String> = (0..buf.area.height)
|
||||
.map(|row| {
|
||||
(0..buf.area.width)
|
||||
.map(|col| buf[(col, row)].symbol().to_string())
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert!(
|
||||
rendered
|
||||
.iter()
|
||||
.any(|line| line.contains("Do you want to approve access to \"example.com\"?")),
|
||||
"expected network title to include host, got {rendered:?}"
|
||||
);
|
||||
assert!(
|
||||
!rendered.iter().any(|line| line.contains("don't ask again")),
|
||||
"network prompt should not show execpolicy option, got {rendered:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_history_cell_wraps_with_two_space_indent() {
|
||||
let command = vec![
|
||||
|
||||
@@ -1004,6 +1004,7 @@ mod tests {
|
||||
id: "1".to_string(),
|
||||
command: vec!["echo".into(), "ok".into()],
|
||||
reason: None,
|
||||
network_approval_context: None,
|
||||
proposed_execpolicy_amendment: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2400,6 +2400,7 @@ impl ChatWidget {
|
||||
id: ev.call_id,
|
||||
command: ev.command,
|
||||
reason: ev.reason,
|
||||
network_approval_context: ev.network_approval_context,
|
||||
proposed_execpolicy_amendment: ev.proposed_execpolicy_amendment,
|
||||
};
|
||||
self.bottom_pane
|
||||
@@ -5428,15 +5429,23 @@ impl ChatWidget {
|
||||
});
|
||||
})]
|
||||
} else {
|
||||
Self::approval_preset_actions(preset.approval, preset.sandbox.clone())
|
||||
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())
|
||||
Self::approval_preset_actions(
|
||||
preset.approval,
|
||||
preset.sandbox.clone(),
|
||||
name.clone(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Self::approval_preset_actions(preset.approval, preset.sandbox.clone())
|
||||
Self::approval_preset_actions(preset.approval, preset.sandbox.clone(), name.clone())
|
||||
};
|
||||
items.push(SelectionItem {
|
||||
name,
|
||||
@@ -5490,6 +5499,7 @@ impl ChatWidget {
|
||||
fn approval_preset_actions(
|
||||
approval: AskForApproval,
|
||||
sandbox: SandboxPolicy,
|
||||
label: String,
|
||||
) -> Vec<SelectionAction> {
|
||||
vec![Box::new(move |tx| {
|
||||
let sandbox_clone = sandbox.clone();
|
||||
@@ -5506,6 +5516,9 @@ impl ChatWidget {
|
||||
}));
|
||||
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),
|
||||
)));
|
||||
})]
|
||||
}
|
||||
|
||||
@@ -5552,6 +5565,7 @@ impl ChatWidget {
|
||||
preset: ApprovalPreset,
|
||||
return_to_permissions: bool,
|
||||
) {
|
||||
let selected_name = preset.label.to_string();
|
||||
let approval = preset.approval;
|
||||
let sandbox = preset.sandbox;
|
||||
let mut header_children: Vec<Box<dyn Renderable>> = Vec::new();
|
||||
@@ -5568,12 +5582,14 @@ impl ChatWidget {
|
||||
));
|
||||
let header = ColumnRenderable::with(header_children);
|
||||
|
||||
let mut accept_actions = Self::approval_preset_actions(approval, sandbox.clone());
|
||||
let mut accept_actions =
|
||||
Self::approval_preset_actions(approval, sandbox.clone(), selected_name.clone());
|
||||
accept_actions.push(Box::new(|tx| {
|
||||
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
|
||||
}));
|
||||
|
||||
let mut accept_and_remember_actions = Self::approval_preset_actions(approval, sandbox);
|
||||
let mut accept_and_remember_actions =
|
||||
Self::approval_preset_actions(approval, sandbox, selected_name);
|
||||
accept_and_remember_actions.push(Box::new(|tx| {
|
||||
tx.send(AppEvent::UpdateFullAccessWarningAcknowledged(true));
|
||||
tx.send(AppEvent::PersistFullAccessWarningAcknowledged);
|
||||
@@ -5683,7 +5699,11 @@ impl ChatWidget {
|
||||
}));
|
||||
}
|
||||
if let (Some(approval), Some(sandbox)) = (approval, sandbox.clone()) {
|
||||
accept_actions.extend(Self::approval_preset_actions(approval, sandbox));
|
||||
accept_actions.extend(Self::approval_preset_actions(
|
||||
approval,
|
||||
sandbox,
|
||||
mode_label.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut accept_and_remember_actions: Vec<SelectionAction> = Vec::new();
|
||||
@@ -5692,7 +5712,11 @@ impl ChatWidget {
|
||||
tx.send(AppEvent::PersistWorldWritableWarningAcknowledged);
|
||||
}));
|
||||
if let (Some(approval), Some(sandbox)) = (approval, sandbox) {
|
||||
accept_and_remember_actions.extend(Self::approval_preset_actions(approval, sandbox));
|
||||
accept_and_remember_actions.extend(Self::approval_preset_actions(
|
||||
approval,
|
||||
sandbox,
|
||||
mode_label.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let items = vec![
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
expression: "lines_to_single_string(&cells[0])"
|
||||
---
|
||||
• Permissions updated to Full Access
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
expression: "lines_to_single_string(&cells[0])"
|
||||
---
|
||||
• Permissions updated to Default
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
expression: "lines_to_single_string(&cells[0])"
|
||||
---
|
||||
• Permissions updated to Default (non-admin sandbox)
|
||||
@@ -20,6 +20,8 @@ use codex_core::config::Config;
|
||||
use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::Constrained;
|
||||
use codex_core::config::ConstraintError;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_core::config::types::WindowsSandboxModeToml;
|
||||
use codex_core::config_loader::RequirementSource;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::models_manager::manager::ModelsManager;
|
||||
@@ -5465,6 +5467,196 @@ async fn approvals_popup_navigation_skips_disabled() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_selection_emits_history_cell_when_selection_changes() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
chat.config.notices.hide_world_writable_warning = Some(true);
|
||||
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
|
||||
}
|
||||
chat.config.notices.hide_full_access_warning = Some(true);
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let cells = drain_insert_history(&mut rx);
|
||||
assert_eq!(
|
||||
cells.len(),
|
||||
1,
|
||||
"expected one permissions selection history cell"
|
||||
);
|
||||
let rendered = lines_to_single_string(&cells[0]);
|
||||
assert!(
|
||||
rendered.contains("Permissions updated to"),
|
||||
"expected permissions selection history message, got: {rendered}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_selection_history_snapshot_after_mode_switch() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
chat.config.notices.hide_world_writable_warning = Some(true);
|
||||
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
|
||||
}
|
||||
chat.config.notices.hide_full_access_warning = Some(true);
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
|
||||
#[cfg(target_os = "windows")]
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let cells = drain_insert_history(&mut rx);
|
||||
assert_eq!(cells.len(), 1, "expected one mode-switch history cell");
|
||||
assert_snapshot!(
|
||||
"permissions_selection_history_after_mode_switch",
|
||||
lines_to_single_string(&cells[0])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_selection_history_snapshot_full_access_to_default() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
chat.config.notices.hide_world_writable_warning = Some(true);
|
||||
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
|
||||
}
|
||||
chat.config.notices.hide_full_access_warning = Some(true);
|
||||
chat.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::Never)
|
||||
.expect("set approval policy");
|
||||
chat.config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("set sandbox policy");
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Up));
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let cells = drain_insert_history(&mut rx);
|
||||
assert_eq!(cells.len(), 1, "expected one mode-switch history cell");
|
||||
let rendered = lines_to_single_string(&cells[0]);
|
||||
#[cfg(target_os = "windows")]
|
||||
insta::with_settings!({ snapshot_suffix => "windows" }, {
|
||||
assert_snapshot!("permissions_selection_history_full_access_to_default", rendered);
|
||||
});
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert_snapshot!(
|
||||
"permissions_selection_history_full_access_to_default",
|
||||
rendered
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_selection_emits_history_cell_when_current_is_selected() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
chat.config.notices.hide_world_writable_warning = Some(true);
|
||||
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
|
||||
}
|
||||
chat.config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("set approval policy");
|
||||
chat.config
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::new_workspace_write_policy())
|
||||
.expect("set sandbox policy");
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let cells = drain_insert_history(&mut rx);
|
||||
assert_eq!(
|
||||
cells.len(),
|
||||
1,
|
||||
"expected history cell even when selecting current permissions"
|
||||
);
|
||||
let rendered = lines_to_single_string(&cells[0]);
|
||||
assert!(
|
||||
rendered.contains("Permissions updated to"),
|
||||
"expected permissions update history message, got: {rendered}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permissions_full_access_history_cell_emitted_only_after_confirmation() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
chat.config.notices.hide_world_writable_warning = Some(true);
|
||||
chat.set_windows_sandbox_mode(Some(WindowsSandboxModeToml::Unelevated));
|
||||
}
|
||||
chat.config.notices.hide_full_access_warning = None;
|
||||
|
||||
chat.open_permissions_popup();
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
|
||||
#[cfg(target_os = "windows")]
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let mut open_confirmation_event = None;
|
||||
let mut cells_before_confirmation = Vec::new();
|
||||
while let Ok(event) = rx.try_recv() {
|
||||
match event {
|
||||
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));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if cfg!(not(target_os = "windows")) {
|
||||
assert!(
|
||||
cells_before_confirmation.is_empty(),
|
||||
"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 popup = render_bottom_popup(&chat, 80);
|
||||
assert!(
|
||||
popup.contains("Enable full access?"),
|
||||
"expected full access confirmation popup, got: {popup}"
|
||||
);
|
||||
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
let cells_after_confirmation = drain_insert_history(&mut rx);
|
||||
let total_history_cells = cells_before_confirmation.len() + cells_after_confirmation.len();
|
||||
assert_eq!(
|
||||
total_history_cells, 1,
|
||||
"expected one full access history cell total"
|
||||
);
|
||||
let rendered = if !cells_before_confirmation.is_empty() {
|
||||
lines_to_single_string(&cells_before_confirmation[0])
|
||||
} else {
|
||||
lines_to_single_string(&cells_after_confirmation[0])
|
||||
};
|
||||
assert!(
|
||||
rendered.contains("Permissions updated to Full Access"),
|
||||
"expected full access update history message, got: {rendered}"
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Snapshot test: command approval modal
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user