@camstack/system 1.2.62 → 1.2.64

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 (90) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/alerts/alerts.addon.js +1 -1
  10. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +3 -3
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/core-blocks/block-package.d.ts +9 -27
  16. package/dist/builtins/core-blocks/block-supervisor.d.ts +15 -0
  17. package/dist/builtins/core-blocks/core-blocks.addon.js +91 -24
  18. package/dist/builtins/core-blocks/core-blocks.addon.mjs +91 -25
  19. package/dist/builtins/device-manager/device-bindings-store.d.ts +28 -5
  20. package/dist/builtins/device-manager/device-linked-devices.d.ts +2 -2
  21. package/dist/builtins/device-manager/device-manager.addon.d.ts +3 -45
  22. package/dist/builtins/device-manager/device-manager.addon.js +123 -884
  23. package/dist/builtins/device-manager/device-manager.addon.mjs +123 -884
  24. package/dist/builtins/device-manager/device-meta-actions.d.ts +0 -8
  25. package/dist/builtins/device-manager/device-meta-store.d.ts +1 -28
  26. package/dist/builtins/device-manager/device-meta-types.d.ts +2 -6
  27. package/dist/builtins/device-manager/device-projection.d.ts +1 -2
  28. package/dist/builtins/device-manager/device-provider-context.d.ts +2 -13
  29. package/dist/builtins/device-manager/device-queries.d.ts +1 -1
  30. package/dist/builtins/device-manager/device-state-mirror.d.ts +8 -49
  31. package/dist/builtins/device-manager/device-sync-resolvers.d.ts +0 -11
  32. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  33. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  34. package/dist/builtins/hub-forwarder/index.js +1 -1
  35. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  37. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  38. package/dist/builtins/local-auth/homeassistant-integration.d.ts +7 -0
  39. package/dist/builtins/local-auth/index.d.ts +1 -0
  40. package/dist/builtins/local-auth/index.js +1 -0
  41. package/dist/builtins/local-auth/index.mjs +2 -2
  42. package/dist/builtins/local-auth/local-auth.addon.js +561 -497
  43. package/dist/builtins/local-auth/local-auth.addon.mjs +580 -517
  44. package/dist/builtins/local-auth/oauth-grants.d.ts +11 -0
  45. package/dist/builtins/local-auth/oauth-session-manager.d.ts +1 -1
  46. package/dist/builtins/local-network/local-network.addon.js +1 -1
  47. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  48. package/dist/builtins/loki-logging/index.js +1 -1
  49. package/dist/builtins/loki-logging/index.mjs +1 -1
  50. package/dist/builtins/native-metrics/native-metrics.addon.js +2 -2
  51. package/dist/builtins/native-metrics/native-metrics.addon.mjs +2 -2
  52. package/dist/builtins/platform-probe/index.js +2 -2
  53. package/dist/builtins/platform-probe/index.mjs +2 -2
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  55. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  56. package/dist/builtins/snapshot/index.js +319 -20
  57. package/dist/builtins/snapshot/index.mjs +319 -20
  58. package/dist/builtins/snapshot/snapshot-keep-warm.d.ts +86 -0
  59. package/dist/builtins/snapshot/snapshot-media-handler.d.ts +48 -1
  60. package/dist/builtins/snapshot/snapshot.addon.d.ts +65 -1
  61. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  62. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  63. package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +15 -0
  64. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +73 -7
  65. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +73 -7
  66. package/dist/builtins/sqlite-storage/vector-index-vec.d.ts +19 -0
  67. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  68. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  69. package/dist/builtins/system-config/system-config.addon.js +1 -1
  70. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  71. package/dist/builtins/winston-logging/index.js +1 -1
  72. package/dist/builtins/winston-logging/index.mjs +1 -1
  73. package/dist/{dist-DnKpFaP1.mjs → dist-CMb7aKS0.mjs} +1227 -1330
  74. package/dist/{dist-DgPUarcs.js → dist-DP3nHlNM.js} +1251 -1366
  75. package/dist/http/asset-compression.d.ts +93 -0
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.js +64 -9
  78. package/dist/index.mjs +65 -11
  79. package/dist/kernel/heap-watch.d.ts +55 -0
  80. package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -2
  81. package/dist/{manifest-python-deps-d4acYXVV.js → manifest-python-deps-BE6l8Y4W.js} +21 -15
  82. package/dist/{manifest-python-deps-CX5tGRqf.mjs → manifest-python-deps-Dz1ryHIN.mjs} +21 -15
  83. package/dist/{model-download-service-D8B-4ktF.mjs → model-download-service-CTBHzORJ.mjs} +220 -2
  84. package/dist/{model-download-service-Cpc6SWzV.js → model-download-service-Caj1kaSi.js} +220 -2
  85. package/package.json +5 -1
  86. package/dist/builtins/device-manager/device-link-cycle.d.ts +0 -18
  87. package/dist/builtins/device-manager/device-link-overlay.d.ts +0 -33
  88. package/dist/builtins/device-manager/device-link-resolver.d.ts +0 -44
  89. package/dist/main-CVLq8IzA.js +0 -2356
  90. package/dist/main-CzH19GkU.mjs +0 -2353
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Content-coding for addon-served static assets: `Accept-Encoding` negotiation,
3
+ * an eligibility rule, and a BOUNDED cache of the compressed bytes.
4
+ *
5
+ * ## Why the ADDON owns the encoding
6
+ *
7
+ * The hub's `/addon/:addonId/*` bridge is registered `{ compress: false }`. That
8
+ * is not an oversight — on 2026-07-16 the global `@fastify/compress` was found
9
+ * emitting an EMPTY brotli stream (`content-encoding: br`, `content-length: 0`)
10
+ * for any compressible body over its 1 KiB threshold once the body had crossed
11
+ * the UDS route bridge of a FORKED addon. It silently broke the notifier SVG
12
+ * icons; the deploy-bundle pull route had already dodged the same class with
13
+ * `compress: false`. The resolution then, and the contract now, is that an addon
14
+ * route owns its content-coding END TO END: it negotiates, it compresses, it
15
+ * stamps `content-encoding` + `Vary`, and the hub pipes those bytes through
16
+ * untouched (`proxyToUpstream` replays the upstream headers verbatim).
17
+ *
18
+ * So compression belongs HERE, in the shared file data-plane, and nowhere above
19
+ * it. Anything that re-compresses on top of this reintroduces the empty-brotli
20
+ * bug rather than doubling the ratio.
21
+ *
22
+ * ## Why the bytes are cached, and why the cache is bounded
23
+ *
24
+ * Measured on the stream-broker embed bundle (`index-<hash>.js`, 1,443,142 B):
25
+ * gzip-6 → 360,868 B, brotli-5 → 327,419 B in 27 ms, brotli-11 → 293,677 B in
26
+ * 2,233 ms. Quality 5 is the request-path choice: 77% off the wire for 27 ms
27
+ * paid ONCE. Quality 11 buys another 2.3% for eighty times the CPU, on a path
28
+ * where a real client is waiting.
29
+ *
30
+ * The cache is an LRU bounded by BOTH entry count and total bytes, and it
31
+ * refuses assets over {@link COMPRESSION_MAX_ASSET_BYTES} outright. An unbounded
32
+ * `Map` keyed by URL is how a long-lived process walks into an OOM, and this
33
+ * repo has already spent a session on one.
34
+ *
35
+ * The key carries `mtimeMs` and `size`, so a rebuild that rewrites an asset in
36
+ * place under an UNCHANGED name (an SPA shell, a service worker) can never be
37
+ * served from the previous build's bytes.
38
+ */
39
+ /** The codings this layer will produce. Identity is the absence of one. */
40
+ export type ContentEncoding = 'br' | 'gzip';
41
+ /**
42
+ * Below this the framing and the `Vary` cache split cost more than the
43
+ * compression saves. Same threshold the hub's global compressor is registered
44
+ * with, deliberately.
45
+ */
46
+ export declare const COMPRESSION_MIN_BYTES = 1024;
47
+ /**
48
+ * Above this an asset is streamed identity and never buffered. Compression
49
+ * requires the whole body in RAM twice; a multi-hundred-MB recording segment
50
+ * must not be able to do that, whatever its content type says.
51
+ */
52
+ export declare const COMPRESSION_MAX_ASSET_BYTES: number;
53
+ /** Is a body of this content type worth compressing at all? */
54
+ export declare function isCompressibleContentType(contentType: string): boolean;
55
+ /**
56
+ * Pick a coding for this request: brotli when offered, else gzip, else identity.
57
+ *
58
+ * Returns `null` for identity — including when the header is ABSENT. A client
59
+ * that offers nothing gets the raw bytes; guessing on its behalf is how a
60
+ * non-negotiating consumer (a native fetch, a probe, an OTA puller) ends up with
61
+ * a body it cannot read.
62
+ */
63
+ export declare function negotiateContentEncoding(acceptEncoding: string | undefined): ContentEncoding | null;
64
+ /** Identity of one compressed body. `mtimeMs`+`size` make a rebuild-in-place a MISS. */
65
+ export interface CompressedAssetKey {
66
+ readonly path: string;
67
+ readonly mtimeMs: number;
68
+ readonly size: number;
69
+ readonly encoding: ContentEncoding;
70
+ }
71
+ /** Counters for the cache. Exported so a test can prove the compressor ran ONCE. */
72
+ export interface AssetCompressionStats {
73
+ /** Bodies actually run through zlib. */
74
+ readonly compressions: number;
75
+ /** Requests served from already-compressed bytes. */
76
+ readonly hits: number;
77
+ readonly entries: number;
78
+ readonly bytes: number;
79
+ }
80
+ /**
81
+ * Compressed bytes for one asset, compressing at most once per key.
82
+ *
83
+ * `null` means "send the original" — the asset is too large to buffer, the
84
+ * source could not be read, or compression did not actually shrink it. Every
85
+ * failure degrades to identity, so this can only ever be an optimisation.
86
+ *
87
+ * Concurrent callers for the same key share ONE compression: a cold cache hit by
88
+ * a dozen parallel asset requests must not run zlib a dozen times.
89
+ */
90
+ export declare function getCompressedAsset(key: CompressedAssetKey, readSource: () => Promise<Buffer>): Promise<Buffer | null>;
91
+ export declare function getAssetCompressionStats(): AssetCompressionStats;
92
+ /** Test seam — the cache and its counters are process-wide. */
93
+ export declare function resetAssetCompressionCache(): void;
package/dist/index.d.ts CHANGED
@@ -51,7 +51,7 @@ export { LivenessMonitorAddon } from './builtins/liveness-monitor/index.js';
51
51
  export { NativeMetricsProvider } from './builtins/native-metrics/native-metrics-provider.js';
