@naisys/hub 3.0.0-beta.25 → 3.0.0-beta.27

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.
@@ -3,6 +3,9 @@ import { HeartbeatSchema, HUB_HEARTBEAT_INTERVAL_MS, HubEvents, } from "@naisys/
3
3
  export function createHubHeartbeatService(naisysServer, { hubDb }, logService) {
4
4
  // Track active agent user IDs per host from heartbeat data
5
5
  const hostActiveAgents = new Map();
6
+ // Track each active agent's current run session and its last heartbeat time.
7
+ // Keyed by hostId so we can drop sessions when a host disconnects.
8
+ const hostActiveSessions = new Map();
6
9
  // Track per-agent notification IDs (latestLogId, latestMailId)
7
10
  const agentNotifications = new Map();
8
11
  /** Update a single notification field for an agent */
@@ -17,8 +20,9 @@ export function createHubHeartbeatService(naisysServer, { hubDb }, logService) {
17
20
  // Handle heartbeat from NAISYS instances
18
21
  naisysServer.registerEvent(HubEvents.HEARTBEAT, async (hostId, data) => {
19
22
  const parsed = HeartbeatSchema.parse(data);
23
+ const activeUserIds = parsed.activeSessions.map((s) => s.userId);
20
24
  // Update in-memory per-host active agent IDs
21
- hostActiveAgents.set(hostId, parsed.activeUserIds);
25
+ hostActiveAgents.set(hostId, activeUserIds);
22
26
  try {
23
27
  const now = new Date().toISOString();
24
28
  // Update host last_active
@@ -27,12 +31,32 @@ export function createHubHeartbeatService(naisysServer, { hubDb }, logService) {
27
31
  data: { last_active: now },
28
32
  });
29
33
  // Update user_notifications.last_active for each active user
30
- if (parsed.activeUserIds.length > 0) {
34
+ if (activeUserIds.length > 0) {
31
35
  await hubDb.user_notifications.updateMany({
32
- where: { user_id: { in: parsed.activeUserIds } },
36
+ where: { user_id: { in: activeUserIds } },
33
37
  data: { last_active: now, latest_host_id: hostId },
34
38
  });
35
39
  }
40
+ // Bump run_session.last_active for each active session so the run-online
41
+ // badge stays lit even during quiet periods with no log writes. The
42
+ // aggregate SESSION_HEARTBEAT broadcast runs on its own interval below.
43
+ const sessionMap = new Map();
44
+ for (const session of parsed.activeSessions) {
45
+ await hubDb.run_session.updateMany({
46
+ where: {
47
+ user_id: session.userId,
48
+ run_id: session.runId,
49
+ session_id: session.sessionId,
50
+ },
51
+ data: { last_active: now },
52
+ });
53
+ sessionMap.set(session.userId, {
54
+ runId: session.runId,
55
+ sessionId: session.sessionId,
56
+ lastActive: now,
57
+ });
58
+ }
59
+ hostActiveSessions.set(hostId, sessionMap);
36
60
  }
37
61
  catch (error) {
38
62
  logService.error(`[Hub:Heartbeat] Error updating heartbeat for host ${hostId}: ${error}`);
@@ -41,6 +65,7 @@ export function createHubHeartbeatService(naisysServer, { hubDb }, logService) {
41
65
  // Clean up tracking when a host disconnects
42
66
  naisysServer.registerEvent(HubEvents.CLIENT_DISCONNECTED, (hostId) => {
43
67
  hostActiveAgents.delete(hostId);
68
+ hostActiveSessions.delete(hostId);
44
69
  throttledPushAgentsStatus();
45
70
  });
46
71
  /** Push aggregate agent status to all connected NAISYS instances */
@@ -66,8 +91,31 @@ export function createHubHeartbeatService(naisysServer, { hubDb }, logService) {
66
91
  throttleTimer = null;
67
92
  }, 500);
68
93
  }
69
- // Periodically push aggregate active user status to all NAISYS instances
70
- const pushInterval = setInterval(pushAgentsStatus, HUB_HEARTBEAT_INTERVAL_MS);
94
+ /** Push aggregate session lastActive bumps to supervisors */
95
+ function pushSessionHeartbeat() {
96
+ const updates = [];
97
+ for (const sessions of hostActiveSessions.values()) {
98
+ for (const [userId, info] of sessions) {
99
+ updates.push({
100
+ userId,
101
+ runId: info.runId,
102
+ sessionId: info.sessionId,
103
+ lastActive: info.lastActive,
104
+ });
105
+ }
106
+ }
107
+ if (updates.length === 0)
108
+ return;
109
+ naisysServer.broadcastToSupervisors(HubEvents.SESSION_HEARTBEAT, {
110
+ updates,
111
+ });
112
+ }
113
+ // Periodically push aggregate active user status to all NAISYS instances,
114
+ // plus aggregate session heartbeats to supervisors.
115
+ const pushInterval = setInterval(() => {
116
+ pushAgentsStatus();
117
+ pushSessionHeartbeat();
118
+ }, HUB_HEARTBEAT_INTERVAL_MS);
71
119
  function getHostActiveAgentCount(hostId) {
72
120
  return hostActiveAgents.get(hostId)?.length ?? 0;
73
121
  }
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@naisys/hub",
3
- "version": "3.0.0-beta.25",
3
+ "version": "3.0.0-beta.27",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@naisys/hub",
9
- "version": "3.0.0-beta.25",
9
+ "version": "3.0.0-beta.27",
10
10
  "dependencies": {
11
- "@naisys/common": "3.0.0-beta.25",
12
- "@naisys/common-node": "3.0.0-beta.25",
13
- "@naisys/hub-database": "3.0.0-beta.25",
14
- "@naisys/hub-protocol": "3.0.0-beta.25",
11
+ "@naisys/common": "3.0.0-beta.27",
12
+ "@naisys/common-node": "3.0.0-beta.27",
13
+ "@naisys/hub-database": "3.0.0-beta.27",
14
+ "@naisys/hub-protocol": "3.0.0-beta.27",
15
15
  "commander": "^14.0.3",
16
16
  "dotenv": "^17.3.1",
17
17
  "fastify": "^5.8.2",
@@ -29,7 +29,7 @@
29
29
  "node": ">=22.0.0"
30
30
  },
31
31
  "peerDependencies": {
32
- "@naisys/supervisor": "3.0.0-beta.25"
32
+ "@naisys/supervisor": "3.0.0-beta.27"
33
33
  },
34
34
  "peerDependenciesMeta": {
35
35
  "@naisys/supervisor": {
@@ -194,32 +194,32 @@
194
194
  "license": "MIT"
195
195
  },
196
196
  "node_modules/@naisys/common": {
197
- "version": "3.0.0-beta.25",
198
- "resolved": "https://registry.npmjs.org/@naisys/common/-/common-3.0.0-beta.25.tgz",
199
- "integrity": "sha512-v21DvH/KlxKMxoTwwlInMyLgqVcSAYNYsMF2z7FXYiRL9fcqX15+S7oFZ2PsB+SR6MXlhN7AachF7BsYi7ikCw==",
197
+ "version": "3.0.0-beta.27",
198
+ "resolved": "https://registry.npmjs.org/@naisys/common/-/common-3.0.0-beta.27.tgz",
199
+ "integrity": "sha512-dwcVbUimPuDMdcFIlEBXnrKmhiz2w2ailPqL5r1GSAqPQoZLnD/158Uj6x51sTbiGjEqrY+pE8yoiG0bBDWSMQ==",
200
200
  "dependencies": {
201
201
  "semver": "^7.7.4",
202
202
  "zod": "^4.3.6"
203
203
  }
204
204
  },
205
205
  "node_modules/@naisys/common-node": {
206
- "version": "3.0.0-beta.25",
207
- "resolved": "https://registry.npmjs.org/@naisys/common-node/-/common-node-3.0.0-beta.25.tgz",
208
- "integrity": "sha512-WMc8gRNBEB3oh14zRjDNezqldLyPz2aXe7KHVZVEkIpShiYY8pMBva1up/1q40NmlWw/m/rbHRa6yrDUuH3qwg==",
206
+ "version": "3.0.0-beta.27",
207
+ "resolved": "https://registry.npmjs.org/@naisys/common-node/-/common-node-3.0.0-beta.27.tgz",
208
+ "integrity": "sha512-1beGPUi25pFEbwBaRRQzJoviIg+y42wGfsLvRUnRz2nOGoJkzJo+5leMW4fpy4ck8zY09iBGgLCmca29qe14Bg==",
209
209
  "dependencies": {
210
- "@naisys/common": "3.0.0-beta.25",
210
+ "@naisys/common": "3.0.0-beta.27",
211
211
  "better-sqlite3": "^12.6.2",
212
212
  "js-yaml": "^4.1.1",
213
213
  "pino": "^10.3.1"
214
214
  }
215
215
  },
216
216
  "node_modules/@naisys/hub-database": {
217
- "version": "3.0.0-beta.25",
218
- "resolved": "https://registry.npmjs.org/@naisys/hub-database/-/hub-database-3.0.0-beta.25.tgz",
219
- "integrity": "sha512-BtlvEaCwULaZlsgAZN6+1kKOxc0yHOyLV7CQmobrdBTuW8TTL4r71WoIL1wIVZFfu4Dz1SgCO5aSPh4LqRJkxg==",
217
+ "version": "3.0.0-beta.27",
218
+ "resolved": "https://registry.npmjs.org/@naisys/hub-database/-/hub-database-3.0.0-beta.27.tgz",
219
+ "integrity": "sha512-N7zcnQ81ly284VZAWramUg0WPZxchVFP4AUj20Ydl1PWUP7ioKd4c86FVvZJzC6Nsi+J2l8bCBKPHhWpqQ+dVQ==",
220
220
  "dependencies": {
221
- "@naisys/common": "3.0.0-beta.25",
222
- "@naisys/common-node": "3.0.0-beta.25",
221
+ "@naisys/common": "3.0.0-beta.27",
222
+ "@naisys/common-node": "3.0.0-beta.27",
223
223
  "@prisma/adapter-better-sqlite3": "^7.5.0",
224
224
  "@prisma/client": "^7.5.0",
225
225
  "better-sqlite3": "^12.6.2",
@@ -227,11 +227,11 @@
227
227
  }
228
228
  },
229
229
  "node_modules/@naisys/hub-protocol": {
230
- "version": "3.0.0-beta.25",
231
- "resolved": "https://registry.npmjs.org/@naisys/hub-protocol/-/hub-protocol-3.0.0-beta.25.tgz",
232
- "integrity": "sha512-kkaE4+62XkBsOegzSBnH2yz+Xmz3iWvFCpRdq9v5P8JVQAJoMYhWrrLDyQyUzdzIcD/ox3Jkl/y4+nwmFdVZsQ==",
230
+ "version": "3.0.0-beta.27",
231
+ "resolved": "https://registry.npmjs.org/@naisys/hub-protocol/-/hub-protocol-3.0.0-beta.27.tgz",
232
+ "integrity": "sha512-GVQe3NM5hKbMeS0lsznO0x3ivpl9nQyB8uVkI3/HXr4BAWJO6sDpXueWiqllGAG/SNQhcr9OTyd0elY0a9Otmg==",
233
233
  "dependencies": {
234
- "@naisys/common": "3.0.0-beta.25",
234
+ "@naisys/common": "3.0.0-beta.27",
235
235
  "zod": "^4.3.6"
236
236
  }
237
237
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naisys/hub",
3
- "version": "3.0.0-beta.25",
3
+ "version": "3.0.0-beta.27",
4
4
  "description": "NAISYS Hub - Adds persistence and multi-instance coordination to NAISYS",
5
5
  "type": "module",
6
6
  "main": "dist/naisysHub.js",
@@ -31,7 +31,7 @@
31
31
  "!dist/**/*.d.ts.map"
32
32
  ],
33
33
  "peerDependencies": {
34
- "@naisys/supervisor": "3.0.0-beta.25"
34
+ "@naisys/supervisor": "3.0.0-beta.27"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@naisys/supervisor": {
@@ -39,10 +39,10 @@
39
39
  }
40
40
  },
41
41
  "dependencies": {
42
- "@naisys/common": "3.0.0-beta.25",
43
- "@naisys/common-node": "3.0.0-beta.25",
44
- "@naisys/hub-database": "3.0.0-beta.25",
45
- "@naisys/hub-protocol": "3.0.0-beta.25",
42
+ "@naisys/common": "3.0.0-beta.27",
43
+ "@naisys/common-node": "3.0.0-beta.27",
44
+ "@naisys/hub-database": "3.0.0-beta.27",
45
+ "@naisys/hub-protocol": "3.0.0-beta.27",
46
46
  "commander": "^14.0.3",
47
47
  "dotenv": "^17.3.1",
48
48
  "fastify": "^5.8.2",