@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
@@ -1,11 +1,14 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_manifest_python_deps = require("./manifest-python-deps-B4BmMoGT.js");
2
+ const require_manifest_python_deps = require("./manifest-python-deps-BWURo7dc.js");
3
+ const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
3
4
  let node_fs = require("node:fs");
4
5
  node_fs = require_chunk.__toESM(node_fs);
5
6
  let node_path = require("node:path");
6
7
  node_path = require_chunk.__toESM(node_path);
7
- let _camstack_types = require("@camstack/types");
8
+ let _camstack_types_addon = require("@camstack/types/addon");
8
9
  let node_url = require("node:url");
10
+ let node_v8 = require("node:v8");
11
+ node_v8 = require_chunk.__toESM(node_v8);
9
12
  let node_module = require("node:module");
10
13
  //#region src/kernel/moleculer/worker-device-restore.ts
11
14
  /**
@@ -41,7 +44,7 @@ async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNo
41
44
  return;
42
45
  }
43
46
  const shared = context.kernel?.readinessRegistry ?? null;
44
- const registry = shared ?? new _camstack_types.ReadinessRegistry({
47
+ const registry = shared ?? new _camstack_types_addon.ReadinessRegistry({
45
48
  eventBus: bus,
46
49
  sourceNodeId,
47
50
  logger: context.logger
@@ -54,13 +57,13 @@ async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNo
54
57
  }, { timeoutMs: 6e4 });
55
58
  log?.debug?.(`[worker-restore] "${addonId}": device-manager READY`);
56
59
  } catch (err) {
57
- if (err instanceof _camstack_types.ReadinessTimeoutError) {
60
+ if (err instanceof _camstack_types_addon.ReadinessTimeoutError) {
58
61
  context.logger?.warn?.(`[worker-restore] device-manager not ready within ${err.waitedMs}ms — skipping for "${addonId}"`);
59
62
  return;
60
63
  }
61
64
  throw err;
62
65
  } finally {
63
- if (!shared && registry instanceof _camstack_types.ReadinessRegistry) registry.close();
66
+ if (!shared && registry instanceof _camstack_types_addon.ReadinessRegistry) registry.close();
64
67
  }
65
68
  try {
66
69
  const deviceManager = Reflect.get(api, "deviceManager");
@@ -86,7 +89,7 @@ async function runWorkerDeviceRestoreWithRetry(addon, context, addonId, sourceNo
86
89
  await restoreFn.call(addon, savedDevices);
87
90
  log?.info?.(`[worker-restore] "${addonId}": restored ${savedDevices.length} device(s)`);
88
91
  } catch (err) {
89
- log?.warn?.(`[worker-restore] "${addonId}": restoreDevices threw: ${(0, _camstack_types.errMsg)(err)}`);
92
+ log?.warn?.(`[worker-restore] "${addonId}": restoreDevices threw: ${(0, _camstack_types_addon.errMsg)(err)}`);
90
93
  }
91
94
  }
92
95
  //#endregion
@@ -167,10 +170,10 @@ function toSerializableRoute(route) {
167
170
  */
