diff --git a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json index ac8d5c4010..a6018ecce2 100644 --- a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json +++ b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json @@ -39,6 +39,13 @@ }, "type": "object" }, + "PermissionGrantScope": { + "enum": [ + "turn", + "session" + ], + "type": "string" + }, "RequestPermissionProfile": { "additionalProperties": false, "properties": { @@ -79,6 +86,14 @@ "null" ] }, + "suggestedScope": { + "allOf": [ + { + "$ref": "#/definitions/PermissionGrantScope" + } + ], + "default": "turn" + }, "threadId": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/ServerRequest.json b/codex-rs/app-server-protocol/schema/json/ServerRequest.json index 337058cafd..d8d3440bbb 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ServerRequest.json @@ -1409,6 +1409,13 @@ } ] }, + "PermissionGrantScope": { + "enum": [ + "turn", + "session" + ], + "type": "string" + }, "PermissionPresetId": { "enum": [ "auto", @@ -1481,6 +1488,14 @@ "null" ] }, + "suggestedScope": { + "allOf": [ + { + "$ref": "#/definitions/PermissionGrantScope" + } + ], + "default": "turn" + }, "threadId": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index bdcac72960..e97b518259 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -3359,6 +3359,14 @@ "null" ] }, + "suggestedScope": { + "allOf": [ + { + "$ref": "#/definitions/PermissionGrantScope" + } + ], + "default": "turn" + }, "threadId": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionsRequestApprovalParams.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionsRequestApprovalParams.ts index efdefead79..68fca99bf6 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/PermissionsRequestApprovalParams.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PermissionsRequestApprovalParams.ts @@ -1,6 +1,7 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PermissionGrantScope } from "./PermissionGrantScope"; import type { RequestPermissionProfile } from "./RequestPermissionProfile"; -export type PermissionsRequestApprovalParams = { threadId: string, turnId: string, itemId: string, reason: string | null, permissions: RequestPermissionProfile, }; +export type PermissionsRequestApprovalParams = { threadId: string, turnId: string, itemId: string, reason: string | null, permissions: RequestPermissionProfile, suggestedScope: PermissionGrantScope, }; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 3d59e4677c..d709628ee5 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -6221,6 +6221,8 @@ pub struct PermissionsRequestApprovalParams { pub item_id: String, pub reason: Option, pub permissions: RequestPermissionProfile, + #[serde(default)] + pub suggested_scope: PermissionGrantScope, } v2_enum_from_core!( @@ -6650,6 +6652,7 @@ mod tests { }), } ); + assert_eq!(params.suggested_scope, PermissionGrantScope::Turn); assert_eq!( CoreRequestPermissionProfile::from(params.permissions), @@ -6671,6 +6674,24 @@ mod tests { ); } + #[test] + fn permissions_request_approval_preserves_suggested_scope() { + let params = serde_json::from_value::(json!({ + "threadId": "thr_123", + "turnId": "turn_123", + "itemId": "call_123", + "reason": "Select a workspace root", + "permissions": { + "network": null, + "fileSystem": null, + }, + "suggestedScope": "session", + })) + .expect("permissions request should deserialize"); + + assert_eq!(params.suggested_scope, PermissionGrantScope::Session); + } + #[test] fn permissions_request_approval_rejects_macos_permissions() { let err = serde_json::from_value::(json!({ diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index fd42b9641f..3ae1add8d9 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -869,6 +869,7 @@ pub(crate) async fn apply_bespoke_event_handling( item_id: request.call_id.clone(), reason: request.reason, permissions: request.permissions.into(), + suggested_scope: request.suggested_scope.into(), }; let (pending_request_id, rx) = outgoing .send_request(ServerRequestPayload::PermissionsRequestApproval(params)) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 3394f01ff8..5cda87a37e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -3329,6 +3329,7 @@ impl Session { turn_id: turn_context.sub_id.clone(), reason: args.reason, permissions: args.permissions, + suggested_scope: args.scope, }); self.send_event(turn_context, event).await; rx_response.await.ok() diff --git a/codex-rs/core/src/codex_delegate.rs b/codex-rs/core/src/codex_delegate.rs index 55b3619e11..155c26fa32 100644 --- a/codex-rs/core/src/codex_delegate.rs +++ b/codex-rs/core/src/codex_delegate.rs @@ -771,6 +771,7 @@ async fn handle_request_permissions( let args = RequestPermissionsArgs { reason: event.reason, permissions: event.permissions, + scope: event.suggested_scope, }; let response_fut = parent_session.request_permissions(parent_ctx, call_id.clone(), args); let response = diff --git a/codex-rs/core/src/codex_delegate_tests.rs b/codex-rs/core/src/codex_delegate_tests.rs index 62ee884815..4e67f5e423 100644 --- a/codex-rs/core/src/codex_delegate_tests.rs +++ b/codex-rs/core/src/codex_delegate_tests.rs @@ -202,6 +202,7 @@ async fn handle_request_permissions_uses_tool_call_id_for_round_trip() { }), ..RequestPermissionProfile::default() }, + suggested_scope: PermissionGrantScope::Turn, }, &cancel_token, ) diff --git a/codex-rs/core/src/codex_tests.rs b/codex-rs/core/src/codex_tests.rs index 3660f43684..d2ed2f7551 100644 --- a/codex-rs/core/src/codex_tests.rs +++ b/codex-rs/core/src/codex_tests.rs @@ -3022,6 +3022,7 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests() }), ..RequestPermissionProfile::default() }, + scope: PermissionGrantScope::Session, }, ) .await @@ -3036,6 +3037,7 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests() panic!("expected request_permissions event"); }; assert_eq!(request.call_id, call_id); + assert_eq!(request.suggested_scope, PermissionGrantScope::Session); session .notify_request_permissions_response(&request.call_id, expected_response.clone()) @@ -3080,6 +3082,7 @@ async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_req }), ..RequestPermissionProfile::default() }, + scope: PermissionGrantScope::Turn, }, ) .await; diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 4cad3847a0..b28b21103b 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -708,8 +708,8 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::RequestPermissionsTool, key: "request_permissions_tool", - stage: Stage::UnderDevelopment, - default_enabled: false, + stage: Stage::Stable, + default_enabled: true, }, FeatureSpec { id: Feature::RequestPermissionPresetTool, diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 7a2ade226d..ac167cfc41 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -107,12 +107,9 @@ fn request_permissions_is_under_development() { } #[test] -fn request_permissions_tool_is_under_development() { - assert_eq!( - Feature::RequestPermissionsTool.stage(), - Stage::UnderDevelopment - ); - assert_eq!(Feature::RequestPermissionsTool.default_enabled(), false); +fn request_permissions_tool_is_stable_and_enabled_by_default() { + assert_eq!(Feature::RequestPermissionsTool.stage(), Stage::Stable); + assert_eq!(Feature::RequestPermissionsTool.default_enabled(), true); } #[test] diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 850b8b4b52..49bd6a998b 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -509,6 +509,7 @@ impl DeveloperInstructions { DeveloperInstructions::new(message) } + #[allow(clippy::too_many_arguments)] pub fn from_policy( sandbox_policy: &SandboxPolicy, approval_policy: AskForApproval, @@ -635,11 +636,11 @@ fn granular_prompt_intro_text() -> &'static str { } fn request_permissions_tool_prompt_section() -> &'static str { - "# request_permissions Tool\n\nThe built-in `request_permissions` tool is available in this session. Invoke it when you need to request additional `network` or `file_system` permissions before later shell-like commands need them. Request only the specific permissions required for the task." + "# request_permissions Tool\n\nThe built-in `request_permissions` tool is available in this session. Invoke it immediately when the user asks conversationally to allow specific filesystem or network access, for example \"allow writing to ~/Downloads in this session\" or \"grant access to /tmp/output\". Request only the specific permissions required. Set `scope` to `session` when the user explicitly asks for session-long access; otherwise use `turn`." } fn request_permission_preset_tool_prompt_section() -> &'static str { - "# request_permission_preset Tool\n\nThe built-in `request_permission_preset` tool is available in this session. Invoke it immediately when the user asks conversationally to change sandboxing, approval, or permission mode, for example \"make this session full access\", \"switch to full access\", \"make the session read-only\", or \"use guardian approvals\". The tool opens the permission-mode picker with the requested preset pre-selected; the session only changes if the user selects a mode there. Do not claim the mode changed until the tool returns an accepted decision. Use `request_permissions` instead for narrow filesystem or network grants required by later shell-like commands." + "# request_permission_preset Tool\n\nThe built-in `request_permission_preset` tool is available in this session. Invoke it immediately when the user asks conversationally to change the broad sandboxing, approval, or permission mode, for example \"make this session full access\", \"switch to full access\", \"make the session read-only\", or \"use guardian approvals\". The tool opens the permission-mode picker with the requested preset pre-selected; the session only changes if the user selects a mode there. Do not claim the mode changed until the tool returns an accepted decision. Use `request_permissions` instead for named paths or narrow filesystem/network grants; never use the preset picker for requests like \"allow writing to ~/Downloads\"." } fn granular_instructions( @@ -1875,6 +1876,8 @@ mod tests { assert!( text.contains("The built-in `request_permissions` tool is available in this session.") ); + assert!(text.contains("allow writing to ~/Downloads in this session")); + assert!(text.contains("Set `scope` to `session`")); } #[test] @@ -1896,8 +1899,10 @@ mod tests { let text = instructions.into_text(); assert!(text.contains("# request_permission_preset Tool")); assert!(text.contains( - "Invoke it immediately when the user asks conversationally to change sandboxing" + "asks conversationally to change the broad sandboxing, approval, or permission mode" )); + assert!(text.contains("Use `request_permissions` instead for named paths")); + assert!(text.contains("never use the preset picker")); } #[test] diff --git a/codex-rs/protocol/src/request_permissions.rs b/codex-rs/protocol/src/request_permissions.rs index 400fde4e20..11fe2b3c4e 100644 --- a/codex-rs/protocol/src/request_permissions.rs +++ b/codex-rs/protocol/src/request_permissions.rs @@ -50,6 +50,8 @@ pub struct RequestPermissionsArgs { #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, pub permissions: RequestPermissionProfile, + #[serde(default)] + pub scope: PermissionGrantScope, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] @@ -70,4 +72,6 @@ pub struct RequestPermissionsEvent { #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, pub permissions: RequestPermissionProfile, + #[serde(default)] + pub suggested_scope: PermissionGrantScope, } diff --git a/codex-rs/tools/src/local_tool.rs b/codex-rs/tools/src/local_tool.rs index fe37984fb0..274cd5d399 100644 --- a/codex-rs/tools/src/local_tool.rs +++ b/codex-rs/tools/src/local_tool.rs @@ -275,6 +275,16 @@ pub fn create_request_permissions_tool(description: String) -> ToolSpec { )), ), ("permissions".to_string(), permission_profile_schema()), + ( + "scope".to_string(), + JsonSchema::string_enum( + vec![json!("turn"), json!("session")], + Some( + "How long the granted permissions should apply. Use \"session\" when the user explicitly asks for access in this session; defaults to \"turn\"." + .to_string(), + ), + ), + ), ]); ToolSpec::Function(ResponsesApiTool { @@ -292,7 +302,7 @@ pub fn create_request_permissions_tool(description: String) -> ToolSpec { } pub fn request_permissions_tool_description() -> String { - "Request additional filesystem or network permissions from the user and wait for the client to grant a subset of the requested permission profile. Granted permissions apply automatically to later shell-like commands in the current turn, or for the rest of the session if the client approves them at session scope." + "Open a permissions request for specific filesystem or network access, such as writing to a named path like ~/Downloads. Use this immediately when the user conversationally asks to allow access to a specific path or network permission. Granted permissions apply automatically to later shell-like commands in the current turn, or for the rest of the session when scope is \"session\" and the client approves them at session scope." .to_string() } diff --git a/codex-rs/tools/src/local_tool_tests.rs b/codex-rs/tools/src/local_tool_tests.rs index b751545b3a..c71578a6c6 100644 --- a/codex-rs/tools/src/local_tool_tests.rs +++ b/codex-rs/tools/src/local_tool_tests.rs @@ -308,6 +308,16 @@ fn request_permissions_tool_includes_full_permission_schema() { )), ), ("permissions".to_string(), permission_profile_schema()), + ( + "scope".to_string(), + JsonSchema::string_enum( + vec![serde_json::json!("turn"), serde_json::json!("session")], + Some( + "How long the granted permissions should apply. Use \"session\" when the user explicitly asks for access in this session; defaults to \"turn\"." + .to_string(), + ), + ), + ), ]); assert_eq!( diff --git a/codex-rs/tools/src/tool_registry_plan_tests.rs b/codex-rs/tools/src/tool_registry_plan_tests.rs index 37e4560c10..5ef00cfad9 100644 --- a/codex-rs/tools/src/tool_registry_plan_tests.rs +++ b/codex-rs/tools/src/tool_registry_plan_tests.rs @@ -123,7 +123,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() { expected.insert(spec.name().to_string(), spec); } - if config.exec_permission_approvals_enabled { + if config.request_permissions_tool_enabled { let spec = create_request_permissions_tool(request_permissions_tool_description()); expected.insert(spec.name().to_string(), spec); } @@ -569,7 +569,7 @@ fn request_user_input_description_reflects_default_mode_feature_flag() { } #[test] -fn request_permissions_requires_feature_flag() { +fn request_permissions_is_enabled_by_default_and_can_be_disabled() { let model_info = model_info(); let features = Features::with_defaults(); let available_models = Vec::new(); @@ -589,31 +589,31 @@ fn request_permissions_requires_feature_flag() { /*deferred_mcp_tools*/ None, &[], ); - assert_lacks_tool_name(&tools, "request_permissions"); - - let mut features = Features::with_defaults(); - features.enable(Feature::RequestPermissionsTool); - let tools_config = ToolsConfig::new(&ToolsConfigParams { - model_info: &model_info, - available_models: &available_models, - features: &features, - image_generation_tool_auth_allowed: true, - web_search_mode: Some(WebSearchMode::Cached), - session_source: SessionSource::Cli, - sandbox_policy: &SandboxPolicy::DangerFullAccess, - windows_sandbox_level: WindowsSandboxLevel::Disabled, - }); - let (tools, _) = build_specs( - &tools_config, - /*mcp_tools*/ None, - /*deferred_mcp_tools*/ None, - &[], - ); let request_permissions_tool = find_tool(&tools, "request_permissions"); assert_eq!( request_permissions_tool.spec, create_request_permissions_tool(request_permissions_tool_description()) ); + + let mut features = Features::with_defaults(); + features.disable(Feature::RequestPermissionsTool); + let tools_config = ToolsConfig::new(&ToolsConfigParams { + model_info: &model_info, + available_models: &available_models, + features: &features, + image_generation_tool_auth_allowed: true, + web_search_mode: Some(WebSearchMode::Cached), + session_source: SessionSource::Cli, + sandbox_policy: &SandboxPolicy::DangerFullAccess, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + }); + let (tools, _) = build_specs( + &tools_config, + /*mcp_tools*/ None, + /*deferred_mcp_tools*/ None, + &[], + ); + assert_lacks_tool_name(&tools, "request_permissions"); } #[test] @@ -693,7 +693,11 @@ fn request_permissions_tool_is_independent_from_additional_permissions() { &[], ); - assert_lacks_tool_name(&tools, "request_permissions"); + let request_permissions_tool = find_tool(&tools, "request_permissions"); + assert_eq!( + request_permissions_tool.spec, + create_request_permissions_tool(request_permissions_tool_description()) + ); } #[test] diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index f060f4c932..a703d6af28 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -1842,6 +1842,7 @@ impl App { call_id: params.item_id.clone(), reason: params.reason.clone(), permissions: params.permissions.clone().into(), + suggested_scope: params.suggested_scope.to_core(), }), ), ServerRequest::PermissionPresetRequestApproval { params, .. } => Some( @@ -8711,11 +8712,13 @@ guardian_approval = true write: Some(vec![test_absolute_path("/tmp/write")]), }), }, + suggested_scope: codex_app_server_protocol::PermissionGrantScope::Turn, }, }; let Some(ThreadInteractiveRequest::Approval(ApprovalRequest::Permissions { permissions, + suggested_scope, .. })) = app .interactive_request_for_thread_request(thread_id, &request) @@ -8736,6 +8739,10 @@ guardian_approval = true }), } ); + assert_eq!( + suggested_scope, + codex_protocol::request_permissions::PermissionGrantScope::Turn + ); } #[tokio::test] diff --git a/codex-rs/tui/src/app/app_server_requests.rs b/codex-rs/tui/src/app/app_server_requests.rs index 24f75d65ba..dc55844da1 100644 --- a/codex-rs/tui/src/app/app_server_requests.rs +++ b/codex-rs/tui/src/app/app_server_requests.rs @@ -394,6 +394,7 @@ mod tests { "network": { "enabled": null } })) .expect("valid permissions"), + suggested_scope: codex_app_server_protocol::PermissionGrantScope::Turn, }, }), None diff --git a/codex-rs/tui/src/bottom_pane/approval_overlay.rs b/codex-rs/tui/src/bottom_pane/approval_overlay.rs index 96b86aacf6..46d7da7434 100644 --- a/codex-rs/tui/src/bottom_pane/approval_overlay.rs +++ b/codex-rs/tui/src/bottom_pane/approval_overlay.rs @@ -60,6 +60,7 @@ pub(crate) enum ApprovalRequest { call_id: String, reason: Option, permissions: RequestPermissionProfile, + suggested_scope: PermissionGrantScope, }, ApplyPatch { thread_id: ThreadId, @@ -144,7 +145,7 @@ impl ApprovalOverlay { header: Box, _features: &Features, ) -> (Vec, SelectionViewParams) { - let (options, title) = match request { + let (options, title, initial_selected_idx) = match request { ApprovalRequest::Exec { available_decisions, network_approval_context, @@ -165,18 +166,24 @@ impl ApprovalOverlay { ) }, ), + None, ), - ApprovalRequest::Permissions { .. } => ( + ApprovalRequest::Permissions { + suggested_scope, .. + } => ( permissions_options(), "Would you like to grant these permissions?".to_string(), + matches!(suggested_scope, PermissionGrantScope::Session).then_some(1), ), ApprovalRequest::ApplyPatch { .. } => ( patch_options(), "Would you like to make the following edits?".to_string(), + None, ), ApprovalRequest::McpElicitation { server_name, .. } => ( elicitation_options(), format!("{server_name} needs your approval."), + None, ), }; @@ -202,6 +209,7 @@ impl ApprovalOverlay { footer_hint: Some(approval_footer_hint(request)), items, header, + initial_selected_idx, ..Default::default() }; @@ -918,6 +926,7 @@ mod tests { write: Some(vec![absolute_path("/tmp/out.txt")]), }), }, + suggested_scope: PermissionGrantScope::Turn, } } @@ -1258,6 +1267,40 @@ mod tests { ); } + #[test] + fn permissions_session_suggestion_preselects_session_scope() { + let (tx, mut rx) = unbounded_channel::(); + let tx = AppEventSender::new(tx); + let mut request = make_permissions_request(); + let ApprovalRequest::Permissions { + suggested_scope, .. + } = &mut request + else { + panic!("expected permissions request"); + }; + *suggested_scope = PermissionGrantScope::Session; + let mut view = ApprovalOverlay::new(request, tx, Features::with_defaults()); + + view.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); + + let mut saw_op = false; + while let Ok(ev) = rx.try_recv() { + if let AppEvent::SubmitThreadOp { + op: Op::RequestPermissionsResponse { response, .. }, + .. + } = ev + { + assert_eq!(response.scope, PermissionGrantScope::Session); + saw_op = true; + break; + } + } + assert!( + saw_op, + "expected preselected session approval to submit a session-scoped response" + ); + } + #[test] fn additional_permissions_prompt_shows_permission_rule_line() { let (tx, _rx) = unbounded_channel::(); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index f254efa1e4..a5e5ec2de1 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1577,6 +1577,7 @@ fn request_permissions_from_params( call_id: params.item_id, reason: params.reason, permissions: params.permissions.into(), + suggested_scope: params.suggested_scope.to_core(), } } @@ -4610,6 +4611,7 @@ impl ChatWidget { call_id: ev.call_id, reason: ev.reason, permissions: ev.permissions, + suggested_scope: ev.suggested_scope, }; self.bottom_pane .push_approval_request(request, &self.config.features); @@ -8644,7 +8646,7 @@ impl ChatWidget { RequestPermissionPresetResponse { decision: RequestPermissionPresetDecision::Accepted, preset: *preset, - message: message.clone(), + message, }, ); } diff --git a/codex-rs/tui/src/chatwidget/tests/approval_requests.rs b/codex-rs/tui/src/chatwidget/tests/approval_requests.rs index 33edd2370e..fd3813bc03 100644 --- a/codex-rs/tui/src/chatwidget/tests/approval_requests.rs +++ b/codex-rs/tui/src/chatwidget/tests/approval_requests.rs @@ -230,6 +230,7 @@ fn app_server_request_permissions_preserves_file_system_permissions() { write: Some(vec![write_path.clone()]), }), }, + suggested_scope: codex_app_server_protocol::PermissionGrantScope::Turn, }); assert_eq!(