52
52
  export { SystemConfigAddon } from './builtins/system-config/index.js';
53
53
  export { CoreBlocksAddon } from './builtins/core-blocks/index.js';
54
- export { LocalAuthAddon } from './builtins/local-auth/index.js';
54
+ export { LocalAuthAddon, HOME_ASSISTANT_OAUTH_INTEGRATION } from './builtins/local-auth/index.js';
55
55
  export { DeviceManagerAddon } from './builtins/device-manager/index.js';
56
56
  export { LifecycleStateMachine } from './lifecycle/lifecycle-state-machine.js';
57
57
  export type { ElementState, ElementStatus, } from './lifecycle/lifecycle-state-machine.js';
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-DgPUarcs.js");
4
- const require_model_download_service = require("./model-download-service-Cpc6SWzV.js");
5
- const require_manifest_python_deps = require("./manifest-python-deps-d4acYXVV.js");
3
+ const require_dist = require("./dist-DP3nHlNM.js");
4
+ const require_model_download_service = require("./model-download-service-Caj1kaSi.js");
5
+ const require_manifest_python_deps = require("./manifest-python-deps-BE6l8Y4W.js");
6
6
  const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
7
7
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
8
8
  const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
@@ -41,10 +41,10 @@ let node_fs$1 = require_chunk.__toESM(node_fs, 1);
41
41
  node_fs = require_chunk.__toESM(node_fs);
