diff --git a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json index 0d5c09193a65..ac8d5c4010c6 100644 --- a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json +++ b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json @@ -28,41 +28,6 @@ }, "type": "object" }, - "AdditionalMacOsPermissions": { - "properties": { - "accessibility": { - "type": "boolean" - }, - "automations": { - "$ref": "#/definitions/MacOsAutomationPermission" - }, - "calendar": { - "type": "boolean" - }, - "contacts": { - "$ref": "#/definitions/MacOsContactsPermission" - }, - "launchServices": { - "type": "boolean" - }, - "preferences": { - "$ref": "#/definitions/MacOsPreferencesPermission" - }, - "reminders": { - "type": "boolean" - } - }, - "required": [ - "accessibility", - "automations", - "calendar", - "contacts", - "launchServices", - "preferences", - "reminders" - ], - "type": "object" - }, "AdditionalNetworkPermissions": { "properties": { "enabled": { @@ -74,7 +39,8 @@ }, "type": "object" }, - "AdditionalPermissionProfile": { + "RequestPermissionProfile": { + "additionalProperties": false, "properties": { "fileSystem": { "anyOf": [ @@ -86,16 +52,6 @@ } ] }, - "macos": { - "anyOf": [ - { - "$ref": "#/definitions/AdditionalMacOsPermissions" - }, - { - "type": "null" - } - ] - }, "network": { "anyOf": [ { @@ -108,49 +64,6 @@ } }, "type": "object" - }, - "MacOsAutomationPermission": { - "oneOf": [ - { - "enum": [ - "none", - "all" - ], - "type": "string" - }, - { - "additionalProperties": false, - "properties": { - "bundle_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "bundle_ids" - ], - "title": "BundleIdsMacOsAutomationPermission", - "type": "object" - } - ] - }, - "MacOsContactsPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, - "MacOsPreferencesPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" } }, "properties": { @@ -158,7 +71,7 @@ "type": "string" }, "permissions": { - "$ref": "#/definitions/AdditionalPermissionProfile" + "$ref": "#/definitions/RequestPermissionProfile" }, "reason": { "type": [ diff --git a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalResponse.json b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalResponse.json index df9e519dcf4d..7b0c2b1a3bc4 100644 --- a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalResponse.json +++ b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalResponse.json @@ -39,65 +39,6 @@ }, "type": "object" }, - "GrantedMacOsPermissions": { - "properties": { - "accessibility": { - "type": [ - "boolean", - "null" - ] - }, - "automations": { - "anyOf": [ - { - "$ref": "#/definitions/MacOsAutomationPermission" - }, - { - "type": "null" - } - ] - }, - "calendar": { - "type": [ - "boolean", - "null" - ] - }, - "contacts": { - "anyOf": [ - { - "$ref": "#/definitions/MacOsContactsPermission" - }, - { - "type": "null" - } - ] - }, - "launchServices": { - "type": [ - "boolean", - "null" - ] - }, - "preferences": { - "anyOf": [ - { - "$ref": "#/definitions/MacOsPreferencesPermission" - }, - { - "type": "null" - } - ] - }, - "reminders": { - "type": [ - "boolean", - "null" - ] - } - }, - "type": "object" - }, "GrantedPermissionProfile": { "properties": { "fileSystem": { @@ -110,16 +51,6 @@ } ] }, - "macos": { - "anyOf": [ - { - "$ref": "#/definitions/GrantedMacOsPermissions" - }, - { - "type": "null" - } - ] - }, "network": { "anyOf": [ { @@ -133,49 +64,6 @@ }, "type": "object" }, - "MacOsAutomationPermission": { - "oneOf": [ - { - "enum": [ - "none", - "all" - ], - "type": "string" - }, - { - "additionalProperties": false, - "properties": { - "bundle_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "bundle_ids" - ], - "title": "BundleIdsMacOsAutomationPermission", - "type": "object" - } - ] - }, - "MacOsContactsPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, - "MacOsPreferencesPermission": { - "enum": [ - "none", - "read_only", - "read_write" - ], - "type": "string" - }, "PermissionGrantScope": { "enum": [ "turn", diff --git a/codex-rs/app-server-protocol/schema/json/ServerRequest.json b/codex-rs/app-server-protocol/schema/json/ServerRequest.json index a00871971fdf..1fbbfb1b0a3c 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ServerRequest.json @@ -1449,7 +1449,7 @@ "type": "string" }, "permissions": { - "$ref": "#/definitions/AdditionalPermissionProfile" + "$ref": "#/definitions/RequestPermissionProfile" }, "reason": { "type": [ @@ -1483,6 +1483,32 @@ } ] }, + "RequestPermissionProfile": { + "additionalProperties": false, + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/AdditionalFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/AdditionalNetworkPermissions" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, "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 75054b0d6abe..3f30eeeea776 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 @@ -2198,65 +2198,6 @@ "title": "FuzzyFileSearchSessionUpdatedNotification", "type": "object" }, - "GrantedMacOsPermissions": { - "properties": { - "accessibility": { - "type": [ - "boolean", - "null" - ] - }, - "automations": { - "anyOf": [ - { - "$ref": "#/definitions/MacOsAutomationPermission" - }, - { - "type": "null" - } - ] - }, - "calendar": { - "type": [ - "boolean", - "null" - ] - }, - "contacts": { - "anyOf": [ - { - "$ref": "#/definitions/MacOsContactsPermission" - }, - { - "type": "null" - } - ] - }, - "launchServices": { - "type": [ - "boolean", - "null" - ] - }, - "preferences": { - "anyOf": [ - { - "$ref": "#/definitions/MacOsPreferencesPermission" - }, - { - "type": "null" - } - ] - }, - "reminders": { - "type": [ - "boolean", - "null" - ] - } - }, - "type": "object" - }, "GrantedPermissionProfile": { "properties": { "fileSystem": { @@ -2269,16 +2210,6 @@ } ] }, - "macos": { - "anyOf": [ - { - "$ref": "#/definitions/GrantedMacOsPermissions" - }, - { - "type": "null" - } - ] - }, "network": { "anyOf": [ { @@ -3324,7 +3255,7 @@ "type": "string" }, "permissions": { - "$ref": "#/definitions/AdditionalPermissionProfile" + "$ref": "#/definitions/RequestPermissionProfile" }, "reason": { "type": [ @@ -3382,6 +3313,32 @@ ], "title": "RequestId" }, + "RequestPermissionProfile": { + "additionalProperties": false, + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/AdditionalFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/AdditionalNetworkPermissions" + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }, "ReviewDecision": { "description": "User's decision in response to an ExecApprovalRequest.", "oneOf": [ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/GrantedMacOsPermissions.ts b/codex-rs/app-server-protocol/schema/typescript/v2/GrantedMacOsPermissions.ts deleted file mode 100644 index edf779488742..000000000000 --- a/codex-rs/app-server-protocol/schema/typescript/v2/GrantedMacOsPermissions.ts +++ /dev/null @@ -1,8 +0,0 @@ -// 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 { MacOsAutomationPermission } from "../MacOsAutomationPermission"; -import type { MacOsContactsPermission } from "../MacOsContactsPermission"; -import type { MacOsPreferencesPermission } from "../MacOsPreferencesPermission"; - -export type GrantedMacOsPermissions = { preferences?: MacOsPreferencesPermission, automations?: MacOsAutomationPermission, launchServices?: boolean, accessibility?: boolean, calendar?: boolean, reminders?: boolean, contacts?: MacOsContactsPermission, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/GrantedPermissionProfile.ts b/codex-rs/app-server-protocol/schema/typescript/v2/GrantedPermissionProfile.ts index 84a9aa3778da..3ae6c6051129 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/GrantedPermissionProfile.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/GrantedPermissionProfile.ts @@ -3,6 +3,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions"; import type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions"; -import type { GrantedMacOsPermissions } from "./GrantedMacOsPermissions"; -export type GrantedPermissionProfile = { network?: AdditionalNetworkPermissions, fileSystem?: AdditionalFileSystemPermissions, macos?: GrantedMacOsPermissions, }; +export type GrantedPermissionProfile = { network?: AdditionalNetworkPermissions, fileSystem?: AdditionalFileSystemPermissions, }; 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 cde277f1a426..efdefead794a 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,6 @@ // 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 { AdditionalPermissionProfile } from "./AdditionalPermissionProfile"; +import type { RequestPermissionProfile } from "./RequestPermissionProfile"; -export type PermissionsRequestApprovalParams = { threadId: string, turnId: string, itemId: string, reason: string | null, permissions: AdditionalPermissionProfile, }; +export type PermissionsRequestApprovalParams = { threadId: string, turnId: string, itemId: string, reason: string | null, permissions: RequestPermissionProfile, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RequestPermissionProfile.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RequestPermissionProfile.ts new file mode 100644 index 000000000000..2bf8d8dffefd --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RequestPermissionProfile.ts @@ -0,0 +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 { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPermissions"; +import type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions"; + +export type RequestPermissionProfile = { network: AdditionalNetworkPermissions | null, fileSystem: AdditionalFileSystemPermissions | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 32272e59bd05..b8419a79ddef 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -115,7 +115,6 @@ export type { GetAccountParams } from "./GetAccountParams"; export type { GetAccountRateLimitsResponse } from "./GetAccountRateLimitsResponse"; export type { GetAccountResponse } from "./GetAccountResponse"; export type { GitInfo } from "./GitInfo"; -export type { GrantedMacOsPermissions } from "./GrantedMacOsPermissions"; export type { GrantedPermissionProfile } from "./GrantedPermissionProfile"; export type { GuardianApprovalReview } from "./GuardianApprovalReview"; export type { GuardianApprovalReviewStatus } from "./GuardianApprovalReviewStatus"; @@ -222,6 +221,7 @@ export type { ReasoningSummaryPartAddedNotification } from "./ReasoningSummaryPa export type { ReasoningSummaryTextDeltaNotification } from "./ReasoningSummaryTextDeltaNotification"; export type { ReasoningTextDeltaNotification } from "./ReasoningTextDeltaNotification"; export type { RemoteSkillSummary } from "./RemoteSkillSummary"; +export type { RequestPermissionProfile } from "./RequestPermissionProfile"; export type { ResidencyRequirement } from "./ResidencyRequirement"; export type { ReviewDelivery } from "./ReviewDelivery"; export type { ReviewStartParams } from "./ReviewStartParams"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index e86900cbc725..bc2e51d36c70 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -80,6 +80,7 @@ use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource; use codex_protocol::protocol::TokenUsage as CoreTokenUsage; use codex_protocol::protocol::TokenUsageInfo as CoreTokenUsageInfo; use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope; +use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile; use codex_protocol::user_input::ByteRange as CoreByteRange; use codex_protocol::user_input::TextElement as CoreTextElement; use codex_protocol::user_input::UserInput as CoreUserInput; @@ -1081,74 +1082,56 @@ impl From for CoreNetworkPermissions { #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] #[ts(export_to = "v2/")] -pub struct AdditionalPermissionProfile { +pub struct RequestPermissionProfile { pub network: Option, pub file_system: Option, - pub macos: Option, } -impl From for AdditionalPermissionProfile { - fn from(value: CorePermissionProfile) -> Self { +impl From for RequestPermissionProfile { + fn from(value: CoreRequestPermissionProfile) -> Self { Self { network: value.network.map(AdditionalNetworkPermissions::from), file_system: value.file_system.map(AdditionalFileSystemPermissions::from), - macos: value.macos.map(AdditionalMacOsPermissions::from), } } } -impl From for CorePermissionProfile { - fn from(value: AdditionalPermissionProfile) -> Self { +impl From for CoreRequestPermissionProfile { + fn from(value: RequestPermissionProfile) -> Self { Self { network: value.network.map(CoreNetworkPermissions::from), file_system: value.file_system.map(CoreFileSystemPermissions::from), - macos: value.macos.map(CoreMacOsSeatbeltProfileExtensions::from), } } } -#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] -pub struct GrantedMacOsPermissions { - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub preferences: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub automations: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub launch_services: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub accessibility: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub calendar: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub reminders: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub contacts: Option, +pub struct AdditionalPermissionProfile { + pub network: Option, + pub file_system: Option, + pub macos: Option, } -impl From for CoreMacOsSeatbeltProfileExtensions { - fn from(value: GrantedMacOsPermissions) -> Self { +impl From for AdditionalPermissionProfile { + fn from(value: CorePermissionProfile) -> Self { Self { - macos_preferences: value - .preferences - .unwrap_or(CoreMacOsPreferencesPermission::None), - macos_automation: value - .automations - .unwrap_or(CoreMacOsAutomationPermission::None), - macos_launch_services: value.launch_services.unwrap_or(false), - macos_accessibility: value.accessibility.unwrap_or(false), - macos_calendar: value.calendar.unwrap_or(false), - macos_reminders: value.reminders.unwrap_or(false), - macos_contacts: value.contacts.unwrap_or(CoreMacOsContactsPermission::None), + network: value.network.map(AdditionalNetworkPermissions::from), + file_system: value.file_system.map(AdditionalFileSystemPermissions::from), + macos: value.macos.map(AdditionalMacOsPermissions::from), + } + } +} + +impl From for CorePermissionProfile { + fn from(value: AdditionalPermissionProfile) -> Self { + Self { + network: value.network.map(CoreNetworkPermissions::from), + file_system: value.file_system.map(CoreFileSystemPermissions::from), + macos: value.macos.map(CoreMacOsSeatbeltProfileExtensions::from), } } } @@ -1163,32 +1146,14 @@ pub struct GrantedPermissionProfile { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub file_system: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - #[ts(optional)] - pub macos: Option, } impl From for CorePermissionProfile { fn from(value: GrantedPermissionProfile) -> Self { - let macos = value.macos.and_then(|macos| { - if macos.preferences.is_none() - && macos.automations.is_none() - && macos.launch_services.is_none() - && macos.accessibility.is_none() - && macos.calendar.is_none() - && macos.reminders.is_none() - && macos.contacts.is_none() - { - None - } else { - Some(CoreMacOsSeatbeltProfileExtensions::from(macos)) - } - }); - Self { network: value.network.map(CoreNetworkPermissions::from), file_system: value.file_system.map(CoreFileSystemPermissions::from), - macos, + macos: None, } } } @@ -5570,7 +5535,7 @@ pub struct PermissionsRequestApprovalParams { pub turn_id: String, pub item_id: String, pub reason: Option, - pub permissions: AdditionalPermissionProfile, + pub permissions: RequestPermissionProfile, } v2_enum_from_core!( @@ -5997,192 +5962,164 @@ mod tests { } #[test] - fn permissions_request_approval_response_accepts_partial_macos_grants() { - let cases = vec![ - (json!({}), Some(GrantedMacOsPermissions::default()), None), - ( - json!({ - "preferences": "read_only", - }), - Some(GrantedMacOsPermissions { - preferences: Some(CoreMacOsPreferencesPermission::ReadOnly), - ..Default::default() - }), - Some(CoreMacOsSeatbeltProfileExtensions { - macos_preferences: CoreMacOsPreferencesPermission::ReadOnly, - macos_automation: CoreMacOsAutomationPermission::None, - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: false, - macos_contacts: CoreMacOsContactsPermission::None, - }), - ), - ( - json!({ - "automations": { - "bundle_ids": ["com.apple.Notes"], - }, - }), - Some(GrantedMacOsPermissions { - automations: Some(CoreMacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ])), - ..Default::default() + fn permissions_request_approval_uses_request_permission_profile() { + let read_only_path = if cfg!(windows) { + r"C:\tmp\read-only" + } else { + "/tmp/read-only" + }; + let read_write_path = if cfg!(windows) { + r"C:\tmp\read-write" + } else { + "/tmp/read-write" + }; + let params = serde_json::from_value::(json!({ + "threadId": "thr_123", + "turnId": "turn_123", + "itemId": "call_123", + "reason": "Select a workspace root", + "permissions": { + "network": { + "enabled": true, + }, + "fileSystem": { + "read": [read_only_path], + "write": [read_write_path], + }, + }, + })) + .expect("permissions request should deserialize"); + + assert_eq!( + params.permissions, + RequestPermissionProfile { + network: Some(AdditionalNetworkPermissions { + enabled: Some(true), }), - Some(CoreMacOsSeatbeltProfileExtensions { - macos_preferences: CoreMacOsPreferencesPermission::None, - macos_automation: CoreMacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), + file_system: Some(AdditionalFileSystemPermissions { + read: Some(vec![ + AbsolutePathBuf::try_from(PathBuf::from(read_only_path)) + .expect("path must be absolute"), + ]), + write: Some(vec![ + AbsolutePathBuf::try_from(PathBuf::from(read_write_path)) + .expect("path must be absolute"), ]), - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: false, - macos_contacts: CoreMacOsContactsPermission::None, - }), - ), - ( - json!({ - "launchServices": true, - }), - Some(GrantedMacOsPermissions { - launch_services: Some(true), - ..Default::default() - }), - Some(CoreMacOsSeatbeltProfileExtensions { - macos_preferences: CoreMacOsPreferencesPermission::None, - macos_automation: CoreMacOsAutomationPermission::None, - macos_launch_services: true, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: false, - macos_contacts: CoreMacOsContactsPermission::None, - }), - ), - ( - json!({ - "accessibility": true, - }), - Some(GrantedMacOsPermissions { - accessibility: Some(true), - ..Default::default() - }), - Some(CoreMacOsSeatbeltProfileExtensions { - macos_preferences: CoreMacOsPreferencesPermission::None, - macos_automation: CoreMacOsAutomationPermission::None, - macos_launch_services: false, - macos_accessibility: true, - macos_calendar: false, - macos_reminders: false, - macos_contacts: CoreMacOsContactsPermission::None, - }), - ), - ( - json!({ - "calendar": true, - }), - Some(GrantedMacOsPermissions { - calendar: Some(true), - ..Default::default() - }), - Some(CoreMacOsSeatbeltProfileExtensions { - macos_preferences: CoreMacOsPreferencesPermission::None, - macos_automation: CoreMacOsAutomationPermission::None, - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: true, - macos_reminders: false, - macos_contacts: CoreMacOsContactsPermission::None, - }), - ), - ( - json!({ - "reminders": true, - }), - Some(GrantedMacOsPermissions { - reminders: Some(true), - ..Default::default() - }), - Some(CoreMacOsSeatbeltProfileExtensions { - macos_preferences: CoreMacOsPreferencesPermission::None, - macos_automation: CoreMacOsAutomationPermission::None, - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: true, - macos_contacts: CoreMacOsContactsPermission::None, - }), - ), - ( - json!({ - "contacts": "read_only", }), - Some(GrantedMacOsPermissions { - contacts: Some(CoreMacOsContactsPermission::ReadOnly), - ..Default::default() + } + ); + + assert_eq!( + CoreRequestPermissionProfile::from(params.permissions), + CoreRequestPermissionProfile { + network: Some(CoreNetworkPermissions { + enabled: Some(true), }), - Some(CoreMacOsSeatbeltProfileExtensions { - macos_preferences: CoreMacOsPreferencesPermission::None, - macos_automation: CoreMacOsAutomationPermission::None, - macos_launch_services: false, - macos_accessibility: false, - macos_calendar: false, - macos_reminders: false, - macos_contacts: CoreMacOsContactsPermission::ReadOnly, + file_system: Some(CoreFileSystemPermissions { + read: Some(vec![ + AbsolutePathBuf::try_from(PathBuf::from(read_only_path)) + .expect("path must be absolute"), + ]), + write: Some(vec![ + AbsolutePathBuf::try_from(PathBuf::from(read_write_path)) + .expect("path must be absolute"), + ]), }), - ), - ]; + } + ); + } - for (macos_json, expected_granted_macos, expected_core_macos) in cases { - let response = serde_json::from_value::(json!({ - "permissions": { - "macos": macos_json, + #[test] + fn permissions_request_approval_rejects_macos_permissions() { + let err = serde_json::from_value::(json!({ + "threadId": "thr_123", + "turnId": "turn_123", + "itemId": "call_123", + "reason": "Select a workspace root", + "permissions": { + "network": null, + "fileSystem": null, + "macos": { + "preferences": "read_only", + "automations": "none", + "launchServices": false, + "accessibility": false, + "calendar": false, + "reminders": false, + "contacts": "none", }, - })) - .expect("partial macos permissions response should deserialize"); - - assert_eq!( - response.permissions, - GrantedPermissionProfile { - macos: expected_granted_macos, - ..Default::default() - } - ); + }, + })) + .expect_err("permissions request should reject macos permissions"); - assert_eq!( - CorePermissionProfile::from(response.permissions), - CorePermissionProfile { - macos: expected_core_macos, - ..Default::default() - } - ); - } + assert!( + err.to_string().contains("unknown field `macos`"), + "unexpected error: {err}" + ); } #[test] - fn permissions_request_approval_response_omits_ungranted_macos_keys_when_serialized() { - let response = PermissionsRequestApprovalResponse { - permissions: GrantedPermissionProfile { - macos: Some(GrantedMacOsPermissions { - accessibility: Some(true), - ..Default::default() - }), - ..Default::default() - }, - scope: PermissionGrantScope::Turn, + fn permissions_request_approval_response_uses_granted_permission_profile_without_macos() { + let read_only_path = if cfg!(windows) { + r"C:\tmp\read-only" + } else { + "/tmp/read-only" + }; + let read_write_path = if cfg!(windows) { + r"C:\tmp\read-write" + } else { + "/tmp/read-write" }; + let response = serde_json::from_value::(json!({ + "permissions": { + "network": { + "enabled": true, + }, + "fileSystem": { + "read": [read_only_path], + "write": [read_write_path], + }, + }, + })) + .expect("permissions response should deserialize"); assert_eq!( - serde_json::to_value(response).expect("response should serialize"), - json!({ - "permissions": { - "macos": { - "accessibility": true, - }, - }, - "scope": "turn", - }) + response.permissions, + GrantedPermissionProfile { + network: Some(AdditionalNetworkPermissions { + enabled: Some(true), + }), + file_system: Some(AdditionalFileSystemPermissions { + read: Some(vec![ + AbsolutePathBuf::try_from(PathBuf::from(read_only_path)) + .expect("path must be absolute"), + ]), + write: Some(vec![ + AbsolutePathBuf::try_from(PathBuf::from(read_write_path)) + .expect("path must be absolute"), + ]), + }), + } + ); + + assert_eq!( + CorePermissionProfile::from(response.permissions), + CorePermissionProfile { + network: Some(CoreNetworkPermissions { + enabled: Some(true), + }), + file_system: Some(CoreFileSystemPermissions { + read: Some(vec![ + AbsolutePathBuf::try_from(PathBuf::from(read_only_path)) + .expect("path must be absolute"), + ]), + write: Some(vec![ + AbsolutePathBuf::try_from(PathBuf::from(read_write_path)) + .expect("path must be absolute"), + ]), + }), + macos: None, + } ); } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 2a64d3cf7eb8..7b37dd782567 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -940,7 +940,7 @@ Order of messages: ### Permission requests -The built-in `request_permissions` tool sends an `item/permissions/requestApproval` JSON-RPC request to the client with the requested permission profile. Today that commonly means additional filesystem access, but the payload is intentionally general so future requests can include non-filesystem permissions too. This request is part of the v2 protocol surface. +The built-in `request_permissions` tool sends an `item/permissions/requestApproval` JSON-RPC request to the client with the requested permission profile. This v2 payload mirrors the standalone tool's narrower permission shape, so it can request network access and additional filesystem access but does not include the broader `macos` branch used by command-execution `additionalPermissions`. ```json { diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs index 4afd4cc2441d..0595110fa0a2 100644 --- a/codex-rs/app-server/src/bespoke_event_handling.rs +++ b/codex-rs/app-server/src/bespoke_event_handling.rs @@ -110,7 +110,6 @@ use codex_core::sandboxing::intersect_permission_profiles; use codex_protocol::ThreadId; use codex_protocol::dynamic_tools::DynamicToolCallOutputContentItem as CoreDynamicToolCallOutputContentItem; use codex_protocol::dynamic_tools::DynamicToolResponse as CoreDynamicToolResponse; -use codex_protocol::models::PermissionProfile as CorePermissionProfile; use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::ApplyPatchApprovalRequestEvent; use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo; @@ -775,7 +774,7 @@ pub(crate) async fn apply_bespoke_event_handling( turn_id: request.turn_id.clone(), item_id: request.call_id.clone(), reason: request.reason, - permissions: CorePermissionProfile::from(request.permissions).into(), + permissions: request.permissions.into(), }; let (pending_request_id, rx) = outgoing .send_request(ServerRequestPayload::PermissionsRequestApproval(params)) diff --git a/codex-rs/app-server/tests/suite/v2/request_permissions.rs b/codex-rs/app-server/tests/suite/v2/request_permissions.rs index 561a0fc741c3..5a0679415d0f 100644 --- a/codex-rs/app-server/tests/suite/v2/request_permissions.rs +++ b/codex-rs/app-server/tests/suite/v2/request_permissions.rs @@ -94,7 +94,6 @@ async fn request_permissions_round_trip() -> Result<()> { read: None, write: Some(vec![requested_writes[0].clone()]), }), - macos: None, }, scope: PermissionGrantScope::Turn, })?,