ales27pm's picture
Add autonomous on-device iOS assistant
7b2dfc5 verified
Raw
History Blame Contribute Delete
3.86 kB
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."
)
}
}