@camstack/system 1.2.28 → 1.2.30
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 +3 -3
- package/dist/addon-runner.mjs +2 -2
- 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.d.ts +0 -7
- package/dist/builtins/alerts/alerts.addon.js +47 -25
- package/dist/builtins/alerts/alerts.addon.mjs +47 -25
- package/dist/builtins/alerts/prune-policy.d.ts +44 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- 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/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +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 +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- 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 +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -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/filter-compiler.d.ts +61 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +50 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +8 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +136 -64
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +136 -64
- package/dist/builtins/storage-orchestrator/data-store-dispatch.d.ts +12 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +18 -7
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +80 -11
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +80 -11
- 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-BXmFvn3h.mjs → dist-Dr_P0DOB.mjs} +492 -166
- package/dist/{dist-BxtIzexq.js → dist-VIwdvws5.js} +497 -165
- package/dist/index.d.ts +2 -0
- package/dist/index.js +150 -2
- package/dist/index.mjs +146 -3
- package/dist/kernel/addon-installer.d.ts +38 -0
- package/dist/kernel/heap-watch.d.ts +36 -0
- package/dist/kernel/transport/uds-event-bus.d.ts +4 -1
- package/dist/{manifest-python-deps-KZ00cbfP.js → manifest-python-deps-BqE5j0-O.js} +25 -4
- package/dist/{manifest-python-deps-BNOApgK0.mjs → manifest-python-deps-Ck4-9K9m.mjs} +25 -4
- package/package.json +7 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { startHeapWatch, buildHeapSample, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, } from './kernel/heap-watch.js';
|
|
2
|
+
export type { HeapSample, HeapWatchSink } from './kernel/heap-watch.js';
|
|
1
3
|
export { ensureBinary, downloadBinary, findInPath, getPlatformInfo, buildBinaryPath, ensureFfmpeg, getFfmpegDownloadUrl, ensurePython, installPythonPackages, installPythonRequirements, getPythonDownloadUrl, PYTHON_VERSION, } from '@camstack/types/node';
|
|
2
4
|
export type { PlatformInfo } from '@camstack/types/node';
|
|
3
5
|
export { ALL_CAPABILITY_DEFINITIONS, METHOD_ACCESS_MAP, expandCapMethods, isArrayOutputSchema, isCollectionArrayMethod, addonSettingsCapability, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, } from '@camstack/types';
|
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-
|
|
3
|
+
const require_dist = require("./dist-VIwdvws5.js");
|
|
4
4
|
const require_model_download_service = require("./model-download-service-hf0ookyy.js");
|
|
5
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
5
|
+
const require_manifest_python_deps = require("./manifest-python-deps-BqE5j0-O.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");
|
|
@@ -24,6 +24,8 @@ require("./builtins/local-auth/index.js");
|
|
|
24
24
|
const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
|
|
25
25
|
require("./builtins/device-manager/index.js");
|
|
26
26
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
27
|
+
let node_v8 = require("node:v8");
|
|
28
|
+
node_v8 = require_chunk.__toESM(node_v8);
|
|
27
29
|
let _camstack_types_node = require("@camstack/types/node");
|
|
28
30
|
let zod = require("zod");
|
|
29
31
|
let node_http = require("node:http");
|
|
@@ -52,6 +54,100 @@ fs = require_chunk.__toESM(fs, 1);
|
|
|
52
54
|
let path = require("path");
|
|
53
55
|
path = require_chunk.__toESM(path, 1);
|
|
54
56
|
let module$1 = require("module");
|
|
57
|
+
//#region src/kernel/heap-watch.ts
|
|
58
|
+
/**
|
|
59
|
+
* heap-watch — hub-main's own memory, on the record.
|
|
60
|
+
*
|
|
61
|
+
* ## Why this exists
|
|
62
|
+
*
|
|
63
|
+
* hub-main is launched with `--max-old-space-size=1536` (docker-entrypoint) and
|
|
64
|
+
* has logged NOTHING about its own heap. When it died on 2026-08-01/02 the only
|
|
65
|
+
* trace was V8's own stderr line:
|
|
66
|
+
*
|
|
67
|
+
* FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
|
|
68
|
+
*
|
|
69
|
+
* which carries no process name, no heap number and no history — the whole
|
|
70
|
+
* server restarts and the evidence is gone. Four of those in ~15h, and the one
|
|
71
|
+
* at 06:57:57 landed nine seconds after a viewer opened a WebRTC session, which
|
|
72
|
+
* is why the app sat in a permanent "loading": the hub was dying as it
|
|
73
|
+
* connected.
|
|
74
|
+
*
|
|
75
|
+
* `[mem]` reports already exist for forked addon runners (SIGUSR2 in
|
|
76
|
+
* `addon-runner.ts`) — hub-main, the one process that actually OOMs, was the
|
|
77
|
+
* one process with no telemetry at all.
|
|
78
|
+
*
|
|
79
|
+
* ## What it gives
|
|
80
|
+
*
|
|
81
|
+
* A heartbeat carrying the numbers that separate the two possible stories, and
|
|
82
|
+
* a WARN as the heap approaches the ceiling — the line that will exist BEFORE
|
|
83
|
+
* the next fatal instead of after it:
|
|
84
|
+
*
|
|
85
|
+
* - `heapUsedMb` climbing steadily between restarts → a leak;
|
|
86
|
+
* - `heapUsedMb` flat with a sharp jump under load → a burst allocation, and
|
|
87
|
+
* the timestamp says what was being served.
|
|
88
|
+
*
|
|
89
|
+
* `external`/`arrayBuffers` ride along because they are NOT bounded by
|
|
90
|
+
* `--max-old-space-size` and are most of the gap between a 1.5GB cap and the
|
|
91
|
+
* ~2.05GB RSS observed live: a diagnosis that only looks at the JS heap
|
|
92
|
+
* misreads that process by 500MB.
|
|
93
|
+
*/
|
|
94
|
+
/** Heartbeat period. Slow enough to be free, fast enough that the sample before
|
|
95
|
+
* a fatal is recent. */
|
|
96
|
+
var HEAP_WATCH_INTERVAL_MS = 6e4;
|
|
97
|
+
/** Warn above this share of the heap ceiling. Chosen so the warning precedes
|
|
98
|
+
* the fatal by more than one interval under realistic growth. */
|
|
99
|
+
var HEAP_WATCH_WARN_RATIO = .8;
|
|
100
|
+
var MB = (bytes) => Math.round(bytes / 1048576);
|
|
101
|
+
/**
|
|
102
|
+
* Build a sample from raw readings. Pure so the thresholds are testable without
|
|
103
|
+
* allocating a gigabyte to reach them.
|
|
104
|
+
*/
|
|
105
|
+
function buildHeapSample(mem, heapLimitBytes, warnRatio = HEAP_WATCH_WARN_RATIO) {
|
|
106
|
+
const usedRatio = heapLimitBytes > 0 ? mem.heapUsed / heapLimitBytes : 0;
|
|
107
|
+
return {
|
|
108
|
+
rssMb: MB(mem.rss),
|
|
109
|
+
heapUsedMb: MB(mem.heapUsed),
|
|
110
|
+
heapTotalMb: MB(mem.heapTotal),
|
|
111
|
+
heapLimitMb: MB(heapLimitBytes),
|
|
112
|
+
externalMb: MB(mem.external),
|
|
113
|
+
arrayBuffersMb: MB(mem.arrayBuffers),
|
|
114
|
+
usedRatio: Math.round(usedRatio * 100) / 100,
|
|
115
|
+
nearLimit: usedRatio >= warnRatio
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
var consoleSink = {
|
|
119
|
+
info: (line) => console.log(line),
|
|
120
|
+
warn: (line) => console.warn(line)
|
|
121
|
+
};
|
|
122
|
+
function format$1(label, s) {
|
|
123
|
+
return `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Start the heartbeat. Returns a stop function.
|
|
127
|
+
*
|
|
128
|
+
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
129
|
+
* a diagnostic that can crash the process it watches is worse than none.
|
|
130
|
+
*/
|
|
131
|
+
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS) {
|
|
132
|
+
const tick = () => {
|
|
133
|
+
try {
|
|
134
|
+
const sample = buildHeapSample(process.memoryUsage(), node_v8.getHeapStatistics().heap_size_limit);
|
|
135
|
+
const line = format$1(label, sample);
|
|
136
|
+
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
137
|
+
else sink.info(line);
|
|
138
|
+
} catch {}
|
|
139
|
+
};
|
|
140
|
+
const timer = setInterval(tick, intervalMs);
|
|
141
|
+
timer.unref?.();
|
|
142
|
+
tick();
|
|
143
|
+
let stopped = false;
|
|
144
|
+
return () => {
|
|
145
|
+
if (stopped) return;
|
|
146
|
+
stopped = true;
|
|
147
|
+
clearInterval(timer);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
55
151
|
//#region src/events/event-bus.ts
|
|
56
152
|
var EventBus = class {
|
|
57
153
|
listeners = /* @__PURE__ */ new Map();
|
|
@@ -3478,6 +3574,25 @@ function readPackageJson(pkgJsonPath) {
|
|
|
3478
3574
|
camstackAddons: Array.isArray(addonsField) ? addonsField : null
|
|
3479
3575
|
};
|
|
3480
3576
|
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Should the bootstrap SKIP seeding `packageName` into /data/addons?
|
|
3579
|
+
*
|
|
3580
|
+
* True only for the package the closure provides, and only when it actually
|
|
3581
|
+
* resolves. Both halves are load-bearing:
|
|
3582
|
+
*
|
|
3583
|
+
* - seeding a copy the closure already provides creates one that WINS over
|
|
3584
|
+
* the closure and is never refreshed (bootstrap is seed-only, and
|
|
3585
|
+
* `camstack deploy` of a framework package is rejected on an image hub), so
|
|
3586
|
+
* the node runs first-boot builtins forever while reporting the new
|
|
3587
|
+
* version;
|
|
3588
|
+
* - skipping it when it does NOT resolve is how a fresh deployment was once
|
|
3589
|
+
* made unbootable ("No addon provides required infrastructure capability
|
|
3590
|
+
* storage-provider"). That is why the guard is resolvability rather than a
|
|
3591
|
+
* blanket removal from the bootstrap list.
|
|
3592
|
+
*/
|
|
3593
|
+
function shouldSkipClosureProvidedSeed(packageName, isResolvable) {
|
|
3594
|
+
return packageName === "@camstack/system" && isResolvable;
|
|
3595
|
+
}
|
|
3481
3596
|
/** Minimal no-op logger for default parameter */
|
|
3482
3597
|
var noopLogger = {
|
|
3483
3598
|
debug() {},
|
|
@@ -3497,6 +3612,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3497
3612
|
workspaceDir;
|
|
3498
3613
|
installSource;
|
|
3499
3614
|
logger;
|
|
3615
|
+
isPackageResolvable;
|
|
3500
3616
|
/**
|
|
3501
3617
|
* Central manifest of installed addons. Tracks version + source +
|
|
3502
3618
|
* timestamps + last-backup pointer per package, written atomically
|
|
@@ -3512,6 +3628,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3512
3628
|
this.workspaceDir = config.workspacePackagesDir;
|
|
3513
3629
|
this.logger = logger;
|
|
3514
3630
|
this.manifest = new AddonManifest(config.addonsDir);
|
|
3631
|
+
this.isPackageResolvable = config.isPackageResolvable;
|
|
3515
3632
|
if (config.installSource) this.installSource = config.installSource;
|
|
3516
3633
|
else this.installSource = "npm";
|
|
3517
3634
|
}
|
|
@@ -3540,6 +3657,25 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3540
3657
|
* hardcoded lists — see design spec
|
|
3541
3658
|
* docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
|
|
3542
3659
|
*/
|
|
3660
|
+
/**
|
|
3661
|
+
* Is `packageName` reachable through normal resolution — i.e. does the
|
|
3662
|
+
* running closure already provide it?
|
|
3663
|
+
*
|
|
3664
|
+
* Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
|
|
3665
|
+
* exercise BOTH sides. That matters more than it looks: the branch that
|
|
3666
|
+
* still seeds is the one protecting a fresh deployment from an unbootable
|
|
3667
|
+
* node, and it is unreachable in a test process where `@camstack/system`
|
|
3668
|
+
* always resolves.
|
|
3669
|
+
*/
|
|
3670
|
+
isResolvableFromClosure(packageName) {
|
|
3671
|
+
if (this.isPackageResolvable) return this.isPackageResolvable(packageName);
|
|
3672
|
+
try {
|
|
3673
|
+
require.resolve(`${packageName}/package.json`);
|
|
3674
|
+
return true;
|
|
3675
|
+
} catch {
|
|
3676
|
+
return false;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3543
3679
|
static deriveBootstrapList(metaPackageName) {
|
|
3544
3680
|
const pkgJsonId = `${metaPackageName}/package.json`;
|
|
3545
3681
|
let pkgPath;
|
|
@@ -3622,6 +3758,10 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3622
3758
|
this.logger.debug(`${packageName} — already installed, skipping`);
|
|
3623
3759
|
continue;
|
|
3624
3760
|
}
|
|
3761
|
+
if (shouldSkipClosureProvidedSeed(packageName, this.isResolvableFromClosure(packageName))) {
|
|
3762
|
+
this.logger.info(`${packageName} — provided by the server closure, not seeding a copy that would shadow it`);
|
|
3763
|
+
continue;
|
|
3764
|
+
}
|
|
3625
3765
|
try {
|
|
3626
3766
|
if (isLocal) {
|
|
3627
3767
|
const pkgDir = this.findLocalPackage(packageName);
|
|
@@ -5948,6 +6088,10 @@ var INFRA_CAPABILITIES = [
|
|
|
5948
6088
|
name: "storage-provider",
|
|
5949
6089
|
required: true
|
|
5950
6090
|
},
|
|
6091
|
+
{
|
|
6092
|
+
name: "data-store-provider",
|
|
6093
|
+
required: true
|
|
6094
|
+
},
|
|
5951
6095
|
{
|
|
5952
6096
|
name: "storage",
|
|
5953
6097
|
required: true
|
|
@@ -94119,6 +94263,8 @@ exports.FsStorageBackend = FsStorageBackend;
|
|
|
94119
94263
|
exports.HEALTH_MONITOR_GRACE_PERIOD_MS = HEALTH_MONITOR_GRACE_PERIOD_MS;
|
|
94120
94264
|
exports.HEALTH_MONITOR_RETRY_INTERVALS_MS = HEALTH_MONITOR_RETRY_INTERVALS_MS;
|
|
94121
94265
|
exports.HEALTH_MONITOR_TICK_MS = HEALTH_MONITOR_TICK_MS;
|
|
94266
|
+
exports.HEAP_WATCH_INTERVAL_MS = HEAP_WATCH_INTERVAL_MS;
|
|
94267
|
+
exports.HEAP_WATCH_WARN_RATIO = HEAP_WATCH_WARN_RATIO;
|
|
94122
94268
|
exports.HUB_CAP_FWD_ACTION = require_manifest_python_deps.HUB_CAP_FWD_ACTION;
|
|
94123
94269
|
exports.HUB_CAP_FWD_SERVICE = require_manifest_python_deps.HUB_CAP_FWD_SERVICE;
|
|
94124
94270
|
exports.HubForwarderAddon = require_builtins_hub_forwarder_index.HubForwarderAddon$1;
|
|
@@ -94193,6 +94339,7 @@ Object.defineProperty(exports, "buildBinaryPath", {
|
|
|
94193
94339
|
}
|
|
94194
94340
|
});
|
|
94195
94341
|
exports.buildCapRouters = buildCapRouters;
|
|
94342
|
+
exports.buildHeapSample = buildHeapSample;
|
|
94196
94343
|
exports.buildLinkChain = require_manifest_python_deps.buildLinkChain;
|
|
94197
94344
|
exports.buildNativeCapProxy = require_manifest_python_deps.buildNativeCapProxy;
|
|
94198
94345
|
exports.buildNodeManifest = buildNodeManifest;
|
|
@@ -94358,6 +94505,7 @@ exports.scopesAllowDeviceCap = scopesAllowDeviceCap;
|
|
|
94358
94505
|
exports.serializeTypedArrays = require_manifest_python_deps.serializeTypedArrays;
|
|
94359
94506
|
exports.setHubConnected = require_manifest_python_deps.setHubConnected;
|
|
94360
94507
|
exports.setNodeEventInterest = require_manifest_python_deps.setNodeEventInterest;
|
|
94508
|
+
exports.startHeapWatch = startHeapWatch;
|
|
94361
94509
|
exports.stripCamstackDeps = stripCamstackDeps;
|
|
94362
94510
|
exports.subscribePassthrough = require_manifest_python_deps.subscribePassthrough;
|
|
94363
94511
|
exports.udsChildLogToWorkerEntry = require_manifest_python_deps.udsChildLogToWorkerEntry;
|
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 {
|
|
2
|
+
import { A as extractNestedAddonId, At as scopeKey, Dt as parseJsonUnknown$1, Et as parseJsonObject, F as isObjectInput, G as objectInputDeclaresAddonId, H as looseSchema, I as isVoidInput, J as procedureAuthKey, L as kebabToCamel, Mt as EventCategory$1, N as isArrayOutputSchema, Ot as readinessKey, P as isCollectionArrayMethod, R as lifecycleJobSchema, St as expandCapMethods, V as logLevelAtMost, _t as asNumber, at as errMsg$1, bt as emitDownForOwnedCaps, c as RUNTIME_DEFAULTS, ct as DEVICE_SETTINGS_CONTRIBUTION_METHODS, gt as asJsonObject$1, kt as resolveCapMount, lt as DEVICE_STATUS_METHOD, m as addonSettingsCapability, mt as ReadinessTimeoutError, pt as ReadinessRegistry, s as METHOD_ACCESS_MAP, st as DATAPLANE_SECRET_HEADER$1, t as ALL_CAPABILITY_DEFINITIONS, vt as asString$1, yt as createEvent } from "./dist-Dr_P0DOB.mjs";
|
|
3
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-Cp9f4dk6.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-
|
|
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-Ck4-9K9m.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, t as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
|
|
@@ -23,6 +23,7 @@ import "./builtins/local-auth/index.mjs";
|
|
|
23
23
|
import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.addon.mjs";
|
|
24
24
|
import "./builtins/device-manager/index.mjs";
|
|
25
25
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
26
|
+
import * as v8 from "node:v8";
|
|
26
27
|
import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
|
|
27
28
|
import { z } from "zod";
|
|
28
29
|
import { request } from "node:http";
|
|
@@ -45,6 +46,100 @@ import { StringDecoder } from "node:string_decoder";
|
|
|
45
46
|
import { lstatSync, readdir as readdir$1, readdirSync, readlinkSync, realpathSync } from "fs";
|
|
46
47
|
import "path";
|
|
47
48
|
import { createRequire } from "module";
|
|
49
|
+
//#region src/kernel/heap-watch.ts
|
|
50
|
+
/**
|
|
51
|
+
* heap-watch — hub-main's own memory, on the record.
|
|
52
|
+
*
|
|
53
|
+
* ## Why this exists
|
|
54
|
+
*
|
|
55
|
+
* hub-main is launched with `--max-old-space-size=1536` (docker-entrypoint) and
|
|
56
|
+
* has logged NOTHING about its own heap. When it died on 2026-08-01/02 the only
|
|
57
|
+
* trace was V8's own stderr line:
|
|
58
|
+
*
|
|
59
|
+
* FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
|
|
60
|
+
*
|
|
61
|
+
* which carries no process name, no heap number and no history — the whole
|
|
62
|
+
* server restarts and the evidence is gone. Four of those in ~15h, and the one
|
|
63
|
+
* at 06:57:57 landed nine seconds after a viewer opened a WebRTC session, which
|
|
64
|
+
* is why the app sat in a permanent "loading": the hub was dying as it
|
|
65
|
+
* connected.
|
|
66
|
+
*
|
|
67
|
+
* `[mem]` reports already exist for forked addon runners (SIGUSR2 in
|
|
68
|
+
* `addon-runner.ts`) — hub-main, the one process that actually OOMs, was the
|
|
69
|
+
* one process with no telemetry at all.
|
|
70
|
+
*
|
|
71
|
+
* ## What it gives
|
|
72
|
+
*
|
|
73
|
+
* A heartbeat carrying the numbers that separate the two possible stories, and
|
|
74
|
+
* a WARN as the heap approaches the ceiling — the line that will exist BEFORE
|
|
75
|
+
* the next fatal instead of after it:
|
|
76
|
+
*
|
|
77
|
+
* - `heapUsedMb` climbing steadily between restarts → a leak;
|
|
78
|
+
* - `heapUsedMb` flat with a sharp jump under load → a burst allocation, and
|
|
79
|
+
* the timestamp says what was being served.
|
|
80
|
+
*
|
|
81
|
+
* `external`/`arrayBuffers` ride along because they are NOT bounded by
|
|
82
|
+
* `--max-old-space-size` and are most of the gap between a 1.5GB cap and the
|
|
83
|
+
* ~2.05GB RSS observed live: a diagnosis that only looks at the JS heap
|
|
84
|
+
* misreads that process by 500MB.
|
|
85
|
+
*/
|
|
86
|
+
/** Heartbeat period. Slow enough to be free, fast enough that the sample before
|
|
87
|
+
* a fatal is recent. */
|
|
88
|
+
var HEAP_WATCH_INTERVAL_MS = 6e4;
|
|
89
|
+
/** Warn above this share of the heap ceiling. Chosen so the warning precedes
|
|
90
|
+
* the fatal by more than one interval under realistic growth. */
|
|
91
|
+
var HEAP_WATCH_WARN_RATIO = .8;
|
|
92
|
+
var MB = (bytes) => Math.round(bytes / 1048576);
|
|
93
|
+
/**
|
|
94
|
+
* Build a sample from raw readings. Pure so the thresholds are testable without
|
|
95
|
+
* allocating a gigabyte to reach them.
|
|
96
|
+
*/
|
|
97
|
+
function buildHeapSample(mem, heapLimitBytes, warnRatio = HEAP_WATCH_WARN_RATIO) {
|
|
98
|
+
const usedRatio = heapLimitBytes > 0 ? mem.heapUsed / heapLimitBytes : 0;
|
|
99
|
+
return {
|
|
100
|
+
rssMb: MB(mem.rss),
|
|
101
|
+
heapUsedMb: MB(mem.heapUsed),
|
|
102
|
+
heapTotalMb: MB(mem.heapTotal),
|
|
103
|
+
heapLimitMb: MB(heapLimitBytes),
|
|
104
|
+
externalMb: MB(mem.external),
|
|
105
|
+
arrayBuffersMb: MB(mem.arrayBuffers),
|
|
106
|
+
usedRatio: Math.round(usedRatio * 100) / 100,
|
|
107
|
+
nearLimit: usedRatio >= warnRatio
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
var consoleSink = {
|
|
111
|
+
info: (line) => console.log(line),
|
|
112
|
+
warn: (line) => console.warn(line)
|
|
113
|
+
};
|
|
114
|
+
function format$1(label, s) {
|
|
115
|
+
return `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Start the heartbeat. Returns a stop function.
|
|
119
|
+
*
|
|
120
|
+
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
121
|
+
* a diagnostic that can crash the process it watches is worse than none.
|
|
122
|
+
*/
|
|
123
|
+
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS) {
|
|
124
|
+
const tick = () => {
|
|
125
|
+
try {
|
|
126
|
+
const sample = buildHeapSample(process.memoryUsage(), v8.getHeapStatistics().heap_size_limit);
|
|
127
|
+
const line = format$1(label, sample);
|
|
128
|
+
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
129
|
+
else sink.info(line);
|
|
130
|
+
} catch {}
|
|
131
|
+
};
|
|
132
|
+
const timer = setInterval(tick, intervalMs);
|
|
133
|
+
timer.unref?.();
|
|
134
|
+
tick();
|
|
135
|
+
let stopped = false;
|
|
136
|
+
return () => {
|
|
137
|
+
if (stopped) return;
|
|
138
|
+
stopped = true;
|
|
139
|
+
clearInterval(timer);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
48
143
|
//#region src/events/event-bus.ts
|
|
49
144
|
var EventBus = class {
|
|
50
145
|
listeners = /* @__PURE__ */ new Map();
|
|
@@ -3471,6 +3566,25 @@ function readPackageJson(pkgJsonPath) {
|
|
|
3471
3566
|
camstackAddons: Array.isArray(addonsField) ? addonsField : null
|
|
3472
3567
|
};
|
|
3473
3568
|
}
|
|
3569
|
+
/**
|
|
3570
|
+
* Should the bootstrap SKIP seeding `packageName` into /data/addons?
|
|
3571
|
+
*
|
|
3572
|
+
* True only for the package the closure provides, and only when it actually
|
|
3573
|
+
* resolves. Both halves are load-bearing:
|
|
3574
|
+
*
|
|
3575
|
+
* - seeding a copy the closure already provides creates one that WINS over
|
|
3576
|
+
* the closure and is never refreshed (bootstrap is seed-only, and
|
|
3577
|
+
* `camstack deploy` of a framework package is rejected on an image hub), so
|
|
3578
|
+
* the node runs first-boot builtins forever while reporting the new
|
|
3579
|
+
* version;
|
|
3580
|
+
* - skipping it when it does NOT resolve is how a fresh deployment was once
|
|
3581
|
+
* made unbootable ("No addon provides required infrastructure capability
|
|
3582
|
+
* storage-provider"). That is why the guard is resolvability rather than a
|
|
3583
|
+
* blanket removal from the bootstrap list.
|
|
3584
|
+
*/
|
|
3585
|
+
function shouldSkipClosureProvidedSeed(packageName, isResolvable) {
|
|
3586
|
+
return packageName === "@camstack/system" && isResolvable;
|
|
3587
|
+
}
|
|
3474
3588
|
/** Minimal no-op logger for default parameter */
|
|
3475
3589
|
var noopLogger = {
|
|
3476
3590
|
debug() {},
|
|
@@ -3490,6 +3604,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3490
3604
|
workspaceDir;
|
|
3491
3605
|
installSource;
|
|
3492
3606
|
logger;
|
|
3607
|
+
isPackageResolvable;
|
|
3493
3608
|
/**
|
|
3494
3609
|
* Central manifest of installed addons. Tracks version + source +
|
|
3495
3610
|
* timestamps + last-backup pointer per package, written atomically
|
|
@@ -3505,6 +3620,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3505
3620
|
this.workspaceDir = config.workspacePackagesDir;
|
|
3506
3621
|
this.logger = logger;
|
|
3507
3622
|
this.manifest = new AddonManifest(config.addonsDir);
|
|
3623
|
+
this.isPackageResolvable = config.isPackageResolvable;
|
|
3508
3624
|
if (config.installSource) this.installSource = config.installSource;
|
|
3509
3625
|
else this.installSource = "npm";
|
|
3510
3626
|
}
|
|
@@ -3533,6 +3649,25 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3533
3649
|
* hardcoded lists — see design spec
|
|
3534
3650
|
* docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
|
|
3535
3651
|
*/
|
|
3652
|
+
/**
|
|
3653
|
+
* Is `packageName` reachable through normal resolution — i.e. does the
|
|
3654
|
+
* running closure already provide it?
|
|
3655
|
+
*
|
|
3656
|
+
* Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
|
|
3657
|
+
* exercise BOTH sides. That matters more than it looks: the branch that
|
|
3658
|
+
* still seeds is the one protecting a fresh deployment from an unbootable
|
|
3659
|
+
* node, and it is unreachable in a test process where `@camstack/system`
|
|
3660
|
+
* always resolves.
|
|
3661
|
+
*/
|
|
3662
|
+
isResolvableFromClosure(packageName) {
|
|
3663
|
+
if (this.isPackageResolvable) return this.isPackageResolvable(packageName);
|
|
3664
|
+
try {
|
|
3665
|
+
__require.resolve(`${packageName}/package.json`);
|
|
3666
|
+
return true;
|
|
3667
|
+
} catch {
|
|
3668
|
+
return false;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3536
3671
|
static deriveBootstrapList(metaPackageName) {
|
|
3537
3672
|
const pkgJsonId = `${metaPackageName}/package.json`;
|
|
3538
3673
|
let pkgPath;
|
|
@@ -3615,6 +3750,10 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3615
3750
|
this.logger.debug(`${packageName} — already installed, skipping`);
|
|
3616
3751
|
continue;
|
|
3617
3752
|
}
|
|
3753
|
+
if (shouldSkipClosureProvidedSeed(packageName, this.isResolvableFromClosure(packageName))) {
|
|
3754
|
+
this.logger.info(`${packageName} — provided by the server closure, not seeding a copy that would shadow it`);
|
|
3755
|
+
continue;
|
|
3756
|
+
}
|
|
3618
3757
|
try {
|
|
3619
3758
|
if (isLocal) {
|
|
3620
3759
|
const pkgDir = this.findLocalPackage(packageName);
|
|
@@ -5941,6 +6080,10 @@ var INFRA_CAPABILITIES = [
|
|
|
5941
6080
|
name: "storage-provider",
|
|
5942
6081
|
required: true
|
|
5943
6082
|
},
|
|
6083
|
+
{
|
|
6084
|
+
name: "data-store-provider",
|
|
6085
|
+
required: true
|
|
6086
|
+
},
|
|
5944
6087
|
{
|
|
5945
6088
|
name: "storage",
|
|
5946
6089
|
required: true
|
|
@@ -94065,4 +94208,4 @@ var LifecycleJobEngine = class {
|
|
|
94065
94208
|
}
|
|
94066
94209
|
};
|
|
94067
94210
|
//#endregion
|
|
94068
|
-
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, 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, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, 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, 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, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, 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, 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, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
|
|
94211
|
+
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, 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_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, 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, 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, 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, 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, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, startHeapWatch, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
|
|
@@ -22,6 +22,25 @@ import { AddonManifest } from './addon-manifest.js';
|
|
|
22
22
|
* `addonsDir/<pkg>` → `packages/<pkg>`.
|
|
23
23
|
*/
|
|
24
24
|
export type InstallSource = 'npm' | 'local' | 'symlink';
|
|
25
|
+
/** The one bootstrap package the server closure carries itself. */
|
|
26
|
+
export declare const CLOSURE_PROVIDED_PACKAGE = "@camstack/system";
|
|
27
|
+
/**
|
|
28
|
+
* Should the bootstrap SKIP seeding `packageName` into /data/addons?
|
|
29
|
+
*
|
|
30
|
+
* True only for the package the closure provides, and only when it actually
|
|
31
|
+
* resolves. Both halves are load-bearing:
|
|
32
|
+
*
|
|
33
|
+
* - seeding a copy the closure already provides creates one that WINS over
|
|
34
|
+
* the closure and is never refreshed (bootstrap is seed-only, and
|
|
35
|
+
* `camstack deploy` of a framework package is rejected on an image hub), so
|
|
36
|
+
* the node runs first-boot builtins forever while reporting the new
|
|
37
|
+
* version;
|
|
38
|
+
* - skipping it when it does NOT resolve is how a fresh deployment was once
|
|
39
|
+
* made unbootable ("No addon provides required infrastructure capability
|
|
40
|
+
* storage-provider"). That is why the guard is resolvability rather than a
|
|
41
|
+
* blanket removal from the bootstrap list.
|
|
42
|
+
*/
|
|
43
|
+
export declare function shouldSkipClosureProvidedSeed(packageName: string, isResolvable: boolean): boolean;
|
|
25
44
|
export interface AddonInstallerConfig {
|
|
26
45
|
/** Directory where addons are stored (e.g., {dataDir}/addons) */
|
|
27
46
|
readonly addonsDir: string;
|
|
@@ -31,6 +50,13 @@ export interface AddonInstallerConfig {
|
|
|
31
50
|
readonly workspacePackagesDir?: string;
|
|
32
51
|
/** Install source mode. Default: auto-detect */
|
|
33
52
|
readonly installSource?: InstallSource;
|
|
53
|
+
/**
|
|
54
|
+
* Override for "does the running closure already provide this package?".
|
|
55
|
+
* Production leaves it unset (real `require.resolve`); a spec sets it to
|
|
56
|
+
* reach the branch that still seeds `@camstack/system`, which is otherwise
|
|
57
|
+
* unreachable in a process where that package always resolves.
|
|
58
|
+
*/
|
|
59
|
+
readonly isPackageResolvable?: (packageName: string) => boolean;
|
|
34
60
|
}
|
|
35
61
|
export type { InstalledPackage };
|
|
36
62
|
/** Outcome of {@link AddonInstaller.sweepBackups}. Absolute paths. */
|
|
@@ -46,6 +72,7 @@ export declare class AddonInstaller {
|
|
|
46
72
|
readonly workspaceDir: string | undefined;
|
|
47
73
|
readonly installSource: InstallSource;
|
|
48
74
|
private readonly logger;
|
|
75
|
+
private readonly isPackageResolvable?;
|
|
49
76
|
/**
|
|
50
77
|
* Central manifest of installed addons. Tracks version + source +
|
|
51
78
|
* timestamps + last-backup pointer per package, written atomically
|
|
@@ -71,6 +98,17 @@ export declare class AddonInstaller {
|
|
|
71
98
|
* hardcoded lists — see design spec
|
|
72
99
|
* docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
|
|
73
100
|
*/
|
|
101
|
+
/**
|
|
102
|
+
* Is `packageName` reachable through normal resolution — i.e. does the
|
|
103
|
+
* running closure already provide it?
|
|
104
|
+
*
|
|
105
|
+
* Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
|
|
106
|
+
* exercise BOTH sides. That matters more than it looks: the branch that
|
|
107
|
+
* still seeds is the one protecting a fresh deployment from an unbootable
|
|
108
|
+
* node, and it is unreachable in a test process where `@camstack/system`
|
|
109
|
+
* always resolves.
|
|
110
|
+
*/
|
|
111
|
+
private isResolvableFromClosure;
|
|
74
112
|
private static deriveBootstrapList;
|
|
75
113
|
/**
|
|
76
114
|
* Bootstrap packages installed on first boot — derived from the
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Heartbeat period. Slow enough to be free, fast enough that the sample before
|
|
2
|
+
* a fatal is recent. */
|
|
3
|
+
export declare const HEAP_WATCH_INTERVAL_MS = 60000;
|
|
4
|
+
/** Warn above this share of the heap ceiling. Chosen so the warning precedes
|
|
5
|
+
* the fatal by more than one interval under realistic growth. */
|
|
6
|
+
export declare const HEAP_WATCH_WARN_RATIO = 0.8;
|
|
7
|
+
export interface HeapSample {
|
|
8
|
+
readonly rssMb: number;
|
|
9
|
+
readonly heapUsedMb: number;
|
|
10
|
+
readonly heapTotalMb: number;
|
|
11
|
+
readonly heapLimitMb: number;
|
|
12
|
+
readonly externalMb: number;
|
|
13
|
+
readonly arrayBuffersMb: number;
|
|
14
|
+
/** heapUsed / heapLimit, 0-1. */
|
|
15
|
+
readonly usedRatio: number;
|
|
16
|
+
/** True once the heap is close enough to the ceiling to be worth shouting. */
|
|
17
|
+
readonly nearLimit: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build a sample from raw readings. Pure so the thresholds are testable without
|
|
21
|
+
* allocating a gigabyte to reach them.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildHeapSample(mem: NodeJS.MemoryUsage, heapLimitBytes: number, warnRatio?: number): HeapSample;
|
|
24
|
+
/** Minimal sink so this module stays usable before any logger exists — the
|
|
25
|
+
* starter runs before the kernel's logging is wired. */
|
|
26
|
+
export interface HeapWatchSink {
|
|
27
|
+
readonly info: (line: string) => void;
|
|
28
|
+
readonly warn: (line: string) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Start the heartbeat. Returns a stop function.
|
|
32
|
+
*
|
|
33
|
+
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
34
|
+
* a diagnostic that can crash the process it watches is worse than none.
|
|
35
|
+
*/
|
|
36
|
+
export declare function startHeapWatch(label?: string, sink?: HeapWatchSink, intervalMs?: number): () => void;
|
|
@@ -5,7 +5,9 @@ import { IEventBus, SystemEvent } from '@camstack/types';
|
|
|
5
5
|
*
|
|
6
6
|
* - `emitEvent` — fire-and-forget: send a system event to the parent.
|
|
7
7
|
* - `onEvent` — register the single handler for parent→child events.
|
|
8
|
-
* (Only one handler is registered; calling again replaces the previous one.
|
|
8
|
+
* (Only one handler is registered; calling again replaces the previous one.
|
|
9
|
+
* Because the slot is single, this module registers it exactly ONCE per
|
|
10
|
+
* channel — the shared-state factory below — never once per addon bus.)
|
|
9
11
|
* - `updateEventPatterns` — declare this owner's live category-pattern set to
|
|
10
12
|
* the parent so it can subscription-filter the event fan-out. Called after
|
|
11
13
|
* every subscribe/unsubscribe with the CURRENT key set (full-set replace).
|
|
@@ -19,6 +21,7 @@ export interface UdsEventChannel {
|
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Create a UDS-backed `IEventBus` for use inside a forked child process.
|
|
24
|
+
* Returns a per-addon facade over the channel's ONE shared state (module doc).
|
|
22
25
|
*
|
|
23
26
|
* @param client - The UDS channel toward the parent (hub or agent).
|
|
24
27
|
* @param addonId - This child's stable addon id — stamped as `sourceAddonId`
|
|
@@ -5164,7 +5164,31 @@ function udsChildLogToWorkerEntry(childId, entry) {
|
|
|
5164
5164
|
//#endregion
|
|
5165
5165
|
//#region src/kernel/transport/uds-event-bus.ts
|
|
5166
5166
|
/**
|
|
5167
|
+
* ONE `SharedBusState` per `UdsEventChannel` (see the module doc's
|
|
5168
|
+
* group-runner contract). Keyed weakly so a channel torn down with its
|
|
5169
|
+
* process does not pin its state.
|
|
5170
|
+
*/
|
|
5171
|
+
var sharedStateByChannel = /* @__PURE__ */ new WeakMap();
|
|
5172
|
+
/**
|
|
5173
|
+
* Resolve (or lazily create) the channel's shared bus state, registering the
|
|
5174
|
+
* single-slot `client.onEvent` handler exactly once — on first creation. The
|
|
5175
|
+
* handler delivers into the SHARED state, so every co-located addon's
|
|
5176
|
+
* subscribers see parent→child events regardless of bus construction order.
|
|
5177
|
+
* It does NOT call `client.emitEvent` (no echo back to the parent).
|
|
5178
|
+
*/
|
|
5179
|
+
function sharedStateFor(client) {
|
|
5180
|
+
const existing = sharedStateByChannel.get(client);
|
|
5181
|
+
if (existing !== void 0) return existing;
|
|
5182
|
+
const created = createSharedBusState();
|
|
5183
|
+
sharedStateByChannel.set(client, created);
|
|
5184
|
+
client.onEvent((event) => {
|
|
5185
|
+
deliverShared(created, event);
|
|
5186
|
+
});
|
|
5187
|
+
return created;
|
|
5188
|
+
}
|
|
5189
|
+
/**
|
|
5167
5190
|
* Create a UDS-backed `IEventBus` for use inside a forked child process.
|
|
5191
|
+
* Returns a per-addon facade over the channel's ONE shared state (module doc).
|
|
5168
5192
|
*
|
|
5169
5193
|
* @param client - The UDS channel toward the parent (hub or agent).
|
|
5170
5194
|
* @param addonId - This child's stable addon id — stamped as `sourceAddonId`
|
|
@@ -5172,10 +5196,7 @@ function udsChildLogToWorkerEntry(childId, entry) {
|
|
|
5172
5196
|
* events to their source addon.
|
|
5173
5197
|
*/
|
|
5174
5198
|
function createUdsEventBus(client, addonId) {
|
|
5175
|
-
const state =
|
|
5176
|
-
client.onEvent((event) => {
|
|
5177
|
-
deliverShared(state, event);
|
|
5178
|
-
});
|
|
5199
|
+
const state = sharedStateFor(client);
|
|
5179
5200
|
const reportPatterns = () => {
|
|
5180
5201
|
client.updateEventPatterns(addonId, [...state.handlers.keys()]);
|
|
5181
5202
|
};
|