@camstack/server 1.2.73 → 1.2.75

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.
@@ -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,84 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.seedBuiltinsFromClosure = seedBuiltinsFromClosure;
37
+ /**
38
+ * Self-heal the agent's builtins package before the infra scan.
39
+ *
40
+ * `bootCoreAddons` scans `config.addonsDir` and nothing else, the image seed
41
+ * ships no `@camstack/system`, and the running closure's copy is NOT loaded
42
+ * as an addon — so an agent whose `/data/addons/@camstack/system` is missing
43
+ * (greenfield container, an operator cleanup, a failed deploy) boots with
44
+ * zero infrastructure. Before D87 that produced a silently gutted node; with
45
+ * the D87 guard it produces a crash-loop. Both are wrong answers to a
46
+ * question the node can answer itself: the closure it is RUNNING carries the
47
+ * exact builtins package it needs.
48
+ *
49
+ * The copy is files-list shaped — `package.json` + `dist` — and NEVER
50
+ * `node_modules`: a nested `node_modules/@camstack/*` carries its own
51
+ * `package.json`s, the addon scan discovers those too, and the same addon
52
+ * initializing twice is a boot abort (measured live 2026-08-08, twice, two
53
+ * different vehicles).
54
+ */
55
+ const fs = __importStar(require("node:fs"));
56
+ const path = __importStar(require("node:path"));
57
+ function seedBuiltinsFromClosure(addonsDir, log = console.log, resolveClosurePkg = () => require.resolve('@camstack/system/package.json')) {
58
+ const target = path.join(addonsDir, '@camstack', 'system');
59
+ if (fs.existsSync(path.join(target, 'package.json')))
60
+ return 'present';
61
+ let closurePkgJson;
62
+ try {
63
+ closurePkgJson = resolveClosurePkg();
64
+ }
65
+ catch {
66
+ log('[Agent] builtins seed: @camstack/system not resolvable from the closure — cannot self-heal');
67
+ return 'unavailable';
68
+ }
69
+ const closureRoot = path.dirname(closurePkgJson);
70
+ const closureDist = path.join(closureRoot, 'dist');
71
+ if (!fs.existsSync(closureDist)) {
72
+ log(`[Agent] builtins seed: closure copy at ${closureRoot} has no dist — cannot self-heal`);
73
+ return 'unavailable';
74
+ }
75
+ fs.mkdirSync(target, { recursive: true });
76
+ fs.copyFileSync(closurePkgJson, path.join(target, 'package.json'));
77
+ fs.cpSync(closureDist, path.join(target, 'dist'), {
78
+ recursive: true,
79
+ filter: (src) => !src.split(path.sep).includes('node_modules'),
80
+ });
81
+ log(`[Agent] builtins seed: @camstack/system was missing under ${addonsDir} — ` +
82
+ `seeded package.json + dist from the running closure (${closureRoot})`);
83
+ return 'seeded';
84
+ }
@@ -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
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.planInfraBoot = planInfraBoot;
4
+ exports.planToResolutions = planToResolutions;
5
+ /**
6
+ * `log-destination` prefers `hub-forwarder` (ships agent logs to the hub)
7
+ * over a local winston — mirrors the historical inline choice.
8
+ */
9
+ function pickCandidate(capName, candidates) {
10
+ if (capName === 'log-destination') {
11
+ return candidates.find((a) => a.id === 'hub-forwarder') ?? candidates[0];
12
+ }
13
+ return candidates[0];
14
+ }
15
+ function planInfraBoot(addons, infraList) {
16
+ const initialized = new Set();
17
+ const steps = [];
18
+ for (const infra of infraList) {
19
+ const candidates = addons.filter((a) => a.capabilities.includes(infra.name));
20
+ const chosen = pickCandidate(infra.name, candidates);
21
+ if (!chosen) {
22
+ steps.push({
23
+ capName: infra.name,
24
+ required: infra.required,
25
+ addonId: null,
26
+ initialize: false,
27
+ });
28
+ continue;
29
+ }
30
+ const first = !initialized.has(chosen.id);
31
+ if (first)
32
+ initialized.add(chosen.id);
33
+ steps.push({
34
+ capName: infra.name,
35
+ required: infra.required,
36
+ addonId: chosen.id,
37
+ initialize: first,
38
+ });
39
+ }
40
+ return steps;
41
+ }
42
+ /** Project a plan onto the guard's resolution shape. */
43
+ function planToResolutions(steps) {
44
+ return steps.map((s) => ({ name: s.capName, required: s.required, addonId: s.addonId }));
45
+ }
@@ -45,6 +45,9 @@ 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");
49
+ const infra_boot_plan_js_1 = require("./infra-boot-plan.js");
50
+ const builtins_seed_js_1 = require("./builtins-seed.js");
48
51
  const agent_service_js_1 = require("./agent-service.js");
