diff --git a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json index 0d5c09193..ac8d5c401 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 df9e519dc..7b0c2b1a3 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 a00871971..1fbbfb1b0 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 124f21a14..bfb716c99 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 edf779488..000000000 --- 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 84a9aa377..3ae6c6051 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 cde277f1a..efdefead7 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 000000000..2bf8d8dff --- /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 32272e59b..b8419a79d 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 524205c67..c5546f49b 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; @@ -1114,6 +1115,33 @@ 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 RequestPermissionProfile { + pub network: Option, + pub file_system: Option, +} + +impl From for RequestPermissionProfile { + fn from(value: CoreRequestPermissionProfile) -> Self { + Self { + network: value.network.map(AdditionalNetworkPermissions::from), + file_system: value.file_system.map(AdditionalFileSystemPermissions::from), + } + } +} + +impl From for CoreRequestPermissionProfile { + fn from(value: RequestPermissionProfile) -> Self { + Self { + network: value.network.map(CoreNetworkPermissions::from), + file_system: value.file_system.map(CoreFileSystemPermissions::from), + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -1143,51 +1171,6 @@ impl From for CorePermissionProfile { } } -#[derive(Serialize, Deserialize, Debug, Clone, Default, 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, -} - -impl From for CoreMacOsSeatbeltProfileExtensions { - fn from(value: GrantedMacOsPermissions) -> 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), - } - } -} - #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -1198,32 +1181,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, } } } @@ -5612,7 +5577,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!( @@ -6050,192 +6015,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() - }), - Some(CoreMacOsSeatbeltProfileExtensions { - macos_preferences: CoreMacOsPreferencesPermission::None, - macos_automation: CoreMacOsAutomationPermission::BundleIds(vec![ - "com.apple.Notes".to_string(), - ]), - 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() - }), - 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, - }), - ), - ]; - - for (macos_json, expected_granted_macos, expected_core_macos) in cases { - let response = serde_json::from_value::(json!({ - "permissions": { - "macos": macos_json, + 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, }, - })) - .expect("partial macos permissions response should deserialize"); + "fileSystem": { + "read": [read_only_path], + "write": [read_write_path], + }, + }, + })) + .expect("permissions request should deserialize"); - assert_eq!( - response.permissions, - GrantedPermissionProfile { - macos: expected_granted_macos, - ..Default::default() - } - ); + assert_eq!( + params.permissions, + RequestPermissionProfile { + 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 { - macos: expected_core_macos, - ..Default::default() - } - ); - } + assert_eq!( + CoreRequestPermissionProfile::from(params.permissions), + CoreRequestPermissionProfile { + 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"), + ]), + }), + } + ); } #[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() + 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", + }, }, - scope: PermissionGrantScope::Turn, + })) + .expect_err("permissions request should reject macos permissions"); + + assert!( + err.to_string().contains("unknown field `macos`"), + "unexpected error: {err}" + ); + } + + #[test] + 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 d043496b9..67bc310da 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -941,7 +941,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 4afd4cc24..0595110fa 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 561a0fc74..5a0679415 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, })?,