@ateam-ai-mobile/plugin-runtime 0.1.7 → 0.1.9
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/{chunk-PKDQLNBL.mjs → chunk-SICFDA5K.mjs} +388 -70
- package/dist/chunk-SICFDA5K.mjs.map +1 -0
- package/dist/index.d.mts +462 -46
- package/dist/index.d.ts +462 -46
- package/dist/index.js +429 -230
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20 -157
- package/dist/index.mjs.map +1 -1
- package/dist/{loader-5LUVXUY2.mjs → loader-MSZ4PVTO.mjs} +2 -2
- package/package.json +2 -2
- package/src/api/plugins.ts +15 -9
- package/src/bundles/bundleManager.ts +187 -64
- package/src/bundles/bundleModuleNames.ts +52 -0
- package/src/bundles/bundleUrl.ts +41 -0
- package/src/bundles/storage.ts +71 -0
- package/src/deviceStateHost.ts +25 -0
- package/src/hooks/usePlugin.ts +15 -4
- package/src/host.ts +2 -0
- package/src/index.ts +16 -37
- package/src/pluginRuntime.ts +22 -11
- package/src/sdk/CameraPreview.tsx +7 -0
- package/src/sdk/device-state.ts +120 -17
- package/src/sdk/index.ts +41 -0
- package/src/sdk/registration.ts +2 -1
- package/src/sdk/types.ts +162 -5
- package/src/sdk/voice.ts +82 -0
- package/src/tools/loader.ts +28 -8
- package/src/tools/registry.ts +14 -7
- package/src/voiceHost.ts +2 -0
- package/dist/chunk-PKDQLNBL.mjs.map +0 -1
- /package/dist/{loader-5LUVXUY2.mjs.map → loader-MSZ4PVTO.mjs.map} +0 -0
package/src/hooks/usePlugin.ts
CHANGED
|
@@ -13,13 +13,19 @@
|
|
|
13
13
|
import { useState, useEffect } from 'react';
|
|
14
14
|
import { bundleManager } from '../bundles/bundleManager';
|
|
15
15
|
import { nativePluginRegistry } from '../host';
|
|
16
|
-
import
|
|
16
|
+
import { resolvePluginGrants } from '../pluginRuntime';
|
|
17
|
+
import type { NativeCapabilities, RegisteredUIPlugin } from '../sdk/types';
|
|
18
|
+
import type { DeviceBridgeDomain } from '../sdk/device-state';
|
|
19
|
+
import { pluginBundleUrl } from '../bundles/bundleUrl';
|
|
17
20
|
|
|
18
21
|
interface PluginManifestLike {
|
|
19
22
|
version?: string;
|
|
20
23
|
name?: string;
|
|
21
24
|
render?: any;
|
|
22
25
|
_connectorId?: string;
|
|
26
|
+
capabilities?: Record<string, unknown>;
|
|
27
|
+
native?: NativeCapabilities;
|
|
28
|
+
stateDomains?: DeviceBridgeDomain[];
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
interface UsePluginResult {
|
|
@@ -59,7 +65,7 @@ function extractBundleUrl(pluginId: string, manifest: PluginManifestLike): { url
|
|
|
59
65
|
const hasNativeComponent = !!(render.reactNative?.component || render.component);
|
|
60
66
|
if (hasNativeComponent) {
|
|
61
67
|
return {
|
|
62
|
-
url:
|
|
68
|
+
url: pluginBundleUrl(pluginId),
|
|
63
69
|
source: injected ? 'ignored-explicit' : 'derived-default',
|
|
64
70
|
...(injected && { ignored: injected }),
|
|
65
71
|
};
|
|
@@ -102,6 +108,11 @@ export function usePlugin(pluginId: string, manifest: PluginManifestLike): UsePl
|
|
|
102
108
|
const bundleHash = extractBundleHash(manifest);
|
|
103
109
|
const componentId = extractComponentId(manifest);
|
|
104
110
|
const lookupId = componentId || pluginId;
|
|
111
|
+
// The manifest's grants — the authority for the bundle's device tools, the
|
|
112
|
+
// same one the renderer gates the component with. Keyed by value so a fresh
|
|
113
|
+
// manifest object with the same grants does not reload the plugin.
|
|
114
|
+
const grants = resolvePluginGrants({ pluginId, manifest });
|
|
115
|
+
const grantsKey = JSON.stringify(grants);
|
|
105
116
|
|
|
106
117
|
if (ignoredUrl) {
|
|
107
118
|
console.warn(`[usePlugin] ${pluginId}: IGNORED connector-injected bundleUrl="${ignoredUrl}". The host derives bundle URLs from pluginId via the canonical route.`);
|
|
@@ -132,7 +143,7 @@ export function usePlugin(pluginId: string, manifest: PluginManifestLike): UsePl
|
|
|
132
143
|
// Path 2: Remote bundle (via global BundleManager)
|
|
133
144
|
if (bundleUrl) {
|
|
134
145
|
try {
|
|
135
|
-
const component = await bundleManager.load(pluginId, bundleUrl, version, bundleHash);
|
|
146
|
+
const component = await bundleManager.load(pluginId, bundleUrl, version, bundleHash, grants);
|
|
136
147
|
if (cancelled) return;
|
|
137
148
|
setPlugin(component);
|
|
138
149
|
setLoading(false);
|
|
@@ -179,7 +190,7 @@ export function usePlugin(pluginId: string, manifest: PluginManifestLike): UsePl
|
|
|
179
190
|
})();
|
|
180
191
|
|
|
181
192
|
return () => { cancelled = true; };
|
|
182
|
-
}, [pluginId, lookupId, bundleUrl, bundleHash, version]);
|
|
193
|
+
}, [pluginId, lookupId, bundleUrl, bundleHash, version, grantsKey]);
|
|
183
194
|
|
|
184
195
|
return { plugin, loading, error, bundleMissing };
|
|
185
196
|
}
|
package/src/host.ts
CHANGED
|
@@ -50,6 +50,7 @@ const noopNative: PluginNative = {
|
|
|
50
50
|
nfc: unavailable('nfc'),
|
|
51
51
|
bluetooth: unavailable('bluetooth'),
|
|
52
52
|
vision: unavailable('vision'),
|
|
53
|
+
maps: unavailable('maps'),
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
let _pluginNative: PluginNative = noopNative;
|
|
@@ -129,6 +130,7 @@ export function gatePluginNative(
|
|
|
129
130
|
// used to be, which crashed with "cannot read properties of undefined".
|
|
130
131
|
bluetooth: caps.bluetooth ? native.bluetooth : blocked('bluetooth'),
|
|
131
132
|
vision: caps.vision ? native.vision : blocked('vision'),
|
|
133
|
+
maps: caps.maps ? native.maps : blocked('maps'),
|
|
132
134
|
// What this plugin DECLARED, carried on the object it already receives.
|
|
133
135
|
//
|
|
134
136
|
// It is how a COMPONENT can be capability-gated: <CameraPreview> is handed
|
package/src/index.ts
CHANGED
|
@@ -19,38 +19,14 @@ export * from './sdk';
|
|
|
19
19
|
|
|
20
20
|
// Device Bridge v2 — the SEMANTIC layer. `props.deviceState.*`: state names,
|
|
21
21
|
// snapshots carrying freshness and confidence, and reads that say `unknown`
|
|
22
|
-
// rather than throwing.
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// glance which one they are in, and merging both into one import undoes that.
|
|
22
|
+
// rather than throwing. Its plugin-facing types and the isActionable /
|
|
23
|
+
// explainCapability helpers come through `export * from './sdk'` above — the
|
|
24
|
+
// plugin SDK is the one place they are listed, because it is the only module a
|
|
25
|
+
// bundle can require. The whole layer is also available under its own path.
|
|
27
26
|
export * as deviceStateV2 from './sdk/device-state';
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
StateQuality,
|
|
32
|
-
Freshness,
|
|
33
|
-
Confidence,
|
|
34
|
-
VisualSnapshot,
|
|
35
|
-
SpatialSnapshot,
|
|
36
|
-
SpatialPoint,
|
|
37
|
-
SpatialIntent,
|
|
38
|
-
ActivitySnapshot,
|
|
39
|
-
EnvironmentSnapshot,
|
|
40
|
-
InteractionSnapshot,
|
|
41
|
-
CapabilitySnapshot,
|
|
42
|
-
CapabilityStatus,
|
|
43
|
-
DeviceContextSnapshot,
|
|
44
|
-
ConnectivitySnapshot,
|
|
45
|
-
} from './sdk/device-state';
|
|
46
|
-
// Device Bridge v2 runtime helpers (VALUES). `neverObserved` is the canonical
|
|
47
|
-
// `unknown` freshness the engine's quality module derives from; the others were
|
|
48
|
-
// already in the built types but missing from the source root export.
|
|
49
|
-
export {
|
|
50
|
-
neverObserved,
|
|
51
|
-
isActionable,
|
|
52
|
-
explainCapability,
|
|
53
|
-
} from './sdk/device-state';
|
|
27
|
+
// The canonical `unknown` freshness the engine's quality module derives from —
|
|
28
|
+
// an ENGINE value (the host's device-state domains), not a plugin one.
|
|
29
|
+
export { neverObserved } from './sdk/device-state';
|
|
54
30
|
|
|
55
31
|
// Device Bridge v2 host DI — the semantic counterpart of
|
|
56
32
|
// setPluginNativeProvider. Separate module on purpose: the moment the two
|
|
@@ -86,11 +62,11 @@ export {
|
|
|
86
62
|
registerBundleModules,
|
|
87
63
|
} from './host';
|
|
88
64
|
|
|
89
|
-
// The ONE runtime contract
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
export { resolvePluginRuntimeProps } from './pluginRuntime';
|
|
93
|
-
export type { PluginRuntimeProps, PluginRuntimeSource } from './pluginRuntime';
|
|
65
|
+
// The ONE runtime contract that gates a plugin's device surface (device/native +
|
|
66
|
+
// deviceState) — every renderer AND its device tools resolve grants through it,
|
|
67
|
+
// so a plugin gets identical access on every surface and in its tools.
|
|
68
|
+
export { resolvePluginRuntimeProps, resolvePluginGrants } from './pluginRuntime';
|
|
69
|
+
export type { PluginRuntimeProps, PluginRuntimeSource, PluginGrants } from './pluginRuntime';
|
|
94
70
|
|
|
95
71
|
// Device tools (plugin `tools/` sections — registered at bundle load)
|
|
96
72
|
export {
|
|
@@ -121,7 +97,7 @@ export {
|
|
|
121
97
|
export { bundleManager } from './bundles/bundleManager';
|
|
122
98
|
export type { LoadedBundleInfo, LoadedBundleSource } from './bundles/bundleManager';
|
|
123
99
|
export { setBundleStorage, MemoryBundleStorage } from './bundles/storage';
|
|
124
|
-
export type { BundleStorage, BundleCacheEntry } from './bundles/storage';
|
|
100
|
+
export type { BundleStorage, BundleCacheEntry, TrustedToolDeclaration } from './bundles/storage';
|
|
125
101
|
|
|
126
102
|
// Bridge
|
|
127
103
|
export { useNativeBridge, createNativeBridge } from './bridge/native';
|
|
@@ -143,6 +119,9 @@ export {
|
|
|
143
119
|
getNativeBundleUrl,
|
|
144
120
|
getNativeBundleHash,
|
|
145
121
|
} from './api/plugins';
|
|
122
|
+
// The one spelling of a plugin's bundle URL (MGAP-C8) — hosts that build or
|
|
123
|
+
// compare one use this, never their own template.
|
|
124
|
+
export { pluginBundleUrl } from './bundles/bundleUrl';
|
|
146
125
|
export type { PluginSummary, PluginSurfaceMeta } from './api/plugins';
|
|
147
126
|
export type { PluginManifest as PlatformPluginManifest } from './api/plugins';
|
|
148
127
|
|
package/src/pluginRuntime.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The ONE runtime contract that turns a plugin's declarations into its gated
|
|
3
3
|
* device surface — `props.device` / `props.native` (L2) and `props.deviceState`
|
|
4
|
-
* (L3) — for EVERY renderer, on EVERY surface.
|
|
4
|
+
* (L3) — for EVERY renderer, on EVERY surface, and for its device tools.
|
|
5
5
|
*
|
|
6
6
|
* WHY THIS EXISTS. Each renderer used to resolve this itself: the embedded route
|
|
7
7
|
* gated both `native` and `deviceState`; the drawer/fullscreen renderer gated
|
|
@@ -29,11 +29,6 @@ export interface PluginRuntimeProps {
|
|
|
29
29
|
voice: VoiceAPI;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* Where a plugin's declarations come from. TRUSTED source resolution lives here,
|
|
34
|
-
* not in each caller: the server `manifest` wins, the loaded bundle's own default
|
|
35
|
-
* export is the fallback.
|
|
36
|
-
*/
|
|
37
32
|
/** One declaration source — either the server manifest or the registry entry. */
|
|
38
33
|
interface PluginDeclaration {
|
|
39
34
|
capabilities?: NativeCapabilities | Record<string, unknown>;
|
|
@@ -68,9 +63,18 @@ function mergeCaps(decl: PluginDeclaration | null | undefined): NativeCapabiliti
|
|
|
68
63
|
return { ...(decl.native as any), ...(decl.capabilities as any) } as NativeCapabilities;
|
|
69
64
|
}
|
|
70
65
|
|
|
66
|
+
/** What a plugin is GRANTED — resolved from its one trusted source. The same
|
|
67
|
+
* value gates its rendered surface and its device tools (the tool registry
|
|
68
|
+
* receives it through bundleManager.load/loadTools). */
|
|
69
|
+
export interface PluginGrants {
|
|
70
|
+
capabilities?: NativeCapabilities;
|
|
71
|
+
stateDomains?: DeviceBridgeDomain[];
|
|
72
|
+
}
|
|
73
|
+
|
|
71
74
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
75
|
+
* Resolve a plugin's grants — the SINGLE place this happens, for every rendered
|
|
76
|
+
* surface AND for the device tools its bundle registers, so a plugin gets
|
|
77
|
+
* identical access through either.
|
|
74
78
|
*
|
|
75
79
|
* TRUST BOUNDARY. Exactly ONE source is authoritative: the server `manifest` for
|
|
76
80
|
* a remote plugin, the registry `plugin` for a host-owned one. They are NEVER
|
|
@@ -80,10 +84,17 @@ function mergeCaps(decl: PluginDeclaration | null | undefined): NativeCapabiliti
|
|
|
80
84
|
* not carry `stateDomains` for a remote plugin, the fix is Core passing it, not
|
|
81
85
|
* trusting the bundle: Core already holds the solution's declaration.)
|
|
82
86
|
*/
|
|
83
|
-
export function
|
|
87
|
+
export function resolvePluginGrants(src: PluginRuntimeSource): PluginGrants {
|
|
84
88
|
const trusted = src.manifest ?? src.plugin ?? null;
|
|
85
|
-
|
|
86
|
-
const stateDomains = trusted?.stateDomains;
|
|
89
|
+
// Copied, so the grant cannot change after the fact by mutating the source.
|
|
90
|
+
const stateDomains = trusted?.stateDomains ? [...trusted.stateDomains] : undefined;
|
|
91
|
+
return { capabilities: mergeCaps(trusted), stateDomains };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Turn a plugin's grants into the gated { device, native, deviceState, voice } it
|
|
95
|
+
* receives on every rendered surface. */
|
|
96
|
+
export function resolvePluginRuntimeProps(src: PluginRuntimeSource): PluginRuntimeProps {
|
|
97
|
+
const { capabilities, stateDomains } = resolvePluginGrants(src);
|
|
87
98
|
const native = gatePluginNative(getPluginNative(), capabilities, src.pluginId);
|
|
88
99
|
const deviceState = gateDeviceState(stateDomains);
|
|
89
100
|
// Voice is a capability flag, gated the same way — the TRUSTED declaration
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CameraPreview — the plugin-facing way to SHOW the user what the camera sees.
|
|
3
3
|
*
|
|
4
|
+
* import { CameraPreview } from '@adas/plugin-sdk';
|
|
5
|
+
* <CameraPreview native={native} /> // native = the plugin's own props.native
|
|
6
|
+
*
|
|
7
|
+
* '@adas/plugin-sdk' is the only SDK module a bundle can require. The same code
|
|
8
|
+
* is published as '@ateam-ai-mobile/plugin-runtime', but that is the host app's
|
|
9
|
+
* package: a bundle that requires it fails at evaluation, by name.
|
|
10
|
+
*
|
|
4
11
|
* WHY THIS EXISTS RATHER THAN HANDING PLUGINS `expo-camera`.
|
|
5
12
|
*
|
|
6
13
|
* The design has exactly two doors to the device: `props.native.*` (mechanical
|
package/src/sdk/device-state.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Device state — Device Bridge v2, the SEMANTIC layer.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Repo path:
|
|
5
5
|
* packages/plugin-runtime/src/sdk/device-state.ts
|
|
6
|
-
*
|
|
7
|
-
* '@adas/
|
|
6
|
+
* Module specifier for plugin bundles:
|
|
7
|
+
* '@adas/plugin-sdk' — the only SDK module a bundle can require. This file
|
|
8
|
+
* once named '@adas/device-state' here (4110943); the require shim never
|
|
9
|
+
* resolved it, so a bundle that imported it got `{}` and failed later. It is
|
|
10
|
+
* now refused by name at evaluation (bundles/bundleModuleNames.ts).
|
|
8
11
|
*
|
|
9
12
|
* ─────────────────────────────────────────────────────────────────────────
|
|
10
13
|
* WHICH LAYER AM I IN?
|
|
@@ -13,8 +16,9 @@
|
|
|
13
16
|
* VERB names: takePhoto, getCurrent, scanBarcode,
|
|
14
17
|
* readOnce, authenticate, pickImage.
|
|
15
18
|
* Returns a BARE value. THROWS when it cannot.
|
|
16
|
-
* Declared in the manifest as `
|
|
17
|
-
*
|
|
19
|
+
* Declared in the manifest as `capabilities: { camera: true }`
|
|
20
|
+
* (the legacy `native: {}` field is still honoured).
|
|
21
|
+
* Its types are imported from '@adas/plugin-sdk'.
|
|
18
22
|
*
|
|
19
23
|
* props.deviceState.* SEMANTIC. "What is true right now, and how well do
|
|
20
24
|
* we know it?"
|
|
@@ -23,7 +27,8 @@
|
|
|
23
27
|
* and able to say `unknown` in every field.
|
|
24
28
|
* A read NEVER throws for absence.
|
|
25
29
|
* Declared in the manifest as `stateDomains: ['visual']`.
|
|
26
|
-
*
|
|
30
|
+
* Defined in this file; its types and helpers are
|
|
31
|
+
* imported from '@adas/plugin-sdk' too.
|
|
27
32
|
*
|
|
28
33
|
* The two containers are the discriminator. Every semantic call in a plugin
|
|
29
34
|
* begins with the eight characters `deviceState.`; every mechanical call
|
|
@@ -163,17 +168,19 @@ export type RestrictionState =
|
|
|
163
168
|
* never raw sensor streams." If you find yourself wanting an event per frame
|
|
164
169
|
* or per GPS fix, you are in the wrong layer.
|
|
165
170
|
*/
|
|
166
|
-
export interface StateEvent<TName extends string = string> {
|
|
171
|
+
export interface StateEvent<TName extends string = string, TPayload = unknown> {
|
|
167
172
|
domain: DeviceBridgeDomain;
|
|
168
173
|
name: TName;
|
|
169
174
|
/** Epoch ms. */
|
|
170
175
|
timestamp: number;
|
|
171
176
|
/**
|
|
172
177
|
* OPEN: per-event payload schemas are not specified anywhere in the three
|
|
173
|
-
* design docs.
|
|
174
|
-
* narrow it rather than read a field that may not exist.
|
|
178
|
+
* design docs. Defaults to `unknown` rather than `any` so a consumer is forced
|
|
179
|
+
* to narrow it rather than read a field that may not exist. A domain that
|
|
180
|
+
* decides its schemas (spatial — see `SpatialStateEvent`) passes `TPayload`,
|
|
181
|
+
* which is the freeze's own second parameter, dropped here until one did.
|
|
175
182
|
*/
|
|
176
|
-
payload?:
|
|
183
|
+
payload?: TPayload;
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
export interface Subscription {
|
|
@@ -192,7 +199,9 @@ export type StateListener<TName extends string = string> = (
|
|
|
192
199
|
* connectivity, capability, interaction and environment only. Rather than mint
|
|
193
200
|
* names those docs deliberately did not mint, their events are typed against
|
|
194
201
|
* the naming RULE. A wrong-shaped name is a compile error; the specific
|
|
195
|
-
* vocabulary stays open, as the docs left it
|
|
202
|
+
* vocabulary stays open, as the docs left it — except where a DECISION closes
|
|
203
|
+
* it (spatial, see `SpatialEvent`), and that list is checked against the rule
|
|
204
|
+
* through `AssertConforming`.
|
|
196
205
|
*/
|
|
197
206
|
export type ConformingEventName =
|
|
198
207
|
| `${string}Changed`
|
|
@@ -202,6 +211,13 @@ export type ConformingEventName =
|
|
|
202
211
|
| `${string}Unavailable`
|
|
203
212
|
| `${string}StateStale`;
|
|
204
213
|
|
|
214
|
+
/**
|
|
215
|
+
* A closed list of names, checked against the naming rule at compile time.
|
|
216
|
+
* Wrap a decided vocabulary in this and a name like `turnCompleted` stops
|
|
217
|
+
* compiling instead of reaching a plugin.
|
|
218
|
+
*/
|
|
219
|
+
export type AssertConforming<T extends ConformingEventName> = T;
|
|
220
|
+
|
|
205
221
|
// ── Errors ────────────────────────────────────────────────────────────────
|
|
206
222
|
|
|
207
223
|
/** FROZEN (API_FREEZE §12). */
|
|
@@ -373,12 +389,88 @@ export interface SpatialSnapshot {
|
|
|
373
389
|
quality: StateQuality;
|
|
374
390
|
}
|
|
375
391
|
|
|
392
|
+
/**
|
|
393
|
+
* DECISION (not frozen) — the spatial event vocabulary. Six names, all ending
|
|
394
|
+
* in a suffix the naming rule allows, which `AssertConforming` enforces.
|
|
395
|
+
*
|
|
396
|
+
* Drawn from the architecture doc's spatial candidates (meaningful movement,
|
|
397
|
+
* meaningful turn, location quality degraded/recovered, state became stale)
|
|
398
|
+
* and named after the SNAPSHOT fields they report, so an event and a read use
|
|
399
|
+
* one vocabulary: `lastMeaningfulTurnChanged` is `changes.lastMeaningfulTurnDeg`
|
|
400
|
+
* changing. The names an earlier note floated (`turnCompleted`, `arrived`,
|
|
401
|
+
* `headingLost`) are rejected — the first two fail the rule, and "arrived" is a
|
|
402
|
+
* destination, which the vocabulary's Governance puts above Device Bridge.
|
|
403
|
+
*/
|
|
404
|
+
export type SpatialEvent = AssertConforming<
|
|
405
|
+
| 'movementStarted'
|
|
406
|
+
| 'movementStopped'
|
|
407
|
+
| 'lastMeaningfulTurnChanged'
|
|
408
|
+
| 'positionConfidenceChanged'
|
|
409
|
+
| 'positionChanged'
|
|
410
|
+
| 'spatialStateStale'
|
|
411
|
+
>;
|
|
412
|
+
|
|
413
|
+
/** A spatial event: the frozen envelope, with its payload always present. */
|
|
414
|
+
type SpatialEventOf<TName extends SpatialEvent, TPayload> =
|
|
415
|
+
StateEvent<TName, TPayload> & { domain: 'spatial'; payload: TPayload };
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* DECISION — what each spatial event carries. Only the facts of the
|
|
419
|
+
* transition, in the snapshot's own field names; everything else is one
|
|
420
|
+
* `getSnapshot()` away, which keeps two shapes of the same fact from drifting.
|
|
421
|
+
*
|
|
422
|
+
* `timestamp` on the envelope is when the transition was RECOGNISED. Where a
|
|
423
|
+
* hold delays that on purpose, `since` carries the true onset.
|
|
424
|
+
*/
|
|
425
|
+
export type SpatialStateEvent =
|
|
426
|
+
/** `movement.state` became `moving` and held (navigation: 3 s). */
|
|
427
|
+
| SpatialEventOf<'movementStarted', {
|
|
428
|
+
previous: 'stationary' | 'unknown';
|
|
429
|
+
speedMps: number | null;
|
|
430
|
+
bearingDeg: number | null;
|
|
431
|
+
since: number;
|
|
432
|
+
}>
|
|
433
|
+
/** `moving` → `stationary`, held (navigation: 5 s). Never fired on moving →
|
|
434
|
+
* `unknown`: losing the speed is not evidence of stopping. */
|
|
435
|
+
| SpatialEventOf<'movementStopped', { previous: 'moving'; since: number }>
|
|
436
|
+
/** A new completed turn. Signed: positive = clockwise = right. */
|
|
437
|
+
| SpatialEventOf<'lastMeaningfulTurnChanged', { lastMeaningfulTurnDeg: number; timestamp: number }>
|
|
438
|
+
/** The GPS fix band (high ≤10 m, medium ≤50 m, else low) changed and held.
|
|
439
|
+
* The fix alone — a compass calibration change is not a position event. */
|
|
440
|
+
| SpatialEventOf<'positionConfidenceChanged', {
|
|
441
|
+
level: ConfidenceLevel;
|
|
442
|
+
previous: ConfidenceLevel;
|
|
443
|
+
accuracyM: number | null;
|
|
444
|
+
direction: 'dropped' | 'recovered';
|
|
445
|
+
}>
|
|
446
|
+
/** Moved at least max(profile floor, 2 × accuracyM) from the last reported
|
|
447
|
+
* position, so GPS jitter never fires it. The session's first fix fires
|
|
448
|
+
* with `previous: null`. */
|
|
449
|
+
| SpatialEventOf<'positionChanged', {
|
|
450
|
+
position: SpatialPoint;
|
|
451
|
+
previous: SpatialPoint | null;
|
|
452
|
+
distanceM: number | null;
|
|
453
|
+
}>
|
|
454
|
+
/** `quality.freshness.state` became `stale`. Once; re-armed by a fresh read. */
|
|
455
|
+
| SpatialEventOf<'spatialStateStale', { observedAt: number | null; ageMs: number | null }>;
|
|
456
|
+
|
|
376
457
|
export interface SpatialDomain {
|
|
377
458
|
getSnapshot(): Promise<SpatialSnapshot>;
|
|
378
459
|
start(options: StartOptions<SpatialIntent>): Promise<StateSession>;
|
|
379
460
|
stop(sessionId?: string): Promise<void>;
|
|
380
|
-
/**
|
|
381
|
-
|
|
461
|
+
/**
|
|
462
|
+
* DECISION: the six `SpatialEvent`s, typed per name — narrow on `e.name` and
|
|
463
|
+
* `e.payload` is that event's shape.
|
|
464
|
+
*
|
|
465
|
+
* Events flow only while a spatial session is open; subscribing opens no
|
|
466
|
+
* sensor. They are derived from the same snapshot `getSnapshot()` returns: a
|
|
467
|
+
* listener that re-reads inside the callback sees the snapshot its event came
|
|
468
|
+
* from. A state change must hold, on at least two fixes, before it fires.
|
|
469
|
+
*
|
|
470
|
+
* From a device tool's `ctx.deviceState` this refuses (RESTRICTED): a call
|
|
471
|
+
* has no lifetime to own the listener.
|
|
472
|
+
*/
|
|
473
|
+
subscribe(listener: (event: SpatialStateEvent) => void): Subscription;
|
|
382
474
|
}
|
|
383
475
|
|
|
384
476
|
// ═════════════════════════════════════════════════════════════════════════
|
|
@@ -567,11 +659,20 @@ export type HumanActivity =
|
|
|
567
659
|
| 'driving'
|
|
568
660
|
| 'unknown';
|
|
569
661
|
|
|
570
|
-
/**
|
|
662
|
+
/**
|
|
663
|
+
* FROZEN. Independent of HumanActivity — walking+pocket, driving+mounted.
|
|
664
|
+
*
|
|
665
|
+
* What is returned TODAY: `handheld`, `table` and `unknown` only. Do not write
|
|
666
|
+
* a branch that waits for `pocket` or `mounted`; it will never run. A pocketed
|
|
667
|
+
* phone shows up in the Visual domain's frame quality (`obstructed: true` /
|
|
668
|
+
* `exposure: 'dark'`), not here.
|
|
669
|
+
*/
|
|
571
670
|
export type DevicePlacement =
|
|
572
671
|
| 'handheld'
|
|
672
|
+
/** RESERVED — never returned until the proximity/light sensor is wired. */
|
|
573
673
|
| 'pocket'
|
|
574
674
|
| 'table'
|
|
675
|
+
/** RESERVED — never returned until Bluetooth/CarPlay mount context is wired. */
|
|
575
676
|
| 'mounted'
|
|
576
677
|
| 'unknown';
|
|
577
678
|
|
|
@@ -1213,8 +1314,10 @@ export interface DeviceToolContextV2Extension {
|
|
|
1213
1314
|
* - Multi-consumer session ownership / reference counting.
|
|
1214
1315
|
* - MediaHandle → bytes resolution, and expiry behaviour.
|
|
1215
1316
|
* - Whether `visual.capture()` may use a different intent from the session.
|
|
1216
|
-
* - Event vocabularies for visual, activity,
|
|
1217
|
-
*
|
|
1317
|
+
* - Event vocabularies for visual, activity, deviceContext. (Spatial's is a
|
|
1318
|
+
* DECISION, recorded at `SpatialEvent`, not a reading of the docs.)
|
|
1319
|
+
* - Per-event payload schemas for every domain but spatial
|
|
1320
|
+
* (`SpatialStateEvent`, likewise a DECISION).
|
|
1218
1321
|
* - Background/foreground semantics for activity and connectivity (specified
|
|
1219
1322
|
* only for visual).
|
|
1220
1323
|
* - Numeric thresholds for fresh/aging/stale, and for "materially changed".
|
package/src/sdk/index.ts
CHANGED
|
@@ -35,6 +35,12 @@ export type {
|
|
|
35
35
|
VisionLabel,
|
|
36
36
|
VisionObject,
|
|
37
37
|
VisionFrameQuality,
|
|
38
|
+
MapsAPI,
|
|
39
|
+
MapsCoordinate,
|
|
40
|
+
MapsAddress,
|
|
41
|
+
MapsPlace,
|
|
42
|
+
MapsRoute,
|
|
43
|
+
MapsRouteStep,
|
|
38
44
|
BiometricsAPI,
|
|
39
45
|
BiometricsAPIv2,
|
|
40
46
|
NativeCapabilities,
|
|
@@ -48,6 +54,7 @@ export type {
|
|
|
48
54
|
DeviceToolContext,
|
|
49
55
|
DeviceToolDefinition,
|
|
50
56
|
DeviceToolFn,
|
|
57
|
+
DeviceToolFailureReason,
|
|
51
58
|
} from './types';
|
|
52
59
|
|
|
53
60
|
// Voice — the live conversational SESSION surface (props.voice).
|
|
@@ -55,8 +62,11 @@ export type {
|
|
|
55
62
|
VoiceAPI,
|
|
56
63
|
VoiceState,
|
|
57
64
|
VoiceSnapshot,
|
|
65
|
+
VoiceDeviceToolCall,
|
|
66
|
+
VoiceDeviceToolReason,
|
|
58
67
|
VoiceTranscriptEntry,
|
|
59
68
|
VoiceStartOptions,
|
|
69
|
+
VoicePersona,
|
|
60
70
|
VoiceEvent,
|
|
61
71
|
VoiceSubscription,
|
|
62
72
|
VoiceCaller,
|
|
@@ -66,6 +76,37 @@ export type {
|
|
|
66
76
|
// provider and its own surfaces call it as this caller. See VoiceSessionProvider.
|
|
67
77
|
export { PLATFORM_VOICE_CALLER } from './voice';
|
|
68
78
|
|
|
79
|
+
// Device state — the SEMANTIC layer: the types of what `props.deviceState`
|
|
80
|
+
// returns, and the two pure helpers for reading a snapshot. They are here, in
|
|
81
|
+
// the one module a bundle can require, because a bundle has no other SDK name
|
|
82
|
+
// ('@adas/device-state' never resolved, and is refused by name). What keeps the
|
|
83
|
+
// two layers apart is the CONTAINER a call starts with — `deviceState.` vs
|
|
84
|
+
// `device.` — not the import line.
|
|
85
|
+
export type {
|
|
86
|
+
DeviceState,
|
|
87
|
+
DeviceBridgeDomain,
|
|
88
|
+
StateQuality,
|
|
89
|
+
Freshness,
|
|
90
|
+
Confidence,
|
|
91
|
+
VisualSnapshot,
|
|
92
|
+
SpatialSnapshot,
|
|
93
|
+
SpatialPoint,
|
|
94
|
+
SpatialIntent,
|
|
95
|
+
SpatialEvent,
|
|
96
|
+
SpatialStateEvent,
|
|
97
|
+
StateEvent,
|
|
98
|
+
StateListener,
|
|
99
|
+
Subscription,
|
|
100
|
+
ActivitySnapshot,
|
|
101
|
+
EnvironmentSnapshot,
|
|
102
|
+
InteractionSnapshot,
|
|
103
|
+
CapabilitySnapshot,
|
|
104
|
+
CapabilityStatus,
|
|
105
|
+
DeviceContextSnapshot,
|
|
106
|
+
ConnectivitySnapshot,
|
|
107
|
+
} from './device-state';
|
|
108
|
+
export { isActionable, explainCapability } from './device-state';
|
|
109
|
+
|
|
69
110
|
// RN primitives (plugins import from the SDK, not from react-native)
|
|
70
111
|
export {
|
|
71
112
|
View,
|
package/src/sdk/registration.ts
CHANGED
|
@@ -54,7 +54,8 @@ export function registerTools(
|
|
|
54
54
|
id: string,
|
|
55
55
|
config: {
|
|
56
56
|
capabilities?: NativeCapabilities;
|
|
57
|
-
/** L3 DeviceState domains the tools
|
|
57
|
+
/** L3 DeviceState domains the tools read via ctx.deviceState (e.g. ['visual']).
|
|
58
|
+
* For a remote bundle the MANIFEST grants them — see RegisteredToolsPlugin. */
|
|
58
59
|
stateDomains?: DeviceBridgeDomain[];
|
|
59
60
|
tools: DeviceToolDefinition[];
|
|
60
61
|
},
|