@ateam-ai-mobile/plugin-runtime 0.1.0 → 0.1.2

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
@@ -64,6 +64,7 @@ __export(index_exports, {
64
64
  View: () => import_react_native.View,
65
65
  bundleManager: () => bundleManager,
66
66
  createNativeBridge: () => createNativeBridge,
67
+ gatePluginNative: () => gatePluginNative,
67
68
  getHaptics: () => getHaptics,
68
69
  getNativeBundleHash: () => getNativeBundleHash,
69
70
  getNativeBundleUrl: () => getNativeBundleUrl,
@@ -410,6 +411,7 @@ var styles = import_react_native2.StyleSheet.create({
410
411
 
411
412
  // src/sdk/usePluginTheme.ts
412
413
  var import_react2 = require("react");
414
+ var import_react_native3 = require("react-native");
413
415
  var import_core = require("@ateam-ai-mobile/core");
414
416
  var adaColors = {
415
417
  bg: import_core.ada.paper,
@@ -432,7 +434,7 @@ var adaColors = {
432
434
  purpleSoft: import_core.ada.orangeSoft
433
435
  };
434
436
  var _colors = adaColors;
435
- var _isDark = false;
437
+ var _isDark = null;
436
438
  var _subscribers = /* @__PURE__ */ new Set();
437
439
  function setPluginThemeColors(colors, opts) {
438
440
  _colors = { ...adaColors, ...colors };
@@ -447,7 +449,9 @@ function subscribe(fn) {
447
449
  }
448
450
  function usePluginTheme() {
449
451
  const colors = (0, import_react2.useSyncExternalStore)(subscribe, () => _colors);
450
- const isDark = (0, import_react2.useSyncExternalStore)(subscribe, () => _isDark);
452
+ const hostIsDark = (0, import_react2.useSyncExternalStore)(subscribe, () => _isDark);
453
+ const scheme = (0, import_react_native3.useColorScheme)();
454
+ const isDark = hostIsDark ?? scheme === "dark";
451
455
  return (0, import_react2.useMemo)(() => ({
452
456
  colors,
453
457
  spacing: {
@@ -548,6 +552,34 @@ function setPluginNativeProvider(native) {
548
552
  function getPluginNative() {
549
553
  return _pluginNative;
550
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 || {};
567
+ 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")
581
+ };
582
+ }
551
583
  var _nativeRegistry = /* @__PURE__ */ new Map();
552
584
  function registerNativePlugin(id, plugin) {
553
585
  _nativeRegistry.set(id, plugin);
@@ -584,1741 +616,1746 @@ function resolveBundleModule(name) {
584
616
  }
585
617
 
586
618
  // src/bundles/bundleManager.ts
587
- var import_react4 = __toESM(require("react"));
588
- var import_react5 = require("react");
619
+ var import_react8 = __toESM(require("react"));
620
+ var import_react9 = require("react");
589
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"));
590
628
  var import_core2 = require("@ateam-ai-mobile/core");
591
629
 
592
- // src/utils/sha256.ts
593
- var K = new Uint32Array([
594
- 1116352408,
595
- 1899447441,
596
- 3049323471,
597
- 3921009573,
598
- 961987163,
599
- 1508970993,
600
- 2453635748,
601
- 2870763221,
602
- 3624381080,
603
- 310598401,
604
- 607225278,
605
- 1426881987,
606
- 1925078388,
607
- 2162078206,
608
- 2614888103,
609
- 3248222580,
610
- 3835390401,
611
- 4022224774,
612
- 264347078,
613
- 604807628,
614
- 770255983,
615
- 1249150122,
616
- 1555081692,
617
- 1996064986,
618
- 2554220882,
619
- 2821834349,
620
- 2952996808,
621
- 3210313671,
622
- 3336571891,
623
- 3584528711,
624
- 113926993,
625
- 338241895,
626
- 666307205,
627
- 773529912,
628
- 1294757372,
629
- 1396182291,
630
- 1695183700,
631
- 1986661051,
632
- 2177026350,
633
- 2456956037,
634
- 2730485921,
635
- 2820302411,
636
- 3259730800,
637
- 3345764771,
638
- 3516065817,
639
- 3600352804,
640
- 4094571909,
641
- 275423344,
642
- 430227734,
643
- 506948616,
644
- 659060556,
645
- 883997877,
646
- 958139571,
647
- 1322822218,
648
- 1537002063,
649
- 1747873779,
650
- 1955562222,
651
- 2024104815,
652
- 2227730452,
653
- 2361852424,
654
- 2428436474,
655
- 2756734187,
656
- 3204031479,
657
- 3329325298
658
- ]);
659
- var rotr = (n, x) => x >>> n | x << 32 - n;
660
- function sha256Hex(message) {
661
- const utf8 = unescape(encodeURIComponent(message));
662
- const bytes = new Uint8Array(utf8.length);
663
- for (let i = 0; i < utf8.length; i++) bytes[i] = utf8.charCodeAt(i);
664
- const bitLen = bytes.length * 8;
665
- const padLen = (56 - (bytes.length + 1) % 64 + 64) % 64;
666
- const buf = new Uint8Array(bytes.length + 1 + padLen + 8);
667
- buf.set(bytes);
668
- buf[bytes.length] = 128;
669
- const len = buf.length;
670
- buf[len - 4] = bitLen >>> 24 & 255;
671
- buf[len - 3] = bitLen >>> 16 & 255;
672
- buf[len - 2] = bitLen >>> 8 & 255;
673
- buf[len - 1] = bitLen & 255;
674
- const h = new Uint32Array([
675
- 1779033703,
676
- 3144134277,
677
- 1013904242,
678
- 2773480762,
679
- 1359893119,
680
- 2600822924,
681
- 528734635,
682
- 1541459225
683
- ]);
684
- const w = new Uint32Array(64);
685
- for (let i = 0; i < buf.length; i += 64) {
686
- for (let t = 0; t < 16; t++) {
687
- const j = i + t * 4;
688
- w[t] = buf[j] << 24 | buf[j + 1] << 16 | buf[j + 2] << 8 | buf[j + 3];
689
- }
690
- for (let t = 16; t < 64; t++) {
691
- const s0 = rotr(7, w[t - 15]) ^ rotr(18, w[t - 15]) ^ w[t - 15] >>> 3;
692
- const s1 = rotr(17, w[t - 2]) ^ rotr(19, w[t - 2]) ^ w[t - 2] >>> 10;
693
- w[t] = w[t - 16] + s0 + w[t - 7] + s1 | 0;
694
- }
695
- let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
696
- for (let t = 0; t < 64; t++) {
697
- const S1 = rotr(6, e) ^ rotr(11, e) ^ rotr(25, e);
698
- const ch = e & f ^ ~e & g;
699
- const temp1 = hh + S1 + ch + K[t] + w[t] | 0;
700
- const S0 = rotr(2, a) ^ rotr(13, a) ^ rotr(22, a);
701
- const maj = a & b ^ a & c ^ b & c;
702
- const temp2 = S0 + maj | 0;
703
- hh = g;
704
- g = f;
705
- f = e;
706
- e = d + temp1 | 0;
707
- d = c;
708
- c = b;
709
- b = a;
710
- a = temp1 + temp2 | 0;
630
+ // 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
+ function AdaOrb({ size = 120, state = "idle", inscription = true }) {
638
+ const showInscription = inscription && size >= 60;
639
+ const center = size / 2;
640
+ const coreR = size * 0.32;
641
+ const innerHaloR = size * 0.42;
642
+ const outerHaloR = size * 0.5;
643
+ const coreScale = (0, import_react_native_reanimated.useSharedValue)(1);
644
+ const innerScale = (0, import_react_native_reanimated.useSharedValue)(1);
645
+ const outerScale = (0, import_react_native_reanimated.useSharedValue)(1);
646
+ const outerOpacity = (0, import_react_native_reanimated.useSharedValue)(0.55);
647
+ const rippleCycle = (0, import_react_native_reanimated.useSharedValue)(0);
648
+ const orbit1 = (0, import_react_native_reanimated.useSharedValue)(0);
649
+ const orbit2 = (0, import_react_native_reanimated.useSharedValue)(0);
650
+ const orbit3 = (0, import_react_native_reanimated.useSharedValue)(0);
651
+ const beat = (0, import_react_native_reanimated.useSharedValue)(1);
652
+ (0, import_react4.useEffect)(() => {
653
+ (0, import_react_native_reanimated.cancelAnimation)(coreScale);
654
+ (0, import_react_native_reanimated.cancelAnimation)(innerScale);
655
+ (0, import_react_native_reanimated.cancelAnimation)(outerScale);
656
+ (0, import_react_native_reanimated.cancelAnimation)(outerOpacity);
657
+ (0, import_react_native_reanimated.cancelAnimation)(rippleCycle);
658
+ (0, import_react_native_reanimated.cancelAnimation)(orbit1);
659
+ (0, import_react_native_reanimated.cancelAnimation)(orbit2);
660
+ (0, import_react_native_reanimated.cancelAnimation)(orbit3);
661
+ (0, import_react_native_reanimated.cancelAnimation)(beat);
662
+ rippleCycle.value = 0;
663
+ orbit1.value = 0;
664
+ orbit2.value = 0;
665
+ orbit3.value = 0;
666
+ beat.value = 1;
667
+ let breathMs = 3600;
668
+ let breathAmount = 0.025;
669
+ if (state === "listening") {
670
+ breathMs = 2e3;
671
+ breathAmount = 0.05;
672
+ rippleCycle.value = (0, import_react_native_reanimated.withRepeat)(
673
+ (0, import_react_native_reanimated.withTiming)(1, { duration: 2e3, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.quad) }),
674
+ -1,
675
+ false
676
+ );
677
+ } else if (state === "thinking") {
678
+ breathMs = 2400;
679
+ breathAmount = 0.04;
680
+ orbit1.value = (0, import_react_native_reanimated.withRepeat)(
681
+ (0, import_react_native_reanimated.withTiming)(1, { duration: 1100, easing: import_react_native_reanimated.Easing.linear }),
682
+ -1,
683
+ false
684
+ );
685
+ orbit2.value = (0, import_react_native_reanimated.withRepeat)(
686
+ (0, import_react_native_reanimated.withTiming)(1, { duration: 1900, easing: import_react_native_reanimated.Easing.linear }),
687
+ -1,
688
+ false
689
+ );
690
+ orbit3.value = (0, import_react_native_reanimated.withRepeat)(
691
+ (0, import_react_native_reanimated.withTiming)(-1, { duration: 2700, easing: import_react_native_reanimated.Easing.linear }),
692
+ -1,
693
+ false
694
+ );
695
+ } else if (state === "speaking") {
696
+ breathMs = 700;
697
+ breathAmount = 0.13;
698
+ beat.value = (0, import_react_native_reanimated.withRepeat)(
699
+ (0, import_react_native_reanimated.withSequence)(
700
+ (0, import_react_native_reanimated.withTiming)(1.08, { duration: 90, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.quad) }),
701
+ (0, import_react_native_reanimated.withTiming)(1, { duration: 130, easing: import_react_native_reanimated.Easing.in(import_react_native_reanimated.Easing.quad) }),
702
+ (0, import_react_native_reanimated.withTiming)(1.05, { duration: 80, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.quad) }),
703
+ (0, import_react_native_reanimated.withTiming)(1, { duration: 200, easing: import_react_native_reanimated.Easing.in(import_react_native_reanimated.Easing.quad) }),
704
+ (0, import_react_native_reanimated.withTiming)(1, { duration: 280 })
705
+ ),
706
+ -1,
707
+ false
708
+ );
711
709
  }
712
- h[0] = h[0] + a | 0;
713
- h[1] = h[1] + b | 0;
714
- h[2] = h[2] + c | 0;
715
- h[3] = h[3] + d | 0;
716
- h[4] = h[4] + e | 0;
717
- h[5] = h[5] + f | 0;
718
- h[6] = h[6] + g | 0;
719
- h[7] = h[7] + hh | 0;
720
- }
721
- let out = "";
722
- for (let i = 0; i < 8; i++) {
723
- out += h[i].toString(16).padStart(8, "0");
724
- }
725
- return out;
726
- }
727
-
728
- // src/bundles/storage.ts
729
- var MemoryBundleStorage = class {
730
- constructor() {
731
- this.index = {};
732
- this.files = /* @__PURE__ */ new Map();
733
- }
734
- async loadIndex() {
735
- return this.index;
736
- }
737
- async saveIndex(index) {
738
- this.index = index;
739
- }
740
- async readBundle(localPath) {
741
- return this.files.get(localPath) ?? null;
742
- }
743
- async writeBundle(key, content) {
744
- const localPath = `memory://${key}`;
745
- this.files.set(localPath, content);
746
- return localPath;
747
- }
748
- async deleteBundle(localPath) {
749
- this.files.delete(localPath);
750
- }
751
- async clearAll() {
752
- this.index = {};
753
- this.files.clear();
754
- }
755
- };
756
- var _storage = new MemoryBundleStorage();
757
- function setBundleStorage(storage) {
758
- _storage = storage;
759
- }
760
- function getBundleStorage() {
761
- return _storage;
762
- }
763
-
764
- // src/bundles/bundleManager.ts
765
- var ReactShim = {
766
- __esModule: true,
767
- default: import_react4.default,
768
- ...import_react4.default,
769
- // Explicitly include hooks (React 19 may not have them as own enumerable props)
770
- useState: import_react5.useState,
771
- useEffect: import_react5.useEffect,
772
- useCallback: import_react5.useCallback,
773
- useMemo: import_react5.useMemo,
774
- useRef: import_react5.useRef,
775
- useContext: import_react5.useContext,
776
- useReducer: import_react5.useReducer,
777
- useLayoutEffect: import_react5.useLayoutEffect,
778
- useImperativeHandle: import_react5.useImperativeHandle,
779
- useDebugValue: import_react5.useDebugValue,
780
- createElement: import_react4.default.createElement,
781
- Fragment: import_react4.default.Fragment,
782
- Component: import_react4.default.Component,
783
- PureComponent: import_react4.default.PureComponent,
784
- createContext: import_react4.default.createContext,
785
- forwardRef: import_react4.default.forwardRef,
786
- memo: import_react4.default.memo,
787
- Children: import_react4.default.Children,
788
- cloneElement: import_react4.default.cloneElement,
789
- isValidElement: import_react4.default.isValidElement,
790
- createRef: import_react4.default.createRef,
791
- lazy: import_react4.default.lazy,
792
- Suspense: import_react4.default.Suspense
793
- };
794
- var PluginSDKShim = Object.freeze({
795
- ...sdk_exports,
796
- PluginSDK: Object.freeze({
797
- ...PluginSDK,
798
- register: PluginSDK.register
799
- }),
800
- register
801
- });
802
- var requireShim = (name) => {
803
- if (name === "react") return ReactShim;
804
- if (name === "react-native") return ReactNative;
805
- if (name === "@adas/plugin-sdk") return PluginSDKShim;
806
- const hostModule = resolveBundleModule(name);
807
- if (hostModule !== void 0) return hostModule;
808
- console.warn(`[BundleManager] Unknown module: ${name} \u2014 register it via registerBundleModules()`);
809
- return {};
810
- };
811
- var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
812
- var BundleManager = class {
813
- constructor() {
814
- /** In-memory module cache: bundleUrl → evaluated module.exports */
815
- this.modules = /* @__PURE__ */ new Map();
816
- /** In-flight download deduplication: bundleUrl → Promise */
817
- this.inflight = /* @__PURE__ */ new Map();
818
- }
819
- // ── Public API ──────────────────────────────────────────────
820
- /**
821
- * Load a plugin component. Returns the RegisteredUIPlugin object.
822
- * Handles: in-memory cache → disk cache → download → evaluate → extract.
823
- *
824
- * `expectedHash` (SHA-256 hex from cp.getContextPlugin manifest) MUST be
825
- * provided for any bundle the user has not previously verified — without
826
- * it, a tampered bundle is `eval`'d as JS with full RN runtime + every
827
- * native capability the manifest declares.
828
- */
829
- async load(pluginId, bundleUrl, version, expectedHash) {
830
- const cached = this.getComponentFromMemory(pluginId, bundleUrl);
831
- if (cached) return cached;
832
- let modulePromise = this.inflight.get(bundleUrl);
833
- if (!modulePromise) {
834
- modulePromise = this.loadModule(bundleUrl, version, expectedHash);
835
- this.inflight.set(bundleUrl, modulePromise);
836
- modulePromise.catch(() => {
837
- }).finally(() => this.inflight.delete(bundleUrl));
838
- }
839
- await modulePromise;
840
- const component = this.getComponentFromMemory(pluginId, bundleUrl);
841
- if (!component) {
842
- throw new Error(
843
- `Bundle loaded from ${bundleUrl} but no component found for "${pluginId}". Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
844
- );
845
- }
846
- return component;
847
- }
848
- /**
849
- * Prewarm: load every plugin bundle in the background after app boot, so a
850
- * later open is instant (in-memory hit) instead of round-tripping the
851
- * network. Errors are swallowed per-plugin — one bad bundle shouldn't
852
- * block others. Concurrency capped at 3.
853
- */
854
- async prewarmAll(items) {
855
- const queue = [...items];
856
- const concurrency = Math.min(3, queue.length);
857
- const start = Date.now();
858
- let okCount = 0;
859
- let failCount = 0;
860
- const worker = async () => {
861
- while (queue.length > 0) {
862
- const it = queue.shift();
863
- if (!it) return;
864
- try {
865
- await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash);
866
- okCount++;
867
- } catch (err) {
868
- failCount++;
869
- console.log(`[prewarm] ${it.pluginId}: ${err instanceof Error ? err.message : String(err)}`);
870
- }
871
- }
710
+ const breathEasing = state === "speaking" ? import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) : import_react_native_reanimated.Easing.inOut(import_react_native_reanimated.Easing.sin);
711
+ coreScale.value = (0, import_react_native_reanimated.withRepeat)(
712
+ (0, import_react_native_reanimated.withSequence)(
713
+ (0, import_react_native_reanimated.withTiming)(1 + breathAmount, { duration: breathMs, easing: breathEasing }),
714
+ (0, import_react_native_reanimated.withTiming)(1, { duration: breathMs, easing: breathEasing })
715
+ ),
716
+ -1,
717
+ false
718
+ );
719
+ innerScale.value = (0, import_react_native_reanimated.withRepeat)(
720
+ (0, import_react_native_reanimated.withSequence)(
721
+ (0, import_react_native_reanimated.withTiming)(1 + breathAmount * 1.4, { duration: breathMs * 1.15, easing: breathEasing }),
722
+ (0, import_react_native_reanimated.withTiming)(1, { duration: breathMs * 1.15, easing: breathEasing })
723
+ ),
724
+ -1,
725
+ false
726
+ );
727
+ outerScale.value = (0, import_react_native_reanimated.withRepeat)(
728
+ (0, import_react_native_reanimated.withSequence)(
729
+ (0, import_react_native_reanimated.withTiming)(1 + breathAmount * 1.8, { duration: breathMs * 1.3, easing: breathEasing }),
730
+ (0, import_react_native_reanimated.withTiming)(1, { duration: breathMs * 1.3, easing: breathEasing })
731
+ ),
732
+ -1,
733
+ false
734
+ );
735
+ outerOpacity.value = (0, import_react_native_reanimated.withRepeat)(
736
+ (0, import_react_native_reanimated.withSequence)(
737
+ (0, import_react_native_reanimated.withTiming)(state === "idle" ? 0.55 : 0.8, { duration: breathMs * 1.3, easing: breathEasing }),
738
+ (0, import_react_native_reanimated.withTiming)(0.35, { duration: breathMs * 1.3, easing: breathEasing })
739
+ ),
740
+ -1,
741
+ false
742
+ );
743
+ }, [state]);
744
+ const coreProps = (0, import_react_native_reanimated.useAnimatedProps)(() => ({
745
+ r: coreR * coreScale.value * beat.value
746
+ }));
747
+ const innerProps = (0, import_react_native_reanimated.useAnimatedProps)(() => ({
748
+ r: innerHaloR * innerScale.value * beat.value
749
+ }));
750
+ const outerProps = (0, import_react_native_reanimated.useAnimatedProps)(() => ({
751
+ r: outerHaloR * outerScale.value,
752
+ opacity: outerOpacity.value
753
+ }));
754
+ const ripple1Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
755
+ const t = rippleCycle.value;
756
+ return {
757
+ r: outerHaloR * (1 + t * 0.55),
758
+ opacity: state === "listening" ? 0.55 * (1 - t) : 0
872
759
  };
873
- await Promise.all(Array.from({ length: concurrency }, worker));
874
- console.log(`[prewarm] ${okCount} loaded, ${failCount} failed in ${Date.now() - start}ms`);
875
- }
876
- /**
877
- * Clear all caches (in-memory + storage).
878
- */
879
- async clearAll() {
880
- this.modules.clear();
881
- try {
882
- await getBundleStorage().clearAll();
883
- } catch (err) {
884
- console.warn("[BundleManager] clearAll error:", err);
885
- }
886
- }
887
- /**
888
- * Clear cache for a specific plugin/bundle.
889
- */
890
- async clearPlugin(pluginId) {
891
- const storage = getBundleStorage();
892
- const index = await storage.loadIndex();
893
- const entry = index[pluginId];
894
- if (entry) {
895
- this.modules.delete(entry.bundleUrl);
896
- delete index[pluginId];
897
- await storage.saveIndex(index);
898
- try {
899
- await storage.deleteBundle(entry.localPath);
900
- } catch {
760
+ });
761
+ const ripple2Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
762
+ const t = (rippleCycle.value + 0.34) % 1;
763
+ return {
764
+ r: outerHaloR * (1 + t * 0.55),
765
+ opacity: state === "listening" ? 0.45 * (1 - t) : 0
766
+ };
767
+ });
768
+ const ripple3Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
769
+ const t = (rippleCycle.value + 0.67) % 1;
770
+ return {
771
+ r: outerHaloR * (1 + t * 0.55),
772
+ opacity: state === "listening" ? 0.35 * (1 - t) : 0
773
+ };
774
+ });
775
+ const spark1Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
776
+ const angle = orbit1.value * Math.PI * 2 - Math.PI / 2;
777
+ const r = coreR * 0.55;
778
+ return {
779
+ cx: center + Math.cos(angle) * r,
780
+ cy: center + Math.sin(angle) * r,
781
+ opacity: state === "thinking" ? 1 : 0
782
+ };
783
+ });
784
+ const spark2Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
785
+ const angle = orbit2.value * Math.PI * 2 + Math.PI * 0.6;
786
+ const r = coreR * 0.85;
787
+ return {
788
+ cx: center + Math.cos(angle) * r,
789
+ cy: center + Math.sin(angle) * r,
790
+ opacity: state === "thinking" ? 0.9 : 0
791
+ };
792
+ });
793
+ const spark3Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
794
+ const angle = orbit3.value * Math.PI * 2 + Math.PI * 1.3;
795
+ const r = coreR * 1.05;
796
+ return {
797
+ cx: center + Math.cos(angle) * r,
798
+ cy: center + Math.sin(angle) * r,
799
+ opacity: state === "thinking" ? 0.7 : 0
800
+ };
801
+ });
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" })
809
+ ] }),
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" })
813
+ ] }),
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" })
817
+ ] }),
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" })
822
+ ] })
823
+ ] }),
824
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
825
+ AnimatedCircle,
826
+ {
827
+ cx: center,
828
+ cy: center,
829
+ stroke: "#FF7A28",
830
+ strokeWidth: 1.5,
831
+ fill: "none",
832
+ animatedProps: ripple1Props
901
833
  }
902
- }
903
- }
904
- // ── Private: module loading pipeline ────────────────────────
905
- async loadModule(bundleUrl, version, expectedHash) {
906
- if (this.modules.has(bundleUrl)) return;
907
- const storage = getBundleStorage();
908
- const index = await storage.loadIndex();
909
- const expectedLc = expectedHash ? expectedHash.toLowerCase() : null;
910
- let content = null;
911
- for (const entry of Object.values(index)) {
912
- if (entry.bundleUrl === bundleUrl && entry.version === version) {
913
- const age = Date.now() - entry.timestamp;
914
- const hashOk = expectedLc && entry.bundleHash && entry.bundleHash.toLowerCase() === expectedLc;
915
- if (age < CACHE_TTL && hashOk) {
916
- content = await storage.readBundle(entry.localPath);
917
- if (content) break;
918
- }
834
+ ),
835
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
836
+ AnimatedCircle,
837
+ {
838
+ cx: center,
839
+ cy: center,
840
+ stroke: "#FF7A28",
841
+ strokeWidth: 1.5,
842
+ fill: "none",
843
+ animatedProps: ripple2Props
919
844
  }
920
- }
921
- if (!content) {
922
- const downloaded = await this.download(bundleUrl, expectedLc);
923
- content = downloaded.content;
924
- const safeName = bundleUrl.replace(/[^a-zA-Z0-9]/g, "_").slice(-80);
925
- try {
926
- const localPath = await storage.writeBundle(`${safeName}@${version}.js`, content);
927
- index[bundleUrl] = {
928
- localPath,
929
- version,
930
- timestamp: Date.now(),
931
- bundleUrl,
932
- bundleHash: downloaded.sha256
933
- };
934
- await storage.saveIndex(index);
935
- } catch (err) {
936
- console.warn("[BundleManager] Cache write error:", err);
845
+ ),
846
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
847
+ AnimatedCircle,
848
+ {
849
+ cx: center,
850
+ cy: center,
851
+ stroke: "#FF7A28",
852
+ strokeWidth: 1.5,
853
+ fill: "none",
854
+ animatedProps: ripple3Props
937
855
  }
938
- }
939
- const mod = this.evaluate(content, bundleUrl);
940
- this.modules.set(bundleUrl, mod);
941
- }
942
- /**
943
- * Download + integrity-verify a bundle. Refuses to return when:
944
- * - manifest hash and computed hash disagree
945
- * - server's `X-Bundle-SHA256` disagrees with manifest hash
946
- * - no expected hash AND no server hash header (cannot prove the bundle
947
- * wasn't tampered with — fail closed rather than trust the wire).
948
- */
949
- async download(bundleUrl, expectedHash) {
950
- const cacheBust = `${bundleUrl}${bundleUrl.includes("?") ? "&" : "?"}t=${Date.now()}`;
951
- const response = await (0, import_core2.apiFetchRaw)(cacheBust);
952
- if (!response.ok) {
953
- const body = await response.text().catch(() => "");
954
- throw new Error(`Download failed: ${response.status} ${response.statusText} \u2014 ${body.substring(0, 200)}`);
955
- }
956
- const content = await response.text();
957
- if (content.length < 50 || content.trimStart().startsWith("<!")) {
958
- throw new Error("Bundle content is empty or HTML \u2014 server may have returned an error page");
959
- }
960
- const headerHash = (response.headers.get("x-bundle-sha256") || "").toLowerCase();
961
- const computed = sha256Hex(content);
962
- if (expectedHash) {
963
- if (computed !== expectedHash) {
964
- throw new Error(
965
- `Bundle integrity FAIL: expected sha256=${expectedHash.slice(0, 12)}... got ${computed.slice(0, 12)}.... Refusing to evaluate.`
966
- );
856
+ ),
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)(
861
+ AnimatedCircle,
862
+ {
863
+ r: Math.max(3, size * 0.05),
864
+ fill: "url(#adaSpark)",
865
+ animatedProps: spark1Props
967
866
  }
968
- if (headerHash && headerHash !== expectedHash) {
969
- throw new Error(
970
- `Bundle hash mismatch: manifest=${expectedHash.slice(0, 12)}... server=${headerHash.slice(0, 12)}.... Refusing to evaluate.`
971
- );
867
+ ),
868
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
869
+ AnimatedCircle,
870
+ {
871
+ r: Math.max(2.5, size * 0.04),
872
+ fill: "url(#adaSpark)",
873
+ animatedProps: spark2Props
972
874
  }
973
- } else if (headerHash) {
974
- if (computed !== headerHash) {
975
- throw new Error(
976
- `Bundle body sha256 (${computed.slice(0, 12)}...) does not match server's X-Bundle-SHA256 (${headerHash.slice(0, 12)}...).`
977
- );
875
+ ),
876
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
877
+ AnimatedCircle,
878
+ {
879
+ r: Math.max(2, size * 0.032),
880
+ fill: "url(#adaSpark)",
881
+ animatedProps: spark3Props
978
882
  }
979
- console.warn(`[BundleManager] No manifest bundleHash \u2014 verified via X-Bundle-SHA256 header only`);
883
+ ),
884
+ showInscription && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
885
+ import_react_native_svg.Text,
886
+ {
887
+ x: center,
888
+ y: center + size * 0.11,
889
+ fontSize: size * 0.34,
890
+ fontWeight: "600",
891
+ fill: "#FFF5E0",
892
+ textAnchor: "middle",
893
+ letterSpacing: -size * 5e-3,
894
+ children: "iA"
895
+ }
896
+ )
897
+ ] }) });
898
+ }
899
+ var styles2 = import_react_native4.StyleSheet.create({
900
+ wrap: {
901
+ alignItems: "center",
902
+ justifyContent: "center"
903
+ }
904
+ });
905
+
906
+ // 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
+ function SurfaceDrawer({
911
+ visible,
912
+ onClose,
913
+ title,
914
+ subtitle,
915
+ privacy,
916
+ showOrb = true,
917
+ heightRatio = 0.85,
918
+ children
919
+ }) {
920
+ 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)(() => {
924
+ if (visible) {
925
+ import_react_native5.Animated.parallel([
926
+ import_react_native5.Animated.timing(fadeAnim, { toValue: 1, duration: 220, useNativeDriver: true }),
927
+ import_react_native5.Animated.spring(slideAnim, { toValue: 0, useNativeDriver: true, tension: 80, friction: 12 })
928
+ ]).start();
980
929
  } else {
981
- throw new Error(
982
- `No bundleHash in manifest and no X-Bundle-SHA256 header from server. Refusing to evaluate untrusted bundle.`
983
- );
930
+ fadeAnim.setValue(0);
931
+ slideAnim.setValue(sheetH);
984
932
  }
985
- return { content, sha256: computed };
933
+ }, [visible]);
934
+ const close = () => {
935
+ import_react_native5.Animated.parallel([
936
+ import_react_native5.Animated.timing(fadeAnim, { toValue: 0, duration: 160, useNativeDriver: true }),
937
+ import_react_native5.Animated.timing(slideAnim, { toValue: sheetH, duration: 200, useNativeDriver: true })
938
+ ]).start(() => onClose());
939
+ };
940
+ 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)(
944
+ import_react_native5.Animated.View,
945
+ {
946
+ style: [
947
+ styles3.sheet,
948
+ { height: sheetH, transform: [{ translateY: slideAnim }] }
949
+ ],
950
+ 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" })
961
+ ] }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles3.privacyText, children: "Only you can see this" })
963
+ ] }) : null
964
+ ] }),
965
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
966
+ import_react_native5.Pressable,
967
+ {
968
+ accessibilityRole: "button",
969
+ accessibilityLabel: "Close",
970
+ style: styles3.closeBtn,
971
+ onPress: close,
972
+ 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" }) })
974
+ }
975
+ )
976
+ ] }),
977
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles3.body, children })
978
+ ]
979
+ }
980
+ )
981
+ ] });
982
+ }
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
986
1064
  }
