agentflow-dashboard 0.2.0 → 0.3.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/dist/chunk-2FTN742J.js +1168 -0
- package/dist/cli.cjs +774 -90
- package/dist/cli.js +66 -35
- package/dist/index.cjs +709 -56
- package/dist/index.js +1 -1
- package/dist/public/dashboard.js +1371 -285
- package/dist/public/index.html +978 -240
- package/package.json +1 -1
- package/public/dashboard.js +1371 -285
- package/public/index.html +978 -240
- package/dist/chunk-L24LYP6L.js +0 -515
package/dist/cli.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(cli_exports, {
|
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(cli_exports);
|
|
35
35
|
var fs3 = __toESM(require("fs"), 1);
|
|
36
|
+
var os = __toESM(require("os"), 1);
|
|
36
37
|
var path3 = __toESM(require("path"), 1);
|
|
37
38
|
|
|
38
39
|
// src/server.ts
|
|
@@ -226,14 +227,23 @@ var import_events = require("events");
|
|
|
226
227
|
var fs = __toESM(require("fs"), 1);
|
|
227
228
|
var path = __toESM(require("path"), 1);
|
|
228
229
|
var TraceWatcher = class extends import_events.EventEmitter {
|
|
229
|
-
|
|
230
|
+
watchers = [];
|
|
230
231
|
traces = /* @__PURE__ */ new Map();
|
|
231
232
|
tracesDir;
|
|
232
|
-
|
|
233
|
+
dataDirs;
|
|
234
|
+
allWatchDirs;
|
|
235
|
+
constructor(tracesDirOrOptions) {
|
|
233
236
|
super();
|
|
234
|
-
|
|
237
|
+
if (typeof tracesDirOrOptions === "string") {
|
|
238
|
+
this.tracesDir = path.resolve(tracesDirOrOptions);
|
|
239
|
+
this.dataDirs = [];
|
|
240
|
+
} else {
|
|
241
|
+
this.tracesDir = path.resolve(tracesDirOrOptions.tracesDir);
|
|
242
|
+
this.dataDirs = (tracesDirOrOptions.dataDirs || []).map((d) => path.resolve(d));
|
|
243
|
+
}
|
|
244
|
+
this.allWatchDirs = [this.tracesDir, ...this.dataDirs];
|
|
235
245
|
this.ensureTracesDir();
|
|
236
|
-
this.
|
|
246
|
+
this.loadExistingFiles();
|
|
237
247
|
this.startWatching();
|
|
238
248
|
}
|
|
239
249
|
ensureTracesDir() {
|
|
@@ -242,18 +252,288 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
242
252
|
console.log(`Created traces directory: ${this.tracesDir}`);
|
|
243
253
|
}
|
|
244
254
|
}
|
|
245
|
-
|
|
255
|
+
loadExistingFiles() {
|
|
256
|
+
let totalFiles = 0;
|
|
257
|
+
for (const dir of this.allWatchDirs) {
|
|
258
|
+
if (!fs.existsSync(dir)) continue;
|
|
259
|
+
try {
|
|
260
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl"));
|
|
261
|
+
totalFiles += files.length;
|
|
262
|
+
for (const file of files) {
|
|
263
|
+
this.loadFile(path.join(dir, file));
|
|
264
|
+
}
|
|
265
|
+
} catch (error) {
|
|
266
|
+
console.error(`Error scanning directory ${dir}:`, error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
console.log(`Scanned ${this.allWatchDirs.length} directories, loaded ${this.traces.size} items from ${totalFiles} files`);
|
|
270
|
+
}
|
|
271
|
+
/** Load a .json trace, .jsonl session file, or .log file. */
|
|
272
|
+
loadFile(filePath) {
|
|
273
|
+
if (filePath.endsWith(".jsonl")) {
|
|
274
|
+
return this.loadSessionFile(filePath);
|
|
275
|
+
}
|
|
276
|
+
if (filePath.endsWith(".log") || filePath.endsWith(".trace")) {
|
|
277
|
+
return this.loadLogFile(filePath);
|
|
278
|
+
}
|
|
279
|
+
return this.loadTraceFile(filePath);
|
|
280
|
+
}
|
|
281
|
+
loadLogFile(filePath) {
|
|
246
282
|
try {
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
283
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
284
|
+
const filename = path.basename(filePath);
|
|
285
|
+
const stats = fs.statSync(filePath);
|
|
286
|
+
const traces = this.parseUniversalLog(content, filename, filePath);
|
|
287
|
+
for (let i = 0; i < traces.length; i++) {
|
|
288
|
+
const trace = traces[i];
|
|
289
|
+
trace.filename = filename;
|
|
290
|
+
trace.lastModified = stats.mtime.getTime();
|
|
291
|
+
trace.sourceType = "trace";
|
|
292
|
+
trace.sourceDir = path.dirname(filePath);
|
|
293
|
+
const key = traces.length === 1 ? this.traceKey(filePath) : `${this.traceKey(filePath)}-${i}`;
|
|
294
|
+
this.traces.set(key, trace);
|
|
251
295
|
}
|
|
252
|
-
|
|
296
|
+
return traces.length > 0;
|
|
253
297
|
} catch (error) {
|
|
254
|
-
console.error(
|
|
298
|
+
console.error(`Error loading log file ${filePath}:`, error);
|
|
299
|
+
return false;
|
|
255
300
|
}
|
|
256
301
|
}
|
|
302
|
+
/** Universal log parser - detects agent activities from any system */
|
|
303
|
+
parseUniversalLog(content, filename, filePath) {
|
|
304
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
305
|
+
const activities = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const line of lines) {
|
|
307
|
+
const activity = this.detectActivityPattern(line);
|
|
308
|
+
if (!activity) continue;
|
|
309
|
+
const sessionId = this.extractSessionIdentifier(activity);
|
|
310
|
+
if (!activities.has(sessionId)) {
|
|
311
|
+
activities.set(sessionId, {
|
|
312
|
+
id: sessionId,
|
|
313
|
+
rootNodeId: "",
|
|
314
|
+
agentId: this.detectAgentIdentifier(activity, filename, filePath),
|
|
315
|
+
name: this.generateActivityName(activity, sessionId),
|
|
316
|
+
trigger: this.detectTrigger(activity),
|
|
317
|
+
startTime: activity.timestamp,
|
|
318
|
+
endTime: activity.timestamp,
|
|
319
|
+
status: "completed",
|
|
320
|
+
nodes: {},
|
|
321
|
+
edges: [],
|
|
322
|
+
events: [],
|
|
323
|
+
metadata: { sessionId, source: filename }
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const session = activities.get(sessionId);
|
|
327
|
+
this.addActivityNode(session, activity);
|
|
328
|
+
if (activity.timestamp > session.endTime) {
|
|
329
|
+
session.endTime = activity.timestamp;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const traces = Array.from(activities.values()).filter(
|
|
333
|
+
(session) => Object.keys(session.nodes).length > 0
|
|
334
|
+
);
|
|
335
|
+
if (traces.length === 0) {
|
|
336
|
+
const stats = fs.statSync(filePath);
|
|
337
|
+
traces.push({
|
|
338
|
+
id: "",
|
|
339
|
+
rootNodeId: "root",
|
|
340
|
+
nodes: {
|
|
341
|
+
"root": {
|
|
342
|
+
id: "root",
|
|
343
|
+
type: "log-file",
|
|
344
|
+
name: filename,
|
|
345
|
+
status: "completed",
|
|
346
|
+
startTime: stats.mtime.getTime(),
|
|
347
|
+
endTime: stats.mtime.getTime(),
|
|
348
|
+
metadata: { lineCount: lines.length, path: filePath }
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
edges: [],
|
|
352
|
+
startTime: stats.mtime.getTime(),
|
|
353
|
+
endTime: stats.mtime.getTime(),
|
|
354
|
+
status: "completed",
|
|
355
|
+
trigger: "file",
|
|
356
|
+
agentId: this.extractAgentFromPath(filePath),
|
|
357
|
+
events: [],
|
|
358
|
+
metadata: { type: "file-trace" }
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return traces;
|
|
362
|
+
}
|
|
363
|
+
/** Detect activity patterns in log lines using universal heuristics */
|
|
364
|
+
detectActivityPattern(line) {
|
|
365
|
+
let timestamp = this.extractTimestamp(line);
|
|
366
|
+
let level = this.extractLogLevel(line);
|
|
367
|
+
let action = this.extractAction(line);
|
|
368
|
+
let kvPairs = this.extractKeyValuePairs(line);
|
|
369
|
+
if (!timestamp) {
|
|
370
|
+
const jsonMatch = line.match(/\{.*\}/);
|
|
371
|
+
if (jsonMatch) {
|
|
372
|
+
try {
|
|
373
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
374
|
+
timestamp = this.parseTimestamp(parsed.timestamp || parsed.time || parsed.ts) || Date.now();
|
|
375
|
+
level = parsed.level || parsed.severity || "info";
|
|
376
|
+
action = parsed.action || parsed.event || parsed.message || "";
|
|
377
|
+
kvPairs = parsed;
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (!timestamp) {
|
|
383
|
+
const kvMatches = line.match(/(\w+)=([^\s]+)/g);
|
|
384
|
+
if (kvMatches && kvMatches.length >= 2) {
|
|
385
|
+
const pairs = {};
|
|
386
|
+
kvMatches.forEach((match) => {
|
|
387
|
+
const [key, value] = match.split("=", 2);
|
|
388
|
+
pairs[key] = this.parseValue(value);
|
|
389
|
+
});
|
|
390
|
+
timestamp = this.parseTimestamp(pairs.timestamp || pairs.time) || Date.now();
|
|
391
|
+
level = pairs.level || "info";
|
|
392
|
+
action = pairs.action || pairs.event || "";
|
|
393
|
+
kvPairs = pairs;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (!timestamp) {
|
|
397
|
+
const logMatch = line.match(/^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)\s+(\w+)?\s*:?\s*(.+)/);
|
|
398
|
+
if (logMatch) {
|
|
399
|
+
timestamp = new Date(logMatch[1]).getTime();
|
|
400
|
+
level = logMatch[2] || "info";
|
|
401
|
+
action = logMatch[3] || "";
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (!timestamp) return null;
|
|
405
|
+
return {
|
|
406
|
+
timestamp,
|
|
407
|
+
level: (level == null ? void 0 : level.toLowerCase()) || "info",
|
|
408
|
+
action,
|
|
409
|
+
component: this.detectComponent(action, kvPairs),
|
|
410
|
+
operation: this.detectOperation(action, kvPairs),
|
|
411
|
+
...kvPairs
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
extractTimestamp(line) {
|
|
415
|
+
const coloredMatch = line.match(/^\[2m(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)\[0m/);
|
|
416
|
+
if (coloredMatch) return new Date(coloredMatch[1]).getTime();
|
|
417
|
+
const isoMatch = line.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
|
|
418
|
+
if (isoMatch) return new Date(isoMatch[1]).getTime();
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
extractLogLevel(line) {
|
|
422
|
+
const coloredMatch = line.match(/\[\[(\d+)m\[\[1m(\w+)\s*\[0m\]/);
|
|
423
|
+
if (coloredMatch) return coloredMatch[2].toLowerCase();
|
|
424
|
+
const levelMatch = line.match(/\b(debug|info|warn|warning|error|fatal|trace)\b/i);
|
|
425
|
+
return levelMatch ? levelMatch[1].toLowerCase() : null;
|
|
426
|
+
}
|
|
427
|
+
extractAction(line) {
|
|
428
|
+
const coloredMatch = line.match(/\[1m([^\[]+?)\s*\[0m/);
|
|
429
|
+
if (coloredMatch) return coloredMatch[1].trim();
|
|
430
|
+
const afterLevel = line.replace(/^.*?(debug|info|warn|warning|error|fatal|trace)\s*:?\s*/i, "");
|
|
431
|
+
return afterLevel.split(" ")[0] || "";
|
|
432
|
+
}
|
|
433
|
+
extractKeyValuePairs(line) {
|
|
434
|
+
const pairs = {};
|
|
435
|
+
const coloredRegex = /\[36m(\w+)\[0m=\[35m([^\[]+?)\[0m/g;
|
|
436
|
+
let match;
|
|
437
|
+
while ((match = coloredRegex.exec(line)) !== null) {
|
|
438
|
+
pairs[match[1]] = this.parseValue(match[2]);
|
|
439
|
+
}
|
|
440
|
+
if (Object.keys(pairs).length === 0) {
|
|
441
|
+
const kvRegex = /(\w+)=([^\s]+)/g;
|
|
442
|
+
while ((match = kvRegex.exec(line)) !== null) {
|
|
443
|
+
pairs[match[1]] = this.parseValue(match[2]);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return pairs;
|
|
447
|
+
}
|
|
448
|
+
parseValue(value) {
|
|
449
|
+
if (value.match(/^\d+$/)) return parseInt(value);
|
|
450
|
+
if (value.match(/^\d+\.\d+$/)) return parseFloat(value);
|
|
451
|
+
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
452
|
+
if (value.startsWith('"') && value.endsWith('"')) return value.slice(1, -1);
|
|
453
|
+
return value;
|
|
454
|
+
}
|
|
455
|
+
parseTimestamp(value) {
|
|
456
|
+
if (!value) return null;
|
|
457
|
+
if (typeof value === "number") return value;
|
|
458
|
+
try {
|
|
459
|
+
return new Date(value).getTime();
|
|
460
|
+
} catch {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
detectComponent(action, kvPairs) {
|
|
465
|
+
if (action.includes(".")) return action.split(".")[0];
|
|
466
|
+
if (kvPairs.component) return kvPairs.component;
|
|
467
|
+
if (kvPairs.service) return kvPairs.service;
|
|
468
|
+
if (kvPairs.module) return kvPairs.module;
|
|
469
|
+
return "unknown";
|
|
470
|
+
}
|
|
471
|
+
detectOperation(action, kvPairs) {
|
|
472
|
+
if (action.includes(".")) return action.split(".").slice(1).join(".");
|
|
473
|
+
if (kvPairs.operation) return kvPairs.operation;
|
|
474
|
+
if (kvPairs.method) return kvPairs.method;
|
|
475
|
+
return action || "activity";
|
|
476
|
+
}
|
|
477
|
+
extractSessionIdentifier(activity) {
|
|
478
|
+
return activity.run_id || activity.session_id || activity.request_id || activity.trace_id || activity.sweep_id || activity.transaction_id || "default";
|
|
479
|
+
}
|
|
480
|
+
detectAgentIdentifier(activity, filename, filePath) {
|
|
481
|
+
if (activity.component !== "unknown") {
|
|
482
|
+
const pathAgent = this.extractAgentFromPath(filePath);
|
|
483
|
+
if (pathAgent !== activity.component) {
|
|
484
|
+
return `${pathAgent}-${activity.component}`;
|
|
485
|
+
}
|
|
486
|
+
return activity.component;
|
|
487
|
+
}
|
|
488
|
+
return this.extractAgentFromPath(filePath);
|
|
489
|
+
}
|
|
490
|
+
extractAgentFromPath(filePath) {
|
|
491
|
+
const filename = path.basename(filePath, path.extname(filePath));
|
|
492
|
+
const pathParts = filePath.split(path.sep);
|
|
493
|
+
for (const part of pathParts.reverse()) {
|
|
494
|
+
if (part.match(/agent|worker|service|daemon|bot|ai|llm/i)) {
|
|
495
|
+
return part;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return filename;
|
|
499
|
+
}
|
|
500
|
+
generateActivityName(activity, sessionId) {
|
|
501
|
+
const component = activity.component !== "unknown" ? activity.component : "Activity";
|
|
502
|
+
const operation = activity.operation !== "activity" ? `: ${activity.operation}` : "";
|
|
503
|
+
return `${component}${operation} (${sessionId})`;
|
|
504
|
+
}
|
|
505
|
+
detectTrigger(activity) {
|
|
506
|
+
var _a, _b;
|
|
507
|
+
if (activity.trigger) return activity.trigger;
|
|
508
|
+
if (activity.method && activity.url) return "api-call";
|
|
509
|
+
if ((_a = activity.operation) == null ? void 0 : _a.includes("start")) return "startup";
|
|
510
|
+
if ((_b = activity.operation) == null ? void 0 : _b.includes("invoke")) return "invocation";
|
|
511
|
+
return "event";
|
|
512
|
+
}
|
|
513
|
+
addActivityNode(session, activity) {
|
|
514
|
+
const nodeId = `${activity.component}-${activity.operation}-${activity.timestamp}`;
|
|
515
|
+
const node = {
|
|
516
|
+
id: nodeId,
|
|
517
|
+
type: activity.component,
|
|
518
|
+
name: `${activity.component}: ${activity.operation}`,
|
|
519
|
+
status: this.getUniversalNodeStatus(activity),
|
|
520
|
+
startTime: activity.timestamp,
|
|
521
|
+
endTime: activity.timestamp,
|
|
522
|
+
metadata: activity
|
|
523
|
+
};
|
|
524
|
+
session.nodes[nodeId] = node;
|
|
525
|
+
if (!session.rootNodeId) {
|
|
526
|
+
session.rootNodeId = nodeId;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
getUniversalNodeStatus(activity) {
|
|
530
|
+
var _a, _b;
|
|
531
|
+
if (activity.level === "error" || activity.level === "fatal") return "failed";
|
|
532
|
+
if (activity.level === "warn" || activity.level === "warning") return "warning";
|
|
533
|
+
if ((_a = activity.operation) == null ? void 0 : _a.match(/start|begin|init/i)) return "running";
|
|
534
|
+
if ((_b = activity.operation) == null ? void 0 : _b.match(/complete|finish|end|done/i)) return "completed";
|
|
535
|
+
return "completed";
|
|
536
|
+
}
|
|
257
537
|
loadTraceFile(filePath) {
|
|
258
538
|
try {
|
|
259
539
|
const content = fs.readFileSync(filePath, "utf8");
|
|
@@ -262,55 +542,404 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
262
542
|
const stats = fs.statSync(filePath);
|
|
263
543
|
graph.filename = filename;
|
|
264
544
|
graph.lastModified = stats.mtime.getTime();
|
|
265
|
-
|
|
545
|
+
graph.sourceType = "trace";
|
|
546
|
+
graph.sourceDir = path.dirname(filePath);
|
|
547
|
+
this.traces.set(this.traceKey(filePath), graph);
|
|
266
548
|
return true;
|
|
267
|
-
} catch
|
|
268
|
-
console.error(`Error loading trace file ${filePath}:`, error);
|
|
549
|
+
} catch {
|
|
269
550
|
return false;
|
|
270
551
|
}
|
|
271
552
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (trace) {
|
|
285
|
-
this.emit("trace-added", trace);
|
|
286
|
-
}
|
|
553
|
+
/** Parse a JSONL session log into a WatchedTrace (best-effort). */
|
|
554
|
+
loadSessionFile(filePath) {
|
|
555
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
556
|
+
try {
|
|
557
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
558
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
559
|
+
if (lines.length === 0) return false;
|
|
560
|
+
const rawEvents = [];
|
|
561
|
+
for (const line of lines) {
|
|
562
|
+
try {
|
|
563
|
+
rawEvents.push(JSON.parse(line));
|
|
564
|
+
} catch {
|
|
287
565
|
}
|
|
288
566
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
567
|
+
if (rawEvents.length === 0) return false;
|
|
568
|
+
const sessionEvent = rawEvents.find((e) => e.type === "session");
|
|
569
|
+
const sessionId = (sessionEvent == null ? void 0 : sessionEvent.id) || path.basename(filePath, ".jsonl");
|
|
570
|
+
const sessionTimestamp = (sessionEvent == null ? void 0 : sessionEvent.timestamp) || ((_a = rawEvents[0]) == null ? void 0 : _a.timestamp);
|
|
571
|
+
const startTime = sessionTimestamp ? new Date(sessionTimestamp).getTime() : 0;
|
|
572
|
+
if (!startTime) return false;
|
|
573
|
+
const parentDir = path.basename(path.dirname(filePath));
|
|
574
|
+
const grandParentDir = path.basename(path.dirname(path.dirname(filePath)));
|
|
575
|
+
const agentId = grandParentDir === "agents" ? parentDir : parentDir;
|
|
576
|
+
const modelEvent = rawEvents.find((e) => e.type === "model_change");
|
|
577
|
+
const provider = (modelEvent == null ? void 0 : modelEvent.provider) || "";
|
|
578
|
+
const modelId = (modelEvent == null ? void 0 : modelEvent.modelId) || "";
|
|
579
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
580
|
+
let lastTimestamp = startTime;
|
|
581
|
+
for (const evt of rawEvents) {
|
|
582
|
+
if (evt.timestamp) {
|
|
583
|
+
const ts = new Date(evt.timestamp).getTime();
|
|
584
|
+
if (ts > lastTimestamp) lastTimestamp = ts;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const firstMessage = rawEvents.find((e) => {
|
|
588
|
+
var _a2;
|
|
589
|
+
return e.type === "message" && ((_a2 = e.message) == null ? void 0 : _a2.role) === "user";
|
|
590
|
+
});
|
|
591
|
+
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) || "";
|
|
592
|
+
const cronMatch = userPrompt.match(/\[cron:(\S+)\s+([^\]]+)\]/);
|
|
593
|
+
const triggerName = cronMatch ? cronMatch[2] : "";
|
|
594
|
+
const trigger = cronMatch ? "cron" : "message";
|
|
595
|
+
let totalInputTokens = 0;
|
|
596
|
+
let totalOutputTokens = 0;
|
|
597
|
+
let totalTokensSum = 0;
|
|
598
|
+
let totalCost = 0;
|
|
599
|
+
let userMessageCount = 0;
|
|
600
|
+
let assistantMessageCount = 0;
|
|
601
|
+
let toolCallCount = 0;
|
|
602
|
+
let thinkingBlockCount = 0;
|
|
603
|
+
const sessionEvents = [];
|
|
604
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
605
|
+
const rootId = `session-${sessionId.slice(0, 8)}`;
|
|
606
|
+
const rootName = triggerName || userPrompt.slice(0, 80) + (userPrompt.length > 80 ? "..." : "") || sessionId;
|
|
607
|
+
for (const evt of rawEvents) {
|
|
608
|
+
const evtTs = evt.timestamp ? new Date(evt.timestamp).getTime() : startTime;
|
|
609
|
+
if (evt.type === "session") {
|
|
610
|
+
sessionEvents.push({
|
|
611
|
+
type: "system",
|
|
612
|
+
timestamp: evtTs,
|
|
613
|
+
name: "Session Started",
|
|
614
|
+
content: `Version: ${evt.version || "unknown"}, CWD: ${evt.cwd || ""}`,
|
|
615
|
+
id: evt.id
|
|
616
|
+
});
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
if (evt.type === "model_change") {
|
|
620
|
+
sessionEvents.push({
|
|
621
|
+
type: "model_change",
|
|
622
|
+
timestamp: evtTs,
|
|
623
|
+
name: "Model Change",
|
|
624
|
+
model: evt.modelId,
|
|
625
|
+
provider: evt.provider,
|
|
626
|
+
content: `${evt.provider}/${evt.modelId}`,
|
|
627
|
+
id: evt.id
|
|
628
|
+
});
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (evt.type === "thinking_level_change") {
|
|
632
|
+
sessionEvents.push({
|
|
633
|
+
type: "system",
|
|
634
|
+
timestamp: evtTs,
|
|
635
|
+
name: "Thinking Level",
|
|
636
|
+
content: evt.thinkingLevel || "",
|
|
637
|
+
id: evt.id
|
|
638
|
+
});
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (evt.type === "custom" && evt.customType === "model-snapshot") {
|
|
642
|
+
sessionEvents.push({
|
|
643
|
+
type: "system",
|
|
644
|
+
timestamp: evtTs,
|
|
645
|
+
name: "Model Snapshot",
|
|
646
|
+
content: JSON.stringify(evt.data || {}).slice(0, 200),
|
|
647
|
+
id: evt.id
|
|
648
|
+
});
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (evt.type === "custom_message" && evt.customType === "openclaw.sessions_yield") {
|
|
652
|
+
sessionEvents.push({
|
|
653
|
+
type: "spawn",
|
|
654
|
+
timestamp: evtTs,
|
|
655
|
+
name: "Subagent Spawn",
|
|
656
|
+
content: ((_e = evt.data) == null ? void 0 : _e.sessionId) || "",
|
|
657
|
+
id: evt.id,
|
|
658
|
+
parentId: evt.parentId
|
|
659
|
+
});
|
|
660
|
+
const spawnId = `spawn-${toolCallCount + thinkingBlockCount + 1}`;
|
|
661
|
+
nodes.set(spawnId, {
|
|
662
|
+
id: spawnId,
|
|
663
|
+
type: "subagent",
|
|
664
|
+
name: "Subagent: " + (((_f = evt.data) == null ? void 0 : _f.sessionId) || "").slice(0, 12),
|
|
665
|
+
startTime: evtTs,
|
|
666
|
+
endTime: evtTs,
|
|
667
|
+
status: "completed",
|
|
668
|
+
parentId: rootId,
|
|
669
|
+
children: [],
|
|
670
|
+
metadata: { sessionId: (_g = evt.data) == null ? void 0 : _g.sessionId }
|
|
671
|
+
});
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (evt.type === "message" && evt.message) {
|
|
675
|
+
const msg = evt.message;
|
|
676
|
+
const role = msg.role;
|
|
677
|
+
const contentBlocks = Array.isArray(msg.content) ? msg.content : [];
|
|
678
|
+
if (role === "user") {
|
|
679
|
+
userMessageCount++;
|
|
680
|
+
const textContent = contentBlocks.filter((b) => b.type === "text").map((b) => b.text || "").join("\n");
|
|
681
|
+
sessionEvents.push({
|
|
682
|
+
type: "user",
|
|
683
|
+
timestamp: evtTs,
|
|
684
|
+
name: "User Message",
|
|
685
|
+
content: textContent,
|
|
686
|
+
id: evt.id,
|
|
687
|
+
parentId: evt.parentId
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
if (role === "assistant") {
|
|
691
|
+
assistantMessageCount++;
|
|
692
|
+
if (msg.usage) {
|
|
693
|
+
const u = msg.usage;
|
|
694
|
+
totalInputTokens += u.input || 0;
|
|
695
|
+
totalOutputTokens += u.output || 0;
|
|
696
|
+
totalTokensSum += u.totalTokens || 0;
|
|
697
|
+
if ((_h = u.cost) == null ? void 0 : _h.total) totalCost += u.cost.total;
|
|
698
|
+
}
|
|
699
|
+
for (const block of contentBlocks) {
|
|
700
|
+
if (block.type === "text" && block.text) {
|
|
701
|
+
sessionEvents.push({
|
|
702
|
+
type: "assistant",
|
|
703
|
+
timestamp: evtTs,
|
|
704
|
+
name: "Assistant",
|
|
705
|
+
content: block.text,
|
|
706
|
+
id: evt.id,
|
|
707
|
+
parentId: evt.parentId,
|
|
708
|
+
tokens: msg.usage ? {
|
|
709
|
+
input: msg.usage.input || 0,
|
|
710
|
+
output: msg.usage.output || 0,
|
|
711
|
+
total: msg.usage.totalTokens || 0,
|
|
712
|
+
cost: (_i = msg.usage.cost) == null ? void 0 : _i.total
|
|
713
|
+
} : void 0,
|
|
714
|
+
model: modelId,
|
|
715
|
+
provider
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
if (block.type === "thinking" && block.thinking) {
|
|
719
|
+
thinkingBlockCount++;
|
|
720
|
+
const thinkId = `thinking-${thinkingBlockCount}`;
|
|
721
|
+
sessionEvents.push({
|
|
722
|
+
type: "thinking",
|
|
723
|
+
timestamp: evtTs,
|
|
724
|
+
name: "Thinking",
|
|
725
|
+
content: block.thinking,
|
|
726
|
+
id: thinkId,
|
|
727
|
+
parentId: evt.id
|
|
728
|
+
});
|
|
729
|
+
nodes.set(thinkId, {
|
|
730
|
+
id: thinkId,
|
|
731
|
+
type: "decision",
|
|
732
|
+
name: "Thinking",
|
|
733
|
+
startTime: evtTs,
|
|
734
|
+
endTime: evtTs,
|
|
735
|
+
status: "completed",
|
|
736
|
+
parentId: rootId,
|
|
737
|
+
children: [],
|
|
738
|
+
metadata: { preview: block.thinking.slice(0, 100) }
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
if (block.type === "toolCall") {
|
|
742
|
+
toolCallCount++;
|
|
743
|
+
const toolName = block.name || "unknown";
|
|
744
|
+
const toolId = `tool-${toolCallCount}`;
|
|
745
|
+
const toolCallId = block.id || toolId;
|
|
746
|
+
sessionEvents.push({
|
|
747
|
+
type: "tool_call",
|
|
748
|
+
timestamp: evtTs,
|
|
749
|
+
name: toolName,
|
|
750
|
+
toolName,
|
|
751
|
+
toolArgs: block.arguments,
|
|
752
|
+
id: toolCallId,
|
|
753
|
+
parentId: evt.id
|
|
754
|
+
});
|
|
755
|
+
toolCallMap.set(toolCallId, sessionEvents.length - 1);
|
|
756
|
+
nodes.set(toolId, {
|
|
757
|
+
id: toolId,
|
|
758
|
+
type: "tool",
|
|
759
|
+
name: toolName,
|
|
760
|
+
startTime: evtTs,
|
|
761
|
+
endTime: evtTs,
|
|
762
|
+
// updated when result arrives
|
|
763
|
+
status: "running",
|
|
764
|
+
parentId: rootId,
|
|
765
|
+
children: [],
|
|
766
|
+
metadata: {
|
|
767
|
+
toolCallId,
|
|
768
|
+
args: block.arguments
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (role === "toolResult") {
|
|
775
|
+
const toolCallId = ((_j = contentBlocks[0]) == null ? void 0 : _j.toolCallId) || evt.parentId;
|
|
776
|
+
const resultContent = contentBlocks.map((b) => b.text || b.content || "").join("\n");
|
|
777
|
+
const hasError = contentBlocks.some((b) => b.isError || b.error);
|
|
778
|
+
const errorText = hasError ? resultContent : void 0;
|
|
779
|
+
sessionEvents.push({
|
|
780
|
+
type: "tool_result",
|
|
781
|
+
timestamp: evtTs,
|
|
782
|
+
name: "Tool Result",
|
|
783
|
+
toolResult: resultContent.slice(0, 2e3),
|
|
784
|
+
toolError: errorText == null ? void 0 : errorText.slice(0, 500),
|
|
785
|
+
id: evt.id,
|
|
786
|
+
parentId: toolCallId
|
|
787
|
+
});
|
|
788
|
+
for (const [nodeId, node] of nodes) {
|
|
789
|
+
if (node.type === "tool" && ((_k = node.metadata) == null ? void 0 : _k.toolCallId) === toolCallId) {
|
|
790
|
+
node.endTime = evtTs;
|
|
791
|
+
node.status = hasError ? "failed" : "completed";
|
|
792
|
+
if (hasError) node.metadata.error = errorText == null ? void 0 : errorText.slice(0, 500);
|
|
793
|
+
const callIdx = toolCallMap.get(toolCallId);
|
|
794
|
+
if (callIdx !== void 0 && sessionEvents[callIdx]) {
|
|
795
|
+
const callTs = sessionEvents[callIdx].timestamp;
|
|
796
|
+
sessionEvents[sessionEvents.length - 1].duration = evtTs - callTs;
|
|
797
|
+
sessionEvents[callIdx].duration = evtTs - callTs;
|
|
798
|
+
}
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
298
802
|
}
|
|
299
803
|
}
|
|
300
804
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
805
|
+
const fileStat = fs.statSync(filePath);
|
|
806
|
+
const fileAge = Date.now() - fileStat.mtime.getTime();
|
|
807
|
+
const lastEvt = rawEvents[rawEvents.length - 1];
|
|
808
|
+
const hasToolError = sessionEvents.some((e) => e.type === "tool_result" && e.toolError);
|
|
809
|
+
const lastIsAssistant = (lastEvt == null ? void 0 : lastEvt.type) === "message" && ((_l = lastEvt == null ? void 0 : lastEvt.message) == null ? void 0 : _l.role) === "assistant";
|
|
810
|
+
const isRecentlyModified = fileAge < 5 * 60 * 1e3;
|
|
811
|
+
let status;
|
|
812
|
+
if (hasToolError) {
|
|
813
|
+
status = "failed";
|
|
814
|
+
} else if (lastIsAssistant) {
|
|
815
|
+
status = "completed";
|
|
816
|
+
} else if (isRecentlyModified) {
|
|
817
|
+
status = "running";
|
|
818
|
+
} else {
|
|
819
|
+
status = "completed";
|
|
308
820
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
821
|
+
const tokenUsage = {
|
|
822
|
+
input: totalInputTokens,
|
|
823
|
+
output: totalOutputTokens,
|
|
824
|
+
total: totalTokensSum || totalInputTokens + totalOutputTokens,
|
|
825
|
+
cost: totalCost
|
|
826
|
+
};
|
|
827
|
+
nodes.set(rootId, {
|
|
828
|
+
id: rootId,
|
|
829
|
+
type: "agent",
|
|
830
|
+
name: rootName,
|
|
831
|
+
startTime,
|
|
832
|
+
endTime: lastTimestamp,
|
|
833
|
+
status,
|
|
834
|
+
parentId: void 0,
|
|
835
|
+
children: Array.from(nodes.keys()).filter((k) => k !== rootId),
|
|
836
|
+
metadata: {
|
|
837
|
+
provider,
|
|
838
|
+
model: modelId,
|
|
839
|
+
sessionId,
|
|
840
|
+
trigger,
|
|
841
|
+
totalTokens: tokenUsage.total,
|
|
842
|
+
inputTokens: tokenUsage.input,
|
|
843
|
+
outputTokens: tokenUsage.output,
|
|
844
|
+
cost: tokenUsage.cost,
|
|
845
|
+
userMessages: userMessageCount,
|
|
846
|
+
assistantMessages: assistantMessageCount,
|
|
847
|
+
toolCalls: toolCallCount,
|
|
848
|
+
thinkingBlocks: thinkingBlockCount,
|
|
849
|
+
"gen_ai.system": provider,
|
|
850
|
+
"gen_ai.request.model": modelId
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
const filename = path.basename(filePath);
|
|
854
|
+
const trace = {
|
|
855
|
+
id: sessionId,
|
|
856
|
+
nodes,
|
|
857
|
+
edges: [],
|
|
858
|
+
events: [],
|
|
859
|
+
startTime,
|
|
860
|
+
agentId,
|
|
861
|
+
trigger,
|
|
862
|
+
name: rootName,
|
|
863
|
+
traceId: sessionId,
|
|
864
|
+
spanId: sessionId,
|
|
865
|
+
filename,
|
|
866
|
+
lastModified: fileStat.mtime.getTime(),
|
|
867
|
+
sourceType: "session",
|
|
868
|
+
sourceDir: path.dirname(filePath),
|
|
869
|
+
sessionEvents,
|
|
870
|
+
tokenUsage,
|
|
871
|
+
metadata: {
|
|
872
|
+
provider,
|
|
873
|
+
model: modelId,
|
|
874
|
+
userMessages: userMessageCount,
|
|
875
|
+
assistantMessages: assistantMessageCount,
|
|
876
|
+
toolCalls: toolCallCount,
|
|
877
|
+
thinkingBlocks: thinkingBlockCount,
|
|
878
|
+
totalEvents: rawEvents.length,
|
|
879
|
+
sessionVersion: sessionEvent == null ? void 0 : sessionEvent.version
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
this.traces.set(this.traceKey(filePath), trace);
|
|
883
|
+
return true;
|
|
884
|
+
} catch {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
/** Unique key for a file across directories. */
|
|
889
|
+
traceKey(filePath) {
|
|
890
|
+
for (const dir of this.allWatchDirs) {
|
|
891
|
+
if (filePath.startsWith(dir)) {
|
|
892
|
+
return path.relative(dir, filePath).replace(/\\/g, "/") + "@" + path.basename(dir);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return filePath;
|
|
896
|
+
}
|
|
897
|
+
startWatching() {
|
|
898
|
+
for (const dir of this.allWatchDirs) {
|
|
899
|
+
if (!fs.existsSync(dir)) continue;
|
|
900
|
+
const watcher = import_chokidar.default.watch(dir, {
|
|
901
|
+
ignored: /^\./,
|
|
902
|
+
persistent: true,
|
|
903
|
+
ignoreInitial: true,
|
|
904
|
+
depth: 0
|
|
905
|
+
// don't recurse into subdirectories
|
|
906
|
+
});
|
|
907
|
+
watcher.on("add", (filePath) => {
|
|
908
|
+
if (filePath.endsWith(".json") || filePath.endsWith(".jsonl") || filePath.endsWith(".log") || filePath.endsWith(".trace")) {
|
|
909
|
+
console.log(`New file: ${path.basename(filePath)}`);
|
|
910
|
+
if (this.loadFile(filePath)) {
|
|
911
|
+
const key = this.traceKey(filePath);
|
|
912
|
+
const trace = this.traces.get(key);
|
|
913
|
+
if (trace) {
|
|
914
|
+
this.emit("trace-added", trace);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
watcher.on("change", (filePath) => {
|
|
920
|
+
if (filePath.endsWith(".json") || filePath.endsWith(".jsonl") || filePath.endsWith(".log") || filePath.endsWith(".trace")) {
|
|
921
|
+
if (this.loadFile(filePath)) {
|
|
922
|
+
const key = this.traceKey(filePath);
|
|
923
|
+
const trace = this.traces.get(key);
|
|
924
|
+
if (trace) {
|
|
925
|
+
this.emit("trace-updated", trace);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
watcher.on("unlink", (filePath) => {
|
|
931
|
+
if (filePath.endsWith(".json") || filePath.endsWith(".jsonl") || filePath.endsWith(".log") || filePath.endsWith(".trace")) {
|
|
932
|
+
const key = this.traceKey(filePath);
|
|
933
|
+
this.traces.delete(key);
|
|
934
|
+
this.emit("trace-removed", key);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
watcher.on("error", (error) => {
|
|
938
|
+
console.error(`Watcher error on ${dir}:`, error);
|
|
939
|
+
});
|
|
940
|
+
this.watchers.push(watcher);
|
|
941
|
+
}
|
|
942
|
+
console.log(`Watching ${this.allWatchDirs.length} directories for JSON/JSONL files`);
|
|
314
943
|
}
|
|
315
944
|
getAllTraces() {
|
|
316
945
|
return Array.from(this.traces.values()).sort((a, b) => {
|
|
@@ -318,7 +947,14 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
318
947
|
});
|
|
319
948
|
}
|
|
320
949
|
getTrace(filename) {
|
|
321
|
-
|
|
950
|
+
const exact = this.traces.get(filename);
|
|
951
|
+
if (exact) return exact;
|
|
952
|
+
for (const [key, trace] of this.traces) {
|
|
953
|
+
if (trace.filename === filename || key.endsWith(filename)) {
|
|
954
|
+
return trace;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return void 0;
|
|
322
958
|
}
|
|
323
959
|
getTracesByAgent(agentId) {
|
|
324
960
|
return this.getAllTraces().filter((trace) => trace.agentId === agentId);
|
|
@@ -337,11 +973,11 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
337
973
|
return Array.from(agentIds).sort();
|
|
338
974
|
}
|
|
339
975
|
stop() {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
this.watcher = void 0;
|
|
343
|
-
console.log("Stopped watching traces directory");
|
|
976
|
+
for (const w of this.watchers) {
|
|
977
|
+
w.close();
|
|
344
978
|
}
|
|
979
|
+
this.watchers = [];
|
|
980
|
+
console.log("Stopped watching all directories");
|
|
345
981
|
}
|
|
346
982
|
getTraceStats() {
|
|
347
983
|
const total = this.traces.size;
|
|
@@ -368,7 +1004,10 @@ var __dirname = path2.dirname(__filename);
|
|
|
368
1004
|
var DashboardServer = class {
|
|
369
1005
|
constructor(config) {
|
|
370
1006
|
this.config = config;
|
|
371
|
-
this.watcher = new TraceWatcher(
|
|
1007
|
+
this.watcher = new TraceWatcher({
|
|
1008
|
+
tracesDir: config.tracesDir,
|
|
1009
|
+
dataDirs: config.dataDirs
|
|
1010
|
+
});
|
|
372
1011
|
this.stats = new AgentStats();
|
|
373
1012
|
this.setupExpress();
|
|
374
1013
|
this.setupWebSocket();
|
|
@@ -414,6 +1053,21 @@ var DashboardServer = class {
|
|
|
414
1053
|
res.status(500).json({ error: "Failed to load trace" });
|
|
415
1054
|
}
|
|
416
1055
|
});
|
|
1056
|
+
this.app.get("/api/traces/:filename/events", (req, res) => {
|
|
1057
|
+
try {
|
|
1058
|
+
const trace = this.watcher.getTrace(req.params.filename);
|
|
1059
|
+
if (!trace) {
|
|
1060
|
+
return res.status(404).json({ error: "Trace not found" });
|
|
1061
|
+
}
|
|
1062
|
+
res.json({
|
|
1063
|
+
events: trace.sessionEvents || [],
|
|
1064
|
+
tokenUsage: trace.tokenUsage || null,
|
|
1065
|
+
sourceType: trace.sourceType || "trace"
|
|
1066
|
+
});
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
res.status(500).json({ error: "Failed to load trace events" });
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
417
1071
|
this.app.get("/api/agents", (req, res) => {
|
|
418
1072
|
try {
|
|
419
1073
|
const agents = this.stats.getAgentsList();
|
|
@@ -549,8 +1203,56 @@ var DashboardServer = class {
|
|
|
549
1203
|
};
|
|
550
1204
|
|
|
551
1205
|
// src/cli.ts
|
|
552
|
-
|
|
1206
|
+
var VERSION = "0.2.2";
|
|
1207
|
+
function getLanAddress() {
|
|
1208
|
+
const interfaces = os.networkInterfaces();
|
|
1209
|
+
for (const name of Object.keys(interfaces)) {
|
|
1210
|
+
for (const iface of interfaces[name] || []) {
|
|
1211
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
1212
|
+
return iface.address;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
function printBanner(config, traceCount, stats) {
|
|
553
1219
|
var _a;
|
|
1220
|
+
const lan = getLanAddress();
|
|
1221
|
+
const host = config.host || "localhost";
|
|
1222
|
+
const port = config.port;
|
|
1223
|
+
const isPublic = host === "0.0.0.0";
|
|
1224
|
+
console.log(`
|
|
1225
|
+
___ _ _____ _
|
|
1226
|
+
/ _ \\ __ _ ___ _ __ | |_| ___| | _____ __
|
|
1227
|
+
| |_| |/ _\` |/ _ \\ '_ \\| __| |_ | |/ _ \\ \\ /\\ / /
|
|
1228
|
+
| _ | (_| | __/ | | | |_| _| | | (_) \\ V V /
|
|
1229
|
+
|_| |_|\\__, |\\___|_| |_|\\__|_| |_|\\___/ \\_/\\_/
|
|
1230
|
+
|___/ dashboard v${VERSION}
|
|
1231
|
+
|
|
1232
|
+
See your agents think.
|
|
1233
|
+
|
|
1234
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
1235
|
+
\u2502 \u{1F916} Agents \u2502 TRACE FILES \u2502 \u{1F4CA} AgentFlow \u2502 SHOWS YOU \u2502 \u{1F310} Your browser \u2502
|
|
1236
|
+
\u2502 Execute tasks, \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500> \u2502 Reads traces, \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500> \u2502 Interactive \u2502
|
|
1237
|
+
\u2502 write JSON \u2502 \u2502 builds graphs, \u2502 \u2502 graph, timeline, \u2502
|
|
1238
|
+
\u2502 trace files. \u2502 \u2502 serves dashboard.\u2502 \u2502 metrics, health. \u2502
|
|
1239
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
1240
|
+
|
|
1241
|
+
Runs locally. Your data never leaves your machine.
|
|
1242
|
+
|
|
1243
|
+
Tabs: \u{1F3AF} Graph \xB7 \u23F1\uFE0F Timeline \xB7 \u{1F4CA} Metrics \xB7 \u{1F6E0}\uFE0F Process Health \xB7 \u26A0\uFE0F Errors
|
|
1244
|
+
|
|
1245
|
+
Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? "\n Data dirs: " + config.dataDirs.join("\n ") : ""}
|
|
1246
|
+
Loaded: ${traceCount} traces \xB7 ${stats.totalAgents} agents \xB7 ${stats.totalExecutions} executions
|
|
1247
|
+
Success: ${stats.globalSuccessRate.toFixed(1)}%${stats.activeAgents > 0 ? ` \xB7 ${stats.activeAgents} active now` : ""}
|
|
1248
|
+
CORS: ${config.enableCors ? "enabled" : "disabled"}
|
|
1249
|
+
WebSocket: live updates enabled
|
|
1250
|
+
|
|
1251
|
+
\u2192 http://localhost:${port}${isPublic && lan ? `
|
|
1252
|
+
\u2192 http://${lan}:${port} (LAN)` : ""}
|
|
1253
|
+
`);
|
|
1254
|
+
}
|
|
1255
|
+
async function startDashboard() {
|
|
554
1256
|
const args = process.argv.slice(2);
|
|
555
1257
|
const config = {
|
|
556
1258
|
port: 3e3,
|
|
@@ -586,43 +1288,27 @@ async function startDashboard() {
|
|
|
586
1288
|
}
|
|
587
1289
|
const tracesPath = path3.resolve(config.tracesDir);
|
|
588
1290
|
if (!fs3.existsSync(tracesPath)) {
|
|
589
|
-
console.log(`Traces directory doesn't exist: ${tracesPath}`);
|
|
590
|
-
console.log("Creating traces directory...");
|
|
591
1291
|
fs3.mkdirSync(tracesPath, { recursive: true });
|
|
592
1292
|
}
|
|
593
1293
|
config.tracesDir = tracesPath;
|
|
594
|
-
console.log("\
|
|
595
|
-
console.log(` Port: ${config.port}`);
|
|
596
|
-
console.log(` Host: ${config.host}`);
|
|
597
|
-
console.log(` Traces: ${config.tracesDir}`);
|
|
598
|
-
console.log(` CORS: ${config.enableCors ? "enabled" : "disabled"}`);
|
|
599
|
-
if ((_a = config.dataDirs) == null ? void 0 : _a.length) {
|
|
600
|
-
console.log(` Data dirs: ${config.dataDirs.join(", ")}`);
|
|
601
|
-
}
|
|
1294
|
+
console.log("\nStarting AgentFlow Dashboard...\n");
|
|
602
1295
|
const dashboard = new DashboardServer(config);
|
|
603
1296
|
process.on("SIGINT", async () => {
|
|
604
|
-
console.log("
|
|
1297
|
+
console.log("\n\u{1F6D1} Shutting down dashboard...");
|
|
605
1298
|
await dashboard.stop();
|
|
606
1299
|
process.exit(0);
|
|
607
1300
|
});
|
|
608
1301
|
process.on("SIGTERM", async () => {
|
|
609
|
-
console.log("\\n\u{1F6D1} Received SIGTERM, shutting down...");
|
|
610
1302
|
await dashboard.stop();
|
|
611
1303
|
process.exit(0);
|
|
612
1304
|
});
|
|
613
1305
|
try {
|
|
614
1306
|
await dashboard.start();
|
|
615
|
-
console.log("\u2705 Dashboard started successfully!");
|
|
616
|
-
console.log(` Open: http://${config.host}:${config.port}`);
|
|
617
1307
|
setTimeout(() => {
|
|
618
1308
|
const stats = dashboard.getStats();
|
|
619
1309
|
const traces = dashboard.getTraces();
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
console.log(` Total Agents: ${stats.totalAgents}`);
|
|
623
|
-
console.log(` Success Rate: ${stats.globalSuccessRate.toFixed(1)}%`);
|
|
624
|
-
console.log(` Active Agents: ${stats.activeAgents}`);
|
|
625
|
-
}, 1e3);
|
|
1310
|
+
printBanner(config, traces.length, stats);
|
|
1311
|
+
}, 1500);
|
|
626
1312
|
} catch (error) {
|
|
627
1313
|
console.error("\u274C Failed to start dashboard:", error);
|
|
628
1314
|
process.exit(1);
|
|
@@ -630,7 +1316,7 @@ async function startDashboard() {
|
|
|
630
1316
|
}
|
|
631
1317
|
function printHelp() {
|
|
632
1318
|
console.log(`
|
|
633
|
-
\u{
|
|
1319
|
+
\u{1F4CA} AgentFlow Dashboard v${VERSION} \u2014 See your agents think.
|
|
634
1320
|
|
|
635
1321
|
Usage:
|
|
636
1322
|
agentflow-dashboard [options]
|
|
@@ -640,23 +1326,21 @@ Options:
|
|
|
640
1326
|
-p, --port <number> Server port (default: 3000)
|
|
641
1327
|
-t, --traces <path> Traces directory (default: ./traces)
|
|
642
1328
|
-h, --host <address> Host address (default: localhost)
|
|
643
|
-
--data-dir <path> Extra directory for process discovery (repeatable)
|
|
1329
|
+
--data-dir <path> Extra data directory for process discovery (repeatable)
|
|
644
1330
|
--cors Enable CORS headers
|
|
645
1331
|
--help Show this help message
|
|
646
1332
|
|
|
647
1333
|
Examples:
|
|
648
|
-
agentflow-dashboard --
|
|
649
|
-
agentflow-dashboard
|
|
650
|
-
agentflow-dashboard --traces ./
|
|
651
|
-
|
|
652
|
-
Features:
|
|
653
|
-
\u2728 Real-time trace monitoring
|
|
654
|
-
\u{1F4CA} Agent performance analytics
|
|
655
|
-
\u{1F3AF} Execution graph visualization
|
|
656
|
-
\u{1F4C8} Success/failure tracking
|
|
657
|
-
\u{1F50D} Multi-agent system overview
|
|
1334
|
+
agentflow-dashboard --traces ./traces --host 0.0.0.0 --cors
|
|
1335
|
+
agentflow-dashboard -p 8080 -t /var/log/agentflow
|
|
1336
|
+
agentflow-dashboard --traces ./traces --data-dir ./workers --data-dir ./cron
|
|
658
1337
|
|
|
659
|
-
|
|
1338
|
+
Tabs:
|
|
1339
|
+
\u{1F3AF} Graph Interactive Cytoscape.js execution graph
|
|
1340
|
+
\u23F1\uFE0F Timeline Waterfall view of node durations
|
|
1341
|
+
\u{1F4CA} Metrics Success rates, durations, node breakdown
|
|
1342
|
+
\u{1F6E0}\uFE0F Process Health PID files, systemd, workers, orphans
|
|
1343
|
+
\u26A0\uFE0F Errors Failed and hung nodes with metadata
|
|
660
1344
|
`);
|
|
661
1345
|
}
|
|
662
1346
|
// Annotate the CommonJS export names for ESM import in node:
|