agentflow-dashboard 0.5.0 → 0.6.0

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/cli.cjs CHANGED
@@ -37,13 +37,13 @@ var os = __toESM(require("os"), 1);
37
37
  var path3 = __toESM(require("path"), 1);
38
38
 
39
39
  // src/server.ts
40
- var import_express = __toESM(require("express"), 1);
41
40
  var fs2 = __toESM(require("fs"), 1);
42
- var import_http = require("http");
41
+ var import_node_http = require("http");
43
42
  var path2 = __toESM(require("path"), 1);
44
- var import_url = require("url");
45
- var import_ws = require("ws");
43
+ var import_node_url = require("url");
46
44
  var import_agentflow_core3 = require("agentflow-core");
45
+ var import_express = __toESM(require("express"), 1);
46
+ var import_ws = require("ws");
47
47
 
48
48
  // src/stats.ts
49
49
  var import_agentflow_core = require("agentflow-core");
@@ -221,12 +221,162 @@ var AgentStats = class {
221
221
  };
222
222
 
223
223
  // src/watcher.ts
224
- var import_agentflow_core2 = require("agentflow-core");
225
- var import_chokidar = __toESM(require("chokidar"), 1);
226
- var import_events = require("events");
224
+ var import_node_events = require("events");
227
225
  var fs = __toESM(require("fs"), 1);
228
226
  var path = __toESM(require("path"), 1);
229
- var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
227
+ var import_agentflow_core2 = require("agentflow-core");
228
+ var import_chokidar = __toESM(require("chokidar"), 1);
229
+
230
+ // src/parsers/log-utils.ts
231
+ function stripAnsi(str) {
232
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
233
+ }
234
+ function parseValue(value) {
235
+ if (value.match(/^\d+$/)) return parseInt(value, 10);
236
+ if (value.match(/^\d+\.\d+$/)) return parseFloat(value);
237
+ if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
238
+ if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
239
+ return value;
240
+ }
241
+ function parseTimestamp(value) {
242
+ if (!value) return null;
243
+ if (typeof value === "number") return value;
244
+ try {
245
+ return new Date(value).getTime();
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
250
+ function extractTimestamp(line) {
251
+ const clean = stripAnsi(line);
252
+ const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
253
+ if (isoMatch) return new Date(isoMatch[1]).getTime();
254
+ return null;
255
+ }
256
+ function extractLogLevel(line) {
257
+ const clean = stripAnsi(line);
258
+ const levelMatch = clean.match(/\b(debug|info|warn|warning|error|fatal|trace)\b/i);
259
+ return levelMatch ? levelMatch[1].trim().toLowerCase() : null;
260
+ }
261
+ function extractAction(line) {
262
+ const clean = stripAnsi(line);
263
+ const actionMatch = clean.match(/\]\s+(\S+)/);
264
+ if (actionMatch) return actionMatch[1].trim();
265
+ const afterLevel = clean.replace(/^.*?(debug|info|warn|warning|error|fatal|trace)\s*\]?\s*/i, "");
266
+ return afterLevel.split(/\s+/)[0] || "";
267
+ }
268
+ function extractKeyValuePairs(line) {
269
+ const pairs = {};
270
+ const clean = stripAnsi(line);
271
+ const kvRegex = /(\w+)=('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|\S+)/g;
272
+ let match;
273
+ while ((match = kvRegex.exec(clean)) !== null) {
274
+ if (match[1] === "Z" || match[1] === "m") continue;
275
+ pairs[match[1]] = parseValue(match[2]);
276
+ }
277
+ return pairs;
278
+ }
279
+ function detectComponent(action, kvPairs) {
280
+ if (action.includes(".")) return action.split(".")[0];
281
+ if (kvPairs.component) return String(kvPairs.component);
282
+ if (kvPairs.service) return String(kvPairs.service);
283
+ if (kvPairs.module) return String(kvPairs.module);
284
+ if (kvPairs.worker) return String(kvPairs.worker);
285
+ return action || "unknown";
286
+ }
287
+ function detectOperation(action, kvPairs) {
288
+ if (action.includes(".")) return action.split(".").slice(1).join(".");
289
+ if (kvPairs.operation) return String(kvPairs.operation);
290
+ if (kvPairs.method) return String(kvPairs.method);
291
+ if (kvPairs.action) return String(kvPairs.action);
292
+ return action || "activity";
293
+ }
294
+ function detectActivityPattern(line) {
295
+ let timestamp = extractTimestamp(line);
296
+ let level = extractLogLevel(line);
297
+ let action = extractAction(line);
298
+ let kvPairs = extractKeyValuePairs(line);
299
+ if (!timestamp) {
300
+ const jsonMatch = line.match(/\{.*\}/);
301
+ if (jsonMatch) {
302
+ try {
303
+ const parsed = JSON.parse(jsonMatch[0]);
304
+ timestamp = parseTimestamp(parsed.timestamp || parsed.time || parsed.ts) || Date.now();
305
+ level = parsed.level || parsed.severity || "info";
306
+ action = parsed.action || parsed.event || parsed.message || "";
307
+ kvPairs = parsed;
308
+ } catch {
309
+ }
310
+ }
311
+ }
312
+ if (!timestamp) {
313
+ const kvMatches = line.match(/(\w+)=([^\s]+)/g);
314
+ if (kvMatches && kvMatches.length >= 2) {
315
+ const pairs = {};
316
+ for (const m of kvMatches) {
317
+ const [key, value] = m.split("=", 2);
318
+ pairs[key] = parseValue(value);
319
+ }
320
+ timestamp = parseTimestamp(pairs.timestamp || pairs.time) || Date.now();
321
+ level = String(pairs.level || "info");
322
+ action = String(pairs.action || pairs.event || "");
323
+ kvPairs = pairs;
324
+ }
325
+ }
326
+ if (!timestamp) {
327
+ const logMatch = line.match(
328
+ /^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)\s+(\w+)?\s*:?\s*(.+)/
329
+ );
330
+ if (logMatch) {
331
+ timestamp = new Date(logMatch[1]).getTime();
332
+ level = logMatch[2] || "info";
333
+ action = logMatch[3] || "";
334
+ }
335
+ }
336
+ if (!timestamp) return null;
337
+ return {
338
+ timestamp,
339
+ level: (level == null ? void 0 : level.toLowerCase()) || "info",
340
+ action,
341
+ component: detectComponent(action, kvPairs),
342
+ operation: detectOperation(action, kvPairs),
343
+ ...kvPairs
344
+ };
345
+ }
346
+ function extractSessionIdentifier(activity) {
347
+ return String(
348
+ activity.session_id || activity.run_id || activity.request_id || activity.trace_id || activity.sweep_id || activity.transaction_id || "default"
349
+ );
350
+ }
351
+ function detectTrigger(activity) {
352
+ if (activity.trigger) return String(activity.trigger);
353
+ if (activity.method && activity.url) return "api-call";
354
+ if (typeof activity.operation === "string" && activity.operation.includes("start"))
355
+ return "startup";
356
+ if (typeof activity.operation === "string" && activity.operation.includes("invoke"))
357
+ return "invocation";
358
+ return "event";
359
+ }
360
+ function getUniversalNodeStatus(activity) {
361
+ if (activity.level === "error" || activity.level === "fatal") return "failed";
362
+ if (activity.level === "warn" || activity.level === "warning") return "warning";
363
+ const op = String(activity.operation || "");
364
+ if (op.match(/start|begin|init/i)) return "running";
365
+ if (op.match(/complete|finish|end|done/i)) return "completed";
366
+ return "completed";
367
+ }
368
+ function openClawSessionIdToAgent(sessionId) {
369
+ if (sessionId.startsWith("janitor-")) return "vault-janitor";
370
+ if (sessionId.startsWith("curator-")) return "vault-curator";
371
+ if (sessionId.startsWith("distiller-")) return "vault-distiller";
372
+ if (sessionId.startsWith("main-")) return "main";
373
+ const firstSegment = sessionId.split("-")[0];
374
+ if (firstSegment) return firstSegment;
375
+ return "openclaw";
376
+ }
377
+
378
+ // src/watcher.ts
379
+ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
230
380
  watchers = [];