42
42
  let node_path = require("node:path");
43
43
  node_path = require_chunk.__toESM(node_path, 1);
44
- let _camstack_types_addon = require("@camstack/types/addon");
45
- let node_child_process = require("node:child_process");
46
44
  let node_util = require("node:util");
47
45
  node_util = require_chunk.__toESM(node_util);
46
+ let _camstack_types_addon = require("@camstack/types/addon");
47
+ let node_child_process = require("node:child_process");
48
48
  let node_os = require("node:os");
49
49
  node_os = require_chunk.__toESM(node_os);
50
50
  let node_fs_promises = require("node:fs/promises");
@@ -139,6 +139,30 @@ var HEAP_WATCH_INTERVAL_MS = 6e4;
139
139
  * the fatal by more than one interval under realistic growth. */
140
140
  var HEAP_WATCH_WARN_RATIO = .8;
141
141
  /**
142
+ * Escalate above this share of the ceiling.
143
+ *
144
+ * 0.70 is 3002MB: clear of the measured routine max (2847MB) so steady state
145
+ * never escalates, and below {@link HEAP_WATCH_WARN_RATIO} so the fast cadence
146
+ * is already running by the time the warn is reachable.
147
+ */
148
+ var HEAP_WATCH_ESCALATE_RATIO = .7;
149
+ /**
150
+ * Release escalation below this share.
151
+ *
152
+ * Hysteresis, not a second threshold: hub-main's heap sawtooths continuously,
153
+ * so a single ratio would flap the mode on every GC. 0.60 is 2573MB — a real
154
+ * retreat rather than the trough of one collection.
155
+ */
156
+ var HEAP_WATCH_DEESCALATE_RATIO = .6;
157
+ /**
158
+ * The cadence state machine, pure so the thresholds are testable without
159
+ * allocating three gigabytes to reach them.
160
+ */
161
+ function nextMode(current, usedRatio, escalateRatio = HEAP_WATCH_ESCALATE_RATIO, deescalateRatio = HEAP_WATCH_DEESCALATE_RATIO) {
162
+ if (current === "escalated") return usedRatio < deescalateRatio ? "steady" : "escalated";
163
+ return usedRatio >= escalateRatio ? "escalated" : "steady";
164
+ }
165
+ /**
142
166
  * Reclaim once the process holds this many MB beyond what it is USING.
143
167
  *
144
168
  * Live calibration, both readings from the same hub minutes apart: 2491MB
@@ -229,6 +253,9 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
229
253
  const now = reclaimOptions?.now ?? (() => Date.now());
230
254
  const triggerMb = reclaimOptions?.triggerMb ?? 1536;
231
255
  const minIntervalMs = reclaimOptions?.minIntervalMs ?? 6e5;
256
+ const fastIntervalMs = reclaimOptions?.fastIntervalMs ?? 2e3;
257
+ const escalateRatio = reclaimOptions?.escalateRatio ?? .7;
258
+ const deescalateRatio = reclaimOptions?.deescalateRatio ?? .6;
232
259
  let lastReclaimAt = Number.NEGATIVE_INFINITY;
233
260
  const read = () => {
234
261
  const limit = reclaimOptions?.heapLimitBytes ?? node_v8.getHeapStatistics().heap_size_limit;
@@ -249,16 +276,43 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
249
276
  const after = read();
250
277
  sink.info(`[mem] reclaim ${label} stranded=${strandedMb(sample)}MB rss=${sample.rssMb}MB→${after.rssMb}MB freed=${sample.rssMb - after.rssMb}MB arrayBuffers=${sample.arrayBuffersMb}MB→${after.arrayBuffersMb}MB took=${now() - startedAt}ms`);
251
278
  };
279
+ let mode = "steady";
280
+ let lastLoggedAt = Number.NEGATIVE_INFINITY;
281
+ /**
282
+ * PROBING and LOGGING are separate cadences, and that separation is the whole
283
+ * point.
284
+ *
285
+ * The 2026-08-08 fatal escaped a 60s heartbeat because the burst began and
286
+ * ended between two samples. Deciding "is the heap elevated?" only when a
287
+ * line is due would reproduce that exactly: the process would still be read
288
+ * once a minute, and a 62-second climb would still be invisible. So the timer
289
+ * runs at the FAST period always — `process.memoryUsage()` is tens of
290
+ * microseconds, i.e. free at this rate — and the slow period governs only how
291
+ * often a steady process is allowed to say so.
292
+ *
293
+ * Never probe slower than the log cadence, or a caller passing a short
294
+ * `intervalMs` would silently get the long one.
295
+ */
296
+ const probeIntervalMs = Math.min(fastIntervalMs, intervalMs);
252
297
  const tick = () => {
253
298
  try {
254
299
  const sample = read();
255
- const line = format$1(label, sample);
256
- if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
257
- else sink.info(line);
300
+ const previous = mode;
301
+ mode = nextMode(previous, sample.usedRatio, escalateRatio, deescalateRatio);
302
+ const at = now();
303
+ const due = at - lastLoggedAt >= intervalMs;
304
+ if (mode === "escalated" || due) {
305
+ lastLoggedAt = at;
306
+ const line = format$1(label, sample);
307
+ if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
308
+ else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
309
+ else sink.info(line);
310
+ }
311
+ if (previous === "escalated" && mode === "steady") sink.info(`[mem] ${label} heap back to routine — logging every ${intervalMs}ms`);
258
312
  maybeReclaim(sample);
259
313
  } catch {}
260
314
  };
