@ateam-ai-mobile/plugin-runtime 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,118 +30,24 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- ActionButton: () => ActionButton,
34
- ActivityIndicator: () => import_react_native.ActivityIndicator,
35
- AdaOrb: () => AdaOrb,
36
- Animated: () => import_react_native.Animated,
37
- Badge: () => Badge,
38
- Card: () => Card,
39
- EmptyState: () => EmptyState,
40
- ErrorState: () => ErrorState,
41
- FlatList: () => import_react_native.FlatList,
42
- Image: () => import_react_native.Image,
43
- ListItem: () => ListItem,
44
- LoadingState: () => LoadingState,
45
- MemoryBundleStorage: () => MemoryBundleStorage,
46
- Modal: () => import_react_native.Modal,
47
- Platform: () => import_react_native.Platform,
48
- PluginSDK: () => PluginSDK,
49
- Pressable: () => import_react_native.Pressable,
50
- RefreshControl: () => import_react_native.RefreshControl,
51
- ScrollView: () => import_react_native.ScrollView,
52
- SearchBar: () => SearchBar,
53
- SectionList: () => import_react_native.SectionList,
54
- StyleSheet: () => import_react_native.StyleSheet,
55
- SurfaceCard: () => SurfaceCard,
56
- SurfaceDispatcher: () => SurfaceDispatcher,
57
- SurfaceDrawer: () => SurfaceDrawer,
58
- SurfaceFullscreen: () => SurfaceFullscreen,
59
- Switch: () => import_react_native.Switch,
60
- TabBar: () => TabBar,
61
- Text: () => import_react_native.Text,
62
- TextInput: () => import_react_native.TextInput,
63
- TouchableOpacity: () => import_react_native.TouchableOpacity,
64
- View: () => import_react_native.View,
65
- bundleManager: () => bundleManager,
66
- createNativeBridge: () => createNativeBridge,
67
- gatePluginNative: () => gatePluginNative,
68
- getHaptics: () => getHaptics,
69
- getNativeBundleHash: () => getNativeBundleHash,
70
- getNativeBundleUrl: () => getNativeBundleUrl,
71
- getNativeComponentId: () => getNativeComponentId,
72
- getPlugin: () => getPlugin,
73
- getPluginNative: () => getPluginNative,
74
- isInlineSurface: () => isInlineSurface,
75
- isMountSurface: () => isMountSurface,
76
- listPlugins: () => listPlugins,
77
- nativePluginRegistry: () => nativePluginRegistry,
78
- register: () => register,
79
- registerBundleModules: () => registerBundleModules,
80
- registerNativePlugin: () => registerNativePlugin,
81
- registerService: () => registerService,
82
- resolvePluginUrl: () => resolvePluginUrl,
83
- setBundleStorage: () => setBundleStorage,
84
- setHapticsProvider: () => setHapticsProvider,
85
- setPluginNativeProvider: () => setPluginNativeProvider,
86
- setPluginThemeColors: () => setPluginThemeColors,
87
- supportsNative: () => supportsNative,
88
- supportsWebView: () => supportsWebView,
89
- unregisterNativePlugin: () => unregisterNativePlugin,
90
- useApi: () => useApi,
91
- useNativeBridge: () => useNativeBridge,
92
- usePlugin: () => usePlugin,
93
- usePluginTheme: () => usePluginTheme,
94
- useRemotePlugins: () => useRemotePlugins,
95
- useSurfaceStore: () => useSurfaceStore
96
- });
97
- module.exports = __toCommonJS(index_exports);
98
-
99
- // src/sdk/index.ts
100
- var sdk_exports = {};
101
- __export(sdk_exports, {
102
- ActionButton: () => ActionButton,
103
- ActivityIndicator: () => import_react_native.ActivityIndicator,
104
- Animated: () => import_react_native.Animated,
105
- Badge: () => Badge,
106
- Card: () => Card,
107
- EmptyState: () => EmptyState,
108
- ErrorState: () => ErrorState,
109
- FlatList: () => import_react_native.FlatList,
110
- Image: () => import_react_native.Image,
111
- ListItem: () => ListItem,
112
- LoadingState: () => LoadingState,
113
- Modal: () => import_react_native.Modal,
114
- Platform: () => import_react_native.Platform,
115
- PluginSDK: () => PluginSDK,
116
- Pressable: () => import_react_native.Pressable,
117
- RefreshControl: () => import_react_native.RefreshControl,
118
- ScrollView: () => import_react_native.ScrollView,
119
- SearchBar: () => SearchBar,
120
- SectionList: () => import_react_native.SectionList,
121
- StyleSheet: () => import_react_native.StyleSheet,
122
- Switch: () => import_react_native.Switch,
123
- TabBar: () => TabBar,
124
- Text: () => import_react_native.Text,
125
- TextInput: () => import_react_native.TextInput,
126
- TouchableOpacity: () => import_react_native.TouchableOpacity,
127
- View: () => import_react_native.View,
128
- register: () => register,
129
- registerService: () => registerService,
130
- setPluginThemeColors: () => setPluginThemeColors,
131
- useApi: () => useApi,
132
- usePluginTheme: () => usePluginTheme
133
- });
134
-
135
33
  // src/sdk/registration.ts
136
34
  function register(id, config) {
137
35
  return {
138
36
  id,
139
37
  type: config.type || "ui",
140
38
  capabilities: config.capabilities,
141
- Component: config.Component
39
+ stateDomains: config.stateDomains,
40
+ Component: config.Component,
41
+ tools: config.tools
42
+ };
43
+ }
44
+ function registerTools(id, config) {
45
+ return {
46
+ id,
47
+ type: "tools",
48
+ capabilities: config.capabilities,
49
+ stateDomains: config.stateDomains,
50
+ tools: config.tools
142
51
  };
143
52
  }
144
53
  function registerService(id, config) {
@@ -151,13 +60,31 @@ function registerService(id, config) {
151
60
  onStop: config.onStop
152
61
  };
153
62
  }
63
+ var init_registration = __esm({
64
+ "src/sdk/registration.ts"() {
65
+ "use strict";
66
+ }
67
+ });
68
+
69
+ // src/sdk/voice.ts
70
+ var PLATFORM_VOICE_CALLER;
71
+ var init_voice = __esm({
72
+ "src/sdk/voice.ts"() {
73
+ "use strict";
74
+ PLATFORM_VOICE_CALLER = /* @__PURE__ */ Symbol("adas.voice.platform");
75
+ }
76
+ });
154
77
 
155
78
  // src/sdk/primitives.ts
156
- var import_react_native = require("react-native");
79
+ var import_react_native;
80
+ var init_primitives = __esm({
81
+ "src/sdk/primitives.ts"() {
82
+ "use strict";
83
+ import_react_native = require("react-native");
84
+ }
85
+ });
157
86
 
158
87
  // src/sdk/components.ts
159
- var import_react = __toESM(require("react"));
160
- var import_react_native2 = require("react-native");
161
88
  function Card({ theme, children, style, onPress }) {
162
89
  const cardStyle = [
163
90
  styles.card,
@@ -172,13 +99,6 @@ function Card({ theme, children, style, onPress }) {
172
99
  }
173
100
  return import_react.default.createElement(import_react_native2.View, { style: cardStyle }, children);
174
101
  }
175
- var BADGE_COLORS = {
176
- accent: { bg: "accentSoft", text: "accent" },
177
- success: { bg: "successSoft", text: "success" },
178
- warning: { bg: "warningSoft", text: "warning" },
179
- error: { bg: "errorSoft", text: "error" },
180
- purple: { bg: "purpleSoft", text: "purple" }
181
- };
182
102
  function Badge({ theme, label, color = "accent", style }) {
183
103
  const mapping = BADGE_COLORS[color];
184
104
  return import_react.default.createElement(
@@ -337,105 +257,270 @@ function ActionButton({ theme, label, icon, onPress, style }) {
337
257
  icon || import_react.default.createElement(import_react_native2.Text, { style: styles.fabText }, label || "+")
338
258
  );
339
259
  }
340
- var styles = import_react_native2.StyleSheet.create({
341
- card: {
342
- padding: 12,
343
- borderRadius: 12,
344
- borderWidth: 1,
345
- marginBottom: 8
346
- },
347
- badge: {
348
- paddingHorizontal: 8,
349
- paddingVertical: 3
350
- },
351
- badgeText: {
352
- fontSize: 11,
353
- fontWeight: "600"
354
- },
355
- tabBar: {
356
- flexDirection: "row",
357
- borderBottomWidth: 1
358
- },
359
- tab: {
360
- flex: 1,
361
- padding: 12,
362
- alignItems: "center",
363
- borderBottomWidth: 2
364
- },
365
- listItem: {
366
- flexDirection: "row",
367
- alignItems: "center",
368
- paddingVertical: 12,
369
- paddingHorizontal: 16,
370
- borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth
371
- },
372
- listItemContent: {
373
- flex: 1
374
- },
375
- searchBar: {
376
- height: 40,
377
- paddingHorizontal: 12,
378
- borderWidth: 1
379
- },
380
- emptyState: {
381
- flex: 1,
382
- justifyContent: "center",
383
- alignItems: "center",
384
- padding: 32
385
- },
386
- retryButton: {
387
- marginTop: 12,
388
- paddingHorizontal: 16,
389
- paddingVertical: 8
390
- },
391
- fab: {
392
- position: "absolute",
393
- bottom: 24,
394
- right: 24,
395
- width: 56,
396
- height: 56,
397
- justifyContent: "center",
398
- alignItems: "center",
399
- shadowColor: "#000",
400
- shadowOffset: { width: 0, height: 2 },
401
- shadowOpacity: 0.25,
402
- shadowRadius: 4,
403
- elevation: 5
404
- },
405
- fabText: {
406
- color: "#fff",
407
- fontSize: 24,
408
- fontWeight: "600"
260
+ var import_react, import_react_native2, BADGE_COLORS, styles;
261
+ var init_components = __esm({
262
+ "src/sdk/components.ts"() {
263
+ "use strict";
264
+ import_react = __toESM(require("react"));
265
+ import_react_native2 = require("react-native");
266
+ BADGE_COLORS = {
267
+ accent: { bg: "accentSoft", text: "accent" },
268
+ success: { bg: "successSoft", text: "success" },
269
+ warning: { bg: "warningSoft", text: "warning" },
270
+ error: { bg: "errorSoft", text: "error" },
271
+ purple: { bg: "purpleSoft", text: "purple" }
272
+ };
273
+ styles = import_react_native2.StyleSheet.create({
274
+ card: {
275
+ padding: 12,
276
+ borderRadius: 12,
277
+ borderWidth: 1,
278
+ marginBottom: 8
279
+ },
280
+ badge: {
281
+ paddingHorizontal: 8,
282
+ paddingVertical: 3
283
+ },
284
+ badgeText: {
285
+ fontSize: 11,
286
+ fontWeight: "600"
287
+ },
288
+ tabBar: {
289
+ flexDirection: "row",
290
+ borderBottomWidth: 1
291
+ },
292
+ tab: {
293
+ flex: 1,
294
+ padding: 12,
295
+ alignItems: "center",
296
+ borderBottomWidth: 2
297
+ },
298
+ listItem: {
299
+ flexDirection: "row",
300
+ alignItems: "center",
301
+ paddingVertical: 12,
302
+ paddingHorizontal: 16,
303
+ borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth
304
+ },
305
+ listItemContent: {
306
+ flex: 1
307
+ },
308
+ searchBar: {
309
+ height: 40,
310
+ paddingHorizontal: 12,
311
+ borderWidth: 1
312
+ },
313
+ emptyState: {
314
+ flex: 1,
315
+ justifyContent: "center",
316
+ alignItems: "center",
317
+ padding: 32
318
+ },
319
+ retryButton: {
320
+ marginTop: 12,
321
+ paddingHorizontal: 16,
322
+ paddingVertical: 8
323
+ },
324
+ fab: {
325
+ position: "absolute",
326
+ bottom: 24,
327
+ right: 24,
328
+ width: 56,
329
+ height: 56,
330
+ justifyContent: "center",
331
+ alignItems: "center",
332
+ shadowColor: "#000",
333
+ shadowOffset: { width: 0, height: 2 },
334
+ shadowOpacity: 0.25,
335
+ shadowRadius: 4,
336
+ elevation: 5
337
+ },
338
+ fabText: {
339
+ color: "#fff",
340
+ fontSize: 24,
341
+ fontWeight: "600"
342
+ }
343
+ });
344
+ }
345
+ });
346
+
347
+ // src/host.ts
348
+ function setHapticsProvider(haptics) {
349
+ _haptics = haptics;
350
+ }
351
+ function getHaptics() {
352
+ return _haptics;
353
+ }
354
+ function unavailable(api) {
355
+ return new Proxy({}, {
356
+ get: (_t, method) => () => Promise.reject(new Error(`[adas] native.${api}.${String(method)} unavailable \u2014 host did not call setPluginNativeProvider()`))
357
+ });
358
+ }
359
+ function setPluginNativeProvider(native) {
360
+ _pluginNative = native;
361
+ }
362
+ function getPluginNative() {
363
+ return _pluginNative;
364
+ }
365
+ function blocked(api, reason) {
366
+ return new Proxy({}, {
367
+ get: (_t, method) => {
368
+ if (typeof method !== "string") return void 0;
369
+ return () => {
370
+ console.warn(`[adas] native.${api}.${method} blocked \u2014 ${reason ?? `capability '${api}' not declared in the plugin manifest`}`);
371
+ };
372
+ }
373
+ });
374
+ }
375
+ function gatePluginNative(native, declared, pluginId) {
376
+ const caps = declared || {};
377
+ const scopedFileSystem = native.fileSystemFor && pluginId ? native.fileSystemFor(pluginId) : null;
378
+ const scopedStorage = native.storageFor && pluginId ? native.storageFor(pluginId) : null;
379
+ const gated = {
380
+ camera: caps.camera ? native.camera : blocked("camera"),
381
+ location: caps.location ? native.location : blocked("location"),
382
+ storage: caps.offline ? scopedStorage ?? blocked("storage", "no trusted pluginId to scope storage \u2014 refusing shared access (fail closed)") : blocked("storage"),
383
+ biometrics: caps.biometrics ? native.biometrics : blocked("biometrics"),
384
+ biometricsV2: caps.biometricsV2 ? native.biometricsV2 : blocked("biometricsV2"),
385
+ notifications: caps.notifications ? native.notifications : blocked("notifications"),
386
+ haptics: caps.haptics ? native.haptics : blocked("haptics"),
387
+ connectivity: native.connectivity,
388
+ fileSystem: caps.fileSystem ? scopedFileSystem ?? blocked("fileSystem", "no trusted pluginId to scope the filesystem \u2014 refusing shared access (fail closed)") : blocked("fileSystem"),
389
+ cookies: caps.cookies ? native.cookies : blocked("cookies"),
390
+ sharing: caps.sharing ? native.sharing : blocked("sharing"),
391
+ scanner: caps.scanner ? native.scanner : blocked("scanner"),
392
+ nfc: caps.nfc ? native.nfc : blocked("nfc"),
393
+ // Gated like the rest even though nothing implements it yet. An undeclared
394
+ // plugin gets the usual blocked proxy; a DECLARED one reaches an API whose
395
+ // every method refuses by name. Both are better than the `undefined` this
396
+ // used to be, which crashed with "cannot read properties of undefined".
397
+ bluetooth: caps.bluetooth ? native.bluetooth : blocked("bluetooth"),
398
+ vision: caps.vision ? native.vision : blocked("vision")
399
+ // What this plugin DECLARED, carried on the object it already receives.
400
+ //
401
+ // It is how a COMPONENT can be capability-gated: <CameraPreview> is handed
402
+ // `native` and reads this, so showing the camera obeys the same flag as
403
+ // calling it. Without this the preview would be a second door to the camera
404
+ // with only one lock. Non-enumerable so it cannot be mistaken for an API.
405
+ };
406
+ Object.defineProperty(gated, "__declaredCapabilities", {
407
+ value: Object.freeze({ ...caps }),
408
+ enumerable: false,
409
+ writable: false
410
+ });
411
+ return gated;
412
+ }
413
+ function setCameraPreviewProvider(Component) {
414
+ _cameraPreview = Component;
415
+ }
416
+ function getCameraPreviewProvider() {
417
+ return _cameraPreview;
418
+ }
419
+ function registerNativePlugin(id, plugin) {
420
+ _nativeRegistry.set(id, plugin);
421
+ }
422
+ function unregisterNativePlugin(id) {
423
+ _nativeRegistry.delete(id);
424
+ }
425
+ function registerBundleModules(modules) {
426
+ for (const [name, mod] of Object.entries(modules)) {
427
+ _bundleModules.set(name, mod);
428
+ }
429
+ }
430
+ function resolveBundleModule(name) {
431
+ return _bundleModules.get(name);
432
+ }
433
+ var _haptics, noopNative, _pluginNative, _cameraPreview, _nativeRegistry, nativePluginRegistry, _bundleModules;
434
+ var init_host = __esm({
435
+ "src/host.ts"() {
436
+ "use strict";
437
+ _haptics = null;
438
+ noopNative = {
439
+ camera: unavailable("camera"),
440
+ location: unavailable("location"),
441
+ storage: unavailable("storage"),
442
+ biometrics: unavailable("biometrics"),
443
+ biometricsV2: unavailable("biometricsV2"),
444
+ notifications: unavailable("notifications"),
445
+ haptics: { impact: () => {
446
+ }, notification: () => {
447
+ }, selection: () => {
448
+ }, error: () => {
449
+ } },
450
+ connectivity: { isOnline: () => true, onChange: () => () => {
451
+ } },
452
+ fileSystem: unavailable("fileSystem"),
453
+ cookies: unavailable("cookies"),
454
+ sharing: unavailable("sharing"),
455
+ scanner: unavailable("scanner"),
456
+ nfc: unavailable("nfc"),
457
+ bluetooth: unavailable("bluetooth"),
458
+ vision: unavailable("vision")
459
+ };
460
+ _pluginNative = noopNative;
461
+ _cameraPreview = null;
462
+ _nativeRegistry = /* @__PURE__ */ new Map();
463
+ nativePluginRegistry = {
464
+ has(id) {
465
+ return _nativeRegistry.has(id);
466
+ },
467
+ async get(id) {
468
+ const entry = _nativeRegistry.get(id);
469
+ if (!entry) return null;
470
+ if (typeof entry === "function") {
471
+ try {
472
+ return await entry();
473
+ } catch (err) {
474
+ console.warn(`[adas-plugins] lazy native plugin '${id}' failed to load:`, err);
475
+ return null;
476
+ }
477
+ }
478
+ return entry;
479
+ }
480
+ };
481
+ _bundleModules = /* @__PURE__ */ new Map();
482
+ }
483
+ });
484
+
485
+ // src/sdk/CameraPreview.tsx
486
+ function declaredCapabilities(native) {
487
+ return native && native.__declaredCapabilities || null;
488
+ }
489
+ function CameraPreview({ native, facing = "back", style, renderUnavailable }) {
490
+ const caps = declaredCapabilities(native);
491
+ if (caps && !caps.camera) {
492
+ return unavailable2(
493
+ "this plugin did not declare `capabilities: { camera: true }`, so it may not show the camera",
494
+ renderUnavailable,
495
+ style
496
+ );
497
+ }
498
+ const Provider = getCameraPreviewProvider();
499
+ if (!Provider) {
500
+ return unavailable2(
501
+ "this app does not provide a camera preview (the host has not called setCameraPreviewProvider)",
502
+ renderUnavailable,
503
+ style
504
+ );
505
+ }
506
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Provider, { facing, style, renderUnavailable });
507
+ }
508
+ function unavailable2(reason, render, style) {
509
+ if (render) return render(reason);
510
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [{ flex: 1, backgroundColor: "#000", alignItems: "center", justifyContent: "center", padding: 20 }, style], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.Text, { style: { color: "#39FF66", fontSize: 12, textAlign: "center" }, children: `NO CAMERA PREVIEW
511
+ ${reason}` }) });
512
+ }
513
+ var import_jsx_runtime;
514
+ var init_CameraPreview = __esm({
515
+ "src/sdk/CameraPreview.tsx"() {
516
+ "use strict";
517
+ init_primitives();
518
+ init_host();
519
+ import_jsx_runtime = require("react/jsx-runtime");
409
520
  }
410
521
  });
411
522
 
412
523
  // src/sdk/usePluginTheme.ts
413
- var import_react2 = require("react");
414
- var import_react_native3 = require("react-native");
415
- var import_core = require("@ateam-ai-mobile/core");
416
- var adaColors = {
417
- bg: import_core.ada.paper,
418
- surface: import_core.ada.paperSoft,
419
- surfaceAlt: import_core.ada.paperDeep,
420
- input: import_core.ada.paper,
421
- border: import_core.ada.border,
422
- text: import_core.ada.ink,
423
- textSecondary: import_core.ada.inkSoft,
424
- textMuted: import_core.ada.inkMuted,
425
- accent: import_core.ada.orange,
426
- accentSoft: import_core.ada.orangeSoft,
427
- success: "#5A8A5C",
428
- successSoft: "rgba(90,138,92,0.14)",
429
- warning: "#D49A4E",
430
- warningSoft: "rgba(212,154,78,0.16)",
431
- error: "#C15545",
432
- errorSoft: "rgba(193,85,69,0.14)",
433
- purple: import_core.ada.orangeDeep,
434
- purpleSoft: import_core.ada.orangeSoft
435
- };
436
- var _colors = adaColors;
437
- var _isDark = null;
438
- var _subscribers = /* @__PURE__ */ new Set();
439
524
  function setPluginThemeColors(colors, opts) {
440
525
  _colors = { ...adaColors, ...colors };
441
526
  if (opts?.isDark !== void 0) _isDark = opts.isDark;
@@ -478,9 +563,40 @@ function usePluginTheme() {
478
563
  isDark
479
564
  }), [colors, isDark]);
480
565
  }
566
+ var import_react2, import_react_native3, import_core, adaColors, _colors, _isDark, _subscribers;
567
+ var init_usePluginTheme = __esm({
568
+ "src/sdk/usePluginTheme.ts"() {
569
+ "use strict";
570
+ import_react2 = require("react");
571
+ import_react_native3 = require("react-native");
572
+ import_core = require("@ateam-ai-mobile/core");
573
+ adaColors = {
574
+ bg: import_core.ada.paper,
575
+ surface: import_core.ada.paperSoft,
576
+ surfaceAlt: import_core.ada.paperDeep,
577
+ input: import_core.ada.paper,
578
+ border: import_core.ada.border,
579
+ text: import_core.ada.ink,
580
+ textSecondary: import_core.ada.inkSoft,
581
+ textMuted: import_core.ada.inkMuted,
582
+ accent: import_core.ada.orange,
583
+ accentSoft: import_core.ada.orangeSoft,
584
+ success: "#5A8A5C",
585
+ successSoft: "rgba(90,138,92,0.14)",
586
+ warning: "#D49A4E",
587
+ warningSoft: "rgba(212,154,78,0.16)",
588
+ error: "#C15545",
589
+ errorSoft: "rgba(193,85,69,0.14)",
590
+ purple: import_core.ada.orangeDeep,
591
+ purpleSoft: import_core.ada.orangeSoft
592
+ };
593
+ _colors = adaColors;
594
+ _isDark = null;
595
+ _subscribers = /* @__PURE__ */ new Set();
596
+ }
597
+ });
481
598
 