231
381
  traces = /* @__PURE__ */ new Map();
232
382
  tracesDir;
@@ -265,7 +415,9 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
265
415
  console.error(`Error scanning directory ${dir}:`, error);
266
416
  }
267
417
  }
268
- console.log(`Scanned ${totalDirectories} directories (recursive), loaded ${this.traces.size} items from ${totalFiles} files`);
418
+ console.log(
419
+ `Scanned ${totalDirectories} directories (recursive), loaded ${this.traces.size} items from ${totalFiles} files`
420
+ );
269
421
  }
270
422
  /** Recursively scan directory for supported file types */
271
423
  scanDirectoryRecursive(dir, depth = 0) {
@@ -307,7 +459,14 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
307
459
  "models.json",
308
460
  "config.json"
309
461
  ]);
310
- static SKIP_SUFFIXES = ["-state.json", "-config.json", "-watch-state.json", ".tmp", ".bak", ".backup"];
462
+ static SKIP_SUFFIXES = [
463
+ "-state.json",
464
+ "-config.json",
465
+ "-watch-state.json",
466
+ ".tmp",
467
+ ".bak",
468
+ ".backup"
469
+ ];
311
470
  /** Load a .json trace, .jsonl session file, or .log file. */
312
471
  loadFile(filePath) {
313
472
  const filename = path.basename(filePath);
@@ -358,16 +517,16 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
358
517
  const lines = content.split("\n").filter((line) => line.trim());
359
518
  const activities = /* @__PURE__ */ new Map();
360
519
  for (const line of lines) {
361
- const activity = this.detectActivityPattern(line);
520
+ const activity = detectActivityPattern(line);
362
521
  if (!activity) continue;
363
- const sessionId = this.extractSessionIdentifier(activity);
522
+ const sessionId = extractSessionIdentifier(activity);
364
523
  if (!activities.has(sessionId)) {
365
524
  activities.set(sessionId, {
366
525
  id: sessionId,
367
526
  rootNodeId: "",
368
527
  agentId: this.detectAgentIdentifier(activity, filename, filePath),
369
528
  name: this.generateActivityName(activity, sessionId),
370
- trigger: this.detectTrigger(activity),
529
+ trigger: detectTrigger(activity),
371
530
  startTime: activity.timestamp,
372
531
  endTime: activity.timestamp,
373
532
  status: "completed",
@@ -390,7 +549,9 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
390
549
  (session) => Object.keys(session.nodes).length > 0
391
550
  );
392
551
  for (const trace of traces) {
393
- const sortedNodes = Object.values(trace.nodes).sort((a, b) => a.startTime - b.startTime);
552
+ const sortedNodes = Object.values(trace.nodes).sort(
553
+ (a, b) => a.startTime - b.startTime
554
+ );
394
555
  trace.sessionEvents = sortedNodes.map((node) => ({
395
556
  type: node.status === "failed" ? "tool_result" : "system",
396
557
  timestamp: node.startTime,
@@ -408,7 +569,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
408
569
  id: "",
409
570
  rootNodeId: "root",
410
571
  nodes: {
411
- "root": {
572
+ root: {
412
573
  id: "root",
413
574
  type: "log-file",
414
575
  name: filename,
@@ -431,125 +592,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
431
592
  }
432
593
  return traces;
433
594
  }
434
- /** Detect activity patterns in log lines using universal heuristics */
435
- detectActivityPattern(line) {
436
- let timestamp = this.extractTimestamp(line);
437
- let level = this.extractLogLevel(line);
438
- let action = this.extractAction(line);
439
- let kvPairs = this.extractKeyValuePairs(line);
440
- if (!timestamp) {
441
- const jsonMatch = line.match(/\{.*\}/);
442
- if (jsonMatch) {
443
- try {
444
- const parsed = JSON.parse(jsonMatch[0]);
445
- timestamp = this.parseTimestamp(parsed.timestamp || parsed.time || parsed.ts) || Date.now();
446
- level = parsed.level || parsed.severity || "info";
447
- action = parsed.action || parsed.event || parsed.message || "";
448
- kvPairs = parsed;
449
- } catch {
450
- }
451
- }
452
- }
453
- if (!timestamp) {
454
- const kvMatches = line.match(/(\w+)=([^\s]+)/g);
455
- if (kvMatches && kvMatches.length >= 2) {
456
- const pairs = {};
457
- kvMatches.forEach((match) => {
458
- const [key, value] = match.split("=", 2);
459
- pairs[key] = this.parseValue(value);
460
- });
461
- timestamp = this.parseTimestamp(pairs.timestamp || pairs.time) || Date.now();
462
- level = pairs.level || "info";
463
- action = pairs.action || pairs.event || "";
464
- kvPairs = pairs;
465
- }
466
- }
467
- if (!timestamp) {
468
- const logMatch = line.match(/^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)\s+(\w+)?\s*:?\s*(.+)/);
469
- if (logMatch) {
470
- timestamp = new Date(logMatch[1]).getTime();
471
- level = logMatch[2] || "info";
472
- action = logMatch[3] || "";
473
- }
474
- }
475
- if (!timestamp) return null;
476
- return {
477
- timestamp,
478
- level: (level == null ? void 0 : level.toLowerCase()) || "info",
479
- action,
480
- component: this.detectComponent(action, kvPairs),
481
- operation: this.detectOperation(action, kvPairs),
482
- ...kvPairs
483
- };
484
- }
485
- /** Strip ANSI escape codes from a string. */
486
- stripAnsi(str) {
487
- return str.replace(/\x1b\[[0-9;]*m/g, "");
488
- }
489
- extractTimestamp(line) {
490
- const clean = this.stripAnsi(line);
491
- const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
492
- if (isoMatch) return new Date(isoMatch[1]).getTime();
493
- return null;
494
- }
495
- extractLogLevel(line) {
496
- const clean = this.stripAnsi(line);
497
- const levelMatch = clean.match(/\b(debug|info|warn|warning|error|fatal|trace)\b/i);
498
- return levelMatch ? levelMatch[1].trim().toLowerCase() : null;
499
- }
500
- extractAction(line) {
501
- const clean = this.stripAnsi(line);
502
- const actionMatch = clean.match(/\]\s+(\S+)/);
503
- if (actionMatch) return actionMatch[1].trim();
504
- const afterLevel = clean.replace(/^.*?(debug|info|warn|warning|error|fatal|trace)\s*\]?\s*/i, "");
505
- return afterLevel.split(/\s+/)[0] || "";
506
- }
507
- extractKeyValuePairs(line) {
508
- const pairs = {};
509
- const clean = this.stripAnsi(line);
510
- const kvRegex = /(\w+)=('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|\S+)/g;
511
- let match;
512
- while ((match = kvRegex.exec(clean)) !== null) {
513
- if (match[1] === "Z" || match[1] === "m") continue;
514
- pairs[match[1]] = this.parseValue(match[2]);
515
- }
516
- return pairs;
517
- }
518
- parseValue(value) {
519
- if (value.match(/^\d+$/)) return parseInt(value);
520
- if (value.match(/^\d+\.\d+$/)) return parseFloat(value);
521
- if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
522
- if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
523
- return value;
524
- }
525
- parseTimestamp(value) {
526
- if (!value) return null;
527
- if (typeof value === "number") return value;
528
- try {
529
- return new Date(value).getTime();
530
- } catch {
531
- return null;
532
- }
533
- }
534
- detectComponent(action, kvPairs) {
535
- if (action.includes(".")) return action.split(".")[0];
536
- if (kvPairs.component) return kvPairs.component;
537
- if (kvPairs.service) return kvPairs.service;
538
- if (kvPairs.module) return kvPairs.module;
539
- if (kvPairs.worker) return kvPairs.worker;
540
- return action || "unknown";
541
- }
542
- detectOperation(action, kvPairs) {
543
- if (action.includes(".")) return action.split(".").slice(1).join(".");
544
- if (kvPairs.operation) return kvPairs.operation;
545
- if (kvPairs.method) return kvPairs.method;
546
- if (kvPairs.action) return kvPairs.action;
547
- return action || "activity";
548
- }
549
- extractSessionIdentifier(activity) {
550
- return activity.session_id || activity.run_id || activity.request_id || activity.trace_id || activity.sweep_id || activity.transaction_id || "default";
551
- }
552
- detectAgentIdentifier(activity, filename, filePath) {
595
+ detectAgentIdentifier(activity, _filename, filePath) {
553
596
  if (activity.agent_id) {
554
597
  const agentId = activity.agent_id;
555
598
  if (agentId.startsWith("vault-")) return agentId;
@@ -593,14 +636,6 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
593
636
  const operation = activity.operation !== "activity" ? `: ${activity.operation}` : "";
594
637
  return `${component}${operation} (${sessionId})`;
595
638
  }
596
- detectTrigger(activity) {
597
- var _a, _b;
598
- if (activity.trigger) return activity.trigger;
599
- if (activity.method && activity.url) return "api-call";
600
- if ((_a = activity.operation) == null ? void 0 : _a.includes("start")) return "startup";
601
- if ((_b = activity.operation) == null ? void 0 : _b.includes("invoke")) return "invocation";
602
- return "event";
603
- }
604
639
  addActivityNode(session, activity) {
605
640
  const nodeId = `${activity.component}-${activity.operation}`;
606
641
  if (session.nodes[nodeId]) {
@@ -618,7 +653,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
618
653
  id: nodeId,
619
654
  type: activity.component,
620
655
  name: `${activity.component}: ${activity.operation}`,
621
- status: this.getUniversalNodeStatus(activity),
656
+ status: getUniversalNodeStatus(activity),
622
657
  startTime: activity.timestamp,
623
658
  endTime: activity.timestamp,
624
659
  children: [],
@@ -629,17 +664,9 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
629
664
  session.rootNodeId = nodeId;
630
665
  }
631
666
  }
632
- getUniversalNodeStatus(activity) {
633
- var _a, _b;
634
- if (activity.level === "error" || activity.level === "fatal") return "failed";
635
- if (activity.level === "warn" || activity.level === "warning") return "warning";
636
- if ((_a = activity.operation) == null ? void 0 : _a.match(/start|begin|init/i)) return "running";
637
- if ((_b = activity.operation) == null ? void 0 : _b.match(/complete|finish|end|done/i)) return "completed";
638
- return "completed";
639
- }
640
667
  /** Parse OpenClaw tslog-format log files with session run results. */
641
668
  loadOpenClawLogFile(content, filename, filePath, stats) {
642
- var _a, _b, _c, _d;
669
+ var _a, _b, _c, _d, _e, _f;
643
670
  const lines = content.split("\n").filter((l) => l.trim());
644
671
  const sessions = /* @__PURE__ */ new Map();
645
672
  for (const line of lines) {
@@ -651,13 +678,13 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
651
678
  if ((inner == null ? void 0 : inner.payloads) && ((_a = inner == null ? void 0 : inner.meta) == null ? void 0 : _a.agentMeta)) {
652
679
  const agentMeta = inner.meta.agentMeta;
653
680
  const sessionId = agentMeta.sessionId || "unknown";
654
- const agentName = this.openClawSessionIdToAgent(sessionId);
681
+ const agentName = openClawSessionIdToAgent(sessionId);
655
682
  const timestamp = parsed.time ? new Date(parsed.time).getTime() : ((_b = parsed._meta) == null ? void 0 : _b.date) ? new Date(parsed._meta.date).getTime() : stats.mtime.getTime();
656
683
  const texts = (inner.payloads || []).map((p) => p.text || "").filter(Boolean);
657
684
  if (!sessions.has(sessionId)) {
658
685
  sessions.set(sessionId, { entries: [] });
659
686
  }
660
- sessions.get(sessionId).entries.push({
687
+ (_c = sessions.get(sessionId)) == null ? void 0 : _c.entries.push({
661
688
  text: texts.join("\n"),
662
689
  timestamp,
663
690
  sessionId,
@@ -672,16 +699,16 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
672
699
  } catch {
673
700
  }
674
701
  }
675
- if (parsed.payloads && ((_c = parsed.meta) == null ? void 0 : _c.agentMeta)) {
702
+ if (parsed.payloads && ((_d = parsed.meta) == null ? void 0 : _d.agentMeta)) {
676
703
  const agentMeta = parsed.meta.agentMeta;
677
704
  const sessionId = agentMeta.sessionId || "unknown";
678
- const agentName = this.openClawSessionIdToAgent(sessionId);
679
- const timestamp = parsed.time ? new Date(parsed.time).getTime() : ((_d = parsed._meta) == null ? void 0 : _d.date) ? new Date(parsed._meta.date).getTime() : stats.mtime.getTime();
705
+ const agentName = openClawSessionIdToAgent(sessionId);
706
+ const timestamp = parsed.time ? new Date(parsed.time).getTime() : ((_e = parsed._meta) == null ? void 0 : _e.date) ? new Date(parsed._meta.date).getTime() : stats.mtime.getTime();
680
707
  const texts = (parsed.payloads || []).map((p) => p.text || "").filter(Boolean);
681
708
  if (!sessions.has(sessionId)) {
682
709
  sessions.set(sessionId, { entries: [] });
683
710
  }
684
- sessions.get(sessionId).entries.push({
711
+ (_f = sessions.get(sessionId)) == null ? void 0 : _f.entries.push({
685
712
  text: texts.join("\n"),
686
713
  timestamp,
687
714
  sessionId,
@@ -760,7 +787,11 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
760
787
  content: e.text,
761
788
  model: e.model,
762
789
  provider: e.provider,
763
- tokens: { input: e.usage.input || 0, output: e.usage.output || 0, total: e.usage.total || 0 },
790
+ tokens: {
791
+ input: e.usage.input || 0,
792
+ output: e.usage.output || 0,
793
+ total: e.usage.total || 0
794
+ },
764
795
  duration: e.durationMs,
765
796
  id: `entry-${idx}`
766
797
  }));
@@ -781,7 +812,12 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
781
812
  sourceType: "session",
782
813
  sourceDir: path.dirname(filePath),
783
814
  sessionEvents,
784
- tokenUsage: { input: totalInput, output: totalOutput, total: totalTokens || totalInput + totalOutput, cost: 0 },
815
+ tokenUsage: {
816
+ input: totalInput,
817
+ output: totalOutput,
818
+ total: totalTokens || totalInput + totalOutput,
819
+ cost: 0
820
+ },
785
821
  metadata: {
786
822
  provider: firstEntry.provider,
787
823
  model: firstEntry.model,
@@ -795,16 +831,6 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
795
831
  }
796
832
  return traceIndex > 0;
797
833
  }
798
- /** Map OpenClaw sessionId prefix to agent name. */
799
- openClawSessionIdToAgent(sessionId) {
800
- if (sessionId.startsWith("janitor-")) return "vault-janitor";
801
- if (sessionId.startsWith("curator-")) return "vault-curator";
802
- if (sessionId.startsWith("distiller-")) return "vault-distiller";
803
- if (sessionId.startsWith("main-")) return "main";
804
- const firstSegment = sessionId.split("-")[0];
805
- if (firstSegment) return firstSegment;
806
- return "openclaw";
807
- }
808
834
  loadTraceFile(filePath) {
809
835
  try {
810
836
  const content = fs.readFileSync(filePath, "utf8");
@@ -943,9 +969,6 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
943
969
  } else {
944
970
  agentId = parentDir;
945
971
  }
946
- if (filePath.includes(".openclaw/") && !agentId.startsWith("openclaw-")) {
947
- agentId = `openclaw-${agentId}`;
948
- }
949
972
  if (filePath.includes(".alfred/") || filePath.includes("alfred")) {
950
973
  if (!agentId.startsWith("alfred-")) {
951
974
  agentId = `alfred-${agentId}`;
@@ -962,10 +985,12 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
962
985
  if (ts > lastTimestamp) lastTimestamp = ts;
963
986
  }
964
987
  }
965
- const firstMessage = rawEvents.find((e) => {
966
- var _a2;
967
- return e.type === "message" && ((_a2 = e.message) == null ? void 0 : _a2.role) === "user";
968
- });
988
+ const firstMessage = rawEvents.find(
989
+ (e) => {
990
+ var _a2;
991
+ return e.type === "message" && ((_a2 = e.message) == null ? void 0 : _a2.role) === "user";
992
+ }
993
+ );
969
994
  const userPrompt = ((_d = (_c = (_b = firstMessage == null ? void 0 : firstMessage.message) == null ? void 0 : _b.content) == null ? void 0 : _c[0]) == null ? void 0 : _d.text) || "";
970
995
  const cronMatch = userPrompt.match(/\[cron:(\S+)\s+([^\]]+)\]/);
971
996
  const triggerName = cronMatch ? cronMatch[2] : "";
@@ -1039,7 +1064,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1039
1064
  nodes.set(spawnId, {
1040
1065
  id: spawnId,
1041
1066
  type: "subagent",
1042
- name: "Subagent: " + (((_f = evt.data) == null ? void 0 : _f.sessionId) || "").slice(0, 12),
1067
+ name: `Subagent: ${(((_f = evt.data) == null ? void 0 : _f.sessionId) || "").slice(0, 12)}`,
1043
1068
  startTime: evtTs,
1044
1069
  endTime: evtTs,
1045
1070
  status: "completed",
@@ -1163,7 +1188,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1163
1188
  id: evt.id,
1164
1189
  parentId: toolCallId
1165
1190
  });
1166
- for (const [nodeId, node] of nodes) {
1191
+ for (const [_nodeId, node] of nodes) {
1167
1192
  if (node.type === "tool" && ((_k = node.metadata) == null ? void 0 : _k.toolCallId) === toolCallId) {
1168
1193
  node.endTime = evtTs;
1169
1194
  node.status = hasError ? "failed" : "completed";
@@ -1331,7 +1356,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1331
1356
  if (filePath.startsWith(dir)) {
1332
1357
  const dirParts = dir.split(path.sep).filter(Boolean);
1333
1358
  const dirSuffix = dirParts.slice(-2).join("/");
1334
- return path.relative(dir, filePath).replace(/\\/g, "/") + "@" + dirSuffix;
1359
+ return `${path.relative(dir, filePath).replace(/\\/g, "/")}@${dirSuffix}`;
1335
1360
  }
1336
1361
  }
1337
1362
  return filePath;
@@ -1400,7 +1425,9 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1400
1425
  });
1401
1426
  this.watchers.push(watcher);
1402
1427
  }
1403
- console.log(`Watching ${this.allWatchDirs.length} directories recursively for JSON/JSONL/LOG/TRACE files`);
1428
+ console.log(
1429
+ `Watching ${this.allWatchDirs.length} directories recursively for JSON/JSONL/LOG/TRACE files`
1430
+ );
1404
1431
  }
1405
1432
  getAllTraces() {
1406
1433
  return Array.from(this.traces.values()).sort((a, b) => {
@@ -1460,7 +1487,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1460
1487
 
1461
1488
  // src/server.ts
1462
1489
  var import_meta = {};
1463
- var __filename = (0, import_url.fileURLToPath)(import_meta.url);
1490
+ var __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
1464
1491
  var __dirname = path2.dirname(__filename);
1465
1492
  function serializeTrace(trace) {
1466
1493
  if (!trace) return trace;
@@ -1482,23 +1509,41 @@ var DashboardServer = class {
1482
1509
  dataDirs: config.dataDirs
1483
1510
  });
1484
1511
  this.stats = new AgentStats();
1512
+ this.knowledgeStore = (0, import_agentflow_core3.createKnowledgeStore)({
1513
+ baseDir: path2.join(config.tracesDir, "..", ".agentflow", "knowledge")
1514
+ });
1485
1515
  this.setupExpress();
1486
1516
  this.setupWebSocket();
1487
1517
  this.setupTraceWatcher();
1518
+ let knowledgeCount = 0;
1488
1519
  for (const trace of this.watcher.getAllTraces()) {
1489
1520
  this.stats.processTrace(trace);
1521
+ if (this.isGraphTrace(trace)) {
1522
+ try {
1523
+ const graph = (0, import_agentflow_core3.loadGraph)(serializeTrace(trace));
1524
+ const event = (0, import_agentflow_core3.createExecutionEvent)(graph);
1525
+ this.knowledgeStore.append(event);
1526
+ knowledgeCount++;
1527
+ } catch {
1528
+ }
1529
+ }
1490
1530
  }
1491
1531
  console.log(`Processed ${this.watcher.getTraceCount()} existing traces for stats`);
1532
+ console.log(`Persisted ${knowledgeCount} graph traces to knowledge store`);
1492
1533
  }
1493
1534
  app = (0, import_express.default)();
1494
- server = (0, import_http.createServer)(this.app);
1535
+ server = (0, import_node_http.createServer)(this.app);
1495
1536
  wss = new import_ws.WebSocketServer({ server: this.server });
1496
1537
  watcher;
1497
1538
  stats;
1498
- processHealthCache = { result: null, ts: 0 };
1539
+ processHealthCache = {
1540
+ result: null,
1541
+ ts: 0
1542
+ };
1543
+ knowledgeStore;
1499
1544
  setupExpress() {
1500
1545
  if (this.config.enableCors) {
1501
- this.app.use((req, res, next) => {
1546
+ this.app.use((_req, res, next) => {
1502
1547
  res.header("Access-Control-Allow-Origin", "*");
1503
1548
  res.header(
1504
1549
  "Access-Control-Allow-Headers",
@@ -1511,11 +1556,11 @@ var DashboardServer = class {
1511
1556
  if (fs2.existsSync(publicDir)) {
1512
1557
  this.app.use(import_express.default.static(publicDir));
1513
1558
  }
1514
- this.app.get("/api/traces", (req, res) => {
1559
+ this.app.get("/api/traces", (_req, res) => {
1515
1560
  try {
1516
1561
  const traces = this.watcher.getAllTraces().map(serializeTrace);
1517
1562
  res.json(traces);
1518
- } catch (error) {
1563
+ } catch (_error) {
1519
1564
  res.status(500).json({ error: "Failed to load traces" });
1520
1565
  }
1521
1566
  });
@@ -1526,7 +1571,7 @@ var DashboardServer = class {
1526
1571
  return res.status(404).json({ error: "Trace not found" });
1527
1572
  }
1528
1573
  res.json(serializeTrace(trace));
1529
- } catch (error) {
1574
+ } catch (_error) {
1530
1575
  res.status(500).json({ error: "Failed to load trace" });
1531
1576
  }
1532
1577
  });
@@ -1541,30 +1586,30 @@ var DashboardServer = class {
1541
1586
  tokenUsage: trace.tokenUsage || null,
1542
1587
  sourceType: trace.sourceType || "trace"
1543
1588
  });
1544
- } catch (error) {
1589
+ } catch (_error) {
1545
1590
  res.status(500).json({ error: "Failed to load trace events" });
1546
1591
  }
1547
1592
  });
1548
- this.app.get("/api/agents", (req, res) => {
1593
+ this.app.get("/api/agents", (_req, res) => {
1549
1594
  try {
1550
1595
  const agents = this.stats.getAgentsList();
1551
1596
  res.json(agents);
1552
- } catch (error) {
1597
+ } catch (_error) {
1553
1598
  res.status(500).json({ error: "Failed to load agents" });
1554
1599
  }
1555
1600
  });
1556
- this.app.get("/api/stats", (req, res) => {
1601
+ this.app.get("/api/stats", (_req, res) => {
1557
1602
  try {
1558
1603
  const globalStats = this.stats.getGlobalStats();
1559
1604
  res.json(globalStats);
1560
- } catch (error) {
1605
+ } catch (_error) {
1561
1606
  res.status(500).json({ error: "Failed to load statistics" });
1562
1607
  }
1563
1608
  });
1564
1609
  this.app.get("/api/agents/:agentId/timeline", (req, res) => {
1565
1610
  try {
1566
1611
  const agentId = req.params.agentId;
1567
- const limit = Math.min(parseInt(req.query.limit) || 50, 200);
1612
+ const limit = Math.min(parseInt(req.query.limit, 10) || 50, 200);
1568
1613
  const rawTraces = this.watcher.getTracesByAgent(agentId);
1569
1614
  if (rawTraces.length === 0) {
1570
1615
  return res.status(404).json({ error: "No traces for agent" });
@@ -1594,7 +1639,9 @@ var DashboardServer = class {
1594
1639
  });
1595
1640
  }
1596
1641
  } else {
1597
- const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1642
+ const sorted = Object.values(nodes).sort(
1643
+ (a, b) => (a.startTime || 0) - (b.startTime || 0)
1644
+ );
1598
1645
  for (const node of sorted) {
1599
1646
  activities.push({
1600
1647
  id: node.id,
@@ -1633,95 +1680,69 @@ var DashboardServer = class {
1633
1680
  this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1634
1681
  try {
1635
1682
  const agentId = req.params.agentId;
1636
- const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
1637
- if (traces.length === 0) {
1683
+ const allTraces = this.watcher.getTracesByAgent(agentId);
1684
+ if (allTraces.length === 0) {
1638
1685
  return res.status(404).json({ error: "No traces for agent" });
1639
1686
  }
1640
- const activityCounts = /* @__PURE__ */ new Map();
1641
- const transitionCounts = /* @__PURE__ */ new Map();
1642
- const activityDurations = /* @__PURE__ */ new Map();
1643
- const activityStatuses = /* @__PURE__ */ new Map();
1644
- let totalTraces = 0;
1645
- for (const trace of traces) {
1646
- totalTraces++;
1647
- const activities = [];
1648
- if (trace.sessionEvents && trace.sessionEvents.length > 0) {
1649
- for (const evt of trace.sessionEvents) {
1650
- const name = evt.toolName || evt.name || evt.type;
1651
- if (!name) continue;
1652
- activities.push({
1653
- name,
1654
- type: evt.type,
1655
- status: evt.toolError ? "failed" : "completed",
1656
- duration: evt.duration || 0
1657
- });
1658
- }
1659
- } else {
1660
- const nodes2 = trace.nodes || {};
1661
- const sorted = Object.values(nodes2).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1662
- for (const node of sorted) {
1663
- activities.push({
1664
- name: node.name || node.type || node.id,
1665
- type: node.type || "unknown",
1666
- status: node.status || "completed",
1667
- duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
1668
- });
1669
- }
1670
- }
1671
- const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
1672
- for (let i = 0; i < seq.length; i++) {
1673
- const act = seq[i];
1674
- activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
1675
- if (i < seq.length - 1) {
1676
- const key = act + " \u2192 " + seq[i + 1];
1677
- transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
1678
- }
1679
- }
1680
- for (const act of activities) {
1681
- if (act.duration > 0) {
1682
- const durs = activityDurations.get(act.name) || [];
1683
- durs.push(act.duration);
1684
- activityDurations.set(act.name, durs);
1685
- }
1686
- const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
1687
- if (act.status === "failed") st.fail++;
1688
- else st.ok++;
1689
- activityStatuses.set(act.name, st);
1690
- }
1687
+ const graphs = this.getGraphTraces(agentId);
1688
+ if (graphs.length > 0) {
1689
+ return res.json(this.buildProcessGraphFromCore(agentId, graphs));
1691
1690
  }
1692
- const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
1693
- const durs = activityDurations.get(name) || [];
1694
- const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
1695
- const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
1696
- return {
1697
- id: name,
1698
- label: name,
1699
- count,
1700
- frequency: count / totalTraces,
1701
- avgDuration,
1702
- failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
1703
- isVirtual: name === "[START]" || name === "[END]"
1704
- };
1705
- });
1706
- const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
1707
- const [source, target] = key.split(" \u2192 ");
1708
- return { source, target, count, frequency: count / totalTraces };
1709
- });
1710
- const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
1711
- const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
1712
- res.json({
1713
- agentId,
1714
- totalTraces,
1715
- nodes,
1716
- edges,
1717
- maxEdgeCount,
1718
- maxNodeCount
1719
- });
1691
+ return res.json(this.buildProcessGraphLegacy(agentId, allTraces));
1720
1692
  } catch (error) {
1721
1693
  console.error("Process graph error:", error);
1722
1694
  res.status(500).json({ error: "Failed to build process graph" });
1723
1695
  }
1724
1696
  });
1697
+ this.app.get("/api/agents/:agentId/variants", (req, res) => {
1698
+ try {
1699
+ const agentId = req.params.agentId;
1700
+ const graphs = this.getGraphTraces(agentId);
1701
+ if (graphs.length === 0) {
1702
+ return res.json({ agentId, totalTraces: 0, variants: [] });
1703
+ }
1704
+ const variants = (0, import_agentflow_core3.findVariants)(graphs).map((v) => ({
1705
+ pathSignature: v.pathSignature,
1706
+ count: v.count,
1707
+ percentage: v.percentage
1708
+ }));
1709
+ res.json({ agentId, totalTraces: graphs.length, variants });
1710
+ } catch (error) {
1711
+ console.error("Variants error:", error);
1712
+ res.status(500).json({ error: "Failed to compute variants" });
1713
+ }
1714
+ });
1715
+ this.app.get("/api/agents/:agentId/bottlenecks", (req, res) => {
1716
+ try {
1717
+ const agentId = req.params.agentId;
1718
+ const graphs = this.getGraphTraces(agentId);
1719
+ if (graphs.length === 0) {
1720
+ return res.json({ agentId, bottlenecks: [] });
1721
+ }
1722
+ const bottlenecks = (0, import_agentflow_core3.getBottlenecks)(graphs).map((b) => ({
1723
+ nodeName: b.nodeName,
1724
+ nodeType: b.nodeType,
1725
+ occurrences: b.occurrences,
1726
+ durations: b.durations
1727
+ }));
1728
+ res.json({ agentId, bottlenecks });
1729
+ } catch (error) {
1730
+ console.error("Bottlenecks error:", error);
1731
+ res.status(500).json({ error: "Failed to compute bottlenecks" });
1732
+ }
1733
+ });
1734
+ this.app.get("/api/agents/:agentId/profile", (req, res) => {
1735
+ try {
1736
+ const profile = this.knowledgeStore.getAgentProfile(req.params.agentId);
1737
+ if (!profile) {
1738
+ return res.status(404).json({ error: "No profile for agent" });
1739
+ }
1740
+ res.json(profile);
1741
+ } catch (error) {
1742
+ console.error("Profile error:", error);
1743
+ res.status(500).json({ error: "Failed to load agent profile" });
1744
+ }
1745
+ });
1725
1746
  this.app.get("/api/stats/:agentId", (req, res) => {
1726
1747
  try {
1727
1748
  const agentStats = this.stats.getAgentStats(req.params.agentId);
@@ -1729,11 +1750,11 @@ var DashboardServer = class {
1729
1750
  return res.status(404).json({ error: "Agent not found" });
1730
1751
  }
1731
1752
  res.json(agentStats);
1732
- } catch (error) {
1753
+ } catch (_error) {
1733
1754
  res.status(500).json({ error: "Failed to load agent statistics" });
1734
1755
  }
1735
1756
  });
1736
- this.app.get("/api/process-health", (req, res) => {
1757
+ this.app.get("/api/process-health", (_req, res) => {
1737
1758
  try {
1738
1759
  const now = Date.now();
1739
1760
  if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
@@ -1778,9 +1799,11 @@ var DashboardServer = class {
1778
1799
  orphans: uniqueProcesses.filter((p) => {
1779
1800
  var _a;
1780
1801
  const alfredKnownPids = /* @__PURE__ */ new Set();
1781
- if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale) alfredKnownPids.add(alfredResult.pidFile.pid);
1802
+ if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale)
1803
+ alfredKnownPids.add(alfredResult.pidFile.pid);
1782
1804
  if (alfredResult.workers) {
1783
- if (alfredResult.workers.orchestratorPid) alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1805
+ if (alfredResult.workers.orchestratorPid)
1806
+ alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1784
1807
  for (const w of alfredResult.workers.workers) {
1785
1808
  if (w.pid) alfredKnownPids.add(w.pid);
1786
1809
  }
@@ -1799,11 +1822,22 @@ var DashboardServer = class {
1799
1822
  result.problems = [...alfredResult.problems || [], ...openclawProblems];
1800
1823
  this.processHealthCache = { result, ts: now };
1801
1824
  res.json(result);
1802
- } catch (error) {
1825
+ } catch (_error) {
1803
1826
  res.status(500).json({ error: "Failed to audit processes" });
1804
1827
  }
1805
1828
  });
1806
- this.app.get("*", (req, res) => {
1829
+ this.app.get("/health", (_req, res) => {
1830
+ res.json({
1831
+ status: "ok",
1832
+ uptime: process.uptime(),
1833
+ traceCount: this.watcher.getTraceCount(),
1834
+ agentCount: this.watcher.getAgentIds().length
1835
+ });
1836
+ });
1837
+ this.app.get("/ready", (_req, res) => {
1838
+ res.json({ status: "ready" });
1839
+ });
1840
+ this.app.get("*", (_req, res) => {
1807
1841
  const indexPath = path2.join(__dirname, "../public/index.html");
1808
1842
  if (fs2.existsSync(indexPath)) {
1809
1843
  res.sendFile(indexPath);
@@ -1832,6 +1866,189 @@ var DashboardServer = class {
1832
1866
  });
1833
1867
  });
1834
1868
  }
1869
+ /**
1870
+ * Filter an agent's traces to valid ExecutionGraphs and convert via loadGraph().
1871
+ * Returns only traces with proper nodes (Map or non-empty object), skipping session-only traces.
1872
+ */
1873
+ getGraphTraces(agentId) {
1874
+ const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
1875
+ const graphs = [];
1876
+ for (const trace of traces) {
1877
+ try {
1878
+ if (trace.sourceType === "session" || trace.sourceType === "log") continue;
1879
+ if (!trace.rootNodeId && !trace.rootId) continue;
1880
+ const nodes = trace.nodes;
1881
+ if (!nodes || typeof nodes === "object" && Object.keys(nodes).length === 0) continue;
1882
+ const nodeValues = Object.values(nodes);
1883
+ if (nodeValues.some((n) => n.type === "log-file" || n.type === "log-entry")) continue;
1884
+ graphs.push((0, import_agentflow_core3.loadGraph)(trace));
1885
+ } catch {
1886
+ }
1887
+ }
1888
+ return graphs;
1889
+ }
1890
+ /**
1891
+ * Build process graph response using core APIs (discoverProcess + getBottlenecks).
1892
+ * Maps core output to the frontend's expected shape with virtual START/END nodes.
1893
+ */
1894
+ buildProcessGraphFromCore(agentId, graphs) {
1895
+ const model = (0, import_agentflow_core3.discoverProcess)(graphs);
1896
+ const bottleneckList = (0, import_agentflow_core3.getBottlenecks)(graphs);
1897
+ const bottleneckMap = /* @__PURE__ */ new Map();
1898
+ for (const b of bottleneckList) {
1899
+ const key = `${b.nodeType}:${b.nodeName}`;
1900
+ bottleneckMap.set(key, {
1901
+ avgDuration: b.durations.median,
1902
+ failRate: 0,
1903
+ // Not directly available from bottleneck data
1904
+ p95: b.durations.p95
1905
+ });
1906
+ bottleneckMap.set(b.nodeName, {
1907
+ avgDuration: b.durations.median,
1908
+ failRate: 0,
1909
+ p95: b.durations.p95
1910
+ });
1911
+ }
1912
+ const nodes = [];
1913
+ const stepCounts = /* @__PURE__ */ new Map();
1914
+ for (const t of model.transitions) {
1915
+ stepCounts.set(t.from, (stepCounts.get(t.from) ?? 0) + t.count);
1916
+ }
1917
+ for (const step of model.steps) {
1918
+ const count = stepCounts.get(step) ?? model.totalGraphs;
1919
+ const bn = bottleneckMap.get(step);
1920
+ const colonIdx = step.indexOf(":");
1921
+ const label = colonIdx >= 0 ? step.slice(colonIdx + 1) : step;
1922
+ nodes.push({
1923
+ id: step,
1924
+ label,
1925
+ count,
1926
+ frequency: count / model.totalGraphs,
1927
+ avgDuration: (bn == null ? void 0 : bn.avgDuration) ?? 0,
1928
+ failRate: (bn == null ? void 0 : bn.failRate) ?? 0,
1929
+ p95Duration: (bn == null ? void 0 : bn.p95) ?? 0,
1930
+ isVirtual: false
1931
+ });
1932
+ }
1933
+ const rootSteps = new Set(model.steps);
1934
+ const childSteps = new Set(model.transitions.map((t) => t.to));
1935
+ const leafSteps = new Set(model.steps);
1936
+ for (const t of model.transitions) {
1937
+ }
1938
+ nodes.push({ id: "[START]", label: "[START]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
1939
+ nodes.push({ id: "[END]", label: "[END]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
1940
+ const edges = model.transitions.map((t) => ({
1941
+ source: t.from,
1942
+ target: t.to,
1943
+ count: t.count,
1944
+ frequency: t.count / model.totalGraphs
1945
+ }));
1946
+ const targetSteps = new Set(model.transitions.map((t) => t.to));
1947
+ for (const step of model.steps) {
1948
+ if (!targetSteps.has(step)) {
1949
+ edges.push({ source: "[START]", target: step, count: model.totalGraphs, frequency: 1 });
1950
+ }
1951
+ }
1952
+ const sourceSteps = new Set(model.transitions.map((t) => t.from));
1953
+ for (const step of model.steps) {
1954
+ if (!sourceSteps.has(step)) {
1955
+ edges.push({ source: step, target: "[END]", count: model.totalGraphs, frequency: 1 });
1956
+ }
1957
+ }
1958
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
1959
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
1960
+ return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
1961
+ }
1962
+ /**
1963
+ * Legacy process graph computation for session-based traces.
1964
+ * Preserved for backward compatibility with JSONL/LOG traces.
1965
+ */
1966
+ buildProcessGraphLegacy(agentId, allTraces) {
1967
+ const traces = allTraces.map(serializeTrace);
1968
+ const activityCounts = /* @__PURE__ */ new Map();
1969
+ const transitionCounts = /* @__PURE__ */ new Map();
1970
+ const activityDurations = /* @__PURE__ */ new Map();
1971
+ const activityStatuses = /* @__PURE__ */ new Map();
1972
+ let totalTraces = 0;
1973
+ for (const trace of traces) {
1974
+ totalTraces++;
1975
+ const activities = [];
1976
+ if (trace.sessionEvents && trace.sessionEvents.length > 0) {
1977
+ for (const evt of trace.sessionEvents) {
1978
+ const name = evt.toolName || evt.name || evt.type;
1979
+ if (!name) continue;
1980
+ activities.push({
1981
+ name,
1982
+ type: evt.type,
1983
+ status: evt.toolError ? "failed" : "completed",
1984
+ duration: evt.duration || 0
1985
+ });
1986
+ }
1987
+ } else {
1988
+ const nodes2 = trace.nodes || {};
1989
+ const sorted = Object.values(nodes2).sort(
1990
+ (a, b) => (a.startTime || 0) - (b.startTime || 0)
1991
+ );
1992
+ for (const node of sorted) {
1993
+ activities.push({
1994
+ name: node.name || node.type || node.id,
1995
+ type: node.type || "unknown",
1996
+ status: node.status || "completed",
1997
+ duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
1998
+ });
1999
+ }
2000
+ }
2001
+ const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
2002
+ for (let i = 0; i < seq.length; i++) {
2003
+ const act = seq[i];
2004
+ activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
2005
+ if (i < seq.length - 1) {
2006
+ const key = `${act} \u2192 ${seq[i + 1]}`;
2007
+ transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
2008
+ }
2009
+ }
2010
+ for (const act of activities) {
2011
+ if (act.duration > 0) {
2012
+ const durs = activityDurations.get(act.name) || [];
2013
+ durs.push(act.duration);
2014
+ activityDurations.set(act.name, durs);
2015
+ }
2016
+ const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
2017
+ if (act.status === "failed") st.fail++;
2018
+ else st.ok++;
2019
+ activityStatuses.set(act.name, st);
2020
+ }
2021
+ }
2022
+ const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
2023
+ const durs = activityDurations.get(name) || [];
2024
+ const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
2025
+ const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
2026
+ return {
2027
+ id: name,
2028
+ label: name,
2029
+ count,
2030
+ frequency: count / totalTraces,
2031
+ avgDuration,
2032
+ failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
2033
+ p95Duration: 0,
2034
+ isVirtual: name === "[START]" || name === "[END]"
2035
+ };
2036
+ });
2037
+ const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
2038
+ const [source, target] = key.split(" \u2192 ");
2039
+ return { source, target, count, frequency: count / totalTraces };
2040
+ });
2041
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
2042
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
2043
+ return { agentId, totalTraces, nodes, edges, maxEdgeCount, maxNodeCount };
2044
+ }
2045
+ /** Check if a trace is a proper ExecutionGraph (not a synthetic session-based trace). */
2046
+ isGraphTrace(trace) {
2047
+ if (trace.sourceType === "session" || trace.sourceType === "log") return false;
2048
+ if (!trace.rootNodeId && !trace.rootId) return false;
2049
+ const nodes = trace.nodes instanceof Map ? Object.fromEntries(trace.nodes) : trace.nodes;
2050
+ return nodes && typeof nodes === "object" && Object.keys(nodes).length > 0;
2051
+ }
1835
2052
  setupTraceWatcher() {
1836
2053
  this.watcher.on("trace-added", (trace) => {
1837
2054
  this.stats.processTrace(trace);
@@ -1839,6 +2056,14 @@ var DashboardServer = class {
1839
2056
  type: "trace-added",
1840
2057
  data: serializeTrace(trace)
1841
2058
  });
2059
+ if (this.isGraphTrace(trace)) {
2060
+ try {
2061
+ const graph = (0, import_agentflow_core3.loadGraph)(serializeTrace(trace));
2062
+ const event = (0, import_agentflow_core3.createExecutionEvent)(graph);
2063
+ this.knowledgeStore.append(event);
2064
+ } catch {
2065
+ }
2066
+ }
1842
2067
  });
1843
2068
  this.watcher.on("trace-updated", (trace) => {
1844
2069
  this.stats.processTrace(trace);
@@ -1931,7 +2156,8 @@ function printBanner(config, traceCount, stats) {
1931
2156
 
1932
2157
  Tabs: \u{1F3AF} Graph \xB7 \u23F1\uFE0F Timeline \xB7 \u{1F4CA} Metrics \xB7 \u{1F6E0}\uFE0F Process Health \xB7 \u26A0\uFE0F Errors
1933
2158
 
1934
- Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? "\n Data dirs: " + config.dataDirs.join("\n ") : ""}
2159
+ Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? `
2160
+ Data dirs: ${config.dataDirs.join("\n ")}` : ""}
1935
2161
  Loaded: ${traceCount} traces \xB7 ${stats.totalAgents} agents \xB7 ${stats.totalExecutions} executions
1936
2162
  Success: ${stats.globalSuccessRate.toFixed(1)}%${stats.activeAgents > 0 ? ` \xB7 ${stats.activeAgents} active now` : ""}
1937
2163
  CORS: ${config.enableCors ? "enabled" : "disabled"}
@@ -1953,7 +2179,7 @@ async function startDashboard() {
1953
2179
  switch (args[i]) {
1954
2180
  case "--port":
1955
2181
  case "-p":
1956
- config.port = parseInt(args[++i]) || 3e3;
2182
+ config.port = parseInt(args[++i], 10) || 3e3;
1957
2183
  break;
1958
2184
  case "--traces":
1959
2185
  case "-t":