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/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,118 +1732,163 @@ 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
|
-
this.app.get("/api/agents/:agentId/
|
|
1755
|
+
this.app.get("/api/agents/:agentId/timeline", (req, res) => {
|
|
1710
1756
|
try {
|
|
1711
1757
|
const agentId = req.params.agentId;
|
|
1712
|
-
const
|
|
1713
|
-
|
|
1758
|
+
const limit = Math.min(parseInt(req.query.limit, 10) || 50, 200);
|
|
1759
|
+
const rawTraces = this.watcher.getTracesByAgent(agentId);
|
|
1760
|
+
if (rawTraces.length === 0) {
|
|
1714
1761
|
return res.status(404).json({ error: "No traces for agent" });
|
|
1715
1762
|
}
|
|
1716
|
-
const
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
for (const trace of traces) {
|
|
1722
|
-
totalTraces++;
|
|
1763
|
+
const traces = rawTraces.sort((a, b) => (b.startTime || 0) - (a.startTime || 0)).slice(0, limit).reverse();
|
|
1764
|
+
const executions = traces.map((t) => {
|
|
1765
|
+
const serialized = serializeTrace(t);
|
|
1766
|
+
const nodes = serialized.nodes || {};
|
|
1767
|
+
const events = serialized.sessionEvents || [];
|
|
1723
1768
|
const activities = [];
|
|
1724
|
-
if (
|
|
1725
|
-
for (
|
|
1726
|
-
const
|
|
1727
|
-
if (
|
|
1769
|
+
if (events.length > 0) {
|
|
1770
|
+
for (let i = 0; i < events.length; i++) {
|
|
1771
|
+
const evt = events[i];
|
|
1772
|
+
if (evt.type === "system" || evt.type === "model_change") continue;
|
|
1773
|
+
const dur = evt.duration || 0;
|
|
1774
|
+
const startTs = dur > 0 ? evt.timestamp - dur : evt.timestamp;
|
|
1775
|
+
const nextTs = i + 1 < events.length ? events[i + 1].timestamp : evt.timestamp;
|
|
1776
|
+
const endTs = dur > 0 ? evt.timestamp : Math.max(nextTs, startTs + 500);
|
|
1728
1777
|
activities.push({
|
|
1729
|
-
|
|
1778
|
+
id: evt.id || `evt-${i}`,
|
|
1779
|
+
name: evt.toolName || evt.name || evt.type,
|
|
1730
1780
|
type: evt.type,
|
|
1731
1781
|
status: evt.toolError ? "failed" : "completed",
|
|
1732
|
-
|
|
1782
|
+
startTime: startTs,
|
|
1783
|
+
endTime: endTs,
|
|
1784
|
+
parentId: evt.parentId
|
|
1733
1785
|
});
|
|
1734
1786
|
}
|
|
1735
1787
|
} else {
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1788
|
+
const sorted = Object.values(nodes).sort(
|
|
1789
|
+
(a, b) => (a.startTime || 0) - (b.startTime || 0)
|
|
1790
|
+
);
|
|
1738
1791
|
for (const node of sorted) {
|
|
1739
1792
|
activities.push({
|
|
1793
|
+
id: node.id,
|
|
1740
1794
|
name: node.name || node.type || node.id,
|
|
1741
1795
|
type: node.type || "unknown",
|
|
1742
1796
|
status: node.status || "completed",
|
|
1743
|
-
|
|
1797
|
+
startTime: node.startTime || t.startTime,
|
|
1798
|
+
endTime: node.endTime || node.startTime || t.startTime,
|
|
1799
|
+
parentId: node.parentId
|
|
1744
1800
|
});
|
|
1745
1801
|
}
|
|
1746
1802
|
}
|
|
1747
|
-
const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
|
|
1748
|
-
for (let i = 0; i < seq.length; i++) {
|
|
1749
|
-
const act = seq[i];
|
|
1750
|
-
activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
|
|
1751
|
-
if (i < seq.length - 1) {
|
|
1752
|
-
const key = act + " \u2192 " + seq[i + 1];
|
|
1753
|
-
transitionCounts.set(key, (transitionCounts.get(key) || 0) + 1);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
for (const act of activities) {
|
|
1757
|
-
if (act.duration > 0) {
|
|
1758
|
-
const durs = activityDurations.get(act.name) || [];
|
|
1759
|
-
durs.push(act.duration);
|
|
1760
|
-
activityDurations.set(act.name, durs);
|
|
1761
|
-
}
|
|
1762
|
-
const st = activityStatuses.get(act.name) || { ok: 0, fail: 0 };
|
|
1763
|
-
if (act.status === "failed") st.fail++;
|
|
1764
|
-
else st.ok++;
|
|
1765
|
-
activityStatuses.set(act.name, st);
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
const nodes = Array.from(activityCounts.entries()).map(([name, count]) => {
|
|
1769
|
-
const durs = activityDurations.get(name) || [];
|
|
1770
|
-
const st = activityStatuses.get(name) || { ok: 0, fail: 0 };
|
|
1771
|
-
const avgDuration = durs.length > 0 ? durs.reduce((a, b) => a + b, 0) / durs.length : 0;
|
|
1772
1803
|
return {
|
|
1773
|
-
id:
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1804
|
+
id: serialized.id || serialized.filename,
|
|
1805
|
+
filename: serialized.filename,
|
|
1806
|
+
name: serialized.name || serialized.filename,
|
|
1807
|
+
agentId: serialized.agentId,
|
|
1808
|
+
trigger: serialized.trigger,
|
|
1809
|
+
status: serialized.status || "completed",
|
|
1810
|
+
sourceType: serialized.sourceType,
|
|
1811
|
+
startTime: serialized.startTime,
|
|
1812
|
+
endTime: serialized.endTime || serialized.startTime,
|
|
1813
|
+
tokenUsage: serialized.tokenUsage,
|
|
1814
|
+
activities
|
|
1780
1815
|
};
|
|
1781
1816
|
});
|
|
1782
|
-
const
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
});
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
res.json({
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1817
|
+
const allTimes = executions.flatMap((e) => [e.startTime, e.endTime]);
|
|
1818
|
+
const minTime = Math.min(...allTimes);
|
|
1819
|
+
const maxTime = Math.max(...allTimes);
|
|
1820
|
+
res.json({ agentId, totalExecutions: rawTraces.length, executions, minTime, maxTime });
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
console.error("Agent timeline error:", error);
|
|
1823
|
+
res.status(500).json({ error: "Failed to build agent timeline" });
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1826
|
+
this.app.get("/api/agents/:agentId/process-graph", (req, res) => {
|
|
1827
|
+
try {
|
|
1828
|
+
const agentId = req.params.agentId;
|
|
1829
|
+
const allTraces = this.watcher.getTracesByAgent(agentId);
|
|
1830
|
+
if (allTraces.length === 0) {
|
|
1831
|
+
return res.status(404).json({ error: "No traces for agent" });
|
|
1832
|
+
}
|
|
1833
|
+
const graphs = this.getGraphTraces(agentId);
|
|
1834
|
+
if (graphs.length > 0) {
|
|
1835
|
+
return res.json(this.buildProcessGraphFromCore(agentId, graphs));
|
|
1836
|
+
}
|
|
1837
|
+
return res.json(this.buildProcessGraphLegacy(agentId, allTraces));
|
|
1796
1838
|
} catch (error) {
|
|
1797
1839
|
console.error("Process graph error:", error);
|
|
1798
1840
|
res.status(500).json({ error: "Failed to build process graph" });
|
|
1799
1841
|
}
|
|
1800
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
|
+
});
|
|
1801
1892
|
this.app.get("/api/stats/:agentId", (req, res) => {
|
|
1802
1893
|
try {
|
|
1803
1894
|
const agentStats = this.stats.getAgentStats(req.params.agentId);
|
|
@@ -1805,11 +1896,11 @@ var DashboardServer = class {
|
|
|
1805
1896
|
return res.status(404).json({ error: "Agent not found" });
|
|
1806
1897
|
}
|
|
1807
1898
|
res.json(agentStats);
|
|
1808
|
-
} catch (
|
|
1899
|
+
} catch (_error) {
|
|
1809
1900
|
res.status(500).json({ error: "Failed to load agent statistics" });
|
|
1810
1901
|
}
|
|
1811
1902
|
});
|
|
1812
|
-
this.app.get("/api/process-health", (
|
|
1903
|
+
this.app.get("/api/process-health", (_req, res) => {
|
|
1813
1904
|
try {
|
|
1814
1905
|
const now = Date.now();
|
|
1815
1906
|
if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
|
|
@@ -1854,9 +1945,11 @@ var DashboardServer = class {
|
|
|
1854
1945
|
orphans: uniqueProcesses.filter((p) => {
|
|
1855
1946
|
var _a;
|
|
1856
1947
|
const alfredKnownPids = /* @__PURE__ */ new Set();
|
|
1857
|
-
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);
|
|
1858
1950
|
if (alfredResult.workers) {
|
|
1859
|
-
if (alfredResult.workers.orchestratorPid)
|
|
1951
|
+
if (alfredResult.workers.orchestratorPid)
|
|
1952
|
+
alfredKnownPids.add(alfredResult.workers.orchestratorPid);
|
|
1860
1953
|
for (const w of alfredResult.workers.workers) {
|
|
1861
1954
|
if (w.pid) alfredKnownPids.add(w.pid);
|
|
1862
1955
|
}
|
|
@@ -1875,11 +1968,22 @@ var DashboardServer = class {
|
|
|
1875
1968
|
result.problems = [...alfredResult.problems || [], ...openclawProblems];
|
|
1876
1969
|
this.processHealthCache = { result, ts: now };
|
|
1877
1970
|
res.json(result);
|
|
1878
|
-
} catch (
|
|
1971
|
+
} catch (_error) {
|
|
1879
1972
|
res.status(500).json({ error: "Failed to audit processes" });
|
|
1880
1973
|
}
|
|
1881
1974
|
});
|
|
1882
|
-
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) => {
|
|
1883
1987
|
const indexPath = path3.join(__dirname, "../public/index.html");
|
|
1884
1988
|
if (fs3.existsSync(indexPath)) {
|
|
1885
1989
|
res.sendFile(indexPath);
|
|
@@ -1908,6 +2012,189 @@ var DashboardServer = class {
|
|
|
1908
2012
|
});
|
|
1909
2013
|
});
|
|
1910
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
|
+
}
|
|
1911
2198
|
setupTraceWatcher() {
|
|
1912
2199
|
this.watcher.on("trace-added", (trace) => {
|
|
1913
2200
|
this.stats.processTrace(trace);
|
|
@@ -1915,6 +2202,14 @@ var DashboardServer = class {
|
|
|
1915
2202
|
type: "trace-added",
|
|
1916
2203
|
data: serializeTrace(trace)
|
|
1917
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
|
+
}
|
|
1918
2213
|
});
|
|
1919
2214
|
this.watcher.on("trace-updated", (trace) => {
|
|
1920
2215
|
this.stats.processTrace(trace);
|