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/index.cjs
CHANGED
|
@@ -33,8 +33,11 @@ __export(index_exports, {
|
|
|
33
33
|
getStats: () => getStats,
|
|
34
34
|
getSubtree: () => getSubtree,
|
|
35
35
|
getTraceTree: () => getTraceTree,
|
|
36
|
+
graphToJson: () => graphToJson,
|
|
36
37
|
groupByTraceId: () => groupByTraceId,
|
|
38
|
+
loadGraph: () => loadGraph,
|
|
37
39
|
runTraced: () => runTraced,
|
|
40
|
+
startLive: () => startLive,
|
|
38
41
|
stitchTrace: () => stitchTrace
|
|
39
42
|
});
|
|
40
43
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -265,6 +268,58 @@ function createGraphBuilder(config) {
|
|
|
265
268
|
return builder;
|
|
266
269
|
}
|
|
267
270
|
|
|
271
|
+
// src/loader.ts
|
|
272
|
+
function toNodesMap(raw) {
|
|
273
|
+
if (raw instanceof Map) return raw;
|
|
274
|
+
if (Array.isArray(raw)) {
|
|
275
|
+
return new Map(raw);
|
|
276
|
+
}
|
|
277
|
+
if (raw !== null && typeof raw === "object") {
|
|
278
|
+
return new Map(Object.entries(raw));
|
|
279
|
+
}
|
|
280
|
+
return /* @__PURE__ */ new Map();
|
|
281
|
+
}
|
|
282
|
+
function loadGraph(input) {
|
|
283
|
+
const raw = typeof input === "string" ? JSON.parse(input) : input;
|
|
284
|
+
const nodes = toNodesMap(raw.nodes);
|
|
285
|
+
return {
|
|
286
|
+
id: raw.id ?? "",
|
|
287
|
+
rootNodeId: raw.rootNodeId ?? raw.rootId ?? "",
|
|
288
|
+
nodes,
|
|
289
|
+
edges: raw.edges ?? [],
|
|
290
|
+
startTime: raw.startTime ?? 0,
|
|
291
|
+
endTime: raw.endTime ?? null,
|
|
292
|
+
status: raw.status ?? "completed",
|
|
293
|
+
trigger: raw.trigger ?? "unknown",
|
|
294
|
+
agentId: raw.agentId ?? "unknown",
|
|
295
|
+
events: raw.events ?? [],
|
|
296
|
+
traceId: raw.traceId,
|
|
297
|
+
spanId: raw.spanId,
|
|
298
|
+
parentSpanId: raw.parentSpanId
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function graphToJson(graph) {
|
|
302
|
+
const nodesObj = {};
|
|
303
|
+
for (const [id, node] of graph.nodes) {
|
|
304
|
+
nodesObj[id] = node;
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
id: graph.id,
|
|
308
|
+
rootNodeId: graph.rootNodeId,
|
|
309
|
+
nodes: nodesObj,
|
|
310
|
+
edges: graph.edges,
|
|
311
|
+
startTime: graph.startTime,
|
|
312
|
+
endTime: graph.endTime,
|
|
313
|
+
status: graph.status,
|
|
314
|
+
trigger: graph.trigger,
|
|
315
|
+
agentId: graph.agentId,
|
|
316
|
+
events: graph.events,
|
|
317
|
+
traceId: graph.traceId,
|
|
318
|
+
spanId: graph.spanId,
|
|
319
|
+
parentSpanId: graph.parentSpanId
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
268
323
|
// src/runner.ts
|
|
269
324
|
var import_node_child_process = require("child_process");
|
|
270
325
|
var import_node_fs = require("fs");
|
|
@@ -300,27 +355,6 @@ function deriveAgentId(command) {
|
|
|
300
355
|
function fileTimestamp() {
|
|
301
356
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
|
|
302
357
|
}
|
|
303
|
-
function graphToJson(graph) {
|
|
304
|
-
const nodesObj = {};
|
|
305
|
-
for (const [id, node] of graph.nodes) {
|
|
306
|
-
nodesObj[id] = node;
|
|
307
|
-
}
|
|
308
|
-
return {
|
|
309
|
-
id: graph.id,
|
|
310
|
-
rootNodeId: graph.rootNodeId,
|
|
311
|
-
nodes: nodesObj,
|
|
312
|
-
edges: graph.edges,
|
|
313
|
-
startTime: graph.startTime,
|
|
314
|
-
endTime: graph.endTime,
|
|
315
|
-
status: graph.status,
|
|
316
|
-
trigger: graph.trigger,
|
|
317
|
-
agentId: graph.agentId,
|
|
318
|
-
events: graph.events,
|
|
319
|
-
traceId: graph.traceId,
|
|
320
|
-
spanId: graph.spanId,
|
|
321
|
-
parentSpanId: graph.parentSpanId
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
358
|
async function runTraced(config) {
|
|
325
359
|
const {
|
|
326
360
|
command,
|
|
@@ -437,69 +471,9 @@ async function runTraced(config) {
|
|
|
437
471
|
};
|
|
438
472
|
}
|
|
439
473
|
|
|
440
|
-
// src/
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
for (const g of graphs) {
|
|
444
|
-
if (!g.traceId) continue;
|
|
445
|
-
const arr = groups.get(g.traceId) ?? [];
|
|
446
|
-
arr.push(g);
|
|
447
|
-
groups.set(g.traceId, arr);
|
|
448
|
-
}
|
|
449
|
-
return groups;
|
|
450
|
-
}
|
|
451
|
-
function stitchTrace(graphs) {
|
|
452
|
-
if (graphs.length === 0) throw new Error("No graphs to stitch");
|
|
453
|
-
const traceId = graphs[0].traceId ?? "";
|
|
454
|
-
const graphsBySpan = /* @__PURE__ */ new Map();
|
|
455
|
-
const childMap = /* @__PURE__ */ new Map();
|
|
456
|
-
let rootGraph = null;
|
|
457
|
-
for (const g of graphs) {
|
|
458
|
-
if (g.spanId) graphsBySpan.set(g.spanId, g);
|
|
459
|
-
if (!g.parentSpanId) {
|
|
460
|
-
if (!rootGraph || g.startTime < rootGraph.startTime) rootGraph = g;
|
|
461
|
-
}
|
|
462
|
-
if (g.parentSpanId) {
|
|
463
|
-
const siblings = childMap.get(g.parentSpanId) ?? [];
|
|
464
|
-
if (g.spanId) siblings.push(g.spanId);
|
|
465
|
-
childMap.set(g.parentSpanId, siblings);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
if (!rootGraph) rootGraph = graphs[0];
|
|
469
|
-
let status = "completed";
|
|
470
|
-
let endTime = 0;
|
|
471
|
-
let startTime = Infinity;
|
|
472
|
-
for (const g of graphs) {
|
|
473
|
-
startTime = Math.min(startTime, g.startTime);
|
|
474
|
-
if (g.status === "failed") status = "failed";
|
|
475
|
-
else if (g.status === "running" && status !== "failed") status = "running";
|
|
476
|
-
if (g.endTime === null) endTime = null;
|
|
477
|
-
else if (endTime !== null) endTime = Math.max(endTime, g.endTime);
|
|
478
|
-
}
|
|
479
|
-
const frozenChildMap = /* @__PURE__ */ new Map();
|
|
480
|
-
for (const [k, v] of childMap) frozenChildMap.set(k, Object.freeze([...v]));
|
|
481
|
-
return Object.freeze({
|
|
482
|
-
traceId,
|
|
483
|
-
graphs: graphsBySpan,
|
|
484
|
-
rootGraph,
|
|
485
|
-
childMap: frozenChildMap,
|
|
486
|
-
startTime,
|
|
487
|
-
endTime,
|
|
488
|
-
status
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
function getTraceTree(trace) {
|
|
492
|
-
const result = [];
|
|
493
|
-
function walk(spanId) {
|
|
494
|
-
const graph = trace.graphs.get(spanId);
|
|
495
|
-
if (graph) result.push(graph);
|
|
496
|
-
const children = trace.childMap.get(spanId) ?? [];
|
|
497
|
-
for (const childSpan of children) walk(childSpan);
|
|
498
|
-
}
|
|
499
|
-
if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
|
|
500
|
-
else result.push(trace.rootGraph);
|
|
501
|
-
return result;
|
|
502
|
-
}
|
|
474
|
+
// src/live.ts
|
|
475
|
+
var import_node_fs2 = require("fs");
|
|
476
|
+
var import_node_path2 = require("path");
|
|
503
477
|
|
|
504
478
|
// src/graph-query.ts
|
|
505
479
|
function getNode(graph, nodeId) {
|
|
@@ -639,6 +613,348 @@ function getStats(graph) {
|
|
|
639
613
|
hungCount
|
|
640
614
|
};
|
|
641
615
|
}
|
|
616
|
+
|
|
617
|
+
// src/graph-stitch.ts
|
|
618
|
+
function groupByTraceId(graphs) {
|
|
619
|
+
const groups = /* @__PURE__ */ new Map();
|
|
620
|
+
for (const g of graphs) {
|
|
621
|
+
if (!g.traceId) continue;
|
|
622
|
+
const arr = groups.get(g.traceId) ?? [];
|
|
623
|
+
arr.push(g);
|
|
624
|
+
groups.set(g.traceId, arr);
|
|
625
|
+
}
|
|
626
|
+
return groups;
|
|
627
|
+
}
|
|
628
|
+
function stitchTrace(graphs) {
|
|
629
|
+
if (graphs.length === 0) throw new Error("No graphs to stitch");
|
|
630
|
+
const traceId = graphs[0].traceId ?? "";
|
|
631
|
+
const graphsBySpan = /* @__PURE__ */ new Map();
|
|
632
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
633
|
+
let rootGraph = null;
|
|
634
|
+
for (const g of graphs) {
|
|
635
|
+
if (g.spanId) graphsBySpan.set(g.spanId, g);
|
|
636
|
+
if (!g.parentSpanId) {
|
|
637
|
+
if (!rootGraph || g.startTime < rootGraph.startTime) rootGraph = g;
|
|
638
|
+
}
|
|
639
|
+
if (g.parentSpanId) {
|
|
640
|
+
const siblings = childMap.get(g.parentSpanId) ?? [];
|
|
641
|
+
if (g.spanId) siblings.push(g.spanId);
|
|
642
|
+
childMap.set(g.parentSpanId, siblings);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (!rootGraph) rootGraph = graphs[0];
|
|
646
|
+
let status = "completed";
|
|
647
|
+
let endTime = 0;
|
|
648
|
+
let startTime = Infinity;
|
|
649
|
+
for (const g of graphs) {
|
|
650
|
+
startTime = Math.min(startTime, g.startTime);
|
|
651
|
+
if (g.status === "failed") status = "failed";
|
|
652
|
+
else if (g.status === "running" && status !== "failed") status = "running";
|
|
653
|
+
if (g.endTime === null) endTime = null;
|
|
654
|
+
else if (endTime !== null) endTime = Math.max(endTime, g.endTime);
|
|
655
|
+
}
|
|
656
|
+
const frozenChildMap = /* @__PURE__ */ new Map();
|
|
657
|
+
for (const [k, v] of childMap) frozenChildMap.set(k, Object.freeze([...v]));
|
|
658
|
+
return Object.freeze({
|
|
659
|
+
traceId,
|
|
660
|
+
graphs: graphsBySpan,
|
|
661
|
+
rootGraph,
|
|
662
|
+
childMap: frozenChildMap,
|
|
663
|
+
startTime,
|
|
664
|
+
endTime,
|
|
665
|
+
status
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
function getTraceTree(trace) {
|
|
669
|
+
const result = [];
|
|
670
|
+
function walk(spanId) {
|
|
671
|
+
const graph = trace.graphs.get(spanId);
|
|
672
|
+
if (graph) result.push(graph);
|
|
673
|
+
const children = trace.childMap.get(spanId) ?? [];
|
|
674
|
+
for (const childSpan of children) walk(childSpan);
|
|
675
|
+
}
|
|
676
|
+
if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
|
|
677
|
+
else result.push(trace.rootGraph);
|
|
678
|
+
return result;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// src/live.ts
|
|
682
|
+
var C = {
|
|
683
|
+
reset: "\x1B[0m",
|
|
684
|
+
bold: "\x1B[1m",
|
|
685
|
+
dim: "\x1B[90m",
|
|
686
|
+
under: "\x1B[4m",
|
|
687
|
+
red: "\x1B[31m",
|
|
688
|
+
green: "\x1B[32m",
|
|
689
|
+
yellow: "\x1B[33m",
|
|
690
|
+
blue: "\x1B[34m",
|
|
691
|
+
magenta: "\x1B[35m",
|
|
692
|
+
cyan: "\x1B[36m",
|
|
693
|
+
white: "\x1B[37m"
|
|
694
|
+
};
|
|
695
|
+
function parseArgs(argv) {
|
|
696
|
+
const config = {
|
|
697
|
+
tracesDir: "./traces",
|
|
698
|
+
refreshMs: 3e3
|
|
699
|
+
};
|
|
700
|
+
const args = argv.slice(0);
|
|
701
|
+
if (args[0] === "live") args.shift();
|
|
702
|
+
let i = 0;
|
|
703
|
+
while (i < args.length) {
|
|
704
|
+
const arg = args[i];
|
|
705
|
+
if (arg === "--help" || arg === "-h") {
|
|
706
|
+
printUsage();
|
|
707
|
+
process.exit(0);
|
|
708
|
+
} else if (arg === "--refresh" || arg === "-r") {
|
|
709
|
+
i++;
|
|
710
|
+
const val = parseInt(args[i] ?? "", 10);
|
|
711
|
+
if (!isNaN(val) && val > 0) config.refreshMs = val * 1e3;
|
|
712
|
+
i++;
|
|
713
|
+
} else if (arg === "--traces-dir" || arg === "-t") {
|
|
714
|
+
i++;
|
|
715
|
+
config.tracesDir = args[i] ?? config.tracesDir;
|
|
716
|
+
i++;
|
|
717
|
+
} else if (!arg.startsWith("-")) {
|
|
718
|
+
config.tracesDir = arg;
|
|
719
|
+
i++;
|
|
720
|
+
} else {
|
|
721
|
+
i++;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
config.tracesDir = (0, import_node_path2.resolve)(config.tracesDir);
|
|
725
|
+
return config;
|
|
726
|
+
}
|
|
727
|
+
function printUsage() {
|
|
728
|
+
console.log(`
|
|
729
|
+
AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
|
|
730
|
+
|
|
731
|
+
Usage:
|
|
732
|
+
agentflow live [traces-dir] [options]
|
|
733
|
+
|
|
734
|
+
Arguments:
|
|
735
|
+
traces-dir Path to the traces directory (default: ./traces)
|
|
736
|
+
|
|
737
|
+
Options:
|
|
738
|
+
-r, --refresh <secs> Refresh interval in seconds (default: 3)
|
|
739
|
+
-t, --traces-dir <path> Explicit traces directory path
|
|
740
|
+
-h, --help Show this help message
|
|
741
|
+
|
|
742
|
+
Examples:
|
|
743
|
+
agentflow live
|
|
744
|
+
agentflow live ./my-traces --refresh 5
|
|
745
|
+
agentflow live /var/log/agentflow/traces -r 10
|
|
746
|
+
`.trim());
|
|
747
|
+
}
|
|
748
|
+
function listTraceFiles(tracesDir) {
|
|
749
|
+
try {
|
|
750
|
+
return (0, import_node_fs2.readdirSync)(tracesDir).filter((f) => f.endsWith(".json")).map((f) => {
|
|
751
|
+
const fp = (0, import_node_path2.join)(tracesDir, f);
|
|
752
|
+
const stat = (0, import_node_fs2.statSync)(fp);
|
|
753
|
+
return { filename: f, path: fp, mtime: stat.mtime.getTime() };
|
|
754
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
755
|
+
} catch {
|
|
756
|
+
return [];
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function safeLoadTrace(fp) {
|
|
760
|
+
try {
|
|
761
|
+
return loadGraph((0, import_node_fs2.readFileSync)(fp, "utf8"));
|
|
762
|
+
} catch {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function analyze(trace) {
|
|
767
|
+
try {
|
|
768
|
+
const stats = getStats(trace);
|
|
769
|
+
const fails = getFailures(trace);
|
|
770
|
+
const hung = getHungNodes(trace);
|
|
771
|
+
return {
|
|
772
|
+
agentId: trace.agentId,
|
|
773
|
+
trigger: trace.trigger,
|
|
774
|
+
traceId: trace.traceId,
|
|
775
|
+
spanId: trace.spanId,
|
|
776
|
+
parentSpanId: trace.parentSpanId,
|
|
777
|
+
nodes: stats.totalNodes,
|
|
778
|
+
success: fails.length === 0 && hung.length === 0,
|
|
779
|
+
failures: fails.length,
|
|
780
|
+
hung: hung.length
|
|
781
|
+
};
|
|
782
|
+
} catch {
|
|
783
|
+
return null;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function getDistributedDepth(dt, spanId) {
|
|
787
|
+
if (!spanId) return 0;
|
|
788
|
+
const graph = dt.graphs.get(spanId);
|
|
789
|
+
if (!graph || !graph.parentSpanId) return 0;
|
|
790
|
+
return 1 + getDistributedDepth(dt, graph.parentSpanId);
|
|
791
|
+
}
|
|
792
|
+
var prevFileCount = 0;
|
|
793
|
+
var newExecCount = 0;
|
|
794
|
+
var sessionStart = Date.now();
|
|
795
|
+
function render(config) {
|
|
796
|
+
const files = listTraceFiles(config.tracesDir);
|
|
797
|
+
if (files.length > prevFileCount && prevFileCount > 0) {
|
|
798
|
+
newExecCount += files.length - prevFileCount;
|
|
799
|
+
}
|
|
800
|
+
prevFileCount = files.length;
|
|
801
|
+
const allTraces = [];
|
|
802
|
+
const agents = {};
|
|
803
|
+
for (const f of files.slice(0, 200)) {
|
|
804
|
+
const trace = safeLoadTrace(f.path);
|
|
805
|
+
if (!trace) continue;
|
|
806
|
+
allTraces.push(trace);
|
|
807
|
+
const a = analyze(trace);
|
|
808
|
+
if (!a) continue;
|
|
809
|
+
if (!agents[a.agentId]) {
|
|
810
|
+
agents[a.agentId] = { name: a.agentId, total: 0, ok: 0, fail: 0, lastTs: 0 };
|
|
811
|
+
}
|
|
812
|
+
const ag = agents[a.agentId];
|
|
813
|
+
ag.total++;
|
|
814
|
+
a.success ? ag.ok++ : ag.fail++;
|
|
815
|
+
if (f.mtime > ag.lastTs) ag.lastTs = f.mtime;
|
|
816
|
+
}
|
|
817
|
+
const agentList = Object.values(agents).sort((a, b) => b.total - a.total);
|
|
818
|
+
const totExec = agentList.reduce((s, a) => s + a.total, 0);
|
|
819
|
+
const totFail = agentList.reduce((s, a) => s + a.fail, 0);
|
|
820
|
+
const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
|
|
821
|
+
const recent = [];
|
|
822
|
+
for (const f of files.slice(0, 15)) {
|
|
823
|
+
const trace = safeLoadTrace(f.path);
|
|
824
|
+
if (!trace) continue;
|
|
825
|
+
const a = analyze(trace);
|
|
826
|
+
if (a) recent.push({ ...a, ts: f.mtime });
|
|
827
|
+
}
|
|
828
|
+
const now = Date.now();
|
|
829
|
+
const buckets = new Array(12).fill(0);
|
|
830
|
+
const failBuckets = new Array(12).fill(0);
|
|
831
|
+
for (const f of files) {
|
|
832
|
+
const age = now - f.mtime;
|
|
833
|
+
if (age > 36e5) continue;
|
|
834
|
+
const idx = 11 - Math.floor(age / 3e5);
|
|
835
|
+
if (idx >= 0 && idx < 12) {
|
|
836
|
+
const trace = safeLoadTrace(f.path);
|
|
837
|
+
if (!trace) continue;
|
|
838
|
+
const a = analyze(trace);
|
|
839
|
+
if (!a) continue;
|
|
840
|
+
buckets[idx]++;
|
|
841
|
+
if (!a.success) failBuckets[idx]++;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const maxBucket = Math.max(...buckets, 1);
|
|
845
|
+
const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
846
|
+
const spark = buckets.map((v, i) => {
|
|
847
|
+
const level = Math.round(v / maxBucket * 8);
|
|
848
|
+
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
849
|
+
}).join("");
|
|
850
|
+
const traceGroups = groupByTraceId(allTraces);
|
|
851
|
+
const distributedTraces = [];
|
|
852
|
+
for (const [_traceId, graphs] of traceGroups) {
|
|
853
|
+
if (graphs.length > 1) {
|
|
854
|
+
try {
|
|
855
|
+
distributedTraces.push(stitchTrace(graphs));
|
|
856
|
+
} catch {
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
distributedTraces.sort((a, b) => b.startTime - a.startTime);
|
|
861
|
+
const upSec = Math.floor((Date.now() - sessionStart) / 1e3);
|
|
862
|
+
const upMin = Math.floor(upSec / 60);
|
|
863
|
+
const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
|
|
864
|
+
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
865
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
866
|
+
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}`);
|
|
867
|
+
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}`);
|
|
868
|
+
const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr}`;
|
|
869
|
+
const pad1 = Math.max(0, 64 - metaLine.length);
|
|
870
|
+
console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
|
|
871
|
+
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}`);
|
|
872
|
+
const sc = totFail === 0 ? C.green : C.yellow;
|
|
873
|
+
console.log("");
|
|
874
|
+
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}`);
|
|
875
|
+
console.log("");
|
|
876
|
+
console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
877
|
+
console.log("");
|
|
878
|
+
console.log(` ${C.bold}${C.under}Agent Runs OK Fail Rate Last Active${C.reset}`);
|
|
879
|
+
for (const ag of agentList) {
|
|
880
|
+
const rate = (ag.ok / ag.total * 100).toFixed(0);
|
|
881
|
+
const lastTime = new Date(ag.lastTs).toLocaleTimeString();
|
|
882
|
+
const isRecent = Date.now() - ag.lastTs < 3e5;
|
|
883
|
+
let status;
|
|
884
|
+
if (ag.fail > 0) status = `${C.red}\u25CF${C.reset}`;
|
|
885
|
+
else if (isRecent) status = `${C.green}\u25CF${C.reset}`;
|
|
886
|
+
else status = `${C.dim}\u25CB${C.reset}`;
|
|
887
|
+
const name = ag.name.padEnd(28);
|
|
888
|
+
const runs = String(ag.total).padStart(5);
|
|
889
|
+
const ok = String(ag.ok).padStart(5);
|
|
890
|
+
const fail = ag.fail > 0 ? `${C.red}${String(ag.fail).padStart(4)}${C.reset}` : String(ag.fail).padStart(4);
|
|
891
|
+
const rateStr = (rate + "%").padStart(5);
|
|
892
|
+
const activeStr = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
|
|
893
|
+
console.log(` ${status} ${name}${runs}${ok}${fail} ${rateStr} ${activeStr}`);
|
|
894
|
+
}
|
|
895
|
+
if (distributedTraces.length > 0) {
|
|
896
|
+
console.log("");
|
|
897
|
+
console.log(` ${C.bold}${C.under}Distributed Traces (multi-agent workflows)${C.reset}`);
|
|
898
|
+
for (const dt of distributedTraces.slice(0, 5)) {
|
|
899
|
+
const traceTime = new Date(dt.startTime).toLocaleTimeString();
|
|
900
|
+
const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
901
|
+
const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
|
|
902
|
+
const tid = dt.traceId.slice(0, 8);
|
|
903
|
+
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}`);
|
|
904
|
+
const tree = getTraceTree(dt);
|
|
905
|
+
for (let i = 0; i < tree.length; i++) {
|
|
906
|
+
const g = tree[i];
|
|
907
|
+
const depth = getDistributedDepth(dt, g.spanId);
|
|
908
|
+
const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
|
|
909
|
+
const isLast = i === tree.length - 1 || getDistributedDepth(dt, tree[i + 1]?.spanId) <= depth;
|
|
910
|
+
const connector = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
911
|
+
const gStatus = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
912
|
+
const gDur = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
|
|
913
|
+
console.log(`${indent}${connector}${gStatus} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gDur}${C.reset}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
console.log("");
|
|
918
|
+
console.log(` ${C.bold}${C.under}Recent Executions${C.reset}`);
|
|
919
|
+
for (const ex of recent.slice(0, 8)) {
|
|
920
|
+
const icon = ex.success ? `${C.green}\u2713${C.reset}` : `${C.red}\u2717${C.reset}`;
|
|
921
|
+
const t = new Date(ex.ts).toLocaleTimeString();
|
|
922
|
+
const agent = ex.agentId.padEnd(28);
|
|
923
|
+
const age = Math.floor((Date.now() - ex.ts) / 1e3);
|
|
924
|
+
const ageStr = age < 60 ? age + "s ago" : Math.floor(age / 60) + "m ago";
|
|
925
|
+
const traceTag = ex.traceId ? ` ${C.magenta}\u29EB${C.reset}` : "";
|
|
926
|
+
console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)} ${ex.nodes} nodes${C.reset}${traceTag}`);
|
|
927
|
+
}
|
|
928
|
+
if (files.length === 0) {
|
|
929
|
+
console.log(` ${C.dim}No trace files found. Waiting for traces in:${C.reset}`);
|
|
930
|
+
console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
|
|
931
|
+
}
|
|
932
|
+
console.log("");
|
|
933
|
+
console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
|
|
934
|
+
console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
935
|
+
}
|
|
936
|
+
function startLive(argv) {
|
|
937
|
+
const config = parseArgs(argv);
|
|
938
|
+
if (!(0, import_node_fs2.existsSync)(config.tracesDir)) {
|
|
939
|
+
console.error(`Traces directory does not exist: ${config.tracesDir}`);
|
|
940
|
+
console.error("Create it or specify a different path: agentflow live <traces-dir>");
|
|
941
|
+
process.exit(1);
|
|
942
|
+
}
|
|
943
|
+
render(config);
|
|
944
|
+
let debounce = null;
|
|
945
|
+
try {
|
|
946
|
+
(0, import_node_fs2.watch)(config.tracesDir, () => {
|
|
947
|
+
if (debounce) clearTimeout(debounce);
|
|
948
|
+
debounce = setTimeout(() => render(config), 500);
|
|
949
|
+
});
|
|
950
|
+
} catch {
|
|
951
|
+
}
|
|
952
|
+
setInterval(() => render(config), config.refreshMs);
|
|
953
|
+
process.on("SIGINT", () => {
|
|
954
|
+
console.log("\n" + C.dim + "Monitor stopped." + C.reset);
|
|
955
|
+
process.exit(0);
|
|
956
|
+
});
|
|
957
|
+
}
|
|
642
958
|
// Annotate the CommonJS export names for ESM import in node:
|
|
643
959
|
0 && (module.exports = {
|
|
644
960
|
createGraphBuilder,
|
|
@@ -654,7 +970,10 @@ function getStats(graph) {
|
|
|
654
970
|
getStats,
|
|
655
971
|
getSubtree,
|
|
656
972
|
getTraceTree,
|
|
973
|
+
graphToJson,
|
|
657
974
|
groupByTraceId,
|
|
975
|
+
loadGraph,
|
|
658
976
|
runTraced,
|
|
977
|
+
startLive,
|
|
659
978
|
stitchTrace
|
|
660
979
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -276,6 +276,58 @@ interface MutableExecutionNode {
|
|
|
276
276
|
*/
|
|
277
277
|
declare function createGraphBuilder(config?: AgentFlowConfig): GraphBuilder;
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Load and deserialize execution graphs from JSON.
|
|
281
|
+
*
|
|
282
|
+
* Handles all serialization formats produced by the runner, graph-builder,
|
|
283
|
+
* and third-party tools:
|
|
284
|
+
* - `nodes` as a plain object `{ "node_001": { ... } }` (runner.ts output)
|
|
285
|
+
* - `nodes` as an array of `[id, node]` pairs (Map JSON serialization)
|
|
286
|
+
* - `nodes` already a Map (in-memory passthrough)
|
|
287
|
+
*
|
|
288
|
+
* @module
|
|
289
|
+
*/
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Deserialize a JSON object (or JSON string) into a valid `ExecutionGraph`.
|
|
293
|
+
*
|
|
294
|
+
* Use this whenever you read a trace file from disk or receive one over the
|
|
295
|
+
* network. It normalizes `nodes` into a proper `Map` regardless of the
|
|
296
|
+
* serialization format.
|
|
297
|
+
*
|
|
298
|
+
* @param input - A parsed JSON object, or a JSON string to be parsed.
|
|
299
|
+
* @returns A valid `ExecutionGraph` ready for use with query functions.
|
|
300
|
+
* @throws {Error} If the input cannot be parsed or is missing required fields.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```ts
|
|
304
|
+
* import { readFileSync } from 'fs';
|
|
305
|
+
* import { loadGraph, getStats } from 'agentflow-core';
|
|
306
|
+
*
|
|
307
|
+
* const graph = loadGraph(readFileSync('trace.json', 'utf8'));
|
|
308
|
+
* console.log(getStats(graph));
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
declare function loadGraph(input: string | Record<string, unknown>): ExecutionGraph;
|
|
312
|
+
/**
|
|
313
|
+
* Serialize an `ExecutionGraph` to a plain JSON-safe object.
|
|
314
|
+
*
|
|
315
|
+
* The inverse of `loadGraph`. `nodes` is written as a plain object keyed by
|
|
316
|
+
* node ID, which is the most readable format for trace files on disk.
|
|
317
|
+
*
|
|
318
|
+
* @param graph - The execution graph to serialize.
|
|
319
|
+
* @returns A plain object safe to pass to `JSON.stringify`.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* import { writeFileSync } from 'fs';
|
|
324
|
+
* import { graphToJson } from 'agentflow-core';
|
|
325
|
+
*
|
|
326
|
+
* writeFileSync('trace.json', JSON.stringify(graphToJson(graph), null, 2));
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
declare function graphToJson(graph: ExecutionGraph): Record<string, unknown>;
|
|
330
|
+
|
|
279
331
|
/**
|
|
280
332
|
* CLI runner that wraps any command with automatic AgentFlow tracing.
|
|
281
333
|
*
|
|
@@ -325,6 +377,25 @@ interface RunResult {
|
|
|
325
377
|
*/
|
|
326
378
|
declare function runTraced(config: RunConfig): Promise<RunResult>;
|
|
327
379
|
|
|
380
|
+
/**
|
|
381
|
+
* AgentFlow Live Monitor — real-time terminal dashboard for any agent system.
|
|
382
|
+
*
|
|
383
|
+
* Usage:
|
|
384
|
+
* agentflow live [traces-dir] [options]
|
|
385
|
+
* agentflow live ./traces --refresh 5
|
|
386
|
+
*
|
|
387
|
+
* Features:
|
|
388
|
+
* - Auto-discovers agents from trace files
|
|
389
|
+
* - Sparkline activity graph (1 hour)
|
|
390
|
+
* - Per-agent success/failure table
|
|
391
|
+
* - Distributed trace tree view
|
|
392
|
+
* - Recent execution feed
|
|
393
|
+
* - fs.watch auto-refresh on new traces
|
|
394
|
+
*
|
|
395
|
+
* @module
|
|
396
|
+
*/
|
|
397
|
+
declare function startLive(argv: string[]): void;
|
|
398
|
+
|
|
328
399
|
declare function groupByTraceId(graphs: ExecutionGraph[]): Map<string, ExecutionGraph[]>;
|
|
329
400
|
declare function stitchTrace(graphs: ExecutionGraph[]): DistributedTrace;
|
|
330
401
|
declare function getTraceTree(trace: DistributedTrace): ExecutionGraph[];
|
|
@@ -445,4 +516,4 @@ declare function getDepth(graph: ExecutionGraph): number;
|
|
|
445
516
|
*/
|
|
446
517
|
declare function getStats(graph: ExecutionGraph): GraphStats;
|
|
447
518
|
|
|
448
|
-
export { type Adapter, type AgentFlowConfig, type DistributedTrace, type EdgeType, type ExecutionEdge, type ExecutionGraph, type ExecutionNode, type GraphBuilder, type GraphStats, type GraphStatus, type MutableExecutionNode, type NodeStatus, type NodeType, type RunConfig, type RunResult, type StartNodeOptions, type TraceEvent, type TraceEventType, type Writer, createGraphBuilder, findWaitingOn, getChildren, getCriticalPath, getDepth, getDuration, getFailures, getHungNodes, getNode, getParent, getStats, getSubtree, getTraceTree, groupByTraceId, runTraced, stitchTrace };
|
|
519
|
+
export { type Adapter, type AgentFlowConfig, type DistributedTrace, type EdgeType, type ExecutionEdge, type ExecutionGraph, type ExecutionNode, type GraphBuilder, type GraphStats, type GraphStatus, type MutableExecutionNode, type NodeStatus, type NodeType, type RunConfig, type RunResult, type StartNodeOptions, type TraceEvent, type TraceEventType, type Writer, createGraphBuilder, findWaitingOn, getChildren, getCriticalPath, getDepth, getDuration, getFailures, getHungNodes, getNode, getParent, getStats, getSubtree, getTraceTree, graphToJson, groupByTraceId, loadGraph, runTraced, startLive, stitchTrace };
|