261
- const timer = setInterval(tick, intervalMs);
315
+ const timer = setInterval(tick, probeIntervalMs);
262
316
  timer.unref?.();
263
317
  tick();
264
318
  let stopped = false;
@@ -94886,6 +94940,7 @@ exports.HEAP_RECLAIM_MIN_INTERVAL_MS = HEAP_RECLAIM_MIN_INTERVAL_MS;
94886
94940
  exports.HEAP_RECLAIM_TRIGGER_MB = HEAP_RECLAIM_TRIGGER_MB;
94887
94941
  exports.HEAP_WATCH_INTERVAL_MS = HEAP_WATCH_INTERVAL_MS;
94888
94942
  exports.HEAP_WATCH_WARN_RATIO = HEAP_WATCH_WARN_RATIO;
94943
+ exports.HOME_ASSISTANT_OAUTH_INTEGRATION = require_builtins_local_auth_local_auth_addon.HOME_ASSISTANT_OAUTH_INTEGRATION;
94889
94944
  exports.HUB_CAP_FWD_ACTION = require_manifest_python_deps.HUB_CAP_FWD_ACTION;
94890
94945
  exports.HUB_CAP_FWD_SERVICE = require_manifest_python_deps.HUB_CAP_FWD_SERVICE;
94891
94946
  exports.HubForwarderAddon = require_builtins_hub_forwarder_index.HubForwarderAddon$1;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { $ as procedureAuthKey, Ct as asJsonObject$1, Dt as emitDownForOwnedCaps, Et as createEvent, Ft as readinessKey, H as kebabToCamel, It as resolveCapMount, K as logLevelAtMost, L as isArrayOutputSchema, Lt as scopeKey, Nt as parseJsonObject, P as extractNestedAddonId, Pt as parseJsonUnknown$1, R as isCollectionArrayMethod, Tt as asString$1, U as lifecycleJobSchema, V as isVoidInput, X as objectInputDeclaresAddonId, bt as ReadinessRegistry, c as METHOD_ACCESS_MAP, ft as errMsg$1, gt as DEVICE_STATUS_METHOD, h as addonSettingsCapability, ht as DEVICE_SETTINGS_CONTRIBUTION_METHODS, kt as expandCapMethods, l as RUNTIME_DEFAULTS, mt as DATAPLANE_SECRET_HEADER$1, q as looseSchema, t as ALL_CAPABILITY_DEFINITIONS, wt as asNumber, xt as ReadinessTimeoutError, z as isObjectInput, zt as EventCategory$1 } from "./dist-DnKpFaP1.mjs";
