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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -511,7 +511,8 @@ var noopNative = {
511
511
  scanner: unavailable("scanner"),
512
512
  nfc: unavailable("nfc"),
513
513
  bluetooth: unavailable("bluetooth"),
514
- vision: unavailable("vision")
514
+ vision: unavailable("vision"),
515
+ maps: unavailable("maps")
515
516
  };
516
517
  var _pluginNative = noopNative;
517
518
  function setPluginNativeProvider(native) {
@@ -553,7 +554,8 @@ function gatePluginNative(native, declared, pluginId) {
553
554
  // every method refuses by name. Both are better than the `undefined` this
554
555
  // used to be, which crashed with "cannot read properties of undefined".
555
556
  bluetooth: caps.bluetooth ? native.bluetooth : blocked("bluetooth"),
556
- vision: caps.vision ? native.vision : blocked("vision")
557
+ vision: caps.vision ? native.vision : blocked("vision"),
558
+ maps: caps.maps ? native.maps : blocked("maps")
557
559
  // What this plugin DECLARED, carried on the object it already receives.
558
560
  //
559
561
  // It is how a COMPONENT can be capability-gated: <CameraPreview> is handed
@@ -2104,6 +2106,19 @@ function gateDeviceState(declared) {
2104
2106
  }
2105
2107
  return out;
2106
2108
  }
2109
+ function withoutSubscribe(state) {
2110
+ if (!state) return state;
2111
+ const out = {};
2112
+ for (const domain of ALL_DOMAINS) {
2113
+ const d = state[domain];
2114
+ out[domain] = d && Object.assign(Object.create(d), {
2115
+ subscribe() {
2116
+ throw denial(domain, "a device tool call has no lifetime to own a subscription. Subscribe from the plugin UI, or read getSnapshot() within the call.");
2117
+ }
2118
+ });
2119
+ }
2120
+ return out;
2121
+ }
2107
2122
  function implementedStateDomains() {
2108
2123
  return ALL_DOMAINS.filter((d) => !!_provider[d]);
2109
2124
  }
@@ -2149,8 +2164,9 @@ function unregisterDeviceTools(pluginId) {
2149
2164
  if (entry.pluginId === pluginId) registry.delete(name);
2150
2165
  }
2151
2166
  }
