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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -75,6 +75,66 @@ var init_voice = __esm({
75
75
  }
76
76
  });
77
77
 
78
+ // src/sdk/device-state.ts
79
+ var device_state_exports = {};
80
+ __export(device_state_exports, {
81
+ DeviceBridgeError: () => DeviceBridgeError,
82
+ explainCapability: () => explainCapability,
83
+ isActionable: () => isActionable,
84
+ neverObserved: () => neverObserved
85
+ });
86
+ function isActionable(quality, budget) {
87
+ const { freshness, confidence } = quality;
88
+ if (freshness.state === "unknown" || freshness.state === "stale") return false;
89
+ if (freshness.ageMs === null || freshness.ageMs > budget.maxAgeMs) return false;
90
+ if (budget.minConfidence) {
91
+ const have = CONFIDENCE_ORDER[confidence?.level ?? "unknown"];
92
+ if (have < CONFIDENCE_ORDER[budget.minConfidence]) return false;
93
+ }
94
+ return true;
95
+ }
96
+ function neverObserved() {
97
+ return { state: "unknown", observedAt: null, ageMs: null };
98
+ }
99
+ function explainCapability(status) {
100
+ if (status.support === "unsupported") return "this device does not have it";
101
+ if (status.permission === "denied") return "permission was denied";
102
+ if (status.permission === "restricted") return "permission is restricted";
103
+ if (status.permission === "notDetermined") return "permission has not been asked for yet";
104
+ if (status.permission === "limited") return "only limited access was granted";
105
+ if (status.restriction === "policyRestricted") return "this plugin did not declare it";
106
+ if (status.restriction === "backgroundRestricted") return "the app is in the background";
107
+ if (status.restriction === "lowPowerRestricted") return "the device is in low-power mode";
108
+ if (status.availability === "busy") return "it is in use by something else";
109
+ if (status.availability === "temporarilyUnavailable") return "it is temporarily unavailable";
110
+ if (status.availability === "unavailable") return "it is unavailable";
111
+ if (status.support === "unknown" || status.availability === "unknown") {
112
+ return "its state could not be determined";
113
+ }
114
+ return null;
115
+ }
116
+ var DeviceBridgeError, CONFIDENCE_ORDER;
117
+ var init_device_state = __esm({
118
+ "src/sdk/device-state.ts"() {
119
+ "use strict";
120
+ DeviceBridgeError = class extends Error {
121
+ constructor(code, message, opts) {
122
+ super(message);
123
+ this.name = "DeviceBridgeError";
124
+ this.code = code;
125
+ this.domain = opts?.domain;
126
+ this.recoverable = opts?.recoverable;
127
+ }
128
+ };
129
+ CONFIDENCE_ORDER = {
130
+ unknown: 0,
131
+ low: 1,
132
+ medium: 2,
133
+ high: 3
134
+ };
135
+ }
136
+ });
137
+
78
138
  // src/sdk/primitives.ts
79
139
  var import_react_native;