987
- evaluate(content, bundleUrl) {
988
- let cleaned = content.replace(
989
- /__toESM\(require\(("[^"]+")\)(?:,\s*\d+)?\)/g,
990
- "require($1)"
991
- );
992
- cleaned = cleaned.replace(
993
- /module\.exports\s*=\s*__toCommonJS\((\w+)\)/g,
994
- "module.exports = $1"
995
- );
996
- const mod = { exports: {} };
997
- try {
998
- const factory = new Function(
999
- "module",
1000
- "exports",
1001
- "require",
1002
- "__filename",
1003
- "__dirname",
1004
- cleaned
1005
- );
1006
- factory(mod, mod.exports, requireShim, bundleUrl, "adas-plugin-bundles");
1007
- } catch (err) {
1008
- throw new Error(`Bundle evaluation failed: ${err instanceof Error ? err.message : String(err)}`);
1009
- }
1010
- const raw = mod.exports;
1011
- const materialized = {};
1012
- for (const key of Object.getOwnPropertyNames(raw)) {
1013
- try {
1014
- materialized[key] = raw[key];
1015
- } catch {
1016
- }
1065
+ });
1066
+
1067
+ // 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
+ function SurfaceCard({
1073
+ icon,
1074
+ title,
1075
+ subtitle,
1076
+ onPress,
1077
+ onLongPress,
1078
+ showArrow,
1079
+ children
1080
+ }) {
1081
+ const arrow = showArrow ?? !!onPress;
1082
+ const handlePress = () => {
1083
+ if (!onPress) return;
1084
+ getHaptics()?.impact("light");
1085
+ onPress();
1086
+ };
1087
+ const handleLongPress = () => {
1088
+ if (!onLongPress) return;
1089
+ getHaptics()?.impact("medium");
1090
+ onLongPress();
1091
+ };
1092
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1093
+ import_react_native6.Pressable,
1094
+ {
1095
+ accessibilityRole: "button",
1096
+ accessibilityLabel: [icon, title, subtitle].filter(Boolean).join(", "),
1097
+ accessibilityHint: onLongPress ? "Long-press for more options" : void 0,
1098
+ style: styles4.card,
1099
+ onPress: onPress ? handlePress : void 0,
1100
+ onLongPress: onLongPress ? handleLongPress : void 0,
1101
+ disabled: !onPress && !onLongPress,
1102
+ 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
1108
+ ] }),
1109
+ arrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.Text, { style: styles4.arrow, children: "\u2192" }) : null
1110
+ ] }),
1111
+ children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native6.View, { style: styles4.body, children }) : null
1112
+ ]
1017
1113
  }
