@camstack/server 1.2.490 → 1.2.492

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.
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentOfflineData = agentOfflineData;
4
+ exports.nodeDisplayName = nodeDisplayName;
5
+ /**
6
+ * The payload of `agent.offline`, built from what Moleculer told us.
7
+ *
8
+ * `unexpected` is Moleculer's own verdict on `$node.disconnected`: `true` when
9
+ * the heartbeat or the socket was lost, `false` when the node sent its
10
+ * DISCONNECT on a clean stop. The notification centre turns it into the body
11
+ * of the "node offline" notification (D820).
12
+ */
13
+ function agentOfflineData(agentId, unexpected, nodeName) {
14
+ return {
15
+ agentId,
16
+ ...(nodeName !== undefined ? { nodeName } : {}),
17
+ ...(unexpected !== undefined
18
+ ? { reason: unexpected ? 'connection-lost' : 'shutdown' }
19
+ : {}),
20
+ };
21
+ }
22
+ /** The history's display name for a node — only a real one, never the id echoed back. */
23
+ function nodeDisplayName(nodeId, history) {
24
+ const name = history?.find((row) => row.id === nodeId)?.name;
25
+ return name !== undefined && name.length > 0 && name !== nodeId ? name : undefined;
26
+ }
@@ -42,6 +42,7 @@ const system_1 = require("@camstack/system");
42
42
  const types_1 = require("@camstack/types");
43
43
  const agent_addon_backfill_1 = require("./agent-addon-backfill");
44
44
  const agent_delivery_ledger_1 = require("./agent-delivery-ledger");
45
+ const agent_liveness_event_1 = require("./agent-liveness-event");
45
46
  const cluster_node_history_mirror_1 = require("./cluster-node-history-mirror");
46
47
  const node_directory_1 = require("./node-directory");
47
48
  /** Turn a forget outcome into the sentence the operator reads. */
@@ -477,12 +478,13 @@ class AgentRegistryService {
477
478
  return;
478
479
  if (kind === 'agent') {
479
480
  console.log(`[agent-registry] Agent connected: ${node.id}`);
481
+ const nodeName = this.knownNodeName(node.id);
480
482
  this.eventBus.emit({
481
483
  id: (0, node_crypto_1.randomUUID)(),
482
484
  timestamp: new Date(),
483
485
  source: { type: 'core', id: 'agent-registry' },
484
486
  category: types_1.EventCategory.AgentOnline,
485
- data: { agentId: node.id },
487
+ data: { agentId: node.id, ...(nodeName !== undefined ? { nodeName } : {}) },
486
488
  });
487
489
  }
488
490
  else {
@@ -495,12 +497,12 @@ class AgentRegistryService {
495
497
  });
496
498
  }
497
499
  });
498
- this.broker.localBus.on('$node.disconnected', ({ node }) => {
500
+ this.broker.localBus.on('$node.disconnected', ({ node, unexpected }) => {
499
501
  const kind = classifyNode(node.id);
500
502
  if (!kind)
501
503
  return;
502
504
  if (kind === 'agent') {
503
- console.log(`[agent-registry] Agent disconnected: ${node.id}`);
505
+ console.log(`[agent-registry] Agent disconnected: ${node.id}${unexpected === true ? ' (unexpected)' : ''}`);
504
506
  // A1: stamp the disconnect time as the node's `lastActive` so its
505
507
  // offline row reports an honest "last seen". Best-effort, no-op if the
506
508
  // node was never snapshotted while online.
@@ -510,7 +512,7 @@ class AgentRegistryService {
510
512
  timestamp: new Date(),
511
513
  source: { type: 'core', id: 'agent-registry' },
512
514
  category: types_1.EventCategory.AgentOffline,
513
- data: { agentId: node.id },
515
+ data: (0, agent_liveness_event_1.agentOfflineData)(node.id, unexpected, this.knownNodeName(node.id)),
514
516
  });
515
517
  }
516
518
  else {
@@ -557,6 +559,10 @@ class AgentRegistryService {
557
559
  // Registry shape varies across Moleculer versions — never fatal.
558
560
  }
559
561
  }
562
+ /** The durable history's display name for a node, when it knows a real one. */
563
+ knownNodeName(nodeId) {
564
+ return (0, agent_liveness_event_1.nodeDisplayName)(nodeId, this.historyMirror?.peek() ?? null);
565
+ }
560
566
  // ---- Placement reconciliation ----
561
567
  /**
562
568
  * Boot-time reconciliation pass. Agents already connected when the hub
@@ -42,6 +42,14 @@ class ClusterNodeHistoryMirror {
42
42
  const rows = await this.refresh();
43
43
  return rows ?? this.entry?.rows ?? null;
44
44
  }
45
+ /**
46
+ * The last successful read, however old, without reading. For a synchronous
47
+ * caller that only wants a label (the node liveness events' display name) —
48
+ * a stale name is still the right name, and no answer is `null`.
49
+ */
50
+ peek() {
51
+ return this.entry?.rows ?? null;
52
+ }
45
53
  /** Drop the mirror (a node was forgotten): the next `current()` must re-read. */
46
54
  invalidate() {
47
55
  this.generation += 1;
@@ -14,7 +14,7 @@ const types_1 = require("@camstack/types");
14
14
  *
15
15
  * The union was the shape until 2026-08-19, and on a three-node cluster one
16
16
  * 6-hourly sweep sent the operator THREE pushes that each listed hub +
17
- * MinidiGianluca + little-unraid and named no node: every scope's change
17
+ * MacMiniAgent + little-unraid and named no node: every scope's change
18
18
  * re-emitted the whole held map, so the notifications differed by one line and
19
19
  * read as identical. A list is only actionable if it belongs to somebody.
20
20
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.490",
3
+ "version": "1.2.492",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "files": [
@@ -37,18 +37,18 @@
37
37
  ]
38
38
  },
39
39
  "dependencies": {
40
- "@camstack/addon-admin-ui": "1.2.363",
41
- "@camstack/addon-agent-ui": "1.2.198",
42
- "@camstack/addon-auth": "1.2.135",
40
+ "@camstack/addon-admin-ui": "1.2.365",
41
+ "@camstack/addon-agent-ui": "1.2.199",
42
+ "@camstack/addon-auth": "1.2.136",
43
43
  "@camstack/addon-decoder-nodeav": "1.2.122",
44
44
  "@camstack/addon-notifiers": "1.2.129",
45
- "@camstack/addon-pipeline": "1.2.347",
46
- "@camstack/addon-pipeline-orchestrator": "1.2.301",
47
- "@camstack/addon-post-analysis": "1.2.348",
45
+ "@camstack/addon-pipeline": "1.2.348",
46
+ "@camstack/addon-pipeline-orchestrator": "1.2.302",
47
+ "@camstack/addon-post-analysis": "1.2.350",
48
48
  "@camstack/sdk": "1.2.128",
49
- "@camstack/system": "1.2.384",
50
- "@camstack/types": "1.2.334",
51
- "@camstack/ui-library": "1.2.256",
49
+ "@camstack/system": "1.2.386",
50
+ "@camstack/types": "1.2.336",
51
+ "@camstack/ui-library": "1.2.257",
52
52
  "@fastify/compress": "^9.0.0",
53
53
  "@fastify/cookie": "^11.0.2",
54
54
  "@fastify/cors": "^11.2.0",