@ateam-ai-mobile/expo 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/bootstrap.js +841 -323
  2. package/dist/bootstrap.js.map +1 -1
  3. package/dist/bootstrap.mjs +9 -7
  4. package/dist/bootstrap.mjs.map +1 -1
  5. package/dist/chunk-7FWYEHN5.mjs +203 -0
  6. package/dist/chunk-7FWYEHN5.mjs.map +1 -0
  7. package/dist/{chunk-4XYXJYN5.mjs → chunk-C7M5WAHA.mjs} +583 -298
  8. package/dist/chunk-C7M5WAHA.mjs.map +1 -0
  9. package/dist/{chunk-5HM6UA2K.mjs → chunk-GF2MPTAB.mjs} +83 -7
  10. package/dist/chunk-GF2MPTAB.mjs.map +1 -0
  11. package/dist/chunk-J6XRNX7P.mjs +19 -0
  12. package/dist/chunk-J6XRNX7P.mjs.map +1 -0
  13. package/dist/{chunk-TZXWVYYR.mjs → chunk-PCAEUX2N.mjs} +1 -1
  14. package/dist/chunk-PCAEUX2N.mjs.map +1 -0
  15. package/dist/{chunk-VQJPSLHP.mjs → chunk-Z5AI2SXX.mjs} +13 -4
  16. package/dist/chunk-Z5AI2SXX.mjs.map +1 -0
  17. package/dist/createBridge.js +41 -10
  18. package/dist/createBridge.js.map +1 -1
  19. package/dist/createBridge.mjs +5 -1
  20. package/dist/createBridge.mjs.map +1 -1
  21. package/dist/device/createDeviceProvider.js +278 -17
  22. package/dist/device/createDeviceProvider.js.map +1 -1
  23. package/dist/device/createDeviceProvider.mjs +5 -3
  24. package/dist/device-bridge/index.d.mts +13 -1
  25. package/dist/device-bridge/index.d.ts +13 -1
  26. package/dist/device-bridge/index.js +49 -12
  27. package/dist/device-bridge/index.js.map +1 -1
  28. package/dist/device-bridge/index.mjs +3 -1
  29. package/dist/index.d.mts +19 -3
  30. package/dist/index.d.ts +19 -3
  31. package/dist/index.js +594 -297
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +2 -1
  34. package/dist/maps/index.d.mts +56 -0
  35. package/dist/maps/index.d.ts +56 -0
  36. package/dist/maps/index.js +95 -0
  37. package/dist/maps/index.js.map +1 -0
  38. package/dist/maps/index.mjs +17 -0
  39. package/dist/maps/index.mjs.map +1 -0
  40. package/dist/vision/index.d.mts +4 -2
  41. package/dist/vision/index.d.ts +4 -2
  42. package/dist/vision/index.js.map +1 -1
  43. package/dist/vision/index.mjs +1 -1
  44. package/expo-module.config.json +1 -1
  45. package/ios/AdaMaps/AdaMapsModule.swift +180 -0
  46. package/ios/AdaMaps.podspec +25 -0
  47. package/ios/AdaVision.podspec +4 -0
  48. package/ios/AdaVisionModule.swift +63 -9
  49. package/package.json +10 -4
  50. package/src/args.ts +56 -0
  51. package/src/createBridge.ts +3 -1
  52. package/src/device/createDeviceProvider.ts +88 -3
  53. package/src/device-bridge/collectors/location.ts +21 -9
  54. package/src/device-state/collectors.ts +5 -1
  55. package/src/device-state/domains/deviceContext.ts +5 -3
  56. package/src/device-state/domains/interaction.ts +2 -1
  57. package/src/device-state/domains/spatial.ts +200 -116
  58. package/src/device-state/domains/visual.ts +34 -13
  59. package/src/device-state/events.ts +49 -0
  60. package/src/device-state/headingSource.ts +43 -8
  61. package/src/device-state/index.ts +3 -2
  62. package/src/device-state/replay.ts +76 -11
  63. package/src/device-state/spatialEvents.ts +300 -0
  64. package/src/geo.ts +23 -0
  65. package/src/maps/index.ts +81 -0
  66. package/src/maps/normalize.ts +225 -0
  67. package/src/vision/index.ts +4 -2
  68. package/dist/chunk-4XYXJYN5.mjs.map +0 -1
  69. package/dist/chunk-5HM6UA2K.mjs.map +0 -1
  70. package/dist/chunk-TZXWVYYR.mjs.map +0 -1
  71. package/dist/chunk-VQJPSLHP.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/device/createDeviceProvider.ts"],"sourcesContent":["/**\n * SDK device-primitive provider (Expo-backed).\n *\n * `createDeviceProvider()` builds the FULL, UNGATED `PluginNative` implementation\n * a host registers once via `setPluginNativeProvider`. Per-plugin capability\n * gating and per-plugin filesystem scoping are applied downstream by\n * plugin-runtime's `gatePluginNative` — this module does not gate.\n *\n * Not a React hook: it holds no React state and must be safe to build at module\n * scope, including on a headless background wake before any UI exists.\n */\nimport * as Haptics from 'expo-haptics';\n// Both are already app dependencies and already used by the device-bridge\n// collectors — safe for eager import (unlike cookies/nfc, which need native\n// modules absent from Expo Go).\nimport * as ExpoLocation from 'expo-location';\nimport * as ImagePicker from 'expo-image-picker';\nimport * as Notifications from 'expo-notifications';\nimport * as DocumentPicker from 'expo-document-picker';\nimport * as FS from 'expo-file-system';\nimport * as SQLite from 'expo-sqlite';\n// The broker behind scanner.scanOnce() and camera.recordVideo(): both need a\n// MOUNTED camera, which a function call cannot provide.\nimport { cameraTasks } from '../camera/cameraTasks';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\n// native.vision: the on-device analyzer (ada-vision module) — resolve a frame id\n// to its local pixels, measure/read/classify on-device, never off the phone.\nimport * as adaVision from '../vision';\n// native.maps: place search + walking routes (ada-maps module, MapKit) and the\n// SDK's one reverse geocode. NOT on-device — queries go to Apple's servers.\nimport * as adaMaps from '../maps';\nimport { countArg, fractionArg, refuseArgument, shown } from '../args';\nimport {\n coordinateArg, mapsFailure, searchArgs, toMapsPlaces, toMapsRoute, toReverseGeocodedPlace,\n} from '../maps/normalize';\n// Device-bridge owns every native module. plugin-sdk delegates so RN\n// plugins never import native modules directly. When adding a new\n// capability: add it to device-bridge, import here, declare in types.ts,\n// wire in the factory below.\n//\n// IMPORTANT: cookies and nfc use native modules NOT available in Expo Go.\n// We lazy-import them so the app doesn't crash at load time.\n// Only biometrics (expo-local-authentication), sharing (expo-sharing),\n// and scanner (expo-camera) are safe for eager import.\nimport {\n biometrics as deviceBiometrics,\n sharing as deviceSharing,\n scanner as deviceScanner,\n} from '../device-bridge';\n\n// Lazy loaders for native modules not in Expo Go\nfunction getDeviceCookies() {\n try { return require('../device-bridge/cookies'); } catch { return null; }\n}\nfunction getDeviceNfc() {\n try { return require('../device-bridge/nfc'); } catch { return null; }\n}\nimport type {\n NativeCapabilities,\n PluginNative,\n CameraAPI,\n LocationAPI,\n StorageAPI,\n BiometricsAPI,\n BiometricsAPIv2,\n NotificationsAPI,\n HapticsAPI,\n ConnectivityAPI,\n FileSystemAPI,\n CookiesAPI,\n WebViewCookie,\n SharingAPI,\n ScannerAPI,\n NfcAPI,\n BluetoothAPI,\n VisionAPI,\n MapsAPI,\n} from '@ateam-ai-mobile/plugin-runtime';\n\nclass CapabilityUnavailableError extends Error {\n code = 'UNAVAILABLE' as const;\n constructor(capability: string, reason?: string) {\n super(reason || `${capability} is not yet implemented. Coming in Phase 2.`);\n this.name = 'CapabilityUnavailableError';\n }\n}\n\nclass CapabilityNotDeclaredError extends Error {\n code = 'NOT_DECLARED' as const;\n constructor(capability: string) {\n super(`Capability '${capability}' not declared in plugin manifest. Add it to the native.${capability} field.`);\n this.name = 'CapabilityNotDeclaredError';\n }\n}\n\nfunction stubApi<T extends object>(capability: string): T {\n return new Proxy({} as T, {\n get(_target, prop) {\n if (typeof prop === 'string') {\n return () => {\n throw new CapabilityUnavailableError(capability);\n };\n }\n },\n });\n}\n\n// ── Location (expo-location — was stubbed, is real now) ───────────\n//\n// `location` was wired to stubApi, so a plugin that correctly DECLARED\n// `location: true` still got \"location is not yet implemented. Coming in\n// Phase 2.\" on every call. expo-location has been a dependency all along and\n// the device-bridge collectors use it; only the plugin-facing API was missing.\n\nfunction createLocationAPI(): LocationAPI {\n /** Ask once, and say what the user can do about a refusal. */\n const ensurePermission = async (): Promise<void> => {\n const existing = await ExpoLocation.getForegroundPermissionsAsync();\n if (existing.status === 'granted') return;\n const asked = await ExpoLocation.requestForegroundPermissionsAsync();\n if (asked.status !== 'granted') {\n // A voice assistant reads this out, so it has to be actionable rather\n // than a status code.\n throw new Error(\n 'Location permission was not granted. Enable it for this app in Settings > Privacy > Location Services.',\n );\n }\n };\n\n return {\n async getCurrent(opts) {\n await ensurePermission();\n // 'high' costs battery and a second or two of settling; default balanced,\n // which is plenty for \"which street am I on\".\n const fix = await ExpoLocation.getCurrentPositionAsync({\n accuracy:\n opts?.accuracy === 'high'\n ? ExpoLocation.Accuracy.High\n : ExpoLocation.Accuracy.Balanced,\n });\n return {\n latitude: fix.coords.latitude,\n longitude: fix.coords.longitude,\n altitude: fix.coords.altitude ?? undefined,\n };\n },\n\n watchPosition(cb) {\n let subscription: { remove(): void } | null = null;\n let cancelled = false;\n\n void (async () => {\n try {\n await ensurePermission();\n if (cancelled) return;\n subscription = await ExpoLocation.watchPositionAsync(\n { accuracy: ExpoLocation.Accuracy.Balanced },\n fix => cb({ latitude: fix.coords.latitude, longitude: fix.coords.longitude }),\n );\n // Unsubscribed while we were still asking for permission — otherwise\n // the GPS stays on with nobody listening.\n if (cancelled) { subscription?.remove(); subscription = null; }\n } catch (err: any) {\n console.warn('[location] watchPosition failed:', err?.message || String(err));\n }\n })();\n\n return () => {\n cancelled = true;\n subscription?.remove();\n subscription = null;\n };\n },\n };\n}\n\n// ── Camera (expo-image-picker — was stubbed, is real now) ─────────\n//\n// Every method here opens the SYSTEM camera or picker UI, which is why they\n// are honest one-shot actions rather than a frame source: expo-camera can\n// deliver a live feed, but only through a MOUNTED CameraView component, which\n// a headless tool call has no way to mount. A continuous visual feed is the\n// semantic Visual domain's job (Device Bridge v2), not this API's.\n\nfunction createCameraAPI(): CameraAPI {\n const ensurePermission = async (): Promise<void> => {\n const existing = await ImagePicker.getCameraPermissionsAsync();\n if (existing.granted) return;\n const asked = await ImagePicker.requestCameraPermissionsAsync();\n if (!asked.granted) {\n throw new Error(\n 'Camera permission was not granted. Enable it for this app in Settings > Privacy > Camera.',\n );\n }\n };\n\n return {\n async takePhoto(opts) {\n await ensurePermission();\n const res = await ImagePicker.launchCameraAsync({\n quality: opts?.quality ?? 0.7,\n cameraType:\n opts?.facing === 'front'\n ? ImagePicker.CameraType.front\n : ImagePicker.CameraType.back,\n });\n // The user backing out is not an error — but it is also not a photo, so\n // it must not come back as an empty-looking success.\n if (res.canceled || !res.assets?.[0]) {\n throw new Error('No photo taken — the camera was dismissed.');\n }\n const a = res.assets[0];\n return { uri: a.uri, width: a.width, height: a.height };\n },\n\n async scanBarcode(opts) {\n // Kept refusing, and now the refusal has somewhere to send you. Scanning\n // is a mounted camera lifecycle, which is why it lives on `scanner`.\n // Silently forwarding would leave a misleading name in every plugin.\n throw new CapabilityUnavailableError(\n 'camera.scanBarcode',\n 'Use native.scanner.scanOnce() instead — scanning needs a mounted camera, ' +\n 'which this call cannot provide. Same arguments, and it returns { data, format }.' +\n (opts?.formats?.length ? ' Pass your formats through to scanOnce.' : ''),\n );\n },\n\n async recordVideo(opts) {\n // A FILE the user deliberately makes — not the Visual domain's frame\n // ring, which is ephemeral and nobody keeps. Runs on the shared camera\n // surface, so it throws the same coded failures as scanOnce.\n return cameraTasks.run('record', {\n maxDurationMs: opts?.maxDurationMs,\n withAudio: opts?.withAudio ?? false,\n });\n },\n\n async pickImage(opts) {\n const existing = await ImagePicker.getMediaLibraryPermissionsAsync();\n if (!existing.granted) {\n const asked = await ImagePicker.requestMediaLibraryPermissionsAsync();\n if (!asked.granted) {\n throw new Error(\n 'Photo library permission was not granted. Enable it for this app in Settings > Privacy > Photos.',\n );\n }\n }\n const res = await ImagePicker.launchImageLibraryAsync({\n allowsMultipleSelection: !!opts?.multiple,\n });\n if (res.canceled) return [];\n return (res.assets || []).map(a => ({ uri: a.uri }));\n },\n };\n}\n\n// ── Storage (AsyncStorage — was stubbed) ──────────────────────────\n//\n// KV is real. `db.execute` is NOT: expo-sqlite is not installed, and a fake\n// SQL layer over AsyncStorage would be a worse lie than an error — it would\n// accept a query and return an empty rowset, which reads as \"no rows matched\".\n\n/**\n * One SQLite file for plugin storage, opened lazily and once.\n *\n * Lazy because opening a database at module load costs startup time for every\n * user who never touches a plugin that needs one; once because two handles to\n * the same file is how you get a locked database under concurrent writes.\n */\n// Typed `any` for the handle: expo-sqlite is an any-shim in this SDK repo's\n// typecheck (the host supplies the real types), so `SQLite.SQLiteDatabase` is\n// not a nameable type here. Runtime is unchanged — SQLite.openDatabaseAsync\n// still returns a real SQLiteDatabase on device.\n// One handle PER plugin database file, opened lazily and once each. A Map (not a\n// single promise) because storage is now per-plugin: each plugin gets its own\n// file, and two handles to the same file is how you get a locked database.\nconst _pluginDbs = new Map<string, Promise<any>>();\nfunction openPluginDb(file: string): Promise<any> {\n const existing = _pluginDbs.get(file);\n if (existing) return existing;\n const opened: Promise<any> = SQLite.openDatabaseAsync(file);\n _pluginDbs.set(file, opened);\n return opened;\n}\n\n// The leading SQL keyword, uppercased, after stripping leading whitespace and\n// leading `-- line` / `/* block */` comments — so a comment cannot smuggle the\n// verb past the cross-file check (e.g. `/* */ ATTACH …`).\nfunction sqlLeadingKeyword(sql: string): string {\n let s = String(sql);\n for (;;) {\n const before = s;\n s = s.replace(/^\\s+/, '');\n if (s.startsWith('--')) { const nl = s.indexOf('\\n'); s = nl === -1 ? '' : s.slice(nl + 1); }\n else if (s.startsWith('/*')) { const end = s.indexOf('*/'); s = end === -1 ? '' : s.slice(end + 2); }\n if (s === before) break;\n }\n const m = s.match(/^[A-Za-z]+/);\n return m ? m[0].toUpperCase() : '';\n}\n\n// R3 SECURITY — per-plugin storage isolation. Both the KV namespace and the\n// SQLite database are scoped to the plugin's OWN id, so Plugin A cannot read,\n// overwrite, or query Plugin B's entries/tables. The id is threaded in from the\n// TRUSTED gate (storageFor(pluginId)) — never a caller-supplied prefix.\n//\n// The id is encoded with encodeURIComponent (not used raw) precisely because KV\n// is a FLAT concatenated keyspace with no structural boundary: a raw id like\n// `mcp:conn:plug` shares the `:` delimiter, so `@plugin:mcp:conn:plug:<k>` from\n// one plugin could collide with another plugin's id+key split. Encoding escapes\n// `:` (→ %3A), making the id segment unambiguous and the mapping injective, so\n// distinct ids can never produce overlapping namespaces regardless of the key.\n// (The filesystem sandbox can use the raw id as a directory name because a\n// directory boundary is structural, not delimiter-based.) The same encoded\n// token names the plugin's database file, giving each plugin a distinct DB.\nfunction createStorageAPI(pluginId: string): StorageAPI {\n const ns = encodeURIComponent(pluginId);\n const key = (k: string) => `@plugin:${ns}:${k}`;\n const dbFile = `plugin-storage-${ns}.db`;\n\n return {\n get: (k) => AsyncStorage.getItem(key(k)),\n set: (k, v) => AsyncStorage.setItem(key(k), v),\n delete: (k) => AsyncStorage.removeItem(key(k)),\n db: {\n async execute(sql: string, params?: any[]) {\n const verb = sqlLeadingKeyword(sql);\n // Per-plugin storage isolation rests on each plugin owning its OWN\n // database FILE. SQLite can cross files — ATTACH mounts another db,\n // VACUUM INTO writes one — so the file boundary only holds if those are\n // refused. Otherwise a plugin could `ATTACH '.../plugin-storage-<other\n // id>.db'` (the id is public and the filename deterministic) and read or\n // overwrite another plugin's tables. Refuse cross-file statements at the\n // trusted gate; a plugin never needs them for its own single database.\n // (runAsync/getAllAsync compile only the FIRST statement, so a trailing\n // `; ATTACH …` never runs — the leading verb is the whole surface.)\n // ATTACH/DETACH/VACUUM are the only cross-FILE statements in the standard\n // expo-sqlite build: loadable extensions are disabled and the file-vtab\n // modules (fileio/csv/zipfile) are not compiled in, so there is no other\n // way for a plugin's own connection to reach another plugin's file. If a\n // future SQLite build enables those, revisit (an allowlist would be the\n // durable form).\n if (verb === 'ATTACH' || verb === 'DETACH' || verb === 'VACUUM') {\n throw new CapabilityUnavailableError(\n 'storage.db',\n `SQL '${verb}' is not permitted: a plugin's storage is confined to its own database and cannot reach another file.`,\n );\n }\n const db = await openPluginDb(dbFile);\n // A statement either RETURNS ROWS or CHANGES THEM, and the two need\n // different calls. Deciding by the leading keyword rather than by\n // trying one and falling back: a failed INSERT retried as a query\n // would run it twice.\n const returnsRows = verb === 'SELECT' || verb === 'PRAGMA' || verb === 'WITH';\n\n if (returnsRows) {\n const rows = await db.getAllAsync(sql, (params ?? []) as any);\n // changes: 0 is the truth for a SELECT, not a placeholder.\n return { rows, changes: 0 };\n }\n const res = await db.runAsync(sql, (params ?? []) as any);\n // rows: [] is likewise true for a write — it did not select anything.\n return { rows: [], changes: res.changes ?? 0 };\n },\n },\n };\n}\n\n/**\n * Wipe ALL plugin storage — KV, SQLite, and the file sandbox — across EVERY\n * plugin. Per-plugin isolation (R3) keeps plugins apart from each other; this is\n * the USER boundary: on sign-out it must run so the NEXT user/tenant cannot read\n * the previous one's plugin data. Best-effort per store — a hiccup clearing one\n * must never block sign-out — so each part is guarded independently.\n */\nexport async function clearAllPluginData(): Promise<void> {\n // 1. KV — every `@plugin:` namespace (all plugins, all keys).\n try {\n const keys = await AsyncStorage.getAllKeys();\n const mine = keys.filter((k: string) => k.startsWith('@plugin:'));\n if (mine.length) await AsyncStorage.multiRemove(mine);\n } catch (e: any) {\n console.warn('[adas] clearAllPluginData: KV clear failed:', e?.message || e);\n }\n\n // 2. SQLite — close any open per-plugin handles, then delete the db files.\n try {\n for (const [, p] of _pluginDbs) {\n try { const db = await p; await db.closeAsync?.(); } catch { /* already closed */ }\n }\n _pluginDbs.clear();\n const sqliteDir = new FS.Directory(FS.Paths.document, 'SQLite');\n if (sqliteDir.exists) {\n for (const entry of sqliteDir.list()) {\n if (/^plugin-storage-.*\\.db$/.test(String(entry.name ?? ''))) {\n try { entry.delete(); } catch { /* best effort */ }\n }\n }\n }\n } catch (e: any) {\n console.warn('[adas] clearAllPluginData: SQLite clear failed:', e?.message || e);\n }\n\n // 3. Files — the whole plugin-files/ sandbox tree (all plugins).\n try {\n const dir = new FS.Directory(FS.Paths.document, 'plugin-files');\n if (dir.exists) dir.delete();\n } catch (e: any) {\n console.warn('[adas] clearAllPluginData: file sandbox clear failed:', e?.message || e);\n }\n}\n\n// ── Notifications (expo-notifications — was stubbed) ──────────────\n\nfunction createNotificationsAPI(): NotificationsAPI {\n return {\n async schedule({ title, body, trigger }) {\n const perm = await Notifications.getPermissionsAsync();\n if (!perm.granted) {\n const asked = await Notifications.requestPermissionsAsync();\n if (!asked.granted) {\n throw new Error(\n 'Notification permission was not granted. Enable it for this app in Settings > Notifications.',\n );\n }\n }\n // `seconds` and `date` are the two the contract declares; anything else\n // would be this API inventing a scheduling model of its own.\n const when: any =\n typeof trigger?.seconds === 'number'\n ? { type: 'timeInterval', seconds: trigger.seconds, repeats: false }\n : trigger?.date\n ? { type: 'date', date: new Date(trigger.date) }\n : null;\n if (!when) {\n throw new Error('schedule() needs trigger.seconds or trigger.date.');\n }\n return Notifications.scheduleNotificationAsync({\n content: { title, body },\n trigger: when,\n });\n },\n\n cancel: (id) => Notifications.cancelScheduledNotificationAsync(id),\n\n onReceived(cb) {\n const sub = Notifications.addNotificationReceivedListener(n => {\n cb({\n id: n.request.identifier,\n title: n.request.content.title || '',\n body: n.request.content.body || '',\n data: n.request.content.data,\n });\n });\n return () => sub.remove();\n },\n };\n}\n\n// ── File system (expo-document-picker — partially real) ───────────\n//\n// pickDocument works. read/write do NOT: expo-file-system is not installed.\n// Named honestly rather than returning empty strings, which would read as an\n// empty file rather than a missing capability.\n\nfunction createFileSystemAPI(pluginId: string): FileSystemAPI {\n /**\n * Every path a plugin gives us is resolved INSIDE its own sandbox directory.\n *\n * A plugin is code a solution ships that runs on someone's phone, so\n * `readFile('/etc/passwd')` — or, more realistically, reading the app's own\n * auth store or another solution's data — must not be reachable. This is the\n * same reasoning that took the `sw.*` filesystem tools out of the kernel and\n * anchored them per tenant.\n *\n * The check is on the RESOLVED path, not the input string: `..` segments are\n * what a blocklist of prefixes misses, and `plugin-files/../../secrets` looks\n * innocent until it is normalised.\n */\n /**\n * Resolved on FIRST USE, never at construction.\n *\n * `new FS.Directory(FS.Paths.document, …)` is a SYNCHRONOUS NATIVE CALL, and\n * `createNativeCapabilities({ fileSystem: true, … })` runs at MODULE SCOPE in\n * src/adas.ts. Building it eagerly therefore did native work during startup\n * module evaluation — on every launch, including a headless background wake\n * from a push, before any UI exists. If the native module is missing or\n * `Paths.document` throws there, the whole app dies with no screen to show it\n * on. Nothing in this factory may touch native until a method is called.\n */\n let _root: InstanceType<typeof FS.Directory> | null = null;\n const rootDir = () => (_root ??= new FS.Directory(FS.Paths.document, 'plugin-files', pluginId));\n\n const resolve = (p: string): InstanceType<typeof FS.File> => {\n const root = rootDir();\n const file = new FS.File(root, p);\n if (!file.uri.startsWith(root.uri)) {\n throw new CapabilityUnavailableError(\n 'fileSystem',\n `Path escapes the plugin's storage area: ${p}. Plugin files live under plugin-files/ and paths cannot traverse out of it.`,\n );\n }\n return file;\n };\n\n return {\n async readFile(path) {\n const file = resolve(path);\n if (!file.exists) {\n // A missing file is not an empty file. Returning '' would read as a\n // document that exists and happens to have nothing in it.\n throw new CapabilityUnavailableError(\n 'fileSystem.readFile',\n `No such file: ${path}. Nothing was read.`,\n );\n }\n return file.text();\n },\n\n async writeFile(path, content) {\n const file = resolve(path);\n // Create parents as needed — a plugin writing 'cache/report.json' should\n // not have to know that directories are a separate concept.\n const parent = file.parentDirectory;\n if (!parent.exists) parent.create({ intermediates: true });\n if (!file.exists) file.create();\n file.write(content);\n },\n async pickDocument(opts) {\n const res = await DocumentPicker.getDocumentAsync({\n type: opts?.types?.length ? opts.types : '*/*',\n copyToCacheDirectory: true,\n });\n // Backing out is not a file. Returning zeros here would look like an\n // empty document the user actually chose.\n if (res.canceled || !res.assets?.[0]) {\n throw new Error('No document chosen — the picker was dismissed.');\n }\n const a = res.assets[0];\n return { uri: a.uri, name: a.name, size: a.size ?? 0 };\n },\n };\n}\n\n// ── Haptics (fully implemented — expo-haptics already installed) ───\n\nfunction createHapticsAPI(): HapticsAPI {\n return {\n impact(style: 'light' | 'medium' | 'heavy') {\n const map = {\n light: Haptics.ImpactFeedbackStyle.Light,\n medium: Haptics.ImpactFeedbackStyle.Medium,\n heavy: Haptics.ImpactFeedbackStyle.Heavy,\n };\n Haptics.impactAsync(map[style]);\n },\n notification(type: 'success' | 'warning' | 'error') {\n const map = {\n success: Haptics.NotificationFeedbackType.Success,\n warning: Haptics.NotificationFeedbackType.Warning,\n error: Haptics.NotificationFeedbackType.Error,\n };\n Haptics.notificationAsync(map[type]);\n },\n // Shorthand for notification('error'). Plugins call this; it was never\n // implemented, and optional chaining meant the failure the user was meant\n // to FEEL simply did not happen.\n error() {\n Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);\n },\n selection() {\n Haptics.selectionAsync();\n },\n };\n}\n\n// ── Connectivity (implemented using basic RN fetch check) ──────\n\nfunction createConnectivityAPI(reachabilityUrl?: string): ConnectivityAPI {\n let online = true;\n const listeners = new Set<(online: boolean) => void>();\n // Lazy + re-armable polling: the instance is now a singleton shared by ALL\n // plugins (injected once via setPluginNativeProvider), so the interval must\n // (a) not run at app boot when nobody is listening, and (b) restart when a\n // later plugin subscribes after an earlier one unsubscribed to zero.\n // Pre-singleton, each Plugin mount got its own instance and clearInterval\n // was harmless; with a shared instance it would kill polling app-wide.\n let interval: ReturnType<typeof setInterval> | null = null;\n\n // Simple connectivity check — upgrade to @react-native-community/netinfo in Phase 2.\n // NO hardcoded URL: the host injects its own health endpoint. With none given\n // we assume reachable (return true) rather than pin an A-Team-specific default.\n const checkOnline = async () => {\n if (!reachabilityUrl) return true;\n try {\n const res = await fetch(reachabilityUrl, {\n method: 'HEAD',\n signal: AbortSignal.timeout(5000),\n });\n return res.ok;\n } catch {\n return false;\n }\n };\n\n const startPolling = () => {\n // Nothing to poll without a reachability URL: `online` stays constant-true,\n // so an interval would only wake the app to learn the same answer.\n if (!reachabilityUrl) return;\n if (interval) return;\n interval = setInterval(async () => {\n const newState = await checkOnline();\n if (newState !== online) {\n online = newState;\n listeners.forEach(cb => cb(online));\n }\n }, 30000);\n };\n\n return {\n isOnline() {\n return online;\n },\n onChange(cb: (online: boolean) => void) {\n listeners.add(cb);\n startPolling();\n return () => {\n listeners.delete(cb);\n if (listeners.size === 0 && interval) {\n clearInterval(interval);\n interval = null;\n }\n };\n },\n };\n}\n\n// ── Cookies (delegates to device-bridge, lazy-loaded) ──\nfunction createCookiesAPI(): CookiesAPI {\n return {\n async get(url: string): Promise<WebViewCookie[]> {\n const mod = getDeviceCookies();\n if (!mod) throw new CapabilityUnavailableError('cookies (native module not available — use a dev build)');\n const list = await mod.get(url, true);\n return list as WebViewCookie[];\n },\n async clear(_url: string): Promise<{ ok: boolean }> {\n const mod = getDeviceCookies();\n if (!mod) throw new CapabilityUnavailableError('cookies (native module not available — use a dev build)');\n const r = await mod.clearAll();\n return { ok: r.ok };\n },\n };\n}\n\n// ── Biometrics v2 (Face ID / Touch ID / fingerprint) ──\nfunction createBiometricsAPIv2(): BiometricsAPIv2 {\n return {\n capabilities: () => deviceBiometrics.capabilities(),\n authenticate: (opts) => deviceBiometrics.authenticate(opts),\n };\n}\n\n// ── Sharing (native share sheet) ──\nfunction createSharingAPI(): SharingAPI {\n return {\n isAvailable: () => deviceSharing.isAvailable(),\n shareFile: (fileUri, opts) => deviceSharing.shareFile(fileUri, opts),\n };\n}\n\n// ── Scanner (QR / barcode permission helper) ──\nfunction createScannerAPI(): ScannerAPI {\n return {\n requestPermissions: () => deviceScanner.requestPermissions(),\n // Raises the shared camera surface and settles on the first code read.\n // The plugin mounts nothing — that lifecycle is owned once, by\n // CameraTaskHost, instead of by every plugin that wants to scan.\n scanOnce: (opts) => cameraTasks.run('scan', {\n formats: opts?.formats,\n timeoutMs: opts?.timeoutMs,\n }),\n };\n}\n\n// ── NFC (tag read, lazy-loaded) ──\nfunction createNfcAPI(): NfcAPI {\n return {\n async isSupported() {\n const mod = getDeviceNfc();\n if (!mod) return false;\n return mod.isSupported();\n },\n async isEnabled() {\n const mod = getDeviceNfc();\n if (!mod) return false;\n return mod.isEnabled();\n },\n readOnce(timeoutMs) {\n const mod = getDeviceNfc();\n if (!mod) return Promise.resolve({ ok: false, error: 'nfc_unavailable' } as any);\n return mod.readOnce(timeoutMs);\n },\n };\n}\n\n// ── Bluetooth / BLE (SHAPE ONLY — every method refuses) ──\n//\n// Nothing implements this yet, and it is wired up anyway. `bluetooth` has been\n// a manifest flag with no API behind it, so a plugin that correctly declared it\n// got `undefined` and crashed with \"cannot read properties of undefined\" — a\n// failure that names nothing and teaches nothing.\n//\n// Refusing BY NAME is the whole difference. The rule this layer runs on is that\n// absence is an answer, but only when it says what is absent.\n//\n// KEPT rather than dropped because BLE beacons are one of the few indoor\n// position signals that survive where GPS does not — which is precisely where\n// Ada Guide ends up the moment the user walks into a building.\nfunction createBluetoothAPI(): BluetoothAPI {\n const refuse = (method: string) =>\n new CapabilityUnavailableError(\n `bluetooth.${method}`,\n 'Bluetooth is not implemented in this build — no BLE native module is installed. ' +\n 'The capability is declared so it fails by name rather than silently; nothing was scanned.',\n );\n\n return {\n // These two answer `false` rather than throwing, because \"is BLE usable?\"\n // is exactly the question a plugin should be allowed to ask before\n // committing to a flow, and `false` is the true answer here.\n async isSupported() { return false; },\n async isEnabled() { return false; },\n // scan() THROWS, because there is no honest empty result: an empty device\n // list is a real finding (\"nothing is advertising nearby\") and returning\n // one would report a scan that never happened.\n async scan() { throw refuse('scan'); },\n };\n}\n\n\n// ── Vision — on-device image understanding (SHAPE ONLY) ──\n//\n// Nothing implements this yet; every method refuses BY NAME. Declared now\n// because it is the last missing piece of Ada Guide and a solution should be\n// able to design against it before the native side exists.\n//\n// WHY IT IS NOT IN THE VISUAL DOMAIN: the freeze reserves Visual for the frame\n// TIMELINE and puts everything interpretive — what is in the scene, what the\n// sign says — ABOVE Device Bridge. This is that layer.\n//\n// FRAMES MUST NEVER LEAVE THE PHONE (Arie, 2026-08-15). Whoever implements\n// this: iOS Vision (VNRecognizeTextRequest / VNClassifyImageRequest) or\n// Android MLKit, both on-device and free per call. An implementation that\n// POSTs the frame anywhere satisfies these types and violates the design.\nfunction createVisionAPI(): VisionAPI {\n // All three methods are implemented on-device via the ada-vision native module:\n // assessQuality (pixel statistics), readText (VNRecognizeText), describe\n // (VNClassifyImage). Frames never leave the phone.\n return {\n // \"Can I see?\" — true once the native module is present in the binary. On a\n // build without it, adaVision.isSupported() answers false, and assessQuality\n // below refuses cleanly (no crash), exactly the pre-module behaviour.\n async isSupported() { return adaVision.isSupported(); },\n // readText: on-device OCR on a published frame (native resolves the id →\n // pixels). [] = looked, found no text (a real finding); throws = could not\n // look (unknown/aged-out id). describe (VNClassifyImage) is the next pass.\n async readText(frameId: string, opts?: { languages?: string[] }) {\n const langs = opts?.languages;\n if (langs != null && (!Array.isArray(langs) || langs.some((l) => typeof l !== 'string'))) {\n throw refuseArgument('vision', 'readText', `languages must be an array of language codes, not ${shown(langs)}`, 'nothing was read');\n }\n return adaVision.readText(frameId, langs);\n },\n // describe: on-device labels (VNClassifyImage), not prose — the sentence is\n // the skill's job. [] = looked, recognised nothing.\n async describe(frameId: string, opts?: { maxLabels?: number }) {\n // Checked here: a NaN/Infinity count would trap Swift's Int conversion (src/args.ts).\n return adaVision.describe(frameId, countArg('vision', 'describe', 'maxLabels', opts?.maxLabels, 5, 50, 'nothing was labelled'));\n },\n // detectObjects: on-device located objects (bundled detector), each with a\n // box — what a guidance loop reads to say WHERE a thing is, not just that the\n // scene contains it. [] = looked, found nothing; throws = could not look.\n async detectObjects(frameId: string, opts?: { maxItems?: number; minConfidence?: number }) {\n const nothing = 'nothing was detected';\n return adaVision.detectObjects(\n frameId,\n countArg('vision', 'detectObjects', 'maxItems', opts?.maxItems, 10, 50, nothing),\n fractionArg('vision', 'detectObjects', 'minConfidence', opts?.minConfidence, 0.25, nothing),\n );\n },\n // assessQuality: resolve the frame id to its LOCAL file, measure on-device,\n // and record the result back onto the frame so a later read of\n // VisualFrameRef.quality reflects it. Throwing here means \"could not look\"\n // (unknown/aged-out id, or no native module) — never a measured verdict.\n async assessQuality(frameId: string) {\n // Quality was measured at publish; the pixels live in the native ring,\n // resolved by id inside native. Null = the frame is unknown or has aged out\n // of the ring — \"could not look\", never \"looked and found nothing\".\n const q = adaVision.qualityById(frameId);\n if (!q) {\n throw new CapabilityUnavailableError(\n 'vision.assessQuality',\n `No frame for id \"${frameId}\" — unknown or aged out of the ring. ` +\n 'This is \"could not look\", not a measurement.',\n );\n }\n return q;\n },\n };\n}\n\n// ── Maps — place search, reverse geocoding, walking routes ──\n//\n// NOT on-device, unlike vision: the query and the coordinates it is given go to\n// Apple's map servers (the platform geocoder on Android). It never reads the\n// user's location — the caller passes every coordinate — so it is not a second\n// door to where the user is.\n//\n// search/route are the ada-maps module (MapKit, iOS only). reverseGeocode is the\n// SDK's one reverse geocode (expo-location), the same call the location\n// collector and the connector bridge make — so it works on Android and on a\n// build without ada-maps.\nfunction createMapsAPI(): MapsAPI {\n // Refuse BY NAME before any native call: what this build or platform is\n // missing, and that nothing left the phone.\n const refuseIfMissing = (verb: string, nothing: string) => {\n const missing = adaMaps.missingReason();\n if (missing) {\n throw new CapabilityUnavailableError(`maps.${verb}`, `maps.${verb} needs ${missing}; ${nothing}.`);\n }\n };\n\n // Every argument is checked (searchArgs / coordinateArg) BEFORE the missing\n // check and before anything crosses the bridge: a remote bundle is plain JS,\n // and a malformed call refuses as INVALID_ARGUMENT instead of crashing Swift\n // (NaN limit) or searching around 0,0 (`{ lat, lng }`).\n return {\n async isSupported() { return adaMaps.isSupported(); },\n // [] = Apple looked and found nothing (a real finding); throws = could not\n // look. The defaults (radiusM 3000, limit 8) live in searchArgs.\n async search(query, opts) {\n const args = searchArgs(query, opts);\n refuseIfMissing('search', 'nothing was searched');\n const raw = await adaMaps\n .search(args.query, args.near, args.radiusM, args.limit)\n .catch(err => { throw mapsFailure('search', err); });\n return toMapsPlaces(raw, args.near);\n },\n // null = nothing at that point; throws = could not look.\n async reverseGeocode(at) {\n const point = coordinateArg('reverseGeocode', 'at', at, 'nothing was looked up');\n const address = await adaMaps\n .reverseGeocode(point)\n .catch(err => { throw mapsFailure('reverseGeocode', err); });\n return address ? toReverseGeocodedPlace(point, address) : null;\n },\n // null = Apple found no route. Never an empty 0 m route, which would read as\n // \"you have arrived\". A remote bundle can pass any string as `mode`, so a\n // mode this build cannot honour refuses rather than silently walking.\n async route(from, to, opts) {\n if (opts?.mode !== undefined && opts.mode !== 'walking') {\n throw new CapabilityUnavailableError(\n 'maps.route',\n `maps.route supports walking only, not \"${String(opts.mode)}\"; no route was requested.`,\n );\n }\n const a = coordinateArg('route', 'from', from, 'no route was requested');\n const b = coordinateArg('route', 'to', to, 'no route was requested');\n refuseIfMissing('route', 'no route was requested');\n const raw = await adaMaps\n .route(a, b)\n .catch(err => { throw mapsFailure('route', err); });\n return raw ? toMapsRoute(raw) : null;\n },\n };\n}\n\n// ── Provider factory ───────────────────────────────────────────\n\nexport interface DeviceProviderConfig {\n /** Endpoint the connectivity check HEAD-fetches (host-injected, e.g. the app's\n * own health URL). Omitted → connectivity assumes reachable (no hardcoded URL). */\n reachabilityUrl?: string;\n}\n\n/**\n * The full device-primitive implementation a host registers once via\n * setPluginNativeProvider. It is UNGATED — per-plugin capability gating and\n * per-plugin filesystem scoping are applied by plugin-runtime's gatePluginNative.\n */\nexport function createDeviceProvider(config?: DeviceProviderConfig): PluginNative {\n const connectivity = createConnectivityAPI(config?.reachabilityUrl);\n return {\n camera: createCameraAPI(),\n location: createLocationAPI(),\n // Only the per-plugin storageFor/fileSystemFor are exposed — NO shared\n // `storage`/`fileSystem` bucket. The gate scopes by pluginId and fails\n // closed without one, so a shared instance would be an unreachable\n // (and dangerous) fallback. Each plugin gets its own namespace + db file /\n // sandbox directory, or nothing.\n storageFor: (pluginId: string) => createStorageAPI(pluginId),\n biometrics: stubApi<BiometricsAPI>('biometrics'),\n biometricsV2: createBiometricsAPIv2(),\n notifications: createNotificationsAPI(),\n haptics: createHapticsAPI(),\n connectivity,\n fileSystemFor: (pluginId: string) => createFileSystemAPI(pluginId),\n cookies: createCookiesAPI(),\n sharing: createSharingAPI(),\n scanner: createScannerAPI(),\n nfc: createNfcAPI(),\n bluetooth: createBluetoothAPI(),\n vision: createVisionAPI(),\n maps: createMapsAPI(),\n };\n}\n"],"mappings":"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","names":["isSupported"]}