1018
- return materialized;
1114
+ );
1115
+ }
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
1019
1158
  }
1020
- // ── Private: component extraction ───────────────────────────
1021
- /**
1022
- * Find a plugin component in a loaded module.
1023
- * Handles: default export, direct key match, multi-plugin .id match.
1024
- */
1025
- getComponentFromMemory(pluginId, bundleUrl) {
1026
- const mod = this.modules.get(bundleUrl);
1027
- if (!mod) return null;
1028
- const shortName = pluginId.includes(":") ? pluginId.split(":").pop() : pluginId;
1029
- if (mod[pluginId] && typeof mod[pluginId].Component === "function") {
1030
- return mod[pluginId];
1031
- }
1032
- const pascal = shortName.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase());
1033
- if (mod[pascal] && typeof mod[pascal] === "object" && typeof mod[pascal].Component === "function") {
1034
- return mod[pascal];
1159
+ });
1160
+
1161
+ // 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
+ function SurfaceFullscreen({
1169
+ visible,
1170
+ onClose,
1171
+ status,
1172
+ hero,
1173
+ children,
1174
+ showWordmark = true,
1175
+ closeIcon = "x",
1176
+ closeLabel = "Close"
1177
+ }) {
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)(() => {
1181
+ if (visible) {
1182
+ import_react_native7.Animated.parallel([
1183
+ import_react_native7.Animated.timing(fadeAnim, { toValue: 1, duration: 280, useNativeDriver: true }),
1184
+ import_react_native7.Animated.spring(heroScale, { toValue: 1, useNativeDriver: true, tension: 60, friction: 11 })
1185
+ ]).start();
1186
+ } else {
1187
+ fadeAnim.setValue(0);
1188
+ heroScale.setValue(0.6);
1035
1189
  }
1036
- if (mod.default && typeof mod.default === "object" && typeof mod.default.Component === "function") {
1037
- return mod.default;
1190
+ }, [visible]);
1191
+ const close = () => {
1192
+ import_react_native7.Animated.parallel([
1193
+ import_react_native7.Animated.timing(fadeAnim, { toValue: 0, duration: 180, useNativeDriver: true }),
1194
+ import_react_native7.Animated.timing(heroScale, { toValue: 0.6, duration: 180, useNativeDriver: true })
1195
+ ]).start(() => onClose());
1196
+ };
1197
+ if (!visible) return null;
1198
+ const renderCloseIcon = () => {
1199
+ 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" }) });
1038
1201
  }
1039
- for (const key of Object.keys(mod)) {
1040
- const val = mod[key];
1041
- if (val && typeof val === "object" && val.id === shortName && typeof val.Component === "function") {
1042
- return val;
1043
- }
1202
+ 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" }) });
1044
1204
  }
1045
- return null;
1046
- }
1047
- describeModule(mod) {
1048
- if (!mod) return "(empty)";
1049
- return Object.keys(mod).map((k) => {
1050
- const v = mod[k];
1051
- if (v && typeof v === "object" && v.id) return `${k}(id="${v.id}")`;
1052
- return k;
1053
- }).join(", ");
1205
+ return closeIcon;
1206
+ };
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
1211
+ ] }),
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)(
1216
+ import_react_native7.Pressable,
1217
+ {
1218
+ accessibilityRole: "button",
1219
+ accessibilityLabel: closeLabel,
1220
+ style: styles5.closeBtn,
1221
+ onPress: close,
1222
+ hitSlop: 12,
1223
+ children: renderCloseIcon()
1224
+ }
1225
+ ),
1226
+ closeLabel ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native7.Text, { style: styles5.closeLabel, children: closeLabel }) : null
1227
+ ] })
1228
+ ] });
1229
+ }
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
1054
1295
  }
1055
- };
1056
- var bundleManager = new BundleManager();
1296
+ });
1057
1297
 
1058
- // src/bridge/native.ts
1059
- var import_react6 = require("react");
1060
- var import_core3 = require("@ateam-ai-mobile/core");
1061
- function buildContext(pluginId, connectorId) {
1062
- const user = (0, import_core3.getUser)();
1063
- return {
1064
- tenant: (0, import_core3.getTenant)(),
1065
- pluginId,
1066
- connectorId,
1067
- user: user ? { id: user.actorId || "", name: user.name || "", roles: [] } : void 0
1068
- };
1069
- }
1070
- function useNativeBridge(pluginId, connectorId, commandBufferRef) {
1071
- const initCallbackRef = (0, import_react6.useRef)(null);
1072
- const commandListenersRef = (0, import_react6.useRef)(/* @__PURE__ */ new Set());
1073
- const initializedRef = (0, import_react6.useRef)(false);
1074
- const fireInit = (0, import_react6.useCallback)(() => {
1075
- if (initializedRef.current || !initCallbackRef.current) return;
1076
- initCallbackRef.current(buildContext(pluginId, connectorId));
1077
- initializedRef.current = true;
1078
- }, [pluginId, connectorId]);
1079
- const onInit = (0, import_react6.useCallback)((cb) => {
1080
- initCallbackRef.current = cb;
1081
- setTimeout(fireInit, 0);
1082
- }, [fireInit]);
1083
- const mcpCall = (0, import_react6.useCallback)(async (toolOrConnector, argsOrTool, cIdOrArgs) => {
1084
- let tool;
1085
- let args;
1086
- let cId;
1087
- if (typeof argsOrTool === "string") {
1088
- cId = toolOrConnector;
1089
- tool = argsOrTool;
1090
- args = cIdOrArgs || {};
1091
- } else {
1092
- tool = toolOrConnector;
1093
- args = argsOrTool;
1094
- cId = typeof cIdOrArgs === "string" ? cIdOrArgs : void 0;
1095
- }
1096
- const targetConnector = cId || connectorId;
1097
- if (!targetConnector) {
1098
- console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2014 no connectorId. tool=${tool}`);
1099
- throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
1100
- }
1101
- try {
1102
- const data = await (0, import_core3.connectorCall)(targetConnector, tool, args);
1103
- if (!data.ok && data.error) {
1104
- throw new Error(data.error);
1105
- }
1106
- return (0, import_core3.unwrapMcpResult)(data.result);
1107
- } catch (err) {
1108
- console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2192 connector=${targetConnector} tool=${tool}:`, err?.message || err);
1109
- throw err;
1298
+ // 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;
1110
1319
  }
1111
- }, [pluginId, connectorId]);
1112
- const mcpFetch = (0, import_react6.useCallback)(async (body) => {
1113
- return (0, import_core3.mcpFetch)(body);
1114
- }, []);
1115
- const sendCommand = (0, import_react6.useCallback)((type, payload) => {
1116
- if (type === "plugin.command.result" && payload?.correlationId) {
1117
- (0, import_core3.postCommandResult)(payload.correlationId, payload.result, payload.error);
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 });
1118
1335
  return;
1119
1336
  }
