@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/index.d.mts CHANGED
@@ -6,12 +6,15 @@ import { View, TextInput } from 'react-native';
6
6
  export { ActivityIndicator, Animated, FlatList, Image, Modal, Platform, Pressable, RefreshControl, ScrollView, SectionList, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View } from 'react-native';
7
7
 
8
8
  /**
9
- * @adas/device-state — Device Bridge v2, the SEMANTIC layer.
9
+ * Device state — Device Bridge v2, the SEMANTIC layer.
10
10
  *
11
- * Intended repo path:
11
+ * Repo path:
12
12
  * packages/plugin-runtime/src/sdk/device-state.ts
13
- * Intended module specifier for plugin bundles:
14
- * '@adas/device-state' (NOT '@adas/plugin-sdk')
13
+ * Module specifier for plugin bundles:
14
+ * '@adas/plugin-sdk' — the only SDK module a bundle can require. This file
15
+ * once named '@adas/device-state' here (4110943); the require shim never
16
+ * resolved it, so a bundle that imported it got `{}` and failed later. It is
17
+ * now refused by name at evaluation (bundles/bundleModuleNames.ts).
15
18
  *
16
19
  * ─────────────────────────────────────────────────────────────────────────
17
20
  * WHICH LAYER AM I IN?
@@ -20,8 +23,9 @@ export { ActivityIndicator, Animated, FlatList, Image, Modal, Platform, Pressabl
20
23
  * VERB names: takePhoto, getCurrent, scanBarcode,
21
24
  * readOnce, authenticate, pickImage.
22
25
  * Returns a BARE value. THROWS when it cannot.
23
- * Declared in the manifest as `native: { camera: true }`.
24
- * Lives in '@adas/plugin-sdk'.
26
+ * Declared in the manifest as `capabilities: { camera: true }`
27
+ * (the legacy `native: {}` field is still honoured).
28
+ * Its types are imported from '@adas/plugin-sdk'.
25
29
  *
26
30
  * props.deviceState.* SEMANTIC. "What is true right now, and how well do
27
31
  * we know it?"
@@ -30,7 +34,8 @@ export { ActivityIndicator, Animated, FlatList, Image, Modal, Platform, Pressabl
30
34
  * and able to say `unknown` in every field.
31
35
  * A read NEVER throws for absence.
32
36
  * Declared in the manifest as `stateDomains: ['visual']`.
33
- * Lives in '@adas/device-state' (this file).
37
+ * Defined in this file; its types and helpers are
38
+ * imported from '@adas/plugin-sdk' too.
34
39
  *
35
40
  * The two containers are the discriminator. Every semantic call in a plugin
36
41
  * begins with the eight characters `deviceState.`; every mechanical call
@@ -117,17 +122,19 @@ type RestrictionState = 'none' | 'backgroundRestricted' | 'systemRestricted' | '
117
122
  * never raw sensor streams." If you find yourself wanting an event per frame
118
123
  * or per GPS fix, you are in the wrong layer.
119
124
  */
