@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.
- package/dist/{chunk-PKDQLNBL.mjs → chunk-RXR6M623.mjs} +269 -57
- package/dist/chunk-RXR6M623.mjs.map +1 -0
- package/dist/index.d.mts +356 -36
- package/dist/index.d.ts +356 -36
- package/dist/index.js +288 -161
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -101
- package/dist/index.mjs.map +1 -1
- package/dist/{loader-5LUVXUY2.mjs → loader-UUOPZQSI.mjs} +2 -2
- package/package.json +2 -2
- package/src/bundles/bundleManager.ts +161 -57
- package/src/bundles/storage.ts +19 -0
- package/src/deviceStateHost.ts +25 -0
- package/src/hooks/usePlugin.ts +13 -3
- package/src/host.ts +2 -0
- package/src/index.ts +11 -6
- package/src/pluginRuntime.ts +22 -11
- package/src/sdk/device-state.ts +100 -10
- package/src/sdk/index.ts +7 -0
- package/src/sdk/registration.ts +2 -1
- package/src/sdk/types.ts +149 -5
- package/src/sdk/voice.ts +38 -0
- package/src/tools/loader.ts +24 -6
- package/src/tools/registry.ts +12 -5
- package/src/voiceHost.ts +2 -0
- package/dist/chunk-PKDQLNBL.mjs.map +0 -1
- /package/dist/{loader-5LUVXUY2.mjs.map → loader-UUOPZQSI.mjs.map} +0 -0
|
@@ -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
|
-
|
|
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
|
-
...
|
|
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
|
-
/**
|
|
2338
|
-
*
|
|
2339
|
-
*
|
|
2340
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
2564
|
-
*
|
|
2565
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
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
|
|
2668
|
-
*
|
|
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
|
-
|
|
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]
|
|
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]
|
|
2679
|
-
|
|
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 &&
|
|
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
|
-
|
|
2776
|
-
|
|
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(
|
|
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-
|
|
3085
|
+
//# sourceMappingURL=chunk-RXR6M623.mjs.map
|