@camstack/server 1.2.703 → 1.2.705

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/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.705] - 2026-10-31
11
+
12
+ ### Fixed
13
+
14
+ - A node agent stopped by a signal now shuts down within 15 seconds and logs what started it, instead of hanging while looking alive but deregistered
15
+
16
+ ## [1.2.704] - 2026-10-30
17
+
18
+ ### Changed
19
+
20
+ - Rebuilt against updated dependencies.
21
+
10
22
  ## [1.2.703] - 2026-10-30
11
23
 
12
24
  ### Added
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SHUTDOWN_DEADLINE_MS = exports.SHUTDOWN_STEP_TIMEOUT_MS = void 0;
4
+ exports.createAgentShutdown = createAgentShutdown;
5
+ exports.SHUTDOWN_STEP_TIMEOUT_MS = 5_000;
6
+ exports.SHUTDOWN_DEADLINE_MS = 15_000;
7
+ function errMessage(err) {
8
+ return err instanceof Error ? err.message : String(err);
9
+ }
10
+ async function runBounded(step, timeoutMs) {
11
+ let timer;
12
+ const timeout = new Promise((resolve) => {
13
+ timer = setTimeout(() => resolve('timed-out'), timeoutMs);
14
+ });
15
+ try {
16
+ return await Promise.race([step.run().then(() => 'done'), timeout]);
17
+ }
18
+ finally {
19
+ clearTimeout(timer);
20
+ }
21
+ }
22
+ /** Returns `shutdown(reason)`; `reason` names the signal or caller that started it. */
23
+ function createAgentShutdown(deps) {
24
+ const stepTimeoutMs = deps.stepTimeoutMs ?? exports.SHUTDOWN_STEP_TIMEOUT_MS;
25
+ const deadlineMs = deps.deadlineMs ?? exports.SHUTDOWN_DEADLINE_MS;
26
+ const { logger } = deps;
27
+ let startedAt = null;
28
+ return async (reason) => {
29
+ if (startedAt !== null) {
30
+ logger.error(`shutdown already in progress for ${Date.now() - startedAt}ms; ${reason} forces exit`);
31
+ deps.exit(1);
32
+ return;
33
+ }
34
+ const began = Date.now();
35
+ startedAt = began;
36
+ logger.info(`shutdown started by ${reason}`);
37
+ const deadline = setTimeout(() => {
38
+ logger.error(`shutdown deadline of ${deadlineMs}ms exceeded (started by ${reason}); exiting 1`);
39
+ deps.exit(1);
40
+ }, deadlineMs);
41
+ for (const step of deps.steps) {
42
+ const stepStart = Date.now();
43
+ try {
44
+ const outcome = await runBounded(step, stepTimeoutMs);
45
+ const took = `${Date.now() - stepStart}ms`;
46
+ if (outcome === 'timed-out') {
47
+ logger.warn(`shutdown step ${step.name} timed out after ${took}; continuing`);
48
+ }
49
+ else {
50
+ logger.info(`shutdown step ${step.name} done in ${took}`);
51
+ }
52
+ }
53
+ catch (err) {
54
+ logger.warn(`shutdown step ${step.name} failed after ${Date.now() - stepStart}ms: ${errMessage(err)}`);
55
+ }
56
+ }
57
+ clearTimeout(deadline);
58
+ logger.info(`shutdown finished in ${Date.now() - began}ms (started by ${reason})`);
59
+ deps.exit(0);
60
+ };
61
+ }
@@ -48,6 +48,7 @@ const addon_scan_js_1 = require("./addon-scan.js");
48
48
  const agent_addon_updates_js_1 = require("./agent-addon-updates.js");
49
49
  const agent_hub_registration_js_1 = require("./agent-hub-registration.js");
50
50
  const agent_mesh_gate_js_1 = require("./agent-mesh-gate.js");
51
+ const agent_shutdown_js_1 = require("./agent-shutdown.js");
51
52
  const agent_pairing_local_js_1 = require("./agent-pairing-local.js");
52
53
  const agent_membership_watch_js_1 = require("./agent-membership-watch.js");
53
54
  const agent_pairing_store_js_1 = require("./agent-pairing-store.js");
@@ -1071,34 +1072,49 @@ async function startAgent(configPath) {
1071
1072
  }).then((listener) => {
1072
1073
  agentHttpListener = listener;
1073
1074
  });
