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