3
- import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-D8B-4ktF.mjs";
4
- import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-CX5tGRqf.mjs";
2
+ import { Ct as asString$1, Dt as expandCapMethods, F as extractNestedAddonId, Ft as scopeKey, H as kebabToCamel, K as logLevelAtMost, L as isArrayOutputSchema, Lt as EventCategory$1, Mt as parseJsonUnknown$1, Nt as readinessKey, Pt as resolveCapMount, R as isCollectionArrayMethod, St as asNumber, Tt as emitDownForOwnedCaps, U as lifecycleJobSchema, V as isVoidInput, Z as objectInputDeclaresAddonId, et as procedureAuthKey, ft as DATAPLANE_SECRET_HEADER$1, g as addonSettingsCapability, jt as parseJsonObject, l as METHOD_ACCESS_MAP, mt as DEVICE_STATUS_METHOD, pt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, q as looseSchema, t as ALL_CAPABILITY_DEFINITIONS, u as RUNTIME_DEFAULTS, ut as errMsg$1, vt as ReadinessRegistry, wt as createEvent, xt as asJsonObject$1, yt as ReadinessTimeoutError, z as isObjectInput } from "./dist-CMb7aKS0.mjs";
3
+ import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-CTBHzORJ.mjs";
4
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-Dz1ryHIN.mjs";
5
5
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BkP504Vq.mjs";
6
6
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
7
7
  import { SqliteSettingsAddon, o as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
@@ -22,7 +22,7 @@ import { SystemConfigAddon } from "./builtins/system-config/system-config.addon.
22
22
  import "./builtins/system-config/index.mjs";
23
23
  import { CoreBlocksAddon } from "./builtins/core-blocks/core-blocks.addon.mjs";
24
24
  import "./builtins/core-blocks/index.mjs";
25
- import { LocalAuthAddon, a as require_ms, i as AuthManager, n as ApiKeyManager, o as require_safe_buffer, r as UserManager, t as ScopedTokenManager } from "./builtins/local-auth/local-auth.addon.mjs";
25
+ import { LocalAuthAddon, a as require_safe_buffer, i as require_ms, n as ScopedTokenManager, o as ApiKeyManager, r as AuthManager, s as HOME_ASSISTANT_OAUTH_INTEGRATION, t as UserManager } from "./builtins/local-auth/local-auth.addon.mjs";
26
26
  import "./builtins/local-auth/index.mjs";
27
27
  import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
28
28
  import "./builtins/device-manager/index.mjs";
@@ -37,10 +37,10 @@ import * as fs$17 from "node:fs";
37
37
  import { accessSync, constants, existsSync, mkdirSync, readFileSync } from "node:fs";
38
38
  import * as path$39 from "node:path";
39
39
  import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
40
- import { asJsonObject, errMsg, parseJsonUnknown } from "@camstack/types/addon";
41
- import { execFile, spawn } from "node:child_process";
42
40
  import * as util$10 from "node:util";
43
41
  import { promisify } from "node:util";
42
+ import { asJsonObject, errMsg, parseJsonUnknown } from "@camstack/types/addon";
43
+ import { execFile, spawn } from "node:child_process";
44
44
  import * as os$18 from "node:os";
45
45
  import { lstat, readdir, readlink, realpath } from "node:fs/promises";
46
46
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -131,6 +131,30 @@ var HEAP_WATCH_INTERVAL_MS = 6e4;
131
131
  * the fatal by more than one interval under realistic growth. */
132
132
  var HEAP_WATCH_WARN_RATIO = .8;
133
133
  /**
134
+ * Escalate above this share of the ceiling.
135
+ *
136
+ * 0.70 is 3002MB: clear of the measured routine max (2847MB) so steady state
137
+ * never escalates, and below {@link HEAP_WATCH_WARN_RATIO} so the fast cadence
138
+ * is already running by the time the warn is reachable.
139
+ */
140
+ var HEAP_WATCH_ESCALATE_RATIO = .7;
141
+ /**
142
+ * Release escalation below this share.
143
+ *
144
+ * Hysteresis, not a second threshold: hub-main's heap sawtooths continuously,
145
+ * so a single ratio would flap the mode on every GC. 0.60 is 2573MB — a real
146
+ * retreat rather than the trough of one collection.
147
+ */
148
+ var HEAP_WATCH_DEESCALATE_RATIO = .6;
149
+ /**
150
+ * The cadence state machine, pure so the thresholds are testable without
151
+ * allocating three gigabytes to reach them.
152
+ */
153
+ function nextMode(current, usedRatio, escalateRatio = HEAP_WATCH_ESCALATE_RATIO, deescalateRatio = HEAP_WATCH_DEESCALATE_RATIO) {
154
+ if (current === "escalated") return usedRatio < deescalateRatio ? "steady" : "escalated";
155
+ return usedRatio >= escalateRatio ? "escalated" : "steady";
156
+ }
157
+ /**
134
158
  * Reclaim once the process holds this many MB beyond what it is USING.
135
159
  *
136
160
  * Live calibration, both readings from the same hub minutes apart: 2491MB
@@ -221,6 +245,9 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
221
245
  const now = reclaimOptions?.now ?? (() => Date.now());
222
246
  const triggerMb = reclaimOptions?.triggerMb ?? 1536;
223
247
  const minIntervalMs = reclaimOptions?.minIntervalMs ?? 6e5;
248
+ const fastIntervalMs = reclaimOptions?.fastIntervalMs ?? 2e3;
249
+ const escalateRatio = reclaimOptions?.escalateRatio ?? .7;
250
+ const deescalateRatio = reclaimOptions?.deescalateRatio ?? .6;
224
251
  let lastReclaimAt = Number.NEGATIVE_INFINITY;
225
252
  const read = () => {
226
253
  const limit = reclaimOptions?.heapLimitBytes ?? v8.getHeapStatistics().heap_size_limit;
@@ -241,16 +268,43 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
241
268
  const after = read();
242
269
  sink.info(`[mem] reclaim ${label} stranded=${strandedMb(sample)}MB rss=${sample.rssMb}MB→${after.rssMb}MB freed=${sample.rssMb - after.rssMb}MB arrayBuffers=${sample.arrayBuffersMb}MB→${after.arrayBuffersMb}MB took=${now() - startedAt}ms`);
243
270
  };
271
+ let mode = "steady";
272
+ let lastLoggedAt = Number.NEGATIVE_INFINITY;
273
+ /**
274
+ * PROBING and LOGGING are separate cadences, and that separation is the whole
275
+ * point.
276
+ *
277
+ * The 2026-08-08 fatal escaped a 60s heartbeat because the burst began and
278
+ * ended between two samples. Deciding "is the heap elevated?" only when a
279
+ * line is due would reproduce that exactly: the process would still be read
280
+ * once a minute, and a 62-second climb would still be invisible. So the timer
281
+ * runs at the FAST period always — `process.memoryUsage()` is tens of
282
+ * microseconds, i.e. free at this rate — and the slow period governs only how
283
+ * often a steady process is allowed to say so.
284
+ *
285
+ * Never probe slower than the log cadence, or a caller passing a short
286
+ * `intervalMs` would silently get the long one.
287
+ */
288
+ const probeIntervalMs = Math.min(fastIntervalMs, intervalMs);
244
289
  const tick = () => {
245
290
  try {
246
291
  const sample = read();
247
- const line = format$1(label, sample);
248
- if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
249
- else sink.info(line);
292
+ const previous = mode;
293
+ mode = nextMode(previous, sample.usedRatio, escalateRatio, deescalateRatio);
294
+ const at = now();
295
+ const due = at - lastLoggedAt >= intervalMs;
296
+ if (mode === "escalated" || due) {
297
+ lastLoggedAt = at;
298
+ const line = format$1(label, sample);
299
+ if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
300
+ else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
301
+ else sink.info(line);
302
+ }
303
+ if (previous === "escalated" && mode === "steady") sink.info(`[mem] ${label} heap back to routine — logging every ${intervalMs}ms`);
250
304
  maybeReclaim(sample);
251
305
  } catch {}
252
306
  };
253
- const timer = setInterval(tick, intervalMs);
307
+ const timer = setInterval(tick, probeIntervalMs);
254
308
  timer.unref?.();
255
309
  tick();
256
310
  let stopped = false;
@@ -94826,4 +94880,4 @@ var LifecycleJobEngine = class {
94826
94880
  }
94827
94881
  };
