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

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,272 @@ 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
+ maps: caps.maps ? native.maps : blocked("maps")
400
+ // What this plugin DECLARED, carried on the object it already receives.
401
+ //
402
+ // It is how a COMPONENT can be capability-gated: <CameraPreview> is handed
403
+ // `native` and reads this, so showing the camera obeys the same flag as
404
+ // calling it. Without this the preview would be a second door to the camera
405
+ // with only one lock. Non-enumerable so it cannot be mistaken for an API.
406
+ };
407
+ Object.defineProperty(gated, "__declaredCapabilities", {
408
+ value: Object.freeze({ ...caps }),
409
+ enumerable: false,
410
+ writable: false
411
+ });
412
+ return gated;
413
+ }
414
+ function setCameraPreviewProvider(Component) {
415
+ _cameraPreview = Component;
416
+ }
417
+ function getCameraPreviewProvider() {
418
+ return _cameraPreview;
419
+ }
420
+ function registerNativePlugin(id, plugin) {
421
+ _nativeRegistry.set(id, plugin);
422
+ }
423
+ function unregisterNativePlugin(id) {
424
+ _nativeRegistry.delete(id);
425
+ }
426
+ function registerBundleModules(modules) {
427
+ for (const [name, mod] of Object.entries(modules)) {
428
+ _bundleModules.set(name, mod);
429
+ }
430
+ }
431
+ function resolveBundleModule(name) {
432
+ return _bundleModules.get(name);
433
+ }
434
+ var _haptics, noopNative, _pluginNative, _cameraPreview, _nativeRegistry, nativePluginRegistry, _bundleModules;
435
+ var init_host = __esm({
436
+ "src/host.ts"() {
437
+ "use strict";
438
+ _haptics = null;
439
+ noopNative = {
440
+ camera: unavailable("camera"),
441
+ location: unavailable("location"),
442
+ storage: unavailable("storage"),
443
+ biometrics: unavailable("biometrics"),
444
+ biometricsV2: unavailable("biometricsV2"),
445
+ notifications: unavailable("notifications"),
446
+ haptics: { impact: () => {
447
+ }, notification: () => {
448
+ }, selection: () => {
449
+ }, error: () => {
450
+ } },
451
+ connectivity: { isOnline: () => true, onChange: () => () => {
452
+ } },
453
+ fileSystem: unavailable("fileSystem"),
454
+ cookies: unavailable("cookies"),
455
+ sharing: unavailable("sharing"),
456
+ scanner: unavailable("scanner"),
457
+ nfc: unavailable("nfc"),
458
+ bluetooth: unavailable("bluetooth"),
459
+ vision: unavailable("vision"),
460
+ maps: unavailable("maps")
461
+ };
462
+ _pluginNative = noopNative;
463
+ _cameraPreview = null;
464
+ _nativeRegistry = /* @__PURE__ */ new Map();
465
+ nativePluginRegistry = {
466
+ has(id) {
467
+ return _nativeRegistry.has(id);
468
+ },
469
+ async get(id) {
470
+ const entry = _nativeRegistry.get(id);
471
+ if (!entry) return null;
472
+ if (typeof entry === "function") {
473
+ try {
474
+ return await entry();
475
+ } catch (err) {
476
+ console.warn(`[adas-plugins] lazy native plugin '${id}' failed to load:`, err);
477
+ return null;
478
+ }
479
+ }
480
+ return entry;
481
+ }
482
+ };
483
+ _bundleModules = /* @__PURE__ */ new Map();
484
+ }
485
+ });
486
+
487
+ // src/sdk/CameraPreview.tsx
488
+ function declaredCapabilities(native) {
489
+ return native && native.__declaredCapabilities || null;
490
+ }
491
+ function CameraPreview({ native, facing = "back", style, renderUnavailable }) {
492
+ const caps = declaredCapabilities(native);
493
+ if (caps && !caps.camera) {
494
+ return unavailable2(
495
+ "this plugin did not declare `capabilities: { camera: true }`, so it may not show the camera",
496
+ renderUnavailable,
497
+ style
498
+ );
499
+ }
500
+ const Provider = getCameraPreviewProvider();
501
+ if (!Provider) {
502
+ return unavailable2(
503
+ "this app does not provide a camera preview (the host has not called setCameraPreviewProvider)",
504
+ renderUnavailable,
505
+ style
506
+ );
507
+ }
508
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Provider, { facing, style, renderUnavailable });
509
+ }
510
+ function unavailable2(reason, render, style) {
511
+ if (render) return render(reason);
512
+ 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
513
+ ${reason}` }) });
514
+ }
515
+ var import_jsx_runtime;
516
+ var init_CameraPreview = __esm({
517
+ "src/sdk/CameraPreview.tsx"() {
518
+ "use strict";
519
+ init_primitives();
520
+ init_host();
521
+ import_jsx_runtime = require("react/jsx-runtime");
409
522
  }
410
523
  });
411
524
 
412
525
  // 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
526
  function setPluginThemeColors(colors, opts) {
440
527
  _colors = { ...adaColors, ...colors };
441
528
  if (opts?.isDark !== void 0) _isDark = opts.isDark;
@@ -478,9 +565,40 @@ function usePluginTheme() {
478
565
  isDark
479
566
  }), [colors, isDark]);
480
567
  }
568
+ var import_react2, import_react_native3, import_core, adaColors, _colors, _isDark, _subscribers;
569
+ var init_usePluginTheme = __esm({
570
+ "src/sdk/usePluginTheme.ts"() {
571
+ "use strict";
572
+ import_react2 = require("react");
573
+ import_react_native3 = require("react-native");
574
+ import_core = require("@ateam-ai-mobile/core");
575
+ adaColors = {
576
+ bg: import_core.ada.paper,
577
+ surface: import_core.ada.paperSoft,
578
+ surfaceAlt: import_core.ada.paperDeep,
579
+ input: import_core.ada.paper,
580
+ border: import_core.ada.border,
581
+ text: import_core.ada.ink,
582
+ textSecondary: import_core.ada.inkSoft,
583
+ textMuted: import_core.ada.inkMuted,
584
+ accent: import_core.ada.orange,
585
+ accentSoft: import_core.ada.orangeSoft,
586
+ success: "#5A8A5C",
587
+ successSoft: "rgba(90,138,92,0.14)",
588
+ warning: "#D49A4E",
589
+ warningSoft: "rgba(212,154,78,0.16)",
590
+ error: "#C15545",
591
+ errorSoft: "rgba(193,85,69,0.14)",
592
+ purple: import_core.ada.orangeDeep,
593
+ purpleSoft: import_core.ada.orangeSoft
594
+ };
595
+ _colors = adaColors;
596
+ _isDark = null;
597
+ _subscribers = /* @__PURE__ */ new Set();
598
+ }
599
+ });
481
600
 
482
601
  // src/sdk/useApi.ts
483
- var import_react3 = require("react");
484
602
  function useApi(bridge) {
485
603
  return (0, import_react3.useMemo)(() => {
486
604
  const apiFn = async (connectorId, tool, args = {}) => {
@@ -509,131 +627,767 @@ function useApi(bridge) {
509
627
  return apiFn;
510
628
  }, [bridge]);
511
629
  }
630
+ var import_react3;
631
+ var init_useApi = __esm({
632
+ "src/sdk/useApi.ts"() {
633
+ "use strict";
634
+ import_react3 = require("react");
635
+ }
636
+ });
512
637
 
513
638
  // 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 || {};
639
+ var sdk_exports = {};
640
+ __export(sdk_exports, {
641
+ ActionButton: () => ActionButton,
642
+ ActivityIndicator: () => import_react_native.ActivityIndicator,
643
+ Animated: () => import_react_native.Animated,
644
+ Badge: () => Badge,
645
+ CameraPreview: () => CameraPreview,
646
+ Card: () => Card,
647
+ EmptyState: () => EmptyState,
648
+ ErrorState: () => ErrorState,
649
+ FlatList: () => import_react_native.FlatList,
650
+ Image: () => import_react_native.Image,
651
+ ListItem: () => ListItem,
652
+ LoadingState: () => LoadingState,
653
+ Modal: () => import_react_native.Modal,
654
+ PLATFORM_VOICE_CALLER: () => PLATFORM_VOICE_CALLER,
655
+ Platform: () => import_react_native.Platform,
656
+ PluginSDK: () => PluginSDK,
657
+ Pressable: () => import_react_native.Pressable,
658
+ RefreshControl: () => import_react_native.RefreshControl,
659
+ ScrollView: () => import_react_native.ScrollView,
660
+ SearchBar: () => SearchBar,
661
+ SectionList: () => import_react_native.SectionList,
662
+ StyleSheet: () => import_react_native.StyleSheet,
663
+ Switch: () => import_react_native.Switch,
664
+ TabBar: () => TabBar,
665
+ Text: () => import_react_native.Text,
666
+ TextInput: () => import_react_native.TextInput,
667
+ TouchableOpacity: () => import_react_native.TouchableOpacity,
668
+ View: () => import_react_native.View,
669
+ register: () => register,
670
+ registerService: () => registerService,
671
+ registerTools: () => registerTools,
672
+ setPluginThemeColors: () => setPluginThemeColors,
673
+ useApi: () => useApi,
674
+ usePluginTheme: () => usePluginTheme
675
+ });
676
+ var PluginSDK;
677
+ var init_sdk = __esm({
678
+ "src/sdk/index.ts"() {
679
+ "use strict";
680
+ init_registration();
681
+ init_registration();
682
+ init_voice();
683
+ init_primitives();
684
+ init_components();
685
+ init_CameraPreview();
686
+ init_usePluginTheme();
687
+ init_useApi();
688
+ PluginSDK = { register, registerService, registerTools };
689
+ }
690
+ });
691
+
692
+ // src/deviceStateHost.ts
693
+ function deniedSnapshot(domain) {
694
+ const base = { quality: UNKNOWN_QUALITY };
695
+ switch (domain) {
696
+ case "spatial":
697
+ return {
698
+ ...base,
699
+ position: null,
700
+ movement: { speedMps: null, bearingDeg: null, distanceRecentM: null, state: "unknown" },
701
+ orientation: { headingDeg: null },
702
+ recentPath: []
703
+ };
704
+ case "visual":
705
+ return {
706
+ ...base,
707
+ camera: { available: false, active: false, facing: "unknown" },
708
+ latest: null,
709
+ recentTimeline: { windowMs: 0, frames: [] }
710
+ };
711
+ case "deviceContext":
712
+ return {
713
+ ...base,
714
+ power: { batteryLevel: null, charging: null, lowPowerMode: null },
715
+ thermal: { state: "unknown" },
716
+ resources: { memoryPressure: "unknown", storagePressure: "unknown" }
717
+ };
718
+ case "connectivity":
719
+ return {
720
+ ...base,
721
+ reachability: "unknown",
722
+ transport: "unknown",
723
+ qualityClass: "unknown",
724
+ stability: "unknown",
725
+ latencyMs: null
726
+ };
727
+ case "interaction":
728
+ return {
729
+ ...base,
730
+ lifecycle: { appState: "unknown", screenOn: null },
731
+ audio: { captureActive: null, playbackActive: null, route: "unknown", interruption: "unknown" }
732
+ };
733
+ case "activity":
734
+ return {
735
+ ...base,
736
+ // `confidence` is REQUIRED on both blocks in the freeze, precisely
737
+ // because an inferred activity must never be handed over without
738
+ // saying how sure the inference is. An empty snapshot is the least
739
+ // sure of all.
740
+ human: { state: "unknown", since: null, confidence: { level: "unknown" } },
741
+ devicePose: { placement: "unknown", orientation: "unknown", confidence: { level: "unknown" } },
742
+ recentTransitions: []
743
+ };
744
+ case "environment":
745
+ return {
746
+ ...base,
747
+ lighting: { level: "unknown", lux: null },
748
+ acoustics: { level: "unknown", approximateDb: null },
749
+ // 'unknown' rather than 'uncertain': uncertain means we looked and
750
+ // could not decide, and nothing looked at all here.
751
+ enclosure: { state: "unknown" },
752
+ proximity: { distanceClass: "unknown" },
753
+ nearbySignals: { bleAvailable: false, beaconCount: null, uwbAvailable: false }
754
+ };
755
+ case "capability": {
756
+ const unknownStatus = { support: "unknown", permission: "unknown", availability: "unknown" };
757
+ return {
758
+ ...base,
759
+ camera: unknownStatus,
760
+ microphone: unknownStatus,
761
+ location: { ...unknownStatus, precision: "unknown", backgroundPermission: "unknown" },
762
+ motion: unknownStatus,
763
+ bluetooth: unknownStatus,
764
+ uwb: unknownStatus,
765
+ nfc: unknownStatus,
766
+ notifications: unknownStatus,
767
+ health: unknownStatus,
768
+ backgroundExecution: unknownStatus
769
+ };
770
+ }
771
+ default:
772
+ return base;
773
+ }
774
+ }
775
+ function deniedDomain(domain, why) {
567
776
  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")
777
+ async getSnapshot() {
778
+ return deniedSnapshot(domain);
779
+ },
780
+ async start() {
781
+ throw denial(domain, why);
782
+ },
783
+ async stop() {
784
+ throw denial(domain, why);
785
+ },
786
+ subscribe() {
787
+ throw denial(domain, why);
788
+ }
789
+ };
790
+ }
791
+ function denial(domain, why) {
792
+ const err = new Error(`deviceState.${domain}: ${why}`);
793
+ err.code = "RESTRICTED";
794
+ err.domain = domain;
795
+ return err;
796
+ }
797
+ function setDeviceStateProvider(state) {
798
+ _provider = state || {};
799
+ }
800
+ function gateDeviceState(declared) {
801
+ const allowed = new Set(declared || []);
802
+ const out = {};
803
+ for (const domain of ALL_DOMAINS) {
804
+ if (!allowed.has(domain)) {
805
+ out[domain] = deniedDomain(
806
+ domain,
807
+ `not declared in this plugin's stateDomains. Add '${domain}' to use it.`
808
+ );
809
+ continue;
810
+ }
811
+ const engine = _provider[domain];
812
+ out[domain] = engine ?? deniedDomain(
813
+ domain,
814
+ "no engine in this build \u2014 the native side does not exist yet. See docs/DEVICE_API_STATUS.md."
815
+ );
816
+ }
817
+ return out;
818
+ }
819
+ function withoutSubscribe(state) {
820
+ if (!state) return state;
821
+ const out = {};
822
+ for (const domain of ALL_DOMAINS) {
823
+ const d = state[domain];
824
+ out[domain] = d && Object.assign(Object.create(d), {
825
+ subscribe() {
826
+ throw denial(domain, "a device tool call has no lifetime to own a subscription. Subscribe from the plugin UI, or read getSnapshot() within the call.");
827
+ }
828
+ });
829
+ }
830
+ return out;
831
+ }
832
+ function implementedStateDomains() {
833
+ return ALL_DOMAINS.filter((d) => !!_provider[d]);
834
+ }
835
+ var UNKNOWN_QUALITY, ALL_DOMAINS, _provider;
836
+ var init_deviceStateHost = __esm({
837
+ "src/deviceStateHost.ts"() {
838
+ "use strict";
839
+ UNKNOWN_QUALITY = {
840
+ freshness: { state: "unknown", observedAt: null, ageMs: null },
841
+ confidence: { level: "unknown" }
842
+ };
843
+ ALL_DOMAINS = [
844
+ "spatial",
845
+ "visual",
846
+ "activity",
847
+ "deviceContext",
848
+ "connectivity",
849
+ "capability",
850
+ "environment",
851
+ "interaction"
852
+ ];
853
+ _provider = {};
854
+ }
855
+ });
856
+
857
+ // src/voiceHost.ts
858
+ function setVoiceProvider(provider) {
859
+ _provider2 = provider || null;
860
+ }
861
+ function implementedVoice() {
862
+ return !!_provider2;
863
+ }
864
+ function voiceDenial(why) {
865
+ const err = new Error(`voice: ${why}`);
866
+ err.code = "RESTRICTED";
867
+ return err;
868
+ }
869
+ function deniedVoice(why) {
870
+ const fail = () => {
871
+ throw voiceDenial(why);
872
+ };
873
+ return {
874
+ async start() {
875
+ return fail();
876
+ },
877
+ stop() {
878
+ fail();
879
+ },
880
+ setMuted() {
881
+ fail();
882
+ },
883
+ prompt() {
884
+ fail();
885
+ },
886
+ observe() {
887
+ fail();
888
+ },
889
+ getSnapshot() {
890
+ return {
891
+ state: "error",
892
+ sessionId: null,
893
+ muted: false,
894
+ speaking: false,
895
+ listening: false,
896
+ userSpeaking: false,
897
+ lastUserSpeechEndedAt: null,
898
+ lastDeviceTool: null,
899
+ authorizedSessionId: null,
900
+ lastError: why
901
+ };
902
+ },
903
+ subscribe() {
904
+ return fail();
905
+ },
906
+ transcript() {
907
+ return [];
908
+ }
909
+ };
910
+ }
911
+ function scopedVoice(provider, pluginId) {
912
+ return {
913
+ start: (opts) => provider.start(opts, pluginId),
914
+ stop: () => provider.stop(pluginId),
915
+ setMuted: (muted) => provider.setMuted(muted, pluginId),
916
+ prompt: (input) => provider.prompt(input, pluginId),
917
+ observe: (text) => provider.observe(text, pluginId),
918
+ getSnapshot: () => provider.getSnapshot(pluginId),
919
+ subscribe: (cb) => provider.subscribe(cb, pluginId),
920
+ transcript: () => provider.transcript(pluginId)
921
+ };
922
+ }
923
+ function gateVoice(declared, pluginId) {
924
+ if (!declared) {
925
+ return deniedVoice(
926
+ "not declared in this plugin's capabilities. Add `capabilities: { voice: true }` to use it."
927
+ );
928
+ }
929
+ if (!pluginId) {
930
+ return deniedVoice(
931
+ "no plugin identity \u2014 voice fails closed without a caller to scope the session to."
932
+ );
933
+ }
934
+ return _provider2 ? scopedVoice(_provider2, pluginId) : deniedVoice(
935
+ "no voice session in this build \u2014 the app has not mounted a VoiceSessionHost / called setVoiceProvider."
936
+ );
937
+ }
938
+ var _provider2;
939
+ var init_voiceHost = __esm({
940
+ "src/voiceHost.ts"() {
941
+ "use strict";
942
+ _provider2 = null;
943
+ }
944
+ });
945
+
946
+ // src/pluginRuntime.ts
947
+ function mergeCaps(decl) {
948
+ if (!decl) return void 0;
949
+ return { ...decl.native, ...decl.capabilities };
950
+ }
951
+ function resolvePluginGrants(src) {
952
+ const trusted = src.manifest ?? src.plugin ?? null;
953
+ const stateDomains = trusted?.stateDomains ? [...trusted.stateDomains] : void 0;
954
+ return { capabilities: mergeCaps(trusted), stateDomains };
955
+ }
956
+ function resolvePluginRuntimeProps(src) {
957
+ const { capabilities, stateDomains } = resolvePluginGrants(src);
958
+ const native = gatePluginNative(getPluginNative(), capabilities, src.pluginId);
959
+ const deviceState = gateDeviceState(stateDomains);
960
+ const voice = gateVoice(!!capabilities?.voice, src.pluginId);
961
+ return { device: native, native, deviceState, voice };
962
+ }
963
+ var init_pluginRuntime = __esm({
964
+ "src/pluginRuntime.ts"() {
965
+ "use strict";
966
+ init_host();
967
+ init_deviceStateHost();
968
+ init_voiceHost();
969
+ }
970
+ });
971
+
972
+ // src/bridge/native.ts
973
+ function buildContext(pluginId, connectorId) {
974
+ const user = (0, import_core2.getUser)();
975
+ return {
976
+ tenant: (0, import_core2.getTenant)(),
977
+ pluginId,
978
+ connectorId,
979
+ user: user ? { id: user.actorId || "", name: user.name || "", roles: user.roles || [] } : void 0
980
+ };
981
+ }
982
+ function useNativeBridge(pluginId, connectorId, commandBufferRef) {
983
+ const initCallbackRef = (0, import_react4.useRef)(null);
984
+ const commandListenersRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
985
+ const initializedRef = (0, import_react4.useRef)(false);
986
+ const fireInit = (0, import_react4.useCallback)(() => {
987
+ if (initializedRef.current || !initCallbackRef.current) return;
988
+ initCallbackRef.current(buildContext(pluginId, connectorId));
989
+ initializedRef.current = true;
990
+ }, [pluginId, connectorId]);
991
+ const onInit = (0, import_react4.useCallback)((cb) => {
992
+ initCallbackRef.current = cb;
993
+ setTimeout(fireInit, 0);
994
+ }, [fireInit]);
995
+ const mcpCall = (0, import_react4.useCallback)(async (toolOrConnector, argsOrTool, cIdOrArgs) => {
996
+ let tool;
997
+ let args;
998
+ let cId;
999
+ if (typeof argsOrTool === "string") {
1000
+ cId = toolOrConnector;
1001
+ tool = argsOrTool;
1002
+ args = cIdOrArgs || {};
1003
+ } else {
1004
+ tool = toolOrConnector;
1005
+ args = argsOrTool;
1006
+ cId = typeof cIdOrArgs === "string" ? cIdOrArgs : void 0;
1007
+ }
1008
+ const targetConnector = cId || connectorId;
1009
+ if (!targetConnector) {
1010
+ console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2014 no connectorId. tool=${tool}`);
1011
+ throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
1012
+ }
1013
+ try {
1014
+ const data = await (0, import_core2.connectorCall)(targetConnector, tool, args);
1015
+ if (!data.ok && data.error) {
1016
+ throw new Error(data.error);
1017
+ }
1018
+ return (0, import_core2.unwrapMcpResult)(data.result);
1019
+ } catch (err) {
1020
+ console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2192 connector=${targetConnector} tool=${tool}:`, err?.message || err);
1021
+ throw err;
1022
+ }
1023
+ }, [pluginId, connectorId]);
1024
+ const mcpFetch = (0, import_react4.useCallback)(async (body) => {
1025
+ return (0, import_core2.mcpFetch)(body);
1026
+ }, []);
1027
+ const sendCommand = (0, import_react4.useCallback)((type, payload) => {
1028
+ if (type === "plugin.command.result" && payload?.correlationId) {
1029
+ (0, import_core2.postCommandResult)(payload.correlationId, payload.result, payload.error);
1030
+ return;
1031
+ }
1032
+ }, [pluginId]);
1033
+ const onCommand = (0, import_react4.useCallback)((cb) => {
1034
+ commandListenersRef.current.add(cb);
1035
+ if (commandBufferRef) {
1036
+ const buf = commandBufferRef.current;
1037
+ const remaining = [];
1038
+ for (const cmd of buf) {
1039
+ if (cmd.pluginId === pluginId && cmd.command !== "__focus") {
1040
+ try {
1041
+ cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1042
+ } catch {
1043
+ }
1044
+ } else {
1045
+ remaining.push(cmd);
1046
+ }
1047
+ }
1048
+ commandBufferRef.current = remaining;
1049
+ }
1050
+ for (const cmd of import_core2.pluginCommandEmitter.drainPluginCommands(pluginId)) {
1051
+ try {
1052
+ cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1053
+ } catch {
1054
+ }
1055
+ }
1056
+ return () => {
1057
+ commandListenersRef.current.delete(cb);
1058
+ };
1059
+ }, [pluginId, commandBufferRef]);
1060
+ (0, import_react4.useEffect)(() => {
1061
+ const unsub = import_core2.pluginCommandEmitter.on("plugin-command", (cmd) => {
1062
+ if (cmd.pluginId !== pluginId) return;
1063
+ if (cmd.command === "__focus") return;
1064
+ commandListenersRef.current.forEach((fn) => {
1065
+ try {
1066
+ fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1067
+ } catch {
1068
+ }
1069
+ });
1070
+ });
1071
+ return unsub;
1072
+ }, [pluginId]);
1073
+ (0, import_react4.useEffect)(() => {
1074
+ const timer = setTimeout(() => {
1075
+ const dispatch = (cmd) => {
1076
+ commandListenersRef.current.forEach((fn) => {
1077
+ try {
1078
+ fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1079
+ } catch {
1080
+ }
1081
+ });
1082
+ };
1083
+ if (commandBufferRef) {
1084
+ const remaining = [];
1085
+ for (const cmd of commandBufferRef.current) {
1086
+ if (cmd.pluginId === pluginId && cmd.command !== "__focus") dispatch(cmd);
1087
+ else remaining.push(cmd);
1088
+ }
1089
+ commandBufferRef.current = remaining;
1090
+ }
1091
+ for (const cmd of import_core2.pluginCommandEmitter.drainPluginCommands(pluginId)) {
1092
+ dispatch(cmd);
1093
+ }
1094
+ }, 500);
1095
+ return () => clearTimeout(timer);
1096
+ }, [pluginId, commandBufferRef]);
1097
+ (0, import_react4.useEffect)(() => {
1098
+ return () => {
1099
+ commandListenersRef.current.clear();
1100
+ initializedRef.current = false;
1101
+ };
1102
+ }, []);
1103
+ const close = (0, import_react4.useCallback)(() => {
1104
+ import_core2.pluginCommandEmitter.emit("plugin-close", { pluginId });
1105
+ }, [pluginId]);
1106
+ const sendMessage = (0, import_react4.useCallback)((text) => {
1107
+ const t = (text || "").trim();
1108
+ if (!t) return;
1109
+ try {
1110
+ import_core2.useChatStore.getState().sendMessage(t);
1111
+ } catch (err) {
1112
+ console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
1113
+ }
1114
+ }, []);
1115
+ const prepareMessage = (0, import_react4.useCallback)((text) => {
1116
+ const t = (text || "").trim();
1117
+ if (!t) return;
1118
+ import_core2.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
1119
+ }, []);
1120
+ return (0, import_react4.useMemo)(
1121
+ () => ({ onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage }),
1122
+ [onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage]
1123
+ );
1124
+ }
1125
+ function createNativeBridge(pluginId, connectorId) {
1126
+ const commandListeners = /* @__PURE__ */ new Set();
1127
+ return {
1128
+ onInit(cb) {
1129
+ setTimeout(() => {
1130
+ cb(buildContext(pluginId, connectorId));
1131
+ }, 0);
1132
+ },
1133
+ async mcpCall(tool, args, cId) {
1134
+ const targetConnector = cId || connectorId;
1135
+ if (!targetConnector) {
1136
+ throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
1137
+ }
1138
+ const data = await (0, import_core2.connectorCall)(targetConnector, tool, args);
1139
+ if (!data.ok && data.error) throw new Error(data.error);
1140
+ return (0, import_core2.unwrapMcpResult)(data.result);
1141
+ },
1142
+ async mcpFetch(body) {
1143
+ return (0, import_core2.mcpFetch)(body);
1144
+ },
1145
+ sendCommand(_type, _payload) {
1146
+ },
1147
+ onCommand(cb) {
1148
+ commandListeners.add(cb);
1149
+ return () => {
1150
+ commandListeners.delete(cb);
1151
+ };
1152
+ },
1153
+ close() {
1154
+ import_core2.pluginCommandEmitter.emit("plugin-close", { pluginId });
1155
+ },
1156
+ sendMessage(text) {
1157
+ const t = (text || "").trim();
1158
+ if (!t) return;
1159
+ try {
1160
+ import_core2.useChatStore.getState().sendMessage(t);
1161
+ } catch (err) {
1162
+ console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
1163
+ }
1164
+ },
1165
+ prepareMessage(text) {
1166
+ const t = (text || "").trim();
1167
+ if (!t) return;
1168
+ import_core2.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
1169
+ }
581
1170
  };
582
1171
  }