1074
- // Graceful shutdown
1075
- const shutdown = async () => {
1076
- console.log('[Agent] Shutting down...');
1077
- // Abort any in-flight upward hub registration retry loop.
1078
- registerAbortController.abort();
1079
- // Dispose the UDS event bridge to remove the parent-bus subscriber and
1080
- // clear the child-event handler, preventing subscriber leaks on shutdown.
1081
- udsEventBridgeDispose?.();
1082
- udsEventBridgeDispose = null;
1083
- for (const [, entry] of loadedAddons) {
1084
- if (entry.addon?.shutdown) {
1085
- try {
1086
- await entry.addon.shutdown();
1087
- }
1088
- catch {
1089
- /* */
1090
- }
1091
- }
1092
- }
1093
- // Withdraw the mDNS advertisement, then stop accepting new HTTP/HTTPS connections before
1094
- // the broker goes away.
1095
- await agentMdns.stop().catch(() => undefined);
1096
- await agentHttpListener?.close().catch(() => undefined);
1097
- await broker.stop();
1098
- process.exit(0);
1099
- };
1100
- process.on('SIGTERM', shutdown);
1101
- process.on('SIGINT', shutdown);
1075
+ // Graceful shutdown: bounded, re-entry guarded, and it logs what started it (agent-shutdown.ts).
1076
+ const shutdown = (0, agent_shutdown_js_1.createAgentShutdown)({
1077
+ logger: consoleLogger,
1078
+ exit: (code) => process.exit(code),
1079
+ steps: [
1080
+ {
1081
+ name: 'abort-registration',
1082
+ run: async () => {
1083
+ // Abort any in-flight upward hub registration retry loop.
1084
+ registerAbortController.abort();
1085
+ // Dispose the UDS event bridge to remove the parent-bus subscriber and
1086
+ // clear the child-event handler, preventing subscriber leaks on shutdown.
1087
+ udsEventBridgeDispose?.();
1088
+ udsEventBridgeDispose = null;
1089
+ },
1090
+ },
1091
+ {
1092
+ name: 'addons',
1093
+ // Concurrent: one slow addon must not eat the budget of the others.
1094
+ run: async () => {
1095
+ const stops = [];
1096
+ for (const [addonId, entry] of loadedAddons) {
1097
+ if (!entry.addon?.shutdown)
1098
+ continue;
1099
+ const began = Date.now();
1100
+ stops.push(Promise.resolve()
1101
+ .then(() => entry.addon?.shutdown?.())
1102
+ .then(() => undefined)
1103
+ .catch((err) => consoleLogger.warn(`addon ${addonId} shutdown failed: ${(0, types_2.errMsg)(err)}`))
1104
+ .finally(() => consoleLogger.info(`addon ${addonId} shutdown took ${Date.now() - began}ms`)));
1105
+ }
1106
+ await Promise.all(stops);
1107
+ },
1108
+ },
1109
+ // Withdraw the mDNS advertisement, then stop accepting new HTTP/HTTPS connections before
1110
+ // the broker goes away.
1111
+ { name: 'mdns', run: () => agentMdns.stop() },
1112
+ { name: 'http-listener', run: async () => void (await agentHttpListener?.close()) },
1113
+ { name: 'broker', run: () => broker.stop() },
1114
+ ],
1115
+ });
1116
+ process.on('SIGTERM', () => void shutdown('SIGTERM'));
1117
+ process.on('SIGINT', () => void shutdown('SIGINT'));
1102
1118
  // An unpaired agent has no hub to read settings from (D857): serve pairing only and
1103
1119
  // relaunch after the certificate is installed (D5). See agent-boot-mode.ts.
1104
1120
  if (!addonsBooted) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.703",
3
+ "version": "1.2.705",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,20 +50,20 @@
50
50
  ]
51
51
  },
52
52
  "dependencies": {
53
- "@camstack/addon-admin-ui": "1.2.527",
54
- "@camstack/addon-agent-ui": "1.2.299",
53
+ "@camstack/addon-admin-ui": "1.2.529",
54
+ "@camstack/addon-agent-ui": "1.2.300",
55
55
  "@camstack/addon-auth": "1.2.172",
56
- "@camstack/addon-benchmark": "1.2.298",
56
+ "@camstack/addon-benchmark": "1.2.299",
57
57
  "@camstack/addon-decoder-nodeav": "1.2.144",
58
- "@camstack/addon-notifiers": "1.2.623",
58
+ "@camstack/addon-notifiers": "1.2.624",
59
59
  "@camstack/addon-pipeline": "1.2.635",
60
- "@camstack/addon-pipeline-orchestrator": "1.2.640",
61
- "@camstack/addon-post-analysis": "1.2.653",
60
+ "@camstack/addon-pipeline-orchestrator": "1.2.642",
61
+ "@camstack/addon-post-analysis": "1.2.654",
62
62
  "@camstack/develop": "0.1.41",
63
63
  "@camstack/sdk": "1.2.154",
64
- "@camstack/system": "1.2.525",
65
- "@camstack/types": "1.2.452",
66
- "@camstack/ui-library": "1.2.357",
64
+ "@camstack/system": "1.2.526",
65
+ "@camstack/types": "1.2.453",
66
+ "@camstack/ui-library": "1.2.358",
67
67
  "@fastify/compress": "^9.0.0",
68
68
  "@fastify/cookie": "^11.0.2",
69
69
  "@fastify/cors": "^11.2.0",