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