@camstack/system 1.0.6 → 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.
Files changed (33) hide show
  1. package/dist/addon-runner.js +40 -23
  2. package/dist/addon-runner.mjs +20 -4
  3. package/dist/addon-utils.d.ts +20 -0
  4. package/dist/addon-utils.js +11 -0
  5. package/dist/addon-utils.mjs +3 -0
  6. package/dist/builtins/device-manager/device-manager.addon.js +8 -8
  7. package/dist/builtins/device-manager/device-manager.addon.mjs +8 -8
  8. package/dist/builtins/native-metrics/native-metrics.addon.d.ts +8 -0
  9. package/dist/builtins/native-metrics/native-metrics.addon.js +50 -3
  10. package/dist/builtins/native-metrics/native-metrics.addon.mjs +50 -3
  11. package/dist/builtins/platform-probe/index.js +27 -139
  12. package/dist/builtins/platform-probe/index.mjs +28 -140
  13. package/dist/builtins/platform-probe/platform-scorer.d.ts +17 -10
  14. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +2 -2
  15. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +2 -2
  16. package/dist/custom-action-registry-BEXwC-oo.mjs +38 -0
  17. package/dist/custom-action-registry-vLYEFTtv.js +43 -0
  18. package/dist/index.js +129 -779
  19. package/dist/index.mjs +100 -750
  20. package/dist/kernel/config-manager.d.ts +4 -4
  21. package/dist/kernel/fs-utils.d.ts +16 -6
  22. package/dist/kernel/index.d.ts +1 -1
  23. package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -1
  24. package/dist/kernel/moleculer/readiness-context.d.ts +2 -1
  25. package/dist/kernel/transport/child-cap-protocol.d.ts +10 -0
  26. package/dist/{manifest-python-deps-B4BmMoGT.js → manifest-python-deps-BWURo7dc.js} +62 -88
  27. package/dist/{manifest-python-deps-CXbKrOdk.mjs → manifest-python-deps-BcrTzHH_.mjs} +55 -75
  28. package/dist/model-download-service-C7AjBsX9.mjs +668 -0
  29. package/dist/model-download-service-JtVQtbb6.js +752 -0
  30. package/dist/process/resource-monitor.d.ts +9 -0
  31. package/dist/{resource-monitor-ClDGFyf6.mjs → resource-monitor-BkP504Vq.mjs} +20 -1
  32. package/dist/{resource-monitor-IIEanuJt.js → resource-monitor-DNNomR-i.js} +21 -1
  33. package/package.json +6 -1
@@ -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';
@@ -1,4 +1,5 @@
1
- import { deviceOpsCapability, AddonApi, CapabilityDefinition, DeviceManagerApi, IDevice, IEventBus, IKernelStreamProbe, IScopedLogger, InferProvider } from '@camstack/types';
1
+ import { deviceOpsCapability } from '@camstack/types/addon';
2
+ import { AddonApi, CapabilityDefinition, DeviceManagerApi, IDevice, IEventBus, IKernelStreamProbe, IScopedLogger, InferProvider } from '@camstack/types';
2
3
  import { ServiceBroker } from 'moleculer';
3
4
  import { DeviceRegistry } from '../device-registry.js';
4
5
  import { CapabilityRegistry } from '../capability-registry.js';
@@ -1,5 +1,6 @@
1
1
  import { ServiceBroker } from 'moleculer';
