agentflow-dashboard 0.2.0 → 0.3.1
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 +24 -7
- package/dist/chunk-EDHK4NJD.js +1319 -0
- package/dist/cli.cjs +777 -90
- package/dist/cli.js +2 -116
- package/dist/index.cjs +869 -68
- package/dist/index.js +1 -1
- package/dist/public/dashboard.js +1749 -287
- package/dist/public/debug.html +43 -0
- package/dist/public/index.html +1191 -239
- package/dist/server.cjs +1350 -0
- package/dist/server.js +6 -0
- package/package.json +2 -2
- package/public/dashboard.js +1749 -287
- package/public/debug.html +43 -0
- package/public/index.html +1191 -239
- package/dist/chunk-L24LYP6L.js +0 -515
package/dist/index.cjs
CHANGED
|
@@ -38,9 +38,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
38
38
|
|
|
39
39
|
// src/server.ts
|
|
40
40
|
var import_express = __toESM(require("express"), 1);
|
|
41
|
-
var
|
|
41
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
42
42
|
var import_http = require("http");
|
|
43
|
-
var
|
|
43
|
+
var path3 = __toESM(require("path"), 1);
|
|
44
44
|
var import_url = require("url");
|
|
45
45
|
var import_ws = require("ws");
|
|
46
46
|
var import_agentflow_core3 = require("agentflow-core");
|
|
@@ -227,14 +227,23 @@ var import_events = require("events");
|
|
|
227
227
|
var fs = __toESM(require("fs"), 1);
|
|
228
228
|
var path = __toESM(require("path"), 1);
|
|
229
229
|
var TraceWatcher = class extends import_events.EventEmitter {
|
|
230
|
-
|
|
230
|
+
watchers = [];
|
|
231
231
|
traces = /* @__PURE__ */ new Map();
|
|
232
232
|
tracesDir;
|
|
233
|
-
|
|
233
|
+
dataDirs;
|
|
234
|
+
allWatchDirs;
|
|
235
|
+
constructor(tracesDirOrOptions) {
|
|
234
236
|
super();
|
|
235
|
-
|
|
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];
|
|
236
245
|
this.ensureTracesDir();
|
|
237
|
-
this.
|
|
246
|
+
this.loadExistingFiles();
|
|
238
247
|
this.startWatching();
|
|
239
248
|
}
|
|
240
249
|
ensureTracesDir() {
|
|
@@ -243,18 +252,288 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
243
252
|
console.log(`Created traces directory: ${this.tracesDir}`);
|
|
244
253
|
}
|
|
245
254
|
}
|
|
246
|
-
|
|
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) {
|
|
247
282
|
try {
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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);
|
|
252
295
|
}
|
|
253
|
-
|
|
296
|
+
return traces.length > 0;
|
|
254
297
|
} catch (error) {
|
|
255
|
-
console.error(
|
|
298
|
+
console.error(`Error loading log file ${filePath}:`, error);
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
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;
|
|
256
527
|
}
|
|
257
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
|
+
}
|
|
258
537
|
loadTraceFile(filePath) {
|
|
259
538
|
try {
|
|
260
539
|
const content = fs.readFileSync(filePath, "utf8");
|
|
@@ -263,55 +542,404 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
263
542
|
const stats = fs.statSync(filePath);
|
|
264
543
|
graph.filename = filename;
|
|
265
544
|
graph.lastModified = stats.mtime.getTime();
|
|
266
|
-
|
|
545
|
+
graph.sourceType = "trace";
|
|
546
|
+
graph.sourceDir = path.dirname(filePath);
|
|
547
|
+
this.traces.set(this.traceKey(filePath), graph);
|
|
267
548
|
return true;
|
|
268
|
-
} catch
|
|
269
|
-
console.error(`Error loading trace file ${filePath}:`, error);
|
|
549
|
+
} catch {
|
|
270
550
|
return false;
|
|
271
551
|
}
|
|
272
552
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (trace) {
|
|
286
|
-
this.emit("trace-added", trace);
|
|
287
|
-
}
|
|
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 {
|
|
288
565
|
}
|
|
289
566
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
+
}
|
|
299
802
|
}
|
|
300
803
|
}
|
|
301
804
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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";
|
|
309
820
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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`);
|
|
315
943
|
}
|
|
316
944
|
getAllTraces() {
|
|
317
945
|
return Array.from(this.traces.values()).sort((a, b) => {
|
|
@@ -319,7 +947,14 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
319
947
|
});
|
|
320
948
|
}
|
|
321
949
|
getTrace(filename) {
|
|
322
|
-
|
|
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;
|
|
323
958
|
}
|
|
324
959
|
getTracesByAgent(agentId) {
|
|
325
960
|
return this.getAllTraces().filter((trace) => trace.agentId === agentId);
|
|
@@ -338,11 +973,11 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
338
973
|
return Array.from(agentIds).sort();
|
|
339
974
|
}
|
|
340
975
|
stop() {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
this.watcher = void 0;
|
|
344
|
-
console.log("Stopped watching traces directory");
|
|
976
|
+
for (const w of this.watchers) {
|
|
977
|
+
w.close();
|
|
345
978
|
}
|
|
979
|
+
this.watchers = [];
|
|
980
|
+
console.log("Stopped watching all directories");
|
|
346
981
|
}
|
|
347
982
|
getTraceStats() {
|
|
348
983
|
const total = this.traces.size;
|
|
@@ -362,14 +997,162 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
362
997
|
}
|
|
363
998
|
};
|
|
364
999
|
|
|
1000
|
+
// src/cli.ts
|
|
1001
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
1002
|
+
var os = __toESM(require("os"), 1);
|
|
1003
|
+
var path2 = __toESM(require("path"), 1);
|
|
1004
|
+
var VERSION = "0.3.1";
|
|
1005
|
+
function getLanAddress() {
|
|
1006
|
+
const interfaces = os.networkInterfaces();
|
|
1007
|
+
for (const name of Object.keys(interfaces)) {
|
|
1008
|
+
for (const iface of interfaces[name] || []) {
|
|
1009
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
1010
|
+
return iface.address;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
function printBanner(config, traceCount, stats) {
|
|
1017
|
+
var _a;
|
|
1018
|
+
const lan = getLanAddress();
|
|
1019
|
+
const host = config.host || "localhost";
|
|
1020
|
+
const port = config.port;
|
|
1021
|
+
const isPublic = host === "0.0.0.0";
|
|
1022
|
+
console.log(`
|
|
1023
|
+
___ _ _____ _
|
|
1024
|
+
/ _ \\ __ _ ___ _ __ | |_| ___| | _____ __
|
|
1025
|
+
| |_| |/ _\` |/ _ \\ '_ \\| __| |_ | |/ _ \\ \\ /\\ / /
|
|
1026
|
+
| _ | (_| | __/ | | | |_| _| | | (_) \\ V V /
|
|
1027
|
+
|_| |_|\\__, |\\___|_| |_|\\__|_| |_|\\___/ \\_/\\_/
|
|
1028
|
+
|___/ dashboard v${VERSION}
|
|
1029
|
+
|
|
1030
|
+
See your agents think.
|
|
1031
|
+
|
|
1032
|
+
\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
|
|
1033
|
+
\u2502 \u{1F916} Agents \u2502 TRACE FILES \u2502 \u{1F4CA} AgentFlow \u2502 SHOWS YOU \u2502 \u{1F310} Your browser \u2502
|
|
1034
|
+
\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
|
|
1035
|
+
\u2502 write JSON \u2502 \u2502 builds graphs, \u2502 \u2502 graph, timeline, \u2502
|
|
1036
|
+
\u2502 trace files. \u2502 \u2502 serves dashboard.\u2502 \u2502 metrics, health. \u2502
|
|
1037
|
+
\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
|
|
1038
|
+
|
|
1039
|
+
Runs locally. Your data never leaves your machine.
|
|
1040
|
+
|
|
1041
|
+
Tabs: \u{1F3AF} Graph \xB7 \u23F1\uFE0F Timeline \xB7 \u{1F4CA} Metrics \xB7 \u{1F6E0}\uFE0F Process Health \xB7 \u26A0\uFE0F Errors
|
|
1042
|
+
|
|
1043
|
+
Traces: ${config.tracesDir}${((_a = config.dataDirs) == null ? void 0 : _a.length) ? "\n Data dirs: " + config.dataDirs.join("\n ") : ""}
|
|
1044
|
+
Loaded: ${traceCount} traces \xB7 ${stats.totalAgents} agents \xB7 ${stats.totalExecutions} executions
|
|
1045
|
+
Success: ${stats.globalSuccessRate.toFixed(1)}%${stats.activeAgents > 0 ? ` \xB7 ${stats.activeAgents} active now` : ""}
|
|
1046
|
+
CORS: ${config.enableCors ? "enabled" : "disabled"}
|
|
1047
|
+
WebSocket: live updates enabled
|
|
1048
|
+
|
|
1049
|
+
\u2192 http://localhost:${port}${isPublic && lan ? `
|
|
1050
|
+
\u2192 http://${lan}:${port} (LAN)` : ""}
|
|
1051
|
+
`);
|
|
1052
|
+
}
|
|
1053
|
+
async function startDashboard() {
|
|
1054
|
+
const args = process.argv.slice(2);
|
|
1055
|
+
const config = {
|
|
1056
|
+
port: 3e3,
|
|
1057
|
+
tracesDir: "./traces",
|
|
1058
|
+
host: "localhost",
|
|
1059
|
+
enableCors: false
|
|
1060
|
+
};
|
|
1061
|
+
for (let i = 0; i < args.length; i++) {
|
|
1062
|
+
switch (args[i]) {
|
|
1063
|
+
case "--port":
|
|
1064
|
+
case "-p":
|
|
1065
|
+
config.port = parseInt(args[++i]) || 3e3;
|
|
1066
|
+
break;
|
|
1067
|
+
case "--traces":
|
|
1068
|
+
case "-t":
|
|
1069
|
+
config.tracesDir = args[++i];
|
|
1070
|
+
break;
|
|
1071
|
+
case "--host":
|
|
1072
|
+
case "-h":
|
|
1073
|
+
config.host = args[++i];
|
|
1074
|
+
break;
|
|
1075
|
+
case "--data-dir":
|
|
1076
|
+
if (!config.dataDirs) config.dataDirs = [];
|
|
1077
|
+
config.dataDirs.push(args[++i]);
|
|
1078
|
+
break;
|
|
1079
|
+
case "--cors":
|
|
1080
|
+
config.enableCors = true;
|
|
1081
|
+
break;
|
|
1082
|
+
case "--help":
|
|
1083
|
+
printHelp();
|
|
1084
|
+
process.exit(0);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
const tracesPath = path2.resolve(config.tracesDir);
|
|
1088
|
+
if (!fs2.existsSync(tracesPath)) {
|
|
1089
|
+
fs2.mkdirSync(tracesPath, { recursive: true });
|
|
1090
|
+
}
|
|
1091
|
+
config.tracesDir = tracesPath;
|
|
1092
|
+
console.log("\nStarting AgentFlow Dashboard...\n");
|
|
1093
|
+
const dashboard = new DashboardServer(config);
|
|
1094
|
+
process.on("SIGINT", async () => {
|
|
1095
|
+
console.log("\n\u{1F6D1} Shutting down dashboard...");
|
|
1096
|
+
await dashboard.stop();
|
|
1097
|
+
process.exit(0);
|
|
1098
|
+
});
|
|
1099
|
+
process.on("SIGTERM", async () => {
|
|
1100
|
+
await dashboard.stop();
|
|
1101
|
+
process.exit(0);
|
|
1102
|
+
});
|
|
1103
|
+
try {
|
|
1104
|
+
await dashboard.start();
|
|
1105
|
+
setTimeout(() => {
|
|
1106
|
+
const stats = dashboard.getStats();
|
|
1107
|
+
const traces = dashboard.getTraces();
|
|
1108
|
+
printBanner(config, traces.length, stats);
|
|
1109
|
+
}, 1500);
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
console.error("\u274C Failed to start dashboard:", error);
|
|
1112
|
+
process.exit(1);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
function printHelp() {
|
|
1116
|
+
console.log(`
|
|
1117
|
+
\u{1F4CA} AgentFlow Dashboard v${VERSION} \u2014 See your agents think.
|
|
1118
|
+
|
|
1119
|
+
Usage:
|
|
1120
|
+
agentflow-dashboard [options]
|
|
1121
|
+
npx agentflow-dashboard [options]
|
|
1122
|
+
|
|
1123
|
+
Options:
|
|
1124
|
+
-p, --port <number> Server port (default: 3000)
|
|
1125
|
+
-t, --traces <path> Traces directory (default: ./traces)
|
|
1126
|
+
-h, --host <address> Host address (default: localhost)
|
|
1127
|
+
--data-dir <path> Extra data directory for process discovery (repeatable)
|
|
1128
|
+
--cors Enable CORS headers
|
|
1129
|
+
--help Show this help message
|
|
1130
|
+
|
|
1131
|
+
Examples:
|
|
1132
|
+
agentflow-dashboard --traces ./traces --host 0.0.0.0 --cors
|
|
1133
|
+
agentflow-dashboard -p 8080 -t /var/log/agentflow
|
|
1134
|
+
agentflow-dashboard --traces ./traces --data-dir ./workers --data-dir ./cron
|
|
1135
|
+
|
|
1136
|
+
Tabs:
|
|
1137
|
+
\u{1F3AF} Graph Interactive Cytoscape.js execution graph
|
|
1138
|
+
\u23F1\uFE0F Timeline Waterfall view of node durations
|
|
1139
|
+
\u{1F4CA} Metrics Success rates, durations, node breakdown
|
|
1140
|
+
\u{1F6E0}\uFE0F Process Health PID files, systemd, workers, orphans
|
|
1141
|
+
\u26A0\uFE0F Errors Failed and hung nodes with metadata
|
|
1142
|
+
`);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
365
1145
|
// src/server.ts
|
|
366
1146
|
var import_meta = {};
|
|
367
1147
|
var __filename = (0, import_url.fileURLToPath)(import_meta.url);
|
|
368
|
-
var __dirname =
|
|
1148
|
+
var __dirname = path3.dirname(__filename);
|
|
369
1149
|
var DashboardServer = class {
|
|
370
1150
|
constructor(config) {
|
|
371
1151
|
this.config = config;
|
|
372
|
-
this.watcher = new TraceWatcher(
|
|
1152
|
+
this.watcher = new TraceWatcher({
|
|
1153
|
+
tracesDir: config.tracesDir,
|
|
1154
|
+
dataDirs: config.dataDirs
|
|
1155
|
+
});
|
|
373
1156
|
this.stats = new AgentStats();
|
|
374
1157
|
this.setupExpress();
|
|
375
1158
|
this.setupWebSocket();
|
|
@@ -392,8 +1175,8 @@ var DashboardServer = class {
|
|
|
392
1175
|
next();
|
|
393
1176
|
});
|
|
394
1177
|
}
|
|
395
|
-
const publicDir =
|
|
396
|
-
if (
|
|
1178
|
+
const publicDir = path3.join(__dirname, "../public");
|
|
1179
|
+
if (fs3.existsSync(publicDir)) {
|
|
397
1180
|
this.app.use(import_express.default.static(publicDir));
|
|
398
1181
|
}
|
|
399
1182
|
this.app.get("/api/traces", (req, res) => {
|
|
@@ -415,6 +1198,21 @@ var DashboardServer = class {
|
|
|
415
1198
|
res.status(500).json({ error: "Failed to load trace" });
|
|
416
1199
|
}
|
|
417
1200
|
});
|
|
1201
|
+
this.app.get("/api/traces/:filename/events", (req, res) => {
|
|
1202
|
+
try {
|
|
1203
|
+
const trace = this.watcher.getTrace(req.params.filename);
|
|
1204
|
+
if (!trace) {
|
|
1205
|
+
return res.status(404).json({ error: "Trace not found" });
|
|
1206
|
+
}
|
|
1207
|
+
res.json({
|
|
1208
|
+
events: trace.sessionEvents || [],
|
|
1209
|
+
tokenUsage: trace.tokenUsage || null,
|
|
1210
|
+
sourceType: trace.sourceType || "trace"
|
|
1211
|
+
});
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
res.status(500).json({ error: "Failed to load trace events" });
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
418
1216
|
this.app.get("/api/agents", (req, res) => {
|
|
419
1217
|
try {
|
|
420
1218
|
const agents = this.stats.getAgentsList();
|
|
@@ -450,7 +1248,7 @@ var DashboardServer = class {
|
|
|
450
1248
|
}
|
|
451
1249
|
const discoveryDirs = [
|
|
452
1250
|
this.config.tracesDir,
|
|
453
|
-
|
|
1251
|
+
path3.dirname(this.config.tracesDir),
|
|
454
1252
|
...this.config.dataDirs || []
|
|
455
1253
|
];
|
|
456
1254
|
const processConfig = (0, import_agentflow_core3.discoverProcessConfig)(discoveryDirs);
|
|
@@ -465,8 +1263,8 @@ var DashboardServer = class {
|
|
|
465
1263
|
}
|
|
466
1264
|
});
|
|
467
1265
|
this.app.get("*", (req, res) => {
|
|
468
|
-
const indexPath =
|
|
469
|
-
if (
|
|
1266
|
+
const indexPath = path3.join(__dirname, "../public/index.html");
|
|
1267
|
+
if (fs3.existsSync(indexPath)) {
|
|
470
1268
|
res.sendFile(indexPath);
|
|
471
1269
|
} else {
|
|
472
1270
|
res.status(404).send("Dashboard not found - public files may not be built");
|
|
@@ -523,21 +1321,21 @@ var DashboardServer = class {
|
|
|
523
1321
|
});
|
|
524
1322
|
}
|
|
525
1323
|
async start() {
|
|
526
|
-
return new Promise((
|
|
1324
|
+
return new Promise((resolve3) => {
|
|
527
1325
|
const host = this.config.host || "localhost";
|
|
528
1326
|
this.server.listen(this.config.port, host, () => {
|
|
529
1327
|
console.log(`AgentFlow Dashboard running at http://${host}:${this.config.port}`);
|
|
530
1328
|
console.log(`Watching traces in: ${this.config.tracesDir}`);
|
|
531
|
-
|
|
1329
|
+
resolve3();
|
|
532
1330
|
});
|
|
533
1331
|
});
|
|
534
1332
|
}
|
|
535
1333
|
async stop() {
|
|
536
|
-
return new Promise((
|
|
1334
|
+
return new Promise((resolve3) => {
|
|
537
1335
|
this.watcher.stop();
|
|
538
1336
|
this.server.close(() => {
|
|
539
1337
|
console.log("Dashboard server stopped");
|
|
540
|
-
|
|
1338
|
+
resolve3();
|
|
541
1339
|
});
|
|
542
1340
|
});
|
|
543
1341
|
}
|
|
@@ -548,6 +1346,9 @@ var DashboardServer = class {
|
|
|
548
1346
|
return this.watcher.getAllTraces();
|
|
549
1347
|
}
|
|
550
1348
|
};
|
|
1349
|
+
if (import_meta.url === `file://${process.argv[1]}`) {
|
|
1350
|
+
startDashboard().catch(console.error);
|
|
1351
|
+
}
|
|
551
1352
|
// Annotate the CommonJS export names for ESM import in node:
|
|
552
1353
|
0 && (module.exports = {
|
|
553
1354
|
AgentStats,
|