1120
- }, [pluginId]);
1121
- const onCommand = (0, import_react6.useCallback)((cb) => {
1122
- commandListenersRef.current.add(cb);
1123
- if (commandBufferRef) {
1124
- const buf = commandBufferRef.current;
1125
- const remaining = [];
1126
- for (const cmd of buf) {
1127
- if (cmd.pluginId === pluginId && cmd.command !== "__focus") {
1128
- try {
1129
- cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1130
- } catch {
1131
- }
1132
- } else {
1133
- remaining.push(cmd);
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;
1134
1354
  }
1135
1355
  }
1136
- commandBufferRef.current = remaining;
1137
- }
1138
- for (const cmd of import_core3.pluginCommandEmitter.drainPluginCommands(pluginId)) {
1139
- try {
1140
- cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1141
- } catch {
1142
- }
1143
- }
1144
- return () => {
1145
- commandListenersRef.current.delete(cb);
1146
- };
1147
- }, [pluginId, commandBufferRef]);
1148
- (0, import_react6.useEffect)(() => {
1149
- const unsub = import_core3.pluginCommandEmitter.on("plugin-command", (cmd) => {
1150
- if (cmd.pluginId !== pluginId) return;
1151
- if (cmd.command === "__focus") return;
1152
- commandListenersRef.current.forEach((fn) => {
1153
- try {
1154
- fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1155
- } catch {
1156
- }
1157
- });
1356
+ return true;
1158
1357
  });
1159
- return unsub;
1160
- }, [pluginId]);
1161
- (0, import_react6.useEffect)(() => {
1162
- const timer = setTimeout(() => {
1163
- const dispatch = (cmd) => {
1164
- commandListenersRef.current.forEach((fn) => {
1165
- try {
1166
- fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1167
- } catch {
1168
- }
1169
- });
1170
- };
1171
- if (commandBufferRef) {
1172
- const remaining = [];
1173
- for (const cmd of commandBufferRef.current) {
1174
- if (cmd.pluginId === pluginId && cmd.command !== "__focus") dispatch(cmd);
1175
- else remaining.push(cmd);
1176
- }
1177
- commandBufferRef.current = remaining;
1178
- }
1179
- for (const cmd of import_core3.pluginCommandEmitter.drainPluginCommands(pluginId)) {
1180
- dispatch(cmd);
1181
- }
1182
- }, 500);
1183
- return () => clearTimeout(timer);
1184
- }, [pluginId, commandBufferRef]);
1185
- (0, import_react6.useEffect)(() => {
1186
- return () => {
1187
- commandListenersRef.current.clear();
1188
- initializedRef.current = false;
1189
- };
1190
- }, []);
1191
- const close = (0, import_react6.useCallback)(() => {
1192
- import_core3.pluginCommandEmitter.emit("plugin-close", { pluginId });
1193
- }, [pluginId]);
1194
- const sendMessage = (0, import_react6.useCallback)((text) => {
1195
- const t = (text || "").trim();
1196
- if (!t) return;
1197
- try {
1198
- import_core3.useChatStore.getState().sendMessage(t);
1199
- } catch (err) {
1200
- console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
1201
- }
1202
- }, []);
1203
- const prepareMessage = (0, import_react6.useCallback)((text) => {
1204
- const t = (text || "").trim();
1205
- if (!t) return;
1206
- import_core3.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
1207
- }, []);
1208
- return (0, import_react6.useMemo)(
1209
- () => ({ onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage }),
1210
- [onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage]
1211
- );
1212
- }
1213
- function createNativeBridge(pluginId, connectorId) {
1214
- const commandListeners = /* @__PURE__ */ new Set();
1215
- return {
1216
- onInit(cb) {
1217
- setTimeout(() => {
1218
- cb(buildContext(pluginId, connectorId));
1219
- }, 0);
1220
- },
1221
- async mcpCall(tool, args, cId) {
1222
- const targetConnector = cId || connectorId;
1223
- if (!targetConnector) {
1224
- throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
1225
- }
1226
- const data = await (0, import_core3.connectorCall)(targetConnector, tool, args);
1227
- if (!data.ok && data.error) throw new Error(data.error);
1228
- return (0, import_core3.unwrapMcpResult)(data.result);
1229
- },
1230
- async mcpFetch(body) {
1231
- return (0, import_core3.mcpFetch)(body);
1232
- },
1233
- sendCommand(_type, _payload) {
1234
- },
1235
- onCommand(cb) {
1236
- commandListeners.add(cb);
1237
- return () => {
1238
- commandListeners.delete(cb);
1239
- };
1240
- },
1241
- close() {
1242
- import_core3.pluginCommandEmitter.emit("plugin-close", { pluginId });
1243
- },
1244
- sendMessage(text) {
1245
- const t = (text || "").trim();
1246
- if (!t) return;
1247
- try {
1248
- import_core3.useChatStore.getState().sendMessage(t);
1249
- } catch (err) {
1250
- console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
1251
- }
1252
- },
1253
- prepareMessage(text) {
1254
- const t = (text || "").trim();
1255
- if (!t) return;
1256
- import_core3.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
1257
- }
1258
- };
1259
- }
1260
-
1261
- // src/hooks/usePlugin.ts
1262
- var import_react7 = require("react");
1263
- function extractBundleUrl(pluginId, manifest) {
1264
- const render = manifest.render;
1265
- if (!render || typeof render !== "object") return { url: null, source: "none" };
1266
- const injected = manifest.bundleUrl || render.bundleUrl || render.reactNative?.bundleUrl || null;
1267
- const hasNativeComponent = !!(render.reactNative?.component || render.component);
1268
- if (hasNativeComponent) {
1269
- return {
1270
- url: `/api/ui-plugins/${encodeURIComponent(pluginId)}/bundle.js`,
1271
- source: injected ? "ignored-explicit" : "derived-default",
1272
- ...injected && { ignored: injected }
1273
- };
1274
1358
  }
1275
- return { url: null, source: "none" };
1276
- }
1277
- function extractBundleHash(manifest) {
1278
- if (manifest.bundleHash) return manifest.bundleHash;
1279
- const render = manifest.render;
1280
- if (!render || typeof render !== "object") return void 0;
1281
- if (render.bundleHash) return render.bundleHash;
1282
- if (render.reactNative?.bundleHash) return render.reactNative.bundleHash;
1283
- return void 0;
1359
+ }));
1360
+ function isMountSurface(m) {
1361
+ return m.type === "drawer" || m.type === "fullscreen";
1284
1362
  }
1285
- function extractComponentId(manifest) {
1286
- const render = manifest.render;
1287
- if (!render || typeof render !== "object") return null;
1288
- return render.component || render.reactNative?.component || null;
1363
+ function isInlineSurface(m) {
1364
+ return m.type === "card" || m.type === "header" || m.type === "ambient" || m.type === "nudge";
1289
1365
  }
1290
- function usePlugin(pluginId, manifest) {
1291
- const [plugin, setPlugin] = (0, import_react7.useState)(null);
1292
- const [loading, setLoading] = (0, import_react7.useState)(true);
1293
- const [error, setError] = (0, import_react7.useState)(null);
1294
- const [bundleMissing, setBundleMissing] = (0, import_react7.useState)(false);
1295
- const version = manifest.version || "1.0.0";
1296
- const { url: bundleUrl, ignored: ignoredUrl } = extractBundleUrl(pluginId, manifest);
1297
- const bundleHash = extractBundleHash(manifest);
1298
- const componentId = extractComponentId(manifest);
1299
- const lookupId = componentId || pluginId;
1300
- if (ignoredUrl) {
1301
- console.warn(`[usePlugin] ${pluginId}: IGNORED connector-injected bundleUrl="${ignoredUrl}". The host derives bundle URLs from pluginId via the canonical route.`);
1302
- }
1366
+
1367
+ // 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
+ function SurfaceDispatcher() {
1372
+ const manifests = useSurfaceStore((s) => s.manifests);
1373
+ const activeDrawer = useSurfaceStore((s) => s.activeDrawer);
1374
+ const activeFullscreen = useSurfaceStore((s) => s.activeFullscreen);
1375
+ const activeProps = useSurfaceStore((s) => s.activeProps);
1376
+ const mountKey = useSurfaceStore((s) => s.mountKey);
1377
+ const hide = useSurfaceStore((s) => s.hide);
1303
1378
  (0, import_react7.useEffect)(() => {
1304
- let cancelled = false;
1305
- (async () => {
1306
- try {
1307
- if (nativePluginRegistry.has(lookupId)) {
1308
- const registered = await nativePluginRegistry.get(lookupId);
1309
- if (cancelled) return;
1310
- if (!registered) {
1311
- throw new Error(`Local plugin not found: ${lookupId}`);
1312
- }
1313
- if (registered.type === "service") {
1314
- throw new Error(`Plugin ${lookupId} is a service plugin and cannot be rendered`);
1315
- }
1316
- setPlugin(registered);
1317
- setLoading(false);
1318
- return;
1319
- }
1320
- if (bundleUrl) {
1321
- try {
1322
- const component = await bundleManager.load(pluginId, bundleUrl, version, bundleHash);
1323
- if (cancelled) return;
1324
- setPlugin(component);
1325
- setLoading(false);
1326
- return;
1327
- } catch (bundleErr) {
1328
- const msg = bundleErr instanceof Error ? bundleErr.message : String(bundleErr);
1329
- console.warn(`[usePlugin] ${pluginId}: Bundle load failed, falling back to WebView:`, msg);
1330
- if (!cancelled) {
1331
- setBundleMissing(true);
1332
- setLoading(false);
1333
- }
1334
- return;
1335
- }
1336
- }
1337
- const iframeUrl = manifest?.render?.iframeUrl || manifest?.render?.mode === "adaptive" && manifest?.render?.iframe?.iframeUrl;
1338
- if (iframeUrl) {
1339
- if (!cancelled) {
1340
- setBundleMissing(true);
1341
- setLoading(false);
1342
- }
1343
- return;
1344
- }
1345
- throw new Error(
1346
- `Plugin "${manifest.name || pluginId}" has no bundle URL and is not in the local registry. Check that the solution definition includes a valid render.reactNative.bundleUrl.`
1347
- );
1348
- } catch (err) {
1349
- if (cancelled) return;
1350
- const msg = err instanceof Error ? err.message : String(err);
1351
- console.error(`[usePlugin] Failed to load ${pluginId}:`, msg);
1352
- setError(msg);
1353
- setLoading(false);
1379
+ const unsub = import_core5.pluginCommandEmitter.on("plugin-close", ({ pluginId } = {}) => {
1380
+ if (!pluginId) return;
1381
+ const s = useSurfaceStore.getState();
1382
+ if (s.activeDrawer === pluginId || s.activeFullscreen === pluginId) {
1383
+ s.hide(pluginId);
1354
1384
  }
1355
- })();
1356
- return () => {
1357
- cancelled = true;
1358
- };
1359
- }, [pluginId, lookupId, bundleUrl, bundleHash, version]);
1360
- return { plugin, loading, error, bundleMissing };
1385
+ });
1386
+ return unsub;
1387
+ }, []);
1388
+ const drawer = activeDrawer ? manifests[activeDrawer] : null;
1389
+ const fullscreen = activeFullscreen ? manifests[activeFullscreen] : null;
1390
+ const drawerProps = activeDrawer ? activeProps[activeDrawer] : void 0;
1391
+ 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)(
1394
+ drawer.Component,
1395
+ {
1396
+ dismiss: () => hide(drawer.id),
1397
+ props: drawerProps
1398
+ },
1399
+ `drawer-${drawer.id}-${mountKey}`
1400
+ ) : null,
1401
+ fullscreen && fullscreen.type === "fullscreen" && fullscreen.Component ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1402
+ fullscreen.Component,
1403
+ {
1404
+ dismiss: () => hide(fullscreen.id),
1405
+ props: fullscreenProps
1406
+ },
1407
+ `fullscreen-${fullscreen.id}-${mountKey}`
1408
+ ) : null
1409
+ ] });
1361
1410
  }
1362
1411
 
