agentflow-core 0.1.3 → 0.2.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-TRKBUPIN.js +933 -0
- package/dist/cli.cjs +521 -33
- package/dist/cli.js +57 -14
- package/dist/index.cjs +403 -84
- package/dist/index.d.cts +72 -1
- package/dist/index.d.ts +72 -1
- package/dist/index.js +22 -205
- package/package.json +1 -1
- package/dist/chunk-DGLK6IBP.js +0 -402
package/dist/cli.cjs
CHANGED
|
@@ -235,6 +235,58 @@ function createGraphBuilder(config) {
|
|
|
235
235
|
return builder;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
// src/loader.ts
|
|
239
|
+
function toNodesMap(raw) {
|
|
240
|
+
if (raw instanceof Map) return raw;
|
|
241
|
+
if (Array.isArray(raw)) {
|
|
242
|
+
return new Map(raw);
|
|
243
|
+
}
|
|
244
|
+
if (raw !== null && typeof raw === "object") {
|
|
245
|
+
return new Map(Object.entries(raw));
|
|
246
|
+
}
|
|
247
|
+
return /* @__PURE__ */ new Map();
|
|
248
|
+
}
|
|
249
|
+
function loadGraph(input) {
|
|
250
|
+
const raw = typeof input === "string" ? JSON.parse(input) : input;
|
|
251
|
+
const nodes = toNodesMap(raw.nodes);
|
|
252
|
+
return {
|
|
253
|
+
id: raw.id ?? "",
|
|
254
|
+
rootNodeId: raw.rootNodeId ?? raw.rootId ?? "",
|
|
255
|
+
nodes,
|
|
256
|
+
edges: raw.edges ?? [],
|
|
257
|
+
startTime: raw.startTime ?? 0,
|
|
258
|
+
endTime: raw.endTime ?? null,
|
|
259
|
+
status: raw.status ?? "completed",
|
|
260
|
+
trigger: raw.trigger ?? "unknown",
|
|
261
|
+
agentId: raw.agentId ?? "unknown",
|
|
262
|
+
events: raw.events ?? [],
|
|
263
|
+
traceId: raw.traceId,
|
|
264
|
+
spanId: raw.spanId,
|
|
265
|
+
parentSpanId: raw.parentSpanId
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function graphToJson(graph) {
|
|
269
|
+
const nodesObj = {};
|
|
270
|
+
for (const [id, node] of graph.nodes) {
|
|
271
|
+
nodesObj[id] = node;
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
id: graph.id,
|
|
275
|
+
rootNodeId: graph.rootNodeId,
|
|
276
|
+
nodes: nodesObj,
|
|
277
|
+
edges: graph.edges,
|
|
278
|
+
startTime: graph.startTime,
|
|
279
|
+
endTime: graph.endTime,
|
|
280
|
+
status: graph.status,
|
|
281
|
+
trigger: graph.trigger,
|
|
282
|
+
agentId: graph.agentId,
|
|
283
|
+
events: graph.events,
|
|
284
|
+
traceId: graph.traceId,
|
|
285
|
+
spanId: graph.spanId,
|
|
286
|
+
parentSpanId: graph.parentSpanId
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
238
290
|
// src/runner.ts
|
|
239
291
|
function globToRegex(pattern) {
|
|
240
292
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
@@ -267,27 +319,6 @@ function deriveAgentId(command) {
|
|
|
267
319
|
function fileTimestamp() {
|
|
268
320
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
|
|
269
321
|
}
|
|
270
|
-
function graphToJson(graph) {
|
|
271
|
-
const nodesObj = {};
|
|
272
|
-
for (const [id, node] of graph.nodes) {
|
|
273
|
-
nodesObj[id] = node;
|
|
274
|
-
}
|
|
275
|
-
return {
|
|
276
|
-
id: graph.id,
|
|
277
|
-
rootNodeId: graph.rootNodeId,
|
|
278
|
-
nodes: nodesObj,
|
|
279
|
-
edges: graph.edges,
|
|
280
|
-
startTime: graph.startTime,
|
|
281
|
-
endTime: graph.endTime,
|
|
282
|
-
status: graph.status,
|
|
283
|
-
trigger: graph.trigger,
|
|
284
|
-
agentId: graph.agentId,
|
|
285
|
-
events: graph.events,
|
|
286
|
-
traceId: graph.traceId,
|
|
287
|
-
spanId: graph.spanId,
|
|
288
|
-
parentSpanId: graph.parentSpanId
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
322
|
async function runTraced(config) {
|
|
292
323
|
const {
|
|
293
324
|
command,
|
|
@@ -404,8 +435,442 @@ async function runTraced(config) {
|
|
|
404
435
|
};
|
|
405
436
|
}
|
|
406
437
|
|
|
407
|
-
// src/
|
|
438
|
+
// src/live.ts
|
|
439
|
+
var import_node_fs2 = require("fs");
|
|
440
|
+
var import_node_path2 = require("path");
|
|
441
|
+
|
|
442
|
+
// src/graph-query.ts
|
|
443
|
+
function getFailures(graph) {
|
|
444
|
+
const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
|
|
445
|
+
return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
|
|
446
|
+
}
|
|
447
|
+
function getHungNodes(graph) {
|
|
448
|
+
return [...graph.nodes.values()].filter(
|
|
449
|
+
(node) => node.status === "running" && node.endTime === null
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
function getDuration(graph) {
|
|
453
|
+
const end = graph.endTime ?? Date.now();
|
|
454
|
+
return end - graph.startTime;
|
|
455
|
+
}
|
|
456
|
+
function getDepth(graph) {
|
|
457
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
458
|
+
if (!root) return -1;
|
|
459
|
+
function dfs(node, depth) {
|
|
460
|
+
if (node.children.length === 0) return depth;
|
|
461
|
+
let maxDepth = depth;
|
|
462
|
+
for (const childId of node.children) {
|
|
463
|
+
const child = graph.nodes.get(childId);
|
|
464
|
+
if (!child) continue;
|
|
465
|
+
const childDepth = dfs(child, depth + 1);
|
|
466
|
+
if (childDepth > maxDepth) maxDepth = childDepth;
|
|
467
|
+
}
|
|
468
|
+
return maxDepth;
|
|
469
|
+
}
|
|
470
|
+
return dfs(root, 0);
|
|
471
|
+
}
|
|
472
|
+
function getStats(graph) {
|
|
473
|
+
const byStatus = {
|
|
474
|
+
running: 0,
|
|
475
|
+
completed: 0,
|
|
476
|
+
failed: 0,
|
|
477
|
+
hung: 0,
|
|
478
|
+
timeout: 0
|
|
479
|
+
};
|
|
480
|
+
const byType = {
|
|
481
|
+
agent: 0,
|
|
482
|
+
tool: 0,
|
|
483
|
+
subagent: 0,
|
|
484
|
+
wait: 0,
|
|
485
|
+
decision: 0,
|
|
486
|
+
custom: 0
|
|
487
|
+
};
|
|
488
|
+
let failureCount = 0;
|
|
489
|
+
let hungCount = 0;
|
|
490
|
+
for (const node of graph.nodes.values()) {
|
|
491
|
+
byStatus[node.status]++;
|
|
492
|
+
byType[node.type]++;
|
|
493
|
+
if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
|
|
494
|
+
failureCount++;
|
|
495
|
+
}
|
|
496
|
+
if (node.status === "running" && node.endTime === null) {
|
|
497
|
+
hungCount++;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
totalNodes: graph.nodes.size,
|
|
502
|
+
byStatus,
|
|
503
|
+
byType,
|
|
504
|
+
depth: getDepth(graph),
|
|
505
|
+
duration: getDuration(graph),
|
|
506
|
+
failureCount,
|
|
507
|
+
hungCount
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/graph-stitch.ts
|
|
512
|
+
function groupByTraceId(graphs) {
|
|
513
|
+
const groups = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const g of graphs) {
|
|
515
|
+
if (!g.traceId) continue;
|
|
516
|
+
const arr = groups.get(g.traceId) ?? [];
|
|
517
|
+
arr.push(g);
|
|
518
|
+
groups.set(g.traceId, arr);
|
|
519
|
+
}
|
|
520
|
+
return groups;
|
|
521
|
+
}
|
|
522
|
+
function stitchTrace(graphs) {
|
|
523
|
+
if (graphs.length === 0) throw new Error("No graphs to stitch");
|
|
524
|
+
const traceId = graphs[0].traceId ?? "";
|
|
525
|
+
const graphsBySpan = /* @__PURE__ */ new Map();
|
|
526
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
527
|
+
let rootGraph = null;
|
|
528
|
+
for (const g of graphs) {
|
|
529
|
+
if (g.spanId) graphsBySpan.set(g.spanId, g);
|
|
530
|
+
if (!g.parentSpanId) {
|
|
531
|
+
if (!rootGraph || g.startTime < rootGraph.startTime) rootGraph = g;
|
|
532
|
+
}
|
|
533
|
+
if (g.parentSpanId) {
|
|
534
|
+
const siblings = childMap.get(g.parentSpanId) ?? [];
|
|
535
|
+
if (g.spanId) siblings.push(g.spanId);
|
|
536
|
+
childMap.set(g.parentSpanId, siblings);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (!rootGraph) rootGraph = graphs[0];
|
|
540
|
+
let status = "completed";
|
|
541
|
+
let endTime = 0;
|
|
542
|
+
let startTime = Infinity;
|
|
543
|
+
for (const g of graphs) {
|
|
544
|
+
startTime = Math.min(startTime, g.startTime);
|
|
545
|
+
if (g.status === "failed") status = "failed";
|
|
546
|
+
else if (g.status === "running" && status !== "failed") status = "running";
|
|
547
|
+
if (g.endTime === null) endTime = null;
|
|
548
|
+
else if (endTime !== null) endTime = Math.max(endTime, g.endTime);
|
|
549
|
+
}
|
|
550
|
+
const frozenChildMap = /* @__PURE__ */ new Map();
|
|
551
|
+
for (const [k, v] of childMap) frozenChildMap.set(k, Object.freeze([...v]));
|
|
552
|
+
return Object.freeze({
|
|
553
|
+
traceId,
|
|
554
|
+
graphs: graphsBySpan,
|
|
555
|
+
rootGraph,
|
|
556
|
+
childMap: frozenChildMap,
|
|
557
|
+
startTime,
|
|
558
|
+
endTime,
|
|
559
|
+
status
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
function getTraceTree(trace) {
|
|
563
|
+
const result = [];
|
|
564
|
+
function walk(spanId) {
|
|
565
|
+
const graph = trace.graphs.get(spanId);
|
|
566
|
+
if (graph) result.push(graph);
|
|
567
|
+
const children = trace.childMap.get(spanId) ?? [];
|
|
568
|
+
for (const childSpan of children) walk(childSpan);
|
|
569
|
+
}
|
|
570
|
+
if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
|
|
571
|
+
else result.push(trace.rootGraph);
|
|
572
|
+
return result;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/live.ts
|
|
576
|
+
var C = {
|
|
577
|
+
reset: "\x1B[0m",
|
|
578
|
+
bold: "\x1B[1m",
|
|
579
|
+
dim: "\x1B[90m",
|
|
580
|
+
under: "\x1B[4m",
|
|
581
|
+
red: "\x1B[31m",
|
|
582
|
+
green: "\x1B[32m",
|
|
583
|
+
yellow: "\x1B[33m",
|
|
584
|
+
blue: "\x1B[34m",
|
|
585
|
+
magenta: "\x1B[35m",
|
|
586
|
+
cyan: "\x1B[36m",
|
|
587
|
+
white: "\x1B[37m"
|
|
588
|
+
};
|
|
408
589
|
function parseArgs(argv) {
|
|
590
|
+
const config = {
|
|
591
|
+
tracesDir: "./traces",
|
|
592
|
+
refreshMs: 3e3
|
|
593
|
+
};
|
|
594
|
+
const args = argv.slice(0);
|
|
595
|
+
if (args[0] === "live") args.shift();
|
|
596
|
+
let i = 0;
|
|
597
|
+
while (i < args.length) {
|
|
598
|
+
const arg = args[i];
|
|
599
|
+
if (arg === "--help" || arg === "-h") {
|
|
600
|
+
printUsage();
|
|
601
|
+
process.exit(0);
|
|
602
|
+
} else if (arg === "--refresh" || arg === "-r") {
|
|
603
|
+
i++;
|
|
604
|
+
const val = parseInt(args[i] ?? "", 10);
|
|
605
|
+
if (!isNaN(val) && val > 0) config.refreshMs = val * 1e3;
|
|
606
|
+
i++;
|
|
607
|
+
} else if (arg === "--traces-dir" || arg === "-t") {
|
|
608
|
+
i++;
|
|
609
|
+
config.tracesDir = args[i] ?? config.tracesDir;
|
|
610
|
+
i++;
|
|
611
|
+
} else if (!arg.startsWith("-")) {
|
|
612
|
+
config.tracesDir = arg;
|
|
613
|
+
i++;
|
|
614
|
+
} else {
|
|
615
|
+
i++;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
config.tracesDir = (0, import_node_path2.resolve)(config.tracesDir);
|
|
619
|
+
return config;
|
|
620
|
+
}
|
|
621
|
+
function printUsage() {
|
|
622
|
+
console.log(`
|
|
623
|
+
AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
|
|
624
|
+
|
|
625
|
+
Usage:
|
|
626
|
+
agentflow live [traces-dir] [options]
|
|
627
|
+
|
|
628
|
+
Arguments:
|
|
629
|
+
traces-dir Path to the traces directory (default: ./traces)
|
|
630
|
+
|
|
631
|
+
Options:
|
|
632
|
+
-r, --refresh <secs> Refresh interval in seconds (default: 3)
|
|
633
|
+
-t, --traces-dir <path> Explicit traces directory path
|
|
634
|
+
-h, --help Show this help message
|
|
635
|
+
|
|
636
|
+
Examples:
|
|
637
|
+
agentflow live
|
|
638
|
+
agentflow live ./my-traces --refresh 5
|
|
639
|
+
agentflow live /var/log/agentflow/traces -r 10
|
|
640
|
+
`.trim());
|
|
641
|
+
}
|
|
642
|
+
function listTraceFiles(tracesDir) {
|
|
643
|
+
try {
|
|
644
|
+
return (0, import_node_fs2.readdirSync)(tracesDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
645
|
+
const fp = (0, import_node_path2.join)(tracesDir, f);
|
|
646
|
+
const stat = (0, import_node_fs2.statSync)(fp);
|
|
647
|
+
return { filename: f, path: fp, mtime: stat.mtime.getTime() };
|
|
648
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
649
|
+
} catch {
|
|
650
|
+
return [];
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function safeLoadTrace(fp) {
|
|
654
|
+
try {
|
|
655
|
+
return loadGraph((0, import_node_fs2.readFileSync)(fp, "utf8"));
|
|
656
|
+
} catch {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function analyze(trace) {
|
|
661
|
+
try {
|
|
662
|
+
const stats = getStats(trace);
|
|
663
|
+
const fails = getFailures(trace);
|
|
664
|
+
const hung = getHungNodes(trace);
|
|
665
|
+
return {
|
|
666
|
+
agentId: trace.agentId,
|
|
667
|
+
trigger: trace.trigger,
|
|
668
|
+
traceId: trace.traceId,
|
|
669
|
+
spanId: trace.spanId,
|
|
670
|
+
parentSpanId: trace.parentSpanId,
|
|
671
|
+
nodes: stats.totalNodes,
|
|
672
|
+
success: fails.length === 0 && hung.length === 0,
|
|
673
|
+
failures: fails.length,
|
|
674
|
+
hung: hung.length
|
|
675
|
+
};
|
|
676
|
+
} catch {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function getDistributedDepth(dt, spanId) {
|
|
681
|
+
if (!spanId) return 0;
|
|
682
|
+
const graph = dt.graphs.get(spanId);
|
|
683
|
+
if (!graph || !graph.parentSpanId) return 0;
|
|
684
|
+
return 1 + getDistributedDepth(dt, graph.parentSpanId);
|
|
685
|
+
}
|
|
686
|
+
var prevFileCount = 0;
|
|
687
|
+
var newExecCount = 0;
|
|
688
|
+
var sessionStart = Date.now();
|
|
689
|
+
function render(config) {
|
|
690
|
+
const files = listTraceFiles(config.tracesDir);
|
|
691
|
+
if (files.length > prevFileCount && prevFileCount > 0) {
|
|
692
|
+
newExecCount += files.length - prevFileCount;
|
|
693
|
+
}
|
|
694
|
+
prevFileCount = files.length;
|
|
695
|
+
const allTraces = [];
|
|
696
|
+
const agents = {};
|
|
697
|
+
for (const f of files.slice(0, 200)) {
|
|
698
|
+
const trace = safeLoadTrace(f.path);
|
|
699
|
+
if (!trace) continue;
|
|
700
|
+
allTraces.push(trace);
|
|
701
|
+
const a = analyze(trace);
|
|
702
|
+
if (!a) continue;
|
|
703
|
+
if (!agents[a.agentId]) {
|
|
704
|
+
agents[a.agentId] = { name: a.agentId, total: 0, ok: 0, fail: 0, lastTs: 0 };
|
|
705
|
+
}
|
|
706
|
+
const ag = agents[a.agentId];
|
|
707
|
+
ag.total++;
|
|
708
|
+
a.success ? ag.ok++ : ag.fail++;
|
|
709
|
+
if (f.mtime > ag.lastTs) ag.lastTs = f.mtime;
|
|
710
|
+
}
|
|
711
|
+
const agentList = Object.values(agents).sort((a, b) => b.total - a.total);
|
|
712
|
+
const totExec = agentList.reduce((s, a) => s + a.total, 0);
|
|
713
|
+
const totFail = agentList.reduce((s, a) => s + a.fail, 0);
|
|
714
|
+
const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
|
|
715
|
+
const recent = [];
|
|
716
|
+
for (const f of files.slice(0, 15)) {
|
|
717
|
+
const trace = safeLoadTrace(f.path);
|
|
718
|
+
if (!trace) continue;
|
|
719
|
+
const a = analyze(trace);
|
|
720
|
+
if (a) recent.push({ ...a, ts: f.mtime });
|
|
721
|
+
}
|
|
722
|
+
const now = Date.now();
|
|
723
|
+
const buckets = new Array(12).fill(0);
|
|
724
|
+
const failBuckets = new Array(12).fill(0);
|
|
725
|
+
for (const f of files) {
|
|
726
|
+
const age = now - f.mtime;
|
|
727
|
+
if (age > 36e5) continue;
|
|
728
|
+
const idx = 11 - Math.floor(age / 3e5);
|
|
729
|
+
if (idx >= 0 && idx < 12) {
|
|
730
|
+
const trace = safeLoadTrace(f.path);
|
|
731
|
+
if (!trace) continue;
|
|
732
|
+
const a = analyze(trace);
|
|
733
|
+
if (!a) continue;
|
|
734
|
+
buckets[idx]++;
|
|
735
|
+
if (!a.success) failBuckets[idx]++;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const maxBucket = Math.max(...buckets, 1);
|
|
739
|
+
const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
740
|
+
const spark = buckets.map((v, i) => {
|
|
741
|
+
const level = Math.round(v / maxBucket * 8);
|
|
742
|
+
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
743
|
+
}).join("");
|
|
744
|
+
const traceGroups = groupByTraceId(allTraces);
|
|
745
|
+
const distributedTraces = [];
|
|
746
|
+
for (const [_traceId, graphs] of traceGroups) {
|
|
747
|
+
if (graphs.length > 1) {
|
|
748
|
+
try {
|
|
749
|
+
distributedTraces.push(stitchTrace(graphs));
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
distributedTraces.sort((a, b) => b.startTime - a.startTime);
|
|
755
|
+
const upSec = Math.floor((Date.now() - sessionStart) / 1e3);
|
|
756
|
+
const upMin = Math.floor(upSec / 60);
|
|
757
|
+
const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
|
|
758
|
+
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
759
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
760
|
+
console.log(`${C.bold}${C.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${C.reset}`);
|
|
761
|
+
console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.bold}${C.white}AGENTFLOW LIVE${C.reset} ${C.green}\u25CF LIVE${C.reset} ${C.dim}${time}${C.reset} ${C.bold}${C.cyan}\u2551${C.reset}`);
|
|
762
|
+
const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr}`;
|
|
763
|
+
const pad1 = Math.max(0, 64 - metaLine.length);
|
|
764
|
+
console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
|
|
765
|
+
console.log(`${C.bold}${C.cyan}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${C.reset}`);
|
|
766
|
+
const sc = totFail === 0 ? C.green : C.yellow;
|
|
767
|
+
console.log("");
|
|
768
|
+
console.log(` ${C.bold}Agents${C.reset} ${sc}${agentList.length}${C.reset} ${C.bold}Executions${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Traces${C.reset} ${sc}${files.length}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset} ${C.bold}Distributed${C.reset} ${C.magenta}${distributedTraces.length}${C.reset}`);
|
|
769
|
+
console.log("");
|
|
770
|
+
console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
771
|
+
console.log("");
|
|
772
|
+
console.log(` ${C.bold}${C.under}Agent Runs OK Fail Rate Last Active${C.reset}`);
|
|
773
|
+
for (const ag of agentList) {
|
|
774
|
+
const rate = (ag.ok / ag.total * 100).toFixed(0);
|
|
775
|
+
const lastTime = new Date(ag.lastTs).toLocaleTimeString();
|
|
776
|
+
const isRecent = Date.now() - ag.lastTs < 3e5;
|
|
777
|
+
let status;
|
|
778
|
+
if (ag.fail > 0) status = `${C.red}\u25CF${C.reset}`;
|
|
779
|
+
else if (isRecent) status = `${C.green}\u25CF${C.reset}`;
|
|
780
|
+
else status = `${C.dim}\u25CB${C.reset}`;
|
|
781
|
+
const name = ag.name.padEnd(28);
|
|
782
|
+
const runs = String(ag.total).padStart(5);
|
|
783
|
+
const ok = String(ag.ok).padStart(5);
|
|
784
|
+
const fail = ag.fail > 0 ? `${C.red}${String(ag.fail).padStart(4)}${C.reset}` : String(ag.fail).padStart(4);
|
|
785
|
+
const rateStr = (rate + "%").padStart(5);
|
|
786
|
+
const activeStr = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
|
|
787
|
+
console.log(` ${status} ${name}${runs}${ok}${fail} ${rateStr} ${activeStr}`);
|
|
788
|
+
}
|
|
789
|
+
if (distributedTraces.length > 0) {
|
|
790
|
+
console.log("");
|
|
791
|
+
console.log(` ${C.bold}${C.under}Distributed Traces (multi-agent workflows)${C.reset}`);
|
|
792
|
+
for (const dt of distributedTraces.slice(0, 5)) {
|
|
793
|
+
const traceTime = new Date(dt.startTime).toLocaleTimeString();
|
|
794
|
+
const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
795
|
+
const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
|
|
796
|
+
const tid = dt.traceId.slice(0, 8);
|
|
797
|
+
console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime}${C.reset} ${C.dim}${dur}${C.reset} ${C.dim}(${dt.graphs.size} agents)${C.reset}`);
|
|
798
|
+
const tree = getTraceTree(dt);
|
|
799
|
+
for (let i = 0; i < tree.length; i++) {
|
|
800
|
+
const g = tree[i];
|
|
801
|
+
const depth = getDistributedDepth(dt, g.spanId);
|
|
802
|
+
const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
|
|
803
|
+
const isLast = i === tree.length - 1 || getDistributedDepth(dt, tree[i + 1]?.spanId) <= depth;
|
|
804
|
+
const connector = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
805
|
+
const gStatus = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
806
|
+
const gDur = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
|
|
807
|
+
console.log(`${indent}${connector}${gStatus} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gDur}${C.reset}`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
console.log("");
|
|
812
|
+
console.log(` ${C.bold}${C.under}Recent Executions${C.reset}`);
|
|
813
|
+
for (const ex of recent.slice(0, 8)) {
|
|
814
|
+
const icon = ex.success ? `${C.green}\u2713${C.reset}` : `${C.red}\u2717${C.reset}`;
|
|
815
|
+
const t = new Date(ex.ts).toLocaleTimeString();
|
|
816
|
+
const agent = ex.agentId.padEnd(28);
|
|
817
|
+
const age = Math.floor((Date.now() - ex.ts) / 1e3);
|
|
818
|
+
const ageStr = age < 60 ? age + "s ago" : Math.floor(age / 60) + "m ago";
|
|
819
|
+
const traceTag = ex.traceId ? ` ${C.magenta}\u29EB${C.reset}` : "";
|
|
820
|
+
console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)} ${ex.nodes} nodes${C.reset}${traceTag}`);
|
|
821
|
+
}
|
|
822
|
+
if (files.length === 0) {
|
|
823
|
+
console.log(` ${C.dim}No trace files found. Waiting for traces in:${C.reset}`);
|
|
824
|
+
console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
|
|
825
|
+
}
|
|
826
|
+
console.log("");
|
|
827
|
+
console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
|
|
828
|
+
console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
829
|
+
}
|
|
830
|
+
function startLive(argv) {
|
|
831
|
+
const config = parseArgs(argv);
|
|
832
|
+
if (!(0, import_node_fs2.existsSync)(config.tracesDir)) {
|
|
833
|
+
console.error(`Traces directory does not exist: ${config.tracesDir}`);
|
|
834
|
+
console.error("Create it or specify a different path: agentflow live <traces-dir>");
|
|
835
|
+
process.exit(1);
|
|
836
|
+
}
|
|
837
|
+
render(config);
|
|
838
|
+
let debounce = null;
|
|
839
|
+
try {
|
|
840
|
+
(0, import_node_fs2.watch)(config.tracesDir, () => {
|
|
841
|
+
if (debounce) clearTimeout(debounce);
|
|
842
|
+
debounce = setTimeout(() => render(config), 500);
|
|
843
|
+
});
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
setInterval(() => render(config), config.refreshMs);
|
|
847
|
+
process.on("SIGINT", () => {
|
|
848
|
+
console.log("\n" + C.dim + "Monitor stopped." + C.reset);
|
|
849
|
+
process.exit(0);
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// src/cli.ts
|
|
854
|
+
function printHelp() {
|
|
855
|
+
console.log(`
|
|
856
|
+
AgentFlow CLI \u2014 execution tracing and live monitoring for AI agent systems.
|
|
857
|
+
|
|
858
|
+
Usage:
|
|
859
|
+
agentflow <command> [options]
|
|
860
|
+
|
|
861
|
+
Commands:
|
|
862
|
+
run [options] -- <cmd> Wrap a command with automatic execution tracing
|
|
863
|
+
live [traces-dir] [options] Real-time terminal monitor for trace files
|
|
864
|
+
|
|
865
|
+
Run \`agentflow <command> --help\` for command-specific options.
|
|
866
|
+
|
|
867
|
+
Examples:
|
|
868
|
+
agentflow run --traces-dir ./traces -- python -m myagent process
|
|
869
|
+
agentflow live ./traces
|
|
870
|
+
agentflow live ./traces --refresh 5
|
|
871
|
+
`.trim());
|
|
872
|
+
}
|
|
873
|
+
function parseRunArgs(argv) {
|
|
409
874
|
const result = {
|
|
410
875
|
tracesDir: "./traces",
|
|
411
876
|
watchDirs: [],
|
|
@@ -463,9 +928,9 @@ function parseArgs(argv) {
|
|
|
463
928
|
result.command = commandArgs;
|
|
464
929
|
return result;
|
|
465
930
|
}
|
|
466
|
-
function
|
|
931
|
+
function printRunUsage() {
|
|
467
932
|
console.log(`
|
|
468
|
-
AgentFlow
|
|
933
|
+
AgentFlow Run \u2014 wrap any command with automatic execution tracing.
|
|
469
934
|
|
|
470
935
|
Usage:
|
|
471
936
|
agentflow run [options] -- <command>
|
|
@@ -479,21 +944,20 @@ Options:
|
|
|
479
944
|
--help Show this help message
|
|
480
945
|
|
|
481
946
|
Examples:
|
|
482
|
-
agentflow run -- python -m
|
|
483
|
-
agentflow run --watch-dir
|
|
484
|
-
agentflow run --traces-dir ./my-traces --agent-id
|
|
947
|
+
agentflow run -- python -m myagent process
|
|
948
|
+
agentflow run --watch-dir ./data -- python worker.py
|
|
949
|
+
agentflow run --traces-dir ./my-traces --agent-id recon -- node agent.js
|
|
485
950
|
`.trim());
|
|
486
951
|
}
|
|
487
|
-
async function
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
printUsage();
|
|
952
|
+
async function runCommand(argv) {
|
|
953
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
954
|
+
printRunUsage();
|
|
491
955
|
process.exit(0);
|
|
492
956
|
}
|
|
493
|
-
const parsed =
|
|
957
|
+
const parsed = parseRunArgs(argv);
|
|
494
958
|
if (parsed.command.length === 0) {
|
|
495
959
|
console.error("Error: No command specified. Use -- to separate agentflow flags from the command.");
|
|
496
|
-
console.error("Example: agentflow run -- python -m
|
|
960
|
+
console.error("Example: agentflow run -- python -m myagent process");
|
|
497
961
|
process.exit(1);
|
|
498
962
|
}
|
|
499
963
|
const commandStr = parsed.command.join(" ");
|
|
@@ -539,4 +1003,28 @@ async function main() {
|
|
|
539
1003
|
process.exit(1);
|
|
540
1004
|
}
|
|
541
1005
|
}
|
|
1006
|
+
async function main() {
|
|
1007
|
+
const argv = process.argv.slice(2);
|
|
1008
|
+
if (argv.length === 0 || argv[0] !== "run" && argv[0] !== "live" && (argv.includes("--help") || argv.includes("-h"))) {
|
|
1009
|
+
printHelp();
|
|
1010
|
+
process.exit(0);
|
|
1011
|
+
}
|
|
1012
|
+
const subcommand = argv[0];
|
|
1013
|
+
switch (subcommand) {
|
|
1014
|
+
case "run":
|
|
1015
|
+
await runCommand(argv);
|
|
1016
|
+
break;
|
|
1017
|
+
case "live":
|
|
1018
|
+
startLive(argv);
|
|
1019
|
+
break;
|
|
1020
|
+
default:
|
|
1021
|
+
if (!subcommand?.startsWith("-")) {
|
|
1022
|
+
startLive(["live", ...argv]);
|
|
1023
|
+
} else {
|
|
1024
|
+
printHelp();
|
|
1025
|
+
process.exit(1);
|
|
1026
|
+
}
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
542
1030
|
main();
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
runTraced
|
|
4
|
-
|
|
3
|
+
runTraced,
|
|
4
|
+
startLive
|
|
5
|
+
} from "./chunk-TRKBUPIN.js";
|
|
5
6
|
|
|
6
7
|
// src/cli.ts
|
|
7
8
|
import { basename } from "path";
|
|
8
|
-
function
|
|
9
|
+
function printHelp() {
|
|
10
|
+
console.log(`
|
|
11
|
+
AgentFlow CLI \u2014 execution tracing and live monitoring for AI agent systems.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
agentflow <command> [options]
|
|
15
|
+
|
|
16
|
+
Commands:
|
|
17
|
+
run [options] -- <cmd> Wrap a command with automatic execution tracing
|
|
18
|
+
live [traces-dir] [options] Real-time terminal monitor for trace files
|
|
19
|
+
|
|
20
|
+
Run \`agentflow <command> --help\` for command-specific options.
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
agentflow run --traces-dir ./traces -- python -m myagent process
|
|
24
|
+
agentflow live ./traces
|
|
25
|
+
agentflow live ./traces --refresh 5
|
|
26
|
+
`.trim());
|
|
27
|
+
}
|
|
28
|
+
function parseRunArgs(argv) {
|
|
9
29
|
const result = {
|
|
10
30
|
tracesDir: "./traces",
|
|
11
31
|
watchDirs: [],
|
|
@@ -63,9 +83,9 @@ function parseArgs(argv) {
|
|
|
63
83
|
result.command = commandArgs;
|
|
64
84
|
return result;
|
|
65
85
|
}
|
|
66
|
-
function
|
|
86
|
+
function printRunUsage() {
|
|
67
87
|
console.log(`
|
|
68
|
-
AgentFlow
|
|
88
|
+
AgentFlow Run \u2014 wrap any command with automatic execution tracing.
|
|
69
89
|
|
|
70
90
|
Usage:
|
|
71
91
|
agentflow run [options] -- <command>
|
|
@@ -79,21 +99,20 @@ Options:
|
|
|
79
99
|
--help Show this help message
|
|
80
100
|
|
|
81
101
|
Examples:
|
|
82
|
-
agentflow run -- python -m
|
|
83
|
-
agentflow run --watch-dir
|
|
84
|
-
agentflow run --traces-dir ./my-traces --agent-id
|
|
102
|
+
agentflow run -- python -m myagent process
|
|
103
|
+
agentflow run --watch-dir ./data -- python worker.py
|
|
104
|
+
agentflow run --traces-dir ./my-traces --agent-id recon -- node agent.js
|
|
85
105
|
`.trim());
|
|
86
106
|
}
|
|
87
|
-
async function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
printUsage();
|
|
107
|
+
async function runCommand(argv) {
|
|
108
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
109
|
+
printRunUsage();
|
|
91
110
|
process.exit(0);
|
|
92
111
|
}
|
|
93
|
-
const parsed =
|
|
112
|
+
const parsed = parseRunArgs(argv);
|
|
94
113
|
if (parsed.command.length === 0) {
|
|
95
114
|
console.error("Error: No command specified. Use -- to separate agentflow flags from the command.");
|
|
96
|
-
console.error("Example: agentflow run -- python -m
|
|
115
|
+
console.error("Example: agentflow run -- python -m myagent process");
|
|
97
116
|
process.exit(1);
|
|
98
117
|
}
|
|
99
118
|
const commandStr = parsed.command.join(" ");
|
|
@@ -139,4 +158,28 @@ async function main() {
|
|
|
139
158
|
process.exit(1);
|
|
140
159
|
}
|
|
141
160
|
}
|
|
161
|
+
async function main() {
|
|
162
|
+
const argv = process.argv.slice(2);
|
|
163
|
+
if (argv.length === 0 || argv[0] !== "run" && argv[0] !== "live" && (argv.includes("--help") || argv.includes("-h"))) {
|
|
164
|
+
printHelp();
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}
|
|
167
|
+
const subcommand = argv[0];
|
|
168
|
+
switch (subcommand) {
|
|
169
|
+
case "run":
|
|
170
|
+
await runCommand(argv);
|
|
171
|
+
break;
|
|
172
|
+
case "live":
|
|
173
|
+
startLive(argv);
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
if (!subcommand?.startsWith("-")) {
|
|
177
|
+
startLive(["live", ...argv]);
|
|
178
|
+
} else {
|
|
179
|
+
printHelp();
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
142
185
|
main();
|