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/server.cjs CHANGED
@@ -32,13 +32,13 @@ __export(server_exports, {
32
32
  DashboardServer: () => DashboardServer
33
33
  });
34
34
  module.exports = __toCommonJS(server_exports);
35
- var import_express = __toESM(require("express"), 1);
36
35
  var fs3 = __toESM(require("fs"), 1);
37
- var import_http = require("http");
36
+ var import_node_http = require("http");
38
37
  var path3 = __toESM(require("path"), 1);
39
- var import_url = require("url");
40
- var import_ws = require("ws");
38
+ var import_node_url = require("url");
41
39
  var import_agentflow_core3 = require("agentflow-core");
40
+ var import_express = __toESM(require("express"), 1);
41
+ var import_ws = require("ws");
42
42
 
43
43
  // src/stats.ts
44
44
  var import_agentflow_core = require("agentflow-core");
@@ -216,12 +216,162 @@ var AgentStats = class {
216
216
  };
217
217
 
218
218
  // src/watcher.ts
219
- var import_agentflow_core2 = require("agentflow-core");
220
- var import_chokidar = __toESM(require("chokidar"), 1);
221
- var import_events = require("events");
219
+ var import_node_events = require("events");
222
220
  var fs = __toESM(require("fs"), 1);
223
221
  var path = __toESM(require("path"), 1);
224
- var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
222
+ var import_agentflow_core2 = require("agentflow-core");
223
+ var import_chokidar = __toESM(require("chokidar"), 1);
224
+
225
+ // src/parsers/log-utils.ts
226
+ function stripAnsi(str) {
227
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
228
+ }
229
+ function parseValue(value) {
230
+ if (value.match(/^\d+$/)) return parseInt(value, 10);
231
+ if (value.match(/^\d+\.\d+$/)) return parseFloat(value);
232
+ if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
233
+ if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
234
+ return value;
235
+ }
236
+ function parseTimestamp(value) {
237
+ if (!value) return null;
238
+ if (typeof value === "number") return value;
239
+ try {
240
+ return new Date(value).getTime();
241
+ } catch {
242
+ return null;
243
+ }
244
+ }
245
+ function extractTimestamp(line) {
246
+ const clean = stripAnsi(line);
247
+ const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
248
+ if (isoMatch) return new Date(isoMatch[1]).getTime();
249
+ return null;
250
+ }
251
+ function extractLogLevel(line) {
252
+ const clean = stripAnsi(line);
253
+ const levelMatch = clean.match(/\b(debug|info|warn|warning|error|fatal|trace)\b/i);
254
+ return levelMatch ? levelMatch[1].trim().toLowerCase() : null;
255
+ }
256
+ function extractAction(line) {
257
+ const clean = stripAnsi(line);
258
+ const actionMatch = clean.match(/\]\s+(\S+)/);
259
+ if (actionMatch) return actionMatch[1].trim();
260
+ const afterLevel = clean.replace(/^.*?(debug|info|warn|warning|error|fatal|trace)\s*\]?\s*/i, "");
261
+ return afterLevel.split(/\s+/)[0] || "";
262
+ }
263
+ function extractKeyValuePairs(line) {
264
+ const pairs = {};
265
+ const clean = stripAnsi(line);
266
+ const kvRegex = /(\w+)=('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|\S+)/g;
267
+ let match;
268
+ while ((match = kvRegex.exec(clean)) !== null) {
269
+ if (match[1] === "Z" || match[1] === "m") continue;
270
+ pairs[match[1]] = parseValue(match[2]);
271
+ }
272
+ return pairs;
273
+ }
274
+ function detectComponent(action, kvPairs) {
275
+ if (action.includes(".")) return action.split(".")[0];
276
+ if (kvPairs.component) return String(kvPairs.component);
277
+ if (kvPairs.service) return String(kvPairs.service);
278
+ if (kvPairs.module) return String(kvPairs.module);
279
+ if (kvPairs.worker) return String(kvPairs.worker);
280
+ return action || "unknown";
281
+ }
282
+ function detectOperation(action, kvPairs) {
283
+ if (action.includes(".")) return action.split(".").slice(1).join(".");
284
+ if (kvPairs.operation) return String(kvPairs.operation);
285
+ if (kvPairs.method) return String(kvPairs.method);
286
+ if (kvPairs.action) return String(kvPairs.action);
287
+ return action || "activity";
288
+ }
289
+ function detectActivityPattern(line) {
290
+ let timestamp = extractTimestamp(line);
291
+ let level = extractLogLevel(line);
292
+ let action = extractAction(line);
293
+ let kvPairs = extractKeyValuePairs(line);
294
+ if (!timestamp) {
295
+ const jsonMatch = line.match(/\{.*\}/);
296
+ if (jsonMatch) {
297
+ try {
298
+ const parsed = JSON.parse(jsonMatch[0]);
299
+ timestamp = parseTimestamp(parsed.timestamp || parsed.time || parsed.ts) || Date.now();
300
+ level = parsed.level || parsed.severity || "info";
301
+ action = parsed.action || parsed.event || parsed.message || "";
302
+ kvPairs = parsed;
303
+ } catch {
304
+ }
305
+ }
306
+ }
307
+ if (!timestamp) {
308
+ const kvMatches = line.match(/(\w+)=([^\s]+)/g);
309
+ if (kvMatches && kvMatches.length >= 2) {
310
+ const pairs = {};
311
+ for (const m of kvMatches) {
312
+ const [key, value] = m.split("=", 2);
313
+ pairs[key] = parseValue(value);
314
+ }
315
+ timestamp = parseTimestamp(pairs.timestamp || pairs.time) || Date.now();
316
+ level = String(pairs.level || "info");
317
+ action = String(pairs.action || pairs.event || "");
318
+ kvPairs = pairs;
319
+ }
320
+ }
321
+ if (!timestamp) {
322
+ const logMatch = line.match(
323
+ /^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)\s+(\w+)?\s*:?\s*(.+)/
324
+ );
325
+ if (logMatch) {
326
+ timestamp = new Date(logMatch[1]).getTime();
327
+ level = logMatch[2] || "info";
328
+ action = logMatch[3] || "";
329
+ }
330
+ }
331
+ if (!timestamp) return null;
332
+ return {
333
+ timestamp,
334
+ level: (level == null ? void 0 : level.toLowerCase()) || "info",
335
+ action,
336
+ component: detectComponent(action, kvPairs),
337
+ operation: detectOperation(action, kvPairs),
338
+ ...kvPairs
339
+ };
340
+ }
341
+ function extractSessionIdentifier(activity) {
342
+ return String(
343
+ activity.session_id || activity.run_id || activity.request_id || activity.trace_id || activity.sweep_id || activity.transaction_id || "default"
344
+ );
345
+ }
346
+ function detectTrigger(activity) {
347
+ if (activity.trigger) return String(activity.trigger);
348
+ if (activity.method && activity.url) return "api-call";
349
+ if (typeof activity.operation === "string" && activity.operation.includes("start"))
350
+ return "startup";
351
+ if (typeof activity.operation === "string" && activity.operation.includes("invoke"))
352
+ return "invocation";
353
+ return "event";
354
+ }
355
+ function getUniversalNodeStatus(activity) {
356
+ if (activity.level === "error" || activity.level === "fatal") return "failed";
357
+ if (activity.level === "warn" || activity.level === "warning") return "warning";
358
+ const op = String(activity.operation || "");
359
+ if (op.match(/start|begin|init/i)) return "running";
360
+ if (op.match(/complete|finish|end|done/i)) return "completed";
361
+ return "completed";
362
+ }
363
+ function openClawSessionIdToAgent(sessionId) {
364
+ if (sessionId.startsWith("janitor-")) return "vault-janitor";
365
+ if (sessionId.startsWith("curator-")) return "vault-curator";
366
+ if (sessionId.startsWith("distiller-")) return "vault-distiller";
367
+ if (sessionId.startsWith("main-")) return "main";
368
+ const firstSegment = sessionId.split("-")[0];
369
+ if (firstSegment) return firstSegment;
370
+ return "openclaw";
371
+ }
372
+
373
+ // src/watcher.ts
374
+ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
225
375
  watchers = [];
