@ateam-ai-mobile/expo 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap.js +841 -323
- package/dist/bootstrap.js.map +1 -1
- package/dist/bootstrap.mjs +9 -7
- package/dist/bootstrap.mjs.map +1 -1
- package/dist/chunk-7FWYEHN5.mjs +203 -0
- package/dist/chunk-7FWYEHN5.mjs.map +1 -0
- package/dist/{chunk-4XYXJYN5.mjs → chunk-C7M5WAHA.mjs} +583 -298
- package/dist/chunk-C7M5WAHA.mjs.map +1 -0
- package/dist/{chunk-5HM6UA2K.mjs → chunk-GF2MPTAB.mjs} +83 -7
- package/dist/chunk-GF2MPTAB.mjs.map +1 -0
- package/dist/chunk-J6XRNX7P.mjs +19 -0
- package/dist/chunk-J6XRNX7P.mjs.map +1 -0
- package/dist/{chunk-TZXWVYYR.mjs → chunk-PCAEUX2N.mjs} +1 -1
- package/dist/chunk-PCAEUX2N.mjs.map +1 -0
- package/dist/{chunk-VQJPSLHP.mjs → chunk-Z5AI2SXX.mjs} +13 -4
- package/dist/chunk-Z5AI2SXX.mjs.map +1 -0
- package/dist/createBridge.js +41 -10
- package/dist/createBridge.js.map +1 -1
- package/dist/createBridge.mjs +5 -1
- package/dist/createBridge.mjs.map +1 -1
- package/dist/device/createDeviceProvider.js +278 -17
- package/dist/device/createDeviceProvider.js.map +1 -1
- package/dist/device/createDeviceProvider.mjs +5 -3
- package/dist/device-bridge/index.d.mts +13 -1
- package/dist/device-bridge/index.d.ts +13 -1
- package/dist/device-bridge/index.js +49 -12
- package/dist/device-bridge/index.js.map +1 -1
- package/dist/device-bridge/index.mjs +3 -1
- package/dist/index.d.mts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +594 -297
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/maps/index.d.mts +56 -0
- package/dist/maps/index.d.ts +56 -0
- package/dist/maps/index.js +95 -0
- package/dist/maps/index.js.map +1 -0
- package/dist/maps/index.mjs +17 -0
- package/dist/maps/index.mjs.map +1 -0
- package/dist/vision/index.d.mts +4 -2
- package/dist/vision/index.d.ts +4 -2
- package/dist/vision/index.js.map +1 -1
- package/dist/vision/index.mjs +1 -1
- package/expo-module.config.json +1 -1
- package/ios/AdaMaps/AdaMapsModule.swift +180 -0
- package/ios/AdaMaps.podspec +25 -0
- package/ios/AdaVision.podspec +4 -0
- package/ios/AdaVisionModule.swift +63 -9
- package/package.json +10 -4
- package/src/args.ts +56 -0
- package/src/createBridge.ts +3 -1
- package/src/device/createDeviceProvider.ts +88 -3
- package/src/device-bridge/collectors/location.ts +21 -9
- package/src/device-state/collectors.ts +5 -1
- package/src/device-state/domains/deviceContext.ts +5 -3
- package/src/device-state/domains/interaction.ts +2 -1
- package/src/device-state/domains/spatial.ts +200 -116
- package/src/device-state/domains/visual.ts +34 -13
- package/src/device-state/events.ts +49 -0
- package/src/device-state/headingSource.ts +43 -8
- package/src/device-state/index.ts +3 -2
- package/src/device-state/replay.ts +76 -11
- package/src/device-state/spatialEvents.ts +300 -0
- package/src/geo.ts +23 -0
- package/src/maps/index.ts +81 -0
- package/src/maps/normalize.ts +225 -0
- package/src/vision/index.ts +4 -2
- package/dist/chunk-4XYXJYN5.mjs.map +0 -1
- package/dist/chunk-5HM6UA2K.mjs.map +0 -1
- package/dist/chunk-TZXWVYYR.mjs.map +0 -1
- package/dist/chunk-VQJPSLHP.mjs.map +0 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Pure shaping for native.maps — no Expo, no React Native, so it runs under
|
|
2
|
+
// plain `node --test` (tests/adaMapsNormalize.test.mjs pins it).
|
|
3
|
+
//
|
|
4
|
+
// The native side hands back loose dictionaries — AdaMaps (MapKit) for search
|
|
5
|
+
// and route, expo-location's geocoder for reverse geocoding. This turns them
|
|
6
|
+
// into the typed MapsAPI shapes, and one set of failure codes. Unknown is
|
|
7
|
+
// `null`, never '' or 0: an empty street reads as a street with no name, and a
|
|
8
|
+
// 0 m route reads as "you have arrived".
|
|
9
|
+
//
|
|
10
|
+
// It also checks what a caller sends BEFORE it crosses the bridge (searchArgs,
|
|
11
|
+
// coordinateArg). A remote bundle is plain JS at runtime, so the types promise
|
|
12
|
+
// nothing: a NaN or Infinity `limit` traps Swift's Int conversion and kills the
|
|
13
|
+
// app, and a `{ lat, lng }` near would search around 0,0 and answer "found
|
|
14
|
+
// nothing" with confidence.
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
MapsAddress,
|
|
18
|
+
MapsCoordinate,
|
|
19
|
+
MapsPlace,
|
|
20
|
+
MapsRoute,
|
|
21
|
+
MapsRouteStep,
|
|
22
|
+
} from '@ateam-ai-mobile/plugin-runtime';
|
|
23
|
+
import { distanceOf } from '../geo';
|
|
24
|
+
import { countArg, refuseArgument as refuse, shown, type ArgumentError } from '../args';
|
|
25
|
+
|
|
26
|
+
/** An address as the native geocoders spell it. AdaMaps sends exactly these
|
|
27
|
+
* keys; expo-location sends these plus `subregion`/`timezone`/
|
|
28
|
+
* `formattedAddress`, which MapsAddress does not carry. */
|
|
29
|
+
export interface RawMapsAddress {
|
|
30
|
+
name?: string | null;
|
|
31
|
+
street?: string | null;
|
|
32
|
+
streetNumber?: string | null;
|
|
33
|
+
district?: string | null;
|
|
34
|
+
city?: string | null;
|
|
35
|
+
region?: string | null;
|
|
36
|
+
postalCode?: string | null;
|
|
37
|
+
country?: string | null;
|
|
38
|
+
isoCountryCode?: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** One AdaMaps search hit (AdaMapsModule.place). */
|
|
42
|
+
export interface RawMapsPlace {
|
|
43
|
+
name: string | null;
|
|
44
|
+
latitude: number;
|
|
45
|
+
longitude: number;
|
|
46
|
+
/** MKPointOfInterestCategory raw value, e.g. "MKPOICategoryPharmacy". */
|
|
47
|
+
category: string | null;
|
|
48
|
+
address: RawMapsAddress;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** An AdaMaps walking route (AdaMapsModule.route) — distances in metres,
|
|
52
|
+
* expectedTravelTime in SECONDS, as MapKit reports them. */
|
|
53
|
+
export interface RawMapsRoute {
|
|
54
|
+
distance: number;
|
|
55
|
+
expectedTravelTime: number;
|
|
56
|
+
steps: Array<{ instructions: string | null; distance: number; notice: string | null; polyline: MapsCoordinate[] }>;
|
|
57
|
+
polyline: MapsCoordinate[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The three ways a maps call can fail to look — see MapsAPI. */
|
|
61
|
+
export type MapsFailureCode = 'UNAVAILABLE' | 'OFFLINE' | 'THROTTLED';
|
|
62
|
+
|
|
63
|
+
/** A malformed call, refused before anything is sent. Not a fourth way to fail
|
|
64
|
+
* to look: retrying will not help, fixing the call will. */
|
|
65
|
+
export type MapsArgumentError = ArgumentError;
|
|
66
|
+
|
|
67
|
+
const SEARCH_RADIUS_DEFAULT_M = 3000;
|
|
68
|
+
/** Keeps the region MapKit is handed a small, valid one. A "near" search wider
|
|
69
|
+
* than this is a different question. */
|
|
70
|
+
const SEARCH_RADIUS_MAX_M = 100_000;
|
|
71
|
+
const SEARCH_LIMIT_DEFAULT = 8;
|
|
72
|
+
const SEARCH_LIMIT_MAX = 25;
|
|
73
|
+
|
|
74
|
+
const text = (v: unknown): string | null =>
|
|
75
|
+
typeof v === 'string' && v.trim() !== '' ? v : null;
|
|
76
|
+
|
|
77
|
+
const refuseArgument = (verb: string, problem: string, nothing: string): MapsArgumentError =>
|
|
78
|
+
refuse('maps', verb, problem, nothing);
|
|
79
|
+
|
|
80
|
+
/** A caller's coordinate, checked and copied to exactly `{ latitude, longitude }`.
|
|
81
|
+
* Anything else — `{ lat, lng }`, strings, NaN, out of range — refuses BY NAME
|
|
82
|
+
* rather than being sent as 0,0 or NaN. */
|
|
83
|
+
export function coordinateArg(verb: string, name: string, value: unknown, nothing: string): MapsCoordinate {
|
|
84
|
+
const c = value as { latitude?: unknown; longitude?: unknown } | null | undefined;
|
|
85
|
+
const latitude = c?.latitude, longitude = c?.longitude;
|
|
86
|
+
if (
|
|
87
|
+
typeof latitude === 'number' && Number.isFinite(latitude) && Math.abs(latitude) <= 90 &&
|
|
88
|
+
typeof longitude === 'number' && Number.isFinite(longitude) && Math.abs(longitude) <= 180
|
|
89
|
+
) {
|
|
90
|
+
return { latitude, longitude };
|
|
91
|
+
}
|
|
92
|
+
throw refuseArgument(
|
|
93
|
+
verb,
|
|
94
|
+
`${name} must be { latitude, longitude } numbers (latitude -90..90, longitude -180..180), not ${shown(value)}`,
|
|
95
|
+
nothing,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** search's arguments, checked and defaulted (radiusM 3000, limit 8). `limit`
|
|
100
|
+
* is floored and capped at 25; everything else out of range refuses. */
|
|
101
|
+
export function searchArgs(
|
|
102
|
+
query: unknown,
|
|
103
|
+
opts: { near?: unknown; radiusM?: unknown; limit?: unknown } | null | undefined,
|
|
104
|
+
): { query: string; near: MapsCoordinate | null; radiusM: number; limit: number } {
|
|
105
|
+
const nothing = 'nothing was searched';
|
|
106
|
+
if (typeof query !== 'string' || query.trim() === '') {
|
|
107
|
+
throw refuseArgument('search', `query must be a non-empty string, not ${shown(query)}`, nothing);
|
|
108
|
+
}
|
|
109
|
+
const near = opts?.near == null ? null : coordinateArg('search', 'near', opts.near, nothing);
|
|
110
|
+
const radiusM = opts?.radiusM ?? SEARCH_RADIUS_DEFAULT_M;
|
|
111
|
+
if (typeof radiusM !== 'number' || !(radiusM > 0 && radiusM <= SEARCH_RADIUS_MAX_M)) {
|
|
112
|
+
throw refuseArgument(
|
|
113
|
+
'search', `radiusM must be metres above 0 and at most ${SEARCH_RADIUS_MAX_M}, not ${shown(radiusM)}`, nothing);
|
|
114
|
+
}
|
|
115
|
+
const limit = countArg('maps', 'search', 'limit', opts?.limit, SEARCH_LIMIT_DEFAULT, SEARCH_LIMIT_MAX, nothing);
|
|
116
|
+
return { query, near, radiusM, limit };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const point = (c: MapsCoordinate): MapsCoordinate => ({
|
|
120
|
+
latitude: Number(c.latitude),
|
|
121
|
+
longitude: Number(c.longitude),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export function toMapsAddress(raw: RawMapsAddress | null | undefined): MapsAddress {
|
|
125
|
+
const r = raw ?? {};
|
|
126
|
+
return {
|
|
127
|
+
name: text(r.name),
|
|
128
|
+
street: text(r.street),
|
|
129
|
+
streetNumber: text(r.streetNumber),
|
|
130
|
+
district: text(r.district),
|
|
131
|
+
city: text(r.city),
|
|
132
|
+
region: text(r.region),
|
|
133
|
+
postalCode: text(r.postalCode),
|
|
134
|
+
country: text(r.country),
|
|
135
|
+
isoCountryCode: text(r.isoCountryCode),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** "MKPOICategoryPharmacy" → "Pharmacy": the vocabulary term, without the
|
|
140
|
+
* framework's prefix. */
|
|
141
|
+
export function toMapsCategory(raw: unknown): string | null {
|
|
142
|
+
const c = text(raw);
|
|
143
|
+
return c ? c.replace(/^MKPOICategory/, '') || null : null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Search hits → places. `distanceM` is straight-line from `near`, and only
|
|
147
|
+
* when the caller gave one — a distance from nowhere would be invented. */
|
|
148
|
+
export function toMapsPlaces(raw: RawMapsPlace[] | null | undefined, near: MapsCoordinate | null): MapsPlace[] {
|
|
149
|
+
return (raw ?? []).map(r => {
|
|
150
|
+
const coordinate = point(r);
|
|
151
|
+
return {
|
|
152
|
+
name: text(r.name),
|
|
153
|
+
coordinate,
|
|
154
|
+
address: toMapsAddress(r.address),
|
|
155
|
+
category: toMapsCategory(r.category),
|
|
156
|
+
distanceM: near ? distanceOf([near, coordinate]) : null,
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** A reverse-geocoded address → a place AT the point asked about. The platform
|
|
162
|
+
* geocoder returns an address, not a snapped point or a category. */
|
|
163
|
+
export function toReverseGeocodedPlace(at: MapsCoordinate, address: MapsAddress): MapsPlace {
|
|
164
|
+
return {
|
|
165
|
+
name: address.name,
|
|
166
|
+
coordinate: point(at),
|
|
167
|
+
address,
|
|
168
|
+
category: null,
|
|
169
|
+
distanceM: null,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** A MapKit route → MapsRoute. Seconds become ms; every step is kept, including
|
|
174
|
+
* Apple's empty-instruction origin step, so indexes match Apple's. */
|
|
175
|
+
export function toMapsRoute(raw: RawMapsRoute): MapsRoute {
|
|
176
|
+
const steps: MapsRouteStep[] = (raw.steps ?? []).map(s => ({
|
|
177
|
+
instructions: typeof s.instructions === 'string' ? s.instructions : '',
|
|
178
|
+
distanceM: Number(s.distance),
|
|
179
|
+
notice: text(s.notice),
|
|
180
|
+
polyline: (s.polyline ?? []).map(point),
|
|
181
|
+
}));
|
|
182
|
+
return {
|
|
183
|
+
mode: 'walking',
|
|
184
|
+
distanceM: Number(raw.distance),
|
|
185
|
+
expectedTravelTimeMs: Math.round(Number(raw.expectedTravelTime) * 1000),
|
|
186
|
+
steps,
|
|
187
|
+
polyline: (raw.polyline ?? []).map(point),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A native rejection → an Error whose `code` is one of MapsAPI's three.
|
|
193
|
+
*
|
|
194
|
+
* AdaMaps already rejects with OFFLINE / THROTTLED / UNAVAILABLE. expo-location
|
|
195
|
+
* (reverseGeocode) has its own codes: on iOS a CLError.network becomes
|
|
196
|
+
* ERR_GEOCODING_NETWORK, which Apple uses for BOTH a lost connection and a rate
|
|
197
|
+
* limit — so it maps to OFFLINE and the message says it may be either. Anything
|
|
198
|
+
* else is UNAVAILABLE, with the platform's own words kept beside ours.
|
|
199
|
+
*/
|
|
200
|
+
export function mapsFailure(verb: string, err: unknown): Error & { code: MapsFailureCode } {
|
|
201
|
+
const nativeCode = String((err as any)?.code ?? '');
|
|
202
|
+
const detail = String((err as any)?.message ?? err ?? '').trim();
|
|
203
|
+
let code: MapsFailureCode;
|
|
204
|
+
let why: string;
|
|
205
|
+
if (nativeCode === 'THROTTLED') {
|
|
206
|
+
code = 'THROTTLED';
|
|
207
|
+
why = 'Apple is rate-limiting map requests from this app — wait before retrying';
|
|
208
|
+
} else if (nativeCode === 'OFFLINE') {
|
|
209
|
+
code = 'OFFLINE';
|
|
210
|
+
why = 'the map service could not be reached — the phone may be offline';
|
|
211
|
+
} else if (nativeCode === 'ERR_GEOCODING_NETWORK') {
|
|
212
|
+
code = 'OFFLINE';
|
|
213
|
+
why = 'the geocoder could not be reached — the phone may be offline, or Apple is ' +
|
|
214
|
+
'rate-limiting requests (iOS reports both the same way)';
|
|
215
|
+
} else {
|
|
216
|
+
code = 'UNAVAILABLE';
|
|
217
|
+
why = 'the map service failed';
|
|
218
|
+
}
|
|
219
|
+
const failure = new Error(
|
|
220
|
+
`maps.${verb} could not look: ${why}. This is not "found nothing".` +
|
|
221
|
+
(detail ? ` (${nativeCode ? nativeCode + ': ' : ''}${detail})` : ''),
|
|
222
|
+
) as Error & { code: MapsFailureCode };
|
|
223
|
+
failure.code = code;
|
|
224
|
+
return failure;
|
|
225
|
+
}
|
package/src/vision/index.ts
CHANGED
|
@@ -48,7 +48,8 @@ export interface VisionText {
|
|
|
48
48
|
text: string;
|
|
49
49
|
/** 0..1 recogniser confidence (never a 1.0 filler). */
|
|
50
50
|
confidence: number | null;
|
|
51
|
-
/** Normalised 0..1 box, origin TOP-left
|
|
51
|
+
/** Normalised 0..1 box, origin TOP-left, in the UPRIGHT portrait frame (native
|
|
52
|
+
* rotates frames at capture) — x runs from the user's left to their right. */
|
|
52
53
|
box?: { x: number; y: number; width: number; height: number };
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -64,7 +65,8 @@ export interface VisionObject {
|
|
|
64
65
|
label: string;
|
|
65
66
|
/** 0..1 detector confidence. */
|
|
66
67
|
confidence: number | null;
|
|
67
|
-
/** Normalised 0..1 box, origin TOP-left
|
|
68
|
+
/** Normalised 0..1 box, origin TOP-left, in the UPRIGHT portrait frame (native
|
|
69
|
+
* rotates frames at capture) — x runs from the user's left to their right. */
|
|
68
70
|
box?: { x: number; y: number; width: number; height: number };
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/haptics.ts","../src/bundleStorage.ts","../src/attachmentCache.ts","../src/device-state/domains/deviceContext.ts","../src/device-state/quality.ts","../src/device-state/domains/connectivity.ts","../src/device-state/domains/capability.ts","../src/device-state/domains/interaction.ts","../src/device-state/visualEngine.ts","../src/device-state/headingSource.ts","../src/device-state/turnDetector.ts","../src/device-state/domains/spatial.ts","../src/device-state/domains/visual.ts","../src/device-state/activityEngine.ts","../src/device-state/activityClassifier.ts","../src/device-state/domains/activity.ts","../src/device-state/index.ts","../src/camera/CameraTaskHost.tsx","../src/device-state/replay.ts"],"sourcesContent":["/**\n * @ateam-ai-mobile/expo — Expo-backed providers for the ADAS SDK.\n *\n * Bare-RN hosts skip this package entirely (core + plugin-runtime have safe\n * defaults); Expo hosts call setupExpoProviders() once at startup to light\n * up haptics, persistent bundle caching, attachment previews, and the\n * expo modules remote bundles may require.\n */\nimport * as ExpoWebBrowser from 'expo-web-browser';\nimport * as ExpoLinking from 'expo-linking';\nimport * as ExpoClipboard from 'expo-clipboard';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport { setAcsCacheStorage } from '@ateam-ai-mobile/core';\nimport {\n setHapticsProvider,\n setBundleStorage,\n registerBundleModules,\n setPluginNativeProvider,\n type PluginNative,\n} from '@ateam-ai-mobile/plugin-runtime';\nimport { setClipboardProvider } from '@ateam-ai-mobile/ui';\n\nimport { expoHaptics } from './haptics';\nimport { ExpoBundleStorage } from './bundleStorage';\nimport { ExpoAttachmentCache } from './attachmentCache';\n\nexport { expoHaptics } from './haptics';\nexport { ExpoBundleStorage } from './bundleStorage';\nexport { ExpoAttachmentCache } from './attachmentCache';\n// NOTE: createBridge (device capabilities bridge) is intentionally NOT\n// exported here — it statically imports the optional expo modules\n// (calendar/contacts/location/…), which would break Metro builds in hosts\n// that don't install them. Install those modules and import it from the\n// subpath: `import { createBridge } from '@ateam-ai-mobile/expo/bridge'`.\n\n/**\n * Wire all Expo providers in one call. Runs before initAdas() is fine —\n * these registries are independent of the core config.\n *\n * Returns the attachment cache so the host can pass it to initAdas():\n * const { attachmentCache } = setupExpoProviders();\n * initAdas({ ..., attachmentCache });\n */\nexport function setupExpoProviders(opts?: {\n /** Extra modules remote bundles may require(), e.g. react-native-webview.\n * Static imports must live in the HOST (it owns the native build). */\n bundleModules?: Record<string, any>;\n /** AsyncStorage key overrides — pass your pre-SDK keys when migrating an\n * existing app so users keep their attachment previews / bundle cache. */\n attachmentIndexKey?: string;\n bundleIndexKey?: string;\n /**\n * The device-primitive implementation plugins receive as `props.device`.\n * Build it with `createDeviceProvider(...)` from `@ateam-ai-mobile/expo/device`\n * and pass it here so the SDK owns the implementation and this call registers\n * it (setPluginNativeProvider). It is injected — not imported by this entry —\n * because createDeviceProvider statically pulls optional expo modules that a\n * host without them cannot build (same reason createBridge is a subpath).\n */\n deviceProvider?: PluginNative;\n}): { attachmentCache: ExpoAttachmentCache } {\n setHapticsProvider(expoHaptics);\n if (opts?.deviceProvider) setPluginNativeProvider(opts.deviceProvider);\n setBundleStorage(new ExpoBundleStorage({ indexKey: opts?.bundleIndexKey }));\n setClipboardProvider({\n setString: (text: string) => ExpoClipboard.setStringAsync(text).then(() => {}),\n });\n registerBundleModules({\n 'expo-web-browser': ExpoWebBrowser,\n 'expo-linking': ExpoLinking,\n ...(opts?.bundleModules || {}),\n });\n // Persist the ACS home-actions feed across cold starts.\n setAcsCacheStorage({\n getItem: (k: string) => AsyncStorage.getItem(k),\n setItem: (k: string, v: string) => AsyncStorage.setItem(k, v),\n });\n return { attachmentCache: new ExpoAttachmentCache({ indexKey: opts?.attachmentIndexKey }) };\n}\n\n// Device Bridge v2 — the semantic state engines this build provides.\n// The host passes this to setDeviceStateProvider(); per-plugin gating by\n// manifest.stateDomains happens in the SDK.\nexport { createDeviceState, IMPLEMENTED_DOMAINS } from './device-state';\nexport type { DeviceStateCollectors } from './device-state';\n// The mounted half of the Visual engine. The app renders this ONCE; it draws\n// nothing until a visual session is open. Required for deviceState.visual to\n// produce frames at all — expo-camera has no headless capture.\n// The rolling frame timeline. Exposed so the host's native.vision layer can\n// resolve a frame id to its on-device file (frameById) and write a measured\n// quality back onto the frame (setFrameQuality) — see visualEngine.ts.\nexport { visualEngine } from './device-state/visualEngine';\nexport type { VisualFrame } from './device-state/visualEngine';\n// The mounted camera surface behind scanner.scanOnce() and\n// camera.recordVideo(). Mount ONCE near the app root; it renders nothing\n// until a plugin asks for a scan or a recording. Without it both APIs\n// reject immediately with UNAVAILABLE — deliberately, since a plugin must\n// never await a promise nothing can settle.\nexport { CameraTaskHost } from './camera/CameraTaskHost';\nexport { cameraTasks } from './camera/cameraTasks';\n// Replay — run guidance logic against a RECORDED WALK instead of a phone.\n// See docs/SPEC_device_walk_recorder.md for the fixture format.\nexport { createReplayDeviceState, parseWalkJsonl } from './device-state/replay';\nexport type { WalkFixture, WalkSample, WalkRefusal, ReplayClock, ReplaySession } from './device-state/replay';\n","/**\n * expo-haptics implementation of the plugin HapticsAPI.\n */\nimport * as Haptics from 'expo-haptics';\nimport type { HapticsAPI } from '@ateam-ai-mobile/plugin-runtime';\n\nexport const expoHaptics: HapticsAPI = {\n impact(style) {\n const map = {\n light: Haptics.ImpactFeedbackStyle.Light,\n medium: Haptics.ImpactFeedbackStyle.Medium,\n heavy: Haptics.ImpactFeedbackStyle.Heavy,\n } as const;\n Haptics.impactAsync(map[style]).catch(() => {});\n },\n notification(type) {\n const map = {\n success: Haptics.NotificationFeedbackType.Success,\n warning: Haptics.NotificationFeedbackType.Warning,\n error: Haptics.NotificationFeedbackType.Error,\n } as const;\n Haptics.notificationAsync(map[type]).catch(() => {});\n },\n selection() {\n Haptics.selectionAsync().catch(() => {});\n },\n // Shorthand for notification('error') — the form plugins actually call.\n error() {\n Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {});\n },\n};\n","/**\n * Disk-backed BundleStorage: bundle files in the Expo cache directory,\n * index in AsyncStorage. Mirrors the reference app's cache semantics\n * (index version key, files under plugin-bundles/).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { BundleStorage, BundleCacheEntry } from '@ateam-ai-mobile/plugin-runtime';\n\nconst CACHE_DIR = `${FileSystem.cacheDirectory}plugin-bundles/`;\nconst DEFAULT_CACHE_INDEX_KEY = 'adas-plugin-bundles-index-v1';\n\nexport class ExpoBundleStorage implements BundleStorage {\n private dirReady = false;\n private indexKey: string;\n\n /** Pass `indexKey` when migrating from an app that already persisted a\n * bundle index under a different key — keeps the existing cache warm. */\n constructor(opts?: { indexKey?: string }) {\n this.indexKey = opts?.indexKey || DEFAULT_CACHE_INDEX_KEY;\n }\n\n private async ensureDir(): Promise<void> {\n if (this.dirReady) return;\n try {\n const info = await FileSystem.getInfoAsync(CACHE_DIR);\n if (!info.exists) {\n await FileSystem.makeDirectoryAsync(CACHE_DIR, { intermediates: true });\n }\n } catch (err) {\n console.warn('[ExpoBundleStorage] cache dir error:', err);\n }\n this.dirReady = true;\n }\n\n async loadIndex(): Promise<Record<string, BundleCacheEntry>> {\n try {\n const raw = await AsyncStorage.getItem(this.indexKey);\n return raw ? JSON.parse(raw) : {};\n } catch {\n return {};\n }\n }\n\n async saveIndex(index: Record<string, BundleCacheEntry>): Promise<void> {\n try {\n await AsyncStorage.setItem(this.indexKey, JSON.stringify(index));\n } catch (err) {\n console.warn('[ExpoBundleStorage] index save error:', err);\n }\n }\n\n async readBundle(localPath: string): Promise<string | null> {\n try {\n const info = await FileSystem.getInfoAsync(localPath);\n if (!info.exists) return null;\n return await FileSystem.readAsStringAsync(localPath);\n } catch {\n return null; // file gone — caller re-downloads\n }\n }\n\n async writeBundle(key: string, content: string): Promise<string> {\n await this.ensureDir();\n const localPath = `${CACHE_DIR}${key}`;\n await FileSystem.writeAsStringAsync(localPath, content);\n return localPath;\n }\n\n async deleteBundle(localPath: string): Promise<void> {\n await FileSystem.deleteAsync(localPath, { idempotent: true }).catch(() => {});\n }\n\n async clearAll(): Promise<void> {\n await FileSystem.deleteAsync(CACHE_DIR, { idempotent: true }).catch(() => {});\n await AsyncStorage.removeItem(this.indexKey).catch(() => {});\n this.dirReady = false;\n }\n}\n","/**\n * Expo implementation of @ateam-ai-mobile/core's AttachmentCache: attachment bytes on\n * disk, index in AsyncStorage. Mirrors the reference app's chat attachment\n * cache (last-100 retention).\n */\nimport * as FileSystem from 'expo-file-system/legacy';\nimport AsyncStorage from '@react-native-async-storage/async-storage';\nimport type { AttachmentCache, AttachmentPreview, OutgoingAttachment } from '@ateam-ai-mobile/core';\n\nconst DEFAULT_INDEX_KEY = 'adas-chat-attachment-index-v1';\nconst DIR = `${FileSystem.documentDirectory}chat-attachments/`;\nconst MAX_JOBS = 100;\n\ninterface CachedAttachment { mimeType: string; name: string; filePath: string }\n\nexport class ExpoAttachmentCache implements AttachmentCache {\n private index: Record<string, CachedAttachment[]> = {};\n private loaded: Promise<void>;\n private indexKey: string;\n\n /** Pass `indexKey` when migrating from an app that already persisted an\n * index under a different AsyncStorage key — otherwise existing users\n * lose every cached preview on upgrade. */\n constructor(opts?: { indexKey?: string }) {\n const indexKey = opts?.indexKey || DEFAULT_INDEX_KEY;\n this.indexKey = indexKey;\n this.loaded = (async () => {\n try {\n const info = await FileSystem.getInfoAsync(DIR);\n if (!info.exists) await FileSystem.makeDirectoryAsync(DIR, { intermediates: true });\n const raw = await AsyncStorage.getItem(indexKey);\n if (raw) this.index = JSON.parse(raw);\n } catch {}\n })();\n }\n\n async ready(): Promise<void> {\n await this.loaded;\n }\n\n async save(jobId: string, attachments: OutgoingAttachment[]): Promise<void> {\n await this.loaded;\n const entries: CachedAttachment[] = [];\n for (let i = 0; i < attachments.length; i++) {\n const att = attachments[i];\n const ext = att.mimeType.startsWith('image/') ? att.mimeType.split('/')[1] || 'jpg' : 'bin';\n const filePath = `${DIR}${jobId}_${i}.${ext}`;\n try {\n await FileSystem.writeAsStringAsync(filePath, att.data, { encoding: FileSystem.EncodingType.Base64 });\n entries.push({ mimeType: att.mimeType, name: att.name, filePath });\n } catch (err) {\n console.warn('[ExpoAttachmentCache] write failed:', err);\n }\n }\n if (entries.length === 0) return;\n\n this.index[jobId] = entries;\n // Keep only the most recent MAX_JOBS jobs; delete evicted files.\n const keys = Object.keys(this.index);\n if (keys.length > MAX_JOBS) {\n for (const k of keys.slice(0, keys.length - MAX_JOBS)) {\n const old = this.index[k];\n if (old) for (const o of old) FileSystem.deleteAsync(o.filePath, { idempotent: true }).catch(() => {});\n delete this.index[k];\n }\n }\n try {\n await AsyncStorage.setItem(this.indexKey, JSON.stringify(this.index));\n } catch {}\n }\n\n get(jobId: string): AttachmentPreview[] | undefined {\n const entries = this.index[jobId];\n if (!entries || entries.length === 0) return undefined;\n return entries.map(e => ({\n mimeType: e.mimeType,\n name: e.name,\n previewUri: e.filePath,\n }));\n }\n}\n","// ── deviceContext ──────────────────────────────────────────────────────────\n// power ✓ thermal ✗ (needs a native module) memory/storage ✗\nimport * as ExpoBattery from 'expo-battery';\nimport type { DeviceState, DeviceContextSnapshot } from '@ateam-ai-mobile/plugin-runtime';\nimport { qualityFrom, qualityUnknown } from '../quality';\n\nexport function createDeviceContextDomain(): DeviceState['deviceContext'] {\n return {\n async getSnapshot(): Promise<DeviceContextSnapshot> {\n const observedAt = Date.now();\n try {\n const [level, state, lowPower] = await Promise.all([\n ExpoBattery.getBatteryLevelAsync(),\n ExpoBattery.getBatteryStateAsync(),\n ExpoBattery.isLowPowerModeEnabledAsync(),\n ]);\n return {\n power: {\n // expo-battery returns -1 when it cannot tell. That is not a level.\n batteryLevel: typeof level === 'number' && level >= 0 ? level : null,\n charging:\n state === ExpoBattery.BatteryState.CHARGING ||\n state === ExpoBattery.BatteryState.FULL,\n lowPowerMode: lowPower,\n },\n // No Expo API for either. iOS needs ProcessInfo.thermalState, Android\n // PowerManager.getThermalStatus — a native module that does not exist.\n thermal: { state: 'unknown' },\n resources: { memoryPressure: 'unknown', storagePressure: 'unknown' },\n quality: qualityFrom(observedAt),\n };\n } catch {\n // The battery read failing is rare and uninteresting; reporting it as\n // 0% would not be.\n return {\n power: { batteryLevel: null, charging: null, lowPowerMode: null },\n thermal: { state: 'unknown' },\n resources: { memoryPressure: 'unknown', storagePressure: 'unknown' },\n quality: qualityUnknown(),\n };\n }\n },\n\n // Real events, not a placeholder: expo-battery exposes native listeners,\n // so a plugin that subscribes genuinely hears about a change rather than\n // sitting on a subscription that can never fire.\n subscribe(listener) {\n const subs = [\n ExpoBattery.addBatteryLevelListener(() => listener({ type: 'batteryLevelChanged' } as any)),\n ExpoBattery.addBatteryStateListener(() => listener({ type: 'chargingChanged' } as any)),\n ];\n return { unsubscribe: () => subs.forEach(x => x?.remove?.()) };\n },\n };\n}\n","/**\n * The honesty helpers — one place, shared by every domain module.\n *\n * A snapshot's `quality` says how much to trust it. R5 centralizes the three\n * ways a domain answers honestly here so no domain re-implements them:\n * - qualityFrom(observedAt) a live read, freshness stamped from its age.\n * - qualityUnknown() \"nothing was observed\" — never dressed as fresh.\n * - unsupported(what) an ACTION this build can't honour, refused by name.\n *\n * The `unknown` freshness shape is NOT re-declared here: it is the single\n * canonical `neverObserved()` from the contract package, so the engine, the gate\n * (deviceStateHost) and the types can never drift into three subtly different\n * \"unknown\"s.\n */\nimport { neverObserved } from '@ateam-ai-mobile/plugin-runtime';\nimport type { StateQuality } from '@ateam-ai-mobile/plugin-runtime';\n\n// ── Freshness ────────────────────────────────────────────────────────────\n//\n// Every snapshot carries when it was observed. `observedAt` is stamped at read\n// time by a live-reading domain; a caching domain passes its cache timestamp\n// instead. This is the one place the fresh/aging/stale thresholds live.\n\nexport const FRESH_MS = 10_000;\nexport const AGING_MS = 60_000;\n\nexport function qualityFrom(\n observedAt: number,\n confidence?: StateQuality['confidence'],\n): StateQuality {\n const ageMs = Date.now() - observedAt;\n return {\n freshness: {\n state: ageMs < FRESH_MS ? 'fresh' : ageMs < AGING_MS ? 'aging' : 'stale',\n observedAt,\n ageMs,\n },\n ...(confidence ? { confidence } : {}),\n };\n}\n\n/** Age genuinely unknown — treated as stale, never as fresh. */\nexport function qualityUnknown(): StateQuality {\n return { freshness: neverObserved() };\n}\n\n/**\n * Refuse, by name, with the frozen error code.\n *\n * Used where the contract declares a method this build cannot honour. The\n * alternative — a no-op that resolves — is the fabrication pattern in method\n * form: the caller cannot tell \"did nothing\" from \"worked\".\n */\nexport function unsupported(what: string): Error {\n const err: any = new Error(\n `${what} is not implemented in this build — the native engine behind it does not exist yet.`,\n );\n err.code = 'UNSUPPORTED';\n return err;\n}\n","// ── connectivity ─────────────────────────────────────────────────────────\n// reachability ✓ transport ✓ quality/stability/metered ✗\nimport type { DeviceState, ConnectivitySnapshot } from '@ateam-ai-mobile/plugin-runtime';\nimport type { DeviceStateCollectors } from '../collectors';\nimport { qualityFrom, qualityUnknown, unsupported } from '../quality';\n\nexport function createConnectivityDomain(collectors: DeviceStateCollectors): DeviceState['connectivity'] {\n return {\n async getSnapshot(): Promise<ConnectivitySnapshot> {\n const observedAt = Date.now();\n try {\n if (!collectors.connectivity) throw unsupported('connectivity collector');\n const data: any = await collectors.connectivity.collectConnectivity();\n const online = data?.is_connected === true;\n const type = String(data?.type || '').toLowerCase();\n\n return {\n reachability: data?.is_connected == null ? 'unknown' : online ? 'online' : 'offline',\n transport:\n type === 'wifi' ? 'wifi'\n : type === 'cellular' ? 'cellular'\n : type === 'ethernet' ? 'ethernet'\n : online ? 'other'\n : data?.is_connected === false ? 'none'\n : 'unknown',\n // All four below need measurement nobody takes yet. `unknown` is the\n // designed answer — \"return unknown rather than stale precision\".\n qualityClass: 'unknown',\n stability: 'unknown',\n latencyMs: null,\n bandwidthClass: 'unknown',\n metered: null,\n constrained: null,\n quality: qualityFrom(observedAt),\n };\n } catch {\n return {\n reachability: 'unknown',\n transport: 'unknown',\n qualityClass: 'unknown',\n stability: 'unknown',\n latencyMs: null,\n bandwidthClass: 'unknown',\n metered: null,\n constrained: null,\n quality: qualityUnknown(),\n };\n }\n },\n\n // Intent selects a native sampling strategy — latency probes, quality\n // classification. Nothing here samples anything, so accepting an intent\n // would be accepting an instruction we do not carry out.\n async start() { throw unsupported('connectivity.start'); },\n async stop() { throw unsupported('connectivity.stop'); },\n // A subscription that can never fire is worse than none: the caller\n // reasonably reads silence as \"nothing changed\".\n subscribe() { throw unsupported('connectivity.subscribe'); },\n };\n}\n","// ── capability ───────────────────────────────────────────────────────────\n// permission ✓ availability ✗ restriction ✗\nimport type { DeviceState, CapabilitySnapshot, CapabilityStatus } from '@ateam-ai-mobile/plugin-runtime';\nimport type { DeviceStateCollectors } from '../collectors';\nimport { qualityFrom, unsupported } from '../quality';\n\n/**\n * The bridge's permission model is 4-valued (`not_requested | granted | denied\n * | not_needed`); the frozen one is 7-valued. The extra states are real OS\n * distinctions nothing currently reads, so they map to `unknown` rather than\n * being collapsed into `denied` — \"restricted by policy\" and \"the user said no\"\n * are different problems with different remedies, and guessing between them\n * would put a wrong instruction in front of the user.\n */\nfunction statusFrom(granted: boolean | null): CapabilityStatus {\n return {\n support: 'unknown',\n permission: granted === null ? 'unknown' : granted ? 'granted' : 'denied',\n availability: 'unknown',\n };\n}\n\nconst UNKNOWN_STATUS: CapabilityStatus = {\n support: 'unknown',\n permission: 'unknown',\n availability: 'unknown',\n};\n\nexport function createCapabilityDomain(collectors: DeviceStateCollectors): DeviceState['capability'] {\n return {\n async getSnapshot(): Promise<CapabilitySnapshot> {\n const observedAt = Date.now();\n const locationGranted = collectors.location\n ? await collectors.location.hasPermission().catch(() => null)\n : null;\n\n return {\n // Only location has a real reader today. The rest are declared by the\n // contract and answered honestly rather than omitted.\n camera: UNKNOWN_STATUS,\n microphone: UNKNOWN_STATUS,\n location: {\n ...statusFrom(locationGranted),\n // iOS 14+ distinguishes precise from approximate; nothing reads it.\n precision: 'unknown',\n backgroundPermission: 'unknown',\n },\n motion: UNKNOWN_STATUS,\n bluetooth: UNKNOWN_STATUS,\n uwb: UNKNOWN_STATUS,\n nfc: UNKNOWN_STATUS,\n quality: qualityFrom(observedAt),\n } as CapabilitySnapshot;\n },\n\n // No OS permission-change stream is wired. Same reasoning as connectivity:\n // silence would be indistinguishable from stability.\n subscribe() { throw unsupported('capability.subscribe'); },\n };\n}\n","// ── interaction ──────────────────────────────────────────────────────────\n// app lifecycle ✓ screenOn ✗ audio ✗\nimport { AppState } from 'react-native';\nimport type { DeviceState, InteractionSnapshot } from '@ateam-ai-mobile/plugin-runtime';\nimport { qualityFrom } from '../quality';\n\n/**\n * Lifecycle only, and honest about the rest.\n *\n * `screenOn` is NOT derivable from AppState: RN reports `inactive` for a\n * notification shade, a control centre swipe, an incoming call AND a locked\n * screen. Mapping `inactive` to \"screen off\" would be wrong most of the time\n * it fired — so it stays null until a native module can answer it.\n *\n * The whole `audio` block reports unknown for the same reason. `interruption`\n * carries the distinction that matters: `none` means \"checked, nothing is\n * interrupting\", `unknown` means \"no knowledge\" — and we have the latter.\n */\nexport function createInteractionDomain(): DeviceState['interaction'] {\n const mapState = (s: string): InteractionSnapshot['lifecycle']['appState'] =>\n s === 'active' ? 'foreground'\n : s === 'background' ? 'background'\n : s === 'inactive' ? 'inactive'\n : 'unknown';\n\n return {\n async getSnapshot(): Promise<InteractionSnapshot> {\n const observedAt = Date.now();\n return {\n lifecycle: {\n appState: mapState(AppState.currentState as string),\n screenOn: null,\n userActive: null,\n idleDurationMs: null,\n },\n audio: {\n captureActive: null,\n playbackActive: null,\n route: 'unknown',\n interruption: 'unknown',\n },\n // The voice block is OMITTED rather than filled with nulls: the design\n // says a platform that cannot report voice session state leaves the\n // group out instead of guessing `false` for each field. The app's own\n // voice store knows this, but it is not reachable from the SDK.\n quality: qualityFrom(observedAt),\n };\n },\n\n // Real: AppState genuinely emits on every foreground/background change,\n // which is the one part of this domain that works.\n subscribe(listener) {\n const sub = AppState.addEventListener('change', next => {\n listener({ type: 'appStateChanged', appState: mapState(next as string) } as any);\n });\n return { unsubscribe: () => sub.remove() };\n },\n };\n}\n","/**\n * The Visual engine — a rolling frame timeline the Visual domain reads.\n *\n * WHY THIS IS SHAPED ODDLY. expo-camera can only produce frames through a\n * MOUNTED `<CameraView>`; there is no headless capture API. So the engine is\n * split in two:\n *\n * this module — the state: the ring, the session, the intent, the cadence.\n * Pure TS, no React, so the domain and tests can use it.\n * VisualEngineHost — a component the app mounts once. When a session is open\n * it mounts the camera and feeds frames in; otherwise it\n * renders nothing and the camera is off.\n *\n * The design's rule is that `getSnapshot()` must be a CACHED read that does not\n * open the camera or take a picture — \"avoid repeatedly starting the camera or\n * capturing a new image for every plugin-tool call during an interactive\n * session\". The ring is what makes that possible: capture runs on the engine's\n * own cadence, and reads serve whatever is already there.\n *\n * THE CADENCE IS THE ENGINE'S CHOICE, NOT THE PLUGIN'S. A plugin declares an\n * INTENT — `navigation`, `document`, `ocr` — and this picks the interval. That\n * is the whole point of the semantic layer: a caller asking for frames every\n * 500ms is specifying an implementation, and cannot know the analysis latency,\n * the battery state or the thermal headroom. Intent survives those changing;\n * a number does not.\n *\n * EACH FRAME CARRIES ITS ORIENTATION — heading, read at capture time. Without\n * it a frame cannot support a guidance instruction: \"turn left at the corner\"\n * needs to know which way the lens faced. Reading it afterwards would pair a\n * frame with a heading from after the user turned, which is how a guide\n * describes the wrong junction with confidence.\n *\n * POSITION IS NOT ON THE FRAME, and that is the freeze's decision, not an\n * omission. VisualFrameRef (API_FREEZE §4) carries `orientation` only; where\n * the user IS belongs to Spatial. A caller that needs both correlates them by\n * `timestamp` — both snapshots carry one. Duplicating position here would\n * create a second copy that can disagree with Spatial's, which is precisely\n * the two-descriptions-of-one-thing problem this whole layer exists to end.\n *\n * NOT IMPLEMENTED, and reported as `unknown`/null rather than guessed:\n * per-frame blur, exposure and obstruction (need image analysis), and\n * pitch/roll (need device-motion, not installed). A frame too dark to use must\n * not be described as `exposure: 'good'` because nothing looked at it.\n */\nimport type { StateQuality } from '@ateam-ai-mobile/plugin-runtime';\n\n/** A frame in the ring. `uri` is a local file the app can read; nothing is uploaded. */\nexport interface VisualFrame {\n handle: { id: string; mimeType: string; expiresAt: number | null };\n timestamp: number;\n /**\n * WHERE THE CAMERA WAS POINTING, stamped at capture time.\n *\n * A frame without this is nearly useless for guidance: \"turn left at the\n * corner\" needs to know which way the lens faced and where the user stood.\n * And it has to be captured WITH the frame, not read afterwards — by the\n * time a snapshot is read the user has walked on, and pairing a frame with a\n * later heading is how a guide describes the wrong junction confidently.\n *\n * Every field is nullable and null means NOT MEASURED. `headingDeg: 0` is due\n * north, so a missing compass must not collapse to 0.\n */\n orientation?: {\n /** True heading if available, else magnetic. Null when the compass said nothing. */\n headingDeg?: number | null;\n /** Not measured — needs device-motion, which is not installed. */\n pitchDeg?: number | null;\n rollDeg?: number | null;\n };\n quality?: { blur?: string; exposure?: string; obstructed?: boolean | null };\n}\n\n/**\n * Supplies the spatial context stamped onto each frame.\n *\n * Injected rather than imported for the same reason the collectors are, and\n * because it must be CHEAP: it is read once per capture, on the engine's\n * cadence. Both getters return null rather than throwing — a frame with no\n * heading is still a usable frame, and losing the picture because the compass\n * was busy would be the wrong trade.\n */\nexport interface VisualContextProvider {\n heading(): { headingDeg: number | null } | null;\n}\n\nexport type VisualIntentName =\n | 'navigation' | 'document' | 'ocr' | 'shopping'\n | 'inspection' | 'accessibility' | 'stream' | 'highQuality';\n\n/**\n * Intent → capture cadence and window.\n *\n * These are the engine's judgement, not the freeze's — the docs say native\n * owns sampling and deliberately do not fix numbers. Recorded here so the\n * reasoning is visible and arguable rather than buried:\n *\n * - `navigation`: a walking user's scene changes continuously, so a steady\n * cadence is right; ~1s is the ceiling at which a guidance instruction is\n * still about the present. Faster mostly queues behind the analysis.\n * - `document` / `ocr`: the subject is held still and the user is aiming. A\n * slow cadence is enough, and a big window is pointless.\n * - `stream`: the fastest we offer, for a caller that really is watching.\n * - `highQuality`: rare, deliberate, single good frame.\n */\nconst PROFILES: Record<VisualIntentName, { intervalMs: number; windowMs: number }> = {\n navigation: { intervalMs: 1000, windowMs: 5000 },\n accessibility: { intervalMs: 1000, windowMs: 5000 },\n stream: { intervalMs: 500, windowMs: 5000 },\n inspection: { intervalMs: 2000, windowMs: 8000 },\n shopping: { intervalMs: 2000, windowMs: 5000 },\n document: { intervalMs: 3000, windowMs: 3000 },\n ocr: { intervalMs: 3000, windowMs: 3000 },\n highQuality: { intervalMs: 5000, windowMs: 5000 },\n};\n\nexport interface VisualSessionState {\n id: string;\n intent: VisualIntentName;\n startedAt: number;\n /** Set while the app is backgrounded. The session SURVIVES; the camera does not. */\n suspended: boolean;\n}\n\ntype Listener = () => void;\n\nclass VisualEngine {\n private frames: VisualFrame[] = [];\n private session: VisualSessionState | null = null;\n private listeners = new Set<Listener>();\n /** Set by the host component while a camera is actually mounted and ready. */\n private cameraReady = false;\n /** Optional: supplies heading/position stamped onto each frame. */\n private context: VisualContextProvider | null = null;\n\n /** Host DI. Without it frames carry no orientation, and say so with nulls. */\n setContextProvider(p: VisualContextProvider | null): void { this.context = p; }\n\n // ── session ──\n\n startSession(intent: VisualIntentName): VisualSessionState {\n if (this.session) return this.session;\n this.session = {\n id: `visual_${Date.now()}`,\n intent: PROFILES[intent] ? intent : 'navigation',\n startedAt: Date.now(),\n suspended: false,\n };\n this.notify();\n return this.session;\n }\n\n stopSession(): void {\n this.session = null;\n // The timeline belongs to the session. Keeping it would let a later read\n // describe a room the user left.\n this.frames = [];\n this.cameraReady = false;\n this.notify();\n }\n\n /**\n * Background: suspend, do not end.\n *\n * iOS tears the camera down when the app leaves the foreground. The session\n * is a STATE that survives that; what does not survive is the claim that the\n * camera is live. While suspended the ring is kept but ages — a read gets\n * old frames clearly marked old, rather than a fresh-looking lie.\n */\n suspend(): void {\n if (!this.session || this.session.suspended) return;\n this.session.suspended = true;\n this.cameraReady = false;\n this.notify();\n }\n\n resume(): void {\n if (!this.session?.suspended) return;\n this.session.suspended = false;\n this.notify();\n }\n\n getSession(): VisualSessionState | null { return this.session; }\n\n /** Is a camera mounted and ready right now? Only the host knows. */\n setCameraReady(ready: boolean): void {\n this.cameraReady = ready;\n this.notify();\n }\n isCameraReady(): boolean { return this.cameraReady && !this.session?.suspended; }\n\n // ── the ring ──\n\n profile() {\n const intent = this.session?.intent || 'navigation';\n return PROFILES[intent];\n }\n\n /**\n * Feed from the native vision session (VisionSession, iOS) — the FAST frame\n * source that replaces the takePictureAsync loop. The frame is IN-MEMORY: the\n * native ring holds the pixels keyed by `id`, and native.vision resolves that\n * id inside the trusted layer. No `uri` crosses here; the quality is a REAL\n * measurement (not `unknown`), and `expiresAt` is the ring's real eviction time.\n * The public VisualFrameRef strip to id-only is owned by the device-state session.\n */\n pushFrameMeta(meta: {\n id: string;\n timestamp: number;\n expiresAt?: number | null;\n quality?: { blur?: string; exposure?: string; obstructed?: boolean | null };\n }): void {\n let heading: { headingDeg: number | null } | null = null;\n try { heading = this.context?.heading() ?? null; } catch { /* a frame without a heading beats no frame */ }\n\n this.frames.push({\n // In-memory: no file. native.vision resolves the id inside the native ring.\n handle: { id: meta.id, mimeType: 'image/none', expiresAt: meta.expiresAt ?? null },\n timestamp: meta.timestamp,\n orientation: { headingDeg: heading?.headingDeg ?? null, pitchDeg: null, rollDeg: null },\n quality: meta.quality ?? { blur: 'unknown', exposure: 'unknown', obstructed: null },\n });\n this.evict();\n this.notify();\n }\n\n // The native ring's actual retention (ms). The engine's effective window is\n // min(intent window, retention) — set by whoever owns the clamp semantics; this\n // just stores the input so that logic has one source of truth to read.\n private _retentionMs: number | null = null;\n setRetentionMs(ms: number | null): void { this._retentionMs = ms && ms > 0 ? ms : null; }\n retentionMs(): number | null { return this._retentionMs; }\n\n /**\n * How far back the timeline ACTUALLY reaches: `min(intent window, native\n * retention)`.\n *\n * The intent asks for a window; native decides what it can afford to keep.\n * Full-res luma at ~24fps is tens of megabytes for a few seconds, so the ring\n * is memory-bounded and shrinks under pressure — `retentionMs` is native\n * publishing what it is really holding.\n *\n * EVERY FRAME IN THE TIMELINE CAN BE ANALYSED. That is the one sentence a\n * plugin author has to hold, and this clamp is what makes it true. The\n * alternative — listing frames past retention marked \"not analysable\" — was\n * considered and rejected: a caller would have to check a flag before every\n * use, and forgetting throws mid-guidance on a frame the model can see\n * listed. Present-but-unusable is the same trap as an empty rowset reading\n * as \"no rows matched\".\n *\n * If per-frame history beyond retention is ever needed, the shape is an\n * AGGREGATE on the snapshot (\"the view has been unusable for N ms\"), never a\n * per-entry flag.\n */\n effectiveWindowMs(): number {\n const intent = this.profile().windowMs;\n const retention = this._retentionMs;\n return retention === null ? intent : Math.min(intent, retention);\n }\n\n /**\n * Time-based, not count-based: the design specifies a rolling WINDOW.\n *\n * Evicts on the EFFECTIVE window, so a frame whose pixels native has already\n * dropped leaves the timeline at the same moment. Without this, `frameById`\n * would start returning null for frames still being listed.\n */\n private evict(): void {\n const cutoff = Date.now() - this.effectiveWindowMs();\n this.frames = this.frames.filter(f => f.timestamp >= cutoff);\n }\n\n timeline(): { windowMs: number; frames: VisualFrame[] } {\n this.evict();\n // The EFFECTIVE window, never the intent's. Reporting the intent would have\n // a guide reason about 8 seconds of history it does not have — precisely\n // the confusion the clamp exists to prevent.\n return { windowMs: this.effectiveWindowMs(), frames: [...this.frames] };\n }\n\n latest(): VisualFrame | null {\n this.evict();\n return this.frames.length ? this.frames[this.frames.length - 1] : null;\n }\n\n /**\n * Resolve a frame handle id (from a VisualFrameRef) back to the live frame so\n * the mechanical layer — native.vision — can read its on-device file. The\n * whole contract of that layer is that it takes an id, never pixels; this is\n * where an id becomes a `handle.uri` it can open. Returns null when the id is\n * unknown OR its frame has aged out of the rolling window (the cache file may\n * also be gone by then) — a real \"I can't look at that frame\" answer, which\n * the caller must treat as distinct from \"I looked and found nothing\".\n */\n frameById(id: string): VisualFrame | null {\n this.evict();\n return this.frames.find(f => f.handle.id === id) ?? null;\n }\n\n /**\n * Record a MEASURED quality back onto a frame (native.vision.assessQuality).\n * Frames are minted with `quality: unknown` because nothing looks at the\n * pixels at capture time; this is how a real measurement replaces that so a\n * later read of VisualFrameRef.quality reflects it. No-op if the frame has\n * aged out — we never resurrect an evicted frame just to label it.\n */\n setFrameQuality(id: string, quality: NonNullable<VisualFrame['quality']>): void {\n const f = this.frames.find(fr => fr.handle.id === id);\n if (!f) return;\n f.quality = quality;\n this.notify();\n }\n\n // ── change notification ──\n\n subscribe(fn: Listener): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n private notify(): void {\n for (const fn of this.listeners) { try { fn(); } catch { /* a bad listener is not the engine's problem */ } }\n }\n}\n\n/** One engine per app. The camera is a singleton resource; so is this. */\nexport const visualEngine = new VisualEngine();\n\n/** Freshness for a visual read — exported so the domain and host agree. */\nexport function frameQuality(frame: VisualFrame | null): StateQuality {\n if (!frame) return { freshness: { state: 'unknown', observedAt: null, ageMs: null } };\n const ageMs = Date.now() - frame.timestamp;\n return {\n freshness: {\n // Tighter than the other domains on purpose: a two-second-old frame of a\n // moving scene is not \"fresh\" in any useful sense.\n state: ageMs < 2000 ? 'fresh' : ageMs < 10_000 ? 'aging' : 'stale',\n observedAt: frame.timestamp,\n ageMs,\n },\n confidence: { level: 'unknown' },\n };\n}\n","/**\n * The compass, kept warm — and the turn detector fed from it.\n *\n * `getHeadingAsync()` is not usable on the capture path: it starts a watch,\n * waits for a reading and stops it, which costs hundreds of milliseconds — per\n * frame. So this holds ONE subscription while a session is open and callers\n * read the last value synchronously.\n *\n * Returns null until a reading arrives, and null means NOT MEASURED. A heading\n * of 0 is due north, so an unstarted compass must never report 0.\n *\n * WHY THE TURN DETECTOR IS FED HERE. A turn happens BETWEEN reads — it is an\n * event, not a state — so a detector driven by sampled snapshots would miss\n * exactly the moment a guide needs. It runs on the compass callback instead.\n * The logic itself lives in `turnDetector.ts`, free of any Expo import, so it\n * can be tested against a scripted walk.\n */\nimport * as ExpoLocation from 'expo-location';\n\nimport { TurnDetector, type TurnEvent } from './turnDetector';\n\nexport type { TurnEvent };\nexport { signedDelta } from './turnDetector';\n\ninterface HeadingReading {\n headingDeg: number | null;\n /**\n * iOS calibration rank: 3 high, 2 medium, 1 low, 0 none. NOT degrees of\n * error, which is why it is never surfaced as `headingAccuracyDeg` — that\n * field wants degrees, and converting a rank into one would be a fabricated\n * error bar. It drives CONFIDENCE instead, which is what it can honestly say.\n */\n calibration: number | null;\n timestamp: number;\n}\n\nlet latest: HeadingReading | null = null;\nlet sub: { remove(): void } | null = null;\nconst turns = new TurnDetector();\n\n/** Start watching. Idempotent; safe to call on every session start. */\nexport async function startHeading(): Promise<void> {\n if (sub) return;\n try {\n sub = await ExpoLocation.watchHeadingAsync(h => {\n // trueHeading is -1 until the compass calibrates; magHeading is the\n // fallback the OS itself recommends.\n const raw = h.trueHeading >= 0 ? h.trueHeading : h.magHeading;\n const deg = typeof raw === 'number' && raw >= 0 ? raw : null;\n const cal = typeof h.accuracy === 'number' && h.accuracy >= 0 ? h.accuracy : null;\n latest = { headingDeg: deg, calibration: cal, timestamp: Date.now() };\n turns.push(deg, Date.now());\n });\n } catch {\n // No compass, or permission refused. Callers get headingDeg: null, which\n // is the honest answer and does not stop anything else working.\n sub = null;\n }\n}\n\nexport function stopHeading(): void {\n sub?.remove();\n sub = null;\n // Clear everything: a heading from a finished session describes a direction\n // the user may have long since turned away from, and a turn from a previous\n // walk is not something a new one should report.\n latest = null;\n turns.reset();\n}\n\n/** The newest reading, or null if the compass has said nothing yet. */\nexport function currentHeading(): { headingDeg: number | null } | null {\n return latest ? { headingDeg: latest.headingDeg } : null;\n}\n\n/** The newest reading with its calibration and age. */\nexport function currentHeadingDetail(): HeadingReading | null {\n return latest;\n}\n\n/** The most recent completed turn. Signed: positive = clockwise = right. */\nexport function lastMeaningfulTurn(): TurnEvent | null {\n return turns.last();\n}\n","/**\n * Turning, and which way.\n *\n * \"Turn right at the corner\" and \"you have just turned left\" are the two\n * sentences a walking guide says most, and neither can be read off a single\n * heading — they need heading OVER TIME. This is that, as a pure state machine\n * with no Expo import, so it can be tested against a scripted walk instead of\n * against a phone in someone's hand.\n *\n * SIGN IS THE WHOLE POINT. Compass degrees increase clockwise, so a positive\n * delta is a turn to the RIGHT and a negative one to the LEFT. A caller that\n * wants only the magnitude can take the absolute value; a caller handed a\n * magnitude cannot recover the side, and \"turn 90°\" without the side is worse\n * than saying nothing.\n *\n * THE HARD PART IS NOT DETECTING TURNS, IT IS NOT INVENTING THEM. A phone held\n * by a walking person swings 15–20° with every stride, and a compass drifts.\n * Compare against a fixed origin and the drift eventually crosses any threshold\n * on its own, so the machine re-baselines whenever the heading holds steady:\n * \"straight ahead\" is wherever you have been pointing for the last couple of\n * seconds, not wherever you were pointing when the session opened.\n */\n\n/** A completed turn. `deg` is signed: **positive = clockwise = right**. */\nexport interface TurnEvent {\n deg: number;\n /** When the turn was recognised as complete. */\n timestamp: number;\n}\n\nexport interface TurnDetectorOptions {\n /**\n * How far the heading must swing before it counts as a turn.\n *\n * 35° is a judgement, not a measurement: a street corner is ~90°, a gentle\n * bend in a corridor 30–45°, and hand-sway while walking routinely 15–20°.\n * Lower and the detector narrates the user's arm.\n */\n minTurnDeg?: number;\n /** Within this of the reference counts as \"still pointing the same way\". */\n stableDeg?: number;\n /** How long it must stay there before the reference moves. */\n stableMs?: number;\n}\n\n/** Signed shortest arc from `a` to `b`, in (-180, 180]. */\nexport function signedDelta(a: number, b: number): number {\n return ((b - a + 540) % 360) - 180;\n}\n\nexport class TurnDetector {\n private readonly minTurnDeg: number;\n private readonly stableDeg: number;\n private readonly stableMs: number;\n\n /** The heading currently treated as \"straight ahead\". */\n private refDeg: number | null = null;\n private stableSince = 0;\n private lastTurn: TurnEvent | null = null;\n\n constructor(opts: TurnDetectorOptions = {}) {\n this.minTurnDeg = opts.minTurnDeg ?? 35;\n this.stableDeg = opts.stableDeg ?? 12;\n this.stableMs = opts.stableMs ?? 2500;\n }\n\n /** Feed one compass reading. `null` degrees means the compass said nothing. */\n push(deg: number | null, now: number): TurnEvent | null {\n if (deg === null) return null;\n\n if (this.refDeg === null) {\n this.refDeg = deg;\n this.stableSince = now;\n return null;\n }\n\n const delta = signedDelta(this.refDeg, deg);\n\n if (Math.abs(delta) >= this.minTurnDeg) {\n // Re-baseline immediately, so a continuous 180° about-face is reported as\n // the turns it is made of. Left as one growing delta it would flip sign\n // at the halfway point and report a right turn for a left one.\n this.lastTurn = { deg: Math.round(delta), timestamp: now };\n this.refDeg = deg;\n this.stableSince = now;\n return this.lastTurn;\n }\n\n if (Math.abs(delta) > this.stableDeg) {\n // Moving, but not far enough yet. Hold the reference and keep watching —\n // this is what lets a slow 90° turn accumulate instead of being\n // re-baselined away one degree at a time.\n this.stableSince = now;\n return null;\n }\n\n // Steady. After a while, treat here as the new straight-ahead.\n if (now - this.stableSince >= this.stableMs) {\n this.refDeg = deg;\n this.stableSince = now;\n }\n return null;\n }\n\n /**\n * The most recent completed turn, or null if none this session.\n *\n * It does NOT expire on its own: the age is derivable from `timestamp`, and\n * \"recent enough to mention\" is a product judgement this layer must not make\n * on the caller's behalf.\n */\n last(): TurnEvent | null {\n return this.lastTurn;\n }\n\n reset(): void {\n this.refDeg = null;\n this.stableSince = 0;\n this.lastTurn = null;\n }\n}\n","// ── spatial ──────────────────────────────────────────────────────────────\n// position ✓ path ✓ (while a session is open) heading ✓ speed ~derived\nimport type { DeviceState, SpatialSnapshot, SpatialPoint, StateQuality } from '@ateam-ai-mobile/plugin-runtime';\nimport { visualEngine } from '../visualEngine';\nimport { startHeading, stopHeading, currentHeadingDetail, lastMeaningfulTurn } from '../headingSource';\nimport type { DeviceStateCollectors, SpeedRef } from '../collectors';\nimport { qualityFrom, qualityUnknown, unsupported } from '../quality';\n\n/**\n * Position, movement, facing and a short recent path.\n *\n * The design's performance rule is that navigation reads come from a WARM\n * native cache, not a fresh GPS+compass call per invocation. There is no such\n * cache today, so `getSnapshot` takes a fix — correct but not cheap. That is\n * why `start()` matters: it opens a `watchPosition` stream AND a compass watch,\n * and every later snapshot serves the newest streamed values instead, which is\n * the warm path this layer can offer without a native module.\n *\n * TWO DIRECTIONS, NOT ONE, and conflating them is the bug this replaced.\n *\n * `orientation.headingDeg` — where the phone is POINTING. The compass.\n * `movement.bearingDeg` — where the user is GOING. Course over ground.\n *\n * They disagree constantly and each answers a different sentence. \"Turn right\n * at the corner\" is decided by the compass; \"you are heading north on Main\n * Street\" by the course. The previous version assigned the same variable to\n * both, which is only right by accident, and that variable was permanently\n * null because the streaming collector dropped the fields.\n *\n * TURNS ARE SIGNED. `changes.lastMeaningfulTurnDeg` is positive for clockwise\n * (right) and negative for anticlockwise (left), so a guide can say which way\n * the user just went without a second field to consult. The freeze names the\n * field but states no convention; this is a decision, recorded here.\n */\nexport function createSpatialDomain(\n collectors: DeviceStateCollectors,\n speedRef: SpeedRef,\n): DeviceState['spatial'] {\n // The warm cache, when a session is open. Newest last, bounded — an unbounded\n // path on a walking user grows without limit.\n const RECENT_PATH_MAX = 60;\n /**\n * Below this the user is standing still.\n *\n * 0.4 m/s is well under a slow walk (~1.2 m/s) and above the jitter a\n * stationary phone's GPS produces. The point of the band is that a guide\n * must not say \"keep going straight\" to someone who has stopped.\n */\n const STATIONARY_MPS = 0.4;\n\n let recentPath: SpatialPoint[] = [];\n let latest: SpatialPoint | null = null;\n /** Speed and course from the newest STREAMED fix — see `latest`. */\n let latestSpeed: number | null = null;\n let latestCourse: number | null = null;\n let stopWatch: (() => void) | null = null;\n let sessionId: string | null = null;\n\n const nonNegative = (v: unknown): number | null =>\n typeof v === 'number' && v >= 0 ? v : null;\n\n /**\n * Speed from the path when the OS declined to estimate one.\n *\n * Only over the last two points and only if they are close in time: two fixes\n * a minute apart give an average that describes neither moment. Returns null\n * rather than a number the caller cannot tell apart from a measured one —\n * the confidence on the snapshot is what marks it as derived.\n */\n const derivedSpeed = (): number | null => {\n if (recentPath.length < 2) return null;\n const b = recentPath[recentPath.length - 1];\n const a = recentPath[recentPath.length - 2];\n const dtS = (b.timestamp - a.timestamp) / 1000;\n if (dtS <= 0 || dtS > 10) return null;\n return distanceOf([a, b]) / dtS;\n };\n\n /** Course from the path, for the same reason and with the same caution. */\n const derivedCourse = (): number | null => {\n if (recentPath.length < 2) return null;\n const b = recentPath[recentPath.length - 1];\n const a = recentPath[recentPath.length - 2];\n if (b.timestamp - a.timestamp > 10_000) return null;\n // Too small a step is noise, not a direction.\n if (distanceOf([a, b]) < 3) return null;\n return bearingBetween(a, b);\n };\n\n return {\n async getSnapshot(): Promise<SpatialSnapshot> {\n // Serve the warm fix when a session is streaming; otherwise take one.\n let point = latest;\n let speedMps = latestSpeed;\n let courseDeg = latestCourse;\n let derived = false;\n\n if (!point) {\n try {\n if (!collectors.location) throw unsupported('location collector');\n const fix: any = await collectors.location.getPosition({ accuracy: 'low' });\n point = {\n latitude: fix.latitude,\n longitude: fix.longitude,\n altitude: fix.altitude ?? null,\n accuracyM: nonNegative(fix.accuracy),\n timestamp: fix.timestamp ?? Date.now(),\n };\n speedMps = nonNegative(fix.speed);\n courseDeg = nonNegative(fix.course);\n } catch {\n // Permission refused, or no fix. Not a position at (0,0).\n return {\n position: null,\n movement: { speedMps: null, bearingDeg: null, distanceRecentM: null, state: 'unknown' },\n orientation: { headingDeg: null },\n recentPath: [],\n quality: qualityUnknown(),\n };\n }\n }\n\n // Fall back to the path only where the OS said nothing.\n if (speedMps === null) { const d = derivedSpeed(); if (d !== null) { speedMps = d; derived = true; } }\n if (courseDeg === null) { const d = derivedCourse(); if (d !== null) { courseDeg = d; derived = true; } }\n\n const heading = currentHeadingDetail();\n const turn = lastMeaningfulTurn();\n\n return {\n position: point,\n movement: {\n speedMps,\n bearingDeg: courseDeg,\n // Needs two points from the SAME session; without a stream there is\n // nothing to measure against, and a distance of 0 would read as\n // \"the user has not moved\".\n distanceRecentM: recentPath.length >= 2 ? distanceOf(recentPath) : null,\n // Only claim stationary/moving when a speed was actually obtained.\n // With no speed the truthful answer is `unknown`, not `stationary` —\n // \"you have stopped\" is a false statement, not a neutral default.\n state: speedMps === null ? 'unknown' : speedMps < STATIONARY_MPS ? 'stationary' : 'moving',\n },\n orientation: {\n headingDeg: heading?.headingDeg ?? null,\n // NOT the calibration rank. iOS reports 0–3 (none…high), which is not\n // a number of degrees, and converting one to the other would be an\n // invented error bar. The rank drives `confidence` below instead.\n headingAccuracyDeg: null,\n },\n changes: {\n lastMeaningfulTurnDeg: turn ? turn.deg : null,\n timestamp: turn ? turn.timestamp : null,\n },\n recentPath: [...recentPath],\n quality: qualityFrom(\n point.timestamp,\n spatialConfidence(point.accuracyM ?? null, heading?.calibration ?? null, derived),\n ),\n };\n },\n\n /**\n * Open the warm window: position stream + compass.\n *\n * The intent is recorded but does NOT yet change sampling — no native\n * strategy layer exists — so it is accepted rather than refused only\n * because the streams it opens are real and useful.\n */\n async start(options) {\n if (stopWatch) return { id: sessionId!, intent: options.intent, startedAt: Date.now() } as any;\n sessionId = `spatial_${Date.now()}`;\n if (!collectors.location) throw unsupported('location collector');\n // The compass is what makes heading and turn detection possible, and it\n // has to run for the session's whole life: a turn happens between reads,\n // so a compass started on demand would miss exactly the event a guide\n // most needs. Idempotent, and shared with a visual session if one is open.\n void startHeading();\n stopWatch = collectors.location.watchPosition((pos: any) => {\n const p: SpatialPoint = {\n latitude: pos.latitude,\n longitude: pos.longitude,\n altitude: pos.altitude ?? null,\n accuracyM: nonNegative(pos.accuracy),\n timestamp: pos.timestamp ?? Date.now(),\n };\n latest = p;\n latestSpeed = nonNegative(pos.speed);\n latestCourse = nonNegative(pos.course);\n speedRef.mps = latestSpeed;\n speedRef.at = p.timestamp;\n recentPath = [...recentPath, p].slice(-RECENT_PATH_MAX);\n });\n return { id: sessionId, intent: options.intent, startedAt: Date.now() } as any;\n },\n\n async stop() {\n stopWatch?.();\n stopWatch = null;\n sessionId = null;\n // The path belongs to the session that recorded it. Keeping it would let\n // a later snapshot report movement from a walk that ended hours ago.\n recentPath = [];\n latest = null;\n latestSpeed = null;\n latestCourse = null;\n speedRef.mps = null;\n speedRef.at = 0;\n // Leave the compass alone if a visual session is still using it — it is\n // shared, and stopping it here would silently blind the frame stamps.\n if (!visualEngine.getSession()) stopHeading();\n },\n\n // No event vocabulary is frozen for spatial, and inventing names here would\n // put them in plugins before the freeze can contradict them.\n subscribe() { throw unsupported('spatial.subscribe'); },\n };\n}\n\n/**\n * How much to trust this spatial reading.\n *\n * Three things degrade it and they are not interchangeable: a loose GPS fix\n * (the position is vague), an uncalibrated compass (the FACING is vague, which\n * is what breaks \"turn left\"), and values derived from the path rather than\n * measured by the OS. The lowest of the three wins, because a caller acting on\n * the snapshot is exposed to the worst part of it, not the average.\n *\n * This is the signal that decides Ada Guide's modality switch: indoors the fix\n * degrades, confidence drops, and that drop is what makes the guide ask for the\n * camera instead of guessing from a GPS fix it should not trust.\n */\nfunction spatialConfidence(\n accuracyM: number | null,\n calibration: number | null,\n derived: boolean,\n): StateQuality['confidence'] {\n const rank = (v: 'high' | 'medium' | 'low' | 'unknown') =>\n v === 'high' ? 3 : v === 'medium' ? 2 : v === 'low' ? 1 : 0;\n\n // Metres of horizontal error. ~10m is a good open-sky fix; beyond ~50m the\n // position is street-level at best and useless for \"turn at the next corner\".\n const fromFix: 'high' | 'medium' | 'low' | 'unknown' =\n accuracyM === null ? 'unknown' : accuracyM <= 10 ? 'high' : accuracyM <= 50 ? 'medium' : 'low';\n\n // iOS calibration rank: 3 high, 2 medium, 1 low, 0 none.\n const fromCompass: 'high' | 'medium' | 'low' | 'unknown' =\n calibration === null ? 'unknown'\n : calibration >= 3 ? 'high'\n : calibration >= 2 ? 'medium'\n : 'low';\n\n const parts = [fromFix, fromCompass, ...(derived ? (['low'] as const) : [])];\n const known = parts.filter(p => p !== 'unknown');\n if (!known.length) return { level: 'unknown' };\n const worst = known.reduce((a, b) => (rank(a) <= rank(b) ? a : b));\n return { level: worst };\n}\n\n/** Initial bearing from `a` to `b`, degrees clockwise from north. */\nfunction bearingBetween(a: SpatialPoint, b: SpatialPoint): number {\n const toRad = (d: number) => (d * Math.PI) / 180;\n const la1 = toRad(a.latitude), la2 = toRad(b.latitude);\n const dLon = toRad(b.longitude - a.longitude);\n const y = Math.sin(dLon) * Math.cos(la2);\n const x = Math.cos(la1) * Math.sin(la2) - Math.sin(la1) * Math.cos(la2) * Math.cos(dLon);\n return (((Math.atan2(y, x) * 180) / Math.PI) + 360) % 360;\n}\n\n/** Metres along a path, great-circle per leg. */\nfunction distanceOf(path: SpatialPoint[]): number {\n const R = 6_371_000;\n let total = 0;\n for (let i = 1; i < path.length; i++) {\n const a = path[i - 1], b = path[i];\n const dLat = ((b.latitude - a.latitude) * Math.PI) / 180;\n const dLon = ((b.longitude - a.longitude) * Math.PI) / 180;\n const la1 = (a.latitude * Math.PI) / 180, la2 = (b.latitude * Math.PI) / 180;\n const h = Math.sin(dLat / 2) ** 2 + Math.cos(la1) * Math.cos(la2) * Math.sin(dLon / 2) ** 2;\n total += 2 * R * Math.asin(Math.sqrt(h));\n }\n return Math.round(total);\n}\n","// ── visual ───────────────────────────────────────────────────────────────\n// frames ✓ heading ✓ freshness ✓ blur/exposure ✗\n//\n// Visual owns the semantic frame timeline/retention: the rolling ring, opaque\n// frame ids, and the freshness the frames carry (frameQuality) — see\n// visualEngine.ts. Opaque ids only above L1; pixels never cross to JS.\nimport type { DeviceState, VisualSnapshot } from '@ateam-ai-mobile/plugin-runtime';\nimport { visualEngine, frameQuality } from '../visualEngine';\nimport { currentHeading, startHeading, stopHeading } from '../headingSource';\nimport { unsupported } from '../quality';\n\n/**\n * Reads the rolling timeline the engine maintains. See visualEngine.ts for why\n * the capture half lives in a mounted component.\n *\n * `getSnapshot` NEVER opens the camera and never takes a picture — that is the\n * design's explicit performance rule, and the reason a guidance loop can ask\n * every turn without paying for it. What it costs is one array read.\n */\nexport function createVisualDomain(): DeviceState['visual'] {\n return {\n async getSnapshot(): Promise<VisualSnapshot> {\n const session = visualEngine.getSession();\n const latest = visualEngine.latest();\n const timeline = visualEngine.timeline();\n\n return {\n camera: {\n // `available` = the app could use a camera at all. `active` = one is\n // running RIGHT NOW. They differ exactly when it matters: a pocketed\n // phone is available but not active, and answering `true` to both\n // would let a guide speak about a scene nothing is watching.\n available: !!session,\n active: visualEngine.isCameraReady(),\n facing: session ? 'back' : 'unknown',\n },\n // null, not a stale frame dressed as current. A caller with no usable\n // frame must see nothing rather than something old.\n latest: latest as any,\n recentTimeline: timeline as any,\n quality: frameQuality(latest),\n };\n },\n\n async start(options) {\n const s = visualEngine.startSession((options?.intent as any) || 'navigation');\n // Warm the compass alongside the camera. Started here rather than at app\n // launch: a magnetometer subscription running all day for a feature\n // nobody opened is exactly the kind of cost this layer is meant to avoid.\n visualEngine.setContextProvider({ heading: currentHeading });\n void startHeading();\n return { id: s.id, intent: s.intent, startedAt: s.startedAt } as any;\n },\n\n async stop() {\n visualEngine.stopSession();\n visualEngine.setContextProvider(null);\n stopHeading();\n },\n\n /**\n * A deliberate one-shot. Distinct from getSnapshot because it is an\n * OPERATION: it waits for a frame rather than serving the newest one.\n *\n * Throws when it cannot, rather than returning an empty result — the\n * design is explicit that capture is an operation and reads are state.\n */\n async capture() {\n if (!visualEngine.getSession()) {\n throw unsupported('visual.capture without an open session — call start({ intent }) first');\n }\n if (!visualEngine.isCameraReady()) {\n const err: any = new Error(\n 'The camera is not running right now — the app may be in the background.',\n );\n err.code = 'TEMPORARILY_UNAVAILABLE';\n throw err;\n }\n // Wait briefly for the NEXT frame rather than returning the last one:\n // \"capture\" means now, and the newest ring entry may be a second old.\n const before = visualEngine.latest()?.timestamp ?? 0;\n const deadline = Date.now() + 4000;\n while (Date.now() < deadline) {\n const f = visualEngine.latest();\n if (f && f.timestamp > before) return { frame: f } as any;\n await new Promise(r => setTimeout(r, 100));\n }\n const err: any = new Error('No frame arrived within 4s.');\n err.code = 'TIMEOUT';\n throw err;\n },\n\n subscribe(listener) {\n const off = visualEngine.subscribe(() => {\n listener({ type: 'visualStateChanged' } as any);\n });\n return { unsubscribe: off };\n },\n };\n}\n","/**\n * The Activity engine — steps and motion, sampled into short windows.\n *\n * Sensors only. Every judgement made from them lives in `activityClassifier`,\n * which is pure and tested; this file's whole job is to keep two subscriptions\n * alive and turn their streams into the three numbers the classifier wants.\n *\n * IT IS SESSION-SCOPED, and that is a power decision rather than a stylistic\n * one. An accelerometer left running is a background drain and a pedometer\n * subscription is a permission-visible one, so neither starts until a plugin\n * declares it needs Activity. `getSnapshot()` with no session open returns an\n * explicit unknown rather than quietly switching the sensors on — a read must\n * not have that side effect.\n */\nimport type { HumanActivityValue } from './activityClassifier';\n\n/**\n * expo-sensors is loaded LAZILY, on the first `start()`, and never at import.\n *\n * This module is reachable from app startup, and expo-sensors is a NATIVE\n * module: an app binary built before it was added does not contain it. A\n * top-level import that threw during evaluation would not degrade the Activity\n * domain — it would fail the bundle and black-screen the whole app, on exactly\n * the builds that predate this change. So the failure is contained here, where\n * it can be reported as \"no sensors\" instead.\n */\nlet sensors: any = null;\nlet sensorsMissing = false;\n\nfunction loadSensors(): any {\n if (sensors || sensorsMissing) return sensors;\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n sensors = require('expo-sensors');\n } catch {\n sensorsMissing = true;\n sensors = null;\n }\n return sensors;\n}\n\n/** Accelerometer cadence. 5 Hz is plenty for pose and for a variance band. */\nconst ACCEL_INTERVAL_MS = 200;\n/** Motion window. Long enough to smooth a stride, short enough to be current. */\nconst MOTION_WINDOW_MS = 3000;\n/**\n * Step window.\n *\n * 20s, because cadence over a shorter window swings wildly at the start and end\n * of walking — someone who takes four steps and stops would otherwise read as\n * a steady 120 spm for a moment.\n */\nconst STEP_WINDOW_MS = 20_000;\n/** Low-pass coefficient isolating gravity from the raw accelerometer. */\nconst GRAVITY_ALPHA = 0.1;\n\ninterface StepSample { steps: number; t: number }\n\nclass ActivityEngine {\n private accelSub: { remove(): void } | null = null;\n private stepSub: { remove(): void } | null = null;\n\n private magnitudes: Array<{ m: number; t: number }> = [];\n private gravity: { x: number; y: number; z: number } | null = null;\n private steps: StepSample[] = [];\n\n private sessionId: string | null = null;\n private startedAt = 0;\n /** When the CURRENT reading last changed — feeds `human.since`. */\n private pedometerAvailable: boolean | null = null;\n\n isRunning(): boolean { return this.sessionId !== null; }\n session(): { id: string; startedAt: number } | null {\n return this.sessionId ? { id: this.sessionId, startedAt: this.startedAt } : null;\n }\n\n /**\n * @param classify how to name the current activity — injected so the engine\n * keeps no opinion of its own and the classifier stays pure and testable.\n */\n async start(classify: () => HumanActivityValue): Promise<{ id: string; startedAt: number }> {\n if (this.sessionId) return this.session()!;\n this.sessionId = `activity_${Date.now()}`;\n this.startedAt = Date.now();\n\n const s = loadSensors();\n if (!s) {\n // An app binary built before expo-sensors was added. The session is open\n // — stopping it would be a lie in the other direction — but nothing\n // samples, so every window stays empty and the classifier says `unknown`.\n this.pedometerAvailable = false;\n this.startTicking(classify);\n return this.session()!;\n }\n const { Accelerometer, Pedometer } = s;\n\n Accelerometer.setUpdateInterval(ACCEL_INTERVAL_MS);\n this.accelSub = Accelerometer.addListener(({ x, y, z }: { x: number; y: number; z: number }) => {\n const now = Date.now();\n // Raw magnitude includes gravity, so a still phone sits at ~1g and the\n // variance of this series is the motion signal.\n this.magnitudes.push({ m: Math.sqrt(x * x + y * y + z * z), t: now });\n const cutoff = now - MOTION_WINDOW_MS;\n if (this.magnitudes.length > 8 && this.magnitudes[0].t < cutoff) {\n this.magnitudes = this.magnitudes.filter(s => s.t >= cutoff);\n }\n // Gravity by low-pass: what survives smoothing is the constant pull, and\n // that is what says which way up the phone is.\n this.gravity = this.gravity\n ? {\n x: this.gravity.x + GRAVITY_ALPHA * (x - this.gravity.x),\n y: this.gravity.y + GRAVITY_ALPHA * (y - this.gravity.y),\n z: this.gravity.z + GRAVITY_ALPHA * (z - this.gravity.z),\n }\n : { x, y, z };\n });\n\n try {\n this.pedometerAvailable = await Pedometer.isAvailableAsync();\n } catch {\n this.pedometerAvailable = false;\n }\n if (this.pedometerAvailable) {\n try {\n this.stepSub = Pedometer.watchStepCount(({ steps }: { steps: number }) => {\n this.steps.push({ steps, t: Date.now() });\n const cutoff = Date.now() - STEP_WINDOW_MS;\n if (this.steps.length > 2 && this.steps[0].t < cutoff) {\n this.steps = this.steps.filter(s => s.t >= cutoff);\n }\n });\n } catch {\n // Permission refused, most likely (iOS Motion & Fitness). Cadence stays\n // null, the classifier says `unknown`, and nothing pretends otherwise.\n this.stepSub = null;\n this.pedometerAvailable = false;\n }\n }\n\n this.startTicking(classify);\n return this.session()!;\n }\n\n stop(): void {\n this.accelSub?.remove();\n this.stepSub?.remove();\n this.accelSub = null;\n this.stepSub = null;\n this.sessionId = null;\n this.stopTicking();\n // The windows belong to the session. Keeping them would let a later read\n // report a walk that finished before the phone went in a drawer.\n this.magnitudes = [];\n this.steps = [];\n this.gravity = null;\n }\n\n /** Variance of accelerometer magnitude over the motion window, in g². */\n accelVariance(): number | null {\n const cutoff = Date.now() - MOTION_WINDOW_MS;\n const window = this.magnitudes.filter(s => s.t >= cutoff);\n // Two samples is not a variance; it is a coincidence.\n if (window.length < 5) return null;\n const mean = window.reduce((a, s) => a + s.m, 0) / window.length;\n return window.reduce((a, s) => a + (s.m - mean) ** 2, 0) / window.length;\n }\n\n currentGravity(): { x: number; y: number; z: number } | null { return this.gravity; }\n\n /**\n * Steps per minute over the step window.\n *\n * Computed from the DIFFERENCE between the first and last cumulative counts,\n * so it does not matter whether the platform counts from subscription start\n * or from midnight.\n *\n * Returns null — not 0 — when the window is too short to divide by. Zero\n * steps per minute is a real claim (\"standing still\") and must only be made\n * once there is a window long enough to support it.\n */\n cadenceSpm(): number | null {\n const cutoff = Date.now() - STEP_WINDOW_MS;\n const window = this.steps.filter(s => s.t >= cutoff);\n if (window.length < 2) {\n // The pedometer is alive and has simply reported nothing recently, which\n // is what standing still looks like: no callbacks at all.\n if (this.pedometerAvailable && Date.now() - this.startedAt > STEP_WINDOW_MS) return 0;\n return null;\n }\n const first = window[0], last = window[window.length - 1];\n const minutes = (last.t - first.t) / 60_000;\n if (minutes <= 0) return null;\n return (last.steps - first.steps) / minutes;\n }\n\n /** Whether a pedometer exists and is permitted. Null before `start()`. */\n hasPedometer(): boolean | null { return this.pedometerAvailable; }\n\n /** When the newest sample arrived — what freshness is actually about. */\n observedAt(): number | null {\n const ts = [\n this.magnitudes[this.magnitudes.length - 1]?.t,\n this.steps[this.steps.length - 1]?.t,\n ].filter((t): t is number => typeof t === 'number');\n return ts.length ? Math.max(...ts) : null;\n }\n\n // ── transitions ──\n //\n // Tracked on a timer inside the engine rather than computed when someone\n // reads, because a transition is an EVENT: \"you started walking\" happens\n // between two reads, and a domain that only classifies on demand reports the\n // state and loses the moment it changed. `since` has the same problem.\n\n private current: HumanActivityValue = 'unknown';\n private since: number | null = null;\n private transitions: Array<{ name: string; timestamp: number }> = [];\n private ticker: ReturnType<typeof setInterval> | null = null;\n\n /** Feed the engine its own classification once a second. */\n private startTicking(classify: () => HumanActivityValue): void {\n this.ticker = setInterval(() => {\n const next = classify();\n if (next === this.current) return;\n this.current = next;\n this.since = Date.now();\n // The transition NAME reuses the frozen HumanActivity vocabulary — it is\n // the state being entered. The freeze leaves transition names as bare\n // strings with no controlled vocabulary, and inventing one (\"walk_start\")\n // would put a word in plugins that the freeze may later contradict.\n this.transitions = [...this.transitions, { name: next, timestamp: this.since }].slice(-10);\n }, 1000);\n }\n\n private stopTicking(): void {\n if (this.ticker) clearInterval(this.ticker);\n this.ticker = null;\n this.current = 'unknown';\n this.since = null;\n this.transitions = [];\n }\n\n /** When the current activity began, or null if it has not been established. */\n currentSince(): number | null { return this.since; }\n /** Newest last, bounded. Empty is a real answer: nothing has changed yet. */\n recentTransitions(): Array<{ name: string; timestamp: number }> {\n return [...this.transitions];\n }\n}\n\n/** One engine per app: the sensors are singleton hardware. */\nexport const activityEngine = new ActivityEngine();\n","/**\n * What the user is doing, and how the phone is being held — as a pure function.\n *\n * Separated from the sensor plumbing so the JUDGEMENTS are testable: every\n * threshold below is a claim about human beings, and a claim that decides\n * whether a guide says \"you have arrived\" to someone sitting in a moving car\n * deserves a test rather than a comment.\n *\n * WHY THIS DOMAIN EXISTS AT ALL WHEN SPATIAL ALREADY REPORTS MOVEMENT: GPS is\n * what fails indoors, and indoors is where a guide most needs to know whether\n * the user is walking. Steps come from the pedometer, which does not care about\n * a roof. Spatial answers \"are you moving through the world\"; Activity answers\n * \"are you walking\" — in a mall those are different questions with different\n * sensors, and only one of them still works.\n */\nimport type { Confidence } from '@ateam-ai-mobile/plugin-runtime';\n\nexport type HumanActivityValue =\n | 'stationary' | 'walking' | 'running' | 'cycling' | 'driving' | 'unknown';\nexport type PlacementValue = 'handheld' | 'pocket' | 'table' | 'mounted' | 'unknown';\nexport type PoseOrientation = 'portrait' | 'landscape' | 'flat' | 'unknown';\n\nexport interface ActivityInput {\n /** Steps per minute over the recent window. `null` = no pedometer reading. */\n cadenceSpm: number | null;\n /**\n * Variance of accelerometer magnitude (g²) over the recent window.\n * `null` = the accelerometer has not reported yet.\n */\n accelVariance: number | null;\n /** Low-pass gravity estimate in g. `null` = not sampled yet. */\n gravity: { x: number; y: number; z: number } | null;\n /**\n * Speed from Spatial, when a spatial session is streaming.\n *\n * Passed IN rather than read here, so this stays pure. It exists for exactly\n * one job: a phone in a moving car looks identical to a phone on a desk —\n * no steps, little vibration — and calling that `stationary` would tell a\n * guide the user has arrived while they are doing 50 km/h.\n */\n speedMps: number | null;\n}\n\nexport interface ActivityVerdict {\n human: { state: HumanActivityValue; confidence: Confidence };\n pose: { placement: PlacementValue; orientation: PoseOrientation; confidence: Confidence };\n}\n\n/**\n * Cadence bands, in steps per minute.\n *\n * Ordinary walking sits around 100–120 spm and running around 160–180. 140 is\n * placed in the gap rather than at either edge: misreading a fast walk as a run\n * is harmless, but the reverse would have a guide give walking directions to\n * someone covering ground twice as fast.\n *\n * 20 spm is the floor for \"walking at all\" — below that it is a few steps of\n * shuffling, which is not travel.\n */\nconst RUN_SPM = 140;\nconst WALK_SPM = 20;\n\n/**\n * Accelerometer magnitude variance, in g².\n *\n * A phone lying on a table reads a near-constant 1g, so its variance is\n * essentially sensor noise. A hand holding a phone never manages that.\n */\nconst STILL_VAR = 0.002;\n/** Above this the phone is being moved about, not merely held. */\nconst ACTIVE_VAR = 0.02;\n\n/**\n * Speed at which the user is certainly not walking, in m/s.\n *\n * 3 m/s is ~11 km/h — above a fast walk (~2 m/s) and below anything a vehicle\n * does in traffic.\n */\nconst VEHICLE_MPS = 3;\n\n/** Gravity component that has to dominate before an axis names the pose. */\nconst AXIS_DOMINANT = 0.75;\n\nexport function classifyActivity(input: ActivityInput): ActivityVerdict {\n return { human: classifyHuman(input), pose: classifyPose(input) };\n}\n\nfunction classifyHuman({ cadenceSpm, accelVariance, speedMps }: ActivityInput): ActivityVerdict['human'] {\n // Steps are the strongest evidence there is, and the only one that survives\n // indoors. Take them first when they exist.\n if (cadenceSpm !== null && cadenceSpm >= RUN_SPM) {\n return { state: 'running', confidence: { level: 'high' } };\n }\n if (cadenceSpm !== null && cadenceSpm >= WALK_SPM) {\n return { state: 'walking', confidence: { level: 'high' } };\n }\n\n // No meaningful stepping. Before calling it stationary, rule out a vehicle:\n // riding is not walking, and it is not standing still either.\n if (speedMps !== null && speedMps >= VEHICLE_MPS) {\n // Moving fast without stepping. Which vehicle it is cannot be told from an\n // accelerometer and a pedometer — separating cycling from driving needs\n // real activity recognition (CMMotionActivity), which is not available\n // here. `unknown` with the speed already published by Spatial is honest;\n // guessing `driving` because it is the commoner case is not.\n return { state: 'unknown', confidence: { level: 'low' } };\n }\n\n if (accelVariance !== null && accelVariance < STILL_VAR && (cadenceSpm === null || cadenceSpm < WALK_SPM)) {\n // Genuinely still. Medium rather than high: a phone resting in a\n // stationary car at a red light produces exactly this, and Spatial's speed\n // cannot separate them either while the car is stopped.\n return { state: 'stationary', confidence: { level: 'medium' } };\n }\n\n // Something is moving but nothing identifies it — a phone being handled\n // while its owner stands still, most often. Naming an activity here would be\n // a guess dressed as a reading.\n return { state: 'unknown', confidence: { level: 'unknown' } };\n}\n\nfunction classifyPose({ gravity, accelVariance }: ActivityInput): ActivityVerdict['pose'] {\n if (!gravity) {\n return { placement: 'unknown', orientation: 'unknown', confidence: { level: 'unknown' } };\n }\n\n const { x, y, z } = gravity;\n const orientation: PoseOrientation =\n Math.abs(z) >= AXIS_DOMINANT ? 'flat'\n : Math.abs(y) >= AXIS_DOMINANT ? 'portrait'\n : Math.abs(x) >= AXIS_DOMINANT ? 'landscape'\n : 'unknown';\n\n // Flat and utterly still is a surface. This is the one placement the sensors\n // can actually establish.\n if (orientation === 'flat' && accelVariance !== null && accelVariance < STILL_VAR) {\n return { placement: 'table', orientation, confidence: { level: 'high' } };\n }\n\n // Upright and moving a little is a hand. Medium, because a phone in a shirt\n // pocket looks much the same and this cannot tell them apart.\n if (orientation !== 'flat' && accelVariance !== null && accelVariance >= ACTIVE_VAR) {\n return { placement: 'handheld', orientation, confidence: { level: 'medium' } };\n }\n\n // `pocket` and `mounted` are NEVER returned. Distinguishing a pocket needs\n // the proximity or light sensor, and a car mount needs Bluetooth or CarPlay\n // context — none of which is wired. The orientation is still reported,\n // because it was actually measured.\n return { placement: 'unknown', orientation, confidence: { level: 'unknown' } };\n}\n","// ── activity ─────────────────────────────────────────────────────────────\n// THE INDOOR MOVEMENT SIGNAL (Spatial stops working indoors).\nimport type { DeviceState, ActivitySnapshot } from '@ateam-ai-mobile/plugin-runtime';\nimport { activityEngine } from '../activityEngine';\nimport { classifyActivity, type ActivityInput } from '../activityClassifier';\nimport type { SpeedRef } from '../collectors';\nimport { qualityFrom, qualityUnknown, unsupported } from '../quality';\n\n/**\n * What the user is doing, and how the phone is being held.\n *\n * THIS IS THE INDOOR MOVEMENT SIGNAL. Spatial answers \"are you moving through\n * the world\" and stops being able to indoors, which is exactly where a guide\n * most needs to know whether its user is walking. The pedometer does not care\n * about a roof.\n *\n * SESSION-SCOPED, unlike the passive domains. An accelerometer left running is\n * a background drain and a pedometer subscription is permission-visible on\n * iOS, so nothing samples until a plugin says it needs this. A `getSnapshot()`\n * with no session open returns an explicit unknown rather than switching the\n * sensors on — a read must not have that side effect, and one that did would\n * also return junk, since the windows would be empty at the moment it started.\n */\nexport function createActivityDomain(speedRef: SpeedRef): DeviceState['activity'] {\n /** A speed older than this says nothing about now. */\n const SPEED_MAX_AGE_MS = 15_000;\n\n const input = (): ActivityInput => ({\n cadenceSpm: activityEngine.cadenceSpm(),\n accelVariance: activityEngine.accelVariance(),\n gravity: activityEngine.currentGravity(),\n speedMps:\n speedRef.at && Date.now() - speedRef.at < SPEED_MAX_AGE_MS ? speedRef.mps : null,\n });\n\n return {\n async getSnapshot(): Promise<ActivitySnapshot> {\n if (!activityEngine.isRunning()) {\n // Nothing is sampling. Every field says so rather than describing a\n // motionless phone, which is what an unstarted engine would look like.\n return {\n human: { state: 'unknown', since: null, confidence: { level: 'unknown' } },\n devicePose: { placement: 'unknown', orientation: 'unknown', confidence: { level: 'unknown' } },\n recentTransitions: [],\n quality: qualityUnknown(),\n };\n }\n\n const verdict = classifyActivity(input());\n const observedAt = activityEngine.observedAt();\n const transitions = activityEngine.recentTransitions();\n\n return {\n human: {\n state: verdict.human.state,\n since: activityEngine.currentSince(),\n confidence: verdict.human.confidence,\n },\n devicePose: {\n placement: verdict.pose.placement,\n orientation: verdict.pose.orientation,\n confidence: verdict.pose.confidence,\n },\n transition: {\n current: verdict.human.state,\n // A hint, and we have no basis for one. The freeze calls `likelyNext`\n // advisory; advisory is not licence to invent it.\n likelyNext: null,\n },\n recentTransitions: transitions,\n quality: observedAt === null\n ? qualityUnknown()\n : qualityFrom(observedAt, verdict.human.confidence),\n };\n },\n\n /**\n * Start sampling. The intent is recorded but does not yet change strategy —\n * there is no native power-tuning layer for it to reach.\n */\n async start(options) {\n const s = await activityEngine.start(() => classifyActivity(input()).human.state);\n return { id: s.id, intent: options.intent, startedAt: s.startedAt } as any;\n },\n\n async stop() { activityEngine.stop(); },\n\n // No activity event vocabulary is frozen — the design notes there is no\n // \"Initial Activity Events\" list at all — so naming events here would put\n // words in plugins that the freeze may contradict.\n subscribe() { throw unsupported('activity.subscribe'); },\n };\n}\n","/**\n * Device Bridge v2 — the runtime behind `props.deviceState`.\n *\n * R5 split the former monolith into one module per domain (./domains/*), a\n * single quality module that owns freshness/confidence/unknown (./quality), and\n * this composition root, which wires the domains and injects their dependencies.\n * The behaviour — and every snapshot shape — is unchanged; only the file layout is.\n *\n * WHAT IS AND IS NOT HERE — stated up front, because a half-built state layer\n * that pretends to be whole is the exact failure this whole effort exists to\n * end:\n *\n * deviceContext power ✓ thermal ✗ memory/storage ✗\n * connectivity reachability ✓ transport ✓ quality/stability/metered ✗\n * capability permission ✓ availability ✗ restriction ✗\n * interaction app lifecycle ✓ screenOn ✗ audio ✗\n * spatial position ✓ path ✓ (while a session is open) heading/speed ✗\n * visual frames ✓ heading ✓ freshness ✓ blur/exposure ✗\n * activity / environment — NOT IMPLEMENTED AT ALL\n *\n * Everything marked ✗ reports `unknown`, which is a real answer in this design\n * and the only honest one. It never reports a plausible default: an invented\n * 'nominal' thermal state is indistinguishable from a measured one, and that is\n * how 56 fabrications reached users from the layer below.\n *\n * A domain with no engine here is not simply missing: `gateDeviceState` (in\n * plugin-runtime/src/deviceStateHost.ts) fills the gap with a domain whose\n * reads answer explicit `unknown`s and whose ACTIONS throw. So a plugin can\n * call any of the eight without an existence check, and still cannot mistake\n * \"not built\" for \"nothing to report\". `environment` is deliberately absent\n * from the composition below for exactly that reason.\n */\nimport type { DeviceState } from '@ateam-ai-mobile/plugin-runtime';\nimport { DeviceStateCollectors, SpeedRef } from './collectors';\nimport { createDeviceContextDomain } from './domains/deviceContext';\nimport { createConnectivityDomain } from './domains/connectivity';\nimport { createCapabilityDomain } from './domains/capability';\nimport { createInteractionDomain } from './domains/interaction';\nimport { createSpatialDomain } from './domains/spatial';\nimport { createVisualDomain } from './domains/visual';\nimport { createActivityDomain } from './domains/activity';\n\nexport type { DeviceStateCollectors, SpeedRef } from './collectors';\n\n/**\n * Build the semantic surface.\n *\n * Only implemented domains appear. `environment` is deliberately absent: a\n * plugin touching it gets a TypeError naming it, which is a far better failure\n * than a snapshot of nulls that reads like a quiet device.\n */\nexport function createDeviceState(collectors: DeviceStateCollectors = {}): Partial<DeviceState> {\n // The ONE explicit cross-domain dependency: Activity needs Spatial's newest\n // speed to tell \"walking\" from \"sitting in a moving car\". Passed as a shared\n // reference, not a cross-domain import — see SpeedRef.\n const speedRef: SpeedRef = { mps: null, at: 0 };\n return {\n deviceContext: createDeviceContextDomain(),\n connectivity: createConnectivityDomain(collectors),\n capability: createCapabilityDomain(collectors),\n interaction: createInteractionDomain(),\n spatial: createSpatialDomain(collectors, speedRef),\n visual: createVisualDomain(),\n activity: createActivityDomain(speedRef),\n };\n}\n\n/** Which domains this build can actually answer. */\nexport const IMPLEMENTED_DOMAINS = [\n 'deviceContext',\n 'connectivity',\n 'capability',\n 'interaction',\n 'spatial',\n 'visual',\n 'activity',\n] as const;\n","/**\n * The mounted half of the camera tasks — scanning and recording.\n *\n * The app renders this ONCE, near the root. It draws nothing until a plugin\n * asks for a scan or a recording; then it takes over the screen with a live\n * preview and settles the plugin's promise when the work is done.\n *\n * WHY IT IS VISIBLE, unlike VisualEngineHost's 1×1 invisible camera. These two\n * tasks need the USER to aim: a barcode has to be framed, a clip has to point\n * at something. An invisible scanner is not a privacy nicety, it is unusable —\n * the user cannot tell what the camera sees or when it has read something.\n *\n * WHY IT CAN ALWAYS BE CANCELLED. The camera is on and pointed at whatever is\n * in front of the person holding it. A capture surface with no way out is a\n * trap; the cancel path settles the promise with a distinct CANCELLED code so\n * the caller can tell \"they chose not to\" from \"it broke\".\n */\nimport React, { useEffect, useRef, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { CameraView, useCameraPermissions, useMicrophonePermissions } from 'expo-camera';\n\nimport { cameraTasks, cameraError, type CameraTask } from './cameraTasks';\n\n/** How long a scan waits before giving up. */\nconst SCAN_TIMEOUT_MS = 30_000;\n/** Ceiling on a single clip, so a forgotten recording cannot fill the disk. */\nconst MAX_RECORD_MS = 60_000;\n\nexport function CameraTaskHost(): React.ReactElement | null {\n const [task, setTask] = useState<CameraTask | null>(cameraTasks.current());\n const [camPermission, requestCamPermission] = useCameraPermissions();\n const [micPermission, requestMicPermission] = useMicrophonePermissions();\n const cameraRef = useRef<CameraView | null>(null);\n /** Guards against a barcode firing twice before the task is cleared. */\n const settling = useRef(false);\n\n // Announce that a surface exists. Without this the broker rejects every\n // request immediately, which is the correct behaviour when no host is\n // mounted — a plugin must never await a promise nothing can settle.\n useEffect(() => {\n cameraTasks.setHostMounted(true);\n return () => cameraTasks.setHostMounted(false);\n }, []);\n\n useEffect(() => cameraTasks.subscribe(() => {\n settling.current = false;\n setTask(cameraTasks.current());\n }), []);\n\n // Ask for permission when a task arrives, not at app start: a camera prompt\n // with no visible reason attached is one users refuse.\n useEffect(() => {\n if (!task) return;\n let cancelled = false;\n void (async () => {\n const cam = camPermission?.granted ? camPermission : await requestCamPermission();\n if (cancelled) return;\n if (!cam?.granted) {\n cameraTasks.fail(cameraError(\n 'PERMISSION_DENIED',\n 'Camera permission was not granted. Enable it for this app in Settings > Privacy > Camera.',\n ));\n return;\n }\n if (task.kind === 'record' && task.opts.withAudio) {\n const mic = micPermission?.granted ? micPermission : await requestMicPermission();\n if (cancelled) return;\n if (!mic?.granted) {\n // Refuse rather than silently recording a silent video — a caller\n // that asked for audio and got none has no way to find out.\n cameraTasks.fail(cameraError(\n 'PERMISSION_DENIED',\n 'Microphone permission was not granted, so a video with sound cannot be recorded.',\n ));\n }\n }\n })();\n return () => { cancelled = true; };\n }, [task]);\n\n // Scan timeout. A scan that never finds a code must END, and say so: leaving\n // the camera up indefinitely is both a battery drain and a promise that\n // never settles.\n useEffect(() => {\n if (task?.kind !== 'scan') return;\n const ms = task.opts.timeoutMs ?? SCAN_TIMEOUT_MS;\n const t = setTimeout(() => {\n cameraTasks.fail(cameraError('TIMEOUT', `No barcode was found within ${Math.round(ms / 1000)}s.`));\n }, ms);\n return () => clearTimeout(t);\n }, [task]);\n\n // Recording runs for its duration and resolves with the file.\n useEffect(() => {\n if (task?.kind !== 'record' || !camPermission?.granted) return;\n let cancelled = false;\n const maxMs = Math.min(task.opts.maxDurationMs ?? 15_000, MAX_RECORD_MS);\n const startedAt = Date.now();\n\n void (async () => {\n // A frame late is better than a crash: the ref is attached on the render\n // after the task arrives.\n await new Promise(r => setTimeout(r, 50));\n if (cancelled || !cameraRef.current) return;\n try {\n const video = await cameraRef.current.recordAsync({ maxDuration: Math.ceil(maxMs / 1000) });\n if (cancelled) return;\n if (!video?.uri) {\n cameraTasks.fail(cameraError('INTERNAL', 'Recording finished but produced no file.'));\n return;\n }\n cameraTasks.complete({ uri: video.uri, durationMs: Date.now() - startedAt });\n } catch (err: any) {\n if (!cancelled) cameraTasks.fail(cameraError('INTERNAL', err?.message || 'Recording failed.'));\n }\n })();\n\n return () => {\n cancelled = true;\n // Stop the hardware if the surface goes away mid-take.\n try { cameraRef.current?.stopRecording(); } catch { /* already stopped */ }\n };\n }, [task, camPermission?.granted]);\n\n if (!task || !camPermission?.granted) return null;\n\n const isScan = task.kind === 'scan';\n\n return (\n <View style={styles.overlay}>\n <CameraView\n ref={cameraRef}\n style={StyleSheet.absoluteFill}\n facing=\"back\"\n mode={isScan ? 'picture' : 'video'}\n barcodeScannerSettings={\n isScan && task.opts.formats?.length\n ? { barcodeTypes: task.opts.formats as any }\n : undefined\n }\n onBarcodeScanned={\n isScan\n ? ({ data, type }) => {\n // onBarcodeScanned fires continuously while a code is in frame.\n // Without this guard the same scan resolves, then tries to\n // resolve an already-cleared task on the very next frame.\n if (settling.current) return;\n settling.current = true;\n cameraTasks.complete({ data, format: type });\n }\n : undefined\n }\n />\n\n <View style={styles.hud} pointerEvents=\"box-none\">\n <Text style={styles.hint}>\n {isScan ? 'Point the camera at the code' : 'Recording…'}\n </Text>\n <Pressable style={styles.cancel} onPress={() => cameraTasks.cancel()} accessibilityRole=\"button\">\n <Text style={styles.cancelText}>Cancel</Text>\n </Pressable>\n </View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n overlay: { ...StyleSheet.absoluteFillObject, backgroundColor: '#000', zIndex: 9999 },\n hud: { flex: 1, justifyContent: 'space-between', paddingVertical: 64, alignItems: 'center' },\n hint: { color: '#fff', fontSize: 16, textAlign: 'center', paddingHorizontal: 24 },\n cancel: { paddingHorizontal: 28, paddingVertical: 14, borderRadius: 24, backgroundColor: 'rgba(255,255,255,0.18)' },\n cancelText: { color: '#fff', fontSize: 16, fontWeight: '600' },\n});\n\nexport default CameraTaskHost;\n","/**\n * Replay — run guidance logic against a RECORDED WALK instead of a phone.\n *\n * WHY THIS EXISTS. Everything device-shaped is verifiable only on real\n * hardware: the simulator has no camera, no pedometer, no moving GPS. So the\n * loop was write → ask for a build → wait → ask someone to walk around the\n * block. We could unit-test that the window clamp picks the smaller number; we\n * could not test that THE GUIDE SAYS \"TURN RIGHT\" AT THE CORNER, which is the\n * only thing a user cares about.\n *\n * A fixture is one real walk, recorded once by the app (see\n * docs/SPEC_device_walk_recorder.md) and replayed here forever.\n *\n * IT REPLAYS AT THE SNAPSHOT LEVEL, not the sensor level, and that is a\n * deliberate boundary. The engines already have their own tests — the turn\n * detector, the activity classifier, the window clamp. What had no test was\n * everything ABOVE them. So this implements the same `DeviceState` surface a\n * plugin sees and serves recorded snapshots through it: the code under test is\n * the guidance logic, unchanged, unaware it is not on a phone.\n *\n * THE CLOCK IS VIRTUAL. A ten-minute walk runs in milliseconds, and a test\n * never waits. `t` in a fixture is milliseconds since the recording started,\n * never a wall clock, so a fixture does not rot when the date changes.\n *\n * WHAT IT REFUSES TO SMOOTH OVER: a fixture's `unknown`s, `null`s and refusals\n * are replayed exactly as recorded. They are the most valuable records in the\n * file — they are what a guide must handle correctly, and precisely what a\n * hand-written fixture would never contain.\n */\nimport type { DeviceState } from '@ateam-ai-mobile/plugin-runtime';\n\n/** One tick of a recorded walk. Domains are stored verbatim, never normalised. */\nexport interface WalkSample {\n /** Milliseconds since recording started. NOT a wall clock. */\n t: number;\n spatial?: any;\n activity?: any;\n visual?: any;\n deviceContext?: any;\n interaction?: any;\n connectivity?: any;\n capability?: any;\n environment?: any;\n}\n\n/**\n * A domain whose `start()` FAILED during recording.\n *\n * Recorded so a replayed walk fails the same way the real one did. Without it,\n * a fixture where the pedometer was denied is indistinguishable from a fixture\n * where activity simply never appeared — and only one of those is a bug.\n */\nexport interface WalkRefusal {\n domain: string;\n /** The error code the real call threw, e.g. RESTRICTED / UNSUPPORTED. */\n code: string;\n message?: string;\n}\n\nexport interface WalkFixture {\n /** Provenance. Without `sdkCommit`, a fixture that stops matching cannot be\n * told apart from a regression. */\n header: {\n recordedAt?: string;\n appVersion?: string;\n sdkCommit?: string;\n device?: string;\n os?: string;\n /** What the visual engine reported it was really holding. */\n retentionMs?: number | null;\n withFrames?: boolean;\n note?: string;\n };\n refusals?: WalkRefusal[];\n /** Ascending by `t`. */\n samples: WalkSample[];\n}\n\n/** A clock a test drives by hand. Nothing here ever waits. */\nexport interface ReplayClock {\n /** Current position in the walk, ms since start. */\n now(): number;\n /** Jump forward. Returns the new position. */\n advance(ms: number): number;\n /** Jump to an absolute offset. */\n seek(ms: number): number;\n /** Run to the end of the fixture. */\n end(): number;\n /** Total length of the recorded walk. */\n durationMs(): number;\n}\n\nexport interface ReplaySession {\n deviceState: DeviceState;\n clock: ReplayClock;\n /** Which domains had a session opened by the code under test. */\n started(): string[];\n}\n\nconst DOMAINS = [\n 'spatial', 'visual', 'activity', 'deviceContext',\n 'connectivity', 'capability', 'environment', 'interaction',\n] as const;\n\n/** The snapshot a domain returns when the fixture has nothing for it. */\nfunction unknownSnapshot(domain: string): any {\n const quality = { freshness: { state: 'unknown', observedAt: null, ageMs: null }, confidence: { level: 'unknown' } };\n switch (domain) {\n case 'spatial':\n return { ...{ quality }, position: null, movement: { speedMps: null, bearingDeg: null, distanceRecentM: null, state: 'unknown' }, orientation: { headingDeg: null }, recentPath: [] };\n case 'visual':\n return { quality, camera: { available: false, active: false, facing: 'unknown' }, latest: null, recentTimeline: { windowMs: 0, frames: [] } };\n case 'activity':\n return { quality, human: { state: 'unknown', since: null, confidence: { level: 'unknown' } }, devicePose: { placement: 'unknown', orientation: 'unknown', confidence: { level: 'unknown' } }, recentTransitions: [] };\n default:\n return { quality };\n }\n}\n\nfunction refusal(domain: string, code: string, message?: string): Error {\n const err: any = new Error(message || `deviceState.${domain}: refused during recording (${code}).`);\n err.code = code;\n err.domain = domain;\n return err;\n}\n\n/**\n * Build a `DeviceState` backed by a recorded walk.\n *\n * Hand the result to the code under test exactly as the host would hand it the\n * real one — it cannot tell the difference, which is the point.\n */\nexport function createReplayDeviceState(fixture: WalkFixture): ReplaySession {\n const samples = [...(fixture.samples || [])].sort((a, b) => a.t - b.t);\n const duration = samples.length ? samples[samples.length - 1].t : 0;\n const refusals = new Map((fixture.refusals || []).map(r => [r.domain, r]));\n const openSessions = new Set<string>();\n\n let cursor = 0;\n\n const clock: ReplayClock = {\n now: () => cursor,\n advance: ms => (cursor = Math.min(duration, Math.max(0, cursor + ms))),\n seek: ms => (cursor = Math.min(duration, Math.max(0, ms))),\n end: () => (cursor = duration),\n durationMs: () => duration,\n };\n\n /**\n * The newest sample AT OR BEFORE the cursor.\n *\n * Never the nearest, and never interpolated. A guide reads what the device\n * had ALREADY produced at that instant; letting a test see a value from\n * slightly in the future would make it pass on information the real code\n * could not have had.\n */\n const sampleAt = (): WalkSample | null => {\n let found: WalkSample | null = null;\n for (const s of samples) {\n if (s.t > cursor) break;\n found = s;\n }\n return found;\n };\n\n const domainFor = (name: string): any => ({\n async getSnapshot() {\n const s = sampleAt();\n const recorded = s ? (s as any)[name] : undefined;\n // Nothing recorded is an honest unknown, not an empty object — the same\n // answer the real gate gives for a domain it cannot serve.\n return recorded ?? unknownSnapshot(name);\n },\n async start() {\n const r = refusals.get(name);\n // Replay the refusal the real walk hit. A fixture recorded with the\n // pedometer denied must fail here, or a test proves the guide works in\n // conditions the user never had.\n if (r) throw refusal(name, r.code, r.message);\n openSessions.add(name);\n return { id: `replay_${name}`, intent: 'replay', startedAt: 0 } as any;\n },\n async stop() { openSessions.delete(name); },\n subscribe() {\n // Deliberately unsupported: a fixture is a timeline a test steps through,\n // so events would have to fire on `advance()`, and a subscription that\n // silently never fires reads as \"nothing has changed\" — the failure this\n // whole layer exists to prevent. Step the clock and read instead.\n throw refusal(name, 'UNSUPPORTED', `deviceState.${name}.subscribe is not replayable — advance the clock and read.`);\n },\n });\n\n const deviceState: any = {};\n for (const d of DOMAINS) deviceState[d] = domainFor(d);\n\n return { deviceState: deviceState as DeviceState, clock, started: () => [...openSessions] };\n}\n\n/** Parse a `.jsonl` recording into a fixture. First line is the header. */\nexport function parseWalkJsonl(text: string): WalkFixture {\n const lines = text.split('\\n').map(l => l.trim()).filter(Boolean);\n if (!lines.length) throw new Error('Empty recording — no header line.');\n\n const first = JSON.parse(lines[0]);\n const header = first.header ?? first;\n const refusals: WalkRefusal[] = first.refusals ?? [];\n const samples: WalkSample[] = [];\n\n for (const line of lines.slice(1)) {\n const rec = JSON.parse(line);\n // Refusals may arrive as their own records mid-file (a session that failed\n // partway through), not only in the header.\n if (rec.refusal) { refusals.push(rec.refusal); continue; }\n if (typeof rec.t === 'number') samples.push(rec);\n }\n\n // A truncated file is still usable — a walk that ended on a flat battery\n // should not cost us the part that was recorded.\n return { header, refusals, samples };\n}\n"],"mappings":"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","names":["AsyncStorage","FileSystem","AsyncStorage","sub","latest","latest","err","s","AsyncStorage"]}
|