583
- var _nativeRegistry = /* @__PURE__ */ new Map();
584
- function registerNativePlugin(id, plugin) {
585
- _nativeRegistry.set(id, plugin);
1172
+ var import_react4, import_core2;
1173
+ var init_native = __esm({
1174
+ "src/bridge/native.ts"() {
1175
+ "use strict";
1176
+ import_react4 = require("react");
1177
+ import_core2 = require("@ateam-ai-mobile/core");
1178
+ }
1179
+ });
1180
+
1181
+ // src/tools/registry.ts
1182
+ function setDeviceToolContextProvider(fn) {
1183
+ contextProvider = fn;
586
1184
  }
587
- function unregisterNativePlugin(id) {
588
- _nativeRegistry.delete(id);
1185
+ function defaultContext(pluginId, capabilities, stateDomains) {
1186
+ return {
1187
+ bridge: createNativeBridge(pluginId),
1188
+ native: gatePluginNative(getPluginNative(), capabilities, pluginId),
1189
+ deviceState: gateDeviceState(stateDomains)
1190
+ };
589
1191
  }
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") {
598
- try {
599
- return await entry();
600
- } catch (err) {
601
- console.warn(`[adas-plugins] lazy native plugin '${id}' failed to load:`, err);
602
- return null;
603
- }
1192
+ function registerDeviceTools(pluginId, tools, capabilities, stateDomains) {
1193
+ const accepted = [];
1194
+ for (const tool of tools || []) {
1195
+ if (!tool || typeof tool.name !== "string" || typeof tool.run !== "function") {
1196
+ console.warn(`[deviceTools] ${pluginId}: skipping malformed tool entry`);
1197
+ continue;
1198
+ }
1199
+ const existing = registry.get(tool.name);
1200
+ if (existing && existing.pluginId !== pluginId) {
1201
+ console.warn(
1202
+ `[deviceTools] ${pluginId}: "${tool.name}" already registered by ${existing.pluginId} \u2014 refusing to override`
1203
+ );
1204
+ continue;
604
1205
  }
605
- return entry;
1206
+ registry.set(tool.name, { pluginId, tool, capabilities, stateDomains });
1207
+ accepted.push(tool.name);
606
1208
  }
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);
1209
+ if (accepted.length > 0) {
1210
+ console.log(`[deviceTools] ${pluginId} registered: ${accepted.join(", ")}`);
612
1211
  }
1212
+ return accepted;
613
1213
  }
614
- function resolveBundleModule(name) {
615
- return _bundleModules.get(name);
1214
+ function unregisterDeviceTools(pluginId) {
1215
+ for (const [name, entry] of registry.entries()) {
1216
+ if (entry.pluginId === pluginId) registry.delete(name);
1217
+ }
616
1218
  }
1219
+ function hasDeviceTool(name, pluginId) {
1220
+ const entry = registry.get(name);
1221
+ return !!entry && (pluginId === void 0 || entry.pluginId === pluginId);
1222
+ }
1223
+ function listDeviceTools() {
1224
+ return Array.from(registry.entries()).map(([name, { pluginId, tool }]) => ({
1225
+ name,
1226
+ pluginId,
1227
+ description: tool.description,
1228
+ input_schema: tool.input_schema
1229
+ }));
1230
+ }
1231
+ async function callDeviceTool(name, args = {}, opts = {}) {
1232
+ const entry = registry.get(name);
1233
+ if (!entry) {
1234
+ return { ok: false, reason: "not_found", error: `No device tool registered as "${name}"` };
1235
+ }
1236
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
1237
+ let timer;
1238
+ try {
1239
+ const build = contextProvider || defaultContext;
1240
+ const built = build(entry.pluginId, entry.capabilities, entry.stateDomains);
1241
+ const ctx = {
1242
+ ...built,
1243
+ // Whoever built the context, a call cannot own a subscription.
1244
+ deviceState: withoutSubscribe(built.deviceState),
1245
+ signal: opts.signal
1246
+ };
1247
+ const result = await Promise.race([
1248
+ Promise.resolve(entry.tool.run(args, ctx)),
1249
+ new Promise((_, reject) => {
1250
+ timer = setTimeout(
1251
+ () => reject(new Error(`Device tool "${name}" timed out after ${timeoutMs}ms`)),
1252
+ timeoutMs
1253
+ );
1254
+ })
1255
+ ]);
1256
+ return { ok: true, result };
1257
+ } catch (err) {
1258
+ const message = err?.message || String(err);
1259
+ const timedOut = message.includes("timed out after");
1260
+ return { ok: false, reason: timedOut ? "timeout" : "failed", error: message };
1261
+ } finally {
1262
+ if (timer) clearTimeout(timer);
1263
+ }
1264
+ }
1265
+ function clearDeviceTools() {
1266
+ registry.clear();
1267
+ }
1268
+ var registry, contextProvider, DEFAULT_TIMEOUT_MS;
1269
+ var init_registry = __esm({
1270
+ "src/tools/registry.ts"() {
1271
+ "use strict";
1272
+ init_native();
1273
+ init_host();
1274
+ init_deviceStateHost();
1275
+ registry = /* @__PURE__ */ new Map();
1276
+ contextProvider = null;
1277
+ DEFAULT_TIMEOUT_MS = 3e4;
1278
+ }
1279
+ });
617
1280
 
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");
1281
+ // src/api/plugins.ts
1282
+ function supportsNative(manifest) {
1283
+ const mode = manifest.render?.mode;
1284
+ return mode === "react-native" || mode === "adaptive";
1285
+ }
1286
+ function getNativeComponentId(manifest) {
1287
+ if (manifest.render?.mode === "react-native") return manifest.render.component;
1288
+ if (manifest.render?.mode === "adaptive") return manifest.render.reactNative?.component;
1289
+ return null;
1290
+ }
1291
+ function getNativeBundleUrl(manifest) {
1292
+ const direct = manifest.bundleUrl;
1293
+ if (direct) return direct;
1294
+ const render = manifest.render;
1295
+ if (render.mode === "react-native") return render.bundleUrl || null;
1296
+ if (render.mode === "adaptive") return render.reactNative?.bundleUrl || null;
1297
+ return null;
1298
+ }
1299
+ function getNativeBundleHash(manifest) {
1300
+ const direct = manifest.bundleHash;
1301
+ if (direct) return direct;
1302
+ const render = manifest.render;
1303
+ if (render.mode === "react-native") return render.bundleHash || null;
1304
+ if (render.mode === "adaptive") return render.reactNative?.bundleHash || null;
1305
+ return null;
1306
+ }
1307
+ function supportsWebView(manifest) {
1308
+ return manifest.render?.mode === "iframe" || manifest.render?.mode === "adaptive";
1309
+ }
1310
+ async function listPlugins() {
1311
+ const res = await (0, import_core3.apiFetch)("/mcp", {
1312
+ method: "POST",
1313
+ body: JSON.stringify({
1314
+ jsonrpc: "2.0",
1315
+ id: Date.now(),
1316
+ method: "tools/call",
1317
+ params: {
1318
+ name: "cp.listContextPlugins",
1319
+ arguments: {}
1320
+ }
1321
+ })
1322
+ });
1323
+ const structured = res.result?.structuredContent?.plugins;
1324
+ if (structured) return structured;
1325
+ const text = res.result?.content?.[0]?.text;
1326
+ if (!text) return [];
1327
+ try {
1328
+ const parsed = JSON.parse(text);
1329
+ return parsed.plugins || parsed || [];
1330
+ } catch {
1331
+ return [];
1332
+ }
1333
+ }
1334
+ async function getPlugin(pluginId) {
1335
+ const res = await (0, import_core3.apiFetch)("/mcp", {
1336
+ method: "POST",
1337
+ body: JSON.stringify({
1338
+ jsonrpc: "2.0",
1339
+ id: Date.now(),
1340
+ method: "tools/call",
1341
+ params: {
1342
+ name: "cp.getContextPlugin",
1343
+ arguments: { id: pluginId }
1344
+ // Backend expects { id }, NOT { pluginId }
1345
+ }
1346
+ })
1347
+ });
1348
+ if (res.result?.structuredContent) return res.result.structuredContent;
1349
+ const text = res.result?.content?.[0]?.text;
1350
+ if (!text) return null;
1351
+ try {
1352
+ const parsed = JSON.parse(text);
1353
+ if (parsed.error) {
1354
+ console.warn("[getPlugin] Backend error:", parsed.error);
1355
+ return null;
1356
+ }
1357
+ return parsed;
1358
+ } catch {
1359
+ return null;
1360
+ }
1361
+ }
1362
+ function resolvePluginUrl(manifest, tenant) {
1363
+ const render = manifest.render;
1364
+ let url;
1365
+ if (render?.mode === "iframe") {
1366
+ url = render.iframeUrl;
1367
+ } else if (render?.mode === "adaptive") {
1368
+ url = render.iframeUrl || render.iframe?.iframeUrl;
1369
+ }
1370
+ if (!url) return "";
1371
+ if (url.startsWith("http")) {
1372
+ const sep = url.includes("?") ? "&" : "?";
1373
+ return tenant ? `${url}${sep}tenant=${encodeURIComponent(tenant)}` : url;
1374
+ }
1375
+ if (tenant && url.startsWith("/mcp-ui/")) {
1376
+ const tenantUrl = url.replace("/mcp-ui/", `/mcp-ui/${encodeURIComponent(tenant)}/`);
1377
+ return `${(0, import_core4.getCoreApiUrl)()}${tenantUrl}`;
1378
+ }
1379
+ return `${(0, import_core4.getCoreApiUrl)()}${url}`;
1380
+ }
1381
+ var import_core3, import_core4;
1382
+ var init_plugins = __esm({
1383
+ "src/api/plugins.ts"() {
1384
+ "use strict";
1385
+ import_core3 = require("@ateam-ai-mobile/core");
1386
+ import_core4 = require("@ateam-ai-mobile/core");
1387
+ }
1388
+ });
629
1389
 
630
1390
  // 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
1391
  function AdaOrb({ size = 120, state = "idle", inscription = true }) {
638
1392
  const showInscription = inscription && size >= 60;
639
1393
  const center = size / 2;
@@ -649,7 +1403,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
649
1403
  const orbit2 = (0, import_react_native_reanimated.useSharedValue)(0);
650
1404
  const orbit3 = (0, import_react_native_reanimated.useSharedValue)(0);
651
1405
  const beat = (0, import_react_native_reanimated.useSharedValue)(1);
652
- (0, import_react4.useEffect)(() => {
1406
+ (0, import_react5.useEffect)(() => {
653
1407
  (0, import_react_native_reanimated.cancelAnimation)(coreScale);
654
1408
  (0, import_react_native_reanimated.cancelAnimation)(innerScale);
655
1409
  (0, import_react_native_reanimated.cancelAnimation)(outerScale);
@@ -799,29 +1553,29 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
799
1553
  opacity: state === "thinking" ? 0.7 : 0
800
1554
  };
801
1555
  });
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" })
1556
+ 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: [
1557
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.Defs, { children: [
1558
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.RadialGradient, { id: "adaCore", cx: "36%", cy: "30%", r: "70%", children: [
1559
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FFE0B0", stopOpacity: "1" }),
1560
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "28%", stopColor: "#FFA85A", stopOpacity: "1" }),
1561
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "65%", stopColor: "#FF7A28", stopOpacity: "1" }),
1562
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#B8410A", stopOpacity: "1" })
809
1563
  ] }),
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" })
1564
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.RadialGradient, { id: "adaInner", cx: "50%", cy: "50%", r: "50%", children: [
1565
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FF8A3C", stopOpacity: "0.55" }),
1566
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FF7028", stopOpacity: "0" })
813
1567
  ] }),
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" })
1568
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.RadialGradient, { id: "adaOuter", cx: "50%", cy: "50%", r: "50%", children: [
1569
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FF7A28", stopOpacity: "0.4" }),
1570
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FF6018", stopOpacity: "0" })
817
1571
  ] }),
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" })
1572
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg.RadialGradient, { id: "adaSpark", cx: "50%", cy: "50%", r: "50%", children: [
1573
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FFFAEB", stopOpacity: "1" }),
1574
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "60%", stopColor: "#FFD095", stopOpacity: "0.9" }),
1575
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FFA85A", stopOpacity: "0" })
822
1576
  ] })
