@camstack/system 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -19,7 +19,7 @@ import { LocalAuthAddon, a as require_safe_buffer, i as AuthManager, n as ApiKey
19
19
  import "./builtins/local-auth/index.mjs";
20
20
  import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
21
21
  import "./builtins/device-manager/index.mjs";
22
- import { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as CapRouteError, G as FrameDecoder, H as UdsLocalTransportServer, I as classifyCapRoute, J as buildUdsNativeCapProxy, K as encodeFrame, L as LocalChildClient, M as AGENT_CAP_FWD_SERVICE, N as CapRouteResolver, O as udsChildLogToWorkerEntry, P as callWithServiceDiscovery, Q as mountNativeCapService, R as LocalChildRegistry, S as ipcChildLink, T as createParentUnownedCallHandler, U as SocketChannel, V as UdsLocalTransportClient, W as localEndpointPath, Y as createBrokerDeviceManagerApi, _ as __resetCapUsageRegistryForTests, a as getWorkerDeviceRegistry, at as capBareAction, b as brokerTransportLink, c as setHubConnected, ct as deserializeTypedArrays, d as registerEventBusService, dt as CapabilityHandle, et as createAddonService, f as AddonDepsManager, ft as CapabilityUnavailableError, g as CapUsageRegistry, h as createHwAccelService, i as createUdsAddonContext, it as capActionSuffix, j as AGENT_CAP_FWD_ACTION, k as createUdsLogger, l as EVENT_TOPIC_PREFIX, lt as serializeTypedArrays, m as resolveHwAccel, mt as resolveAddonClass, n as adaptBrokerToCluster, nt as NATIVE_PROVIDER_SERVICE_INFIX, o as getOrInitReadinessRegistry, ot as capServiceName, p as createKernelHwAccel, pt as installManifestNativeDeps, q as buildNativeCapProxy, r as createAddonContext, rt as capActionName, s as getOrInitReadinessRegistryForClient, st as parseCapAction, t as installManifestPythonDeps, tt as validateProviderRegistrations, u as getBrokerEventBus, ut as DeviceRegistry, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-CoJXeb9u.mjs";
22
+ import { A as createUdsLoggerWithControl, B as createLocalTransport, C as ipcParentLink, D as createUdsEventBus, E as createUdsEventBridge, F as CapRouteError, G as FrameDecoder, H as UdsLocalTransportServer, I as classifyCapRoute, J as buildUdsNativeCapProxy, K as encodeFrame, L as LocalChildClient, M as AGENT_CAP_FWD_SERVICE, N as CapRouteResolver, O as udsChildLogToWorkerEntry, P as callWithServiceDiscovery, Q as mountNativeCapService, R as LocalChildRegistry, S as ipcChildLink, T as createParentUnownedCallHandler, U as SocketChannel, V as UdsLocalTransportClient, W as localEndpointPath, Y as createBrokerDeviceManagerApi, _ as __resetCapUsageRegistryForTests, a as getWorkerDeviceRegistry, at as capBareAction, b as brokerTransportLink, c as setHubConnected, ct as deserializeTypedArrays, d as registerEventBusService, dt as CapabilityHandle, et as createAddonService, f as AddonDepsManager, ft as CapabilityUnavailableError, g as CapUsageRegistry, h as createHwAccelService, i as createUdsAddonContext, it as capActionSuffix, j as AGENT_CAP_FWD_ACTION, k as createUdsLogger, l as EVENT_TOPIC_PREFIX, lt as serializeTypedArrays, m as resolveHwAccel, mt as resolveAddonClass, n as adaptBrokerToCluster, nt as NATIVE_PROVIDER_SERVICE_INFIX, o as getOrInitReadinessRegistry, ot as capServiceName, p as createKernelHwAccel, pt as installManifestNativeDeps, q as buildNativeCapProxy, r as createAddonContext, rt as capActionName, s as getOrInitReadinessRegistryForClient, st as parseCapAction, t as installManifestPythonDeps, tt as validateProviderRegistrations, u as getBrokerEventBus, ut as DeviceRegistry, v as getCapUsageRegistry, w as localProviderLink, x as buildLinkChain, y as brokerCallForCap, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-BcrTzHH_.mjs";
23
23
  import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
24
24
  import { n as require_src, t as require_graceful_fs } from "./graceful-fs-BoR9GuPS.mjs";
25
25
  import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
@@ -30,7 +30,7 @@ import * as path$40 from "node:path";
30
30
  import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
31
31
  import { DATAPLANE_SECRET_HEADER, EventCategory, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, asJsonObject, asNumber, asString, createEvent, emitDownForOwnedCaps, errMsg, lifecycleJobSchema, parseJsonObject, parseJsonUnknown, readinessKey, scopeKey } from "@camstack/types";
32
32
  import { X509Certificate, createHash, randomUUID, timingSafeEqual } from "node:crypto";
33
- import { execFile, execFileSync, spawn } from "node:child_process";
33
+ import { execFile, spawn } from "node:child_process";
34
34
  import * as util$8 from "node:util";
35
35
  import { promisify } from "node:util";
36
36
  import * as vm from "node:vm";
@@ -177,7 +177,7 @@ function proxyToUpstream(opts) {
177
177
  }
178
178
  //#endregion
179
179
  //#region src/python/python-env-manager.ts
180
- var execFileAsync$2 = promisify(execFile);
180
+ var execFileAsync$3 = promisify(execFile);
181
181
  var PythonEnvManager = class {
182
182
  venvPath;
183
183
  cachedProbe = null;
@@ -187,12 +187,12 @@ var PythonEnvManager = class {
187
187
  async probe() {
188
188
  if (this.cachedProbe) return this.cachedProbe;
189
189
  for (const cmd of ["python3", "python"]) try {
190
- const { stdout } = await execFileAsync$2(cmd, ["--version"]);
190
+ const { stdout } = await execFileAsync$3(cmd, ["--version"]);
191
191
  const version = stdout.trim().replace("Python ", "");
192
192
  const major = parseInt(version.split(".")[0] ?? "0", 10);
193
193
  const minor = parseInt(version.split(".")[1] ?? "0", 10);
194
194
  if (major < 3 || major === 3 && minor < 10) continue;
195
- const { stdout: pathOut } = await execFileAsync$2(cmd, ["-c", "import sys; print(sys.executable)"]);
195
+ const { stdout: pathOut } = await execFileAsync$3(cmd, ["-c", "import sys; print(sys.executable)"]);
196
196
  this.cachedProbe = {
197
197
  available: true,
198
198
  version,
@@ -208,13 +208,13 @@ var PythonEnvManager = class {
208
208
  async ensure(options) {
209
209
  const probe = await this.probe();
210
210
  if (!probe.available || !probe.path) throw new Error("Python 3.10+ is required but not found on this system");
211
- if (!fs$17.existsSync(path$40.join(this.venvPath, "bin", "python"))) await execFileAsync$2(probe.path, [
211
+ if (!fs$17.existsSync(path$40.join(this.venvPath, "bin", "python"))) await execFileAsync$3(probe.path, [
212
212
  "-m",
213
213
  "venv",
214
214
  this.venvPath
215
215
  ]);
216
216
  const venvPython = path$40.join(this.venvPath, "bin", "python");
217
- if (options.packages.length > 0) await execFileAsync$2(venvPython, [
217
+ if (options.packages.length > 0) await execFileAsync$3(venvPython, [
218
218
  "-m",
219
219
  "pip",
220
220
  "install",
@@ -1413,12 +1413,11 @@ var StorageManager = class {
1413
1413
  return namespace ? this.createNamespacedLocation(location, namespace) : location;
1414
1414
  }
1415
1415
  createLegacyShim() {
1416
- const self = this;
1417
1416
  return {
1418
1417
  async initialize() {},
1419
1418
  async shutdown() {},
1420
- getLocation(name) {
1421
- return self.getLocation(name);
1419
+ getLocation: (name) => {
1420
+ return this.getLocation(name);
1422
1421
  }
1423
1422
  };
1424
1423
  }
@@ -1694,7 +1693,7 @@ function matchPath(pattern, path) {
1694
1693
  }
1695
1694
  //#endregion
1696
1695
  //#region src/tls/cert-manager.ts
1697
- var execFileAsync$1 = promisify(execFile);
1696
+ var execFileAsync$2 = promisify(execFile);
1698
1697
  /**
1699
1698
  * Ensure a self-signed TLS certificate exists in the given directory.
1700
1699
  * Generates one if missing. Returns paths to cert and key files.
@@ -1731,7 +1730,7 @@ async function ensureTlsCert(dataDir, options) {
1731
1730
  for (const dns of sanDns) sanParts.push(`DNS:${dns}`);
1732
1731
  for (const ip of sanIps) sanParts.push(`IP:${ip}`);
1733
1732
  const sanString = sanParts.join(",");
1734
- await execFileAsync$1("openssl", [
1733
+ await execFileAsync$2("openssl", [
1735
1734
  "req",
1736
1735
  "-x509",
1737
1736
  "-newkey",
@@ -2564,6 +2563,7 @@ var AddonEngineManager = class {
2564
2563
  };
2565
2564
  //#endregion
2566
2565
  //#region src/kernel/fs-utils.ts
2566
+ var execFileAsync$1 = promisify(execFile);
2567
2567
  /**
2568
2568
  * Ensure a directory exists (recursive).
2569
2569
  * Single source of truth — replaces scattered mkdirSync calls.
@@ -2572,18 +2572,20 @@ function ensureDir(dirPath) {
2572
2572
  fs$17.mkdirSync(dirPath, { recursive: true });
2573
2573
  }
2574
2574
  /**
2575
- * Copy a directory recursively.
2576
- * Single source of truth — extracted from addon-installer + first-boot-installer.
2575
+ * Copy a directory recursively — ASYNC so it never blocks the event loop.
2576
+ *
2577
+ * The hub installs addons into `/data/addons`, which on Unraid is a slow
2578
+ * shfs/FUSE mount. The former synchronous `fs.copyFileSync`-per-file loop
2579
+ * blocked the Node event loop for the whole copy of a large bundle (e.g.
2580
+ * `addon-pipeline`), which froze the hub's HTTP listener mid-`camstack deploy`
2581
+ * (accept backlog piling up, existing connections stuck in CLOSE_WAIT). Using
2582
+ * `fs.promises.cp` keeps the I/O off the event loop. (Node ≥18 stable.)
2577
2583
  */
2578
- function copyDirRecursive(src, dest) {
2579
- ensureDir(dest);
2580
- const entries = fs$17.readdirSync(src, { withFileTypes: true });
2581
- for (const entry of entries) {
2582
- const srcPath = path$40.join(src, entry.name);
2583
- const destPath = path$40.join(dest, entry.name);
2584
- if (entry.isDirectory()) copyDirRecursive(srcPath, destPath);
2585
- else fs$17.copyFileSync(srcPath, destPath);
2586
- }
2584
+ async function copyDirRecursive(src, dest) {
2585
+ await fs$17.promises.cp(src, dest, {
2586
+ recursive: true,
2587
+ force: true
2588
+ });
2587
2589
  }
2588
2590
  /**
2589
2591
  * Strip @camstack/* dependencies and devDependencies from a package.json object.
@@ -2613,7 +2615,7 @@ function stripCamstackDeps(pkg) {
2613
2615
  * Copies directories (not individual files) from source to destination.
2614
2616
  * Skips "dist" (already handled) and glob patterns.
2615
2617
  */
2616
- function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
2618
+ async function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
2617
2619
  const rawFiles = pkgJson.files;
2618
2620
  if (!Array.isArray(rawFiles)) return;
2619
2621
  for (const fileEntry of rawFiles) {
@@ -2622,11 +2624,11 @@ function copyExtraFileDirs(pkgJson, sourceDir, destDir) {
2622
2624
  const srcPath = path$40.join(sourceDir, fileEntry);
2623
2625
  if (!fs$17.existsSync(srcPath)) continue;
2624
2626
  const destPath = path$40.join(destDir, fileEntry);
2625
- const stat = fs$17.statSync(srcPath);
2626
- if (stat.isDirectory()) copyDirRecursive(srcPath, destPath);
2627
+ const stat = await fs$17.promises.stat(srcPath);
2628
+ if (stat.isDirectory()) await copyDirRecursive(srcPath, destPath);
2627
2629
  else if (stat.isFile()) {
2628
2630
  ensureDir(path$40.dirname(destPath));
2629
- fs$17.copyFileSync(srcPath, destPath);
2631
+ await fs$17.promises.copyFile(srcPath, destPath);
2630
2632
  }
2631
2633
  }
2632
2634
  }
@@ -2671,12 +2673,16 @@ function ensureLibraryBuilt(packageName, packagesDir) {
2671
2673
  /**
2672
2674
  * Install a single npm package into a target directory (package.json + dist/).
2673
2675
  * No validation on camstack.addons -- works for any @camstack/* package.
2674
- * Uses synchronous child_process calls (suitable for first-boot and update paths).
2676
+ *
2677
+ * ASYNC: uses `execFile`/`fs.promises` throughout so a package install on a
2678
+ * live node never blocks the event loop (the `npm pack` + tar extract + copy to
2679
+ * the slow shfs/FUSE `/data` would otherwise freeze the HTTP listener). See
2680
+ * `copyDirRecursive` for the wedge this prevents.
2675
2681
  */
2676
- function installPackageFromNpmSync(packageName, targetDir) {
2677
- const tmpDir = fs$17.mkdtempSync(path$40.join(os$17.tmpdir(), "camstack-install-"));
2682
+ async function installPackageFromNpm(packageName, targetDir) {
2683
+ const tmpDir = await fs$17.promises.mkdtemp(path$40.join(os$17.tmpdir(), "camstack-install-"));
2678
2684
  try {
2679
- const tgzFilename = execFileSync("npm", [
2685
+ const { stdout } = await execFileAsync$1("npm", [
2680
2686
  "pack",
2681
2687
  packageName,
2682
2688
  "--pack-destination",
@@ -2684,12 +2690,13 @@ function installPackageFromNpmSync(packageName, targetDir) {
2684
2690
  ], {
2685
2691
  timeout: 12e4,
2686
2692
  encoding: "utf-8"
2687
- }).trim().split("\n").pop()?.trim();
2693
+ });
2694
+ const tgzFilename = stdout.trim().split("\n").pop()?.trim();
2688
2695
  if (!tgzFilename) throw new Error("npm pack produced no output");
2689
2696
  const tgzPath = path$40.join(tmpDir, tgzFilename);
2690
2697
  const extractDir = path$40.join(tmpDir, "extracted");
2691
2698
  ensureDir(extractDir);
2692
- execFileSync("tar", [
2699
+ await execFileAsync$1("tar", [
2693
2700
  "-xzf",
2694
2701
  tgzPath,
2695
2702
  "-C",
@@ -2697,20 +2704,20 @@ function installPackageFromNpmSync(packageName, targetDir) {
2697
2704
  ], { timeout: 3e4 });
2698
2705
  const packageSubDir = path$40.join(extractDir, "package");
2699
2706
  const srcPkgJsonDir = fs$17.existsSync(path$40.join(packageSubDir, "package.json")) ? packageSubDir : extractDir;
2700
- fs$17.rmSync(targetDir, {
2707
+ await fs$17.promises.rm(targetDir, {
2701
2708
  recursive: true,
2702
2709
  force: true
2703
2710
  });
2704
2711
  ensureDir(targetDir);
2705
- fs$17.copyFileSync(path$40.join(srcPkgJsonDir, "package.json"), path$40.join(targetDir, "package.json"));
2712
+ await fs$17.promises.copyFile(path$40.join(srcPkgJsonDir, "package.json"), path$40.join(targetDir, "package.json"));
2706
2713
  const distSrc = path$40.join(srcPkgJsonDir, "dist");
2707
- if (fs$17.existsSync(distSrc)) copyDirRecursive(distSrc, path$40.join(targetDir, "dist"));
2714
+ if (fs$17.existsSync(distSrc)) await copyDirRecursive(distSrc, path$40.join(targetDir, "dist"));
2708
2715
  try {
2709
- const npmPkg = asJsonObject(parseJsonUnknown(fs$17.readFileSync(path$40.join(srcPkgJsonDir, "package.json"), "utf-8")));
2710
- if (npmPkg) copyExtraFileDirs(npmPkg, srcPkgJsonDir, targetDir);
2716
+ const npmPkg = asJsonObject(parseJsonUnknown(await fs$17.promises.readFile(path$40.join(srcPkgJsonDir, "package.json"), "utf-8")));
2717
+ if (npmPkg) await copyExtraFileDirs(npmPkg, srcPkgJsonDir, targetDir);
2711
2718
  } catch {}
2712
2719
  } finally {
2713
- fs$17.rmSync(tmpDir, {
2720
+ await fs$17.promises.rm(tmpDir, {
2714
2721
  recursive: true,
2715
2722
  force: true
2716
2723
  });
@@ -3160,15 +3167,15 @@ var AddonInstaller = class AddonInstaller {
3160
3167
  await this.ensureBuilt(packageName, sourceDir, pkgData);
3161
3168
  const distDir = path$40.join(sourceDir, "dist");
3162
3169
  if (!fs$17.existsSync(distDir)) throw new Error(`${packageName} has no dist/ after build`);
3163
- fs$17.rmSync(targetDir, {
3170
+ await fs$17.promises.rm(targetDir, {
3164
3171
  recursive: true,
3165
3172
  force: true
3166
3173
  });
3167
3174
  ensureDir(targetDir);
3168
- fs$17.writeFileSync(path$40.join(targetDir, "package.json"), JSON.stringify(stripCamstackDeps(pkgData), null, 2));
3169
- copyDirRecursive(distDir, path$40.join(targetDir, "dist"));
3170
- copyExtraFileDirs(pkgData, sourceDir, targetDir);
3171
- fs$17.writeFileSync(path$40.join(targetDir, ".install-source"), "local");
3175
+ await fs$17.promises.writeFile(path$40.join(targetDir, "package.json"), JSON.stringify(stripCamstackDeps(pkgData), null, 2));
3176
+ await copyDirRecursive(distDir, path$40.join(targetDir, "dist"));
3177
+ await copyExtraFileDirs(pkgData, sourceDir, targetDir);
3178
+ await fs$17.promises.writeFile(path$40.join(targetDir, ".install-source"), "local");
3172
3179
  const localPkgVersion = asString(pkgData.version, "0.0.0");
3173
3180
  this.manifest.upsert(packageName, {
3174
3181
  version: localPkgVersion,
@@ -3288,17 +3295,17 @@ var AddonInstaller = class AddonInstaller {
3288
3295
  if (!pkgView) throw new Error(`Invalid package.json at ${pkgJsonPath}`);
3289
3296
  if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
3290
3297
  const targetDir = path$40.join(this.addonsDir, pkgView.name);
3291
- fs$17.rmSync(targetDir, {
3298
+ await fs$17.promises.rm(targetDir, {
3292
3299
  recursive: true,
3293
3300
  force: true
3294
3301
  });
3295
3302
  ensureDir(targetDir);
3296
3303
  const sourceDir = path$40.dirname(pkgJsonPath);
3297
3304
  const strippedManifest = stripCamstackDeps(pkgView.raw);
3298
- fs$17.writeFileSync(path$40.join(targetDir, "package.json"), JSON.stringify(strippedManifest, null, 2));
3305
+ await fs$17.promises.writeFile(path$40.join(targetDir, "package.json"), JSON.stringify(strippedManifest, null, 2));
3299
3306
  const sourceDist = path$40.join(sourceDir, "dist");
3300
- if (fs$17.existsSync(sourceDist)) copyDirRecursive(sourceDist, path$40.join(targetDir, "dist"));
3301
- copyExtraFileDirs(pkgView.raw, sourceDir, targetDir);
3307
+ if (fs$17.existsSync(sourceDist)) await copyDirRecursive(sourceDist, path$40.join(targetDir, "dist"));
3308
+ await copyExtraFileDirs(pkgView.raw, sourceDir, targetDir);
3302
3309
  const strippedRuntimeDeps = strippedManifest["dependencies"];
3303
3310
  if (strippedRuntimeDeps != null && typeof strippedRuntimeDeps === "object" && Object.keys(strippedRuntimeDeps).length > 0) {
3304
3311
  this.logger.info(`${pkgView.name} — installing runtime dependencies`, { meta: { targetDir } });
@@ -3322,7 +3329,7 @@ var AddonInstaller = class AddonInstaller {
3322
3329
  try {
3323
3330
  await installManifestNativeDeps(targetDir, pkgView.raw, this.logger, this.registry);
3324
3331
  } catch (nativeErr) {
3325
- fs$17.rmSync(targetDir, {
3332
+ await fs$17.promises.rm(targetDir, {
3326
3333
  recursive: true,
3327
3334
  force: true
3328
3335
  });
@@ -3337,7 +3344,7 @@ var AddonInstaller = class AddonInstaller {
3337
3344
  version: pkgView.version
3338
3345
  };
3339
3346
  } finally {
3340
- fs$17.rmSync(tmpDir, {
3347
+ await fs$17.promises.rm(tmpDir, {
3341
3348
  recursive: true,
3342
3349
  force: true
3343
3350
  });
@@ -4105,7 +4112,7 @@ var CapabilityRegistry = class CapabilityRegistry {
4105
4112
  const bare = this.bareAddonId(addonId);
4106
4113
  const nodeMap = this.singletonNodeOverrides.get(capabilityName);
4107
4114
  if (nodeMap) {
4108
- for (const [nodeId, ov] of [...nodeMap]) if (ov === bare) nodeMap.delete(nodeId);
4115
+ for (const [nodeId, ov] of Array.from(nodeMap)) if (ov === bare) nodeMap.delete(nodeId);
4109
4116
  if (nodeMap.size === 0) this.singletonNodeOverrides.delete(capabilityName);
4110
4117
  }
4111
4118
  this.logger.info("Provider unregistered from capability", {
@@ -7382,20 +7389,20 @@ var ConfigManager = class ConfigManager {
7382
7389
  setSettingsStore(store) {
7383
7390
  this.settingsStore = store;
7384
7391
  }
7385
- get(path) {
7386
- return this.resolveConfigValue(path);
7392
+ get(configPath) {
7393
+ return this.resolveConfigValue(configPath);
7387
7394
  }
7388
- resolveConfigValue(path) {
7389
- const bootstrapValue = this.getFromBootstrap(path);
7395
+ resolveConfigValue(configPath) {
7396
+ const bootstrapValue = this.getFromBootstrap(configPath);
7390
7397
  if (bootstrapValue !== void 0) return bootstrapValue;
7391
7398
  if (this.settingsStore !== null) {
7392
- const storeValue = this.settingsStore.getSystem(path);
7399
+ const storeValue = this.settingsStore.getSystem(configPath);
7393
7400
  if (storeValue !== void 0) return storeValue;
7394
- const nested = this.getNestedFromSystemSettings(path);
7401
+ const nested = this.getNestedFromSystemSettings(configPath);
7395
7402
  if (nested !== null) return nested;
7396
7403
  }
7397
- if (path in RUNTIME_DEFAULTS) return RUNTIME_DEFAULTS[path];
7398
- return this.getFromRuntimeDefaults(path) ?? void 0;
7404
+ if (configPath in RUNTIME_DEFAULTS) return RUNTIME_DEFAULTS[configPath];
7405
+ return this.getFromRuntimeDefaults(configPath) ?? void 0;
7399
7406
  }
7400
7407
  /**
7401
7408
  * Write a value to the settings-store.
@@ -7611,8 +7618,8 @@ var ConfigManager = class ConfigManager {
7611
7618
  };
7612
7619
  this.saveRuntimeState();
7613
7620
  }
7614
- getBootstrap(path) {
7615
- return this.getFromBootstrap(path);
7621
+ getBootstrap(configPath) {
7622
+ return this.getFromBootstrap(configPath);
7616
7623
  }
7617
7624
  /** Features accessor -- reads from settings-store when available, falls back to RUNTIME_DEFAULTS */
7618
7625
  get features() {
@@ -7702,8 +7709,8 @@ var ConfigManager = class ConfigManager {
7702
7709
  * Deep-set a value in a nested plain object using a dot-notation path.
7703
7710
  * Returns a new object (immutable).
7704
7711
  */
7705
- setNested(obj, path, value) {
7706
- const [head, ...rest] = path.split(".");
7712
+ setNested(obj, configPath, value) {
7713
+ const [head, ...rest] = configPath.split(".");
7707
7714
  if (!head) return obj;
7708
7715
  if (rest.length === 0) return {
7709
7716
  ...obj,
@@ -7743,8 +7750,8 @@ var ConfigManager = class ConfigManager {
7743
7750
  warnDefaultCredentials() {
7744
7751
  if (this.bootstrapConfig.auth.adminPassword === "changeme") console.warn("[ConfigManager] Warning: Using default admin password \"changeme\". Set auth.adminPassword in your config.yaml or the CAMSTACK_ADMIN_PASS env var.");
7745
7752
  }
7746
- getFromBootstrap(path) {
7747
- const keys = path.split(".");
7753
+ getFromBootstrap(configPath) {
7754
+ const keys = configPath.split(".");
7748
7755
  let current = this.bootstrapConfig;
7749
7756
  for (const key of keys) {
7750
7757
  if (!isRecord(current)) return void 0;
@@ -7752,8 +7759,8 @@ var ConfigManager = class ConfigManager {
7752
7759
  }
7753
7760
  return current;
7754
7761
  }
7755
- getFromRuntimeDefaults(path) {
7756
- const prefix = path + ".";
7762
+ getFromRuntimeDefaults(configPath) {
7763
+ const prefix = configPath + ".";
7757
7764
  const result = {};
7758
7765
  let found = false;
7759
7766
  for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) if (key.startsWith(prefix)) {
@@ -7768,10 +7775,10 @@ var ConfigManager = class ConfigManager {
7768
7775
  * e.g. path='features' matches keys 'features.streaming', 'features.notifications', etc.
7769
7776
  * Returns an object keyed by the sub-key, or undefined if nothing is found.
7770
7777
  */
7771
- getNestedFromSystemSettings(path) {
7778
+ getNestedFromSystemSettings(configPath) {
7772
7779
  if (this.settingsStore === null) return null;
7773
7780
  const all = this.settingsStore.getAllSystem();
7774
- const prefix = path + ".";
7781
+ const prefix = configPath + ".";
7775
7782
  const result = {};
7776
7783
  let found = false;
7777
7784
  for (const [key, value] of Object.entries(all)) if (key.startsWith(prefix)) {
@@ -90932,7 +90939,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
90932
90939
  respawned.restartCount = prevRestartCount + 1;
90933
90940
  });
90934
90941
  restarted.push(name);
90935
- } catch (err) {
90942
+ } catch {
90936
90943
  failed.push(name);
90937
90944
  }
90938
90945
  return {
@@ -91327,8 +91334,8 @@ var LifecycleJobEngine = class {
91327
91334
  } finally {
91328
91335
  clearTimeout(timer);
91329
91336
  }
91330
- if (packages.length === 0) throw new Error("stageFramework returned no packages");
91331
91337
  const [firstPackage] = packages;
91338
+ if (!firstPackage) throw new Error("stageFramework returned no packages");
91332
91339
  this.advance(job.jobId, task, "staged", { stagedPath: firstPackage.stagedPath });
91333
91340
  requestFrameworkSwap({
91334
91341
  jobId: job.jobId,
@@ -91351,14 +91358,15 @@ var LifecycleJobEngine = class {
91351
91358
  */
91352
91359
  async fetchAddonsBounded(job, addonTasks) {
91353
91360
  const limit = Math.max(1, this.deps.fetchConcurrency ?? DEFAULT_FETCH_CONCURRENCY);
91354
- const outcomes = new Array(addonTasks.length);
91361
+ const outcomes = Array.from({ length: addonTasks.length });
91355
91362
  let nextIndex = 0;
91356
91363
  const worker = async () => {
91357
91364
  for (;;) {
91358
91365
  const index = nextIndex;
91359
91366
  nextIndex += 1;
91360
- if (index >= addonTasks.length) return;
91361
- outcomes[index] = await this.fetchAddonTask(job, addonTasks[index]);
91367
+ const addonTask = addonTasks[index];
91368
+ if (addonTask === void 0) return;
91369
+ outcomes[index] = await this.fetchAddonTask(job, addonTask);
91362
91370
  }
91363
91371
  };
91364
91372
  const workerCount = Math.min(limit, addonTasks.length);
@@ -91484,4 +91492,4 @@ async function stageFrameworkLockstep(input) {
91484
91492
  return results;
91485
91493
  }
91486
91494
  //#endregion
91487
- export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FRAMEWORK_LOCKSTEP, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterSecretMatches, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubService, createHwAccelService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpmSync, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isClusterSecretMismatchError, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, stageFrameworkLockstep, stripCamstackDeps, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
91495
+ export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FRAMEWORK_LOCKSTEP, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterSecretMatches, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubService, createHwAccelService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensurePython, ensureTlsCert, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isClusterSecretMismatchError, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, stageFrameworkLockstep, stripCamstackDeps, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
@@ -79,8 +79,8 @@ export declare class ConfigManager {
79
79
  * as a convenience alias for legacy call sites; it performs NO runtime
80
80
  * check and is the one documented type-level bridge in this function.
81
81
  */
82
- get(path: string): unknown;
83
- get<T>(path: string): T | undefined;
82
+ get(configPath: string): unknown;
83
+ get<T>(configPath: string): T | undefined;
84
84
  private resolveConfigValue;
85
85
  /**
86
86
  * Write a value to the settings-store.
@@ -171,8 +171,8 @@ export declare class ConfigManager {
171
171
  /** Get a value from the parsed bootstrap config.
172
172
  * Generic overload is a documented type-level bridge — callers are responsible
173
173
  * for passing a T that matches the config.yaml shape. */
174
- getBootstrap(path: string): unknown;
175
- getBootstrap<T>(path: string): T | undefined;
174
+ getBootstrap(configPath: string): unknown;
175
+ getBootstrap<T>(configPath: string): T | undefined;
176
176
  /** Features accessor -- reads from settings-store when available, falls back to RUNTIME_DEFAULTS */
177
177
  get features(): FeatureManifest;
178
178
  /**
@@ -4,10 +4,16 @@
4
4
  */
5
5
  export declare function ensureDir(dirPath: string): void;
6
6
  /**
7
- * Copy a directory recursively.
8
- * Single source of truth — extracted from addon-installer + first-boot-installer.
7
+ * Copy a directory recursively — ASYNC so it never blocks the event loop.
8
+ *
9
+ * The hub installs addons into `/data/addons`, which on Unraid is a slow
10
+ * shfs/FUSE mount. The former synchronous `fs.copyFileSync`-per-file loop
11
+ * blocked the Node event loop for the whole copy of a large bundle (e.g.
12
+ * `addon-pipeline`), which froze the hub's HTTP listener mid-`camstack deploy`
13
+ * (accept backlog piling up, existing connections stuck in CLOSE_WAIT). Using
14
+ * `fs.promises.cp` keeps the I/O off the event loop. (Node ≥18 stable.)
9
15
  */
10
- export declare function copyDirRecursive(src: string, dest: string): void;
16
+ export declare function copyDirRecursive(src: string, dest: string): Promise<void>;
11
17
  /**
12
18
  * Strip @camstack/* dependencies and devDependencies from a package.json object.
13
19
  * Used when installing addons into the addons directory — @camstack packages
@@ -21,7 +27,7 @@ export declare function stripCamstackDeps(pkg: Record<string, unknown>): Record<
21
27
  * Copies directories (not individual files) from source to destination.
22
28
  * Skips "dist" (already handled) and glob patterns.
23
29
  */
24
- export declare function copyExtraFileDirs(pkgJson: Record<string, unknown>, sourceDir: string, destDir: string): void;
30
+ export declare function copyExtraFileDirs(pkgJson: Record<string, unknown>, sourceDir: string, destDir: string): Promise<void>;
25
31
  /**
26
32
  * Check if any file in src/ is newer than dist/.
27
33
  * Returns true if rebuild is needed.
@@ -36,6 +42,10 @@ export declare function ensureLibraryBuilt(packageName: string, packagesDir: str
36
42
  /**
37
43
  * Install a single npm package into a target directory (package.json + dist/).
38
44
  * No validation on camstack.addons -- works for any @camstack/* package.
39
- * Uses synchronous child_process calls (suitable for first-boot and update paths).
45
+ *
46
+ * ASYNC: uses `execFile`/`fs.promises` throughout so a package install on a
47
+ * live node never blocks the event loop (the `npm pack` + tar extract + copy to
48
+ * the slow shfs/FUSE `/data` would otherwise freeze the HTTP listener). See
49
+ * `copyDirRecursive` for the wedge this prevents.
40
50
  */
41
- export declare function installPackageFromNpmSync(packageName: string, targetDir: string): void;
51
+ export declare function installPackageFromNpm(packageName: string, targetDir: string): Promise<void>;
@@ -5,7 +5,7 @@ export type { AddonInstallerConfig, InstallSource, InstalledPackage } from './ad
5
5
  export { AddonInstaller } from './addon-installer.js';
6
6
  export type { AddonInstallSource, AddonManifestEntry, AddonManifestFile } from './addon-manifest.js';
7
7
  export { AddonManifest } from './addon-manifest.js';
8
- export { copyDirRecursive, ensureDir, stripCamstackDeps, copyExtraFileDirs, ensureLibraryBuilt, installPackageFromNpmSync, isSourceNewer, } from './fs-utils.js';
8
+ export { copyDirRecursive, ensureDir, stripCamstackDeps, copyExtraFileDirs, ensureLibraryBuilt, installPackageFromNpm, isSourceNewer, } from './fs-utils.js';
9
9
  export { detectWorkspacePackagesDir } from './workspace-detect.js';
10
10
  export { RESTART_MARKER_FILE, clearPendingRestart, getRestartMarkerPath, readPendingRestart, scheduleSelfRestart, writePendingRestart, } from './restart-coordinator.js';
11
11
  export type { PendingRestartMarker, RestartKind, ScheduleSelfRestartOptions, } from './restart-coordinator.js';
@@ -23,6 +23,14 @@ export interface CapCallOutMessage {
23
23
  readonly method: string;
24
24
  readonly args: unknown;
25
25
  readonly deviceId?: number;
26
+ /**
27
+ * Optional per-call node pin (out-of-band; NOT part of the validated method
28
+ * args). When set, the parent routes this singleton/in-process cap call to
29
+ * the named node's provider instead of the default (hub) resolution — used to
30
+ * query a remote agent's in-process singleton (e.g. the agent's own
31
+ * `platform-probe` hardware). Set via `ctx.api`'s `onNode(nodeId)` modifier.
32
+ */
33
+ readonly nodeId?: string;
26
34
  }
27
35
  /** Child → parent: a system event produced by the child to be forwarded to the hub event bus. */
28
36
  export interface ChildEventMessage {
@@ -59,6 +67,8 @@ export interface CapCallMessage {
59
67
  readonly method: string;
60
68
  readonly args: unknown;
61
69
  readonly deviceId?: number;
70
+ /** Optional per-call node pin (out-of-band routing hint; see {@link CapCallOutMessage.nodeId}). */
71
+ readonly nodeId?: string;
62
72
  }
63
73
  /** Parent → child: a system event delivered by the hub for the child to handle locally. */
64
74
  export interface ParentEventMessage {
@@ -5,6 +5,7 @@ let node_fs = require("node:fs");
5
5
  node_fs = require_chunk.__toESM(node_fs);
6
6
  let node_path = require("node:path");
7
7
  node_path = require_chunk.__toESM(node_path);
8
+ let _camstack_types = require("@camstack/types");
8
9
  let node_crypto = require("node:crypto");
9
10
  node_crypto = require_chunk.__toESM(node_crypto);
10
11
  let node_child_process = require("node:child_process");
@@ -3647,7 +3648,8 @@ var LocalChildRegistry = class {
3647
3648
  capName: out.capName,
3648
3649
  method: out.method,
3649
3650
  args: out.args,
3650
- ...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {}
3651
+ ...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
3652
+ ...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {}
3651
3653
  };
3652
3654
  if (this.resolveChildId(out.capName, out.deviceId) !== null) {
3653
3655
  if (!this.egressRoutedCaps.has(out.capName)) {
@@ -3892,7 +3894,8 @@ var LocalChildClient = class {
3892
3894
  capName: input.capName,
3893
3895
  method: input.method,
3894
3896
  args: input.args,
3895
- ...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {}
3897
+ ...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
3898
+ ...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
3896
3899
  };
3897
3900
  return this.channel.request(msg);
3898
3901
  }
@@ -4586,7 +4589,10 @@ function createParentUnownedCallHandler(deps) {
4586
4589
  const deviceId = input.deviceId ?? extractDeviceId(input.args);
4587
4590
  const resolver = deps.getResolver();
4588
4591
  if (resolver !== null) try {
4589
- const route = resolver.resolveCapRoute(input.capName, deviceId !== void 0 ? { deviceId } : {});
4592
+ const route = resolver.resolveCapRoute(input.capName, {
4593
+ ...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
4594
+ ...deviceId !== void 0 ? { deviceId } : {}
4595
+ });
4590
4596
  return await resolver.dispatch(route, input.method, input.args);
4591
4597
  } catch (err) {
4592
4598
  if (!(err instanceof CapRouteError) || err.reason !== "no-provider") throw err;
@@ -4623,7 +4629,7 @@ function createParentUnownedCallHandler(deps) {
4623
4629
  return await brokerCallForCap(deps.broker, input.capName, input.method, input.args);
4624
4630
  } catch (cause) {
4625
4631
  const reason = cause instanceof Error ? cause.message : String(cause);
4626
- throw new Error(`parent could not route child unowned cap call "${input.capName}.${input.method}": ${reason}`, cause instanceof Error ? { cause } : void 0);
4632
+ throw new Error(`parent could not route child unowned cap call "${input.capName}.${input.method}": ${reason}`, { cause });
4627
4633
  }
4628
4634
  };
4629
4635
  }
@@ -5031,11 +5037,13 @@ function ipcParentLink(getCallOut) {
5031
5037
  const input = op.input;
5032
5038
  const inputObj = input && typeof input === "object" && !Array.isArray(input) ? input : null;
5033
5039
  const deviceId = inputObj && typeof Reflect.get(inputObj, "deviceId") === "number" ? Reflect.get(inputObj, "deviceId") : void 0;
5040
+ const pinnedNodeId = (0, _camstack_types.readNodePin)(op.context);
5034
5041
  const capCallInput = {
5035
5042
  capName: parsed.capName,
5036
5043
  method: parsed.method,
5037
5044
  args: op.input,
5038
- ...deviceId !== void 0 ? { deviceId } : {}
5045
+ ...deviceId !== void 0 ? { deviceId } : {},
5046
+ ...pinnedNodeId !== void 0 ? { nodeId: pinnedNodeId } : {}
5039
5047
  };
5040
5048
  return observable((observer) => {
5041
5049
  callOut(capCallInput).then((data) => {
@@ -5100,7 +5108,7 @@ var CapUsageRegistry = class {
5100
5108
  let window = byCap.get(rec.capName);
5101
5109
  if (!window) {
5102
5110
  window = {
5103
- buckets: new Array(this.retentionSeconds).fill(0),
5111
+ buckets: Array.from({ length: this.retentionSeconds }, () => 0),
5104
5112
  lastCallAtMs: 0
5105
5113
  };
5106
5114
  byCap.set(rec.capName, window);
@@ -6277,16 +6285,25 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
6277
6285
  };
6278
6286
  const writeBlob = async (coll, key, value, namespace) => {
6279
6287
  if (!settingsApi) return;
6280
- await settingsApi.set.mutate(namespace !== void 0 ? {
6281
- namespace,
6282
- collection: coll,
6283
- key,
6284
- value
6285
- } : {
6286
- collection: coll,
6287
- key,
6288
- value
6289
- });
6288
+ try {
6289
+ await settingsApi.set.mutate(namespace !== void 0 ? {
6290
+ namespace,
6291
+ collection: coll,
6292
+ key,
6293
+ value
6294
+ } : {
6295
+ collection: coll,
6296
+ key,
6297
+ value
6298
+ });
6299
+ } catch (err) {
6300
+ const isTrpcError = err instanceof _trpc_client.TRPCClientError;
6301
+ scopedLogger.warn(`settings write failed (${coll}/${key}) — ${isTrpcError ? "transport error" : "store error"}`, { meta: {
6302
+ collection: coll,
6303
+ key,
6304
+ error: (0, _camstack_types_addon.errMsg)(err)
6305
+ } });
6306
+ }
6290
6307
  };
6291
6308
  const rmwChains = /* @__PURE__ */ new Map();
6292
6309
  const serializeRmw = (coll, key, op) => {
@@ -6295,7 +6312,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
6295
6312
  rmwChains.set(chainKey, next);
6296
6313
  next.finally(() => {
6297
6314
  if (rmwChains.get(chainKey) === next) rmwChains.delete(chainKey);
6298
- });
6315
+ }).catch(() => void 0);
6299
6316
  return next;
6300
6317
  };
6301
6318
  const settingsView = {