1363
- // src/hooks/useRemotePlugins.ts
1364
- var import_react8 = require("react");
1365
- var import_core4 = require("@ateam-ai-mobile/core");
1366
- function useRemotePlugins(solutionId) {
1367
- const [plugins, setPlugins] = (0, import_react8.useState)([]);
1368
- const [loading, setLoading] = (0, import_react8.useState)(true);
1369
- const [refreshing, setRefreshing] = (0, import_react8.useState)(false);
1370
- const [error, setError] = (0, import_react8.useState)(null);
1371
- const fetchRemotePlugins = (0, import_react8.useCallback)(async () => {
1372
- try {
1373
- setError(null);
1374
- const targetSolutionId = solutionId || "current";
1375
- let data;
1376
- try {
1377
- data = await (0, import_core4.apiFetch)(`/api/solutions/${targetSolutionId}/ui-plugins`);
1378
- } catch (err) {
1379
- if (err?.message?.includes("404")) {
1380
- console.warn(`[useRemotePlugins] Solution ${targetSolutionId} not found or has no plugins`);
1381
- setPlugins([]);
1382
- return;
1383
- }
1384
- throw err;
1385
- }
1386
- const uiPlugins = (data.plugins || []).map((plugin) => ({
1387
- id: plugin.id,
1388
- name: plugin.name,
1389
- version: plugin.version || "1.0.0",
1390
- source: "remote",
1391
- manifest: {
1392
- id: plugin.id,
1393
- name: plugin.name,
1394
- version: plugin.version || "1.0.0",
1395
- type: "ui",
1396
- description: plugin.description || "",
1397
- capabilities: plugin.capabilities || {},
1398
- channels: plugin.channels || ["default"],
1399
- render: plugin.render && typeof plugin.render === "object" ? plugin.render : { mode: "react-native", component: plugin.name || plugin.id },
1400
- entry_point: plugin.entry_point || plugin.name,
1401
- sandbox: plugin.sandbox !== false,
1402
- requires_auth: plugin.requires_auth !== false
1403
- }
1404
- }));
1405
- setPlugins(uiPlugins);
1406
- } catch (err) {
1407
- const message = err instanceof Error ? err.message : String(err);
1408
- console.error("[useRemotePlugins] Failed to fetch plugins:", message);
1409
- setError(message);
1410
- setPlugins([]);
1411
- } finally {
1412
- setLoading(false);
1413
- setRefreshing(false);
1412
+ // 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
+ function sha256Hex(message) {
1481
+ const utf8 = unescape(encodeURIComponent(message));
1482
+ const bytes = new Uint8Array(utf8.length);
1483
+ for (let i = 0; i < utf8.length; i++) bytes[i] = utf8.charCodeAt(i);
1484
+ const bitLen = bytes.length * 8;
1485
+ const padLen = (56 - (bytes.length + 1) % 64 + 64) % 64;
1486
+ const buf = new Uint8Array(bytes.length + 1 + padLen + 8);
1487
+ buf.set(bytes);
1488
+ buf[bytes.length] = 128;
1489
+ const len = buf.length;
1490
+ buf[len - 4] = bitLen >>> 24 & 255;
1491
+ buf[len - 3] = bitLen >>> 16 & 255;
1492
+ buf[len - 2] = bitLen >>> 8 & 255;
1493
+ buf[len - 1] = bitLen & 255;
1494
+ const h = new Uint32Array([
1495
+ 1779033703,
1496
+ 3144134277,
1497
+ 1013904242,
1498
+ 2773480762,
1499
+ 1359893119,
1500
+ 2600822924,
1501
+ 528734635,
1502
+ 1541459225
1503
+ ]);
1504
+ const w = new Uint32Array(64);
1505
+ for (let i = 0; i < buf.length; i += 64) {
1506
+ for (let t = 0; t < 16; t++) {
1507
+ const j = i + t * 4;
1508
+ w[t] = buf[j] << 24 | buf[j + 1] << 16 | buf[j + 2] << 8 | buf[j + 3];
1414
1509
  }
1415
- }, [solutionId]);
1416
- const refresh = (0, import_react8.useCallback)(async () => {
1417
- setRefreshing(true);
1418
- await fetchRemotePlugins();
1419
- }, [fetchRemotePlugins]);
1420
- (0, import_react8.useEffect)(() => {
1421
- setLoading(true);
1422
- fetchRemotePlugins();
1423
- }, [fetchRemotePlugins]);
1424
- return {
1425
- plugins,
1426
- loading,
1427
- error,
1428
- refreshing,
1429
- refresh
1430
- };
1510
+ for (let t = 16; t < 64; t++) {
1511
+ const s0 = rotr(7, w[t - 15]) ^ rotr(18, w[t - 15]) ^ w[t - 15] >>> 3;
1512
+ const s1 = rotr(17, w[t - 2]) ^ rotr(19, w[t - 2]) ^ w[t - 2] >>> 10;
1513
+ w[t] = w[t - 16] + s0 + w[t - 7] + s1 | 0;
1514
+ }
1515
+ let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
1516
+ for (let t = 0; t < 64; t++) {
1517
+ const S1 = rotr(6, e) ^ rotr(11, e) ^ rotr(25, e);
1518
+ const ch = e & f ^ ~e & g;
1519
+ const temp1 = hh + S1 + ch + K[t] + w[t] | 0;
1520
+ const S0 = rotr(2, a) ^ rotr(13, a) ^ rotr(22, a);
1521
+ const maj = a & b ^ a & c ^ b & c;
1522
+ const temp2 = S0 + maj | 0;
1523
+ hh = g;
1524
+ g = f;
1525
+ f = e;
1526
+ e = d + temp1 | 0;
1527
+ d = c;
1528
+ c = b;
1529
+ b = a;
1530
+ a = temp1 + temp2 | 0;
1531
+ }
1532
+ h[0] = h[0] + a | 0;
1533
+ h[1] = h[1] + b | 0;
1534
+ h[2] = h[2] + c | 0;
1535
+ h[3] = h[3] + d | 0;
1536
+ h[4] = h[4] + e | 0;
1537
+ h[5] = h[5] + f | 0;
1538
+ h[6] = h[6] + g | 0;
1539
+ h[7] = h[7] + hh | 0;
1540
+ }
1541
+ let out = "";
1542
+ for (let i = 0; i < 8; i++) {
1543
+ out += h[i].toString(16).padStart(8, "0");
1544
+ }
1545
+ return out;
1431
1546
  }
1432
1547
 
1433
- // src/api/plugins.ts
1434
- var import_core5 = require("@ateam-ai-mobile/core");
1435
- var import_core6 = require("@ateam-ai-mobile/core");
1436
- function supportsNative(manifest) {
1437
- const mode = manifest.render?.mode;
1438
- return mode === "react-native" || mode === "adaptive";
1439
- }
1440
- function getNativeComponentId(manifest) {
1441
- if (manifest.render?.mode === "react-native") return manifest.render.component;
1442
- if (manifest.render?.mode === "adaptive") return manifest.render.reactNative?.component;
1443
- return null;
1444
- }
1445
- function getNativeBundleUrl(manifest) {
1446
- const direct = manifest.bundleUrl;
1447
- if (direct) return direct;
1448
- const render = manifest.render;
1449
- if (render.mode === "react-native") return render.bundleUrl || null;
1450
- if (render.mode === "adaptive") return render.reactNative?.bundleUrl || null;
1451
- return null;
1452
- }
1453
- function getNativeBundleHash(manifest) {
1454
- const direct = manifest.bundleHash;
1455
- if (direct) return direct;
1456
- const render = manifest.render;
1457
- if (render.mode === "react-native") return render.bundleHash || null;
1458
- if (render.mode === "adaptive") return render.reactNative?.bundleHash || null;
1459
- return null;
1460
- }
1461
- function supportsWebView(manifest) {
1462
- return manifest.render?.mode === "iframe" || manifest.render?.mode === "adaptive";
1463
- }
1464
- async function listPlugins() {
1465
- const res = await (0, import_core5.apiFetch)("/mcp", {
1466
- method: "POST",
1467
- body: JSON.stringify({
1468
- jsonrpc: "2.0",
1469
- id: Date.now(),
1470
- method: "tools/call",
1471
- params: {
1472
- name: "cp.listContextPlugins",
1473
- arguments: {}
1474
- }
1475
- })
1476
- });
1477
- const structured = res.result?.structuredContent?.plugins;
1478
- if (structured) return structured;
1479
- const text = res.result?.content?.[0]?.text;
1480
- if (!text) return [];
1481
- try {
1482
- const parsed = JSON.parse(text);
1483
- return parsed.plugins || parsed || [];
1484
- } catch {
1485
- return [];
1548
+ // src/bundles/storage.ts
1549
+ var MemoryBundleStorage = class {
1550
+ constructor() {
1551
+ this.index = {};
1552
+ this.files = /* @__PURE__ */ new Map();
1486
1553
  }
1487
- }
1488
- async function getPlugin(pluginId) {
1489
- const res = await (0, import_core5.apiFetch)("/mcp", {
1490
- method: "POST",
1491
- body: JSON.stringify({
1492
- jsonrpc: "2.0",
1493
- id: Date.now(),
1494
- method: "tools/call",
1495
- params: {
1496
- name: "cp.getContextPlugin",
1497
- arguments: { id: pluginId }
1498
- // Backend expects { id }, NOT { pluginId }
1499
- }
1500
- })
1501
- });
1502
- if (res.result?.structuredContent) return res.result.structuredContent;
1503
- const text = res.result?.content?.[0]?.text;
1504
- if (!text) return null;
1505
- try {
1506
- const parsed = JSON.parse(text);
1507
- if (parsed.error) {
1508
- console.warn("[getPlugin] Backend error:", parsed.error);
1509
- return null;
1510
- }
1511
- return parsed;
1512
- } catch {
1513
- return null;
1554
+ async loadIndex() {
1555
+ return this.index;
1514
1556
  }
1515
- }
1516
- function resolvePluginUrl(manifest, tenant) {
1517
- const render = manifest.render;
1518
- let url;
1519
- if (render?.mode === "iframe") {
1520
- url = render.iframeUrl;
1521
- } else if (render?.mode === "adaptive") {
1522
- url = render.iframeUrl || render.iframe?.iframeUrl;
1557
+ async saveIndex(index) {
1558
+ this.index = index;
1523
1559
  }
1524
- if (!url) return "";
1525
- if (url.startsWith("http")) {
1526
- const sep = url.includes("?") ? "&" : "?";
1527
- return tenant ? `${url}${sep}tenant=${encodeURIComponent(tenant)}` : url;
1560
+ async readBundle(localPath) {
1561
+ return this.files.get(localPath) ?? null;
1528
1562
  }
1529
- if (tenant && url.startsWith("/mcp-ui/")) {
1530
- const tenantUrl = url.replace("/mcp-ui/", `/mcp-ui/${encodeURIComponent(tenant)}/`);
1531
- return `${(0, import_core6.getCoreApiUrl)()}${tenantUrl}`;
1563
+ async writeBundle(key, content) {
1564
+ const localPath = `memory://${key}`;
1565
+ this.files.set(localPath, content);
1566
+ return localPath;
1532
1567
  }
1533
- return `${(0, import_core6.getCoreApiUrl)()}${url}`;
1534
- }
1535
-
1536
- // src/surfaces/SurfaceDrawer.tsx
1537
- var import_react10 = require("react");
1538
- var import_react_native4 = require("react-native");
1539
- var import_react_native_svg2 = __toESM(require("react-native-svg"));
1540
- var import_core7 = require("@ateam-ai-mobile/core");
1541
-
1542
- // src/surfaces/AdaOrb.tsx
1543
- var import_react9 = require("react");
1544
- var import_react_native3 = require("react-native");
1545
- var import_react_native_reanimated = __toESM(require("react-native-reanimated"));
1546
- var import_react_native_svg = __toESM(require("react-native-svg"));
1547
- var import_jsx_runtime = require("react/jsx-runtime");
1548
- var AnimatedCircle = import_react_native_reanimated.default.createAnimatedComponent(import_react_native_svg.Circle);
1549
- function AdaOrb({ size = 120, state = "idle", inscription = true }) {
1550
- const showInscription = inscription && size >= 60;
1551
- const center = size / 2;
1552
- const coreR = size * 0.32;
1553
- const innerHaloR = size * 0.42;
1554
- const outerHaloR = size * 0.5;
1555
- const coreScale = (0, import_react_native_reanimated.useSharedValue)(1);
1556
- const innerScale = (0, import_react_native_reanimated.useSharedValue)(1);
1557
- const outerScale = (0, import_react_native_reanimated.useSharedValue)(1);
1558
- const outerOpacity = (0, import_react_native_reanimated.useSharedValue)(0.55);
1559
- const rippleCycle = (0, import_react_native_reanimated.useSharedValue)(0);
1560
- const orbit1 = (0, import_react_native_reanimated.useSharedValue)(0);
1561
- const orbit2 = (0, import_react_native_reanimated.useSharedValue)(0);
1562
- const orbit3 = (0, import_react_native_reanimated.useSharedValue)(0);
1563
- const beat = (0, import_react_native_reanimated.useSharedValue)(1);
1564
- (0, import_react9.useEffect)(() => {
1565
- (0, import_react_native_reanimated.cancelAnimation)(coreScale);
1566
- (0, import_react_native_reanimated.cancelAnimation)(innerScale);
1567
- (0, import_react_native_reanimated.cancelAnimation)(outerScale);
1568
- (0, import_react_native_reanimated.cancelAnimation)(outerOpacity);
1569
- (0, import_react_native_reanimated.cancelAnimation)(rippleCycle);
1570
- (0, import_react_native_reanimated.cancelAnimation)(orbit1);
1571
- (0, import_react_native_reanimated.cancelAnimation)(orbit2);
1572
- (0, import_react_native_reanimated.cancelAnimation)(orbit3);
1573
- (0, import_react_native_reanimated.cancelAnimation)(beat);
1574
- rippleCycle.value = 0;
1575
- orbit1.value = 0;
1576
- orbit2.value = 0;
1577
- orbit3.value = 0;
1578
- beat.value = 1;
1579
- let breathMs = 3600;
1580
- let breathAmount = 0.025;
1581
- if (state === "listening") {
1582
- breathMs = 2e3;
1583
- breathAmount = 0.05;
1584
- rippleCycle.value = (0, import_react_native_reanimated.withRepeat)(
1585
- (0, import_react_native_reanimated.withTiming)(1, { duration: 2e3, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.quad) }),
1586
- -1,
1587
- false
1588
- );
1589
- } else if (state === "thinking") {
1590
- breathMs = 2400;
1591
- breathAmount = 0.04;
1592
- orbit1.value = (0, import_react_native_reanimated.withRepeat)(
1593
- (0, import_react_native_reanimated.withTiming)(1, { duration: 1100, easing: import_react_native_reanimated.Easing.linear }),
1594
- -1,
1595
- false
1596
- );
1597
- orbit2.value = (0, import_react_native_reanimated.withRepeat)(
1598
- (0, import_react_native_reanimated.withTiming)(1, { duration: 1900, easing: import_react_native_reanimated.Easing.linear }),
1599
- -1,
1600
- false
1601
- );
1602
- orbit3.value = (0, import_react_native_reanimated.withRepeat)(
1603
- (0, import_react_native_reanimated.withTiming)(-1, { duration: 2700, easing: import_react_native_reanimated.Easing.linear }),
1604
- -1,
1605
- false
1606
- );
1607
- } else if (state === "speaking") {
1608
- breathMs = 700;
1609
- breathAmount = 0.13;
1610
- beat.value = (0, import_react_native_reanimated.withRepeat)(
1611
- (0, import_react_native_reanimated.withSequence)(
1612
- (0, import_react_native_reanimated.withTiming)(1.08, { duration: 90, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.quad) }),
1613
- (0, import_react_native_reanimated.withTiming)(1, { duration: 130, easing: import_react_native_reanimated.Easing.in(import_react_native_reanimated.Easing.quad) }),
1614
- (0, import_react_native_reanimated.withTiming)(1.05, { duration: 80, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.quad) }),
1615
- (0, import_react_native_reanimated.withTiming)(1, { duration: 200, easing: import_react_native_reanimated.Easing.in(import_react_native_reanimated.Easing.quad) }),
1616
- (0, import_react_native_reanimated.withTiming)(1, { duration: 280 })
1617
- ),
1618
- -1,
1619
- false
1568
+ async deleteBundle(localPath) {
1569
+ this.files.delete(localPath);
1570
+ }
1571
+ async clearAll() {
1572
+ this.index = {};
1573
+ this.files.clear();
1574
+ }
1575
+ };
1576
+ var _storage = new MemoryBundleStorage();
1577
+ function setBundleStorage(storage) {
1578
+ _storage = storage;
1579
+ }
1580
+ function getBundleStorage() {
1581
+ return _storage;
1582
+ }
1583
+
1584
+ // src/bundles/bundleManager.ts
1585
+ var ReactShim = {
1586
+ __esModule: true,
1587
+ default: import_react8.default,
1588
+ ...import_react8.default,
1589
+ // Explicitly include hooks (React 19 may not have them as own enumerable props)
1590
+ useState: import_react9.useState,
1591
+ useEffect: import_react9.useEffect,
1592
+ useCallback: import_react9.useCallback,
1593
+ useMemo: import_react9.useMemo,
1594
+ useRef: import_react9.useRef,
1595
+ useContext: import_react9.useContext,
1596
+ useReducer: import_react9.useReducer,
1597
+ useLayoutEffect: import_react9.useLayoutEffect,
1598
+ useImperativeHandle: import_react9.useImperativeHandle,
1599
+ useDebugValue: import_react9.useDebugValue,
1600
+ createElement: import_react8.default.createElement,
1601
+ Fragment: import_react8.default.Fragment,
1602
+ Component: import_react8.default.Component,
1603
+ PureComponent: import_react8.default.PureComponent,
1604
+ createContext: import_react8.default.createContext,
1605
+ forwardRef: import_react8.default.forwardRef,
1606
+ memo: import_react8.default.memo,
1607
+ Children: import_react8.default.Children,
1608
+ cloneElement: import_react8.default.cloneElement,
1609
+ isValidElement: import_react8.default.isValidElement,
1610
+ createRef: import_react8.default.createRef,
1611
+ lazy: import_react8.default.lazy,
1612
+ Suspense: import_react8.default.Suspense
1613
+ };
1614
+ var PluginSDKShim = Object.freeze({
1615
+ ...sdk_exports,
1616
+ SurfaceDrawer,
1617
+ SurfaceCard,
1618
+ SurfaceFullscreen,
1619
+ useSurfaceStore,
1620
+ SurfaceDispatcher,
1621
+ PluginSDK: Object.freeze({
1622
+ ...PluginSDK,
1623
+ register: PluginSDK.register
1624
+ }),
1625
+ register
1626
+ });
1627
+ var requireShim = (name) => {
1628
+ if (name === "react") return ReactShim;
1629
+ if (name === "react-native") return ReactNative;
1630
+ if (name === "@adas/plugin-sdk") return PluginSDKShim;
1631
+ const hostModule = resolveBundleModule(name);
1632
+ if (hostModule !== void 0) return hostModule;
1633
+ console.warn(`[BundleManager] Unknown module: ${name} \u2014 register it via registerBundleModules()`);
1634
+ return {};
1635
+ };
1636
+ var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
1637
+ var BundleManager = class {
1638
+ constructor() {
1639
+ /** In-memory module cache: bundleUrl → evaluated module.exports */
1640
+ this.modules = /* @__PURE__ */ new Map();
1641
+ /** In-flight download deduplication: bundleUrl → Promise */
1642
+ this.inflight = /* @__PURE__ */ new Map();
1643
+ }
1644
+ // ── Public API ──────────────────────────────────────────────
1645
+ /**
1646
+ * Load a plugin component. Returns the RegisteredUIPlugin object.
1647
+ * Handles: in-memory cache → disk cache → download → evaluate → extract.
1648
+ *
1649
+ * `expectedHash` (SHA-256 hex from cp.getContextPlugin manifest) MUST be
1650
+ * provided for any bundle the user has not previously verified — without
1651
+ * it, a tampered bundle is `eval`'d as JS with full RN runtime + every
1652
+ * native capability the manifest declares.
1653
+ */
1654
+ async load(pluginId, bundleUrl, version, expectedHash) {
1655
+ const cached = this.getComponentFromMemory(pluginId, bundleUrl);
1656
+ if (cached) return cached;
1657
+ let modulePromise = this.inflight.get(bundleUrl);
1658
+ if (!modulePromise) {
1659
+ modulePromise = this.loadModule(bundleUrl, version, expectedHash);
1660
+ this.inflight.set(bundleUrl, modulePromise);
1661
+ modulePromise.catch(() => {
1662
+ }).finally(() => this.inflight.delete(bundleUrl));
1663
+ }
1664
+ await modulePromise;
1665
+ const component = this.getComponentFromMemory(pluginId, bundleUrl);
1666
+ if (!component) {
1667
+ throw new Error(
1668
+ `Bundle loaded from ${bundleUrl} but no component found for "${pluginId}". Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
1620
1669
  );
1621
1670
  }
1622
- const breathEasing = state === "speaking" ? import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) : import_react_native_reanimated.Easing.inOut(import_react_native_reanimated.Easing.sin);
1623
- coreScale.value = (0, import_react_native_reanimated.withRepeat)(
1624
- (0, import_react_native_reanimated.withSequence)(
1625
- (0, import_react_native_reanimated.withTiming)(1 + breathAmount, { duration: breathMs, easing: breathEasing }),
1626
- (0, import_react_native_reanimated.withTiming)(1, { duration: breathMs, easing: breathEasing })
1627
- ),
1628
- -1,
1629
- false
1630
- );
1631
- innerScale.value = (0, import_react_native_reanimated.withRepeat)(
1632
- (0, import_react_native_reanimated.withSequence)(
1633
- (0, import_react_native_reanimated.withTiming)(1 + breathAmount * 1.4, { duration: breathMs * 1.15, easing: breathEasing }),
1634
- (0, import_react_native_reanimated.withTiming)(1, { duration: breathMs * 1.15, easing: breathEasing })
1635
- ),
1636
- -1,
1637
- false
1638
- );
1639
- outerScale.value = (0, import_react_native_reanimated.withRepeat)(
1640
- (0, import_react_native_reanimated.withSequence)(
1641
- (0, import_react_native_reanimated.withTiming)(1 + breathAmount * 1.8, { duration: breathMs * 1.3, easing: breathEasing }),
1642
- (0, import_react_native_reanimated.withTiming)(1, { duration: breathMs * 1.3, easing: breathEasing })
1643
- ),
1644
- -1,
1645
- false
1646
- );
1647
- outerOpacity.value = (0, import_react_native_reanimated.withRepeat)(
1648
- (0, import_react_native_reanimated.withSequence)(
1649
- (0, import_react_native_reanimated.withTiming)(state === "idle" ? 0.55 : 0.8, { duration: breathMs * 1.3, easing: breathEasing }),
1650
- (0, import_react_native_reanimated.withTiming)(0.35, { duration: breathMs * 1.3, easing: breathEasing })
1651
- ),
1652
- -1,
1653
- false
1654
- );
1655
- }, [state]);
1656
- const coreProps = (0, import_react_native_reanimated.useAnimatedProps)(() => ({
1657
- r: coreR * coreScale.value * beat.value
1658
- }));
1659
- const innerProps = (0, import_react_native_reanimated.useAnimatedProps)(() => ({
1660
- r: innerHaloR * innerScale.value * beat.value
1661
- }));
1662
- const outerProps = (0, import_react_native_reanimated.useAnimatedProps)(() => ({
1663
- r: outerHaloR * outerScale.value,
1664
- opacity: outerOpacity.value
1665
- }));
1666
- const ripple1Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
1667
- const t = rippleCycle.value;
1668
- return {
1669
- r: outerHaloR * (1 + t * 0.55),
1670
- opacity: state === "listening" ? 0.55 * (1 - t) : 0
1671
- };
1672
- });
1673
- const ripple2Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
1674
- const t = (rippleCycle.value + 0.34) % 1;
1675
- return {
1676
- r: outerHaloR * (1 + t * 0.55),
1677
- opacity: state === "listening" ? 0.45 * (1 - t) : 0
1678
- };
1679
- });
1680
- const ripple3Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
1681
- const t = (rippleCycle.value + 0.67) % 1;
1682
- return {
1683
- r: outerHaloR * (1 + t * 0.55),
1684
- opacity: state === "listening" ? 0.35 * (1 - t) : 0
1685
- };
1686
- });
1687
- const spark1Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
1688
- const angle = orbit1.value * Math.PI * 2 - Math.PI / 2;
1689
- const r = coreR * 0.55;
1690
- return {
1691
- cx: center + Math.cos(angle) * r,
1692
- cy: center + Math.sin(angle) * r,
1693
- opacity: state === "thinking" ? 1 : 0
1694
- };
1695
- });
1696
- const spark2Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
1697
- const angle = orbit2.value * Math.PI * 2 + Math.PI * 0.6;
1698
- const r = coreR * 0.85;
1699
- return {
1700
- cx: center + Math.cos(angle) * r,
1701
- cy: center + Math.sin(angle) * r,
1702
- opacity: state === "thinking" ? 0.9 : 0
1703
- };
1704
- });
1705
- const spark3Props = (0, import_react_native_reanimated.useAnimatedProps)(() => {
1706
- const angle = orbit3.value * Math.PI * 2 + Math.PI * 1.3;
1707
- const r = coreR * 1.05;
1708
- return {
1709
- cx: center + Math.cos(angle) * r,
1710
- cy: center + Math.sin(angle) * r,
1711
- opacity: state === "thinking" ? 0.7 : 0
1671
+ return component;
1672
+ }
1673
+ /**
1674
+ * Prewarm: load every plugin bundle in the background after app boot, so a
1675
+ * later open is instant (in-memory hit) instead of round-tripping the
1676
+ * network. Errors are swallowed per-plugin — one bad bundle shouldn't
1677
+ * block others. Concurrency capped at 3.
1678
+ */
1679
+ async prewarmAll(items) {
1680
+ const queue = [...items];
1681
+ const concurrency = Math.min(3, queue.length);
1682
+ const start = Date.now();
1683
+ let okCount = 0;
1684
+ let failCount = 0;
1685
+ const worker = async () => {
1686
+ while (queue.length > 0) {
1687
+ const it = queue.shift();
1688
+ if (!it) return;
1689
+ try {
1690
+ await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash);
1691
+ okCount++;
1692
+ } catch (err) {
1693
+ failCount++;
1694
+ console.log(`[prewarm] ${it.pluginId}: ${err instanceof Error ? err.message : String(err)}`);
1695
+ }
1696
+ }
1712
1697
  };
1713
- });
1714
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.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: [
1715
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.Defs, { children: [
1716
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.RadialGradient, { id: "adaCore", cx: "36%", cy: "30%", r: "70%", children: [
1717
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FFE0B0", stopOpacity: "1" }),
1718
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "28%", stopColor: "#FFA85A", stopOpacity: "1" }),
1719
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "65%", stopColor: "#FF7A28", stopOpacity: "1" }),
1720
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#B8410A", stopOpacity: "1" })
1721
- ] }),
1722
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.RadialGradient, { id: "adaInner", cx: "50%", cy: "50%", r: "50%", children: [
1723
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FF8A3C", stopOpacity: "0.55" }),
1724
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FF7028", stopOpacity: "0" })
1725
- ] }),
1726
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.RadialGradient, { id: "adaOuter", cx: "50%", cy: "50%", r: "50%", children: [
1727
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FF7A28", stopOpacity: "0.4" }),
1728
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FF6018", stopOpacity: "0" })
1729
- ] }),
1730
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_svg.RadialGradient, { id: "adaSpark", cx: "50%", cy: "50%", r: "50%", children: [
1731
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "0%", stopColor: "#FFFAEB", stopOpacity: "1" }),
1732
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "60%", stopColor: "#FFD095", stopOpacity: "0.9" }),
1733
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_svg.Stop, { offset: "100%", stopColor: "#FFA85A", stopOpacity: "0" })
1734
- ] })
1735
- ] }),
1736
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1737
- AnimatedCircle,
1738
- {
1739
- cx: center,
1740
- cy: center,
1741
- stroke: "#FF7A28",
1742
- strokeWidth: 1.5,
1743
- fill: "none",
1744
- animatedProps: ripple1Props
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 {
1726
+ }
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;
1743
+ }
1745
1744
  }
1746
- ),
1747
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1748
- AnimatedCircle,
1749
- {
1750
- cx: center,
1751
- cy: center,
1752
- stroke: "#FF7A28",
1753
- strokeWidth: 1.5,
1754
- fill: "none",
1755
- animatedProps: ripple2Props
1745
+ }
1746
+ if (!content) {
1747
+ const downloaded = await this.download(bundleUrl, expectedLc);
1748
+ content = downloaded.content;
1749
+ const safeName = bundleUrl.replace(/[^a-zA-Z0-9]/g, "_").slice(-80);
1750
+ try {
1751
+ const localPath = await storage.writeBundle(`${safeName}@${version}.js`, content);
1752
+ index[bundleUrl] = {
1753
+ localPath,
1754
+ version,
1755
+ timestamp: Date.now(),
1756
+ bundleUrl,
1757
+ bundleHash: downloaded.sha256
1758
+ };
1759
+ await storage.saveIndex(index);
1760
+ } catch (err) {
1761
+ console.warn("[BundleManager] Cache write error:", err);
1756
1762
  }
1757
- ),
1758
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1759
- AnimatedCircle,
1760
- {
1761
- cx: center,
1762
- cy: center,
1763
- stroke: "#FF7A28",
1764
- strokeWidth: 1.5,
1765
- fill: "none",
1766
- animatedProps: ripple3Props
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
+ );
1767
1792
  }
1768
- ),
1769
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaOuter)", animatedProps: outerProps }),
1770
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaInner)", animatedProps: innerProps }),
1771
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaCore)", animatedProps: coreProps }),
1772
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1773
- AnimatedCircle,
1774
- {
1775
- r: Math.max(3, size * 0.05),
1776
- fill: "url(#adaSpark)",
1777
- animatedProps: spark1Props
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
+ );
1778
1797
  }
1779
- ),
1780
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1781
- AnimatedCircle,
1782
- {
1783
- r: Math.max(2.5, size * 0.04),
1784
- fill: "url(#adaSpark)",
1785
- animatedProps: spark2Props
1798
+ } else if (headerHash) {
1799
+ if (computed !== headerHash) {
1800
+ throw new Error(
1801
+ `Bundle body sha256 (${computed.slice(0, 12)}...) does not match server's X-Bundle-SHA256 (${headerHash.slice(0, 12)}...).`
1802
+ );
1786
1803
  }
1787
- ),
1788
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1789
- AnimatedCircle,
1790
- {
1791
- r: Math.max(2, size * 0.032),
1792
- fill: "url(#adaSpark)",
1793
- animatedProps: spark3Props
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 {
1841
+ }
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;
1868
+ }
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);
1930
+ }
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 {
1956
+ }
1957
+ } else {
1958
+ remaining.push(cmd);
1959
+ }
1960
+ }
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) => {
1978
+ try {
1979
+ fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1980
+ } catch {
1981
+ }
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) => {
1990
+ try {
1991
+ fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
1992
+ } catch {
1993
+ }
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
+ }
2002
+ commandBufferRef.current = remaining;
1794
2003
  }
1795
- ),
1796
- showInscription && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1797
- import_react_native_svg.Text,
1798
- {
1799
- x: center,
1800
- y: center + size * 0.11,
1801
- fontSize: size * 0.34,
1802
- fontWeight: "600",
1803
- fill: "#FFF5E0",
1804
- textAnchor: "middle",
1805
- letterSpacing: -size * 5e-3,
1806
- children: "iA"
2004
+ for (const cmd of import_core7.pluginCommandEmitter.drainPluginCommands(pluginId)) {
2005
+ dispatch(cmd);
1807
2006
  }
1808
- )
1809
- ] }) });
1810
- }
1811
- var styles2 = import_react_native3.StyleSheet.create({
1812
- wrap: {
1813
- alignItems: "center",
1814
- justifyContent: "center"
1815
- }
1816
- });
1817
-
1818
- // src/surfaces/SurfaceDrawer.tsx
1819
- var import_jsx_runtime2 = require("react/jsx-runtime");
1820
- var SCREEN_H = import_react_native4.Dimensions.get("window").height;
1821
- var fontFam = import_react_native4.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1822
- function SurfaceDrawer({
1823
- visible,
1824
- onClose,
1825
- title,
1826
- subtitle,
1827
- privacy,
1828
- showOrb = true,
1829
- heightRatio = 0.85,
1830
- children
1831
- }) {
1832
- const sheetH = Math.round(SCREEN_H * heightRatio);
1833
- const fadeAnim = (0, import_react10.useRef)(new import_react_native4.Animated.Value(0)).current;
1834
- const slideAnim = (0, import_react10.useRef)(new import_react_native4.Animated.Value(sheetH)).current;
2007
+ }, 500);
2008
+ return () => clearTimeout(timer);
2009
+ }, [pluginId, commandBufferRef]);
1835
2010
  (0, import_react10.useEffect)(() => {
1836
- if (visible) {
1837
- import_react_native4.Animated.parallel([
1838
- import_react_native4.Animated.timing(fadeAnim, { toValue: 1, duration: 220, useNativeDriver: true }),
1839
- import_react_native4.Animated.spring(slideAnim, { toValue: 0, useNativeDriver: true, tension: 80, friction: 12 })
1840
- ]).start();
1841
- } else {
1842
- fadeAnim.setValue(0);
1843
- slideAnim.setValue(sheetH);
2011
+ return () => {
2012
+ commandListenersRef.current.clear();
2013
+ initializedRef.current = false;
2014
+ };
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));
1844
2026
  }
