agentflow-dashboard 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,20 @@
1
1
  // src/server.ts
2
- import express from "express";
3
2
  import * as fs3 from "fs";
4
3
  import { createServer } from "http";
5
4
  import * as path3 from "path";
6
5
  import { fileURLToPath } from "url";
6
+ import {
7
+ auditProcesses,
8
+ createExecutionEvent,
9
+ createKnowledgeStore,
10
+ discoverProcess,
11
+ discoverProcessConfig,
12
+ findVariants,
13
+ getBottlenecks,
14
+ loadGraph as loadGraph2
15
+ } from "agentflow-core";
16
+ import express from "express";
7
17
  import { WebSocketServer } from "ws";
8
- import { discoverProcessConfig, auditProcesses } from "agentflow-core";
9
18
 
10
19
  // src/stats.ts
11
20
  import { getFailures, getHungNodes, getStats } from "agentflow-core";
@@ -183,11 +192,161 @@ var AgentStats = class {
183
192
  };
184
193
 
185
194
  // src/watcher.ts
186
- import { loadGraph } from "agentflow-core";
187
- import chokidar from "chokidar";
188
195
  import { EventEmitter } from "events";
189
196
  import * as fs from "fs";
190
197
  import * as path from "path";
198
+ import { loadGraph } from "agentflow-core";
199
+ import chokidar from "chokidar";
200
+
201
+ // src/parsers/log-utils.ts
202
+ function stripAnsi(str) {
203
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
204
+ }
205
+ function parseValue(value) {
206
+ if (value.match(/^\d+$/)) return parseInt(value, 10);
207
+ if (value.match(/^\d+\.\d+$/)) return parseFloat(value);
208
+ if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
209
+ if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
210
+ return value;
211
+ }
212
+ function parseTimestamp(value) {
213
+ if (!value) return null;
214
+ if (typeof value === "number") return value;
215
+ try {
216
+ return new Date(value).getTime();
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ function extractTimestamp(line) {
222
+ const clean = stripAnsi(line);
223
+ const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
224
+ if (isoMatch) return new Date(isoMatch[1]).getTime();
225
+ return null;
226
+ }
227
+ function extractLogLevel(line) {
228
+ const clean = stripAnsi(line);
229
+ const levelMatch = clean.match(/\b(debug|info|warn|warning|error|fatal|trace)\b/i);
230
+ return levelMatch ? levelMatch[1].trim().toLowerCase() : null;
231
+ }
232
+ function extractAction(line) {
233
+ const clean = stripAnsi(line);
234
+ const actionMatch = clean.match(/\]\s+(\S+)/);
235
+ if (actionMatch) return actionMatch[1].trim();
236
+ const afterLevel = clean.replace(/^.*?(debug|info|warn|warning|error|fatal|trace)\s*\]?\s*/i, "");
237
+ return afterLevel.split(/\s+/)[0] || "";
238
+ }
239
+ function extractKeyValuePairs(line) {
240
+ const pairs = {};
241
+ const clean = stripAnsi(line);
242
+ const kvRegex = /(\w+)=('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|\S+)/g;
243
+ let match;
244
+ while ((match = kvRegex.exec(clean)) !== null) {
245
+ if (match[1] === "Z" || match[1] === "m") continue;
246
+ pairs[match[1]] = parseValue(match[2]);
247
+ }
248
+ return pairs;
249
+ }
250
+ function detectComponent(action, kvPairs) {
251
+ if (action.includes(".")) return action.split(".")[0];
252
+ if (kvPairs.component) return String(kvPairs.component);
253
+ if (kvPairs.service) return String(kvPairs.service);
254
+ if (kvPairs.module) return String(kvPairs.module);
255
+ if (kvPairs.worker) return String(kvPairs.worker);
256
+ return action || "unknown";
257
+ }
258
+ function detectOperation(action, kvPairs) {
259
+ if (action.includes(".")) return action.split(".").slice(1).join(".");
260
+ if (kvPairs.operation) return String(kvPairs.operation);
261
+ if (kvPairs.method) return String(kvPairs.method);
262
+ if (kvPairs.action) return String(kvPairs.action);
263
+ return action || "activity";
264
+ }
265
+ function detectActivityPattern(line) {
266
+ let timestamp = extractTimestamp(line);
267
+ let level = extractLogLevel(line);
268
+ let action = extractAction(line);
269
+ let kvPairs = extractKeyValuePairs(line);
270
+ if (!timestamp) {
271
+ const jsonMatch = line.match(/\{.*\}/);
272
+ if (jsonMatch) {
273
+ try {
274
+ const parsed = JSON.parse(jsonMatch[0]);
275
+ timestamp = parseTimestamp(parsed.timestamp || parsed.time || parsed.ts) || Date.now();
276
+ level = parsed.level || parsed.severity || "info";
277
+ action = parsed.action || parsed.event || parsed.message || "";
278
+ kvPairs = parsed;
279
+ } catch {
280
+ }
281
+ }
282
+ }
283
+ if (!timestamp) {
284
+ const kvMatches = line.match(/(\w+)=([^\s]+)/g);
285
+ if (kvMatches && kvMatches.length >= 2) {
286
+ const pairs = {};
287
+ for (const m of kvMatches) {
288
+ const [key, value] = m.split("=", 2);
289
+ pairs[key] = parseValue(value);
290
+ }
291
+ timestamp = parseTimestamp(pairs.timestamp || pairs.time) || Date.now();
292
+ level = String(pairs.level || "info");
293
+ action = String(pairs.action || pairs.event || "");
294
+ kvPairs = pairs;
295
+ }
296
+ }
297
+ if (!timestamp) {
298
+ const logMatch = line.match(
299
+ /^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)\s+(\w+)?\s*:?\s*(.+)/
300
+ );
301
+ if (logMatch) {
302
+ timestamp = new Date(logMatch[1]).getTime();
303
+ level = logMatch[2] || "info";
304
+ action = logMatch[3] || "";
305
+ }
306
+ }
307
+ if (!timestamp) return null;
308
+ return {
309
+ timestamp,
310
+ level: (level == null ? void 0 : level.toLowerCase()) || "info",
311
+ action,
312
+ component: detectComponent(action, kvPairs),
313
+ operation: detectOperation(action, kvPairs),
314
+ ...kvPairs
315
+ };
316
+ }
317
+ function extractSessionIdentifier(activity) {
318
+ return String(
319
+ activity.session_id || activity.run_id || activity.request_id || activity.trace_id || activity.sweep_id || activity.transaction_id || "default"
320
+ );
321
+ }
322
+ function detectTrigger(activity) {
323
+ if (activity.trigger) return String(activity.trigger);
324
+ if (activity.method && activity.url) return "api-call";
325
+ if (typeof activity.operation === "string" && activity.operation.includes("start"))
326
+ return "startup";
327
+ if (typeof activity.operation === "string" && activity.operation.includes("invoke"))
328
+ return "invocation";
329
+ return "event";
330
+ }
331
+ function getUniversalNodeStatus(activity) {
332
+ if (activity.level === "error" || activity.level === "fatal") return "failed";
333
+ if (activity.level === "warn" || activity.level === "warning") return "warning";
334
+ const op = String(activity.operation || "");
335
+ if (op.match(/start|begin|init/i)) return "running";
336
+ if (op.match(/complete|finish|end|done/i)) return "completed";
337
+ return "completed";
338
+ }
339
+ function openClawSessionIdToAgent(sessionId) {
340
+ if (sessionId.startsWith("janitor-")) return "vault-janitor";
341
+ if (sessionId.startsWith("curator-")) return "vault-curator";
342
+ if (sessionId.startsWith("distiller-")) return "vault-distiller";
343
+ if (sessionId.startsWith("main-")) return "main";
344
+ const firstSegment = sessionId.split("-")[0];
345
+ if (firstSegment) return firstSegment;
346
+ return "openclaw";
347
+ }
348
+
349
+ // src/watcher.ts
191
350
  var TraceWatcher = class _TraceWatcher extends EventEmitter {
192
351
  watchers = [];
193
352
  traces = /* @__PURE__ */ new Map();
@@ -227,7 +386,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
227
386
  console.error(`Error scanning directory ${dir}:`, error);
228
387
  }
