@ateam-ai-mobile/plugin-runtime 0.1.7 → 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/{chunk-PKDQLNBL.mjs → chunk-SICFDA5K.mjs} +388 -70
- package/dist/chunk-SICFDA5K.mjs.map +1 -0
- package/dist/index.d.mts +462 -46
- package/dist/index.d.ts +462 -46
- package/dist/index.js +429 -230
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20 -157
- package/dist/index.mjs.map +1 -1
- package/dist/{loader-5LUVXUY2.mjs → loader-MSZ4PVTO.mjs} +2 -2
- package/package.json +2 -2
- package/src/api/plugins.ts +15 -9
- package/src/bundles/bundleManager.ts +187 -64
- package/src/bundles/bundleModuleNames.ts +52 -0
- package/src/bundles/bundleUrl.ts +41 -0
- package/src/bundles/storage.ts +71 -0
- package/src/deviceStateHost.ts +25 -0
- package/src/hooks/usePlugin.ts +15 -4
- package/src/host.ts +2 -0
- package/src/index.ts +16 -37
- package/src/pluginRuntime.ts +22 -11
- package/src/sdk/CameraPreview.tsx +7 -0
- package/src/sdk/device-state.ts +120 -17
- package/src/sdk/index.ts +41 -0
- package/src/sdk/registration.ts +2 -1
- package/src/sdk/types.ts +162 -5
- package/src/sdk/voice.ts +82 -0
- package/src/tools/loader.ts +28 -8
- package/src/tools/registry.ts +14 -7
- package/src/voiceHost.ts +2 -0
- package/dist/chunk-PKDQLNBL.mjs.map +0 -1
- /package/dist/{loader-5LUVXUY2.mjs.map → loader-MSZ4PVTO.mjs.map} +0 -0
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({
|
|
@@ -395,7 +455,8 @@ function gatePluginNative(native, declared, pluginId) {
|
|
|
395
455
|
// every method refuses by name. Both are better than the `undefined` this
|
|
396
456
|
// used to be, which crashed with "cannot read properties of undefined".
|
|
397
457
|
bluetooth: caps.bluetooth ? native.bluetooth : blocked("bluetooth"),
|
|
398
|
-
vision: caps.vision ? native.vision : blocked("vision")
|
|
458
|
+
vision: caps.vision ? native.vision : blocked("vision"),
|
|
459
|
+
maps: caps.maps ? native.maps : blocked("maps")
|
|
399
460
|
// What this plugin DECLARED, carried on the object it already receives.
|
|
400
461
|
//
|
|
401
462
|
// It is how a COMPONENT can be capability-gated: <CameraPreview> is handed
|
|
@@ -455,7 +516,8 @@ var init_host = __esm({
|
|
|
455
516
|
scanner: unavailable("scanner"),
|
|
456
517
|
nfc: unavailable("nfc"),
|
|
457
518
|
bluetooth: unavailable("bluetooth"),
|
|
458
|
-
vision: unavailable("vision")
|
|
519
|
+
vision: unavailable("vision"),
|
|
520
|
+
maps: unavailable("maps")
|
|
459
521
|
};
|
|
460
522
|
_pluginNative = noopNative;
|
|
461
523
|
_cameraPreview = null;
|
|
@@ -664,6 +726,8 @@ __export(sdk_exports, {
|
|
|
664
726
|
TextInput: () => import_react_native.TextInput,
|
|
665
727
|
TouchableOpacity: () => import_react_native.TouchableOpacity,
|
|
666
728
|
View: () => import_react_native.View,
|
|
729
|
+
explainCapability: () => explainCapability,
|
|
730
|
+
isActionable: () => isActionable,
|
|
667
731
|
register: () => register,
|
|
668
732
|
registerService: () => registerService,
|
|
669
733
|
registerTools: () => registerTools,
|
|
@@ -678,6 +742,7 @@ var init_sdk = __esm({
|
|
|
678
742
|
init_registration();
|
|
679
743
|
init_registration();
|
|
680
744
|
init_voice();
|
|
745
|
+
init_device_state();
|
|
681
746
|
init_primitives();
|
|
682
747
|
init_components();
|
|
683
748
|
init_CameraPreview();
|
|
@@ -814,6 +879,19 @@ function gateDeviceState(declared) {
|
|
|
814
879
|
}
|
|
815
880
|
return out;
|
|
816
881
|
}
|
|
882
|
+
function withoutSubscribe(state) {
|
|
883
|
+
if (!state) return state;
|
|
884
|
+
const out = {};
|
|
885
|
+
for (const domain of ALL_DOMAINS) {
|
|
886
|
+
const d = state[domain];
|
|
887
|
+
out[domain] = d && Object.assign(Object.create(d), {
|
|
888
|
+
subscribe() {
|
|
889
|
+
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.");
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
return out;
|
|
894
|
+
}
|
|
817
895
|
function implementedStateDomains() {
|
|
818
896
|
return ALL_DOMAINS.filter((d) => !!_provider[d]);
|
|
819
897
|
}
|
|
@@ -839,6 +917,121 @@ var init_deviceStateHost = __esm({
|
|
|
839
917
|
}
|
|
840
918
|
});
|
|
841
919
|
|
|
920
|
+
// src/voiceHost.ts
|
|
921
|
+
function setVoiceProvider(provider) {
|
|
922
|
+
_provider2 = provider || null;
|
|
923
|
+
}
|
|
924
|
+
function implementedVoice() {
|
|
925
|
+
return !!_provider2;
|
|
926
|
+
}
|
|
927
|
+
function voiceDenial(why) {
|
|
928
|
+
const err = new Error(`voice: ${why}`);
|
|
929
|
+
err.code = "RESTRICTED";
|
|
930
|
+
return err;
|
|
931
|
+
}
|
|
932
|
+
function deniedVoice(why) {
|
|
933
|
+
const fail = () => {
|
|
934
|
+
throw voiceDenial(why);
|
|
935
|
+
};
|
|
936
|
+
return {
|
|
937
|
+
async start() {
|
|
938
|
+
return fail();
|
|
939
|
+
},
|
|
940
|
+
stop() {
|
|
941
|
+
fail();
|
|
942
|
+
},
|
|
943
|
+
setMuted() {
|
|
944
|
+
fail();
|
|
945
|
+
},
|
|
946
|
+
prompt() {
|
|
947
|
+
fail();
|
|
948
|
+
},
|
|
949
|
+
observe() {
|
|
950
|
+
fail();
|
|
951
|
+
},
|
|
952
|
+
getSnapshot() {
|
|
953
|
+
return {
|
|
954
|
+
state: "error",
|
|
955
|
+
sessionId: null,
|
|
956
|
+
muted: false,
|
|
957
|
+
speaking: false,
|
|
958
|
+
listening: false,
|
|
959
|
+
userSpeaking: false,
|
|
960
|
+
lastUserSpeechEndedAt: null,
|
|
961
|
+
lastDeviceTool: null,
|
|
962
|
+
authorizedSessionId: null,
|
|
963
|
+
lastError: why
|
|
964
|
+
};
|
|
965
|
+
},
|
|
966
|
+
subscribe() {
|
|
967
|
+
return fail();
|
|
968
|
+
},
|
|
969
|
+
transcript() {
|
|
970
|
+
return [];
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
function scopedVoice(provider, pluginId) {
|
|
975
|
+
return {
|
|
976
|
+
start: (opts) => provider.start(opts, pluginId),
|
|
977
|
+
stop: () => provider.stop(pluginId),
|
|
978
|
+
setMuted: (muted) => provider.setMuted(muted, pluginId),
|
|
979
|
+
prompt: (input) => provider.prompt(input, pluginId),
|
|
980
|
+
observe: (text) => provider.observe(text, pluginId),
|
|
981
|
+
getSnapshot: () => provider.getSnapshot(pluginId),
|
|
982
|
+
subscribe: (cb) => provider.subscribe(cb, pluginId),
|
|
983
|
+
transcript: () => provider.transcript(pluginId)
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function gateVoice(declared, pluginId) {
|
|
987
|
+
if (!declared) {
|
|
988
|
+
return deniedVoice(
|
|
989
|
+
"not declared in this plugin's capabilities. Add `capabilities: { voice: true }` to use it."
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
if (!pluginId) {
|
|
993
|
+
return deniedVoice(
|
|
994
|
+
"no plugin identity \u2014 voice fails closed without a caller to scope the session to."
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
return _provider2 ? scopedVoice(_provider2, pluginId) : deniedVoice(
|
|
998
|
+
"no voice session in this build \u2014 the app has not mounted a VoiceSessionHost / called setVoiceProvider."
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
var _provider2;
|
|
1002
|
+
var init_voiceHost = __esm({
|
|
1003
|
+
"src/voiceHost.ts"() {
|
|
1004
|
+
"use strict";
|
|
1005
|
+
_provider2 = null;
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
// src/pluginRuntime.ts
|
|
1010
|
+
function mergeCaps(decl) {
|
|
1011
|
+
if (!decl) return void 0;
|
|
1012
|
+
return { ...decl.native, ...decl.capabilities };
|
|
1013
|
+
}
|
|
1014
|
+
function resolvePluginGrants(src) {
|
|
1015
|
+
const trusted = src.manifest ?? src.plugin ?? null;
|
|
1016
|
+
const stateDomains = trusted?.stateDomains ? [...trusted.stateDomains] : void 0;
|
|
1017
|
+
return { capabilities: mergeCaps(trusted), stateDomains };
|
|
1018
|
+
}
|
|
1019
|
+
function resolvePluginRuntimeProps(src) {
|
|
1020
|
+
const { capabilities, stateDomains } = resolvePluginGrants(src);
|
|
1021
|
+
const native = gatePluginNative(getPluginNative(), capabilities, src.pluginId);
|
|
1022
|
+
const deviceState = gateDeviceState(stateDomains);
|
|
1023
|
+
const voice = gateVoice(!!capabilities?.voice, src.pluginId);
|
|
1024
|
+
return { device: native, native, deviceState, voice };
|
|
1025
|
+
}
|
|
1026
|
+
var init_pluginRuntime = __esm({
|
|
1027
|
+
"src/pluginRuntime.ts"() {
|
|
1028
|
+
"use strict";
|
|
1029
|
+
init_host();
|
|
1030
|
+
init_deviceStateHost();
|
|
1031
|
+
init_voiceHost();
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
|
|
842
1035
|
// src/bridge/native.ts
|
|
843
1036
|
function buildContext(pluginId, connectorId) {
|
|
844
1037
|
const user = (0, import_core2.getUser)();
|
|
@@ -1086,8 +1279,9 @@ function unregisterDeviceTools(pluginId) {
|
|
|
1086
1279
|
if (entry.pluginId === pluginId) registry.delete(name);
|
|
1087
1280
|
}
|
|
1088
1281
|
}
|
|
1089
|
-
function hasDeviceTool(name) {
|
|
1090
|
-
|
|
1282
|
+
function hasDeviceTool(name, pluginId) {
|
|
1283
|
+
const entry = registry.get(name);
|
|
1284
|
+
return !!entry && (pluginId === void 0 || entry.pluginId === pluginId);
|
|
1091
1285
|
}
|
|
1092
1286
|
function listDeviceTools() {
|
|
1093
1287
|
return Array.from(registry.entries()).map(([name, { pluginId, tool }]) => ({
|
|
@@ -1106,8 +1300,11 @@ async function callDeviceTool(name, args = {}, opts = {}) {
|
|
|
1106
1300
|
let timer;
|
|
1107
1301
|
try {
|
|
1108
1302
|
const build = contextProvider || defaultContext;
|
|
1303
|
+
const built = build(entry.pluginId, entry.capabilities, entry.stateDomains);
|
|
1109
1304
|
const ctx = {
|
|
1110
|
-
...
|
|
1305
|
+
...built,
|
|
1306
|
+
// Whoever built the context, a call cannot own a subscription.
|
|
1307
|
+
deviceState: withoutSubscribe(built.deviceState),
|
|
1111
1308
|
signal: opts.signal
|
|
1112
1309
|
};
|
|
1113
1310
|
const result = await Promise.race([
|
|
@@ -1144,6 +1341,26 @@ var init_registry = __esm({
|
|
|
1144
1341
|
}
|
|
1145
1342
|
});
|
|
1146
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
|
+
|
|
1147
1364
|
// src/api/plugins.ts
|
|
1148
1365
|
function supportsNative(manifest) {
|
|
1149
1366
|
const mode = manifest.render?.mode;
|
|
@@ -1155,12 +1372,9 @@ function getNativeComponentId(manifest) {
|
|
|
1155
1372
|
return null;
|
|
1156
1373
|
}
|
|
1157
1374
|
function getNativeBundleUrl(manifest) {
|
|
1158
|
-
const direct = manifest.bundleUrl;
|
|
1159
|
-
if (direct) return direct;
|
|
1160
1375
|
const render = manifest.render;
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
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;
|
|
1164
1378
|
}
|
|
1165
1379
|
function getNativeBundleHash(manifest) {
|
|
1166
1380
|
const direct = manifest.bundleHash;
|
|
@@ -1250,6 +1464,7 @@ var init_plugins = __esm({
|
|
|
1250
1464
|
"use strict";
|
|
1251
1465
|
import_core3 = require("@ateam-ai-mobile/core");
|
|
1252
1466
|
import_core4 = require("@ateam-ai-mobile/core");
|
|
1467
|
+
init_bundleUrl();
|
|
1253
1468
|
}
|
|
1254
1469
|
});
|
|
1255
1470
|
|
|
@@ -2078,6 +2293,20 @@ var init_SurfaceDispatcher = __esm({
|
|
|
2078
2293
|
}
|
|
2079
2294
|
});
|
|
2080
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
|
+
|
|
2081
2310
|
// src/utils/sha256.ts
|
|
2082
2311
|
function sha256Hex(message) {
|
|
2083
2312
|
const utf8 = unescape(encodeURIComponent(message));
|
|
@@ -2244,6 +2473,22 @@ function selectOwnedCacheEntries(entries, tenant) {
|
|
|
2244
2473
|
if (!tenant) return [];
|
|
2245
2474
|
return (entries || []).filter((e) => !!e && e.tenant === tenant);
|
|
2246
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
|
+
}
|
|
2247
2492
|
function setBundleStorage(storage) {
|
|
2248
2493
|
_storage = storage;
|
|
2249
2494
|
}
|
|
@@ -2254,6 +2499,7 @@ var MemoryBundleStorage, _storage;
|
|
|
2254
2499
|
var init_storage = __esm({
|
|
2255
2500
|
"src/bundles/storage.ts"() {
|
|
2256
2501
|
"use strict";
|
|
2502
|
+
init_bundleUrl();
|
|
2257
2503
|
MemoryBundleStorage = class {
|
|
2258
2504
|
constructor() {
|
|
2259
2505
|
this.index = {};
|
|
@@ -2286,7 +2532,10 @@ var init_storage = __esm({
|
|
|
2286
2532
|
});
|
|
2287
2533
|
|
|
2288
2534
|
// src/bundles/bundleManager.ts
|
|
2289
|
-
|
|
2535
|
+
function bundleKey(pluginId, bundleUrl) {
|
|
2536
|
+
return isPluginBundleUrl(bundleUrl, pluginId) ? pluginBundleUrl(pluginId) : bundleUrl;
|
|
2537
|
+
}
|
|
2538
|
+
var import_react9, import_react10, ReactNative, import_core9, ReactShim, PluginSDKShim, requireShim, CACHE_TTL, hasComponent, hasTools, BundleManager, bundleManager;
|
|
2290
2539
|
var init_bundleManager = __esm({
|
|
2291
2540
|
"src/bundles/bundleManager.ts"() {
|
|
2292
2541
|
"use strict";
|
|
@@ -2301,9 +2550,11 @@ var init_bundleManager = __esm({
|
|
|
2301
2550
|
init_store();
|
|
2302
2551
|
init_SurfaceDispatcher();
|
|
2303
2552
|
init_host();
|
|
2553
|
+
init_bundleModuleNames();
|
|
2304
2554
|
init_sha256();
|
|
2305
2555
|
init_integrity();
|
|
2306
2556
|
init_storage();
|
|
2557
|
+
init_bundleUrl();
|
|
2307
2558
|
init_registry();
|
|
2308
2559
|
ReactShim = {
|
|
2309
2560
|
__esModule: true,
|
|
@@ -2350,13 +2601,17 @@ var init_bundleManager = __esm({
|
|
|
2350
2601
|
requireShim = (name) => {
|
|
2351
2602
|
if (name === "react") return ReactShim;
|
|
2352
2603
|
if (name === "react-native") return ReactNative;
|
|
2353
|
-
if (name ===
|
|
2604
|
+
if (name === PLUGIN_SDK_MODULE) return PluginSDKShim;
|
|
2605
|
+
const refusal = sdkModuleRefusal(name);
|
|
2606
|
+
if (refusal) throw new Error(`[BundleManager] ${refusal}`);
|
|
2354
2607
|
const hostModule = resolveBundleModule(name);
|
|
2355
2608
|
if (hostModule !== void 0) return hostModule;
|
|
2356
2609
|
console.warn(`[BundleManager] Unknown module: ${name} \u2014 register it via registerBundleModules()`);
|
|
2357
2610
|
return {};
|
|
2358
2611
|
};
|
|
2359
2612
|
CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
|
|
2613
|
+
hasComponent = (val) => !!val && typeof val === "object" && typeof val.Component === "function";
|
|
2614
|
+
hasTools = (val) => !!val && typeof val === "object" && Array.isArray(val.tools) && val.tools.length > 0;
|
|
2360
2615
|
BundleManager = class {
|
|
2361
2616
|
constructor() {
|
|
2362
2617
|
/** In-memory module cache: bundleUrl → evaluated module.exports. Holds ONE
|
|
@@ -2387,6 +2642,9 @@ var init_bundleManager = __esm({
|
|
|
2387
2642
|
* of the previous tenant's tools.
|
|
2388
2643
|
*/
|
|
2389
2644
|
this.registeredToolPluginIds = /* @__PURE__ */ new Set();
|
|
2645
|
+
/** bundleUrl → the trusted declaration last recorded on its disk-cache entry
|
|
2646
|
+
* this session, so a memory hit with unchanged grants skips the index I/O. */
|
|
2647
|
+
this.recordedDeclarations = /* @__PURE__ */ new Map();
|
|
2390
2648
|
/**
|
|
2391
2649
|
* Enforce the in-memory tenant boundary. Call at the top of every public load
|
|
2392
2650
|
* path, BEFORE any cache read.
|
|
@@ -2432,6 +2690,7 @@ var init_bundleManager = __esm({
|
|
|
2432
2690
|
}
|
|
2433
2691
|
}
|
|
2434
2692
|
this.registeredToolPluginIds.clear();
|
|
2693
|
+
this.recordedDeclarations.clear();
|
|
2435
2694
|
this.modules.clear();
|
|
2436
2695
|
this.provenance.clear();
|
|
2437
2696
|
this.inflight.clear();
|
|
@@ -2443,13 +2702,15 @@ var init_bundleManager = __esm({
|
|
|
2443
2702
|
isStale(epoch, tenant) {
|
|
2444
2703
|
return this.epoch !== epoch || (0, import_core9.getTenant)() !== tenant;
|
|
2445
2704
|
}
|
|
2446
|
-
/**
|
|
2447
|
-
*
|
|
2448
|
-
*
|
|
2449
|
-
|
|
2705
|
+
/** Record a memory serve: the running bytes are the already-evaluated module,
|
|
2706
|
+
* and `expected` is refreshed from the manifest hash the caller JUST passed —
|
|
2707
|
+
* so after a redeploy the record reads hash !== expected (STALE) instead of
|
|
2708
|
+
* the old in-sync pair. The ONE place both memory paths (load()'s early return
|
|
2709
|
+
* and loadModule's) record reuse. No-op if nothing was recorded yet. */
|
|
2710
|
+
markMemoryHit(bundleUrl, expectedHash) {
|
|
2450
2711
|
const prev = this.provenance.get(bundleUrl);
|
|
2451
|
-
if (prev
|
|
2452
|
-
this.provenance.set(bundleUrl, { ...prev, source: "memory" });
|
|
2712
|
+
if (prev) {
|
|
2713
|
+
this.provenance.set(bundleUrl, { ...prev, source: "memory", expected: expectedHash?.toLowerCase() ?? prev.expected });
|
|
2453
2714
|
}
|
|
2454
2715
|
}
|
|
2455
2716
|
// ── Public API ──────────────────────────────────────────────
|
|
@@ -2461,13 +2722,22 @@ var init_bundleManager = __esm({
|
|
|
2461
2722
|
* provided for any bundle the user has not previously verified — without
|
|
2462
2723
|
* it, a tampered bundle is `eval`'d as JS with full RN runtime + every
|
|
2463
2724
|
* native capability the manifest declares.
|
|
2725
|
+
*
|
|
2726
|
+
* `grants` is what the MANIFEST grants this plugin (resolvePluginGrants) — the
|
|
2727
|
+
* only authority its device tools are registered under. Omitted (e.g. a
|
|
2728
|
+
* prewarm from the slim plugin list) → the bundle is loaded but its tools are
|
|
2729
|
+
* NOT registered: without the manifest there is nothing trusted to gate them
|
|
2730
|
+
* on, and registering with nothing would downgrade a registration the
|
|
2731
|
+
* manifest-driven tool loader already made.
|
|
2464
2732
|
*/
|
|
2465
|
-
async load(pluginId, bundleUrl, version, expectedHash) {
|
|
2733
|
+
async load(pluginId, bundleUrl, version, expectedHash, grants) {
|
|
2734
|
+
bundleUrl = bundleKey(pluginId, bundleUrl);
|
|
2466
2735
|
const tenant = this.requireTenant();
|
|
2467
2736
|
const epoch = this.epoch;
|
|
2468
2737
|
const cached = this.getComponentFromMemory(pluginId, bundleUrl);
|
|
2469
2738
|
if (cached) {
|
|
2470
|
-
this.markMemoryHit(bundleUrl);
|
|
2739
|
+
this.markMemoryHit(bundleUrl, expectedHash);
|
|
2740
|
+
if (grants) await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
|
|
2471
2741
|
return cached;
|
|
2472
2742
|
}
|
|
2473
2743
|
let modulePromise = this.inflight.get(bundleUrl);
|
|
@@ -2484,6 +2754,7 @@ var init_bundleManager = __esm({
|
|
|
2484
2754
|
if (this.isStale(epoch, tenant)) {
|
|
2485
2755
|
throw new Error("[BundleManager] tenant changed while loading \u2014 discarding (fail closed)");
|
|
2486
2756
|
}
|
|
2757
|
+
if (grants) await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
|
|
2487
2758
|
const component = this.getComponentFromMemory(pluginId, bundleUrl);
|
|
2488
2759
|
if (!component) {
|
|
2489
2760
|
throw new Error(
|
|
@@ -2498,8 +2769,15 @@ var init_bundleManager = __esm({
|
|
|
2498
2769
|
* Same download, same integrity check, same evaluation as `load()`; the only
|
|
2499
2770
|
* difference is that a bundle exporting tools and no `Component` is a success
|
|
2500
2771
|
* here rather than "no component found". Returns the tool names registered.
|
|
2772
|
+
*
|
|
2773
|
+
* `grants` is REQUIRED: the tools are registered under `pluginId` with exactly
|
|
2774
|
+
* what the manifest grants (see load()), never what the bundle claims.
|
|
2501
2775
|
*/
|
|
2502
|
-
async loadTools(pluginId, bundleUrl, version, expectedHash) {
|
|
2776
|
+
async loadTools(pluginId, bundleUrl, version, expectedHash, grants) {
|
|
2777
|
+
if (!grants) {
|
|
2778
|
+
throw new Error(`[BundleManager] loadTools(${pluginId}) needs the manifest's grants (resolvePluginGrants) \u2014 refusing to register tools on the bundle's own claim`);
|
|
2779
|
+
}
|
|
2780
|
+
bundleUrl = bundleKey(pluginId, bundleUrl);
|
|
2503
2781
|
const tenant = this.requireTenant();
|
|
2504
2782
|
const epoch = this.epoch;
|
|
2505
2783
|
let modulePromise = this.inflight.get(bundleUrl);
|
|
@@ -2516,6 +2794,7 @@ var init_bundleManager = __esm({
|
|
|
2516
2794
|
if (this.isStale(epoch, tenant)) {
|
|
2517
2795
|
throw new Error("[BundleManager] tenant changed while loading tools \u2014 discarding (fail closed)");
|
|
2518
2796
|
}
|
|
2797
|
+
await this.commitTools(pluginId, bundleUrl, grants, tenant, epoch);
|
|
2519
2798
|
const names = listDeviceTools().filter((t) => t.pluginId === pluginId).map((t) => t.name);
|
|
2520
2799
|
if (names.length === 0) {
|
|
2521
2800
|
console.warn(
|
|
@@ -2541,7 +2820,7 @@ var init_bundleManager = __esm({
|
|
|
2541
2820
|
const it = queue.shift();
|
|
2542
2821
|
if (!it) return;
|
|
2543
2822
|
try {
|
|
2544
|
-
await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash);
|
|
2823
|
+
await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash, it.grants);
|
|
2545
2824
|
okCount++;
|
|
2546
2825
|
} catch (err) {
|
|
2547
2826
|
failCount++;
|
|
@@ -2569,11 +2848,12 @@ var init_bundleManager = __esm({
|
|
|
2569
2848
|
*/
|
|
2570
2849
|
async clearPlugin(pluginId) {
|
|
2571
2850
|
const storage = getBundleStorage();
|
|
2572
|
-
const index = await storage
|
|
2573
|
-
const
|
|
2851
|
+
const index = await loadCacheIndex(storage);
|
|
2852
|
+
const bundleUrl = pluginBundleUrl(pluginId);
|
|
2853
|
+
const entry = index[bundleUrl];
|
|
2574
2854
|
if (entry) {
|
|
2575
|
-
this.modules.delete(
|
|
2576
|
-
delete index[
|
|
2855
|
+
this.modules.delete(bundleUrl);
|
|
2856
|
+
delete index[bundleUrl];
|
|
2577
2857
|
await storage.saveIndex(index);
|
|
2578
2858
|
try {
|
|
2579
2859
|
await storage.deleteBundle(entry.localPath);
|
|
@@ -2598,12 +2878,11 @@ var init_bundleManager = __esm({
|
|
|
2598
2878
|
throw new Error("[BundleManager] tenant changed before load started \u2014 discarding (fail closed)");
|
|
2599
2879
|
}
|
|
2600
2880
|
if (this.modules.has(bundleUrl)) {
|
|
2601
|
-
|
|
2602
|
-
if (prev) this.provenance.set(bundleUrl, { ...prev, source: "memory", expected: expectedHash?.toLowerCase() ?? prev.expected });
|
|
2881
|
+
this.markMemoryHit(bundleUrl, expectedHash);
|
|
2603
2882
|
return;
|
|
2604
2883
|
}
|
|
2605
2884
|
const storage = getBundleStorage();
|
|
2606
|
-
const index = await storage
|
|
2885
|
+
const index = await loadCacheIndex(storage);
|
|
2607
2886
|
const expectedLc = expectedHash ? expectedHash.toLowerCase() : null;
|
|
2608
2887
|
let content = null;
|
|
2609
2888
|
let verifiedHash = null;
|
|
@@ -2666,30 +2945,90 @@ var init_bundleManager = __esm({
|
|
|
2666
2945
|
console.log(
|
|
2667
2946
|
`[BundleManager] RUNNING ${bundleUrl} v${version} sha=${(verifiedHash || "none").slice(0, 12)} (${servedFrom}${expectedLc && verifiedHash && expectedLc !== verifiedHash ? `, EXPECTED ${expectedLc.slice(0, 12)} \u2014 STALE` : ""})`
|
|
2668
2947
|
);
|
|
2669
|
-
this.registerToolsFromModule(mod, bundleUrl);
|
|
2670
2948
|
}
|
|
2671
2949
|
/**
|
|
2672
|
-
*
|
|
2673
|
-
*
|
|
2674
|
-
*
|
|
2950
|
+
* Register THIS plugin's device tools under the TRUSTED declaration, then
|
|
2951
|
+
* record that declaration on the disk-cache entry. Runs on every trusted load
|
|
2952
|
+
* — memory hit included — so the grants in force are always the latest the
|
|
2953
|
+
* manifest gave (a revoked grant stops applying on the next load, not the next
|
|
2954
|
+
* app start). Callers run it only after their stale check.
|
|
2675
2955
|
*/
|
|
2676
|
-
|
|
2956
|
+
async commitTools(pluginId, bundleUrl, grants, tenant, epoch) {
|
|
2957
|
+
const declaration = {
|
|
2958
|
+
pluginId,
|
|
2959
|
+
capabilities: grants.capabilities,
|
|
2960
|
+
stateDomains: grants.stateDomains
|
|
2961
|
+
};
|
|
2962
|
+
this.registerToolsFromModule(this.modules.get(bundleUrl), declaration);
|
|
2963
|
+
await this.recordTrustedDeclaration(bundleUrl, declaration, tenant, epoch);
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Put the plugin's own `tools` section in the device tool registry — under the
|
|
2967
|
+
* pluginId and grants the MANIFEST resolved, never the bundle's own `id`,
|
|
2968
|
+
* `capabilities` or `stateDomains`. A bundle is code the manifest authorised
|
|
2969
|
+
* by hash; what it may touch is the manifest's to say, same as its rendered
|
|
2970
|
+
* surface (resolvePluginRuntimeProps). Its own `id` would also let it register
|
|
2971
|
+
* as another plugin — including the host's platform tools, which a same-id
|
|
2972
|
+
* re-registration replaces.
|
|
2973
|
+
*
|
|
2974
|
+
* Only this plugin's export (the same lookup the component uses) is
|
|
2975
|
+
* registered. Another export's tools in the same bundle belong to another
|
|
2976
|
+
* plugin and register when THAT plugin's manifest loads it.
|
|
2977
|
+
*/
|
|
2978
|
+
registerToolsFromModule(mod, { pluginId, capabilities, stateDomains }) {
|
|
2677
2979
|
if (!mod) return;
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2980
|
+
const own = this.findPluginExport(mod, pluginId, hasTools);
|
|
2981
|
+
const others = Object.keys(mod).filter((k) => hasTools(mod[k]) && mod[k] !== own);
|
|
2982
|
+
if (others.length > 0) {
|
|
2983
|
+
console.warn(
|
|
2984
|
+
`[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`
|
|
2985
|
+
);
|
|
2986
|
+
}
|
|
2987
|
+
if (!own) return;
|
|
2988
|
+
const ungranted = [
|
|
2989
|
+
...(Array.isArray(own.stateDomains) ? own.stateDomains : []).filter((d) => !stateDomains?.includes(d)),
|
|
2990
|
+
...Object.keys(own.capabilities || {}).filter((k) => own.capabilities[k] === true && !capabilities?.[k])
|
|
2991
|
+
];
|
|
2992
|
+
if (ungranted.length > 0) {
|
|
2993
|
+
console.warn(
|
|
2994
|
+
`[BundleManager] ${pluginId}: bundle declares ${ungranted.join(", ")} but the manifest does not grant it \u2014 denied`
|
|
2995
|
+
);
|
|
2996
|
+
}
|
|
2997
|
+
try {
|
|
2998
|
+
registerDeviceTools(pluginId, own.tools, capabilities, stateDomains);
|
|
2999
|
+
this.registeredToolPluginIds.add(pluginId);
|
|
3000
|
+
} catch (err) {
|
|
3001
|
+
console.warn(
|
|
3002
|
+
`[BundleManager] tool registration failed for ${pluginId}:`,
|
|
3003
|
+
err instanceof Error ? err.message : String(err)
|
|
3004
|
+
);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Record the trusted declaration on this tenant's disk-cache entry for the
|
|
3009
|
+
* bytes RUNNING — the recovery path (tools/loader loadFromDiskCache) has no
|
|
3010
|
+
* manifest and re-registers with exactly this. Only onto an entry this tenant
|
|
3011
|
+
* owns whose hash is the running hash: the grant was issued for these bytes,
|
|
3012
|
+
* and those are the bytes recovery will evaluate. Best-effort — a lost write
|
|
3013
|
+
* leaves no `trusted`, which recovery treats as fail closed.
|
|
3014
|
+
*/
|
|
3015
|
+
async recordTrustedDeclaration(bundleUrl, declaration, tenant, epoch) {
|
|
3016
|
+
const key = JSON.stringify(declaration);
|
|
3017
|
+
if (this.recordedDeclarations.get(bundleUrl) === key) return;
|
|
3018
|
+
try {
|
|
3019
|
+
const storage = getBundleStorage();
|
|
3020
|
+
const index = await loadCacheIndex(storage);
|
|
3021
|
+
const entry = index[bundleUrl];
|
|
3022
|
+
const running = this.provenance.get(bundleUrl)?.hash;
|
|
3023
|
+
if (!entry || entry.tenant !== tenant || !running || entry.bundleHash?.toLowerCase() !== running) return;
|
|
3024
|
+
if (this.isStale(epoch, tenant)) return;
|
|
3025
|
+
if (JSON.stringify(entry.trusted) !== key) {
|
|
3026
|
+
index[bundleUrl] = { ...entry, trusted: declaration };
|
|
3027
|
+
await storage.saveIndex(index);
|
|
2692
3028
|
}
|
|
3029
|
+
this.recordedDeclarations.set(bundleUrl, key);
|
|
3030
|
+
} catch (err) {
|
|
3031
|
+
console.warn("[BundleManager] could not record trusted declaration:", err);
|
|
2693
3032
|
}
|
|
2694
3033
|
}
|
|
2695
3034
|
/**
|
|
@@ -2772,29 +3111,26 @@ var init_bundleManager = __esm({
|
|
|
2772
3111
|
return materialized;
|
|
2773
3112
|
}
|
|
2774
3113
|
// ── Private: component extraction ───────────────────────────
|
|
3114
|
+
/** Find a plugin component in a loaded module. */
|
|
3115
|
+
getComponentFromMemory(pluginId, bundleUrl) {
|
|
3116
|
+
return this.findPluginExport(this.modules.get(bundleUrl), pluginId, hasComponent);
|
|
3117
|
+
}
|
|
2775
3118
|
/**
|
|
2776
|
-
* Find
|
|
2777
|
-
*
|
|
3119
|
+
* Find THIS plugin's export in a loaded module — the one lookup both the
|
|
3120
|
+
* component and the device tools use, so a plugin's UI and its tools always
|
|
3121
|
+
* come from the same export. `accept` says what the caller needs from it.
|
|
3122
|
+
* Handles: direct key match, PascalCase key, default export, multi-plugin .id match.
|
|
2778
3123
|
*/
|
|
2779
|
-
|
|
2780
|
-
const mod = this.modules.get(bundleUrl);
|
|
3124
|
+
findPluginExport(mod, pluginId, accept) {
|
|
2781
3125
|
if (!mod) return null;
|
|
2782
3126
|
const shortName = pluginId.includes(":") ? pluginId.split(":").pop() : pluginId;
|
|
2783
|
-
if (mod[pluginId]
|
|
2784
|
-
return mod[pluginId];
|
|
2785
|
-
}
|
|
3127
|
+
if (accept(mod[pluginId])) return mod[pluginId];
|
|
2786
3128
|
const pascal = shortName.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase());
|
|
2787
|
-
if (mod[pascal]
|
|
2788
|
-
|
|
2789
|
-
}
|
|
2790
|
-
if (mod.default && typeof mod.default === "object" && typeof mod.default.Component === "function") {
|
|
2791
|
-
return mod.default;
|
|
2792
|
-
}
|
|
3129
|
+
if (accept(mod[pascal])) return mod[pascal];
|
|
3130
|
+
if (accept(mod.default)) return mod.default;
|
|
2793
3131
|
for (const key of Object.keys(mod)) {
|
|
2794
3132
|
const val = mod[key];
|
|
2795
|
-
if (val &&
|
|
2796
|
-
return val;
|
|
2797
|
-
}
|
|
3133
|
+
if (val && (val.id === pluginId || val.id === shortName) && accept(val)) return val;
|
|
2798
3134
|
}
|
|
2799
3135
|
return null;
|
|
2800
3136
|
}
|
|
@@ -2853,7 +3189,8 @@ async function ensureDeviceToolsLoaded({ force = false } = {}) {
|
|
|
2853
3189
|
m.id,
|
|
2854
3190
|
bundleUrl,
|
|
2855
3191
|
m.version || "1.0.0",
|
|
2856
|
-
getNativeBundleHash(m) || void 0
|
|
3192
|
+
getNativeBundleHash(m) || void 0,
|
|
3193
|
+
resolvePluginGrants({ pluginId: m.id, manifest: m })
|
|
2857
3194
|
);
|
|
2858
3195
|
} catch (err) {
|
|
2859
3196
|
console.warn(`[deviceTools] ${m.id} failed to load:`, err?.message || String(err));
|
|
@@ -2872,7 +3209,7 @@ async function ensureDeviceToolsLoaded({ force = false } = {}) {
|
|
|
2872
3209
|
}
|
|
2873
3210
|
async function loadFromDiskCache() {
|
|
2874
3211
|
try {
|
|
2875
|
-
const index = await getBundleStorage()
|
|
3212
|
+
const index = await loadCacheIndex(getBundleStorage());
|
|
2876
3213
|
const entries = Object.values(index || {});
|
|
2877
3214
|
if (entries.length === 0) return [];
|
|
2878
3215
|
const tenant = (0, import_core10.getTenant)();
|
|
@@ -2885,10 +3222,16 @@ async function loadFromDiskCache() {
|
|
|
2885
3222
|
if (skipped > 0) {
|
|
2886
3223
|
console.log(`[deviceTools] disk-cache recovery: ${mine.length} for "${tenant}", skipped ${skipped} not owned by it`);
|
|
2887
3224
|
}
|
|
2888
|
-
|
|
2889
|
-
|
|
3225
|
+
const declared = mine.filter((e) => !!e.trusted?.pluginId);
|
|
3226
|
+
if (declared.length < mine.length) {
|
|
3227
|
+
console.log(
|
|
3228
|
+
`[deviceTools] disk-cache recovery: skipped ${mine.length - declared.length} with no recorded manifest grant (fail closed)`
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
if (declared.length === 0) return [];
|
|
3232
|
+
await Promise.all(declared.map(async (e) => {
|
|
2890
3233
|
try {
|
|
2891
|
-
await bundleManager.loadTools(
|
|
3234
|
+
await bundleManager.loadTools(e.trusted.pluginId, e.bundleUrl, e.version, e.bundleHash, e.trusted);
|
|
2892
3235
|
} catch {
|
|
2893
3236
|
}
|
|
2894
3237
|
}));
|
|
@@ -2909,6 +3252,7 @@ var init_loader = __esm({
|
|
|
2909
3252
|
init_bundleManager();
|
|
2910
3253
|
init_registry();
|
|
2911
3254
|
init_storage();
|
|
3255
|
+
init_pluginRuntime();
|
|
2912
3256
|
import_core10 = require("@ateam-ai-mobile/core");
|
|
2913
3257
|
inflight = null;
|
|
2914
3258
|
loadedOnce = false;
|
|
@@ -2983,6 +3327,7 @@ __export(index_exports, {
|
|
|
2983
3327
|
listPlugins: () => listPlugins,
|
|
2984
3328
|
nativePluginRegistry: () => nativePluginRegistry,
|
|
2985
3329
|
neverObserved: () => neverObserved,
|
|
3330
|
+
pluginBundleUrl: () => pluginBundleUrl,
|
|
2986
3331
|
register: () => register,
|
|
2987
3332
|
registerBundleModules: () => registerBundleModules,
|
|
2988
3333
|
registerDeviceTools: () => registerDeviceTools,
|
|
@@ -2991,6 +3336,7 @@ __export(index_exports, {
|
|
|
2991
3336
|
registerService: () => registerService,
|
|
2992
3337
|
registerTools: () => registerTools,
|
|
2993
3338
|
resetDeviceToolLoader: () => resetDeviceToolLoader,
|
|
3339
|
+
resolvePluginGrants: () => resolvePluginGrants,
|
|
2994
3340
|
resolvePluginRuntimeProps: () => resolvePluginRuntimeProps,
|
|
2995
3341
|
resolvePluginUrl: () => resolvePluginUrl,
|
|
2996
3342
|
setBundleStorage: () => setBundleStorage,
|
|
@@ -3016,166 +3362,12 @@ __export(index_exports, {
|
|
|
3016
3362
|
});
|
|
3017
3363
|
module.exports = __toCommonJS(index_exports);
|
|
3018
3364
|
init_sdk();
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
var device_state_exports = {};
|
|
3022
|
-
__export(device_state_exports, {
|
|
3023
|
-
DeviceBridgeError: () => DeviceBridgeError,
|
|
3024
|
-
explainCapability: () => explainCapability,
|
|
3025
|
-
isActionable: () => isActionable,
|
|
3026
|
-
neverObserved: () => neverObserved
|
|
3027
|
-
});
|
|
3028
|
-
var DeviceBridgeError = class extends Error {
|
|
3029
|
-
constructor(code, message, opts) {
|
|
3030
|
-
super(message);
|
|
3031
|
-
this.name = "DeviceBridgeError";
|
|
3032
|
-
this.code = code;
|
|
3033
|
-
this.domain = opts?.domain;
|
|
3034
|
-
this.recoverable = opts?.recoverable;
|
|
3035
|
-
}
|
|
3036
|
-
};
|
|
3037
|
-
var CONFIDENCE_ORDER = {
|
|
3038
|
-
unknown: 0,
|
|
3039
|
-
low: 1,
|
|
3040
|
-
medium: 2,
|
|
3041
|
-
high: 3
|
|
3042
|
-
};
|
|
3043
|
-
function isActionable(quality, budget) {
|
|
3044
|
-
const { freshness, confidence } = quality;
|
|
3045
|
-
if (freshness.state === "unknown" || freshness.state === "stale") return false;
|
|
3046
|
-
if (freshness.ageMs === null || freshness.ageMs > budget.maxAgeMs) return false;
|
|
3047
|
-
if (budget.minConfidence) {
|
|
3048
|
-
const have = CONFIDENCE_ORDER[confidence?.level ?? "unknown"];
|
|
3049
|
-
if (have < CONFIDENCE_ORDER[budget.minConfidence]) return false;
|
|
3050
|
-
}
|
|
3051
|
-
return true;
|
|
3052
|
-
}
|
|
3053
|
-
function neverObserved() {
|
|
3054
|
-
return { state: "unknown", observedAt: null, ageMs: null };
|
|
3055
|
-
}
|
|
3056
|
-
function explainCapability(status) {
|
|
3057
|
-
if (status.support === "unsupported") return "this device does not have it";
|
|
3058
|
-
if (status.permission === "denied") return "permission was denied";
|
|
3059
|
-
if (status.permission === "restricted") return "permission is restricted";
|
|
3060
|
-
if (status.permission === "notDetermined") return "permission has not been asked for yet";
|
|
3061
|
-
if (status.permission === "limited") return "only limited access was granted";
|
|
3062
|
-
if (status.restriction === "policyRestricted") return "this plugin did not declare it";
|
|
3063
|
-
if (status.restriction === "backgroundRestricted") return "the app is in the background";
|
|
3064
|
-
if (status.restriction === "lowPowerRestricted") return "the device is in low-power mode";
|
|
3065
|
-
if (status.availability === "busy") return "it is in use by something else";
|
|
3066
|
-
if (status.availability === "temporarilyUnavailable") return "it is temporarily unavailable";
|
|
3067
|
-
if (status.availability === "unavailable") return "it is unavailable";
|
|
3068
|
-
if (status.support === "unknown" || status.availability === "unknown") {
|
|
3069
|
-
return "its state could not be determined";
|
|
3070
|
-
}
|
|
3071
|
-
return null;
|
|
3072
|
-
}
|
|
3073
|
-
|
|
3074
|
-
// src/index.ts
|
|
3365
|
+
init_device_state();
|
|
3366
|
+
init_device_state();
|
|
3075
3367
|
init_deviceStateHost();
|
|
3076
|
-
|
|
3077
|
-
// src/voiceHost.ts
|
|
3078
|
-
var _provider2 = null;
|
|
3079
|
-
function setVoiceProvider(provider) {
|
|
3080
|
-
_provider2 = provider || null;
|
|
3081
|
-
}
|
|
3082
|
-
function implementedVoice() {
|
|
3083
|
-
return !!_provider2;
|
|
3084
|
-
}
|
|
3085
|
-
function voiceDenial(why) {
|
|
3086
|
-
const err = new Error(`voice: ${why}`);
|
|
3087
|
-
err.code = "RESTRICTED";
|
|
3088
|
-
return err;
|
|
3089
|
-
}
|
|
3090
|
-
function deniedVoice(why) {
|
|
3091
|
-
const fail = () => {
|
|
3092
|
-
throw voiceDenial(why);
|
|
3093
|
-
};
|
|
3094
|
-
return {
|
|
3095
|
-
async start() {
|
|
3096
|
-
return fail();
|
|
3097
|
-
},
|
|
3098
|
-
stop() {
|
|
3099
|
-
fail();
|
|
3100
|
-
},
|
|
3101
|
-
setMuted() {
|
|
3102
|
-
fail();
|
|
3103
|
-
},
|
|
3104
|
-
prompt() {
|
|
3105
|
-
fail();
|
|
3106
|
-
},
|
|
3107
|
-
observe() {
|
|
3108
|
-
fail();
|
|
3109
|
-
},
|
|
3110
|
-
getSnapshot() {
|
|
3111
|
-
return {
|
|
3112
|
-
state: "error",
|
|
3113
|
-
sessionId: null,
|
|
3114
|
-
muted: false,
|
|
3115
|
-
speaking: false,
|
|
3116
|
-
listening: false,
|
|
3117
|
-
userSpeaking: false,
|
|
3118
|
-
lastUserSpeechEndedAt: null,
|
|
3119
|
-
lastError: why
|
|
3120
|
-
};
|
|
3121
|
-
},
|
|
3122
|
-
subscribe() {
|
|
3123
|
-
return fail();
|
|
3124
|
-
},
|
|
3125
|
-
transcript() {
|
|
3126
|
-
return [];
|
|
3127
|
-
}
|
|
3128
|
-
};
|
|
3129
|
-
}
|
|
3130
|
-
function scopedVoice(provider, pluginId) {
|
|
3131
|
-
return {
|
|
3132
|
-
start: (opts) => provider.start(opts, pluginId),
|
|
3133
|
-
stop: () => provider.stop(pluginId),
|
|
3134
|
-
setMuted: (muted) => provider.setMuted(muted, pluginId),
|
|
3135
|
-
prompt: (input) => provider.prompt(input, pluginId),
|
|
3136
|
-
observe: (text) => provider.observe(text, pluginId),
|
|
3137
|
-
getSnapshot: () => provider.getSnapshot(pluginId),
|
|
3138
|
-
subscribe: (cb) => provider.subscribe(cb, pluginId),
|
|
3139
|
-
transcript: () => provider.transcript(pluginId)
|
|
3140
|
-
};
|
|
3141
|
-
}
|
|
3142
|
-
function gateVoice(declared, pluginId) {
|
|
3143
|
-
if (!declared) {
|
|
3144
|
-
return deniedVoice(
|
|
3145
|
-
"not declared in this plugin's capabilities. Add `capabilities: { voice: true }` to use it."
|
|
3146
|
-
);
|
|
3147
|
-
}
|
|
3148
|
-
if (!pluginId) {
|
|
3149
|
-
return deniedVoice(
|
|
3150
|
-
"no plugin identity \u2014 voice fails closed without a caller to scope the session to."
|
|
3151
|
-
);
|
|
3152
|
-
}
|
|
3153
|
-
return _provider2 ? scopedVoice(_provider2, pluginId) : deniedVoice(
|
|
3154
|
-
"no voice session in this build \u2014 the app has not mounted a VoiceSessionHost / called setVoiceProvider."
|
|
3155
|
-
);
|
|
3156
|
-
}
|
|
3157
|
-
|
|
3158
|
-
// src/index.ts
|
|
3368
|
+
init_voiceHost();
|
|
3159
3369
|
init_host();
|
|
3160
|
-
|
|
3161
|
-
// src/pluginRuntime.ts
|
|
3162
|
-
init_host();
|
|
3163
|
-
init_deviceStateHost();
|
|
3164
|
-
function mergeCaps(decl) {
|
|
3165
|
-
if (!decl) return void 0;
|
|
3166
|
-
return { ...decl.native, ...decl.capabilities };
|
|
3167
|
-
}
|
|
3168
|
-
function resolvePluginRuntimeProps(src) {
|
|
3169
|
-
const trusted = src.manifest ?? src.plugin ?? null;
|
|
3170
|
-
const capabilities = mergeCaps(trusted);
|
|
3171
|
-
const stateDomains = trusted?.stateDomains;
|
|
3172
|
-
const native = gatePluginNative(getPluginNative(), capabilities, src.pluginId);
|
|
3173
|
-
const deviceState = gateDeviceState(stateDomains);
|
|
3174
|
-
const voice = gateVoice(!!capabilities?.voice, src.pluginId);
|
|
3175
|
-
return { device: native, native, deviceState, voice };
|
|
3176
|
-
}
|
|
3177
|
-
|
|
3178
|
-
// src/index.ts
|
|
3370
|
+
init_pluginRuntime();
|
|
3179
3371
|
init_registry();
|
|
3180
3372
|
init_loader();
|
|
3181
3373
|
|
|
@@ -3293,6 +3485,8 @@ init_native();
|
|
|
3293
3485
|
var import_react11 = require("react");
|
|
3294
3486
|
init_bundleManager();
|
|
3295
3487
|
init_host();
|
|
3488
|
+
init_pluginRuntime();
|
|
3489
|
+
init_bundleUrl();
|
|
3296
3490
|
function extractBundleUrl(pluginId, manifest) {
|
|
3297
3491
|
const render = manifest.render;
|
|
3298
3492
|
if (!render || typeof render !== "object") return { url: null, source: "none" };
|
|
@@ -3300,7 +3494,7 @@ function extractBundleUrl(pluginId, manifest) {
|
|
|
3300
3494
|
const hasNativeComponent = !!(render.reactNative?.component || render.component);
|
|
3301
3495
|
if (hasNativeComponent) {
|
|
3302
3496
|
return {
|
|
3303
|
-
url:
|
|
3497
|
+
url: pluginBundleUrl(pluginId),
|
|
3304
3498
|
source: injected ? "ignored-explicit" : "derived-default",
|
|
3305
3499
|
...injected && { ignored: injected }
|
|
3306
3500
|
};
|
|
@@ -3330,6 +3524,8 @@ function usePlugin(pluginId, manifest) {
|
|
|
3330
3524
|
const bundleHash = extractBundleHash(manifest);
|
|
3331
3525
|
const componentId = extractComponentId(manifest);
|
|
3332
3526
|
const lookupId = componentId || pluginId;
|
|
3527
|
+
const grants = resolvePluginGrants({ pluginId, manifest });
|
|
3528
|
+
const grantsKey = JSON.stringify(grants);
|
|
3333
3529
|
if (ignoredUrl) {
|
|
3334
3530
|
console.warn(`[usePlugin] ${pluginId}: IGNORED connector-injected bundleUrl="${ignoredUrl}". The host derives bundle URLs from pluginId via the canonical route.`);
|
|
3335
3531
|
}
|
|
@@ -3352,7 +3548,7 @@ function usePlugin(pluginId, manifest) {
|
|
|
3352
3548
|
}
|
|
3353
3549
|
if (bundleUrl) {
|
|
3354
3550
|
try {
|
|
3355
|
-
const component = await bundleManager.load(pluginId, bundleUrl, version, bundleHash);
|
|
3551
|
+
const component = await bundleManager.load(pluginId, bundleUrl, version, bundleHash, grants);
|
|
3356
3552
|
if (cancelled) return;
|
|
3357
3553
|
setPlugin(component);
|
|
3358
3554
|
setLoading(false);
|
|
@@ -3389,7 +3585,7 @@ function usePlugin(pluginId, manifest) {
|
|
|
3389
3585
|
return () => {
|
|
3390
3586
|
cancelled = true;
|
|
3391
3587
|
};
|
|
3392
|
-
}, [pluginId, lookupId, bundleUrl, bundleHash, version]);
|
|
3588
|
+
}, [pluginId, lookupId, bundleUrl, bundleHash, version, grantsKey]);
|
|
3393
3589
|
return { plugin, loading, error, bundleMissing };
|
|
3394
3590
|
}
|
|
3395
3591
|
|
|
@@ -3465,6 +3661,7 @@ function useRemotePlugins(solutionId) {
|
|
|
3465
3661
|
|
|
3466
3662
|
// src/index.ts
|
|
3467
3663
|
init_plugins();
|
|
3664
|
+
init_bundleUrl();
|
|
3468
3665
|
init_SurfaceDrawer();
|
|
3469
3666
|
init_SurfaceCard();
|
|
3470
3667
|
init_SurfaceFullscreen();
|
|
@@ -3538,6 +3735,7 @@ init_SurfaceDispatcher();
|
|
|
3538
3735
|
listPlugins,
|
|
3539
3736
|
nativePluginRegistry,
|
|
3540
3737
|
neverObserved,
|
|
3738
|
+
pluginBundleUrl,
|
|
3541
3739
|
register,
|
|
3542
3740
|
registerBundleModules,
|
|
3543
3741
|
registerDeviceTools,
|
|
@@ -3546,6 +3744,7 @@ init_SurfaceDispatcher();
|
|
|
3546
3744
|
registerService,
|
|
3547
3745
|
registerTools,
|
|
3548
3746
|
resetDeviceToolLoader,
|
|
3747
|
+
resolvePluginGrants,
|
|
3549
3748
|
resolvePluginRuntimeProps,
|
|
3550
3749
|
resolvePluginUrl,
|
|
3551
3750
|
setBundleStorage,
|