482
599
  // src/sdk/useApi.ts
483
- var import_react3 = require("react");
484
600
  function useApi(bridge) {
485
601
  return (0, import_react3.useMemo)(() => {
486
602
  const apiFn = async (connectorId, tool, args = {}) => {
@@ -509,131 +625,635 @@ function useApi(bridge) {
509
625
  return apiFn;
510
626
  }, [bridge]);
511
627
  }
628
+ var import_react3;
629
+ var init_useApi = __esm({
630
+ "src/sdk/useApi.ts"() {
631
+ "use strict";
632
+ import_react3 = require("react");
633
+ }
634
+ });
512
635
 
513
636
  // src/sdk/index.ts
514
- var PluginSDK = { register, registerService };
515
-
516
- // src/host.ts
517
- var _haptics = null;
518
- function setHapticsProvider(haptics) {
519
- _haptics = haptics;
520
- }
521
- function getHaptics() {
522
- return _haptics;
523
- }
524
- function unavailable(api) {
525
- return new Proxy({}, {
526
- get: (_t, method) => () => Promise.reject(new Error(`[adas] native.${api}.${String(method)} unavailable \u2014 host did not call setPluginNativeProvider()`))
527
- });
528
- }
529
- var noopNative = {
530
- camera: unavailable("camera"),
531
- location: unavailable("location"),
532
- storage: unavailable("storage"),
533
- biometrics: unavailable("biometrics"),
534
- biometricsV2: unavailable("biometricsV2"),
535
- notifications: unavailable("notifications"),
536
- haptics: { impact: () => {
537
- }, notification: () => {
538
- }, selection: () => {
539
- } },
540
- connectivity: { isOnline: () => true, onChange: () => () => {
541
- } },
542
- fileSystem: unavailable("fileSystem"),
543
- cookies: unavailable("cookies"),
544
- sharing: unavailable("sharing"),
545
- scanner: unavailable("scanner"),
546
- nfc: unavailable("nfc")
547
- };
548
- var _pluginNative = noopNative;
549
- function setPluginNativeProvider(native) {
550
- _pluginNative = native;
551
- }
552
- function getPluginNative() {
553
- return _pluginNative;
554
- }
555
- function blocked(api) {
556
- return new Proxy({}, {
557
- get: (_t, method) => {
558
- if (typeof method !== "string") return void 0;
559
- return () => {
560
- console.warn(`[adas] native.${api}.${method} blocked \u2014 capability '${api}' not declared in the plugin manifest`);
561
- };
562
- }
563
- });
564
- }
565
- function gatePluginNative(native, declared) {
566
- const caps = declared || {};
637
+ var sdk_exports = {};
638
+ __export(sdk_exports, {
639
+ ActionButton: () => ActionButton,
640
+ ActivityIndicator: () => import_react_native.ActivityIndicator,
641
+ Animated: () => import_react_native.Animated,
642
+ Badge: () => Badge,
643
+ CameraPreview: () => CameraPreview,
644
+ Card: () => Card,
645
+ EmptyState: () => EmptyState,
646
+ ErrorState: () => ErrorState,
647
+ FlatList: () => import_react_native.FlatList,
648
+ Image: () => import_react_native.Image,
649
+ ListItem: () => ListItem,
650
+ LoadingState: () => LoadingState,
651
+ Modal: () => import_react_native.Modal,
652
+ PLATFORM_VOICE_CALLER: () => PLATFORM_VOICE_CALLER,
653
+ Platform: () => import_react_native.Platform,
654
+ PluginSDK: () => PluginSDK,
655
+ Pressable: () => import_react_native.Pressable,
656
+ RefreshControl: () => import_react_native.RefreshControl,
657
+ ScrollView: () => import_react_native.ScrollView,
658
+ SearchBar: () => SearchBar,
659
+ SectionList: () => import_react_native.SectionList,
660
+ StyleSheet: () => import_react_native.StyleSheet,
661
+ Switch: () => import_react_native.Switch,
662
+ TabBar: () => TabBar,
663
+ Text: () => import_react_native.Text,
664
+ TextInput: () => import_react_native.TextInput,
665
+ TouchableOpacity: () => import_react_native.TouchableOpacity,
666
+ View: () => import_react_native.View,
667
+ register: () => register,
668
+ registerService: () => registerService,
669
+ registerTools: () => registerTools,
670
+ setPluginThemeColors: () => setPluginThemeColors,
671
+ useApi: () => useApi,
672
+ usePluginTheme: () => usePluginTheme
673
+ });
674
+ var PluginSDK;
675
+ var init_sdk = __esm({
676
+ "src/sdk/index.ts"() {
677
+ "use strict";
678
+ init_registration();
679
+ init_registration();
680
+ init_voice();
681
+ init_primitives();
682
+ init_components();
683
+ init_CameraPreview();
684
+ init_usePluginTheme();
685
+ init_useApi();
686
+ PluginSDK = { register, registerService, registerTools };
687
+ }
688
+ });
689
+
690
+ // src/deviceStateHost.ts
691
+ function deniedSnapshot(domain) {
692
+ const base = { quality: UNKNOWN_QUALITY };
693
+ switch (domain) {
694
+ case "spatial":
695
+ return {
696
+ ...base,
697
+ position: null,
698
+ movement: { speedMps: null, bearingDeg: null, distanceRecentM: null, state: "unknown" },
699
+ orientation: { headingDeg: null },
700
+ recentPath: []
701
+ };
702
+ case "visual":
703
+ return {
704
+ ...base,
705
+ camera: { available: false, active: false, facing: "unknown" },
706
+ latest: null,
707
+ recentTimeline: { windowMs: 0, frames: [] }
708
+ };
709
+ case "deviceContext":
710
+ return {
711
+ ...base,
712
+ power: { batteryLevel: null, charging: null, lowPowerMode: null },
713
+ thermal: { state: "unknown" },
714
+ resources: { memoryPressure: "unknown", storagePressure: "unknown" }
715
+ };
716
+ case "connectivity":
717
+ return {
718
+ ...base,
719
+ reachability: "unknown",
720
+ transport: "unknown",
721
+ qualityClass: "unknown",
722
+ stability: "unknown",
723
+ latencyMs: null
724
+ };
725
+ case "interaction":
726
+ return {
727
+ ...base,
728
+ lifecycle: { appState: "unknown", screenOn: null },
729
+ audio: { captureActive: null, playbackActive: null, route: "unknown", interruption: "unknown" }
730
+ };
731
+ case "activity":
732
+ return {
733
+ ...base,
734
+ // `confidence` is REQUIRED on both blocks in the freeze, precisely
735
+ // because an inferred activity must never be handed over without
736
+ // saying how sure the inference is. An empty snapshot is the least
737
+ // sure of all.
738
+ human: { state: "unknown", since: null, confidence: { level: "unknown" } },
739
+ devicePose: { placement: "unknown", orientation: "unknown", confidence: { level: "unknown" } },
740
+ recentTransitions: []
741
+ };
742
+ case "environment":
743
+ return {
744
+ ...base,
745
+ lighting: { level: "unknown", lux: null },
746
+ acoustics: { level: "unknown", approximateDb: null },
747
+ // 'unknown' rather than 'uncertain': uncertain means we looked and
748
+ // could not decide, and nothing looked at all here.
749
+ enclosure: { state: "unknown" },
750
+ proximity: { distanceClass: "unknown" },
751
+ nearbySignals: { bleAvailable: false, beaconCount: null, uwbAvailable: false }
752
+ };
753
+ case "capability": {
754
+ const unknownStatus = { support: "unknown", permission: "unknown", availability: "unknown" };
755
+ return {
756
+ ...base,
757
+ camera: unknownStatus,
758
+ microphone: unknownStatus,
759
+ location: { ...unknownStatus, precision: "unknown", backgroundPermission: "unknown" },
760
+ motion: unknownStatus,
761
+ bluetooth: unknownStatus,
762
+ uwb: unknownStatus,
763
+ nfc: unknownStatus,
764
+ notifications: unknownStatus,
765
+ health: unknownStatus,
766
+ backgroundExecution: unknownStatus
767
+ };
768
+ }
769
+ default:
770
+ return base;
771
+ }
772
+ }
773
+ function deniedDomain(domain, why) {
567
774
  return {
568
- camera: caps.camera ? native.camera : blocked("camera"),
569
- location: caps.location ? native.location : blocked("location"),
570
- storage: caps.offline ? native.storage : blocked("storage"),
571
- biometrics: caps.biometrics ? native.biometrics : blocked("biometrics"),
572
- biometricsV2: caps.biometricsV2 ? native.biometricsV2 : blocked("biometricsV2"),
573
- notifications: caps.notifications ? native.notifications : blocked("notifications"),
574
- haptics: caps.haptics ? native.haptics : blocked("haptics"),
575
- connectivity: native.connectivity,
576
- fileSystem: caps.fileSystem ? native.fileSystem : blocked("fileSystem"),
577
- cookies: caps.cookies ? native.cookies : blocked("cookies"),
578
- sharing: caps.sharing ? native.sharing : blocked("sharing"),
579
- scanner: caps.scanner ? native.scanner : blocked("scanner"),
580
- nfc: caps.nfc ? native.nfc : blocked("nfc")
775
+ async getSnapshot() {
776
+ return deniedSnapshot(domain);
777
+ },
778
+ async start() {
779
+ throw denial(domain, why);
780
+ },
781
+ async stop() {
782
+ throw denial(domain, why);
783
+ },
784
+ subscribe() {
785
+ throw denial(domain, why);
786
+ }
581
787
  };
582
788
  }
583
- var _nativeRegistry = /* @__PURE__ */ new Map();
584
- function registerNativePlugin(id, plugin) {
585
- _nativeRegistry.set(id, plugin);
789
+ function denial(domain, why) {
790
+ const err = new Error(`deviceState.${domain}: ${why}`);
791
+ err.code = "RESTRICTED";
792
+ err.domain = domain;
793
+ return err;
586
794
  }
587
- function unregisterNativePlugin(id) {
588
- _nativeRegistry.delete(id);
795
+ function setDeviceStateProvider(state) {
796
+ _provider = state || {};
589
797
  }
590
- var nativePluginRegistry = {
591
- has(id) {
592
- return _nativeRegistry.has(id);
593
- },
594
- async get(id) {
595
- const entry = _nativeRegistry.get(id);
596
- if (!entry) return null;
597
- if (typeof entry === "function") {
798
+ function gateDeviceState(declared) {
799
+ const allowed = new Set(declared || []);
800
+ const out = {};
801
+ for (const domain of ALL_DOMAINS) {
802
+ if (!allowed.has(domain)) {
803
+ out[domain] = deniedDomain(
804
+ domain,
805
+ `not declared in this plugin's stateDomains. Add '${domain}' to use it.`
806
+ );
807
+ continue;
808
+ }
809
+ const engine = _provider[domain];
810
+ out[domain] = engine ?? deniedDomain(
811
+ domain,
812
+ "no engine in this build \u2014 the native side does not exist yet. See docs/DEVICE_API_STATUS.md."
813
+ );
814
+ }
815
+ return out;
816
+ }
817
+ function implementedStateDomains() {
818
+ return ALL_DOMAINS.filter((d) => !!_provider[d]);
819
+ }
820
+ var UNKNOWN_QUALITY, ALL_DOMAINS, _provider;
821
+ var init_deviceStateHost = __esm({
822
+ "src/deviceStateHost.ts"() {
823
+ "use strict";
824
+ UNKNOWN_QUALITY = {
825
+ freshness: { state: "unknown", observedAt: null, ageMs: null },
826
+ confidence: { level: "unknown" }
827
+ };
828
+ ALL_DOMAINS = [
829
+ "spatial",
830
+ "visual",
831
+ "activity",
832
+ "deviceContext",
833
+ "connectivity",
834
+ "capability",
835
+ "environment",
836
+ "interaction"
837
+ ];
838
+ _provider = {};
839
+ }
840
+ });
841
+
842
+ // src/bridge/native.ts
843
+ function buildContext(pluginId, connectorId) {
844
+ const user = (0, import_core2.getUser)();
845
+ return {
846
+ tenant: (0, import_core2.getTenant)(),
847
+ pluginId,
848
+ connectorId,
849
+ user: user ? { id: user.actorId || "", name: user.name || "", roles: user.roles || [] } : void 0
850
+ };
851
+ }
852
+ function useNativeBridge(pluginId, connectorId, commandBufferRef) {
853
+ const initCallbackRef = (0, import_react4.useRef)(null);
854
+ const commandListenersRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
855
+ const initializedRef = (0, import_react4.useRef)(false);
856
+ const fireInit = (0, import_react4.useCallback)(() => {
857
+ if (initializedRef.current || !initCallbackRef.current) return;
858
+ initCallbackRef.current(buildContext(pluginId, connectorId));
859
+ initializedRef.current = true;
860
+ }, [pluginId, connectorId]);
861
+ const onInit = (0, import_react4.useCallback)((cb) => {
862
+ initCallbackRef.current = cb;
863
+ setTimeout(fireInit, 0);
864
+ }, [fireInit]);
865
+ const mcpCall = (0, import_react4.useCallback)(async (toolOrConnector, argsOrTool, cIdOrArgs) => {
866
+ let tool;
867
+ let args;
868
+ let cId;
869
+ if (typeof argsOrTool === "string") {
870
+ cId = toolOrConnector;
871
+ tool = argsOrTool;
872
+ args = cIdOrArgs || {};
873
+ } else {
874
+ tool = toolOrConnector;
875
+ args = argsOrTool;
876
+ cId = typeof cIdOrArgs === "string" ? cIdOrArgs : void 0;
877
+ }
878
+ const targetConnector = cId || connectorId;
879
+ if (!targetConnector) {
880
+ console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2014 no connectorId. tool=${tool}`);
881
+ throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
882
+ }
883
+ try {
884
+ const data = await (0, import_core2.connectorCall)(targetConnector, tool, args);
885
+ if (!data.ok && data.error) {
886
+ throw new Error(data.error);
887
+ }
888
+ return (0, import_core2.unwrapMcpResult)(data.result);
889
+ } catch (err) {
890
+ console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2192 connector=${targetConnector} tool=${tool}:`, err?.message || err);
891
+ throw err;
892
+ }
893
+ }, [pluginId, connectorId]);
894
+ const mcpFetch = (0, import_react4.useCallback)(async (body) => {
895
+ return (0, import_core2.mcpFetch)(body);
896
+ }, []);
897
+ const sendCommand = (0, import_react4.useCallback)((type, payload) => {
898
+ if (type === "plugin.command.result" && payload?.correlationId) {
899
+ (0, import_core2.postCommandResult)(payload.correlationId, payload.result, payload.error);
900
+ return;
901
+ }
902
+ }, [pluginId]);
903
+ const onCommand = (0, import_react4.useCallback)((cb) => {
904
+ commandListenersRef.current.add(cb);
905
+ if (commandBufferRef) {
906
+ const buf = commandBufferRef.current;
907
+ const remaining = [];
908
+ for (const cmd of buf) {
909
+ if (cmd.pluginId === pluginId && cmd.command !== "__focus") {
910
+ try {
911
+ cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
912
+ } catch {
913
+ }
914
+ } else {
915
+ remaining.push(cmd);
916
+ }
917
+ }
918
+ commandBufferRef.current = remaining;
919
+ }
920
+ for (const cmd of import_core2.pluginCommandEmitter.drainPluginCommands(pluginId)) {
598
921
  try {
599
- return await entry();
922
+ cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
923
+ } catch {
924
+ }
925
+ }
926
+ return () => {
927
+ commandListenersRef.current.delete(cb);
928
+ };
929
+ }, [pluginId, commandBufferRef]);
930
+ (0, import_react4.useEffect)(() => {
931
+ const unsub = import_core2.pluginCommandEmitter.on("plugin-command", (cmd) => {
932
+ if (cmd.pluginId !== pluginId) return;
933
+ if (cmd.command === "__focus") return;
934
+ commandListenersRef.current.forEach((fn) => {
935
+ try {
936
+ fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
937
+ } catch {
938
+ }
939
+ });
940
+ });
941
+ return unsub;
942
+ }, [pluginId]);
943
+ (0, import_react4.useEffect)(() => {
944
+ const timer = setTimeout(() => {
945
+ const dispatch = (cmd) => {
946
+ commandListenersRef.current.forEach((fn) => {
947
+ try {
948
+ fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
949
+ } catch {
950
+ }
951
+ });
952
+ };
953
+ if (commandBufferRef) {
954
+ const remaining = [];
955
+ for (const cmd of commandBufferRef.current) {
956
+ if (cmd.pluginId === pluginId && cmd.command !== "__focus") dispatch(cmd);
957
+ else remaining.push(cmd);
958
+ }
959
+ commandBufferRef.current = remaining;
960
+ }
961
+ for (const cmd of import_core2.pluginCommandEmitter.drainPluginCommands(pluginId)) {
962
+ dispatch(cmd);
963
+ }
964
+ }, 500);
965
+ return () => clearTimeout(timer);
966
+ }, [pluginId, commandBufferRef]);
967
+ (0, import_react4.useEffect)(() => {
968
+ return () => {
969
+ commandListenersRef.current.clear();
970
+ initializedRef.current = false;
971
+ };
972
+ }, []);
973
+ const close = (0, import_react4.useCallback)(() => {
974
+ import_core2.pluginCommandEmitter.emit("plugin-close", { pluginId });
975
+ }, [pluginId]);
976
+ const sendMessage = (0, import_react4.useCallback)((text) => {
977
+ const t = (text || "").trim();
978
+ if (!t) return;
979
+ try {
980
+ import_core2.useChatStore.getState().sendMessage(t);
981
+ } catch (err) {
982
+ console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
983
+ }
984
+ }, []);
985
+ const prepareMessage = (0, import_react4.useCallback)((text) => {
986
+ const t = (text || "").trim();
987
+ if (!t) return;
988
+ import_core2.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
989
+ }, []);
990
+ return (0, import_react4.useMemo)(
991
+ () => ({ onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage }),
992
+ [onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage]
993
+ );
994
+ }
995
+ function createNativeBridge(pluginId, connectorId) {
996
+ const commandListeners = /* @__PURE__ */ new Set();
997
+ return {
998
+ onInit(cb) {
999
+ setTimeout(() => {
1000
+ cb(buildContext(pluginId, connectorId));
1001
+ }, 0);
1002
+ },
1003
+ async mcpCall(tool, args, cId) {
1004
+ const targetConnector = cId || connectorId;
1005
+ if (!targetConnector) {
1006
+ throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
1007
+ }
1008
+ const data = await (0, import_core2.connectorCall)(targetConnector, tool, args);
1009
+ if (!data.ok && data.error) throw new Error(data.error);
1010
+ return (0, import_core2.unwrapMcpResult)(data.result);
1011
+ },
1012
+ async mcpFetch(body) {
1013
+ return (0, import_core2.mcpFetch)(body);
1014
+ },
1015
+ sendCommand(_type, _payload) {
1016
+ },
1017
+ onCommand(cb) {
1018
+ commandListeners.add(cb);
1019
+ return () => {
1020
+ commandListeners.delete(cb);
1021
+ };
1022
+ },
1023
+ close() {
1024
+ import_core2.pluginCommandEmitter.emit("plugin-close", { pluginId });
1025
+ },
1026
+ sendMessage(text) {
1027
+ const t = (text || "").trim();
1028
+ if (!t) return;
1029
+ try {
1030
+ import_core2.useChatStore.getState().sendMessage(t);
600
1031
  } catch (err) {
601
- console.warn(`[adas-plugins] lazy native plugin '${id}' failed to load:`, err);
602
- return null;
1032
+ console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
603
1033
  }
1034
+ },
1035
+ prepareMessage(text) {
1036
+ const t = (text || "").trim();
1037
+ if (!t) return;
1038
+ import_core2.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
604
1039
  }
605
- return entry;
1040
+ };
1041
+ }
1042
+ var import_react4, import_core2;
1043
+ var init_native = __esm({
1044
+ "src/bridge/native.ts"() {
1045
+ "use strict";
1046
+ import_react4 = require("react");
1047
+ import_core2 = require("@ateam-ai-mobile/core");
606
1048
  }
607
- };
608
- var _bundleModules = /* @__PURE__ */ new Map();
609
- function registerBundleModules(modules) {
610
- for (const [name, mod] of Object.entries(modules)) {
611
- _bundleModules.set(name, mod);
1049
+ });
1050
+
1051
+ // src/tools/registry.ts
1052
+ function setDeviceToolContextProvider(fn) {
1053
+ contextProvider = fn;
1054
+ }
1055
+ function defaultContext(pluginId, capabilities, stateDomains) {
1056
+ return {
1057
+ bridge: createNativeBridge(pluginId),
1058
+ native: gatePluginNative(getPluginNative(), capabilities, pluginId),
1059
+ deviceState: gateDeviceState(stateDomains)
1060
+ };
1061
+ }
1062
+ function registerDeviceTools(pluginId, tools, capabilities, stateDomains) {
1063
+ const accepted = [];
1064
+ for (const tool of tools || []) {
1065
+ if (!tool || typeof tool.name !== "string" || typeof tool.run !== "function") {
1066
+ console.warn(`[deviceTools] ${pluginId}: skipping malformed tool entry`);
1067
+ continue;
1068
+ }
1069
+ const existing = registry.get(tool.name);
1070
+ if (existing && existing.pluginId !== pluginId) {
1071
+ console.warn(
1072
+ `[deviceTools] ${pluginId}: "${tool.name}" already registered by ${existing.pluginId} \u2014 refusing to override`
1073
+ );
1074
+ continue;
1075
+ }
1076
+ registry.set(tool.name, { pluginId, tool, capabilities, stateDomains });
1077
+ accepted.push(tool.name);
612
1078
  }
1079
+ if (accepted.length > 0) {
1080
+ console.log(`[deviceTools] ${pluginId} registered: ${accepted.join(", ")}`);
1081
+ }
1082
+ return accepted;
613
1083
  }
614
- function resolveBundleModule(name) {
615
- return _bundleModules.get(name);
1084
+ function unregisterDeviceTools(pluginId) {
1085
+ for (const [name, entry] of registry.entries()) {
1086
+ if (entry.pluginId === pluginId) registry.delete(name);
1087
+ }
616
1088
  }
1089
+ function hasDeviceTool(name) {
1090
+ return registry.has(name);
1091
+ }
1092
+ function listDeviceTools() {
1093
+ return Array.from(registry.entries()).map(([name, { pluginId, tool }]) => ({
1094
+ name,
1095
+ pluginId,
1096
+ description: tool.description,
1097
+ input_schema: tool.input_schema
1098
+ }));
1099
+ }
1100
+ async function callDeviceTool(name, args = {}, opts = {}) {
1101
+ const entry = registry.get(name);
1102
+ if (!entry) {
1103
+ return { ok: false, reason: "not_found", error: `No device tool registered as "${name}"` };
1104
+ }
1105
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1106
+ let timer;
1107
+ try {
1108
+ const build = contextProvider || defaultContext;
1109
+ const ctx = {
1110
+ ...build(entry.pluginId, entry.capabilities, entry.stateDomains),
1111
+ signal: opts.signal
1112
+ };
1113
+ const result = await Promise.race([
1114
+ Promise.resolve(entry.tool.run(args, ctx)),
1115
+ new Promise((_, reject) => {
1116
+ timer = setTimeout(
1117
+ () => reject(new Error(`Device tool "${name}" timed out after ${timeoutMs}ms`)),
1118
+ timeoutMs
1119
+ );
1120
+ })
1121
+ ]);
1122
+ return { ok: true, result };
1123
+ } catch (err) {
1124
+ const message = err?.message || String(err);
1125
+ const timedOut = message.includes("timed out after");
1126
+ return { ok: false, reason: timedOut ? "timeout" : "failed", error: message };
1127
+ } finally {
1128
+ if (timer) clearTimeout(timer);
1129
+ }
1130
+ }
1131
+ function clearDeviceTools() {
1132
+ registry.clear();
1133
+ }
1134
+ var registry, contextProvider, DEFAULT_TIMEOUT_MS;
1135
+ var init_registry = __esm({
1136
+ "src/tools/registry.ts"() {
1137
+ "use strict";
1138
+ init_native();
1139
+ init_host();
1140
+ init_deviceStateHost();
1141
+ registry = /* @__PURE__ */ new Map();
1142
+ contextProvider = null;
1143
+ DEFAULT_TIMEOUT_MS = 3e4;
1144
+ }
1145
+ });
617
1146
 
618
- // src/bundles/bundleManager.ts
619
- var import_react8 = __toESM(require("react"));
620
- var import_react9 = require("react");
621
- var ReactNative = __toESM(require("react-native"));
622
- var import_core6 = require("@ateam-ai-mobile/core");
623
-
624
- // src/surfaces/SurfaceDrawer.tsx
625
- var import_react5 = require("react");
626
- var import_react_native5 = require("react-native");
627
- var import_react_native_svg2 = __toESM(require("react-native-svg"));
628
- var import_core2 = require("@ateam-ai-mobile/core");
1147
+ // src/api/plugins.ts
1148
+ function supportsNative(manifest) {
1149
+ const mode = manifest.render?.mode;
1150
+ return mode === "react-native" || mode === "adaptive";
1151
+ }
1152
+ function getNativeComponentId(manifest) {
1153
+ if (manifest.render?.mode === "react-native") return manifest.render.component;
1154
+ if (manifest.render?.mode === "adaptive") return manifest.render.reactNative?.component;
1155
+ return null;
1156
+ }
1157
+ function getNativeBundleUrl(manifest) {
1158
+ const direct = manifest.bundleUrl;
1159
+ if (direct) return direct;
1160
+ const render = manifest.render;
1161
+ if (render.mode === "react-native") return render.bundleUrl || null;
1162
+ if (render.mode === "adaptive") return render.reactNative?.bundleUrl || null;
1163
+ return null;
1164
+ }
1165
+ function getNativeBundleHash(manifest) {
1166
+ const direct = manifest.bundleHash;
1167
+ if (direct) return direct;
1168
+ const render = manifest.render;
1169
+ if (render.mode === "react-native") return render.bundleHash || null;
1170
+ if (render.mode === "adaptive") return render.reactNative?.bundleHash || null;
1171
+ return null;
1172
+ }
1173
+ function supportsWebView(manifest) {
1174
+ return manifest.render?.mode === "iframe" || manifest.render?.mode === "adaptive";
1175
+ }
1176
+ async function listPlugins() {
1177
+ const res = await (0, import_core3.apiFetch)("/mcp", {
1178
+ method: "POST",
1179
+ body: JSON.stringify({
1180
+ jsonrpc: "2.0",
1181
+ id: Date.now(),
1182
+ method: "tools/call",
1183
+ params: {
1184
+ name: "cp.listContextPlugins",
1185
+ arguments: {}
1186
+ }
1187
+ })
1188
+ });
1189
+ const structured = res.result?.structuredContent?.plugins;
1190
+ if (structured) return structured;
1191
+ const text = res.result?.content?.[0]?.text;
1192
+ if (!text) return [];
1193
+ try {
1194
+ const parsed = JSON.parse(text);
1195
+ return parsed.plugins || parsed || [];
1196
+ } catch {
1197
+ return [];
1198
+ }
1199
+ }
1200
+ async function getPlugin(pluginId) {
1201
+ const res = await (0, import_core3.apiFetch)("/mcp", {
1202
+ method: "POST",
1203
+ body: JSON.stringify({
1204
+ jsonrpc: "2.0",
1205
+ id: Date.now(),
1206
+ method: "tools/call",
1207
+ params: {
1208
+ name: "cp.getContextPlugin",
1209
+ arguments: { id: pluginId }
1210
+ // Backend expects { id }, NOT { pluginId }
1211
+ }
1212
+ })
1213
+ });
1214
+ if (res.result?.structuredContent) return res.result.structuredContent;
1215
+ const text = res.result?.content?.[0]?.text;
1216
+ if (!text) return null;
1217
+ try {
1218
+ const parsed = JSON.parse(text);
1219
+ if (parsed.error) {
1220
+ console.warn("[getPlugin] Backend error:", parsed.error);
1221
+ return null;
1222
+ }
1223
+ return parsed;
1224
+ } catch {
1225
+ return null;
1226
+ }
1227
+ }
1228
+ function resolvePluginUrl(manifest, tenant) {
1229
+ const render = manifest.render;
1230
+ let url;
1231
+ if (render?.mode === "iframe") {
1232
+ url = render.iframeUrl;
1233
+ } else if (render?.mode === "adaptive") {
1234
+ url = render.iframeUrl || render.iframe?.iframeUrl;
1235
+ }
1236
+ if (!url) return "";
1237
+ if (url.startsWith("http")) {
1238
+ const sep = url.includes("?") ? "&" : "?";
1239
+ return tenant ? `${url}${sep}tenant=${encodeURIComponent(tenant)}` : url;
1240
+ }
1241
+ if (tenant && url.startsWith("/mcp-ui/")) {
1242
+ const tenantUrl = url.replace("/mcp-ui/", `/mcp-ui/${encodeURIComponent(tenant)}/`);
1243
+ return `${(0, import_core4.getCoreApiUrl)()}${tenantUrl}`;
1244
+ }
1245
+ return `${(0, import_core4.getCoreApiUrl)()}${url}`;
1246
+ }
1247
+ var import_core3, import_core4;
1248
+ var init_plugins = __esm({
1249
+ "src/api/plugins.ts"() {
1250
+ "use strict";
1251
+ import_core3 = require("@ateam-ai-mobile/core");
1252
+ import_core4 = require("@ateam-ai-mobile/core");
1253
+ }
1254
+ });
629
1255
 
630
1256
  // src/surfaces/AdaOrb.tsx
631
- var import_react4 = require("react");
632
- var import_react_native4 = require("react-native");
633
- var import_react_native_reanimated = __toESM(require("react-native-reanimated"));
634
- var import_react_native_svg = __toESM(require("react-native-svg"));
635
- var import_jsx_runtime = require("react/jsx-runtime");
636
- var AnimatedCircle = import_react_native_reanimated.default.createAnimatedComponent(import_react_native_svg.Circle);
637
1257
  function AdaOrb({ size = 120, state = "idle", inscription = true }) {
638
1258
  const showInscription = inscription && size >= 60;
639
1259
  const center = size / 2;
@@ -649,7 +1269,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
649
1269
  const orbit2 = (0, import_react_native_reanimated.useSharedValue)(0);
650
1270
  const orbit3 = (0, import_react_native_reanimated.useSharedValue)(0);
651
1271
  const beat = (0, import_react_native_reanimated.useSharedValue)(1);
652
- (0, import_react4.useEffect)(() => {
1272
+ (0, import_react5.useEffect)(() => {
653
1273
  (0, import_react_native_reanimated.cancelAnimation)(coreScale);
654
1274
  (0, import_react_native_reanimated.cancelAnimation)(innerScale);
655
1275
  (0, import_react_native_reanimated.cancelAnimation)(outerScale);
@@ -799,29 +1419,29 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
799
1419
  opacity: state === "thinking" ? 0.7 : 0
800
1420
  };
801
1421
  });
802
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.View, { style: [styles2.wrap, { width: size, height: size }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.default, { width: size, height: size, children: [
803
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.Defs, { children: [
804
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.RadialGradient, { id: "adaCore", cx: "36%", cy: "30%", r: "70%", children: [
805
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FFE0B0", stopOpacity: "1" }),
806
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "28%", stopColor: "#FFA85A", stopOpacity: "1" }),
807
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "65%", stopColor: "#FF7A28", stopOpacity: "1" }),
808
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#B8410A", stopOpacity: "1" })
1422
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.View, { style: [styles2.wrap, { width: size, height: size }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.default, { width: size, height: size, children: [
1423
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.Defs, { children: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.RadialGradient, { id: "adaCore", cx: "36%", cy: "30%", r: "70%", children: [
1425
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FFE0B0", stopOpacity: "1" }),
1426
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "28%", stopColor: "#FFA85A", stopOpacity: "1" }),
1427
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "65%", stopColor: "#FF7A28", stopOpacity: "1" }),
1428
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#B8410A", stopOpacity: "1" })
809
1429
  ] }),
810
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.RadialGradient, { id: "adaInner", cx: "50%", cy: "50%", r: "50%", children: [
811
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FF8A3C", stopOpacity: "0.55" }),
812
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FF7028", stopOpacity: "0" })
1430
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.RadialGradient, { id: "adaInner", cx: "50%", cy: "50%", r: "50%", children: [
1431
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FF8A3C", stopOpacity: "0.55" }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FF7028", stopOpacity: "0" })
813
1433
  ] }),
814
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.RadialGradient, { id: "adaOuter", cx: "50%", cy: "50%", r: "50%", children: [
815
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FF7A28", stopOpacity: "0.4" }),
816
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FF6018", stopOpacity: "0" })
1434
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.RadialGradient, { id: "adaOuter", cx: "50%", cy: "50%", r: "50%", children: [
1435
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FF7A28", stopOpacity: "0.4" }),
1436
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FF6018", stopOpacity: "0" })
817
1437
  ] }),
818
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.RadialGradient, { id: "adaSpark", cx: "50%", cy: "50%", r: "50%", children: [
819
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FFFAEB", stopOpacity: "1" }),
820
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "60%", stopColor: "#FFD095", stopOpacity: "0.9" }),
821
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FFA85A", stopOpacity: "0" })
1438
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.RadialGradient, { id: "adaSpark", cx: "50%", cy: "50%", r: "50%", children: [
1439
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FFFAEB", stopOpacity: "1" }),
1440
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "60%", stopColor: "#FFD095", stopOpacity: "0.9" }),
1441
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FFA85A", stopOpacity: "0" })
822
1442
  ] })
823
1443
  ] }),
824
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1444
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
825
1445
  AnimatedCircle,
826
1446
  {
827
1447
  cx: center,
@@ -832,7 +1452,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
832
1452
  animatedProps: ripple1Props
833
1453
  }
834
1454
  ),
835
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1455
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
836
1456
  AnimatedCircle,
837
1457
  {
838
1458
  cx: center,
@@ -843,7 +1463,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
843
1463
  animatedProps: ripple2Props
844
1464
  }
845
1465
  ),
846
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1466
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
847
1467
  AnimatedCircle,
848
1468
  {
849
1469
  cx: center,
@@ -854,10 +1474,10 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
854
1474
  animatedProps: ripple3Props
855
1475
  }
856
1476
  ),
857
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaOuter)", animatedProps: outerProps }),
858
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaInner)", animatedProps: innerProps }),
859
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaCore)", animatedProps: coreProps }),
860
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1477
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaOuter)", animatedProps: outerProps }),
1478
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaInner)", animatedProps: innerProps }),
1479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaCore)", animatedProps: coreProps }),
1480
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
861
1481
  AnimatedCircle,