94828
94882
  //#endregion
94829
- export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, 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, CoreBlocksAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HEAP_RECLAIM_MIN_INTERVAL_MS, HEAP_RECLAIM_TRIGGER_MB, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LivenessMonitorAddon, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, LokiDestination, LokiLoggingAddon, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, QUARANTINE_DIRNAME, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, addonSettingsCapability, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildHeapSample, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyAddonDir, classifyCapRoute, clearPendingRestart, clusterEventTopic, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, createV8Reclaimer, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, expandCapMethods, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, quarantineAddonResidue, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, selectAddonResidue, serializeTypedArrays, setHubConnected, setNodeEventInterest, shouldReclaim, startHeapWatch, strandedMb, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
94883
+ export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, 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, CoreBlocksAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HEAP_RECLAIM_MIN_INTERVAL_MS, HEAP_RECLAIM_TRIGGER_MB, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, HOME_ASSISTANT_OAUTH_INTEGRATION, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LivenessMonitorAddon, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, LokiDestination, LokiLoggingAddon, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, QUARANTINE_DIRNAME, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, addonSettingsCapability, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildHeapSample, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyAddonDir, classifyCapRoute, clearPendingRestart, clusterEventTopic, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, createV8Reclaimer, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, expandCapMethods, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, quarantineAddonResidue, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, selectAddonResidue, serializeTypedArrays, setHubConnected, setNodeEventInterest, shouldReclaim, startHeapWatch, strandedMb, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
@@ -4,6 +4,55 @@ export declare const HEAP_WATCH_INTERVAL_MS = 60000;
4
4
  /** Warn above this share of the heap ceiling. Chosen so the warning precedes
5
5
  * the fatal by more than one interval under realistic growth. */
6
6
  export declare const HEAP_WATCH_WARN_RATIO = 0.8;