823
1577
  ] }),
824
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1578
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
825
1579
  AnimatedCircle,
826
1580
  {
827
1581
  cx: center,
@@ -832,7 +1586,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
832
1586
  animatedProps: ripple1Props
833
1587
  }
834
1588
  ),
835
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1589
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
836
1590
  AnimatedCircle,
837
1591
  {
838
1592
  cx: center,
@@ -843,7 +1597,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
843
1597
  animatedProps: ripple2Props
844
1598
  }
845
1599
  ),
846
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1600
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
847
1601
  AnimatedCircle,
848
1602
  {
849
1603
  cx: center,
@@ -854,10 +1608,10 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
854
1608
  animatedProps: ripple3Props
855
1609
  }
856
1610
  ),
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)(
1611
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaOuter)", animatedProps: outerProps }),
1612
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaInner)", animatedProps: innerProps }),
1613
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaCore)", animatedProps: coreProps }),
1614
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
861
1615
  AnimatedCircle,
862
1616
  {
863
1617
  r: Math.max(3, size * 0.05),
@@ -865,7 +1619,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
865
1619
  animatedProps: spark1Props
866
1620
  }
867
1621
  ),
868
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1622
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
869
1623
  AnimatedCircle,
870
1624
  {
871
1625
  r: Math.max(2.5, size * 0.04),
@@ -873,7 +1627,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
873
1627
  animatedProps: spark2Props
874
1628
  }
875
1629
  ),
876
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1630
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
877
1631
  AnimatedCircle,
878
1632
  {
879
1633
  r: Math.max(2, size * 0.032),
@@ -881,7 +1635,7 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
881
1635
  animatedProps: spark3Props
882
1636
  }
883
1637
  ),
884
- showInscription && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1638
+ showInscription && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
885
1639
  import_react_native_svg.Text,
886
1640
  {
887
1641
  x: center,
@@ -896,17 +1650,26 @@ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
896
1650
  )
897
1651
  ] }) });
898
1652
  }