2
- import { IEventBus, IScopedLogger, ReadinessRegistry } from '@camstack/types';
2
+ import { IEventBus, IScopedLogger } from '@camstack/types';
3
+ import { ReadinessRegistry } from '@camstack/types/addon';
3
4
  /**
4
5
  * Minimal interface for the `LocalChildClient` surface used in UDS-mode
5
6
  * readiness hydration. Declared here (rather than importing the concrete
@@ -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 {
@@ -13,6 +13,7 @@ let node_util = require("node:util");
13
13
  let node_os = require("node:os");
14
14
  node_os = require_chunk.__toESM(node_os);
15
15
  let node_fs_promises = require("node:fs/promises");
16
+ let _camstack_types_addon = require("@camstack/types/addon");
16
17
  let _trpc_client = require("@trpc/client");
17
18
  let node_net = require("node:net");
18
19
  //#region src/kernel/addon-class-resolver.ts
@@ -103,7 +104,7 @@ async function installManifestNativeDeps(addonDir, pkgRaw, logger, registry) {
103
104
  } catch (err) {
104
105
  logger.warn("Failed to write native deps marker", { meta: {
105
106
  markerFile,
106
- error: (0, _camstack_types.errMsg)(err)
107
+ error: (0, _camstack_types_addon.errMsg)(err)
107
108
  } });
108
109
  }
109
110
  return;
@@ -130,7 +131,7 @@ async function installManifestNativeDeps(addonDir, pkgRaw, logger, registry) {
130
131
  timeout: 3e5
131
132
  });
132
133
  } catch (err) {
133
- throw new Error(`npm install of native deps failed for ${addonDir}: ${(0, _camstack_types.errMsg)(err)}`, { cause: err });
134
+ throw new Error(`npm install of native deps failed for ${addonDir}: ${(0, _camstack_types_addon.errMsg)(err)}`, { cause: err });
134
135
  }
135
136
  await rebuildNativeDeps(addonDir, pending.map(([name]) => name), logger);
136
137
  try {
@@ -138,7 +139,7 @@ async function installManifestNativeDeps(addonDir, pkgRaw, logger, registry) {
138
139
  } catch (err) {
139
140
  logger.warn("Failed to write native deps marker", { meta: {
140
141
  markerFile,
141
- error: (0, _camstack_types.errMsg)(err)
142
+ error: (0, _camstack_types_addon.errMsg)(err)
142
143
  } });
143
144
  }
144
145
  }
@@ -175,7 +176,7 @@ function copyPrebuiltNativeDep(name, addonDir, logger) {
175
176
  logger.warn("Prebuilt native dep copy failed — will try npm install", { meta: {
176
177
  name,
177
178
  source,
178
- error: (0, _camstack_types.errMsg)(err)
179
+ error: (0, _camstack_types_addon.errMsg)(err)
179
180
  } });
180
181
  }
181
182
  }
@@ -208,13 +209,13 @@ function candidateHoistedDirs(name, addonDir) {
208
209
  }
209
210
  /** Read & validate `camstack.nativeDependencies`. Returns null when absent. */
210
211
  function readNativeDeps(pkgRaw) {
211
- const camstack = (0, _camstack_types.asJsonObject)(pkgRaw["camstack"]);
212
+ const camstack = (0, _camstack_types_addon.asJsonObject)(pkgRaw["camstack"]);
212
213
  if (!camstack) return null;
213
- const native = (0, _camstack_types.asJsonObject)(camstack["nativeDependencies"]);
214
+ const native = (0, _camstack_types_addon.asJsonObject)(camstack["nativeDependencies"]);
214
215
  if (!native) return null;
215
216
  const out = {};
216
217
  for (const [k, v] of Object.entries(native)) {
217
- const range = (0, _camstack_types.asString)(v);
218
+ const range = (0, _camstack_types_addon.asString)(v);
218
219
  if (range) out[k] = range;
219
220
  }
220
221
  return Object.keys(out).length > 0 ? out : null;
@@ -281,7 +282,7 @@ async function rebuildNativeDeps(addonDir, packageNames, logger) {
281
282
  } catch (err) {
282
283
  logger.warn("Electron rebuild failed (continuing — prebuilt binary may be present)", { meta: {
283
284
  addonDir,
284
- error: (0, _camstack_types.errMsg)(err)
285
+ error: (0, _camstack_types_addon.errMsg)(err)
285
286
  } });
286
287
  }
287
288
  return;
@@ -299,7 +300,7 @@ async function rebuildNativeDeps(addonDir, packageNames, logger) {
299
300
  } catch (err) {
300
301
  logger.warn("npm rebuild failed (continuing — prebuilt binary may be present)", { meta: {
301
302
  addonDir,
302
- error: (0, _camstack_types.errMsg)(err)
303
+ error: (0, _camstack_types_addon.errMsg)(err)
303
304
  } });
304
305
  }
305
306
  }
@@ -382,43 +383,6 @@ var CapabilityHandle = class {
382
383
  }
383
384
  };
384
385
  //#endregion