226
376
  traces = /* @__PURE__ */ new Map();
227
377
  tracesDir;
@@ -260,7 +410,9 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
260
410
  console.error(`Error scanning directory ${dir}:`, error);
261
411
  }
262
412
  }
263
- console.log(`Scanned ${totalDirectories} directories (recursive), loaded ${this.traces.size} items from ${totalFiles} files`);
413
+ console.log(
414
+ `Scanned ${totalDirectories} directories (recursive), loaded ${this.traces.size} items from ${totalFiles} files`
415
+ );
264
416
  }
265
417
  /** Recursively scan directory for supported file types */
266
418
  scanDirectoryRecursive(dir, depth = 0) {
@@ -302,7 +454,14 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
302
454
  "models.json",
303
455
  "config.json"
304
456
  ]);
305
- static SKIP_SUFFIXES = ["-state.json", "-config.json", "-watch-state.json", ".tmp", ".bak", ".backup"];
457
+ static SKIP_SUFFIXES = [
458
+ "-state.json",
459
+ "-config.json",
460
+ "-watch-state.json",
461
+ ".tmp",
462
+ ".bak",
463
+ ".backup"
464
+ ];
306
465
  /** Load a .json trace, .jsonl session file, or .log file. */
307
466
  loadFile(filePath) {
308
467
  const filename = path.basename(filePath);
@@ -353,16 +512,16 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
353
512
  const lines = content.split("\n").filter((line) => line.trim());
354
513
  const activities = /* @__PURE__ */ new Map();
355
514
  for (const line of lines) {
356
- const activity = this.detectActivityPattern(line);
515
+ const activity = detectActivityPattern(line);
357
516
  if (!activity) continue;
358
- const sessionId = this.extractSessionIdentifier(activity);
517
+ const sessionId = extractSessionIdentifier(activity);
359
518
  if (!activities.has(sessionId)) {
360
519
  activities.set(sessionId, {
361
520
  id: sessionId,
362
521
  rootNodeId: "",
363
522
  agentId: this.detectAgentIdentifier(activity, filename, filePath),
364
523
  name: this.generateActivityName(activity, sessionId),
365
- trigger: this.detectTrigger(activity),
524
+ trigger: detectTrigger(activity),
366
525
  startTime: activity.timestamp,
367
526
  endTime: activity.timestamp,
368
527
  status: "completed",
@@ -385,7 +544,9 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
385
544
  (session) => Object.keys(session.nodes).length > 0
386
545
  );
387
546
  for (const trace of traces) {
388
- const sortedNodes = Object.values(trace.nodes).sort((a, b) => a.startTime - b.startTime);
547
+ const sortedNodes = Object.values(trace.nodes).sort(
548
+ (a, b) => a.startTime - b.startTime
549
+ );
389
550
  trace.sessionEvents = sortedNodes.map((node) => ({
390
551
  type: node.status === "failed" ? "tool_result" : "system",
391
552
  timestamp: node.startTime,
@@ -403,7 +564,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
403
564
  id: "",
404
565
  rootNodeId: "root",
405
566
  nodes: {
406
- "root": {
567
+ root: {
407
568
  id: "root",
408
569
  type: "log-file",
409
570
  name: filename,
@@ -426,125 +587,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
426
587
  }
427
588
  return traces;
428
589
  }
429
- /** Detect activity patterns in log lines using universal heuristics */
430
- detectActivityPattern(line) {
431
- let timestamp = this.extractTimestamp(line);
432
- let level = this.extractLogLevel(line);
433
- let action = this.extractAction(line);
434
- let kvPairs = this.extractKeyValuePairs(line);
435
- if (!timestamp) {
436
- const jsonMatch = line.match(/\{.*\}/);
437
- if (jsonMatch) {
438
- try {
439
- const parsed = JSON.parse(jsonMatch[0]);
440
- timestamp = this.parseTimestamp(parsed.timestamp || parsed.time || parsed.ts) || Date.now();
441
- level = parsed.level || parsed.severity || "info";
442
- action = parsed.action || parsed.event || parsed.message || "";
443
- kvPairs = parsed;
444
- } catch {
445
- }
446
- }
447
- }
448
- if (!timestamp) {
449
- const kvMatches = line.match(/(\w+)=([^\s]+)/g);
450
- if (kvMatches && kvMatches.length >= 2) {
451
- const pairs = {};
452
- kvMatches.forEach((match) => {
453
- const [key, value] = match.split("=", 2);
454
- pairs[key] = this.parseValue(value);
455
- });
456
- timestamp = this.parseTimestamp(pairs.timestamp || pairs.time) || Date.now();
457
- level = pairs.level || "info";
458
- action = pairs.action || pairs.event || "";
459
- kvPairs = pairs;
460
- }
461
- }
462
- if (!timestamp) {
463
- const logMatch = line.match(/^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)\s+(\w+)?\s*:?\s*(.+)/);
464
- if (logMatch) {
465
- timestamp = new Date(logMatch[1]).getTime();
466
- level = logMatch[2] || "info";
467
- action = logMatch[3] || "";
468
- }
469
- }
470
- if (!timestamp) return null;
471
- return {
472
- timestamp,
473
- level: (level == null ? void 0 : level.toLowerCase()) || "info",
474
- action,
475
- component: this.detectComponent(action, kvPairs),
476
- operation: this.detectOperation(action, kvPairs),
477
- ...kvPairs
478
- };
479
- }
480
- /** Strip ANSI escape codes from a string. */
481
- stripAnsi(str) {
482
- return str.replace(/\x1b\[[0-9;]*m/g, "");
483
- }
484
- extractTimestamp(line) {
485
- const clean = this.stripAnsi(line);
486
- const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
487
- if (isoMatch) return new Date(isoMatch[1]).getTime();
488
- return null;
489
- }
490
- extractLogLevel(line) {
491
- const clean = this.stripAnsi(line);
492
- const levelMatch = clean.match(/\b(debug|info|warn|warning|error|fatal|trace)\b/i);
493
- return levelMatch ? levelMatch[1].trim().toLowerCase() : null;
494
- }
495
- extractAction(line) {
496
- const clean = this.stripAnsi(line);
497
- const actionMatch = clean.match(/\]\s+(\S+)/);
498
- if (actionMatch) return actionMatch[1].trim();
499
- const afterLevel = clean.replace(/^.*?(debug|info|warn|warning|error|fatal|trace)\s*\]?\s*/i, "");
500
- return afterLevel.split(/\s+/)[0] || "";
501
- }
502
- extractKeyValuePairs(line) {
503
- const pairs = {};
504
- const clean = this.stripAnsi(line);
505
- const kvRegex = /(\w+)=('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|\S+)/g;
506
- let match;
507
- while ((match = kvRegex.exec(clean)) !== null) {
508
- if (match[1] === "Z" || match[1] === "m") continue;
509
- pairs[match[1]] = this.parseValue(match[2]);
510
- }
511
- return pairs;
512
- }
513
- parseValue(value) {
514
- if (value.match(/^\d+$/)) return parseInt(value);
515
- if (value.match(/^\d+\.\d+$/)) return parseFloat(value);
516
- if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
517
- if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
518
- return value;
519
- }
520
- parseTimestamp(value) {
521
- if (!value) return null;
522
- if (typeof value === "number") return value;
523
- try {
524
- return new Date(value).getTime();
525
- } catch {
526
- return null;
527
- }
528
- }
529
- detectComponent(action, kvPairs) {
530
- if (action.includes(".")) return action.split(".")[0];
531
- if (kvPairs.component) return kvPairs.component;
532
- if (kvPairs.service) return kvPairs.service;
533
- if (kvPairs.module) return kvPairs.module;
534
- if (kvPairs.worker) return kvPairs.worker;
535
- return action || "unknown";
536
- }
537
- detectOperation(action, kvPairs) {
538
- if (action.includes(".")) return action.split(".").slice(1).join(".");
539
- if (kvPairs.operation) return kvPairs.operation;
540
- if (kvPairs.method) return kvPairs.method;
541
- if (kvPairs.action) return kvPairs.action;
542
- return action || "activity";
543
- }
544
- extractSessionIdentifier(activity) {
545
- return activity.session_id || activity.run_id || activity.request_id || activity.trace_id || activity.sweep_id || activity.transaction_id || "default";
546
- }
547
- detectAgentIdentifier(activity, filename, filePath) {
590
+ detectAgentIdentifier(activity, _filename, filePath) {
548
591
  if (activity.agent_id) {
549
592
  const agentId = activity.agent_id;
550
593
  if (agentId.startsWith("vault-")) return agentId;
@@ -588,14 +631,6 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
588
631
  const operation = activity.operation !== "activity" ? `: ${activity.operation}` : "";
589
632
  return `${component}${operation} (${sessionId})`;
590
633
  }
591
- detectTrigger(activity) {
592
- var _a, _b;
593
- if (activity.trigger) return activity.trigger;
594
- if (activity.method && activity.url) return "api-call";
595
- if ((_a = activity.operation) == null ? void 0 : _a.includes("start")) return "startup";
596
- if ((_b = activity.operation) == null ? void 0 : _b.includes("invoke")) return "invocation";
597
- return "event";
598
- }
599
634
  addActivityNode(session, activity) {
600
635
  const nodeId = `${activity.component}-${activity.operation}`;
601
636
  if (session.nodes[nodeId]) {
@@ -613,7 +648,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
613
648
  id: nodeId,
614
649
  type: activity.component,
615
650
  name: `${activity.component}: ${activity.operation}`,
616
- status: this.getUniversalNodeStatus(activity),
651
+ status: getUniversalNodeStatus(activity),
617
652
  startTime: activity.timestamp,
618
653
  endTime: activity.timestamp,
619
654
  children: [],
@@ -624,17 +659,9 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
624
659
  session.rootNodeId = nodeId;
625
660
  }
626
661
  }
627
- getUniversalNodeStatus(activity) {
628
- var _a, _b;
629
- if (activity.level === "error" || activity.level === "fatal") return "failed";
630
- if (activity.level === "warn" || activity.level === "warning") return "warning";
631
- if ((_a = activity.operation) == null ? void 0 : _a.match(/start|begin|init/i)) return "running";
632
- if ((_b = activity.operation) == null ? void 0 : _b.match(/complete|finish|end|done/i)) return "completed";
633
- return "completed";
634
- }
635
662
  /** Parse OpenClaw tslog-format log files with session run results. */
636
663
  loadOpenClawLogFile(content, filename, filePath, stats) {
637
- var _a, _b, _c, _d;
664
+ var _a, _b, _c, _d, _e, _f;
638
665
  const lines = content.split("\n").filter((l) => l.trim());
639
666
  const sessions = /* @__PURE__ */ new Map();
640
667
  for (const line of lines) {
@@ -646,13 +673,13 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
646
673
  if ((inner == null ? void 0 : inner.payloads) && ((_a = inner == null ? void 0 : inner.meta) == null ? void 0 : _a.agentMeta)) {
647
674
  const agentMeta = inner.meta.agentMeta;
648
675
  const sessionId = agentMeta.sessionId || "unknown";
649
- const agentName = this.openClawSessionIdToAgent(sessionId);
676
+ const agentName = openClawSessionIdToAgent(sessionId);
650
677
  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();
651
678
  const texts = (inner.payloads || []).map((p) => p.text || "").filter(Boolean);
652
679
  if (!sessions.has(sessionId)) {
653
680
  sessions.set(sessionId, { entries: [] });
654
681
  }
655
- sessions.get(sessionId).entries.push({
682
+ (_c = sessions.get(sessionId)) == null ? void 0 : _c.entries.push({
656
683
  text: texts.join("\n"),
657
684
  timestamp,
658
685
  sessionId,
@@ -667,16 +694,16 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
667
694
  } catch {
668
695
  }
669
696
  }
670
- if (parsed.payloads && ((_c = parsed.meta) == null ? void 0 : _c.agentMeta)) {
697
+ if (parsed.payloads && ((_d = parsed.meta) == null ? void 0 : _d.agentMeta)) {
671
698
  const agentMeta = parsed.meta.agentMeta;
672
699
  const sessionId = agentMeta.sessionId || "unknown";
673
- const agentName = this.openClawSessionIdToAgent(sessionId);
674
- 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();
700
+ const agentName = openClawSessionIdToAgent(sessionId);
701
+ 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();
675
702
  const texts = (parsed.payloads || []).map((p) => p.text || "").filter(Boolean);
676
703
  if (!sessions.has(sessionId)) {
677
704
  sessions.set(sessionId, { entries: [] });
678
705
  }
679
- sessions.get(sessionId).entries.push({
706
+ (_f = sessions.get(sessionId)) == null ? void 0 : _f.entries.push({
680
707
  text: texts.join("\n"),
681
708
  timestamp,
682
709
  sessionId,
@@ -755,7 +782,11 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
755
782
  content: e.text,
756
783
  model: e.model,
757
784
  provider: e.provider,
758
- tokens: { input: e.usage.input || 0, output: e.usage.output || 0, total: e.usage.total || 0 },
785
+ tokens: {
786
+ input: e.usage.input || 0,
787
+ output: e.usage.output || 0,
788
+ total: e.usage.total || 0
789
+ },
759
790
  duration: e.durationMs,
760
791
  id: `entry-${idx}`
761
792
  }));
@@ -776,7 +807,12 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
776
807
  sourceType: "session",
777
808
  sourceDir: path.dirname(filePath),
778
809
  sessionEvents,
779
- tokenUsage: { input: totalInput, output: totalOutput, total: totalTokens || totalInput + totalOutput, cost: 0 },
810
+ tokenUsage: {
811
+ input: totalInput,
812
+ output: totalOutput,
813
+ total: totalTokens || totalInput + totalOutput,
814
+ cost: 0
815
+ },
780
816
  metadata: {
781
817
  provider: firstEntry.provider,
782
818
  model: firstEntry.model,
@@ -790,16 +826,6 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
790
826
  }
791
827
  return traceIndex > 0;
792
828
  }
793
- /** Map OpenClaw sessionId prefix to agent name. */
794
- openClawSessionIdToAgent(sessionId) {
795
- if (sessionId.startsWith("janitor-")) return "vault-janitor";
796
- if (sessionId.startsWith("curator-")) return "vault-curator";
797
- if (sessionId.startsWith("distiller-")) return "vault-distiller";
798
- if (sessionId.startsWith("main-")) return "main";
799
- const firstSegment = sessionId.split("-")[0];
800
- if (firstSegment) return firstSegment;
801
- return "openclaw";
802
- }
803
829
  loadTraceFile(filePath) {
804
830
  try {
805
831
  const content = fs.readFileSync(filePath, "utf8");
@@ -938,9 +964,6 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
938
964
  } else {
939
965
  agentId = parentDir;
940
966
  }
941
- if (filePath.includes(".openclaw/") && !agentId.startsWith("openclaw-")) {
942
- agentId = `openclaw-${agentId}`;
943
- }
944
967
  if (filePath.includes(".alfred/") || filePath.includes("alfred")) {
945
968
  if (!agentId.startsWith("alfred-")) {
946
969
  agentId = `alfred-${agentId}`;
@@ -957,10 +980,12 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
957
980
  if (ts > lastTimestamp) lastTimestamp = ts;
958
981
  }
959
982
  }
960
- const firstMessage = rawEvents.find((e) => {
961
- var _a2;
962
- return e.type === "message" && ((_a2 = e.message) == null ? void 0 : _a2.role) === "user";
963
- });
983
+ const firstMessage = rawEvents.find(
984
+ (e) => {
985
+ var _a2;
986
+ return e.type === "message" && ((_a2 = e.message) == null ? void 0 : _a2.role) === "user";
987
+ }
988
+ );
964
989
  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) || "";
965
990
  const cronMatch = userPrompt.match(/\[cron:(\S+)\s+([^\]]+)\]/);
966
991
  const triggerName = cronMatch ? cronMatch[2] : "";
@@ -1034,7 +1059,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1034
1059
  nodes.set(spawnId, {
1035
1060
  id: spawnId,
1036
1061
  type: "subagent",
1037
- name: "Subagent: " + (((_f = evt.data) == null ? void 0 : _f.sessionId) || "").slice(0, 12),
1062
+ name: `Subagent: ${(((_f = evt.data) == null ? void 0 : _f.sessionId) || "").slice(0, 12)}`,
1038
1063
  startTime: evtTs,
1039
1064
  endTime: evtTs,
1040
1065
  status: "completed",
@@ -1158,7 +1183,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1158
1183
  id: evt.id,
1159
1184
  parentId: toolCallId
1160
1185
  });
1161
- for (const [nodeId, node] of nodes) {
1186
+ for (const [_nodeId, node] of nodes) {
1162
1187
  if (node.type === "tool" && ((_k = node.metadata) == null ? void 0 : _k.toolCallId) === toolCallId) {
1163
1188
  node.endTime = evtTs;
1164
1189
  node.status = hasError ? "failed" : "completed";
@@ -1326,7 +1351,7 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1326
1351
  if (filePath.startsWith(dir)) {
1327
1352
  const dirParts = dir.split(path.sep).filter(Boolean);
1328
1353
  const dirSuffix = dirParts.slice(-2).join("/");
1329
- return path.relative(dir, filePath).replace(/\\/g, "/") + "@" + dirSuffix;
1354
+ return `${path.relative(dir, filePath).replace(/\\/g, "/")}@${dirSuffix}`;
1330
1355
  }
1331
1356
  }
1332
1357
  return filePath;
@@ -1395,7 +1420,9 @@ var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
1395
1420
  });
1396
1421
  this.watchers.push(watcher);
1397
1422
  }
1398
- console.log(`Watching ${this.allWatchDirs.length} directories recursively for JSON/JSONL/LOG/TRACE files`);
1423
+ console.log(
1424
+ `Watching ${this.allWatchDirs.length} directories recursively for JSON/JSONL/LOG/TRACE files`
1425
+ );
1399
1426
  }
1400
1427
  getAllTraces() {
1401
1428
  return Array.from(this.traces.values()).sort((a, b) => {
@@ -1496,7 +1523,8 @@ function printBanner(config, traceCount, stats) {
1496
1523
 
1497
1524
  Tabs: \u{1F3AF} Graph \xB7 \u23F1\uFE0F Timeline \xB7 \u{1F4CA} Metrics \xB7 \u{1F6E0}\uFE0F Process Health \xB7 \u26A0\uFE0F Errors
1498
1525
 
1499
- Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? "\n Data dirs: " + config.dataDirs.join("\n ") : ""}
1526
+ Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? `
1527
+ Data dirs: ${config.dataDirs.join("\n ")}` : ""}
1500
1528
  Loaded: ${traceCount} traces \xB7 ${stats.totalAgents} agents \xB7 ${stats.totalExecutions} executions
1501
1529
  Success: ${stats.globalSuccessRate.toFixed(1)}%${stats.activeAgents > 0 ? ` \xB7 ${stats.activeAgents} active now` : ""}
1502
1530
  CORS: ${config.enableCors ? "enabled" : "disabled"}
@@ -1518,7 +1546,7 @@ async function startDashboard() {
1518
1546
  switch (args[i]) {
1519
1547
  case "--port":
1520
1548
  case "-p":
1521
- config.port = parseInt(args[++i]) || 3e3;
1549
+ config.port = parseInt(args[++i], 10) || 3e3;
1522
1550
  break;
1523
1551
  case "--traces":
1524
1552
  case "-t":
@@ -1600,7 +1628,7 @@ Tabs:
1600
1628
 
1601
1629
  // src/server.ts
1602
1630
  var import_meta = {};
1603
- var __filename = (0, import_url.fileURLToPath)(import_meta.url);
1631
+ var __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
1604
1632
  var __dirname = path3.dirname(__filename);
1605
1633
  function serializeTrace(trace) {
1606
1634
  if (!trace) return trace;
@@ -1622,23 +1650,41 @@ var DashboardServer = class {
1622
1650
  dataDirs: config.dataDirs
1623
1651
  });
1624
1652
  this.stats = new AgentStats();
1653
+ this.knowledgeStore = (0, import_agentflow_core3.createKnowledgeStore)({
1654
+ baseDir: path3.join(config.tracesDir, "..", ".agentflow", "knowledge")
1655
+ });
1625
1656
  this.setupExpress();
1626
1657
  this.setupWebSocket();
1627
1658
  this.setupTraceWatcher();
1659
+ let knowledgeCount = 0;
1628
1660
  for (const trace of this.watcher.getAllTraces()) {
1629
1661
  this.stats.processTrace(trace);
1662
+ if (this.isGraphTrace(trace)) {
1663
+ try {
1664
+ const graph = (0, import_agentflow_core3.loadGraph)(serializeTrace(trace));
1665
+ const event = (0, import_agentflow_core3.createExecutionEvent)(graph);
1666
+ this.knowledgeStore.append(event);
1667
+ knowledgeCount++;
1668
+ } catch {
1669
+ }
1670
+ }
1630
1671
  }
1631
1672
  console.log(`Processed ${this.watcher.getTraceCount()} existing traces for stats`);
1673
+ console.log(`Persisted ${knowledgeCount} graph traces to knowledge store`);
1632
1674
  }
1633
1675
  app = (0, import_express.default)();
1634
- server = (0, import_http.createServer)(this.app);
1676
+ server = (0, import_node_http.createServer)(this.app);
1635
1677
  wss = new import_ws.WebSocketServer({ server: this.server });
1636
1678
  watcher;
1637
1679
  stats;
1638
- processHealthCache = { result: null, ts: 0 };
1680
+ processHealthCache = {
1681
+ result: null,
1682
+ ts: 0
1683
+ };
1684
+ knowledgeStore;
1639
1685
  setupExpress() {
1640
1686
  if (this.config.enableCors) {
1641
- this.app.use((req, res, next) => {
1687
+ this.app.use((_req, res, next) => {
1642
1688
  res.header("Access-Control-Allow-Origin", "*");
1643
1689
  res.header(
1644
1690
  "Access-Control-Allow-Headers",
@@ -1651,11 +1697,11 @@ var DashboardServer = class {
1651
1697
  if (fs3.existsSync(publicDir)) {
1652
1698
  this.app.use(import_express.default.static(publicDir));
1653
1699
  }
1654
- this.app.get("/api/traces", (req, res) => {
1700
+ this.app.get("/api/traces", (_req, res) => {
1655
1701
  try {
1656
1702
  const traces = this.watcher.getAllTraces().map(serializeTrace);
1657
1703
  res.json(traces);
1658
- } catch (error) {
1704
+ } catch (_error) {
1659
1705
  res.status(500).json({ error: "Failed to load traces" });
1660
1706
  }
1661
1707
  });
@@ -1666,7 +1712,7 @@ var DashboardServer = class {
1666
1712
  return res.status(404).json({ error: "Trace not found" });
1667
1713
  }
1668
1714
  res.json(serializeTrace(trace));
1669
- } catch (error) {
1715
+ } catch (_error) {
1670
1716
  res.status(500).json({ error: "Failed to load trace" });
1671
1717
  }
1672
1718
  });
@@ -1681,118 +1727,163 @@ var DashboardServer = class {
1681
1727
  tokenUsage: trace.tokenUsage || null,
1682
1728
  sourceType: trace.sourceType || "trace"
1683
1729
  });
1684
- } catch (error) {
1730
+ } catch (_error) {
1685
1731
  res.status(500).json({ error: "Failed to load trace events" });
1686
1732
  }
1687
1733
  });
1688
- this.app.get("/api/agents", (req, res) => {
1734
+ this.app.get("/api/agents", (_req, res) => {
1689
1735
  try {
1690
1736
  const agents = this.stats.getAgentsList();
1691
1737
  res.json(agents);
1692
- } catch (error) {
1738
+ } catch (_error) {
1693
1739
  res.status(500).json({ error: "Failed to load agents" });
1694
1740
  }
1695
1741
  });
1696
- this.app.get("/api/stats", (req, res) => {
1742
+ this.app.get("/api/stats", (_req, res) => {
1697
1743
  try {
1698
1744
  const globalStats = this.stats.getGlobalStats();
1699
1745
  res.json(globalStats);
1700
- } catch (error) {
1746
+ } catch (_error) {
1701
1747
  res.status(500).json({ error: "Failed to load statistics" });
1702
1748
  }
1703
1749
  });
1704
- this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1750
+ this.app.get("/api/agents/:agentId/timeline", (req, res) => {
1705
1751
  try {
1706
1752
  const agentId = req.params.agentId;
1707
- const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
1708
- if (traces.length === 0) {
1753
+ const limit = Math.min(parseInt(req.query.limit, 10) || 50, 200);
1754
+ const rawTraces = this.watcher.getTracesByAgent(agentId);
1755
+ if (rawTraces.length === 0) {
1709
1756
  return res.status(404).json({ error: "No traces for agent" });
1710
1757
  }
1711
- const activityCounts = /* @__PURE__ */ new Map();
1712
- const transitionCounts = /* @__PURE__ */ new Map();
1713
- const activityDurations = /* @__PURE__ */ new Map();
1714
- const activityStatuses = /* @__PURE__ */ new Map();
1715
- let totalTraces = 0;
1716
- for (const trace of traces) {
1717
- totalTraces++;
1758
+ const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
1759
+ const executions = traces.map((t) => {
1760
+ const serialized = serializeTrace(t);
1761
+ const nodes = serialized.nodes || {};
1762
+ const events = serialized.sessionEvents || [];
1718
1763
  const activities = [];
1719
- if (trace.sessionEvents && trace.sessionEvents.length > 0) {
1720
- for (const evt of trace.sessionEvents) {
1721
- const name = evt.toolName || evt.name || evt.type;
1722
- if (!name) continue;
1764
+ if (events.length > 0) {
1765
+ for (let i = 0; i < events.length; i++) {
1766
+ const evt = events[i];
1767
+ if (evt.type === "system" || evt.type === "model_change") continue;
1768
+ const dur = evt.duration || 0;
1769
+ const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
1770
+ const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
1771
+ const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
1723
1772
  activities.push({
1724
- name,
1773
+ id: evt.id || `evt-${i}`,
1774
+ name: evt.toolName || evt.name || evt.type,
1725
1775
  type: evt.type,
1726
1776
  status: evt.toolError ? "failed" : "completed",
1727
- duration: evt.duration || 0
1777
+ startTime: startTs,
1778
+ endTime: endTs,
1779
+ parentId: evt.parentId
1728
1780
  });
1729
1781
  }
1730
1782
  } else {
1731
- const nodes2 = trace.nodes || {};
1732
- const sorted = Object.values(nodes2).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1783
+ const sorted = Object.values(nodes).sort(
1784
+ (a, b) => (a.startTime || 0) - (b.startTime || 0)
1785
+ );
1733
1786
  for (const node of sorted) {
1734
1787
  activities.push({
1788
+ id: node.id,
1735
1789
  name: node.name || node.type || node.id,
1736
1790
  type: node.type || "unknown",
1737
1791
  status: node.status || "completed",
1738
- duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
1792
+ startTime: node.startTime || t.startTime,
1793
+ endTime: node.endTime || node.startTime || t.startTime,
1794
+ parentId: node.parentId
1739
1795
  });
1740
1796
  }
1741
1797
  }
1742
- const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
1743
- for (let i = 0; i < seq.length; i++) {
1744
- const act = seq[i];
1745
- activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
1746
- if (i < seq.length - 1) {
1747
- const key = act + " \u2192 " + seq[i + 1];
1748
- transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
1749
- }
1750
- }
1751
- for (const act of activities) {
1752
- if (act.duration > 0) {
1753
- const durs = activityDurations.get(act.name) || [];
1754
- durs.push(act.duration);
1755
- activityDurations.set(act.name, durs);
1756
- }
1757
- const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
1758
- if (act.status === "failed") st.fail++;
1759
- else st.ok++;
1760
- activityStatuses.set(act.name, st);
1761
- }
1762
- }
1763
- const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
1764
- const durs = activityDurations.get(name) || [];
1765
- const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
1766
- const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
1767
1798
  return {
1768
- id: name,
1769
- label: name,
1770
- count,
1771
- frequency: count / totalTraces,
1772
- avgDuration,
1773
- failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
1774
- isVirtual: name === "[START]" || name === "[END]"
1799
+ id: serialized.id || serialized.filename,
1800
+ filename: serialized.filename,
1801
+ name: serialized.name || serialized.filename,
1802
+ agentId: serialized.agentId,
1803
+ trigger: serialized.trigger,
1804
+ status: serialized.status || "completed",
1805
+ sourceType: serialized.sourceType,
1806
+ startTime: serialized.startTime,
1807
+ endTime: serialized.endTime || serialized.startTime,
1808
+ tokenUsage: serialized.tokenUsage,
1809
+ activities
1775
1810
  };
1776
1811
  });
1777
- const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
1778
- const [source, target] = key.split(" \u2192 ");
1779
- return { source, target, count, frequency: count / totalTraces };
1780
- });
1781
- const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
1782
- const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
1783
- res.json({
1784
- agentId,
1785
- totalTraces,
1786
- nodes,
1787
- edges,
1788
- maxEdgeCount,
1789
- maxNodeCount
1790
- });
1812
+ const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
1813
+ const minTime = Math.min(...allTimes);
1814
+ const maxTime = Math.max(...allTimes);
1815
+ res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
1816
+ } catch (error) {
1817
+ console.error("Agent timeline error:", error);
1818
+ res.status(500).json({ error: "Failed to build agent timeline" });
1819
+ }
1820
+ });
1821
+ this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1822
+ try {
1823
+ const agentId = req.params.agentId;
1824
+ const allTraces = this.watcher.getTracesByAgent(agentId);
1825
+ if (allTraces.length === 0) {
1826
+ return res.status(404).json({ error: "No traces for agent" });
1827
+ }
1828
+ const graphs = this.getGraphTraces(agentId);
1829
+ if (graphs.length > 0) {
1830
+ return res.json(this.buildProcessGraphFromCore(agentId, graphs));
1831
+ }
1832
+ return res.json(this.buildProcessGraphLegacy(agentId, allTraces));
1791
1833
  } catch (error) {
1792
1834
  console.error("Process graph error:", error);
1793
1835
  res.status(500).json({ error: "Failed to build process graph" });
1794
1836
  }
1795
1837
  });
1838
+ this.app.get("/api/agents/:agentId/variants", (req, res) => {
1839
+ try {
1840
+ const agentId = req.params.agentId;
1841
+ const graphs = this.getGraphTraces(agentId);
1842
+ if (graphs.length === 0) {
1843
+ return res.json({ agentId, totalTraces: 0, variants: [] });
1844
+ }
1845
+ const variants = (0, import_agentflow_core3.findVariants)(graphs).map((v) => ({
1846
+ pathSignature: v.pathSignature,
1847
+ count: v.count,
1848
+ percentage: v.percentage
1849
+ }));
1850
+ res.json({ agentId, totalTraces: graphs.length, variants });
1851
+ } catch (error) {
1852
+ console.error("Variants error:", error);
1853
+ res.status(500).json({ error: "Failed to compute variants" });
1854
+ }
1855
+ });
1856
+ this.app.get("/api/agents/:agentId/bottlenecks", (req, res) => {
1857
+ try {
1858
+ const agentId = req.params.agentId;
1859
+ const graphs = this.getGraphTraces(agentId);
1860
+ if (graphs.length === 0) {
1861
+ return res.json({ agentId, bottlenecks: [] });
1862
+ }
1863
+ const bottlenecks = (0, import_agentflow_core3.getBottlenecks)(graphs).map((b) => ({
1864
+ nodeName: b.nodeName,
1865
+ nodeType: b.nodeType,
1866
+ occurrences: b.occurrences,
1867
+ durations: b.durations
1868
+ }));
1869
+ res.json({ agentId, bottlenecks });
1870
+ } catch (error) {
1871
+ console.error("Bottlenecks error:", error);
1872
+ res.status(500).json({ error: "Failed to compute bottlenecks" });
1873
+ }
1874
+ });
1875
+ this.app.get("/api/agents/:agentId/profile", (req, res) => {
1876
+ try {
1877
+ const profile = this.knowledgeStore.getAgentProfile(req.params.agentId);
1878
+ if (!profile) {
1879
+ return res.status(404).json({ error: "No profile for agent" });
1880
+ }
1881
+ res.json(profile);
1882
+ } catch (error) {
1883
+ console.error("Profile error:", error);
1884
+ res.status(500).json({ error: "Failed to load agent profile" });
1885
+ }
1886
+ });
1796
1887
  this.app.get("/api/stats/:agentId", (req, res) => {
1797
1888
  try {
1798
1889
  const agentStats = this.stats.getAgentStats(req.params.agentId);
@@ -1800,11 +1891,11 @@ var DashboardServer = class {
1800
1891
  return res.status(404).json({ error: "Agent not found" });
1801
1892
  }
1802
1893
  res.json(agentStats);
1803
- } catch (error) {
1894
+ } catch (_error) {
1804
1895
  res.status(500).json({ error: "Failed to load agent statistics" });
1805
1896
  }
1806
1897
  });
1807
- this.app.get("/api/process-health", (req, res) => {
1898
+ this.app.get("/api/process-health", (_req, res) => {
1808
1899
  try {
1809
1900
  const now = Date.now();
1810
1901
  if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
@@ -1849,9 +1940,11 @@ var DashboardServer = class {
1849
1940
  orphans: uniqueProcesses.filter((p) => {
1850
1941
  var _a;
1851
1942
  const alfredKnownPids = /* @__PURE__ */ new Set();
1852
- if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale) alfredKnownPids.add(alfredResult.pidFile.pid);
1943
+ if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale)
1944
+ alfredKnownPids.add(alfredResult.pidFile.pid);
1853
1945
  if (alfredResult.workers) {
1854
- if (alfredResult.workers.orchestratorPid) alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1946
+ if (alfredResult.workers.orchestratorPid)
1947
+ alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1855
1948
  for (const w of alfredResult.workers.workers) {
1856
1949
  if (w.pid) alfredKnownPids.add(w.pid);
1857
1950
  }
@@ -1870,11 +1963,22 @@ var DashboardServer = class {
1870
1963
  result.problems = [...alfredResult.problems || [], ...openclawProblems];
1871
1964
  this.processHealthCache = { result, ts: now };
1872
1965
  res.json(result);
1873
- } catch (error) {
1966
+ } catch (_error) {
1874
1967
  res.status(500).json({ error: "Failed to audit processes" });
1875
1968
  }
1876
1969
  });
1877
- this.app.get("*", (req, res) => {
1970
+ this.app.get("/health", (_req, res) => {
1971
+ res.json({
1972
+ status: "ok",
1973
+ uptime: process.uptime(),
1974
+ traceCount: this.watcher.getTraceCount(),
1975
+ agentCount: this.watcher.getAgentIds().length
1976
+ });
1977
+ });
1978
+ this.app.get("/ready", (_req, res) => {
1979
+ res.json({ status: "ready" });
1980
+ });
1981
+ this.app.get("*", (_req, res) => {
1878
1982
  const indexPath = path3.join(__dirname, "../public/index.html");
1879
1983
  if (fs3.existsSync(indexPath)) {
1880
1984
  res.sendFile(indexPath);
@@ -1903,6 +2007,189 @@ var DashboardServer = class {
1903
2007
  });
1904
2008
  });
1905
2009
  }
2010
+ /**
2011
+ * Filter an agent's traces to valid ExecutionGraphs and convert via loadGraph().
2012
+ * Returns only traces with proper nodes (Map or non-empty object), skipping session-only traces.
2013
+ */
2014
+ getGraphTraces(agentId) {
2015
+ const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
2016
+ const graphs = [];
2017
+ for (const trace of traces) {
2018
+ try {
2019
+ if (trace.sourceType === "session" || trace.sourceType === "log") continue;
2020
+ if (!trace.rootNodeId && !trace.rootId) continue;
2021
+ const nodes = trace.nodes;
2022
+ if (!nodes || typeof nodes === "object" && Object.keys(nodes).length === 0) continue;
2023
+ const nodeValues = Object.values(nodes);
2024
+ if (nodeValues.some((n) => n.type === "log-file" || n.type === "log-entry")) continue;
2025
+ graphs.push((0, import_agentflow_core3.loadGraph)(trace));
2026
+ } catch {
2027
+ }
2028
+ }
2029
+ return graphs;
2030
+ }
2031
+ /**
2032
+ * Build process graph response using core APIs (discoverProcess + getBottlenecks).
2033
+ * Maps core output to the frontend's expected shape with virtual START/END nodes.
2034
+ */
2035
+ buildProcessGraphFromCore(agentId, graphs) {
2036
+ const model = (0, import_agentflow_core3.discoverProcess)(graphs);
2037
+ const bottleneckList = (0, import_agentflow_core3.getBottlenecks)(graphs);
2038
+ const bottleneckMap = /* @__PURE__ */ new Map();
2039
+ for (const b of bottleneckList) {
2040
+ const key = `${b.nodeType}:${b.nodeName}`;
2041
+ bottleneckMap.set(key, {
2042
+ avgDuration: b.durations.median,
2043
+ failRate: 0,
2044
+ // Not directly available from bottleneck data
2045
+ p95: b.durations.p95
2046
+ });
2047
+ bottleneckMap.set(b.nodeName, {
2048
+ avgDuration: b.durations.median,
2049
+ failRate: 0,
2050
+ p95: b.durations.p95
2051
+ });
2052
+ }
2053
+ const nodes = [];
2054
+ const stepCounts = /* @__PURE__ */ new Map();
2055
+ for (const t of model.transitions) {
2056
+ stepCounts.set(t.from, (stepCounts.get(t.from) ?? 0) + t.count);
2057
+ }
2058
+ for (const step of model.steps) {
2059
+ const count = stepCounts.get(step) ?? model.totalGraphs;
2060
+ const bn = bottleneckMap.get(step);
2061
+ const colonIdx = step.indexOf(":");
2062
+ const label = colonIdx >= 0 ? step.slice(colonIdx + 1) : step;
2063
+ nodes.push({
2064
+ id: step,
2065
+ label,
2066
+ count,
2067
+ frequency: count / model.totalGraphs,
2068
+ avgDuration: (bn == null ? void 0 : bn.avgDuration) ?? 0,
2069
+ failRate: (bn == null ? void 0 : bn.failRate) ?? 0,
2070
+ p95Duration: (bn == null ? void 0 : bn.p95) ?? 0,
2071
+ isVirtual: false
2072
+ });
2073
+ }
2074
+ const rootSteps = new Set(model.steps);
2075
+ const childSteps = new Set(model.transitions.map((t) => t.to));
2076
+ const leafSteps = new Set(model.steps);
2077
+ for (const t of model.transitions) {
2078
+ }
2079
+ nodes.push({ id: "[START]", label: "[START]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
2080
+ nodes.push({ id: "[END]", label: "[END]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
2081
+ const edges = model.transitions.map((t) => ({
2082
+ source: t.from,
2083
+ target: t.to,
2084
+ count: t.count,
2085
+ frequency: t.count / model.totalGraphs
2086
+ }));
2087
+ const targetSteps = new Set(model.transitions.map((t) => t.to));
2088
+ for (const step of model.steps) {
2089
+ if (!targetSteps.has(step)) {
2090
+ edges.push({ source: "[START]", target: step, count: model.totalGraphs, frequency: 1 });
2091
+ }
2092
+ }
2093
+ const sourceSteps = new Set(model.transitions.map((t) => t.from));
2094
+ for (const step of model.steps) {
2095
+ if (!sourceSteps.has(step)) {
2096
+ edges.push({ source: step, target: "[END]", count: model.totalGraphs, frequency: 1 });
2097
+ }
2098
+ }
2099
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
2100
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
2101
+ return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
2102
+ }
2103
+ /**
2104
+ * Legacy process graph computation for session-based traces.
2105
+ * Preserved for backward compatibility with JSONL/LOG traces.
2106
+ */
2107
+ buildProcessGraphLegacy(agentId, allTraces) {
2108
+ const traces = allTraces.map(serializeTrace);
2109
+ const activityCounts = /* @__PURE__ */ new Map();
2110
+ const transitionCounts = /* @__PURE__ */ new Map();
2111
+ const activityDurations = /* @__PURE__ */ new Map();
2112
+ const activityStatuses = /* @__PURE__ */ new Map();
2113
+ let totalTraces = 0;
2114
+ for (const trace of traces) {
2115
+ totalTraces++;
2116
+ const activities = [];
2117
+ if (trace.sessionEvents && trace.sessionEvents.length > 0) {
2118
+ for (const evt of trace.sessionEvents) {
2119
+ const name = evt.toolName || evt.name || evt.type;
2120
+ if (!name) continue;
2121
+ activities.push({
2122
+ name,
2123
+ type: evt.type,
2124
+ status: evt.toolError ? "failed" : "completed",
2125
+ duration: evt.duration || 0
2126
+ });
2127
+ }
2128
+ } else {
2129
+ const nodes2 = trace.nodes || {};
2130
+ const sorted = Object.values(nodes2).sort(
2131
+ (a, b) => (a.startTime || 0) - (b.startTime || 0)
2132
+ );
2133
+ for (const node of sorted) {
2134
+ activities.push({
2135
+ name: node.name || node.type || node.id,
2136
+ type: node.type || "unknown",
2137
+ status: node.status || "completed",
2138
+ duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
2139
+ });
2140
+ }
2141
+ }
2142
+ const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
2143
+ for (let i = 0; i < seq.length; i++) {
2144
+ const act = seq[i];
2145
+ activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
2146
+ if (i < seq.length - 1) {
2147
+ const key = `${act} \u2192 ${seq[i + 1]}`;
2148
+ transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
2149
+ }
2150
+ }
2151
+ for (const act of activities) {
2152
+ if (act.duration > 0) {
2153
+ const durs = activityDurations.get(act.name) || [];
2154
+ durs.push(act.duration);
2155
+ activityDurations.set(act.name, durs);
2156
+ }
2157
+ const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
2158
+ if (act.status === "failed") st.fail++;
2159
+ else st.ok++;
2160
+ activityStatuses.set(act.name, st);
2161
+ }
2162
+ }
2163
+ const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
2164
+ const durs = activityDurations.get(name) || [];
2165
+ const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
2166
+ const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
2167
+ return {
2168
+ id: name,
2169
+ label: name,
2170
+ count,
2171
+ frequency: count / totalTraces,
2172
+ avgDuration,
2173
+ failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
2174
+ p95Duration: 0,
2175
+ isVirtual: name === "[START]" || name === "[END]"
2176
+ };
2177
+ });
2178
+ const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
2179
+ const [source, target] = key.split(" \u2192 ");
2180
+ return { source, target, count, frequency: count / totalTraces };
2181
+ });
2182
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
2183
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
2184
+ return { agentId, totalTraces, nodes, edges, maxEdgeCount, maxNodeCount };
2185
+ }
2186
+ /** Check if a trace is a proper ExecutionGraph (not a synthetic session-based trace). */
2187
+ isGraphTrace(trace) {
2188
+ if (trace.sourceType === "session" || trace.sourceType === "log") return false;
2189
+ if (!trace.rootNodeId && !trace.rootId) return false;
2190
+ const nodes = trace.nodes instanceof Map ? Object.fromEntries(trace.nodes) : trace.nodes;
2191
+ return nodes && typeof nodes === "object" && Object.keys(nodes).length > 0;
2192
+ }
1906
2193
  setupTraceWatcher() {
1907
2194
  this.watcher.on("trace-added", (trace) => {
1908
2195
  this.stats.processTrace(trace);
@@ -1910,6 +2197,14 @@ var DashboardServer = class {
1910
2197
  type: "trace-added",
1911
2198
  data: serializeTrace(trace)
1912
2199
  });
2200
+ if (this.isGraphTrace(trace)) {
2201
+ try {
2202
+ const graph = (0, import_agentflow_core3.loadGraph)(serializeTrace(trace));
2203
+ const event = (0, import_agentflow_core3.createExecutionEvent)(graph);
2204
+ this.knowledgeStore.append(event);
2205
+ } catch {
2206
+ }
2207
+ }
1913
2208
  });
1914
2209
  this.watcher.on("trace-updated", (trace) => {
1915
2210
  this.stats.processTrace(trace);