2152
- function hasDeviceTool(name) {
2153
- return registry.has(name);
2167
+ function hasDeviceTool(name, pluginId) {
2168
+ const entry = registry.get(name);
2169
+ return !!entry && (pluginId === void 0 || entry.pluginId === pluginId);
2154
2170
  }
2155
2171
  function listDeviceTools() {
2156
2172
  return Array.from(registry.entries()).map(([name, { pluginId, tool }]) => ({
@@ -2169,8 +2185,11 @@ async function callDeviceTool(name, args = {}, opts = {}) {
2169
2185
  let timer;
2170
2186
  try {
2171
2187
  const build = contextProvider || defaultContext;
2188
+ const built = build(entry.pluginId, entry.capabilities, entry.stateDomains);
2172
2189
  const ctx = {
2173
- ...build(entry.pluginId, entry.capabilities, entry.stateDomains),
2190
+ ...built,
2191
+ // Whoever built the context, a call cannot own a subscription.
2192
+ deviceState: withoutSubscribe(built.deviceState),
2174
2193
  signal: opts.signal
2175
2194
  };
2176
2195
  const result = await Promise.race([
@@ -2248,6 +2267,8 @@ var requireShim = (name) => {
2248
2267
  return {};
2249
2268
  };
2250
2269
  var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
2270
+ var hasComponent = (val) => !!val && typeof val === "object" && typeof val.Component === "function";
2271
+ var hasTools = (val) => !!val && typeof val === "object" && Array.isArray(val.tools) && val.tools.length > 0;
2251
2272
  var BundleManager = class {
2252
2273
  constructor() {
2253
2274
  /** In-memory module cache: bundleUrl → evaluated module.exports. Holds ONE
@@ -2278,6 +2299,9 @@ var BundleManager = class {
2278
2299
  * of the previous tenant's tools.
2279
2300
  */
2280
2301
  this.registeredToolPluginIds = /* @__PURE__ */ new Set();
2302
+ /** bundleUrl → the trusted declaration last recorded on its disk-cache entry
2303
+ * this session, so a memory hit with unchanged grants skips the index I/O. */
2304
+ this.recordedDeclarations = /* @__PURE__ */ new Map();
2281
2305
  /**
2282
2306
  * Enforce the in-memory tenant boundary. Call at the top of every public load
2283
2307
  * path, BEFORE any cache read.
@@ -2323,6 +2347,7 @@ var BundleManager = class {
2323
2347
  }
2324
2348
  }
2325
2349
  this.registeredToolPluginIds.clear();
2350
+ this.recordedDeclarations.clear();
2326
2351
  this.modules.clear();
2327
2352
  this.provenance.clear();
2328
2353
  this.inflight.clear();
@@ -2334,13 +2359,15 @@ var BundleManager = class {
2334
2359
  isStale(epoch, tenant) {
2335
2360
  return this.epoch !== epoch || getTenant2() !== tenant;
2336
2361
  }
2337
- /** Flip an existing provenance record to source:'memory' — a later serve of an
2338
- * already-evaluated bundle came from the in-memory cache, not a fresh
2339
- * download/disk read. No-op if nothing was recorded yet. */
2340
- markMemoryHit(bundleUrl) {
2362
+ /** Record a memory serve: the running bytes are the already-evaluated module,
2363
+ * and `expected` is refreshed from the manifest hash the caller JUST passed —
2364
+ * so after a redeploy the record reads hash !== expected (STALE) instead of
2365
+ * the old in-sync pair. The ONE place both memory paths (load()'s early return
2366
+ * and loadModule's) record reuse. No-op if nothing was recorded yet. */
2367
+ markMemoryHit(bundleUrl, expectedHash) {
2341
2368
  const prev = this.provenance.get(bundleUrl);
2342
- if (prev && prev.source !== "memory") {
2343
- this.provenance.set(bundleUrl, { ...prev, source: "memory" });
2369
+ if (prev) {
2370
+ this.provenance.set(bundleUrl, { ...prev, source: "memory", expected: expectedHash?.toLowerCase() ?? prev.expected });
2344
2371
  }
2345
2372
  }
2346
2373
  // ── Public API ──────────────────────────────────────────────
@@ -2352,13 +2379,21 @@ var BundleManager = class {
2352
2379
  * provided for any bundle the user has not previously verified — without
2353
2380
  * it, a tampered bundle is `eval`'d as JS with full RN runtime + every
2354
2381
  * native capability the manifest declares.
2382
+ *
2383
+ * `grants` is what the MANIFEST grants this plugin (resolvePluginGrants) — the
2384
+ * only authority its device tools are registered under. Omitted (e.g. a
2385
+ * prewarm from the slim plugin list) → the bundle is loaded but its tools are
2386
+ * NOT registered: without the manifest there is nothing trusted to gate them
2387
+ * on, and registering with nothing would downgrade a registration the
2388
+ * manifest-driven tool loader already made.
2355
2389
  */
2356
- async load(pluginId, bundleUrl, version, expectedHash) {
2390
+ async load(pluginId, bundleUrl, version, expectedHash, grants) {
2357
2391
  const tenant = this.requireTenant();
2358
2392
  const epoch = this.epoch;
2359
2393
  const cached = this.getComponentFromMemory(pluginId, bundleUrl);
2360
2394
  if (cached) {
2361
- this.markMemoryHit(bundleUrl);
2395
+ this.markMemoryHit(bundleUrl, expectedHash);
2396
+ if (grants) await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
2362
2397
  return cached;
2363
2398
  }
2364
2399
  let modulePromise = this.inflight.get(bundleUrl);
@@ -2375,6 +2410,7 @@ var BundleManager = class {
2375
2410
  if (this.isStale(epoch, tenant)) {
2376
2411
  throw new Error("[BundleManager] tenant changed while loading \u2014 discarding (fail closed)");
2377
2412
  }
2413
+ if (grants) await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
2378
2414
  const component = this.getComponentFromMemory(pluginId, bundleUrl);
2379
2415
  if (!component) {
2380
2416
  throw new Error(
@@ -2389,8 +2425,14 @@ var BundleManager = class {
2389
2425
  * Same download, same integrity check, same evaluation as `load()`; the only
2390
2426
  * difference is that a bundle exporting tools and no `Component` is a success
2391
2427
  * here rather than "no component found". Returns the tool names registered.
2428
+ *
2429
+ * `grants` is REQUIRED: the tools are registered under `pluginId` with exactly
2430
+ * what the manifest grants (see load()), never what the bundle claims.
2392
2431
  */
2393
- async loadTools(pluginId, bundleUrl, version, expectedHash) {
2432
+ async loadTools(pluginId, bundleUrl, version, expectedHash, grants) {
2433
+ if (!grants) {
2434
+ throw new Error(`[BundleManager] loadTools(${pluginId}) needs the manifest's grants (resolvePluginGrants) \u2014 refusing to register tools on the bundle's own claim`);
2435
+ }
2394
2436
  const tenant = this.requireTenant();
2395
2437
  const epoch = this.epoch;
2396
2438
  let modulePromise = this.inflight.get(bundleUrl);
@@ -2407,6 +2449,7 @@ var BundleManager = class {
2407
2449
  if (this.isStale(epoch, tenant)) {
2408
2450
  throw new Error("[BundleManager] tenant changed while loading tools \u2014 discarding (fail closed)");
2409
2451
  }
2452
+ await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
2410
2453
  const names = listDeviceTools().filter((t) => t.pluginId === pluginId).map((t) => t.name);
2411
2454
  if (names.length === 0) {
2412
2455
  console.warn(
@@ -2432,7 +2475,7 @@ var BundleManager = class {
2432
2475
  const it = queue.shift();
2433
2476
  if (!it) return;
2434
2477
  try {
2435
- await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash);
2478
+ await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash, it.grants);
2436
2479
  okCount++;
2437
2480
  } catch (err) {
2438
2481
  failCount++;
@@ -2489,8 +2532,7 @@ var BundleManager = class {
2489
2532
  throw new Error("[BundleManager] tenant changed before load started \u2014 discarding (fail closed)");
2490
2533
  }
2491
2534
  if (this.modules.has(bundleUrl)) {
2492
- const prev = this.provenance.get(bundleUrl);
2493
- if (prev) this.provenance.set(bundleUrl, { ...prev, source: "memory", expected: expectedHash?.toLowerCase() ?? prev.expected });
2535
+ this.markMemoryHit(bundleUrl, expectedHash);
2494
2536
  return;
2495
2537
  }
2496
2538
  const storage = getBundleStorage();
@@ -2557,30 +2599,90 @@ var BundleManager = class {
2557
2599
  console.log(
2558
2600
  `[BundleManager] RUNNING ${bundleUrl} v${version} sha=${(verifiedHash || "none").slice(0, 12)} (${servedFrom}${expectedLc && verifiedHash && expectedLc !== verifiedHash ? `, EXPECTED ${expectedLc.slice(0, 12)} \u2014 STALE` : ""})`
2559
2601
  );
2560
- this.registerToolsFromModule(mod, bundleUrl);
2561
2602
  }
2562
2603
  /**
2563
- * Pick up every `tools` section in an evaluated bundle and put it in the
2564
- * device tool registry. Runs for UI, hybrid and tools-only bundles alike: a
2565
- * plugin that ships both gets its tools registered without its UI mounting.
2604
+ * Register THIS plugin's device tools under the TRUSTED declaration, then
2605
+ * record that declaration on the disk-cache entry. Runs on every trusted load
2606
+ * — memory hit included — so the grants in force are always the latest the
2607
+ * manifest gave (a revoked grant stops applying on the next load, not the next
2608
+ * app start). Callers run it only after their stale check.
2566
2609
  */
2567
- registerToolsFromModule(mod, bundleUrl) {
2610
+ async commitTools(pluginId, bundleUrl, grants, tenant, epoch) {
2611
+ const declaration = {
2612
+ pluginId,
2613
+ capabilities: grants.capabilities,
2614
+ stateDomains: grants.stateDomains
2615
+ };
2616
+ this.registerToolsFromModule(this.modules.get(bundleUrl), declaration);
2617
+ await this.recordTrustedDeclaration(bundleUrl, declaration, tenant, epoch);
2618
+ }
2619
+ /**
2620
+ * Put the plugin's own `tools` section in the device tool registry — under the
2621
+ * pluginId and grants the MANIFEST resolved, never the bundle's own `id`,
2622
+ * `capabilities` or `stateDomains`. A bundle is code the manifest authorised
2623
+ * by hash; what it may touch is the manifest's to say, same as its rendered
2624
+ * surface (resolvePluginRuntimeProps). Its own `id` would also let it register
2625
+ * as another plugin — including the host's platform tools, which a same-id
2626
+ * re-registration replaces.
2627
+ *
2628
+ * Only this plugin's export (the same lookup the component uses) is
2629
+ * registered. Another export's tools in the same bundle belong to another
2630
+ * plugin and register when THAT plugin's manifest loads it.
2631
+ */
2632
+ registerToolsFromModule(mod, { pluginId, capabilities, stateDomains }) {
2568
2633
  if (!mod) return;
2569
- for (const key of Object.keys(mod)) {
2570
- const val = mod[key];
2571
- if (!val || typeof val !== "object" || !Array.isArray(val.tools) || val.tools.length === 0) {
2572
- continue;
2573
- }
2574
- const pluginId = typeof val.id === "string" ? val.id : `${bundleUrl}#${key}`;
2575
- try {
2576
- registerDeviceTools(pluginId, val.tools, val.capabilities, val.stateDomains);
2577
- this.registeredToolPluginIds.add(pluginId);
2578
- } catch (err) {
2579
- console.warn(
2580
- `[BundleManager] tool registration failed for ${pluginId}:`,
2581
- err instanceof Error ? err.message : String(err)
2582
- );
2634
+ const own = this.findPluginExport(mod, pluginId, hasTools);
2635
+ const others = Object.keys(mod).filter((k) => hasTools(mod[k]) && mod[k] !== own);
2636
+ if (others.length > 0) {
2637
+ console.warn(
2638
+ `[BundleManager] ${pluginId}: not registering tools from export(s) ${others.join(", ")} \u2014 they are not this plugin's, and only their own manifest can grant them`
2639
+ );
2640
+ }
2641
+ if (!own) return;
2642
+ const ungranted = [
2643
+ ...(Array.isArray(own.stateDomains) ? own.stateDomains : []).filter((d) => !stateDomains?.includes(d)),
2644
+ ...Object.keys(own.capabilities || {}).filter((k) => own.capabilities[k] === true && !capabilities?.[k])
2645
+ ];
2646
+ if (ungranted.length > 0) {
2647
+ console.warn(
2648
+ `[BundleManager] ${pluginId}: bundle declares ${ungranted.join(", ")} but the manifest does not grant it \u2014 denied`
2649
+ );
2650
+ }
2651
+ try {
2652
+ registerDeviceTools(pluginId, own.tools, capabilities, stateDomains);
2653
+ this.registeredToolPluginIds.add(pluginId);
2654
+ } catch (err) {
2655
+ console.warn(
2656
+ `[BundleManager] tool registration failed for ${pluginId}:`,
2657
+ err instanceof Error ? err.message : String(err)
2658
+ );
2659
+ }
2660
+ }
2661
+ /**
2662
+ * Record the trusted declaration on this tenant's disk-cache entry for the
2663
+ * bytes RUNNING — the recovery path (tools/loader loadFromDiskCache) has no
2664
+ * manifest and re-registers with exactly this. Only onto an entry this tenant
2665
+ * owns whose hash is the running hash: the grant was issued for these bytes,
2666
+ * and those are the bytes recovery will evaluate. Best-effort — a lost write
2667
+ * leaves no `trusted`, which recovery treats as fail closed.
2668
+ */
2669
+ async recordTrustedDeclaration(bundleUrl, declaration, tenant, epoch) {
2670
+ const key = JSON.stringify(declaration);
2671
+ if (this.recordedDeclarations.get(bundleUrl) === key) return;
2672
+ try {
2673
+ const storage = getBundleStorage();
2674
+ const index = await storage.loadIndex();
2675
+ const entry = index[bundleUrl];
2676
+ const running = this.provenance.get(bundleUrl)?.hash;
2677
+ if (!entry || entry.tenant !== tenant || !running || entry.bundleHash?.toLowerCase() !== running) return;
2678
+ if (this.isStale(epoch, tenant)) return;
2679
+ if (JSON.stringify(entry.trusted) !== key) {
2680
+ index[bundleUrl] = { ...entry, trusted: declaration };
2681
+ await storage.saveIndex(index);
2583
2682
  }
2683
+ this.recordedDeclarations.set(bundleUrl, key);
2684
+ } catch (err) {
2685
+ console.warn("[BundleManager] could not record trusted declaration:", err);
2584
2686
  }
2585
2687
  }
2586
2688
  /**
@@ -2663,29 +2765,26 @@ var BundleManager = class {
2663
2765
  return materialized;
2664
2766
  }
2665
2767
  // ── Private: component extraction ───────────────────────────
2768
+ /** Find a plugin component in a loaded module. */
2769
+ getComponentFromMemory(pluginId, bundleUrl) {
2770
+ return this.findPluginExport(this.modules.get(bundleUrl), pluginId, hasComponent);
2771
+ }
2666
2772
  /**
2667
- * Find a plugin component in a loaded module.
2668
- * Handles: default export, direct key match, multi-plugin .id match.
2773
+ * Find THIS plugin's export in a loaded module — the one lookup both the
2774
+ * component and the device tools use, so a plugin's UI and its tools always
2775
+ * come from the same export. `accept` says what the caller needs from it.
2776
+ * Handles: direct key match, PascalCase key, default export, multi-plugin .id match.
2669
2777
  */
2670
- getComponentFromMemory(pluginId, bundleUrl) {
2671
- const mod = this.modules.get(bundleUrl);
2778
+ findPluginExport(mod, pluginId, accept) {
2672
2779
  if (!mod) return null;
2673
2780
  const shortName = pluginId.includes(":") ? pluginId.split(":").pop() : pluginId;
2674
- if (mod[pluginId] && typeof mod[pluginId].Component === "function") {
2675
- return mod[pluginId];
2676
- }
2781
+ if (accept(mod[pluginId])) return mod[pluginId];
2677
2782
  const pascal = shortName.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase());
2678
- if (mod[pascal] && typeof mod[pascal] === "object" && typeof mod[pascal].Component === "function") {
2679
- return mod[pascal];
2680
- }
2681
- if (mod.default && typeof mod.default === "object" && typeof mod.default.Component === "function") {
2682
- return mod.default;
2683
- }
2783
+ if (accept(mod[pascal])) return mod[pascal];
2784
+ if (accept(mod.default)) return mod.default;
2684
2785
  for (const key of Object.keys(mod)) {
2685
2786
  const val = mod[key];
2686
- if (val && typeof val === "object" && val.id === shortName && typeof val.Component === "function") {
2687
- return val;
2688
- }
2787
+ if (val && (val.id === pluginId || val.id === shortName) && accept(val)) return val;
2689
2788
  }
2690
2789
  return null;
2691
2790
  }
@@ -2700,6 +2799,107 @@ var BundleManager = class {
2700
2799
  };
2701
2800
  var bundleManager = new BundleManager();
2702
2801
 
2802
+ // src/voiceHost.ts
2803
+ var _provider2 = null;
2804
+ function setVoiceProvider(provider) {
2805
+ _provider2 = provider || null;
2806
+ }
2807
+ function implementedVoice() {
2808
+ return !!_provider2;
2809
+ }
2810
+ function voiceDenial(why) {
2811
+ const err = new Error(`voice: ${why}`);
2812
+ err.code = "RESTRICTED";
2813
+ return err;
2814
+ }
2815
+ function deniedVoice(why) {
2816
+ const fail = () => {
2817
+ throw voiceDenial(why);
2818
+ };
2819
+ return {
2820
+ async start() {
2821
+ return fail();
2822
+ },
2823
+ stop() {
2824
+ fail();
2825
+ },
2826
+ setMuted() {
2827
+ fail();
2828
+ },
2829
+ prompt() {
2830
+ fail();
2831
+ },
2832
+ observe() {
2833
+ fail();
2834
+ },
2835
+ getSnapshot() {
2836
+ return {
2837
+ state: "error",
2838
+ sessionId: null,
2839
+ muted: false,
2840
+ speaking: false,
2841
+ listening: false,
2842
+ userSpeaking: false,
2843
+ lastUserSpeechEndedAt: null,
2844
+ lastDeviceTool: null,
2845
+ authorizedSessionId: null,
2846
+ lastError: why
2847
+ };
2848
+ },
2849
+ subscribe() {
2850
+ return fail();
2851
+ },
2852
+ transcript() {
2853
+ return [];
2854
+ }
2855
+ };
2856
+ }
2857
+ function scopedVoice(provider, pluginId) {
2858
+ return {
2859
+ start: (opts) => provider.start(opts, pluginId),
2860
+ stop: () => provider.stop(pluginId),
2861
+ setMuted: (muted) => provider.setMuted(muted, pluginId),
2862
+ prompt: (input) => provider.prompt(input, pluginId),
2863
+ observe: (text) => provider.observe(text, pluginId),
2864
+ getSnapshot: () => provider.getSnapshot(pluginId),
2865
+ subscribe: (cb) => provider.subscribe(cb, pluginId),
2866
+ transcript: () => provider.transcript(pluginId)
2867
+ };
2868
+ }
2869
+ function gateVoice(declared, pluginId) {
2870
+ if (!declared) {
2871
+ return deniedVoice(
2872
+ "not declared in this plugin's capabilities. Add `capabilities: { voice: true }` to use it."
2873
+ );
2874
+ }
2875
+ if (!pluginId) {
2876
+ return deniedVoice(
2877
+ "no plugin identity \u2014 voice fails closed without a caller to scope the session to."
2878
+ );
2879
+ }
2880
+ return _provider2 ? scopedVoice(_provider2, pluginId) : deniedVoice(
2881
+ "no voice session in this build \u2014 the app has not mounted a VoiceSessionHost / called setVoiceProvider."
2882
+ );
2883
+ }
2884
+
2885
+ // src/pluginRuntime.ts
2886
+ function mergeCaps(decl) {
2887
+ if (!decl) return void 0;
2888
+ return { ...decl.native, ...decl.capabilities };
2889
+ }
2890
+ function resolvePluginGrants(src) {
2891
+ const trusted = src.manifest ?? src.plugin ?? null;
2892
+ const stateDomains = trusted?.stateDomains ? [...trusted.stateDomains] : void 0;
2893
+ return { capabilities: mergeCaps(trusted), stateDomains };
2894
+ }
2895
+ function resolvePluginRuntimeProps(src) {
2896
+ const { capabilities, stateDomains } = resolvePluginGrants(src);
2897
+ const native = gatePluginNative(getPluginNative(), capabilities, src.pluginId);
2898
+ const deviceState = gateDeviceState(stateDomains);
2899
+ const voice = gateVoice(!!capabilities?.voice, src.pluginId);
2900
+ return { device: native, native, deviceState, voice };
2901
+ }
2902
+
2703
2903
  // src/tools/loader.ts
2704
2904
  import { getTenant as getTenant3 } from "@ateam-ai-mobile/core";
2705
2905
  var inflight = null;
@@ -2740,7 +2940,8 @@ async function ensureDeviceToolsLoaded({ force = false } = {}) {
2740
2940
  m.id,
2741
2941
  bundleUrl,
2742
2942
  m.version || "1.0.0",
2743
- getNativeBundleHash(m) || void 0
2943
+ getNativeBundleHash(m) || void 0,
2944
+ resolvePluginGrants({ pluginId: m.id, manifest: m })
2744
2945
  );
2745
2946
  } catch (err) {
2746
2947
  console.warn(`[deviceTools] ${m.id} failed to load:`, err?.message || String(err));
@@ -2772,10 +2973,16 @@ async function loadFromDiskCache() {
2772
2973
  if (skipped > 0) {
2773
2974
  console.log(`[deviceTools] disk-cache recovery: ${mine.length} for "${tenant}", skipped ${skipped} not owned by it`);
2774
2975
  }
2775
- if (mine.length === 0) return [];
2776
- await Promise.all(mine.map(async (e) => {
2976
+ const declared = mine.filter((e) => !!e.trusted?.pluginId);
2977
+ if (declared.length < mine.length) {
2978
+ console.log(
2979
+ `[deviceTools] disk-cache recovery: skipped ${mine.length - declared.length} with no recorded manifest grant (fail closed)`
2980
+ );
2981
+ }
2982
+ if (declared.length === 0) return [];
2983
+ await Promise.all(declared.map(async (e) => {
2777
2984
  try {
2778
- await bundleManager.loadTools("cache:" + e.bundleUrl, e.bundleUrl, e.version, e.bundleHash);
2985
+ await bundleManager.loadTools(e.trusted.pluginId, e.bundleUrl, e.version, e.bundleHash, e.trusted);
2779
2986
  } catch {
2780
2987
  }
2781
2988
  }));
@@ -2839,6 +3046,11 @@ export {
2839
3046
  setDeviceStateProvider,
2840
3047
  gateDeviceState,
2841
3048
  implementedStateDomains,
3049
+ setVoiceProvider,
3050
+ implementedVoice,
3051
+ gateVoice,
3052
+ resolvePluginGrants,
3053
+ resolvePluginRuntimeProps,
2842
3054
  useNativeBridge,
2843
3055
  createNativeBridge,
2844
3056
  setDeviceToolContextProvider,
@@ -2870,4 +3082,4 @@ export {
2870
3082
  ensureDeviceToolsLoaded,
2871
3083
  resetDeviceToolLoader
2872
3084
  };
2873
- //# sourceMappingURL=chunk-PKDQLNBL.mjs.map
3085
+ //# sourceMappingURL=chunk-RXR6M623.mjs.map