@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.
@@ -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 type { RegisteredUIPlugin } from '../sdk/types';
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: `/api/ui-plugins/${encodeURIComponent(pluginId)}/bundle.js`,
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
- // Exported under its own path rather than star-exported into this namespace,
25
- // deliberately: the whole point of the layer is that a reader can tell at a
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
- export type {
29
- DeviceState,
30
- DeviceBridgeDomain,
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 every renderer uses to gate a plugin's device surface
90
- // (device/native + deviceState) — so a plugin gets identical access on every
91
- // surface. See pluginRuntime.ts for why this is centralized.
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
 
@@ -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
- * Turn a plugin's declarations into the gated { device, native, deviceState } it
73
- * receives — the SINGLE place this happens, so every surface is identical.
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 resolvePluginRuntimeProps(src: PluginRuntimeSource): PluginRuntimeProps {
87
+ export function resolvePluginGrants(src: PluginRuntimeSource): PluginGrants {
84
88
  const trusted = src.manifest ?? src.plugin ?? null;
85
- const capabilities = mergeCaps(trusted);
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
@@ -1,10 +1,13 @@
1
1
  /**
2
- * @adas/device-state — Device Bridge v2, the SEMANTIC layer.
2
+ * Device state — Device Bridge v2, the SEMANTIC layer.
3
3
  *
4
- * Intended repo path:
4
+ * Repo path:
5
5
  * packages/plugin-runtime/src/sdk/device-state.ts
6
- * Intended module specifier for plugin bundles:
7
- * '@adas/device-state' (NOT '@adas/plugin-sdk')
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 `native: { camera: true }`.
17
- * Lives in '@adas/plugin-sdk'.
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
- * Lives in '@adas/device-state' (this file).
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. Typed `unknown` rather than `any` so a consumer is forced to
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?: unknown;
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
- /** OPEN: no frozen spatial event vocabulary; rule-conforming names only. */
381
- subscribe(listener: StateListener<ConformingEventName>): Subscription;
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
- /** FROZEN. Independent of HumanActivity — walking+pocket, driving+mounted. */
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, spatial, deviceContext.
1217
- * - Per-event payload schemas for every domain.
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,
@@ -54,7 +54,8 @@ export function registerTools(
54
54
  id: string,
55
55
  config: {
56
56
  capabilities?: NativeCapabilities;
57
- /** L3 DeviceState domains the tools may read via ctx.deviceState (e.g. ['visual']). */
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
  },