diff --git a/codex-rs/app-server-protocol/schema/json/EventMsg.json b/codex-rs/app-server-protocol/schema/json/EventMsg.json index f52905c96..5bb53de04 100644 --- a/codex-rs/app-server-protocol/schema/json/EventMsg.json +++ b/codex-rs/app-server-protocol/schema/json/EventMsg.json @@ -3794,24 +3794,30 @@ "MacOsSeatbeltProfileExtensions": { "properties": { "macos_accessibility": { + "default": false, "type": "boolean" }, "macos_automation": { - "$ref": "#/definitions/MacOsAutomationPermission" + "allOf": [ + { + "$ref": "#/definitions/MacOsAutomationPermission" + } + ], + "default": "none" }, "macos_calendar": { + "default": false, "type": "boolean" }, "macos_preferences": { - "$ref": "#/definitions/MacOsPreferencesPermission" + "allOf": [ + { + "$ref": "#/definitions/MacOsPreferencesPermission" + } + ], + "default": "read_only" } }, - "required": [ - "macos_accessibility", - "macos_automation", - "macos_calendar", - "macos_preferences" - ], "type": "object" }, "McpAuthStatus": { 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 16a182f8d..3994067ce 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 @@ -5277,24 +5277,30 @@ "MacOsSeatbeltProfileExtensions": { "properties": { "macos_accessibility": { + "default": false, "type": "boolean" }, "macos_automation": { - "$ref": "#/definitions/MacOsAutomationPermission" + "allOf": [ + { + "$ref": "#/definitions/MacOsAutomationPermission" + } + ], + "default": "none" }, "macos_calendar": { + "default": false, "type": "boolean" }, "macos_preferences": { - "$ref": "#/definitions/MacOsPreferencesPermission" + "allOf": [ + { + "$ref": "#/definitions/MacOsPreferencesPermission" + } + ], + "default": "read_only" } }, - "required": [ - "macos_accessibility", - "macos_automation", - "macos_calendar", - "macos_preferences" - ], "type": "object" }, "McpInvocation": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index d74bdcc16..b0e5ee2b6 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -7412,24 +7412,30 @@ "MacOsSeatbeltProfileExtensions": { "properties": { "macos_accessibility": { + "default": false, "type": "boolean" }, "macos_automation": { - "$ref": "#/definitions/MacOsAutomationPermission" + "allOf": [ + { + "$ref": "#/definitions/MacOsAutomationPermission" + } + ], + "default": "none" }, "macos_calendar": { + "default": false, "type": "boolean" }, "macos_preferences": { - "$ref": "#/definitions/MacOsPreferencesPermission" + "allOf": [ + { + "$ref": "#/definitions/MacOsPreferencesPermission" + } + ], + "default": "read_only" } }, - "required": [ - "macos_accessibility", - "macos_automation", - "macos_calendar", - "macos_preferences" - ], "type": "object" }, "McpAuthStatus": { diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index f7f5adf64..525fe609d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -4550,6 +4550,46 @@ mod tests { ); } + #[test] + fn command_execution_request_approval_accepts_macos_automation_bundle_ids_object() { + let params = serde_json::from_value::(json!({ + "threadId": "thr_123", + "turnId": "turn_123", + "itemId": "call_123", + "command": "cat file", + "cwd": "/tmp", + "commandActions": null, + "reason": null, + "networkApprovalContext": null, + "additionalPermissions": { + "network": null, + "fileSystem": null, + "macos": { + "preferences": "read_only", + "automations": { + "bundle_ids": ["com.apple.Notes"] + }, + "accessibility": false, + "calendar": false + } + }, + "proposedExecpolicyAmendment": null, + "proposedNetworkPolicyAmendments": null, + "availableDecisions": null + })) + .expect("bundle_ids object should deserialize"); + + assert_eq!( + params + .additional_permissions + .and_then(|permissions| permissions.macos) + .map(|macos| macos.automations), + Some(CoreMacOsAutomationPermission::BundleIds(vec![ + "com.apple.Notes".to_string(), + ])) + ); + } + #[test] fn sandbox_policy_round_trips_external_sandbox_network_access() { let v2_policy = SandboxPolicy::ExternalSandbox { diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index 0e5bc7e6d..ed73837c5 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -116,6 +116,7 @@ pub enum MacOsAutomationPermission { enum MacOsAutomationPermissionDe { Mode(String), BundleIds(Vec), + BundleIdsObject { bundle_ids: Vec }, } impl TryFrom for MacOsAutomationPermission { @@ -124,6 +125,7 @@ impl TryFrom for MacOsAutomationPermission { /// Accepts one of: /// - `"none"` or `"all"` /// - a plain list of bundle IDs, e.g. `["com.apple.Notes"]` + /// - an object with bundle IDs, e.g. `{"bundle_ids": ["com.apple.Notes"]}` fn try_from(value: MacOsAutomationPermissionDe) -> Result { let permission = match value { MacOsAutomationPermissionDe::Mode(value) => { @@ -138,7 +140,8 @@ impl TryFrom for MacOsAutomationPermission { )); } } - MacOsAutomationPermissionDe::BundleIds(bundle_ids) => { + MacOsAutomationPermissionDe::BundleIds(bundle_ids) + | MacOsAutomationPermissionDe::BundleIdsObject { bundle_ids } => { let bundle_ids = bundle_ids .into_iter() .map(|bundle_id| bundle_id.trim().to_string()) @@ -157,6 +160,7 @@ impl TryFrom for MacOsAutomationPermission { } #[derive(Debug, Clone, PartialEq, Eq, Default, Hash, Serialize, Deserialize, JsonSchema, TS)] +#[serde(default)] pub struct MacOsSeatbeltProfileExtensions { pub macos_preferences: MacOsPreferencesPermission, pub macos_automation: MacOsAutomationPermission, @@ -1421,6 +1425,27 @@ mod tests { ); } + #[test] + fn macos_seatbelt_profile_extensions_deserializes_missing_fields_to_defaults() { + let permissions = + serde_json::from_value::(serde_json::json!({ + "macos_automation": ["com.apple.Notes"] + })) + .expect("deserialize macos permissions"); + + assert_eq!( + permissions, + MacOsSeatbeltProfileExtensions { + macos_preferences: MacOsPreferencesPermission::ReadOnly, + macos_automation: MacOsAutomationPermission::BundleIds(vec![ + "com.apple.Notes".to_string(), + ]), + macos_accessibility: false, + macos_calendar: false, + } + ); + } + #[test] fn macos_automation_permission_deserializes_all_and_none() { let all = serde_json::from_str::("\"all\"") @@ -1432,6 +1457,19 @@ mod tests { assert_eq!(none, MacOsAutomationPermission::None); } + #[test] + fn macos_automation_permission_deserializes_bundle_ids_object() { + let permission = serde_json::from_value::(serde_json::json!({ + "bundle_ids": ["com.apple.Notes"] + })) + .expect("deserialize bundle_ids object automation permission"); + + assert_eq!( + permission, + MacOsAutomationPermission::BundleIds(vec!["com.apple.Notes".to_string(),]) + ); + } + #[test] fn convert_mcp_content_to_items_builds_data_urls_when_missing_prefix() { let contents = vec![serde_json::json!({