agentflow-dashboard 0.4.1 → 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/index.cjs CHANGED
@@ -37,13 +37,13 @@ __export(index_exports, {
37
37
  module.exports = __toCommonJS(index_exports);
38
38
 
39
39
  // src/server.ts
40
- var import_express = __toESM(require("express"), 1);
41
40
  var fs3 = __toESM(require("fs"), 1);
42
- var import_http = require("http");
41
+ var import_node_http = require("http");
43
42
  var path3 = __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) => {
@@ -1501,7 +1528,8 @@ function printBanner(config, traceCount, stats) {
1501
1528
 
1502
1529
  Tabs: \u{1F3AF} Graph \xB7 \u23F1\uFE0F Timeline \xB7 \u{1F4CA} Metrics \xB7 \u{1F6E0}\uFE0F Process Health \xB7 \u26A0\uFE0F Errors
1503
1530
 
1504
- Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? "\n Data dirs: " + config.dataDirs.join("\n ") : ""}
1531
+ Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? `
1532
+ Data dirs: ${config.dataDirs.join("\n ")}` : ""}
1505
1533
  Loaded: ${traceCount} traces \xB7 ${stats.totalAgents} agents \xB7 ${stats.totalExecutions} executions
1506
1534
  Success: ${stats.globalSuccessRate.toFixed(1)}%${stats.activeAgents > 0 ? ` \xB7 ${stats.activeAgents} active now` : ""}
1507
1535
  CORS: ${config.enableCors ? "enabled" : "disabled"}
@@ -1523,7 +1551,7 @@ async function startDashboard() {
1523
1551
  switch (args[i]) {
1524
1552
  case "--port":
1525
1553
  case "-p":
1526
- config.port = parseInt(args[++i]) || 3e3;
1554
+ config.port = parseInt(args[++i], 10) || 3e3;
1527
1555
  break;
1528
1556
  case "--traces":
1529
1557
  case "-t":
@@ -1605,7 +1633,7 @@ Tabs:
1605
1633
 
1606
1634
  // src/server.ts
1607
1635
  var import_meta = {};
1608
- var __filename = (0, import_url.fileURLToPath)(import_meta.url);
1636
+ var __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
1609
1637
  var __dirname = path3.dirname(__filename);
1610
1638
  function serializeTrace(trace) {
1611
1639
  if (!trace) return trace;
@@ -1627,23 +1655,41 @@ var DashboardServer = class {
1627
1655
  dataDirs: config.dataDirs
1628
1656
  });
1629
1657
  this.stats = new AgentStats();
1658
+ this.knowledgeStore = (0, import_agentflow_core3.createKnowledgeStore)({
1659
+ baseDir: path3.join(config.tracesDir, "..", ".agentflow", "knowledge")
1660
+ });
1630
1661
  this.setupExpress();
1631
1662
  this.setupWebSocket();
1632
1663
  this.setupTraceWatcher();
1664
+ let knowledgeCount = 0;
1633
1665
  for (const trace of this.watcher.getAllTraces()) {
1634
1666
  this.stats.processTrace(trace);
1667
+ if (this.isGraphTrace(trace)) {
1668
+ try {
1669
+ const graph = (0, import_agentflow_core3.loadGraph)(serializeTrace(trace));
1670
+ const event = (0, import_agentflow_core3.createExecutionEvent)(graph);
1671
+ this.knowledgeStore.append(event);
1672
+ knowledgeCount++;
1673
+ } catch {
1674
+ }
1675
+ }
1635
1676
  }
1636
1677
  console.log(`Processed ${this.watcher.getTraceCount()} existing traces for stats`);
1678
+ console.log(`Persisted ${knowledgeCount} graph traces to knowledge store`);
1637
1679
  }
1638
1680
  app = (0, import_express.default)();
1639
- server = (0, import_http.createServer)(this.app);
1681
+ server = (0, import_node_http.createServer)(this.app);
1640
1682
  wss = new import_ws.WebSocketServer({ server: this.server });
1641
1683
  watcher;
1642
1684
  stats;
1643
- processHealthCache = { result: null, ts: 0 };
1685
+ processHealthCache = {
1686
+ result: null,
1687
+ ts: 0
1688
+ };
1689
+ knowledgeStore;
1644
1690
  setupExpress() {
1645
1691
  if (this.config.enableCors) {
1646
- this.app.use((req, res, next) => {
1692
+ this.app.use((_req, res, next) => {
1647
1693
  res.header("Access-Control-Allow-Origin", "*");
1648
1694
  res.header(
1649
1695
  "Access-Control-Allow-Headers",
@@ -1656,11 +1702,11 @@ var DashboardServer = class {
1656
1702
  if (fs3.existsSync(publicDir)) {
1657
1703
  this.app.use(import_express.default.static(publicDir));
1658
1704
  }
1659
- this.app.get("/api/traces", (req, res) => {
1705
+ this.app.get("/api/traces", (_req, res) => {
1660
1706
  try {
1661
1707
  const traces = this.watcher.getAllTraces().map(serializeTrace);
1662
1708
  res.json(traces);
1663
- } catch (error) {
1709
+ } catch (_error) {
1664
1710
  res.status(500).json({ error: "Failed to load traces" });
1665
1711
  }
1666
1712
  });
@@ -1671,7 +1717,7 @@ var DashboardServer = class {
1671
1717
  return res.status(404).json({ error: "Trace not found" });
1672
1718
  }
1673
1719
  res.json(serializeTrace(trace));
1674
- } catch (error) {
1720
+ } catch (_error) {
1675
1721
  res.status(500).json({ error: "Failed to load trace" });
1676
1722
  }
1677
1723
  });
@@ -1686,118 +1732,163 @@ var DashboardServer = class {
1686
1732
  tokenUsage: trace.tokenUsage || null,
1687
1733
  sourceType: trace.sourceType || "trace"
1688
1734
  });
1689
- } catch (error) {
1735
+ } catch (_error) {
1690
1736
  res.status(500).json({ error: "Failed to load trace events" });
1691
1737
  }
1692
1738
  });
1693
- this.app.get("/api/agents", (req, res) => {
1739
+ this.app.get("/api/agents", (_req, res) => {
1694
1740
  try {
1695
1741
  const agents = this.stats.getAgentsList();
1696
1742
  res.json(agents);
1697
- } catch (error) {
1743
+ } catch (_error) {
1698
1744
  res.status(500).json({ error: "Failed to load agents" });
1699
1745
  }
1700
1746
  });
1701
- this.app.get("/api/stats", (req, res) => {
1747
+ this.app.get("/api/stats", (_req, res) => {
1702
1748
  try {
1703
1749
  const globalStats = this.stats.getGlobalStats();
1704
1750
  res.json(globalStats);
1705
- } catch (error) {
1751
+ } catch (_error) {
1706
1752
  res.status(500).json({ error: "Failed to load statistics" });
1707
1753
  }
1708
1754
  });
1709
- this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1755
+ this.app.get("/api/agents/:agentId/timeline", (req, res) => {
1710
1756
  try {
1711
1757
  const agentId = req.params.agentId;
1712
- const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
1713
- if (traces.length === 0) {
1758
+ const limit = Math.min(parseInt(req.query.limit, 10) || 50, 200);
1759
+ const rawTraces = this.watcher.getTracesByAgent(agentId);
1760
+ if (rawTraces.length === 0) {
1714
1761
  return res.status(404).json({ error: "No traces for agent" });
1715
1762
  }
1716
- const activityCounts = /* @__PURE__ */ new Map();
1717
- const transitionCounts = /* @__PURE__ */ new Map();
1718
- const activityDurations = /* @__PURE__ */ new Map();
1719
- const activityStatuses = /* @__PURE__ */ new Map();
1720
- let totalTraces = 0;
1721
- for (const trace of traces) {
1722
- totalTraces++;
1763
+ const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
1764
+ const executions = traces.map((t) => {
1765
+ const serialized = serializeTrace(t);
1766
+ const nodes = serialized.nodes || {};
1767
+ const events = serialized.sessionEvents || [];
1723
1768
  const activities = [];
1724
- if (trace.sessionEvents && trace.sessionEvents.length > 0) {
1725
- for (const evt of trace.sessionEvents) {
1726
- const name = evt.toolName || evt.name || evt.type;
1727
- if (!name) continue;
1769
+ if (events.length > 0) {
1770
+ for (let i = 0; i < events.length; i++) {
1771
+ const evt = events[i];
1772
+ if (evt.type === "system" || evt.type === "model_change") continue;
1773
+ const dur = evt.duration || 0;
1774
+ const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
1775
+ const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
1776
+ const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
1728
1777
  activities.push({
1729
- name,
1778
+ id: evt.id || `evt-${i}`,
1779
+ name: evt.toolName || evt.name || evt.type,
1730
1780
  type: evt.type,
1731
1781
  status: evt.toolError ? "failed" : "completed",
1732
- duration: evt.duration || 0
1782
+ startTime: startTs,
1783
+ endTime: endTs,
1784
+ parentId: evt.parentId
1733
1785
  });
1734
1786
  }
1735
1787
  } else {
1736
- const nodes2 = trace.nodes || {};
1737
- const sorted = Object.values(nodes2).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1788
+ const sorted = Object.values(nodes).sort(
1789
+ (a, b) => (a.startTime || 0) - (b.startTime || 0)
1790
+ );
1738
1791
  for (const node of sorted) {
1739
1792
  activities.push({
1793
+ id: node.id,
1740
1794
  name: node.name || node.type || node.id,
1741
1795
  type: node.type || "unknown",
1742
1796
  status: node.status || "completed",
1743
- duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
1797
+ startTime: node.startTime || t.startTime,
1798
+ endTime: node.endTime || node.startTime || t.startTime,
1799
+ parentId: node.parentId
1744
1800
  });
1745
1801
  }
1746
1802
  }
1747
- const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
1748
- for (let i = 0; i < seq.length; i++) {
1749
- const act = seq[i];
1750
- activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
1751
- if (i < seq.length - 1) {
1752
- const key = act + " \u2192 " + seq[i + 1];
1753
- transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
1754
- }
1755
- }
1756
- for (const act of activities) {
1757
- if (act.duration > 0) {
1758
- const durs = activityDurations.get(act.name) || [];
1759
- durs.push(act.duration);
1760
- activityDurations.set(act.name, durs);
1761
- }
1762
- const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
1763
- if (act.status === "failed") st.fail++;
1764
- else st.ok++;
1765
- activityStatuses.set(act.name, st);
1766
- }
1767
- }
1768
- const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
1769
- const durs = activityDurations.get(name) || [];
1770
- const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
1771
- const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
1772
1803
  return {
1773
- id: name,
1774
- label: name,
1775
- count,
1776
- frequency: count / totalTraces,
1777
- avgDuration,
1778
- failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
1779
- isVirtual: name === "[START]" || name === "[END]"
1804
+ id: serialized.id || serialized.filename,
1805
+ filename: serialized.filename,
1806
+ name: serialized.name || serialized.filename,
1807
+ agentId: serialized.agentId,
1808
+ trigger: serialized.trigger,
1809
+ status: serialized.status || "completed",
1810
+ sourceType: serialized.sourceType,
1811
+ startTime: serialized.startTime,
1812
+ endTime: serialized.endTime || serialized.startTime,
1813
+ tokenUsage: serialized.tokenUsage,
1814
+ activities
1780
1815
  };
1781
1816
  });
1782
- const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
1783
- const [source, target] = key.split(" \u2192 ");
1784
- return { source, target, count, frequency: count / totalTraces };
1785
- });
1786
- const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
1787
- const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
1788
- res.json({
1789
- agentId,
1790
- totalTraces,
1791
- nodes,
1792
- edges,
1793
- maxEdgeCount,
1794
- maxNodeCount
1795
- });
1817
+ const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
1818
+ const minTime = Math.min(...allTimes);
1819
+ const maxTime = Math.max(...allTimes);
1820
+ res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
1821
+ } catch (error) {
1822
+ console.error("Agent timeline error:", error);
1823
+ res.status(500).json({ error: "Failed to build agent timeline" });
1824
+ }
1825
+ });
1826
+ this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1827
+ try {
1828
+ const agentId = req.params.agentId;
1829
+ const allTraces = this.watcher.getTracesByAgent(agentId);
1830
+ if (allTraces.length === 0) {
1831
+ return res.status(404).json({ error: "No traces for agent" });
1832
+ }
1833
+ const graphs = this.getGraphTraces(agentId);
1834
+ if (graphs.length > 0) {
1835
+ return res.json(this.buildProcessGraphFromCore(agentId, graphs));
1836
+ }
1837
+ return res.json(this.buildProcessGraphLegacy(agentId, allTraces));
1796
1838
  } catch (error) {
1797
1839
  console.error("Process graph error:", error);
1798
1840
  res.status(500).json({ error: "Failed to build process graph" });
1799
1841
  }
1800
1842
  });
1843
+ this.app.get("/api/agents/:agentId/variants", (req, res) => {
1844
+ try {
1845
+ const agentId = req.params.agentId;
1846
+ const graphs = this.getGraphTraces(agentId);
1847
+ if (graphs.length === 0) {
1848
+ return res.json({ agentId, totalTraces: 0, variants: [] });
1849
+ }
1850
+ const variants = (0, import_agentflow_core3.findVariants)(graphs).map((v) => ({
1851
+ pathSignature: v.pathSignature,
1852
+ count: v.count,
1853
+ percentage: v.percentage
1854
+ }));
1855
+ res.json({ agentId, totalTraces: graphs.length, variants });
1856
+ } catch (error) {
1857
+ console.error("Variants error:", error);
1858
+ res.status(500).json({ error: "Failed to compute variants" });
1859
+ }
1860
+ });
1861
+ this.app.get("/api/agents/:agentId/bottlenecks", (req, res) => {
1862
+ try {
1863
+ const agentId = req.params.agentId;
1864
+ const graphs = this.getGraphTraces(agentId);
1865
+ if (graphs.length === 0) {
1866
+ return res.json({ agentId, bottlenecks: [] });
1867
+ }
1868
+ const bottlenecks = (0, import_agentflow_core3.getBottlenecks)(graphs).map((b) => ({
1869
+ nodeName: b.nodeName,
1870
+ nodeType: b.nodeType,
1871
+ occurrences: b.occurrences,
1872
+ durations: b.durations
1873
+ }));
1874
+ res.json({ agentId, bottlenecks });
1875
+ } catch (error) {
1876
+ console.error("Bottlenecks error:", error);
1877
+ res.status(500).json({ error: "Failed to compute bottlenecks" });
1878
+ }
1879
+ });
1880
+ this.app.get("/api/agents/:agentId/profile", (req, res) => {
1881
+ try {
1882
+ const profile = this.knowledgeStore.getAgentProfile(req.params.agentId);
1883
+ if (!profile) {
1884
+ return res.status(404).json({ error: "No profile for agent" });
1885
+ }
1886
+ res.json(profile);
1887
+ } catch (error) {
1888
+ console.error("Profile error:", error);
1889
+ res.status(500).json({ error: "Failed to load agent profile" });
1890
+ }
1891
+ });
1801
1892
  this.app.get("/api/stats/:agentId", (req, res) => {
1802
1893
  try {
1803
1894
  const agentStats = this.stats.getAgentStats(req.params.agentId);
@@ -1805,11 +1896,11 @@ var DashboardServer = class {
1805
1896
  return res.status(404).json({ error: "Agent not found" });
1806
1897
  }
1807
1898
  res.json(agentStats);
1808
- } catch (error) {
1899
+ } catch (_error) {
1809
1900
  res.status(500).json({ error: "Failed to load agent statistics" });
1810
1901
  }
1811
1902
  });
1812
- this.app.get("/api/process-health", (req, res) => {
1903
+ this.app.get("/api/process-health", (_req, res) => {
1813
1904
  try {
1814
1905
  const now = Date.now();
1815
1906
  if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
@@ -1854,9 +1945,11 @@ var DashboardServer = class {
1854
1945
  orphans: uniqueProcesses.filter((p) => {
1855
1946
  var _a;
1856
1947
  const alfredKnownPids = /* @__PURE__ */ new Set();
1857
- if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale) alfredKnownPids.add(alfredResult.pidFile.pid);
1948
+ if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale)
1949
+ alfredKnownPids.add(alfredResult.pidFile.pid);
1858
1950
  if (alfredResult.workers) {
1859
- if (alfredResult.workers.orchestratorPid) alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1951
+ if (alfredResult.workers.orchestratorPid)
1952
+ alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1860
1953
  for (const w of alfredResult.workers.workers) {
1861
1954
  if (w.pid) alfredKnownPids.add(w.pid);
1862
1955
  }
@@ -1875,11 +1968,22 @@ var DashboardServer = class {
1875
1968
  result.problems = [...alfredResult.problems || [], ...openclawProblems];
1876
1969
  this.processHealthCache = { result, ts: now };
1877
1970
  res.json(result);
1878
- } catch (error) {
1971
+ } catch (_error) {
1879
1972
  res.status(500).json({ error: "Failed to audit processes" });
1880
1973
  }
1881
1974
  });
1882
- this.app.get("*", (req, res) => {
1975
+ this.app.get("/health", (_req, res) => {
1976
+ res.json({
1977
+ status: "ok",
1978
+ uptime: process.uptime(),
1979
+ traceCount: this.watcher.getTraceCount(),
1980
+ agentCount: this.watcher.getAgentIds().length
1981
+ });
1982
+ });
1983
+ this.app.get("/ready", (_req, res) => {
1984
+ res.json({ status: "ready" });
1985
+ });
1986
+ this.app.get("*", (_req, res) => {
1883
1987
  const indexPath = path3.join(__dirname, "../public/index.html");
1884
1988
  if (fs3.existsSync(indexPath)) {
1885
1989
  res.sendFile(indexPath);
@@ -1908,6 +2012,189 @@ var DashboardServer = class {
1908
2012
  });
1909
2013
  });
1910
2014
  }
2015
+ /**
2016
+ * Filter an agent's traces to valid ExecutionGraphs and convert via loadGraph().
2017
+ * Returns only traces with proper nodes (Map or non-empty object), skipping session-only traces.
2018
+ */
2019
+ getGraphTraces(agentId) {
2020
+ const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
2021
+ const graphs = [];
2022
+ for (const trace of traces) {
2023
+ try {
2024
+ if (trace.sourceType === "session" || trace.sourceType === "log") continue;
2025
+ if (!trace.rootNodeId && !trace.rootId) continue;
2026
+ const nodes = trace.nodes;
2027
+ if (!nodes || typeof nodes === "object" && Object.keys(nodes).length === 0) continue;
2028
+ const nodeValues = Object.values(nodes);
2029
+ if (nodeValues.some((n) => n.type === "log-file" || n.type === "log-entry")) continue;
2030
+ graphs.push((0, import_agentflow_core3.loadGraph)(trace));
2031
+ } catch {
2032
+ }
2033
+ }
2034
+ return graphs;
2035
+ }
2036
+ /**
2037
+ * Build process graph response using core APIs (discoverProcess + getBottlenecks).
2038
+ * Maps core output to the frontend's expected shape with virtual START/END nodes.
2039
+ */
2040
+ buildProcessGraphFromCore(agentId, graphs) {
2041
+ const model = (0, import_agentflow_core3.discoverProcess)(graphs);
2042
+ const bottleneckList = (0, import_agentflow_core3.getBottlenecks)(graphs);
2043
+ const bottleneckMap = /* @__PURE__ */ new Map();
2044
+ for (const b of bottleneckList) {
2045
+ const key = `${b.nodeType}:${b.nodeName}`;
2046
+ bottleneckMap.set(key, {
2047
+ avgDuration: b.durations.median,
2048
+ failRate: 0,
2049
+ // Not directly available from bottleneck data
2050
+ p95: b.durations.p95
2051
+ });
2052
+ bottleneckMap.set(b.nodeName, {
2053
+ avgDuration: b.durations.median,
2054
+ failRate: 0,
2055
+ p95: b.durations.p95
2056
+ });
2057
+ }
2058
+ const nodes = [];
2059
+ const stepCounts = /* @__PURE__ */ new Map();
2060
+ for (const t of model.transitions) {
2061
+ stepCounts.set(t.from, (stepCounts.get(t.from) ?? 0) + t.count);
2062
+ }
2063
+ for (const step of model.steps) {
2064
+ const count = stepCounts.get(step) ?? model.totalGraphs;
2065
+ const bn = bottleneckMap.get(step);
2066
+ const colonIdx = step.indexOf(":");
2067
+ const label = colonIdx >= 0 ? step.slice(colonIdx + 1) : step;
2068
+ nodes.push({
2069
+ id: step,
2070
+ label,
2071
+ count,
2072
+ frequency: count / model.totalGraphs,
2073
+ avgDuration: (bn == null ? void 0 : bn.avgDuration) ?? 0,
2074
+ failRate: (bn == null ? void 0 : bn.failRate) ?? 0,
2075
+ p95Duration: (bn == null ? void 0 : bn.p95) ?? 0,
2076
+ isVirtual: false
2077
+ });
2078
+ }
2079
+ const rootSteps = new Set(model.steps);
2080
+ const childSteps = new Set(model.transitions.map((t) => t.to));
2081
+ const leafSteps = new Set(model.steps);
2082
+ for (const t of model.transitions) {
2083
+ }
2084
+ nodes.push({ id: "[START]", label: "[START]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
2085
+ nodes.push({ id: "[END]", label: "[END]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
2086
+ const edges = model.transitions.map((t) => ({
2087
+ source: t.from,
2088
+ target: t.to,
2089
+ count: t.count,
2090
+ frequency: t.count / model.totalGraphs
2091
+ }));
2092
+ const targetSteps = new Set(model.transitions.map((t) => t.to));
2093
+ for (const step of model.steps) {
2094
+ if (!targetSteps.has(step)) {
2095
+ edges.push({ source: "[START]", target: step, count: model.totalGraphs, frequency: 1 });
2096
+ }
2097
+ }
2098
+ const sourceSteps = new Set(model.transitions.map((t) => t.from));
2099
+ for (const step of model.steps) {
2100
+ if (!sourceSteps.has(step)) {
2101
+ edges.push({ source: step, target: "[END]", count: model.totalGraphs, frequency: 1 });
2102
+ }
2103
+ }
2104
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
2105
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
2106
+ return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
2107
+ }
2108
+ /**
2109
+ * Legacy process graph computation for session-based traces.
2110
+ * Preserved for backward compatibility with JSONL/LOG traces.
2111
+ */
2112
+ buildProcessGraphLegacy(agentId, allTraces) {
2113
+ const traces = allTraces.map(serializeTrace);
2114
+ const activityCounts = /* @__PURE__ */ new Map();
2115
+ const transitionCounts = /* @__PURE__ */ new Map();
2116
+ const activityDurations = /* @__PURE__ */ new Map();
2117
+ const activityStatuses = /* @__PURE__ */ new Map();
2118
+ let totalTraces = 0;
2119
+ for (const trace of traces) {
2120
+ totalTraces++;
2121
+ const activities = [];
2122
+ if (trace.sessionEvents && trace.sessionEvents.length > 0) {
2123
+ for (const evt of trace.sessionEvents) {
2124
+ const name = evt.toolName || evt.name || evt.type;
2125
+ if (!name) continue;
2126
+ activities.push({
2127
+ name,
2128
+ type: evt.type,
2129
+ status: evt.toolError ? "failed" : "completed",
2130
+ duration: evt.duration || 0
2131
+ });
2132
+ }
2133
+ } else {
2134
+ const nodes2 = trace.nodes || {};
2135
+ const sorted = Object.values(nodes2).sort(
2136
+ (a, b) => (a.startTime || 0) - (b.startTime || 0)
2137
+ );
2138
+ for (const node of sorted) {
2139
+ activities.push({
2140
+ name: node.name || node.type || node.id,
2141
+ type: node.type || "unknown",
2142
+ status: node.status || "completed",
2143
+ duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
2144
+ });
2145
+ }
2146
+ }
2147
+ const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
2148
+ for (let i = 0; i < seq.length; i++) {
2149
+ const act = seq[i];
2150
+ activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
2151
+ if (i < seq.length - 1) {
2152
+ const key = `${act} \u2192 ${seq[i + 1]}`;
2153
+ transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
2154
+ }
2155
+ }
2156
+ for (const act of activities) {
2157
+ if (act.duration > 0) {
2158
+ const durs = activityDurations.get(act.name) || [];
2159
+ durs.push(act.duration);
2160
+ activityDurations.set(act.name, durs);
2161
+ }
2162
+ const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
2163
+ if (act.status === "failed") st.fail++;
2164
+ else st.ok++;
2165
+ activityStatuses.set(act.name, st);
2166
+ }
2167
+ }
2168
+ const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
2169
+ const durs = activityDurations.get(name) || [];
2170
+ const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
2171
+ const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
2172
+ return {
2173
+ id: name,
2174
+ label: name,
2175
+ count,
2176
+ frequency: count / totalTraces,
2177
+ avgDuration,
2178
+ failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
2179
+ p95Duration: 0,
2180
+ isVirtual: name === "[START]" || name === "[END]"
2181
+ };
2182
+ });
2183
+ const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
2184
+ const [source, target] = key.split(" \u2192 ");
2185
+ return { source, target, count, frequency: count / totalTraces };
2186
+ });
2187
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
2188
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
2189
+ return { agentId, totalTraces, nodes, edges, maxEdgeCount, maxNodeCount };
2190
+ }
2191
+ /** Check if a trace is a proper ExecutionGraph (not a synthetic session-based trace). */
2192
+ isGraphTrace(trace) {
2193
+ if (trace.sourceType === "session" || trace.sourceType === "log") return false;
2194
+ if (!trace.rootNodeId && !trace.rootId) return false;
2195
+ const nodes = trace.nodes instanceof Map ? Object.fromEntries(trace.nodes) : trace.nodes;
2196
+ return nodes && typeof nodes === "object" && Object.keys(nodes).length > 0;
2197
+ }
1911
2198
  setupTraceWatcher() {
1912
2199
  this.watcher.on("trace-added", (trace) => {
1913
2200
  this.stats.processTrace(trace);
@@ -1915,6 +2202,14 @@ var DashboardServer = class {
1915
2202
  type: "trace-added",
1916
2203
  data: serializeTrace(trace)
1917
2204
  });
2205
+ if (this.isGraphTrace(trace)) {
2206
+ try {
2207
+ const graph = (0, import_agentflow_core3.loadGraph)(serializeTrace(trace));
2208
+ const event = (0, import_agentflow_core3.createExecutionEvent)(graph);
2209
+ this.knowledgeStore.append(event);
2210
+ } catch {
2211
+ }
2212
+ }
1918
2213
  });
1919
2214
  this.watcher.on("trace-updated", (trace) => {
1920
2215
  this.stats.processTrace(trace);