macsync/docs/modules/icalls.md
Natalie ad8e126dd1 docs(mac-sync): outbox/read architecture, handoffs, module docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:35:13 -04:00

89 lines
4 KiB
Markdown

# 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:<Z_PK>`) 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.