862
1482
  {
863
1483
  r: Math.max(3, size * 0.05),
@@ -865,7 +1485,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
865
1485
  animatedProps: spark1Props
866
1486
  }
867
1487
  ),
868
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1488
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
869
1489
  AnimatedCircle,
870
1490
  {
871
1491
  r: Math.max(2.5, size * 0.04),
@@ -873,7 +1493,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
873
1493
  animatedProps: spark2Props
874
1494
  }
875
1495
  ),
876
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1496
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
877
1497
  AnimatedCircle,
878
1498
  {
879
1499
  r: Math.max(2, size * 0.032),
@@ -881,7 +1501,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
881
1501
  animatedProps: spark3Props
882
1502
  }
883
1503
  ),
884
- showInscription && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1504
+ showInscription && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
885
1505
  import_react_native_svg.Text,
886
1506
  {
887
1507
  x: center,
@@ -896,17 +1516,26 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
896
1516
  )
897
1517
  ] }) });
898
1518
  }
899
- var styles2 = import_react_native4.StyleSheet.create({
900
- wrap: {
901
- alignItems: "center",
902
- justifyContent: "center"
1519
+ var import_react5, import_react_native4, import_react_native_reanimated, import_react_native_svg, import_jsx_runtime2, AnimatedCircle, styles2;
1520
+ var init_AdaOrb = __esm({
1521
+ "src/surfaces/AdaOrb.tsx"() {
1522
+ "use strict";
1523
+ import_react5 = require("react");
1524
+ import_react_native4 = require("react-native");
1525
+ import_react_native_reanimated = __toESM(require("react-native-reanimated"));
1526
+ import_react_native_svg = __toESM(require("react-native-svg"));
1527
+ import_jsx_runtime2 = require("react/jsx-runtime");
1528
+ AnimatedCircle = import_react_native_reanimated.default.createAnimatedComponent(import_react_native_svg.Circle);
1529
+ styles2 = import_react_native4.StyleSheet.create({
1530
+ wrap: {
1531
+ alignItems: "center",
1532
+ justifyContent: "center"
1533
+ }
1534
+ });
903
1535
  }
904
1536
  });
905
1537
 
906
1538
  // src/surfaces/SurfaceDrawer.tsx
907
- var import_jsx_runtime2 = require("react/jsx-runtime");
908
- var SCREEN_H = import_react_native5.Dimensions.get("window").height;
909
- var fontFam = import_react_native5.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
910
1539
  function SurfaceDrawer({
911
1540
  visible,
912
1541
  onClose,
@@ -918,9 +1547,9 @@ function SurfaceDrawer({
918
1547
  children
919
1548
  }) {
920
1549
  const sheetH = Math.round(SCREEN_H * heightRatio);
921
- const fadeAnim = (0, import_react5.useRef)(new import_react_native5.Animated.Value(0)).current;
922
- const slideAnim = (0, import_react5.useRef)(new import_react_native5.Animated.Value(sheetH)).current;
923
- (0, import_react5.useEffect)(() => {
1550
+ const fadeAnim = (0, import_react6.useRef)(new import_react_native5.Animated.Value(0)).current;
1551
+ const slideAnim = (0, import_react6.useRef)(new import_react_native5.Animated.Value(sheetH)).current;
1552
+ (0, import_react6.useEffect)(() => {
924
1553
  if (visible) {
925
1554
  import_react_native5.Animated.parallel([
926
1555
  import_react_native5.Animated.timing(fadeAnim, { toValue: 1, duration: 220, useNativeDriver: true }),
@@ -938,9 +1567,9 @@ function SurfaceDrawer({
938
1567
  ]).start(() => onClose());
939
1568
  };
940
1569
  if (!visible) return null;
941
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native5.Animated.View, { style: [styles3.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
942
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Pressable, { style: import_react_native5.StyleSheet.absoluteFill, onPress: close }),
943
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1570
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.Animated.View, { style: [styles3.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
1571
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Pressable, { style: import_react_native5.StyleSheet.absoluteFill, onPress: close }),
1572
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
944
1573
  import_react_native5.Animated.View,
945
1574
  {
946
1575
  style: [
@@ -948,21 +1577,21 @@ function SurfaceDrawer({
948
1577
  { height: sheetH, transform: [{ translateY: slideAnim }] }
949
1578
  ],
950
1579
  children: [
951
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles3.handle }),
952
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native5.View, { style: styles3.header, children: [
953
- showOrb ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles3.headerOrb, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AdaOrb, { size: 36, state: "idle" }) }) : null,
954
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native5.View, { style: { flex: 1 }, children: [
955
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles3.title, children: title }),
956
- subtitle ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles3.subtitle, children: subtitle }) : null,
957
- privacy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native5.View, { style: styles3.privacyPill, children: [
958
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg2.default, { width: 11, height: 11, viewBox: "0 0 24 24", fill: "none", stroke: import_core2.ada.inkSoft, strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: [
959
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg2.Path, { d: "M19 11H5a2 2 0 00-2 2v7a2 2 0 002 2h14a2 2 0 002-2v-7a2 2 0 00-2-2z" }),
960
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg2.Path, { d: "M7 11V7a5 5 0 0110 0v4" })
1580
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.View, { style: styles3.handle }),
1581
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: styles3.header, children: [
1582
+ showOrb ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.View, { style: styles3.headerOrb, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AdaOrb, { size: 36, state: "idle" }) }) : null,
1583
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: { flex: 1 }, children: [
1584
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles3.title, children: title }),
1585
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles3.subtitle, children: subtitle }) : null,
1586
+ privacy ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: styles3.privacyPill, children: [
1587
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native_svg2.default, { width: 11, height: 11, viewBox: "0 0 24 24", fill: "none", stroke: import_core5.ada.inkSoft, strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: [
1588
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_svg2.Path, { d: "M19 11H5a2 2 0 00-2 2v7a2 2 0 002 2h14a2 2 0 002-2v-7a2 2 0 00-2-2z" }),
1589
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_svg2.Path, { d: "M7 11V7a5 5 0 0110 0v4" })
961
1590
  ] }),
962
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles3.privacyText, children: "Only you can see this" })
1591
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles3.privacyText, children: "Only you can see this" })
963
1592
  ] }) : null
964
1593
  ] }),
965
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1594
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
966
1595
  import_react_native5.Pressable,
967
1596
  {
968
1597
  accessibilityRole: "button",
@@ -970,105 +1599,115 @@ function SurfaceDrawer({
970
1599
  style: styles3.closeBtn,
971
1600
  onPress: close,
972
1601
  hitSlop: 12,
973
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg2.default, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: import_core2.ada.inkSoft, strokeWidth: 2.2, strokeLinecap: "round", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg2.Path, { d: "M18 6L6 18M6 6l12 12" }) })
1602
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_svg2.default, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: import_core5.ada.inkSoft, strokeWidth: 2.2, strokeLinecap: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_svg2.Path, { d: "M18 6L6 18M6 6l12 12" }) })
974
1603
  }
975
1604
  )
976
1605
  ] }),
977
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles3.body, children })
1606
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.View, { style: styles3.body, children })
978
1607
  ]
979
1608
  }
980
1609
  )
981
1610
  ] });
982
1611
  }
