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

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