1845
- }, [visible]);
1846
- const close = () => {
1847
- import_react_native4.Animated.parallel([
1848
- import_react_native4.Animated.timing(fadeAnim, { toValue: 0, duration: 160, useNativeDriver: true }),
1849
- import_react_native4.Animated.timing(slideAnim, { toValue: sheetH, duration: 200, useNativeDriver: true })
1850
- ]).start(() => onClose());
1851
- };
1852
- if (!visible) return null;
1853
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native4.Animated.View, { style: [styles3.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
1854
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.Pressable, { style: import_react_native4.StyleSheet.absoluteFill, onPress: close }),
1855
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1856
- import_react_native4.Animated.View,
1857
- {
1858
- style: [
1859
- styles3.sheet,
1860
- { height: sheetH, transform: [{ translateY: slideAnim }] }
1861
- ],
1862
- children: [
1863
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.View, { style: styles3.handle }),
1864
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native4.View, { style: styles3.header, children: [
1865
- showOrb ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.View, { style: styles3.headerOrb, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AdaOrb, { size: 36, state: "idle" }) }) : null,
1866
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native4.View, { style: { flex: 1 }, children: [
1867
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.Text, { style: styles3.title, children: title }),
1868
- subtitle ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.Text, { style: styles3.subtitle, children: subtitle }) : null,
1869
- privacy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native4.View, { style: styles3.privacyPill, children: [
1870
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native_svg2.default, { width: 11, height: 11, viewBox: "0 0 24 24", fill: "none", stroke: import_core7.ada.inkSoft, strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: [
1871
- /* @__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" }),
1872
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_svg2.Path, { d: "M7 11V7a5 5 0 0110 0v4" })
1873
- ] }),
1874
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.Text, { style: styles3.privacyText, children: "Only you can see this" })
1875
- ] }) : null
1876
- ] }),
1877
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1878
- import_react_native4.Pressable,
1879
- {
1880
- accessibilityRole: "button",
1881
- accessibilityLabel: "Close",
1882
- style: styles3.closeBtn,
1883
- onPress: close,
1884
- hitSlop: 12,
1885
- 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_core7.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" }) })
1886
- }
1887
- )
1888
- ] }),
1889
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.View, { style: styles3.body, children })
1890
- ]
1891
- }
1892
- )
1893
- ] });
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
+ );
1894
2037
  }
1895
- var styles3 = import_react_native4.StyleSheet.create({
1896
- overlay: {
1897
- position: "absolute",
1898
- top: 0,
1899
- left: 0,
1900
- right: 0,
1901
- bottom: 0,
1902
- backgroundColor: "rgba(26, 20, 16, 0.35)",
1903
- justifyContent: "flex-end",
1904
- zIndex: 999,
1905
- elevation: 999
1906
- },
1907
- sheet: {
1908
- backgroundColor: import_core7.ada.paper,
1909
- borderTopLeftRadius: 28,
1910
- borderTopRightRadius: 28,
1911
- overflow: "hidden"
1912
- },
1913
- handle: {
1914
- width: 40,
1915
- height: 4,
1916
- borderRadius: 2,
1917
- backgroundColor: import_core7.ada.inkMuted,
1918
- opacity: 0.3,
1919
- alignSelf: "center",
1920
- marginTop: 10,
1921
- marginBottom: 6
1922
- },
1923
- header: {
1924
- flexDirection: "row",
1925
- alignItems: "center",
1926
- paddingHorizontal: 22,
1927
- paddingVertical: 16,
1928
- gap: 12,
1929
- borderBottomWidth: 1,
1930
- borderBottomColor: import_core7.ada.border
1931
- },
1932
- headerOrb: {
1933
- width: 36,
1934
- height: 36,
1935
- alignItems: "center",
1936
- justifyContent: "center"
1937
- },
1938
- title: {
1939
- fontSize: 17,
1940
- fontWeight: "600",
1941
- color: import_core7.ada.ink,
1942
- letterSpacing: -0.2,
1943
- fontFamily: fontFam
1944
- },
1945
- subtitle: {
1946
- fontSize: 13,
1947
- fontWeight: "400",
1948
- color: import_core7.ada.inkSoft,
1949
- marginTop: 2,
1950
- letterSpacing: -0.1,
1951
- fontFamily: fontFam
1952
- },
1953
- privacyPill: {
1954
- flexDirection: "row",
1955
- alignItems: "center",
1956
- gap: 5,
1957
- marginTop: 4
1958
- },
1959
- privacyText: {
1960
- fontSize: 11,
1961
- fontWeight: "500",
1962
- color: import_core7.ada.inkSoft,
1963
- letterSpacing: 0.2,
1964
- fontFamily: fontFam
1965
- },
1966
- closeBtn: {
1967
- width: 32,
1968
- height: 32,
1969
- borderRadius: 16,
1970
- backgroundColor: import_core7.ada.paperSoft,
1971
- justifyContent: "center",
1972
- alignItems: "center"
1973
- },
1974
- body: {
1975
- flex: 1
1976
- }
1977
- });
1978
-
1979
- // src/surfaces/SurfaceCard.tsx
1980
- var import_react_native5 = require("react-native");
1981
- var import_core8 = require("@ateam-ai-mobile/core");
1982
- var import_jsx_runtime3 = require("react/jsx-runtime");
1983
- var fontFam2 = import_react_native5.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1984
- function SurfaceCard({
1985
- icon,
1986
- title,
1987
- subtitle,
1988
- onPress,
1989
- onLongPress,
1990
- showArrow,
1991
- children
1992
- }) {
1993
- const arrow = showArrow ?? !!onPress;
1994
- const handlePress = () => {
1995
- if (!onPress) return;
1996
- getHaptics()?.impact("light");
1997
- onPress();
1998
- };
1999
- const handleLongPress = () => {
2000
- if (!onLongPress) return;
2001
- getHaptics()?.impact("medium");
2002
- onLongPress();
2003
- };
2004
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2005
- import_react_native5.Pressable,
2006
- {
2007
- accessibilityRole: "button",
2008
- accessibilityLabel: [icon, title, subtitle].filter(Boolean).join(", "),
2009
- accessibilityHint: onLongPress ? "Long-press for more options" : void 0,
2010
- style: styles4.card,
2011
- onPress: onPress ? handlePress : void 0,
2012
- onLongPress: onLongPress ? handleLongPress : void 0,
2013
- disabled: !onPress && !onLongPress,
2014
- children: [
2015
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: styles4.row, children: [
2016
- icon ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles4.icon, children: icon }) : null,
2017
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native5.View, { style: { flex: 1 }, children: [
2018
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles4.title, children: title }),
2019
- subtitle ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles4.subtitle, children: subtitle }) : null
2020
- ] }),
2021
- arrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Text, { style: styles4.arrow, children: "\u2192" }) : null
2022
- ] }),
2023
- children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.View, { style: styles4.body, children }) : null
2024
- ]
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`);
2050
+ }
2051
+ const data = await (0, import_core7.connectorCall)(targetConnector, tool, args);
2052
+ if (!data.ok && data.error) throw new Error(data.error);
2053
+ return (0, import_core7.unwrapMcpResult)(data.result);
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;
2072
+ try {
2073
+ import_core7.useChatStore.getState().sendMessage(t);
2074
+ } catch (err) {
2075
+ console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
2076
+ }
2077
+ },
2078
+ prepareMessage(text) {
2079
+ const t = (text || "").trim();
2080
+ if (!t) return;
2081
+ import_core7.pluginCommandEmitter.emit("prepare-chat-message", { text: t });
2025
2082
  }
2026
- );
2083
+ };
2027
2084
  }
2028
- var styles4 = import_react_native5.StyleSheet.create({
2029
- card: {
2030
- backgroundColor: import_core8.ada.paperSoft,
2031
- borderWidth: 1,
2032
- borderColor: import_core8.ada.border,
2033
- borderRadius: import_core8.borderRadius.xl,
2034
- paddingHorizontal: 16,
2035
- paddingVertical: 14
2036
- },
2037
- row: {
2038
- flexDirection: "row",
2039
- alignItems: "center",
2040
- gap: 14
2041
- },
2042
- icon: {
2043
- fontSize: 22
2044
- },
2045
- title: {
2046
- fontSize: 15,
2047
- fontWeight: "600",
2048
- color: import_core8.ada.ink,
2049
- letterSpacing: -0.2,
2050
- fontFamily: fontFam2
2051
- },
2052
- subtitle: {
2053
- fontSize: 13,
2054
- fontWeight: "400",
2055
- color: import_core8.ada.inkMuted,
2056
- marginTop: 2,
2057
- letterSpacing: -0.1,
2058
- fontFamily: fontFam2
2059
- },
2060
- arrow: {
2061
- fontSize: 18,
2062
- color: import_core8.ada.inkMuted,
2063
- fontWeight: "400"
2064
- },
2065
- body: {
2066
- marginTop: 10,
2067
- paddingTop: 10,
2068
- borderTopWidth: 1,
2069
- borderTopColor: import_core8.ada.border
2070
- }
2071
- });
2072
2085
 
2073
- // src/surfaces/SurfaceFullscreen.tsx
2086
+ // src/hooks/usePlugin.ts
2074
2087
  var import_react11 = require("react");
2075
- var import_react_native6 = require("react-native");
2076
- var import_react_native_svg3 = __toESM(require("react-native-svg"));
2077
- var import_core9 = require("@ateam-ai-mobile/core");
2078
- var import_jsx_runtime4 = require("react/jsx-runtime");
2079
- var fontFam3 = import_react_native6.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
2080
- function SurfaceFullscreen({
2081
- visible,
2082
- onClose,
2083
- status,
2084
- hero,
2085
- children,
2086
- showWordmark = true,
2087
- closeIcon = "x",
2088
- closeLabel = "Close"
2089
- }) {
2090
- const fadeAnim = (0, import_react11.useRef)(new import_react_native6.Animated.Value(0)).current;
2091
- const heroScale = (0, import_react11.useRef)(new import_react_native6.Animated.Value(0.6)).current;
2092
- (0, import_react11.useEffect)(() => {
2093
- if (visible) {
2094
- import_react_native6.Animated.parallel([
2095
- import_react_native6.Animated.timing(fadeAnim, { toValue: 1, duration: 280, useNativeDriver: true }),
2096
- import_react_native6.Animated.spring(heroScale, { toValue: 1, useNativeDriver: true, tension: 60, friction: 11 })
2097
- ]).start();
2098
- } else {
2099
- fadeAnim.setValue(0);
2100
- heroScale.setValue(0.6);
2101
- }
2102
- }, [visible]);
2103
- const close = () => {
2104
- import_react_native6.Animated.parallel([
2105
- import_react_native6.Animated.timing(fadeAnim, { toValue: 0, duration: 180, useNativeDriver: true }),
2106
- import_react_native6.Animated.timing(heroScale, { toValue: 0.6, duration: 180, useNativeDriver: true })
2107
- ]).start(() => onClose());
2108
- };
2109
- if (!visible) return null;
2110
- const renderCloseIcon = () => {
2111
- if (closeIcon === "hangup") {
2112
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_svg3.default, { width: 22, height: 22, viewBox: "0 0 24 24", fill: import_core9.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" }) });
2113
- }
2114
- if (closeIcon === "x") {
2115
- 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_core9.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" }) });
2116
- }
2117
- return closeIcon;
2118
- };
2119
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native6.Animated.View, { style: [styles5.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
2120
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native6.View, { style: styles5.topBar, children: [
2121
- showWordmark ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles5.wordmark, children: "ada" }) : null,
2122
- status ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles5.status, children: status }) : null
2123
- ] }),
2124
- hero ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.View, { style: styles5.heroWrap, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Animated.View, { style: { transform: [{ scale: heroScale }] }, children: hero }) }) : null,
2125
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.View, { style: styles5.body, children }),
2126
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native6.View, { style: styles5.bottomBar, children: [
2127
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2128
- import_react_native6.Pressable,
2129
- {
2130
- accessibilityRole: "button",
2131
- accessibilityLabel: closeLabel,
2132
- style: styles5.closeBtn,
2133
- onPress: close,
2134
- hitSlop: 12,
2135
- children: renderCloseIcon()
2136
- }
2137
- ),
2138
- closeLabel ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.Text, { style: styles5.closeLabel, children: closeLabel }) : null
2139
- ] })
2140
- ] });
2088
+ function extractBundleUrl(pluginId, manifest) {
2089
+ const render = manifest.render;
2090
+ if (!render || typeof render !== "object") return { url: null, source: "none" };
2091
+ const injected = manifest.bundleUrl || render.bundleUrl || render.reactNative?.bundleUrl || null;
2092
+ const hasNativeComponent = !!(render.reactNative?.component || render.component);
2093
+ if (hasNativeComponent) {
2094
+ return {
2095
+ url: `/api/ui-plugins/${encodeURIComponent(pluginId)}/bundle.js`,
2096
+ source: injected ? "ignored-explicit" : "derived-default",
2097
+ ...injected && { ignored: injected }
2098
+ };
2099
+ }
2100
+ return { url: null, source: "none" };
2141
2101
  }
2142
- var styles5 = import_react_native6.StyleSheet.create({
2143
- overlay: {
2144
- position: "absolute",
2145
- top: 0,
2146
- left: 0,
2147
- right: 0,
2148
- bottom: 0,
2149
- backgroundColor: import_core9.ada.paper,
2150
- zIndex: 1e3,
2151
- elevation: 1e3
2152
- },
2153
- topBar: {
2154
- paddingTop: import_react_native6.Platform.OS === "ios" ? 64 : 36,
2155
- paddingHorizontal: 24,
2156
- alignItems: "center",
2157
- gap: 6
2158
- },
2159
- wordmark: {
2160
- fontSize: 14,
2161
- fontFamily: fontFam3,
2162
- fontWeight: "500",
2163
- color: import_core9.ada.inkMuted,
2164
- letterSpacing: 4
2165
- },
2166
- status: {
2167
- fontSize: 17,
2168
- fontFamily: fontFam3,
2169
- fontWeight: "500",
2170
- color: import_core9.ada.ink,
2171
- letterSpacing: -0.2
2172
- },
2173
- heroWrap: {
2174
- paddingTop: 24,
2175
- paddingBottom: 12,
2176
- alignItems: "center",
2177
- justifyContent: "center"
2178
- },
2179
- body: {
2180
- flex: 1
2181
- },
2182
- bottomBar: {
2183
- paddingBottom: import_react_native6.Platform.OS === "ios" ? 48 : 32,
2184
- paddingTop: 12,
2185
- alignItems: "center",
2186
- gap: 8
2187
- },
2188
- closeBtn: {
2189
- width: 64,
2190
- height: 64,
2191
- borderRadius: 32,
2192
- backgroundColor: import_core9.ada.ink,
2193
- justifyContent: "center",
2194
- alignItems: "center",
2195
- shadowColor: import_core9.ada.ink,
2196
- shadowOffset: { width: 0, height: 8 },
2197
- shadowOpacity: 0.18,
2198
- shadowRadius: 14,
2199
- elevation: 8
2200
- },
2201
- closeLabel: {
2202
- fontSize: 13,
2203
- fontFamily: fontFam3,
2204
- fontWeight: "500",
2205
- color: import_core9.ada.inkMuted,
2206
- letterSpacing: 0.3
2102
+ function extractBundleHash(manifest) {
2103
+ if (manifest.bundleHash) return manifest.bundleHash;
2104
+ const render = manifest.render;
2105
+ if (!render || typeof render !== "object") return void 0;
2106
+ if (render.bundleHash) return render.bundleHash;
2107
+ if (render.reactNative?.bundleHash) return render.reactNative.bundleHash;
2108
+ return void 0;
2109
+ }
2110
+ function extractComponentId(manifest) {
2111
+ const render = manifest.render;
2112
+ if (!render || typeof render !== "object") return null;
2113
+ return render.component || render.reactNative?.component || null;
2114
+ }
2115
+ function usePlugin(pluginId, manifest) {
2116
+ const [plugin, setPlugin] = (0, import_react11.useState)(null);
2117
+ const [loading, setLoading] = (0, import_react11.useState)(true);
2118
+ const [error, setError] = (0, import_react11.useState)(null);
2119
+ const [bundleMissing, setBundleMissing] = (0, import_react11.useState)(false);
2120
+ const version = manifest.version || "1.0.0";
2121
+ const { url: bundleUrl, ignored: ignoredUrl } = extractBundleUrl(pluginId, manifest);
2122
+ const bundleHash = extractBundleHash(manifest);
2123
+ const componentId = extractComponentId(manifest);
2124
+ const lookupId = componentId || pluginId;
2125
+ if (ignoredUrl) {
2126
+ console.warn(`[usePlugin] ${pluginId}: IGNORED connector-injected bundleUrl="${ignoredUrl}". The host derives bundle URLs from pluginId via the canonical route.`);
2207
2127
  }
2208
- });
2209
-
2210
- // src/registry/store.ts
2211
- var import_zustand = require("zustand");
2212
- var useSurfaceStore = (0, import_zustand.create)((set, get) => ({
2213
- manifests: {},
2214
- activeDrawer: null,
2215
- activeFullscreen: null,
2216
- activeProps: {},
2217
- mountKey: 0,
2218
- register: (manifest) => set((s) => ({ manifests: { ...s.manifests, [manifest.id]: manifest } })),
2219
- unregister: (id) => set((s) => {
2220
- const next = { ...s.manifests };
2221
- delete next[id];
2222
- const nextProps = { ...s.activeProps };
2223
- delete nextProps[id];
2224
- return { manifests: next, activeProps: nextProps };
2225
- }),
2226
- show: (id, props) => {
2227
- const m = get().manifests[id];
2228
- if (!m) {
2229
- console.warn("[SurfaceStore] show: unknown surface id", id);
2230
- return;
2231
- }
2232
- set((s) => {
2233
- const nextProps = props !== void 0 ? { ...s.activeProps, [id]: props } : s.activeProps;
2234
- const mountKey = s.mountKey + 1;
2235
- if (m.type === "drawer") return { activeDrawer: id, activeProps: nextProps, mountKey };
2236
- if (m.type === "fullscreen") return { activeFullscreen: id, activeProps: nextProps, mountKey };
2237
- return { mountKey };
2238
- });
2239
- },
2240
- hide: (id) => {
2241
- const s = get();
2242
- if (!id) {
2243
- const cleanup2 = { ...s.activeProps };
2244
- if (s.activeDrawer) delete cleanup2[s.activeDrawer];
2245
- if (s.activeFullscreen) delete cleanup2[s.activeFullscreen];
2246
- set({ activeDrawer: null, activeFullscreen: null, activeProps: cleanup2 });
2247
- return;
2248
- }
2249
- const cleanup = { ...s.activeProps };
2250
- delete cleanup[id];
2251
- const patch = { activeProps: cleanup };
2252
- if (s.activeDrawer === id) patch.activeDrawer = null;
2253
- if (s.activeFullscreen === id) patch.activeFullscreen = null;
2254
- set(patch);
2255
- },
2256
- list: (filter) => {
2257
- const all = Object.values(get().manifests);
2258
- return all.filter((m) => {
2259
- if (filter?.type && m.type !== filter.type) return false;
2260
- if (filter?.visibility && !filter.visibility.includes(m.visibility)) return false;
2261
- if (filter?.placement) {
2262
- if (filter.placement === "unset") {
2263
- if (m.placement) return false;
2264
- } else if (m.placement !== filter.placement) {
2265
- return false;
2128
+ (0, import_react11.useEffect)(() => {
2129
+ let cancelled = false;
2130
+ (async () => {
2131
+ try {
2132
+ if (nativePluginRegistry.has(lookupId)) {
2133
+ const registered = await nativePluginRegistry.get(lookupId);
2134
+ if (cancelled) return;
2135
+ if (!registered) {
2136
+ throw new Error(`Local plugin not found: ${lookupId}`);
2137
+ }
2138
+ if (registered.type === "service") {
2139
+ throw new Error(`Plugin ${lookupId} is a service plugin and cannot be rendered`);
2140
+ }
2141
+ setPlugin(registered);
2142
+ setLoading(false);
2143
+ return;
2144
+ }
2145
+ if (bundleUrl) {
2146
+ try {
2147
+ const component = await bundleManager.load(pluginId, bundleUrl, version, bundleHash);
2148
+ if (cancelled) return;
2149
+ setPlugin(component);
2150
+ setLoading(false);
2151
+ return;
2152
+ } catch (bundleErr) {
2153
+ const msg = bundleErr instanceof Error ? bundleErr.message : String(bundleErr);
2154
+ console.warn(`[usePlugin] ${pluginId}: Bundle load failed, falling back to WebView:`, msg);
2155
+ if (!cancelled) {
2156
+ setBundleMissing(true);
2157
+ setLoading(false);
2158
+ }
2159
+ return;
2160
+ }
2161
+ }
2162
+ const iframeUrl = manifest?.render?.iframeUrl || manifest?.render?.mode === "adaptive" && manifest?.render?.iframe?.iframeUrl;
2163
+ if (iframeUrl) {
2164
+ if (!cancelled) {
2165
+ setBundleMissing(true);
2166
+ setLoading(false);
2167
+ }
2168
+ return;
2266
2169
  }
2170
+ throw new Error(
2171
+ `Plugin "${manifest.name || pluginId}" has no bundle URL and is not in the local registry. Check that the solution definition includes a valid render.reactNative.bundleUrl.`
2172
+ );
2173
+ } catch (err) {
2174
+ if (cancelled) return;
2175
+ const msg = err instanceof Error ? err.message : String(err);
2176
+ console.error(`[usePlugin] Failed to load ${pluginId}:`, msg);
2177
+ setError(msg);
2178
+ setLoading(false);
2267
2179
  }
2268
- return true;
2269
- });
2270
- }
2271
- }));
2272
- function isMountSurface(m) {
2273
- return m.type === "drawer" || m.type === "fullscreen";
2274
- }
2275
- function isInlineSurface(m) {
2276
- return m.type === "card" || m.type === "header" || m.type === "ambient" || m.type === "nudge";
2180
+ })();
2181
+ return () => {
2182
+ cancelled = true;
2183
+ };
2184
+ }, [pluginId, lookupId, bundleUrl, bundleHash, version]);
2185
+ return { plugin, loading, error, bundleMissing };
2277
2186
  }
2278
2187
 
2279
- // src/registry/SurfaceDispatcher.tsx
2188
+ // src/hooks/useRemotePlugins.ts
2280
2189
  var import_react12 = require("react");
2281
- var import_core10 = require("@ateam-ai-mobile/core");
2282
- var import_jsx_runtime5 = require("react/jsx-runtime");
2283
- function SurfaceDispatcher() {
2284
- const manifests = useSurfaceStore((s) => s.manifests);
2285
- const activeDrawer = useSurfaceStore((s) => s.activeDrawer);
2286
- const activeFullscreen = useSurfaceStore((s) => s.activeFullscreen);
2287
- const activeProps = useSurfaceStore((s) => s.activeProps);
2288
- const mountKey = useSurfaceStore((s) => s.mountKey);
2289
- const hide = useSurfaceStore((s) => s.hide);
2190
+ var import_core8 = require("@ateam-ai-mobile/core");
2191
+ function useRemotePlugins(solutionId) {
2192
+ const [plugins, setPlugins] = (0, import_react12.useState)([]);
2193
+ const [loading, setLoading] = (0, import_react12.useState)(true);
2194
+ const [refreshing, setRefreshing] = (0, import_react12.useState)(false);
2195
+ const [error, setError] = (0, import_react12.useState)(null);
2196
+ const fetchRemotePlugins = (0, import_react12.useCallback)(async () => {
2197
+ try {
2198
+ setError(null);
2199
+ const targetSolutionId = solutionId || "current";
2200
+ let data;
2201
+ try {
2202
+ data = await (0, import_core8.apiFetch)(`/api/solutions/${targetSolutionId}/ui-plugins`);
2203
+ } catch (err) {
2204
+ if (err?.message?.includes("404")) {
2205
+ console.warn(`[useRemotePlugins] Solution ${targetSolutionId} not found or has no plugins`);
2206
+ setPlugins([]);
2207
+ return;
2208
+ }
2209
+ throw err;
2210
+ }
2211
+ const uiPlugins = (data.plugins || []).map((plugin) => ({
2212
+ id: plugin.id,
2213
+ name: plugin.name,
2214
+ version: plugin.version || "1.0.0",
2215
+ source: "remote",
2216
+ manifest: {
2217
+ id: plugin.id,
2218
+ name: plugin.name,
2219
+ version: plugin.version || "1.0.0",
2220
+ type: "ui",
2221
+ description: plugin.description || "",
2222
+ capabilities: plugin.capabilities || {},
2223
+ channels: plugin.channels || ["default"],
2224
+ render: plugin.render && typeof plugin.render === "object" ? plugin.render : { mode: "react-native", component: plugin.name || plugin.id },
2225
+ entry_point: plugin.entry_point || plugin.name,
2226
+ sandbox: plugin.sandbox !== false,
2227
+ requires_auth: plugin.requires_auth !== false
2228
+ }
2229
+ }));
2230
+ setPlugins(uiPlugins);
2231
+ } catch (err) {
2232
+ const message = err instanceof Error ? err.message : String(err);
2233
+ console.error("[useRemotePlugins] Failed to fetch plugins:", message);
2234
+ setError(message);
2235
+ setPlugins([]);
2236
+ } finally {
2237
+ setLoading(false);
2238
+ setRefreshing(false);
2239
+ }
2240
+ }, [solutionId]);
2241
+ const refresh = (0, import_react12.useCallback)(async () => {
2242
+ setRefreshing(true);
2243
+ await fetchRemotePlugins();
2244
+ }, [fetchRemotePlugins]);
2290
2245
  (0, import_react12.useEffect)(() => {
2291
- const unsub = import_core10.pluginCommandEmitter.on("plugin-close", ({ pluginId } = {}) => {
2292
- if (!pluginId) return;
2293
- const s = useSurfaceStore.getState();
2294
- if (s.activeDrawer === pluginId || s.activeFullscreen === pluginId) {
2295
- s.hide(pluginId);
2246
+ setLoading(true);
2247
+ fetchRemotePlugins();
2248
+ }, [fetchRemotePlugins]);
2249
+ return {
2250
+ plugins,
2251
+ loading,
2252
+ error,
2253
+ refreshing,
2254
+ refresh
2255
+ };
2256
+ }
2257
+
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: {}
2296
2299
  }
2297
- });
2298
- return unsub;
2299
- }, []);
2300
- const drawer = activeDrawer ? manifests[activeDrawer] : null;
2301
- const fullscreen = activeFullscreen ? manifests[activeFullscreen] : null;
2302
- const drawerProps = activeDrawer ? activeProps[activeDrawer] : void 0;
2303
- const fullscreenProps = activeFullscreen ? activeProps[activeFullscreen] : void 0;
2304
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
2305
- drawer && drawer.type === "drawer" && drawer.Component ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2306
- drawer.Component,
2307
- {
2308
- dismiss: () => hide(drawer.id),
2309
- props: drawerProps
2310
- },
2311
- `drawer-${drawer.id}-${mountKey}`
2312
- ) : null,
2313
- fullscreen && fullscreen.type === "fullscreen" && fullscreen.Component ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2314
- fullscreen.Component,
2315
- {
2316
- dismiss: () => hide(fullscreen.id),
2317
- props: fullscreenProps
2318
- },
2319
- `fullscreen-${fullscreen.id}-${mountKey}`
2320
- ) : null
2321
- ] });
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}`;
2322
2359
  }
2323
2360
  // Annotate the CommonJS export names for ESM import in node:
2324
2361
  0 && (module.exports = {
@@ -2356,6 +2393,7 @@ function SurfaceDispatcher() {
2356
2393
  View,
2357
2394
  bundleManager,
2358
2395
  createNativeBridge,
2396
+ gatePluginNative,
2359
2397
  getHaptics,
2360
2398
  getNativeBundleHash,
2361
2399
  getNativeBundleUrl,