983
- var styles3 = import_react_native5.StyleSheet.create({
984
- overlay: {
985
- position: "absolute",
986
- top: 0,
987
- left: 0,
988
- right: 0,
989
- bottom: 0,
990
- backgroundColor: "rgba(26, 20, 16, 0.35)",
991
- justifyContent: "flex-end",
992
- zIndex: 999,
993
- elevation: 999
994
- },
995
- sheet: {
996
- backgroundColor: import_core2.ada.paper,
997
- borderTopLeftRadius: 28,
998
- borderTopRightRadius: 28,
999
- overflow: "hidden"
1000
- },
1001
- handle: {
1002
- width: 40,
1003
- height: 4,
1004
- borderRadius: 2,
1005
- backgroundColor: import_core2.ada.inkMuted,
1006
- opacity: 0.3,
1007
- alignSelf: "center",
1008
- marginTop: 10,
1009
- marginBottom: 6
1010
- },
1011
- header: {
1012
- flexDirection: "row",
1013
- alignItems: "center",
1014
- paddingHorizontal: 22,
1015
- paddingVertical: 16,
1016
- gap: 12,
1017
- borderBottomWidth: 1,
1018
- borderBottomColor: import_core2.ada.border
1019
- },
1020
- headerOrb: {
1021
- width: 36,
1022
- height: 36,
1023
- alignItems: "center",
1024
- justifyContent: "center"
1025
- },
1026
- title: {
1027
- fontSize: 17,
1028
- fontWeight: "600",
1029
- color: import_core2.ada.ink,
1030
- letterSpacing: -0.2,
1031
- fontFamily: fontFam
1032
- },
1033
- subtitle: {
1034
- fontSize: 13,
1035
- fontWeight: "400",
1036
- color: import_core2.ada.inkSoft,
1037
- marginTop: 2,
1038
- letterSpacing: -0.1,
1039
- fontFamily: fontFam
1040
- },
1041
- privacyPill: {
1042
- flexDirection: "row",
1043
- alignItems: "center",
1044
- gap: 5,
1045
- marginTop: 4
1046
- },
1047
- privacyText: {
1048
- fontSize: 11,
1049
- fontWeight: "500",
1050
- color: import_core2.ada.inkSoft,
1051
- letterSpacing: 0.2,
1052
- fontFamily: fontFam
1053
- },
1054
- closeBtn: {
1055
- width: 32,
1056
- height: 32,
1057
- borderRadius: 16,
1058
- backgroundColor: import_core2.ada.paperSoft,
1059
- justifyContent: "center",
1060
- alignItems: "center"
1061
- },
1062
- body: {
1063
- flex: 1
1612
+ var import_react6, import_react_native5, import_react_native_svg2, import_core5, import_jsx_runtime3, SCREEN_H, fontFam, styles3;
1613
+ var init_SurfaceDrawer = __esm({
1614
+ "src/surfaces/SurfaceDrawer.tsx"() {
1615
+ "use strict";
1616
+ import_react6 = require("react");
1617
+ import_react_native5 = require("react-native");
1618
+ import_react_native_svg2 = __toESM(require("react-native-svg"));
1619
+ import_core5 = require("@ateam-ai-mobile/core");
1620
+ init_AdaOrb();
1621
+ import_jsx_runtime3 = require("react/jsx-runtime");
1622
+ SCREEN_H = import_react_native5.Dimensions.get("window").height;
1623
+ fontFam = import_react_native5.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1624
+ styles3 = import_react_native5.StyleSheet.create({
1625
+ overlay: {
1626
+ position: "absolute",
1627
+ top: 0,
1628
+ left: 0,
1629
+ right: 0,
1630
+ bottom: 0,
1631
+ backgroundColor: "rgba(26, 20, 16, 0.35)",
1632
+ justifyContent: "flex-end",
1633
+ zIndex: 999,
1634
+ elevation: 999
1635
+ },
1636
+ sheet: {
1637
+ backgroundColor: import_core5.ada.paper,
1638
+ borderTopLeftRadius: 28,
1639
+ borderTopRightRadius: 28,
1640
+ overflow: "hidden"
1641
+ },
1642
+ handle: {
1643
+ width: 40,
1644
+ height: 4,
1645
+ borderRadius: 2,
1646
+ backgroundColor: import_core5.ada.inkMuted,
1647
+ opacity: 0.3,
1648
+ alignSelf: "center",
1649
+ marginTop: 10,
1650
+ marginBottom: 6
1651
+ },
1652
+ header: {
1653
+ flexDirection: "row",
1654
+ alignItems: "center",
1655
+ paddingHorizontal: 22,
1656
+ paddingVertical: 16,
1657
+ gap: 12,
1658
+ borderBottomWidth: 1,
1659
+ borderBottomColor: import_core5.ada.border
1660
+ },
1661
+ headerOrb: {
1662
+ width: 36,
1663
+ height: 36,
1664
+ alignItems: "center",
1665
+ justifyContent: "center"
1666
+ },
1667
+ title: {
1668
+ fontSize: 17,
1669
+ fontWeight: "600",
1670
+ color: import_core5.ada.ink,
1671
+ letterSpacing: -0.2,
1672
+ fontFamily: fontFam
1673
+ },
1674
+ subtitle: {
1675
+ fontSize: 13,
1676
+ fontWeight: "400",
1677
+ color: import_core5.ada.inkSoft,
1678
+ marginTop: 2,
1679
+ letterSpacing: -0.1,
1680
+ fontFamily: fontFam
1681
+ },
1682
+ privacyPill: {
1683
+ flexDirection: "row",
1684
+ alignItems: "center",
1685
+ gap: 5,
1686
+ marginTop: 4
1687
+ },
1688
+ privacyText: {
1689
+ fontSize: 11,
1690
+ fontWeight: "500",
1691
+ color: import_core5.ada.inkSoft,
1692
+ letterSpacing: 0.2,
1693
+ fontFamily: fontFam
1694
+ },
1695
+ closeBtn: {
1696
+ width: 32,
1697
+ height: 32,
1698
+ borderRadius: 16,
1699
+ backgroundColor: import_core5.ada.paperSoft,
1700
+ justifyContent: "center",
1701
+ alignItems: "center"
1702
+ },
1703
+ body: {
1704
+ flex: 1
1705
+ }
1706
+ });
1064
1707
  }
1065
1708
  });
1066
1709
 
1067
1710
  // src/surfaces/SurfaceCard.tsx
1068
- var import_react_native6 = require("react-native");
1069
- var import_core3 = require("@ateam-ai-mobile/core");
1070
- var import_jsx_runtime3 = require("react/jsx-runtime");
1071
- var fontFam2 = import_react_native6.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1072
1711
  function SurfaceCard({
1073
1712
  icon,
1074
1713
  title,
@@ -1089,7 +1728,7 @@ function SurfaceCard({
1089
1728
  getHaptics()?.impact("medium");
1090
1729
  onLongPress();
1091
1730
  };
1092
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1731
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1093
1732
  import_react_native6.Pressable,
1094
1733
  {
1095
1734
  accessibilityRole: "button",
@@ -1100,71 +1739,76 @@ function SurfaceCard({
1100
1739
  onLongPress: onLongPress ? handleLongPress : void 0,
1101
1740
  disabled: !onPress && !onLongPress,
1102
1741
  children: [
1103
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native6.View, { style: styles4.row, children: [
1104
- icon ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.Text, { style: styles4.icon, children: icon }) : null,
1105
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native6.View, { style: { flex: 1 }, children: [
1106
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.Text, { style: styles4.title, children: title }),
1107
- subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.Text, { style: styles4.subtitle, children: subtitle }) : null
1742
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native6.View, { style: styles4.row, children: [
1743
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles4.icon, children: icon }) : null,
1744
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native6.View, { style: { flex: 1 }, children: [
1745
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles4.title, children: title }),
1746
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles4.subtitle, children: subtitle }) : null
1108
1747
  ] }),
1109
- arrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.Text, { style: styles4.arrow, children: "\u2192" }) : null
1748
+ arrow ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles4.arrow, children: "\u2192" }) : null
1110
1749
  ] }),
1111
- children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: styles4.body, children }) : null
1750
+ children ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.View, { style: styles4.body, children }) : null
1112
1751
  ]
1113
1752
  }
1114
1753
  );
1115
1754
  }
1116
- var styles4 = import_react_native6.StyleSheet.create({
1117
- card: {
1118
- backgroundColor: import_core3.ada.paperSoft,
1119
- borderWidth: 1,
1120
- borderColor: import_core3.ada.border,
1121
- borderRadius: import_core3.borderRadius.xl,
1122
- paddingHorizontal: 16,
1123
- paddingVertical: 14
1124
- },
1125
- row: {
1126
- flexDirection: "row",
1127
- alignItems: "center",
1128
- gap: 14
1129
- },
1130
- icon: {
1131
- fontSize: 22
1132
- },
1133
- title: {
1134
- fontSize: 15,
1135
- fontWeight: "600",
1136
- color: import_core3.ada.ink,
1137
- letterSpacing: -0.2,
1138
- fontFamily: fontFam2
1139
- },
1140
- subtitle: {
1141
- fontSize: 13,
1142
- fontWeight: "400",
1143
- color: import_core3.ada.inkMuted,
1144
- marginTop: 2,
1145
- letterSpacing: -0.1,
1146
- fontFamily: fontFam2
1147
- },
1148
- arrow: {
1149
- fontSize: 18,
1150
- color: import_core3.ada.inkMuted,
1151
- fontWeight: "400"
1152
- },
1153
- body: {
1154
- marginTop: 10,
1155
- paddingTop: 10,
1156
- borderTopWidth: 1,
1157
- borderTopColor: import_core3.ada.border
1755
+ var import_react_native6, import_core6, import_jsx_runtime4, fontFam2, styles4;
1756
+ var init_SurfaceCard = __esm({
1757
+ "src/surfaces/SurfaceCard.tsx"() {
1758
+ "use strict";
1759
+ import_react_native6 = require("react-native");
1760
+ init_host();
1761
+ import_core6 = require("@ateam-ai-mobile/core");
1762
+ import_jsx_runtime4 = require("react/jsx-runtime");
1763
+ fontFam2 = import_react_native6.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1764
+ styles4 = import_react_native6.StyleSheet.create({
1765
+ card: {
1766
+ backgroundColor: import_core6.ada.paperSoft,
1767
+ borderWidth: 1,
1768
+ borderColor: import_core6.ada.border,
1769
+ borderRadius: import_core6.borderRadius.xl,
1770
+ paddingHorizontal: 16,
1771
+ paddingVertical: 14
1772
+ },
1773
+ row: {
1774
+ flexDirection: "row",
1775
+ alignItems: "center",
1776
+ gap: 14
1777
+ },
1778
+ icon: {
1779
+ fontSize: 22
1780
+ },
1781
+ title: {
1782
+ fontSize: 15,
1783
+ fontWeight: "600",
1784
+ color: import_core6.ada.ink,
1785
+ letterSpacing: -0.2,
1786
+ fontFamily: fontFam2
1787
+ },
1788
+ subtitle: {
1789
+ fontSize: 13,
1790
+ fontWeight: "400",
1791
+ color: import_core6.ada.inkMuted,
1792
+ marginTop: 2,
1793
+ letterSpacing: -0.1,
1794
+ fontFamily: fontFam2
1795
+ },
1796
+ arrow: {
1797
+ fontSize: 18,
1798
+ color: import_core6.ada.inkMuted,
1799
+ fontWeight: "400"
1800
+ },
1801
+ body: {
1802
+ marginTop: 10,
1803
+ paddingTop: 10,
1804
+ borderTopWidth: 1,
1805
+ borderTopColor: import_core6.ada.border
1806
+ }
1807
+ });
1158
1808
  }
1159
1809
  });
1160
1810
 
1161
1811
  // src/surfaces/SurfaceFullscreen.tsx
1162
- var import_react6 = require("react");
1163
- var import_react_native7 = require("react-native");
1164
- var import_react_native_svg3 = __toESM(require("react-native-svg"));
1165
- var import_core4 = require("@ateam-ai-mobile/core");
1166
- var import_jsx_runtime4 = require("react/jsx-runtime");
1167
- var fontFam3 = import_react_native7.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1168
1812
  function SurfaceFullscreen({
1169
1813
  visible,
1170
1814
  onClose,
@@ -1175,9 +1819,9 @@ function SurfaceFullscreen({
1175
1819
  closeIcon = "x",
1176
1820
  closeLabel = "Close"
1177
1821
  }) {
1178
- const fadeAnim = (0, import_react6.useRef)(new import_react_native7.Animated.Value(0)).current;
1179
- const heroScale = (0, import_react6.useRef)(new import_react_native7.Animated.Value(0.6)).current;
1180
- (0, import_react6.useEffect)(() => {
1822
+ const fadeAnim = (0, import_react7.useRef)(new import_react_native7.Animated.Value(0)).current;
1823
+ const heroScale = (0, import_react7.useRef)(new import_react_native7.Animated.Value(0.6)).current;
1824
+ (0, import_react7.useEffect)(() => {
1181
1825
  if (visible) {
1182
1826
  import_react_native7.Animated.parallel([
1183
1827
  import_react_native7.Animated.timing(fadeAnim, { toValue: 1, duration: 280, useNativeDriver: true }),
@@ -1197,22 +1841,22 @@ function SurfaceFullscreen({
1197
1841
  if (!visible) return null;
1198
1842
  const renderCloseIcon = () => {
1199
1843
  if (closeIcon === "hangup") {
1200
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_svg3.default, { width: 22, height: 22, viewBox: "0 0 24 24", fill: import_core4.ada.paper, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_svg3.Path, { d: "M3.68 16.07l3.92-3.11c.28-.23.68-.28 1.01-.14l2.77 1.15c.36.15.61.5.61.89v2.82c4.34-.76 7.93-4.35 8.69-8.69h-2.82c-.39 0-.74-.25-.89-.61L16.12 5.6c-.13-.33-.09-.73.14-1.01l3.11-3.92c.29-.37.81-.41 1.15-.08l3.24 3.24c.2.2.31.47.31.76C24 14.44 14.44 24 4.59 24c-.29 0-.56-.11-.76-.31L.59 20.45c-.33-.34-.29-.86.09-1.15z" }) });
1844
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_svg3.default, { width: 22, height: 22, viewBox: "0 0 24 24", fill: import_core7.ada.paper, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_svg3.Path, { d: "M3.68 16.07l3.92-3.11c.28-.23.68-.28 1.01-.14l2.77 1.15c.36.15.61.5.61.89v2.82c4.34-.76 7.93-4.35 8.69-8.69h-2.82c-.39 0-.74-.25-.89-.61L16.12 5.6c-.13-.33-.09-.73.14-1.01l3.11-3.92c.29-.37.81-.41 1.15-.08l3.24 3.24c.2.2.31.47.31.76C24 14.44 14.44 24 4.59 24c-.29 0-.56-.11-.76-.31L.59 20.45c-.33-.34-.29-.86.09-1.15z" }) });
1201
1845
  }
1202
1846
  if (closeIcon === "x") {
1203
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_svg3.default, { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: import_core4.ada.paper, strokeWidth: 2.5, strokeLinecap: "round", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_svg3.Path, { d: "M18 6L6 18M6 6l12 12" }) });
1847
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_svg3.default, { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: import_core7.ada.paper, strokeWidth: 2.5, strokeLinecap: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_svg3.Path, { d: "M18 6L6 18M6 6l12 12" }) });
1204
1848
  }
1205
1849
  return closeIcon;
1206
1850
  };
1207
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native7.Animated.View, { style: [styles5.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
1208
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native7.View, { style: styles5.topBar, children: [
1209
- showWordmark ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.Text, { style: styles5.wordmark, children: "ada" }) : null,
1210
- status ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.Text, { style: styles5.status, children: status }) : null
1851
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.Animated.View, { style: [styles5.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
1852
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.View, { style: styles5.topBar, children: [
1853
+ showWordmark ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.Text, { style: styles5.wordmark, children: "ada" }) : null,
1854
+ status ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.Text, { style: styles5.status, children: status }) : null
1211
1855
  ] }),
1212
- hero ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.View, { style: styles5.heroWrap, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.Animated.View, { style: { transform: [{ scale: heroScale }] }, children: hero }) }) : null,
1213
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.View, { style: styles5.body, children }),
1214
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native7.View, { style: styles5.bottomBar, children: [
1215
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1856
+ hero ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.View, { style: styles5.heroWrap, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.Animated.View, { style: { transform: [{ scale: heroScale }] }, children: hero }) }) : null,
1857
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.View, { style: styles5.body, children }),
1858
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.View, { style: styles5.bottomBar, children: [
1859
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1216
1860
  import_react_native7.Pressable,
1217
1861
  {
1218
1862
  accessibilityRole: "button",
@@ -1223,151 +1867,166 @@ function SurfaceFullscreen({
1223
1867
  children: renderCloseIcon()
1224
1868
  }
1225
1869
  ),
1226
- closeLabel ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.Text, { style: styles5.closeLabel, children: closeLabel }) : null
1870
+ closeLabel ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.Text, { style: styles5.closeLabel, children: closeLabel }) : null
1227
1871
  ] })
1228
1872
  ] });
1229
1873
  }
