import Foundation /// Sensitive system data is opt-in per capability. Keeping the future /// capabilities in the persisted vocabulary lets Settings evolve without /// changing the policy contract used by tools. enum AgentSystemCapability: String, CaseIterable, Sendable, Codable, Hashable { case calendarRead = "calendar_read" case contactsLookup = "contacts_lookup" case locationSnapshot = "location_snapshot" } enum CalendarQueryWindow: String, CaseIterable, Sendable, Codable, Equatable { case today case tomorrow case nextSevenDays = "next_7_days" func dateInterval(containing now: Date, calendar: Calendar) -> DateInterval { let startOfToday = calendar.startOfDay(for: now) let startOffset: Int let endOffset: Int switch self { case .today: startOffset = 0 endOffset = 1 case .tomorrow: startOffset = 1 endOffset = 2 case .nextSevenDays: startOffset = 0 endOffset = 7 } let start = calendar.date( byAdding: .day, value: startOffset, to: startOfToday ) ?? startOfToday let end = calendar.date( byAdding: .day, value: endOffset, to: startOfToday ) ?? start.addingTimeInterval(TimeInterval(endOffset - startOffset) * 86_400) return DateInterval(start: start, end: end) } var displayLabel: String { switch self { case .today: "today" case .tomorrow: "tomorrow" case .nextSevenDays: "for the next 7 days" } } } /// Deliberately contains only the fields the assistant may disclose. EventKit /// identifiers, notes, attendees, URLs, locations, and recurrence metadata /// never cross this boundary. struct SystemCalendarEvent: Sendable, Equatable { let title: String let startsAt: Date let endsAt: Date let isAllDay: Bool } struct SystemCalendarEventPage: Sendable, Equatable { let events: [SystemCalendarEvent] let hasMore: Bool } enum SystemCalendarAuthorization: String, Sendable, Equatable { case fullAccess case notDetermined case denied case restricted case writeOnly } enum SystemCalendarReaderError: LocalizedError, Sendable, Equatable { case fullAccessRequired(SystemCalendarAuthorization) case unavailable(String) var errorDescription: String? { switch self { case .fullAccessRequired(let status): switch status { case .notDetermined: "Calendar full access has not been granted. Grant access from Dolphin Settings before trying again." case .denied: "Calendar full access is denied. Enable it in iOS Settings before trying again." case .restricted: "Calendar full access is restricted on this iPhone." case .writeOnly: "Calendar access is write-only. Full access is required to read events." case .fullAccess: "Calendar events are temporarily unavailable." } case .unavailable(let reason): "Calendar events are unavailable: \(reason)" } } } protocol SystemCalendarReading: Sendable { func authorizationStatus() async -> SystemCalendarAuthorization /// Implementations return at most `limit` events and indicate whether more /// matched. This method must never request permission. func readEvents( in interval: DateInterval, limit: Int ) async throws -> SystemCalendarEventPage } /// Pure fallback used by host tests and non-iOS consumers. The production app /// injects EventKit explicitly, keeping platform APIs out of the agent core. struct UnavailableSystemCalendarReader: SystemCalendarReading { func authorizationStatus() async -> SystemCalendarAuthorization { .denied } func readEvents( in interval: DateInterval, limit: Int ) async throws -> SystemCalendarEventPage { throw SystemCalendarReaderError.unavailable( "No system calendar adapter is available in this environment." ) } }