385
- //#region src/kernel/custom-action-registry.ts
386
- /**
387
- * CustomActionRegistry — per-process registry of addon custom actions.
388
- *
389
- * Populated at boot from each addon's `AddonInitResult.customActions` +
390
- * `handleCustomAction` handler. Rejects actions declared with scope other
391
- * than 'system' (today only 'system' is runtime-supported; the descriptor
392
- * allows future scopes for forward compat).
393
- */
394
- var CustomActionRegistry = class {
395
- byAddon = /* @__PURE__ */ new Map();
396
- registerAddon(addonId, catalog, handler) {
397
- const actions = /* @__PURE__ */ new Map();
398
- for (const [name, spec] of Object.entries(catalog)) {
399
- const scope = spec.scope ?? { kind: "system" };
400
- if (scope.kind !== "system") throw new Error(`custom action '${addonId}.${name}' declared scope '${scope.kind}' — not yet implemented`);
401
- actions.set(name, {
402
- spec,
403
- handler: (input) => handler(name, input)
404
- });
405
- }
406
- this.byAddon.set(addonId, actions);
407
- }
408
- unregisterAddon(addonId) {
409
- this.byAddon.delete(addonId);
410
- }
411
- resolve(addonId, action) {
412
- return this.byAddon.get(addonId)?.get(action) ?? null;
413
- }
414
- listActions(addonId) {
415
- return [...this.byAddon.get(addonId)?.keys() ?? []];
416
- }
417
- listAddons() {
418
- return [...this.byAddon.keys()];
419
- }
420
- };
421
- //#endregion
422
386
  //#region src/kernel/device-registry.ts