1230
- var styles5 = import_react_native7.StyleSheet.create({
1231
- overlay: {
1232
- position: "absolute",
1233
- top: 0,
1234
- left: 0,
1235
- right: 0,
1236
- bottom: 0,
1237
- backgroundColor: import_core4.ada.paper,
1238
- zIndex: 1e3,
1239
- elevation: 1e3
1240
- },
1241
- topBar: {
1242
- paddingTop: import_react_native7.Platform.OS === "ios" ? 64 : 36,
1243
- paddingHorizontal: 24,
1244
- alignItems: "center",
1245
- gap: 6
1246
- },
1247
- wordmark: {
1248
- fontSize: 14,
1249
- fontFamily: fontFam3,
1250
- fontWeight: "500",
1251
- color: import_core4.ada.inkMuted,
1252
- letterSpacing: 4
1253
- },
1254
- status: {
1255
- fontSize: 17,
1256
- fontFamily: fontFam3,
1257
- fontWeight: "500",
1258
- color: import_core4.ada.ink,
1259
- letterSpacing: -0.2
1260
- },
1261
- heroWrap: {
1262
- paddingTop: 24,
1263
- paddingBottom: 12,
1264
- alignItems: "center",
1265
- justifyContent: "center"
1266
- },
1267
- body: {
1268
- flex: 1
1269
- },
1270
- bottomBar: {
1271
- paddingBottom: import_react_native7.Platform.OS === "ios" ? 48 : 32,
1272
- paddingTop: 12,
1273
- alignItems: "center",
1274
- gap: 8
1275
- },
1276
- closeBtn: {
1277
- width: 64,
1278
- height: 64,
1279
- borderRadius: 32,
1280
- backgroundColor: import_core4.ada.ink,
1281
- justifyContent: "center",
1282
- alignItems: "center",
1283
- shadowColor: import_core4.ada.ink,
1284
- shadowOffset: { width: 0, height: 8 },
1285
- shadowOpacity: 0.18,
1286
- shadowRadius: 14,
1287
- elevation: 8
1288
- },
1289
- closeLabel: {
1290
- fontSize: 13,
1291
- fontFamily: fontFam3,
1292
- fontWeight: "500",
1293
- color: import_core4.ada.inkMuted,
1294
- letterSpacing: 0.3
1874
+ var import_react7, import_react_native7, import_react_native_svg3, import_core7, import_jsx_runtime5, fontFam3, styles5;
1875
+ var init_SurfaceFullscreen = __esm({
1876
+ "src/surfaces/SurfaceFullscreen.tsx"() {
1877
+ "use strict";
1878
+ import_react7 = require("react");
1879
+ import_react_native7 = require("react-native");
1880
+ import_react_native_svg3 = __toESM(require("react-native-svg"));
1881
+ import_core7 = require("@ateam-ai-mobile/core");
1882
+ import_jsx_runtime5 = require("react/jsx-runtime");
1883
+ fontFam3 = import_react_native7.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1884
+ styles5 = import_react_native7.StyleSheet.create({
1885
+ overlay: {
1886
+ position: "absolute",
1887
+ top: 0,
1888
+ left: 0,
1889
+ right: 0,
1890
+ bottom: 0,
1891
+ backgroundColor: import_core7.ada.paper,
1892
+ zIndex: 1e3,
1893
+ elevation: 1e3
1894
+ },
1895
+ topBar: {
1896
+ paddingTop: import_react_native7.Platform.OS === "ios" ? 64 : 36,
1897
+ paddingHorizontal: 24,
1898
+ alignItems: "center",
1899
+ gap: 6
1900
+ },
1901
+ wordmark: {
1902
+ fontSize: 14,
1903
+ fontFamily: fontFam3,
1904
+ fontWeight: "500",
1905
+ color: import_core7.ada.inkMuted,
1906
+ letterSpacing: 4
1907
+ },
1908
+ status: {
1909
+ fontSize: 17,
1910
+ fontFamily: fontFam3,
1911
+ fontWeight: "500",
1912
+ color: import_core7.ada.ink,
1913
+ letterSpacing: -0.2
1914
+ },
1915
+ heroWrap: {
1916
+ paddingTop: 24,
1917
+ paddingBottom: 12,
1918
+ alignItems: "center",
1919
+ justifyContent: "center"
1920
+ },
1921
+ body: {
1922
+ flex: 1
1923
+ },
1924
+ bottomBar: {
1925
+ paddingBottom: import_react_native7.Platform.OS === "ios" ? 48 : 32,
1926
+ paddingTop: 12,
1927
+ alignItems: "center",
1928
+ gap: 8
1929
+ },
1930
+ closeBtn: {
1931
+ width: 64,
1932
+ height: 64,
1933
+ borderRadius: 32,
1934
+ backgroundColor: import_core7.ada.ink,
1935
+ justifyContent: "center",
1936
+ alignItems: "center",
1937
+ shadowColor: import_core7.ada.ink,
1938
+ shadowOffset: { width: 0, height: 8 },
1939
+ shadowOpacity: 0.18,
1940
+ shadowRadius: 14,
1941
+ elevation: 8
1942
+ },
1943
+ closeLabel: {
1944
+ fontSize: 13,
1945
+ fontFamily: fontFam3,
1946
+ fontWeight: "500",
1947
+ color: import_core7.ada.inkMuted,
1948
+ letterSpacing: 0.3
1949
+ }
1950
+ });
1295
1951
  }
1296
1952
  });
1297
1953
 
1298
1954
  // src/registry/store.ts
1299
- var import_zustand = require("zustand");
1300
- var useSurfaceStore = (0, import_zustand.create)((set, get) => ({
1301
- manifests: {},
1302
- activeDrawer: null,
1303
- activeFullscreen: null,
1304
- activeProps: {},
1305
- mountKey: 0,
1306
- register: (manifest) => set((s) => ({ manifests: { ...s.manifests, [manifest.id]: manifest } })),
1307
- unregister: (id) => set((s) => {
1308
- const next = { ...s.manifests };
1309
- delete next[id];
1310
- const nextProps = { ...s.activeProps };
1311
- delete nextProps[id];
1312
- return { manifests: next, activeProps: nextProps };
1313
- }),
1314
- show: (id, props) => {
1315
- const m = get().manifests[id];
1316
- if (!m) {
1317
- console.warn("[SurfaceStore] show: unknown surface id", id);
1318
- return;
1319
- }
1320
- set((s) => {
1321
- const nextProps = props !== void 0 ? { ...s.activeProps, [id]: props } : s.activeProps;
1322
- const mountKey = s.mountKey + 1;
1323
- if (m.type === "drawer") return { activeDrawer: id, activeProps: nextProps, mountKey };
1324
- if (m.type === "fullscreen") return { activeFullscreen: id, activeProps: nextProps, mountKey };
1325
- return { mountKey };
1326
- });
1327
- },
1328
- hide: (id) => {
1329
- const s = get();
1330
- if (!id) {
1331
- const cleanup2 = { ...s.activeProps };
1332
- if (s.activeDrawer) delete cleanup2[s.activeDrawer];
1333
- if (s.activeFullscreen) delete cleanup2[s.activeFullscreen];
1334
- set({ activeDrawer: null, activeFullscreen: null, activeProps: cleanup2 });
1335
- return;
1336
- }
1337
- const cleanup = { ...s.activeProps };
1338
- delete cleanup[id];
1339
- const patch = { activeProps: cleanup };
1340
- if (s.activeDrawer === id) patch.activeDrawer = null;
1341
- if (s.activeFullscreen === id) patch.activeFullscreen = null;
1342
- set(patch);
1343
- },
1344
- list: (filter) => {
1345
- const all = Object.values(get().manifests);
1346
- return all.filter((m) => {
1347
- if (filter?.type && m.type !== filter.type) return false;
1348
- if (filter?.visibility && !filter.visibility.includes(m.visibility)) return false;
1349
- if (filter?.placement) {
1350
- if (filter.placement === "unset") {
1351
- if (m.placement) return false;
1352
- } else if (m.placement !== filter.placement) {
1353
- return false;
1354
- }
1355
- }
1356
- return true;
1357
- });
1358
- }
1359
- }));
1360
1955
  function isMountSurface(m) {
1361
1956
  return m.type === "drawer" || m.type === "fullscreen";
1362
1957
  }
1363
1958
  function isInlineSurface(m) {
1364
1959
  return m.type === "card" || m.type === "header" || m.type === "ambient" || m.type === "nudge";
1365
1960
  }
1961
+ var import_zustand, useSurfaceStore;
1962
+ var init_store = __esm({
1963
+ "src/registry/store.ts"() {
1964
+ "use strict";
1965
+ import_zustand = require("zustand");
1966
+ useSurfaceStore = (0, import_zustand.create)((set, get) => ({
1967
+ manifests: {},
1968
+ activeDrawer: null,
1969
+ activeFullscreen: null,
1970
+ activeProps: {},
1971
+ mountKey: 0,
1972
+ register: (manifest) => set((s) => ({ manifests: { ...s.manifests, [manifest.id]: manifest } })),
1973
+ unregister: (id) => set((s) => {
1974
+ const next = { ...s.manifests };
1975
+ delete next[id];
1976
+ const nextProps = { ...s.activeProps };
1977
+ delete nextProps[id];
1978
+ return { manifests: next, activeProps: nextProps };
1979
+ }),
1980
+ show: (id, props) => {
1981
+ const m = get().manifests[id];
1982
+ if (!m) {
1983
+ console.warn("[SurfaceStore] show: unknown surface id", id);
1984
+ return;
1985
+ }
1986
+ set((s) => {
1987
+ const nextProps = props !== void 0 ? { ...s.activeProps, [id]: props } : s.activeProps;
1988
+ const mountKey = s.mountKey + 1;
1989
+ if (m.type === "drawer") return { activeDrawer: id, activeProps: nextProps, mountKey };
1990
+ if (m.type === "fullscreen") return { activeFullscreen: id, activeProps: nextProps, mountKey };
1991
+ return { mountKey };
1992
+ });
1993
+ },
1994
+ hide: (id) => {
1995
+ const s = get();
1996
+ if (!id) {
1997
+ const cleanup2 = { ...s.activeProps };
1998
+ if (s.activeDrawer) delete cleanup2[s.activeDrawer];
1999
+ if (s.activeFullscreen) delete cleanup2[s.activeFullscreen];
2000
+ set({ activeDrawer: null, activeFullscreen: null, activeProps: cleanup2 });
2001
+ return;
2002
+ }
2003
+ const cleanup = { ...s.activeProps };
2004
+ delete cleanup[id];
2005
+ const patch = { activeProps: cleanup };
2006
+ if (s.activeDrawer === id) patch.activeDrawer = null;
2007
+ if (s.activeFullscreen === id) patch.activeFullscreen = null;
2008
+ set(patch);
2009
+ },
2010
+ list: (filter) => {
2011
+ const all = Object.values(get().manifests);
2012
+ return all.filter((m) => {
2013
+ if (filter?.type && m.type !== filter.type) return false;
2014
+ if (filter?.visibility && !filter.visibility.includes(m.visibility)) return false;
2015
+ if (filter?.placement) {
2016
+ if (filter.placement === "unset") {
2017
+ if (m.placement) return false;
2018
+ } else if (m.placement !== filter.placement) {
2019
+ return false;
2020
+ }
2021
+ }
2022
+ return true;
2023
+ });
2024
+ }
2025
+ }));
2026
+ }
2027
+ });
1366
2028
 
1367
2029
  // src/registry/SurfaceDispatcher.tsx
1368
- var import_react7 = require("react");
1369
- var import_core5 = require("@ateam-ai-mobile/core");
1370
- var import_jsx_runtime5 = require("react/jsx-runtime");
1371
2030
  function SurfaceDispatcher() {
1372
2031
  const manifests = useSurfaceStore((s) => s.manifests);
1373
2032
  const activeDrawer = useSurfaceStore((s) => s.activeDrawer);
@@ -1375,8 +2034,8 @@ function SurfaceDispatcher() {
1375
2034
  const activeProps = useSurfaceStore((s) => s.activeProps);
1376
2035
  const mountKey = useSurfaceStore((s) => s.mountKey);
1377
2036
  const hide = useSurfaceStore((s) => s.hide);
1378
- (0, import_react7.useEffect)(() => {
1379
- const unsub = import_core5.pluginCommandEmitter.on("plugin-close", ({ pluginId } = {}) => {
2037
+ (0, import_react8.useEffect)(() => {
2038
+ const unsub = import_core8.pluginCommandEmitter.on("plugin-close", ({ pluginId } = {}) => {
1380
2039
  if (!pluginId) return;
1381
2040
  const s = useSurfaceStore.getState();
1382
2041
  if (s.activeDrawer === pluginId || s.activeFullscreen === pluginId) {
@@ -1389,8 +2048,8 @@ function SurfaceDispatcher() {
1389
2048
  const fullscreen = activeFullscreen ? manifests[activeFullscreen] : null;
1390
2049
  const drawerProps = activeDrawer ? activeProps[activeDrawer] : void 0;
1391
2050
  const fullscreenProps = activeFullscreen ? activeProps[activeFullscreen] : void 0;
1392
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1393
- drawer && drawer.type === "drawer" && drawer.Component ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2051
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2052
+ drawer && drawer.type === "drawer" && drawer.Component ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1394
2053
  drawer.Component,
1395
2054
  {
1396
2055
  dismiss: () => hide(drawer.id),
@@ -1398,7 +2057,7 @@ function SurfaceDispatcher() {
1398
2057
  },
1399
2058
  `drawer-${drawer.id}-${mountKey}`
1400
2059
  ) : null,
1401
- fullscreen && fullscreen.type === "fullscreen" && fullscreen.Component ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2060
+ fullscreen && fullscreen.type === "fullscreen" && fullscreen.Component ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1402
2061
  fullscreen.Component,
1403
2062
  {
1404
2063
  dismiss: () => hide(fullscreen.id),
@@ -1408,75 +2067,18 @@ function SurfaceDispatcher() {
1408
2067
  ) : null
1409
2068
  ] });
1410
2069
  }
2070
+ var import_react8, import_core8, import_jsx_runtime6;
2071
+ var init_SurfaceDispatcher = __esm({
2072
+ "src/registry/SurfaceDispatcher.tsx"() {
2073
+ "use strict";
2074
+ import_react8 = require("react");
2075
+ init_store();
2076
+ import_core8 = require("@ateam-ai-mobile/core");
2077
+ import_jsx_runtime6 = require("react/jsx-runtime");
2078
+ }
2079
+ });
1411
2080
 
1412
2081
  // src/utils/sha256.ts
1413
- var K = new Uint32Array([
1414
- 1116352408,
1415
- 1899447441,
1416
- 3049323471,
1417
- 3921009573,
1418
- 961987163,
1419
- 1508970993,
1420
- 2453635748,
1421
- 2870763221,
1422
- 3624381080,
1423
- 310598401,
1424
- 607225278,
1425
- 1426881987,
1426
- 1925078388,
1427
- 2162078206,
1428
- 2614888103,
1429
- 3248222580,
1430
- 3835390401,
1431
- 4022224774,
1432
- 264347078,
1433
- 604807628,
1434
- 770255983,
1435
- 1249150122,
1436
- 1555081692,
1437
- 1996064986,
1438
- 2554220882,
1439
- 2821834349,
1440
- 2952996808,
1441
- 3210313671,
1442
- 3336571891,
1443
- 3584528711,
1444
- 113926993,
1445
- 338241895,
1446
- 666307205,
1447
- 773529912,
1448
- 1294757372,
1449
- 1396182291,
1450
- 1695183700,
1451
- 1986661051,
1452
- 2177026350,
1453
- 2456956037,
1454
- 2730485921,
1455
- 2820302411,
1456
- 3259730800,
1457
- 3345764771,
1458
- 3516065817,
1459
- 3600352804,
1460
- 4094571909,
1461
- 275423344,
1462
- 430227734,
1463
- 506948616,
1464
- 659060556,
1465
- 883997877,
1466
- 958139571,
1467
- 1322822218,
1468
- 1537002063,
1469
- 1747873779,
1470
- 1955562222,
1471
- 2024104815,
1472
- 2227730452,
1473
- 2361852424,
1474
- 2428436474,
1475
- 2756734187,
1476
- 3204031479,
1477
- 3329325298
1478
- ]);
1479
- var rotr = (n, x) => x >>> n | x << 32 - n;
1480
2082
  function sha256Hex(message) {
1481
2083
  const utf8 = unescape(encodeURIComponent(message));
1482
2084
  const bytes = new Uint8Array(utf8.length);
@@ -1544,547 +2146,1153 @@ function sha256Hex(message) {
1544
2146
  }
1545
2147
  return out;
1546
2148
  }
1547
-
1548
- // src/bundles/storage.ts
1549
- var MemoryBundleStorage = class {
1550
- constructor() {
1551
- this.index = {};
1552
- this.files = /* @__PURE__ */ new Map();
1553
- }
1554
- async loadIndex() {
1555
- return this.index;
1556
- }
1557
- async saveIndex(index) {
1558
- this.index = index;
1559
- }
1560
- async readBundle(localPath) {
1561
- return this.files.get(localPath) ?? null;
2149
+ var K, rotr;
2150
+ var init_sha256 = __esm({
2151
+ "src/utils/sha256.ts"() {
2152
+ "use strict";
2153
+ K = new Uint32Array([
2154
+ 1116352408,
2155
+ 1899447441,
2156
+ 3049323471,
2157
+ 3921009573,
2158
+ 961987163,
2159
+ 1508970993,
2160
+ 2453635748,
2161
+ 2870763221,
2162
+ 3624381080,
2163
+ 310598401,
2164
+ 607225278,
2165
+ 1426881987,
2166
+ 1925078388,
2167
+ 2162078206,
2168
+ 2614888103,
2169
+ 3248222580,
2170
+ 3835390401,
2171
+ 4022224774,
2172
+ 264347078,
2173
+ 604807628,
2174
+ 770255983,
2175
+ 1249150122,
2176
+ 1555081692,
2177
+ 1996064986,
2178
+ 2554220882,
2179
+ 2821834349,
2180
+ 2952996808,
2181
+ 3210313671,
2182
+ 3336571891,
2183
+ 3584528711,
2184
+ 113926993,
2185
+ 338241895,
2186
+ 666307205,
2187
+ 773529912,
2188
+ 1294757372,
2189
+ 1396182291,
2190
+ 1695183700,
2191
+ 1986661051,
2192
+ 2177026350,
2193
+ 2456956037,
2194
+ 2730485921,
2195
+ 2820302411,
2196
+ 3259730800,
2197
+ 3345764771,
2198
+ 3516065817,
2199
+ 3600352804,
2200
+ 4094571909,
2201
+ 275423344,
2202
+ 430227734,
2203
+ 506948616,
2204
+ 659060556,
2205
+ 883997877,
2206
+ 958139571,
2207
+ 1322822218,
2208
+ 1537002063,
2209
+ 1747873779,
2210
+ 1955562222,
2211
+ 2024104815,
2212
+ 2227730452,
2213
+ 2361852424,
2214
+ 2428436474,
2215
+ 2756734187,
2216
+ 3204031479,
2217
+ 3329325298
2218
+ ]);
2219
+ rotr = (n, x) => x >>> n | x << 32 - n;
1562
2220
  }
1563
- async writeBundle(key, content) {
1564
- const localPath = `memory://${key}`;
1565
- this.files.set(localPath, content);
1566
- return localPath;
2221
+ });
2222
+
2223
+ // src/bundles/integrity.ts
2224
+ function assertBundleIntegrity(content, verifiedHash, label) {
2225
+ const digest = sha256Hex(content);
2226
+ if (!verifiedHash) {
2227
+ throw new Error(`[bundle-integrity] refusing to evaluate ${label}: no verified SHA-256 (fail closed).`);
1567
2228
  }
1568
- async deleteBundle(localPath) {
1569
- this.files.delete(localPath);
2229
+ if (digest !== verifiedHash.toLowerCase()) {
2230
+ throw new Error(
2231
+ `[bundle-integrity] integrity FAIL at eval for ${label}: content sha256 ${digest.slice(0, 12)}\u2026 \u2260 verified ${verifiedHash.slice(0, 12)}\u2026. Refusing to evaluate.`
2232
+ );
1570
2233
  }
1571
- async clearAll() {
1572
- this.index = {};
1573
- this.files.clear();
2234
+ }
2235
+ var init_integrity = __esm({
2236
+ "src/bundles/integrity.ts"() {
2237
+ "use strict";
2238
+ init_sha256();
1574
2239
  }
1575
- };
1576
- var _storage = new MemoryBundleStorage();
2240
+ });
2241
+
2242
+ // src/bundles/storage.ts
2243
+ function selectOwnedCacheEntries(entries, tenant) {
2244
+ if (!tenant) return [];
2245
+ return (entries || []).filter((e) => !!e && e.tenant === tenant);
2246
+ }
1577
2247
  function setBundleStorage(storage) {
1578
2248
  _storage = storage;
1579
2249
  }
1580
2250
  function getBundleStorage() {
1581
2251
  return _storage;
1582
- }
1583
-
1584
- // src/bundles/bundleManager.ts
1585
- var ReactShim = {
1586
- __esModule: true,
1587
- default: import_react8.default,
1588
- ...import_react8.default,
1589
- // Explicitly include hooks (React 19 may not have them as own enumerable props)
1590
- useState: import_react9.useState,
1591
- useEffect: import_react9.useEffect,
1592
- useCallback: import_react9.useCallback,
1593
- useMemo: import_react9.useMemo,
1594
- useRef: import_react9.useRef,
1595
- useContext: import_react9.useContext,
1596
- useReducer: import_react9.useReducer,
1597
- useLayoutEffect: import_react9.useLayoutEffect,
1598
- useImperativeHandle: import_react9.useImperativeHandle,
1599
- useDebugValue: import_react9.useDebugValue,
1600
- createElement: import_react8.default.createElement,
1601
- Fragment: import_react8.default.Fragment,
1602
- Component: import_react8.default.Component,
1603
- PureComponent: import_react8.default.PureComponent,
1604
- createContext: import_react8.default.createContext,
1605
- forwardRef: import_react8.default.forwardRef,
1606
- memo: import_react8.default.memo,
1607
- Children: import_react8.default.Children,
1608
- cloneElement: import_react8.default.cloneElement,
1609
- isValidElement: import_react8.default.isValidElement,
1610
- createRef: import_react8.default.createRef,
1611
- lazy: import_react8.default.lazy,
1612
- Suspense: import_react8.default.Suspense
1613
- };
1614
- var PluginSDKShim = Object.freeze({
1615
- ...sdk_exports,
1616
- SurfaceDrawer,
1617
- SurfaceCard,
1618
- SurfaceFullscreen,
1619
- useSurfaceStore,
1620
- SurfaceDispatcher,
1621
- PluginSDK: Object.freeze({
1622
- ...PluginSDK,
1623
- register: PluginSDK.register
1624
- }),
1625
- register
1626
- });
1627
- var requireShim = (name) => {
1628
- if (name === "react") return ReactShim;
1629
- if (name === "react-native") return ReactNative;
1630
- if (name === "@adas/plugin-sdk") return PluginSDKShim;
1631
- const hostModule = resolveBundleModule(name);
1632
- if (hostModule !== void 0) return hostModule;
1633
- console.warn(`[BundleManager] Unknown module: ${name} \u2014 register it via registerBundleModules()`);
1634
- return {};
1635
- };
1636
- var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
1637
- var BundleManager = class {
1638
- constructor() {
1639
- /** In-memory module cache: bundleUrl → evaluated module.exports */
1640
- this.modules = /* @__PURE__ */ new Map();
1641
- /** In-flight download deduplication: bundleUrl → Promise */
1642
- this.inflight = /* @__PURE__ */ new Map();
1643
- }
1644
- // ── Public API ──────────────────────────────────────────────
1645
- /**
1646
- * Load a plugin component. Returns the RegisteredUIPlugin object.
1647
- * Handles: in-memory cache → disk cache → download → evaluate → extract.
1648
- *
1649
- * `expectedHash` (SHA-256 hex from cp.getContextPlugin manifest) MUST be
1650
- * provided for any bundle the user has not previously verified — without
1651
- * it, a tampered bundle is `eval`'d as JS with full RN runtime + every
1652
- * native capability the manifest declares.
1653
- */
1654
- async load(pluginId, bundleUrl, version, expectedHash) {
1655
- const cached = this.getComponentFromMemory(pluginId, bundleUrl);
1656
- if (cached) return cached;
1657
- let modulePromise = this.inflight.get(bundleUrl);
1658
- if (!modulePromise) {
1659
- modulePromise = this.loadModule(bundleUrl, version, expectedHash);
1660
- this.inflight.set(bundleUrl, modulePromise);
1661
- modulePromise.catch(() => {
1662
- }).finally(() => this.inflight.delete(bundleUrl));
1663
- }
1664
- await modulePromise;
1665
- const component = this.getComponentFromMemory(pluginId, bundleUrl);
1666
- if (!component) {
1667
- throw new Error(
1668
- `Bundle loaded from ${bundleUrl} but no component found for "${pluginId}". Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
1669
- );
1670
- }
1671
- return component;
1672
- }
1673
- /**
1674
- * Prewarm: load every plugin bundle in the background after app boot, so a
1675
- * later open is instant (in-memory hit) instead of round-tripping the
1676
- * network. Errors are swallowed per-plugin — one bad bundle shouldn't
1677
- * block others. Concurrency capped at 3.
1678
- */
1679
- async prewarmAll(items) {
1680
- const queue = [...items];
1681
- const concurrency = Math.min(3, queue.length);
1682
- const start = Date.now();
1683
- let okCount = 0;
1684
- let failCount = 0;
1685
- const worker = async () => {
1686
- while (queue.length > 0) {
1687
- const it = queue.shift();
1688
- if (!it) return;
1689
- try {
1690
- await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash);
1691
- okCount++;
1692
- } catch (err) {
1693
- failCount++;
1694
- console.log(`[prewarm] ${it.pluginId}: ${err instanceof Error ? err.message : String(err)}`);
1695
- }
2252
+ }
2253
+ var MemoryBundleStorage, _storage;
2254
+ var init_storage = __esm({
2255
+ "src/bundles/storage.ts"() {
2256
+ "use strict";
2257
+ MemoryBundleStorage = class {
2258
+ constructor() {
2259
+ this.index = {};
2260
+ this.files = /* @__PURE__ */ new Map();
2261
+ }
2262
+ async loadIndex() {
2263
+ return this.index;
2264
+ }
2265
+ async saveIndex(index) {
2266
+ this.index = index;
2267
+ }
2268
+ async readBundle(localPath) {
2269
+ return this.files.get(localPath) ?? null;
2270
+ }
2271
+ async writeBundle(key, content) {
2272
+ const localPath = `memory://${key}`;
2273
+ this.files.set(localPath, content);
2274
+ return localPath;
2275
+ }
2276
+ async deleteBundle(localPath) {
2277
+ this.files.delete(localPath);
2278
+ }
2279
+ async clearAll() {
2280
+ this.index = {};
2281
+ this.files.clear();
1696
2282
  }
1697
2283
  };
1698
- await Promise.all(Array.from({ length: concurrency }, worker));
1699
- console.log(`[prewarm] ${okCount} loaded, ${failCount} failed in ${Date.now() - start}ms`);
1700
- }
1701
- /**
1702
- * Clear all caches (in-memory + storage).
1703
- */
1704
- async clearAll() {
1705
- this.modules.clear();
1706
- try {
1707
- await getBundleStorage().clearAll();
1708
- } catch (err) {
1709
- console.warn("[BundleManager] clearAll error:", err);
1710
- }
2284
+ _storage = new MemoryBundleStorage();
1711
2285
  }
1712
- /**
1713
- * Clear cache for a specific plugin/bundle.
1714
- */
1715
- async clearPlugin(pluginId) {
1716
- const storage = getBundleStorage();
1717
- const index = await storage.loadIndex();
1718
- const entry = index[pluginId];
1719
- if (entry) {
1720
- this.modules.delete(entry.bundleUrl);
1721
- delete index[pluginId];
1722
- await storage.saveIndex(index);
1723
- try {
1724
- await storage.deleteBundle(entry.localPath);
1725
- } catch {
2286
+ });
2287
+
2288
+ // src/bundles/bundleManager.ts
2289
+ var import_react9, import_react10, ReactNative, import_core9, ReactShim, PluginSDKShim, requireShim, CACHE_TTL, BundleManager, bundleManager;
2290
+ var init_bundleManager = __esm({
2291
+ "src/bundles/bundleManager.ts"() {
2292
+ "use strict";
2293
+ import_react9 = __toESM(require("react"));
2294
+ import_react10 = require("react");
2295
+ ReactNative = __toESM(require("react-native"));
2296
+ import_core9 = require("@ateam-ai-mobile/core");
2297
+ init_sdk();
2298
+ init_SurfaceDrawer();
2299
+ init_SurfaceCard();
2300
+ init_SurfaceFullscreen();
2301
+ init_store();
2302
+ init_SurfaceDispatcher();
2303
+ init_host();
2304
+ init_sha256();
2305
+ init_integrity();
2306
+ init_storage();
2307
+ init_registry();
2308
+ ReactShim = {
2309
+ __esModule: true,
2310
+ default: import_react9.default,
2311
+ ...import_react9.default,
2312
+ // Explicitly include hooks (React 19 may not have them as own enumerable props)
2313
+ useState: import_react10.useState,
2314
+ useEffect: import_react10.useEffect,
2315
+ useCallback: import_react10.useCallback,
2316
+ useMemo: import_react10.useMemo,
2317
+ useRef: import_react10.useRef,
2318
+ useContext: import_react10.useContext,
2319
+ useReducer: import_react10.useReducer,
2320
+ useLayoutEffect: import_react10.useLayoutEffect,
2321
+ useImperativeHandle: import_react10.useImperativeHandle,
2322
+ useDebugValue: import_react10.useDebugValue,
2323
+ createElement: import_react9.default.createElement,
2324
+ Fragment: import_react9.default.Fragment,
2325
+ Component: import_react9.default.Component,
2326
+ PureComponent: import_react9.default.PureComponent,
2327
+ createContext: import_react9.default.createContext,
2328
+ forwardRef: import_react9.default.forwardRef,
2329
+ memo: import_react9.default.memo,
2330
+ Children: import_react9.default.Children,
2331
+ cloneElement: import_react9.default.cloneElement,
2332
+ isValidElement: import_react9.default.isValidElement,
2333
+ createRef: import_react9.default.createRef,
2334
+ lazy: import_react9.default.lazy,
2335
+ Suspense: import_react9.default.Suspense
2336
+ };
2337
+ PluginSDKShim = Object.freeze({
2338
+ ...sdk_exports,
2339
+ SurfaceDrawer,
2340
+ SurfaceCard,
2341
+ SurfaceFullscreen,
2342
+ useSurfaceStore,
2343
+ SurfaceDispatcher,
2344
+ PluginSDK: Object.freeze({
2345
+ ...PluginSDK,
2346
+ register: PluginSDK.register
2347
+ }),
2348
+ register
2349
+ });
2350
+ requireShim = (name) => {
2351
+ if (name === "react") return ReactShim;
2352
+ if (name === "react-native") return ReactNative;
2353
+ if (name === "@adas/plugin-sdk") return PluginSDKShim;
2354
+ const hostModule = resolveBundleModule(name);
2355
+ if (hostModule !== void 0) return hostModule;
2356
+ console.warn(`[BundleManager] Unknown module: ${name} \u2014 register it via registerBundleModules()`);
2357
+ return {};
2358
+ };
2359
+ CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
2360
+ BundleManager = class {
2361
+ constructor() {
2362
+ /** In-memory module cache: bundleUrl → evaluated module.exports. Holds ONE
2363
+ * tenant's modules at a time — the tenant boundary is `cacheTenant` /
2364
+ * `requireTenant()`, NOT the key. */
2365
+ this.modules = /* @__PURE__ */ new Map();
2366
+ /** What is actually RUNNING, keyed by bundleUrl (for the current tenant). See
2367
+ * getLoadedBundles(). */
2368
+ this.provenance = /* @__PURE__ */ new Map();
2369
+ /** In-flight download deduplication: bundleUrl → Promise */
2370
+ this.inflight = /* @__PURE__ */ new Map();
2371
+ /**
2372
+ * The tenant the in-memory caches (modules / provenance / inflight) currently
2373
+ * belong to. This is the tenant BOUNDARY for evaluated code.
2374
+ *
2375
+ * A bundleUrl resolves PER-TENANT on the server — one url, different bytes for
2376
+ * different tenants — so a memory hit keyed on url alone would hand tenant B
2377
+ * the module tenant A already evaluated and ran. PR #9 scoped the DISK cache
2378
+ * (per-tenant index + hash gate); the in-memory map was left url-keyed. This
2379
+ * closes that: the caches only ever hold ONE tenant's modules, and a switch
2380
+ * drops them before anything is served.
2381
+ */
2382
+ this.cacheTenant = null;
2383
+ /**
2384
+ * pluginIds this manager registered device tools for. A tenant switch drops
2385
+ * the in-memory modules AND unregisters these, so the incoming tenant's bundle
2386
+ * re-registers into a clean registry instead of being refused as a duplicate
2387
+ * of the previous tenant's tools.
2388
+ */
2389
+ this.registeredToolPluginIds = /* @__PURE__ */ new Set();
2390
+ /**
2391
+ * Enforce the in-memory tenant boundary. Call at the top of every public load
2392
+ * path, BEFORE any cache read.
2393
+ *
2394
+ * FAIL CLOSED: without a tenant identity we cannot prove a cached module is
2395
+ * this session's, so we drop everything and refuse rather than risk serving
2396
+ * one tenant's evaluated code to another. On a tenant change we drop the
2397
+ * previous tenant's modules (and their tool registrations) before proceeding.
2398
+ */
2399
+ /**
2400
+ * Monotonic load generation. Bumped on every in-memory wipe (a tenant change,
2401
+ * fail-closed, or clearAll). A load captures it at ENTRY; a completion whose
2402
+ * generation no longer matches is stale and is discarded before it can commit.
2403
+ * This closes the async race the sequential guard cannot: tenant A's in-flight
2404
+ * loadModule can resolve AFTER a switch to B — clearing `inflight` does not
2405
+ * cancel that Promise — and would otherwise write A's module / provenance /
2406
+ * device tools into the singleton now marked cacheTenant=B.
2407
+ */
2408
+ this.epoch = 0;
1726
2409
  }
1727
- }
1728
- }
1729
- // ── Private: module loading pipeline ────────────────────────
1730
- async loadModule(bundleUrl, version, expectedHash) {
1731
- if (this.modules.has(bundleUrl)) return;
1732
- const storage = getBundleStorage();
1733
- const index = await storage.loadIndex();
1734
- const expectedLc = expectedHash ? expectedHash.toLowerCase() : null;
1735
- let content = null;
1736
- for (const entry of Object.values(index)) {
1737
- if (entry.bundleUrl === bundleUrl && entry.version === version) {
1738
- const age = Date.now() - entry.timestamp;
1739
- const hashOk = expectedLc && entry.bundleHash && entry.bundleHash.toLowerCase() === expectedLc;
1740
- if (age < CACHE_TTL && hashOk) {
1741
- content = await storage.readBundle(entry.localPath);
1742
- if (content) break;
2410
+ requireTenant() {
2411
+ const tenant = (0, import_core9.getTenant)();
2412
+ if (!tenant) {
2413
+ this.dropInMemory();
2414
+ this.cacheTenant = null;
2415
+ throw new Error(
2416
+ "[BundleManager] refusing to load a tenant-scoped bundle without a tenant identity (fail closed)"
2417
+ );
2418
+ }
2419
+ if (tenant !== this.cacheTenant) {
2420
+ this.dropInMemory();
2421
+ this.cacheTenant = tenant;
1743
2422
  }
2423
+ return tenant;
1744
2424
  }
1745
- }
1746
- if (!content) {
1747
- const downloaded = await this.download(bundleUrl, expectedLc);
1748
- content = downloaded.content;
1749
- const safeName = bundleUrl.replace(/[^a-zA-Z0-9]/g, "_").slice(-80);
1750
- try {
1751
- const localPath = await storage.writeBundle(`${safeName}@${version}.js`, content);
1752
- index[bundleUrl] = {
1753
- localPath,
1754
- version,
1755
- timestamp: Date.now(),
1756
- bundleUrl,
1757
- bundleHash: downloaded.sha256
1758
- };
1759
- await storage.saveIndex(index);
1760
- } catch (err) {
1761
- console.warn("[BundleManager] Cache write error:", err);
2425
+ /** Drop every in-memory cache and unregister the device tools this manager
2426
+ * registered — used on a tenant change and by clearAll(). */
2427
+ dropInMemory() {
2428
+ for (const pluginId of this.registeredToolPluginIds) {
2429
+ try {
2430
+ unregisterDeviceTools(pluginId);
2431
+ } catch {
2432
+ }
2433
+ }
2434
+ this.registeredToolPluginIds.clear();
2435
+ this.modules.clear();
2436
+ this.provenance.clear();
2437
+ this.inflight.clear();
2438
+ this.epoch++;
1762
2439
  }
1763
- }
1764
- const mod = this.evaluate(content, bundleUrl);
1765
- this.modules.set(bundleUrl, mod);
1766
- }
1767
- /**
1768
- * Download + integrity-verify a bundle. Refuses to return when:
1769
- * - manifest hash and computed hash disagree
1770
- * - server's `X-Bundle-SHA256` disagrees with manifest hash
1771
- * - no expected hash AND no server hash header (cannot prove the bundle
1772
- * wasn't tampered with — fail closed rather than trust the wire).
1773
- */
1774
- async download(bundleUrl, expectedHash) {
1775
- const cacheBust = `${bundleUrl}${bundleUrl.includes("?") ? "&" : "?"}t=${Date.now()}`;
1776
- const response = await (0, import_core6.apiFetchRaw)(cacheBust);
1777
- if (!response.ok) {
1778
- const body = await response.text().catch(() => "");
1779
- throw new Error(`Download failed: ${response.status} ${response.statusText} \u2014 ${body.substring(0, 200)}`);
1780
- }
1781
- const content = await response.text();
1782
- if (content.length < 50 || content.trimStart().startsWith("<!")) {
1783
- throw new Error("Bundle content is empty or HTML \u2014 server may have returned an error page");
1784
- }
1785
- const headerHash = (response.headers.get("x-bundle-sha256") || "").toLowerCase();
1786
- const computed = sha256Hex(content);
1787
- if (expectedHash) {
1788
- if (computed !== expectedHash) {
1789
- throw new Error(
1790
- `Bundle integrity FAIL: expected sha256=${expectedHash.slice(0, 12)}... got ${computed.slice(0, 12)}.... Refusing to evaluate.`
1791
- );
2440
+ /** True once the boundary has moved since a load captured `epoch` — i.e. a
2441
+ * tenant switch (or wipe) happened while this load was in flight. Its results
2442
+ * belong to a tenant that is no longer current and must be discarded. */
2443
+ isStale(epoch, tenant) {
2444
+ return this.epoch !== epoch || (0, import_core9.getTenant)() !== tenant;
1792
2445
  }
1793
- if (headerHash && headerHash !== expectedHash) {
1794
- throw new Error(
1795
- `Bundle hash mismatch: manifest=${expectedHash.slice(0, 12)}... server=${headerHash.slice(0, 12)}.... Refusing to evaluate.`
1796
- );
2446
+ /** Flip an existing provenance record to source:'memory' — a later serve of an
2447
+ * already-evaluated bundle came from the in-memory cache, not a fresh
2448
+ * download/disk read. No-op if nothing was recorded yet. */
2449
+ markMemoryHit(bundleUrl) {
2450
+ const prev = this.provenance.get(bundleUrl);
2451
+ if (prev && prev.source !== "memory") {
2452
+ this.provenance.set(bundleUrl, { ...prev, source: "memory" });
2453
+ }
1797
2454
  }
1798
- } else if (headerHash) {
1799
- if (computed !== headerHash) {
1800
- throw new Error(
1801
- `Bundle body sha256 (${computed.slice(0, 12)}...) does not match server's X-Bundle-SHA256 (${headerHash.slice(0, 12)}...).`
1802
- );
2455
+ // ── Public API ──────────────────────────────────────────────
2456
+ /**
2457
+ * Load a plugin component. Returns the RegisteredUIPlugin object.
2458
+ * Handles: in-memory cache → disk cache → download → evaluate → extract.
2459
+ *
2460
+ * `expectedHash` (SHA-256 hex from cp.getContextPlugin manifest) MUST be
2461
+ * provided for any bundle the user has not previously verified — without
2462
+ * it, a tampered bundle is `eval`'d as JS with full RN runtime + every
2463
+ * native capability the manifest declares.
2464
+ */
2465
+ async load(pluginId, bundleUrl, version, expectedHash) {
2466
+ const tenant = this.requireTenant();
2467
+ const epoch = this.epoch;
2468
+ const cached = this.getComponentFromMemory(pluginId, bundleUrl);
2469
+ if (cached) {
2470
+ this.markMemoryHit(bundleUrl);
2471
+ return cached;
2472
+ }
2473
+ let modulePromise = this.inflight.get(bundleUrl);
2474
+ if (!modulePromise) {
2475
+ const promise = this.loadModule(bundleUrl, version, expectedHash, tenant, epoch);
2476
+ modulePromise = promise;
2477
+ this.inflight.set(bundleUrl, promise);
2478
+ promise.catch(() => {
2479
+ }).finally(() => {
2480
+ if (this.inflight.get(bundleUrl) === promise) this.inflight.delete(bundleUrl);
2481
+ });
2482
+ }
2483
+ await modulePromise;
2484
+ if (this.isStale(epoch, tenant)) {
2485
+ throw new Error("[BundleManager] tenant changed while loading \u2014 discarding (fail closed)");
2486
+ }
2487
+ const component = this.getComponentFromMemory(pluginId, bundleUrl);
2488
+ if (!component) {
2489
+ throw new Error(
2490
+ `Bundle loaded from ${bundleUrl} but no component found for "${pluginId}". Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
2491
+ );
2492
+ }
2493
+ return component;
1803
2494
  }
1804
- console.warn(`[BundleManager] No manifest bundleHash \u2014 verified via X-Bundle-SHA256 header only`);
1805
- } else {
1806
- throw new Error(
1807
- `No bundleHash in manifest and no X-Bundle-SHA256 header from server. Refusing to evaluate untrusted bundle.`
1808
- );
1809
- }
1810
- return { content, sha256: computed };
1811
- }
1812
- evaluate(content, bundleUrl) {
1813
- let cleaned = content.replace(
1814
- /__toESM\(require\(("[^"]+")\)(?:,\s*\d+)?\)/g,
1815
- "require($1)"
1816
- );
1817
- cleaned = cleaned.replace(
1818
- /module\.exports\s*=\s*__toCommonJS\((\w+)\)/g,
1819
- "module.exports = $1"
1820
- );
1821
- const mod = { exports: {} };
1822
- try {
1823
- const factory = new Function(
1824
- "module",
1825
- "exports",
1826
- "require",
1827
- "__filename",
1828
- "__dirname",
1829
- cleaned
1830
- );
1831
- factory(mod, mod.exports, requireShim, bundleUrl, "adas-plugin-bundles");
1832
- } catch (err) {
1833
- throw new Error(`Bundle evaluation failed: ${err instanceof Error ? err.message : String(err)}`);
1834
- }
1835
- const raw = mod.exports;
1836
- const materialized = {};
1837
- for (const key of Object.getOwnPropertyNames(raw)) {
1838
- try {
1839
- materialized[key] = raw[key];
1840
- } catch {
2495
+ /**
2496
+ * Load a bundle for its `tools/` section only — no UI required.
2497
+ *
2498
+ * Same download, same integrity check, same evaluation as `load()`; the only
2499
+ * difference is that a bundle exporting tools and no `Component` is a success
2500
+ * here rather than "no component found". Returns the tool names registered.
2501
+ */
2502
+ async loadTools(pluginId, bundleUrl, version, expectedHash) {
2503
+ const tenant = this.requireTenant();
2504
+ const epoch = this.epoch;
2505
+ let modulePromise = this.inflight.get(bundleUrl);
2506
+ if (!modulePromise) {
2507
+ const promise = this.loadModule(bundleUrl, version, expectedHash, tenant, epoch);
2508
+ modulePromise = promise;
2509
+ this.inflight.set(bundleUrl, promise);
2510
+ promise.catch(() => {
2511
+ }).finally(() => {
2512
+ if (this.inflight.get(bundleUrl) === promise) this.inflight.delete(bundleUrl);
2513
+ });
2514
+ }
2515
+ await modulePromise;
2516
+ if (this.isStale(epoch, tenant)) {
2517
+ throw new Error("[BundleManager] tenant changed while loading tools \u2014 discarding (fail closed)");
2518
+ }
2519
+ const names = listDeviceTools().filter((t) => t.pluginId === pluginId).map((t) => t.name);
2520
+ if (names.length === 0) {
2521
+ console.warn(
2522
+ `[BundleManager] ${pluginId}: bundle loaded from ${bundleUrl} but registered no tools. Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
2523
+ );
2524
+ }
2525
+ return names;
1841
2526
  }
1842
- }
1843
- return materialized;
1844
- }
1845
- // ── Private: component extraction ───────────────────────────
1846
- /**
1847
- * Find a plugin component in a loaded module.
1848
- * Handles: default export, direct key match, multi-plugin .id match.
1849
- */
1850
- getComponentFromMemory(pluginId, bundleUrl) {
1851
- const mod = this.modules.get(bundleUrl);
1852
- if (!mod) return null;
1853
- const shortName = pluginId.includes(":") ? pluginId.split(":").pop() : pluginId;
1854
- if (mod[pluginId] && typeof mod[pluginId].Component === "function") {
1855
- return mod[pluginId];
1856
- }
1857
- const pascal = shortName.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase());
1858
- if (mod[pascal] && typeof mod[pascal] === "object" && typeof mod[pascal].Component === "function") {
1859
- return mod[pascal];
1860
- }
1861
- if (mod.default && typeof mod.default === "object" && typeof mod.default.Component === "function") {
1862
- return mod.default;
1863
- }
1864
- for (const key of Object.keys(mod)) {
1865
- const val = mod[key];
1866
- if (val && typeof val === "object" && val.id === shortName && typeof val.Component === "function") {
1867
- return val;
2527
+ /**
2528
+ * Prewarm: load every plugin bundle in the background after app boot, so a
2529
+ * later open is instant (in-memory hit) instead of round-tripping the
2530
+ * network. Errors are swallowed per-plugin — one bad bundle shouldn't
2531
+ * block others. Concurrency capped at 3.
2532
+ */
2533
+ async prewarmAll(items) {
2534
+ const queue = [...items];
2535
+ const concurrency = Math.min(3, queue.length);
2536
+ const start = Date.now();
2537
+ let okCount = 0;
2538
+ let failCount = 0;
2539
+ const worker = async () => {
2540
+ while (queue.length > 0) {
2541
+ const it = queue.shift();
2542
+ if (!it) return;
2543
+ try {
2544
+ await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash);
2545
+ okCount++;
2546
+ } catch (err) {
2547
+ failCount++;
2548
+ console.log(`[prewarm] ${it.pluginId}: ${err instanceof Error ? err.message : String(err)}`);
2549
+ }
2550
+ }
2551
+ };
2552
+ await Promise.all(Array.from({ length: concurrency }, worker));
2553
+ console.log(`[prewarm] ${okCount} loaded, ${failCount} failed in ${Date.now() - start}ms`);
1868
2554
  }
1869
- }
1870
- return null;
1871
- }
1872
- describeModule(mod) {
1873
- if (!mod) return "(empty)";
1874
- return Object.keys(mod).map((k) => {
1875
- const v = mod[k];
1876
- if (v && typeof v === "object" && v.id) return `${k}(id="${v.id}")`;
1877
- return k;
1878
- }).join(", ");
1879
- }
1880
- };
1881
- var bundleManager = new BundleManager();
1882
-
1883
- // src/bridge/native.ts
1884
- var import_react10 = require("react");
1885
- var import_core7 = require("@ateam-ai-mobile/core");
1886
- function buildContext(pluginId, connectorId) {
1887
- const user = (0, import_core7.getUser)();
1888
- return {
1889
- tenant: (0, import_core7.getTenant)(),
1890
- pluginId,
1891
- connectorId,
1892
- user: user ? { id: user.actorId || "", name: user.name || "", roles: user.roles || [] } : void 0
1893
- };
1894
- }
1895
- function useNativeBridge(pluginId, connectorId, commandBufferRef) {
1896
- const initCallbackRef = (0, import_react10.useRef)(null);
1897
- const commandListenersRef = (0, import_react10.useRef)(/* @__PURE__ */ new Set());
1898
- const initializedRef = (0, import_react10.useRef)(false);
1899
- const fireInit = (0, import_react10.useCallback)(() => {
1900
- if (initializedRef.current || !initCallbackRef.current) return;
1901
- initCallbackRef.current(buildContext(pluginId, connectorId));
1902
- initializedRef.current = true;
1903
- }, [pluginId, connectorId]);
1904
- const onInit = (0, import_react10.useCallback)((cb) => {
1905
- initCallbackRef.current = cb;
1906
- setTimeout(fireInit, 0);
1907
- }, [fireInit]);
1908
- const mcpCall = (0, import_react10.useCallback)(async (toolOrConnector, argsOrTool, cIdOrArgs) => {
1909
- let tool;
1910
- let args;
1911
- let cId;
1912
- if (typeof argsOrTool === "string") {
1913
- cId = toolOrConnector;
1914
- tool = argsOrTool;
1915
- args = cIdOrArgs || {};
1916
- } else {
1917
- tool = toolOrConnector;
1918
- args = argsOrTool;
1919
- cId = typeof cIdOrArgs === "string" ? cIdOrArgs : void 0;
1920
- }
1921
- const targetConnector = cId || connectorId;
1922
- if (!targetConnector) {
1923
- console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2014 no connectorId. tool=${tool}`);
1924
- throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
1925
- }
1926
- try {
1927
- const data = await (0, import_core7.connectorCall)(targetConnector, tool, args);
1928
- if (!data.ok && data.error) {
1929
- throw new Error(data.error);
2555
+ /**
2556
+ * Clear all caches (in-memory + storage).
2557
+ */
2558
+ async clearAll() {
2559
+ this.dropInMemory();
2560
+ this.cacheTenant = null;
2561
+ try {
2562
+ await getBundleStorage().clearAll();
2563
+ } catch (err) {
2564
+ console.warn("[BundleManager] clearAll error:", err);
2565
+ }
1930
2566
  }
1931
- return (0, import_core7.unwrapMcpResult)(data.result);
1932
- } catch (err) {
1933
- console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2192 connector=${targetConnector} tool=${tool}:`, err?.message || err);
1934
- throw err;
1935
- }
1936
- }, [pluginId, connectorId]);
1937
- const mcpFetch = (0, import_react10.useCallback)(async (body) => {
1938
- return (0, import_core7.mcpFetch)(body);
1939
- }, []);
1940
- const sendCommand = (0, import_react10.useCallback)((type, payload) => {
1941
- if (type === "plugin.command.result" && payload?.correlationId) {
1942
- (0, import_core7.postCommandResult)(payload.correlationId, payload.result, payload.error);
1943
- return;
1944
- }
1945
- }, [pluginId]);
1946
- const onCommand = (0, import_react10.useCallback)((cb) => {
1947
- commandListenersRef.current.add(cb);
1948
- if (commandBufferRef) {
1949
- const buf = commandBufferRef.current;
1950
- const remaining = [];
1951
- for (const cmd of buf) {
1952
- if (cmd.pluginId === pluginId && cmd.command !== "__focus") {
2567
+ /**
2568
+ * Clear cache for a specific plugin/bundle.
2569
+ */
2570
+ async clearPlugin(pluginId) {
2571
+ const storage = getBundleStorage();
2572
+ const index = await storage.loadIndex();
2573
+ const entry = index[pluginId];
2574
+ if (entry) {
2575
+ this.modules.delete(entry.bundleUrl);
2576
+ delete index[pluginId];
2577
+ await storage.saveIndex(index);
1953
2578
  try {
1954
- cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
2579
+ await storage.deleteBundle(entry.localPath);
1955
2580
  } catch {
1956
2581
  }
1957
- } else {
1958
- remaining.push(cmd);
1959
2582
  }
1960
2583
  }
1961
- commandBufferRef.current = remaining;
1962
- }
1963
- for (const cmd of import_core7.pluginCommandEmitter.drainPluginCommands(pluginId)) {
1964
- try {
1965
- cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1966
- } catch {
2584
+ /**
2585
+ * What every loaded bundle IS, right now: the sha256 of the bytes running,
2586
+ * the hash the server advertised, and where they came from.
2587
+ *
2588
+ * Deliberately not filtered or prettified — this is the raw answer to "did my
2589
+ * redeploy reach the phone?", and it is comparable directly against the
2590
+ * manifest `bundleHash` and the server's X-Bundle-SHA256 header.
2591
+ */
2592
+ getLoadedBundles() {
2593
+ return [...this.provenance.values()];
1967
2594
  }
1968
- }
1969
- return () => {
1970
- commandListenersRef.current.delete(cb);
1971
- };
1972
- }, [pluginId, commandBufferRef]);
1973
- (0, import_react10.useEffect)(() => {
1974
- const unsub = import_core7.pluginCommandEmitter.on("plugin-command", (cmd) => {
1975
- if (cmd.pluginId !== pluginId) return;
1976
- if (cmd.command === "__focus") return;
1977
- commandListenersRef.current.forEach((fn) => {
2595
+ // ── Private: module loading pipeline ────────────────────────
2596
+ async loadModule(bundleUrl, version, expectedHash, expectedTenant, expectedEpoch) {
2597
+ if (this.isStale(expectedEpoch, expectedTenant)) {
2598
+ throw new Error("[BundleManager] tenant changed before load started \u2014 discarding (fail closed)");
2599
+ }
2600
+ if (this.modules.has(bundleUrl)) {
2601
+ const prev = this.provenance.get(bundleUrl);
2602
+ if (prev) this.provenance.set(bundleUrl, { ...prev, source: "memory", expected: expectedHash?.toLowerCase() ?? prev.expected });
2603
+ return;
2604
+ }
2605
+ const storage = getBundleStorage();
2606
+ const index = await storage.loadIndex();
2607
+ const expectedLc = expectedHash ? expectedHash.toLowerCase() : null;
2608
+ let content = null;
2609
+ let verifiedHash = null;
2610
+ let servedFrom = "download";
2611
+ for (const entry of Object.values(index)) {
2612
+ if (entry.bundleUrl === bundleUrl && entry.version === version) {
2613
+ const age = Date.now() - entry.timestamp;
2614
+ const hashOk = expectedLc && entry.bundleHash && entry.bundleHash.toLowerCase() === expectedLc;
2615
+ if (age < CACHE_TTL && hashOk) {
2616
+ content = await storage.readBundle(entry.localPath);
2617
+ if (content) {
2618
+ verifiedHash = entry.bundleHash.toLowerCase();
2619
+ servedFrom = "disk-cache";
2620
+ break;
2621
+ }
2622
+ }
2623
+ }
2624
+ }
2625
+ if (!content) {
2626
+ const downloaded = await this.download(bundleUrl, expectedLc);
2627
+ content = downloaded.content;
2628
+ verifiedHash = downloaded.sha256.toLowerCase();
2629
+ if (this.isStale(expectedEpoch, expectedTenant)) {
2630
+ throw new Error("[BundleManager] tenant changed during download \u2014 discarding (fail closed)");
2631
+ }
2632
+ try {
2633
+ const localPath = await storage.writeBundle(`${verifiedHash}.js`, content);
2634
+ if (!this.isStale(expectedEpoch, expectedTenant)) {
2635
+ index[bundleUrl] = {
2636
+ localPath,
2637
+ version,
2638
+ timestamp: Date.now(),
2639
+ bundleUrl,
2640
+ bundleHash: downloaded.sha256,
2641
+ tenant: expectedTenant
2642
+ };
2643
+ await storage.saveIndex(index);
2644
+ }
2645
+ } catch (err) {
2646
+ console.warn("[BundleManager] Cache write error:", err);
2647
+ }
2648
+ }
2649
+ if (this.isStale(expectedEpoch, expectedTenant)) {
2650
+ throw new Error("[BundleManager] tenant changed while loading \u2014 discarding stale bundle before eval (fail closed)");
2651
+ }
2652
+ const mod = this.evaluate(content, bundleUrl, verifiedHash);
2653
+ this.modules.set(bundleUrl, mod);
2654
+ this.provenance.set(bundleUrl, {
2655
+ bundleUrl,
2656
+ version,
2657
+ hash: verifiedHash,
2658
+ expected: expectedLc,
2659
+ source: servedFrom,
2660
+ // The captured owner — same rule as the disk stamp. The final stale check
2661
+ // above guarantees getTenant() === expectedTenant here, but binding it to
2662
+ // the captured value keeps the ownership record impossible to mislabel.
2663
+ tenant: expectedTenant,
2664
+ loadedAt: Date.now()
2665
+ });
2666
+ console.log(
2667
+ `[BundleManager] RUNNING ${bundleUrl} v${version} sha=${(verifiedHash || "none").slice(0, 12)} (${servedFrom}${expectedLc && verifiedHash && expectedLc !== verifiedHash ? `, EXPECTED ${expectedLc.slice(0, 12)} \u2014 STALE` : ""})`
2668
+ );
2669
+ this.registerToolsFromModule(mod, bundleUrl);
2670
+ }
2671
+ /**
2672
+ * Pick up every `tools` section in an evaluated bundle and put it in the
2673
+ * device tool registry. Runs for UI, hybrid and tools-only bundles alike: a
2674
+ * plugin that ships both gets its tools registered without its UI mounting.
2675
+ */
2676
+ registerToolsFromModule(mod, bundleUrl) {
2677
+ if (!mod) return;
2678
+ for (const key of Object.keys(mod)) {
2679
+ const val = mod[key];
2680
+ if (!val || typeof val !== "object" || !Array.isArray(val.tools) || val.tools.length === 0) {
2681
+ continue;
2682
+ }
2683
+ const pluginId = typeof val.id === "string" ? val.id : `${bundleUrl}#${key}`;
2684
+ try {
2685
+ registerDeviceTools(pluginId, val.tools, val.capabilities, val.stateDomains);
2686
+ this.registeredToolPluginIds.add(pluginId);
2687
+ } catch (err) {
2688
+ console.warn(
2689
+ `[BundleManager] tool registration failed for ${pluginId}:`,
2690
+ err instanceof Error ? err.message : String(err)
2691
+ );
2692
+ }
2693
+ }
2694
+ }
2695
+ /**
2696
+ * Download + integrity-verify a bundle. Refuses to return when:
2697
+ * - manifest hash and computed hash disagree
2698
+ * - server's `X-Bundle-SHA256` disagrees with manifest hash
2699
+ * - no expected hash AND no server hash header (cannot prove the bundle
2700
+ * wasn't tampered with — fail closed rather than trust the wire).
2701
+ */
2702
+ async download(bundleUrl, expectedHash) {
2703
+ const cacheBust = `${bundleUrl}${bundleUrl.includes("?") ? "&" : "?"}t=${Date.now()}`;
2704
+ const response = await (0, import_core9.apiFetchRaw)(cacheBust);
2705
+ if (!response.ok) {
2706
+ const body = await response.text().catch(() => "");
2707
+ throw new Error(`Download failed: ${response.status} ${response.statusText} \u2014 ${body.substring(0, 200)}`);
2708
+ }
2709
+ const content = await response.text();
2710
+ if (content.length < 50 || content.trimStart().startsWith("<!")) {
2711
+ throw new Error("Bundle content is empty or HTML \u2014 server may have returned an error page");
2712
+ }
2713
+ const headerHash = (response.headers.get("x-bundle-sha256") || "").toLowerCase();
2714
+ const computed = sha256Hex(content);
2715
+ if (expectedHash) {
2716
+ if (computed !== expectedHash) {
2717
+ throw new Error(
2718
+ `Bundle integrity FAIL: expected sha256=${expectedHash.slice(0, 12)}... got ${computed.slice(0, 12)}.... Refusing to evaluate.`
2719
+ );
2720
+ }
2721
+ if (headerHash && headerHash !== expectedHash) {
2722
+ throw new Error(
2723
+ `Bundle hash mismatch: manifest=${expectedHash.slice(0, 12)}... server=${headerHash.slice(0, 12)}.... Refusing to evaluate.`
2724
+ );
2725
+ }
2726
+ } else if (headerHash) {
2727
+ if (computed !== headerHash) {
2728
+ throw new Error(
2729
+ `Bundle body sha256 (${computed.slice(0, 12)}...) does not match server's X-Bundle-SHA256 (${headerHash.slice(0, 12)}...).`
2730
+ );
2731
+ }
2732
+ console.warn(`[BundleManager] No manifest bundleHash \u2014 verified via X-Bundle-SHA256 header only`);
2733
+ } else {
2734
+ throw new Error(
2735
+ `No bundleHash in manifest and no X-Bundle-SHA256 header from server. Refusing to evaluate untrusted bundle.`
2736
+ );
2737
+ }
2738
+ return { content, sha256: computed };
2739
+ }
2740
+ evaluate(content, bundleUrl, verifiedHash) {
2741
+ assertBundleIntegrity(content, verifiedHash, bundleUrl);
2742
+ let cleaned = content.replace(
2743
+ /__toESM\(require\(("[^"]+")\)(?:,\s*\d+)?\)/g,
2744
+ "require($1)"
2745
+ );
2746
+ cleaned = cleaned.replace(
2747
+ /module\.exports\s*=\s*__toCommonJS\((\w+)\)/g,
2748
+ "module.exports = $1"
2749
+ );
2750
+ const mod = { exports: {} };
1978
2751
  try {
1979
- fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1980
- } catch {
2752
+ const factory = new Function(
2753
+ "module",
2754
+ "exports",
2755
+ "require",
2756
+ "__filename",
2757
+ "__dirname",
2758
+ cleaned
2759
+ );
2760
+ factory(mod, mod.exports, requireShim, bundleUrl, "adas-plugin-bundles");
2761
+ } catch (err) {
2762
+ throw new Error(`Bundle evaluation failed: ${err instanceof Error ? err.message : String(err)}`);
1981
2763
  }
1982
- });
1983
- });
1984
- return unsub;
1985
- }, [pluginId]);
1986
- (0, import_react10.useEffect)(() => {
1987
- const timer = setTimeout(() => {
1988
- const dispatch = (cmd) => {
1989
- commandListenersRef.current.forEach((fn) => {
2764
+ const raw = mod.exports;
2765
+ const materialized = {};
2766
+ for (const key of Object.getOwnPropertyNames(raw)) {
1990
2767
  try {
1991
- fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
2768
+ materialized[key] = raw[key];
1992
2769
  } catch {
1993
2770
  }
1994
- });
1995
- };
1996
- if (commandBufferRef) {
1997
- const remaining = [];
1998
- for (const cmd of commandBufferRef.current) {
1999
- if (cmd.pluginId === pluginId && cmd.command !== "__focus") dispatch(cmd);
2000
- else remaining.push(cmd);
2001
2771
  }
2002
- commandBufferRef.current = remaining;
2772
+ return materialized;
2003
2773
  }
2004
- for (const cmd of import_core7.pluginCommandEmitter.drainPluginCommands(pluginId)) {
2005
- dispatch(cmd);
2774
+ // ── Private: component extraction ───────────────────────────
2775
+ /**
2776
+ * Find a plugin component in a loaded module.
2777
+ * Handles: default export, direct key match, multi-plugin .id match.
2778
+ */
2779
+ getComponentFromMemory(pluginId, bundleUrl) {
2780
+ const mod = this.modules.get(bundleUrl);
2781
+ if (!mod) return null;
2782
+ const shortName = pluginId.includes(":") ? pluginId.split(":").pop() : pluginId;
2783
+ if (mod[pluginId] && typeof mod[pluginId].Component === "function") {
2784
+ return mod[pluginId];
2785
+ }
2786
+ const pascal = shortName.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase());
2787
+ if (mod[pascal] && typeof mod[pascal] === "object" && typeof mod[pascal].Component === "function") {
2788
+ return mod[pascal];
2789
+ }
2790
+ if (mod.default && typeof mod.default === "object" && typeof mod.default.Component === "function") {
2791
+ return mod.default;
2792
+ }
2793
+ for (const key of Object.keys(mod)) {
2794
+ const val = mod[key];
2795
+ if (val && typeof val === "object" && val.id === shortName && typeof val.Component === "function") {
2796
+ return val;
2797
+ }
2798
+ }
2799
+ return null;
2800
+ }
2801
+ describeModule(mod) {
2802
+ if (!mod) return "(empty)";
2803
+ return Object.keys(mod).map((k) => {
2804
+ const v = mod[k];
2805
+ if (v && typeof v === "object" && v.id) return `${k}(id="${v.id}")`;
2806
+ return k;
2807
+ }).join(", ");
2006
2808
  }
2007
- }, 500);
2008
- return () => clearTimeout(timer);
2009
- }, [pluginId, commandBufferRef]);
2010
- (0, import_react10.useEffect)(() => {
2011
- return () => {
2012
- commandListenersRef.current.clear();
2013
- initializedRef.current = false;
2014
2809
  };
2015
- }, []);
2016
- const close = (0, import_react10.useCallback)(() => {
2017
- import_core7.pluginCommandEmitter.emit("plugin-close", { pluginId });
2018
- }, [pluginId]);
2019
- const sendMessage = (0, import_react10.useCallback)((text) => {
2020
- const t = (text || "").trim();
2021
- if (!t) return;
2810
+ bundleManager = new BundleManager();
2811
+ }
2812
+ });
2813
+
2814
+ // src/tools/loader.ts
2815
+ var loader_exports = {};
2816
+ __export(loader_exports, {
2817
+ ensureDeviceToolsLoaded: () => ensureDeviceToolsLoaded,
2818
+ resetDeviceToolLoader: () => resetDeviceToolLoader
2819
+ });
2820
+ async function ensureDeviceToolsLoaded({ force = false } = {}) {
2821
+ if (loadedOnce && !force) return listDeviceTools().map((t) => t.name);
2822
+ if (inflight) return inflight;
2823
+ inflight = (async () => {
2022
2824
  try {
2023
- import_core7.useChatStore.getState().sendMessage(t);
2024
- } catch (err) {
2025
- console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
2825
+ const summaries = await listPlugins();
2826
+ const manifests = await Promise.all(
2827
+ (summaries || []).map(
2828
+ (s) => getPlugin(s.id).catch(() => null)
2829
+ )
2830
+ );
2831
+ const targets = manifests.filter((m) => {
2832
+ const declared = m?.capabilities?.device_tools;
2833
+ return Array.isArray(declared) && declared.length > 0;
2834
+ });
2835
+ if (targets.length === 0) {
2836
+ const recovered = await loadFromDiskCache();
2837
+ if (recovered.length > 0) {
2838
+ console.log(`[deviceTools] catalog listed none \u2014 recovered from cache: ${recovered.join(", ")}`);
2839
+ return recovered;
2840
+ }
2841
+ console.log("[deviceTools] no device tools available yet \u2014 will look again next call");
2842
+ return [];
2843
+ }
2844
+ await Promise.all(
2845
+ targets.map(async (m) => {
2846
+ const bundleUrl = getNativeBundleUrl(m);
2847
+ if (!bundleUrl) {
2848
+ console.warn(`[deviceTools] ${m.id} declares device_tools but has no native bundle`);
2849
+ return;
2850
+ }
2851
+ try {
2852
+ await bundleManager.loadTools(
2853
+ m.id,
2854
+ bundleUrl,
2855
+ m.version || "1.0.0",
2856
+ getNativeBundleHash(m) || void 0
2857
+ );
2858
+ } catch (err) {
2859
+ console.warn(`[deviceTools] ${m.id} failed to load:`, err?.message || String(err));
2860
+ }
2861
+ })
2862
+ );
2863
+ const names = listDeviceTools().map((t) => t.name);
2864
+ loadedOnce = names.length > 0;
2865
+ console.log(`[deviceTools] ready: ${names.length > 0 ? names.join(", ") : "(none registered \u2014 will retry next call)"}`);
2866
+ return names;
2867
+ } finally {
2868
+ inflight = null;
2026
2869
  }
2027
- }, []);
2028
- const prepareMessage = (0, import_react10.useCallback)((text) => {
2029
- const t = (text || "").trim();
2030
- if (!t) return;
2031
- import_core7.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
2032
- }, []);
2033
- return (0, import_react10.useMemo)(
2034
- () => ({ onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage }),
2035
- [onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage]
2036
- );
2870
+ })();
2871
+ return inflight;
2872
+ }
2873
+ async function loadFromDiskCache() {
2874
+ try {
2875
+ const index = await getBundleStorage().loadIndex();
2876
+ const entries = Object.values(index || {});
2877
+ if (entries.length === 0) return [];
2878
+ const tenant = (0, import_core10.getTenant)();
2879
+ if (!tenant) {
2880
+ console.warn("[deviceTools] disk-cache recovery skipped \u2014 no active tenant");
2881
+ return [];
2882
+ }
2883
+ const mine = selectOwnedCacheEntries(entries, tenant);
2884
+ const skipped = entries.length - mine.length;
2885
+ if (skipped > 0) {
2886
+ console.log(`[deviceTools] disk-cache recovery: ${mine.length} for "${tenant}", skipped ${skipped} not owned by it`);
2887
+ }
2888
+ if (mine.length === 0) return [];
2889
+ await Promise.all(mine.map(async (e) => {
2890
+ try {
2891
+ await bundleManager.loadTools("cache:" + e.bundleUrl, e.bundleUrl, e.version, e.bundleHash);
2892
+ } catch {
2893
+ }
2894
+ }));
2895
+ return listDeviceTools().map((t) => t.name);
2896
+ } catch (err) {
2897
+ console.warn("[deviceTools] disk-cache recovery failed:", err?.message || String(err));
2898
+ return [];
2899
+ }
2900
+ }
2901
+ function resetDeviceToolLoader() {
2902
+ loadedOnce = false;
2903
+ }
2904
+ var import_core10, inflight, loadedOnce;
2905
+ var init_loader = __esm({
2906
+ "src/tools/loader.ts"() {
2907
+ "use strict";
2908
+ init_plugins();
2909
+ init_bundleManager();
2910
+ init_registry();
2911
+ init_storage();
2912
+ import_core10 = require("@ateam-ai-mobile/core");
2913
+ inflight = null;
2914
+ loadedOnce = false;
2915
+ }
2916
+ });
2917
+
2918
+ // src/index.ts
2919
+ var index_exports = {};
2920
+ __export(index_exports, {
2921
+ ActionButton: () => ActionButton,
2922
+ ActivityIndicator: () => import_react_native.ActivityIndicator,
2923
+ AdaOrb: () => AdaOrb,
2924
+ Animated: () => import_react_native.Animated,
2925
+ Badge: () => Badge,
2926
+ CameraPreview: () => CameraPreview,
2927
+ Card: () => Card,
2928
+ EmptyState: () => EmptyState,
2929
+ ErrorState: () => ErrorState,
2930
+ FlatList: () => import_react_native.FlatList,
2931
+ Image: () => import_react_native.Image,
2932
+ ListItem: () => ListItem,
2933
+ LoadingState: () => LoadingState,
2934
+ MemoryBundleStorage: () => MemoryBundleStorage,
2935
+ Modal: () => import_react_native.Modal,
2936
+ PLATFORM_VOICE_CALLER: () => PLATFORM_VOICE_CALLER,
2937
+ Platform: () => import_react_native.Platform,
2938
+ PluginSDK: () => PluginSDK,
2939
+ Pressable: () => import_react_native.Pressable,
2940
+ RefreshControl: () => import_react_native.RefreshControl,
2941
+ ScrollView: () => import_react_native.ScrollView,
2942
+ SearchBar: () => SearchBar,
2943
+ SectionList: () => import_react_native.SectionList,
2944
+ StyleSheet: () => import_react_native.StyleSheet,
2945
+ SurfaceCard: () => SurfaceCard,
2946
+ SurfaceDispatcher: () => SurfaceDispatcher,
2947
+ SurfaceDrawer: () => SurfaceDrawer,
2948
+ SurfaceFullscreen: () => SurfaceFullscreen,
2949
+ Switch: () => import_react_native.Switch,
2950
+ TabBar: () => TabBar,
2951
+ Text: () => import_react_native.Text,
2952
+ TextInput: () => import_react_native.TextInput,
2953
+ TouchableOpacity: () => import_react_native.TouchableOpacity,
2954
+ View: () => import_react_native.View,
2955
+ __resetSessionSyncForTest: () => __resetSessionSyncForTest,
2956
+ bundleManager: () => bundleManager,
2957
+ callDeviceTool: () => callDeviceTool,
2958
+ clearDeviceTools: () => clearDeviceTools,
2959
+ createNativeBridge: () => createNativeBridge,
2960
+ deviceStateV2: () => device_state_exports,
2961
+ deviceToolsActionForSession: () => deviceToolsActionForSession,
2962
+ ensureDeviceToolsLoaded: () => ensureDeviceToolsLoaded,
2963
+ explainCapability: () => explainCapability,
2964
+ gateDeviceState: () => gateDeviceState,
2965
+ gatePluginNative: () => gatePluginNative,
2966
+ gateVoice: () => gateVoice,
2967
+ getCameraPreviewProvider: () => getCameraPreviewProvider,
2968
+ getHaptics: () => getHaptics,
2969
+ getNativeBundleHash: () => getNativeBundleHash,
2970
+ getNativeBundleUrl: () => getNativeBundleUrl,
2971
+ getNativeComponentId: () => getNativeComponentId,
2972
+ getPlugin: () => getPlugin,
2973
+ getPluginNative: () => getPluginNative,
2974
+ handleServerDeviceToolCall: () => handleServerDeviceToolCall,
2975
+ hasDeviceTool: () => hasDeviceTool,
2976
+ implementedStateDomains: () => implementedStateDomains,
2977
+ implementedVoice: () => implementedVoice,
2978
+ isActionable: () => isActionable,
2979
+ isDeviceToolCommand: () => isDeviceToolCommand,
2980
+ isInlineSurface: () => isInlineSurface,
2981
+ isMountSurface: () => isMountSurface,
2982
+ listDeviceTools: () => listDeviceTools,
2983
+ listPlugins: () => listPlugins,
2984
+ nativePluginRegistry: () => nativePluginRegistry,
2985
+ neverObserved: () => neverObserved,
2986
+ register: () => register,
2987
+ registerBundleModules: () => registerBundleModules,
2988
+ registerDeviceTools: () => registerDeviceTools,
2989
+ registerNativePlugin: () => registerNativePlugin,
2990
+ registerPlatformDeviceTools: () => registerPlatformDeviceTools,
2991
+ registerService: () => registerService,
2992
+ registerTools: () => registerTools,
2993
+ resetDeviceToolLoader: () => resetDeviceToolLoader,
2994
+ resolvePluginRuntimeProps: () => resolvePluginRuntimeProps,
2995
+ resolvePluginUrl: () => resolvePluginUrl,
2996
+ setBundleStorage: () => setBundleStorage,
2997
+ setCameraPreviewProvider: () => setCameraPreviewProvider,
2998
+ setDeviceStateProvider: () => setDeviceStateProvider,
2999
+ setDeviceToolContextProvider: () => setDeviceToolContextProvider,
3000
+ setDeviceToolResultPoster: () => setDeviceToolResultPoster,
3001
+ setHapticsProvider: () => setHapticsProvider,
3002
+ setPluginNativeProvider: () => setPluginNativeProvider,
3003
+ setPluginThemeColors: () => setPluginThemeColors,
3004
+ setVoiceProvider: () => setVoiceProvider,
3005
+ supportsNative: () => supportsNative,
3006
+ supportsWebView: () => supportsWebView,
3007
+ syncDeviceToolsForSession: () => syncDeviceToolsForSession,
3008
+ unregisterDeviceTools: () => unregisterDeviceTools,
3009
+ unregisterNativePlugin: () => unregisterNativePlugin,
3010
+ useApi: () => useApi,
3011
+ useNativeBridge: () => useNativeBridge,
3012
+ usePlugin: () => usePlugin,
3013
+ usePluginTheme: () => usePluginTheme,
3014
+ useRemotePlugins: () => useRemotePlugins,
3015
+ useSurfaceStore: () => useSurfaceStore
3016
+ });
3017
+ module.exports = __toCommonJS(index_exports);
3018
+ init_sdk();
3019
+
3020
+ // src/sdk/device-state.ts
3021
+ var device_state_exports = {};
3022
+ __export(device_state_exports, {
3023
+ DeviceBridgeError: () => DeviceBridgeError,
3024
+ explainCapability: () => explainCapability,
3025
+ isActionable: () => isActionable,
3026
+ neverObserved: () => neverObserved
3027
+ });
3028
+ var DeviceBridgeError = class extends Error {
3029
+ constructor(code, message, opts) {
3030
+ super(message);
3031
+ this.name = "DeviceBridgeError";
3032
+ this.code = code;
3033
+ this.domain = opts?.domain;
3034
+ this.recoverable = opts?.recoverable;
3035
+ }
3036
+ };
3037
+ var CONFIDENCE_ORDER = {
3038
+ unknown: 0,
3039
+ low: 1,
3040
+ medium: 2,
3041
+ high: 3
3042
+ };
3043
+ function isActionable(quality, budget) {
3044
+ const { freshness, confidence } = quality;
3045
+ if (freshness.state === "unknown" || freshness.state === "stale") return false;
3046
+ if (freshness.ageMs === null || freshness.ageMs > budget.maxAgeMs) return false;
3047
+ if (budget.minConfidence) {
3048
+ const have = CONFIDENCE_ORDER[confidence?.level ?? "unknown"];
3049
+ if (have < CONFIDENCE_ORDER[budget.minConfidence]) return false;
3050
+ }
3051
+ return true;
2037
3052
  }
2038
- function createNativeBridge(pluginId, connectorId) {
2039
- const commandListeners = /* @__PURE__ */ new Set();
3053
+ function neverObserved() {
3054
+ return { state: "unknown", observedAt: null, ageMs: null };
3055
+ }
3056
+ function explainCapability(status) {
3057
+ if (status.support === "unsupported") return "this device does not have it";
3058
+ if (status.permission === "denied") return "permission was denied";
3059
+ if (status.permission === "restricted") return "permission is restricted";
3060
+ if (status.permission === "notDetermined") return "permission has not been asked for yet";
3061
+ if (status.permission === "limited") return "only limited access was granted";
3062
+ if (status.restriction === "policyRestricted") return "this plugin did not declare it";
3063
+ if (status.restriction === "backgroundRestricted") return "the app is in the background";
3064
+ if (status.restriction === "lowPowerRestricted") return "the device is in low-power mode";
3065
+ if (status.availability === "busy") return "it is in use by something else";
3066
+ if (status.availability === "temporarilyUnavailable") return "it is temporarily unavailable";
3067
+ if (status.availability === "unavailable") return "it is unavailable";
3068
+ if (status.support === "unknown" || status.availability === "unknown") {
3069
+ return "its state could not be determined";
3070
+ }
3071
+ return null;
3072
+ }
3073
+
3074
+ // src/index.ts
3075
+ init_deviceStateHost();
3076
+
3077
+ // src/voiceHost.ts
3078
+ var _provider2 = null;
3079
+ function setVoiceProvider(provider) {
3080
+ _provider2 = provider || null;
3081
+ }
3082
+ function implementedVoice() {
3083
+ return !!_provider2;
3084
+ }
3085
+ function voiceDenial(why) {
3086
+ const err = new Error(`voice: ${why}`);
3087
+ err.code = "RESTRICTED";
3088
+ return err;
3089
+ }
3090
+ function deniedVoice(why) {
3091
+ const fail = () => {
3092
+ throw voiceDenial(why);
3093
+ };
2040
3094
  return {
2041
- onInit(cb) {
2042
- setTimeout(() => {
2043
- cb(buildContext(pluginId, connectorId));
2044
- }, 0);
3095
+ async start() {
3096
+ return fail();
2045
3097
  },
2046
- async mcpCall(tool, args, cId) {
2047
- const targetConnector = cId || connectorId;
2048
- if (!targetConnector) {
2049
- throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
2050
- }
2051
- const data = await (0, import_core7.connectorCall)(targetConnector, tool, args);
2052
- if (!data.ok && data.error) throw new Error(data.error);
2053
- return (0, import_core7.unwrapMcpResult)(data.result);
3098
+ stop() {
3099
+ fail();
2054
3100
  },
2055
- async mcpFetch(body) {
2056
- return (0, import_core7.mcpFetch)(body);
3101
+ setMuted() {
3102
+ fail();
2057
3103
  },
2058
- sendCommand(_type, _payload) {
3104
+ prompt() {
3105
+ fail();
2059
3106
  },
2060
- onCommand(cb) {
2061
- commandListeners.add(cb);
2062
- return () => {
2063
- commandListeners.delete(cb);
2064
- };
3107
+ observe() {
3108
+ fail();
2065
3109
  },
2066
- close() {
2067
- import_core7.pluginCommandEmitter.emit("plugin-close", { pluginId });
3110
+ getSnapshot() {
3111
+ return {
3112
+ state: "error",
3113
+ sessionId: null,
3114
+ muted: false,
3115
+ speaking: false,
3116
+ listening: false,
3117
+ userSpeaking: false,
3118
+ lastUserSpeechEndedAt: null,
3119
+ lastError: why
3120
+ };
2068
3121
  },
2069
- sendMessage(text) {
2070
- const t = (text || "").trim();
2071
- if (!t) return;
2072
- try {
2073
- import_core7.useChatStore.getState().sendMessage(t);
2074
- } catch (err) {
2075
- console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
2076
- }
3122
+ subscribe() {
3123
+ return fail();
2077
3124
  },
2078
- prepareMessage(text) {
2079
- const t = (text || "").trim();
2080
- if (!t) return;
2081
- import_core7.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
3125
+ transcript() {
3126
+ return [];
2082
3127
  }
2083
3128
  };
2084
3129
  }
3130
+ function scopedVoice(provider, pluginId) {
3131
+ return {
3132
+ start: (opts) => provider.start(opts, pluginId),
3133
+ stop: () => provider.stop(pluginId),
3134
+ setMuted: (muted) => provider.setMuted(muted, pluginId),
3135
+ prompt: (input) => provider.prompt(input, pluginId),
3136
+ observe: (text) => provider.observe(text, pluginId),
3137
+ getSnapshot: () => provider.getSnapshot(pluginId),
3138
+ subscribe: (cb) => provider.subscribe(cb, pluginId),
3139
+ transcript: () => provider.transcript(pluginId)
3140
+ };
3141
+ }
3142
+ function gateVoice(declared, pluginId) {
3143
+ if (!declared) {
3144
+ return deniedVoice(
3145
+ "not declared in this plugin's capabilities. Add `capabilities: { voice: true }` to use it."
3146
+ );
3147
+ }
3148
+ if (!pluginId) {
3149
+ return deniedVoice(
3150
+ "no plugin identity \u2014 voice fails closed without a caller to scope the session to."
3151
+ );
3152
+ }
3153
+ return _provider2 ? scopedVoice(_provider2, pluginId) : deniedVoice(
3154
+ "no voice session in this build \u2014 the app has not mounted a VoiceSessionHost / called setVoiceProvider."
3155
+ );
3156
+ }
3157
+
3158
+ // src/index.ts
3159
+ init_host();
3160
+
3161
+ // src/pluginRuntime.ts
3162
+ init_host();
3163
+ init_deviceStateHost();
3164
+ function mergeCaps(decl) {
3165
+ if (!decl) return void 0;
3166
+ return { ...decl.native, ...decl.capabilities };
3167
+ }
3168
+ function resolvePluginRuntimeProps(src) {
3169
+ const trusted = src.manifest ?? src.plugin ?? null;
3170
+ const capabilities = mergeCaps(trusted);
3171
+ const stateDomains = trusted?.stateDomains;
3172
+ const native = gatePluginNative(getPluginNative(), capabilities, src.pluginId);
3173
+ const deviceState = gateDeviceState(stateDomains);
3174
+ const voice = gateVoice(!!capabilities?.voice, src.pluginId);
3175
+ return { device: native, native, deviceState, voice };
3176
+ }
3177
+
3178
+ // src/index.ts
3179
+ init_registry();
3180
+ init_loader();
3181
+
3182
+ // src/tools/platformTools.ts
3183
+ init_registry();
3184
+ var PLATFORM_PLUGIN_ID = "platform:device-tools";
3185
+ function registerPlatformDeviceTools() {
3186
+ registerDeviceTools(PLATFORM_PLUGIN_ID, [
3187
+ {
3188
+ name: "device.echo",
3189
+ description: "Echo a string back from the user's own device.",
3190
+ input_schema: {
3191
+ type: "object",
3192
+ properties: { text: { type: "string" } },
3193
+ required: ["text"]
3194
+ },
3195
+ run: (args) => ({
3196
+ ok: true,
3197
+ echoed: args && args.text || "",
3198
+ // The proof: a server-side implementation could not say this truthfully.
3199
+ ran_on: "device"
3200
+ })
3201
+ },
3202
+ {
3203
+ name: "device.ping",
3204
+ description: "Check that the device is reachable.",
3205
+ input_schema: { type: "object", properties: {} },
3206
+ run: () => ({ ok: true, ran_on: "device", at: (/* @__PURE__ */ new Date()).toISOString() })
3207
+ }
3208
+ ]);
3209
+ }
3210
+
3211
+ // src/tools/sessionSync.ts
3212
+ init_registry();
3213
+ function deviceToolsActionForSession(next, prevTenant2) {
3214
+ const tenant = next.authenticated ? next.tenant ?? null : null;
3215
+ if (tenant && tenant !== prevTenant2) return { action: "load", tenant };
3216
+ if (!tenant && prevTenant2) return { action: "reset", tenant: null };
3217
+ return { action: "none", tenant: prevTenant2 };
3218
+ }
3219
+ var REAL_DEPS = {
3220
+ clear: clearDeviceTools,
3221
+ registerPlatform: registerPlatformDeviceTools,
3222
+ // `./loader` reaches react-native via the bundle manager; import it only when
3223
+ // an effect actually fires (real app), never at module load (tests).
3224
+ resetLoader: () => {
3225
+ void Promise.resolve().then(() => (init_loader(), loader_exports)).then((m) => m.resetDeviceToolLoader());
3226
+ },
3227
+ load: () => {
3228
+ void Promise.resolve().then(() => (init_loader(), loader_exports)).then((m) => {
3229
+ m.resetDeviceToolLoader();
3230
+ return m.ensureDeviceToolsLoaded({ force: true });
3231
+ });
3232
+ }
3233
+ };
3234
+ var prevTenant = null;
3235
+ function syncDeviceToolsForSession(next, deps = REAL_DEPS) {
3236
+ const { action, tenant } = deviceToolsActionForSession(next, prevTenant);
3237
+ prevTenant = tenant;
3238
+ if (action === "load") {
3239
+ deps.clear();
3240
+ deps.registerPlatform();
3241
+ deps.load();
3242
+ } else if (action === "reset") {
3243
+ deps.resetLoader();
3244
+ }
3245
+ return action;
3246
+ }
3247
+ function __resetSessionSyncForTest() {
3248
+ prevTenant = null;
3249
+ }
3250
+
3251
+ // src/tools/serverCalls.ts
3252
+ init_registry();
3253
+ init_loader();
3254
+ var postResult = null;
3255
+ function setDeviceToolResultPoster(fn) {
3256
+ postResult = fn;
3257
+ }
3258
+ function isDeviceToolCommand(cmd) {
3259
+ return !!cmd && cmd.kind === "device_tool" && typeof cmd.tool === "string" && !!cmd.correlationId;
3260
+ }
3261
+ async function handleServerDeviceToolCall(cmd) {
3262
+ if (!isDeviceToolCommand(cmd)) return;
3263
+ const { tool, args, correlationId } = cmd;
3264
+ if (!postResult) {
3265
+ console.warn("[deviceTools] server called a tool but no result poster is installed");
3266
+ return;
3267
+ }
3268
+ try {
3269
+ await ensureDeviceToolsLoaded();
3270
+ const outcome = await callDeviceTool(tool, args || {});
3271
+ console.log(`[deviceTools] server-called ${tool} \u2192 ${outcome.ok ? "ok" : outcome.reason}`);
3272
+ await postResult(
3273
+ correlationId,
3274
+ outcome.ok ? { ok: true, ...unwrap(outcome.result) } : void 0,
3275
+ outcome.ok ? void 0 : outcome.error
3276
+ );
3277
+ } catch (err) {
3278
+ await postResult(correlationId, void 0, err?.message || String(err)).catch(() => {
3279
+ });
3280
+ }
3281
+ }
3282
+ function unwrap(result) {
3283
+ if (result && typeof result === "object" && !Array.isArray(result)) return result;
3284
+ return { result };
3285
+ }
3286
+
3287
+ // src/index.ts
3288
+ init_bundleManager();
3289
+ init_storage();
3290
+ init_native();
2085
3291
 
2086
3292
  // src/hooks/usePlugin.ts
2087
3293
  var import_react11 = require("react");
3294
+ init_bundleManager();
3295
+ init_host();
2088
3296
  function extractBundleUrl(pluginId, manifest) {
2089
3297
  const render = manifest.render;
2090
3298
  if (!render || typeof render !== "object") return { url: null, source: "none" };
@@ -2187,7 +3395,7 @@ function usePlugin(pluginId, manifest) {
2187
3395
 
2188
3396
  // src/hooks/useRemotePlugins.ts
2189
3397
  var import_react12 = require("react");
2190
- var import_core8 = require("@ateam-ai-mobile/core");
3398
+ var import_core11 = require("@ateam-ai-mobile/core");
2191
3399
  function useRemotePlugins(solutionId) {
2192
3400
  const [plugins, setPlugins] = (0, import_react12.useState)([]);
2193
3401
  const [loading, setLoading] = (0, import_react12.useState)(true);
@@ -2199,7 +3407,7 @@ function useRemotePlugins(solutionId) {
2199
3407
  const targetSolutionId = solutionId || "current";
2200
3408
  let data;
2201
3409
  try {
2202
- data = await (0, import_core8.apiFetch)(`/api/solutions/${targetSolutionId}/ui-plugins`);
3410
+ data = await (0, import_core11.apiFetch)(`/api/solutions/${targetSolutionId}/ui-plugins`);
2203
3411
  } catch (err) {
2204
3412
  if (err?.message?.includes("404")) {
2205
3413
  console.warn(`[useRemotePlugins] Solution ${targetSolutionId} not found or has no plugins`);
@@ -2255,108 +3463,14 @@ function useRemotePlugins(solutionId) {
2255
3463
  };
2256
3464
  }
2257
3465
 
2258
- // src/api/plugins.ts
2259
- var import_core9 = require("@ateam-ai-mobile/core");
2260
- var import_core10 = require("@ateam-ai-mobile/core");
2261
- function supportsNative(manifest) {
2262
- const mode = manifest.render?.mode;
2263
- return mode === "react-native" || mode === "adaptive";
2264
- }
2265
- function getNativeComponentId(manifest) {
2266
- if (manifest.render?.mode === "react-native") return manifest.render.component;
2267
- if (manifest.render?.mode === "adaptive") return manifest.render.reactNative?.component;
2268
- return null;
2269
- }
2270
- function getNativeBundleUrl(manifest) {
2271
- const direct = manifest.bundleUrl;
2272
- if (direct) return direct;
2273
- const render = manifest.render;
2274
- if (render.mode === "react-native") return render.bundleUrl || null;
2275
- if (render.mode === "adaptive") return render.reactNative?.bundleUrl || null;
2276
- return null;
2277
- }
2278
- function getNativeBundleHash(manifest) {
2279
- const direct = manifest.bundleHash;
2280
- if (direct) return direct;
2281
- const render = manifest.render;
2282
- if (render.mode === "react-native") return render.bundleHash || null;
2283
- if (render.mode === "adaptive") return render.reactNative?.bundleHash || null;
2284
- return null;
2285
- }
2286
- function supportsWebView(manifest) {
2287
- return manifest.render?.mode === "iframe" || manifest.render?.mode === "adaptive";
2288
- }
2289
- async function listPlugins() {
2290
- const res = await (0, import_core9.apiFetch)("/mcp", {
2291
- method: "POST",
2292
- body: JSON.stringify({
2293
- jsonrpc: "2.0",
2294
- id: Date.now(),
2295
- method: "tools/call",
2296
- params: {
2297
- name: "cp.listContextPlugins",
2298
- arguments: {}
2299
- }
2300
- })
2301
- });
2302
- const structured = res.result?.structuredContent?.plugins;
2303
- if (structured) return structured;
2304
- const text = res.result?.content?.[0]?.text;
2305
- if (!text) return [];
2306
- try {
2307
- const parsed = JSON.parse(text);
2308
- return parsed.plugins || parsed || [];
2309
- } catch {
2310
- return [];
2311
- }
2312
- }
2313
- async function getPlugin(pluginId) {
2314
- const res = await (0, import_core9.apiFetch)("/mcp", {
2315
- method: "POST",
2316
- body: JSON.stringify({
2317
- jsonrpc: "2.0",
2318
- id: Date.now(),
2319
- method: "tools/call",
2320
- params: {
2321
- name: "cp.getContextPlugin",
2322
- arguments: { id: pluginId }
2323
- // Backend expects { id }, NOT { pluginId }
2324
- }
2325
- })
2326
- });
2327
- if (res.result?.structuredContent) return res.result.structuredContent;
2328
- const text = res.result?.content?.[0]?.text;
2329
- if (!text) return null;
2330
- try {
2331
- const parsed = JSON.parse(text);
2332
- if (parsed.error) {
2333
- console.warn("[getPlugin] Backend error:", parsed.error);
2334
- return null;
2335
- }
2336
- return parsed;
2337
- } catch {
2338
- return null;
2339
- }
2340
- }
2341
- function resolvePluginUrl(manifest, tenant) {
2342
- const render = manifest.render;
2343
- let url;
2344
- if (render?.mode === "iframe") {
2345
- url = render.iframeUrl;
2346
- } else if (render?.mode === "adaptive") {
2347
- url = render.iframeUrl || render.iframe?.iframeUrl;
2348
- }
2349
- if (!url) return "";
2350
- if (url.startsWith("http")) {
2351
- const sep = url.includes("?") ? "&" : "?";
2352
- return tenant ? `${url}${sep}tenant=${encodeURIComponent(tenant)}` : url;
2353
- }
2354
- if (tenant && url.startsWith("/mcp-ui/")) {
2355
- const tenantUrl = url.replace("/mcp-ui/", `/mcp-ui/${encodeURIComponent(tenant)}/`);
2356
- return `${(0, import_core10.getCoreApiUrl)()}${tenantUrl}`;
2357
- }
2358
- return `${(0, import_core10.getCoreApiUrl)()}${url}`;
2359
- }
3466
+ // src/index.ts
3467
+ init_plugins();
3468
+ init_SurfaceDrawer();
3469
+ init_SurfaceCard();
3470
+ init_SurfaceFullscreen();
3471
+ init_AdaOrb();
3472
+ init_store();
3473
+ init_SurfaceDispatcher();
2360
3474
  // Annotate the CommonJS export names for ESM import in node:
2361
3475
  0 && (module.exports = {
2362
3476
  ActionButton,
@@ -2364,6 +3478,7 @@ function resolvePluginUrl(manifest, tenant) {
2364
3478
  AdaOrb,
2365
3479
  Animated,
2366
3480
  Badge,
3481
+ CameraPreview,
2367
3482
  Card,
2368
3483
  EmptyState,
2369
3484
  ErrorState,
@@ -2373,6 +3488,7 @@ function resolvePluginUrl(manifest, tenant) {
2373
3488
  LoadingState,
2374
3489
  MemoryBundleStorage,
2375
3490
  Modal,
3491
+ PLATFORM_VOICE_CALLER,
2376
3492
  Platform,
2377
3493
  PluginSDK,
2378
3494
  Pressable,
@@ -2391,30 +3507,60 @@ function resolvePluginUrl(manifest, tenant) {
2391
3507
  TextInput,
2392
3508
  TouchableOpacity,
2393
3509
  View,
3510
+ __resetSessionSyncForTest,
2394
3511
  bundleManager,
3512
+ callDeviceTool,
3513
+ clearDeviceTools,
2395
3514
  createNativeBridge,
3515
+ deviceStateV2,
3516
+ deviceToolsActionForSession,
3517
+ ensureDeviceToolsLoaded,
3518
+ explainCapability,
3519
+ gateDeviceState,
2396
3520
  gatePluginNative,
3521
+ gateVoice,
3522
+ getCameraPreviewProvider,
2397
3523
  getHaptics,
2398
3524
  getNativeBundleHash,
2399
3525
  getNativeBundleUrl,
2400
3526
  getNativeComponentId,
2401
3527
  getPlugin,
2402
3528
  getPluginNative,
3529
+ handleServerDeviceToolCall,
3530
+ hasDeviceTool,
3531
+ implementedStateDomains,
3532
+ implementedVoice,
3533
+ isActionable,
3534
+ isDeviceToolCommand,
2403
3535
  isInlineSurface,
2404
3536
  isMountSurface,
3537
+ listDeviceTools,
2405
3538
  listPlugins,
2406
3539
  nativePluginRegistry,
3540
+ neverObserved,
2407
3541
  register,
2408
3542
  registerBundleModules,
3543
+ registerDeviceTools,
2409
3544
  registerNativePlugin,
3545
+ registerPlatformDeviceTools,
2410
3546
  registerService,
3547
+ registerTools,
3548
+ resetDeviceToolLoader,
3549
+ resolvePluginRuntimeProps,
2411
3550
  resolvePluginUrl,
2412
3551
  setBundleStorage,
3552
+ setCameraPreviewProvider,
3553
+ setDeviceStateProvider,
3554
+ setDeviceToolContextProvider,
3555
+ setDeviceToolResultPoster,
2413
3556
  setHapticsProvider,
2414
3557
  setPluginNativeProvider,
2415
3558
  setPluginThemeColors,
3559
+ setVoiceProvider,
2416
3560
  supportsNative,
2417
3561
  supportsWebView,
3562
+ syncDeviceToolsForSession,
3563
+ unregisterDeviceTools,
2418
3564
  unregisterNativePlugin,
2419
3565
  useApi,
2420
3566
  useNativeBridge,