899
- var styles2 = import_react_native4.StyleSheet.create({
900
- wrap: {
901
- alignItems: "center",
902
- justifyContent: "center"
1653
+ var import_react5, import_react_native4, import_react_native_reanimated, import_react_native_svg, import_jsx_runtime2, AnimatedCircle, styles2;
1654
+ var init_AdaOrb = __esm({
1655
+ "src/surfaces/AdaOrb.tsx"() {
1656
+ "use strict";
1657
+ import_react5 = require("react");
1658
+ import_react_native4 = require("react-native");
1659
+ import_react_native_reanimated = __toESM(require("react-native-reanimated"));
1660
+ import_react_native_svg = __toESM(require("react-native-svg"));
1661
+ import_jsx_runtime2 = require("react/jsx-runtime");
1662
+ AnimatedCircle = import_react_native_reanimated.default.createAnimatedComponent(import_react_native_svg.Circle);
1663
+ styles2 = import_react_native4.StyleSheet.create({
1664
+ wrap: {
1665
+ alignItems: "center",
1666
+ justifyContent: "center"
1667
+ }
1668
+ });
903
1669
  }
904
1670
  });
905
1671
 
906
1672
  // 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
1673
  function SurfaceDrawer({
911
1674
  visible,
912
1675
  onClose,
@@ -918,9 +1681,9 @@ function SurfaceDrawer({
918
1681
  children
919
1682
  }) {
920
1683
  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)(() => {
1684
+ const fadeAnim = (0, import_react6.useRef)(new import_react_native5.Animated.Value(0)).current;
1685
+ const slideAnim = (0, import_react6.useRef)(new import_react_native5.Animated.Value(sheetH)).current;
1686
+ (0, import_react6.useEffect)(() => {
924
1687
  if (visible) {
925
1688
  import_react_native5.Animated.parallel([
926
1689
  import_react_native5.Animated.timing(fadeAnim, { toValue: 1, duration: 220, useNativeDriver: true }),
@@ -938,9 +1701,9 @@ function SurfaceDrawer({
938
1701
  ]).start(() => onClose());
939
1702
  };
940
1703
  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)(
1704
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.Animated.View, { style: [styles3.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
1705
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Pressable, { style: import_react_native5.StyleSheet.absoluteFill, onPress: close }),
1706
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
944
1707
  import_react_native5.Animated.View,
945
1708
  {
946
1709
  style: [
@@ -948,21 +1711,21 @@ function SurfaceDrawer({
948
1711
  { height: sheetH, transform: [{ translateY: slideAnim }] }
949
1712
  ],
950
1713
  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" })
1714
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.View, { style: styles3.handle }),
1715
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: styles3.header, children: [
1716
+ 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,
1717
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: { flex: 1 }, children: [
1718
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles3.title, children: title }),
1719
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles3.subtitle, children: subtitle }) : null,
1720
+ privacy ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: styles3.privacyPill, children: [
1721
+ /* @__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: [
1722
+ /* @__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" }),
1723
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_svg2.Path, { d: "M7 11V7a5 5 0 0110 0v4" })
961
1724
  ] }),
962
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles3.privacyText, children: "Only you can see this" })
1725
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles3.privacyText, children: "Only you can see this" })
963
1726
  ] }) : null
964
1727
  ] }),
965
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1728
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
966
1729
  import_react_native5.Pressable,
967
1730
  {
968
1731
  accessibilityRole: "button",
@@ -970,105 +1733,115 @@ function SurfaceDrawer({
970
1733
  style: styles3.closeBtn,
971
1734
  onPress: close,
972
1735
  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" }) })
1736
+ 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
1737
  }
975
1738
  )
976
1739
  ] }),
977
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles3.body, children })
1740
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.View, { style: styles3.body, children })
978
1741
  ]
979
1742
  }
980
1743
  )
981
1744
  ] });
982
1745
  }
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
1746
+ var import_react6, import_react_native5, import_react_native_svg2, import_core5, import_jsx_runtime3, SCREEN_H, fontFam, styles3;
1747
+ var init_SurfaceDrawer = __esm({
1748
+ "src/surfaces/SurfaceDrawer.tsx"() {
1749
+ "use strict";
1750
+ import_react6 = require("react");
1751
+ import_react_native5 = require("react-native");
1752
+ import_react_native_svg2 = __toESM(require("react-native-svg"));
1753
+ import_core5 = require("@ateam-ai-mobile/core");
1754
+ init_AdaOrb();
1755
+ import_jsx_runtime3 = require("react/jsx-runtime");
1756
+ SCREEN_H = import_react_native5.Dimensions.get("window").height;
1757
+ fontFam = import_react_native5.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1758
+ styles3 = import_react_native5.StyleSheet.create({
1759
+ overlay: {
1760
+ position: "absolute",
1761
+ top: 0,
1762
+ left: 0,
1763
+ right: 0,
1764
+ bottom: 0,
1765
+ backgroundColor: "rgba(26, 20, 16, 0.35)",
1766
+ justifyContent: "flex-end",
1767
+ zIndex: 999,
1768
+ elevation: 999
1769
+ },
1770
+ sheet: {
1771
+ backgroundColor: import_core5.ada.paper,
1772
+ borderTopLeftRadius: 28,
1773
+ borderTopRightRadius: 28,
1774
+ overflow: "hidden"
1775
+ },
1776
+ handle: {
1777
+ width: 40,
1778
+ height: 4,
1779
+ borderRadius: 2,
1780
+ backgroundColor: import_core5.ada.inkMuted,
1781
+ opacity: 0.3,
1782
+ alignSelf: "center",
1783
+ marginTop: 10,
1784
+ marginBottom: 6
1785
+ },
1786
+ header: {
1787
+ flexDirection: "row",
1788
+ alignItems: "center",
1789
+ paddingHorizontal: 22,
1790
+ paddingVertical: 16,
1791
+ gap: 12,
1792
+ borderBottomWidth: 1,
1793
+ borderBottomColor: import_core5.ada.border
1794
+ },
1795
+ headerOrb: {
1796
+ width: 36,
1797
+ height: 36,
1798
+ alignItems: "center",
1799
+ justifyContent: "center"
1800
+ },
1801
+ title: {
1802
+ fontSize: 17,
1803
+ fontWeight: "600",
1804
+ color: import_core5.ada.ink,
1805
+ letterSpacing: -0.2,
1806
+ fontFamily: fontFam
1807
+ },
1808
+ subtitle: {
1809
+ fontSize: 13,
1810
+ fontWeight: "400",
1811
+ color: import_core5.ada.inkSoft,
1812
+ marginTop: 2,
1813
+ letterSpacing: -0.1,
1814
+ fontFamily: fontFam
1815
+ },
1816
+ privacyPill: {
1817
+ flexDirection: "row",
1818
+ alignItems: "center",
1819
+ gap: 5,
1820
+ marginTop: 4
1821
+ },
1822
+ privacyText: {
1823
+ fontSize: 11,
1824
+ fontWeight: "500",
1825
+ color: import_core5.ada.inkSoft,
1826
+ letterSpacing: 0.2,
1827
+ fontFamily: fontFam
1828
+ },
1829
+ closeBtn: {
1830
+ width: 32,
1831
+ height: 32,
1832
+ borderRadius: 16,
1833
+ backgroundColor: import_core5.ada.paperSoft,
1834
+ justifyContent: "center",
1835
+ alignItems: "center"
1836
+ },
1837
+ body: {
1838
+ flex: 1
1839
+ }
1840
+ });
1064
1841
  }
1065
1842
  });
1066
1843
 
1067
1844
  // 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
1845
  function SurfaceCard({
1073
1846
  icon,
1074
1847
  title,
@@ -1089,7 +1862,7 @@ function SurfaceCard({
1089
1862
  getHaptics()?.impact("medium");
1090
1863
  onLongPress();
1091
1864
  };
1092
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1865
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1093
1866
  import_react_native6.Pressable,
1094
1867
  {
1095
1868
  accessibilityRole: "button",
@@ -1100,71 +1873,76 @@ function SurfaceCard({
1100
1873
  onLongPress: onLongPress ? handleLongPress : void 0,
1101
1874
  disabled: !onPress && !onLongPress,
1102
1875
  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
1876
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native6.View, { style: styles4.row, children: [
1877
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles4.icon, children: icon }) : null,
1878
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native6.View, { style: { flex: 1 }, children: [
1879
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles4.title, children: title }),
1880
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles4.subtitle, children: subtitle }) : null
1108
1881
  ] }),
1109
- arrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.Text, { style: styles4.arrow, children: "\u2192" }) : null
1882
+ arrow ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles4.arrow, children: "\u2192" }) : null
1110
1883
  ] }),
1111
- children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: styles4.body, children }) : null
1884
+ children ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.View, { style: styles4.body, children }) : null
1112
1885
  ]
1113
1886
  }
1114
1887
  );
1115
1888
  }
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
1889
+ var import_react_native6, import_core6, import_jsx_runtime4, fontFam2, styles4;
1890
+ var init_SurfaceCard = __esm({
1891
+ "src/surfaces/SurfaceCard.tsx"() {
1892
+ "use strict";
1893
+ import_react_native6 = require("react-native");
1894
+ init_host();
1895
+ import_core6 = require("@ateam-ai-mobile/core");
1896
+ import_jsx_runtime4 = require("react/jsx-runtime");
1897
+ fontFam2 = import_react_native6.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1898
+ styles4 = import_react_native6.StyleSheet.create({
1899
+ card: {
1900
+ backgroundColor: import_core6.ada.paperSoft,
1901
+ borderWidth: 1,
1902
+ borderColor: import_core6.ada.border,
1903
+ borderRadius: import_core6.borderRadius.xl,
1904
+ paddingHorizontal: 16,
1905
+ paddingVertical: 14
1906
+ },
1907
+ row: {
1908
+ flexDirection: "row",
1909
+ alignItems: "center",
1910
+ gap: 14
1911
+ },
1912
+ icon: {
1913
+ fontSize: 22
1914
+ },
1915
+ title: {
1916
+ fontSize: 15,
1917
+ fontWeight: "600",
1918
+ color: import_core6.ada.ink,
1919
+ letterSpacing: -0.2,
1920
+ fontFamily: fontFam2
1921
+ },
1922
+ subtitle: {
1923
+ fontSize: 13,
1924
+ fontWeight: "400",
1925
+ color: import_core6.ada.inkMuted,
1926
+ marginTop: 2,
1927
+ letterSpacing: -0.1,
1928
+ fontFamily: fontFam2
1929
+ },
1930
+ arrow: {
1931
+ fontSize: 18,
1932
+ color: import_core6.ada.inkMuted,
1933
+ fontWeight: "400"
1934
+ },
1935
+ body: {
1936
+ marginTop: 10,
1937
+ paddingTop: 10,
1938
+ borderTopWidth: 1,
1939
+ borderTopColor: import_core6.ada.border
1940
+ }
1941
+ });
1158
1942
  }
1159
1943
  });
1160
1944
 
1161
1945
  // 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
1946
  function SurfaceFullscreen({
1169
1947
  visible,
1170
1948
  onClose,
@@ -1175,9 +1953,9 @@ function SurfaceFullscreen({
1175
1953
  closeIcon = "x",
1176
1954
  closeLabel = "Close"
1177
1955
  }) {
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)(() => {
1956
+ const fadeAnim = (0, import_react7.useRef)(new import_react_native7.Animated.Value(0)).current;
1957
+ const heroScale = (0, import_react7.useRef)(new import_react_native7.Animated.Value(0.6)).current;
1958
+ (0, import_react7.useEffect)(() => {
1181
1959
  if (visible) {
1182
1960
  import_react_native7.Animated.parallel([
1183
1961
  import_react_native7.Animated.timing(fadeAnim, { toValue: 1, duration: 280, useNativeDriver: true }),
@@ -1197,22 +1975,22 @@ function SurfaceFullscreen({
1197
1975
  if (!visible) return null;
1198
1976
  const renderCloseIcon = () => {
1199
1977
  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" }) });
1978
+ 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
1979
  }
1202
1980
  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" }) });
1981
+ 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
1982
  }
1205
1983
  return closeIcon;
1206
1984
  };
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
1985
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.Animated.View, { style: [styles5.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
1986
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.View, { style: styles5.topBar, children: [
1987
+ showWordmark ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.Text, { style: styles5.wordmark, children: "ada" }) : null,
1988
+ status ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.Text, { style: styles5.status, children: status }) : null
1211
1989
  ] }),
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)(
1990
+ 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,
1991
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.View, { style: styles5.body, children }),
1992
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.View, { style: styles5.bottomBar, children: [
1993
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1216
1994
  import_react_native7.Pressable,
1217
1995
  {
1218
1996
  accessibilityRole: "button",
@@ -1223,151 +2001,166 @@ function SurfaceFullscreen({
1223
2001
  children: renderCloseIcon()
1224
2002
  }
1225
2003
  ),
1226
- closeLabel ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.Text, { style: styles5.closeLabel, children: closeLabel }) : null
2004
+ closeLabel ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.Text, { style: styles5.closeLabel, children: closeLabel }) : null
1227
2005
  ] })
1228
2006
  ] });
1229
2007
  }
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
2008
+ var import_react7, import_react_native7, import_react_native_svg3, import_core7, import_jsx_runtime5, fontFam3, styles5;
2009
+ var init_SurfaceFullscreen = __esm({
2010
+ "src/surfaces/SurfaceFullscreen.tsx"() {
2011
+ "use strict";
2012
+ import_react7 = require("react");
2013
+ import_react_native7 = require("react-native");
2014
+ import_react_native_svg3 = __toESM(require("react-native-svg"));
2015
+ import_core7 = require("@ateam-ai-mobile/core");
2016
+ import_jsx_runtime5 = require("react/jsx-runtime");
2017
+ fontFam3 = import_react_native7.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
2018
+ styles5 = import_react_native7.StyleSheet.create({
2019
+ overlay: {
2020
+ position: "absolute",
2021
+ top: 0,
2022
+ left: 0,
2023
+ right: 0,
2024
+ bottom: 0,
2025
+ backgroundColor: import_core7.ada.paper,
2026
+ zIndex: 1e3,
2027
+ elevation: 1e3
2028
+ },
2029
+ topBar: {
2030
+ paddingTop: import_react_native7.Platform.OS === "ios" ? 64 : 36,
2031
+ paddingHorizontal: 24,
2032
+ alignItems: "center",
2033
+ gap: 6
2034
+ },
2035
+ wordmark: {
2036
+ fontSize: 14,
2037
+ fontFamily: fontFam3,
2038
+ fontWeight: "500",
2039
+ color: import_core7.ada.inkMuted,
2040
+ letterSpacing: 4
2041
+ },
2042
+ status: {
2043
+ fontSize: 17,
2044
+ fontFamily: fontFam3,
2045
+ fontWeight: "500",
2046
+ color: import_core7.ada.ink,
2047
+ letterSpacing: -0.2
2048
+ },
2049
+ heroWrap: {
2050
+ paddingTop: 24,
2051
+ paddingBottom: 12,
2052
+ alignItems: "center",
2053
+ justifyContent: "center"
2054
+ },
2055
+ body: {
2056
+ flex: 1
2057
+ },
2058
+ bottomBar: {
2059
+ paddingBottom: import_react_native7.Platform.OS === "ios" ? 48 : 32,
2060
+ paddingTop: 12,
2061
+ alignItems: "center",
2062
+ gap: 8
2063
+ },
2064
+ closeBtn: {
2065
+ width: 64,
2066
+ height: 64,
2067
+ borderRadius: 32,
2068
+ backgroundColor: import_core7.ada.ink,
2069
+ justifyContent: "center",
2070
+ alignItems: "center",
2071
+ shadowColor: import_core7.ada.ink,
2072
+ shadowOffset: { width: 0, height: 8 },
2073
+ shadowOpacity: 0.18,
2074
+ shadowRadius: 14,
2075
+ elevation: 8
2076
+ },
2077
+ closeLabel: {
2078
+ fontSize: 13,
2079
+ fontFamily: fontFam3,
2080
+ fontWeight: "500",
2081
+ color: import_core7.ada.inkMuted,
2082
+ letterSpacing: 0.3
2083
+ }
2084
+ });
1295
2085
  }
1296
2086
  });
1297
2087
 
1298
2088
  // 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
2089
  function isMountSurface(m) {
1361
2090
  return m.type === "drawer" || m.type === "fullscreen";
1362
2091
  }
1363
2092
  function isInlineSurface(m) {
1364
2093
  return m.type === "card" || m.type === "header" || m.type === "ambient" || m.type === "nudge";
1365
2094
  }
2095
+ var import_zustand, useSurfaceStore;
2096
+ var init_store = __esm({
2097
+ "src/registry/store.ts"() {
2098
+ "use strict";
2099
+ import_zustand = require("zustand");
2100
+ useSurfaceStore = (0, import_zustand.create)((set, get) => ({
2101
+ manifests: {},
2102
+ activeDrawer: null,
2103
+ activeFullscreen: null,
2104
+ activeProps: {},
2105
+ mountKey: 0,
2106
+ register: (manifest) => set((s) => ({ manifests: { ...s.manifests, [manifest.id]: manifest } })),
2107
+ unregister: (id) => set((s) => {
2108
+ const next = { ...s.manifests };
2109
+ delete next[id];
2110
+ const nextProps = { ...s.activeProps };
2111
+ delete nextProps[id];
2112
+ return { manifests: next, activeProps: nextProps };
2113
+ }),
2114
+ show: (id, props) => {
2115
+ const m = get().manifests[id];
2116
+ if (!m) {
2117
+ console.warn("[SurfaceStore] show: unknown surface id", id);
2118
+ return;
2119
+ }
2120
+ set((s) => {
2121
+ const nextProps = props !== void 0 ? { ...s.activeProps, [id]: props } : s.activeProps;
2122
+ const mountKey = s.mountKey + 1;
2123
+ if (m.type === "drawer") return { activeDrawer: id, activeProps: nextProps, mountKey };
2124
+ if (m.type === "fullscreen") return { activeFullscreen: id, activeProps: nextProps, mountKey };
2125
+ return { mountKey };
2126
+ });
2127
+ },
2128
+ hide: (id) => {
2129
+ const s = get();
2130
+ if (!id) {
2131
+ const cleanup2 = { ...s.activeProps };
2132
+ if (s.activeDrawer) delete cleanup2[s.activeDrawer];
2133
+ if (s.activeFullscreen) delete cleanup2[s.activeFullscreen];
2134
+ set({ activeDrawer: null, activeFullscreen: null, activeProps: cleanup2 });
2135
+ return;
2136
+ }
2137
+ const cleanup = { ...s.activeProps };
2138
+ delete cleanup[id];
2139
+ const patch = { activeProps: cleanup };
2140
+ if (s.activeDrawer === id) patch.activeDrawer = null;
2141
+ if (s.activeFullscreen === id) patch.activeFullscreen = null;
2142
+ set(patch);
2143
+ },
2144
+ list: (filter) => {
2145
+ const all = Object.values(get().manifests);
2146
+ return all.filter((m) => {
2147
+ if (filter?.type && m.type !== filter.type) return false;
2148
+ if (filter?.visibility && !filter.visibility.includes(m.visibility)) return false;
2149
+ if (filter?.placement) {
2150
+ if (filter.placement === "unset") {
2151
+ if (m.placement) return false;
2152
+ } else if (m.placement !== filter.placement) {
2153
+ return false;
2154
+ }
2155
+ }
2156
+ return true;
2157
+ });
2158
+ }
2159
+ }));
2160
+ }
2161
+ });
1366
2162
 
1367
2163
  // 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
2164
  function SurfaceDispatcher() {
1372
2165
  const manifests = useSurfaceStore((s) => s.manifests);
1373
2166
  const activeDrawer = useSurfaceStore((s) => s.activeDrawer);
@@ -1375,8 +2168,8 @@ function SurfaceDispatcher() {
1375
2168
  const activeProps = useSurfaceStore((s) => s.activeProps);
1376
2169
  const mountKey = useSurfaceStore((s) => s.mountKey);
1377
2170
  const hide = useSurfaceStore((s) => s.hide);
1378
- (0, import_react7.useEffect)(() => {
1379
- const unsub = import_core5.pluginCommandEmitter.on("plugin-close", ({ pluginId } = {}) => {
2171
+ (0, import_react8.useEffect)(() => {
2172
+ const unsub = import_core8.pluginCommandEmitter.on("plugin-close", ({ pluginId } = {}) => {
1380
2173
  if (!pluginId) return;
1381
2174
  const s = useSurfaceStore.getState();
1382
2175
  if (s.activeDrawer === pluginId || s.activeFullscreen === pluginId) {
@@ -1389,8 +2182,8 @@ function SurfaceDispatcher() {
1389
2182
  const fullscreen = activeFullscreen ? manifests[activeFullscreen] : null;
1390
2183
  const drawerProps = activeDrawer ? activeProps[activeDrawer] : void 0;
1391
2184
  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)(
2185
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2186
+ drawer && drawer.type === "drawer" && drawer.Component ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1394
2187
  drawer.Component,
1395
2188
  {
1396
2189
  dismiss: () => hide(drawer.id),
@@ -1398,7 +2191,7 @@ function SurfaceDispatcher() {
1398
2191
  },
1399
2192
  `drawer-${drawer.id}-${mountKey}`
1400
2193
  ) : null,
1401
- fullscreen && fullscreen.type === "fullscreen" && fullscreen.Component ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2194
+ fullscreen && fullscreen.type === "fullscreen" && fullscreen.Component ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1402
2195
  fullscreen.Component,
1403
2196
  {
1404
2197
  dismiss: () => hide(fullscreen.id),
@@ -1408,75 +2201,18 @@ function SurfaceDispatcher() {
1408
2201
  ) : null
1409
2202
  ] });
1410
2203
  }
2204
+ var import_react8, import_core8, import_jsx_runtime6;
2205
+ var init_SurfaceDispatcher = __esm({
2206
+ "src/registry/SurfaceDispatcher.tsx"() {
2207
+ "use strict";
2208
+ import_react8 = require("react");
2209
+ init_store();
2210
+ import_core8 = require("@ateam-ai-mobile/core");
2211
+ import_jsx_runtime6 = require("react/jsx-runtime");
2212
+ }
2213
+ });
1411
2214
 
1412
2215
  // 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
2216
  function sha256Hex(message) {
1481
2217
  const utf8 = unescape(encodeURIComponent(message));
1482
2218
  const bytes = new Uint8Array(utf8.length);
@@ -1544,547 +2280,1143 @@ function sha256Hex(message) {
1544
2280
  }
1545
2281
  return out;
1546
2282
  }
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;
2283
+ var K, rotr;
2284
+ var init_sha256 = __esm({
2285
+ "src/utils/sha256.ts"() {
2286
+ "use strict";
2287
+ K = new Uint32Array([
2288
+ 1116352408,
2289
+ 1899447441,
2290
+ 3049323471,
2291
+ 3921009573,
2292
+ 961987163,
2293
+ 1508970993,
2294
+ 2453635748,
2295
+ 2870763221,
2296
+ 3624381080,
2297
+ 310598401,
2298
+ 607225278,
2299
+ 1426881987,
2300
+ 1925078388,
2301
+ 2162078206,
2302
+ 2614888103,
2303
+ 3248222580,
2304
+ 3835390401,
2305
+ 4022224774,
2306
+ 264347078,
2307
+ 604807628,
2308
+ 770255983,
2309
+ 1249150122,
2310
+ 1555081692,
2311
+ 1996064986,
2312
+ 2554220882,
2313
+ 2821834349,
2314
+ 2952996808,
2315
+ 3210313671,
2316
+ 3336571891,
2317
+ 3584528711,
2318
+ 113926993,
2319
+ 338241895,
2320
+ 666307205,
2321
+ 773529912,
2322
+ 1294757372,
2323
+ 1396182291,
2324
+ 1695183700,
2325
+ 1986661051,
2326
+ 2177026350,
2327
+ 2456956037,
2328
+ 2730485921,
2329
+ 2820302411,
2330
+ 3259730800,
2331
+ 3345764771,
2332
+ 3516065817,
2333
+ 3600352804,
2334
+ 4094571909,
2335
+ 275423344,
2336
+ 430227734,
2337
+ 506948616,
2338
+ 659060556,
2339
+ 883997877,
2340
+ 958139571,
2341
+ 1322822218,
2342
+ 1537002063,
2343
+ 1747873779,
2344
+ 1955562222,
2345
+ 2024104815,
2346
+ 2227730452,
2347
+ 2361852424,
2348
+ 2428436474,
2349
+ 2756734187,
2350
+ 3204031479,
2351
+ 3329325298
2352
+ ]);
2353
+ rotr = (n, x) => x >>> n | x << 32 - n;
1556
2354
  }
1557
- async saveIndex(index) {
1558
- this.index = index;
1559
- }
1560
- async readBundle(localPath) {
1561
- return this.files.get(localPath) ?? null;
1562
- }
1563
- async writeBundle(key, content) {
1564
- const localPath = `memory://${key}`;
1565
- this.files.set(localPath, content);
1566
- return localPath;
2355
+ });
2356
+
2357
+ // src/bundles/integrity.ts
2358
+ function assertBundleIntegrity(content, verifiedHash, label) {
2359
+ const digest = sha256Hex(content);
2360
+ if (!verifiedHash) {
2361
+ throw new Error(`[bundle-integrity] refusing to evaluate ${label}: no verified SHA-256 (fail closed).`);
1567
2362
  }
1568
- async deleteBundle(localPath) {
1569
- this.files.delete(localPath);
2363
+ if (digest !== verifiedHash.toLowerCase()) {
2364
+ throw new Error(
2365
+ `[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.`
2366
+ );
1570
2367
  }
1571
- async clearAll() {
1572
- this.index = {};
1573
- this.files.clear();
2368
+ }
2369
+ var init_integrity = __esm({
2370
+ "src/bundles/integrity.ts"() {
2371
+ "use strict";
2372
+ init_sha256();
1574
2373
  }
1575
- };
1576
- var _storage = new MemoryBundleStorage();
2374
+ });
2375
+
2376
+ // src/bundles/storage.ts
2377
+ function selectOwnedCacheEntries(entries, tenant) {
2378
+ if (!tenant) return [];
2379
+ return (entries || []).filter((e) => !!e && e.tenant === tenant);
2380
+ }
1577
2381
  function setBundleStorage(storage) {
1578
2382
  _storage = storage;
1579
2383
  }
1580
2384
  function getBundleStorage() {
1581
2385
  return _storage;
1582
2386
  }
2387
+ var MemoryBundleStorage, _storage;
2388
+ var init_storage = __esm({
2389
+ "src/bundles/storage.ts"() {
2390
+ "use strict";
2391
+ MemoryBundleStorage = class {
2392
+ constructor() {
2393
+ this.index = {};
2394
+ this.files = /* @__PURE__ */ new Map();
2395
+ }
2396
+ async loadIndex() {
2397
+ return this.index;
2398
+ }
2399
+ async saveIndex(index) {
2400
+ this.index = index;
2401
+ }
2402
+ async readBundle(localPath) {
2403
+ return this.files.get(localPath) ?? null;
2404
+ }
2405
+ async writeBundle(key, content) {
2406
+ const localPath = `memory://${key}`;
2407
+ this.files.set(localPath, content);
2408
+ return localPath;
2409
+ }
2410
+ async deleteBundle(localPath) {
2411
+ this.files.delete(localPath);
2412
+ }
2413
+ async clearAll() {
2414
+ this.index = {};
2415
+ this.files.clear();
2416
+ }
2417
+ };
2418
+ _storage = new MemoryBundleStorage();
2419
+ }
2420
+ });
1583
2421
 
1584
2422
  // 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)}`);
2423
+ var import_react9, import_react10, ReactNative, import_core9, ReactShim, PluginSDKShim, requireShim, CACHE_TTL, hasComponent, hasTools, BundleManager, bundleManager;
2424
+ var init_bundleManager = __esm({
2425
+ "src/bundles/bundleManager.ts"() {
2426
+ "use strict";
2427
+ import_react9 = __toESM(require("react"));
2428
+ import_react10 = require("react");
2429
+ ReactNative = __toESM(require("react-native"));
2430
+ import_core9 = require("@ateam-ai-mobile/core");
2431
+ init_sdk();
2432
+ init_SurfaceDrawer();
2433
+ init_SurfaceCard();
2434
+ init_SurfaceFullscreen();
2435
+ init_store();
2436
+ init_SurfaceDispatcher();
2437
+ init_host();
2438
+ init_sha256();
2439
+ init_integrity();
2440
+ init_storage();
2441
+ init_registry();
2442
+ ReactShim = {
2443
+ __esModule: true,
2444
+ default: import_react9.default,
2445
+ ...import_react9.default,
2446
+ // Explicitly include hooks (React 19 may not have them as own enumerable props)
2447
+ useState: import_react10.useState,
2448
+ useEffect: import_react10.useEffect,
2449
+ useCallback: import_react10.useCallback,
2450
+ useMemo: import_react10.useMemo,
2451
+ useRef: import_react10.useRef,
2452
+ useContext: import_react10.useContext,
2453
+ useReducer: import_react10.useReducer,
2454
+ useLayoutEffect: import_react10.useLayoutEffect,
2455
+ useImperativeHandle: import_react10.useImperativeHandle,
2456
+ useDebugValue: import_react10.useDebugValue,
2457
+ createElement: import_react9.default.createElement,
2458
+ Fragment: import_react9.default.Fragment,
2459
+ Component: import_react9.default.Component,
2460
+ PureComponent: import_react9.default.PureComponent,
2461
+ createContext: import_react9.default.createContext,
2462
+ forwardRef: import_react9.default.forwardRef,
2463
+ memo: import_react9.default.memo,
2464
+ Children: import_react9.default.Children,
2465
+ cloneElement: import_react9.default.cloneElement,
2466
+ isValidElement: import_react9.default.isValidElement,
2467
+ createRef: import_react9.default.createRef,
2468
+ lazy: import_react9.default.lazy,
2469
+ Suspense: import_react9.default.Suspense
2470
+ };
2471
+ PluginSDKShim = Object.freeze({
2472
+ ...sdk_exports,
2473
+ SurfaceDrawer,
2474
+ SurfaceCard,
2475
+ SurfaceFullscreen,
2476
+ useSurfaceStore,
2477
+ SurfaceDispatcher,
2478
+ PluginSDK: Object.freeze({
2479
+ ...PluginSDK,
2480
+ register: PluginSDK.register
2481
+ }),
2482
+ register
2483
+ });
2484
+ requireShim = (name) => {
2485
+ if (name === "react") return ReactShim;
2486
+ if (name === "react-native") return ReactNative;
2487
+ if (name === "@adas/plugin-sdk") return PluginSDKShim;
2488
+ const hostModule = resolveBundleModule(name);
2489
+ if (hostModule !== void 0) return hostModule;
2490
+ console.warn(`[BundleManager] Unknown module: ${name} \u2014 register it via registerBundleModules()`);
2491
+ return {};
2492
+ };
2493
+ CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
2494
+ hasComponent = (val) => !!val && typeof val === "object" && typeof val.Component === "function";
2495
+ hasTools = (val) => !!val && typeof val === "object" && Array.isArray(val.tools) && val.tools.length > 0;
2496
+ BundleManager = class {
2497
+ constructor() {
2498
+ /** In-memory module cache: bundleUrl → evaluated module.exports. Holds ONE
2499
+ * tenant's modules at a time — the tenant boundary is `cacheTenant` /
2500
+ * `requireTenant()`, NOT the key. */
2501
+ this.modules = /* @__PURE__ */ new Map();
2502
+ /** What is actually RUNNING, keyed by bundleUrl (for the current tenant). See
2503
+ * getLoadedBundles(). */
2504
+ this.provenance = /* @__PURE__ */ new Map();
2505
+ /** In-flight download deduplication: bundleUrl → Promise */
2506
+ this.inflight = /* @__PURE__ */ new Map();
2507
+ /**
2508
+ * The tenant the in-memory caches (modules / provenance / inflight) currently
2509
+ * belong to. This is the tenant BOUNDARY for evaluated code.
2510
+ *
2511
+ * A bundleUrl resolves PER-TENANT on the server — one url, different bytes for
2512
+ * different tenants — so a memory hit keyed on url alone would hand tenant B
2513
+ * the module tenant A already evaluated and ran. PR #9 scoped the DISK cache
2514
+ * (per-tenant index + hash gate); the in-memory map was left url-keyed. This
2515
+ * closes that: the caches only ever hold ONE tenant's modules, and a switch
2516
+ * drops them before anything is served.
2517
+ */
2518
+ this.cacheTenant = null;
2519
+ /**
2520
+ * pluginIds this manager registered device tools for. A tenant switch drops
2521
+ * the in-memory modules AND unregisters these, so the incoming tenant's bundle
2522
+ * re-registers into a clean registry instead of being refused as a duplicate
2523
+ * of the previous tenant's tools.
2524
+ */
2525
+ this.registeredToolPluginIds = /* @__PURE__ */ new Set();
2526
+ /** bundleUrl → the trusted declaration last recorded on its disk-cache entry
2527
+ * this session, so a memory hit with unchanged grants skips the index I/O. */
2528
+ this.recordedDeclarations = /* @__PURE__ */ new Map();
2529
+ /**
2530
+ * Enforce the in-memory tenant boundary. Call at the top of every public load
2531
+ * path, BEFORE any cache read.
2532
+ *
2533
+ * FAIL CLOSED: without a tenant identity we cannot prove a cached module is
2534
+ * this session's, so we drop everything and refuse rather than risk serving
2535
+ * one tenant's evaluated code to another. On a tenant change we drop the
2536
+ * previous tenant's modules (and their tool registrations) before proceeding.
2537
+ */
2538
+ /**
2539
+ * Monotonic load generation. Bumped on every in-memory wipe (a tenant change,
2540
+ * fail-closed, or clearAll). A load captures it at ENTRY; a completion whose
2541
+ * generation no longer matches is stale and is discarded before it can commit.
2542
+ * This closes the async race the sequential guard cannot: tenant A's in-flight
2543
+ * loadModule can resolve AFTER a switch to B — clearing `inflight` does not
2544
+ * cancel that Promise — and would otherwise write A's module / provenance /
2545
+ * device tools into the singleton now marked cacheTenant=B.
2546
+ */
2547
+ this.epoch = 0;
2548
+ }
2549
+ requireTenant() {
2550
+ const tenant = (0, import_core9.getTenant)();
2551
+ if (!tenant) {
2552
+ this.dropInMemory();
2553
+ this.cacheTenant = null;
2554
+ throw new Error(
2555
+ "[BundleManager] refusing to load a tenant-scoped bundle without a tenant identity (fail closed)"
2556
+ );
2557
+ }
2558
+ if (tenant !== this.cacheTenant) {
2559
+ this.dropInMemory();
2560
+ this.cacheTenant = tenant;
1695
2561
  }
2562
+ return tenant;
1696
2563
  }
1697
- };
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
- }
1711
- }
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 {
2564
+ /** Drop every in-memory cache and unregister the device tools this manager
2565
+ * registered — used on a tenant change and by clearAll(). */
2566
+ dropInMemory() {
2567
+ for (const pluginId of this.registeredToolPluginIds) {
2568
+ try {
2569
+ unregisterDeviceTools(pluginId);
2570
+ } catch {
2571
+ }
2572
+ }
2573
+ this.registeredToolPluginIds.clear();
2574
+ this.recordedDeclarations.clear();
2575
+ this.modules.clear();
2576
+ this.provenance.clear();
2577
+ this.inflight.clear();
2578
+ this.epoch++;
1726
2579
  }
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;
2580
+ /** True once the boundary has moved since a load captured `epoch` — i.e. a
2581
+ * tenant switch (or wipe) happened while this load was in flight. Its results
2582
+ * belong to a tenant that is no longer current and must be discarded. */
2583
+ isStale(epoch, tenant) {
2584
+ return this.epoch !== epoch || (0, import_core9.getTenant)() !== tenant;
2585
+ }
2586
+ /** Record a memory serve: the running bytes are the already-evaluated module,
2587
+ * and `expected` is refreshed from the manifest hash the caller JUST passed —
2588
+ * so after a redeploy the record reads hash !== expected (STALE) instead of
2589
+ * the old in-sync pair. The ONE place both memory paths (load()'s early return
2590
+ * and loadModule's) record reuse. No-op if nothing was recorded yet. */
2591
+ markMemoryHit(bundleUrl, expectedHash) {
2592
+ const prev = this.provenance.get(bundleUrl);
2593
+ if (prev) {
2594
+ this.provenance.set(bundleUrl, { ...prev, source: "memory", expected: expectedHash?.toLowerCase() ?? prev.expected });
1743
2595
  }
1744
2596
  }
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
2597
+ // ── Public API ──────────────────────────────────────────────
2598
+ /**
2599
+ * Load a plugin component. Returns the RegisteredUIPlugin object.
2600
+ * Handles: in-memory cache → disk cache → download → evaluate → extract.
2601
+ *
2602
+ * `expectedHash` (SHA-256 hex from cp.getContextPlugin manifest) MUST be
2603
+ * provided for any bundle the user has not previously verified — without
2604
+ * it, a tampered bundle is `eval`'d as JS with full RN runtime + every
2605
+ * native capability the manifest declares.
2606
+ *
2607
+ * `grants` is what the MANIFEST grants this plugin (resolvePluginGrants) — the
2608
+ * only authority its device tools are registered under. Omitted (e.g. a
2609
+ * prewarm from the slim plugin list) → the bundle is loaded but its tools are
2610
+ * NOT registered: without the manifest there is nothing trusted to gate them
2611
+ * on, and registering with nothing would downgrade a registration the
2612
+ * manifest-driven tool loader already made.
2613
+ */
2614
+ async load(pluginId, bundleUrl, version, expectedHash, grants) {
2615
+ const tenant = this.requireTenant();
2616
+ const epoch = this.epoch;
2617
+ const cached = this.getComponentFromMemory(pluginId, bundleUrl);
2618
+ if (cached) {
2619
+ this.markMemoryHit(bundleUrl, expectedHash);
2620
+ if (grants) await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
2621
+ return cached;
2622
+ }
2623
+ let modulePromise = this.inflight.get(bundleUrl);
2624
+ if (!modulePromise) {
2625
+ const promise = this.loadModule(bundleUrl, version, expectedHash, tenant, epoch);
2626
+ modulePromise = promise;
2627
+ this.inflight.set(bundleUrl, promise);
2628
+ promise.catch(() => {
2629
+ }).finally(() => {
2630
+ if (this.inflight.get(bundleUrl) === promise) this.inflight.delete(bundleUrl);
2631
+ });
2632
+ }
2633
+ await modulePromise;
2634
+ if (this.isStale(epoch, tenant)) {
2635
+ throw new Error("[BundleManager] tenant changed while loading \u2014 discarding (fail closed)");
2636
+ }
2637
+ if (grants) await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
2638
+ const component = this.getComponentFromMemory(pluginId, bundleUrl);
2639
+ if (!component) {
2640
+ throw new Error(
2641
+ `Bundle loaded from ${bundleUrl} but no component found for "${pluginId}". Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
2642
+ );
2643
+ }
2644
+ return component;
2645
+ }
2646
+ /**
2647
+ * Load a bundle for its `tools/` section only — no UI required.
2648
+ *
2649
+ * Same download, same integrity check, same evaluation as `load()`; the only
2650
+ * difference is that a bundle exporting tools and no `Component` is a success
2651
+ * here rather than "no component found". Returns the tool names registered.
2652
+ *
2653
+ * `grants` is REQUIRED: the tools are registered under `pluginId` with exactly
2654
+ * what the manifest grants (see load()), never what the bundle claims.
2655
+ */
2656
+ async loadTools(pluginId, bundleUrl, version, expectedHash, grants) {
2657
+ if (!grants) {
2658
+ throw new Error(`[BundleManager] loadTools(${pluginId}) needs the manifest's grants (resolvePluginGrants) \u2014 refusing to register tools on the bundle's own claim`);
2659
+ }
2660
+ const tenant = this.requireTenant();
2661
+ const epoch = this.epoch;
2662
+ let modulePromise = this.inflight.get(bundleUrl);
2663
+ if (!modulePromise) {
2664
+ const promise = this.loadModule(bundleUrl, version, expectedHash, tenant, epoch);
2665
+ modulePromise = promise;
2666
+ this.inflight.set(bundleUrl, promise);
2667
+ promise.catch(() => {
2668
+ }).finally(() => {
2669
+ if (this.inflight.get(bundleUrl) === promise) this.inflight.delete(bundleUrl);
2670
+ });
2671
+ }
2672
+ await modulePromise;
2673
+ if (this.isStale(epoch, tenant)) {
2674
+ throw new Error("[BundleManager] tenant changed while loading tools \u2014 discarding (fail closed)");
2675
+ }
2676
+ await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
2677
+ const names = listDeviceTools().filter((t) => t.pluginId === pluginId).map((t) => t.name);
2678
+ if (names.length === 0) {
2679
+ console.warn(
2680
+ `[BundleManager] ${pluginId}: bundle loaded from ${bundleUrl} but registered no tools. Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
2681
+ );
2682
+ }
2683
+ return names;
2684
+ }
2685
+ /**
2686
+ * Prewarm: load every plugin bundle in the background after app boot, so a
2687
+ * later open is instant (in-memory hit) instead of round-tripping the
2688
+ * network. Errors are swallowed per-plugin — one bad bundle shouldn't
2689
+ * block others. Concurrency capped at 3.
2690
+ */
2691
+ async prewarmAll(items) {
2692
+ const queue = [...items];
2693
+ const concurrency = Math.min(3, queue.length);
2694
+ const start = Date.now();
2695
+ let okCount = 0;
2696
+ let failCount = 0;
2697
+ const worker = async () => {
2698
+ while (queue.length > 0) {
2699
+ const it = queue.shift();
2700
+ if (!it) return;
2701
+ try {
2702
+ await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash, it.grants);
2703
+ okCount++;
2704
+ } catch (err) {
2705
+ failCount++;
2706
+ console.log(`[prewarm] ${it.pluginId}: ${err instanceof Error ? err.message : String(err)}`);
2707
+ }
2708
+ }
1758
2709
  };
1759
- await storage.saveIndex(index);
1760
- } catch (err) {
1761
- console.warn("[BundleManager] Cache write error:", err);
2710
+ await Promise.all(Array.from({ length: concurrency }, worker));
2711
+ console.log(`[prewarm] ${okCount} loaded, ${failCount} failed in ${Date.now() - start}ms`);
1762
2712
  }
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
- );
2713
+ /**
2714
+ * Clear all caches (in-memory + storage).
2715
+ */
2716
+ async clearAll() {
2717
+ this.dropInMemory();
2718
+ this.cacheTenant = null;
2719
+ try {
2720
+ await getBundleStorage().clearAll();
2721
+ } catch (err) {
2722
+ console.warn("[BundleManager] clearAll error:", err);
2723
+ }
1792
2724
  }
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
- );
2725
+ /**
2726
+ * Clear cache for a specific plugin/bundle.
2727
+ */
2728
+ async clearPlugin(pluginId) {
2729
+ const storage = getBundleStorage();
2730
+ const index = await storage.loadIndex();
2731
+ const entry = index[pluginId];
2732
+ if (entry) {
2733
+ this.modules.delete(entry.bundleUrl);
2734
+ delete index[pluginId];
2735
+ await storage.saveIndex(index);
2736
+ try {
2737
+ await storage.deleteBundle(entry.localPath);
2738
+ } catch {
2739
+ }
2740
+ }
1797
2741
  }
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)}...).`
2742
+ /**
2743
+ * What every loaded bundle IS, right now: the sha256 of the bytes running,
2744
+ * the hash the server advertised, and where they came from.
2745
+ *
2746
+ * Deliberately not filtered or prettified — this is the raw answer to "did my
2747
+ * redeploy reach the phone?", and it is comparable directly against the
2748
+ * manifest `bundleHash` and the server's X-Bundle-SHA256 header.
2749
+ */
2750
+ getLoadedBundles() {
2751
+ return [...this.provenance.values()];
2752
+ }
2753
+ // ── Private: module loading pipeline ────────────────────────
2754
+ async loadModule(bundleUrl, version, expectedHash, expectedTenant, expectedEpoch) {
2755
+ if (this.isStale(expectedEpoch, expectedTenant)) {
2756
+ throw new Error("[BundleManager] tenant changed before load started \u2014 discarding (fail closed)");
2757
+ }
2758
+ if (this.modules.has(bundleUrl)) {
2759
+ this.markMemoryHit(bundleUrl, expectedHash);
2760
+ return;
2761
+ }
2762
+ const storage = getBundleStorage();
2763
+ const index = await storage.loadIndex();
2764
+ const expectedLc = expectedHash ? expectedHash.toLowerCase() : null;
2765
+ let content = null;
2766
+ let verifiedHash = null;
2767
+ let servedFrom = "download";
2768
+ for (const entry of Object.values(index)) {
2769
+ if (entry.bundleUrl === bundleUrl && entry.version === version) {
2770
+ const age = Date.now() - entry.timestamp;
2771
+ const hashOk = expectedLc && entry.bundleHash && entry.bundleHash.toLowerCase() === expectedLc;
2772
+ if (age < CACHE_TTL && hashOk) {
2773
+ content = await storage.readBundle(entry.localPath);
2774
+ if (content) {
2775
+ verifiedHash = entry.bundleHash.toLowerCase();
2776
+ servedFrom = "disk-cache";
2777
+ break;
2778
+ }
2779
+ }
2780
+ }
2781
+ }
2782
+ if (!content) {
2783
+ const downloaded = await this.download(bundleUrl, expectedLc);
2784
+ content = downloaded.content;
2785
+ verifiedHash = downloaded.sha256.toLowerCase();
2786
+ if (this.isStale(expectedEpoch, expectedTenant)) {
2787
+ throw new Error("[BundleManager] tenant changed during download \u2014 discarding (fail closed)");
2788
+ }
2789
+ try {
2790
+ const localPath = await storage.writeBundle(`${verifiedHash}.js`, content);
2791
+ if (!this.isStale(expectedEpoch, expectedTenant)) {
2792
+ index[bundleUrl] = {
2793
+ localPath,
2794
+ version,
2795
+ timestamp: Date.now(),
2796
+ bundleUrl,
2797
+ bundleHash: downloaded.sha256,
2798
+ tenant: expectedTenant
2799
+ };
2800
+ await storage.saveIndex(index);
2801
+ }
2802
+ } catch (err) {
2803
+ console.warn("[BundleManager] Cache write error:", err);
2804
+ }
2805
+ }
2806
+ if (this.isStale(expectedEpoch, expectedTenant)) {
2807
+ throw new Error("[BundleManager] tenant changed while loading \u2014 discarding stale bundle before eval (fail closed)");
2808
+ }
2809
+ const mod = this.evaluate(content, bundleUrl, verifiedHash);
2810
+ this.modules.set(bundleUrl, mod);
2811
+ this.provenance.set(bundleUrl, {
2812
+ bundleUrl,
2813
+ version,
2814
+ hash: verifiedHash,
2815
+ expected: expectedLc,
2816
+ source: servedFrom,
2817
+ // The captured owner — same rule as the disk stamp. The final stale check
2818
+ // above guarantees getTenant() === expectedTenant here, but binding it to
2819
+ // the captured value keeps the ownership record impossible to mislabel.
2820
+ tenant: expectedTenant,
2821
+ loadedAt: Date.now()
2822
+ });
2823
+ console.log(
2824
+ `[BundleManager] RUNNING ${bundleUrl} v${version} sha=${(verifiedHash || "none").slice(0, 12)} (${servedFrom}${expectedLc && verifiedHash && expectedLc !== verifiedHash ? `, EXPECTED ${expectedLc.slice(0, 12)} \u2014 STALE` : ""})`
1802
2825
  );
1803
2826
  }
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 {
2827
+ /**
2828
+ * Register THIS plugin's device tools under the TRUSTED declaration, then
2829
+ * record that declaration on the disk-cache entry. Runs on every trusted load
2830
+ * — memory hit included — so the grants in force are always the latest the
2831
+ * manifest gave (a revoked grant stops applying on the next load, not the next
2832
+ * app start). Callers run it only after their stale check.
2833
+ */
2834
+ async commitTools(pluginId, bundleUrl, grants, tenant, epoch) {
2835
+ const declaration = {
2836
+ pluginId,
2837
+ capabilities: grants.capabilities,
2838
+ stateDomains: grants.stateDomains
2839
+ };
2840
+ this.registerToolsFromModule(this.modules.get(bundleUrl), declaration);
2841
+ await this.recordTrustedDeclaration(bundleUrl, declaration, tenant, epoch);
1841
2842
  }
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;
2843
+ /**
2844
+ * Put the plugin's own `tools` section in the device tool registry — under the
2845
+ * pluginId and grants the MANIFEST resolved, never the bundle's own `id`,
2846
+ * `capabilities` or `stateDomains`. A bundle is code the manifest authorised
2847
+ * by hash; what it may touch is the manifest's to say, same as its rendered
2848
+ * surface (resolvePluginRuntimeProps). Its own `id` would also let it register
2849
+ * as another plugin — including the host's platform tools, which a same-id
2850
+ * re-registration replaces.
2851
+ *
2852
+ * Only this plugin's export (the same lookup the component uses) is
2853
+ * registered. Another export's tools in the same bundle belong to another
2854
+ * plugin and register when THAT plugin's manifest loads it.
2855
+ */
2856
+ registerToolsFromModule(mod, { pluginId, capabilities, stateDomains }) {
2857
+ if (!mod) return;
2858
+ const own = this.findPluginExport(mod, pluginId, hasTools);
2859
+ const others = Object.keys(mod).filter((k) => hasTools(mod[k]) && mod[k] !== own);
2860
+ if (others.length > 0) {
2861
+ console.warn(
2862
+ `[BundleManager] ${pluginId}: not registering tools from export(s) ${others.join(", ")} \u2014 they are not this plugin's, and only their own manifest can grant them`
2863
+ );
2864
+ }
2865
+ if (!own) return;
2866
+ const ungranted = [
2867
+ ...(Array.isArray(own.stateDomains) ? own.stateDomains : []).filter((d) => !stateDomains?.includes(d)),
2868
+ ...Object.keys(own.capabilities || {}).filter((k) => own.capabilities[k] === true && !capabilities?.[k])
2869
+ ];
2870
+ if (ungranted.length > 0) {
2871
+ console.warn(
2872
+ `[BundleManager] ${pluginId}: bundle declares ${ungranted.join(", ")} but the manifest does not grant it \u2014 denied`
2873
+ );
2874
+ }
2875
+ try {
2876
+ registerDeviceTools(pluginId, own.tools, capabilities, stateDomains);
2877
+ this.registeredToolPluginIds.add(pluginId);
2878
+ } catch (err) {
2879
+ console.warn(
2880
+ `[BundleManager] tool registration failed for ${pluginId}:`,
2881
+ err instanceof Error ? err.message : String(err)
2882
+ );
2883
+ }
1868
2884
  }
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);
2885
+ /**
2886
+ * Record the trusted declaration on this tenant's disk-cache entry for the
2887
+ * bytes RUNNING — the recovery path (tools/loader loadFromDiskCache) has no
2888
+ * manifest and re-registers with exactly this. Only onto an entry this tenant
2889
+ * owns whose hash is the running hash: the grant was issued for these bytes,
2890
+ * and those are the bytes recovery will evaluate. Best-effort — a lost write
2891
+ * leaves no `trusted`, which recovery treats as fail closed.
2892
+ */
2893
+ async recordTrustedDeclaration(bundleUrl, declaration, tenant, epoch) {
2894
+ const key = JSON.stringify(declaration);
2895
+ if (this.recordedDeclarations.get(bundleUrl) === key) return;
2896
+ try {
2897
+ const storage = getBundleStorage();
2898
+ const index = await storage.loadIndex();
2899
+ const entry = index[bundleUrl];
2900
+ const running = this.provenance.get(bundleUrl)?.hash;
2901
+ if (!entry || entry.tenant !== tenant || !running || entry.bundleHash?.toLowerCase() !== running) return;
2902
+ if (this.isStale(epoch, tenant)) return;
2903
+ if (JSON.stringify(entry.trusted) !== key) {
2904
+ index[bundleUrl] = { ...entry, trusted: declaration };
2905
+ await storage.saveIndex(index);
2906
+ }
2907
+ this.recordedDeclarations.set(bundleUrl, key);
2908
+ } catch (err) {
2909
+ console.warn("[BundleManager] could not record trusted declaration:", err);
2910
+ }
1930
2911
  }
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") {
1953
- try {
1954
- cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1955
- } catch {
2912
+ /**
2913
+ * Download + integrity-verify a bundle. Refuses to return when:
2914
+ * - manifest hash and computed hash disagree
2915
+ * - server's `X-Bundle-SHA256` disagrees with manifest hash
2916
+ * - no expected hash AND no server hash header (cannot prove the bundle
2917
+ * wasn't tampered with — fail closed rather than trust the wire).
2918
+ */
2919
+ async download(bundleUrl, expectedHash) {
2920
+ const cacheBust = `${bundleUrl}${bundleUrl.includes("?") ? "&" : "?"}t=${Date.now()}`;
2921
+ const response = await (0, import_core9.apiFetchRaw)(cacheBust);
2922
+ if (!response.ok) {
2923
+ const body = await response.text().catch(() => "");
2924
+ throw new Error(`Download failed: ${response.status} ${response.statusText} \u2014 ${body.substring(0, 200)}`);
2925
+ }
2926
+ const content = await response.text();
2927
+ if (content.length < 50 || content.trimStart().startsWith("<!")) {
2928
+ throw new Error("Bundle content is empty or HTML \u2014 server may have returned an error page");
2929
+ }
2930
+ const headerHash = (response.headers.get("x-bundle-sha256") || "").toLowerCase();
2931
+ const computed = sha256Hex(content);
2932
+ if (expectedHash) {
2933
+ if (computed !== expectedHash) {
2934
+ throw new Error(
2935
+ `Bundle integrity FAIL: expected sha256=${expectedHash.slice(0, 12)}... got ${computed.slice(0, 12)}.... Refusing to evaluate.`
2936
+ );
2937
+ }
2938
+ if (headerHash && headerHash !== expectedHash) {
2939
+ throw new Error(
2940
+ `Bundle hash mismatch: manifest=${expectedHash.slice(0, 12)}... server=${headerHash.slice(0, 12)}.... Refusing to evaluate.`
2941
+ );
1956
2942
  }
2943
+ } else if (headerHash) {
2944
+ if (computed !== headerHash) {
2945
+ throw new Error(
2946
+ `Bundle body sha256 (${computed.slice(0, 12)}...) does not match server's X-Bundle-SHA256 (${headerHash.slice(0, 12)}...).`
2947
+ );
2948
+ }
2949
+ console.warn(`[BundleManager] No manifest bundleHash \u2014 verified via X-Bundle-SHA256 header only`);
1957
2950
  } else {
1958
- remaining.push(cmd);
2951
+ throw new Error(
2952
+ `No bundleHash in manifest and no X-Bundle-SHA256 header from server. Refusing to evaluate untrusted bundle.`
2953
+ );
1959
2954
  }
2955
+ return { content, sha256: computed };
1960
2956
  }
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 {
1967
- }
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) => {
2957
+ evaluate(content, bundleUrl, verifiedHash) {
2958
+ assertBundleIntegrity(content, verifiedHash, bundleUrl);
2959
+ let cleaned = content.replace(
2960
+ /__toESM\(require\(("[^"]+")\)(?:,\s*\d+)?\)/g,
2961
+ "require($1)"
2962
+ );
2963
+ cleaned = cleaned.replace(
2964
+ /module\.exports\s*=\s*__toCommonJS\((\w+)\)/g,
2965
+ "module.exports = $1"
2966
+ );
2967
+ const mod = { exports: {} };
1978
2968
  try {
1979
- fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1980
- } catch {
2969
+ const factory = new Function(
2970
+ "module",
2971
+ "exports",
2972
+ "require",
2973
+ "__filename",
2974
+ "__dirname",
2975
+ cleaned
2976
+ );
2977
+ factory(mod, mod.exports, requireShim, bundleUrl, "adas-plugin-bundles");
2978
+ } catch (err) {
2979
+ throw new Error(`Bundle evaluation failed: ${err instanceof Error ? err.message : String(err)}`);
1981
2980
  }
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) => {
2981
+ const raw = mod.exports;
2982
+ const materialized = {};
2983
+ for (const key of Object.getOwnPropertyNames(raw)) {
1990
2984
  try {
1991
- fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
2985
+ materialized[key] = raw[key];
1992
2986
  } catch {
1993
2987
  }
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
2988
  }
2002
- commandBufferRef.current = remaining;
2989
+ return materialized;
2003
2990
  }
2004
- for (const cmd of import_core7.pluginCommandEmitter.drainPluginCommands(pluginId)) {
2005
- dispatch(cmd);
2991
+ // ── Private: component extraction ───────────────────────────
2992
+ /** Find a plugin component in a loaded module. */
2993
+ getComponentFromMemory(pluginId, bundleUrl) {
2994
+ return this.findPluginExport(this.modules.get(bundleUrl), pluginId, hasComponent);
2995
+ }
2996
+ /**
2997
+ * Find THIS plugin's export in a loaded module — the one lookup both the
2998
+ * component and the device tools use, so a plugin's UI and its tools always
2999
+ * come from the same export. `accept` says what the caller needs from it.
3000
+ * Handles: direct key match, PascalCase key, default export, multi-plugin .id match.
3001
+ */
3002
+ findPluginExport(mod, pluginId, accept) {
3003
+ if (!mod) return null;
3004
+ const shortName = pluginId.includes(":") ? pluginId.split(":").pop() : pluginId;
3005
+ if (accept(mod[pluginId])) return mod[pluginId];
3006
+ const pascal = shortName.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase());
3007
+ if (accept(mod[pascal])) return mod[pascal];
3008
+ if (accept(mod.default)) return mod.default;
3009
+ for (const key of Object.keys(mod)) {
3010
+ const val = mod[key];
3011
+ if (val && (val.id === pluginId || val.id === shortName) && accept(val)) return val;
3012
+ }
3013
+ return null;
3014
+ }
3015
+ describeModule(mod) {
3016
+ if (!mod) return "(empty)";
3017
+ return Object.keys(mod).map((k) => {
3018
+ const v = mod[k];
3019
+ if (v && typeof v === "object" && v.id) return `${k}(id="${v.id}")`;
3020
+ return k;
3021
+ }).join(", ");
2006
3022
  }
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
3023
  };
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;
2022
- try {
2023
- import_core7.useChatStore.getState().sendMessage(t);
2024
- } catch (err) {
2025
- console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
2026
- }
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
- );
2037
- }
2038
- function createNativeBridge(pluginId, connectorId) {
2039
- const commandListeners = /* @__PURE__ */ new Set();
2040
- return {
2041
- onInit(cb) {
2042
- setTimeout(() => {
2043
- cb(buildContext(pluginId, connectorId));
2044
- }, 0);
2045
- },
2046
- async mcpCall(tool, args, cId) {
2047
- const targetConnector = cId || connectorId;
2048
- if (!targetConnector) {
2049
- throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
3024
+ bundleManager = new BundleManager();
3025
+ }
3026
+ });
3027
+
3028
+ // src/tools/loader.ts
3029
+ var loader_exports = {};
3030
+ __export(loader_exports, {
3031
+ ensureDeviceToolsLoaded: () => ensureDeviceToolsLoaded,
3032
+ resetDeviceToolLoader: () => resetDeviceToolLoader
3033
+ });
3034
+ async function ensureDeviceToolsLoaded({ force = false } = {}) {
3035
+ if (loadedOnce && !force) return listDeviceTools().map((t) => t.name);
3036
+ if (inflight) return inflight;
3037
+ inflight = (async () => {
3038
+ try {
3039
+ const summaries = await listPlugins();
3040
+ const manifests = await Promise.all(
3041
+ (summaries || []).map(
3042
+ (s) => getPlugin(s.id).catch(() => null)
3043
+ )
3044
+ );
3045
+ const targets = manifests.filter((m) => {
3046
+ const declared = m?.capabilities?.device_tools;
3047
+ return Array.isArray(declared) && declared.length > 0;
3048
+ });
3049
+ if (targets.length === 0) {
3050
+ const recovered = await loadFromDiskCache();
3051
+ if (recovered.length > 0) {
3052
+ console.log(`[deviceTools] catalog listed none \u2014 recovered from cache: ${recovered.join(", ")}`);
3053
+ return recovered;
3054
+ }
3055
+ console.log("[deviceTools] no device tools available yet \u2014 will look again next call");
3056
+ return [];
2050
3057
  }
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);
2054
- },
2055
- async mcpFetch(body) {
2056
- return (0, import_core7.mcpFetch)(body);
2057
- },
2058
- sendCommand(_type, _payload) {
2059
- },
2060
- onCommand(cb) {
2061
- commandListeners.add(cb);
2062
- return () => {
2063
- commandListeners.delete(cb);
2064
- };
2065
- },
2066
- close() {
2067
- import_core7.pluginCommandEmitter.emit("plugin-close", { pluginId });
2068
- },
2069
- sendMessage(text) {
2070
- const t = (text || "").trim();
2071
- if (!t) return;
3058
+ await Promise.all(
3059
+ targets.map(async (m) => {
3060
+ const bundleUrl = getNativeBundleUrl(m);
3061
+ if (!bundleUrl) {
3062
+ console.warn(`[deviceTools] ${m.id} declares device_tools but has no native bundle`);
3063
+ return;
3064
+ }
3065
+ try {
3066
+ await bundleManager.loadTools(
3067
+ m.id,
3068
+ bundleUrl,
3069
+ m.version || "1.0.0",
3070
+ getNativeBundleHash(m) || void 0,
3071
+ resolvePluginGrants({ pluginId: m.id, manifest: m })
3072
+ );
3073
+ } catch (err) {
3074
+ console.warn(`[deviceTools] ${m.id} failed to load:`, err?.message || String(err));
3075
+ }
3076
+ })
3077
+ );
3078
+ const names = listDeviceTools().map((t) => t.name);
3079
+ loadedOnce = names.length > 0;
3080
+ console.log(`[deviceTools] ready: ${names.length > 0 ? names.join(", ") : "(none registered \u2014 will retry next call)"}`);
3081
+ return names;
3082
+ } finally {
3083
+ inflight = null;
3084
+ }
3085
+ })();
3086
+ return inflight;
3087
+ }
3088
+ async function loadFromDiskCache() {
3089
+ try {
3090
+ const index = await getBundleStorage().loadIndex();
3091
+ const entries = Object.values(index || {});
3092
+ if (entries.length === 0) return [];
3093
+ const tenant = (0, import_core10.getTenant)();
3094
+ if (!tenant) {
3095
+ console.warn("[deviceTools] disk-cache recovery skipped \u2014 no active tenant");
3096
+ return [];
3097
+ }
3098
+ const mine = selectOwnedCacheEntries(entries, tenant);
3099
+ const skipped = entries.length - mine.length;
3100
+ if (skipped > 0) {
3101
+ console.log(`[deviceTools] disk-cache recovery: ${mine.length} for "${tenant}", skipped ${skipped} not owned by it`);
3102
+ }
3103
+ const declared = mine.filter((e) => !!e.trusted?.pluginId);
3104
+ if (declared.length < mine.length) {
3105
+ console.log(
3106
+ `[deviceTools] disk-cache recovery: skipped ${mine.length - declared.length} with no recorded manifest grant (fail closed)`
3107
+ );
3108
+ }
3109
+ if (declared.length === 0) return [];
3110
+ await Promise.all(declared.map(async (e) => {
2072
3111
  try {
2073
- import_core7.useChatStore.getState().sendMessage(t);
2074
- } catch (err) {
2075
- console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
3112
+ await bundleManager.loadTools(e.trusted.pluginId, e.bundleUrl, e.version, e.bundleHash, e.trusted);
3113
+ } catch {
2076
3114
  }
3115
+ }));
3116
+ return listDeviceTools().map((t) => t.name);
3117
+ } catch (err) {
3118
+ console.warn("[deviceTools] disk-cache recovery failed:", err?.message || String(err));
3119
+ return [];
3120
+ }
3121
+ }
3122
+ function resetDeviceToolLoader() {
3123
+ loadedOnce = false;
3124
+ }
3125
+ var import_core10, inflight, loadedOnce;
3126
+ var init_loader = __esm({
3127
+ "src/tools/loader.ts"() {
3128
+ "use strict";
3129
+ init_plugins();
3130
+ init_bundleManager();
3131
+ init_registry();
3132
+ init_storage();
3133
+ init_pluginRuntime();
3134
+ import_core10 = require("@ateam-ai-mobile/core");
3135
+ inflight = null;
3136
+ loadedOnce = false;
3137
+ }
3138
+ });
3139
+
3140
+ // src/index.ts
3141
+ var index_exports = {};
3142
+ __export(index_exports, {
3143
+ ActionButton: () => ActionButton,
3144
+ ActivityIndicator: () => import_react_native.ActivityIndicator,
3145
+ AdaOrb: () => AdaOrb,
3146
+ Animated: () => import_react_native.Animated,
3147
+ Badge: () => Badge,
3148
+ CameraPreview: () => CameraPreview,
3149
+ Card: () => Card,
3150
+ EmptyState: () => EmptyState,
3151
+ ErrorState: () => ErrorState,
3152
+ FlatList: () => import_react_native.FlatList,
3153
+ Image: () => import_react_native.Image,
3154
+ ListItem: () => ListItem,
3155
+ LoadingState: () => LoadingState,
3156
+ MemoryBundleStorage: () => MemoryBundleStorage,
3157
+ Modal: () => import_react_native.Modal,
3158
+ PLATFORM_VOICE_CALLER: () => PLATFORM_VOICE_CALLER,
3159
+ Platform: () => import_react_native.Platform,
3160
+ PluginSDK: () => PluginSDK,
3161
+ Pressable: () => import_react_native.Pressable,
3162
+ RefreshControl: () => import_react_native.RefreshControl,
3163
+ ScrollView: () => import_react_native.ScrollView,
3164
+ SearchBar: () => SearchBar,
3165
+ SectionList: () => import_react_native.SectionList,
3166
+ StyleSheet: () => import_react_native.StyleSheet,
3167
+ SurfaceCard: () => SurfaceCard,
3168
+ SurfaceDispatcher: () => SurfaceDispatcher,
3169
+ SurfaceDrawer: () => SurfaceDrawer,
3170
+ SurfaceFullscreen: () => SurfaceFullscreen,
3171
+ Switch: () => import_react_native.Switch,
3172
+ TabBar: () => TabBar,
3173
+ Text: () => import_react_native.Text,
3174
+ TextInput: () => import_react_native.TextInput,
3175
+ TouchableOpacity: () => import_react_native.TouchableOpacity,
3176
+ View: () => import_react_native.View,
3177
+ __resetSessionSyncForTest: () => __resetSessionSyncForTest,
3178
+ bundleManager: () => bundleManager,
3179
+ callDeviceTool: () => callDeviceTool,
3180
+ clearDeviceTools: () => clearDeviceTools,
3181
+ createNativeBridge: () => createNativeBridge,
3182
+ deviceStateV2: () => device_state_exports,
3183
+ deviceToolsActionForSession: () => deviceToolsActionForSession,
3184
+ ensureDeviceToolsLoaded: () => ensureDeviceToolsLoaded,
3185
+ explainCapability: () => explainCapability,
3186
+ gateDeviceState: () => gateDeviceState,
3187
+ gatePluginNative: () => gatePluginNative,
3188
+ gateVoice: () => gateVoice,
3189
+ getCameraPreviewProvider: () => getCameraPreviewProvider,
3190
+ getHaptics: () => getHaptics,
3191
+ getNativeBundleHash: () => getNativeBundleHash,
3192
+ getNativeBundleUrl: () => getNativeBundleUrl,
3193
+ getNativeComponentId: () => getNativeComponentId,
3194
+ getPlugin: () => getPlugin,
3195
+ getPluginNative: () => getPluginNative,
3196
+ handleServerDeviceToolCall: () => handleServerDeviceToolCall,
3197
+ hasDeviceTool: () => hasDeviceTool,
3198
+ implementedStateDomains: () => implementedStateDomains,
3199
+ implementedVoice: () => implementedVoice,
3200
+ isActionable: () => isActionable,
3201
+ isDeviceToolCommand: () => isDeviceToolCommand,
3202
+ isInlineSurface: () => isInlineSurface,
3203
+ isMountSurface: () => isMountSurface,
3204
+ listDeviceTools: () => listDeviceTools,
3205
+ listPlugins: () => listPlugins,
3206
+ nativePluginRegistry: () => nativePluginRegistry,
3207
+ neverObserved: () => neverObserved,
3208
+ register: () => register,
3209
+ registerBundleModules: () => registerBundleModules,
3210
+ registerDeviceTools: () => registerDeviceTools,
3211
+ registerNativePlugin: () => registerNativePlugin,
3212
+ registerPlatformDeviceTools: () => registerPlatformDeviceTools,
3213
+ registerService: () => registerService,
3214
+ registerTools: () => registerTools,
3215
+ resetDeviceToolLoader: () => resetDeviceToolLoader,
3216
+ resolvePluginGrants: () => resolvePluginGrants,
3217
+ resolvePluginRuntimeProps: () => resolvePluginRuntimeProps,
3218
+ resolvePluginUrl: () => resolvePluginUrl,
3219
+ setBundleStorage: () => setBundleStorage,
3220
+ setCameraPreviewProvider: () => setCameraPreviewProvider,
3221
+ setDeviceStateProvider: () => setDeviceStateProvider,
3222
+ setDeviceToolContextProvider: () => setDeviceToolContextProvider,
3223
+ setDeviceToolResultPoster: () => setDeviceToolResultPoster,
3224
+ setHapticsProvider: () => setHapticsProvider,
3225
+ setPluginNativeProvider: () => setPluginNativeProvider,
3226
+ setPluginThemeColors: () => setPluginThemeColors,
3227
+ setVoiceProvider: () => setVoiceProvider,
3228
+ supportsNative: () => supportsNative,
3229
+ supportsWebView: () => supportsWebView,
3230
+ syncDeviceToolsForSession: () => syncDeviceToolsForSession,
3231
+ unregisterDeviceTools: () => unregisterDeviceTools,
3232
+ unregisterNativePlugin: () => unregisterNativePlugin,
3233
+ useApi: () => useApi,
3234
+ useNativeBridge: () => useNativeBridge,
3235
+ usePlugin: () => usePlugin,
3236
+ usePluginTheme: () => usePluginTheme,
3237
+ useRemotePlugins: () => useRemotePlugins,
3238
+ useSurfaceStore: () => useSurfaceStore
3239
+ });
3240
+ module.exports = __toCommonJS(index_exports);
3241
+ init_sdk();
3242
+
3243
+ // src/sdk/device-state.ts
3244
+ var device_state_exports = {};
3245
+ __export(device_state_exports, {
3246
+ DeviceBridgeError: () => DeviceBridgeError,
3247
+ explainCapability: () => explainCapability,
3248
+ isActionable: () => isActionable,
3249
+ neverObserved: () => neverObserved
3250
+ });
3251
+ var DeviceBridgeError = class extends Error {
3252
+ constructor(code, message, opts) {
3253
+ super(message);
3254
+ this.name = "DeviceBridgeError";
3255
+ this.code = code;
3256
+ this.domain = opts?.domain;
3257
+ this.recoverable = opts?.recoverable;
3258
+ }
3259
+ };
3260
+ var CONFIDENCE_ORDER = {
3261
+ unknown: 0,
3262
+ low: 1,
3263
+ medium: 2,
3264
+ high: 3
3265
+ };
3266
+ function isActionable(quality, budget) {
3267
+ const { freshness, confidence } = quality;
3268
+ if (freshness.state === "unknown" || freshness.state === "stale") return false;
3269
+ if (freshness.ageMs === null || freshness.ageMs > budget.maxAgeMs) return false;
3270
+ if (budget.minConfidence) {
3271
+ const have = CONFIDENCE_ORDER[confidence?.level ?? "unknown"];
3272
+ if (have < CONFIDENCE_ORDER[budget.minConfidence]) return false;
3273
+ }
3274
+ return true;
3275
+ }
3276
+ function neverObserved() {
3277
+ return { state: "unknown", observedAt: null, ageMs: null };
3278
+ }
3279
+ function explainCapability(status) {
3280
+ if (status.support === "unsupported") return "this device does not have it";
3281
+ if (status.permission === "denied") return "permission was denied";
3282
+ if (status.permission === "restricted") return "permission is restricted";
3283
+ if (status.permission === "notDetermined") return "permission has not been asked for yet";
3284
+ if (status.permission === "limited") return "only limited access was granted";
3285
+ if (status.restriction === "policyRestricted") return "this plugin did not declare it";
3286
+ if (status.restriction === "backgroundRestricted") return "the app is in the background";
3287
+ if (status.restriction === "lowPowerRestricted") return "the device is in low-power mode";
3288
+ if (status.availability === "busy") return "it is in use by something else";
3289
+ if (status.availability === "temporarilyUnavailable") return "it is temporarily unavailable";
3290
+ if (status.availability === "unavailable") return "it is unavailable";
3291
+ if (status.support === "unknown" || status.availability === "unknown") {
3292
+ return "its state could not be determined";
3293
+ }
3294
+ return null;
3295
+ }
3296
+
3297
+ // src/index.ts
3298
+ init_deviceStateHost();
3299
+ init_voiceHost();
3300
+ init_host();
3301
+ init_pluginRuntime();
3302
+ init_registry();
3303
+ init_loader();
3304
+
3305
+ // src/tools/platformTools.ts
3306
+ init_registry();
3307
+ var PLATFORM_PLUGIN_ID = "platform:device-tools";
3308
+ function registerPlatformDeviceTools() {
3309
+ registerDeviceTools(PLATFORM_PLUGIN_ID, [
3310
+ {
3311
+ name: "device.echo",
3312
+ description: "Echo a string back from the user's own device.",
3313
+ input_schema: {
3314
+ type: "object",
3315
+ properties: { text: { type: "string" } },
3316
+ required: ["text"]
3317
+ },
3318
+ run: (args) => ({
3319
+ ok: true,
3320
+ echoed: args && args.text || "",
3321
+ // The proof: a server-side implementation could not say this truthfully.
3322
+ ran_on: "device"
3323
+ })
2077
3324
  },
2078
- prepareMessage(text) {
2079
- const t = (text || "").trim();
2080
- if (!t) return;
2081
- import_core7.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
3325
+ {
3326
+ name: "device.ping",
3327
+ description: "Check that the device is reachable.",
3328
+ input_schema: { type: "object", properties: {} },
3329
+ run: () => ({ ok: true, ran_on: "device", at: (/* @__PURE__ */ new Date()).toISOString() })
2082
3330
  }
2083
- };
3331
+ ]);
3332
+ }
3333
+
3334
+ // src/tools/sessionSync.ts
3335
+ init_registry();
3336
+ function deviceToolsActionForSession(next, prevTenant2) {
3337
+ const tenant = next.authenticated ? next.tenant ?? null : null;
3338
+ if (tenant && tenant !== prevTenant2) return { action: "load", tenant };
3339
+ if (!tenant && prevTenant2) return { action: "reset", tenant: null };
3340
+ return { action: "none", tenant: prevTenant2 };
3341
+ }
3342
+ var REAL_DEPS = {
3343
+ clear: clearDeviceTools,
3344
+ registerPlatform: registerPlatformDeviceTools,
3345
+ // `./loader` reaches react-native via the bundle manager; import it only when
3346
+ // an effect actually fires (real app), never at module load (tests).
3347
+ resetLoader: () => {
3348
+ void Promise.resolve().then(() => (init_loader(), loader_exports)).then((m) => m.resetDeviceToolLoader());
3349
+ },
3350
+ load: () => {
3351
+ void Promise.resolve().then(() => (init_loader(), loader_exports)).then((m) => {
3352
+ m.resetDeviceToolLoader();
3353
+ return m.ensureDeviceToolsLoaded({ force: true });
3354
+ });
3355
+ }
3356
+ };
3357
+ var prevTenant = null;
3358
+ function syncDeviceToolsForSession(next, deps = REAL_DEPS) {
3359
+ const { action, tenant } = deviceToolsActionForSession(next, prevTenant);
3360
+ prevTenant = tenant;
3361
+ if (action === "load") {
3362
+ deps.clear();
3363
+ deps.registerPlatform();
3364
+ deps.load();
3365
+ } else if (action === "reset") {
3366
+ deps.resetLoader();
3367
+ }
3368
+ return action;
3369
+ }
3370
+ function __resetSessionSyncForTest() {
3371
+ prevTenant = null;
3372
+ }
3373
+
3374
+ // src/tools/serverCalls.ts
3375
+ init_registry();
3376
+ init_loader();
3377
+ var postResult = null;
3378
+ function setDeviceToolResultPoster(fn) {
3379
+ postResult = fn;
3380
+ }
3381
+ function isDeviceToolCommand(cmd) {
3382
+ return !!cmd && cmd.kind === "device_tool" && typeof cmd.tool === "string" && !!cmd.correlationId;
3383
+ }
3384
+ async function handleServerDeviceToolCall(cmd) {
3385
+ if (!isDeviceToolCommand(cmd)) return;
3386
+ const { tool, args, correlationId } = cmd;
3387
+ if (!postResult) {
3388
+ console.warn("[deviceTools] server called a tool but no result poster is installed");
3389
+ return;
3390
+ }
3391
+ try {
3392
+ await ensureDeviceToolsLoaded();
3393
+ const outcome = await callDeviceTool(tool, args || {});
3394
+ console.log(`[deviceTools] server-called ${tool} \u2192 ${outcome.ok ? "ok" : outcome.reason}`);
3395
+ await postResult(
3396
+ correlationId,
3397
+ outcome.ok ? { ok: true, ...unwrap(outcome.result) } : void 0,
3398
+ outcome.ok ? void 0 : outcome.error
3399
+ );
3400
+ } catch (err) {
3401
+ await postResult(correlationId, void 0, err?.message || String(err)).catch(() => {
3402
+ });
3403
+ }
2084
3404
  }
