# iCalls (`ICallsSync`) ## Purpose Ingest macOS call history (Phone/FaceTime) into the server for timelines, recent activity, and correlation with messages/contacts/prospects. ## Direction Read-only. Call logs are append-only records emitted by the system; there is no Sender or send queue. ## OS surface `~/Library/Application Support/CallHistoryDB/CallHistory.storedata` (and legacy `~/Library/CallHistoryDB/CallHistory.storedata`). Direct readonly SQLite via GRDB (Core Data schema: `ZCALLRECORD`, `ZUNIQUE_ID`, `ZDATE` as Apple reference time, `ZADDRESS`, `ZORIGINATED`, `ZANSWERED`, `ZDURATION`, `ZCALLTYPE`, `ZSERVICE_PROVIDER`, `ZNAME`). `com.apple.security.files.all` entitlement (already present for chat.db) is sufficient. No extra TCC prompt beyond Full Disk Access. ## Files - `Reader.swift` — `CallHistoryReader`: - Tries current (`Application Support/CallHistoryDB`) then legacy path. - `fetchCalls(since: Date?)` — filters on `ZDATE > ref`, oldest-first. - **WAL snapshot for freshness**: `makeSnapshot` copies the live `.storedata` + `-wal` + `-shm` into a temp dir; `readCalls` opens the copy read-write so SQLite replays un-checkpointed frames. Recent calls (critical for triage) are no longer missed. Snapshot is cleaned up with `defer`. - Timestamp conversion: `Date(timeIntervalSinceReferenceDate:)`. - Basic CNContact enrichment for display names when `ZNAME` absent (best-effort). - Uses `PhoneUtils.normalize`. Exposed via `isAccessible` for Base auth gating. - `APIClient.swift`: - `syncCalls(_:)` → `POST /client/icalls/sync` - `getStats()` → `GET /client/icalls/stats` - `SyncManager.swift` — 120s read interval (per operator request), no send queue hooks. Batches of 200. Simple `syncedThisSession` + total from server. Authorization hooks gate on `reader.isAccessible` (Full Disk). ## Timing - Read interval: **120s** (`@packages/icalls/Sources/ICallsSync/SyncManager.swift`). ## Server - Entity: `macsync.calls` (dedicated table, not mixed into messages). Unique on `(device_id, external_id)`. - Ingest: `features/icalls/ingestCalls` (dedup + upsert). Records `calls_synced` in sync-history. - Queries: `/my/calls` (filter by deviceId, direction, callType, since (ISO), limit, offset) + `/my/calls/stats`. Returns address, normalizedAddress, contactName, direction, callType (telephony/facetime_*), answered, durationSeconds, startedAt, serviceProvider. Sorted newest-first for triage use. - Client ingest: `/client/icalls/sync` + `/stats` (wrapped in sync-history 'calls'). - MCP: `recent_calls` tool (mcp/) exposes the surface for agents/triage. ## Local Mac access (no remote server hop) - `LocalWebServer` (`/api/calls?limit=N`) — structured recent calls, same shape as server. Useful for "Control your Mac" agents or local tools. Also reports `callHistoryDb` (available + error) in `/api/diagnostics` (shown in Dashboard "Native Subsystems" card). - Same Full Disk grant as iMessage (no extra prompt). ## Web - `web/src/api/calls.ts` + `fetchCalls`/`fetchCallStats` - `web/src/tabs/Calls/index.tsx` (filter by direction/callType, table view) - Integrated in `App.tsx` route, `AppShell` nav, Dashboard KPI tile (from throughput) + "Call History DB" row in Native Subsystems (from diagnostics). - Prospect cockpit `/thread` also surfaces `recentCalls` for the handle. ## Known characteristics - First sync backfills available history (can be thousands of rows; rows are tiny). - `ZUNIQUE_ID` (or synthetic `zpk:`) is the stable external id. - Multi-party FaceTime participants live in join tables (`Z_2REMOTEPARTICIPANTHANDLES` + `ZHANDLE`); v1 surfaces primary `ZADDRESS` only. - Duration 0 + unanswered often indicates missed/ringing-out attempts. - See [known limitations](./known-limitations.md#icalls-limitations-mac-callhistory-only) for WAL snapshot details, iPhone cellular gap, etc. ## Adding / extending Follow the read-only path in `docs/adding-a-module.md` (no send queue / Sender / admin send routes). Add columns or participant join expansion in a follow-up migration if needed.