@camstack/system 1.2.84 → 1.2.86
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +32 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +32 -2
- package/dist/builtins/device-manager/device-meta-actions.d.ts +12 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +66 -11
- package/dist/builtins/snapshot/index.mjs +66 -11
- package/dist/builtins/snapshot/snapshot.addon.d.ts +39 -3
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-BAwlOEUy.mjs → dist-B90N7AWO.mjs} +575 -34
- package/dist/{dist-CQy9KjsL.js → dist-BV5Bug9t.js} +575 -34
- package/dist/index.js +73 -16
- package/dist/index.mjs +73 -16
- package/dist/kernel/addon-installer.d.ts +49 -5
- package/dist/kernel/moleculer/event-bus-core.d.ts +47 -1
- package/dist/kernel/transport/child-cap-protocol.d.ts +32 -0
- package/dist/kernel/transport/hub-cap-forward.d.ts +8 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +19 -0
- package/dist/{manifest-python-deps-DG_LLcZF.js → manifest-python-deps-C0zkl1Ae.js} +133 -53
- package/dist/{manifest-python-deps-C1DMs1J9.mjs → manifest-python-deps-fWzdEasI.mjs} +133 -53
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("./dist-
|
|
3
|
+
const require_dist = require("./dist-BV5Bug9t.js");
|
|
4
4
|
const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
|
|
5
5
|
require("./builtins/alerts/index.js");
|
|
6
6
|
const require_formatter = require("./formatter-DqAKDlvN.js");
|
|
@@ -25,7 +25,7 @@ const require_builtins_system_config_system_config_addon = require("./builtins/s
|
|
|
25
25
|
require("./builtins/system-config/index.js");
|
|
26
26
|
const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
|
|
27
27
|
const require_file_data_plane = require("./file-data-plane-DUHPHa-Y.js");
|
|
28
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
28
|
+
const require_manifest_python_deps = require("./manifest-python-deps-C0zkl1Ae.js");
|
|
29
29
|
const require_resource_monitor = require("./resource-monitor-CdnzxBLP.js");
|
|
30
30
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
31
31
|
let zod = require("zod");
|
|
@@ -4227,6 +4227,12 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4227
4227
|
* installs from the admin UI go through `install()` instead, which
|
|
4228
4228
|
* defaults to npm in production.
|
|
4229
4229
|
*
|
|
4230
|
+
* There is no version argument and there is deliberately no pin: the system is
|
|
4231
|
+
* addons-agnostic and `latest` is the whole contract at first boot (operator,
|
|
4232
|
+
* 2026-08-13). A copy already under the addon root is never replaced either —
|
|
4233
|
+
* bootstrap is seed-only, and overwriting a deployed bundle would be an
|
|
4234
|
+
* un-deploy ([D90](../../../../docs/decisions/adr-0090.md)).
|
|
4235
|
+
*
|
|
4230
4236
|
* @param packages — optional custom package list (default: REQUIRED_PACKAGES)
|
|
4231
4237
|
*/
|
|
4232
4238
|
async ensureRequiredPackages(packages) {
|
|
@@ -4651,6 +4657,61 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4651
4657
|
throw new Error(`${packageName} — runtime dependency install failed: ${require_dist.errMsg(err)}`, { cause: err });
|
|
4652
4658
|
}
|
|
4653
4659
|
}
|
|
4660
|
+
/**
|
|
4661
|
+
* Make `pkgDir`'s NATIVE modules loadable on THIS host, before it is swapped
|
|
4662
|
+
* in: install whatever `camstack.nativeDependencies` declares, then ensure
|
|
4663
|
+
* every traditional native present in the tree actually carries a compiled
|
|
4664
|
+
* `.node` for the running ABI.
|
|
4665
|
+
*
|
|
4666
|
+
* ONE implementation, for the same reason as {@link installRuntimeDependencies}
|
|
4667
|
+
* — every path that materialises an addon tree calls it, so no call site can
|
|
4668
|
+
* forget it. `installFromTgz` (deploy/upload) always ran these two steps; the
|
|
4669
|
+
* OTA staged-swap path ran NEITHER even after the runtime-dep fix (2026-08-12,
|
|
4670
|
+
* task #15, which named this gap explicitly). Same outage class one layer
|
|
4671
|
+
* down: an addon with native modules was broken by its FIRST OTA update, its
|
|
4672
|
+
* dist landing without an ABI-matched binary, and every load dying at the
|
|
4673
|
+
* binding (`Could not locate the bindings file`) — from the outside
|
|
4674
|
+
* indistinguishable from a bad bundle.
|
|
4675
|
+
*
|
|
4676
|
+
* **Two distinct steps, both load-bearing.**
|
|
4677
|
+
* `installManifestNativeDeps` fetches the DECLARED natives (prebuilt host copy
|
|
4678
|
+
* first — offline — then npm + an ABI rebuild), and is idempotent through a
|
|
4679
|
+
* content-hash marker. `ensureAddonNativePrebuilds` is the post-install pass:
|
|
4680
|
+
* npm can leave a traditional native (`better-sqlite3`) SOURCE-ONLY on a
|
|
4681
|
+
* toolchain-less host, so it re-fetches the prebuild for the running ABI and
|
|
4682
|
+
* THROWS naming module + addon if the binary is still absent. No bundled
|
|
4683
|
+
* source is available on this path (unlike `installCopy`) — fetch only.
|
|
4684
|
+
*
|
|
4685
|
+
* **Failure is FATAL**, mirroring the tgz path: callers complete the tree
|
|
4686
|
+
* BEFORE they swap, so an abort simply means the swap never happens and the
|
|
4687
|
+
* previous version keeps serving. Shipping a native-less copy instead would
|
|
4688
|
+
* trade one broken version for a crash-loop.
|
|
4689
|
+
*
|
|
4690
|
+
* A missing/unreadable manifest is logged and skipped — `installRuntimeDependencies`
|
|
4691
|
+
* has already said so loudly, and a tree with no readable package.json
|
|
4692
|
+
* declares no natives.
|
|
4693
|
+
*/
|
|
4694
|
+
async installNativeDependencies(pkgDir, packageName) {
|
|
4695
|
+
const pkgJsonPath = node_path.join(pkgDir, "package.json");
|
|
4696
|
+
const pkgView = readPackageJson(pkgJsonPath);
|
|
4697
|
+
if (pkgView == null) {
|
|
4698
|
+
this.logger.error(`${packageName} — no readable package.json in the prepared tree; native dependencies NOT installed`, { meta: { pkgJsonPath } });
|
|
4699
|
+
return;
|
|
4700
|
+
}
|
|
4701
|
+
try {
|
|
4702
|
+
await require_manifest_python_deps.installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
|
|
4703
|
+
} catch (nativeErr) {
|
|
4704
|
+
this.logger.error(`${packageName} — native dependency install FAILED; install ABORTED (a swapped-in copy would fail every load at the binding)`, { meta: {
|
|
4705
|
+
pkgDir,
|
|
4706
|
+
error: require_dist.errMsg(nativeErr)
|
|
4707
|
+
} });
|
|
4708
|
+
throw new Error(`${packageName} — native deps install failed: ${require_dist.errMsg(nativeErr)}`, { cause: nativeErr });
|
|
4709
|
+
}
|
|
4710
|
+
await ensureAddonNativePrebuilds(pkgDir, {
|
|
4711
|
+
addonName: packageName,
|
|
4712
|
+
logger: this.logger
|
|
4713
|
+
});
|
|
4714
|
+
}
|
|
4654
4715
|
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
4655
4716
|
async installFromTgz(tgzPath) {
|
|
4656
4717
|
const stagingRoot = node_path.join(this.addonsDir, ".staging");
|
|
@@ -4675,15 +4736,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4675
4736
|
if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
|
|
4676
4737
|
const targetDir = node_path.join(this.addonsDir, pkgView.name);
|
|
4677
4738
|
await this.installRuntimeDependencies(pkgDir, pkgView.name);
|
|
4678
|
-
|
|
4679
|
-
await require_manifest_python_deps.installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
|
|
4680
|
-
} catch (nativeErr) {
|
|
4681
|
-
throw new Error(`${pkgView.name} — native deps install failed: ${require_dist.errMsg(nativeErr)}`, { cause: nativeErr });
|
|
4682
|
-
}
|
|
4683
|
-
await ensureAddonNativePrebuilds(pkgDir, {
|
|
4684
|
-
addonName: pkgView.name,
|
|
4685
|
-
logger: this.logger
|
|
4686
|
-
});
|
|
4739
|
+
await this.installNativeDependencies(pkgDir, pkgView.name);
|
|
4687
4740
|
await this.evictInstallDir(targetDir);
|
|
4688
4741
|
ensureDir(node_path.dirname(targetDir));
|
|
4689
4742
|
await node_fs.promises.rename(pkgDir, targetDir);
|
|
@@ -4788,9 +4841,12 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4788
4841
|
*
|
|
4789
4842
|
* "Skips npm entirely" is true only of the REGISTRY round-trip: the staged
|
|
4790
4843
|
* tree is the raw unpacked tarball, so the addon's own
|
|
4791
|
-
* `camstack.runtimeDependencies` still have
|
|
4792
|
-
* as `installFromTgz` does. Skipping
|
|
4793
|
-
* addon on its first OTA update
|
|
4844
|
+
* `camstack.runtimeDependencies` AND `camstack.nativeDependencies` still have
|
|
4845
|
+
* to be installed into it, exactly as `installFromTgz` does. Skipping the
|
|
4846
|
+
* first broke every runtime-dep addon on its first OTA update; skipping the
|
|
4847
|
+
* second broke every addon with a native module the same way, one layer down
|
|
4848
|
+
* (`Could not locate the bindings file` instead of `Cannot find module`) —
|
|
4849
|
+
* see {@link installRuntimeDependencies} and {@link installNativeDependencies}.
|
|
4794
4850
|
*
|
|
4795
4851
|
* Order matters and is `installFromTgz`'s: the tree is COMPLETED FIRST, and
|
|
4796
4852
|
* the live install is only then backed up and replaced. So the addon keeps
|
|
@@ -4803,8 +4859,8 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4803
4859
|
* restored and the error is rethrown — identical recovery to `applyUpdate`.
|
|
4804
4860
|
*
|
|
4805
4861
|
* Throws if the package is not tracked in the manifest, if `addonDir`
|
|
4806
|
-
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep
|
|
4807
|
-
* install fails.
|
|
4862
|
+
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep /
|
|
4863
|
+
* native-dep install fails.
|
|
4808
4864
|
*/
|
|
4809
4865
|
async applyUpdateFromStaged(packageName, version, stagedPath) {
|
|
4810
4866
|
const current = this.manifest.get(packageName);
|
|
@@ -4812,6 +4868,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4812
4868
|
const addonDir = node_path.join(this.addonsDir, packageName);
|
|
4813
4869
|
if (!node_fs.existsSync(addonDir)) throw new Error(`Cannot update ${packageName}: install dir ${addonDir} missing`);
|
|
4814
4870
|
await this.installRuntimeDependencies(stagedPath, packageName);
|
|
4871
|
+
await this.installNativeDependencies(stagedPath, packageName);
|
|
4815
4872
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4816
4873
|
const backupRoot = node_path.join(this.addonsDir, ".backups", packageName);
|
|
4817
4874
|
ensureDir(backupRoot);
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { $ as objectInputDeclaresAddonId, At as emitDownForOwnedCaps, B as isArrayOutputSchema, Bt as scopeKey, Ct as ReadinessRegistry, Dt as asNumber, Et as asJsonObject$1, G as kebabToCamel, H as isObjectInput, Ht as EventCategory$1, It as parseJsonObject, K as lifecycleJobSchema, Lt as parseJsonUnknown$1, Mt as expandCapMethods, Ot as asString$1, R as extractNestedAddonId, Rt as readinessKey, V as isCollectionArrayMethod, W as isVoidInput, X as looseSchema, Y as logLevelAtMost, _t as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, ht as errMsg$1, it as scopesAllowDeviceCap, kt as createEvent, nt as procedureAuthKey, rt as scopesAllowAddon, t as ALL_CAPABILITY_DEFINITIONS, vt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, wt as ReadinessTimeoutError, y as addonSettingsCapability, yt as DEVICE_STATUS_METHOD, zt as resolveCapMount } from "./dist-
|
|
2
|
+
import { $ as objectInputDeclaresAddonId, At as emitDownForOwnedCaps, B as isArrayOutputSchema, Bt as scopeKey, Ct as ReadinessRegistry, Dt as asNumber, Et as asJsonObject$1, G as kebabToCamel, H as isObjectInput, Ht as EventCategory$1, It as parseJsonObject, K as lifecycleJobSchema, Lt as parseJsonUnknown$1, Mt as expandCapMethods, Ot as asString$1, R as extractNestedAddonId, Rt as readinessKey, V as isCollectionArrayMethod, W as isVoidInput, X as looseSchema, Y as logLevelAtMost, _t as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, ht as errMsg$1, it as scopesAllowDeviceCap, kt as createEvent, nt as procedureAuthKey, rt as scopesAllowAddon, t as ALL_CAPABILITY_DEFINITIONS, vt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, wt as ReadinessTimeoutError, y as addonSettingsCapability, yt as DEVICE_STATUS_METHOD, zt as resolveCapMount } from "./dist-B90N7AWO.mjs";
|
|
3
3
|
import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
|
|
4
4
|
import "./builtins/alerts/index.mjs";
|
|
5
5
|
import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
|
|
@@ -24,7 +24,7 @@ import { SystemConfigAddon } from "./builtins/system-config/system-config.addon.
|
|
|
24
24
|
import "./builtins/system-config/index.mjs";
|
|
25
25
|
import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
|
|
26
26
|
import { a as parseTokenizedUrl, c as collectModelFiles, d as downloadModel, f as ensureModel, h as isModelDownloaded, i as parseRangeHeader, l as deleteModelFromDisk, m as getModelFilePath, n as contentTypeFor, o as resolveFilePath, p as fetchJson, r as createAuthenticatedFileServer, s as ModelDownloadService, t as createFileDataPlaneHandler, u as downloadFile } from "./file-data-plane-CuE_hBli.mjs";
|
|
27
|
-
import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as runNpm, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveNpmInvocation, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveAddonClass, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-
|
|
27
|
+
import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as runNpm, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveNpmInvocation, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveAddonClass, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-fWzdEasI.mjs";
|
|
28
28
|
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BWmQ5i-o.mjs";
|
|
29
29
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
30
30
|
import { z } from "zod";
|
|
@@ -4219,6 +4219,12 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4219
4219
|
* installs from the admin UI go through `install()` instead, which
|
|
4220
4220
|
* defaults to npm in production.
|
|
4221
4221
|
*
|
|
4222
|
+
* There is no version argument and there is deliberately no pin: the system is
|
|
4223
|
+
* addons-agnostic and `latest` is the whole contract at first boot (operator,
|
|
4224
|
+
* 2026-08-13). A copy already under the addon root is never replaced either —
|
|
4225
|
+
* bootstrap is seed-only, and overwriting a deployed bundle would be an
|
|
4226
|
+
* un-deploy ([D90](../../../../docs/decisions/adr-0090.md)).
|
|
4227
|
+
*
|
|
4222
4228
|
* @param packages — optional custom package list (default: REQUIRED_PACKAGES)
|
|
4223
4229
|
*/
|
|
4224
4230
|
async ensureRequiredPackages(packages) {
|
|
@@ -4643,6 +4649,61 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4643
4649
|
throw new Error(`${packageName} — runtime dependency install failed: ${errMsg$1(err)}`, { cause: err });
|
|
4644
4650
|
}
|
|
4645
4651
|
}
|
|
4652
|
+
/**
|
|
4653
|
+
* Make `pkgDir`'s NATIVE modules loadable on THIS host, before it is swapped
|
|
4654
|
+
* in: install whatever `camstack.nativeDependencies` declares, then ensure
|
|
4655
|
+
* every traditional native present in the tree actually carries a compiled
|
|
4656
|
+
* `.node` for the running ABI.
|
|
4657
|
+
*
|
|
4658
|
+
* ONE implementation, for the same reason as {@link installRuntimeDependencies}
|
|
4659
|
+
* — every path that materialises an addon tree calls it, so no call site can
|
|
4660
|
+
* forget it. `installFromTgz` (deploy/upload) always ran these two steps; the
|
|
4661
|
+
* OTA staged-swap path ran NEITHER even after the runtime-dep fix (2026-08-12,
|
|
4662
|
+
* task #15, which named this gap explicitly). Same outage class one layer
|
|
4663
|
+
* down: an addon with native modules was broken by its FIRST OTA update, its
|
|
4664
|
+
* dist landing without an ABI-matched binary, and every load dying at the
|
|
4665
|
+
* binding (`Could not locate the bindings file`) — from the outside
|
|
4666
|
+
* indistinguishable from a bad bundle.
|
|
4667
|
+
*
|
|
4668
|
+
* **Two distinct steps, both load-bearing.**
|
|
4669
|
+
* `installManifestNativeDeps` fetches the DECLARED natives (prebuilt host copy
|
|
4670
|
+
* first — offline — then npm + an ABI rebuild), and is idempotent through a
|
|
4671
|
+
* content-hash marker. `ensureAddonNativePrebuilds` is the post-install pass:
|
|
4672
|
+
* npm can leave a traditional native (`better-sqlite3`) SOURCE-ONLY on a
|
|
4673
|
+
* toolchain-less host, so it re-fetches the prebuild for the running ABI and
|
|
4674
|
+
* THROWS naming module + addon if the binary is still absent. No bundled
|
|
4675
|
+
* source is available on this path (unlike `installCopy`) — fetch only.
|
|
4676
|
+
*
|
|
4677
|
+
* **Failure is FATAL**, mirroring the tgz path: callers complete the tree
|
|
4678
|
+
* BEFORE they swap, so an abort simply means the swap never happens and the
|
|
4679
|
+
* previous version keeps serving. Shipping a native-less copy instead would
|
|
4680
|
+
* trade one broken version for a crash-loop.
|
|
4681
|
+
*
|
|
4682
|
+
* A missing/unreadable manifest is logged and skipped — `installRuntimeDependencies`
|
|
4683
|
+
* has already said so loudly, and a tree with no readable package.json
|
|
4684
|
+
* declares no natives.
|
|
4685
|
+
*/
|
|
4686
|
+
async installNativeDependencies(pkgDir, packageName) {
|
|
4687
|
+
const pkgJsonPath = path$39.join(pkgDir, "package.json");
|
|
4688
|
+
const pkgView = readPackageJson(pkgJsonPath);
|
|
4689
|
+
if (pkgView == null) {
|
|
4690
|
+
this.logger.error(`${packageName} — no readable package.json in the prepared tree; native dependencies NOT installed`, { meta: { pkgJsonPath } });
|
|
4691
|
+
return;
|
|
4692
|
+
}
|
|
4693
|
+
try {
|
|
4694
|
+
await installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
|
|
4695
|
+
} catch (nativeErr) {
|
|
4696
|
+
this.logger.error(`${packageName} — native dependency install FAILED; install ABORTED (a swapped-in copy would fail every load at the binding)`, { meta: {
|
|
4697
|
+
pkgDir,
|
|
4698
|
+
error: errMsg$1(nativeErr)
|
|
4699
|
+
} });
|
|
4700
|
+
throw new Error(`${packageName} — native deps install failed: ${errMsg$1(nativeErr)}`, { cause: nativeErr });
|
|
4701
|
+
}
|
|
4702
|
+
await ensureAddonNativePrebuilds(pkgDir, {
|
|
4703
|
+
addonName: packageName,
|
|
4704
|
+
logger: this.logger
|
|
4705
|
+
});
|
|
4706
|
+
}
|
|
4646
4707
|
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
4647
4708
|
async installFromTgz(tgzPath) {
|
|
4648
4709
|
const stagingRoot = path$39.join(this.addonsDir, ".staging");
|
|
@@ -4667,15 +4728,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4667
4728
|
if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
|
|
4668
4729
|
const targetDir = path$39.join(this.addonsDir, pkgView.name);
|
|
4669
4730
|
await this.installRuntimeDependencies(pkgDir, pkgView.name);
|
|
4670
|
-
|
|
4671
|
-
await installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
|
|
4672
|
-
} catch (nativeErr) {
|
|
4673
|
-
throw new Error(`${pkgView.name} — native deps install failed: ${errMsg$1(nativeErr)}`, { cause: nativeErr });
|
|
4674
|
-
}
|
|
4675
|
-
await ensureAddonNativePrebuilds(pkgDir, {
|
|
4676
|
-
addonName: pkgView.name,
|
|
4677
|
-
logger: this.logger
|
|
4678
|
-
});
|
|
4731
|
+
await this.installNativeDependencies(pkgDir, pkgView.name);
|
|
4679
4732
|
await this.evictInstallDir(targetDir);
|
|
4680
4733
|
ensureDir(path$39.dirname(targetDir));
|
|
4681
4734
|
await fs$17.promises.rename(pkgDir, targetDir);
|
|
@@ -4780,9 +4833,12 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4780
4833
|
*
|
|
4781
4834
|
* "Skips npm entirely" is true only of the REGISTRY round-trip: the staged
|
|
4782
4835
|
* tree is the raw unpacked tarball, so the addon's own
|
|
4783
|
-
* `camstack.runtimeDependencies` still have
|
|
4784
|
-
* as `installFromTgz` does. Skipping
|
|
4785
|
-
* addon on its first OTA update
|
|
4836
|
+
* `camstack.runtimeDependencies` AND `camstack.nativeDependencies` still have
|
|
4837
|
+
* to be installed into it, exactly as `installFromTgz` does. Skipping the
|
|
4838
|
+
* first broke every runtime-dep addon on its first OTA update; skipping the
|
|
4839
|
+
* second broke every addon with a native module the same way, one layer down
|
|
4840
|
+
* (`Could not locate the bindings file` instead of `Cannot find module`) —
|
|
4841
|
+
* see {@link installRuntimeDependencies} and {@link installNativeDependencies}.
|
|
4786
4842
|
*
|
|
4787
4843
|
* Order matters and is `installFromTgz`'s: the tree is COMPLETED FIRST, and
|
|
4788
4844
|
* the live install is only then backed up and replaced. So the addon keeps
|
|
@@ -4795,8 +4851,8 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4795
4851
|
* restored and the error is rethrown — identical recovery to `applyUpdate`.
|
|
4796
4852
|
*
|
|
4797
4853
|
* Throws if the package is not tracked in the manifest, if `addonDir`
|
|
4798
|
-
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep
|
|
4799
|
-
* install fails.
|
|
4854
|
+
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep /
|
|
4855
|
+
* native-dep install fails.
|
|
4800
4856
|
*/
|
|
4801
4857
|
async applyUpdateFromStaged(packageName, version, stagedPath) {
|
|
4802
4858
|
const current = this.manifest.get(packageName);
|
|
@@ -4804,6 +4860,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4804
4860
|
const addonDir = path$39.join(this.addonsDir, packageName);
|
|
4805
4861
|
if (!fs$17.existsSync(addonDir)) throw new Error(`Cannot update ${packageName}: install dir ${addonDir} missing`);
|
|
4806
4862
|
await this.installRuntimeDependencies(stagedPath, packageName);
|
|
4863
|
+
await this.installNativeDependencies(stagedPath, packageName);
|
|
4807
4864
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4808
4865
|
const backupRoot = path$39.join(this.addonsDir, ".backups", packageName);
|
|
4809
4866
|
ensureDir(backupRoot);
|
|
@@ -186,6 +186,12 @@ export declare class AddonInstaller {
|
|
|
186
186
|
* installs from the admin UI go through `install()` instead, which
|
|
187
187
|
* defaults to npm in production.
|
|
188
188
|
*
|
|
189
|
+
* There is no version argument and there is deliberately no pin: the system is
|
|
190
|
+
* addons-agnostic and `latest` is the whole contract at first boot (operator,
|
|
191
|
+
* 2026-08-13). A copy already under the addon root is never replaced either —
|
|
192
|
+
* bootstrap is seed-only, and overwriting a deployed bundle would be an
|
|
193
|
+
* un-deploy ([D90](../../../../docs/decisions/adr-0090.md)).
|
|
194
|
+
*
|
|
189
195
|
* @param packages — optional custom package list (default: REQUIRED_PACKAGES)
|
|
190
196
|
*/
|
|
191
197
|
ensureRequiredPackages(packages?: readonly string[]): Promise<void>;
|
|
@@ -341,6 +347,41 @@ export declare class AddonInstaller {
|
|
|
341
347
|
* package entirely).
|
|
342
348
|
*/
|
|
343
349
|
private installRuntimeDependencies;
|
|
350
|
+
/**
|
|
351
|
+
* Make `pkgDir`'s NATIVE modules loadable on THIS host, before it is swapped
|
|
352
|
+
* in: install whatever `camstack.nativeDependencies` declares, then ensure
|
|
353
|
+
* every traditional native present in the tree actually carries a compiled
|
|
354
|
+
* `.node` for the running ABI.
|
|
355
|
+
*
|
|
356
|
+
* ONE implementation, for the same reason as {@link installRuntimeDependencies}
|
|
357
|
+
* — every path that materialises an addon tree calls it, so no call site can
|
|
358
|
+
* forget it. `installFromTgz` (deploy/upload) always ran these two steps; the
|
|
359
|
+
* OTA staged-swap path ran NEITHER even after the runtime-dep fix (2026-08-12,
|
|
360
|
+
* task #15, which named this gap explicitly). Same outage class one layer
|
|
361
|
+
* down: an addon with native modules was broken by its FIRST OTA update, its
|
|
362
|
+
* dist landing without an ABI-matched binary, and every load dying at the
|
|
363
|
+
* binding (`Could not locate the bindings file`) — from the outside
|
|
364
|
+
* indistinguishable from a bad bundle.
|
|
365
|
+
*
|
|
366
|
+
* **Two distinct steps, both load-bearing.**
|
|
367
|
+
* `installManifestNativeDeps` fetches the DECLARED natives (prebuilt host copy
|
|
368
|
+
* first — offline — then npm + an ABI rebuild), and is idempotent through a
|
|
369
|
+
* content-hash marker. `ensureAddonNativePrebuilds` is the post-install pass:
|
|
370
|
+
* npm can leave a traditional native (`better-sqlite3`) SOURCE-ONLY on a
|
|
371
|
+
* toolchain-less host, so it re-fetches the prebuild for the running ABI and
|
|
372
|
+
* THROWS naming module + addon if the binary is still absent. No bundled
|
|
373
|
+
* source is available on this path (unlike `installCopy`) — fetch only.
|
|
374
|
+
*
|
|
375
|
+
* **Failure is FATAL**, mirroring the tgz path: callers complete the tree
|
|
376
|
+
* BEFORE they swap, so an abort simply means the swap never happens and the
|
|
377
|
+
* previous version keeps serving. Shipping a native-less copy instead would
|
|
378
|
+
* trade one broken version for a crash-loop.
|
|
379
|
+
*
|
|
380
|
+
* A missing/unreadable manifest is logged and skipped — `installRuntimeDependencies`
|
|
381
|
+
* has already said so loudly, and a tree with no readable package.json
|
|
382
|
+
* declares no natives.
|
|
383
|
+
*/
|
|
384
|
+
private installNativeDependencies;
|
|
344
385
|
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
345
386
|
installFromTgz(tgzPath: string): Promise<{
|
|
346
387
|
name: string;
|
|
@@ -381,9 +422,12 @@ export declare class AddonInstaller {
|
|
|
381
422
|
*
|
|
382
423
|
* "Skips npm entirely" is true only of the REGISTRY round-trip: the staged
|
|
383
424
|
* tree is the raw unpacked tarball, so the addon's own
|
|
384
|
-
* `camstack.runtimeDependencies` still have
|
|
385
|
-
* as `installFromTgz` does. Skipping
|
|
386
|
-
* addon on its first OTA update
|
|
425
|
+
* `camstack.runtimeDependencies` AND `camstack.nativeDependencies` still have
|
|
426
|
+
* to be installed into it, exactly as `installFromTgz` does. Skipping the
|
|
427
|
+
* first broke every runtime-dep addon on its first OTA update; skipping the
|
|
428
|
+
* second broke every addon with a native module the same way, one layer down
|
|
429
|
+
* (`Could not locate the bindings file` instead of `Cannot find module`) —
|
|
430
|
+
* see {@link installRuntimeDependencies} and {@link installNativeDependencies}.
|
|
387
431
|
*
|
|
388
432
|
* Order matters and is `installFromTgz`'s: the tree is COMPLETED FIRST, and
|
|
389
433
|
* the live install is only then backed up and replaced. So the addon keeps
|
|
@@ -396,8 +440,8 @@ export declare class AddonInstaller {
|
|
|
396
440
|
* restored and the error is rethrown — identical recovery to `applyUpdate`.
|
|
397
441
|
*
|
|
398
442
|
* Throws if the package is not tracked in the manifest, if `addonDir`
|
|
399
|
-
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep
|
|
400
|
-
* install fails.
|
|
443
|
+
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep /
|
|
444
|
+
* native-dep install fails.
|
|
401
445
|
*/
|
|
402
446
|
applyUpdateFromStaged(packageName: string, version: string, stagedPath: string): Promise<{
|
|
403
447
|
name: string;
|
|
@@ -2,7 +2,12 @@ import { EventFilter, SystemEvent } from '@camstack/types';
|
|
|
2
2
|
export interface SubscriberEntry {
|
|
3
3
|
/** Original filter — kept whole so non-category dimensions are honoured per-event. */
|
|
4
4
|
readonly filter: EventFilter | string | undefined;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Pre-computed category pattern for the cheap pre-filter — the FIRST of
|
|
7
|
+
* {@link extractCategoryPatterns} when the filter names several. Diagnostic
|
|
8
|
+
* only: the authority on which keys an entry occupies is the handler map
|
|
9
|
+
* itself, written by {@link registerSubscriber}.
|
|
10
|
+
*/
|
|
6
11
|
readonly categoryPattern: string;
|
|
7
12
|
readonly handler: (event: SystemEvent) => void;
|
|
8
13
|
}
|
|
@@ -47,7 +52,48 @@ export interface SharedBusState {
|
|
|
47
52
|
*/
|
|
48
53
|
export declare function createSharedBusState(retainRecent?: boolean): SharedBusState;
|
|
49
54
|
export declare function matchesPattern(pattern: string, category: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The FIRST category pattern of a filter.
|
|
57
|
+
*
|
|
58
|
+
* Kept for the diagnostic `SubscriberEntry.categoryPattern` field and for
|
|
59
|
+
* callers that genuinely want one string. It is NOT the registration path:
|
|
60
|
+
* a filter naming several categories must occupy several map keys, which is
|
|
61
|
+
* what {@link extractCategoryPatterns} + {@link registerSubscriber} do. Using
|
|
62
|
+
* this function to key a subscriber is the bug described on
|
|
63
|
+
* `extractCategoryPatterns`.
|
|
64
|
+
*/
|
|
50
65
|
export declare function extractCategoryPattern(filter: EventFilter | string): string;
|
|
66
|
+
/**
|
|
67
|
+
* EVERY category pattern a filter asks for — the registration key set.
|
|
68
|
+
*
|
|
69
|
+
* `EventFilter.category` has always accepted an array, and until 2026-08-12
|
|
70
|
+
* the delivery path kept only `cat[0]`. Inside a forked runner that was not
|
|
71
|
+
* merely a local filter: `createUdsEventBus` reports its handler-map keys to
|
|
72
|
+
* the parent as this child's forwarding interest, so the categories that never
|
|
73
|
+
* became keys were never forwarded across the process boundary at all. A
|
|
74
|
+
* ten-category subscribe received one category, silently.
|
|
75
|
+
*
|
|
76
|
+
* Duplicates are collapsed so a filter that names the same category twice does
|
|
77
|
+
* not put the entry in one key twice.
|
|
78
|
+
*/
|
|
79
|
+
export declare function extractCategoryPatterns(filter: EventFilter | string): readonly string[];
|
|
80
|
+
/**
|
|
81
|
+
* Register one handler under every category pattern its filter names, and
|
|
82
|
+
* return the unsubscribe that removes it from all of them.
|
|
83
|
+
*
|
|
84
|
+
* The ONE place a subscriber enters `state.handlers` — both bus
|
|
85
|
+
* implementations (broker and UDS) call it, so "which keys does this
|
|
86
|
+
* subscriber occupy" has a single answer. An emptied key is DELETED rather
|
|
87
|
+
* than left as an empty `Set`: on a child bus the key set is what the parent
|
|
88
|
+
* is told to forward, and an empty set left behind pins the parent to forward
|
|
89
|
+
* a category nobody wants.
|
|
90
|
+
*
|
|
91
|
+
* @param onKeysChanged - Called after the key set may have changed (both on
|
|
92
|
+
* registration and on unsubscribe), for buses that mirror their key set
|
|
93
|
+
* somewhere — the UDS child's parent report, the broker's local-interest
|
|
94
|
+
* surface. Receives the patterns this call touched.
|
|
95
|
+
*/
|
|
96
|
+
export declare function registerSubscriber(state: SharedBusState, filter: EventFilter | string, handler: (event: SystemEvent) => void, onKeysChanged?: (patterns: readonly string[], added: boolean) => void): () => void;
|
|
51
97
|
/**
|
|
52
98
|
* Match an event against the full `EventFilter`. The Map-keyed subscribe path
|
|
53
99
|
* only handles the category pattern (deciding which handler set to fan out to);
|
|
@@ -62,6 +62,32 @@ export interface CapCallOutMessage {
|
|
|
62
62
|
* `platform-probe` hardware). Set via `ctx.api`'s `onNode(nodeId)` modifier.
|
|
63
63
|
*/
|
|
64
64
|
readonly nodeId?: string;
|
|
65
|
+
/**
|
|
66
|
+
* "Resolve the per-device NATIVE provider for this cap, never a wrapper."
|
|
67
|
+
* Set by `ctx.getNativeProvider(...)` in a forked runner.
|
|
68
|
+
*
|
|
69
|
+
* ## Why a flag and not just the cap name
|
|
70
|
+
*
|
|
71
|
+
* A wrapper and its natives share ONE cap name. As long as the wrapper lived
|
|
72
|
+
* in hub-main that was harmless — the wrapper resolved its native through the
|
|
73
|
+
* hub's `CapabilityRegistry`, which knows the difference. A wrapper in a
|
|
74
|
+
* runner has no such registry, and every generic route back out of the
|
|
75
|
+
* process resolves `capName` the way every OTHER caller does: to the wrapper.
|
|
76
|
+
* `localProviderLink` answers it from the runner's own provider before the
|
|
77
|
+
* call ever reaches the socket, and `resolveChildId` would hand it back to
|
|
78
|
+
* the wrapper's own child if it did. Either way the wrapper calls itself:
|
|
79
|
+
* the second entry joins the first's single flight and waits on a promise
|
|
80
|
+
* only it can settle.
|
|
81
|
+
*
|
|
82
|
+
* So the intent has to travel. `native: true` makes the parent skip
|
|
83
|
+
* capName-based resolution entirely and ask the ONE native authority
|
|
84
|
+
* (`CapabilityRegistry.getNativeProvider`, which covers in-process natives,
|
|
85
|
+
* hub-local UDS vendor children and remote agents alike) for `(cap, device)`.
|
|
86
|
+
* With no native for the pair the call FAILS — it never degrades into the
|
|
87
|
+
* wrapper — with a message a wrapper can read as "absent" and fall through to
|
|
88
|
+
* its own strategy.
|
|
89
|
+
*/
|
|
90
|
+
readonly native?: boolean;
|
|
65
91
|
}
|
|
66
92
|
/** Child → parent: a system event produced by the child to be forwarded to the hub event bus. */
|
|
67
93
|
export interface ChildEventMessage {
|
|
@@ -110,6 +136,12 @@ export interface CapCallMessage {
|
|
|
110
136
|
* the child dispatch falls back to its sole provider for the cap.
|
|
111
137
|
*/
|
|
112
138
|
readonly addonId?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Native-only routing hint — see {@link CapCallOutMessage.native}. Carried
|
|
141
|
+
* here because `CapCallInput` (the type every parent-side router takes) is
|
|
142
|
+
* this message minus its discriminant; a child DISPATCH ignores it.
|
|
143
|
+
*/
|
|
144
|
+
readonly native?: boolean;
|
|
113
145
|
}
|
|
114
146
|
/** Parent → child: a system event delivered by the hub for the child to handle locally. */
|
|
115
147
|
export interface ParentEventMessage {
|
|
@@ -16,6 +16,14 @@ export interface HubCapForwardParams {
|
|
|
16
16
|
readonly args: unknown;
|
|
17
17
|
readonly deviceId?: number;
|
|
18
18
|
readonly nodeId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Native-only routing hint (see `CapCallOutMessage.native`). An agent has no
|
|
21
|
+
* native authority of its own, so a forked wrapper on an agent asking for a
|
|
22
|
+
* per-device native forwards the question here. It MUST survive the envelope:
|
|
23
|
+
* without it the hub resolves the cap NAME, i.e. the wrapper the caller is
|
|
24
|
+
* trying to skip.
|
|
25
|
+
*/
|
|
26
|
+
readonly native?: boolean;
|
|
19
27
|
}
|
|
20
28
|
/**
|
|
21
29
|
* Handler that routes an agent-originated cap call through the hub's own
|
|
@@ -183,6 +183,25 @@ export interface ParentUnownedCallDeps {
|
|
|
183
183
|
* exactly: the miss falls straight to the broker fallback with no retry.
|
|
184
184
|
*/
|
|
185
185
|
readonly isCoreServiceCap?: (capName: string) => boolean;
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the per-device NATIVE provider for `(capName, deviceId)` — the
|
|
188
|
+
* `native: true` route (see {@link CapCallInput.native}).
|
|
189
|
+
*
|
|
190
|
+
* Wired to this node's `CapabilityRegistry.getNativeProvider`, which is the
|
|
191
|
+
* SINGLE authority on the question: it already covers in-process natives,
|
|
192
|
+
* hub-local UDS vendor children (through the hub's `setNativeFallback`
|
|
193
|
+
* resolver) and remote agents. Nothing here re-derives that — a rule
|
|
194
|
+
* implemented twice is a rule that drifts, and this one previously drifted
|
|
195
|
+
* into a wrapper answering on behalf of the native it was supposed to call.
|
|
196
|
+
*
|
|
197
|
+
* Returns null when no addon publishes the cap for that device. The handler
|
|
198
|
+
* then FAILS the call rather than falling back to capName routing: falling
|
|
199
|
+
* back is how a wrapper ends up calling itself.
|
|
200
|
+
*
|
|
201
|
+
* Omitted ⇒ a `native: true` call is forwarded to the hub (agents) or
|
|
202
|
+
* rejected (no forwarder) — never silently downgraded to wrapper routing.
|
|
203
|
+
*/
|
|
204
|
+
readonly resolveNative?: (capName: string, deviceId: number) => Record<string, (input: unknown) => Promise<unknown>> | null;
|
|
186
205
|
/** Optional logger for the broker-fallback diagnostic line. */
|
|
187
206
|
readonly logger?: ParentUnownedCallLogger;
|
|
188
207
|
}
|