49
52
  const agent_group_runner_js_1 = require("./agent-group-runner.js");
50
53
  const register_agent_cap_dispatch_js_1 = require("./register-agent-cap-dispatch.js");
@@ -409,6 +412,13 @@ async function startAgent(configPath) {
409
412
  }
410
413
  return loaded;
411
414
  },
415
+ // The reload above changed this node's capability set — re-derive the
416
+ // manifest from the live registry and re-run the atomic `registerNode`
417
+ // so the hub's `CapRouteResolver` can route to what this node now has.
418
+ // Hung on the existing `$agent.reload` trigger; no timer (D3).
419
+ reconcileNodeRegistration: () => {
420
+ triggerUpwardRegistration();
421
+ },
412
422
  // D3 subtree aggregation: when a group-runner child delivers its manifest
413
423
  // via `$agent.registerNode`, merge it into the local subtree registry and
414
424
  // immediately re-register the complete union with the hub.
@@ -759,6 +769,10 @@ async function startAgent(configPath) {
759
769
  // Core infra addons to load on agent — all infra including log-destination (hub-forwarder)
760
770
  const AGENT_INFRA = system_1.INFRA_CAPABILITIES;
761
771
  async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFactory) {
772
+ // Self-heal FIRST: a missing builtins package is answerable from the
773
+ // running closure (see `builtins-seed.ts`) — without this, the D87 guard
774
+ // below turns a recoverable gap into a crash-loop.
775
+ (0, builtins_seed_js_1.seedBuiltinsFromClosure)(config.addonsDir);
762
776
  // Scan every installed addon package — infra providers may live outside
763
777
  // `@camstack/system` (e.g. `@camstack/addon-platform-probe-native`).
764
778
  const packageDirs = resolveAddonPackageDirs(config.addonsDir);
@@ -776,21 +790,38 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
776
790
  console.warn(`[Agent] Failed to scan ${dir}: ${(0, types_2.errMsg)(err)}`);
777
791
  }
778
792
  }