168
171
  async function dispatchSettings(addon, addonId, method, args) {
169
172
  switch (method) {
170
- case "getGlobalSettings": return typeof addon.getGlobalSettings === "function" ? await addon.getGlobalSettings(args.overlay, args.cap) ?? null : null;
173
+ case "getGlobalSettings": return typeof addon.getGlobalSettings === "function" ? await addon.getGlobalSettings(args.overlay, args.cap, args.nodeId) ?? null : null;
171
174
  case "updateGlobalSettings":
172
175
  if (typeof addon.updateGlobalSettings !== "function") throw new Error(`child-addon-call: addon "${addonId}" does not implement updateGlobalSettings`);
173
- await addon.updateGlobalSettings(args.patch ?? {});
176
+ await addon.updateGlobalSettings(args.patch ?? {}, args.nodeId);
174
177
  return { success: true };
175
178
  case "getDeviceSettings": return typeof addon.getDeviceSettings === "function" && typeof args.deviceId === "number" ? await addon.getDeviceSettings(args.deviceId) ?? null : null;
176
179
  case "updateDeviceSettings":
@@ -272,11 +275,11 @@ function createChildAddonCallDispatch(deps) {
272
275
  * every loaded addon before closing the UDS channel.
273
276
  */
274
277
  process.on("uncaughtException", (err) => {
275
- console.error(`[addon-runner CRASH] uncaughtException: ${(0, _camstack_types.errMsg)(err)}\n${err instanceof Error ? err.stack ?? "" : ""}`);
278
+ console.error(`[addon-runner CRASH] uncaughtException: ${(0, _camstack_types_addon.errMsg)(err)}\n${err instanceof Error ? err.stack ?? "" : ""}`);
276
279
  process.exit(1);
277
280
  });
278
281
  process.on("unhandledRejection", (reason) => {
279
- console.error(`[addon-runner CRASH] unhandledRejection: ${(0, _camstack_types.errMsg)(reason)}\n${reason instanceof Error ? reason.stack ?? "" : ""}`);
282
+ console.error(`[addon-runner CRASH] unhandledRejection: ${(0, _camstack_types_addon.errMsg)(reason)}\n${reason instanceof Error ? reason.stack ?? "" : ""}`);
280
283
  });
281
284
  process.on("exit", (code) => {
282
285
  console.error(`[addon-runner EXIT] code=${String(code)} nodeId=${process.env["CAMSTACK_RUNNER_ID"] ?? "?"}`);
@@ -297,7 +300,7 @@ function parseAddonsSpec(raw) {
297
300
  try {
298
301
  parsed = JSON.parse(raw);
299
302
  } catch (err) {
300
- throw new Error(`[addon-runner] CAMSTACK_RUNNER_ADDONS is not valid JSON: ${(0, _camstack_types.errMsg)(err)}`, { cause: err });
303
+ throw new Error(`[addon-runner] CAMSTACK_RUNNER_ADDONS is not valid JSON: ${(0, _camstack_types_addon.errMsg)(err)}`, { cause: err });
301
304
  }
302
305
  if (!Array.isArray(parsed)) throw new Error(`[addon-runner] CAMSTACK_RUNNER_ADDONS must be a JSON array`);
303
306
  const specs = [];
@@ -370,7 +373,7 @@ async function main() {
370
373
  try {
371
374
  specs = parseAddonsSpec(addonsJson);
372
375
  } catch (err) {
373
- console.error((0, _camstack_types.errMsg)(err));
376
+ console.error((0, _camstack_types_addon.errMsg)(err));
374
377
  process.exit(1);
375
378
  }
376
379
  if (specs.length === 0) {
@@ -393,7 +396,7 @@ async function main() {
393
396
  AddonClass
394
397
  });
395
398
  } catch (err) {
396
- console.error(`[addon-runner] Failed to load "${spec.addonId}": ${(0, _camstack_types.errMsg)(err)}`);
399
+ console.error(`[addon-runner] Failed to load "${spec.addonId}": ${(0, _camstack_types_addon.errMsg)(err)}`);
397
400
  process.exit(1);
398
401
  }
399
402
  let storageProvider;
@@ -463,7 +466,7 @@ async function main() {
463
466
  * removed `custom.<action>` Moleculer action). Constructed up front so the
464
467
  * addon-call handler can close over it at client-construction time.
465
468
  */
466
- const customActionRegistry = new require_manifest_python_deps.CustomActionRegistry();
469
+ const customActionRegistry = new require_custom_action_registry.CustomActionRegistry();
467
470
  let shuttingDown = false;
468
471
  const udsChildClient = new require_manifest_python_deps.LocalChildClient({
469
472
  nodeId: parentNodeId,
@@ -488,12 +491,12 @@ async function main() {
488
491
  try {
489
492
  await udsChildClient.start();
490
493
  } catch (err) {
491
- console.error(`[addon-runner] "${nodeId}" UDS connect failed — exiting: ${(0, _camstack_types.errMsg)(err)}`);
494
+ console.error(`[addon-runner] "${nodeId}" UDS connect failed — exiting: ${(0, _camstack_types_addon.errMsg)(err)}`);
492
495
  process.exit(1);
493
496
  }
494
497
  runnerLog.info("UDS child transport connected", { meta: { childId: runnerId } });
495
498
  if (storageProvider) runnerLog.info(`"${nodeId}" storage provider rooted at ${dataDir}`);
496
- else runnerLog.warn(`"${nodeId}" failed to load FilesystemStorageProvider`, { meta: { error: (0, _camstack_types.errMsg)(storageProviderError) } });
499
+ else runnerLog.warn(`"${nodeId}" failed to load FilesystemStorageProvider`, { meta: { error: (0, _camstack_types_addon.errMsg)(storageProviderError) } });
497
500
  const refreshChildCaps = (reason) => {
498
501
  const freshDescriptors = buildChildCapDescriptors(runnerManifest, require_manifest_python_deps.getWorkerNativeCapSnapshot());
499
502
  udsChildClient.updateCaps(freshDescriptors).catch((err) => {
@@ -523,12 +526,12 @@ async function main() {
523
526
  const pkgRaw = JSON.parse(node_fs.readFileSync(node_path.join(spec.addonDir, "package.json"), "utf-8"));
524
527
  await require_manifest_python_deps.installManifestNativeDeps(spec.addonDir, pkgRaw, context.logger);
525
528
  } catch (nativeErr) {
526
- console.error(`[addon-runner] native deps install failed for "${spec.addonId}": ${(0, _camstack_types.errMsg)(nativeErr)}`);
529
+ console.error(`[addon-runner] native deps install failed for "${spec.addonId}": ${(0, _camstack_types_addon.errMsg)(nativeErr)}`);
527
530
  process.exit(1);
528
531
  }
529
532
  await require_manifest_python_deps.installManifestPythonDeps(declaration, spec.addonDir, context.deps, context.logger);
530
533
  const registeredProviders = /* @__PURE__ */ new Map();
531
- const initResult = (0, _camstack_types.normalizeAddonInitResult)(await addon.initialize(context));
534
+ const initResult = (0, _camstack_types_addon.normalizeAddonInitResult)(await addon.initialize(context));
532
535
  for (const reg of initResult?.providers ?? []) {
533
536
  const capName = reg.capability.name;
534
537
  registeredProviders.set(capName, reg.provider);
@@ -564,7 +567,7 @@ async function main() {
564
567
  initResult: initResult ?? null
565
568
  });
566
569
  } catch (err) {
567
- console.error(`[addon-runner] Init failed for "${spec.addonId}": ${(0, _camstack_types.errMsg)(err)}`);
570
+ console.error(`[addon-runner] Init failed for "${spec.addonId}": ${(0, _camstack_types_addon.errMsg)(err)}`);
568
571
  process.exit(1);
569
572
  }
570
573
  if (deviceRestorePromises.length > 0 && !shuttingDown) await Promise.allSettled(deviceRestorePromises);
@@ -574,7 +577,7 @@ async function main() {
574
577
  if (hubReachableFired) return;
575
578
  hubReachableFired = true;
576
579
  for (const entry of loaded) if (typeof entry.addon.onHubReachable === "function") Promise.resolve(entry.addon.onHubReachable()).catch((err) => {
577
- runnerLog.error(`"${entry.addonId}" onHubReachable() threw`, { meta: { error: (0, _camstack_types.errMsg)(err) } });
580
+ runnerLog.error(`"${entry.addonId}" onHubReachable() threw`, { meta: { error: (0, _camstack_types_addon.errMsg)(err) } });
578
581
  });
579
582
  };
580
583
  udsChildClient.onConnected(() => {
@@ -584,6 +587,20 @@ async function main() {
584
587
  fireHubReachableOnce();
585
588
  runnerLog.info(`"${nodeId}" started — ${loaded.length} addon(s) ready (UDS connected)`);
586
589
  process.stdout.write(`READY:${nodeId}\n`);
590
+ process.on("SIGUSR2", () => {
591
+ const MB = (x) => Math.round(x / 1048576 * 10) / 10;
592
+ try {
593
+ const mu = process.memoryUsage();
594
+ const spaces = node_v8.getHeapSpaceStatistics().filter((s) => s.space_used_size > 0).map((s) => `${s.space_name}=${MB(s.space_used_size)}`).join(" ");
595
+ const addons = loaded.map((e) => e.addonId).join(",");
596
+ runnerLog.info(`[mem] node="${nodeId}" addons=[${addons}] rss=${MB(mu.rss)} heapUsed=${MB(mu.heapUsed)} heapTotal=${MB(mu.heapTotal)} external=${MB(mu.external)} arrayBuffers=${MB(mu.arrayBuffers)} | ${spaces}`);
597
+ const snapPath = `/tmp/heap-${nodeId.replace(/[^\w.-]/g, "_")}.heapsnapshot`;
598
+ node_v8.writeHeapSnapshot(snapPath);
599
+ runnerLog.info(`[mem] node="${nodeId}" heap snapshot written -> ${snapPath}`);
600
+ } catch (err) {
601
+ runnerLog.warn(`[mem] snapshot failed: ${(0, _camstack_types_addon.errMsg)(err)}`);
602
+ }
603
+ });
587
604
  const shutdown = async () => {
588
605
  shuttingDown = true;
589
606
  require_manifest_python_deps.setWorkerNativeCapsChangeListener(null);
@@ -595,12 +612,12 @@ async function main() {
595
612
  for (const entry of loaded) try {
596
613
  await entry.addon.shutdown();
597
614
  } catch (err) {
598
- runnerLog.error(`"${entry.addonId}" shutdown threw`, { meta: { error: (0, _camstack_types.errMsg)(err) } });
615
+ runnerLog.error(`"${entry.addonId}" shutdown threw`, { meta: { error: (0, _camstack_types_addon.errMsg)(err) } });
599
616
  }
600
617
  try {
601
618
  await udsChildClient.close();
602
619
  } catch (err) {
603
- runnerLog.warn("UDS child transport close threw during shutdown", { meta: { error: (0, _camstack_types.errMsg)(err) } });
620
+ runnerLog.warn("UDS child transport close threw during shutdown", { meta: { error: (0, _camstack_types_addon.errMsg)(err) } });
604
621
  }
605
622
  process.exit(0);
606
623
  };
@@ -619,7 +636,7 @@ async function main() {
619
636
  }, 5e3);
620
637
  }
621
638
  main().catch((err) => {
622
- console.error(`[addon-runner] Fatal error: ${(0, _camstack_types.errMsg)(err)}`);
639
+ console.error(`[addon-runner] Fatal error: ${(0, _camstack_types_addon.errMsg)(err)}`);
623
640
  process.exit(1);
624
641
  });
625
642
  //#endregion
@@ -1,9 +1,11 @@
1
- import { $ as setWorkerNativeCapsChangeListener, A as createUdsLoggerWithControl, L as LocalChildClient, X as getWorkerNativeCapProvider, Z as getWorkerNativeCapSnapshot, dt as CustomActionRegistry, ht as resolveAddonClass, i as createUdsAddonContext, mt as installManifestNativeDeps, t as installManifestPythonDeps, tt as validateProviderRegistrations } from "./manifest-python-deps-CXbKrOdk.mjs";
1
+ import { $ as setWorkerNativeCapsChangeListener, A as createUdsLoggerWithControl, L as LocalChildClient, X as getWorkerNativeCapProvider, Z as getWorkerNativeCapSnapshot, i as createUdsAddonContext, mt as resolveAddonClass, pt as installManifestNativeDeps, t as installManifestPythonDeps, tt as validateProviderRegistrations } from "./manifest-python-deps-BcrTzHH_.mjs";
2
+ import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
2
3
  import { register } from "node:module";
3
4
  import * as fs from "node:fs";
4
5
  import * as path$1 from "node:path";
5
- import { ReadinessRegistry, ReadinessTimeoutError, errMsg, normalizeAddonInitResult } from "@camstack/types";
6
+ import { ReadinessRegistry, ReadinessTimeoutError, errMsg, normalizeAddonInitResult } from "@camstack/types/addon";
6
7
  import { pathToFileURL } from "node:url";
8
+ import * as v8 from "node:v8";
7
9
  //#region src/kernel/moleculer/worker-device-restore.ts
8
10
  /**
9
11
  * Worker-side device restore helper.
@@ -164,10 +166,10 @@ function toSerializableRoute(route) {
164
166
  */
165
167
  async function dispatchSettings(addon, addonId, method, args) {
166
168
  switch (method) {
167
- case "getGlobalSettings": return typeof addon.getGlobalSettings === "function" ? await addon.getGlobalSettings(args.overlay, args.cap) ?? null : null;
169
+ case "getGlobalSettings": return typeof addon.getGlobalSettings === "function" ? await addon.getGlobalSettings(args.overlay, args.cap, args.nodeId) ?? null : null;
168
170
  case "updateGlobalSettings":
169
171
  if (typeof addon.updateGlobalSettings !== "function") throw new Error(`child-addon-call: addon "${addonId}" does not implement updateGlobalSettings`);
170
- await addon.updateGlobalSettings(args.patch ?? {});
172
+ await addon.updateGlobalSettings(args.patch ?? {}, args.nodeId);
171
173
  return { success: true };
172
174
  case "getDeviceSettings": return typeof addon.getDeviceSettings === "function" && typeof args.deviceId === "number" ? await addon.getDeviceSettings(args.deviceId) ?? null : null;
173
175
  case "updateDeviceSettings":
@@ -581,6 +583,20 @@ async function main() {
581
583
  fireHubReachableOnce();
582
584
  runnerLog.info(`"${nodeId}" started — ${loaded.length} addon(s) ready (UDS connected)`);
583
585
  process.stdout.write(`READY:${nodeId}\n`);
586
+ process.on("SIGUSR2", () => {
587
+ const MB = (x) => Math.round(x / 1048576 * 10) / 10;
588
+ try {
589
+ const mu = process.memoryUsage();
590
+ const spaces = v8.getHeapSpaceStatistics().filter((s) => s.space_used_size > 0).map((s) => `${s.space_name}=${MB(s.space_used_size)}`).join(" ");
591
+ const addons = loaded.map((e) => e.addonId).join(",");
592
+ runnerLog.info(`[mem] node="${nodeId}" addons=[${addons}] rss=${MB(mu.rss)} heapUsed=${MB(mu.heapUsed)} heapTotal=${MB(mu.heapTotal)} external=${MB(mu.external)} arrayBuffers=${MB(mu.arrayBuffers)} | ${spaces}`);
593
+ const snapPath = `/tmp/heap-${nodeId.replace(/[^\w.-]/g, "_")}.heapsnapshot`;
594
+ v8.writeHeapSnapshot(snapPath);
595
+ runnerLog.info(`[mem] node="${nodeId}" heap snapshot written -> ${snapPath}`);
596
+ } catch (err) {
597
+ runnerLog.warn(`[mem] snapshot failed: ${errMsg(err)}`);
598
+ }
599
+ });
584
600
  const shutdown = async () => {
585
601
  shuttingDown = true;
586
602
  setWorkerNativeCapsChangeListener(null);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Lightweight addon-facing utility surface for `@camstack/system`.
3
+ *
4
+ * Importing the root barrel (`@camstack/system`) loads the ENTIRE framework —
5
+ * the kernel (moleculer) + every builtin addon (sqlite-storage, winston-logging,
6
+ * device-manager, …) — ~64MB of V8 heap PER process, even when an addon only
7
+ * needs a model-download or data-plane helper. Node has no runtime tree-shaking
8
+ * and the package is host-provided (loaded as the prebuilt barrel), so every
9
+ * addon that imported one util from the barrel paid the full ~64MB — multiplied
10
+ * across every forked pipeline runner.
11
+ *
12
+ * This subpath re-exports ONLY the addon-facing helpers, each from its DEEP,
13
+ * kernel-free source module, so importing it costs a few MB instead of ~64MB.
14
+ *
15
+ * See memory: camstack_runner_ram_and_build_rootcause.
16
+ */
17
+ export { downloadFile, ensureModel, isModelDownloaded, deleteModelFromDisk, } from './download/model-downloader.js';
18
+ export { ModelDownloadService } from './download/model-download-service.js';
19
+ export { createFileDataPlaneHandler } from './http/file-data-plane.js';
20
+ export { CustomActionRegistry } from './kernel/custom-action-registry.js';
@@ -0,0 +1,11 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ require("./chunk-Cek0wNdY.js");
3
+ const require_model_download_service = require("./model-download-service-JtVQtbb6.js");
4
+ const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
5
+ exports.CustomActionRegistry = require_custom_action_registry.CustomActionRegistry;
6
+ exports.ModelDownloadService = require_model_download_service.ModelDownloadService;
7
+ exports.createFileDataPlaneHandler = require_model_download_service.createFileDataPlaneHandler;
8
+ exports.deleteModelFromDisk = require_model_download_service.deleteModelFromDisk;
9
+ exports.downloadFile = require_model_download_service.downloadFile;
10
+ exports.ensureModel = require_model_download_service.ensureModel;
11
+ exports.isModelDownloaded = require_model_download_service.isModelDownloaded;
@@ -0,0 +1,3 @@
1
+ import { a as ensureModel, c as isModelDownloaded, l as createFileDataPlaneHandler, n as deleteModelFromDisk, r as downloadFile, t as ModelDownloadService } from "./model-download-service-C7AjBsX9.mjs";
2
+ import { t as CustomActionRegistry } from "./custom-action-registry-BEXwC-oo.mjs";
3
+ export { CustomActionRegistry, ModelDownloadService, createFileDataPlaneHandler, deleteModelFromDisk, downloadFile, ensureModel, isModelDownloaded };
@@ -1638,8 +1638,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1638
1638
  if (!persisted) return null;
1639
1639
  const links = (persisted.meta.deviceLinks ?? []).filter((l) => l.target.cap === cap);
1640
1640
  if (links.length === 0) return null;
1641
- const registry = this.capabilityRegistry;
1642
- const schema = registry?.getDefinition(cap)?.status?.schema;
1641
+ const capRegistry = this.capabilityRegistry;
1642
+ const schema = capRegistry?.getDefinition(cap)?.status?.schema;
1643
1643
  if (!schema) return null;
1644
1644
  const allMeta = await readMeta();
1645
1645
  let containerStableId = persisted.stableId;
@@ -1655,7 +1655,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1655
1655
  const srcId = resolveSourceDeviceId(containerStableId, link.source.sourceKey, allMeta);
1656
1656
  if (srcId === null) continue;
1657
1657
  try {
1658
- const srcProvider = registry?.getProviderForDevice(link.source.cap, srcId);
1658
+ const srcProvider = capRegistry?.getProviderForDevice(link.source.cap, srcId);
1659
1659
  const raw = (0, _camstack_types.getByPath)(typeof srcProvider?.getStatus === "function" ? await srcProvider.getStatus({ deviceId: srcId }) : void 0, link.source.fieldPath);
1660
1660
  resolved.push({
1661
1661
  link,
@@ -1840,7 +1840,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
1840
1840
  this.capabilityRegistry?.unregisterAllNativeForDevice(deviceId);
1841
1841
  idToAddonId.delete(deviceId);
1842
1842
  this.devicesWithLinks.delete(deviceId);
1843
- for (const key of this.lastEmittedOverlay.keys()) if (key.startsWith(`${deviceId}:`)) this.lastEmittedOverlay.delete(key);
1843
+ for (const overlayKey of this.lastEmittedOverlay.keys()) if (overlayKey.startsWith(`${deviceId}:`)) this.lastEmittedOverlay.delete(overlayKey);
1844
1844
  await rebuildLinkDependents();
1845
1845
  this.ctx.logger.info("removed device", { tags: {
1846
1846
  deviceId,
@@ -2386,7 +2386,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2386
2386
  listLocations: async () => {
2387
2387
  const store = await settings.readAddonStore();
2388
2388
  const meta = store.deviceMeta ?? {};
2389
- const registry = store.locations ?? [];
2389
+ const locations = store.locations ?? [];
2390
2390
  const seen = /* @__PURE__ */ new Map();
2391
2391
  const consider = (raw) => {
2392
2392
  if (typeof raw !== "string") return;
@@ -2395,7 +2395,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2395
2395
  const key = trimmed.toLowerCase();
2396
2396
  if (!seen.has(key)) seen.set(key, trimmed);
2397
2397
  };
2398
- for (const label of registry) consider(label);
2398
+ for (const label of locations) consider(label);
2399
2399
  for (const m of Object.values(meta)) consider(m.location);
2400
2400
  return [...seen.values()].toSorted((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
2401
2401
  },
@@ -2960,9 +2960,9 @@ var DeviceManagerAddon = class DeviceManagerAddon extends _camstack_types.BaseAd
2960
2960
  return this.getDeviceAggregate(input.deviceId, "live");
2961
2961
  },
2962
2962
  getDeviceAggregate: async (input) => {
2963
- const [settings, live] = await Promise.all([this.getDeviceAggregate(input.deviceId, "settings"), this.getDeviceAggregate(input.deviceId, "live")]);
2963
+ const [settingsAggregate, live] = await Promise.all([this.getDeviceAggregate(input.deviceId, "settings"), this.getDeviceAggregate(input.deviceId, "live")]);
2964
2964
  return {
2965
- settings,
2965
+ settings: settingsAggregate,
2966
2966
  live
2967
2967
  };
2968
2968
  },
@@ -1633,8 +1633,8 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1633
1633
  if (!persisted) return null;
1634
1634
  const links = (persisted.meta.deviceLinks ?? []).filter((l) => l.target.cap === cap);
1635
1635
  if (links.length === 0) return null;
1636
- const registry = this.capabilityRegistry;
1637
- const schema = registry?.getDefinition(cap)?.status?.schema;
1636
+ const capRegistry = this.capabilityRegistry;
1637
+ const schema = capRegistry?.getDefinition(cap)?.status?.schema;
1638
1638
  if (!schema) return null;
1639
1639
  const allMeta = await readMeta();
1640
1640
  let containerStableId = persisted.stableId;
@@ -1650,7 +1650,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1650
1650
  const srcId = resolveSourceDeviceId(containerStableId, link.source.sourceKey, allMeta);
1651
1651
  if (srcId === null) continue;
1652
1652
  try {
1653
- const srcProvider = registry?.getProviderForDevice(link.source.cap, srcId);
1653
+ const srcProvider = capRegistry?.getProviderForDevice(link.source.cap, srcId);
1654
1654
  const raw = getByPath(typeof srcProvider?.getStatus === "function" ? await srcProvider.getStatus({ deviceId: srcId }) : void 0, link.source.fieldPath);
1655
1655
  resolved.push({
1656
1656
  link,
@@ -1835,7 +1835,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
1835
1835
  this.capabilityRegistry?.unregisterAllNativeForDevice(deviceId);
1836
1836
  idToAddonId.delete(deviceId);
1837
1837
  this.devicesWithLinks.delete(deviceId);
1838
- for (const key of this.lastEmittedOverlay.keys()) if (key.startsWith(`${deviceId}:`)) this.lastEmittedOverlay.delete(key);
1838
+ for (const overlayKey of this.lastEmittedOverlay.keys()) if (overlayKey.startsWith(`${deviceId}:`)) this.lastEmittedOverlay.delete(overlayKey);
1839
1839
  await rebuildLinkDependents();
1840
1840
  this.ctx.logger.info("removed device", { tags: {
1841
1841
  deviceId,
@@ -2381,7 +2381,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2381
2381
  listLocations: async () => {
2382
2382
  const store = await settings.readAddonStore();
2383
2383
  const meta = store.deviceMeta ?? {};
2384
- const registry = store.locations ?? [];
2384
+ const locations = store.locations ?? [];
2385
2385
  const seen = /* @__PURE__ */ new Map();
2386
2386
  const consider = (raw) => {
2387
2387
  if (typeof raw !== "string") return;
@@ -2390,7 +2390,7 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2390
2390
  const key = trimmed.toLowerCase();
2391
2391
  if (!seen.has(key)) seen.set(key, trimmed);
2392
2392
  };
2393
- for (const label of registry) consider(label);
2393
+ for (const label of locations) consider(label);
2394
2394
  for (const m of Object.values(meta)) consider(m.location);
2395
2395
  return [...seen.values()].toSorted((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
2396
2396
  },
@@ -2955,9 +2955,9 @@ var DeviceManagerAddon = class DeviceManagerAddon extends BaseAddon {
2955
2955
  return this.getDeviceAggregate(input.deviceId, "live");
2956
2956
  },
2957
2957
  getDeviceAggregate: async (input) => {
2958
- const [settings, live] = await Promise.all([this.getDeviceAggregate(input.deviceId, "settings"), this.getDeviceAggregate(input.deviceId, "live")]);
2958
+ const [settingsAggregate, live] = await Promise.all([this.getDeviceAggregate(input.deviceId, "settings"), this.getDeviceAggregate(input.deviceId, "live")]);
2959
2959
  return {
2960
- settings,
2960
+ settings: settingsAggregate,
2961
2961
  live
2962
2962
  };
2963
2963
  },
@@ -66,6 +66,14 @@ export default class NativeMetricsAddon extends BaseAddon<NativeMetricsConfig> {
66
66
  * the dedicated `$process.restart` action, not this kill API.
67
67
  */
68
68
  private killProcess;
69
+ /**
70
+ * Ask the addon's forked runner to write a V8 heap snapshot (SIGUSR2 → the
71
+ * runner's diagnostic handler in addon-runner.ts), for deep per-addon memory
72
+ * attribution. Resolves the addon's worker pid from the kernel `$process.list`
73
+ * and signals it; the runner writes `/tmp/heap-<sanitized nodeId>.heapsnapshot`
74
+ * and logs its memoryUsage + heap-space breakdown. '$hub' targets this process.
75
+ */
76
+ private dumpHeapSnapshot;
69
77
  /** Raw `ps` scan returning every pid + command + resource stats. */
70
78
  private runPs;
71
79
  protected globalSettingsSchema(): import('@camstack/types').ConfigUISchema;
@@ -123,13 +123,15 @@ var NativeMetricsProvider = class {
123
123
  return getCpuTemperatureInternal();
124
124
  }
125
125
  async getProcessStats(input) {
126
- const { getPidStats } = await Promise.resolve().then(() => require("../../resource-monitor-IIEanuJt.js")).then((n) => n.resource_monitor_exports);
126
+ const { getPidStats } = await Promise.resolve().then(() => require("../../resource-monitor-DNNomR-i.js")).then((n) => n.resource_monitor_exports);
127
127
  const raw = await getPidStats(input.pids);
128
128
  const result = [];
129
129
  for (const [pid, s] of raw) result.push({
130
130
  pid,
131
131
  cpu: s.cpu,
132
- memory: s.memory
132
+ memory: s.memory,
133
+ privateBytes: s.privateBytes,
134
+ sharedBytes: s.sharedBytes
133
135
  });
134
136
  return result;
135
137
  }
@@ -596,7 +598,8 @@ var NativeMetricsAddon = class extends _camstack_types.BaseAddon {
596
598
  listAddonInstances: () => this.listAddonInstances(),
597
599
  getAddonStats: (params) => this.getAddonStats(params.addonId),
598
600
  listNodeProcesses: () => this.listNodeProcesses(),
599
- killProcess: (params) => this.killProcess(params)
601
+ killProcess: (params) => this.killProcess(params),
602
+ dumpHeapSnapshot: (params) => this.dumpHeapSnapshot(params)
600
603
  };
601
604
  this.snapshotTimer = setInterval(() => this.emitMetricsSnapshots(), METRICS_SNAPSHOT_INTERVAL_MS);
602
605
  return [{
@@ -845,6 +848,50 @@ var NativeMetricsAddon = class extends _camstack_types.BaseAddon {
845
848
  };
846
849
  }
847
850
  }
851
+ /**
852
+ * Ask the addon's forked runner to write a V8 heap snapshot (SIGUSR2 → the
853
+ * runner's diagnostic handler in addon-runner.ts), for deep per-addon memory
854
+ * attribution. Resolves the addon's worker pid from the kernel `$process.list`
855
+ * and signals it; the runner writes `/tmp/heap-<sanitized nodeId>.heapsnapshot`
856
+ * and logs its memoryUsage + heap-space breakdown. '$hub' targets this process.
857
+ */
858
+ async dumpHeapSnapshot(input) {
859
+ const sanitize = (nodeId) => nodeId.replace(/[^\w.-]/g, "_");
860
+ let pid;
861
+ let nodeId;
862
+ if (input.addonId === "$hub") {
863
+ pid = process.pid;
864
+ nodeId = this.ctx.kernel.cluster?.broker?.nodeID ?? "$hub";
865
+ } else {
866
+ const target = (await this.listWorkerInstances()).find((w) => w.addonId === input.addonId);
867
+ if (!target) return {
868
+ success: false,
869
+ reason: `no runner process for addon '${input.addonId}'`
870
+ };
871
+ pid = target.pid;
872
+ nodeId = target.nodeId;
873
+ }
874
+ const path = `/tmp/heap-${sanitize(nodeId)}.heapsnapshot`;
875
+ try {
876
+ process.kill(pid, "SIGUSR2");
877
+ this.ctx.logger.info("Requested heap snapshot from runner", { meta: {
878
+ addonId: input.addonId,
879
+ pid,
880
+ path
881
+ } });
882
+ return {
883
+ success: true,
884
+ pid,
885
+ path
886
+ };
887
+ } catch (err) {
888
+ return {
889
+ success: false,
890
+ reason: err instanceof Error ? err.message : String(err),
891
+ pid
892
+ };
893
+ }
894
+ }
848
895
  /** Raw `ps` scan returning every pid + command + resource stats. */
849
896
  async runPs() {
850
897
  try {
@@ -116,13 +116,15 @@ var NativeMetricsProvider = class {
116
116
  return getCpuTemperatureInternal();
117
117
  }
118
118
  async getProcessStats(input) {
119
- const { getPidStats } = await import("../../resource-monitor-ClDGFyf6.mjs").then((n) => n.r);
119
+ const { getPidStats } = await import("../../resource-monitor-BkP504Vq.mjs").then((n) => n.r);
120
120
  const raw = await getPidStats(input.pids);
121
121
  const result = [];
122
122
  for (const [pid, s] of raw) result.push({
123
123
  pid,
124
124
  cpu: s.cpu,
125
- memory: s.memory
125
+ memory: s.memory,
126
+ privateBytes: s.privateBytes,
127
+ sharedBytes: s.sharedBytes
126
128
  });
127
129
  return result;
128
130
  }
@@ -589,7 +591,8 @@ var NativeMetricsAddon = class extends BaseAddon {
589
591
  listAddonInstances: () => this.listAddonInstances(),
590
592
  getAddonStats: (params) => this.getAddonStats(params.addonId),
591
593
  listNodeProcesses: () => this.listNodeProcesses(),
592
- killProcess: (params) => this.killProcess(params)
594
+ killProcess: (params) => this.killProcess(params),
595
+ dumpHeapSnapshot: (params) => this.dumpHeapSnapshot(params)
593
596
  };
594
597
  this.snapshotTimer = setInterval(() => this.emitMetricsSnapshots(), METRICS_SNAPSHOT_INTERVAL_MS);
595
598
  return [{
@@ -838,6 +841,50 @@ var NativeMetricsAddon = class extends BaseAddon {
838
841
  };
839
842
  }
840
843
  }
844
+ /**
845
+ * Ask the addon's forked runner to write a V8 heap snapshot (SIGUSR2 → the
846
+ * runner's diagnostic handler in addon-runner.ts), for deep per-addon memory
847
+ * attribution. Resolves the addon's worker pid from the kernel `$process.list`
848
+ * and signals it; the runner writes `/tmp/heap-<sanitized nodeId>.heapsnapshot`
849
+ * and logs its memoryUsage + heap-space breakdown. '$hub' targets this process.
850
+ */
851
+ async dumpHeapSnapshot(input) {
852
+ const sanitize = (nodeId) => nodeId.replace(/[^\w.-]/g, "_");
853
+ let pid;
854
+ let nodeId;
855
+ if (input.addonId === "$hub") {
856
+ pid = process.pid;
857
+ nodeId = this.ctx.kernel.cluster?.broker?.nodeID ?? "$hub";
858
+ } else {
859
+ const target = (await this.listWorkerInstances()).find((w) => w.addonId === input.addonId);
860
+ if (!target) return {
861
+ success: false,
862
+ reason: `no runner process for addon '${input.addonId}'`
863
+ };
864
+ pid = target.pid;
865
+ nodeId = target.nodeId;
866
+ }
867
+ const path = `/tmp/heap-${sanitize(nodeId)}.heapsnapshot`;
868
+ try {
869
+ process.kill(pid, "SIGUSR2");
870
+ this.ctx.logger.info("Requested heap snapshot from runner", { meta: {
871
+ addonId: input.addonId,
872
+ pid,
873
+ path
874
+ } });
875
+ return {
876
+ success: true,
877
+ pid,
878
+ path
879
+ };
880
+ } catch (err) {
881
+ return {
882
+ success: false,
883
+ reason: err instanceof Error ? err.message : String(err),
884
+ pid
885
+ };
886
+ }
887
+ }
841
888
  /** Raw `ps` scan returning every pid + command + resource stats. */
842
889
  async runPs() {
843
890
  try {