80
140
  var init_primitives = __esm({
@@ -666,6 +726,8 @@ __export(sdk_exports, {
666
726
  TextInput: () => import_react_native.TextInput,
667
727
  TouchableOpacity: () => import_react_native.TouchableOpacity,
668
728
  View: () => import_react_native.View,
729
+ explainCapability: () => explainCapability,
730
+ isActionable: () => isActionable,
669
731
  register: () => register,
670
732
  registerService: () => registerService,
671
733
  registerTools: () => registerTools,
@@ -680,6 +742,7 @@ var init_sdk = __esm({
680
742
  init_registration();
681
743
  init_registration();
682
744
  init_voice();
745
+ init_device_state();
683
746
  init_primitives();
684
747
  init_components();
685
748
  init_CameraPreview();
@@ -1278,6 +1341,26 @@ var init_registry = __esm({
1278
1341
  }
1279
1342
  });
1280
1343
 
1344
+ // src/bundles/bundleUrl.ts
1345
+ function pluginBundleUrl(pluginId) {
1346
+ return `/api/ui-plugins/${encodeURIComponent(pluginId)}/bundle.js`;
1347
+ }
1348
+ function isPluginBundleUrl(url, pluginId) {
1349
+ if (typeof url !== "string") return false;
1350
+ const canonical = pluginBundleUrl(pluginId);
1351
+ if (url === canonical) return true;
1352
+ try {
1353
+ return decodeURIComponent(url) === decodeURIComponent(canonical);
1354
+ } catch {
1355
+ return false;
1356
+ }
1357
+ }
1358
+ var init_bundleUrl = __esm({
1359
+ "src/bundles/bundleUrl.ts"() {
1360
+ "use strict";
1361
+ }
1362
+ });
1363
+
1281
1364
  // src/api/plugins.ts
1282
1365
  function supportsNative(manifest) {
1283
1366
  const mode = manifest.render?.mode;
@@ -1289,12 +1372,9 @@ function getNativeComponentId(manifest) {
1289
1372
  return null;
1290
1373
  }
1291
1374
  function getNativeBundleUrl(manifest) {
1292
- const direct = manifest.bundleUrl;
1293
- if (direct) return direct;
1294
1375
  const render = manifest.render;
1295
- if (render.mode === "react-native") return render.bundleUrl || null;
1296
- if (render.mode === "adaptive") return render.reactNative?.bundleUrl || null;
1297
- return null;
1376
+ const declared = manifest.bundleUrl || (render?.mode === "react-native" ? render.bundleUrl : null) || (render?.mode === "adaptive" ? render.reactNative?.bundleUrl : null);
1377
+ return declared && manifest.id ? pluginBundleUrl(manifest.id) : null;
1298
1378
  }
1299
1379
  function getNativeBundleHash(manifest) {
1300
1380
  const direct = manifest.bundleHash;
@@ -1384,6 +1464,7 @@ var init_plugins = __esm({
1384
1464
  "use strict";
1385
1465
  import_core3 = require("@ateam-ai-mobile/core");
1386
1466
  import_core4 = require("@ateam-ai-mobile/core");
1467
+ init_bundleUrl();
1387
1468
  }
1388
1469
  });
1389
1470
 
@@ -2212,6 +2293,20 @@ var init_SurfaceDispatcher = __esm({
2212
2293
  }
2213
2294
  });
2214
2295
 
2296
+ // src/bundles/bundleModuleNames.ts
2297
+ function sdkModuleRefusal(name) {
2298
+ if (name === PLUGIN_SDK_MODULE) return null;
2299
+ if (!name.startsWith("@ateam-ai-mobile/") && !name.startsWith("@adas/")) return null;
2300
+ return `a plugin bundle required '${name}', which does not exist inside a bundle. Import from '${PLUGIN_SDK_MODULE}' instead \u2014 it is the only SDK module a bundle can require (values and types alike), and the one to keep external when building. '@ateam-ai-mobile/*' are the host app's packages, not the plugin's.`;
2301
+ }
2302
+ var PLUGIN_SDK_MODULE;
2303
+ var init_bundleModuleNames = __esm({
2304
+ "src/bundles/bundleModuleNames.ts"() {
2305
+ "use strict";
2306
+ PLUGIN_SDK_MODULE = "@adas/plugin-sdk";
2307
+ }
2308
+ });
2309
+
2215
2310
  // src/utils/sha256.ts
2216
2311
  function sha256Hex(message) {
2217
2312
  const utf8 = unescape(encodeURIComponent(message));
@@ -2378,6 +2473,22 @@ function selectOwnedCacheEntries(entries, tenant) {
2378
2473
  if (!tenant) return [];
2379
2474
  return (entries || []).filter((e) => !!e && e.tenant === tenant);
2380
2475
  }
2476
+ async function loadCacheIndex(storage = getBundleStorage()) {
2477
+ const index = await storage.loadIndex() || {};
2478
+ for (const [key, entry] of Object.entries(index)) {
2479
+ const pluginId = entry?.trusted?.pluginId;
2480
+ if (!pluginId) continue;
2481
+ const canonical = pluginBundleUrl(pluginId);
2482
+ if (key === canonical) continue;
2483
+ if (!isPluginBundleUrl(key, pluginId) || !isPluginBundleUrl(entry.bundleUrl, pluginId)) continue;
2484
+ delete index[key];
2485
+ const held = index[canonical];
2486
+ if (!held || (entry.timestamp ?? 0) > (held.timestamp ?? 0)) {
2487
+ index[canonical] = { ...entry, bundleUrl: canonical };
2488
+ }
2489
+ }
2490
+ return index;
2491
+ }
2381
2492
  function setBundleStorage(storage) {
2382
2493
  _storage = storage;
2383
2494
  }
@@ -2388,6 +2499,7 @@ var MemoryBundleStorage, _storage;
2388
2499
  var init_storage = __esm({
2389
2500
  "src/bundles/storage.ts"() {
2390
2501
  "use strict";
2502
+ init_bundleUrl();
2391
2503
  MemoryBundleStorage = class {
2392
2504
  constructor() {
2393
2505
  this.index = {};
@@ -2420,6 +2532,9 @@ var init_storage = __esm({
2420
2532
  });
2421
2533
 
2422
2534
  // src/bundles/bundleManager.ts
2535
+ function bundleKey(pluginId, bundleUrl) {
2536
+ return isPluginBundleUrl(bundleUrl, pluginId) ? pluginBundleUrl(pluginId) : bundleUrl;
2537
+ }
2423
2538
  var import_react9, import_react10, ReactNative, import_core9, ReactShim, PluginSDKShim, requireShim, CACHE_TTL, hasComponent, hasTools, BundleManager, bundleManager;
2424
2539
  var init_bundleManager = __esm({
2425
2540
  "src/bundles/bundleManager.ts"() {
@@ -2435,9 +2550,11 @@ var init_bundleManager = __esm({
2435
2550
  init_store();
2436
2551
  init_SurfaceDispatcher();
2437
2552
  init_host();
2553
+ init_bundleModuleNames();
2438
2554
  init_sha256();
2439
2555
  init_integrity();
2440
2556
  init_storage();
2557
+ init_bundleUrl();
2441
2558
  init_registry();
2442
2559
  ReactShim = {
2443
2560
  __esModule: true,
@@ -2484,7 +2601,9 @@ var init_bundleManager = __esm({
2484
2601
  requireShim = (name) => {
2485
2602
  if (name === "react") return ReactShim;
2486
2603
  if (name === "react-native") return ReactNative;
2487
- if (name === "@adas/plugin-sdk") return PluginSDKShim;
2604
+ if (name === PLUGIN_SDK_MODULE) return PluginSDKShim;
2605
+ const refusal = sdkModuleRefusal(name);
2606
+ if (refusal) throw new Error(`[BundleManager] ${refusal}`);
2488
2607
  const hostModule = resolveBundleModule(name);
2489
2608
  if (hostModule !== void 0) return hostModule;
2490
2609
  console.warn(`[BundleManager] Unknown module: ${name} \u2014 register it via registerBundleModules()`);
@@ -2612,6 +2731,7 @@ var init_bundleManager = __esm({
2612
2731
  * manifest-driven tool loader already made.
2613
2732
  */
2614
2733
  async load(pluginId, bundleUrl, version, expectedHash, grants) {
2734
+ bundleUrl = bundleKey(pluginId, bundleUrl);
2615
2735
  const tenant = this.requireTenant();
2616
2736
  const epoch = this.epoch;
2617
2737
  const cached = this.getComponentFromMemory(pluginId, bundleUrl);
@@ -2657,6 +2777,7 @@ var init_bundleManager = __esm({
2657
2777
  if (!grants) {
2658
2778
  throw new Error(`[BundleManager] loadTools(${pluginId}) needs the manifest's grants (resolvePluginGrants) \u2014 refusing to register tools on the bundle's own claim`);
2659
2779
  }
2780
+ bundleUrl = bundleKey(pluginId, bundleUrl);
2660
2781
  const tenant = this.requireTenant();
2661
2782
  const epoch = this.epoch;
2662
2783
  let modulePromise = this.inflight.get(bundleUrl);
@@ -2727,11 +2848,12 @@ var init_bundleManager = __esm({
2727
2848
  */
2728
2849
  async clearPlugin(pluginId) {
2729
2850
  const storage = getBundleStorage();
2730
- const index = await storage.loadIndex();
2731
- const entry = index[pluginId];
2851
+ const index = await loadCacheIndex(storage);
2852
+ const bundleUrl = pluginBundleUrl(pluginId);
2853
+ const entry = index[bundleUrl];
2732
2854
  if (entry) {
2733
- this.modules.delete(entry.bundleUrl);
2734
- delete index[pluginId];
2855
+ this.modules.delete(bundleUrl);
2856
+ delete index[bundleUrl];
2735
2857
  await storage.saveIndex(index);
2736
2858
  try {
2737
2859
  await storage.deleteBundle(entry.localPath);
@@ -2760,7 +2882,7 @@ var init_bundleManager = __esm({
2760
2882
  return;
2761
2883
  }
2762
2884
  const storage = getBundleStorage();
2763
- const index = await storage.loadIndex();
2885
+ const index = await loadCacheIndex(storage);
2764
2886
  const expectedLc = expectedHash ? expectedHash.toLowerCase() : null;
2765
2887
  let content = null;
2766
2888
  let verifiedHash = null;
@@ -2895,7 +3017,7 @@ var init_bundleManager = __esm({
2895
3017
  if (this.recordedDeclarations.get(bundleUrl) === key) return;
2896
3018
  try {
2897
3019
  const storage = getBundleStorage();
2898
- const index = await storage.loadIndex();
3020
+ const index = await loadCacheIndex(storage);
2899
3021
  const entry = index[bundleUrl];
2900
3022
  const running = this.provenance.get(bundleUrl)?.hash;
2901
3023
  if (!entry || entry.tenant !== tenant || !running || entry.bundleHash?.toLowerCase() !== running) return;
@@ -3087,7 +3209,7 @@ async function ensureDeviceToolsLoaded({ force = false } = {}) {
3087
3209
  }
3088
3210
  async function loadFromDiskCache() {
3089
3211
  try {
3090
- const index = await getBundleStorage().loadIndex();
3212
+ const index = await loadCacheIndex(getBundleStorage());
3091
3213
  const entries = Object.values(index || {});
3092
3214
  if (entries.length === 0) return [];
3093
3215
  const tenant = (0, import_core10.getTenant)();
@@ -3205,6 +3327,7 @@ __export(index_exports, {
3205
3327
  listPlugins: () => listPlugins,
3206
3328
  nativePluginRegistry: () => nativePluginRegistry,
3207
3329
  neverObserved: () => neverObserved,
3330
+ pluginBundleUrl: () => pluginBundleUrl,
3208
3331
  register: () => register,
3209
3332
  registerBundleModules: () => registerBundleModules,
3210
3333
  registerDeviceTools: () => registerDeviceTools,
@@ -3239,62 +3362,8 @@ __export(index_exports, {
3239
3362
  });
3240
3363
  module.exports = __toCommonJS(index_exports);
3241
3364
  init_sdk();
3242
-
3243
- // src/sdk/device-state.ts
3244
- var device_state_exports = {};
3245
- __export(device_state_exports, {
3246
- DeviceBridgeError: () => DeviceBridgeError,
3247
- explainCapability: () => explainCapability,
3248
- isActionable: () => isActionable,
3249
- neverObserved: () => neverObserved
3250
- });
3251
- var DeviceBridgeError = class extends Error {
3252
- constructor(code, message, opts) {
3253
- super(message);
3254
- this.name = "DeviceBridgeError";
3255
- this.code = code;
3256
- this.domain = opts?.domain;
3257
- this.recoverable = opts?.recoverable;
3258
- }
3259
- };
3260
- var CONFIDENCE_ORDER = {
3261
- unknown: 0,
3262
- low: 1,
3263
- medium: 2,
3264
- high: 3
3265
- };
3266
- function isActionable(quality, budget) {
3267
- const { freshness, confidence } = quality;
3268
- if (freshness.state === "unknown" || freshness.state === "stale") return false;
3269
- if (freshness.ageMs === null || freshness.ageMs > budget.maxAgeMs) return false;
3270
- if (budget.minConfidence) {
3271
- const have = CONFIDENCE_ORDER[confidence?.level ?? "unknown"];
3272
- if (have < CONFIDENCE_ORDER[budget.minConfidence]) return false;
3273
- }
3274
- return true;
3275
- }
3276
- function neverObserved() {
3277
- return { state: "unknown", observedAt: null, ageMs: null };
3278
- }
3279
- function explainCapability(status) {
3280
- if (status.support === "unsupported") return "this device does not have it";
3281
- if (status.permission === "denied") return "permission was denied";
3282
- if (status.permission === "restricted") return "permission is restricted";
3283
- if (status.permission === "notDetermined") return "permission has not been asked for yet";
3284
- if (status.permission === "limited") return "only limited access was granted";
3285
- if (status.restriction === "policyRestricted") return "this plugin did not declare it";
3286
- if (status.restriction === "backgroundRestricted") return "the app is in the background";
3287
- if (status.restriction === "lowPowerRestricted") return "the device is in low-power mode";
3288
- if (status.availability === "busy") return "it is in use by something else";
3289
- if (status.availability === "temporarilyUnavailable") return "it is temporarily unavailable";
3290
- if (status.availability === "unavailable") return "it is unavailable";
3291
- if (status.support === "unknown" || status.availability === "unknown") {
3292
- return "its state could not be determined";
3293
- }
3294
- return null;
3295
- }
3296
-
3297
- // src/index.ts
3365
+ init_device_state();
3366
+ init_device_state();
3298
3367
  init_deviceStateHost();
3299
3368
  init_voiceHost();
3300
3369
  init_host();
@@ -3417,6 +3486,7 @@ var import_react11 = require("react");
3417
3486
  init_bundleManager();
3418
3487
  init_host();
3419
3488
  init_pluginRuntime();
3489
+ init_bundleUrl();
3420
3490
  function extractBundleUrl(pluginId, manifest) {
3421
3491
  const render = manifest.render;
3422
3492
  if (!render || typeof render !== "object") return { url: null, source: "none" };
@@ -3424,7 +3494,7 @@ function extractBundleUrl(pluginId, manifest) {
3424
3494
  const hasNativeComponent = !!(render.reactNative?.component || render.component);
3425
3495
  if (hasNativeComponent) {
3426
3496
  return {
3427
- url: `/api/ui-plugins/${encodeURIComponent(pluginId)}/bundle.js`,
3497
+ url: pluginBundleUrl(pluginId),
3428
3498
  source: injected ? "ignored-explicit" : "derived-default",
3429
3499
  ...injected && { ignored: injected }
3430
3500
  };
@@ -3591,6 +3661,7 @@ function useRemotePlugins(solutionId) {
3591
3661
 
3592
3662
  // src/index.ts
3593
3663
  init_plugins();
3664
+ init_bundleUrl();
3594
3665
  init_SurfaceDrawer();
3595
3666
  init_SurfaceCard();
3596
3667
  init_SurfaceFullscreen();
@@ -3664,6 +3735,7 @@ init_SurfaceDispatcher();
3664
3735
  listPlugins,
3665
3736
  nativePluginRegistry,
3666
3737
  neverObserved,
3738
+ pluginBundleUrl,
3667
3739
  register,
3668
3740
  registerBundleModules,
3669
3741
  registerDeviceTools,