@ateam-ai-mobile/expo 0.1.4 → 0.1.7
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/android/build.gradle +16 -0
- package/android/src/main/java/expo/modules/adavision/AdaVisionModule.kt +75 -0
- package/dist/attachmentCache-DhQTqi2E.d.mts +18 -0
- package/dist/attachmentCache-DhQTqi2E.d.ts +18 -0
- package/dist/bootstrap.d.mts +95 -0
- package/dist/bootstrap.d.ts +95 -0
- package/dist/bootstrap.js +2473 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/bootstrap.mjs +64 -0
- package/dist/bootstrap.mjs.map +1 -0
- package/dist/chunk-4BMWVN25.mjs +91 -0
- package/dist/chunk-4BMWVN25.mjs.map +1 -0
- package/dist/chunk-4XYXJYN5.mjs +1513 -0
- package/dist/chunk-4XYXJYN5.mjs.map +1 -0
- package/dist/chunk-5HM6UA2K.mjs +571 -0
- package/dist/chunk-5HM6UA2K.mjs.map +1 -0
- package/dist/chunk-6LXF3JVX.mjs +96 -0
- package/dist/chunk-6LXF3JVX.mjs.map +1 -0
- package/dist/chunk-FF6ZZZXH.mjs +71 -0
- package/dist/chunk-FF6ZZZXH.mjs.map +1 -0
- package/dist/chunk-KOXF2FZM.mjs +92 -0
- package/dist/chunk-KOXF2FZM.mjs.map +1 -0
- package/dist/chunk-L4V7OCJ3.mjs +71 -0
- package/dist/chunk-L4V7OCJ3.mjs.map +1 -0
- package/dist/chunk-TZXWVYYR.mjs +67 -0
- package/dist/chunk-TZXWVYYR.mjs.map +1 -0
- package/dist/chunk-VQJPSLHP.mjs +1801 -0
- package/dist/chunk-VQJPSLHP.mjs.map +1 -0
- package/dist/chunk-WMJKH4XE.mjs +34 -0
- package/dist/chunk-WMJKH4XE.mjs.map +1 -0
- package/dist/chunk-XQSY6MHP.mjs +213 -0
- package/dist/chunk-XQSY6MHP.mjs.map +1 -0
- package/dist/createBridge.mjs +2 -0
- package/dist/createBridge.mjs.map +1 -1
- package/dist/device/createDeviceProvider.d.mts +23 -0
- package/dist/device/createDeviceProvider.d.ts +23 -0
- package/dist/device/createDeviceProvider.js +967 -0
- package/dist/device/createDeviceProvider.js.map +1 -0
- package/dist/device/createDeviceProvider.mjs +17 -0
- package/dist/device/createDeviceProvider.mjs.map +1 -0
- package/dist/device-bridge/collectors/appEngagement.d.mts +10 -0
- package/dist/device-bridge/collectors/appEngagement.d.ts +10 -0
- package/dist/device-bridge/collectors/appEngagement.js +128 -0
- package/dist/device-bridge/collectors/appEngagement.js.map +1 -0
- package/dist/device-bridge/collectors/appEngagement.mjs +12 -0
- package/dist/device-bridge/collectors/appEngagement.mjs.map +1 -0
- package/dist/device-bridge/collectors/health.d.mts +24 -0
- package/dist/device-bridge/collectors/health.d.ts +24 -0
- package/dist/device-bridge/collectors/health.js +238 -0
- package/dist/device-bridge/collectors/health.js.map +1 -0
- package/dist/device-bridge/collectors/health.mjs +18 -0
- package/dist/device-bridge/collectors/health.mjs.map +1 -0
- package/dist/device-bridge/cookies.d.mts +49 -0
- package/dist/device-bridge/cookies.d.ts +49 -0
- package/dist/device-bridge/cookies.js +90 -0
- package/dist/device-bridge/cookies.js.map +1 -0
- package/dist/device-bridge/cookies.mjs +14 -0
- package/dist/device-bridge/cookies.mjs.map +1 -0
- package/dist/device-bridge/index.d.mts +483 -0
- package/dist/device-bridge/index.d.ts +483 -0
- package/dist/device-bridge/index.js +2084 -0
- package/dist/device-bridge/index.js.map +1 -0
- package/dist/device-bridge/index.mjs +32 -0
- package/dist/device-bridge/index.mjs.map +1 -0
- package/dist/device-bridge/nfc.d.mts +30 -0
- package/dist/device-bridge/nfc.d.ts +30 -0
- package/dist/device-bridge/nfc.js +109 -0
- package/dist/device-bridge/nfc.js.map +1 -0
- package/dist/device-bridge/nfc.mjs +14 -0
- package/dist/device-bridge/nfc.mjs.map +1 -0
- package/dist/index.d.mts +493 -18
- package/dist/index.d.ts +493 -18
- package/dist/index.js +1409 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -194
- package/dist/index.mjs.map +1 -1
- package/dist/oauth/expoOAuth.d.mts +18 -0
- package/dist/oauth/expoOAuth.d.ts +18 -0
- package/dist/oauth/expoOAuth.js +102 -0
- package/dist/oauth/expoOAuth.js.map +1 -0
- package/dist/oauth/expoOAuth.mjs +8 -0
- package/dist/oauth/expoOAuth.mjs.map +1 -0
- package/dist/types-BRanJYTn.d.mts +138 -0
- package/dist/types-BRanJYTn.d.ts +138 -0
- package/dist/vision/index.d.mts +81 -0
- package/dist/vision/index.d.ts +81 -0
- package/dist/vision/index.js +101 -0
- package/dist/vision/index.js.map +1 -0
- package/dist/vision/index.mjs +28 -0
- package/dist/vision/index.mjs.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/AdaVision.podspec +28 -0
- package/ios/AdaVisionModule.swift +432 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/analytics/coremldata.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/coremldata.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/metadata.json +773 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/analytics/coremldata.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/coremldata.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/model.mil +720 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/weights/weight.bin +0 -0
- package/ios/Resources/yolov8n-oiv7.mlmodelc/model1/coremldata.bin +0 -0
- package/package.json +107 -7
- package/src/bootstrap.ts +159 -0
- package/src/camera/CameraTaskHost.tsx +175 -0
- package/src/camera/cameraTasks.ts +129 -0
- package/src/device/createDeviceProvider.ts +832 -0
- package/src/device-bridge/actions/calendar-actions.ts +217 -0
- package/src/device-bridge/actions/dnd.ts +74 -0
- package/src/device-bridge/actions/health-actions.ts +58 -0
- package/src/device-bridge/actions/index.ts +12 -0
- package/src/device-bridge/actions/messaging.ts +74 -0
- package/src/device-bridge/actions/navigation.ts +88 -0
- package/src/device-bridge/actions/queue-poller.ts +151 -0
- package/src/device-bridge/background.ts +137 -0
- package/src/device-bridge/biometrics.ts +64 -0
- package/src/device-bridge/capabilities.ts +218 -0
- package/src/device-bridge/collectors/appEngagement.ts +120 -0
- package/src/device-bridge/collectors/battery.ts +83 -0
- package/src/device-bridge/collectors/calendar.ts +162 -0
- package/src/device-bridge/collectors/camera.ts +27 -0
- package/src/device-bridge/collectors/connectivity.ts +60 -0
- package/src/device-bridge/collectors/contacts.ts +200 -0
- package/src/device-bridge/collectors/health.ts +299 -0
- package/src/device-bridge/collectors/index.ts +12 -0
- package/src/device-bridge/collectors/location.ts +224 -0
- package/src/device-bridge/collectors/mood.ts +141 -0
- package/src/device-bridge/collectors/notifications.ts +70 -0
- package/src/device-bridge/cookies.ts +89 -0
- package/src/device-bridge/dynamic-connector.ts +422 -0
- package/src/device-bridge/index.ts +467 -0
- package/src/device-bridge/nfc.ts +85 -0
- package/src/device-bridge/plugin.ts +71 -0
- package/src/device-bridge/relay-client.ts +237 -0
- package/src/device-bridge/scanner.ts +42 -0
- package/src/device-bridge/sharing.ts +35 -0
- package/src/device-bridge/types.ts +326 -0
- package/src/device-state/activityClassifier.ts +151 -0
- package/src/device-state/activityEngine.ts +252 -0
- package/src/device-state/collectors.ts +31 -0
- package/src/device-state/domains/activity.ts +93 -0
- package/src/device-state/domains/capability.ts +60 -0
- package/src/device-state/domains/connectivity.ts +60 -0
- package/src/device-state/domains/deviceContext.ts +55 -0
- package/src/device-state/domains/interaction.ts +59 -0
- package/src/device-state/domains/spatial.ts +283 -0
- package/src/device-state/domains/visual.ts +100 -0
- package/src/device-state/headingSource.ts +84 -0
- package/src/device-state/index.ts +77 -0
- package/src/device-state/quality.ts +60 -0
- package/src/device-state/replay.ts +220 -0
- package/src/device-state/turnDetector.ts +121 -0
- package/src/device-state/visualEngine.ts +341 -0
- package/src/haptics.ts +4 -0
- package/src/index.ts +37 -0
- package/src/oauth/expoOAuth.tsx +113 -0
- package/src/vision/analysis.js +80 -0
- package/src/vision/index.ts +144 -0
- package/src/vision/ring.js +35 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
private index;
|
|
6
|
-
private loaded;
|
|
7
|
-
private indexKey;
|
|
8
|
-
/** Pass `indexKey` when migrating from an app that already persisted an
|
|
9
|
-
* index under a different AsyncStorage key — otherwise existing users
|
|
10
|
-
* lose every cached preview on upgrade. */
|
|
11
|
-
constructor(opts?: {
|
|
12
|
-
indexKey?: string;
|
|
13
|
-
});
|
|
14
|
-
ready(): Promise<void>;
|
|
15
|
-
save(jobId: string, attachments: OutgoingAttachment[]): Promise<void>;
|
|
16
|
-
get(jobId: string): AttachmentPreview[] | undefined;
|
|
17
|
-
}
|
|
1
|
+
import { HapticsAPI, BundleStorage, BundleCacheEntry, DeviceState, PluginNative } from '@ateam-ai-mobile/plugin-runtime';
|
|
2
|
+
import { E as ExpoAttachmentCache } from './attachmentCache-DhQTqi2E.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import '@ateam-ai-mobile/core';
|
|
18
5
|
|
|
19
6
|
declare const expoHaptics: HapticsAPI;
|
|
20
7
|
|
|
@@ -35,6 +22,485 @@ declare class ExpoBundleStorage implements BundleStorage {
|
|
|
35
22
|
clearAll(): Promise<void>;
|
|
36
23
|
}
|
|
37
24
|
|
|
25
|
+
/**
|
|
26
|
+
* The explicit dependencies the composition root injects into the domains.
|
|
27
|
+
*
|
|
28
|
+
* Collectors arrive by INJECTION rather than import: the two repos keep
|
|
29
|
+
* device-bridge in different places, so a hard relative import can only compile
|
|
30
|
+
* in one of them. Injection also matches how the rest of this SDK takes host
|
|
31
|
+
* capabilities, and lets a test drive the engines without an Expo runtime.
|
|
32
|
+
*/
|
|
33
|
+
interface DeviceStateCollectors {
|
|
34
|
+
connectivity?: {
|
|
35
|
+
collectConnectivity(): Promise<any>;
|
|
36
|
+
};
|
|
37
|
+
location?: {
|
|
38
|
+
getPosition(opts?: {
|
|
39
|
+
accuracy?: 'low' | 'high';
|
|
40
|
+
}): Promise<any>;
|
|
41
|
+
watchPosition(cb: (pos: any) => void): () => void;
|
|
42
|
+
hasPermission(): Promise<boolean>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Device Bridge v2 — the runtime behind `props.deviceState`.
|
|
48
|
+
*
|
|
49
|
+
* R5 split the former monolith into one module per domain (./domains/*), a
|
|
50
|
+
* single quality module that owns freshness/confidence/unknown (./quality), and
|
|
51
|
+
* this composition root, which wires the domains and injects their dependencies.
|
|
52
|
+
* The behaviour — and every snapshot shape — is unchanged; only the file layout is.
|
|
53
|
+
*
|
|
54
|
+
* WHAT IS AND IS NOT HERE — stated up front, because a half-built state layer
|
|
55
|
+
* that pretends to be whole is the exact failure this whole effort exists to
|
|
56
|
+
* end:
|
|
57
|
+
*
|
|
58
|
+
* deviceContext power ✓ thermal ✗ memory/storage ✗
|
|
59
|
+
* connectivity reachability ✓ transport ✓ quality/stability/metered ✗
|
|
60
|
+
* capability permission ✓ availability ✗ restriction ✗
|
|
61
|
+
* interaction app lifecycle ✓ screenOn ✗ audio ✗
|
|
62
|
+
* spatial position ✓ path ✓ (while a session is open) heading/speed ✗
|
|
63
|
+
* visual frames ✓ heading ✓ freshness ✓ blur/exposure ✗
|
|
64
|
+
* activity / environment — NOT IMPLEMENTED AT ALL
|
|
65
|
+
*
|
|
66
|
+
* Everything marked ✗ reports `unknown`, which is a real answer in this design
|
|
67
|
+
* and the only honest one. It never reports a plausible default: an invented
|
|
68
|
+
* 'nominal' thermal state is indistinguishable from a measured one, and that is
|
|
69
|
+
* how 56 fabrications reached users from the layer below.
|
|
70
|
+
*
|
|
71
|
+
* A domain with no engine here is not simply missing: `gateDeviceState` (in
|
|
72
|
+
* plugin-runtime/src/deviceStateHost.ts) fills the gap with a domain whose
|
|
73
|
+
* reads answer explicit `unknown`s and whose ACTIONS throw. So a plugin can
|
|
74
|
+
* call any of the eight without an existence check, and still cannot mistake
|
|
75
|
+
* "not built" for "nothing to report". `environment` is deliberately absent
|
|
76
|
+
* from the composition below for exactly that reason.
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build the semantic surface.
|
|
81
|
+
*
|
|
82
|
+
* Only implemented domains appear. `environment` is deliberately absent: a
|
|
83
|
+
* plugin touching it gets a TypeError naming it, which is a far better failure
|
|
84
|
+
* than a snapshot of nulls that reads like a quiet device.
|
|
85
|
+
*/
|
|
86
|
+
declare function createDeviceState(collectors?: DeviceStateCollectors): Partial<DeviceState>;
|
|
87
|
+
/** Which domains this build can actually answer. */
|
|
88
|
+
declare const IMPLEMENTED_DOMAINS: readonly ["deviceContext", "connectivity", "capability", "interaction", "spatial", "visual", "activity"];
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The Visual engine — a rolling frame timeline the Visual domain reads.
|
|
92
|
+
*
|
|
93
|
+
* WHY THIS IS SHAPED ODDLY. expo-camera can only produce frames through a
|
|
94
|
+
* MOUNTED `<CameraView>`; there is no headless capture API. So the engine is
|
|
95
|
+
* split in two:
|
|
96
|
+
*
|
|
97
|
+
* this module — the state: the ring, the session, the intent, the cadence.
|
|
98
|
+
* Pure TS, no React, so the domain and tests can use it.
|
|
99
|
+
* VisualEngineHost — a component the app mounts once. When a session is open
|
|
100
|
+
* it mounts the camera and feeds frames in; otherwise it
|
|
101
|
+
* renders nothing and the camera is off.
|
|
102
|
+
*
|
|
103
|
+
* The design's rule is that `getSnapshot()` must be a CACHED read that does not
|
|
104
|
+
* open the camera or take a picture — "avoid repeatedly starting the camera or
|
|
105
|
+
* capturing a new image for every plugin-tool call during an interactive
|
|
106
|
+
* session". The ring is what makes that possible: capture runs on the engine's
|
|
107
|
+
* own cadence, and reads serve whatever is already there.
|
|
108
|
+
*
|
|
109
|
+
* THE CADENCE IS THE ENGINE'S CHOICE, NOT THE PLUGIN'S. A plugin declares an
|
|
110
|
+
* INTENT — `navigation`, `document`, `ocr` — and this picks the interval. That
|
|
111
|
+
* is the whole point of the semantic layer: a caller asking for frames every
|
|
112
|
+
* 500ms is specifying an implementation, and cannot know the analysis latency,
|
|
113
|
+
* the battery state or the thermal headroom. Intent survives those changing;
|
|
114
|
+
* a number does not.
|
|
115
|
+
*
|
|
116
|
+
* EACH FRAME CARRIES ITS ORIENTATION — heading, read at capture time. Without
|
|
117
|
+
* it a frame cannot support a guidance instruction: "turn left at the corner"
|
|
118
|
+
* needs to know which way the lens faced. Reading it afterwards would pair a
|
|
119
|
+
* frame with a heading from after the user turned, which is how a guide
|
|
120
|
+
* describes the wrong junction with confidence.
|
|
121
|
+
*
|
|
122
|
+
* POSITION IS NOT ON THE FRAME, and that is the freeze's decision, not an
|
|
123
|
+
* omission. VisualFrameRef (API_FREEZE §4) carries `orientation` only; where
|
|
124
|
+
* the user IS belongs to Spatial. A caller that needs both correlates them by
|
|
125
|
+
* `timestamp` — both snapshots carry one. Duplicating position here would
|
|
126
|
+
* create a second copy that can disagree with Spatial's, which is precisely
|
|
127
|
+
* the two-descriptions-of-one-thing problem this whole layer exists to end.
|
|
128
|
+
*
|
|
129
|
+
* NOT IMPLEMENTED, and reported as `unknown`/null rather than guessed:
|
|
130
|
+
* per-frame blur, exposure and obstruction (need image analysis), and
|
|
131
|
+
* pitch/roll (need device-motion, not installed). A frame too dark to use must
|
|
132
|
+
* not be described as `exposure: 'good'` because nothing looked at it.
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
/** A frame in the ring. `uri` is a local file the app can read; nothing is uploaded. */
|
|
136
|
+
interface VisualFrame {
|
|
137
|
+
handle: {
|
|
138
|
+
id: string;
|
|
139
|
+
mimeType: string;
|
|
140
|
+
expiresAt: number | null;
|
|
141
|
+
};
|
|
142
|
+
timestamp: number;
|
|
143
|
+
/**
|
|
144
|
+
* WHERE THE CAMERA WAS POINTING, stamped at capture time.
|
|
145
|
+
*
|
|
146
|
+
* A frame without this is nearly useless for guidance: "turn left at the
|
|
147
|
+
* corner" needs to know which way the lens faced and where the user stood.
|
|
148
|
+
* And it has to be captured WITH the frame, not read afterwards — by the
|
|
149
|
+
* time a snapshot is read the user has walked on, and pairing a frame with a
|
|
150
|
+
* later heading is how a guide describes the wrong junction confidently.
|
|
151
|
+
*
|
|
152
|
+
* Every field is nullable and null means NOT MEASURED. `headingDeg: 0` is due
|
|
153
|
+
* north, so a missing compass must not collapse to 0.
|
|
154
|
+
*/
|
|
155
|
+
orientation?: {
|
|
156
|
+
/** True heading if available, else magnetic. Null when the compass said nothing. */
|
|
157
|
+
headingDeg?: number | null;
|
|
158
|
+
/** Not measured — needs device-motion, which is not installed. */
|
|
159
|
+
pitchDeg?: number | null;
|
|
160
|
+
rollDeg?: number | null;
|
|
161
|
+
};
|
|
162
|
+
quality?: {
|
|
163
|
+
blur?: string;
|
|
164
|
+
exposure?: string;
|
|
165
|
+
obstructed?: boolean | null;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Supplies the spatial context stamped onto each frame.
|
|
170
|
+
*
|
|
171
|
+
* Injected rather than imported for the same reason the collectors are, and
|
|
172
|
+
* because it must be CHEAP: it is read once per capture, on the engine's
|
|
173
|
+
* cadence. Both getters return null rather than throwing — a frame with no
|
|
174
|
+
* heading is still a usable frame, and losing the picture because the compass
|
|
175
|
+
* was busy would be the wrong trade.
|
|
176
|
+
*/
|
|
177
|
+
interface VisualContextProvider {
|
|
178
|
+
heading(): {
|
|
179
|
+
headingDeg: number | null;
|
|
180
|
+
} | null;
|
|
181
|
+
}
|
|
182
|
+
type VisualIntentName = 'navigation' | 'document' | 'ocr' | 'shopping' | 'inspection' | 'accessibility' | 'stream' | 'highQuality';
|
|
183
|
+
interface VisualSessionState {
|
|
184
|
+
id: string;
|
|
185
|
+
intent: VisualIntentName;
|
|
186
|
+
startedAt: number;
|
|
187
|
+
/** Set while the app is backgrounded. The session SURVIVES; the camera does not. */
|
|
188
|
+
suspended: boolean;
|
|
189
|
+
}
|
|
190
|
+
type Listener$1 = () => void;
|
|
191
|
+
declare class VisualEngine {
|
|
192
|
+
private frames;
|
|
193
|
+
private session;
|
|
194
|
+
private listeners;
|
|
195
|
+
/** Set by the host component while a camera is actually mounted and ready. */
|
|
196
|
+
private cameraReady;
|
|
197
|
+
/** Optional: supplies heading/position stamped onto each frame. */
|
|
198
|
+
private context;
|
|
199
|
+
/** Host DI. Without it frames carry no orientation, and say so with nulls. */
|
|
200
|
+
setContextProvider(p: VisualContextProvider | null): void;
|
|
201
|
+
startSession(intent: VisualIntentName): VisualSessionState;
|
|
202
|
+
stopSession(): void;
|
|
203
|
+
/**
|
|
204
|
+
* Background: suspend, do not end.
|
|
205
|
+
*
|
|
206
|
+
* iOS tears the camera down when the app leaves the foreground. The session
|
|
207
|
+
* is a STATE that survives that; what does not survive is the claim that the
|
|
208
|
+
* camera is live. While suspended the ring is kept but ages — a read gets
|
|
209
|
+
* old frames clearly marked old, rather than a fresh-looking lie.
|
|
210
|
+
*/
|
|
211
|
+
suspend(): void;
|
|
212
|
+
resume(): void;
|
|
213
|
+
getSession(): VisualSessionState | null;
|
|
214
|
+
/** Is a camera mounted and ready right now? Only the host knows. */
|
|
215
|
+
setCameraReady(ready: boolean): void;
|
|
216
|
+
isCameraReady(): boolean;
|
|
217
|
+
profile(): {
|
|
218
|
+
intervalMs: number;
|
|
219
|
+
windowMs: number;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Feed from the native vision session (VisionSession, iOS) — the FAST frame
|
|
223
|
+
* source that replaces the takePictureAsync loop. The frame is IN-MEMORY: the
|
|
224
|
+
* native ring holds the pixels keyed by `id`, and native.vision resolves that
|
|
225
|
+
* id inside the trusted layer. No `uri` crosses here; the quality is a REAL
|
|
226
|
+
* measurement (not `unknown`), and `expiresAt` is the ring's real eviction time.
|
|
227
|
+
* The public VisualFrameRef strip to id-only is owned by the device-state session.
|
|
228
|
+
*/
|
|
229
|
+
pushFrameMeta(meta: {
|
|
230
|
+
id: string;
|
|
231
|
+
timestamp: number;
|
|
232
|
+
expiresAt?: number | null;
|
|
233
|
+
quality?: {
|
|
234
|
+
blur?: string;
|
|
235
|
+
exposure?: string;
|
|
236
|
+
obstructed?: boolean | null;
|
|
237
|
+
};
|
|
238
|
+
}): void;
|
|
239
|
+
private _retentionMs;
|
|
240
|
+
setRetentionMs(ms: number | null): void;
|
|
241
|
+
retentionMs(): number | null;
|
|
242
|
+
/**
|
|
243
|
+
* How far back the timeline ACTUALLY reaches: `min(intent window, native
|
|
244
|
+
* retention)`.
|
|
245
|
+
*
|
|
246
|
+
* The intent asks for a window; native decides what it can afford to keep.
|
|
247
|
+
* Full-res luma at ~24fps is tens of megabytes for a few seconds, so the ring
|
|
248
|
+
* is memory-bounded and shrinks under pressure — `retentionMs` is native
|
|
249
|
+
* publishing what it is really holding.
|
|
250
|
+
*
|
|
251
|
+
* EVERY FRAME IN THE TIMELINE CAN BE ANALYSED. That is the one sentence a
|
|
252
|
+
* plugin author has to hold, and this clamp is what makes it true. The
|
|
253
|
+
* alternative — listing frames past retention marked "not analysable" — was
|
|
254
|
+
* considered and rejected: a caller would have to check a flag before every
|
|
255
|
+
* use, and forgetting throws mid-guidance on a frame the model can see
|
|
256
|
+
* listed. Present-but-unusable is the same trap as an empty rowset reading
|
|
257
|
+
* as "no rows matched".
|
|
258
|
+
*
|
|
259
|
+
* If per-frame history beyond retention is ever needed, the shape is an
|
|
260
|
+
* AGGREGATE on the snapshot ("the view has been unusable for N ms"), never a
|
|
261
|
+
* per-entry flag.
|
|
262
|
+
*/
|
|
263
|
+
effectiveWindowMs(): number;
|
|
264
|
+
/**
|
|
265
|
+
* Time-based, not count-based: the design specifies a rolling WINDOW.
|
|
266
|
+
*
|
|
267
|
+
* Evicts on the EFFECTIVE window, so a frame whose pixels native has already
|
|
268
|
+
* dropped leaves the timeline at the same moment. Without this, `frameById`
|
|
269
|
+
* would start returning null for frames still being listed.
|
|
270
|
+
*/
|
|
271
|
+
private evict;
|
|
272
|
+
timeline(): {
|
|
273
|
+
windowMs: number;
|
|
274
|
+
frames: VisualFrame[];
|
|
275
|
+
};
|
|
276
|
+
latest(): VisualFrame | null;
|
|
277
|
+
/**
|
|
278
|
+
* Resolve a frame handle id (from a VisualFrameRef) back to the live frame so
|
|
279
|
+
* the mechanical layer — native.vision — can read its on-device file. The
|
|
280
|
+
* whole contract of that layer is that it takes an id, never pixels; this is
|
|
281
|
+
* where an id becomes a `handle.uri` it can open. Returns null when the id is
|
|
282
|
+
* unknown OR its frame has aged out of the rolling window (the cache file may
|
|
283
|
+
* also be gone by then) — a real "I can't look at that frame" answer, which
|
|
284
|
+
* the caller must treat as distinct from "I looked and found nothing".
|
|
285
|
+
*/
|
|
286
|
+
frameById(id: string): VisualFrame | null;
|
|
287
|
+
/**
|
|
288
|
+
* Record a MEASURED quality back onto a frame (native.vision.assessQuality).
|
|
289
|
+
* Frames are minted with `quality: unknown` because nothing looks at the
|
|
290
|
+
* pixels at capture time; this is how a real measurement replaces that so a
|
|
291
|
+
* later read of VisualFrameRef.quality reflects it. No-op if the frame has
|
|
292
|
+
* aged out — we never resurrect an evicted frame just to label it.
|
|
293
|
+
*/
|
|
294
|
+
setFrameQuality(id: string, quality: NonNullable<VisualFrame['quality']>): void;
|
|
295
|
+
subscribe(fn: Listener$1): () => void;
|
|
296
|
+
private notify;
|
|
297
|
+
}
|
|
298
|
+
/** One engine per app. The camera is a singleton resource; so is this. */
|
|
299
|
+
declare const visualEngine: VisualEngine;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The mounted half of the camera tasks — scanning and recording.
|
|
303
|
+
*
|
|
304
|
+
* The app renders this ONCE, near the root. It draws nothing until a plugin
|
|
305
|
+
* asks for a scan or a recording; then it takes over the screen with a live
|
|
306
|
+
* preview and settles the plugin's promise when the work is done.
|
|
307
|
+
*
|
|
308
|
+
* WHY IT IS VISIBLE, unlike VisualEngineHost's 1×1 invisible camera. These two
|
|
309
|
+
* tasks need the USER to aim: a barcode has to be framed, a clip has to point
|
|
310
|
+
* at something. An invisible scanner is not a privacy nicety, it is unusable —
|
|
311
|
+
* the user cannot tell what the camera sees or when it has read something.
|
|
312
|
+
*
|
|
313
|
+
* WHY IT CAN ALWAYS BE CANCELLED. The camera is on and pointed at whatever is
|
|
314
|
+
* in front of the person holding it. A capture surface with no way out is a
|
|
315
|
+
* trap; the cancel path settles the promise with a distinct CANCELLED code so
|
|
316
|
+
* the caller can tell "they chose not to" from "it broke".
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
declare function CameraTaskHost(): React.ReactElement | null;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The broker between a plugin's `await` and a mounted camera.
|
|
323
|
+
*
|
|
324
|
+
* WHY THIS EXISTS. expo-camera has no headless capture: scanning a barcode and
|
|
325
|
+
* recording a clip both require a MOUNTED `<CameraView>`. But a plugin calls
|
|
326
|
+
* `native.scanner.scanOnce()` and expects a promise — it has no way to mount
|
|
327
|
+
* anything, and it must not have to.
|
|
328
|
+
*
|
|
329
|
+
* So the API enqueues a TASK here and awaits it; `CameraTaskHost`, mounted once
|
|
330
|
+
* by the app, notices, shows the camera, does the work, and settles the
|
|
331
|
+
* promise. This is the same split as the Visual engine, for the same reason —
|
|
332
|
+
* and it is why `native.camera.scanBarcode` refuses and points here rather than
|
|
333
|
+
* pretending a function call can own a React lifecycle.
|
|
334
|
+
*
|
|
335
|
+
* ONE TASK AT A TIME, and it is the phone that says so: there is one camera. A
|
|
336
|
+
* second request while one is running is REJECTED rather than queued, because a
|
|
337
|
+
* queued scan would open the camera again minutes later, long after the user
|
|
338
|
+
* moved on and with no idea why it appeared.
|
|
339
|
+
*/
|
|
340
|
+
interface ScanTask {
|
|
341
|
+
kind: 'scan';
|
|
342
|
+
opts: {
|
|
343
|
+
formats?: string[];
|
|
344
|
+
timeoutMs?: number;
|
|
345
|
+
};
|
|
346
|
+
resolve: (v: {
|
|
347
|
+
data: string;
|
|
348
|
+
format: string;
|
|
349
|
+
}) => void;
|
|
350
|
+
reject: (e: Error) => void;
|
|
351
|
+
}
|
|
352
|
+
interface RecordTask {
|
|
353
|
+
kind: 'record';
|
|
354
|
+
opts: {
|
|
355
|
+
maxDurationMs?: number;
|
|
356
|
+
withAudio?: boolean;
|
|
357
|
+
};
|
|
358
|
+
resolve: (v: {
|
|
359
|
+
uri: string;
|
|
360
|
+
durationMs: number | null;
|
|
361
|
+
}) => void;
|
|
362
|
+
reject: (e: Error) => void;
|
|
363
|
+
}
|
|
364
|
+
type CameraTask = ScanTask | RecordTask;
|
|
365
|
+
type Listener = () => void;
|
|
366
|
+
declare class CameraTaskBroker {
|
|
367
|
+
private task;
|
|
368
|
+
private listeners;
|
|
369
|
+
/** Set by the host while a camera is mounted and ready. */
|
|
370
|
+
private hostMounted;
|
|
371
|
+
current(): CameraTask | null;
|
|
372
|
+
subscribe(fn: Listener): () => void;
|
|
373
|
+
/**
|
|
374
|
+
* Declare that a `CameraTaskHost` exists.
|
|
375
|
+
*
|
|
376
|
+
* Without it nothing will ever settle the promise, so a request must fail
|
|
377
|
+
* IMMEDIATELY and say why. The alternative is a plugin awaiting forever,
|
|
378
|
+
* which is the worst failure of the set: no error, no timeout, no camera,
|
|
379
|
+
* and a conversation that simply stops.
|
|
380
|
+
*/
|
|
381
|
+
setHostMounted(mounted: boolean): void;
|
|
382
|
+
isHostMounted(): boolean;
|
|
383
|
+
/** Enqueue and hand back the promise the plugin awaits. */
|
|
384
|
+
run<T>(kind: CameraTask['kind'], opts: any): Promise<T>;
|
|
385
|
+
/** Settle the running task successfully and clear it. */
|
|
386
|
+
complete(value: any): void;
|
|
387
|
+
/** Settle the running task with a failure and clear it. */
|
|
388
|
+
fail(err: Error): void;
|
|
389
|
+
/**
|
|
390
|
+
* The user backed out.
|
|
391
|
+
*
|
|
392
|
+
* A distinct code, because "the user chose not to" is not a malfunction and
|
|
393
|
+
* a guide should say something different about it than about a broken lens.
|
|
394
|
+
*/
|
|
395
|
+
cancel(): void;
|
|
396
|
+
private notify;
|
|
397
|
+
}
|
|
398
|
+
/** One broker per app: there is one camera. */
|
|
399
|
+
declare const cameraTasks: CameraTaskBroker;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Replay — run guidance logic against a RECORDED WALK instead of a phone.
|
|
403
|
+
*
|
|
404
|
+
* WHY THIS EXISTS. Everything device-shaped is verifiable only on real
|
|
405
|
+
* hardware: the simulator has no camera, no pedometer, no moving GPS. So the
|
|
406
|
+
* loop was write → ask for a build → wait → ask someone to walk around the
|
|
407
|
+
* block. We could unit-test that the window clamp picks the smaller number; we
|
|
408
|
+
* could not test that THE GUIDE SAYS "TURN RIGHT" AT THE CORNER, which is the
|
|
409
|
+
* only thing a user cares about.
|
|
410
|
+
*
|
|
411
|
+
* A fixture is one real walk, recorded once by the app (see
|
|
412
|
+
* docs/SPEC_device_walk_recorder.md) and replayed here forever.
|
|
413
|
+
*
|
|
414
|
+
* IT REPLAYS AT THE SNAPSHOT LEVEL, not the sensor level, and that is a
|
|
415
|
+
* deliberate boundary. The engines already have their own tests — the turn
|
|
416
|
+
* detector, the activity classifier, the window clamp. What had no test was
|
|
417
|
+
* everything ABOVE them. So this implements the same `DeviceState` surface a
|
|
418
|
+
* plugin sees and serves recorded snapshots through it: the code under test is
|
|
419
|
+
* the guidance logic, unchanged, unaware it is not on a phone.
|
|
420
|
+
*
|
|
421
|
+
* THE CLOCK IS VIRTUAL. A ten-minute walk runs in milliseconds, and a test
|
|
422
|
+
* never waits. `t` in a fixture is milliseconds since the recording started,
|
|
423
|
+
* never a wall clock, so a fixture does not rot when the date changes.
|
|
424
|
+
*
|
|
425
|
+
* WHAT IT REFUSES TO SMOOTH OVER: a fixture's `unknown`s, `null`s and refusals
|
|
426
|
+
* are replayed exactly as recorded. They are the most valuable records in the
|
|
427
|
+
* file — they are what a guide must handle correctly, and precisely what a
|
|
428
|
+
* hand-written fixture would never contain.
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
/** One tick of a recorded walk. Domains are stored verbatim, never normalised. */
|
|
432
|
+
interface WalkSample {
|
|
433
|
+
/** Milliseconds since recording started. NOT a wall clock. */
|
|
434
|
+
t: number;
|
|
435
|
+
spatial?: any;
|
|
436
|
+
activity?: any;
|
|
437
|
+
visual?: any;
|
|
438
|
+
deviceContext?: any;
|
|
439
|
+
interaction?: any;
|
|
440
|
+
connectivity?: any;
|
|
441
|
+
capability?: any;
|
|
442
|
+
environment?: any;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* A domain whose `start()` FAILED during recording.
|
|
446
|
+
*
|
|
447
|
+
* Recorded so a replayed walk fails the same way the real one did. Without it,
|
|
448
|
+
* a fixture where the pedometer was denied is indistinguishable from a fixture
|
|
449
|
+
* where activity simply never appeared — and only one of those is a bug.
|
|
450
|
+
*/
|
|
451
|
+
interface WalkRefusal {
|
|
452
|
+
domain: string;
|
|
453
|
+
/** The error code the real call threw, e.g. RESTRICTED / UNSUPPORTED. */
|
|
454
|
+
code: string;
|
|
455
|
+
message?: string;
|
|
456
|
+
}
|
|
457
|
+
interface WalkFixture {
|
|
458
|
+
/** Provenance. Without `sdkCommit`, a fixture that stops matching cannot be
|
|
459
|
+
* told apart from a regression. */
|
|
460
|
+
header: {
|
|
461
|
+
recordedAt?: string;
|
|
462
|
+
appVersion?: string;
|
|
463
|
+
sdkCommit?: string;
|
|
464
|
+
device?: string;
|
|
465
|
+
os?: string;
|
|
466
|
+
/** What the visual engine reported it was really holding. */
|
|
467
|
+
retentionMs?: number | null;
|
|
468
|
+
withFrames?: boolean;
|
|
469
|
+
note?: string;
|
|
470
|
+
};
|
|
471
|
+
refusals?: WalkRefusal[];
|
|
472
|
+
/** Ascending by `t`. */
|
|
473
|
+
samples: WalkSample[];
|
|
474
|
+
}
|
|
475
|
+
/** A clock a test drives by hand. Nothing here ever waits. */
|
|
476
|
+
interface ReplayClock {
|
|
477
|
+
/** Current position in the walk, ms since start. */
|
|
478
|
+
now(): number;
|
|
479
|
+
/** Jump forward. Returns the new position. */
|
|
480
|
+
advance(ms: number): number;
|
|
481
|
+
/** Jump to an absolute offset. */
|
|
482
|
+
seek(ms: number): number;
|
|
483
|
+
/** Run to the end of the fixture. */
|
|
484
|
+
end(): number;
|
|
485
|
+
/** Total length of the recorded walk. */
|
|
486
|
+
durationMs(): number;
|
|
487
|
+
}
|
|
488
|
+
interface ReplaySession {
|
|
489
|
+
deviceState: DeviceState;
|
|
490
|
+
clock: ReplayClock;
|
|
491
|
+
/** Which domains had a session opened by the code under test. */
|
|
492
|
+
started(): string[];
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Build a `DeviceState` backed by a recorded walk.
|
|
496
|
+
*
|
|
497
|
+
* Hand the result to the code under test exactly as the host would hand it the
|
|
498
|
+
* real one — it cannot tell the difference, which is the point.
|
|
499
|
+
*/
|
|
500
|
+
declare function createReplayDeviceState(fixture: WalkFixture): ReplaySession;
|
|
501
|
+
/** Parse a `.jsonl` recording into a fixture. First line is the header. */
|
|
502
|
+
declare function parseWalkJsonl(text: string): WalkFixture;
|
|
503
|
+
|
|
38
504
|
/**
|
|
39
505
|
* Wire all Expo providers in one call. Runs before initAdas() is fine —
|
|
40
506
|
* these registries are independent of the core config.
|
|
@@ -51,8 +517,17 @@ declare function setupExpoProviders(opts?: {
|
|
|
51
517
|
* existing app so users keep their attachment previews / bundle cache. */
|
|
52
518
|
attachmentIndexKey?: string;
|
|
53
519
|
bundleIndexKey?: string;
|
|
520
|
+
/**
|
|
521
|
+
* The device-primitive implementation plugins receive as `props.device`.
|
|
522
|
+
* Build it with `createDeviceProvider(...)` from `@ateam-ai-mobile/expo/device`
|
|
523
|
+
* and pass it here so the SDK owns the implementation and this call registers
|
|
524
|
+
* it (setPluginNativeProvider). It is injected — not imported by this entry —
|
|
525
|
+
* because createDeviceProvider statically pulls optional expo modules that a
|
|
526
|
+
* host without them cannot build (same reason createBridge is a subpath).
|
|
527
|
+
*/
|
|
528
|
+
deviceProvider?: PluginNative;
|
|
54
529
|
}): {
|
|
55
530
|
attachmentCache: ExpoAttachmentCache;
|
|
56
531
|
};
|
|
57
532
|
|
|
58
|
-
export { ExpoAttachmentCache, ExpoBundleStorage, expoHaptics, setupExpoProviders };
|
|
533
|
+
export { CameraTaskHost, type DeviceStateCollectors, ExpoAttachmentCache, ExpoBundleStorage, IMPLEMENTED_DOMAINS, type ReplayClock, type ReplaySession, type VisualFrame, type WalkFixture, type WalkRefusal, type WalkSample, cameraTasks, createDeviceState, createReplayDeviceState, expoHaptics, parseWalkJsonl, setupExpoProviders, visualEngine };
|