@@ -0,0 +1,19 @@
1
+ // src/geo.ts
2
+ function distanceOf(path) {
3
+ const R = 6371e3;
4
+ let total = 0;
5
+ for (let i = 1; i < path.length; i++) {
6
+ const a = path[i - 1], b = path[i];
7
+ const dLat = (b.latitude - a.latitude) * Math.PI / 180;
8
+ const dLon = (b.longitude - a.longitude) * Math.PI / 180;
9
+ const la1 = a.latitude * Math.PI / 180, la2 = b.latitude * Math.PI / 180;
10
+ const h = Math.sin(dLat / 2) ** 2 + Math.cos(la1) * Math.cos(la2) * Math.sin(dLon / 2) ** 2;
11
+ total += 2 * R * Math.asin(Math.sqrt(h));
12
+ }
13
+ return Math.round(total);
14
+ }
15
+
16
+ export {
17
+ distanceOf
18
+ };
19
+ //# sourceMappingURL=chunk-J6XRNX7P.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/geo.ts"],"sourcesContent":["// Great-circle distance — the SDK's one answer to \"how far apart are these\".\n//\n// Moved out of device-state/domains/spatial.ts when native.maps needed the same\n// question answered (a search result's distance from `near`). Two haversines\n// would be two answers that could drift; both callers import this one.\n//\n// No imports on purpose: it runs under plain `node --test` with the maps\n// normaliser that uses it.\n\n/** Metres along a path, great-circle per leg, rounded to the metre. */\nexport function distanceOf(path: ReadonlyArray<{ latitude: number; longitude: number }>): number {\n const R = 6_371_000;\n let total = 0;\n for (let i = 1; i < path.length; i++) {\n const a = path[i - 1], b = path[i];\n const dLat = ((b.latitude - a.latitude) * Math.PI) / 180;\n const dLon = ((b.longitude - a.longitude) * Math.PI) / 180;\n const la1 = (a.latitude * Math.PI) / 180, la2 = (b.latitude * Math.PI) / 180;\n const h = Math.sin(dLat / 2) ** 2 + Math.cos(la1) * Math.cos(la2) * Math.sin(dLon / 2) ** 2;\n total += 2 * R * Math.asin(Math.sqrt(h));\n }\n return Math.round(total);\n}\n"],"mappings":";AAUO,SAAS,WAAW,MAAsE;AAC/F,QAAM,IAAI;AACV,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC;AACjC,UAAM,QAAS,EAAE,WAAW,EAAE,YAAY,KAAK,KAAM;AACrD,UAAM,QAAS,EAAE,YAAY,EAAE,aAAa,KAAK,KAAM;AACvD,UAAM,MAAO,EAAE,WAAW,KAAK,KAAM,KAAK,MAAO,EAAE,WAAW,KAAK,KAAM;AACzE,UAAM,IAAI,KAAK,IAAI,OAAO,CAAC,KAAK,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO,CAAC,KAAK;AAC1F,aAAS,IAAI,IAAI,KAAK,KAAK,KAAK,KAAK,CAAC,CAAC;AAAA,EACzC;AACA,SAAO,KAAK,MAAM,KAAK;AACzB;","names":[]}
@@ -64,4 +64,4 @@ export {
64
64
  describe,
65
65
  detectObjects
66
66
  };
