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.