120
- interface StateEvent<TName extends string = string> {
125
+ interface StateEvent<TName extends string = string, TPayload = unknown> {
121
126
  domain: DeviceBridgeDomain;
122
127
  name: TName;
123
128
  /** Epoch ms. */
124
129
  timestamp: number;
125
130
  /**
126
131
  * OPEN: per-event payload schemas are not specified anywhere in the three
127
- * design docs. Typed `unknown` rather than `any` so a consumer is forced to
128
- * narrow it rather than read a field that may not exist.
132
+ * design docs. Defaults to `unknown` rather than `any` so a consumer is forced
133
+ * to narrow it rather than read a field that may not exist. A domain that
134
+ * decides its schemas (spatial — see `SpatialStateEvent`) passes `TPayload`,
135
+ * which is the freeze's own second parameter, dropped here until one did.
129
136
  */
130
- payload?: unknown;
137
+ payload?: TPayload;
131
138
  }
132
139
  interface Subscription {
133
140
  unsubscribe(): void;
@@ -141,9 +148,17 @@ type StateListener<TName extends string = string> = (event: StateEvent<TName>) =
141
148
  * connectivity, capability, interaction and environment only. Rather than mint
142
149
  * names those docs deliberately did not mint, their events are typed against
143
150
  * the naming RULE. A wrong-shaped name is a compile error; the specific
144
- * vocabulary stays open, as the docs left it.
151
+ * vocabulary stays open, as the docs left it — except where a DECISION closes
152
+ * it (spatial, see `SpatialEvent`), and that list is checked against the rule
153
+ * through `AssertConforming`.
145
154
  */
146
155
  type ConformingEventName = `${string}Changed` | `${string}Started` | `${string}Stopped` | `${string}Available` | `${string}Unavailable` | `${string}StateStale`;
156
+ /**
157
+ * A closed list of names, checked against the naming rule at compile time.
158
+ * Wrap a decided vocabulary in this and a name like `turnCompleted` stops
159
+ * compiling instead of reaching a plugin.
160
+ */
161
+ type AssertConforming<T extends ConformingEventName> = T;
147
162
  /** FROZEN (API_FREEZE §12). */
148
163
  type DeviceBridgeErrorCode = 'UNSUPPORTED' | 'PERMISSION_DENIED' | 'RESTRICTED' | 'BUSY' | 'TEMPORARILY_UNAVAILABLE' | 'STALE' | 'TIMEOUT' | 'CANCELLED' | 'INTERNAL';
149
164
  /**
@@ -272,12 +287,89 @@ interface SpatialSnapshot {
272
287
  };
273
288
  quality: StateQuality;
274
289
  }
290
+ /**
291
+ * DECISION (not frozen) — the spatial event vocabulary. Six names, all ending
292
+ * in a suffix the naming rule allows, which `AssertConforming` enforces.
293
+ *
294
+ * Drawn from the architecture doc's spatial candidates (meaningful movement,
295
+ * meaningful turn, location quality degraded/recovered, state became stale)
296
+ * and named after the SNAPSHOT fields they report, so an event and a read use
297
+ * one vocabulary: `lastMeaningfulTurnChanged` is `changes.lastMeaningfulTurnDeg`
298
+ * changing. The names an earlier note floated (`turnCompleted`, `arrived`,
299
+ * `headingLost`) are rejected — the first two fail the rule, and "arrived" is a
300
+ * destination, which the vocabulary's Governance puts above Device Bridge.
301
+ */
302
+ type SpatialEvent = AssertConforming<'movementStarted' | 'movementStopped' | 'lastMeaningfulTurnChanged' | 'positionConfidenceChanged' | 'positionChanged' | 'spatialStateStale'>;
303
+ /** A spatial event: the frozen envelope, with its payload always present. */
304
+ type SpatialEventOf<TName extends SpatialEvent, TPayload> = StateEvent<TName, TPayload> & {
305
+ domain: 'spatial';
306
+ payload: TPayload;
307
+ };
308
+ /**
309
+ * DECISION — what each spatial event carries. Only the facts of the
310
+ * transition, in the snapshot's own field names; everything else is one
311
+ * `getSnapshot()` away, which keeps two shapes of the same fact from drifting.
312
+ *
313
+ * `timestamp` on the envelope is when the transition was RECOGNISED. Where a
314
+ * hold delays that on purpose, `since` carries the true onset.
315
+ */
316
+ type SpatialStateEvent =
317
+ /** `movement.state` became `moving` and held (navigation: 3 s). */
318
+ SpatialEventOf<'movementStarted', {
319
+ previous: 'stationary' | 'unknown';
320
+ speedMps: number | null;
321
+ bearingDeg: number | null;
322
+ since: number;
323
+ }>
324
+ /** `moving` → `stationary`, held (navigation: 5 s). Never fired on moving →
325
+ * `unknown`: losing the speed is not evidence of stopping. */
326
+ | SpatialEventOf<'movementStopped', {
327
+ previous: 'moving';
328
+ since: number;
329
+ }>
330
+ /** A new completed turn. Signed: positive = clockwise = right. */
331
+ | SpatialEventOf<'lastMeaningfulTurnChanged', {
332
+ lastMeaningfulTurnDeg: number;
333
+ timestamp: number;
334
+ }>
335
+ /** The GPS fix band (high ≤10 m, medium ≤50 m, else low) changed and held.
336
+ * The fix alone — a compass calibration change is not a position event. */
337
+ | SpatialEventOf<'positionConfidenceChanged', {
338
+ level: ConfidenceLevel;
339
+ previous: ConfidenceLevel;
340
+ accuracyM: number | null;
341
+ direction: 'dropped' | 'recovered';
342
+ }>
343
+ /** Moved at least max(profile floor, 2 × accuracyM) from the last reported
344
+ * position, so GPS jitter never fires it. The session's first fix fires
345
+ * with `previous: null`. */
346
+ | SpatialEventOf<'positionChanged', {
347
+ position: SpatialPoint;
348
+ previous: SpatialPoint | null;
349
+ distanceM: number | null;
350
+ }>
351
+ /** `quality.freshness.state` became `stale`. Once; re-armed by a fresh read. */
352
+ | SpatialEventOf<'spatialStateStale', {
353
+ observedAt: number | null;
354
+ ageMs: number | null;
355
+ }>;
275
356
  interface SpatialDomain {
276
357
  getSnapshot(): Promise<SpatialSnapshot>;
277
358
  start(options: StartOptions<SpatialIntent>): Promise<StateSession>;
278
359
  stop(sessionId?: string): Promise<void>;
279
- /** OPEN: no frozen spatial event vocabulary; rule-conforming names only. */
280
- subscribe(listener: StateListener<ConformingEventName>): Subscription;
360
+ /**
361
+ * DECISION: the six `SpatialEvent`s, typed per name — narrow on `e.name` and
362
+ * `e.payload` is that event's shape.
363
+ *
364
+ * Events flow only while a spatial session is open; subscribing opens no
365
+ * sensor. They are derived from the same snapshot `getSnapshot()` returns: a
366
+ * listener that re-reads inside the callback sees the snapshot its event came
367
+ * from. A state change must hold, on at least two fixes, before it fires.
368
+ *
369
+ * From a device tool's `ctx.deviceState` this refuses (RESTRICTED): a call
370
+ * has no lifetime to own the listener.
371
+ */
372
+ subscribe(listener: (event: SpatialStateEvent) => void): Subscription;
281
373
  }
282
374
  /** FROZEN (API_FREEZE §4) + VOCABULARY §"Visual Intents". Nine values. */
283
375
  type VisualIntent = 'navigation' | 'document' | 'ocr' | 'shopping' | 'inspection' | 'accessibility' | 'stream' | 'highQuality' | 'lowPower';
@@ -431,8 +523,19 @@ interface VisualDomain {
431
523
  }
432
524
  /** FROZEN (API_FREEZE §5, VOCABULARY §"Human Activity Values"). */
433
525
  type HumanActivity = 'stationary' | 'walking' | 'running' | 'cycling' | 'driving' | 'unknown';
434
- /** FROZEN. Independent of HumanActivity — walking+pocket, driving+mounted. */
435
- type DevicePlacement = 'handheld' | 'pocket' | 'table' | 'mounted' | 'unknown';
526
+ /**
527
+ * FROZEN. Independent of HumanActivity — walking+pocket, driving+mounted.
528
+ *
529
+ * What is returned TODAY: `handheld`, `table` and `unknown` only. Do not write
530
+ * a branch that waits for `pocket` or `mounted`; it will never run. A pocketed
531
+ * phone shows up in the Visual domain's frame quality (`obstructed: true` /
532
+ * `exposure: 'dark'`), not here.
533
+ */
534
+ type DevicePlacement = 'handheld'
535
+ /** RESERVED — never returned until the proximity/light sensor is wired. */
536
+ | 'pocket' | 'table'
537
+ /** RESERVED — never returned until Bluetooth/CarPlay mount context is wired. */
538
+ | 'mounted' | 'unknown';
436
539
  /**
437
540
  * FROZEN. Note `driving` is BOTH an intent here and a HumanActivity value
438
541
  * above. The docs define no relationship between the two and do not flag the
@@ -841,6 +944,7 @@ interface DeviceToolContextV2Extension {
841
944
  type deviceState_ActivityDomain = ActivityDomain;
842
945
  type deviceState_ActivityIntent = ActivityIntent;
843
946
  type deviceState_ActivitySnapshot = ActivitySnapshot;
947
+ type deviceState_AssertConforming<T extends ConformingEventName> = AssertConforming<T>;
844
948
  type deviceState_AvailabilityState = AvailabilityState;
845
949
  type deviceState_CapabilityDomain = CapabilityDomain;
846
950
  type deviceState_CapabilityEvent = CapabilityEvent;
@@ -877,12 +981,14 @@ type deviceState_MediaHandle = MediaHandle;
877
981
  type deviceState_PluginPropsV2Extension = PluginPropsV2Extension;
878
982
  type deviceState_RestrictionState = RestrictionState;
879
983
  type deviceState_SpatialDomain = SpatialDomain;
984
+ type deviceState_SpatialEvent = SpatialEvent;
880
985
  type deviceState_SpatialIntent = SpatialIntent;
881
986
  type deviceState_SpatialPoint = SpatialPoint;
882
987
  type deviceState_SpatialSnapshot = SpatialSnapshot;
988
+ type deviceState_SpatialStateEvent = SpatialStateEvent;
883
989
  type deviceState_StartOptions<TIntent extends string> = StartOptions<TIntent>;
884
990
  type deviceState_StateDomainDeclaration = StateDomainDeclaration;
885
- type deviceState_StateEvent<TName extends string = string> = StateEvent<TName>;
991
+ type deviceState_StateEvent<TName extends string = string, TPayload = unknown> = StateEvent<TName, TPayload>;
886
992
  type deviceState_StateListener<TName extends string = string> = StateListener<TName>;
887
993
  type deviceState_StateQuality = StateQuality;
888
994
  type deviceState_StateSession = StateSession;
@@ -900,7 +1006,7 @@ declare const deviceState_explainCapability: typeof explainCapability;
900
1006
  declare const deviceState_isActionable: typeof isActionable;
901
1007
  declare const deviceState_neverObserved: typeof neverObserved;
902
1008
  declare namespace deviceState {
903
- export { type deviceState_ActivityDomain as ActivityDomain, type deviceState_ActivityIntent as ActivityIntent, type deviceState_ActivitySnapshot as ActivitySnapshot, type deviceState_AvailabilityState as AvailabilityState, type deviceState_CapabilityDomain as CapabilityDomain, type deviceState_CapabilityEvent as CapabilityEvent, type deviceState_CapabilitySnapshot as CapabilitySnapshot, type deviceState_CapabilityStatus as CapabilityStatus, type deviceState_Confidence as Confidence, type deviceState_ConfidenceLevel as ConfidenceLevel, type deviceState_ConformingEventName as ConformingEventName, type deviceState_ConnectivityDomain as ConnectivityDomain, type deviceState_ConnectivityEvent as ConnectivityEvent, type deviceState_ConnectivityIntent as ConnectivityIntent, type deviceState_ConnectivitySnapshot as ConnectivitySnapshot, type deviceState_DeviceBridgeDomain as DeviceBridgeDomain, deviceState_DeviceBridgeError as DeviceBridgeError, type deviceState_DeviceBridgeErrorCode as DeviceBridgeErrorCode, type deviceState_DeviceBridgeV2 as DeviceBridgeV2, type deviceState_DeviceContextDomain as DeviceContextDomain, type deviceState_DeviceContextSnapshot as DeviceContextSnapshot, type deviceState_DevicePlacement as DevicePlacement, type deviceState_DeviceState as DeviceState, type deviceState_DeviceToolContextV2Extension as DeviceToolContextV2Extension, type deviceState_EnvironmentDomain as EnvironmentDomain, type deviceState_EnvironmentEvent as EnvironmentEvent, type deviceState_EnvironmentIntent as EnvironmentIntent, type deviceState_EnvironmentSnapshot as EnvironmentSnapshot, type deviceState_Freshness as Freshness, type deviceState_FreshnessState as FreshnessState, type deviceState_HumanActivity as HumanActivity, type deviceState_InteractionDomain as InteractionDomain, type deviceState_InteractionEvent as InteractionEvent, type deviceState_InteractionSnapshot as InteractionSnapshot, type deviceState_MediaHandle as MediaHandle, type PermissionState$1 as PermissionState, type deviceState_PluginPropsV2Extension as PluginPropsV2Extension, type deviceState_RestrictionState as RestrictionState, type deviceState_SpatialDomain as SpatialDomain, type deviceState_SpatialIntent as SpatialIntent, type deviceState_SpatialPoint as SpatialPoint, type deviceState_SpatialSnapshot as SpatialSnapshot, type deviceState_StartOptions as StartOptions, type deviceState_StateDomainDeclaration as StateDomainDeclaration, type deviceState_StateEvent as StateEvent, type deviceState_StateListener as StateListener, type deviceState_StateQuality as StateQuality, type deviceState_StateSession as StateSession, type deviceState_StatefulDomain as StatefulDomain, type deviceState_Subscription as Subscription, type deviceState_SupportState as SupportState, type deviceState_VisualCaptureOptions as VisualCaptureOptions, type deviceState_VisualCaptureResult as VisualCaptureResult, type deviceState_VisualDomain as VisualDomain, type deviceState_VisualFrameQuality as VisualFrameQuality, type deviceState_VisualFrameRef as VisualFrameRef, type deviceState_VisualIntent as VisualIntent, type deviceState_VisualSnapshot as VisualSnapshot, deviceState_explainCapability as explainCapability, deviceState_isActionable as isActionable, deviceState_neverObserved as neverObserved };
1009
+ export { type deviceState_ActivityDomain as ActivityDomain, type deviceState_ActivityIntent as ActivityIntent, type deviceState_ActivitySnapshot as ActivitySnapshot, type deviceState_AssertConforming as AssertConforming, type deviceState_AvailabilityState as AvailabilityState, type deviceState_CapabilityDomain as CapabilityDomain, type deviceState_CapabilityEvent as CapabilityEvent, type deviceState_CapabilitySnapshot as CapabilitySnapshot, type deviceState_CapabilityStatus as CapabilityStatus, type deviceState_Confidence as Confidence, type deviceState_ConfidenceLevel as ConfidenceLevel, type deviceState_ConformingEventName as ConformingEventName, type deviceState_ConnectivityDomain as ConnectivityDomain, type deviceState_ConnectivityEvent as ConnectivityEvent, type deviceState_ConnectivityIntent as ConnectivityIntent, type deviceState_ConnectivitySnapshot as ConnectivitySnapshot, type deviceState_DeviceBridgeDomain as DeviceBridgeDomain, deviceState_DeviceBridgeError as DeviceBridgeError, type deviceState_DeviceBridgeErrorCode as DeviceBridgeErrorCode, type deviceState_DeviceBridgeV2 as DeviceBridgeV2, type deviceState_DeviceContextDomain as DeviceContextDomain, type deviceState_DeviceContextSnapshot as DeviceContextSnapshot, type deviceState_DevicePlacement as DevicePlacement, type deviceState_DeviceState as DeviceState, type deviceState_DeviceToolContextV2Extension as DeviceToolContextV2Extension, type deviceState_EnvironmentDomain as EnvironmentDomain, type deviceState_EnvironmentEvent as EnvironmentEvent, type deviceState_EnvironmentIntent as EnvironmentIntent, type deviceState_EnvironmentSnapshot as EnvironmentSnapshot, type deviceState_Freshness as Freshness, type deviceState_FreshnessState as FreshnessState, type deviceState_HumanActivity as HumanActivity, type deviceState_InteractionDomain as InteractionDomain, type deviceState_InteractionEvent as InteractionEvent, type deviceState_InteractionSnapshot as InteractionSnapshot, type deviceState_MediaHandle as MediaHandle, type PermissionState$1 as PermissionState, type deviceState_PluginPropsV2Extension as PluginPropsV2Extension, type deviceState_RestrictionState as RestrictionState, type deviceState_SpatialDomain as SpatialDomain, type deviceState_SpatialEvent as SpatialEvent, type deviceState_SpatialIntent as SpatialIntent, type deviceState_SpatialPoint as SpatialPoint, type deviceState_SpatialSnapshot as SpatialSnapshot, type deviceState_SpatialStateEvent as SpatialStateEvent, type deviceState_StartOptions as StartOptions, type deviceState_StateDomainDeclaration as StateDomainDeclaration, type deviceState_StateEvent as StateEvent, type deviceState_StateListener as StateListener, type deviceState_StateQuality as StateQuality, type deviceState_StateSession as StateSession, type deviceState_StatefulDomain as StatefulDomain, type deviceState_Subscription as Subscription, type deviceState_SupportState as SupportState, type deviceState_VisualCaptureOptions as VisualCaptureOptions, type deviceState_VisualCaptureResult as VisualCaptureResult, type deviceState_VisualDomain as VisualDomain, type deviceState_VisualFrameQuality as VisualFrameQuality, type deviceState_VisualFrameRef as VisualFrameRef, type deviceState_VisualIntent as VisualIntent, type deviceState_VisualSnapshot as VisualSnapshot, deviceState_explainCapability as explainCapability, deviceState_isActionable as isActionable, deviceState_neverObserved as neverObserved };
904
1010
  }
905
1011
 
906
1012
  /**
@@ -923,6 +1029,7 @@ declare namespace deviceState {
923
1029
  *
924
1030
  * See docs/handoff/SPEC_voice_session_without_the_voice_screen_2026-09-12.md.
925
1031
  */
1032
+
926
1033
  /** The session lifecycle — identical vocabulary to the built-in `CallState`. */
927
1034
  type VoiceState = 'idle' | 'connecting' | 'connected' | 'error';
928
1035
  /** One line of the conversation. `tool` entries are device/skill tool activity. */
@@ -931,6 +1038,40 @@ interface VoiceTranscriptEntry {
931
1038
  text: string;
932
1039
  ts: number;
933
1040
  }
1041
+ /**
1042
+ * Why a device-tool call the session received did not produce a result.
1043
+ * - the tool RAN and did not return: `not_found` | `no_context` | `timeout` |
1044
+ * `failed` (it threw) — see DeviceToolFailureReason;
1045
+ * - the phone REFUSED it before running: `not_ready` (Core's tool allowset for
1046
+ * this session had not arrived yet — transient, a retry can succeed) or
1047
+ * `not_authorized` (not allowed in this session — final);
1048
+ * - `invalid`: the call itself was malformed (it named no tool, or carried no
1049
+ * correlationId to answer on) — not run.
1050
+ */
1051
+ type VoiceDeviceToolReason = DeviceToolFailureReason | 'not_ready' | 'not_authorized' | 'invalid';
1052
+ /**
1053
+ * One finished device-tool call, as `VoiceSnapshot.lastDeviceTool` reports it.
1054
+ * `name` and `at` keep their original meaning, so a reader of just those two is
1055
+ * unaffected.
1056
+ */
1057
+ type VoiceDeviceToolCall = {
1058
+ /** The tool the model called ('' if the call named none — see `invalid`). */
1059
+ name: string;
1060
+ /** Epoch ms the call REACHED the phone (not when it finished — that is at + ms). */
1061
+ at: number;
1062
+ /** Milliseconds from arrival to outcome (≈0 for a refusal). */
1063
+ ms: number;
1064
+ } & ({
1065
+ ok: true;
1066
+ reason: null;
1067
+ error: null;
1068
+ } | {
1069
+ ok: false;
1070
+ reason: VoiceDeviceToolReason;
1071
+ /** What went wrong, clipped to 200 characters: tool errors can echo the
1072
+ * call's arguments, which may carry what the user said. */
1073
+ error: string;
1074
+ });
934
1075
  /** A point-in-time read of the session — what a reasoning loop polls. */
935
1076
  interface VoiceSnapshot {
936
1077
  state: VoiceState;
@@ -954,11 +1095,56 @@ interface VoiceSnapshot {
954
1095
  /** The last error, if `state === 'error'`; also carries a refusal reason when
955
1096
  * this surface was handed to a plugin that did not declare `voice`. */
956
1097
  lastError: string | null;
1098
+ /**
1099
+ * The most recently FINISHED device-tool call the model made through this
1100
+ * session, with its outcome — or null if none has finished yet. Set once the
1101
+ * outcome is known (ran, refused, malformed, threw, timed out), never on
1102
+ * arrival, so a refused call can no longer look like one that ran.
1103
+ *
1104
+ * SCOPED to the caller: a plugin sees only calls to tools IT registered (never
1105
+ * another solution's tool names or errors, even on its own call); the platform
1106
+ * sees every call. Cleared when the session ends or a new one starts.
1107
+ * Diagnostic — every solution's debug view gets "why did that call fail" free.
1108
+ */
1109
+ lastDeviceTool: VoiceDeviceToolCall | null;
1110
+ /** The sessionId the Core allowset is bound to. Diagnostic: authorization
1111
+ * requires it to equal `sessionId`; a mismatch is why tools refuse. */
1112
+ authorizedSessionId: string | null;
957
1113
  }
958
1114
  interface VoiceStartOptions {
959
1115
  /** Hand the mic to a specific skill on connect (voice /session → takeMic),
960
1116
  * exactly as `sys.focusUiPlugin("platform:voice", { skillSlug })` does. */
961
1117
  skillSlug?: string;
1118
+ /**
1119
+ * A PER-CALL persona override, same shape as the authored
1120
+ * `solution.voice.persona` ({ name, style }). Declared on the voice
1121
+ * session-create call (POST /session `persona`); the server sanitizes it
1122
+ * (name/style only, length-capped) and layers it OVER the solution's authored
1123
+ * persona for THIS call only — prompt-layer, so it can never touch caller
1124
+ * verification or tool allow-lists. Set once at session start and constant
1125
+ * for the call, so it adds no per-turn prompt pushes.
1126
+ *
1127
+ * Use it when a plugin needs its own voice persona without owning a skill
1128
+ * (e.g. a guide HUD). A solution-wide persona belongs in solution.voice.
1129
+ */
1130
+ persona?: VoicePersona;
1131
+ /**
1132
+ * `'none'` = a CLEAN call: the voice gets none of the workspace's skills — no
1133
+ * skill manifest, no skill tools, no platform welcome line, no mid-call
1134
+ * skill-state prompt refresh — only the voice basics, this call's persona and
1135
+ * the CALLING plugin's own device tools (other solutions' tools registered on
1136
+ * the same phone are neither offered nor run). For a plugin that owns its conversation (e.g.
1137
+ * a guide HUD). Reduce-only: it can remove what the voice gets, never add.
1138
+ * Absent = the workspace's skills, as today. Not combinable with `skillSlug`.
1139
+ */
1140
+ skills?: 'none';
1141
+ }
1142
+ /** The authored persona shape (solution.voice.persona). */
1143
+ interface VoicePersona {
1144
+ /** The name the voice uses for itself ("You are <name> …"). */
1145
+ name?: string;
1146
+ /** Free-text style/behaviour guidance rendered under the identity line. */
1147
+ style?: string;
962
1148
  }
963
1149
  /**
964
1150
  * What changed. Coarse on purpose: `state` covers connect/mute/session/partials,
@@ -1411,6 +1597,108 @@ interface VisionAPI {
1411
1597
  /** Measure whether a frame is usable. Feeds VisualFrameRef.quality. */
1412
1598
  assessQuality(frameId: string): Promise<VisionFrameQuality>;
1413
1599
  }
1600
+ /** The same `{ latitude, longitude }` `location.getCurrent` returns, so a fix
1601
+ * passes straight in. */
1602
+ interface MapsCoordinate {
1603
+ latitude: number;
1604
+ longitude: number;
1605
+ }
1606
+ /** A postal address, field by field. `null` = the map data has no value — never
1607
+ * an empty string, which would read as a street with no name. */
1608
+ interface MapsAddress {
1609
+ /** The placemark's own name — a building, or often number + street. */
1610
+ name: string | null;
1611
+ street: string | null;
1612
+ streetNumber: string | null;
1613
+ /** Neighbourhood / sub-locality. */
1614
+ district: string | null;
1615
+ city: string | null;
1616
+ /** State / province / administrative area. */
1617
+ region: string | null;
1618
+ postalCode: string | null;
1619
+ country: string | null;
1620
+ /** ISO 3166-1 alpha-2, e.g. "GB". */
1621
+ isoCountryCode: string | null;
1622
+ }
1623
+ interface MapsPlace {
1624
+ /** What the map calls it ("Boots", "10 Downing St"). Null when unnamed. */
1625
+ name: string | null;
1626
+ coordinate: MapsCoordinate;
1627
+ address: MapsAddress;
1628
+ /** Apple's point-of-interest category as a vocabulary term ("Pharmacy",
1629
+ * "Cafe", "PublicTransport") — NOT free text. Null for a plain address, or
1630
+ * when the service gives none. */
1631
+ category: string | null;
1632
+ /** Straight-line metres from `opts.near`, when you passed one — NOT walking
1633
+ * distance (ask `route` for that). Null without `near`. */
1634
+ distanceM: number | null;
1635
+ }
1636
+ interface MapsRouteStep {
1637
+ /** Apple's instruction ("Turn right onto Baker St"). The FIRST step is usually
1638
+ * an origin step with an empty instruction — kept, not dropped, so step
1639
+ * indexes line up with Apple's. */
1640
+ instructions: string;
1641
+ distanceM: number;
1642
+ /** An advisory Apple attaches to this step. Null when there is none. */
1643
+ notice: string | null;
1644
+ polyline: MapsCoordinate[];
1645
+ }
1646
+ interface MapsRoute {
1647
+ mode: 'walking';
1648
+ distanceM: number;
1649
+ /** Apple's estimate for the whole route. */
1650
+ expectedTravelTimeMs: number;
1651
+ steps: MapsRouteStep[];
1652
+ /** The whole route, start to end. */
1653
+ polyline: MapsCoordinate[];
1654
+ }
1655
+ interface MapsAPI {
1656
+ /** Can this build search and route? True when the AdaMaps module is in the
1657
+ * binary (iOS). False on Android and on an older build — `reverseGeocode`
1658
+ * does not need the module and may still work there. */
1659
+ isSupported(): Promise<boolean>;
1660
+ /**
1661
+ * Find places by free text — "pharmacy", "10 Downing Street".
1662
+ *
1663
+ * `near` centres the search on a coordinate YOU supply, within `radiusM`
1664
+ * (default 3000, above 0 and at most 100 000). On iOS 18+ results are
1665
+ * confined to that area; before 18 it is a strong hint and a farther result
1666
+ * can come back — each result's `distanceM` says how far. At most `limit`
1667
+ * results (default 8, from 1; more than 25 is capped at 25).
1668
+ *
1669
+ * Network, Apple's servers. `[]` = Apple looked and found nothing. iOS only:
1670
+ * throws UNAVAILABLE on Android or a build without AdaMaps.
1671
+ */
1672
+ search(query: string, opts?: {
1673
+ near?: MapsCoordinate;
1674
+ radiusM?: number;
1675
+ limit?: number;
1676
+ }): Promise<MapsPlace[]>;
1677
+ /**
1678
+ * What is at a coordinate — its address and name.
1679
+ *
1680
+ * iOS AND Android (the platform geocoder); needs no AdaMaps module. The
1681
+ * result's `coordinate` is the point you asked about, and its
1682
+ * `category` / `distanceM` are null. `null` = nothing there.
1683
+ *
1684
+ * Network, not on-device. iOS reports a rate limit and a lost connection as
1685
+ * the SAME error, so OFFLINE here can also mean "too many requests". Android's
1686
+ * geocoder refuses without location permission (UNAVAILABLE) — that is the
1687
+ * platform's rule, not a location read.
1688
+ */
1689
+ reverseGeocode(at: MapsCoordinate): Promise<MapsPlace | null>;
1690
+ /**
1691
+ * A walking route between two coordinates YOU supply.
1692
+ *
1693
+ * `null` = Apple found no route — never a fake 0 m route, which would read as
1694
+ * "you have arrived". Walking is the only mode; any other refuses by name.
1695
+ * Network, Apple's servers. iOS only: throws UNAVAILABLE on Android or a build
1696
+ * without AdaMaps.
1697
+ */
1698
+ route(from: MapsCoordinate, to: MapsCoordinate, opts?: {
1699
+ mode?: 'walking';
1700
+ }): Promise<MapsRoute | null>;
1701
+ }
1414
1702
  interface BluetoothAPI {
1415
1703
  /** Is BLE hardware present and permitted? */
1416
1704
  isSupported(): Promise<boolean>;
@@ -1481,6 +1769,9 @@ interface PluginNative {
1481
1769
  bluetooth: BluetoothAPI;
1482
1770
  /** SHAPE ONLY in this build — see VisionAPI. On-device image understanding. */
1483
1771
  vision: VisionAPI;
1772
+ /** Place search, reverse geocoding, walking routes — see MapsAPI. Sends
1773
+ * queries to Apple's map servers; NOT on-device. */
1774
+ maps: MapsAPI;
1484
1775
  }
1485
1776
  interface NativeCapabilities$1 {
1486
1777
  camera?: boolean;
@@ -1496,6 +1787,9 @@ interface NativeCapabilities$1 {
1496
1787
  /** On-device image understanding (OCR, labels, frame quality). Refuses by
1497
1788
  * name until the native module lands — see VisionAPI. */
1498
1789
  vision?: boolean;
1790
+ /** Place search, reverse geocoding, walking routes. Means "may send place
1791
+ * queries to Apple Maps" — network, not on-device. See MapsAPI. */
1792
+ maps?: boolean;
1499
1793
  /** The live voice SESSION (props.voice). A privacy surface — a declaring
1500
1794
  * plugin can read the conversation transcript — so it is gated like any
1501
1795
  * other capability, and coupled to the platform's unsuppressable mic
@@ -1649,8 +1943,9 @@ interface PluginManifest$1 {
1649
1943
  };
1650
1944
  capabilities?: Record<string, unknown>;
1651
1945
  /** L3 DeviceState domains this plugin reads (props.deviceState), as Core passes
1652
- * them through from the bundle's declaration. Absent → fall back to the loaded
1653
- * bundle's own `stateDomains`. Read via resolvePluginRuntimeProps. */
1946
+ * them through from the solution's declaration. Absent → NO domains (fail
1947
+ * closed): the loaded bundle's own `stateDomains` is never consulted when a
1948
+ * manifest exists. Read via resolvePluginRuntimeProps. */
1654
1949
  stateDomains?: DeviceBridgeDomain[];
1655
1950
  _source?: string;
1656
1951
  _connectorId?: string;
@@ -1688,12 +1983,27 @@ interface DeviceToolContext {
1688
1983
  * the same gating a UI plugin's `props.deviceState` gets. This is what lets an
1689
1984
  * EXTERNAL tool (e.g. Ada Guide's `guide.observe`) read `ctx.deviceState.visual`
1690
1985
  * over the public surface, with no SDK-internal access.
1986
+ *
1987
+ * `subscribe` refuses (RESTRICTED) on every domain: a call has no lifetime to
1988
+ * own a subscription, and the engines outlive it. Read instead.
1691
1989
  */
1692
1990
  deviceState: DeviceState;
1693
1991
  /** Cooperative cancellation: caller cancelled, or the session went away. */
1694
1992
  signal?: AbortSignal;
1695
1993
  }
1696
1994
  type DeviceToolFn = (args: any, ctx: DeviceToolContext) => Promise<any> | any;
1995
+ /**
1996
+ * Why running a device tool produced no result (`callDeviceTool`'s
1997
+ * `{ ok: false, reason }`). The one list: the registry returns these, and the
1998
+ * voice snapshot's `lastDeviceTool.reason` carries them unchanged.
1999
+ * - `not_found` — no tool is registered under that name.
2000
+ * - `no_context` — the tool's device context could not be built.
2001
+ * - `timeout` — the tool did not finish within the call's timeout.
2002
+ * - `failed` — the tool threw; the error is its message.
2003
+ * A tool that RETURNS a value has succeeded as far as the platform is concerned,
2004
+ * even if the value describes a failure — throw to report one.
2005
+ */
2006
+ type DeviceToolFailureReason = 'not_found' | 'no_context' | 'timeout' | 'failed';
1697
2007
  interface DeviceToolDefinition {
1698
2008
  /** Domain name, as the catalog and the caller see it. e.g. "guide.observe" */
1699
2009
  name: string;
@@ -1708,9 +2018,10 @@ interface RegisteredToolsPlugin {
1708
2018
  type: 'tools';
1709
2019
  capabilities?: NativeCapabilities$1;
1710
2020
  /**
1711
- * L3 DeviceState domains this plugin's tools may read via `ctx.deviceState`
1712
- * (e.g. `['visual']`). Undeclared domains answer "not declared". Same
1713
- * mechanism as a UI plugin's manifest `stateDomains`.
2021
+ * L3 DeviceState domains this plugin's tools read via `ctx.deviceState` (e.g.
2022
+ * `['visual']`). For a remote bundle this is the author's statement, not a
2023
+ * grant: the tools get the manifest's `stateDomains` (resolvePluginGrants),
2024
+ * and a domain declared here but not there is denied with a warning.
1714
2025
  */
1715
2026
  stateDomains?: DeviceBridgeDomain[];
1716
2027
  tools: DeviceToolDefinition[];
@@ -1748,7 +2059,8 @@ declare function register(id: string, config: {
1748
2059
  */
1749
2060
  declare function registerTools(id: string, config: {
1750
2061
  capabilities?: NativeCapabilities$1;
1751
- /** L3 DeviceState domains the tools may read via ctx.deviceState (e.g. ['visual']). */
2062
+ /** L3 DeviceState domains the tools read via ctx.deviceState (e.g. ['visual']).
2063
+ * For a remote bundle the MANIFEST grants them — see RegisteredToolsPlugin. */
1752
2064
  stateDomains?: DeviceBridgeDomain[];
1753
2065
  tools: DeviceToolDefinition[];
1754
2066
  }): RegisteredToolsPlugin;
@@ -2006,11 +2318,6 @@ interface PluginRuntimeProps {
2006
2318
  deviceState: DeviceState;
2007
2319
  voice: VoiceAPI;
2008
2320
  }
2009
- /**
2010
- * Where a plugin's declarations come from. TRUSTED source resolution lives here,
2011
- * not in each caller: the server `manifest` wins, the loaded bundle's own default
2012
- * export is the fallback.
2013
- */
2014
2321
  /** One declaration source — either the server manifest or the registry entry. */
2015
2322
  interface PluginDeclaration {
2016
2323
  capabilities?: NativeCapabilities$1 | Record<string, unknown>;
@@ -2033,9 +2340,17 @@ interface PluginRuntimeSource {
2033
2340
  */
2034
2341
  plugin?: PluginDeclaration | null;
2035
2342
  }
2343
+ /** What a plugin is GRANTED — resolved from its one trusted source. The same
2344
+ * value gates its rendered surface and its device tools (the tool registry
2345
+ * receives it through bundleManager.load/loadTools). */
2346
+ interface PluginGrants {
2347
+ capabilities?: NativeCapabilities$1;
2348
+ stateDomains?: DeviceBridgeDomain[];
2349
+ }
2036
2350
  /**
2037
- * Turn a plugin's declarations into the gated { device, native, deviceState } it
2038
- * receives — the SINGLE place this happens, so every surface is identical.
2351
+ * Resolve a plugin's grants — the SINGLE place this happens, for every rendered
2352
+ * surface AND for the device tools its bundle registers, so a plugin gets
2353
+ * identical access through either.
2039
2354
  *
2040
2355
  * TRUST BOUNDARY. Exactly ONE source is authoritative: the server `manifest` for
2041
2356
  * a remote plugin, the registry `plugin` for a host-owned one. They are NEVER
@@ -2045,6 +2360,9 @@ interface PluginRuntimeSource {
2045
2360
  * not carry `stateDomains` for a remote plugin, the fix is Core passing it, not
2046
2361
  * trusting the bundle: Core already holds the solution's declaration.)
2047
2362
  */
2363
+ declare function resolvePluginGrants(src: PluginRuntimeSource): PluginGrants;
2364
+ /** Turn a plugin's grants into the gated { device, native, deviceState, voice } it
2365
+ * receives on every rendered surface. */
2048
2366
  declare function resolvePluginRuntimeProps(src: PluginRuntimeSource): PluginRuntimeProps;
2049
2367
 
2050
2368
  interface DeviceToolSummary {
@@ -2059,7 +2377,7 @@ type DeviceToolOutcome = {
2059
2377
  } | {
2060
2378
  ok: false;
2061
2379
  error: string;
2062
- reason: 'not_found' | 'no_context' | 'timeout' | 'failed';
2380
+ reason: DeviceToolFailureReason;
2063
2381
  };
2064
2382
  /**
2065
2383
  * Override how a device tool reaches the Device Bridge. Rarely needed — the
@@ -2077,7 +2395,9 @@ declare function setDeviceToolContextProvider(fn: (pluginId: string, capabilitie
2077
2395
  declare function registerDeviceTools(pluginId: string, tools: DeviceToolDefinition[], capabilities?: NativeCapabilities$1, stateDomains?: DeviceBridgeDomain[]): string[];
2078
2396
  /** Drop every tool owned by a plugin (bundle eviction / plugin revoked). */
2079
2397
  declare function unregisterDeviceTools(pluginId: string): void;
2080
- declare function hasDeviceTool(name: string): boolean;
2398
+ /** Is `name` registered — and, given `pluginId`, registered BY that plugin?
2399
+ * (A clean voice call only lets its owner's own tools run.) */
2400
+ declare function hasDeviceTool(name: string, pluginId?: string): boolean;
2081
2401
  declare function listDeviceTools(): DeviceToolSummary[];
2082
2402
  /**
2083
2403
  * Invoke a device tool. Never throws — every failure comes back as a typed
@@ -2210,6 +2530,9 @@ declare class BundleManager {
2210
2530
  * of the previous tenant's tools.
2211
2531
  */
2212
2532
  private registeredToolPluginIds;
2533
+ /** bundleUrl → the trusted declaration last recorded on its disk-cache entry
2534
+ * this session, so a memory hit with unchanged grants skips the index I/O. */
2535
+ private recordedDeclarations;
2213
2536
  /**
2214
2537
  * Enforce the in-memory tenant boundary. Call at the top of every public load
2215
2538
  * path, BEFORE any cache read.
@@ -2237,9 +2560,11 @@ declare class BundleManager {
2237
2560
  * tenant switch (or wipe) happened while this load was in flight. Its results
2238
2561
  * belong to a tenant that is no longer current and must be discarded. */
2239
2562
  private isStale;
2240
- /** Flip an existing provenance record to source:'memory' — a later serve of an
2241
- * already-evaluated bundle came from the in-memory cache, not a fresh
2242
- * download/disk read. No-op if nothing was recorded yet. */
2563
+ /** Record a memory serve: the running bytes are the already-evaluated module,
2564
+ * and `expected` is refreshed from the manifest hash the caller JUST passed —
2565
+ * so after a redeploy the record reads hash !== expected (STALE) instead of
2566
+ * the old in-sync pair. The ONE place both memory paths (load()'s early return
2567
+ * and loadModule's) record reuse. No-op if nothing was recorded yet. */
2243
2568
  private markMemoryHit;
2244
2569
  /**
2245
2570
  * Load a plugin component. Returns the RegisteredUIPlugin object.
@@ -2249,16 +2574,26 @@ declare class BundleManager {
2249
2574
  * provided for any bundle the user has not previously verified — without
2250
2575
  * it, a tampered bundle is `eval`'d as JS with full RN runtime + every
2251
2576
  * native capability the manifest declares.
2577
+ *
2578
+ * `grants` is what the MANIFEST grants this plugin (resolvePluginGrants) — the
2579
+ * only authority its device tools are registered under. Omitted (e.g. a
2580
+ * prewarm from the slim plugin list) → the bundle is loaded but its tools are
2581
+ * NOT registered: without the manifest there is nothing trusted to gate them
2582
+ * on, and registering with nothing would downgrade a registration the
2583
+ * manifest-driven tool loader already made.
2252
2584
  */
2253
- load(pluginId: string, bundleUrl: string, version: string, expectedHash?: string): Promise<RegisteredUIPlugin>;
2585
+ load(pluginId: string, bundleUrl: string, version: string, expectedHash?: string, grants?: PluginGrants): Promise<RegisteredUIPlugin>;
2254
2586
  /**
2255
2587
  * Load a bundle for its `tools/` section only — no UI required.
2256
2588
  *
2257
2589
  * Same download, same integrity check, same evaluation as `load()`; the only
2258
2590
  * difference is that a bundle exporting tools and no `Component` is a success
2259
2591
  * here rather than "no component found". Returns the tool names registered.
2592
+ *
2593
+ * `grants` is REQUIRED: the tools are registered under `pluginId` with exactly
2594
+ * what the manifest grants (see load()), never what the bundle claims.
2260
2595
  */
2261
- loadTools(pluginId: string, bundleUrl: string, version: string, expectedHash?: string): Promise<string[]>;
2596
+ loadTools(pluginId: string, bundleUrl: string, version: string, expectedHash: string | undefined, grants: PluginGrants): Promise<string[]>;
2262
2597
  /**
2263
2598
  * Prewarm: load every plugin bundle in the background after app boot, so a
2264
2599
  * later open is instant (in-memory hit) instead of round-tripping the
@@ -2270,6 +2605,8 @@ declare class BundleManager {
2270
2605
  bundleUrl: string;
2271
2606
  version: string;
2272
2607
  bundleHash?: string;
2608
+ /** The manifest's grants, when the caller has the manifest (see load()). */
2609
+ grants?: PluginGrants;
2273
2610
  }>): Promise<void>;
2274
2611
  /**
2275
2612
  * Clear all caches (in-memory + storage).
@@ -2290,11 +2627,36 @@ declare class BundleManager {
2290
2627
  getLoadedBundles(): LoadedBundleInfo[];
2291
2628
  private loadModule;
2292
2629
  /**
2293
- * Pick up every `tools` section in an evaluated bundle and put it in the
2294
- * device tool registry. Runs for UI, hybrid and tools-only bundles alike: a
2295
- * plugin that ships both gets its tools registered without its UI mounting.
2630
+ * Register THIS plugin's device tools under the TRUSTED declaration, then
2631
+ * record that declaration on the disk-cache entry. Runs on every trusted load
2632
+ * — memory hit included — so the grants in force are always the latest the
2633
+ * manifest gave (a revoked grant stops applying on the next load, not the next
2634
+ * app start). Callers run it only after their stale check.
2635
+ */
2636
+ private commitTools;
2637
+ /**
2638
+ * Put the plugin's own `tools` section in the device tool registry — under the
2639
+ * pluginId and grants the MANIFEST resolved, never the bundle's own `id`,
2640
+ * `capabilities` or `stateDomains`. A bundle is code the manifest authorised
2641
+ * by hash; what it may touch is the manifest's to say, same as its rendered
2642
+ * surface (resolvePluginRuntimeProps). Its own `id` would also let it register
2643
+ * as another plugin — including the host's platform tools, which a same-id
2644
+ * re-registration replaces.
2645
+ *
2646
+ * Only this plugin's export (the same lookup the component uses) is
2647
+ * registered. Another export's tools in the same bundle belong to another
2648
+ * plugin and register when THAT plugin's manifest loads it.
2296
2649
  */
2297
2650
  private registerToolsFromModule;
2651
+ /**
2652
+ * Record the trusted declaration on this tenant's disk-cache entry for the
2653
+ * bytes RUNNING — the recovery path (tools/loader loadFromDiskCache) has no
2654
+ * manifest and re-registers with exactly this. Only onto an entry this tenant
2655
+ * owns whose hash is the running hash: the grant was issued for these bytes,
2656
+ * and those are the bytes recovery will evaluate. Best-effort — a lost write
2657
+ * leaves no `trusted`, which recovery treats as fail closed.
2658
+ */
2659
+ private recordTrustedDeclaration;
2298
2660
  /**
2299
2661
  * Download + integrity-verify a bundle. Refuses to return when:
2300
2662
  * - manifest hash and computed hash disagree
@@ -2304,11 +2666,15 @@ declare class BundleManager {
2304
2666
  */
2305
2667
  private download;
2306
2668
  private evaluate;
2669
+ /** Find a plugin component in a loaded module. */
2670
+ private getComponentFromMemory;
2307
2671
  /**
2308
- * Find a plugin component in a loaded module.
2309
- * Handles: default export, direct key match, multi-plugin .id match.
2672
+ * Find THIS plugin's export in a loaded module — the one lookup both the
2673
+ * component and the device tools use, so a plugin's UI and its tools always
2674
+ * come from the same export. `accept` says what the caller needs from it.
2675
+ * Handles: direct key match, PascalCase key, default export, multi-plugin .id match.
2310
2676
  */
2311
- private getComponentFromMemory;
2677
+ private findPluginExport;
2312
2678
  private describeModule;
2313
2679
  }
2314
2680
  /** Singleton — shared across all plugin renderers */
@@ -2321,6 +2687,7 @@ declare const bundleManager: BundleManager;
2321
2687
  * default is in-memory (bundles re-download per app session — correct,
2322
2688
  * just slower on cold start).
2323
2689
  */
2690
+
2324
2691
  interface BundleCacheEntry {
2325
2692
  localPath: string;
2326
2693
  version: string;
@@ -2342,6 +2709,23 @@ interface BundleCacheEntry {
2342
2709
  * untrusted. Never read a missing tenant as "mine".
2343
2710
  */
2344
2711
  tenant?: string;
2712
+ /**
2713
+ * The trusted declaration these bytes' device tools were last registered
2714
+ * under: the pluginId and grants resolved from the server manifest that named
2715
+ * this bundle (resolvePluginGrants). Recorded so the disk-cache recovery path —
2716
+ * which runs exactly when there is NO manifest — re-registers with the same
2717
+ * authority instead of the bundle's own claim.
2718
+ *
2719
+ * Absent → no trusted declaration is known for these bytes, so recovery skips
2720
+ * the entry (fail closed) and the catalog path registers it once the owning
2721
+ * connector answers again.
2722
+ */
2723
+ trusted?: TrustedToolDeclaration;
2724
+ }
2725
+ /** Who a bundle's device tools belong to and what they are granted — always
2726
+ * from the manifest, never from the bundle. */
2727
+ interface TrustedToolDeclaration extends PluginGrants {
2728
+ pluginId: string;
2345
2729
  }
2346
2730
  interface BundleStorage {
2347
2731
  loadIndex(): Promise<Record<string, BundleCacheEntry>>;
@@ -2572,6 +2956,9 @@ interface PluginManifestLike {
2572
2956
  name?: string;
2573
2957
  render?: any;
2574
2958
  _connectorId?: string;
2959
+ capabilities?: Record<string, unknown>;
2960
+ native?: NativeCapabilities$1;
2961
+ stateDomains?: DeviceBridgeDomain[];
2575
2962
  }
2576
2963
  interface UsePluginResult {
2577
2964
  plugin: RegisteredUIPlugin | null;
@@ -2708,7 +3095,15 @@ declare function supportsNative(manifest: PluginManifest): boolean;
2708
3095
  */
2709
3096
  declare function getNativeComponentId(manifest: PluginManifest): string | null;
2710
3097
  /**
2711
- * Get the native bundle URL from a manifest (if it has native support).
3098
+ * The native bundle URL for a manifest that has a bundle on the server, or null.
3099
+ *
3100
+ * Core writes `bundleUrl` into the manifest only when the connector's bundle
3101
+ * exists, so its PRESENCE says "there is a bundle". Its SPELLING is not used:
3102
+ * Core writes the id unencoded, and a connector could inject any URL. The URL
3103
+ * returned is always the one derivation, pluginBundleUrl(manifest.id), the same
3104
+ * one the rendered surface uses. Returning the manifest's own string (6294cc3)
3105
+ * gave the device-tool loader a second spelling, so one plugin downloaded and
3106
+ * evaluated twice, as two module instances (MGAP-C8).
2712
3107
  */
2713
3108
  declare function getNativeBundleUrl(manifest: PluginManifest): string | null;
2714
3109
  /**
@@ -2743,6 +3138,27 @@ declare function getPlugin(pluginId: string): Promise<PluginManifest | null>;
2743
3138
  */
2744
3139
  declare function resolvePluginUrl(manifest: PluginManifest, tenant?: string): string;
2745
3140
 
3141
+ /**
3142
+ * THE bundle URL of a plugin: the one place it is spelled.
3143
+ *
3144
+ * Core serves a plugin's React Native bundle at
3145
+ * `/api/ui-plugins/<pluginId>/bundle.js`, where the id is URL-encoded because
3146
+ * plugin ids contain ':' (`mcp:<connector>:<plugin>`). The bundle manager keys
3147
+ * everything it holds by this string: evaluated modules, in-flight downloads and
3148
+ * the disk index. To it, two spellings of one plugin's URL are two plugins, which
3149
+ * means two downloads, two evaluations and two separate copies of module state.
3150
+ *
3151
+ * That happened (MGAP-C8). The rendered surface derived the encoded route, while
3152
+ * the device-tool loader (6294cc3) used the manifest's `bundleUrl`, which Core
3153
+ * fills in UNENCODED. A plugin that was on screen and also had device tools ran
3154
+ * as two module instances, and its tools never saw its UI's state.
3155
+ *
3156
+ * So the URL is DERIVED from the plugin id and never read from a manifest (a
3157
+ * connector-supplied URL is ignored, as in ea435149). Every path that builds a
3158
+ * plugin's bundle URL, or looks one up, calls this function.
3159
+ */
3160
+ declare function pluginBundleUrl(pluginId: string): string;
3161
+
2746
3162
  interface SurfaceDrawerProps {
2747
3163
  visible: boolean;
2748
3164
  onClose: () => void;
@@ -2890,4 +3306,4 @@ declare function isInlineSurface(m: SurfaceManifest): m is InlineSurfaceManifest
2890
3306
 
2891
3307
  declare function SurfaceDispatcher(): react_jsx_runtime.JSX.Element;
2892
3308
 
2893
- export { ActionButton, type ActivitySnapshot, AdaOrb, type AdaOrbState, Badge, type BiometricsAPI, type BiometricsAPIv2, type BluetoothAPI, type BluetoothDevice, type Bridge, type BundleCacheEntry, type BundleStorage, type CameraAPI, CameraPreview, type CameraPreviewProps, type CapabilitySnapshot, type CapabilityStatus, Card, type Confidence, type ConnectivityAPI, type ConnectivitySnapshot, type CookiesAPI, type DeviceBridgeDomain, type DeviceContextSnapshot, type DeviceState, type DeviceToolContext, type DeviceToolDefinition, type DeviceToolFn, type DeviceToolOutcome, type DeviceToolSummary, type DeviceToolsAction, type DeviceToolsSessionDeps, EmptyState, type EnvironmentSnapshot, ErrorState, type FileSystemAPI, type Freshness, type HapticsAPI, type InlineSurfaceManifest, type InteractionSnapshot, ListItem, type LoadedBundleInfo, type LoadedBundleSource, LoadingState, type LocationAPI, MemoryBundleStorage, type MountSurfaceManifest, type NativeCapabilities$1 as NativeCapabilities, type NfcAPI, type NotificationsAPI, PLATFORM_VOICE_CALLER, type PluginManifest as PlatformPluginManifest, type PluginBridge, type PluginContext, type PluginHooks$1 as PluginHooks, type PluginManifest$1 as PluginManifest, type PluginNative, type PluginProps, type PluginRuntimeProps, type PluginRuntimeSource, PluginSDK, type PluginSummary, type PluginSurfaceMeta, type RegisteredPlugin, type RegisteredServicePlugin, type RegisteredToolsPlugin, type RegisteredUIPlugin, type RemotePlugin, type RenderMode, type ScannerAPI, SearchBar, type SessionSnapshot, type SharingAPI, type SpatialIntent, type SpatialPoint, type SpatialSnapshot, type StateQuality, type StorageAPI, SurfaceCard, type SurfaceCardProps, SurfaceDispatcher, SurfaceDrawer, type SurfaceDrawerProps, SurfaceFullscreen, type SurfaceFullscreenProps, type SurfaceManifest, type SurfacePlacement, type SurfaceType, type SurfaceVisibility, TabBar, type ThemeTokens, type VisionAPI, type VisionFrameQuality, type VisionLabel, type VisionObject, type VisionText, type VisualSnapshot, type VoiceAPI, type VoiceCaller, type VoiceEvent, type VoiceSessionProvider, type VoiceSnapshot, type VoiceStartOptions, type VoiceState, type VoiceSubscription, type VoiceTranscriptEntry, type WebViewCookie, __resetSessionSyncForTest, bundleManager, callDeviceTool, clearDeviceTools, createNativeBridge, deviceState as deviceStateV2, deviceToolsActionForSession, ensureDeviceToolsLoaded, explainCapability, gateDeviceState, gatePluginNative, gateVoice, getCameraPreviewProvider, getHaptics, getNativeBundleHash, getNativeBundleUrl, getNativeComponentId, getPlugin, getPluginNative, handleServerDeviceToolCall, hasDeviceTool, implementedStateDomains, implementedVoice, isActionable, isDeviceToolCommand, isInlineSurface, isMountSurface, listDeviceTools, listPlugins, nativePluginRegistry, neverObserved, register, registerBundleModules, registerDeviceTools, registerNativePlugin, registerPlatformDeviceTools, registerService, registerTools, resetDeviceToolLoader, resolvePluginRuntimeProps, resolvePluginUrl, setBundleStorage, setCameraPreviewProvider, setDeviceStateProvider, setDeviceToolContextProvider, setDeviceToolResultPoster, setHapticsProvider, setPluginNativeProvider, setPluginThemeColors, setVoiceProvider, supportsNative, supportsWebView, syncDeviceToolsForSession, unregisterDeviceTools, unregisterNativePlugin, useApi, useNativeBridge, usePlugin, usePluginTheme, useRemotePlugins, useSurfaceStore };
3309
+ export { ActionButton, type ActivitySnapshot, AdaOrb, type AdaOrbState, Badge, type BiometricsAPI, type BiometricsAPIv2, type BluetoothAPI, type BluetoothDevice, type Bridge, type BundleCacheEntry, type BundleStorage, type CameraAPI, CameraPreview, type CameraPreviewProps, type CapabilitySnapshot, type CapabilityStatus, Card, type Confidence, type ConnectivityAPI, type ConnectivitySnapshot, type CookiesAPI, type DeviceBridgeDomain, type DeviceContextSnapshot, type DeviceState, type DeviceToolContext, type DeviceToolDefinition, type DeviceToolFailureReason, type DeviceToolFn, type DeviceToolOutcome, type DeviceToolSummary, type DeviceToolsAction, type DeviceToolsSessionDeps, EmptyState, type EnvironmentSnapshot, ErrorState, type FileSystemAPI, type Freshness, type HapticsAPI, type InlineSurfaceManifest, type InteractionSnapshot, ListItem, type LoadedBundleInfo, type LoadedBundleSource, LoadingState, type LocationAPI, type MapsAPI, type MapsAddress, type MapsCoordinate, type MapsPlace, type MapsRoute, type MapsRouteStep, MemoryBundleStorage, type MountSurfaceManifest, type NativeCapabilities$1 as NativeCapabilities, type NfcAPI, type NotificationsAPI, PLATFORM_VOICE_CALLER, type PluginManifest as PlatformPluginManifest, type PluginBridge, type PluginContext, type PluginGrants, type PluginHooks$1 as PluginHooks, type PluginManifest$1 as PluginManifest, type PluginNative, type PluginProps, type PluginRuntimeProps, type PluginRuntimeSource, PluginSDK, type PluginSummary, type PluginSurfaceMeta, type RegisteredPlugin, type RegisteredServicePlugin, type RegisteredToolsPlugin, type RegisteredUIPlugin, type RemotePlugin, type RenderMode, type ScannerAPI, SearchBar, type SessionSnapshot, type SharingAPI, type SpatialEvent, type SpatialIntent, type SpatialPoint, type SpatialSnapshot, type SpatialStateEvent, type StateEvent, type StateListener, type StateQuality, type StorageAPI, type Subscription, SurfaceCard, type SurfaceCardProps, SurfaceDispatcher, SurfaceDrawer, type SurfaceDrawerProps, SurfaceFullscreen, type SurfaceFullscreenProps, type SurfaceManifest, type SurfacePlacement, type SurfaceType, type SurfaceVisibility, TabBar, type ThemeTokens, type TrustedToolDeclaration, type VisionAPI, type VisionFrameQuality, type VisionLabel, type VisionObject, type VisionText, type VisualSnapshot, type VoiceAPI, type VoiceCaller, type VoiceDeviceToolCall, type VoiceDeviceToolReason, type VoiceEvent, type VoicePersona, type VoiceSessionProvider, type VoiceSnapshot, type VoiceStartOptions, type VoiceState, type VoiceSubscription, type VoiceTranscriptEntry, type WebViewCookie, __resetSessionSyncForTest, bundleManager, callDeviceTool, clearDeviceTools, createNativeBridge, deviceState as deviceStateV2, deviceToolsActionForSession, ensureDeviceToolsLoaded, explainCapability, gateDeviceState, gatePluginNative, gateVoice, getCameraPreviewProvider, getHaptics, getNativeBundleHash, getNativeBundleUrl, getNativeComponentId, getPlugin, getPluginNative, handleServerDeviceToolCall, hasDeviceTool, implementedStateDomains, implementedVoice, isActionable, isDeviceToolCommand, isInlineSurface, isMountSurface, listDeviceTools, listPlugins, nativePluginRegistry, neverObserved, pluginBundleUrl, register, registerBundleModules, registerDeviceTools, registerNativePlugin, registerPlatformDeviceTools, registerService, registerTools, resetDeviceToolLoader, resolvePluginGrants, resolvePluginRuntimeProps, resolvePluginUrl, setBundleStorage, setCameraPreviewProvider, setDeviceStateProvider, setDeviceToolContextProvider, setDeviceToolResultPoster, setHapticsProvider, setPluginNativeProvider, setPluginThemeColors, setVoiceProvider, supportsNative, supportsWebView, syncDeviceToolsForSession, unregisterDeviceTools, unregisterNativePlugin, useApi, useNativeBridge, usePlugin, usePluginTheme, useRemotePlugins, useSurfaceStore };