229
388
  }
230
- console.log(`Scanned ${totalDirectories} directories (recursive), loaded ${this.traces.size} items from ${totalFiles} files`);
389
+ console.log(
390
+ `Scanned ${totalDirectories} directories (recursive), loaded ${this.traces.size} items from ${totalFiles} files`
391
+ );
231
392
  }
232
393
  /** Recursively scan directory for supported file types */
233
394
  scanDirectoryRecursive(dir, depth = 0) {
@@ -269,7 +430,14 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
269
430
  "models.json",
270
431
  "config.json"
271
432
  ]);
272
- static SKIP_SUFFIXES = ["-state.json", "-config.json", "-watch-state.json", ".tmp", ".bak", ".backup"];
433
+ static SKIP_SUFFIXES = [
434
+ "-state.json",
435
+ "-config.json",
436
+ "-watch-state.json",
437
+ ".tmp",
438
+ ".bak",
439
+ ".backup"
440
+ ];
273
441
  /** Load a .json trace, .jsonl session file, or .log file. */
274
442
  loadFile(filePath) {
275
443
  const filename = path.basename(filePath);
@@ -320,16 +488,16 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
320
488
  const lines = content.split("\n").filter((line) => line.trim());
321
489
  const activities = /* @__PURE__ */ new Map();
322
490
  for (const line of lines) {
323
- const activity = this.detectActivityPattern(line);
491
+ const activity = detectActivityPattern(line);
324
492
  if (!activity) continue;
325
- const sessionId = this.extractSessionIdentifier(activity);
493
+ const sessionId = extractSessionIdentifier(activity);
326
494
  if (!activities.has(sessionId)) {
327
495
  activities.set(sessionId, {
328
496
  id: sessionId,
329
497
  rootNodeId: "",
330
498
  agentId: this.detectAgentIdentifier(activity, filename, filePath),
331
499
  name: this.generateActivityName(activity, sessionId),
332
- trigger: this.detectTrigger(activity),
500
+ trigger: detectTrigger(activity),
333
501
  startTime: activity.timestamp,
334
502
  endTime: activity.timestamp,
335
503
  status: "completed",
@@ -352,7 +520,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
352
520
  (session) => Object.keys(session.nodes).length > 0
353
521
  );
354
522
  for (const trace of traces) {
355
- const sortedNodes = Object.values(trace.nodes).sort((a, b) => a.startTime - b.startTime);
523
+ const sortedNodes = Object.values(trace.nodes).sort(
524
+ (a, b) => a.startTime - b.startTime
525
+ );
356
526
  trace.sessionEvents = sortedNodes.map((node) => ({
357
527
  type: node.status === "failed" ? "tool_result" : "system",
358
528
  timestamp: node.startTime,
@@ -370,7 +540,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
370
540
  id: "",
371
541
  rootNodeId: "root",
372
542
  nodes: {
373
- "root": {
543
+ root: {
374
544
  id: "root",
375
545
  type: "log-file",
376
546
  name: filename,
@@ -393,125 +563,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
393
563
  }
394
564
  return traces;
395
565
  }
396
- /** Detect activity patterns in log lines using universal heuristics */
397
- detectActivityPattern(line) {
398
- let timestamp = this.extractTimestamp(line);
399
- let level = this.extractLogLevel(line);
400
- let action = this.extractAction(line);
401
- let kvPairs = this.extractKeyValuePairs(line);
402
- if (!timestamp) {
403
- const jsonMatch = line.match(/\{.*\}/);
404
- if (jsonMatch) {
405
- try {
406
- const parsed = JSON.parse(jsonMatch[0]);
407
- timestamp = this.parseTimestamp(parsed.timestamp || parsed.time || parsed.ts) || Date.now();
408
- level = parsed.level || parsed.severity || "info";
409
- action = parsed.action || parsed.event || parsed.message || "";
410
- kvPairs = parsed;
411
- } catch {
412
- }
413
- }
414
- }
415
- if (!timestamp) {
416
- const kvMatches = line.match(/(\w+)=([^\s]+)/g);
417
- if (kvMatches && kvMatches.length >= 2) {
418
- const pairs = {};
419
- kvMatches.forEach((match) => {
420
- const [key, value] = match.split("=", 2);
421
- pairs[key] = this.parseValue(value);
422
- });
423
- timestamp = this.parseTimestamp(pairs.timestamp || pairs.time) || Date.now();
424
- level = pairs.level || "info";
425
- action = pairs.action || pairs.event || "";
426
- kvPairs = pairs;
427
- }
428
- }
429
- if (!timestamp) {
430
- const logMatch = line.match(/^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)\s+(\w+)?\s*:?\s*(.+)/);
431
- if (logMatch) {
432
- timestamp = new Date(logMatch[1]).getTime();
433
- level = logMatch[2] || "info";
434
- action = logMatch[3] || "";
435
- }
436
- }
437
- if (!timestamp) return null;
438
- return {
439
- timestamp,
440
- level: (level == null ? void 0 : level.toLowerCase()) || "info",
441
- action,
442
- component: this.detectComponent(action, kvPairs),
443
- operation: this.detectOperation(action, kvPairs),
444
- ...kvPairs
445
- };
446
- }
447
- /** Strip ANSI escape codes from a string. */
448
- stripAnsi(str) {
449
- return str.replace(/\x1b\[[0-9;]*m/g, "");
450
- }
451
- extractTimestamp(line) {
452
- const clean = this.stripAnsi(line);
453
- const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
454
- if (isoMatch) return new Date(isoMatch[1]).getTime();
455
- return null;
456
- }
457
- extractLogLevel(line) {
458
- const clean = this.stripAnsi(line);
459
- const levelMatch = clean.match(/\b(debug|info|warn|warning|error|fatal|trace)\b/i);
460
- return levelMatch ? levelMatch[1].trim().toLowerCase() : null;
461
- }
462
- extractAction(line) {
463
- const clean = this.stripAnsi(line);
464
- const actionMatch = clean.match(/\]\s+(\S+)/);
465
- if (actionMatch) return actionMatch[1].trim();
466
- const afterLevel = clean.replace(/^.*?(debug|info|warn|warning|error|fatal|trace)\s*\]?\s*/i, "");
467
- return afterLevel.split(/\s+/)[0] || "";
468
- }
469
- extractKeyValuePairs(line) {
470
- const pairs = {};
471
- const clean = this.stripAnsi(line);
472
- const kvRegex = /(\w+)=('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|\S+)/g;
473
- let match;
474
- while ((match = kvRegex.exec(clean)) !== null) {
475
- if (match[1] === "Z" || match[1] === "m") continue;
476
- pairs[match[1]] = this.parseValue(match[2]);
477
- }
478
- return pairs;
479
- }
480
- parseValue(value) {
481
- if (value.match(/^\d+$/)) return parseInt(value);
482
- if (value.match(/^\d+\.\d+$/)) return parseFloat(value);
483
- if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
484
- if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
485
- return value;
486
- }
487
- parseTimestamp(value) {
488
- if (!value) return null;
489
- if (typeof value === "number") return value;
490
- try {
491
- return new Date(value).getTime();
492
- } catch {
493
- return null;
494
- }
495
- }
496
- detectComponent(action, kvPairs) {
497
- if (action.includes(".")) return action.split(".")[0];
498
- if (kvPairs.component) return kvPairs.component;
499
- if (kvPairs.service) return kvPairs.service;
500
- if (kvPairs.module) return kvPairs.module;
501
- if (kvPairs.worker) return kvPairs.worker;
502
- return action || "unknown";
503
- }
504
- detectOperation(action, kvPairs) {
505
- if (action.includes(".")) return action.split(".").slice(1).join(".");
506
- if (kvPairs.operation) return kvPairs.operation;
507
- if (kvPairs.method) return kvPairs.method;
508
- if (kvPairs.action) return kvPairs.action;
509
- return action || "activity";
510
- }
511
- extractSessionIdentifier(activity) {
512
- return activity.session_id || activity.run_id || activity.request_id || activity.trace_id || activity.sweep_id || activity.transaction_id || "default";
513
- }
514
- detectAgentIdentifier(activity, filename, filePath) {
566
+ detectAgentIdentifier(activity, _filename, filePath) {
515
567
  if (activity.agent_id) {
516
568
  const agentId = activity.agent_id;
517
569
  if (agentId.startsWith("vault-")) return agentId;
@@ -555,14 +607,6 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
555
607
  const operation = activity.operation !== "activity" ? `: ${activity.operation}` : "";
556
608
  return `${component}${operation} (${sessionId})`;
557
609
  }
558
- detectTrigger(activity) {
559
- var _a, _b;
560
- if (activity.trigger) return activity.trigger;
561
- if (activity.method && activity.url) return "api-call";
562
- if ((_a = activity.operation) == null ? void 0 : _a.includes("start")) return "startup";
563
- if ((_b = activity.operation) == null ? void 0 : _b.includes("invoke")) return "invocation";
564
- return "event";
565
- }
566
610
  addActivityNode(session, activity) {
567
611
  const nodeId = `${activity.component}-${activity.operation}`;
568
612
  if (session.nodes[nodeId]) {
@@ -580,7 +624,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
580
624
  id: nodeId,
581
625
  type: activity.component,
582
626
  name: `${activity.component}: ${activity.operation}`,
583
- status: this.getUniversalNodeStatus(activity),
627
+ status: getUniversalNodeStatus(activity),
584
628
  startTime: activity.timestamp,
585
629
  endTime: activity.timestamp,
586
630
  children: [],
@@ -591,17 +635,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
591
635
  session.rootNodeId = nodeId;
592
636
  }
593
637
  }
594
- getUniversalNodeStatus(activity) {
595
- var _a, _b;
596
- if (activity.level === "error" || activity.level === "fatal") return "failed";
597
- if (activity.level === "warn" || activity.level === "warning") return "warning";
598
- if ((_a = activity.operation) == null ? void 0 : _a.match(/start|begin|init/i)) return "running";
599
- if ((_b = activity.operation) == null ? void 0 : _b.match(/complete|finish|end|done/i)) return "completed";
600
- return "completed";
601
- }
602
638
  /** Parse OpenClaw tslog-format log files with session run results. */
603
639
  loadOpenClawLogFile(content, filename, filePath, stats) {
604
- var _a, _b, _c, _d;
640
+ var _a, _b, _c, _d, _e, _f;
605
641
  const lines = content.split("\n").filter((l) => l.trim());
606
642
  const sessions = /* @__PURE__ */ new Map();
607
643
  for (const line of lines) {
@@ -613,13 +649,13 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
613
649
  if ((inner == null ? void 0 : inner.payloads) && ((_a = inner == null ? void 0 : inner.meta) == null ? void 0 : _a.agentMeta)) {
614
650
  const agentMeta = inner.meta.agentMeta;
615
651
  const sessionId = agentMeta.sessionId || "unknown";
616
- const agentName = this.openClawSessionIdToAgent(sessionId);
652
+ const agentName = openClawSessionIdToAgent(sessionId);
617
653
  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();
618
654
  const texts = (inner.payloads || []).map((p) => p.text || "").filter(Boolean);
619
655
  if (!sessions.has(sessionId)) {
620
656
  sessions.set(sessionId, { entries: [] });
621
657
  }
622
- sessions.get(sessionId).entries.push({
658
+ (_c = sessions.get(sessionId)) == null ? void 0 : _c.entries.push({
623
659
  text: texts.join("\n"),
624
660
  timestamp,
625
661
  sessionId,
@@ -634,16 +670,16 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
634
670
  } catch {
635
671
  }
636
672
  }
637
- if (parsed.payloads && ((_c = parsed.meta) == null ? void 0 : _c.agentMeta)) {
673
+ if (parsed.payloads && ((_d = parsed.meta) == null ? void 0 : _d.agentMeta)) {
638
674
  const agentMeta = parsed.meta.agentMeta;
639
675
  const sessionId = agentMeta.sessionId || "unknown";
640
- const agentName = this.openClawSessionIdToAgent(sessionId);
641
- 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();
676
+ const agentName = openClawSessionIdToAgent(sessionId);
677
+ 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();
642
678
  const texts = (parsed.payloads || []).map((p) => p.text || "").filter(Boolean);
643
679
  if (!sessions.has(sessionId)) {
644
680
  sessions.set(sessionId, { entries: [] });
645
681
  }
646
- sessions.get(sessionId).entries.push({
682
+ (_f = sessions.get(sessionId)) == null ? void 0 : _f.entries.push({
647
683
  text: texts.join("\n"),
648
684
  timestamp,
649
685
  sessionId,
@@ -722,7 +758,11 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
722
758
  content: e.text,
723
759
  model: e.model,
724
760
  provider: e.provider,
725
- tokens: { input: e.usage.input || 0, output: e.usage.output || 0, total: e.usage.total || 0 },
761
+ tokens: {
762
+ input: e.usage.input || 0,
763
+ output: e.usage.output || 0,
764
+ total: e.usage.total || 0
765
+ },
726
766
  duration: e.durationMs,
727
767
  id: `entry-${idx}`
728
768
  }));
@@ -743,7 +783,12 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
743
783
  sourceType: "session",
744
784
  sourceDir: path.dirname(filePath),
745
785
  sessionEvents,
746
- tokenUsage: { input: totalInput, output: totalOutput, total: totalTokens || totalInput + totalOutput, cost: 0 },
786
+ tokenUsage: {
787
+ input: totalInput,
788
+ output: totalOutput,
789
+ total: totalTokens || totalInput + totalOutput,
790
+ cost: 0
791
+ },
747
792
  metadata: {
748
793
  provider: firstEntry.provider,
749
794
  model: firstEntry.model,
@@ -757,16 +802,6 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
757
802
  }
758
803
  return traceIndex > 0;
759
804
  }
760
- /** Map OpenClaw sessionId prefix to agent name. */
761
- openClawSessionIdToAgent(sessionId) {
762
- if (sessionId.startsWith("janitor-")) return "vault-janitor";
763
- if (sessionId.startsWith("curator-")) return "vault-curator";
764
- if (sessionId.startsWith("distiller-")) return "vault-distiller";
765
- if (sessionId.startsWith("main-")) return "main";
766
- const firstSegment = sessionId.split("-")[0];
767
- if (firstSegment) return firstSegment;
768
- return "openclaw";
769
- }
770
805
  loadTraceFile(filePath) {
771
806
  try {
772
807
  const content = fs.readFileSync(filePath, "utf8");
@@ -905,9 +940,6 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
905
940
  } else {
906
941
  agentId = parentDir;
907
942
  }
908
- if (filePath.includes(".openclaw/") && !agentId.startsWith("openclaw-")) {
909
- agentId = `openclaw-${agentId}`;
910
- }
911
943
  if (filePath.includes(".alfred/") || filePath.includes("alfred")) {
912
944
  if (!agentId.startsWith("alfred-")) {
913
945
  agentId = `alfred-${agentId}`;
@@ -924,10 +956,12 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
924
956
  if (ts > lastTimestamp) lastTimestamp = ts;
925
957
  }
926
958
  }
927
- const firstMessage = rawEvents.find((e) => {
928
- var _a2;
929
- return e.type === "message" && ((_a2 = e.message) == null ? void 0 : _a2.role) === "user";
930
- });
959
+ const firstMessage = rawEvents.find(
960
+ (e) => {
961
+ var _a2;
962
+ return e.type === "message" && ((_a2 = e.message) == null ? void 0 : _a2.role) === "user";
963
+ }
964
+ );
931
965
  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) || "";
932
966
  const cronMatch = userPrompt.match(/\[cron:(\S+)\s+([^\]]+)\]/);
933
967
  const triggerName = cronMatch ? cronMatch[2] : "";
@@ -1001,7 +1035,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
1001
1035
  nodes.set(spawnId, {
1002
1036
  id: spawnId,
1003
1037
  type: "subagent",
1004
- name: "Subagent: " + (((_f = evt.data) == null ? void 0 : _f.sessionId) || "").slice(0, 12),
1038
+ name: `Subagent: ${(((_f = evt.data) == null ? void 0 : _f.sessionId) || "").slice(0, 12)}`,
1005
1039
  startTime: evtTs,
1006
1040
  endTime: evtTs,
1007
1041
  status: "completed",
@@ -1125,7 +1159,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
1125
1159
  id: evt.id,
1126
1160
  parentId: toolCallId
1127
1161
  });
1128
- for (const [nodeId, node] of nodes) {
1162
+ for (const [_nodeId, node] of nodes) {
1129
1163
  if (node.type === "tool" && ((_k = node.metadata) == null ? void 0 : _k.toolCallId) === toolCallId) {
1130
1164
  node.endTime = evtTs;
1131
1165
  node.status = hasError ? "failed" : "completed";
@@ -1293,7 +1327,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
1293
1327
  if (filePath.startsWith(dir)) {
1294
1328
  const dirParts = dir.split(path.sep).filter(Boolean);
1295
1329
  const dirSuffix = dirParts.slice(-2).join("/");
1296
- return path.relative(dir, filePath).replace(/\\/g, "/") + "@" + dirSuffix;
1330
+ return `${path.relative(dir, filePath).replace(/\\/g, "/")}@${dirSuffix}`;
1297
1331
  }
1298
1332
  }
1299
1333
  return filePath;
@@ -1362,7 +1396,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
1362
1396
  });
1363
1397
  this.watchers.push(watcher);
1364
1398
  }
1365
- console.log(`Watching ${this.allWatchDirs.length} directories recursively for JSON/JSONL/LOG/TRACE files`);
1399
+ console.log(
1400
+ `Watching ${this.allWatchDirs.length} directories recursively for JSON/JSONL/LOG/TRACE files`
1401
+ );
1366
1402
  }
1367
1403
  getAllTraces() {
1368
1404
  return Array.from(this.traces.values()).sort((a, b) => {
@@ -1463,7 +1499,8 @@ function printBanner(config, traceCount, stats) {
1463
1499
 
1464
1500
  Tabs: \u{1F3AF} Graph \xB7 \u23F1\uFE0F Timeline \xB7 \u{1F4CA} Metrics \xB7 \u{1F6E0}\uFE0F Process Health \xB7 \u26A0\uFE0F Errors
1465
1501
 
1466
- Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? "\n Data dirs: " + config.dataDirs.join("\n ") : ""}
1502
+ Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? `
1503
+ Data dirs: ${config.dataDirs.join("\n ")}` : ""}
1467
1504
  Loaded: ${traceCount} traces \xB7 ${stats.totalAgents} agents \xB7 ${stats.totalExecutions} executions
1468
1505
  Success: ${stats.globalSuccessRate.toFixed(1)}%${stats.activeAgents > 0 ? ` \xB7 ${stats.activeAgents} active now` : ""}
1469
1506
  CORS: ${config.enableCors ? "enabled" : "disabled"}
@@ -1485,7 +1522,7 @@ async function startDashboard() {
1485
1522
  switch (args[i]) {
1486
1523
  case "--port":
1487
1524
  case "-p":
1488
- config.port = parseInt(args[++i]) || 3e3;
1525
+ config.port = parseInt(args[++i], 10) || 3e3;
1489
1526
  break;
1490
1527
  case "--traces":
1491
1528
  case "-t":
@@ -1588,23 +1625,41 @@ var DashboardServer = class {
1588
1625
  dataDirs: config.dataDirs
1589
1626
  });
1590
1627
  this.stats = new AgentStats();
1628
+ this.knowledgeStore = createKnowledgeStore({
1629
+ baseDir: path3.join(config.tracesDir, "..", ".agentflow", "knowledge")
1630
+ });
1591
1631
  this.setupExpress();
1592
1632
  this.setupWebSocket();
1593
1633
  this.setupTraceWatcher();
1634
+ let knowledgeCount = 0;
1594
1635
  for (const trace of this.watcher.getAllTraces()) {
1595
1636
  this.stats.processTrace(trace);
1637
+ if (this.isGraphTrace(trace)) {
1638
+ try {
1639
+ const graph = loadGraph2(serializeTrace(trace));
1640
+ const event = createExecutionEvent(graph);
1641
+ this.knowledgeStore.append(event);
1642
+ knowledgeCount++;
1643
+ } catch {
1644
+ }
1645
+ }
1596
1646
  }
1597
1647
  console.log(`Processed ${this.watcher.getTraceCount()} existing traces for stats`);
1648
+ console.log(`Persisted ${knowledgeCount} graph traces to knowledge store`);
1598
1649
  }
1599
1650
  app = express();
1600
1651
  server = createServer(this.app);
1601
1652
  wss = new WebSocketServer({ server: this.server });
1602
1653
  watcher;
1603
1654
  stats;
1604
- processHealthCache = { result: null, ts: 0 };
1655
+ processHealthCache = {
1656
+ result: null,
1657
+ ts: 0
1658
+ };
1659
+ knowledgeStore;
1605
1660
  setupExpress() {
1606
1661
  if (this.config.enableCors) {
1607
- this.app.use((req, res, next) => {
1662
+ this.app.use((_req, res, next) => {
1608
1663
  res.header("Access-Control-Allow-Origin", "*");
1609
1664
  res.header(
1610
1665
  "Access-Control-Allow-Headers",
@@ -1617,11 +1672,11 @@ var DashboardServer = class {
1617
1672
  if (fs3.existsSync(publicDir)) {
1618
1673
  this.app.use(express.static(publicDir));
1619
1674
  }
1620
- this.app.get("/api/traces", (req, res) => {
1675
+ this.app.get("/api/traces", (_req, res) => {
1621
1676
  try {
1622
1677
  const traces = this.watcher.getAllTraces().map(serializeTrace);
1623
1678
  res.json(traces);
1624
- } catch (error) {
1679
+ } catch (_error) {
1625
1680
  res.status(500).json({ error: "Failed to load traces" });
1626
1681
  }
1627
1682
  });
@@ -1632,7 +1687,7 @@ var DashboardServer = class {
1632
1687
  return res.status(404).json({ error: "Trace not found" });
1633
1688
  }
1634
1689
  res.json(serializeTrace(trace));
1635
- } catch (error) {
1690
+ } catch (_error) {
1636
1691
  res.status(500).json({ error: "Failed to load trace" });
1637
1692
  }
1638
1693
  });
@@ -1647,30 +1702,30 @@ var DashboardServer = class {
1647
1702
  tokenUsage: trace.tokenUsage || null,
1648
1703
  sourceType: trace.sourceType || "trace"
1649
1704
  });
1650
- } catch (error) {
1705
+ } catch (_error) {
1651
1706
  res.status(500).json({ error: "Failed to load trace events" });
1652
1707
  }
1653
1708
  });
1654
- this.app.get("/api/agents", (req, res) => {
1709
+ this.app.get("/api/agents", (_req, res) => {
1655
1710
  try {
1656
1711
  const agents = this.stats.getAgentsList();
1657
1712
  res.json(agents);
1658
- } catch (error) {
1713
+ } catch (_error) {
1659
1714
  res.status(500).json({ error: "Failed to load agents" });
1660
1715
  }
1661
1716
  });
1662
- this.app.get("/api/stats", (req, res) => {
1717
+ this.app.get("/api/stats", (_req, res) => {
1663
1718
  try {
1664
1719
  const globalStats = this.stats.getGlobalStats();
1665
1720
  res.json(globalStats);
1666
- } catch (error) {
1721
+ } catch (_error) {
1667
1722
  res.status(500).json({ error: "Failed to load statistics" });
1668
1723
  }
1669
1724
  });
1670
1725
  this.app.get("/api/agents/:agentId/timeline", (req, res) => {
1671
1726
  try {
1672
1727
  const agentId = req.params.agentId;
1673
- const limit = Math.min(parseInt(req.query.limit) || 50, 200);
1728
+ const limit = Math.min(parseInt(req.query.limit, 10) || 50, 200);
1674
1729
  const rawTraces = this.watcher.getTracesByAgent(agentId);
1675
1730
  if (rawTraces.length === 0) {
1676
1731
  return res.status(404).json({ error: "No traces for agent" });
@@ -1700,7 +1755,9 @@ var DashboardServer = class {
1700
1755
  });
1701
1756
  }
1702
1757
  } else {
1703
- const sorted = Object.values(nodes).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1758
+ const sorted = Object.values(nodes).sort(
1759
+ (a, b) => (a.startTime || 0) - (b.startTime || 0)
1760
+ );
1704
1761
  for (const node of sorted) {
1705
1762
  activities.push({
1706
1763
  id: node.id,
@@ -1739,95 +1796,69 @@ var DashboardServer = class {
1739
1796
  this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
1740
1797
  try {
1741
1798
  const agentId = req.params.agentId;
1742
- const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
1743
- if (traces.length === 0) {
1799
+ const allTraces = this.watcher.getTracesByAgent(agentId);
1800
+ if (allTraces.length === 0) {
1744
1801
  return res.status(404).json({ error: "No traces for agent" });
1745
1802
  }
1746
- const activityCounts = /* @__PURE__ */ new Map();
1747
- const transitionCounts = /* @__PURE__ */ new Map();
1748
- const activityDurations = /* @__PURE__ */ new Map();
1749
- const activityStatuses = /* @__PURE__ */ new Map();
1750
- let totalTraces = 0;
1751
- for (const trace of traces) {
1752
- totalTraces++;
1753
- const activities = [];
1754
- if (trace.sessionEvents && trace.sessionEvents.length > 0) {
1755
- for (const evt of trace.sessionEvents) {
1756
- const name = evt.toolName || evt.name || evt.type;
1757
- if (!name) continue;
1758
- activities.push({
1759
- name,
1760
- type: evt.type,
1761
- status: evt.toolError ? "failed" : "completed",
1762
- duration: evt.duration || 0
1763
- });
1764
- }
1765
- } else {
1766
- const nodes2 = trace.nodes || {};
1767
- const sorted = Object.values(nodes2).sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
1768
- for (const node of sorted) {
1769
- activities.push({
1770
- name: node.name || node.type || node.id,
1771
- type: node.type || "unknown",
1772
- status: node.status || "completed",
1773
- duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
1774
- });
1775
- }
1776
- }
1777
- const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
1778
- for (let i = 0; i < seq.length; i++) {
1779
- const act = seq[i];
1780
- activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
1781
- if (i < seq.length - 1) {
1782
- const key = act + " \u2192 " + seq[i + 1];
1783
- transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
1784
- }
1785
- }
1786
- for (const act of activities) {
1787
- if (act.duration > 0) {
1788
- const durs = activityDurations.get(act.name) || [];
1789
- durs.push(act.duration);
1790
- activityDurations.set(act.name, durs);
1791
- }
1792
- const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
1793
- if (act.status === "failed") st.fail++;
1794
- else st.ok++;
1795
- activityStatuses.set(act.name, st);
1796
- }
1803
+ const graphs = this.getGraphTraces(agentId);
1804
+ if (graphs.length > 0) {
1805
+ return res.json(this.buildProcessGraphFromCore(agentId, graphs));
1797
1806
  }
1798
- const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
1799
- const durs = activityDurations.get(name) || [];
1800
- const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
1801
- const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
1802
- return {
1803
- id: name,
1804
- label: name,
1805
- count,
1806
- frequency: count / totalTraces,
1807
- avgDuration,
1808
- failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
1809
- isVirtual: name === "[START]" || name === "[END]"
1810
- };
1811
- });
1812
- const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
1813
- const [source, target] = key.split(" \u2192 ");
1814
- return { source, target, count, frequency: count / totalTraces };
1815
- });
1816
- const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
1817
- const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
1818
- res.json({
1819
- agentId,
1820
- totalTraces,
1821
- nodes,
1822
- edges,
1823
- maxEdgeCount,
1824
- maxNodeCount
1825
- });
1807
+ return res.json(this.buildProcessGraphLegacy(agentId, allTraces));
1826
1808
  } catch (error) {
1827
1809
  console.error("Process graph error:", error);
1828
1810
  res.status(500).json({ error: "Failed to build process graph" });
1829
1811
  }
1830
1812
  });
1813
+ this.app.get("/api/agents/:agentId/variants", (req, res) => {
1814
+ try {
1815
+ const agentId = req.params.agentId;
1816
+ const graphs = this.getGraphTraces(agentId);
1817
+ if (graphs.length === 0) {
1818
+ return res.json({ agentId, totalTraces: 0, variants: [] });
1819
+ }
1820
+ const variants = findVariants(graphs).map((v) => ({
1821
+ pathSignature: v.pathSignature,
1822
+ count: v.count,
1823
+ percentage: v.percentage
1824
+ }));
1825
+ res.json({ agentId, totalTraces: graphs.length, variants });
1826
+ } catch (error) {
1827
+ console.error("Variants error:", error);
1828
+ res.status(500).json({ error: "Failed to compute variants" });
1829
+ }
1830
+ });
1831
+ this.app.get("/api/agents/:agentId/bottlenecks", (req, res) => {
1832
+ try {
1833
+ const agentId = req.params.agentId;
1834
+ const graphs = this.getGraphTraces(agentId);
1835
+ if (graphs.length === 0) {
1836
+ return res.json({ agentId, bottlenecks: [] });
1837
+ }
1838
+ const bottlenecks = getBottlenecks(graphs).map((b) => ({
1839
+ nodeName: b.nodeName,
1840
+ nodeType: b.nodeType,
1841
+ occurrences: b.occurrences,
1842
+ durations: b.durations
1843
+ }));
1844
+ res.json({ agentId, bottlenecks });
1845
+ } catch (error) {
1846
+ console.error("Bottlenecks error:", error);
1847
+ res.status(500).json({ error: "Failed to compute bottlenecks" });
1848
+ }
1849
+ });
1850
+ this.app.get("/api/agents/:agentId/profile", (req, res) => {
1851
+ try {
1852
+ const profile = this.knowledgeStore.getAgentProfile(req.params.agentId);
1853
+ if (!profile) {
1854
+ return res.status(404).json({ error: "No profile for agent" });
1855
+ }
1856
+ res.json(profile);
1857
+ } catch (error) {
1858
+ console.error("Profile error:", error);
1859
+ res.status(500).json({ error: "Failed to load agent profile" });
1860
+ }
1861
+ });
1831
1862
  this.app.get("/api/stats/:agentId", (req, res) => {
1832
1863
  try {
1833
1864
  const agentStats = this.stats.getAgentStats(req.params.agentId);
@@ -1835,11 +1866,11 @@ var DashboardServer = class {
1835
1866
  return res.status(404).json({ error: "Agent not found" });
1836
1867
  }
1837
1868
  res.json(agentStats);
1838
- } catch (error) {
1869
+ } catch (_error) {
1839
1870
  res.status(500).json({ error: "Failed to load agent statistics" });
1840
1871
  }
1841
1872
  });
1842
- this.app.get("/api/process-health", (req, res) => {
1873
+ this.app.get("/api/process-health", (_req, res) => {
1843
1874
  try {
1844
1875
  const now = Date.now();
1845
1876
  if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
@@ -1884,9 +1915,11 @@ var DashboardServer = class {
1884
1915
  orphans: uniqueProcesses.filter((p) => {
1885
1916
  var _a;
1886
1917
  const alfredKnownPids = /* @__PURE__ */ new Set();
1887
- if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale) alfredKnownPids.add(alfredResult.pidFile.pid);
1918
+ if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale)
1919
+ alfredKnownPids.add(alfredResult.pidFile.pid);
1888
1920
  if (alfredResult.workers) {
1889
- if (alfredResult.workers.orchestratorPid) alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1921
+ if (alfredResult.workers.orchestratorPid)
1922
+ alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1890
1923
  for (const w of alfredResult.workers.workers) {
1891
1924
  if (w.pid) alfredKnownPids.add(w.pid);
1892
1925
  }
@@ -1905,11 +1938,22 @@ var DashboardServer = class {
1905
1938
  result.problems = [...alfredResult.problems || [], ...openclawProblems];
1906
1939
  this.processHealthCache = { result, ts: now };
1907
1940
  res.json(result);
1908
- } catch (error) {
1941
+ } catch (_error) {
1909
1942
  res.status(500).json({ error: "Failed to audit processes" });
1910
1943
  }
1911
1944
  });
1912
- this.app.get("*", (req, res) => {
1945
+ this.app.get("/health", (_req, res) => {
1946
+ res.json({
1947
+ status: "ok",
1948
+ uptime: process.uptime(),
1949
+ traceCount: this.watcher.getTraceCount(),
1950
+ agentCount: this.watcher.getAgentIds().length
1951
+ });
1952
+ });
1953
+ this.app.get("/ready", (_req, res) => {
1954
+ res.json({ status: "ready" });
1955
+ });
1956
+ this.app.get("*", (_req, res) => {
1913
1957
  const indexPath = path3.join(__dirname, "../public/index.html");
1914
1958
  if (fs3.existsSync(indexPath)) {
1915
1959
  res.sendFile(indexPath);
@@ -1938,6 +1982,189 @@ var DashboardServer = class {
1938
1982
  });
1939
1983
  });
1940
1984
  }
1985
+ /**
1986
+ * Filter an agent's traces to valid ExecutionGraphs and convert via loadGraph().
1987
+ * Returns only traces with proper nodes (Map or non-empty object), skipping session-only traces.
1988
+ */
1989
+ getGraphTraces(agentId) {
1990
+ const traces = this.watcher.getTracesByAgent(agentId).map(serializeTrace);
1991
+ const graphs = [];
1992
+ for (const trace of traces) {
1993
+ try {
1994
+ if (trace.sourceType === "session" || trace.sourceType === "log") continue;
1995
+ if (!trace.rootNodeId && !trace.rootId) continue;
1996
+ const nodes = trace.nodes;
1997
+ if (!nodes || typeof nodes === "object" && Object.keys(nodes).length === 0) continue;
1998
+ const nodeValues = Object.values(nodes);
1999
+ if (nodeValues.some((n) => n.type === "log-file" || n.type === "log-entry")) continue;
2000
+ graphs.push(loadGraph2(trace));
2001
+ } catch {
2002
+ }
2003
+ }
2004
+ return graphs;
2005
+ }
2006
+ /**
2007
+ * Build process graph response using core APIs (discoverProcess + getBottlenecks).
2008
+ * Maps core output to the frontend's expected shape with virtual START/END nodes.
2009
+ */
2010
+ buildProcessGraphFromCore(agentId, graphs) {
2011
+ const model = discoverProcess(graphs);
2012
+ const bottleneckList = getBottlenecks(graphs);
2013
+ const bottleneckMap = /* @__PURE__ */ new Map();
2014
+ for (const b of bottleneckList) {
2015
+ const key = `${b.nodeType}:${b.nodeName}`;
2016
+ bottleneckMap.set(key, {
2017
+ avgDuration: b.durations.median,
2018
+ failRate: 0,
2019
+ // Not directly available from bottleneck data
2020
+ p95: b.durations.p95
2021
+ });
2022
+ bottleneckMap.set(b.nodeName, {
2023
+ avgDuration: b.durations.median,
2024
+ failRate: 0,
2025
+ p95: b.durations.p95
2026
+ });
2027
+ }
2028
+ const nodes = [];
2029
+ const stepCounts = /* @__PURE__ */ new Map();
2030
+ for (const t of model.transitions) {
2031
+ stepCounts.set(t.from, (stepCounts.get(t.from) ?? 0) + t.count);
2032
+ }
2033
+ for (const step of model.steps) {
2034
+ const count = stepCounts.get(step) ?? model.totalGraphs;
2035
+ const bn = bottleneckMap.get(step);
2036
+ const colonIdx = step.indexOf(":");
2037
+ const label = colonIdx >= 0 ? step.slice(colonIdx + 1) : step;
2038
+ nodes.push({
2039
+ id: step,
2040
+ label,
2041
+ count,
2042
+ frequency: count / model.totalGraphs,
2043
+ avgDuration: (bn == null ? void 0 : bn.avgDuration) ?? 0,
2044
+ failRate: (bn == null ? void 0 : bn.failRate) ?? 0,
2045
+ p95Duration: (bn == null ? void 0 : bn.p95) ?? 0,
2046
+ isVirtual: false
2047
+ });
2048
+ }
2049
+ const rootSteps = new Set(model.steps);
2050
+ const childSteps = new Set(model.transitions.map((t) => t.to));
2051
+ const leafSteps = new Set(model.steps);
2052
+ for (const t of model.transitions) {
2053
+ }
2054
+ nodes.push({ id: "[START]", label: "[START]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
2055
+ nodes.push({ id: "[END]", label: "[END]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
2056
+ const edges = model.transitions.map((t) => ({
2057
+ source: t.from,
2058
+ target: t.to,
2059
+ count: t.count,
2060
+ frequency: t.count / model.totalGraphs
2061
+ }));
2062
+ const targetSteps = new Set(model.transitions.map((t) => t.to));
2063
+ for (const step of model.steps) {
2064
+ if (!targetSteps.has(step)) {
2065
+ edges.push({ source: "[START]", target: step, count: model.totalGraphs, frequency: 1 });
2066
+ }
2067
+ }
2068
+ const sourceSteps = new Set(model.transitions.map((t) => t.from));
2069
+ for (const step of model.steps) {
2070
+ if (!sourceSteps.has(step)) {
2071
+ edges.push({ source: step, target: "[END]", count: model.totalGraphs, frequency: 1 });
2072
+ }
2073
+ }
2074
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
2075
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
2076
+ return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
2077
+ }
2078
+ /**
2079
+ * Legacy process graph computation for session-based traces.
2080
+ * Preserved for backward compatibility with JSONL/LOG traces.
2081
+ */
2082
+ buildProcessGraphLegacy(agentId, allTraces) {
2083
+ const traces = allTraces.map(serializeTrace);
2084
+ const activityCounts = /* @__PURE__ */ new Map();
2085
+ const transitionCounts = /* @__PURE__ */ new Map();
2086
+ const activityDurations = /* @__PURE__ */ new Map();
2087
+ const activityStatuses = /* @__PURE__ */ new Map();
2088
+ let totalTraces = 0;
2089
+ for (const trace of traces) {
2090
+ totalTraces++;
2091
+ const activities = [];
2092
+ if (trace.sessionEvents && trace.sessionEvents.length > 0) {
2093
+ for (const evt of trace.sessionEvents) {
2094
+ const name = evt.toolName || evt.name || evt.type;
2095
+ if (!name) continue;
2096
+ activities.push({
2097
+ name,
2098
+ type: evt.type,
2099
+ status: evt.toolError ? "failed" : "completed",
2100
+ duration: evt.duration || 0
2101
+ });
2102
+ }
2103
+ } else {
2104
+ const nodes2 = trace.nodes || {};
2105
+ const sorted = Object.values(nodes2).sort(
2106
+ (a, b) => (a.startTime || 0) - (b.startTime || 0)
2107
+ );
2108
+ for (const node of sorted) {
2109
+ activities.push({
2110
+ name: node.name || node.type || node.id,
2111
+ type: node.type || "unknown",
2112
+ status: node.status || "completed",
2113
+ duration: (node.endTime || node.startTime || 0) - (node.startTime || 0)
2114
+ });
2115
+ }
2116
+ }
2117
+ const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
2118
+ for (let i = 0; i < seq.length; i++) {
2119
+ const act = seq[i];
2120
+ activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
2121
+ if (i < seq.length - 1) {
2122
+ const key = `${act} \u2192 ${seq[i + 1]}`;
2123
+ transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
2124
+ }
2125
+ }
2126
+ for (const act of activities) {
2127
+ if (act.duration > 0) {
2128
+ const durs = activityDurations.get(act.name) || [];
2129
+ durs.push(act.duration);
2130
+ activityDurations.set(act.name, durs);
2131
+ }
2132
+ const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
2133
+ if (act.status === "failed") st.fail++;
2134
+ else st.ok++;
2135
+ activityStatuses.set(act.name, st);
2136
+ }
2137
+ }
2138
+ const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
2139
+ const durs = activityDurations.get(name) || [];
2140
+ const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
2141
+ const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
2142
+ return {
2143
+ id: name,
2144
+ label: name,
2145
+ count,
2146
+ frequency: count / totalTraces,
2147
+ avgDuration,
2148
+ failRate: st.ok + st.fail > 0 ? st.fail / (st.ok + st.fail) : 0,
2149
+ p95Duration: 0,
2150
+ isVirtual: name === "[START]" || name === "[END]"
2151
+ };
2152
+ });
2153
+ const edges = Array.from(transitionCounts.entries()).map(([key, count]) => {
2154
+ const [source, target] = key.split(" \u2192 ");
2155
+ return { source, target, count, frequency: count / totalTraces };
2156
+ });
2157
+ const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
2158
+ const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
2159
+ return { agentId, totalTraces, nodes, edges, maxEdgeCount, maxNodeCount };
2160
+ }
2161
+ /** Check if a trace is a proper ExecutionGraph (not a synthetic session-based trace). */
2162
+ isGraphTrace(trace) {
2163
+ if (trace.sourceType === "session" || trace.sourceType === "log") return false;
2164
+ if (!trace.rootNodeId && !trace.rootId) return false;
2165
+ const nodes = trace.nodes instanceof Map ? Object.fromEntries(trace.nodes) : trace.nodes;
2166
+ return nodes && typeof nodes === "object" && Object.keys(nodes).length > 0;
2167
+ }
1941
2168
  setupTraceWatcher() {
1942
2169
  this.watcher.on("trace-added", (trace) => {
1943
2170
  this.stats.processTrace(trace);
@@ -1945,6 +2172,14 @@ var DashboardServer = class {
1945
2172
  type: "trace-added",
1946
2173
  data: serializeTrace(trace)
1947
2174
  });
2175
+ if (this.isGraphTrace(trace)) {
2176
+ try {
2177
+ const graph = loadGraph2(serializeTrace(trace));
2178
+ const event = createExecutionEvent(graph);
2179
+ this.knowledgeStore.append(event);
2180
+ } catch {
2181
+ }
2182
+ }
1948
2183
  });
1949
2184
  this.watcher.on("trace-updated", (trace) => {
1950
2185
  this.stats.processTrace(trace);