nexus-prime 3.2.0 → 3.2.2

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,98 +3,321 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { nexusEventBus } from '../engines/event-bus.js';
6
+ import { podNetwork } from '../engines/pod-network.js';
6
7
  const __filename = fileURLToPath(import.meta.url);
7
8
  const __dirname = path.dirname(__filename);
8
- const PORT = parseInt(process.env.NEXUS_DASHBOARD_PORT || '3377', 10);
9
9
  const HOST = process.env.NEXUS_DASHBOARD_HOST || '127.0.0.1';
10
+ const DEFAULT_PORT = parseInt(process.env.NEXUS_DASHBOARD_PORT || '3377', 10);
11
+ const MAX_PORT_SCAN = 24;
12
+ const DASHBOARD_API_VERSION = '2';
13
+ const REQUIRED_CAPABILITIES = {
14
+ runs: true,
15
+ memory: true,
16
+ pod: true,
17
+ clients: true,
18
+ events: true,
19
+ stream: true,
20
+ };
10
21
  export class DashboardServer {
11
22
  server;
12
23
  clients = new Set();
13
24
  unsubscribeBus = null;
14
25
  runtimeProvider;
26
+ memoryProvider;
27
+ adaptersProvider;
28
+ clientRegistryProvider;
15
29
  repoRoot;
30
+ dashboardUrl = null;
31
+ dashboardMode = 'idle';
32
+ activePort = null;
33
+ started = false;
34
+ initializePromise = null;
16
35
  constructor(options = {}) {
17
36
  this.runtimeProvider = options.runtimeProvider;
37
+ this.memoryProvider = options.memoryProvider;
38
+ this.adaptersProvider = options.adaptersProvider;
39
+ this.clientRegistryProvider = options.clientRegistryProvider;
18
40
  this.repoRoot = options.repoRoot ?? process.cwd();
19
- this.server = http.createServer(this.requestHandler.bind(this));
41
+ this.server = http.createServer((req, res) => {
42
+ void this.requestHandler(req, res);
43
+ });
44
+ this.server.on('error', (error) => {
45
+ if (this.dashboardMode === 'bound') {
46
+ console.error('[Dashboard] Server error:', error.message);
47
+ }
48
+ });
20
49
  }
21
50
  start() {
22
51
  if (process.env.NEXUS_DASHBOARD_DISABLED === '1') {
23
52
  console.error('[Dashboard] Disabled by NEXUS_DASHBOARD_DISABLED=1');
24
53
  return;
25
54
  }
26
- this.server.listen(PORT, HOST, () => {
27
- const address = this.server.address();
28
- const printablePort = typeof address === 'object' && address ? address.port : PORT;
29
- console.error(`[Dashboard] Runtime console live at http://${HOST}:${printablePort}`);
30
- });
31
- this.server.on('error', (e) => {
32
- if (e.code === 'EADDRINUSE') {
33
- console.error(`[Dashboard] Port ${PORT} occupied. Bridging to active dashboard cluster.`);
34
- }
35
- else {
36
- console.error(`[Dashboard] Server error:`, e.message);
37
- }
38
- });
39
- this.unsubscribeBus = nexusEventBus.onEvent((event) => {
40
- this.broadcast(event);
55
+ if (this.started) {
56
+ return;
57
+ }
58
+ this.started = true;
59
+ this.initializePromise = this.initialize().catch((error) => {
60
+ this.started = false;
61
+ this.dashboardMode = 'idle';
62
+ this.dashboardUrl = null;
63
+ this.activePort = null;
64
+ console.error('[Dashboard] Failed to start dashboard:', error instanceof Error ? error.message : String(error));
41
65
  });
42
- nexusEventBus.startFilePolling(1500);
43
66
  }
44
67
  stop() {
45
68
  if (this.unsubscribeBus) {
46
69
  this.unsubscribeBus();
47
70
  this.unsubscribeBus = null;
48
71
  }
49
- nexusEventBus.stopFilePolling();
50
- for (const res of this.clients) {
51
- res.end();
72
+ if (this.dashboardMode === 'bound') {
73
+ nexusEventBus.stopFilePolling();
74
+ for (const res of this.clients) {
75
+ res.end();
76
+ }
77
+ this.clients.clear();
78
+ this.server.close();
52
79
  }
53
- this.clients.clear();
54
- this.server.close();
80
+ this.dashboardMode = 'idle';
81
+ this.dashboardUrl = null;
82
+ this.activePort = null;
83
+ this.started = false;
55
84
  }
56
85
  getAddress() {
57
- const address = this.server.address();
58
- if (!address || typeof address === 'string')
59
- return null;
60
- return `http://${HOST}:${address.port}`;
61
- }
62
- requestHandler(req, res) {
63
- const url = new URL(req.url || '/', `http://${HOST}:${PORT}`);
64
- if (url.pathname === '/' || url.pathname === '/index.html') {
86
+ return this.dashboardUrl;
87
+ }
88
+ async initialize() {
89
+ const probe = await this.probeDashboard(DEFAULT_PORT);
90
+ if (probe.status === 'compatible') {
91
+ this.dashboardMode = 'reused';
92
+ this.dashboardUrl = probe.url;
93
+ this.activePort = DEFAULT_PORT;
94
+ console.error(`[Dashboard] Reusing compatible dashboard at ${probe.url}`);
95
+ return;
96
+ }
97
+ const startPort = probe.status === 'incompatible' ? DEFAULT_PORT + 1 : DEFAULT_PORT;
98
+ const fallbackPort = await this.bindFirstAvailablePort(startPort, DEFAULT_PORT + MAX_PORT_SCAN);
99
+ this.dashboardMode = 'bound';
100
+ this.activePort = fallbackPort;
101
+ this.dashboardUrl = this.buildUrl(fallbackPort);
102
+ this.unsubscribeBus = nexusEventBus.onEvent((event) => this.broadcast(event));
103
+ nexusEventBus.startFilePolling();
104
+ if (probe.status === 'incompatible') {
105
+ console.error(`[Dashboard] Incompatible dashboard detected at ${probe.url} (${probe.reason || 'missing compatibility contract'}). New dashboard started at ${this.dashboardUrl}`);
106
+ return;
107
+ }
108
+ console.error(`[Dashboard] Topology console listening at ${this.dashboardUrl}`);
109
+ }
110
+ async requestHandler(req, res) {
111
+ const url = new URL(req.url || '/', this.dashboardUrl ?? this.buildUrl(this.activePort ?? DEFAULT_PORT));
112
+ if (req.method === 'OPTIONS') {
113
+ this.respondOptions(res);
114
+ return;
115
+ }
116
+ if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/index.html')) {
65
117
  this.serveDashboard(res);
66
118
  return;
67
119
  }
68
- if (url.pathname === '/stream') {
120
+ if (req.method === 'GET' && url.pathname === '/stream') {
69
121
  this.serveSSE(req, res);
70
122
  return;
71
123
  }
72
- if (url.pathname === '/api/runs') {
73
- this.respondJson(res, this.getRuntime()?.listRuns(20) ?? []);
124
+ if (req.method === 'GET' && url.pathname === '/api/runs') {
125
+ const limit = parseInt(url.searchParams.get('limit') || '20', 10);
126
+ this.respondJson(res, this.getRuntime()?.listRuns(limit) ?? []);
74
127
  return;
75
128
  }
76
- if (url.pathname.startsWith('/api/runs/')) {
129
+ if (req.method === 'GET' && url.pathname.startsWith('/api/runs/')) {
77
130
  const runId = decodeURIComponent(url.pathname.replace('/api/runs/', ''));
78
131
  const run = this.getRuntime()?.getRun(runId);
79
132
  this.respondJson(res, run ?? { error: 'run-not-found', runId }, run ? 200 : 404);
80
133
  return;
81
134
  }
82
- if (url.pathname === '/api/skills') {
135
+ if (req.method === 'GET' && url.pathname === '/api/skills') {
83
136
  this.respondJson(res, this.getRuntime()?.listSkills() ?? []);
84
137
  return;
85
138
  }
86
- if (url.pathname === '/api/workflows') {
139
+ if (req.method === 'GET' && url.pathname === '/api/workflows') {
87
140
  this.respondJson(res, this.getRuntime()?.listWorkflows() ?? []);
88
141
  return;
89
142
  }
90
- if (url.pathname === '/api/backends') {
143
+ if (req.method === 'GET' && url.pathname === '/api/backends') {
91
144
  this.respondJson(res, this.getRuntime()?.getBackendCatalog() ?? {});
92
145
  return;
93
146
  }
94
- if (url.pathname === '/api/health') {
147
+ if (req.method === 'GET' && url.pathname === '/api/health') {
95
148
  this.respondJson(res, this.collectHealth());
96
149
  return;
97
150
  }
151
+ if (req.method === 'GET' && url.pathname === '/api/memory') {
152
+ const limit = parseInt(url.searchParams.get('limit') || '40', 10);
153
+ const tier = url.searchParams.get('tier') ?? undefined;
154
+ const tag = url.searchParams.get('tag') ?? undefined;
155
+ const linkedType = url.searchParams.get('linkedType') ?? undefined;
156
+ const recencyMs = url.searchParams.get('recencyMs');
157
+ const memory = this.getMemory();
158
+ this.respondJson(res, memory?.listSnapshots(limit, {
159
+ tier: tier,
160
+ tag,
161
+ linkedType: linkedType,
162
+ recencyMs: recencyMs ? parseInt(recencyMs, 10) : undefined,
163
+ }) ?? []);
164
+ return;
165
+ }
166
+ if (req.method === 'GET' && url.pathname.startsWith('/api/memory/') && url.pathname.endsWith('/network')) {
167
+ const id = decodeURIComponent(url.pathname.replace('/api/memory/', '').replace('/network', '').replace(/\/$/, ''));
168
+ const depth = parseInt(url.searchParams.get('depth') || '2', 10);
169
+ const limit = parseInt(url.searchParams.get('limit') || '18', 10);
170
+ const memory = this.getMemory();
171
+ this.respondJson(res, memory?.getNetworkSnapshot(id, depth, limit) ?? { focusId: id, nodes: [], links: [] });
172
+ return;
173
+ }
174
+ if (req.method === 'GET' && url.pathname.startsWith('/api/memory/')) {
175
+ const id = decodeURIComponent(url.pathname.replace('/api/memory/', ''));
176
+ const memory = this.getMemory();
177
+ const detail = memory?.getDetail(id);
178
+ this.respondJson(res, detail ?? { error: 'memory-not-found', id }, detail ? 200 : 404);
179
+ return;
180
+ }
181
+ if (req.method === 'GET' && url.pathname === '/api/pod') {
182
+ const limit = parseInt(url.searchParams.get('limit') || '40', 10);
183
+ this.respondJson(res, podNetwork.getDashboardSnapshot(limit));
184
+ return;
185
+ }
186
+ if (req.method === 'GET' && url.pathname.startsWith('/api/pod/')) {
187
+ const workerId = decodeURIComponent(url.pathname.replace('/api/pod/', ''));
188
+ const limit = parseInt(url.searchParams.get('limit') || '20', 10);
189
+ this.respondJson(res, podNetwork.getWorker(workerId, limit));
190
+ return;
191
+ }
192
+ if (req.method === 'GET' && url.pathname === '/api/clients') {
193
+ this.respondJson(res, this.getClientRegistry()?.listClients(this.getAdapters()) ?? []);
194
+ return;
195
+ }
196
+ if (req.method === 'GET' && url.pathname === '/api/events') {
197
+ const category = url.searchParams.get('category');
198
+ const type = url.searchParams.get('type');
199
+ const limit = parseInt(url.searchParams.get('limit') || '80', 10);
200
+ const events = this.getEventCards()
201
+ .filter((event) => !category || event.category === category)
202
+ .filter((event) => !type || event.type === type)
203
+ .slice(0, Math.max(limit, 1));
204
+ this.respondJson(res, events);
205
+ return;
206
+ }
207
+ if (req.method === 'POST' && url.pathname === '/api/skills/deploy') {
208
+ const body = await this.readJsonBody(req);
209
+ const runtime = this.getRuntime();
210
+ const deployed = runtime?.deploySkill(String(body.skillId), body.scope);
211
+ if (deployed) {
212
+ nexusEventBus.emit('skill.deploy', {
213
+ skillId: deployed.skillId,
214
+ scope: deployed.scope,
215
+ status: deployed.rolloutStatus,
216
+ });
217
+ }
218
+ this.respondJson(res, deployed ?? { error: 'skill-not-found' }, deployed ? 200 : 404);
219
+ return;
220
+ }
221
+ if (req.method === 'POST' && url.pathname === '/api/skills/revoke') {
222
+ const body = await this.readJsonBody(req);
223
+ const runtime = this.getRuntime();
224
+ const revoked = runtime?.revokeSkill(String(body.skillId));
225
+ if (revoked) {
226
+ nexusEventBus.emit('skill.revoke', {
227
+ skillId: revoked.skillId,
228
+ status: revoked.rolloutStatus,
229
+ });
230
+ }
231
+ this.respondJson(res, revoked ?? { error: 'skill-not-found' }, revoked ? 200 : 404);
232
+ return;
233
+ }
234
+ if (req.method === 'POST' && url.pathname === '/api/workflows/deploy') {
235
+ const body = await this.readJsonBody(req);
236
+ const runtime = this.getRuntime();
237
+ const deployed = runtime?.deployWorkflow(String(body.workflowId), body.scope);
238
+ if (deployed) {
239
+ nexusEventBus.emit('workflow.deploy', {
240
+ workflowId: deployed.workflowId,
241
+ scope: deployed.scope,
242
+ status: deployed.rolloutStatus,
243
+ });
244
+ }
245
+ this.respondJson(res, deployed ?? { error: 'workflow-not-found' }, deployed ? 200 : 404);
246
+ return;
247
+ }
248
+ if (req.method === 'POST' && url.pathname === '/api/workflows/run') {
249
+ const body = await this.readJsonBody(req);
250
+ const runtime = this.getRuntime();
251
+ if (!runtime) {
252
+ this.respondJson(res, { error: 'runtime-unavailable' }, 503);
253
+ return;
254
+ }
255
+ try {
256
+ const run = await runtime.runWorkflow(String(body.workflowId), body.goal ? String(body.goal) : undefined);
257
+ nexusEventBus.emit('workflow.run', {
258
+ workflowId: String(body.workflowId),
259
+ runId: run.runId,
260
+ status: run.state,
261
+ });
262
+ this.respondJson(res, run);
263
+ }
264
+ catch (error) {
265
+ this.respondJson(res, { error: error instanceof Error ? error.message : 'workflow-run-failed' }, 400);
266
+ }
267
+ return;
268
+ }
269
+ if (req.method === 'POST' && url.pathname === '/api/runtime/execute') {
270
+ const body = await this.readJsonBody(req);
271
+ const runtime = this.getRuntime();
272
+ if (!runtime) {
273
+ this.respondJson(res, { error: 'runtime-unavailable' }, 503);
274
+ return;
275
+ }
276
+ if (!body.goal || typeof body.goal !== 'string') {
277
+ this.respondJson(res, { error: 'goal-required' }, 400);
278
+ return;
279
+ }
280
+ const run = await runtime.run(body);
281
+ nexusEventBus.emit('dashboard.action', {
282
+ action: 'runtime.execute',
283
+ status: run.state,
284
+ target: run.runId,
285
+ });
286
+ this.respondJson(res, run, 201);
287
+ return;
288
+ }
289
+ if (req.method === 'POST' && url.pathname.startsWith('/api/clients/') && url.pathname.endsWith('/reconnect')) {
290
+ const clientId = decodeURIComponent(url.pathname.replace('/api/clients/', '').replace('/reconnect', '').replace(/\/$/, ''));
291
+ const registry = this.getClientRegistry();
292
+ if (!registry) {
293
+ this.respondJson(res, { error: 'client-registry-unavailable' }, 503);
294
+ return;
295
+ }
296
+ const client = registry.reconnect(clientId);
297
+ nexusEventBus.emit('dashboard.action', {
298
+ action: 'client.reconnect',
299
+ status: 'ok',
300
+ target: clientId,
301
+ });
302
+ this.respondJson(res, client);
303
+ return;
304
+ }
305
+ if (req.method === 'POST' && url.pathname.startsWith('/api/clients/') && url.pathname.endsWith('/clear')) {
306
+ const clientId = decodeURIComponent(url.pathname.replace('/api/clients/', '').replace('/clear', '').replace(/\/$/, ''));
307
+ const registry = this.getClientRegistry();
308
+ if (!registry) {
309
+ this.respondJson(res, { error: 'client-registry-unavailable' }, 503);
310
+ return;
311
+ }
312
+ registry.clear(clientId);
313
+ nexusEventBus.emit('dashboard.action', {
314
+ action: 'client.clear',
315
+ status: 'ok',
316
+ target: clientId,
317
+ });
318
+ this.respondJson(res, { ok: true, clientId });
319
+ return;
320
+ }
98
321
  res.writeHead(404);
99
322
  res.end('Not found');
100
323
  }
@@ -114,12 +337,12 @@ export class DashboardServer {
114
337
  res.writeHead(200, {
115
338
  'Content-Type': 'text/event-stream',
116
339
  'Cache-Control': 'no-cache',
117
- 'Connection': 'keep-alive',
118
- 'Access-Control-Allow-Origin': '*'
340
+ Connection: 'keep-alive',
341
+ 'Access-Control-Allow-Origin': '*',
119
342
  });
120
343
  res.write('retry: 3000\n\n');
121
344
  res.write(`event: bootstrap\ndata: ${JSON.stringify({ connected: true, timestamp: Date.now() })}\n\n`);
122
- const history = nexusEventBus.getHistory();
345
+ const history = this.getEventCards();
123
346
  for (const evt of history) {
124
347
  res.write(`data: ${JSON.stringify(evt)}\n\n`);
125
348
  }
@@ -134,11 +357,20 @@ export class DashboardServer {
134
357
  });
135
358
  }
136
359
  broadcast(event) {
137
- const dataStr = `data: ${JSON.stringify(event)}\n\n`;
360
+ const normalized = this.normalizeEvent(event);
361
+ const dataStr = `data: ${JSON.stringify(normalized)}\n\n`;
138
362
  for (const res of this.clients) {
139
363
  res.write(dataStr);
140
364
  }
141
365
  }
366
+ respondOptions(res) {
367
+ res.writeHead(204, {
368
+ 'Access-Control-Allow-Origin': '*',
369
+ 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
370
+ 'Access-Control-Allow-Headers': 'Content-Type',
371
+ });
372
+ res.end();
373
+ }
142
374
  respondJson(res, data, statusCode = 200) {
143
375
  res.writeHead(statusCode, {
144
376
  'Content-Type': 'application/json',
@@ -147,14 +379,58 @@ export class DashboardServer {
147
379
  });
148
380
  res.end(JSON.stringify(data, null, 2));
149
381
  }
382
+ async readJsonBody(req) {
383
+ const chunks = [];
384
+ for await (const chunk of req) {
385
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
386
+ }
387
+ if (!chunks.length)
388
+ return {};
389
+ try {
390
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
391
+ }
392
+ catch {
393
+ return {};
394
+ }
395
+ }
150
396
  getRuntime() {
151
397
  return this.runtimeProvider?.();
152
398
  }
399
+ getMemory() {
400
+ return this.memoryProvider?.();
401
+ }
402
+ getAdapters() {
403
+ return this.adaptersProvider?.() ?? [];
404
+ }
405
+ getClientRegistry() {
406
+ return this.clientRegistryProvider?.();
407
+ }
408
+ getEventCards() {
409
+ return nexusEventBus.getHistory().map((event) => this.normalizeEvent(event)).reverse();
410
+ }
411
+ normalizeEvent(event) {
412
+ const category = mapEventCategory(event.type);
413
+ const severity = mapEventSeverity(event.type, event.data);
414
+ return {
415
+ id: event.id,
416
+ type: event.type,
417
+ title: mapEventTitle(event.type),
418
+ source: mapEventSource(event.type, event.data),
419
+ time: event.timestamp,
420
+ severity,
421
+ category,
422
+ summary: summarizeEvent(event.type, event.data),
423
+ payload: event.data,
424
+ };
425
+ }
153
426
  collectHealth() {
154
427
  const packageJsonPath = path.join(this.repoRoot, 'package.json');
155
428
  const workflowPath = path.join(this.repoRoot, '.github', 'workflows', 'pages.yml');
156
429
  const docsDir = path.join(this.repoRoot, 'docs');
157
430
  const runtime = this.getRuntime();
431
+ const memory = this.getMemory();
432
+ const clientRegistry = this.getClientRegistry();
433
+ const podSnapshot = podNetwork.getDashboardSnapshot(20);
158
434
  let packageVersion = 'unknown';
159
435
  if (fs.existsSync(packageJsonPath)) {
160
436
  try {
@@ -169,12 +445,28 @@ export class DashboardServer {
169
445
  const raw = fs.readFileSync(workflowPath, 'utf-8');
170
446
  pagesWorkflowValid = raw.includes('steps.deployment.outputs.page_url');
171
447
  }
448
+ const clients = clientRegistry?.listClients(this.getAdapters()) ?? [];
172
449
  return {
450
+ dashboardApiVersion: DASHBOARD_API_VERSION,
451
+ capabilities: { ...REQUIRED_CAPABILITIES },
452
+ dashboardUrl: this.getAddress(),
453
+ dashboardMode: this.dashboardMode,
173
454
  connection: {
174
455
  stream: this.clients.size > 0 ? 'connected' : 'idle',
175
456
  subscribers: this.clients.size,
176
457
  },
177
458
  runtime: runtime?.getHealth() ?? { runtime: 'unavailable' },
459
+ memory: memory?.getStats() ?? { prefrontal: 0, hippocampus: 0, cortex: 0, totalLinks: 0, oldestEntry: null, topTags: [] },
460
+ pod: {
461
+ workers: podSnapshot.activeWorkers.length,
462
+ lastMessageTimestamp: podSnapshot.lastMessageTimestamp,
463
+ confidenceBands: podSnapshot.confidenceBands,
464
+ },
465
+ clients: {
466
+ total: clients.length,
467
+ active: clients.filter((client) => client.state === 'active').length,
468
+ inferred: clients.filter((client) => client.state === 'inferred').length,
469
+ },
178
470
  release: {
179
471
  packageVersion,
180
472
  },
@@ -188,5 +480,236 @@ export class DashboardServer {
188
480
  },
189
481
  };
190
482
  }
483
+ buildUrl(port) {
484
+ return `http://${HOST}:${port}`;
485
+ }
486
+ async probeDashboard(port) {
487
+ const url = this.buildUrl(port);
488
+ try {
489
+ const response = await this.requestProbe(`${url}/api/health`);
490
+ if (response.statusCode !== 200) {
491
+ return {
492
+ status: 'incompatible',
493
+ url,
494
+ reason: `health-status-${response.statusCode}`,
495
+ };
496
+ }
497
+ let payload = null;
498
+ try {
499
+ payload = JSON.parse(response.body);
500
+ }
501
+ catch {
502
+ return {
503
+ status: 'incompatible',
504
+ url,
505
+ reason: 'health-invalid-json',
506
+ };
507
+ }
508
+ if (this.isCompatibleHealth(payload)) {
509
+ return {
510
+ status: 'compatible',
511
+ url,
512
+ health: payload,
513
+ };
514
+ }
515
+ return {
516
+ status: 'incompatible',
517
+ url,
518
+ health: payload,
519
+ reason: 'health-incompatible',
520
+ };
521
+ }
522
+ catch (error) {
523
+ if (this.isFreePortProbeError(error)) {
524
+ return {
525
+ status: 'free',
526
+ url,
527
+ reason: error instanceof Error ? error.message : 'connection-refused',
528
+ };
529
+ }
530
+ return {
531
+ status: 'incompatible',
532
+ url,
533
+ reason: error instanceof Error ? error.message : 'probe-failed',
534
+ };
535
+ }
536
+ }
537
+ isCompatibleHealth(payload) {
538
+ if (!payload || payload.dashboardApiVersion !== DASHBOARD_API_VERSION) {
539
+ return false;
540
+ }
541
+ return Object.entries(REQUIRED_CAPABILITIES).every(([key, expected]) => payload.capabilities?.[key] === expected);
542
+ }
543
+ isFreePortProbeError(error) {
544
+ const code = typeof error === 'object' && error && 'code' in error ? String(error.code) : '';
545
+ return code === 'ECONNREFUSED' || code === 'ECONNRESET' || code === 'EHOSTUNREACH' || code === 'ENOTFOUND';
546
+ }
547
+ requestProbe(url) {
548
+ return new Promise((resolve, reject) => {
549
+ const req = http.get(url, { timeout: 1200 }, (res) => {
550
+ const chunks = [];
551
+ res.on('data', (chunk) => {
552
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
553
+ });
554
+ res.on('end', () => {
555
+ resolve({
556
+ statusCode: res.statusCode ?? 0,
557
+ body: Buffer.concat(chunks).toString('utf8'),
558
+ });
559
+ });
560
+ });
561
+ req.on('timeout', () => {
562
+ req.destroy(new Error('probe-timeout'));
563
+ });
564
+ req.on('error', reject);
565
+ });
566
+ }
567
+ async bindFirstAvailablePort(startPort, endPort) {
568
+ let lastError = null;
569
+ for (let port = startPort; port <= endPort; port += 1) {
570
+ try {
571
+ await this.listenOnPort(port);
572
+ return port;
573
+ }
574
+ catch (error) {
575
+ lastError = error;
576
+ const code = typeof error === 'object' && error && 'code' in error ? String(error.code) : '';
577
+ if (code !== 'EADDRINUSE') {
578
+ throw error;
579
+ }
580
+ }
581
+ }
582
+ throw lastError instanceof Error
583
+ ? lastError
584
+ : new Error(`No free dashboard port found in range ${startPort}-${endPort}`);
585
+ }
586
+ listenOnPort(port) {
587
+ return new Promise((resolve, reject) => {
588
+ const handleListening = () => {
589
+ cleanup();
590
+ resolve();
591
+ };
592
+ const handleError = (error) => {
593
+ cleanup();
594
+ reject(error);
595
+ };
596
+ const cleanup = () => {
597
+ this.server.off('listening', handleListening);
598
+ this.server.off('error', handleError);
599
+ };
600
+ this.server.once('listening', handleListening);
601
+ this.server.once('error', handleError);
602
+ this.server.listen(port, HOST);
603
+ });
604
+ }
605
+ }
606
+ function mapEventCategory(type) {
607
+ if (type.startsWith('memory.'))
608
+ return 'memory';
609
+ if (type.startsWith('pod.'))
610
+ return 'pod';
611
+ if (type.startsWith('phantom.'))
612
+ return 'runtime';
613
+ if (type.startsWith('client.'))
614
+ return 'clients';
615
+ if (type.startsWith('skill.'))
616
+ return 'skills';
617
+ if (type.startsWith('workflow.'))
618
+ return 'workflows';
619
+ if (type.startsWith('tokens.') || type.startsWith('cas.') || type.startsWith('kv.'))
620
+ return 'tokens';
621
+ return 'system';
622
+ }
623
+ function mapEventSeverity(type, payload) {
624
+ if (type === 'guardrail.check') {
625
+ return payload.passed ? 'good' : 'bad';
626
+ }
627
+ if (type === 'phantom.merge' || type === 'phantom.merge.complete' || type === 'workflow.run') {
628
+ return payload.status === 'failed' ? 'bad' : 'good';
629
+ }
630
+ if (type === 'client.inferred')
631
+ return 'warn';
632
+ if (type === 'dashboard.action' && payload.status === 'failed')
633
+ return 'bad';
634
+ if (type === 'pod.signal')
635
+ return 'info';
636
+ return 'info';
637
+ }
638
+ function mapEventTitle(type) {
639
+ return {
640
+ 'system.boot': 'Runtime boot',
641
+ 'memory.store': 'Memory stored',
642
+ 'memory.recall': 'Memory recall',
643
+ 'pod.signal': 'POD signal',
644
+ 'tokens.optimized': 'Tokens optimized',
645
+ 'phantom.worker.start': 'Worker start',
646
+ 'phantom.worker.complete': 'Worker complete',
647
+ 'phantom.merge.complete': 'Merge complete',
648
+ 'phantom.merge': 'Merge decision',
649
+ 'guardrail.check': 'Guardrail check',
650
+ 'ghost.pass': 'Ghost pass',
651
+ 'graph.query': 'Graph query',
652
+ 'darwin.cycle': 'Darwin cycle',
653
+ 'session.dna': 'Session DNA',
654
+ 'skill.register': 'Skill registered',
655
+ 'skill.deploy': 'Skill deployed',
656
+ 'skill.revoke': 'Skill revoked',
657
+ 'workflow.deploy': 'Workflow deployed',
658
+ 'workflow.run': 'Workflow run',
659
+ 'client.heartbeat': 'Client heartbeat',
660
+ 'client.inferred': 'Client inferred',
661
+ 'client.status': 'Client status',
662
+ 'dashboard.action': 'Dashboard action',
663
+ 'nexusnet.publish': 'NexusNet publish',
664
+ 'nexusnet.sync': 'NexusNet sync',
665
+ 'entanglement.create': 'Entanglement created',
666
+ 'entanglement.collapse': 'Entanglement collapsed',
667
+ 'entanglement.correlate': 'Entanglement correlated',
668
+ 'cas.encode': 'CAS encode',
669
+ 'cas.decode': 'CAS decode',
670
+ 'cas.pattern_learned': 'CAS pattern',
671
+ 'kv.merge': 'KV merge',
672
+ 'kv.adapt': 'KV adapt',
673
+ 'kv.consensus': 'KV consensus',
674
+ }[type] ?? type;
675
+ }
676
+ function mapEventSource(type, payload) {
677
+ if (type.startsWith('client.'))
678
+ return String(payload.displayName ?? payload.clientId ?? 'client');
679
+ if (type === 'pod.signal')
680
+ return String(payload.workerId ?? 'pod');
681
+ if (type.startsWith('phantom.'))
682
+ return String(payload.workerId ?? payload.winner ?? 'runtime');
683
+ if (type.startsWith('skill.'))
684
+ return String(payload.skillId ?? payload.name ?? 'skill');
685
+ if (type.startsWith('workflow.'))
686
+ return String(payload.workflowId ?? 'workflow');
687
+ return 'nexus-prime';
688
+ }
689
+ function summarizeEvent(type, payload) {
690
+ switch (type) {
691
+ case 'memory.store':
692
+ return `Priority ${payload.priority ?? 'n/a'} · ${payload.tags?.join(', ') ?? 'no tags'}`;
693
+ case 'memory.recall':
694
+ return `Recalled ${payload.count ?? 0} memories for "${payload.query ?? ''}"`;
695
+ case 'pod.signal':
696
+ return String(payload.content ?? 'POD signal received');
697
+ case 'tokens.optimized':
698
+ return `Saved ${payload.savings ?? 0} tokens across ${payload.files ?? 0} files`;
699
+ case 'phantom.worker.start':
700
+ return `${payload.approach ?? 'worker'} started for ${payload.goal ?? 'task'}`;
701
+ case 'phantom.worker.complete':
702
+ return `Confidence ${payload.confidence ?? 0}`;
703
+ case 'phantom.merge':
704
+ return `${payload.action ?? 'merge'} · ${payload.winner ?? 'unknown winner'}`;
705
+ case 'client.heartbeat':
706
+ case 'client.inferred':
707
+ case 'client.status':
708
+ return JSON.stringify(payload);
709
+ case 'dashboard.action':
710
+ return `${payload.action ?? 'action'} → ${payload.status ?? 'unknown'}`;
711
+ default:
712
+ return JSON.stringify(payload);
713
+ }
191
714
  }
192
715
  //# sourceMappingURL=server.js.map