@groundnuty/macf-channel-server 0.2.38 → 0.2.41

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/shutdown.js CHANGED
@@ -1,26 +1,95 @@
1
1
  /**
2
- * Registers SIGTERM and SIGINT handlers that clean up the agent's
3
- * registry variable and stop the HTTPS server.
2
+ * Registers SIGTERM, SIGINT, and MCP-stdin-close handlers that clean up
3
+ * the agent's registry variable and stop the HTTPS server.
4
+ *
5
+ * The channel-server runs as the Claude TUI's MCP stdio child. A normal TUI
6
+ * exit (`/exit`, or SIGTERM-to-the-TUI) does NOT deliver a SIGTERM/SIGINT to
7
+ * this process — it only sees its stdin reach EOF as the parent goes away.
8
+ * So the stdin `'end'`/`'close'` wiring (macf#627) is the graceful-deregister
9
+ * trigger on a normal TUI exit; the signal handlers cover direct kills; the
10
+ * DR-031 TTL heartbeat (#589) remains the backstop for hard kills (SIGKILL /
11
+ * OOM / power loss) where no handler runs at all.
4
12
  *
5
13
  * Returns a cleanup function that can also be called directly.
6
14
  */
7
15
  export function registerShutdownHandler(config) {
8
- const { agentName, registry, httpsServer, logger } = config;
16
+ const { agentName, registry, instanceId, httpsServer, healthState, registryHeartbeat, logger } = config;
9
17
  let shuttingDown = false;
10
18
  let lastResult = true;
11
- async function cleanup() {
19
+ async function cleanup(trigger) {
12
20
  if (shuttingDown)
13
21
  return lastResult;
14
22
  shuttingDown = true;
15
- logger.info('shutdown_start', { agent: agentName });
23
+ logger.info('shutdown_start', {
24
+ agent: agentName,
25
+ ...(trigger !== undefined ? { trigger } : {}),
26
+ });
16
27
  let ok = true;
28
+ // Release the background OTLP-reachability probe interval (DR-030). Best-
29
+ // effort + never flips `ok`: a probe-cleanup hiccup must not mask a real
30
+ // deregister/stop failure.
31
+ try {
32
+ healthState?.dispose?.();
33
+ }
34
+ catch {
35
+ // ignore — the interval is unref()'d, so a missed clear can't pin exit
36
+ }
37
+ // Clear the registry-heartbeat interval (DR-031, groundnuty/macf#568). Same
38
+ // best-effort posture as the otel probe above — the interval is unref()'d, so
39
+ // a missed clear can't pin exit, and a hiccup must not mask a real failure.
40
+ try {
41
+ registryHeartbeat?.stop?.();
42
+ }
43
+ catch {
44
+ // ignore — unref()'d interval; a missed clear can't pin exit
45
+ }
46
+ // Instance-id-guarded deregister (DR-031, groundnuty/macf#553 root-cause):
47
+ // remove our registry slot ONLY if it is still ours. `deregisterConditional`
48
+ // is contracted never to throw — a registry hiccup surfaces as reason:'error'
49
+ // — but we still wrap it so a future contract slip can't crash shutdown.
17
50
  try {
18
- await registry.remove(agentName);
19
- logger.info('shutdown_deregistered', { agent: agentName });
51
+ const result = await registry.deregisterConditional(agentName, instanceId);
52
+ if (result.deregistered) {
53
+ logger.info('shutdown_deregistered', { agent: agentName, instance_id: instanceId });
54
+ }
55
+ else if (result.reason === 'not-ours') {
56
+ // A newer instance took over our slot (groundnuty/macf#424) while we ran.
57
+ // Leaving its registration intact is CORRECT — deleting it would re-
58
+ // introduce the missing/stale-entry bug (#553) for a live peer. Not a
59
+ // failure: keep `ok` true.
60
+ logger.info('shutdown_deregister_skipped', {
61
+ agent: agentName,
62
+ instance_id: instanceId,
63
+ reason: result.reason,
64
+ detail: 'registry slot held by a different instance — not ours to delete (DR-031, #424)',
65
+ });
66
+ }
67
+ else if (result.reason === 'absent') {
68
+ // Slot already gone (or unreadable) — nothing to deregister. Not a failure.
69
+ logger.info('shutdown_deregister_skipped', {
70
+ agent: agentName,
71
+ instance_id: instanceId,
72
+ reason: result.reason,
73
+ detail: 'registry slot already empty',
74
+ });
75
+ }
76
+ else {
77
+ // reason === 'error': a registry read/delete failed. Surface it loud and
78
+ // flip `ok` so external monitors (systemd, macf-actions heartbeat) see
79
+ // the degraded exit rather than absorbing a possibly-stale slot into a
80
+ // clean exit (#103 R2). Shutdown still proceeds — fail-open, not crash.
81
+ logger.error('shutdown_deregister_failed', {
82
+ agent: agentName,
83
+ instance_id: instanceId,
84
+ reason: result.reason,
85
+ });
86
+ ok = false;
87
+ }
20
88
  }
21
89
  catch (err) {
22
90
  logger.error('shutdown_deregister_failed', {
23
91
  agent: agentName,
92
+ instance_id: instanceId,
24
93
  error: err instanceof Error ? err.message : String(err),
25
94
  });
26
95
  ok = false;
@@ -42,12 +111,27 @@ export function registerShutdownHandler(config) {
42
111
  }
43
112
  // Exit 1 when any cleanup step failed so external monitors (systemd,
44
113
  // macf-actions heartbeat) surface the degraded state instead of
45
- // silently absorbing it into a clean exit (#103 R2).
46
- const handler = () => {
47
- cleanup().then(ok => process.exit(ok ? 0 : 1), () => process.exit(1));
114
+ // silently absorbing it into a clean exit (#103 R2). The `trigger` is
115
+ // recorded once (in `shutdown_start`) by whichever path wins the once-guard.
116
+ const handlerFor = (trigger) => () => {
117
+ cleanup(trigger).then(ok => process.exit(ok ? 0 : 1), () => process.exit(1));
48
118
  };
49
- process.on('SIGTERM', handler);
50
- process.on('SIGINT', handler);
119
+ process.on('SIGTERM', handlerFor('SIGTERM'));
120
+ process.on('SIGINT', handlerFor('SIGINT'));
121
+ // macf#627: a normal Claude-TUI exit does NOT signal this MCP-stdio child —
122
+ // it only sees stdin reach EOF ('end') / be destroyed ('close') as the parent
123
+ // departs. Wire the SAME deregister handler to both events so a graceful TUI
124
+ // exit deregisters rather than stranding the registry slot until the DR-031
125
+ // TTL backstop (#589). `cleanup()`'s once-guard (set synchronously before any
126
+ // await) makes a stdin-close racing a SIGTERM — or 'end' then 'close' — run
127
+ // the deregister exactly once. The child lingers ~8s after TUI exit
128
+ // (devops-observed), ample for the async deregister: the in-flight `cleanup()`
129
+ // promise plus the still-bound HTTPS server keep the event loop alive, and we
130
+ // exit only from `cleanup()`'s `.then()` — never mid-deregister.
131
+ const stdin = config.stdin ?? process.stdin;
132
+ const stdinCloseHandler = handlerFor('mcp-stdin-close');
133
+ stdin.on('end', stdinCloseHandler);
134
+ stdin.on('close', stdinCloseHandler);
51
135
  return cleanup;
52
136
  }
53
137
  //# sourceMappingURL=shutdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../src/shutdown.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAKvC;IACC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC5D,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,UAAU,GAAG,IAAI,CAAC;IAEtB,KAAK,UAAU,OAAO;QACpB,IAAI,YAAY;YAAE,OAAO,UAAU,CAAC;QACpC,YAAY,GAAG,IAAI,CAAC;QAEpB,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACpD,IAAI,EAAE,GAAG,IAAI,CAAC;QAEd,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE;gBACzC,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,EAAE,GAAG,KAAK,CAAC;QACb,CAAC;QAED,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE;gBAC1C,KAAK,EAAE,SAAS;gBAChB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,EAAE,GAAG,KAAK,CAAC;QACb,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3D,UAAU,GAAG,EAAE,CAAC;QAChB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,qEAAqE;IACrE,gEAAgE;IAChE,qDAAqD;IACrD,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,OAAO,EAAE,CAAC,IAAI,CACZ,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAC9B,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CACtB,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE9B,OAAO,OAAO,CAAC;AACjB,CAAC"}
1
+ {"version":3,"file":"shutdown.js","sourceRoot":"","sources":["../src/shutdown.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundnuty/macf-channel-server",
3
- "version": "0.2.38",
3
+ "version": "0.2.41",
4
4
  "description": "MCP channel server for the Multi-Agent Coordination Framework. HTTPS + mTLS endpoint agents connect to for inter-agent messaging (notify), CI-completion routing, and /sign certificate issuance. Invoked by Claude Code's plugin via `npx -y @groundnuty/macf-channel-server` per DR-022.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "test:integration": "vitest run --config vitest.integration.config.ts"
34
34
  },
35
35
  "dependencies": {
36
- "@groundnuty/macf-core": "0.2.38",
36
+ "@groundnuty/macf-core": "0.2.41",
37
37
  "@modelcontextprotocol/sdk": "~1.29.0",
38
38
  "@opentelemetry/api": "1.9.1",
39
39
  "@opentelemetry/core": "2.7.0",