779
- for (const infra of AGENT_INFRA) {
780
- const candidates = loader.listAddons().filter((a) => a.declaration.capabilities?.some((c) => {
781
- const capName = typeof c === 'string' ? c : c.name;
782
- return capName === infra.name;
783
- }));
784
- // For log-destination, prefer hub-forwarder over winston-logging
785
- const addon = infra.name === 'log-destination'
786
- ? (candidates.find((a) => a.declaration.id === 'hub-forwarder') ?? candidates[0])
787
- : candidates[0];
788
- if (!addon) {
789
- if (infra.required) {
790
- console.error(`[Agent] Required infrastructure addon for "${infra.name}" not found`);
791
- }
793
+ // Resolution is per-CAPABILITY (the guard names every missing required
794
+ // cap), initialization is per-ADDON: since D44 one addon legitimately
795
+ // serves several infra caps (`storage-orchestrator` owns both `storage`
796
+ // and `settings-store`), and initializing it once per capability
797
+ // re-registers the same provider — a boot abort by the registry's own
798
+ // double-registration error. That combination took both agents down on
799
+ // 2026-08-08 the moment D87 promoted infra failures to fatal. The plan
800
+ // makes the dedupe explicit and testable (see `infra-boot-plan.ts`).
801
+ const byId = new Map(loader.listAddons().map((a) => [a.declaration.id, a]));
802
+ const plan = (0, infra_boot_plan_js_1.planInfraBoot)(loader.listAddons().map((a) => ({
803
+ id: a.declaration.id,
804
+ capabilities: (a.declaration.capabilities ?? []).map((c) => typeof c === 'string' ? c : c.name),
805
+ })), AGENT_INFRA);
806
+ const resolutions = (0, infra_boot_plan_js_1.planToResolutions)(plan);
807
+ for (const step of plan) {
808
+ if (step.addonId === null) {
809
+ // Say it per-capability (an OPTIONAL miss is the only thing this line
810
+ // will ever report given the guard below); the required ones abort
811
+ // boot together after the pass.
812
+ console.error(`[Agent] Infrastructure addon for "${step.capName}" not found`);
792
813
  continue;
793
814
  }
815
+ if (!step.initialize) {
816
+ // Served by an addon an earlier capability already booted — the
817
+ // entry exists so a deployment missing it fails loudly, not to boot
818
+ // the addon twice.
819
+ console.log(`[Agent] Infra capability "${step.capName}" served by already-booted "${step.addonId}"`);
820
+ continue;
821
+ }
822
+ const addon = byId.get(step.addonId);
823
+ if (!addon)
824
+ continue;
794
825
  const addonId = addon.declaration.id;
795
826
  try {
796
827
  const instance = new addon.addonClass();
@@ -829,11 +860,17 @@ async function bootCoreAddons(broker, config, registry, loadedAddons, loggerFact
829
860
  catch (err) {
830
861
  const msg = (0, types_2.errMsg)(err);
831
862
  console.error(`[Agent] Failed to initialize core addon "${addonId}": ${msg}`);
832
- if (infra.required) {
863
+ if (step.required) {
833
864
  throw new Error(`Required infrastructure addon "${addonId}" failed: ${msg}`, { cause: err });
834
865
  }
835
866
  }
836
867
  }
868
+ // A required infra capability with NO addon at all is strictly worse than
869
+ // one whose addon threw (which already aborted above) — and it used to be
870
+ // the SILENT branch. Abort here instead of booting a node that cannot
871
+ // store, configure or probe itself and whose manifest then makes every hub
872
+ // routing decision about it wrong. See `infra-boot-guard.ts`.
873
+ (0, infra_boot_guard_js_1.assertRequiredInfraResolved)(resolutions, config.addonsDir);
837
874
  }
838
875
  // ---------------------------------------------------------------------------
839
876
  // 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
+ }
@@ -5817,6 +5817,105 @@ function createCapRouter_pipelineAnalytics(getProvider, createRemoteProxy) {
5817
5817
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5818
5818
  return p.getTrainingExportUrl(methodInput);
5819
5819
  }),
5820
+ listRetrainStaging: trpc_middleware_js_1.adminProcedure
5821
+ .input(types_81.pipelineAnalyticsCapability.methods.listRetrainStaging.input.loose())
5822
+ .output(types_81.pipelineAnalyticsCapability.methods.listRetrainStaging.output)
5823
+ .query(async ({ input, ctx }) => {
5824
+ const { nodeId, ...methodInput } = input;
5825
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5826
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5827
+ return p.listRetrainStaging(methodInput);
5828
+ }),
5829
+ listRetrainFrames: trpc_middleware_js_1.adminProcedure
5830
+ .input(types_81.pipelineAnalyticsCapability.methods.listRetrainFrames.input.loose())
5831
+ .output(types_81.pipelineAnalyticsCapability.methods.listRetrainFrames.output)
5832
+ .query(async ({ input, ctx }) => {
5833
+ const { nodeId, ...methodInput } = input;
5834
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5835
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5836
+ return p.listRetrainFrames(methodInput);
5837
+ }),
5838
+ selectRetrainFrames: trpc_middleware_js_1.adminProcedure
5839
+ .input(types_81.pipelineAnalyticsCapability.methods.selectRetrainFrames.input.loose())
5840
+ .output(types_81.pipelineAnalyticsCapability.methods.selectRetrainFrames.output)
5841
+ .mutation(async ({ input, ctx }) => {
5842
+ const { nodeId, ...methodInput } = input;
5843
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5844
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5845
+ return p.selectRetrainFrames(methodInput);
5846
+ }),
5847
+ deselectRetrainFrame: trpc_middleware_js_1.adminProcedure
5848
+ .input(types_81.pipelineAnalyticsCapability.methods.deselectRetrainFrame.input.loose())
5849
+ .output(types_81.pipelineAnalyticsCapability.methods.deselectRetrainFrame.output)
5850
+ .mutation(async ({ input, ctx }) => {
5851
+ const { nodeId, ...methodInput } = input;
5852
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5853
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5854
+ return p.deselectRetrainFrame(methodInput);
5855
+ }),
5856
+ getRetrainFrameImage: trpc_middleware_js_1.adminProcedure
5857
+ .input(types_81.pipelineAnalyticsCapability.methods.getRetrainFrameImage.input.loose())
5858
+ .output(types_81.pipelineAnalyticsCapability.methods.getRetrainFrameImage.output)
5859
+ .query(async ({ input, ctx }) => {
5860
+ const { nodeId, ...methodInput } = input;
5861
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5862
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5863
+ return p.getRetrainFrameImage(methodInput);
5864
+ }),
5865
+ proposeRetrainAnnotations: trpc_middleware_js_1.adminProcedure
5866
+ .input(types_81.pipelineAnalyticsCapability.methods.proposeRetrainAnnotations.input.loose())
5867
+ .output(types_81.pipelineAnalyticsCapability.methods.proposeRetrainAnnotations.output)
5868
+ .mutation(async ({ input, ctx }) => {
5869
+ const { nodeId, ...methodInput } = input;
5870
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5871
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5872
+ return p.proposeRetrainAnnotations(methodInput);
5873
+ }),
5874
+ listRetrainAnnotations: trpc_middleware_js_1.adminProcedure
5875
+ .input(types_81.pipelineAnalyticsCapability.methods.listRetrainAnnotations.input.loose())
5876
+ .output(types_81.pipelineAnalyticsCapability.methods.listRetrainAnnotations.output)
5877
+ .query(async ({ input, ctx }) => {
5878
+ const { nodeId, ...methodInput } = input;
5879
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5880
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5881
+ return p.listRetrainAnnotations(methodInput);
5882
+ }),
5883
+ saveRetrainAnnotations: trpc_middleware_js_1.adminProcedure
5884
+ .input(types_81.pipelineAnalyticsCapability.methods.saveRetrainAnnotations.input.loose())
5885
+ .output(types_81.pipelineAnalyticsCapability.methods.saveRetrainAnnotations.output)
5886
+ .mutation(async ({ input, ctx }) => {
5887
+ const { nodeId, ...methodInput } = input;
5888
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5889
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5890
+ return p.saveRetrainAnnotations(methodInput);
5891
+ }),
5892
+ completeRetrainTrack: trpc_middleware_js_1.adminProcedure
5893
+ .input(types_81.pipelineAnalyticsCapability.methods.completeRetrainTrack.input.loose())
5894
+ .output(types_81.pipelineAnalyticsCapability.methods.completeRetrainTrack.output)
5895
+ .mutation(async ({ input, ctx }) => {
5896
+ const { nodeId, ...methodInput } = input;
5897
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5898
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5899
+ return p.completeRetrainTrack(methodInput);
5900
+ }),
5901
+ restageRetrainTrack: trpc_middleware_js_1.adminProcedure
5902
+ .input(types_81.pipelineAnalyticsCapability.methods.restageRetrainTrack.input.loose())
5903
+ .output(types_81.pipelineAnalyticsCapability.methods.restageRetrainTrack.output)
5904
+ .mutation(async ({ input, ctx }) => {
5905
+ const { nodeId, ...methodInput } = input;
5906
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5907
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5908
+ return p.restageRetrainTrack(methodInput);
5909
+ }),
5910
+ getRetrainExportUrl: trpc_middleware_js_1.adminProcedure
5911
+ .input(types_81.pipelineAnalyticsCapability.methods.getRetrainExportUrl.input.loose())
5912
+ .output(types_81.pipelineAnalyticsCapability.methods.getRetrainExportUrl.output)
5913
+ .query(async ({ input, ctx }) => {
5914
+ const { nodeId, ...methodInput } = input;
5915
+ const p = resolveProvider('pipeline-analytics', nodeId, () => getProvider(ctx), createRemoteProxy);
5916
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
5917
+ return p.getRetrainExportUrl(methodInput);
5918
+ }),
5820
5919
  getEventMedia: trpc_middleware_js_1.protectedProcedure
5821
5920
  .input(types_81.pipelineAnalyticsCapability.methods.getEventMedia.input.loose())
5822
5921
  .output(types_81.pipelineAnalyticsCapability.methods.getEventMedia.output)
package/dist/main.js CHANGED
@@ -42,6 +42,7 @@ const ws_1 = require("@trpc/server/adapters/ws");
42
42
  const static_1 = __importDefault(require("@fastify/static"));
43
43
  const compress_1 = __importDefault(require("@fastify/compress"));
44
44
  const cookie_1 = __importDefault(require("@fastify/cookie"));
45
+ const sse_no_compression_js_1 = require("./api/sse-no-compression.js");
45
46
  const ws_2 = require("ws");
46
47
  const fs = __importStar(require("node:fs"));
47
48
  const path = __importStar(require("node:path"));
@@ -223,7 +224,16 @@ async function bootstrap() {
223
224
  // replaces — so the telemetry reaches a running hub the same day it is
224
225
  // written. See packages/system/src/kernel/heap-watch.ts for why it exists
225
226
  // (four silent OOMs in ~15h, one nine seconds after a viewer connected).
226
- (0, system_2.startHeapWatch)();
227
+ //
228
+ // The reclaimer is what turns the heartbeat from a report into a fix. hub-main's
229
+ // RSS is a HIGH-WATER MARK — measured over 1019 samples and seven boots, it never
230
+ // once fell below the running maximum of heapTotal+external — because V8 keeps the
231
+ // 256KB pages it committed at a sawtooth peak, and an ordinary major GC does not
232
+ // return them. `createV8Reclaimer()` returns undefined if V8 declines, and the
233
+ // heartbeat then behaves exactly as it did before.
234
+ const heapReclaimer = (0, system_2.createV8Reclaimer)();
235
+ const heapReclaim = heapReclaimer === undefined ? undefined : { reclaim: heapReclaimer };
236
+ (0, system_2.startHeapWatch)('hub-main', undefined, undefined, heapReclaim);
227
237
  // Clean up orphaned processes from previous crashes before starting
228
238
  cleanupOrphanProcesses();
229
239
  // SPA fallback — set later when admin UI is resolved, used by addon route catch-all
@@ -252,6 +262,13 @@ async function bootstrap() {
252
262
  // Registered before @fastify/static so the compress plugin wraps the
253
263
  // static send path — hashed admin-ui chunks go from ~2MB to ~600KB on
254
264
  // the wire. threshold:1024 skips compression for tiny payloads.
265
+ //
266
+ // SSE opt-out FIRST: without it a gzipped low-rate event stream starves
267
+ // silently in the compressor buffer — see `api/sse-no-compression.ts` for
268
+ // the mechanism and the live measurement.
269
+ fastify.addHook('onRequest', async (request) => {
270
+ (0, sse_no_compression_js_1.markSseRequestNoCompression)(request);
271
+ });
255
272
  await fastify.register(compress_1.default, { global: true, threshold: 1024 });
256
273
  // Data-plane POST bodies: the addon reverse-proxy (`proxyToUpstream`) pipes
257
274
  // `request.raw` upstream, but Fastify's default application/json parser would
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.73",
3
+ "version": "1.2.75",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -33,19 +33,19 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "@camstack/addon-admin-ui": "1.2.34",
36
+ "@camstack/addon-admin-ui": "1.2.35",
37
37
  "@camstack/addon-agent-ui": "1.2.10",
38
38
  "@camstack/addon-auth": "1.2.11",
39
39
  "@camstack/addon-decoder-nodeav": "1.2.9",
40
40
  "@camstack/addon-notifiers": "1.2.13",
41
- "@camstack/addon-pipeline": "1.2.45",
42
- "@camstack/addon-pipeline-orchestrator": "1.2.28",
43
- "@camstack/addon-post-analysis": "1.2.50",
41
+ "@camstack/addon-pipeline": "1.2.46",
42
+ "@camstack/addon-pipeline-orchestrator": "1.2.29",
43
+ "@camstack/addon-post-analysis": "1.2.51",
44
44
  "@camstack/sdk": "1.2.10",
45
45
  "@camstack/shm-ring": "1.1.9",
46
- "@camstack/system": "1.2.60",
47
- "@camstack/types": "1.2.44",
48
- "@camstack/ui-library": "1.2.32",
46
+ "@camstack/system": "1.2.61",
47
+ "@camstack/types": "1.2.45",
48
+ "@camstack/ui-library": "1.2.33",
49
49
  "@fastify/compress": "^9.0.0",
50
50
  "@fastify/cookie": "^11.0.2",
51
51
  "@fastify/cors": "^11.2.0",