67
- //# sourceMappingURL=chunk-TZXWVYYR.mjs.map
67
+ //# sourceMappingURL=chunk-PCAEUX2N.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vision/index.ts"],"sourcesContent":["// ada-vision — on-device image understanding, our own native code (no camera lib).\n//\n// On-device, frames never leave the phone:\n// assessQuality(uri) — one-shot quality of a LOCAL frame file.\n// startVision()/onVisionFrame — the headless camera session that publishes a\n// timeline frame (id + quality, never pixels) at a\n// cadence; readText/describe/detectObjects resolve\n// a published id ON-DEVICE.\n//\n// The luma math mirrors ./analysis.js (which has the exhaustive unit tests).\n//\n// requireOptionalNativeModule: on a binary WITHOUT the native module, the import\n// is null, isSupported() is false, and the session calls refuse cleanly (no crash).\n\nimport type { ComponentType } from 'react';\nimport { requireOptionalNativeModule } from 'expo-modules-core';\nimport { requireNativeView } from 'expo';\n\n/** TEST-ONLY live preview bound to the same headless session (so a tester can\n * aim). The real Guide runs headless and never mounts this. Null on a binary\n * without the native view, so importing this module never crashes. */\nexport const CameraPreview: ComponentType<any> | null = (() => {\n try { return requireNativeView('AdaVision'); } catch { return null; }\n})();\n\nexport interface RawFrameQuality {\n blur: 'sharp' | 'slight' | 'severe' | 'unknown';\n exposure: 'good' | 'dark' | 'bright' | 'unknown';\n /** true = lens covered; false = clearly not; null = not assessed. */\n obstructed: boolean | null;\n}\n\ninterface Subscription { remove(): void }\n\n/** A published timeline frame from the vision session (metadata only — never\n * pixels; the buffer stays in the native ring, resolved by id). */\nexport interface VisionFrame {\n id: string;\n ts: number;\n expiresAt: number;\n blur: RawFrameQuality['blur'];\n exposure: RawFrameQuality['exposure'];\n obstructed: RawFrameQuality['obstructed'];\n}\n\n/** One OCR result on a frame — matches the SDK's VisionText. */\nexport interface VisionText {\n text: string;\n /** 0..1 recogniser confidence (never a 1.0 filler). */\n confidence: number | null;\n /** Normalised 0..1 box, origin TOP-left, in the UPRIGHT portrait frame (native\n * rotates frames at capture) — x runs from the user's left to their right. */\n box?: { x: number; y: number; width: number; height: number };\n}\n\n/** One classifier label — a vocabulary term, NOT prose. Matches VisionLabel. */\nexport interface VisionLabel {\n label: string;\n confidence: number | null;\n}\n\n/** One detected object — a located item with a box, from the bundled detector. */\nexport interface VisionObject {\n /** Open-Images class name (e.g. \"Car\", \"Traffic light\", \"Building\", \"Person\"). */\n label: string;\n /** 0..1 detector confidence. */\n confidence: number | null;\n /** Normalised 0..1 box, origin TOP-left, in the UPRIGHT portrait frame (native\n * rotates frames at capture) — x runs from the user's left to their right. */\n box?: { x: number; y: number; width: number; height: number };\n}\n\ninterface AdaVisionNative {\n isSupported(): boolean;\n assessQuality(uri: string): Promise<RawFrameQuality>;\n startVision(intervalMs: number): Promise<void>;\n stopVision(): void;\n visionRetentionMs(): number;\n qualityById(id: string): RawFrameQuality | null;\n readText(id: string, languages: string[] | null): Promise<VisionText[]>;\n describe(id: string, maxLabels: number): Promise<VisionLabel[]>;\n detectObjects(id: string, maxItems: number, minConfidence: number): Promise<VisionObject[]>;\n addListener(event: 'visionFrame', cb: (f: VisionFrame) => void): Subscription;\n}\n\nconst native = requireOptionalNativeModule<AdaVisionNative>('AdaVision');\n\n/** Is the native vision module present in this binary? */\nexport function isSupported(): boolean {\n try { return native?.isSupported() ?? false; } catch { return false; }\n}\n\n/** One-shot: measure a frame's quality from its LOCAL file. Throws if it cannot look. */\nexport async function assessQuality(uri: string): Promise<RawFrameQuality> {\n if (!native) throw new Error('ada-vision native module not present in this build');\n return native.assessQuality(uri);\n}\n\n// ── Vision session (feeds the Visual Engine) ──\n\n/** Start the vision session — publishes a timeline frame every `intervalMs`. */\nexport async function startVision(intervalMs = 1000): Promise<void> {\n if (!native) throw new Error('ada-vision native module not present in this build');\n return native.startVision(intervalMs);\n}\n\n/** Stop the vision session and release the camera. */\nexport function stopVision(): void {\n native?.stopVision?.();\n}\n\n/** Subscribe to published timeline frames (id + quality + expiry; no pixels). */\nexport function onVisionFrame(cb: (f: VisionFrame) => void): Subscription {\n if (!native) return { remove() {} };\n return native.addListener('visionFrame', cb);\n}\n\n/** How far back the native ring can still resolve a frame id (ms). */\nexport function visionRetentionMs(): number {\n return native?.visionRetentionMs?.() ?? 0;\n}\n\n/** Resolve a published frame id → its measured quality (null if evicted). */\nexport function qualityById(id: string): RawFrameQuality | null {\n return native?.qualityById?.(id) ?? null;\n}\n\n/** On-device OCR on a published frame. [] = looked, no text; throws = couldn't look. */\nexport async function readText(id: string, languages?: string[]): Promise<VisionText[]> {\n if (!native) throw new Error('ada-vision native module not present in this build');\n return native.readText(id, languages ?? null);\n}\n\n/** On-device image classification → labels (not prose). [] = nothing recognised. */\nexport async function describe(id: string, maxLabels = 5): Promise<VisionLabel[]> {\n if (!native) throw new Error('ada-vision native module not present in this build');\n return native.describe(id, maxLabels);\n}\n\n/** On-device object detection (bundled YOLOv8n, Open-Images vocabulary) → located\n * items with boxes. [] = looked, found nothing above `minConfidence`; throws =\n * couldn't look (id aged out, or the model isn't in this build). */\nexport async function detectObjects(id: string, maxItems = 10, minConfidence = 0.25): Promise<VisionObject[]> {\n if (!native) throw new Error('ada-vision native module not present in this build');\n return native.detectObjects(id, maxItems, minConfidence);\n}\n"],"mappings":";AAeA,SAAS,mCAAmC;AAC5C,SAAS,yBAAyB;AAK3B,IAAM,iBAA4C,MAAM;AAC7D,MAAI;AAAE,WAAO,kBAAkB,WAAW;AAAA,EAAG,QAAQ;AAAE,WAAO;AAAA,EAAM;AACtE,GAAG;AA8DH,IAAM,SAAS,4BAA6C,WAAW;AAGhE,SAAS,cAAuB;AACrC,MAAI;AAAE,WAAO,QAAQ,YAAY,KAAK;AAAA,EAAO,QAAQ;AAAE,WAAO;AAAA,EAAO;AACvE;AAGA,eAAsB,cAAc,KAAuC;AACzE,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,oDAAoD;AACjF,SAAO,OAAO,cAAc,GAAG;AACjC;AAKA,eAAsB,YAAY,aAAa,KAAqB;AAClE,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,oDAAoD;AACjF,SAAO,OAAO,YAAY,UAAU;AACtC;AAGO,SAAS,aAAmB;AACjC,UAAQ,aAAa;AACvB;AAGO,SAAS,cAAc,IAA4C;AACxE,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS;AAAA,EAAC,EAAE;AAClC,SAAO,OAAO,YAAY,eAAe,EAAE;AAC7C;AAGO,SAAS,oBAA4B;AAC1C,SAAO,QAAQ,oBAAoB,KAAK;AAC1C;AAGO,SAAS,YAAY,IAAoC;AAC9D,SAAO,QAAQ,cAAc,EAAE,KAAK;AACtC;AAGA,eAAsB,SAAS,IAAY,WAA6C;AACtF,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,oDAAoD;AACjF,SAAO,OAAO,SAAS,IAAI,aAAa,IAAI;AAC9C;AAGA,eAAsB,SAAS,IAAY,YAAY,GAA2B;AAChF,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,oDAAoD;AACjF,SAAO,OAAO,SAAS,IAAI,SAAS;AACtC;AAKA,eAAsB,cAAc,IAAY,WAAW,IAAI,gBAAgB,MAA+B;AAC5G,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,oDAAoD;AACjF,SAAO,OAAO,cAAc,IAAI,UAAU,aAAa;AACzD;","names":[]}
@@ -8,6 +8,9 @@ import {
8
8
  hasPermission,
9
9
  requestPermission
10
10
  } from "./chunk-XQSY6MHP.mjs";
11
+ import {
12
+ reverseGeocode
13
+ } from "./chunk-7FWYEHN5.mjs";
11
14
  import {
12
15
  __export
13
16
  } from "./chunk-WMJKH4XE.mjs";
