From 184a590a3d779d2f01754b42e658ddd8c083d3a7 Mon Sep 17 00:00:00 2001 From: Eulices Lopez <105620565+eulicesl@users.noreply.github.com> Date: Fri, 20 Mar 2026 07:02:29 -0400 Subject: [PATCH 1/2] fix(ios): restore contacts and calendar permission prompts --- .../Sources/Calendar/CalendarService.swift | 30 +++++++++++++++++-- .../Sources/Contacts/ContactsService.swift | 8 +++-- apps/ios/Sources/Info.plist | 6 ++++ apps/ios/project.yml | 3 ++ 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/apps/ios/Sources/Calendar/CalendarService.swift b/apps/ios/Sources/Calendar/CalendarService.swift index 94b2d9ea3f5..01854345c86 100644 --- a/apps/ios/Sources/Calendar/CalendarService.swift +++ b/apps/ios/Sources/Calendar/CalendarService.swift @@ -6,7 +6,12 @@ final class CalendarService: CalendarServicing { func events(params: OpenClawCalendarEventsParams) async throws -> OpenClawCalendarEventsPayload { let store = EKEventStore() let status = EKEventStore.authorizationStatus(for: .event) - let authorized = EventKitAuthorization.allowsRead(status: status) + let authorized: Bool + if status == .notDetermined { + authorized = await Self.requestEventAccess(store: store) + } else { + authorized = EventKitAuthorization.allowsRead(status: status) + } guard authorized else { throw NSError(domain: "Calendar", code: 1, userInfo: [ NSLocalizedDescriptionKey: "CALENDAR_PERMISSION_REQUIRED: grant Calendar permission", @@ -39,7 +44,12 @@ final class CalendarService: CalendarServicing { func add(params: OpenClawCalendarAddParams) async throws -> OpenClawCalendarAddPayload { let store = EKEventStore() let status = EKEventStore.authorizationStatus(for: .event) - let authorized = EventKitAuthorization.allowsWrite(status: status) + let authorized: Bool + if status == .notDetermined { + authorized = await Self.requestEventAccess(store: store) + } else { + authorized = EventKitAuthorization.allowsWrite(status: status) + } guard authorized else { throw NSError(domain: "Calendar", code: 2, userInfo: [ NSLocalizedDescriptionKey: "CALENDAR_PERMISSION_REQUIRED: grant Calendar permission", @@ -95,6 +105,22 @@ final class CalendarService: CalendarServicing { return OpenClawCalendarAddPayload(event: payload) } + private static func requestEventAccess(store: EKEventStore) async -> Bool { + if #available(iOS 17.0, *) { + return await withCheckedContinuation { continuation in + store.requestFullAccessToEvents { granted, _ in + continuation.resume(returning: granted) + } + } + } + + return await withCheckedContinuation { continuation in + store.requestAccess(to: .event) { granted, _ in + continuation.resume(returning: granted) + } + } + } + private static func resolveCalendar( store: EKEventStore, calendarId: String?, diff --git a/apps/ios/Sources/Contacts/ContactsService.swift b/apps/ios/Sources/Contacts/ContactsService.swift index efe89f8a218..36e507c9a42 100644 --- a/apps/ios/Sources/Contacts/ContactsService.swift +++ b/apps/ios/Sources/Contacts/ContactsService.swift @@ -103,9 +103,11 @@ final class ContactsService: ContactsServicing { case .authorized, .limited: return true case .notDetermined: - // Don’t prompt during node.invoke; the caller should instruct the user to grant permission. - // Prompts block the invoke and lead to timeouts in headless flows. - return false + return await withCheckedContinuation { continuation in + store.requestAccess(for: .contacts) { granted, _ in + continuation.resume(returning: granted) + } + } case .restricted, .denied: return false @unknown default: diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist index 5908021fad3..a3125becdcc 100644 --- a/apps/ios/Sources/Info.plist +++ b/apps/ios/Sources/Info.plist @@ -50,6 +50,12 @@ NSCameraUsageDescription OpenClaw can capture photos or short video clips when requested via the gateway. + NSCalendarsUsageDescription + OpenClaw uses your calendars to show events and scheduling context when you enable calendar access. + NSCalendarsFullAccessUsageDescription + OpenClaw uses your calendars to show events and scheduling context when you enable calendar access. + NSContactsUsageDescription + OpenClaw uses your contacts so you can search and reference people while using the assistant. NSLocalNetworkUsageDescription OpenClaw discovers and connects to your OpenClaw gateway on the local network. NSLocationAlwaysAndWhenInUseUsageDescription diff --git a/apps/ios/project.yml b/apps/ios/project.yml index 53e6489a25b..67522e4e617 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -134,6 +134,9 @@ targets: NSBonjourServices: - _openclaw-gw._tcp NSCameraUsageDescription: OpenClaw can capture photos or short video clips when requested via the gateway. + NSCalendarsUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access. + NSCalendarsFullAccessUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access. + NSContactsUsageDescription: OpenClaw uses your contacts so you can search and reference people while using the assistant. NSLocationWhenInUseUsageDescription: OpenClaw uses your location when you allow location sharing. NSLocationAlwaysAndWhenInUseUsageDescription: OpenClaw can share your location in the background when you enable Always. NSMicrophoneUsageDescription: OpenClaw needs microphone access for voice wake. From 3540c35cc2f0dd2b98ccf6bea1ff3050b674fd84 Mon Sep 17 00:00:00 2001 From: Eulices Lopez <105620565+eulicesl@users.noreply.github.com> Date: Fri, 20 Mar 2026 07:25:46 -0400 Subject: [PATCH 2/2] fix(ios): tighten calendar scope and avoid headless contacts prompts --- .../ios/Sources/Calendar/CalendarService.swift | 18 +++++++++++++++++- .../ios/Sources/Contacts/ContactsService.swift | 7 ++----- apps/ios/Sources/Info.plist | 2 ++ apps/ios/project.yml | 1 + 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/ios/Sources/Calendar/CalendarService.swift b/apps/ios/Sources/Calendar/CalendarService.swift index 01854345c86..e11a6ee4075 100644 --- a/apps/ios/Sources/Calendar/CalendarService.swift +++ b/apps/ios/Sources/Calendar/CalendarService.swift @@ -46,7 +46,7 @@ final class CalendarService: CalendarServicing { let status = EKEventStore.authorizationStatus(for: .event) let authorized: Bool if status == .notDetermined { - authorized = await Self.requestEventAccess(store: store) + authorized = await Self.requestWriteOnlyEventAccess(store: store) } else { authorized = EventKitAuthorization.allowsWrite(status: status) } @@ -121,6 +121,22 @@ final class CalendarService: CalendarServicing { } } + private static func requestWriteOnlyEventAccess(store: EKEventStore) async -> Bool { + if #available(iOS 17.0, *) { + return await withCheckedContinuation { continuation in + store.requestWriteOnlyAccessToEvents { granted, _ in + continuation.resume(returning: granted) + } + } + } + + return await withCheckedContinuation { continuation in + store.requestAccess(to: .event) { granted, _ in + continuation.resume(returning: granted) + } + } + } + private static func resolveCalendar( store: EKEventStore, calendarId: String?, diff --git a/apps/ios/Sources/Contacts/ContactsService.swift b/apps/ios/Sources/Contacts/ContactsService.swift index 36e507c9a42..d38bc7a923c 100644 --- a/apps/ios/Sources/Contacts/ContactsService.swift +++ b/apps/ios/Sources/Contacts/ContactsService.swift @@ -103,11 +103,8 @@ final class ContactsService: ContactsServicing { case .authorized, .limited: return true case .notDetermined: - return await withCheckedContinuation { continuation in - store.requestAccess(for: .contacts) { granted, _ in - continuation.resume(returning: granted) - } - } + // Avoid prompting during node.invoke; headless/unattended flows should fail fast. + return false case .restricted, .denied: return false @unknown default: diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist index a3125becdcc..8950acf4b01 100644 --- a/apps/ios/Sources/Info.plist +++ b/apps/ios/Sources/Info.plist @@ -54,6 +54,8 @@ OpenClaw uses your calendars to show events and scheduling context when you enable calendar access. NSCalendarsFullAccessUsageDescription OpenClaw uses your calendars to show events and scheduling context when you enable calendar access. + NSCalendarsWriteOnlyAccessUsageDescription + OpenClaw uses your calendars to add events when you enable calendar access. NSContactsUsageDescription OpenClaw uses your contacts so you can search and reference people while using the assistant. NSLocalNetworkUsageDescription diff --git a/apps/ios/project.yml b/apps/ios/project.yml index 67522e4e617..f345eac5baf 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -136,6 +136,7 @@ targets: NSCameraUsageDescription: OpenClaw can capture photos or short video clips when requested via the gateway. NSCalendarsUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access. NSCalendarsFullAccessUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access. + NSCalendarsWriteOnlyAccessUsageDescription: OpenClaw uses your calendars to add events when you enable calendar access. NSContactsUsageDescription: OpenClaw uses your contacts so you can search and reference people while using the assistant. NSLocationWhenInUseUsageDescription: OpenClaw uses your location when you allow location sharing. NSLocationAlwaysAndWhenInUseUsageDescription: OpenClaw can share your location in the background when you enable Always.