3405
+ function unwrap(result) {
3406
+ if (result && typeof result === "object" && !Array.isArray(result)) return result;
3407
+ return { result };
3408
+ }
3409
+
3410
+ // src/index.ts
3411
+ init_bundleManager();
3412
+ init_storage();
3413
+ init_native();
2085
3414
 
2086
3415
  // src/hooks/usePlugin.ts
2087
3416
  var import_react11 = require("react");
3417
+ init_bundleManager();
3418
+ init_host();
3419
+ init_pluginRuntime();
2088
3420
  function extractBundleUrl(pluginId, manifest) {
2089
3421
  const render = manifest.render;
2090
3422
  if (!render || typeof render !== "object") return { url: null, source: "none" };
@@ -2122,6 +3454,8 @@ function usePlugin(pluginId, manifest) {
2122
3454
  const bundleHash = extractBundleHash(manifest);
2123
3455
  const componentId = extractComponentId(manifest);
2124
3456
  const lookupId = componentId || pluginId;
3457
+ const grants = resolvePluginGrants({ pluginId, manifest });
3458
+ const grantsKey = JSON.stringify(grants);
2125
3459
  if (ignoredUrl) {
2126
3460
  console.warn(`[usePlugin] ${pluginId}: IGNORED connector-injected bundleUrl="${ignoredUrl}". The host derives bundle URLs from pluginId via the canonical route.`);
2127
3461
  }
@@ -2144,7 +3478,7 @@ function usePlugin(pluginId, manifest) {
2144
3478
  }
2145
3479
  if (bundleUrl) {
2146
3480
  try {
2147
- const component = await bundleManager.load(pluginId, bundleUrl, version, bundleHash);
3481
+ const component = await bundleManager.load(pluginId, bundleUrl, version, bundleHash, grants);
2148
3482
  if (cancelled) return;
2149
3483
  setPlugin(component);
2150
3484
  setLoading(false);
@@ -2181,13 +3515,13 @@ function usePlugin(pluginId, manifest) {
2181
3515
  return () => {
2182
3516
  cancelled = true;
2183
3517
  };
2184
- }, [pluginId, lookupId, bundleUrl, bundleHash, version]);
3518
+ }, [pluginId, lookupId, bundleUrl, bundleHash, version, grantsKey]);
2185
3519
  return { plugin, loading, error, bundleMissing };
2186
3520
  }
