@camstack/server 1.2.72 → 1.2.74
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/agent/agent-service.js +7 -0
- package/dist/agent/infra-boot-guard.js +55 -0
- package/dist/agent/main.js +25 -3
- package/dist/api/sse-no-compression.js +12 -0
- package/dist/api/trpc/generated-cap-mounts.js +2 -1
- package/dist/api/trpc/generated-cap-routers.js +941 -737
- package/dist/core/auth/share-token.service.js +32 -9
- package/dist/main.js +18 -1
- package/package.json +8 -8
|
@@ -545,6 +545,13 @@ function createAgentService(deps) {
|
|
|
545
545
|
return { success: false, loaded: [] };
|
|
546
546
|
}
|
|
547
547
|
const loaded = await deps.reloadDeployedAddons();
|
|
548
|
+
// The reload just changed this node's capability set. Re-run the
|
|
549
|
+
// atomic registerNode so the hub's routing authority learns it now,
|
|
550
|
+
// instead of at the next restart / reconnect. Unconditional: an
|
|
551
|
+
// empty `loaded` still covers a RE-load of an existing addon whose
|
|
552
|
+
// manifest gained a capability, and registerNode is an idempotent
|
|
553
|
+
// replace (D3), so a no-op re-register costs one acked RPC.
|
|
554
|
+
deps.reconcileNodeRegistration?.();
|
|
548
555
|
return { success: true, loaded };
|
|
549
556
|
},
|
|
550
557
|
},
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* infra-boot-guard.ts — the agent's "did my infrastructure actually load?" gate.
|
|
4
|
+
*
|
|
5
|
+
* `bootCoreAddons` resolves each {@link INFRA_CAPABILITIES} entry to an addon
|
|
6
|
+
* package found under `<dataDir>/addons`. Two things can go wrong, and until
|
|
7
|
+
* 2026-08-08 only ONE of them was fatal:
|
|
8
|
+
*
|
|
9
|
+
* - the addon was found but its `initialize()` threw → boot aborted (correct);
|
|
10
|
+
* - the addon was NOT FOUND at all → `console.error`, continue.
|
|
11
|
+
*
|
|
12
|
+
* The second branch is strictly worse than the first and it was the quiet one.
|
|
13
|
+
* On `little-unraid` the whole `@camstack/system` package was renamed out of
|
|
14
|
+
* `/data/addons/@camstack/` at 16:40 on 2026-08-07 (a builtin-shadow cleanup).
|
|
15
|
+
* On an AGENT that directory is the SOLE source of the builtins — there is no
|
|
16
|
+
* seed fallback — so the next boot came up with ZERO infra addons: no storage,
|
|
17
|
+
* no settings-store, no metrics, no `platform-probe`, and no `hub-forwarder`.
|
|
18
|
+
*
|
|
19
|
+
* The node then reported `healthy`, registered its 14 forked addons with the
|
|
20
|
+
* hub, and kept running. Nothing said otherwise, because the ONE addon that
|
|
21
|
+
* ships an agent's logs to the hub (`hub-forwarder`, a `log-destination`) is in
|
|
22
|
+
* the same missing package: the failure was unobservable BY CONSTRUCTION. It
|
|
23
|
+
* surfaced five hours later, and only indirectly, as
|
|
24
|
+
* `getNodeInferenceDevices → reachable:false` on the hub — because the node's
|
|
25
|
+
* `registerNode` manifest legitimately no longer contained `platform-probe`,
|
|
26
|
+
* so `CapRouteResolver.nodeKnowsCap` correctly answered "no provider".
|
|
27
|
+
*
|
|
28
|
+
* `InfraCapability.required` is documented as "boot aborts when this
|
|
29
|
+
* capability's addon fails to initialize". A capability whose addon is not
|
|
30
|
+
* even present has failed harder. This guard makes both branches agree.
|
|
31
|
+
*
|
|
32
|
+
* It reports EVERY missing required capability in one throw, not just the
|
|
33
|
+
* first — an operator staring at a crash-loop needs the whole list, and the
|
|
34
|
+
* four that vanished together on little-unraid vanished for one single reason.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.assertRequiredInfraResolved = assertRequiredInfraResolved;
|
|
38
|
+
/**
|
|
39
|
+
* Throw when any `required` infra capability resolved to no addon at all.
|
|
40
|
+
*
|
|
41
|
+
* Called AFTER the resolution pass so the message names every missing
|
|
42
|
+
* capability at once. Non-required misses (`platform-probe`,
|
|
43
|
+
* `metrics-provider`, `log-destination`) are the caller's business — they are
|
|
44
|
+
* already logged individually and are legitimately absent on some nodes.
|
|
45
|
+
*/
|
|
46
|
+
function assertRequiredInfraResolved(resolutions, addonsDir) {
|
|
47
|
+
const missing = resolutions.filter((r) => r.required && r.addonId === null).map((r) => r.name);
|
|
48
|
+
if (missing.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
throw new Error(`Required infrastructure capabilities have no addon under "${addonsDir}": ` +
|
|
51
|
+
`${missing.join(', ')}. The agent's builtins package (@camstack/system) is ` +
|
|
52
|
+
`missing or unreadable — this node cannot store, configure or probe itself, ` +
|
|
53
|
+
`and every hub routing decision that consults its manifest would be wrong. ` +
|
|
54
|
+
`Refusing to boot degraded.`);
|
|
55
|
+
}
|
package/dist/agent/main.js
CHANGED
|
@@ -45,6 +45,7 @@ const types_2 = require("@camstack/types");
|
|
|
45
45
|
const agent_config_js_1 = require("./agent-config.js");
|
|
46
46
|
const agent_update_service_js_1 = require("./agent-update-service.js");
|
|
47
47
|
const server_management_provider_js_1 = require("../api/core/server-management.provider.js");
|
|
48
|
+
const infra_boot_guard_js_1 = require("./infra-boot-guard.js");
|
|
48
49
|
const agent_service_js_1 = require("./agent-service.js");
|
|
49
50
|
const agent_group_runner_js_1 = require("./agent-group-runner.js");
|
|
50
51
|
const register_agent_cap_dispatch_js_1 = require("./register-agent-cap-dispatch.js");
|
|
@@ -409,6 +410,13 @@ async function startAgent(configPath) {
|
|
|
409
410
|
}
|
|
410
411
|
return loaded;
|
|
411
412
|
},
|
|
413
|
+
// The reload above changed this node's capability set — re-derive the
|
|
414
|
+
// manifest from the live registry and re-run the atomic `registerNode`
|
|
415
|
+
// so the hub's `CapRouteResolver` can route to what this node now has.
|
|
416
|
+
// Hung on the existing `$agent.reload` trigger; no timer (D3).
|
|
417
|
+
reconcileNodeRegistration: () => {
|
|
418
|
+
triggerUpwardRegistration();
|
|
419
|
+
},
|
|
412
420
|
// D3 subtree aggregation: when a group-runner child delivers its manifest
|
|
413
421
|
// via `$agent.registerNode`, merge it into the local subtree registry and
|
|
414
422
|
// immediately re-register the complete union with the hub.
|
|
@@ -776,6 +784,10 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
776
784
|
console.warn(`[Agent] Failed to scan ${dir}: ${(0, types_2.errMsg)(err)}`);
|
|
777
785
|
}
|
|
778
786
|
}
|
|
787
|
+
// Every infra capability and the addon that will serve it — collected as we
|
|
788
|
+
// go so `assertRequiredInfraResolved` can name ALL missing required caps in
|
|
789
|
+
// one throw once the pass is done (see `infra-boot-guard.ts`).
|
|
790
|
+
const resolutions = [];
|
|
779
791
|
for (const infra of AGENT_INFRA) {
|
|
780
792
|
const candidates = loader.listAddons().filter((a) => a.declaration.capabilities?.some((c) => {
|
|
781
793
|
const capName = typeof c === 'string' ? c : c.name;
|
|
@@ -786,12 +798,16 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
786
798
|
? (candidates.find((a) => a.declaration.id === 'hub-forwarder') ?? candidates[0])
|
|
787
799
|
: candidates[0];
|
|
788
800
|
if (!addon) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
801
|
+
// Say it per-capability (an OPTIONAL miss is the only thing this line
|
|
802
|
+
// will ever report once the guard below is in place), then record the
|
|
803
|
+
// miss so the required ones abort boot together rather than one at a
|
|
804
|
+
// time.
|
|
805
|
+
console.error(`[Agent] Infrastructure addon for "${infra.name}" not found`);
|
|
806
|
+
resolutions.push({ name: infra.name, required: infra.required, addonId: null });
|
|
792
807
|
continue;
|
|
793
808
|
}
|
|
794
809
|
const addonId = addon.declaration.id;
|
|
810
|
+
resolutions.push({ name: infra.name, required: infra.required, addonId });
|
|
795
811
|
try {
|
|
796
812
|
const instance = new addon.addonClass();
|
|
797
813
|
// Seed ctx.kernel.storage from whatever storage provider is already
|
|
@@ -834,6 +850,12 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
|
|
|
834
850
|
}
|
|
835
851
|
}
|
|
836
852
|
}
|
|
853
|
+
// A required infra capability with NO addon at all is strictly worse than
|
|
854
|
+
// one whose addon threw (which already aborted above) — and it used to be
|
|
855
|
+
// the SILENT branch. Abort here instead of booting a node that cannot
|
|
856
|
+
// store, configure or probe itself and whose manifest then makes every hub
|
|
857
|
+
// routing decision about it wrong. See `infra-boot-guard.ts`.
|
|
858
|
+
(0, infra_boot_guard_js_1.assertRequiredInfraResolved)(resolutions, config.addonsDir);
|
|
837
859
|
}
|
|
838
860
|
// ---------------------------------------------------------------------------
|
|
839
861
|
// Phase 1.5: Load cluster-capable addon packages
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.markSseRequestNoCompression = markSseRequestNoCompression;
|
|
4
|
+
/** Mutates the request headers in place; returns whether it marked the request. */
|
|
5
|
+
function markSseRequestNoCompression(request) {
|
|
6
|
+
const accept = request.headers.accept;
|
|
7
|
+
if (typeof accept === 'string' && accept.includes('text/event-stream')) {
|
|
8
|
+
request.headers['x-no-compression'] = '1';
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// AUTO-GENERATED by scripts/generate-cap-mounts.ts — DO NOT EDIT
|
|
3
3
|
// Re-run: npx tsx scripts/generate-cap-mounts.ts
|
|
4
4
|
//
|
|
5
|
-
// Mounted:
|
|
5
|
+
// Mounted: 144 Skipped (legacy): 3
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.LEGACY_SHAPE_SKIP = void 0;
|
|
8
8
|
exports.mountAllCaps = mountAllCaps;
|
|
@@ -350,6 +350,7 @@ function mountAllCaps(services) {
|
|
|
350
350
|
return entries[0]?.[1] ?? null;
|
|
351
351
|
}, remoteCapProxy),
|
|
352
352
|
osd: (0, generated_cap_routers_js_1.createCapRouter_osd)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'osd'), remoteCapProxy),
|
|
353
|
+
osdManager: (0, generated_cap_routers_js_1.createCapRouter_osdManager)((_ctx) => reg?.getSingleton('osd-manager') ?? null, remoteCapProxy),
|
|
353
354
|
petFeeder: (0, generated_cap_routers_js_1.createCapRouter_petFeeder)((_ctx) => (0, cap_mount_helpers_js_1.requireDeviceScoped)(reg, 'pet-feeder'), remoteCapProxy),
|
|
354
355
|
pipelineAnalytics: (0, generated_cap_routers_js_1.createCapRouter_pipelineAnalytics)((_ctx) => reg?.getSingleton('pipeline-analytics') ?? null, remoteCapProxy),
|
|
355
356
|
pipelineExecutor: (0, generated_cap_routers_js_1.createCapRouter_pipelineExecutor)((_ctx) => reg?.getSingleton('pipeline-executor') ??
|