7
+ /**
8
+ * ## The heartbeat could not see the thing it was built to see
9
+ *
10
+ * 2026-08-08 18:28:32Z, hub-main (pid 8) died on `Reached heap limit`. The last
11
+ * `[mem]` line was 18:27:30.433 reading `heapUsed=1086MB` — 25%, unremarkable —
12
+ * and V8's own trace 62 seconds later read 4083MB with a Mark-Compact that
13
+ * reclaimed 4MB of 4090MB. Between those two facts there is not one sample.
14
+ *
15
+ * At `HEAP_WATCH_INTERVAL_MS` the whole event fits inside a single gap, so
16
+ * `HEAP_WATCH_WARN_RATIO` never had a reading to fire on. The docblock above
17
+ * promises "the line that will exist BEFORE the next fatal instead of after
18
+ * it"; on the only occasion that mattered, it did not exist.
19
+ *
20
+ * Measured the same day over 397 consecutive live samples: p50 1266MB, p95
21
+ * 1995MB, **max 2847MB** — 0.66 of the 4288MB ceiling. So the routine sawtooth
22
+ * never approaches 0.8, and the only path to 0.8 is a burst that outruns the
23
+ * sampler. A threshold reachable only through a blind spot is decoration.
24
+ *
25
+ * The fix is not a faster heartbeat — 30 lines a minute forever, to describe a
26
+ * process that is fine, is how a log stops being read. It is to sample fast
27
+ * only while the heap is somewhere the routine sawtooth does not go.
28
+ */
29
+ /** Sampling period once escalated. Bounded below by log volume, above by the
30
+ * 62s window the 2026-08-08 fatal hid in: ~30 readings across that window is
31
+ * a trajectory, and a trajectory separates a burst from a climb. */
32
+ export declare const HEAP_WATCH_FAST_INTERVAL_MS = 2000;
33
+ /**
34
+ * Escalate above this share of the ceiling.
35
+ *
36
+ * 0.70 is 3002MB: clear of the measured routine max (2847MB) so steady state
37
+ * never escalates, and below {@link HEAP_WATCH_WARN_RATIO} so the fast cadence
38
+ * is already running by the time the warn is reachable.
39
+ */
40
+ export declare const HEAP_WATCH_ESCALATE_RATIO = 0.7;
41
+ /**
42
+ * Release escalation below this share.
43
+ *
44
+ * Hysteresis, not a second threshold: hub-main's heap sawtooths continuously,
45
+ * so a single ratio would flap the mode on every GC. 0.60 is 2573MB — a real
46
+ * retreat rather than the trough of one collection.
47
+ */
48
+ export declare const HEAP_WATCH_DEESCALATE_RATIO = 0.6;
49
+ /** Sampling cadence. `escalated` costs log lines and buys resolution. */
50
+ export type HeapWatchMode = 'steady' | 'escalated';
51
+ /**
52
+ * The cadence state machine, pure so the thresholds are testable without
53
+ * allocating three gigabytes to reach them.
54
+ */
55
+ export declare function nextMode(current: HeapWatchMode, usedRatio: number, escalateRatio?: number, deescalateRatio?: number): HeapWatchMode;
7
56
  /**
8
57
  * Reclaim once the process holds this many MB beyond what it is USING.
9
58
  *
@@ -73,6 +122,12 @@ export interface HeapReclaimOptions {
73
122
  readonly triggerMb?: number;
74
123
  readonly minIntervalMs?: number;
75
124
  readonly now?: () => number;
125
+ /** Sampling cadence once escalated. See {@link HEAP_WATCH_FAST_INTERVAL_MS}. */
126
+ readonly fastIntervalMs?: number;
127
+ /** Ratio at which the fast cadence begins. See {@link HEAP_WATCH_ESCALATE_RATIO}. */
128
+ readonly escalateRatio?: number;
129
+ /** Ratio at which it releases. See {@link HEAP_WATCH_DEESCALATE_RATIO}. */
130
+ readonly deescalateRatio?: number;
76
131
  }
77
132
  /**
78
133
  * Start the heartbeat. Returns a stop function.
@@ -1,8 +1,8 @@
1
+ import { AddonApi, CapabilityDefinition, DeviceManagerApi, IDevice, IEventBus, IKernelStreamProbe, InferProvider, IScopedLogger } from '@camstack/types';
1
2
  import { deviceOpsCapability } from '@camstack/types/addon';
2
- import { AddonApi, CapabilityDefinition, DeviceManagerApi, IDevice, IEventBus, IKernelStreamProbe, IScopedLogger, InferProvider } from '@camstack/types';
3
3
  import { ServiceBroker } from 'moleculer';
4
- import { DeviceRegistry } from '../device-registry.js';
5
4
  import { CapabilityRegistry } from '../capability-registry.js';
5
+ import { DeviceRegistry } from '../device-registry.js';
6
6
  import { LocalChildRegistry } from '../transport/local-child-registry.js';
7
7
  export { NATIVE_PROVIDER_SERVICE_INFIX } from '../transport/cap-action-name.js';
8
8
  export interface WorkerDeviceManagerOptions {
@@ -7,9 +7,9 @@ let node_fs = require("node:fs");
7
7
  node_fs = require_chunk.__toESM(node_fs);
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
10
+ let node_util = require("node:util");
10
11
  let _camstack_types_addon = require("@camstack/types/addon");
11
12
  let node_child_process = require("node:child_process");
12
- 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");
@@ -1779,18 +1779,6 @@ function createBrokerDeviceManagerApi(opts) {
1779
1779
  meta: { error: err instanceof Error ? err.message : String(err) }
1780
1780
  });
1781
1781
  });
1782
- if (initialMeta.deviceLinks !== void 0) await callDeviceManager(api, "setDeviceLinks", {
1783
- deviceId: id,
1784
- deviceLinks: initialMeta.deviceLinks
1785
- }).catch((err) => {
1786
- opts.logger.warn("create: setDeviceLinks pre-seed failed", {
1787
- tags: {
1788
- stableId,
1789
- deviceId: id
1790
- },
1791
- meta: { error: err instanceof Error ? err.message : String(err) }
1792
- });
1793
- });
1794
1782
  if (initialMeta.display !== void 0) await callDeviceManager(api, "setDisplay", {
1795
1783
  deviceId: id,
1796
1784
  display: initialMeta.display
@@ -3523,6 +3511,16 @@ function encodeFrame(value) {
3523
3511
  /** Bytes in the big-endian length prefix that opens every frame. */
3524
3512
  var HEADER_BYTES = 4;
