@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.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +3 -3
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/block-package.d.ts +9 -27
- package/dist/builtins/core-blocks/block-supervisor.d.ts +15 -0
- package/dist/builtins/core-blocks/core-blocks.addon.js +91 -24
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +91 -25
- package/dist/builtins/device-manager/device-bindings-store.d.ts +28 -5
- package/dist/builtins/device-manager/device-linked-devices.d.ts +2 -2
- package/dist/builtins/device-manager/device-manager.addon.d.ts +3 -45
- package/dist/builtins/device-manager/device-manager.addon.js +123 -884
- package/dist/builtins/device-manager/device-manager.addon.mjs +123 -884
- package/dist/builtins/device-manager/device-meta-actions.d.ts +0 -8
- package/dist/builtins/device-manager/device-meta-store.d.ts +1 -28
- package/dist/builtins/device-manager/device-meta-types.d.ts +2 -6
- package/dist/builtins/device-manager/device-projection.d.ts +1 -2
- package/dist/builtins/device-manager/device-provider-context.d.ts +2 -13
- package/dist/builtins/device-manager/device-queries.d.ts +1 -1
- package/dist/builtins/device-manager/device-state-mirror.d.ts +8 -49
- package/dist/builtins/device-manager/device-sync-resolvers.d.ts +0 -11
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/homeassistant-integration.d.ts +7 -0
- package/dist/builtins/local-auth/index.d.ts +1 -0
- package/dist/builtins/local-auth/index.js +1 -0
- package/dist/builtins/local-auth/index.mjs +2 -2
- package/dist/builtins/local-auth/local-auth.addon.js +561 -497
- package/dist/builtins/local-auth/local-auth.addon.mjs +580 -517
- package/dist/builtins/local-auth/oauth-grants.d.ts +11 -0
- package/dist/builtins/local-auth/oauth-session-manager.d.ts +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +2 -2
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +2 -2
- package/dist/builtins/platform-probe/index.js +2 -2
- package/dist/builtins/platform-probe/index.mjs +2 -2
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +319 -20
- package/dist/builtins/snapshot/index.mjs +319 -20
- package/dist/builtins/snapshot/snapshot-keep-warm.d.ts +86 -0
- package/dist/builtins/snapshot/snapshot-media-handler.d.ts +48 -1
- package/dist/builtins/snapshot/snapshot.addon.d.ts +65 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +15 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +73 -7
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +73 -7
- package/dist/builtins/sqlite-storage/vector-index-vec.d.ts +19 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-DnKpFaP1.mjs → dist-CMb7aKS0.mjs} +1227 -1330
- package/dist/{dist-DgPUarcs.js → dist-DP3nHlNM.js} +1251 -1366
- package/dist/http/asset-compression.d.ts +93 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +64 -9
- package/dist/index.mjs +65 -11
- package/dist/kernel/heap-watch.d.ts +55 -0
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -2
- package/dist/{manifest-python-deps-d4acYXVV.js → manifest-python-deps-BE6l8Y4W.js} +21 -15
- package/dist/{manifest-python-deps-CX5tGRqf.mjs → manifest-python-deps-Dz1ryHIN.mjs} +21 -15
- package/dist/{model-download-service-D8B-4ktF.mjs → model-download-service-CTBHzORJ.mjs} +220 -2
- package/dist/{model-download-service-Cpc6SWzV.js → model-download-service-Caj1kaSi.js} +220 -2
- package/package.json +5 -1
- package/dist/builtins/device-manager/device-link-cycle.d.ts +0 -18
- package/dist/builtins/device-manager/device-link-overlay.d.ts +0 -33
- package/dist/builtins/device-manager/device-link-resolver.d.ts +0 -44
- package/dist/main-CVLq8IzA.js +0 -2356
- 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-
|
|
4
|
-
const require_model_download_service = require("./model-download-service-
|
|
5
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
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,
|
|
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 {
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
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,
|
|
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
|
|
3559
|
+
const len = this.readLength();
|
|
3560
|
+
const total = HEADER_BYTES + len;
|
|
3562
3561
|
if (this.buffered < total) break;
|
|
3563
|
-
|
|
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
|
|
3557
|
+
const len = this.readLength();
|
|
3558
|
+
const total = HEADER_BYTES + len;
|
|
3560
3559
|
if (this.buffered < total) break;
|
|
3561
|
-
|
|
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
|
}
|