423
387
  var DeviceRegistry = class {
424
388
  /** Primary map: every registered device keyed by its progressive
@@ -963,7 +927,7 @@ var nativeCapReadinessGeneration = typeof crypto !== "undefined" && crypto.rando
963
927
  function mountNativeCapService(broker, addonId, cap) {
964
928
  const serviceName = capServiceName(addonId, cap.name, true);
965
929
  if (mountedNativeCapServices.has(serviceName)) return;
966
- const expandedMethods = (0, _camstack_types.expandCapMethods)(cap);
930
+ const expandedMethods = (0, _camstack_types_addon.expandCapMethods)(cap);
967
931
  if (expandedMethods === void 0 || Object.keys(expandedMethods).length === 0 || typeof broker.createService !== "function") {
968
932
  mountedNativeCapServices.add(serviceName);
969
933
  return;
@@ -1069,7 +1033,7 @@ async function callWithServiceDiscoveryRetry(broker, actionName, input) {
1069
1033
  if (!(err instanceof Error ? err.message : String(err)).includes("is not found")) throw err;
1070
1034
  attempt++;
1071
1035
  if (attempt === 1) broker.logger.warn(`native-cap-bridge: service not found for "${actionName}", retrying with exponential backoff…`);
1072
- await (0, _camstack_types.sleep)(delay);
1036
+ await (0, _camstack_types_addon.sleep)(delay);
1073
1037
  delay = Math.min(delay * 2, BACKOFF_MAX_MS);
1074
1038
  }
1075
1039
  }
@@ -1174,7 +1138,7 @@ function createBrokerDeviceManagerApi(opts) {
1174
1138
  parentDeviceId,
1175
1139
  logger: (() => {
1176
1140
  const base = opts.logger.child(stableId);
1177
- const containerDeviceId = parentDeviceId ?? (deviceMeta?.type === _camstack_types.DeviceType.Container ? id : null);
1141
+ const containerDeviceId = parentDeviceId ?? (deviceMeta?.type === _camstack_types_addon.DeviceType.Container ? id : null);
1178
1142
  return base.withTags(containerDeviceId !== null ? {
1179
1143
  deviceId: id,
1180
1144
  containerDeviceId
@@ -1200,7 +1164,7 @@ function createBrokerDeviceManagerApi(opts) {
1200
1164
  id,
1201
1165
  stableId,
1202
1166
  addonId,
1203
- type: _camstack_types.DeviceType.Camera,
1167
+ type: _camstack_types_addon.DeviceType.Camera,
1204
1168
  name: stableId,
1205
1169
  location: null,
1206
1170
  disabled: false,
@@ -1208,7 +1172,7 @@ function createBrokerDeviceManagerApi(opts) {
1208
1172
  metadata: null
1209
1173
  },
1210
1174
  fetchDevice: async (deviceId) => {
1211
- return (0, _camstack_types.createDeviceProxy)(api, await api.deviceManager.getBindings.query({ deviceId }));
1175
+ return (0, _camstack_types_addon.createDeviceProxy)(api, await api.deviceManager.getBindings.query({ deviceId }));
1212
1176
  },
1213
1177
  get devices() {
1214
1178
  return selfApi;
@@ -1266,7 +1230,7 @@ function createBrokerDeviceManagerApi(opts) {
1266
1230
  type: "device",
1267
1231
  id
1268
1232
  },
1269
- category: _camstack_types.EventCategory.DeviceBindingsChanged,
1233
+ category: _camstack_types_addon.EventCategory.DeviceBindingsChanged,
1270
1234
  data: {
1271
1235
  deviceId: id,
1272
1236
  capName: cap.name,
@@ -1275,7 +1239,7 @@ function createBrokerDeviceManagerApi(opts) {
1275
1239
  nodeId
1276
1240
  }
1277
1241
  });
1278
- (0, _camstack_types.emitReadiness)(eventBus, {
1242
+ (0, _camstack_types_addon.emitReadiness)(eventBus, {
1279
1243
  capName: cap.name,
1280
1244
  scope: {
1281
1245
  type: "device",
@@ -1513,7 +1477,7 @@ function createBrokerDeviceManagerApi(opts) {
1513
1477
  },
1514
1478
  register: async (device) => {
1515
1479
  registry.register(addonId, device);
1516
- buildContext(device.stableId, device.id, device.parentDeviceId).registerNativeCap(_camstack_types.deviceOpsCapability, buildDeviceOpsProvider(device));
1480
+ buildContext(device.stableId, device.id, device.parentDeviceId).registerNativeCap(_camstack_types_addon.deviceOpsCapability, buildDeviceOpsProvider(device));
1517
1481
  try {
1518
1482
  await callDeviceManager(api, "registerDevice", {
1519
1483
  addonId,
@@ -1650,7 +1614,7 @@ function createBrokerDeviceManagerApi(opts) {
1650
1614
  type: "device",
1651
1615
  id: device.id
1652
1616
  },
1653
- category: _camstack_types.EventCategory.DeviceReady,
1617
+ category: _camstack_types_addon.EventCategory.DeviceReady,
1654
1618
  data: { deviceId: device.id }
1655
1619
  });
1656
1620
  };
@@ -1789,7 +1753,7 @@ function createBrokerDeviceManagerApi(opts) {
1789
1753
  type: "device",
1790
1754
  id: deviceId
1791
1755
  },
1792
- category: _camstack_types.EventCategory.DeviceBindingsChanged,
1756
+ category: _camstack_types_addon.EventCategory.DeviceBindingsChanged,
1793
1757
  data: {
1794
1758
  deviceId,
1795
1759
  capName,
@@ -1798,7 +1762,7 @@ function createBrokerDeviceManagerApi(opts) {
1798
1762
  nodeId
1799
1763
  }
1800
1764
  });
1801
- (0, _camstack_types.emitReadiness)(eventBus, {
1765
+ (0, _camstack_types_addon.emitReadiness)(eventBus, {
1802
1766
  capName,
1803
1767
  scope: {
1804
1768
  type: "device",
@@ -3684,7 +3648,8 @@ var LocalChildRegistry = class {
3684
3648
  capName: out.capName,
3685
3649
  method: out.method,
3686
3650
  args: out.args,
3687
- ...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {}
3651
+ ...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
3652
+ ...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {}
3688
3653
  };
3689
3654
  if (this.resolveChildId(out.capName, out.deviceId) !== null) {
3690
3655
  if (!this.egressRoutedCaps.has(out.capName)) {
@@ -3929,7 +3894,8 @@ var LocalChildClient = class {
3929
3894
  capName: input.capName,
3930
3895
  method: input.method,
3931
3896
  args: input.args,
3932
- ...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {}
3897
+ ...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
3898
+ ...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
3933
3899
  };
3934
3900
  return this.channel.request(msg);
3935
3901
  }
@@ -4623,7 +4589,10 @@ function createParentUnownedCallHandler(deps) {
4623
4589
  const deviceId = input.deviceId ?? extractDeviceId(input.args);
4624
4590
  const resolver = deps.getResolver();
4625
4591
  if (resolver !== null) try {
4626
- 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
+ });
4627
4596
  return await resolver.dispatch(route, input.method, input.args);
4628
4597
  } catch (err) {
4629
4598
  if (!(err instanceof CapRouteError) || err.reason !== "no-provider") throw err;
@@ -4660,7 +4629,7 @@ function createParentUnownedCallHandler(deps) {
4660
4629
  return await brokerCallForCap(deps.broker, input.capName, input.method, input.args);
4661
4630
  } catch (cause) {
4662
4631
  const reason = cause instanceof Error ? cause.message : String(cause);
4663
- 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 });
4664
4633
  }
4665
4634
  };
4666
4635
  }
@@ -5068,11 +5037,13 @@ function ipcParentLink(getCallOut) {
5068
5037
  const input = op.input;
5069
5038
  const inputObj = input && typeof input === "object" && !Array.isArray(input) ? input : null;
5070
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);
5071
5041
  const capCallInput = {
5072
5042
  capName: parsed.capName,
5073
5043
  method: parsed.method,
5074
5044
  args: op.input,
5075
- ...deviceId !== void 0 ? { deviceId } : {}
5045
+ ...deviceId !== void 0 ? { deviceId } : {},
5046
+ ...pinnedNodeId !== void 0 ? { nodeId: pinnedNodeId } : {}
5076
5047
  };
5077
5048
  return observable((observer) => {
5078
5049
  callOut(capCallInput).then((data) => {
@@ -5137,7 +5108,7 @@ var CapUsageRegistry = class {
5137
5108
  let window = byCap.get(rec.capName);
5138
5109
  if (!window) {
5139
5110
  window = {
5140
- buckets: new Array(this.retentionSeconds).fill(0),
5111
+ buckets: Array.from({ length: this.retentionSeconds }, () => 0),
5141
5112
  lastCallAtMs: 0
5142
5113
  };
5143
5114
  byCap.set(rec.capName, window);
@@ -5357,7 +5328,7 @@ function createAddonDataPlaneFacility(args) {
5357
5328
  let server = null;
5358
5329
  let baseUrl = "";
5359
5330
  const route = (req, res) => {
5360
- if (req.headers[_camstack_types.DATAPLANE_SECRET_HEADER] !== secret) {
5331
+ if (req.headers[_camstack_types_addon.DATAPLANE_SECRET_HEADER] !== secret) {
5361
5332
  res.writeHead(403).end();
5362
5333
  return;
5363
5334
  }
@@ -5902,7 +5873,7 @@ function getOrInitReadinessRegistry(broker, eventBus, logger) {
5902
5873
  const bkr = broker;
5903
5874
  const existing = brokerReadinessRegistries.get(broker);
5904
5875
  if (existing) return existing;
5905
- const registry = new _camstack_types.ReadinessRegistry({
5876
+ const registry = new _camstack_types_addon.ReadinessRegistry({
5906
5877
  eventBus,
5907
5878
  sourceNodeId: bkr.nodeID,
5908
5879
  logger
@@ -5969,7 +5940,7 @@ function getOrInitReadinessRegistry(broker, eventBus, logger) {
5969
5940
  function getOrInitReadinessRegistryForClient(client, eventBus, logger, nodeId) {
5970
5941
  const existing = clientReadinessRegistries.get(nodeId);
5971
5942
  if (existing) return existing;
5972
- const registry = new _camstack_types.ReadinessRegistry({
5943
+ const registry = new _camstack_types_addon.ReadinessRegistry({
5973
5944
  eventBus,
5974
5945
  sourceNodeId: nodeId,
5975
5946
  logger
@@ -6282,12 +6253,12 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
6282
6253
  const api = (0, _trpc_client.createTRPCClient)({ links });
6283
6254
  const scopedLogger = options?.createLogger?.(addonId) ?? (runtime.mode === "broker" ? createRemoteLogger(runtime.broker, addonId) : createUdsLogger(runtime.client, addonId, nodeId));
6284
6255
  const scopedEventBus = runtime.mode === "broker" ? createBrokerEventBus(runtime.broker, addonId) : createUdsEventBus(runtime.client, addonId);
6285
- const workerDisposerChain = new _camstack_types.DisposerChain({ onError: (err, index) => {
6256
+ const workerDisposerChain = new _camstack_types_addon.DisposerChain({ onError: (err, index) => {
6286
6257
  scopedLogger.error(`Disposer #${index} threw during teardown`, { meta: { error: err instanceof Error ? err.message : String(err) } });
6287
6258
  } });
6288
6259
  registerWorkerDisposerChain(nodeId, addonId, workerDisposerChain);
6289
6260
  const bindingCache = /* @__PURE__ */ new Map();
6290
- scopedEventBus.subscribe({ category: _camstack_types.EventCategory.DeviceBindingsChanged }, (e) => {
6261
+ scopedEventBus.subscribe({ category: _camstack_types_addon.EventCategory.DeviceBindingsChanged }, (e) => {
6291
6262
  const data = e.data ?? {};
6292
6263
  if (typeof data.deviceId === "number") {
6293
6264
  bindingCache.delete(data.deviceId);
@@ -6314,16 +6285,25 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
6314
6285
  };
6315
6286
  const writeBlob = async (coll, key, value, namespace) => {
6316
6287
  if (!settingsApi) return;
6317
- await settingsApi.set.mutate(namespace !== void 0 ? {
6318
- namespace,
6319
- collection: coll,
6320
- key,
6321
- value
6322
- } : {
6323
- collection: coll,
6324
- key,
6325
- value
6326
- });
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
+ }
6327
6307
  };
6328
6308
  const rmwChains = /* @__PURE__ */ new Map();
6329
6309
  const serializeRmw = (coll, key, op) => {
@@ -6332,7 +6312,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
6332
6312
  rmwChains.set(chainKey, next);
6333
6313
  next.finally(() => {
6334
6314
  if (rmwChains.get(chainKey) === next) rmwChains.delete(chainKey);
6335
- });
6315
+ }).catch(() => void 0);
6336
6316
  return next;
6337
6317
  };
6338
6318
  const settingsView = {
@@ -6386,7 +6366,7 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
6386
6366
  const readinessRegistry = () => runtime.mode === "broker" ? getOrInitReadinessRegistry(runtime.broker, scopedEventBus, scopedLogger) : getOrInitReadinessRegistryForClient(runtime.client, scopedEventBus, scopedLogger, nodeId);
6387
6367
  const capHandleCache = /* @__PURE__ */ new Map();
6388
6368
  function getOrCreateHandle(capName, scope, timeoutMs) {
6389
- const key = `${capName}::${(0, _camstack_types.scopeKey)(scope)}`;
6369
+ const key = `${capName}::${(0, _camstack_types_addon.scopeKey)(scope)}`;
6390
6370
  const existing = capHandleCache.get(key);
6391
6371
  if (existing) return existing;
6392
6372
  const handle = new CapabilityHandle(capName, scope, readinessRegistry(), timeoutMs);
@@ -6482,10 +6462,10 @@ async function buildAddonContext(runtime, declaration, dataDir, options) {
6482
6462
  },
6483
6463
  fetchDevice: async (deviceId) => {
6484
6464
  const cached = bindingCache.get(deviceId);
6485
- if (cached) return (0, _camstack_types.createDeviceProxy)(api, cached);
6465
+ if (cached) return (0, _camstack_types_addon.createDeviceProxy)(api, cached);
6486
6466
  const binding = await api.deviceManager.getBindings.query({ deviceId });
6487
6467
  bindingCache.set(deviceId, binding);
6488
- return (0, _camstack_types.createDeviceProxy)(api, binding);
6468
+ return (0, _camstack_types_addon.createDeviceProxy)(api, binding);
6489
6469
  },
6490
6470
  useCapability(capName, scope = { type: "global" }) {
6491
6471
  return getOrCreateHandle(capName, scope, Number.POSITIVE_INFINITY);
@@ -6615,12 +6595,6 @@ Object.defineProperty(exports, "CapabilityUnavailableError", {
6615
6595
  return CapabilityUnavailableError;
6616
6596
  }
6617
6597
  });
6618
- Object.defineProperty(exports, "CustomActionRegistry", {
6619
- enumerable: true,
6620
- get: function() {
6621
- return CustomActionRegistry;
6622
- }
6623
- });
6624
6598
  Object.defineProperty(exports, "DeviceRegistry", {
6625
6599
  enumerable: true,
6626
6600
  get: function() {