3525
3513
  /**
3514
+ * Copy a frame body out of its backing store before decoding when the store
3515
+ * holds this many bytes MORE than the frame itself. msgpack `bin` fields
3516
+ * decode as VIEWS of the decode input, so one retained field (a state slice,
3517
+ * a forwarded log payload) pins the input's WHOLE backing store — with 64 KiB
3518
+ * socket read chunks that pinned 55k chunks (3.5 GB off-heap) on hub-main.
3519
+ * The exact-fit reassembly path (media frames, `Buffer.concat` sized to the
3520
+ * frame) wastes only the 4-byte header and stays zero-extra-copy.
3521
+ */
3522
+ var SLAB_WASTE_MAX_BYTES = 4096;
3523
+ /**
3526
3524
  * Reassembles length-prefixed MsgPack frames from a byte stream.
3527
3525
  *
3528
3526
  * **Why this is a chunk LIST and not a growing Buffer.** The original
@@ -3558,9 +3556,17 @@ var FrameDecoder = class {
3558
3556
  }
3559
3557
  const frames = [];
3560
3558
  while (this.buffered >= HEADER_BYTES) {
3561
- const total = HEADER_BYTES + this.readLength();
3559
+ const len = this.readLength();
3560
+ const total = HEADER_BYTES + len;
3562
3561
  if (this.buffered < total) break;
3563
- frames.push(decode(this.take(total).subarray(HEADER_BYTES)));
3562
+ const frame = this.take(total);
3563
+ const wasted = frame.buffer.byteLength - frame.byteLength;
3564
+ let body;
3565
+ if (wasted > SLAB_WASTE_MAX_BYTES) {
3566
+ body = Buffer.allocUnsafeSlow(len);
3567
+ frame.copy(body, 0, HEADER_BYTES);
3568
+ } else body = frame.subarray(HEADER_BYTES);
3569
+ frames.push(decode(body));
3564
3570
  }
3565
3571
  return frames;
3566
3572
  }
@@ -5,9 +5,9 @@ import { createServer } from "node:http";
5
5
  import * as fs from "node:fs";
6
6
  import * as path$1 from "node:path";
7
7
  import { isAbsolute, join } from "node:path";
8
+ import { promisify } from "node:util";
8
9
  import { DATAPLANE_SECRET_HEADER, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep, withCapInputDefaults } from "@camstack/types/addon";
9
10
  import { execFile } from "node:child_process";
10
- import { promisify } from "node:util";
11
11
  import * as os from "node:os";
12
12
  import { tmpdir } from "node:os";
13
13
  import { unlink } from "node:fs/promises";
@@ -1777,18 +1777,6 @@ function createBrokerDeviceManagerApi(opts) {
1777
1777
  meta: { error: err instanceof Error ? err.message : String(err) }
1778
1778
  });
1779
1779
  });
1780
- if (initialMeta.deviceLinks !== void 0) await callDeviceManager(api, "setDeviceLinks", {
1781
- deviceId: id,
1782
- deviceLinks: initialMeta.deviceLinks
1783
- }).catch((err) => {
1784
- opts.logger.warn("create: setDeviceLinks pre-seed failed", {
1785
- tags: {
1786
- stableId,
1787
- deviceId: id
1788
- },
1789
- meta: { error: err instanceof Error ? err.message : String(err) }
1790
- });
1791
- });
1792
1780
  if (initialMeta.display !== void 0) await callDeviceManager(api, "setDisplay", {
1793
1781
  deviceId: id,
1794
1782
  display: initialMeta.display
@@ -3521,6 +3509,16 @@ function encodeFrame(value) {
3521
3509
  /** Bytes in the big-endian length prefix that opens every frame. */
3522
3510
  var HEADER_BYTES = 4;
3523
3511
  /**
3512
+ * Copy a frame body out of its backing store before decoding when the store
3513
+ * holds this many bytes MORE than the frame itself. msgpack `bin` fields
3514
+ * decode as VIEWS of the decode input, so one retained field (a state slice,
3515
+ * a forwarded log payload) pins the input's WHOLE backing store — with 64 KiB
3516
+ * socket read chunks that pinned 55k chunks (3.5 GB off-heap) on hub-main.
3517
+ * The exact-fit reassembly path (media frames, `Buffer.concat` sized to the
3518
+ * frame) wastes only the 4-byte header and stays zero-extra-copy.
3519
+ */
3520
+ var SLAB_WASTE_MAX_BYTES = 4096;
3521
+ /**
3524
3522
  * Reassembles length-prefixed MsgPack frames from a byte stream.
3525
3523
  *
3526
3524
  * **Why this is a chunk LIST and not a growing Buffer.** The original
@@ -3556,9 +3554,17 @@ var FrameDecoder = class {
3556
3554
  }
3557
3555
  const frames = [];
3558
3556
  while (this.buffered >= HEADER_BYTES) {
3559
- const total = HEADER_BYTES + this.readLength();
3557
+ const len = this.readLength();
3558
+ const total = HEADER_BYTES + len;
3560
3559
  if (this.buffered < total) break;
3561
- frames.push(decode(this.take(total).subarray(HEADER_BYTES)));
3560
+ const frame = this.take(total);
3561
+ const wasted = frame.buffer.byteLength - frame.byteLength;
3562
+ let body;
3563
+ if (wasted > SLAB_WASTE_MAX_BYTES) {
3564
+ body = Buffer.allocUnsafeSlow(len);
3565
+ frame.copy(body, 0, HEADER_BYTES);
3566
+ } else body = frame.subarray(HEADER_BYTES);
3567
+ frames.push(decode(body));
3562
3568
  }
3563
3569
  return frames;
3564
3570
  }