@@ -876,7 +879,7 @@ async function collectCurrent() {
876
879
  let address = `${location.coords.latitude.toFixed(4)}, ${location.coords.longitude.toFixed(4)}`;
877
880
  let locationType = "unknown";
878
881
  try {
879
- const [geocoded] = await ExpoLocation.reverseGeocodeAsync({
882
+ const geocoded = await reverseGeocode({
880
883
  latitude: location.coords.latitude,
881
884
  longitude: location.coords.longitude
882
885
  });
@@ -934,7 +937,13 @@ function toPosition(fix) {
934
937
  function nonNegative(v) {
935
938
  return typeof v === "number" && v >= 0 ? v : void 0;
936
939
  }
937
- function watchPosition(cb) {
940
+ var WATCH_TIER = {
941
+ navigation: ExpoLocation.Accuracy.BestForNavigation,
942
+ high: ExpoLocation.Accuracy.High,
943
+ balanced: ExpoLocation.Accuracy.Balanced,
944
+ low: ExpoLocation.Accuracy.Low
945
+ };
946
+ function watchPosition(cb, opts) {
938
947
  let subscription = null;
939
948
  let cancelled = false;
940
949
  (async () => {
@@ -942,7 +951,7 @@ function watchPosition(cb) {
942
951
  await ensurePermission();
943
952
  if (cancelled) return;
944
953
  subscription = await ExpoLocation.watchPositionAsync(
945
- { accuracy: ExpoLocation.Accuracy.Balanced },
954
+ { accuracy: WATCH_TIER[opts?.accuracy ?? "balanced"] },
946
955
  (fix) => cb(toPosition(fix))
947
956
  );
948
957
  if (cancelled) {
@@ -1798,4 +1807,4 @@ export {
1798
1807
  scanner_exports,
1799
1808
  DeviceBridge
1800
1809
  };
1801
- //# sourceMappingURL=chunk-VQJPSLHP.mjs.map
1810
+ //# sourceMappingURL=chunk-Z5AI2SXX.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/device-bridge/relay-client.ts","../src/device-bridge/actions/messaging.ts","../src/device-bridge/actions/calendar-actions.ts","../src/device-bridge/actions/dnd.ts","../src/device-bridge/actions/navigation.ts","../src/device-bridge/actions/health-actions.ts","../src/device-bridge/actions/queue-poller.ts","../src/device-bridge/background.ts","../src/device-bridge/capabilities.ts","../src/device-bridge/collectors/calendar.ts","../src/device-bridge/collectors/contacts.ts","../src/device-bridge/collectors/location.ts","../src/device-bridge/collectors/battery.ts","../src/device-bridge/collectors/connectivity.ts","../src/device-bridge/collectors/notifications.ts","../src/device-bridge/collectors/mood.ts","../src/device-bridge/collectors/camera.ts","../src/device-bridge/biometrics.ts","../src/device-bridge/sharing.ts","../src/device-bridge/scanner.ts","../src/device-bridge/index.ts"],"sourcesContent":["/**\n * Relay Client — REST client for the cloud relay service.\n * Handles data push, action polling, and offline buffering.\n *\n * Endpoints:\n * POST /api/device/{deviceId}/sync ← push data bundle\n * GET /api/device/{deviceId}/data/{domain} ← read latest (used by connector, not phone)\n * POST /api/device/{deviceId}/actions ← queue action (used by connector)\n * GET /api/device/{deviceId}/actions ← poll pending actions\n * DELETE /api/device/{deviceId}/actions/{id} ← confirm action executed\n */\n\nimport type {\n DeviceDataBundle,\n PendingAction,\n ActionResult,\n RelayResponse,\n} from './types';\n\nexport interface RelayClientConfig {\n relayUrl: string;\n deviceId: string;\n tenant: string;\n actorId?: string;\n apiKey?: string;\n timeout?: number; // ms, default 10000\n}\n\nexport class RelayClient {\n private baseUrl: string;\n private deviceId: string;\n private tenant: string;\n private actorId: string;\n private apiKey: string;\n private timeout: number;\n private offlineQueue: DeviceDataBundle[] = [];\n\n constructor(config: RelayClientConfig) {\n // Strip trailing slash\n this.baseUrl = config.relayUrl.replace(/\\/$/, '');\n this.deviceId = config.deviceId;\n this.tenant = config.tenant;\n this.actorId = config.actorId ?? 'default';\n this.apiKey = config.apiKey ?? '';\n this.timeout = config.timeout ?? 10_000;\n }\n\n // ─── Data Sync ───\n\n /**\n * Push a full device data bundle to the relay.\n * If offline, buffers the bundle and retries on next sync.\n */\n async pushData(bundle: DeviceDataBundle): Promise<RelayResponse<void>> {\n try {\n const response = await this.fetch(\n `/api/device/${this.deviceId}/sync`,\n {\n method: 'POST',\n body: JSON.stringify(bundle),\n }\n );\n\n if (response.ok) {\n // Flush any offline queue on successful connection\n await this.flushOfflineQueue();\n return { ok: true };\n }\n\n return {\n ok: false,\n error: `Relay returned ${response.status}: ${await response.text()}`,\n };\n } catch (error) {\n // Network error — buffer for later\n this.offlineQueue.push(bundle);\n return {\n ok: false,\n error: `Offline — buffered (${this.offlineQueue.length} pending)`,\n };\n }\n }\n\n /**\n * Push a single domain update (partial sync).\n */\n async pushDomain(\n domain: string,\n data: unknown\n ): Promise<RelayResponse<void>> {\n try {\n const response = await this.fetch(\n `/api/device/${this.deviceId}/sync/${domain}`,\n {\n method: 'POST',\n body: JSON.stringify({ domain, data, timestamp: new Date().toISOString(), actorId: this.actorId }),\n }\n );\n\n if (response.ok) return { ok: true };\n return {\n ok: false,\n error: `Relay returned ${response.status}`,\n };\n } catch (error) {\n return { ok: false, error: 'Network error — domain sync failed' };\n }\n }\n\n // ─── Action Queue ───\n\n /**\n * Poll for pending actions the phone needs to execute.\n */\n async pollActions(): Promise<RelayResponse<PendingAction[]>> {\n try {\n const response = await this.fetch(\n `/api/device/${this.deviceId}/actions`,\n { method: 'GET' }\n );\n\n if (response.ok) {\n const data = await response.json();\n return { ok: true, data: data.actions ?? [] };\n }\n return { ok: false, error: `Relay returned ${response.status}` };\n } catch (error) {\n return { ok: false, error: 'Network error — action poll failed' };\n }\n }\n\n /**\n * Report action execution result and remove from queue.\n */\n async completeAction(result: ActionResult): Promise<RelayResponse<void>> {\n try {\n const response = await this.fetch(\n `/api/device/${this.deviceId}/actions/${result.id}`,\n {\n method: 'DELETE',\n body: JSON.stringify(result),\n }\n );\n\n if (response.ok) return { ok: true };\n return { ok: false, error: `Relay returned ${response.status}` };\n } catch (error) {\n return { ok: false, error: 'Network error — action complete failed' };\n }\n }\n\n // ─── Health ───\n\n /**\n * Ping the relay to check connectivity.\n */\n async ping(): Promise<boolean> {\n try {\n const response = await this.fetch(\n `/api/health`,\n { method: 'GET' },\n 3_000 // short timeout for health check\n );\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Get count of buffered offline bundles.\n */\n getOfflineQueueSize(): number {\n return this.offlineQueue.length;\n }\n\n // ─── Internal ───\n\n private async fetch(\n path: string,\n init: RequestInit,\n timeout?: number\n ): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(\n () => controller.abort(),\n timeout ?? this.timeout\n );\n\n try {\n return await fetch(`${this.baseUrl}${path}`, {\n ...init,\n signal: controller.signal,\n headers: {\n 'Content-Type': 'application/json',\n 'X-ADAS-TENANT': this.tenant,\n 'X-ADAS-ACTOR': this.actorId,\n 'X-Device-Id': this.deviceId,\n // The relay (post-2026-04-25) gates every /api/device/* call on\n // a shared secret. Send it as `Authorization: Bearer` so the\n // server's relayAuth middleware accepts the request. Empty/missing\n // secret = 401 from the relay (correct fail-closed behavior).\n ...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}),\n ...(init.headers || {}),\n },\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async flushOfflineQueue(): Promise<void> {\n if (this.offlineQueue.length === 0) return;\n\n const queue = [...this.offlineQueue];\n this.offlineQueue = [];\n\n for (const bundle of queue) {\n try {\n const response = await this.fetch(\n `/api/device/${this.deviceId}/sync`,\n {\n method: 'POST',\n body: JSON.stringify(bundle),\n }\n );\n if (!response.ok) {\n // Put it back if still failing\n this.offlineQueue.push(bundle);\n }\n } catch {\n this.offlineQueue.push(bundle);\n break; // Stop trying if still offline\n }\n }\n }\n}\n","/**\n * Messaging Action Executor — sends SMS via expo-sms.\n * Executes 'messaging.send' actions from the action queue.\n */\n\nimport * as SMS from 'expo-sms';\nimport type { ActionResult } from '../types';\n\n/**\n * Check if SMS is available on this device.\n */\nexport async function isAvailable(): Promise<boolean> {\n return SMS.isAvailableAsync();\n}\n\n/**\n * Execute a messaging.send action.\n *\n * Params expected:\n * - contact_name: string (for display only)\n * - phone: string (phone number to send to)\n * - message: string (message text)\n */\nexport async function execute(\n actionId: string,\n params: Record<string, unknown>\n): Promise<ActionResult> {\n const phone = params.phone as string;\n const message = params.message as string;\n\n if (!phone || !message) {\n return {\n id: actionId,\n ok: false,\n error: 'Missing phone or message parameter',\n executed_at: new Date().toISOString(),\n };\n }\n\n try {\n const available = await SMS.isAvailableAsync();\n if (!available) {\n return {\n id: actionId,\n ok: false,\n error: 'SMS not available on this device',\n executed_at: new Date().toISOString(),\n };\n }\n\n // Opens the native SMS compose screen with pre-filled content\n const { result } = await SMS.sendSMSAsync([phone], message);\n\n return {\n id: actionId,\n ok: result === 'sent' || result === 'unknown', // 'unknown' on Android means it was sent\n result: {\n to: params.contact_name || phone,\n phone,\n message,\n status: result,\n sent_at: new Date().toISOString(),\n },\n executed_at: new Date().toISOString(),\n };\n } catch (error) {\n return {\n id: actionId,\n ok: false,\n error: `SMS send failed: ${error instanceof Error ? error.message : String(error)}`,\n executed_at: new Date().toISOString(),\n };\n }\n}\n","/**\n * Calendar Action Executor — creates/modifies events via expo-calendar.\n * Executes 'calendar.create', 'calendar.reschedule', 'calendar.cancel' actions.\n */\n\nimport * as ExpoCalendar from 'expo-calendar';\n// Type-only alias: the value namespace above cannot be used in type\n// position, so `ExpoCalendar.Calendar`-style annotations need this.\n// Derived rather than imported — see collectors/calendar.ts.\ntype Event = Awaited<ReturnType<typeof ExpoCalendar.getEventsAsync>>[number];\nimport type { ActionResult } from '../types';\n\n/**\n * Get the default calendar ID for creating events.\n */\nasync function getDefaultCalendarId(): Promise<string | null> {\n const calendars = await ExpoCalendar.getCalendarsAsync(ExpoCalendar.EntityTypes.EVENT);\n\n // Prefer the default calendar\n const defaultCal = calendars.find(c => c.isPrimary) || calendars[0];\n return defaultCal?.id || null;\n}\n\n/**\n * Execute a calendar.create action.\n *\n * Params expected:\n * - title: string\n * - date: string (YYYY-MM-DD)\n * - start_time: string (HH:MM)\n * - end_time?: string (HH:MM)\n * - location?: string\n * - attendees?: string[] (names — no email lookup here)\n * - notes?: string\n */\nexport async function executeCreate(\n actionId: string,\n params: Record<string, unknown>\n): Promise<ActionResult> {\n const title = params.title as string;\n const date = params.date as string;\n const startTime = params.start_time as string;\n const endTime = params.end_time as string | undefined;\n\n if (!title || !date || !startTime) {\n return {\n id: actionId,\n ok: false,\n error: 'Missing title, date, or start_time',\n executed_at: new Date().toISOString(),\n };\n }\n\n try {\n const calendarId = await getDefaultCalendarId();\n if (!calendarId) {\n return {\n id: actionId,\n ok: false,\n error: 'No calendar found on device',\n executed_at: new Date().toISOString(),\n };\n }\n\n const [year, month, day] = date.split('-').map(Number);\n const [startH, startM] = startTime.split(':').map(Number);\n\n const startDate = new Date(year, month - 1, day, startH, startM);\n let endDate: Date;\n\n if (endTime) {\n const [endH, endM] = endTime.split(':').map(Number);\n endDate = new Date(year, month - 1, day, endH, endM);\n } else {\n // Default 1 hour duration\n endDate = new Date(startDate.getTime() + 60 * 60 * 1000);\n }\n\n const eventId = await ExpoCalendar.createEventAsync(calendarId, {\n title,\n startDate,\n endDate,\n location: (params.location as string) || undefined,\n notes: (params.notes as string) || undefined,\n timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n });\n\n return {\n id: actionId,\n ok: true,\n result: {\n event_id: eventId,\n title,\n date,\n start: startTime,\n end: endTime || formatTime(endDate),\n message: `Event \"${title}\" created successfully`,\n },\n executed_at: new Date().toISOString(),\n };\n } catch (error) {\n return {\n id: actionId,\n ok: false,\n error: `Calendar create failed: ${error instanceof Error ? error.message : String(error)}`,\n executed_at: new Date().toISOString(),\n };\n }\n}\n\n/**\n * Execute a calendar.reschedule action.\n *\n * Params expected:\n * - event_id: string\n * - new_date?: string (YYYY-MM-DD)\n * - new_start_time?: string (HH:MM)\n * - new_end_time?: string (HH:MM)\n */\nexport async function executeReschedule(\n actionId: string,\n params: Record<string, unknown>\n): Promise<ActionResult> {\n const eventId = params.event_id as string;\n\n if (!eventId) {\n return {\n id: actionId,\n ok: false,\n error: 'Missing event_id',\n executed_at: new Date().toISOString(),\n };\n }\n\n try {\n const updates: Partial<Event> = {};\n\n if (params.new_date && params.new_start_time) {\n const [year, month, day] = (params.new_date as string).split('-').map(Number);\n const [h, m] = (params.new_start_time as string).split(':').map(Number);\n updates.startDate = new Date(year, month - 1, day, h, m);\n\n if (params.new_end_time) {\n const [eh, em] = (params.new_end_time as string).split(':').map(Number);\n updates.endDate = new Date(year, month - 1, day, eh, em);\n } else {\n updates.endDate = new Date(updates.startDate.getTime() + 60 * 60 * 1000);\n }\n }\n\n await ExpoCalendar.updateEventAsync(eventId, updates);\n\n return {\n id: actionId,\n ok: true,\n result: { event_id: eventId, message: 'Event rescheduled successfully' },\n executed_at: new Date().toISOString(),\n };\n } catch (error) {\n return {\n id: actionId,\n ok: false,\n error: `Calendar reschedule failed: ${error instanceof Error ? error.message : String(error)}`,\n executed_at: new Date().toISOString(),\n };\n }\n}\n\n/**\n * Execute a calendar.cancel action.\n *\n * Params expected:\n * - event_id: string\n */\nexport async function executeCancel(\n actionId: string,\n params: Record<string, unknown>\n): Promise<ActionResult> {\n const eventId = params.event_id as string;\n\n if (!eventId) {\n return {\n id: actionId,\n ok: false,\n error: 'Missing event_id',\n executed_at: new Date().toISOString(),\n };\n }\n\n try {\n await ExpoCalendar.deleteEventAsync(eventId);\n\n return {\n id: actionId,\n ok: true,\n result: { event_id: eventId, message: 'Event cancelled successfully' },\n executed_at: new Date().toISOString(),\n };\n } catch (error) {\n return {\n id: actionId,\n ok: false,\n error: `Calendar cancel failed: ${error instanceof Error ? error.message : String(error)}`,\n executed_at: new Date().toISOString(),\n };\n }\n}\n\n// ─── Helpers ───\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n });\n}\n","/**\n * DND Action Executor — manages Do Not Disturb via expo-notifications.\n * Executes 'dnd.set' actions from the action queue.\n *\n * Note: True system-level DND control requires platform-specific APIs:\n * - iOS: Focus modes (requires entitlements)\n * - Android: NotificationManager.setInterruptionFilter (requires DND access)\n *\n * For now, this manages DND state within the PA system:\n * - Stores DND state in relay for the connector to read\n * - Controls notification channel behavior in-app\n */\n\nimport type { ActionResult, DndState } from '../types';\n\n// In-memory DND state (synced to relay)\nlet currentDndState: DndState = {\n enabled: false,\n schedule: { start: '22:00', end: '07:00' },\n allowlist: [],\n};\n\n/**\n * Get current DND state.\n */\nexport function getState(): DndState {\n return { ...currentDndState };\n}\n\n/**\n * Execute a dnd.set action.\n *\n * Params expected:\n * - enabled?: boolean\n * - schedule_start?: string (HH:MM)\n * - schedule_end?: string (HH:MM)\n * - allowlist?: string[]\n */\nexport async function execute(\n actionId: string,\n params: Record<string, unknown>\n): Promise<ActionResult> {\n try {\n if (params.enabled !== undefined) {\n currentDndState.enabled = params.enabled as boolean;\n }\n if (params.schedule_start) {\n currentDndState.schedule.start = params.schedule_start as string;\n }\n if (params.schedule_end) {\n currentDndState.schedule.end = params.schedule_end as string;\n }\n if (params.allowlist) {\n currentDndState.allowlist = params.allowlist as string[];\n }\n\n return {\n id: actionId,\n ok: true,\n result: {\n message: `DND ${currentDndState.enabled ? 'enabled' : 'disabled'}`,\n dnd: currentDndState,\n },\n executed_at: new Date().toISOString(),\n };\n } catch (error) {\n return {\n id: actionId,\n ok: false,\n error: `DND set failed: ${error instanceof Error ? error.message : String(error)}`,\n executed_at: new Date().toISOString(),\n };\n }\n}\n","/**\n * Navigation Action Executor — opens maps app via expo-linking.\n * Executes 'navigation.start' actions from the action queue.\n */\n\nimport * as Linking from 'expo-linking';\nimport { Platform } from 'react-native';\nimport type { ActionResult } from '../types';\n\n/**\n * Execute a navigation.start action.\n * Opens the native maps app with turn-by-turn directions.\n *\n * Params expected:\n * - destination: string (address or place name)\n * - mode?: 'driving' | 'transit' | 'walking' (default: 'driving')\n */\nexport async function execute(\n actionId: string,\n params: Record<string, unknown>\n): Promise<ActionResult> {\n const destination = params.destination as string;\n const mode = (params.mode as string) || 'driving';\n\n if (!destination) {\n return {\n id: actionId,\n ok: false,\n error: 'Missing destination parameter',\n executed_at: new Date().toISOString(),\n };\n }\n\n try {\n const encodedDest = encodeURIComponent(destination);\n let url: string;\n\n if (Platform.OS === 'ios') {\n // Apple Maps with directions\n const modeMap: Record<string, string> = {\n driving: 'd',\n transit: 'r',\n walking: 'w',\n };\n url = `maps://maps.apple.com/?daddr=${encodedDest}&dirflg=${modeMap[mode] || 'd'}`;\n } else {\n // Google Maps on Android\n const modeMap: Record<string, string> = {\n driving: 'driving',\n transit: 'transit',\n walking: 'walking',\n };\n url = `google.navigation:q=${encodedDest}&mode=${modeMap[mode] || 'driving'}`;\n }\n\n const canOpen = await Linking.canOpenURL(url);\n if (!canOpen) {\n // Fallback to Google Maps web URL\n const webModeMap: Record<string, string> = {\n driving: 'driving',\n transit: 'transit',\n walking: 'walking',\n };\n url = `https://www.google.com/maps/dir/?api=1&destination=${encodedDest}&travelmode=${webModeMap[mode] || 'driving'}`;\n }\n\n await Linking.openURL(url);\n\n return {\n id: actionId,\n ok: true,\n result: {\n message: `Navigation started to ${destination} via ${mode}`,\n destination,\n mode,\n status: 'navigating',\n },\n executed_at: new Date().toISOString(),\n };\n } catch (error) {\n return {\n id: actionId,\n ok: false,\n error: `Navigation failed: ${error instanceof Error ? error.message : String(error)}`,\n executed_at: new Date().toISOString(),\n };\n }\n}\n","/**\n * Health Action Executor — on-demand reads from HealthKit.\n *\n * Two action types:\n * - 'health.today' → returns one HealthDayMetrics for today\n * - 'health.history' → returns last N days (1..30), newest-first\n *\n * Both are read-only; no params are required for `today`.\n * `history` accepts { days: number } (default 30).\n *\n * For periodic snapshots use the `health` capability instead — this\n * executor exists so a connector can pull fresher / longer-range data\n * without waiting for the next sync tick.\n */\nimport * as health from '../collectors/health';\nimport type { ActionResult } from '../types';\n\nexport async function executeToday(actionId: string): Promise<ActionResult> {\n try {\n const today = await health.collectToday();\n return {\n id: actionId,\n ok: true,\n result: { today },\n executed_at: new Date().toISOString(),\n };\n } catch (e: any) {\n return {\n id: actionId,\n ok: false,\n error: String(e?.message || e),\n executed_at: new Date().toISOString(),\n };\n }\n}\n\nexport async function executeHistory(\n actionId: string,\n params: Record<string, unknown>,\n): Promise<ActionResult> {\n try {\n const days = Number(params?.days) || 30;\n const list = await health.collectHistory(days);\n return {\n id: actionId,\n ok: true,\n result: { days: list },\n executed_at: new Date().toISOString(),\n };\n } catch (e: any) {\n return {\n id: actionId,\n ok: false,\n error: String(e?.message || e),\n executed_at: new Date().toISOString(),\n };\n }\n}\n","/**\n * Action Queue Poller — polls the relay for pending actions and executes them.\n * Routes each action to the appropriate executor based on action type.\n */\n\nimport type { RelayClient } from '../relay-client';\nimport type { PendingAction, ActionResult, ActionType } from '../types';\nimport * as messaging from './messaging';\nimport * as calendarActions from './calendar-actions';\nimport * as dnd from './dnd';\nimport * as navigation from './navigation';\nimport * as healthActions from './health-actions';\n\nexport interface QueuePollerConfig {\n /** Polling interval in ms (default: 5000) */\n pollInterval?: number;\n /** Relay client instance */\n relayClient: RelayClient;\n /** Callback when an action is executed */\n onActionExecuted?: (result: ActionResult) => void;\n /** Callback for errors */\n onError?: (error: Error) => void;\n}\n\nexport class QueuePoller {\n private relayClient: RelayClient;\n private pollInterval: number;\n private timer: ReturnType<typeof setInterval> | null = null;\n private running = false;\n private onActionExecuted?: (result: ActionResult) => void;\n private onError?: (error: Error) => void;\n\n constructor(config: QueuePollerConfig) {\n this.relayClient = config.relayClient;\n this.pollInterval = config.pollInterval ?? 5_000;\n this.onActionExecuted = config.onActionExecuted;\n this.onError = config.onError;\n }\n\n /**\n * Start polling for actions.\n */\n start(): void {\n if (this.running) return;\n this.running = true;\n\n // Poll immediately, then on interval\n this.poll();\n this.timer = setInterval(() => this.poll(), this.pollInterval);\n }\n\n /**\n * Stop polling.\n */\n stop(): void {\n this.running = false;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n }\n\n /**\n * Check if poller is running.\n */\n isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Execute a single poll cycle.\n */\n private async poll(): Promise<void> {\n if (!this.running) return;\n\n try {\n const response = await this.relayClient.pollActions();\n if (!response.ok || !response.data) return;\n\n for (const action of response.data) {\n // The relay only ever returns pending actions — it filters in the\n // query. A missing status therefore means \"the server didn't say\",\n // not \"already handled\", and treating it as the latter discarded\n // every queued action while every write tool still answered \"queued\".\n // Only an explicitly non-pending status is skipped.\n if (action.status && action.status !== 'pending') continue;\n\n const result = await this.executeAction(action);\n\n // Report result back to relay\n await this.relayClient.completeAction(result);\n\n this.onActionExecuted?.(result);\n }\n } catch (error) {\n this.onError?.(\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Route an action to the appropriate executor.\n */\n private async executeAction(action: PendingAction): Promise<ActionResult> {\n const { id, type, params } = action;\n\n try {\n switch (type as ActionType) {\n case 'messaging.send':\n return await messaging.execute(id, params);\n\n case 'calendar.create':\n return await calendarActions.executeCreate(id, params);\n\n case 'calendar.reschedule':\n return await calendarActions.executeReschedule(id, params);\n\n case 'calendar.cancel':\n return await calendarActions.executeCancel(id, params);\n\n case 'dnd.set':\n return await dnd.execute(id, params);\n\n case 'navigation.start':\n return await navigation.execute(id, params);\n\n case 'health.today':\n return await healthActions.executeToday(id);\n\n case 'health.history':\n return await healthActions.executeHistory(id, params);\n\n default:\n return {\n id,\n ok: false,\n error: `Unknown action type: ${type}`,\n executed_at: new Date().toISOString(),\n };\n }\n } catch (error) {\n return {\n id,\n ok: false,\n error: `Action execution failed: ${error instanceof Error ? error.message : String(error)}`,\n executed_at: new Date().toISOString(),\n };\n }\n }\n}\n","/**\n * Background Sync — registers an expo-task-manager task for periodic\n * data sync when the app is in the background, and exposes a one-shot\n * `syncDomains()` for silent-push triggered syncs.\n *\n * iOS minimum background fetch interval is ~15 minutes.\n * Android allows more frequent background execution via WorkManager.\n *\n * Usage:\n * import { registerBackgroundSync, syncDomains } from '@mobile-pa/device-bridge';\n *\n * // Periodic background fetch — call once at app startup, outside any component\n * registerBackgroundSync({\n * relayUrl: 'https://relay.example.com',\n * deviceId: 'device-123',\n * tenant: 'mobile-pa',\n * actorId: 'user-abc',\n * apiKey: 'key-xxx',\n * });\n *\n * // Silent-push triggered sync — from FCM background message handler\n * await syncDomains(config, ['location', 'calendar']);\n */\n\nimport * as TaskManager from 'expo-task-manager';\nimport * as BackgroundFetch from 'expo-background-fetch';\nimport { RelayClient } from './relay-client';\nimport * as caps from './capabilities';\nimport type { Capability } from './types';\n\nexport const BACKGROUND_TASK_NAME = 'DEVICE_BRIDGE_BACKGROUND_SYNC';\n\nexport interface BackgroundSyncConfig {\n relayUrl: string;\n deviceId: string;\n tenant: string;\n actorId?: string;\n apiKey: string;\n}\n\nconst CAPABILITY_FACTORIES: Record<string, () => Capability> = {\n calendar: caps.calendarCapability,\n contacts: caps.contactsCapability,\n location: caps.locationCapability,\n battery: caps.batteryCapability,\n connectivity: caps.connectivityCapability,\n notifications: caps.notificationsCapability,\n health: caps.healthCapability,\n};\n\n/**\n * Register the periodic background-fetch task.\n * Call once at app startup, outside any React component.\n */\nexport async function registerBackgroundSync(\n config: BackgroundSyncConfig\n): Promise<void> {\n TaskManager.defineTask(BACKGROUND_TASK_NAME, async () => {\n try {\n const hasNew = await syncDomains(config, ['location', 'battery', 'calendar']);\n return hasNew\n ? BackgroundFetch.BackgroundFetchResult.NewData\n : BackgroundFetch.BackgroundFetchResult.NoData;\n } catch (error) {\n console.error('[DeviceBridge] Background sync failed:', error);\n return BackgroundFetch.BackgroundFetchResult.Failed;\n }\n });\n\n await BackgroundFetch.registerTaskAsync(BACKGROUND_TASK_NAME, {\n minimumInterval: 300, // 5 minutes (iOS will clamp to ~15 min)\n stopOnTerminate: false,\n startOnBoot: true,\n });\n}\n\n/**\n * Unregister the background sync task.\n */\nexport async function unregisterBackgroundSync(): Promise<void> {\n const isRegistered = await TaskManager.isTaskRegisteredAsync(\n BACKGROUND_TASK_NAME\n );\n if (isRegistered) {\n await BackgroundFetch.unregisterTaskAsync(BACKGROUND_TASK_NAME);\n }\n}\n\n/**\n * Collect and push the listed domains once. Used by both the periodic\n * background-fetch task and the silent-push handler.\n *\n * Returns true if at least one domain pushed new data.\n */\nexport async function syncDomains(\n config: BackgroundSyncConfig,\n domains: string[]\n): Promise<boolean> {\n const relay = new RelayClient({\n relayUrl: config.relayUrl,\n deviceId: config.deviceId,\n tenant: config.tenant,\n actorId: config.actorId,\n apiKey: config.apiKey,\n timeout: 15_000, // longer than foreground — background tasks are budget-limited but not latency-sensitive\n });\n\n const results = await Promise.allSettled(\n domains.map(d => collectAndPush(relay, d))\n );\n\n return results.some(r => r.status === 'fulfilled' && r.value);\n}\n\n// ─── Internal ───\n\nasync function collectAndPush(\n relay: RelayClient,\n domain: string\n): Promise<boolean> {\n const factory = CAPABILITY_FACTORIES[domain];\n if (!factory) {\n console.warn(`[DeviceBridge] Unknown sync domain: ${domain}`);\n return false;\n }\n\n const cap = factory();\n\n if (cap.requiresPermission) {\n const granted = await cap.checkPermission();\n if (!granted) return false;\n }\n\n const data = await cap.collect();\n const result = await relay.pushDomain(domain, data);\n return result.ok;\n}\n","/**\n * Built-in Capabilities — wraps each collector as a registerable Capability.\n *\n * Each capability defines:\n * - id, name, description, reason (for UI)\n * - permission check/request\n * - collect function\n *\n * The host app can register these selectively, or add custom ones.\n */\n\nimport * as calendar from './collectors/calendar';\nimport * as contacts from './collectors/contacts';\nimport * as location from './collectors/location';\nimport * as battery from './collectors/battery';\nimport * as connectivity from './collectors/connectivity';\nimport * as notifications from './collectors/notifications';\nimport * as health from './collectors/health';\nimport * as mood from './collectors/mood';\nimport * as camera from './collectors/camera';\nimport type { Capability } from './types';\n\n// ─── Factory functions — call these to get a Capability instance ───\n\nexport function calendarCapability(): Capability {\n return {\n id: 'calendar',\n name: 'Calendar',\n description: 'Syncs your calendar events so your assistant knows your schedule.',\n reason: 'Needed for scheduling, meeting prep, commute alerts, and daily briefings.',\n icon: 'calendar',\n requiresPermission: true,\n permissionState: 'not_requested',\n requestPermission: calendar.requestPermission,\n checkPermission: calendar.hasPermission,\n collect: async () => {\n const [today, upcoming] = await Promise.all([\n calendar.collectToday(),\n calendar.collectUpcoming(7),\n ]);\n return { today, upcoming };\n },\n defaultInterval: 60_000, // 1 min\n };\n}\n\nexport function contactsCapability(): Capability {\n return {\n id: 'contacts',\n name: 'Contacts',\n description: 'Syncs your contacts so your assistant can find people and relationships.',\n reason: 'Needed for messaging, birthday reminders, and relationship insights.',\n icon: 'people',\n requiresPermission: true,\n permissionState: 'not_requested',\n requestPermission: contacts.requestPermission,\n checkPermission: contacts.hasPermission,\n collect: async () => {\n const list = await contacts.collectAll();\n // null = no changes since last successful sync → bridge skips push.\n // Critical for users with 6000+ contacts: full payload is ~1MB and\n // would otherwise re-upload every 5 min with no actual change.\n if (list === null) return null as any;\n return { contacts: list };\n },\n defaultInterval: 300_000, // 5 min\n };\n}\n\nexport function locationCapability(): Capability {\n return {\n id: 'location',\n name: 'Location',\n description: 'Syncs your location for weather, commute, and context-aware assistance.',\n reason: 'Needed for commute estimates, weather at your location, and situation detection.',\n icon: 'location',\n requiresPermission: true,\n permissionState: 'not_requested',\n requestPermission: location.requestPermission,\n checkPermission: location.hasPermission,\n collect: location.collectCurrent,\n defaultInterval: 30_000, // 30 sec\n };\n}\n\nexport function batteryCapability(): Capability {\n return {\n id: 'battery',\n name: 'Battery',\n description: 'Syncs battery level and charging status.',\n reason: 'Helps your assistant adjust behavior when battery is low.',\n icon: 'battery',\n requiresPermission: false,\n permissionState: 'not_needed',\n requestPermission: async () => true,\n checkPermission: async () => true,\n collect: battery.collectBattery,\n defaultInterval: 60_000, // 1 min\n subscribe: battery.subscribeToBattery,\n };\n}\n\nexport function connectivityCapability(): Capability {\n return {\n id: 'connectivity',\n name: 'Connectivity',\n description: 'Syncs WiFi/cellular status.',\n reason: 'Helps your assistant know if you have internet access.',\n icon: 'wifi',\n requiresPermission: false,\n permissionState: 'not_needed',\n requestPermission: async () => true,\n checkPermission: async () => true,\n collect: connectivity.collectConnectivity,\n defaultInterval: 30_000, // 30 sec\n };\n}\n\nexport function healthCapability(): Capability {\n return {\n id: 'health',\n name: 'Health',\n description: 'Reads daily steps, floors, calories, and distance from Apple Health.',\n reason: 'Powers the personal health coach and pairs activity data with nutrition tracking.',\n icon: 'heart',\n requiresPermission: true,\n permissionState: 'not_requested',\n requestPermission: health.requestPermission,\n checkPermission: health.hasPermission,\n collect: async () => {\n // Push today AND the last 30 days every sync. The connector slices\n // `days` to whatever range the caller asked for. ~30 sequential\n // HealthKit reads on iOS take <300ms total — cheap.\n const days = await health.collectHistory(30);\n const today = days[0] || (await health.collectToday());\n return { today, days };\n },\n defaultInterval: 300_000, // 5 min\n };\n}\n\nexport function moodCapability(): Capability {\n return {\n id: 'mood',\n name: 'Mood',\n description: 'Sends a daily behavioral-signal summary (activity, routine) to infer mood — not a medical diagnosis.',\n reason: 'Lets ada gently notice changes in energy, stress, and routine and check in.',\n icon: 'heart',\n // No OS permission of its own — it derives from collectors that gate\n // themselves (e.g. health). Treated as not_needed so the bridge syncs it\n // whenever the user has the Mood toggle on.\n requiresPermission: false,\n permissionState: 'not_needed',\n requestPermission: async () => true,\n checkPermission: async () => true,\n collect: async () => {\n // One compact daily summary (window/timezone/collected_at/today).\n // Pushed as the `mood` relay domain; backend runs /api/mood/analyze.\n return mood.collectToday();\n },\n defaultInterval: 3_600_000, // hourly — it's a daily summary, no need to spam\n };\n}\n\nexport function cameraCapability(): Capability {\n return {\n id: 'camera',\n name: 'Camera',\n description:\n \"Lets ada see through your camera to guide you — reading text and describing what's in \" +\n 'front of you. Frames are analysed on the phone; nothing is stored or uploaded.',\n reason: 'Powers visual guidance (Ada Guide): a live view with on-device text and scene understanding.',\n icon: 'camera',\n requiresPermission: true,\n permissionState: 'not_requested',\n requestPermission: camera.requestPermission,\n checkPermission: camera.hasPermission,\n // LIVE, not synced: the camera is used in the moment (deviceState.visual / a\n // widget's preview) and never collected to the relay. So collect never\n // pushes — this capability's value is the USER GRANT + OS permission, exposed\n // to the backend like every other, not a data payload. `null` = nothing to\n // push, which the bridge skips (same contract contacts uses when unchanged).\n collect: async () => null as any,\n defaultInterval: 3_600_000, // never really collects; keep the poll rare\n };\n}\n\nexport function notificationsCapability(): Capability {\n return {\n id: 'notifications',\n name: 'Notifications',\n description: 'Syncs your notifications for triage and smart grouping.',\n reason: 'Needed for notification triage, priority filtering, and Do Not Disturb.',\n icon: 'notifications',\n requiresPermission: true,\n permissionState: 'not_requested',\n requestPermission: notifications.requestPermission,\n checkPermission: notifications.hasPermission,\n collect: notifications.collectNotifications,\n defaultInterval: 15_000, // 15 sec\n };\n}\n\n// ─── Convenience: get all built-in capabilities ───\n\nexport function allBuiltIn(): Capability[] {\n return [\n calendarCapability(),\n contactsCapability(),\n locationCapability(),\n batteryCapability(),\n connectivityCapability(),\n notificationsCapability(),\n healthCapability(),\n moodCapability(),\n cameraCapability(),\n ];\n}\n","/**\n * Calendar Collector — reads device calendar via expo-calendar.\n * Output format matches device-mock-mcp's calendar.today / calendar.upcoming schemas.\n */\n\nimport * as ExpoCalendar from 'expo-calendar';\n// Type-only alias: the value namespace above cannot be used in type\n// position, so `ExpoCalendar.Calendar`-style annotations need this.\n// expo-calendar's types are not visible through the package entry under\n// bundler resolution, so derive them from the APIs that return them —\n// the shape then always matches the installed version.\ntype Calendar = Awaited<ReturnType<typeof ExpoCalendar.getCalendarsAsync>>[number];\ntype Event = Awaited<ReturnType<typeof ExpoCalendar.getEventsAsync>>[number];\nimport type { CalendarEvent, CalendarTodayData, CalendarUpcomingData } from '../types';\n\n/**\n * Request calendar permissions.\n * @returns true if granted\n */\nexport async function requestPermission(): Promise<boolean> {\n const { status } = await ExpoCalendar.requestCalendarPermissionsAsync();\n return status === 'granted';\n}\n\n/**\n * Check if calendar permission is granted.\n */\nexport async function hasPermission(): Promise<boolean> {\n const { status } = await ExpoCalendar.getCalendarPermissionsAsync();\n return status === 'granted';\n}\n\n/**\n * Get all calendar events for today.\n * Matches the device.calendar.today tool output schema.\n */\nexport async function collectToday(): Promise<CalendarTodayData> {\n const now = new Date();\n const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);\n\n const calendars = await ExpoCalendar.getCalendarsAsync(ExpoCalendar.EntityTypes.EVENT);\n const userCalendarIds = getUserCalendarIds(calendars);\n\n const events = await ExpoCalendar.getEventsAsync(userCalendarIds, startOfDay, endOfDay);\n const filtered = filterHolidays(events);\n\n const mapped: CalendarEvent[] = filtered.map(e => ({\n id: e.id,\n title: e.title || 'Untitled',\n date: startOfDay.toISOString().slice(0, 10),\n start: formatTime(new Date(e.startDate)),\n end: formatTime(new Date(e.endDate)),\n location: e.location || '',\n attendees: ((e as any).attendees || []).map((a: { name?: string; email?: string }) => a.name || a.email || 'Unknown'),\n notes: e.notes || '',\n }));\n\n // Sort by start time\n mapped.sort((a, b) => a.start.localeCompare(b.start));\n\n return {\n ok: true,\n date: startOfDay.toISOString().slice(0, 10),\n day_of_week: now.toLocaleDateString('en-US', { weekday: 'long' }),\n event_count: mapped.length,\n events: mapped,\n };\n}\n\n/**\n * Get upcoming calendar events for the next N days (tomorrow and beyond).\n * Matches the device.calendar.upcoming tool output schema.\n */\nexport async function collectUpcoming(daysAhead: number = 3): Promise<CalendarUpcomingData> {\n const now = new Date();\n const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);\n const endDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + daysAhead + 1);\n\n const calendars = await ExpoCalendar.getCalendarsAsync(ExpoCalendar.EntityTypes.EVENT);\n const userCalendarIds = getUserCalendarIds(calendars);\n\n const events = await ExpoCalendar.getEventsAsync(userCalendarIds, tomorrow, endDate);\n const filtered = filterHolidays(events);\n\n const mapped: CalendarEvent[] = filtered.map(e => {\n const startDt = new Date(e.startDate);\n return {\n id: e.id,\n title: e.title || 'Untitled',\n date: startDt.toISOString().slice(0, 10),\n start: formatTime(startDt),\n end: formatTime(new Date(e.endDate)),\n location: e.location || '',\n attendees: ((e as any).attendees || []).map((a: { name?: string; email?: string }) => a.name || a.email || 'Unknown'),\n notes: e.notes || '',\n };\n });\n\n // Sort by date then start time\n mapped.sort((a, b) =>\n a.date === b.date ? a.start.localeCompare(b.start) : a.date.localeCompare(b.date)\n );\n\n return {\n ok: true,\n from: tomorrow.toISOString().slice(0, 10),\n days_ahead: daysAhead,\n event_count: mapped.length,\n events: mapped,\n };\n}\n\n// ─── Helpers ───\n\n/**\n * Filter out holiday/subscription calendars — only keep user-owned calendars\n * (local, CalDAV, Exchange, Google). Subscription calendars are read-only\n * holiday feeds that clutter the real schedule.\n */\nfunction getUserCalendarIds(calendars: Calendar[]): string[] {\n const SKIP_SOURCES = ['birthdays', 'other']; // 'other' = subscribed holiday feeds on iOS\n const SKIP_TYPES = ['birthdays', 'other'];\n\n return calendars\n .filter(c => {\n const sourceType = ((c.source as any)?.type || '').toLowerCase();\n const calType = (c.type || '').toLowerCase();\n // Skip subscription/holiday calendars\n if (SKIP_SOURCES.includes(sourceType)) return false;\n if (SKIP_TYPES.includes(calType)) return false;\n return true;\n })\n .map(c => c.id);\n}\n\n/**\n * Extra safety: filter out all-day events with holiday-like patterns\n * in case they slip through from non-subscription calendars.\n */\nfunction filterHolidays(events: Event[]): Event[] {\n const HOLIDAY_PATTERNS = [\n /holiday/i, /observance/i, /eve\\b.*\\btentative\\b/i, /\\btentative\\b/i,\n ];\n\n return events.filter(e => {\n // All-day events spanning exactly one day with \"tentative\" or \"observance\" in notes → holiday\n const isAllDay = (e as any).allDay === true;\n const notes = (e.notes || '').toLowerCase();\n if (isAllDay && HOLIDAY_PATTERNS.some(p => p.test(notes))) return false;\n if (isAllDay && HOLIDAY_PATTERNS.some(p => p.test(e.title || ''))) return false;\n return true;\n });\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n });\n}\n","/**\n * Contacts Collector — reads device contacts via expo-contacts.\n *\n * Two important properties for users with large address books (6000+ contacts):\n *\n * 1. Hash-based change detection. We persist a map of `contactId → contentHash`\n * across launches and on each collect cycle compare the freshly-read hashes\n * to the stored ones. If nothing changed, `collectAll()` returns null and\n * `syncCapability` skips the relay push entirely — no per-tick 1MB upload.\n *\n * 2. Reduced field set. iOS 16+ restricts which contact fields are accessible\n * without explicit user grants. Requesting `Note`/`Relationships` causes\n * `getContactsAsync` to throw \"Failed to serialize contact data:\n * Unauthorized Keys\", failing the entire collect. We stick to the four\n * fields actually used downstream (Name, PhoneNumbers, Emails, Birthday).\n *\n * Output format on push (when there ARE changes) is unchanged from before —\n * a full `Contact[]` — so the relay/MCP layer doesn't have to know about the\n * incremental scheme. The relay simply gets pushed less often.\n */\nimport * as ExpoContacts from 'expo-contacts';\n// Type-only alias: the value namespace above cannot be used in type\n// position, so `ExpoContacts.Calendar`-style annotations need this.\n// expo-contacts exports no `Date` type; take the shape from Contact itself.\ntype ExpoContact = Awaited<ReturnType<typeof ExpoContacts.getContactsAsync>>['data'][number];\ntype ContactDate = NonNullable<ExpoContact['birthday']>;\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { Contact } from '../types';\n\nconst HASH_STORE_KEY = 'device-bridge.contacts.hashes.v1';\n\n/** Request contacts permissions. */\nexport async function requestPermission(): Promise<boolean> {\n const { status } = await ExpoContacts.requestPermissionsAsync();\n return status === 'granted';\n}\n\n/** Check if contacts permission is granted. */\nexport async function hasPermission(): Promise<boolean> {\n const { status } = await ExpoContacts.getPermissionsAsync();\n return status === 'granted';\n}\n\n/**\n * Diagnostic counters from the most recent collect — exposed so the Profile\n * debug view can surface \"what changed since last sync\".\n */\nexport interface ContactsCollectStats {\n total: number;\n added: number;\n updated: number;\n removed: number;\n unchanged: number;\n full_sync: boolean;\n}\nexport let lastContactsStats: ContactsCollectStats | null = null;\n\n/**\n * Read all contacts and detect changes vs the previously-stored hash map.\n *\n * Returns:\n * - the full Contact[] when anything changed (or this is the first sync),\n * - null when every contact's hash matches what we pushed last time, signalling\n * the bridge that no relay push is needed this cycle.\n */\nexport async function collectAll(): Promise<Contact[] | null> {\n // Only the four fields we actually surface downstream. Adding more reawakens\n // the iOS \"Unauthorized Keys\" failure unless the user has granted them via\n // a separate flow.\n const result = await ExpoContacts.getContactsAsync({\n fields: [\n ExpoContacts.Fields.Name,\n ExpoContacts.Fields.PhoneNumbers,\n ExpoContacts.Fields.Emails,\n ExpoContacts.Fields.Birthday,\n ],\n sort: ExpoContacts.SortTypes.LastName,\n });\n\n const raw = Array.isArray(result?.data) ? result.data : [];\n\n // Map raw contacts to our schema, computing a stable per-contact hash that's\n // independent of array ordering and resilient to spurious whitespace/case\n // changes in fields we don't actually compare on.\n const contacts: Contact[] = [];\n const currentHashes: Record<string, string> = {};\n for (let i = 0; i < raw.length; i++) {\n const c = raw[i];\n const id = c.id || `c${i}`;\n const mapped: Contact = {\n id,\n name: c.name || `${c.firstName || ''} ${c.lastName || ''}`.trim() || 'Unknown',\n relationship: '', // dropped — was the iOS Unauthorized-Keys trigger\n phone: c.phoneNumbers?.[0]?.number || null,\n email: c.emails?.[0]?.email || null,\n birthday: formatBirthday(c.birthday),\n notes: '', // dropped — Notes is restricted on iOS 16+\n };\n contacts.push(mapped);\n currentHashes[id] = hashContact(mapped);\n }\n\n // Compare against last-pushed hashes.\n let stored: Record<string, string> = {};\n try {\n const blob = await AsyncStorage.getItem(HASH_STORE_KEY);\n if (blob) stored = JSON.parse(blob) as Record<string, string>;\n } catch {\n /* corrupted store — treat as empty, will full-sync */\n }\n const fullSync = Object.keys(stored).length === 0;\n\n let added = 0;\n let updated = 0;\n let unchanged = 0;\n for (const id of Object.keys(currentHashes)) {\n const prev = stored[id];\n if (prev === undefined) added++;\n else if (prev !== currentHashes[id]) updated++;\n else unchanged++;\n }\n let removed = 0;\n for (const id of Object.keys(stored)) {\n if (currentHashes[id] === undefined) removed++;\n }\n\n lastContactsStats = {\n total: contacts.length,\n added,\n updated,\n removed,\n unchanged,\n full_sync: fullSync,\n };\n\n // No changes → tell the bridge to skip the relay push this cycle. Saves\n // a 6000-row payload going up the wire every 5 minutes when nothing moved.\n if (!fullSync && added === 0 && updated === 0 && removed === 0) {\n return null;\n }\n\n // Persist the new hash map AFTER deciding to push. If the push fails we'll\n // re-detect the same changes on the next cycle (push is idempotent enough).\n try {\n await AsyncStorage.setItem(HASH_STORE_KEY, JSON.stringify(currentHashes));\n } catch {\n /* a failed persist just means we'll redo the diff next time — non-fatal */\n }\n\n return contacts;\n}\n\n// ─── Helpers ────────────────────────────────────────────────\n\n/**\n * Stable string hash (FNV-1a 32-bit, hex). Pure JS, no native dep, fine for\n * change detection. NOT cryptographic — a determined collision attacker could\n * mask a contact change, but that's not in our threat model.\n */\nfunction fnv1a(str: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(16).padStart(8, '0');\n}\n\nfunction hashContact(c: Contact): string {\n // Concatenate the canonical-cased, trimmed values of every field we surface.\n // Order matters and is fixed; null becomes a single sentinel character so\n // empty-vs-missing differs from \"actual whitespace\" in the hash space.\n const parts = [\n c.id,\n (c.name || '').trim(),\n c.phone || '\\0',\n (c.email || '\\0').toLowerCase(),\n c.birthday || '\\0',\n ];\n return fnv1a(parts.join('\u001f')); // unit separator — won't appear in any field\n}\n\nfunction formatBirthday(birthday: ContactDate | undefined): string | null {\n if (!birthday) return null;\n const { year, month, day } = birthday;\n if (month === undefined || day === undefined) return null;\n const m = String(month + 1).padStart(2, '0');\n const d = String(day).padStart(2, '0');\n\n // No year is the COMMON case on iOS — the contact editor lets you omit it,\n // and most people do. Substituting 1900 made that indistinguishable from a\n // real birth year, and the server dutifully computed an age from it: \"Dana\n // is turning 126 next Tuesday.\"\n //\n // `--MM-DD` is the ISO 8601 form for a recurring month-day with no year, so\n // the absence is stated rather than filled in.\n if (!year) return `--${m}-${d}`;\n\n return `${year}-${m}-${d}`;\n}\n","/**\n * Location Collector — reads device location via expo-location.\n * Output format matches device-mock-mcp's location.current schema.\n */\n\nimport * as ExpoLocation from 'expo-location';\nimport type { MapsAddress } from '@ateam-ai-mobile/plugin-runtime';\n// The SDK's one reverse geocode — the same call native.maps.reverseGeocode makes.\nimport { reverseGeocode } from '../../maps';\nimport type { LocationData } from '../types';\n\n/**\n * Request location permissions (foreground + background).\n */\nexport async function requestPermission(): Promise<boolean> {\n const { status: fg } = await ExpoLocation.requestForegroundPermissionsAsync();\n if (fg !== 'granted') return false;\n\n // Background is optional — skip in Expo Go (crashes without native module)\n try {\n await ExpoLocation.requestBackgroundPermissionsAsync();\n } catch {\n // Expected in Expo Go — foreground is enough for basic functionality\n }\n return fg === 'granted';\n}\n\n/**\n * Check if location permission is granted.\n */\nexport async function hasPermission(): Promise<boolean> {\n const { status } = await ExpoLocation.getForegroundPermissionsAsync();\n return status === 'granted';\n}\n\n/**\n * Collect current location.\n * Matches the device.location.current tool output schema.\n */\nexport async function collectCurrent(): Promise<LocationData> {\n const location = await ExpoLocation.getCurrentPositionAsync({\n accuracy: ExpoLocation.Accuracy.Balanced,\n });\n\n // Reverse geocode to get address\n let place = 'Current Location';\n let address = `${location.coords.latitude.toFixed(4)}, ${location.coords.longitude.toFixed(4)}`;\n let locationType = 'unknown';\n\n try {\n const geocoded = await reverseGeocode({\n latitude: location.coords.latitude,\n longitude: location.coords.longitude,\n });\n\n if (geocoded) {\n place = geocoded.name || geocoded.street || place;\n address = [\n geocoded.streetNumber,\n geocoded.street,\n geocoded.city,\n geocoded.region,\n ]\n .filter(Boolean)\n .join(', ') || address;\n\n // Attempt to classify location type\n locationType = classifyLocation(geocoded);\n }\n } catch {\n // Reverse geocoding can fail — use coordinates only\n }\n\n return {\n ok: true,\n timestamp: new Date().toISOString(),\n location: {\n place,\n address,\n lat: location.coords.latitude,\n lng: location.coords.longitude,\n type: locationType,\n },\n };\n}\n\n// ─── Plugin-facing position API ───\n// `collectCurrent` above answers the device-context question (\"where am I, in\n// words\") and reverse-geocodes. A plugin tool usually wants the raw fix and\n// nothing else, so these stay separate: no geocoding round trip, no address.\n\nexport interface Position {\n latitude: number;\n longitude: number;\n altitude?: number;\n accuracy?: number;\n timestamp: number;\n /**\n * Metres per second. `undefined` = the OS gave no estimate.\n *\n * iOS reports -1 rather than omitting it, and -1 m/s passed through as a\n * speed would read as \"moving backwards\"; it is normalised away here so no\n * caller has to know the platform's sentinel.\n */\n speed?: number;\n /**\n * COURSE OVER GROUND in degrees — the direction the user is TRAVELLING.\n *\n * Deliberately not called `heading`: this and the compass answer different\n * questions and disagree constantly. Walking backwards down a corridor gives\n * a course of south and a compass heading of north; a phone held sideways\n * while walking forward gives the same course and a heading 90° off.\n * \"Turn right\" needs the compass. \"You are heading north on Main Street\"\n * needs this. The old code assigned one to the other, which is only ever\n * right by accident.\n *\n * `undefined` when not moving or not estimated — iOS reports -1 for both.\n */\n course?: number;\n}\n\n/** Ensure foreground permission, asking once if we do not have it yet. */\nasync function ensurePermission(): Promise<void> {\n if (await hasPermission()) return;\n const granted = await requestPermission();\n if (!granted) {\n // Callers surface this verbatim to the user (a voice assistant reads it\n // out), so it has to say what the user can do about it.\n throw new Error(\n 'Location permission was not granted. Enable it for this app in iOS Settings > Privacy > Location Services.',\n );\n }\n}\n\n/** One position fix. `accuracy: \"high\"` costs battery and time — default low. */\nexport async function getPosition(\n opts: { accuracy?: 'low' | 'high' } = {},\n): Promise<Position> {\n await ensurePermission();\n const fix = await ExpoLocation.getCurrentPositionAsync({\n accuracy:\n opts.accuracy === 'high'\n ? ExpoLocation.Accuracy.High\n : ExpoLocation.Accuracy.Balanced,\n });\n return toPosition(fix);\n}\n\n/**\n * One place that turns an expo fix into a `Position`.\n *\n * It exists because the sentinel handling has to be identical on the one-shot\n * and the streamed path. It was not: `watchPosition` passed lat/lon only, so\n * the semantic Spatial domain could never report speed or course no matter\n * what the OS supplied, and reported `null` as if the phone had no GPS.\n */\nfunction toPosition(fix: any): Position {\n return {\n latitude: fix.coords.latitude,\n longitude: fix.coords.longitude,\n altitude: fix.coords.altitude ?? undefined,\n accuracy: nonNegative(fix.coords.accuracy),\n timestamp: fix.timestamp ?? Date.now(),\n speed: nonNegative(fix.coords.speed),\n course: nonNegative(fix.coords.heading),\n };\n}\n\n/**\n * iOS uses -1 for \"no estimate\" on accuracy, speed and course alike. Dropping\n * the field says \"not measured\"; passing -1 through says \"measured, and the\n * answer is minus one\", which a model will happily repeat.\n */\nfunction nonNegative(v: unknown): number | undefined {\n return typeof v === 'number' && v >= 0 ? v : undefined;\n}\n\nconst WATCH_TIER = {\n navigation: ExpoLocation.Accuracy.BestForNavigation,\n high: ExpoLocation.Accuracy.High,\n balanced: ExpoLocation.Accuracy.Balanced,\n low: ExpoLocation.Accuracy.Low,\n} as const;\n\n/**\n * Stream position updates. Returns an unsubscribe function — call it, or the\n * GPS keeps running and drains the battery.\n *\n * `accuracy` picks the GPS tier. The default stays Balanced (≈100 m on iOS),\n * which is right for \"which city block\" and wrong for \"turn at the next corner\":\n * a navigation session asks for BestForNavigation and pays the battery for it.\n */\nexport function watchPosition(\n cb: (pos: Position) => void,\n opts?: { accuracy?: keyof typeof WATCH_TIER },\n): () => void {\n let subscription: { remove(): void } | null = null;\n let cancelled = false;\n\n (async () => {\n try {\n await ensurePermission();\n if (cancelled) return;\n subscription = await ExpoLocation.watchPositionAsync(\n { accuracy: WATCH_TIER[opts?.accuracy ?? 'balanced'] },\n fix => cb(toPosition(fix)),\n );\n // Unsubscribed while we were still asking for permission.\n if (cancelled) { subscription?.remove(); subscription = null; }\n } catch (err: any) {\n console.warn('[location] watchPosition failed:', err?.message || String(err));\n }\n })();\n\n return () => {\n cancelled = true;\n subscription?.remove();\n subscription = null;\n };\n}\n\n// ─── Helpers ───\n\nfunction classifyLocation(geocoded: MapsAddress): string {\n // Simple heuristic — can be enhanced with user-labeled places from memory\n const name = (geocoded.name || '').toLowerCase();\n const street = (geocoded.street || '').toLowerCase();\n\n if (name.includes('home') || name.includes('residence')) return 'home';\n if (name.includes('office') || name.includes('workplace') || name.includes('tech park')) return 'work';\n if (name.includes('school') || name.includes('elementary') || name.includes('university')) return 'school';\n if (name.includes('gym') || name.includes('fitness')) return 'fitness';\n if (name.includes('hospital') || name.includes('clinic') || name.includes('dental')) return 'medical';\n\n return 'other';\n}\n","/**\n * Battery Collector — reads battery status via expo-battery.\n * Output format matches device-mock-mcp's device.battery schema.\n */\n\nimport * as ExpoBattery from 'expo-battery';\n// Type-only alias: the value namespace above cannot be used in type\n// position, so `ExpoBattery.Calendar`-style annotations need this.\n// BatteryState is not re-exported from the package entry — derive it from\n// the API that returns it so we never hand-maintain a copy of the enum.\ntype BatteryState = Awaited<ReturnType<typeof ExpoBattery.getBatteryStateAsync>>;\nimport type { BatteryData } from '../types';\n\n// No permission needed for battery\n\n/**\n * Collect current battery status.\n * Matches the device.battery tool output schema.\n */\nexport async function collectBattery(): Promise<BatteryData> {\n const level = await ExpoBattery.getBatteryLevelAsync();\n const state = await ExpoBattery.getBatteryStateAsync();\n const lowPower = await ExpoBattery.isLowPowerModeEnabledAsync();\n\n return {\n ok: true,\n timestamp: new Date().toISOString(),\n battery: {\n level: Math.round(level * 100), // expo-battery returns 0-1\n state: mapBatteryState(state),\n low_power_mode: lowPower,\n },\n };\n}\n\n/**\n * Subscribe to OS-level battery changes (level + charging state).\n *\n * iOS suspends JS timers when the app is backgrounded, so a pure setInterval\n * sync drifts: the relay ends up stuck on whatever level we last sampled\n * before the app went to sleep (commonly 100% from an overnight charge).\n * expo-battery's listeners fire from the native side and survive that gap,\n * so we get a fresh push as soon as the OS notices a change.\n *\n * Level changes are throttled to >=5pp deltas — without that, iOS emits a\n * level event for every 1% tick which would spam the relay.\n */\nexport function subscribeToBattery(callback: () => void): () => void {\n let lastLevel: number | null = null;\n\n const levelSub = ExpoBattery.addBatteryLevelListener(({ batteryLevel }) => {\n const pct = Math.round(batteryLevel * 100);\n if (lastLevel === null || Math.abs(pct - lastLevel) >= 5) {\n lastLevel = pct;\n callback();\n }\n });\n\n const stateSub = ExpoBattery.addBatteryStateListener(() => {\n callback();\n });\n\n return () => {\n levelSub?.remove?.();\n stateSub?.remove?.();\n };\n}\n\n// ─── Helpers ───\n\nfunction mapBatteryState(\n state: BatteryState\n): 'charging' | 'unplugged' | 'full' {\n switch (state) {\n case ExpoBattery.BatteryState.CHARGING:\n return 'charging';\n case ExpoBattery.BatteryState.FULL:\n return 'full';\n case ExpoBattery.BatteryState.UNPLUGGED:\n default:\n return 'unplugged';\n }\n}\n","/**\n * Connectivity Collector — reads network state via expo-network.\n * Output format matches device-mock-mcp's device.connectivity schema.\n */\n\nimport * as ExpoNetwork from 'expo-network';\nimport type { ConnectivityData } from '../types';\n\n// No special permission needed for network state\n\n/**\n * Collect current connectivity status.\n * Matches the device.connectivity tool output schema.\n */\nexport async function collectConnectivity(): Promise<ConnectivityData> {\n const networkState = await ExpoNetwork.getNetworkStateAsync();\n\n let type: 'wifi' | 'cellular' | 'none' = 'none';\n let wifiName: string | undefined;\n let cellularType: string | undefined;\n\n if (networkState.type === ExpoNetwork.NetworkStateType.WIFI) {\n type = 'wifi';\n // Wi-Fi SSID requires additional permissions on newer OS versions\n // and may not always be available\n try {\n wifiName = await getWifiName();\n } catch {\n wifiName = undefined;\n }\n } else if (networkState.type === ExpoNetwork.NetworkStateType.CELLULAR) {\n type = 'cellular';\n cellularType = getCellularGeneration();\n }\n\n return {\n ok: true,\n timestamp: new Date().toISOString(),\n connectivity: {\n type,\n is_connected: networkState.isConnected ?? false,\n ...(wifiName && { wifi_name: wifiName }),\n ...(cellularType && { cellular_type: cellularType }),\n },\n };\n}\n\n// ─── Helpers ───\n\nasync function getWifiName(): Promise<string | undefined> {\n // expo-network doesn't expose SSID directly on all platforms\n // This would need a native module or expo-wifi in production\n return undefined;\n}\n\nfunction getCellularGeneration(): string {\n // expo-network doesn't expose cellular generation\n // In production, use a native module for this\n return 'LTE';\n}\n","/**\n * Notifications Collector — reads notification state via expo-notifications.\n * Output format matches device-mock-mcp's notifications.list schema.\n *\n * Note: On iOS/Android, expo-notifications only gives access to notifications\n * that YOUR app has posted or received. System-wide notification access\n * requires platform-specific APIs (NotificationListenerService on Android,\n * UNUserNotificationCenter on iOS with app extensions).\n *\n * For the PA, the relay approach means we capture notifications delivered\n * TO our app (from the PA system), not FROM other apps.\n * Other-app notifications would need a native module or accessibility service.\n */\n\nimport * as ExpoNotifications from 'expo-notifications';\nimport type { Notification, NotificationsData } from '../types';\n\n/**\n * Request notification permissions.\n */\nexport async function requestPermission(): Promise<boolean> {\n const { status } = await ExpoNotifications.requestPermissionsAsync();\n return status === 'granted';\n}\n\n/**\n * Check if notification permission is granted.\n */\nexport async function hasPermission(): Promise<boolean> {\n const { status } = await ExpoNotifications.getPermissionsAsync();\n return status === 'granted';\n}\n\n/**\n * Collect delivered notifications from the notification tray.\n * Only captures notifications delivered to THIS app.\n * Matches the device.notifications.list tool output schema.\n */\nexport async function collectNotifications(): Promise<NotificationsData> {\n const presented = await ExpoNotifications.getPresentedNotificationsAsync();\n\n const notifications: Notification[] = presented.map((n, i) => ({\n id: n.request.identifier || `n${i}`,\n app: (n.request.content.data?.app as string) || 'A-Team',\n title: n.request.content.title || '',\n body: n.request.content.body || '',\n timestamp: n.date ? new Date(n.date).toISOString() : new Date().toISOString(),\n sender: (n.request.content.data?.sender as string) || null,\n priority: mapPriority(n.request.content.data?.priority as string),\n read: false, // Presented notifications are unread by definition\n }));\n\n const unread = notifications.filter(n => !n.read).length;\n\n return {\n ok: true,\n timestamp: new Date().toISOString(),\n total: notifications.length,\n unread,\n notifications,\n };\n}\n\n// ─── Helpers ───\n\nfunction mapPriority(p: string | undefined): 'high' | 'medium' | 'low' {\n if (p === 'high' || p === 'max') return 'high';\n if (p === 'low' || p === 'min') return 'low';\n return 'medium';\n}\n","/**\n * Mood Signals Collector — assembles the daily behavioral-signal summary the\n * Mood Intelligence Kit's backend expects (MoodSignalToday), and emits it as\n * a compact daily object. It does NOT call the kit's HTTP client; transport is\n * the existing device-bridge relay path (pushed as the `mood` domain), same as\n * health/location/etc. The backend reads the relay domain and runs analysis.\n *\n * Philosophy (from the kit's DESIGN): send a concise daily SUMMARY, never raw\n * event logs. We fill the fields we can source today from existing collectors\n * and leave the rest null — the backend's baseline/fallback tolerates nulls.\n *\n * EXACT per-field contract + where each value comes from on THIS platform\n * (iOS). \"blocked\" = Apple does not expose it to third-party apps, so it is\n * permanently null on iOS; \"addable\" = we have a source but haven't wired it.\n *\n * steps ← health (HealthKit) ✅ sourced now\n * distance_km ← health (HealthKit) ✅ sourced now\n * sleep_min ← health (HealthKit sleep) ✅ sourced now\n * app_sessions ← our own app analytics ✅ sourced now\n * app_active_min ← our own app analytics ✅ sourced now\n * resting_hr (extra) ← health (HealthKit) ✅ sourced now\n * active_energy_kcal(x) ← health (HealthKit) ✅ sourced now\n * exercise_min (extra) ← health (HealthKit) ✅ sourced now\n * places_count ← location collector (distinct) ⏳ addable (not wired)\n * checkin_mood ← user self-report UI ⏳ addable (no UI yet)\n * screen_min ← iOS Screen Time ❌ blocked by iOS\n * night_screen_min ← iOS Screen Time ❌ blocked by iOS\n * social_app_min ← other apps' usage ❌ blocked by iOS\n * video_app_min ← other apps' usage ❌ blocked by iOS\n * productivity_app_min ← other apps' usage ❌ blocked by iOS\n * messages_sent ← Messages content/metadata ❌ blocked by iOS\n * calls_count ← call log ❌ blocked by iOS\n *\n * (extra) = not in the kit's MoodSignalToday but sent alongside; the backend\n * tolerates extra fields and these are strong mood proxies.\n *\n * The kit's spec listed many sources because several exist on Android\n * (UsageStatsManager etc.); on iOS most are sandbox-blocked. We send what we\n * truly have and leave the rest null — the backend baseline/fallback tolerates\n * nulls and weights only present fields.\n */\nimport * as health from './health';\nimport * as appEngagement from './appEngagement';\n\n// Mirrors the kit's MoodSignalToday (packages/mood-client-rn/src/types.ts).\nexport interface MoodSignalToday {\n steps: number | null;\n sleep_min: number | null;\n screen_min: number | null;\n night_screen_min: number | null;\n places_count: number | null;\n distance_km: number | null;\n social_app_min: number | null;\n video_app_min: number | null;\n productivity_app_min: number | null;\n messages_sent: number | null;\n calls_count: number | null;\n app_sessions: number | null;\n app_active_min: number | null;\n checkin_mood?: 'great' | 'ok' | 'low' | 'bad' | null;\n}\n\nfunction emptySignals(): MoodSignalToday {\n return {\n steps: null,\n sleep_min: null,\n screen_min: null,\n night_screen_min: null,\n places_count: null,\n distance_km: null,\n social_app_min: null,\n video_app_min: null,\n productivity_app_min: null,\n messages_sent: null,\n calls_count: null,\n app_sessions: null,\n app_active_min: null,\n checkin_mood: null,\n };\n}\n\n/**\n * Build today's mood signal summary from available collectors. Returns the\n * full MoodSignalToday plus collection metadata the backend wants\n * (window, timezone, collected_at) so it can run analyze without guessing.\n */\nexport async function collectToday(): Promise<{\n window: 'today';\n timezone: string;\n collected_at: string;\n today: MoodSignalToday;\n}> {\n const today = emptySignals();\n\n // Health-sourced fields (best-effort; collector returns zeros if HK denied).\n try {\n const h = await health.collectToday();\n if (h) {\n today.steps = typeof h.steps === 'number' ? h.steps : null;\n today.distance_km = typeof h.distance_km === 'number' ? h.distance_km : null;\n }\n } catch {\n // leave nulls — backend baseline handles missing fields\n }\n\n // HealthKit mood extras: sleep, resting HR, active energy, exercise minutes.\n try {\n const x = await health.collectMoodExtras();\n today.sleep_min = x.sleep_min;\n // resting_hr / active_energy_kcal / exercise_min aren't in the kit's\n // MoodSignalToday contract, but the backend tolerates extra fields and\n // they're strong mood proxies — include them alongside.\n (today as any).resting_hr = x.resting_hr;\n (today as any).active_energy_kcal = x.active_energy_kcal;\n (today as any).exercise_min = x.exercise_min;\n } catch {\n // leave nulls\n }\n\n // Our own app-engagement analytics (no OS permission; the only on-iOS source\n // for app_sessions / app_active_min).\n try {\n const e = await appEngagement.getToday();\n today.app_sessions = e.app_sessions;\n today.app_active_min = e.app_active_min;\n } catch {\n // leave nulls\n }\n\n let timezone = 'UTC';\n try {\n timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';\n } catch { /* RN without full ICU — fall back to UTC */ }\n\n return {\n window: 'today',\n timezone,\n collected_at: new Date().toISOString(),\n today,\n };\n}\n","/**\n * Camera capability — permission only, NO collector.\n *\n * Unlike calendar/contacts/health, the camera is a LIVE capability: frames are\n * used in the moment (deviceState.visual, a widget's camera preview) and are\n * analysed on-device — never collected to the relay. So this module carries only\n * the OS-permission check/request that the capability toggle drives; there is no\n * `collect`. The user's grant (this toggle) is what gates access, on the same\n * path as location/contacts.\n */\nimport { Camera } from 'expo-camera';\n\nexport async function hasPermission(): Promise<boolean> {\n try {\n return (await Camera.getCameraPermissionsAsync()).granted;\n } catch {\n return false;\n }\n}\n\nexport async function requestPermission(): Promise<boolean> {\n try {\n return (await Camera.requestCameraPermissionsAsync()).granted;\n } catch {\n return false;\n }\n}\n","/**\n * Device-bridge: Biometrics (Face ID / Touch ID / fingerprint)\n *\n * Used by solutions that need a biometric gate before sensitive actions\n * (e.g. \"unlock payment\", \"confirm financial transfer\", \"reveal stored\n * credentials\"). Also useful for protecting session cookies at rest.\n *\n * Wraps expo-local-authentication.\n */\n\nimport * as LocalAuthentication from 'expo-local-authentication';\n\nexport type BiometricType = 'fingerprint' | 'face' | 'iris' | 'none';\n\nexport interface BiometricCapabilities {\n hasHardware: boolean;\n isEnrolled: boolean;\n /**\n * Never contains 'none' — the mapper filters it out, since 'none' means\n * \"the OS reported an auth type we do not recognise\" and is not a\n * biometric a caller can offer. Typed as the guarantee the code makes.\n */\n supportedTypes: Exclude<BiometricType, 'none'>[];\n}\n\n/** What can the device do right now? */\nexport async function capabilities(): Promise<BiometricCapabilities> {\n const [hasHardware, isEnrolled, types] = await Promise.all([\n LocalAuthentication.hasHardwareAsync(),\n LocalAuthentication.isEnrolledAsync(),\n LocalAuthentication.supportedAuthenticationTypesAsync(),\n ]);\n const supportedTypes = types.map((t) => {\n if (t === LocalAuthentication.AuthenticationType.FINGERPRINT) return 'fingerprint';\n if (t === LocalAuthentication.AuthenticationType.FACIAL_RECOGNITION) return 'face';\n if (t === LocalAuthentication.AuthenticationType.IRIS) return 'iris';\n return 'none';\n }).filter((t) => t !== 'none') as Exclude<BiometricType, 'none'>[];\n return { hasHardware, isEnrolled, supportedTypes };\n}\n\nexport interface AuthenticateOptions {\n /** User-visible reason shown in the system dialog. */\n reason: string;\n /** Label on the \"cancel\" button (iOS only; Android has system default). */\n cancelLabel?: string;\n /** If biometrics aren't enrolled, fall back to device passcode. Default true. */\n fallbackToPasscode?: boolean;\n}\n\n/** Prompt the user for biometric auth. Resolves { success } or { success:false, error }. */\nexport async function authenticate(opts: AuthenticateOptions): Promise<{ success: boolean; error?: string }> {\n try {\n const res = await LocalAuthentication.authenticateAsync({\n promptMessage: opts.reason,\n cancelLabel: opts.cancelLabel,\n disableDeviceFallback: opts.fallbackToPasscode === false,\n });\n if (res.success) return { success: true };\n return { success: false, error: (res as any).error || 'unknown' };\n } catch (e: any) {\n return { success: false, error: String(e?.message || e) };\n }\n}\n","/**\n * Device-bridge: Sharing (native share sheet)\n *\n * Wraps expo-sharing. Lets solutions hand content off to the OS share sheet\n * — any installed app can receive it (Messages, WhatsApp, Mail, Files, etc.).\n *\n * Typical use: \"Share this article / calendar invite / photo with a contact.\"\n */\n\nimport * as Sharing from 'expo-sharing';\n\nexport interface ShareOptions {\n /** MIME type, e.g. 'application/pdf'. Optional. */\n mimeType?: string;\n /** iOS UTI, e.g. 'public.image'. Optional. */\n UTI?: string;\n /** Dialog title (Android only). */\n dialogTitle?: string;\n}\n\n/** Share a local file URI. */\nexport async function shareFile(fileUri: string, opts: ShareOptions = {}): Promise<{ ok: boolean; error?: string }> {\n try {\n const available = await Sharing.isAvailableAsync();\n if (!available) return { ok: false, error: 'sharing_unavailable' };\n await Sharing.shareAsync(fileUri, opts);\n return { ok: true };\n } catch (e: any) {\n return { ok: false, error: String(e?.message || e) };\n }\n}\n\nexport async function isAvailable(): Promise<boolean> {\n return Sharing.isAvailableAsync();\n}\n","/**\n * Device-bridge: Scanner (QR / barcode)\n *\n * Wraps expo-camera (which replaced the deprecated expo-barcode-scanner\n * in Expo SDK 50+). The CameraView component has built-in barcode scanning.\n *\n * Use cases:\n * - Wi-Fi QR handoff\n * - OAuth / pairing codes\n * - Payment QR codes\n * - Product lookup\n *\n * Plugins mount <CameraView onBarcodeScanned={...} /> directly. This module\n * exposes the permission helper + a result normalizer so plugins don't need\n * to import expo-camera directly.\n */\n\nimport { CameraView, useCameraPermissions, type BarcodeScanningResult } from 'expo-camera';\n\nexport interface ScanResult {\n type: string; // e.g. 'qr', 'ean13', 'code128'\n data: string; // decoded content\n}\n\n/** Request camera permission if not already granted. */\nexport async function requestPermissions(): Promise<{ granted: boolean; status: string }> {\n // useCameraPermissions is a hook; for the non-hook flow plugins should use\n // Camera.requestCameraPermissionsAsync. But it's easier to expose the hook\n // helper — plugins typically use the hook form inside their Component.\n // For service-style callers, fall back to the imperative API:\n const { Camera } = await import('expo-camera');\n const { status } = await Camera.requestCameraPermissionsAsync();\n return { granted: status === 'granted', status };\n}\n\n/** Normalize a CameraView barcode result to a stable shape. */\nexport function normalizeResult(r: BarcodeScanningResult): ScanResult {\n return { type: r.type, data: r.data };\n}\n\n/** Re-export the component + hook so plugins consume them via device-bridge. */\nexport { CameraView, useCameraPermissions };\n","/**\n * @mobile-pa/device-bridge\n *\n * Isolated device data sync module. Zero coupling to chat, voice, or UI.\n * Pluggable capability system — register built-in or custom capabilities.\n *\n * Usage (host app controls permission flow):\n *\n * import { DeviceBridge, capabilities } from '@mobile-pa/device-bridge';\n *\n * const bridge = new DeviceBridge({ relayUrl, deviceId, apiKey });\n *\n * // 1. Register capabilities\n * bridge.registerAll(capabilities.allBuiltIn());\n *\n * // 2. Permission flow — host app shows its own UI per capability\n * for (const cap of bridge.getPendingPermissions()) {\n * // → show explanation: cap.name, cap.description, cap.reason\n * // → user taps \"Allow\"\n * await bridge.requestPermission(cap.id);\n * }\n *\n * // 3. Start syncing (only syncs permitted capabilities)\n * await bridge.startSync();\n *\n * // 4. Add more capabilities later (even after sync started)\n * bridge.register(myCustomCapability);\n * await bridge.requestPermission(myCustomCapability.id);\n * // → auto-starts syncing if bridge is already running\n */\n\nimport { RelayClient } from './relay-client';\nimport { QueuePoller } from './actions/queue-poller';\nimport { registerBackgroundSync, unregisterBackgroundSync } from './background';\nimport type {\n DeviceBridgeConfig,\n BridgeStatus,\n CapabilityStatus,\n DomainSyncStatus,\n SyncState,\n Capability,\n PermissionState,\n ActionResult,\n} from './types';\n\n// Re-export types\nexport type {\n DeviceBridgeConfig,\n BridgeStatus,\n CapabilityStatus,\n DomainSyncStatus,\n BridgeError,\n ActionResult,\n Capability,\n PermissionState,\n} from './types';\n\n// Re-export capability factories\nexport * as capabilities from './capabilities';\n\nexport class DeviceBridge {\n private config: DeviceBridgeConfig;\n private relay: RelayClient;\n private queuePoller: QueuePoller;\n\n // Capability registry\n private caps: Map<string, Capability> = new Map();\n private syncTimers: Map<string, ReturnType<typeof setInterval>> = new Map();\n private subscriptions: Map<string, () => void> = new Map();\n private domainStatus: Map<string, DomainSyncStatus> = new Map();\n private running = false;\n\n constructor(config: DeviceBridgeConfig) {\n this.config = config;\n\n this.relay = new RelayClient({\n relayUrl: config.relayUrl,\n deviceId: config.deviceId,\n tenant: config.tenant,\n actorId: config.actorId,\n apiKey: config.apiKey,\n });\n\n this.queuePoller = new QueuePoller({\n relayClient: this.relay,\n pollInterval: 5_000,\n onActionExecuted: () => this.notifyStatusChange(),\n onError: (error) => this.config.onError?.({\n domain: 'actions',\n message: error.message,\n code: 'ACTION_ERROR',\n timestamp: new Date().toISOString(),\n }),\n });\n }\n\n // ─── Capability Registration ───\n\n /**\n * Register a capability. Does NOT request permission — just makes it known.\n * Can be called before or after startSync().\n */\n register(cap: Capability): void {\n this.caps.set(cap.id, cap);\n this.domainStatus.set(cap.id, {\n domain: cap.id,\n lastSync: null,\n state: 'idle',\n });\n }\n\n /**\n * Register multiple capabilities at once.\n */\n registerAll(caps: Capability[]): void {\n for (const cap of caps) {\n this.register(cap);\n }\n }\n\n /**\n * Get a registered capability by ID.\n * Use this to read name/description/reason for your permission UI.\n */\n getCapability(id: string): Capability | undefined {\n return this.caps.get(id);\n }\n\n /**\n * Get all registered capabilities with their current status.\n */\n listCapabilities(): CapabilityStatus[] {\n return Array.from(this.caps.values()).map(cap => ({\n id: cap.id,\n name: cap.name,\n permission: cap.permissionState,\n sync: this.domainStatus.get(cap.id) || {\n domain: cap.id,\n lastSync: null,\n state: 'idle' as SyncState,\n },\n }));\n }\n\n // ─── Permissions (host app controls the flow) ───\n\n /**\n * Request permission for ONE capability.\n * Call after showing your own explanation screen.\n * If bridge is already running and granted, syncing starts automatically.\n */\n async requestPermission(capId: string): Promise<boolean> {\n const cap = this.caps.get(capId);\n if (!cap) throw new Error(`Capability \"${capId}\" not registered`);\n\n if (!cap.requiresPermission) {\n cap.permissionState = 'not_needed';\n return true;\n }\n\n const granted = await cap.requestPermission();\n cap.permissionState = granted ? 'granted' : 'denied';\n\n // Auto-start syncing if bridge is already running\n if (granted && this.running) {\n this.startCapabilitySync(cap);\n }\n\n this.notifyStatusChange();\n return granted;\n }\n\n /**\n * Check permission WITHOUT prompting.\n */\n async checkPermission(capId: string): Promise<PermissionState> {\n const cap = this.caps.get(capId);\n if (!cap) throw new Error(`Capability \"${capId}\" not registered`);\n if (!cap.requiresPermission) return 'not_needed';\n\n const granted = await cap.checkPermission();\n cap.permissionState = granted ? 'granted' : cap.permissionState;\n return cap.permissionState;\n }\n\n /**\n * Request ALL permissions sequentially (one at a time, not bombarded).\n */\n async requestAllPermissions(): Promise<Map<string, boolean>> {\n const results = new Map<string, boolean>();\n\n for (const [id, cap] of this.caps) {\n if (!cap.requiresPermission) {\n cap.permissionState = 'not_needed';\n results.set(id, true);\n continue;\n }\n const granted = await cap.requestPermission();\n cap.permissionState = granted ? 'granted' : 'denied';\n results.set(id, granted);\n }\n\n this.notifyStatusChange();\n return results;\n }\n\n /**\n * Get capabilities that still need permission.\n * Use this to drive a \"next permission\" onboarding flow.\n */\n getPendingPermissions(): Capability[] {\n return Array.from(this.caps.values()).filter(\n cap => cap.requiresPermission && cap.permissionState === 'not_requested'\n );\n }\n\n // ─── Sync Control ───\n\n /**\n * Start syncing all permitted capabilities + action queue poller.\n */\n async startSync(): Promise<void> {\n if (this.running) return;\n this.running = true;\n\n for (const [, cap] of this.caps) {\n const permitted =\n !cap.requiresPermission ||\n cap.permissionState === 'granted' ||\n cap.permissionState === 'not_needed';\n\n if (permitted) {\n this.startCapabilitySync(cap);\n } else {\n this.updateDomainStatus(cap.id, 'error', 'Permission not granted');\n }\n }\n\n this.queuePoller.start();\n\n // R7: the bridge OWNS its background-fetch lifecycle. Registering the OS task\n // here (from this bridge's own config) — instead of the host doing it\n // separately — is what lets stopSync() reliably UNREGISTER it: before R7 the\n // host registered it but stopBridge never unregistered, so the OS task kept\n // firing with the PREVIOUS user's config across logout/tenant-switch.\n // Best-effort: a host without expo-background-fetch just skips it. Needs a\n // tenant + apiKey (both optional on DeviceBridgeConfig) to hit the relay —\n // without them the background task has no identity to sync as, so skip it.\n if (this.config.tenant && this.config.apiKey) {\n registerBackgroundSync({\n relayUrl: this.config.relayUrl,\n deviceId: this.config.deviceId,\n tenant: this.config.tenant,\n actorId: this.config.actorId,\n apiKey: this.config.apiKey,\n }).catch((e: any) => console.warn('[adas/bridge] registerBackgroundSync failed:', e?.message || e));\n }\n\n this.notifyStatusChange();\n }\n\n /**\n * Stop all syncing.\n */\n stopSync(): void {\n this.running = false;\n\n for (const [id, timer] of this.syncTimers) {\n clearInterval(timer);\n this.updateDomainStatus(id, 'stopped');\n }\n this.syncTimers.clear();\n for (const unsub of this.subscriptions.values()) {\n try { unsub(); } catch { /* swallow — best effort cleanup */ }\n }\n this.subscriptions.clear();\n this.queuePoller.stop();\n // R7: unregister the OS background-fetch task so it can't keep firing with\n // this (now stale) user's config after logout/tenant-switch.\n unregisterBackgroundSync().catch((e: any) => console.warn('[adas/bridge] unregisterBackgroundSync failed:', e?.message || e));\n this.notifyStatusChange();\n }\n\n /**\n * Force immediate sync of all permitted capabilities.\n */\n async syncNow(): Promise<void> {\n const promises = Array.from(this.caps.values())\n .filter(c => c.permissionState === 'granted' || c.permissionState === 'not_needed')\n .map(c => this.syncCapability(c));\n await Promise.allSettled(promises);\n }\n\n /**\n * Force immediate sync of a single capability by id. Used by the host to\n * refresh fast-moving sources (battery) on app-foreground so the relay\n * gets a fresh reading the moment the user opens the app.\n */\n async syncCapabilityNow(capId: string): Promise<void> {\n const cap = this.caps.get(capId);\n if (!cap) return;\n const permitted =\n !cap.requiresPermission ||\n cap.permissionState === 'granted' ||\n cap.permissionState === 'not_needed';\n if (!permitted) return;\n await this.syncCapability(cap);\n }\n\n /**\n * Get current bridge status.\n */\n getStatus(): BridgeStatus {\n return {\n running: this.running,\n capabilities: this.listCapabilities(),\n lastFullSync: this.getLastFullSync(),\n pendingActions: this.relay.getOfflineQueueSize(),\n };\n }\n\n /**\n * Check if the relay is reachable.\n */\n async checkConnectivity(): Promise<boolean> {\n return this.relay.ping();\n }\n\n // ─── Internal ───\n\n private startCapabilitySync(cap: Capability): void {\n if (this.syncTimers.has(cap.id)) return;\n\n const interval = this.config.syncIntervals?.[cap.id] ?? cap.defaultInterval;\n this.syncCapability(cap);\n const timer = setInterval(() => this.syncCapability(cap), interval);\n this.syncTimers.set(cap.id, timer);\n\n // Wire any OS-event subscription (e.g. battery level/state) so we push\n // on real change rather than just on the polling cadence. iOS suspends\n // JS timers in the background, which is exactly when these events still\n // fire from native.\n if (cap.subscribe && !this.subscriptions.has(cap.id)) {\n try {\n const unsub = cap.subscribe(() => {\n if (this.running) this.syncCapability(cap);\n });\n this.subscriptions.set(cap.id, unsub);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n this.config.onError?.({\n domain: cap.id,\n message: `subscribe failed: ${message}`,\n code: 'SUBSCRIBE_ERROR',\n timestamp: new Date().toISOString(),\n });\n }\n }\n }\n\n private async syncCapability(cap: Capability): Promise<void> {\n if (!this.running) return;\n this.updateDomainStatus(cap.id, 'syncing');\n\n try {\n const data = await cap.collect();\n // Collectors that did the work and concluded \"nothing to push\" return\n // null (e.g. contacts hashing detects no changes since last cycle —\n // saves a 1MB+ payload for 6000 contacts). Stay 'idle', don't hit the\n // network, but don't treat it as an error either.\n if (data === null) {\n this.updateDomainStatus(cap.id, 'idle');\n return;\n }\n const result = await this.relay.pushDomain(cap.id, data);\n\n if (result.ok) {\n this.updateDomainStatus(cap.id, 'idle');\n } else {\n this.updateDomainStatus(cap.id, 'error', result.error);\n this.config.onError?.({\n domain: cap.id,\n message: result.error || 'Push failed',\n code: 'RELAY_ERROR',\n timestamp: new Date().toISOString(),\n });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.updateDomainStatus(cap.id, 'error', message);\n this.config.onError?.({\n domain: cap.id,\n message,\n code: 'COLLECTOR_ERROR',\n timestamp: new Date().toISOString(),\n });\n }\n }\n\n private updateDomainStatus(id: string, state: SyncState, error?: string): void {\n const current = this.domainStatus.get(id);\n this.domainStatus.set(id, {\n domain: id,\n lastSync: state === 'idle' ? new Date().toISOString() : (current?.lastSync ?? null),\n state,\n error,\n itemCount: current?.itemCount,\n });\n }\n\n private getLastFullSync(): string | null {\n const syncs = Array.from(this.domainStatus.values())\n .map(d => d.lastSync)\n .filter((s): s is string => s !== null);\n if (syncs.length === 0) return null;\n return syncs.sort()[0];\n }\n\n private notifyStatusChange(): void {\n this.config.onStatusChange?.(this.getStatus());\n }\n}\n\n// ─── Background Sync Support ───\nexport {\n registerBackgroundSync,\n unregisterBackgroundSync,\n syncDomains,\n BACKGROUND_TASK_NAME,\n} from './background';\nexport type { BackgroundSyncConfig } from './background';\n\n// ─── Re-exports for advanced usage ───\nexport { RelayClient } from './relay-client';\nexport { QueuePoller } from './actions/queue-poller';\n\n// ─────────────────────────────────────────────────────────────────────\n// PLATFORM NATIVE CAPABILITIES\n// device-bridge owns every native dependency. plugin-sdk delegates to\n// these namespaces so RN plugins never import native modules directly.\n// Adding a new native capability = one file here + one plugin-sdk\n// delegation + one rebuild — THEN every future solution consumes it\n// as plain JS, no rebuild required.\n// ─────────────────────────────────────────────────────────────────────\n\n// Biometrics — Face ID / Touch ID / fingerprint (expo-local-authentication)\nexport * as biometrics from './biometrics';\nexport type { BiometricType, BiometricCapabilities, AuthenticateOptions } from './biometrics';\n\n// Sharing — native share sheet (expo-sharing)\nexport * as sharing from './sharing';\nexport type { ShareOptions } from './sharing';\n\n// Scanner — QR / barcode camera (expo-camera)\nexport * as scanner from './scanner';\nexport type { ScanResult } from './scanner';\n\n// Location — GPS fixes for plugin tools (expo-location, safe in Expo Go)\nexport * as location from './collectors/location';\nexport type { Position } from './collectors/location';\n\n// NOTE: cookies (@react-native-cookies/cookies) and nfc (react-native-nfc-manager)\n// require native modules NOT included in Expo Go. They are imported lazily\n// via require() in useNativeCapabilities.ts. Do NOT add eager exports here\n// or the app will crash in Expo Go.\n// To use: require('../../packages/device-bridge/src/cookies')\n// To use: require('../../packages/device-bridge/src/nfc')\n"],"mappings":"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","names":["execute","execute","execute","ExpoCalendar","requestPermission","hasPermission","collectToday","formatTime","requestPermission","hasPermission","hasPermission","requestPermission","requestPermission","hasPermission","requestPermission","hasPermission","collectToday","hasPermission","requestPermission","requestPermission","hasPermission","collectToday","Camera"]}