@ateam-ai-mobile/expo 0.1.21 → 0.1.22
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.
- package/dist/aiLive/provider.d.mts +10 -4
- package/dist/aiLive/provider.d.ts +10 -4
- package/dist/aiLive/provider.js +119 -43
- package/dist/aiLive/provider.js.map +1 -1
- package/dist/aiLive/provider.mjs +5 -3
- package/dist/bootstrap.js +120 -46
- package/dist/bootstrap.js.map +1 -1
- package/dist/bootstrap.mjs +4 -4
- package/dist/capabilities/index.js +19 -1
- package/dist/capabilities/index.js.map +1 -1
- package/dist/capabilities/index.mjs +21 -3
- package/dist/capabilities/index.mjs.map +1 -1
- package/dist/{chunk-BAZFMTCC.mjs → chunk-4HJ5FO3S.mjs} +2 -2
- package/dist/{chunk-C62NQA4F.mjs → chunk-GGEFDRQQ.mjs} +2 -2
- package/dist/chunk-GGEFDRQQ.mjs.map +1 -0
- package/dist/{chunk-OEX4OKYQ.mjs → chunk-NH3CHSLR.mjs} +2 -2
- package/dist/{chunk-JUBLG3PN.mjs → chunk-PV2EHH2P.mjs} +119 -44
- package/dist/chunk-PV2EHH2P.mjs.map +1 -0
- package/dist/device/createDeviceProvider.mjs +3 -3
- package/dist/device-bridge/index.js +1 -1
- package/dist/device-bridge/index.js.map +1 -1
- package/dist/device-bridge/index.mjs +2 -2
- package/dist/push/silentSync.js.map +1 -1
- package/dist/push/silentSync.mjs +1 -1
- package/package.json +4 -4
- package/src/aiLive/provider.ts +138 -49
- package/src/aiLive/streamProtocol.ts +2 -2
- package/src/capabilities/store.ts +21 -1
- package/src/device-bridge/capabilities.ts +2 -1
- package/dist/chunk-C62NQA4F.mjs.map +0 -1
- package/dist/chunk-JUBLG3PN.mjs.map +0 -1
- /package/dist/{chunk-BAZFMTCC.mjs.map → chunk-4HJ5FO3S.mjs.map} +0 -0
- /package/dist/{chunk-OEX4OKYQ.mjs.map → chunk-NH3CHSLR.mjs.map} +0 -0
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
} from "../chunk-GA7DS3PA.mjs";
|
|
12
12
|
import {
|
|
13
13
|
DeviceBridge
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-4HJ5FO3S.mjs";
|
|
15
15
|
import {
|
|
16
16
|
capabilities_exports
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-GGEFDRQQ.mjs";
|
|
18
18
|
import "../chunk-XQSY6MHP.mjs";
|
|
19
19
|
import "../chunk-KOXF2FZM.mjs";
|
|
20
20
|
import "../chunk-7FWYEHN5.mjs";
|
|
@@ -28,7 +28,7 @@ import "../chunk-WMJKH4XE.mjs";
|
|
|
28
28
|
import { create } from "zustand";
|
|
29
29
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
30
30
|
import { getTenant, getUser, onAppForeground } from "@ateam-ai-mobile/core";
|
|
31
|
-
import { setPicturesToAiConsentProvider } from "@ateam-ai-mobile/plugin-runtime";
|
|
31
|
+
import { setPicturesToAiConsentProvider, notifyPicturesToAiConsentChanged } from "@ateam-ai-mobile/plugin-runtime";
|
|
32
32
|
|
|
33
33
|
// src/capabilities/picturesToAi.ts
|
|
34
34
|
function picturesToAiAllowed(s) {
|
|
@@ -259,6 +259,24 @@ var useCapabilitiesStore = create((set, get) => {
|
|
|
259
259
|
};
|
|
260
260
|
});
|
|
261
261
|
setPicturesToAiConsentProvider(() => picturesToAiAllowed(useCapabilitiesStore.getState()));
|
|
262
|
+
useCapabilitiesStore.subscribe(() => notifyPicturesToAiConsentChanged());
|
|
263
|
+
async function recheckCameraPermission() {
|
|
264
|
+
const cam = builtInCapabilities.find((c) => c.id === "camera");
|
|
265
|
+
if (!cam) return;
|
|
266
|
+
let granted = false;
|
|
267
|
+
try {
|
|
268
|
+
granted = await cam.checkPermission() === true;
|
|
269
|
+
} catch {
|
|
270
|
+
granted = false;
|
|
271
|
+
}
|
|
272
|
+
const current = useCapabilitiesStore.getState().capabilities.find((c) => c.id === "camera");
|
|
273
|
+
if (!current) return;
|
|
274
|
+
if (granted && current.osPermission !== "granted") useCapabilitiesStore.getState().setOsPermission("camera", "granted");
|
|
275
|
+
if (!granted && current.osPermission === "granted") useCapabilitiesStore.getState().setOsPermission("camera", "denied");
|
|
276
|
+
}
|
|
277
|
+
onAppForeground(() => {
|
|
278
|
+
void recheckCameraPermission();
|
|
279
|
+
});
|
|
262
280
|
|
|
263
281
|
// src/capabilities/CapabilitiesSetup.tsx
|
|
264
282
|
import { useState } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/capabilities/store.ts","../../src/capabilities/picturesToAi.ts","../../src/capabilities/CapabilitiesSetup.tsx","../../src/capabilities/CapabilityCard.tsx"],"sourcesContent":["import { create } from 'zustand';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { getTenant, getUser, onAppForeground } from '@ateam-ai-mobile/core';\nimport { setPicturesToAiConsentProvider } from '@ateam-ai-mobile/plugin-runtime';\nimport { DeviceBridge, capabilities as capFactories } from '../device-bridge';\nimport type { Capability, PermissionState } from '../device-bridge/types';\nimport { getOrCreateDeviceId } from '../device-bridge/deviceId';\nimport { getDeviceSyncConfig } from './config';\nimport { STORAGE_KEY, SETUP_KEY, HIDDEN_FROM_UI, loadChoices, isEnabled } from './choices';\nimport { picturesToAiAllowed } from './picturesToAi';\n\n// The key the app used for this switch before the SDK owned it (branch pictures-to-ai-switch).\nconst PICTURES_TO_AI_KEY = 'ateam.capabilities.picturesToAi';\n\n/**\n * Device permissions — the one source of truth for what this device shares.\n *\n * Owns the DeviceBridge instance. Capability definitions come FROM the device\n * bridge (allBuiltIn()), not hardcoded anywhere.\n *\n * Two levels of permission:\n * 1. User opt-in (a toggle in the host's UI) — saved in AsyncStorage\n * 2. OS permission (system dialog) — requested only AFTER the user opts in\n *\n * Flow:\n * App start → initialize() loads built-in caps + saved choices\n * Setup screen → user toggles → completeSetup()\n * Host sees signed in + hasCompletedSetup → startBridge()\n * startBridge() → registers enabled caps, requests OS perms, starts sync\n * Settings toggle → toggleCapability() → starts/stops sync on the fly\n *\n * Moved from ateam-mobile src/stores/capabilitiesStore.ts (2026-10-29). The\n * tenant and user are now read from the SDK session at start (never a stored\n * copy), the bridge no longer starts under a made-up 'default' user, and a\n * start after a team switch replaces the bridge that still synced to the old\n * team (the old switch-time stop went away with the app's switchTenant, df7f6f72).\n */\n\nexport interface CapabilityState {\n id: string;\n name: string;\n description: string;\n reason: string;\n icon: string;\n requiresPermission: boolean;\n /** User's choice in the host's UI */\n enabled: boolean;\n /** OS-level permission status */\n osPermission: PermissionState;\n}\n\ninterface CapabilitiesState {\n capabilities: CapabilityState[];\n /** The \"Camera pictures to AI\" switch under Camera: the person's half of the\n * consent props.aiLive needs. Off until they turn it on (default no). */\n picturesToAi: boolean;\n hasCompletedSetup: boolean;\n isLoading: boolean;\n bridgeRunning: boolean;\n\n /** Initialize — load built-in capabilities from device-bridge + saved user preferences */\n initialize: () => Promise<void>;\n /** Toggle a capability on/off, persist, and update bridge if running */\n toggleCapability: (id: string, enabled: boolean) => Promise<void>;\n /** Set and persist \"Camera pictures to AI\". The host asks first (PICTURES_TO_AI_ASK). */\n setPicturesToAi: (on: boolean) => Promise<void>;\n /** Update OS permission state for a capability */\n setOsPermission: (id: string, state: PermissionState) => void;\n /** Mark initial setup as complete */\n completeSetup: () => Promise<void>;\n /**\n * Start the device bridge for the signed-in user and team (call after setup is\n * done, and again whenever the team or user changes). Safe to call often: calls\n * run one at a time, a bridge already running for this session is kept, and one\n * running for another team or user is replaced.\n */\n startBridge: () => Promise<void>;\n /** Stop the device bridge (call on sign-out; a team switch is a startBridge() call) */\n stopBridge: () => void;\n /** Force an immediate sync of all permitted capabilities. */\n syncNow: () => Promise<{ ok: boolean; error?: string }>;\n}\n\n// Module-level bridge singleton — NOT in Zustand (it's a mutable object with timers)\nlet bridge: DeviceBridge | null = null;\n\n// Who the running bridge syncs as, so a start after a team switch replaces it.\nlet bridgeOwner: { tenant: string; actorId: string } | null = null;\n\n// Starts run one at a time (the host's effect and a toggle can ask together).\nlet startQueue: Promise<void> = Promise.resolve();\n\n// Bumped by stopBridge: a start that was waiting when the bridge stopped (sign-out\n// mid-start) gives up instead of syncing as the user who just left.\nlet startGeneration = 0;\n\n// Cache the built-in Capability objects (with collect/permission functions)\nlet builtInCapabilities: Capability[] = [];\n\n// The foreground battery-refresh is registered with the SDK's foreground\n// coordinator (onAppForeground) instead of a private AppState listener — one\n// signal, deliberate order. iOS pauses JS timers in the background, so the\n// periodic battery poll goes stale; pushing on foreground guarantees a fresh\n// reading the moment the user opens the app. Kept at module scope so stopBridge\n// can unsubscribe it cleanly.\nlet foregroundUnsub: (() => void) | null = null;\n\nexport const useCapabilitiesStore = create<CapabilitiesState>((set, get) => {\n const runStart = async (): Promise<void> => {\n // Tenant and user come from the SDK session — the token's claims, the same\n // ones every request acts on. No session, no sync: device data is scoped per\n // user on the relay, so it never goes under a guessed or shared name.\n const tenant = getTenant();\n const actorId = getUser()?.actorId;\n\n if (bridge) {\n if (bridgeOwner?.tenant === tenant && bridgeOwner?.actorId === actorId) {\n console.log('[Capabilities] Bridge already running');\n return;\n }\n console.log('[Capabilities] Team or user changed — restarting bridge');\n get().stopBridge();\n }\n\n const { capabilities: caps } = get();\n const enabledCaps = caps.filter(c => c.enabled);\n\n if (enabledCaps.length === 0) {\n console.log('[Capabilities] No capabilities enabled — bridge not started');\n return;\n }\n\n const config = getDeviceSyncConfig();\n if (!config) {\n console.error('[Capabilities] Bridge not started: device sync is not configured — pass deviceSync to bootstrapExpoAdas (or call configureDeviceSync)');\n return;\n }\n\n if (!tenant || !actorId) {\n console.error(`[Capabilities] Bridge not started: no signed-in ${!tenant ? 'tenant' : 'user'}`);\n return;\n }\n\n const generation = startGeneration;\n\n // Get or generate a persistent unique device ID for this install\n const deviceId = await getOrCreateDeviceId(config.secureStorage);\n if (generation !== startGeneration) return;\n\n console.log(`[Capabilities] Starting bridge for tenant=${tenant} actor=${actorId} device=${deviceId} with ${enabledCaps.length} capabilities: ${enabledCaps.map(c => c.id).join(', ')}`);\n\n const started = new DeviceBridge({\n relayUrl: config.relayUrl(),\n deviceId,\n tenant,\n actorId,\n // RelayClient sends `Authorization: Bearer <apiKey>` so the relay's\n // auth gate accepts the call. Empty string = misconfig → relay returns\n // 401, surfaced as a sync error in the UI.\n apiKey: config.relayKey,\n onStatusChange: (status) => {\n // Sync OS permission states back to store\n for (const capStatus of status.capabilities) {\n const current = get().capabilities.find(c => c.id === capStatus.id);\n if (current && current.osPermission !== capStatus.permission) {\n get().setOsPermission(capStatus.id, capStatus.permission);\n }\n }\n },\n onError: (err) => {\n console.warn(`[device-bridge] ${err.domain}: ${err.message}`);\n },\n });\n bridge = started;\n bridgeOwner = { tenant, actorId };\n\n try {\n // Register ONLY user-enabled capabilities\n const enabledIds = new Set(enabledCaps.map(c => c.id));\n const capsToRegister = builtInCapabilities.filter(c => enabledIds.has(c.id));\n started.registerAll(capsToRegister);\n\n // Request OS permissions for capabilities that need them\n await started.requestAllPermissions();\n if (generation !== startGeneration) return; // stopBridge already stopped it\n\n // Update OS permission states in store\n const statuses = started.listCapabilities();\n set(state => ({\n bridgeRunning: true,\n capabilities: state.capabilities.map(c => {\n const status = statuses.find(s => s.id === c.id);\n return status ? { ...c, osPermission: status.permission } : c;\n }),\n }));\n\n await started.startSync();\n if (generation !== startGeneration) {\n // Stopped while starting: stop again, in case sync started after that stop.\n started.stopSync();\n return;\n }\n console.log('[Capabilities] Bridge started and syncing');\n } catch (err) {\n // A failed start must not stay recorded as \"already running\" — the next\n // start would keep answering that and never retry.\n if (bridge === started) get().stopBridge();\n throw err;\n }\n\n // Foreground refresh: force an immediate battery sync when the app returns\n // to the foreground, so the relay reflects the real level instead of the\n // stale value the suspended polling timer left behind. Registered with the\n // SDK coordinator (fires after the ACS reconnect) — only once.\n if (!foregroundUnsub) {\n foregroundUnsub = onAppForeground(() => {\n bridge?.syncCapabilityNow('battery').catch((err) => {\n console.warn('[Capabilities] foreground battery sync failed:', err);\n });\n });\n }\n\n // The periodic background-fetch task is owned by the bridge itself —\n // DeviceBridge.startSync() registers it (from its own config) and stopSync()\n // UNREGISTERS it, so the OS task can't keep firing with a stale user's\n // config after logout/tenant-switch.\n };\n\n return {\n capabilities: [],\n picturesToAi: false,\n hasCompletedSetup: false,\n isLoading: true,\n bridgeRunning: false,\n\n initialize: async () => {\n try {\n // Get capability definitions FROM device-bridge — single source of truth.\n // Filter out capabilities whose user toggle is owned by another screen\n // (e.g. notifications → the host's notification settings).\n builtInCapabilities = capFactories.allBuiltIn().filter(c => !HIDDEN_FROM_UI.has(c.id));\n\n // Saved user choices (override the defaults for returning users)\n const { setupDone, prefs } = await loadChoices();\n const picturesToAi = (await AsyncStorage.getItem(PICTURES_TO_AI_KEY)) === 'true';\n\n // Map to UI state (no functions, just data + enabled flag)\n const capStates: CapabilityState[] = builtInCapabilities.map(cap => ({\n id: cap.id,\n name: cap.name,\n description: cap.description,\n reason: cap.reason,\n icon: cap.icon || cap.id,\n requiresPermission: cap.requiresPermission,\n enabled: isEnabled(cap.id, prefs),\n osPermission: cap.permissionState,\n }));\n\n set({\n capabilities: capStates,\n picturesToAi,\n hasCompletedSetup: setupDone,\n isLoading: false,\n });\n\n console.log(`[Capabilities] Initialized ${capStates.length} capabilities from device-bridge (setup ${setupDone ? 'done' : 'pending'})`);\n } catch (err) {\n console.error('[Capabilities] Initialize failed:', err);\n set({ isLoading: false });\n }\n },\n\n toggleCapability: async (id: string, enabled: boolean) => {\n // Update UI state\n set(state => ({\n capabilities: state.capabilities.map(cap =>\n cap.id === id ? { ...cap, enabled } : cap\n ),\n }));\n\n // Persist all preferences\n const prefs: Record<string, boolean> = {};\n for (const cap of get().capabilities) {\n prefs[cap.id] = cap.id === id ? enabled : cap.enabled;\n }\n await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));\n\n // Turning the camera off also clears \"Camera pictures to AI\", so turning the\n // camera back on later never silently re-opens that door: the person is asked\n // again. (While the camera is off the consent already reads as no.)\n if (id === 'camera' && !enabled && get().picturesToAi) {\n await get().setPicturesToAi(false);\n }\n\n // If bridge is running, dynamically update\n if (bridge && get().bridgeRunning) {\n if (enabled) {\n // Register + request permission for newly enabled capability\n const builtIn = builtInCapabilities.find(c => c.id === id);\n if (builtIn) {\n bridge.register(builtIn);\n const granted = await bridge.requestPermission(id);\n set(state => ({\n capabilities: state.capabilities.map(c =>\n c.id === id ? { ...c, osPermission: granted ? 'granted' : 'denied' } : c\n ),\n }));\n }\n } else {\n // Stop and restart bridge without this capability\n console.log(`[Capabilities] ${id} disabled — restarting bridge`);\n get().stopBridge();\n await get().startBridge();\n }\n }\n },\n\n setPicturesToAi: async (on: boolean) => {\n set({ picturesToAi: on });\n await AsyncStorage.setItem(PICTURES_TO_AI_KEY, on ? 'true' : 'false');\n },\n\n setOsPermission: (id: string, permState: PermissionState) => {\n set(s => ({\n capabilities: s.capabilities.map(cap =>\n cap.id === id ? { ...cap, osPermission: permState } : cap\n ),\n }));\n },\n\n completeSetup: async () => {\n await AsyncStorage.setItem(SETUP_KEY, 'true');\n set({ hasCompletedSetup: true });\n },\n\n startBridge: () => {\n const run = startQueue.then(runStart);\n startQueue = run.catch((err) => {\n console.error('[Capabilities] Bridge start failed:', err);\n });\n return run;\n },\n\n stopBridge: () => {\n startGeneration++;\n if (bridge) {\n bridge.stopSync();\n bridge = null;\n bridgeOwner = null;\n set({ bridgeRunning: false });\n console.log('[Capabilities] Bridge stopped');\n }\n if (foregroundUnsub) {\n foregroundUnsub();\n foregroundUnsub = null;\n }\n },\n\n syncNow: async () => {\n if (!bridge) return { ok: false, error: 'bridge not running' };\n try {\n await bridge.syncNow();\n // bridge.syncNow uses Promise.allSettled internally so it never throws.\n // Read per-capability status to find any actual failures we should\n // surface — particularly for health, which pushes large bundles and\n // can time out / partial-fail without bubbling.\n const statuses = bridge.listCapabilities();\n const failed = statuses.filter(s =>\n s.sync?.state === 'error' || s.sync?.state === 'syncing',\n );\n if (failed.length > 0) {\n const summary = failed\n .map(s => `${s.id}:${s.sync?.state}${s.sync?.error ? `(${s.sync.error})` : ''}`)\n .join(', ');\n return { ok: false, error: summary };\n }\n return { ok: true };\n } catch (e: any) {\n return { ok: false, error: e?.message || String(e) };\n }\n },\n };\n});\n\n// The person's half of \"camera pictures to AI\", for plugin-runtime's mayPicturesLeavePhone():\n// read from this store at CALL time (derive, never store). A host that never loads this store\n// has no consent provider, and the answer is no.\nsetPicturesToAiConsentProvider(() => picturesToAiAllowed(useCapabilitiesStore.getState()));\n","/**\n * \"Camera pictures to AI\" — the phone owner's half of a two-sided consent\n * (owner decision 2026-10-25, amending \"frames never leave the phone\").\n *\n * A camera picture may go to an AI service off the phone only when the\n * solution's developer declared `picturesToAi` in its manifest AND the person on\n * this phone allowed it here. plugin-runtime's mayPicturesLeavePhone() checks\n * both; this half is installed as its consent provider by the capabilities store.\n *\n * Moved from ateam-mobile (branch pictures-to-ai-switch, 9541f22) into the SDK\n * with the store it belongs to. Pure, so tests run it under plain node.\n */\n\nexport interface PicturesToAiInputs {\n /** The switch under Camera in the host's \"What Ada can access\". */\n picturesToAi: boolean;\n capabilities: ReadonlyArray<{ id: string; enabled: boolean; osPermission: string }>;\n}\n\n/** Derived at read time, never stored: the switch is on AND the camera itself is\n * on and granted by the OS. Anything else answers no. */\nexport function picturesToAiAllowed(s: PicturesToAiInputs): boolean {\n if (s.picturesToAi !== true) return false;\n const camera = s.capabilities.find((c) => c.id === 'camera');\n return !!camera && camera.enabled === true && camera.osPermission === 'granted';\n}\n\n/** What the person is told before allowing it. Apple guideline 5.1.2(i): say\n * where personal data goes before sharing it with an outside AI, and get\n * explicit permission first. */\nexport const PICTURES_TO_AI_ASK = {\n title: 'Send camera pictures to AI?',\n message:\n 'Solutions that ask for it may send pictures from your camera to an AI service ' +\n '(OpenAI or Anthropic) through A-Team, to understand what the camera sees. ' +\n 'Only while such a solution is open, and \"Sending to AI\" shows while it does. ' +\n 'You can turn this off here at any time.',\n allow: 'Allow',\n cancel: 'Not now',\n} as const;\n","import { useState, type ReactNode } from 'react';\nimport {\n View,\n Text,\n ScrollView,\n Pressable,\n StyleSheet,\n ActivityIndicator,\n SafeAreaView,\n Platform,\n} from 'react-native';\nimport { ada, borderRadius, fontSize, spacing } from '@ateam-ai-mobile/core';\nimport { useCapabilitiesStore } from './store';\nimport CapabilityCard from './CapabilityCard';\n\nconst fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;\n\nexport interface CapabilitiesSetupProps {\n /** Called after the user continues or skips; the choices are already saved. */\n onComplete: () => void;\n /** Drawn above the title — e.g. the host's logo. */\n header?: ReactNode;\n title?: string;\n subtitle?: string;\n /** Shown while the saved choices load. Default: a spinner. */\n loading?: ReactNode;\n}\n\n/**\n * Drop-in first-run screen: the user picks what this device shares, then\n * continues (or skips, which turns everything off). Reads and writes\n * useCapabilitiesStore; the host starts the bridge once setup is complete.\n */\nexport default function CapabilitiesSetup({\n onComplete,\n header,\n title = 'What can the assistant use?',\n subtitle = \"Choose what it can see on this device. You're in charge — change it anytime in settings.\",\n loading,\n}: CapabilitiesSetupProps) {\n const {\n capabilities,\n toggleCapability,\n completeSetup,\n isLoading,\n } = useCapabilitiesStore();\n\n const [requesting, setRequesting] = useState(false);\n\n const enabledCount = capabilities.filter(c => c.enabled).length;\n const permissionCount = capabilities.filter(c => c.enabled && c.requiresPermission).length;\n\n const handleContinue = async () => {\n setRequesting(true);\n await completeSetup();\n setRequesting(false);\n onComplete();\n };\n\n const handleSkip = async () => {\n for (const cap of capabilities) {\n if (cap.enabled) {\n await toggleCapability(cap.id, false);\n }\n }\n await completeSetup();\n onComplete();\n };\n\n if (isLoading) {\n return (\n <SafeAreaView style={styles.center}>\n {loading ?? <ActivityIndicator size=\"large\" color={ada.inkMuted} />}\n </SafeAreaView>\n );\n }\n\n return (\n <SafeAreaView style={styles.container}>\n <ScrollView contentContainerStyle={styles.content}>\n <View style={styles.header}>\n {header ? <View style={styles.heroRow}>{header}</View> : null}\n <Text style={styles.title}>{title}</Text>\n <Text style={styles.subtitle}>{subtitle}</Text>\n </View>\n\n <View style={styles.section}>\n <Text style={styles.sectionTitle}>NEEDS YOUR PERMISSION</Text>\n {capabilities.filter(c => c.requiresPermission).map(cap => (\n <CapabilityCard\n key={cap.id}\n capability={cap}\n onToggle={toggleCapability}\n />\n ))}\n </View>\n\n <View style={styles.section}>\n <Text style={styles.sectionTitle}>ALWAYS AVAILABLE</Text>\n {capabilities.filter(c => !c.requiresPermission).map(cap => (\n <CapabilityCard\n key={cap.id}\n capability={cap}\n onToggle={toggleCapability}\n />\n ))}\n </View>\n\n <View style={styles.summaryBox}>\n <Text style={styles.summaryText}>\n {enabledCount} of {capabilities.length} on\n {permissionCount > 0 && ` · ${permissionCount} will ask for permission`}\n </Text>\n </View>\n </ScrollView>\n\n <View style={styles.footer}>\n <Pressable\n style={[styles.continueBtn, requesting && styles.continueBtnDisabled]}\n onPress={handleContinue}\n disabled={requesting}\n >\n {requesting ? (\n <ActivityIndicator size=\"small\" color={ada.paper} />\n ) : (\n <Text style={styles.continueBtnText}>\n {enabledCount > 0 ? 'Continue' : 'Continue without these'}\n </Text>\n )}\n </Pressable>\n\n <Pressable style={styles.skipBtn} onPress={handleSkip}>\n <Text style={styles.skipBtnText}>Skip for now</Text>\n </Pressable>\n </View>\n </SafeAreaView>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n backgroundColor: ada.paper,\n },\n center: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n backgroundColor: ada.paper,\n },\n content: {\n paddingBottom: 20,\n },\n header: {\n paddingHorizontal: spacing.xl,\n paddingTop: 20,\n paddingBottom: spacing.lg,\n },\n heroRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 10,\n marginBottom: spacing.lg,\n },\n title: {\n color: ada.ink,\n fontSize: 28,\n fontWeight: '700',\n letterSpacing: -0.5,\n marginBottom: 8,\n fontFamily: fontFam,\n },\n subtitle: {\n color: ada.inkSoft,\n fontSize: fontSize.md,\n lineHeight: 22,\n letterSpacing: -0.1,\n fontFamily: fontFam,\n },\n section: {\n paddingHorizontal: spacing.lg,\n marginBottom: spacing.lg,\n },\n sectionTitle: {\n color: ada.inkMuted,\n fontSize: 11,\n fontWeight: '700',\n letterSpacing: 1.4,\n marginBottom: spacing.sm,\n paddingHorizontal: 4,\n fontFamily: fontFam,\n },\n summaryBox: {\n marginHorizontal: spacing.lg,\n backgroundColor: ada.orangeSoft,\n borderRadius: borderRadius.md,\n padding: spacing.md,\n alignItems: 'center',\n },\n summaryText: {\n color: ada.orangeDeep,\n fontSize: fontSize.sm,\n fontWeight: '600',\n fontFamily: fontFam,\n letterSpacing: -0.1,\n },\n footer: {\n paddingHorizontal: spacing.lg,\n paddingVertical: spacing.lg,\n borderTopWidth: 1,\n borderTopColor: ada.border,\n gap: spacing.sm,\n },\n continueBtn: {\n backgroundColor: ada.ink,\n borderRadius: 999,\n paddingVertical: 16,\n alignItems: 'center',\n },\n continueBtnDisabled: {\n opacity: 0.6,\n },\n continueBtnText: {\n color: ada.paper,\n fontSize: fontSize.lg,\n fontWeight: '600',\n fontFamily: fontFam,\n letterSpacing: -0.2,\n },\n skipBtn: {\n paddingVertical: 12,\n alignItems: 'center',\n },\n skipBtnText: {\n color: ada.inkMuted,\n fontSize: fontSize.md,\n fontFamily: fontFam,\n },\n});\n","import { View, Text, Switch, Pressable, StyleSheet, Linking, Platform } from 'react-native';\nimport { ada, borderRadius, fontSize, spacing } from '@ateam-ai-mobile/core';\nimport type { CapabilityState } from './store';\n\nconst fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;\n\nconst ICONS: Record<string, string> = {\n calendar: '📅',\n contacts: '👥',\n location: '📍',\n battery: '🔋',\n wifi: '📶',\n notifications: '🔔',\n};\n\nexport interface CapabilityCardProps {\n capability: CapabilityState;\n onToggle: (id: string, enabled: boolean) => void;\n}\n\n/** One capability with its on/off switch and OS-permission badge. */\nexport default function CapabilityCard({ capability, onToggle }: CapabilityCardProps) {\n const { id, name, description, reason, icon, enabled, osPermission, requiresPermission } = capability;\n\n const isDenied = osPermission === 'denied';\n const isGranted = osPermission === 'granted' || osPermission === 'not_needed';\n\n const handleToggle = (value: boolean) => {\n if (isDenied && value) {\n Linking.openSettings();\n return;\n }\n onToggle(id, value);\n };\n\n return (\n <View style={[styles.card, enabled && styles.cardEnabled]}>\n <View style={styles.row}>\n <View style={[styles.iconBox, enabled && styles.iconBoxEnabled]}>\n <Text style={styles.iconText}>{ICONS[icon] || '⚙️'}</Text>\n </View>\n\n <View style={styles.info}>\n <Text style={styles.name}>{name}</Text>\n <Text style={styles.description}>{description}</Text>\n </View>\n\n <Switch\n value={enabled}\n onValueChange={handleToggle}\n trackColor={{ false: ada.border, true: ada.orange }}\n thumbColor=\"#fff\"\n ios_backgroundColor={ada.border}\n />\n </View>\n\n {enabled && (\n <Text style={styles.reason}>{reason}</Text>\n )}\n\n {enabled && requiresPermission && (\n <View style={styles.statusRow}>\n {isDenied ? (\n <Pressable onPress={() => Linking.openSettings()}>\n <View style={styles.deniedBadge}>\n <Text style={styles.deniedText}>Blocked — tap to open Settings</Text>\n </View>\n </Pressable>\n ) : isGranted ? (\n <View style={styles.grantedBadge}>\n <Text style={styles.grantedText}>Active</Text>\n </View>\n ) : (\n <View style={styles.pendingBadge}>\n <Text style={styles.pendingText}>Will ask permission</Text>\n </View>\n )}\n </View>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n card: {\n backgroundColor: ada.paperSoft,\n borderRadius: borderRadius.lg,\n borderWidth: 1,\n borderColor: ada.border,\n padding: spacing.lg,\n marginBottom: spacing.md,\n },\n cardEnabled: {\n borderColor: ada.orange,\n borderWidth: 1.5,\n },\n row: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: spacing.md,\n },\n iconBox: {\n width: 44,\n height: 44,\n borderRadius: 12,\n backgroundColor: ada.paperDeep,\n justifyContent: 'center',\n alignItems: 'center',\n },\n iconBoxEnabled: {\n backgroundColor: ada.orangeSoft,\n },\n iconText: {\n fontSize: 20,\n },\n info: {\n flex: 1,\n },\n name: {\n color: ada.ink,\n fontSize: fontSize.lg,\n fontWeight: '600',\n fontFamily: fontFam,\n letterSpacing: -0.2,\n },\n description: {\n color: ada.inkSoft,\n fontSize: fontSize.sm,\n marginTop: 2,\n lineHeight: 18,\n fontFamily: fontFam,\n letterSpacing: -0.05,\n },\n reason: {\n color: ada.inkMuted,\n fontSize: fontSize.xs,\n marginTop: spacing.sm,\n marginLeft: 44 + spacing.md,\n lineHeight: 16,\n fontFamily: fontFam,\n },\n statusRow: {\n marginTop: spacing.sm,\n marginLeft: 44 + spacing.md,\n },\n grantedBadge: {\n backgroundColor: 'rgba(90, 138, 92, 0.14)',\n borderRadius: borderRadius.sm,\n paddingHorizontal: spacing.sm,\n paddingVertical: 3,\n alignSelf: 'flex-start',\n },\n grantedText: {\n color: '#5A8A5C',\n fontSize: fontSize.xs,\n fontWeight: '700',\n fontFamily: fontFam,\n letterSpacing: 0.2,\n },\n pendingBadge: {\n backgroundColor: ada.orangeSoft,\n borderRadius: borderRadius.sm,\n paddingHorizontal: spacing.sm,\n paddingVertical: 3,\n alignSelf: 'flex-start',\n },\n pendingText: {\n color: ada.orangeDeep,\n fontSize: fontSize.xs,\n fontWeight: '700',\n fontFamily: fontFam,\n letterSpacing: 0.2,\n },\n deniedBadge: {\n backgroundColor: 'rgba(193, 85, 69, 0.14)',\n borderRadius: borderRadius.sm,\n paddingHorizontal: spacing.sm,\n paddingVertical: 3,\n alignSelf: 'flex-start',\n },\n deniedText: {\n color: '#C15545',\n fontSize: fontSize.xs,\n fontWeight: '700',\n fontFamily: fontFam,\n letterSpacing: 0.2,\n },\n});\n"],"mappings":"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","names":["View","Text","Pressable","StyleSheet","Platform","ada","borderRadius","fontSize","spacing","jsx","jsxs","fontFam","Platform","styles","ada","View","Text","Pressable","StyleSheet","spacing","fontSize","borderRadius"]}
|
|
1
|
+
{"version":3,"sources":["../../src/capabilities/store.ts","../../src/capabilities/picturesToAi.ts","../../src/capabilities/CapabilitiesSetup.tsx","../../src/capabilities/CapabilityCard.tsx"],"sourcesContent":["import { create } from 'zustand';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { getTenant, getUser, onAppForeground } from '@ateam-ai-mobile/core';\nimport { setPicturesToAiConsentProvider, notifyPicturesToAiConsentChanged } from '@ateam-ai-mobile/plugin-runtime';\nimport { DeviceBridge, capabilities as capFactories } from '../device-bridge';\nimport type { Capability, PermissionState } from '../device-bridge/types';\nimport { getOrCreateDeviceId } from '../device-bridge/deviceId';\nimport { getDeviceSyncConfig } from './config';\nimport { STORAGE_KEY, SETUP_KEY, HIDDEN_FROM_UI, loadChoices, isEnabled } from './choices';\nimport { picturesToAiAllowed } from './picturesToAi';\n\n// The key the app used for this switch before the SDK owned it (branch pictures-to-ai-switch).\nconst PICTURES_TO_AI_KEY = 'ateam.capabilities.picturesToAi';\n\n/**\n * Device permissions — the one source of truth for what this device shares.\n *\n * Owns the DeviceBridge instance. Capability definitions come FROM the device\n * bridge (allBuiltIn()), not hardcoded anywhere.\n *\n * Two levels of permission:\n * 1. User opt-in (a toggle in the host's UI) — saved in AsyncStorage\n * 2. OS permission (system dialog) — requested only AFTER the user opts in\n *\n * Flow:\n * App start → initialize() loads built-in caps + saved choices\n * Setup screen → user toggles → completeSetup()\n * Host sees signed in + hasCompletedSetup → startBridge()\n * startBridge() → registers enabled caps, requests OS perms, starts sync\n * Settings toggle → toggleCapability() → starts/stops sync on the fly\n *\n * Moved from ateam-mobile src/stores/capabilitiesStore.ts (2026-10-29). The\n * tenant and user are now read from the SDK session at start (never a stored\n * copy), the bridge no longer starts under a made-up 'default' user, and a\n * start after a team switch replaces the bridge that still synced to the old\n * team (the old switch-time stop went away with the app's switchTenant, df7f6f72).\n */\n\nexport interface CapabilityState {\n id: string;\n name: string;\n description: string;\n reason: string;\n icon: string;\n requiresPermission: boolean;\n /** User's choice in the host's UI */\n enabled: boolean;\n /** OS-level permission status */\n osPermission: PermissionState;\n}\n\ninterface CapabilitiesState {\n capabilities: CapabilityState[];\n /** The \"Camera pictures to AI\" switch under Camera: the person's half of the\n * consent props.aiLive needs. Off until they turn it on (default no). */\n picturesToAi: boolean;\n hasCompletedSetup: boolean;\n isLoading: boolean;\n bridgeRunning: boolean;\n\n /** Initialize — load built-in capabilities from device-bridge + saved user preferences */\n initialize: () => Promise<void>;\n /** Toggle a capability on/off, persist, and update bridge if running */\n toggleCapability: (id: string, enabled: boolean) => Promise<void>;\n /** Set and persist \"Camera pictures to AI\". The host asks first (PICTURES_TO_AI_ASK). */\n setPicturesToAi: (on: boolean) => Promise<void>;\n /** Update OS permission state for a capability */\n setOsPermission: (id: string, state: PermissionState) => void;\n /** Mark initial setup as complete */\n completeSetup: () => Promise<void>;\n /**\n * Start the device bridge for the signed-in user and team (call after setup is\n * done, and again whenever the team or user changes). Safe to call often: calls\n * run one at a time, a bridge already running for this session is kept, and one\n * running for another team or user is replaced.\n */\n startBridge: () => Promise<void>;\n /** Stop the device bridge (call on sign-out; a team switch is a startBridge() call) */\n stopBridge: () => void;\n /** Force an immediate sync of all permitted capabilities. */\n syncNow: () => Promise<{ ok: boolean; error?: string }>;\n}\n\n// Module-level bridge singleton — NOT in Zustand (it's a mutable object with timers)\nlet bridge: DeviceBridge | null = null;\n\n// Who the running bridge syncs as, so a start after a team switch replaces it.\nlet bridgeOwner: { tenant: string; actorId: string } | null = null;\n\n// Starts run one at a time (the host's effect and a toggle can ask together).\nlet startQueue: Promise<void> = Promise.resolve();\n\n// Bumped by stopBridge: a start that was waiting when the bridge stopped (sign-out\n// mid-start) gives up instead of syncing as the user who just left.\nlet startGeneration = 0;\n\n// Cache the built-in Capability objects (with collect/permission functions)\nlet builtInCapabilities: Capability[] = [];\n\n// The foreground battery-refresh is registered with the SDK's foreground\n// coordinator (onAppForeground) instead of a private AppState listener — one\n// signal, deliberate order. iOS pauses JS timers in the background, so the\n// periodic battery poll goes stale; pushing on foreground guarantees a fresh\n// reading the moment the user opens the app. Kept at module scope so stopBridge\n// can unsubscribe it cleanly.\nlet foregroundUnsub: (() => void) | null = null;\n\nexport const useCapabilitiesStore = create<CapabilitiesState>((set, get) => {\n const runStart = async (): Promise<void> => {\n // Tenant and user come from the SDK session — the token's claims, the same\n // ones every request acts on. No session, no sync: device data is scoped per\n // user on the relay, so it never goes under a guessed or shared name.\n const tenant = getTenant();\n const actorId = getUser()?.actorId;\n\n if (bridge) {\n if (bridgeOwner?.tenant === tenant && bridgeOwner?.actorId === actorId) {\n console.log('[Capabilities] Bridge already running');\n return;\n }\n console.log('[Capabilities] Team or user changed — restarting bridge');\n get().stopBridge();\n }\n\n const { capabilities: caps } = get();\n const enabledCaps = caps.filter(c => c.enabled);\n\n if (enabledCaps.length === 0) {\n console.log('[Capabilities] No capabilities enabled — bridge not started');\n return;\n }\n\n const config = getDeviceSyncConfig();\n if (!config) {\n console.error('[Capabilities] Bridge not started: device sync is not configured — pass deviceSync to bootstrapExpoAdas (or call configureDeviceSync)');\n return;\n }\n\n if (!tenant || !actorId) {\n console.error(`[Capabilities] Bridge not started: no signed-in ${!tenant ? 'tenant' : 'user'}`);\n return;\n }\n\n const generation = startGeneration;\n\n // Get or generate a persistent unique device ID for this install\n const deviceId = await getOrCreateDeviceId(config.secureStorage);\n if (generation !== startGeneration) return;\n\n console.log(`[Capabilities] Starting bridge for tenant=${tenant} actor=${actorId} device=${deviceId} with ${enabledCaps.length} capabilities: ${enabledCaps.map(c => c.id).join(', ')}`);\n\n const started = new DeviceBridge({\n relayUrl: config.relayUrl(),\n deviceId,\n tenant,\n actorId,\n // RelayClient sends `Authorization: Bearer <apiKey>` so the relay's\n // auth gate accepts the call. Empty string = misconfig → relay returns\n // 401, surfaced as a sync error in the UI.\n apiKey: config.relayKey,\n onStatusChange: (status) => {\n // Sync OS permission states back to store\n for (const capStatus of status.capabilities) {\n const current = get().capabilities.find(c => c.id === capStatus.id);\n if (current && current.osPermission !== capStatus.permission) {\n get().setOsPermission(capStatus.id, capStatus.permission);\n }\n }\n },\n onError: (err) => {\n console.warn(`[device-bridge] ${err.domain}: ${err.message}`);\n },\n });\n bridge = started;\n bridgeOwner = { tenant, actorId };\n\n try {\n // Register ONLY user-enabled capabilities\n const enabledIds = new Set(enabledCaps.map(c => c.id));\n const capsToRegister = builtInCapabilities.filter(c => enabledIds.has(c.id));\n started.registerAll(capsToRegister);\n\n // Request OS permissions for capabilities that need them\n await started.requestAllPermissions();\n if (generation !== startGeneration) return; // stopBridge already stopped it\n\n // Update OS permission states in store\n const statuses = started.listCapabilities();\n set(state => ({\n bridgeRunning: true,\n capabilities: state.capabilities.map(c => {\n const status = statuses.find(s => s.id === c.id);\n return status ? { ...c, osPermission: status.permission } : c;\n }),\n }));\n\n await started.startSync();\n if (generation !== startGeneration) {\n // Stopped while starting: stop again, in case sync started after that stop.\n started.stopSync();\n return;\n }\n console.log('[Capabilities] Bridge started and syncing');\n } catch (err) {\n // A failed start must not stay recorded as \"already running\" — the next\n // start would keep answering that and never retry.\n if (bridge === started) get().stopBridge();\n throw err;\n }\n\n // Foreground refresh: force an immediate battery sync when the app returns\n // to the foreground, so the relay reflects the real level instead of the\n // stale value the suspended polling timer left behind. Registered with the\n // SDK coordinator (fires after the ACS reconnect) — only once.\n if (!foregroundUnsub) {\n foregroundUnsub = onAppForeground(() => {\n bridge?.syncCapabilityNow('battery').catch((err) => {\n console.warn('[Capabilities] foreground battery sync failed:', err);\n });\n });\n }\n\n // The periodic background-fetch task is owned by the bridge itself —\n // DeviceBridge.startSync() registers it (from its own config) and stopSync()\n // UNREGISTERS it, so the OS task can't keep firing with a stale user's\n // config after logout/tenant-switch.\n };\n\n return {\n capabilities: [],\n picturesToAi: false,\n hasCompletedSetup: false,\n isLoading: true,\n bridgeRunning: false,\n\n initialize: async () => {\n try {\n // Get capability definitions FROM device-bridge — single source of truth.\n // Filter out capabilities whose user toggle is owned by another screen\n // (e.g. notifications → the host's notification settings).\n builtInCapabilities = capFactories.allBuiltIn().filter(c => !HIDDEN_FROM_UI.has(c.id));\n\n // Saved user choices (override the defaults for returning users)\n const { setupDone, prefs } = await loadChoices();\n const picturesToAi = (await AsyncStorage.getItem(PICTURES_TO_AI_KEY)) === 'true';\n\n // Map to UI state (no functions, just data + enabled flag)\n const capStates: CapabilityState[] = builtInCapabilities.map(cap => ({\n id: cap.id,\n name: cap.name,\n description: cap.description,\n reason: cap.reason,\n icon: cap.icon || cap.id,\n requiresPermission: cap.requiresPermission,\n enabled: isEnabled(cap.id, prefs),\n osPermission: cap.permissionState,\n }));\n\n set({\n capabilities: capStates,\n picturesToAi,\n hasCompletedSetup: setupDone,\n isLoading: false,\n });\n\n console.log(`[Capabilities] Initialized ${capStates.length} capabilities from device-bridge (setup ${setupDone ? 'done' : 'pending'})`);\n } catch (err) {\n console.error('[Capabilities] Initialize failed:', err);\n set({ isLoading: false });\n }\n },\n\n toggleCapability: async (id: string, enabled: boolean) => {\n // Update UI state\n set(state => ({\n capabilities: state.capabilities.map(cap =>\n cap.id === id ? { ...cap, enabled } : cap\n ),\n }));\n\n // Persist all preferences\n const prefs: Record<string, boolean> = {};\n for (const cap of get().capabilities) {\n prefs[cap.id] = cap.id === id ? enabled : cap.enabled;\n }\n await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));\n\n // Turning the camera off also clears \"Camera pictures to AI\", so turning the\n // camera back on later never silently re-opens that door: the person is asked\n // again. (While the camera is off the consent already reads as no.)\n if (id === 'camera' && !enabled && get().picturesToAi) {\n await get().setPicturesToAi(false);\n }\n\n // If bridge is running, dynamically update\n if (bridge && get().bridgeRunning) {\n if (enabled) {\n // Register + request permission for newly enabled capability\n const builtIn = builtInCapabilities.find(c => c.id === id);\n if (builtIn) {\n bridge.register(builtIn);\n const granted = await bridge.requestPermission(id);\n set(state => ({\n capabilities: state.capabilities.map(c =>\n c.id === id ? { ...c, osPermission: granted ? 'granted' : 'denied' } : c\n ),\n }));\n }\n } else {\n // Stop and restart bridge without this capability\n console.log(`[Capabilities] ${id} disabled — restarting bridge`);\n get().stopBridge();\n await get().startBridge();\n }\n }\n },\n\n setPicturesToAi: async (on: boolean) => {\n set({ picturesToAi: on });\n await AsyncStorage.setItem(PICTURES_TO_AI_KEY, on ? 'true' : 'false');\n },\n\n setOsPermission: (id: string, permState: PermissionState) => {\n set(s => ({\n capabilities: s.capabilities.map(cap =>\n cap.id === id ? { ...cap, osPermission: permState } : cap\n ),\n }));\n },\n\n completeSetup: async () => {\n await AsyncStorage.setItem(SETUP_KEY, 'true');\n set({ hasCompletedSetup: true });\n },\n\n startBridge: () => {\n const run = startQueue.then(runStart);\n startQueue = run.catch((err) => {\n console.error('[Capabilities] Bridge start failed:', err);\n });\n return run;\n },\n\n stopBridge: () => {\n startGeneration++;\n if (bridge) {\n bridge.stopSync();\n bridge = null;\n bridgeOwner = null;\n set({ bridgeRunning: false });\n console.log('[Capabilities] Bridge stopped');\n }\n if (foregroundUnsub) {\n foregroundUnsub();\n foregroundUnsub = null;\n }\n },\n\n syncNow: async () => {\n if (!bridge) return { ok: false, error: 'bridge not running' };\n try {\n await bridge.syncNow();\n // bridge.syncNow uses Promise.allSettled internally so it never throws.\n // Read per-capability status to find any actual failures we should\n // surface — particularly for health, which pushes large bundles and\n // can time out / partial-fail without bubbling.\n const statuses = bridge.listCapabilities();\n const failed = statuses.filter(s =>\n s.sync?.state === 'error' || s.sync?.state === 'syncing',\n );\n if (failed.length > 0) {\n const summary = failed\n .map(s => `${s.id}:${s.sync?.state}${s.sync?.error ? `(${s.sync.error})` : ''}`)\n .join(', ');\n return { ok: false, error: summary };\n }\n return { ok: true };\n } catch (e: any) {\n return { ok: false, error: e?.message || String(e) };\n }\n },\n };\n});\n\n// The person's half of \"camera pictures to AI\", for plugin-runtime's mayPicturesLeavePhone():\n// read from this store at CALL time (derive, never store). A host that never loads this store\n// has no consent provider, and the answer is no.\nsetPicturesToAiConsentProvider(() => picturesToAiAllowed(useCapabilitiesStore.getState()));\n// Any change the answer reads (the switch, the camera's own switch, the OS permission the\n// foreground refresh re-reads) is announced, so a session that is sending re-asks at once and\n// stops on a no (GPT review 2026-10-30). Announced on every change: the listener re-asks the\n// derived answer, so nothing is compared or cached here.\nuseCapabilitiesStore.subscribe(() => notifyPicturesToAiConsentChanged());\n// The camera permission is the OS's, and it can be taken away in iOS Settings while the app is in\n// the background, where nothing in this store changes. So it is read again every time the app\n// comes back (Codex P1 on adas-mobile-sdk#53): a change lands in the store, and the subscription\n// above announces it.\nexport async function recheckCameraPermission(): Promise<void> {\n const cam = builtInCapabilities.find((c) => c.id === 'camera');\n if (!cam) return; // not initialised: no camera state to correct\n let granted = false;\n try { granted = (await cam.checkPermission()) === true; } catch { granted = false; }\n const current = useCapabilitiesStore.getState().capabilities.find((c) => c.id === 'camera');\n if (!current) return;\n if (granted && current.osPermission !== 'granted') useCapabilitiesStore.getState().setOsPermission('camera', 'granted');\n if (!granted && current.osPermission === 'granted') useCapabilitiesStore.getState().setOsPermission('camera', 'denied');\n}\nonAppForeground(() => { void recheckCameraPermission(); });\n","/**\n * \"Camera pictures to AI\" — the phone owner's half of a two-sided consent\n * (owner decision 2026-10-25, amending \"frames never leave the phone\").\n *\n * A camera picture may go to an AI service off the phone only when the\n * solution's developer declared `picturesToAi` in its manifest AND the person on\n * this phone allowed it here. plugin-runtime's mayPicturesLeavePhone() checks\n * both; this half is installed as its consent provider by the capabilities store.\n *\n * Moved from ateam-mobile (branch pictures-to-ai-switch, 9541f22) into the SDK\n * with the store it belongs to. Pure, so tests run it under plain node.\n */\n\nexport interface PicturesToAiInputs {\n /** The switch under Camera in the host's \"What Ada can access\". */\n picturesToAi: boolean;\n capabilities: ReadonlyArray<{ id: string; enabled: boolean; osPermission: string }>;\n}\n\n/** Derived at read time, never stored: the switch is on AND the camera itself is\n * on and granted by the OS. Anything else answers no. */\nexport function picturesToAiAllowed(s: PicturesToAiInputs): boolean {\n if (s.picturesToAi !== true) return false;\n const camera = s.capabilities.find((c) => c.id === 'camera');\n return !!camera && camera.enabled === true && camera.osPermission === 'granted';\n}\n\n/** What the person is told before allowing it. Apple guideline 5.1.2(i): say\n * where personal data goes before sharing it with an outside AI, and get\n * explicit permission first. */\nexport const PICTURES_TO_AI_ASK = {\n title: 'Send camera pictures to AI?',\n message:\n 'Solutions that ask for it may send pictures from your camera to an AI service ' +\n '(OpenAI or Anthropic) through A-Team, to understand what the camera sees. ' +\n 'Only while such a solution is open, and \"Sending to AI\" shows while it does. ' +\n 'You can turn this off here at any time.',\n allow: 'Allow',\n cancel: 'Not now',\n} as const;\n","import { useState, type ReactNode } from 'react';\nimport {\n View,\n Text,\n ScrollView,\n Pressable,\n StyleSheet,\n ActivityIndicator,\n SafeAreaView,\n Platform,\n} from 'react-native';\nimport { ada, borderRadius, fontSize, spacing } from '@ateam-ai-mobile/core';\nimport { useCapabilitiesStore } from './store';\nimport CapabilityCard from './CapabilityCard';\n\nconst fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;\n\nexport interface CapabilitiesSetupProps {\n /** Called after the user continues or skips; the choices are already saved. */\n onComplete: () => void;\n /** Drawn above the title — e.g. the host's logo. */\n header?: ReactNode;\n title?: string;\n subtitle?: string;\n /** Shown while the saved choices load. Default: a spinner. */\n loading?: ReactNode;\n}\n\n/**\n * Drop-in first-run screen: the user picks what this device shares, then\n * continues (or skips, which turns everything off). Reads and writes\n * useCapabilitiesStore; the host starts the bridge once setup is complete.\n */\nexport default function CapabilitiesSetup({\n onComplete,\n header,\n title = 'What can the assistant use?',\n subtitle = \"Choose what it can see on this device. You're in charge — change it anytime in settings.\",\n loading,\n}: CapabilitiesSetupProps) {\n const {\n capabilities,\n toggleCapability,\n completeSetup,\n isLoading,\n } = useCapabilitiesStore();\n\n const [requesting, setRequesting] = useState(false);\n\n const enabledCount = capabilities.filter(c => c.enabled).length;\n const permissionCount = capabilities.filter(c => c.enabled && c.requiresPermission).length;\n\n const handleContinue = async () => {\n setRequesting(true);\n await completeSetup();\n setRequesting(false);\n onComplete();\n };\n\n const handleSkip = async () => {\n for (const cap of capabilities) {\n if (cap.enabled) {\n await toggleCapability(cap.id, false);\n }\n }\n await completeSetup();\n onComplete();\n };\n\n if (isLoading) {\n return (\n <SafeAreaView style={styles.center}>\n {loading ?? <ActivityIndicator size=\"large\" color={ada.inkMuted} />}\n </SafeAreaView>\n );\n }\n\n return (\n <SafeAreaView style={styles.container}>\n <ScrollView contentContainerStyle={styles.content}>\n <View style={styles.header}>\n {header ? <View style={styles.heroRow}>{header}</View> : null}\n <Text style={styles.title}>{title}</Text>\n <Text style={styles.subtitle}>{subtitle}</Text>\n </View>\n\n <View style={styles.section}>\n <Text style={styles.sectionTitle}>NEEDS YOUR PERMISSION</Text>\n {capabilities.filter(c => c.requiresPermission).map(cap => (\n <CapabilityCard\n key={cap.id}\n capability={cap}\n onToggle={toggleCapability}\n />\n ))}\n </View>\n\n <View style={styles.section}>\n <Text style={styles.sectionTitle}>ALWAYS AVAILABLE</Text>\n {capabilities.filter(c => !c.requiresPermission).map(cap => (\n <CapabilityCard\n key={cap.id}\n capability={cap}\n onToggle={toggleCapability}\n />\n ))}\n </View>\n\n <View style={styles.summaryBox}>\n <Text style={styles.summaryText}>\n {enabledCount} of {capabilities.length} on\n {permissionCount > 0 && ` · ${permissionCount} will ask for permission`}\n </Text>\n </View>\n </ScrollView>\n\n <View style={styles.footer}>\n <Pressable\n style={[styles.continueBtn, requesting && styles.continueBtnDisabled]}\n onPress={handleContinue}\n disabled={requesting}\n >\n {requesting ? (\n <ActivityIndicator size=\"small\" color={ada.paper} />\n ) : (\n <Text style={styles.continueBtnText}>\n {enabledCount > 0 ? 'Continue' : 'Continue without these'}\n </Text>\n )}\n </Pressable>\n\n <Pressable style={styles.skipBtn} onPress={handleSkip}>\n <Text style={styles.skipBtnText}>Skip for now</Text>\n </Pressable>\n </View>\n </SafeAreaView>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n backgroundColor: ada.paper,\n },\n center: {\n flex: 1,\n justifyContent: 'center',\n alignItems: 'center',\n backgroundColor: ada.paper,\n },\n content: {\n paddingBottom: 20,\n },\n header: {\n paddingHorizontal: spacing.xl,\n paddingTop: 20,\n paddingBottom: spacing.lg,\n },\n heroRow: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 10,\n marginBottom: spacing.lg,\n },\n title: {\n color: ada.ink,\n fontSize: 28,\n fontWeight: '700',\n letterSpacing: -0.5,\n marginBottom: 8,\n fontFamily: fontFam,\n },\n subtitle: {\n color: ada.inkSoft,\n fontSize: fontSize.md,\n lineHeight: 22,\n letterSpacing: -0.1,\n fontFamily: fontFam,\n },\n section: {\n paddingHorizontal: spacing.lg,\n marginBottom: spacing.lg,\n },\n sectionTitle: {\n color: ada.inkMuted,\n fontSize: 11,\n fontWeight: '700',\n letterSpacing: 1.4,\n marginBottom: spacing.sm,\n paddingHorizontal: 4,\n fontFamily: fontFam,\n },\n summaryBox: {\n marginHorizontal: spacing.lg,\n backgroundColor: ada.orangeSoft,\n borderRadius: borderRadius.md,\n padding: spacing.md,\n alignItems: 'center',\n },\n summaryText: {\n color: ada.orangeDeep,\n fontSize: fontSize.sm,\n fontWeight: '600',\n fontFamily: fontFam,\n letterSpacing: -0.1,\n },\n footer: {\n paddingHorizontal: spacing.lg,\n paddingVertical: spacing.lg,\n borderTopWidth: 1,\n borderTopColor: ada.border,\n gap: spacing.sm,\n },\n continueBtn: {\n backgroundColor: ada.ink,\n borderRadius: 999,\n paddingVertical: 16,\n alignItems: 'center',\n },\n continueBtnDisabled: {\n opacity: 0.6,\n },\n continueBtnText: {\n color: ada.paper,\n fontSize: fontSize.lg,\n fontWeight: '600',\n fontFamily: fontFam,\n letterSpacing: -0.2,\n },\n skipBtn: {\n paddingVertical: 12,\n alignItems: 'center',\n },\n skipBtnText: {\n color: ada.inkMuted,\n fontSize: fontSize.md,\n fontFamily: fontFam,\n },\n});\n","import { View, Text, Switch, Pressable, StyleSheet, Linking, Platform } from 'react-native';\nimport { ada, borderRadius, fontSize, spacing } from '@ateam-ai-mobile/core';\nimport type { CapabilityState } from './store';\n\nconst fontFam = Platform.OS === 'ios' ? 'SF Pro Rounded' : undefined;\n\nconst ICONS: Record<string, string> = {\n calendar: '📅',\n contacts: '👥',\n location: '📍',\n battery: '🔋',\n wifi: '📶',\n notifications: '🔔',\n};\n\nexport interface CapabilityCardProps {\n capability: CapabilityState;\n onToggle: (id: string, enabled: boolean) => void;\n}\n\n/** One capability with its on/off switch and OS-permission badge. */\nexport default function CapabilityCard({ capability, onToggle }: CapabilityCardProps) {\n const { id, name, description, reason, icon, enabled, osPermission, requiresPermission } = capability;\n\n const isDenied = osPermission === 'denied';\n const isGranted = osPermission === 'granted' || osPermission === 'not_needed';\n\n const handleToggle = (value: boolean) => {\n if (isDenied && value) {\n Linking.openSettings();\n return;\n }\n onToggle(id, value);\n };\n\n return (\n <View style={[styles.card, enabled && styles.cardEnabled]}>\n <View style={styles.row}>\n <View style={[styles.iconBox, enabled && styles.iconBoxEnabled]}>\n <Text style={styles.iconText}>{ICONS[icon] || '⚙️'}</Text>\n </View>\n\n <View style={styles.info}>\n <Text style={styles.name}>{name}</Text>\n <Text style={styles.description}>{description}</Text>\n </View>\n\n <Switch\n value={enabled}\n onValueChange={handleToggle}\n trackColor={{ false: ada.border, true: ada.orange }}\n thumbColor=\"#fff\"\n ios_backgroundColor={ada.border}\n />\n </View>\n\n {enabled && (\n <Text style={styles.reason}>{reason}</Text>\n )}\n\n {enabled && requiresPermission && (\n <View style={styles.statusRow}>\n {isDenied ? (\n <Pressable onPress={() => Linking.openSettings()}>\n <View style={styles.deniedBadge}>\n <Text style={styles.deniedText}>Blocked — tap to open Settings</Text>\n </View>\n </Pressable>\n ) : isGranted ? (\n <View style={styles.grantedBadge}>\n <Text style={styles.grantedText}>Active</Text>\n </View>\n ) : (\n <View style={styles.pendingBadge}>\n <Text style={styles.pendingText}>Will ask permission</Text>\n </View>\n )}\n </View>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n card: {\n backgroundColor: ada.paperSoft,\n borderRadius: borderRadius.lg,\n borderWidth: 1,\n borderColor: ada.border,\n padding: spacing.lg,\n marginBottom: spacing.md,\n },\n cardEnabled: {\n borderColor: ada.orange,\n borderWidth: 1.5,\n },\n row: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: spacing.md,\n },\n iconBox: {\n width: 44,\n height: 44,\n borderRadius: 12,\n backgroundColor: ada.paperDeep,\n justifyContent: 'center',\n alignItems: 'center',\n },\n iconBoxEnabled: {\n backgroundColor: ada.orangeSoft,\n },\n iconText: {\n fontSize: 20,\n },\n info: {\n flex: 1,\n },\n name: {\n color: ada.ink,\n fontSize: fontSize.lg,\n fontWeight: '600',\n fontFamily: fontFam,\n letterSpacing: -0.2,\n },\n description: {\n color: ada.inkSoft,\n fontSize: fontSize.sm,\n marginTop: 2,\n lineHeight: 18,\n fontFamily: fontFam,\n letterSpacing: -0.05,\n },\n reason: {\n color: ada.inkMuted,\n fontSize: fontSize.xs,\n marginTop: spacing.sm,\n marginLeft: 44 + spacing.md,\n lineHeight: 16,\n fontFamily: fontFam,\n },\n statusRow: {\n marginTop: spacing.sm,\n marginLeft: 44 + spacing.md,\n },\n grantedBadge: {\n backgroundColor: 'rgba(90, 138, 92, 0.14)',\n borderRadius: borderRadius.sm,\n paddingHorizontal: spacing.sm,\n paddingVertical: 3,\n alignSelf: 'flex-start',\n },\n grantedText: {\n color: '#5A8A5C',\n fontSize: fontSize.xs,\n fontWeight: '700',\n fontFamily: fontFam,\n letterSpacing: 0.2,\n },\n pendingBadge: {\n backgroundColor: ada.orangeSoft,\n borderRadius: borderRadius.sm,\n paddingHorizontal: spacing.sm,\n paddingVertical: 3,\n alignSelf: 'flex-start',\n },\n pendingText: {\n color: ada.orangeDeep,\n fontSize: fontSize.xs,\n fontWeight: '700',\n fontFamily: fontFam,\n letterSpacing: 0.2,\n },\n deniedBadge: {\n backgroundColor: 'rgba(193, 85, 69, 0.14)',\n borderRadius: borderRadius.sm,\n paddingHorizontal: spacing.sm,\n paddingVertical: 3,\n alignSelf: 'flex-start',\n },\n deniedText: {\n color: '#C15545',\n fontSize: fontSize.xs,\n fontWeight: '700',\n fontFamily: fontFam,\n letterSpacing: 0.2,\n },\n});\n"],"mappings":"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","names":["View","Text","Pressable","StyleSheet","Platform","ada","borderRadius","fontSize","spacing","jsx","jsxs","fontFam","Platform","styles","ada","View","Text","Pressable","StyleSheet","spacing","fontSize","borderRadius"]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
RelayClient,
|
|
3
3
|
registerBackgroundSync,
|
|
4
4
|
unregisterBackgroundSync
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GGEFDRQQ.mjs";
|
|
6
6
|
import {
|
|
7
7
|
collectHistory,
|
|
8
8
|
collectToday
|
|
@@ -812,4 +812,4 @@ export {
|
|
|
812
812
|
scanner_exports,
|
|
813
813
|
DeviceBridge
|
|
814
814
|
};
|
|
815
|
-
//# sourceMappingURL=chunk-
|
|
815
|
+
//# sourceMappingURL=chunk-4HJ5FO3S.mjs.map
|
|
@@ -880,7 +880,7 @@ function cameraCapability() {
|
|
|
880
880
|
return {
|
|
881
881
|
id: "camera",
|
|
882
882
|
name: "Camera",
|
|
883
|
-
description:
|
|
883
|
+
description: `Lets ada see through your camera to guide you \u2014 reading text and describing what's in front of you. Frames are analysed on the phone and nothing is stored; nothing is uploaded unless you also turn on "Camera pictures to AI".`,
|
|
884
884
|
reason: "Powers visual guidance (Ada Guide): a live view with on-device text and scene understanding.",
|
|
885
885
|
icon: "camera",
|
|
886
886
|
requiresPermission: true,
|
|
@@ -1006,4 +1006,4 @@ export {
|
|
|
1006
1006
|
unregisterBackgroundSync,
|
|
1007
1007
|
syncDomains
|
|
1008
1008
|
};
|
|
1009
|
-
//# sourceMappingURL=chunk-
|
|
1009
|
+
//# sourceMappingURL=chunk-GGEFDRQQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/device-bridge/relay-client.ts","../src/device-bridge/collectors/location.ts","../src/device-bridge/collectors/connectivity.ts","../src/device-bridge/capabilities.ts","../src/device-bridge/collectors/calendar.ts","../src/device-bridge/collectors/contacts.ts","../src/device-bridge/collectors/battery.ts","../src/device-bridge/collectors/notifications.ts","../src/device-bridge/collectors/mood.ts","../src/device-bridge/collectors/camera.ts","../src/device-bridge/background.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 * 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 * 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 * 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 and nothing is stored; nothing is uploaded ' +\n 'unless you also turn on \"Camera pictures to AI\".',\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 * 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 * 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 * 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"],"mappings":"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","names":["hasPermission","requestPermission","requestPermission","hasPermission","requestPermission","hasPermission","collectToday","requestPermission","hasPermission","requestPermission","hasPermission","collectToday","hasPermission","requestPermission","requestPermission","hasPermission","collectToday"]}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
biometrics_exports,
|
|
17
17
|
scanner_exports,
|
|
18
18
|
sharing_exports
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-4HJ5FO3S.mjs";
|
|
20
20
|
import {
|
|
21
21
|
coordinateArg,
|
|
22
22
|
countArg,
|
|
@@ -644,4 +644,4 @@ export {
|
|
|
644
644
|
clearAllPluginData,
|
|
645
645
|
createDeviceProvider
|
|
646
646
|
};
|
|
647
|
-
//# sourceMappingURL=chunk-
|
|
647
|
+
//# sourceMappingURL=chunk-NH3CHSLR.mjs.map
|