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