2187
3521
 
2188
3522
  // src/hooks/useRemotePlugins.ts
2189
3523
  var import_react12 = require("react");
2190
- var import_core8 = require("@ateam-ai-mobile/core");
3524
+ var import_core11 = require("@ateam-ai-mobile/core");
2191
3525
  function useRemotePlugins(solutionId) {
2192
3526
  const [plugins, setPlugins] = (0, import_react12.useState)([]);
2193
3527
  const [loading, setLoading] = (0, import_react12.useState)(true);
@@ -2199,7 +3533,7 @@ function useRemotePlugins(solutionId) {
2199
3533
  const targetSolutionId = solutionId || "current";
2200
3534
  let data;
2201
3535
  try {
2202
- data = await (0, import_core8.apiFetch)(`/api/solutions/${targetSolutionId}/ui-plugins`);
3536
+ data = await (0, import_core11.apiFetch)(`/api/solutions/${targetSolutionId}/ui-plugins`);
2203
3537
  } catch (err) {
2204
3538
  if (err?.message?.includes("404")) {
2205
3539
  console.warn(`[useRemotePlugins] Solution ${targetSolutionId} not found or has no plugins`);
@@ -2255,108 +3589,14 @@ function useRemotePlugins(solutionId) {
2255
3589
  };
2256
3590
  }
2257
3591
 
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
- }
3592
+ // src/index.ts
3593
+ init_plugins();
3594
+ init_SurfaceDrawer();
3595
+ init_SurfaceCard();
3596
+ init_SurfaceFullscreen();
3597
+ init_AdaOrb();
3598
+ init_store();
3599
+ init_SurfaceDispatcher();
2360
3600
  // Annotate the CommonJS export names for ESM import in node:
2361
3601
  0 && (module.exports = {
2362
3602
  ActionButton,
@@ -2364,6 +3604,7 @@ function resolvePluginUrl(manifest, tenant) {
2364
3604
  AdaOrb,
2365
3605
  Animated,
2366
3606
  Badge,
3607
+ CameraPreview,
2367
3608
  Card,
2368
3609
  EmptyState,
2369
3610
  ErrorState,
@@ -2373,6 +3614,7 @@ function resolvePluginUrl(manifest, tenant) {
2373
3614
  LoadingState,
2374
3615
  MemoryBundleStorage,
2375
3616
  Modal,
3617
+ PLATFORM_VOICE_CALLER,
2376
3618
  Platform,
2377
3619
  PluginSDK,
2378
3620
  Pressable,
@@ -2391,30 +3633,61 @@ function resolvePluginUrl(manifest, tenant) {
2391
3633
  TextInput,
2392
3634
  TouchableOpacity,
2393
3635
  View,
3636
+ __resetSessionSyncForTest,
2394
3637
  bundleManager,
3638
+ callDeviceTool,
3639
+ clearDeviceTools,
2395
3640
  createNativeBridge,
3641
+ deviceStateV2,
3642
+ deviceToolsActionForSession,
3643
+ ensureDeviceToolsLoaded,
3644
+ explainCapability,
3645
+ gateDeviceState,
2396
3646
  gatePluginNative,
3647
+ gateVoice,
3648
+ getCameraPreviewProvider,
2397
3649
  getHaptics,
2398
3650
  getNativeBundleHash,
2399
3651
  getNativeBundleUrl,
2400
3652
  getNativeComponentId,
2401
3653
  getPlugin,
2402
3654
  getPluginNative,
3655
+ handleServerDeviceToolCall,
3656
+ hasDeviceTool,
3657
+ implementedStateDomains,
3658
+ implementedVoice,
3659
+ isActionable,
3660
+ isDeviceToolCommand,
2403
3661
  isInlineSurface,
2404
3662
  isMountSurface,
3663
+ listDeviceTools,
2405
3664
  listPlugins,
2406
3665
  nativePluginRegistry,
3666
+ neverObserved,
2407
3667
  register,
2408
3668
  registerBundleModules,
3669
+ registerDeviceTools,
2409
3670
  registerNativePlugin,
3671
+ registerPlatformDeviceTools,
2410
3672
  registerService,
3673
+ registerTools,
3674
+ resetDeviceToolLoader,
3675
+ resolvePluginGrants,
3676
+ resolvePluginRuntimeProps,
2411
3677
  resolvePluginUrl,
2412
3678
  setBundleStorage,
3679
+ setCameraPreviewProvider,
3680
+ setDeviceStateProvider,
3681
+ setDeviceToolContextProvider,
3682
+ setDeviceToolResultPoster,
2413
3683
  setHapticsProvider,
2414
3684
  setPluginNativeProvider,
2415
3685
  setPluginThemeColors,
3686
+ setVoiceProvider,
2416
3687
  supportsNative,
2417
3688
  supportsWebView,
3689
+ syncDeviceToolsForSession,
3690
+ unregisterDeviceTools,
2418
3691
  unregisterNativePlugin,
2419
3692
  useApi,
2420
3693
  useNativeBridge,