agentflow-core 0.1.4 → 0.2.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/dist/index.cjs CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  groupByTraceId: () => groupByTraceId,
38
38
  loadGraph: () => loadGraph,
39
39
  runTraced: () => runTraced,
40
+ startLive: () => startLive,
40
41
  stitchTrace: () => stitchTrace
41
42
  });
42
43
  module.exports = __toCommonJS(index_exports);
@@ -470,69 +471,9 @@ async function runTraced(config) {
470
471
  };
471
472
  }
472
473
 
473
- // src/graph-stitch.ts
474
- function groupByTraceId(graphs) {
475
- const groups = /* @__PURE__ */ new Map();
476
- for (const g of graphs) {
477
- if (!g.traceId) continue;
478
- const arr = groups.get(g.traceId) ?? [];
479
- arr.push(g);
480
- groups.set(g.traceId, arr);
481
- }
482
- return groups;
483
- }
484
- function stitchTrace(graphs) {
485
- if (graphs.length === 0) throw new Error("No graphs to stitch");
486
- const traceId = graphs[0].traceId ?? "";
487
- const graphsBySpan = /* @__PURE__ */ new Map();
488
- const childMap = /* @__PURE__ */ new Map();
489
- let rootGraph = null;
490
- for (const g of graphs) {
491
- if (g.spanId) graphsBySpan.set(g.spanId, g);
492
- if (!g.parentSpanId) {
493
- if (!rootGraph || g.startTime < rootGraph.startTime) rootGraph = g;
494
- }
495
- if (g.parentSpanId) {
496
- const siblings = childMap.get(g.parentSpanId) ?? [];
497
- if (g.spanId) siblings.push(g.spanId);
498
- childMap.set(g.parentSpanId, siblings);
499
- }
500
- }
501
- if (!rootGraph) rootGraph = graphs[0];
502
- let status = "completed";
503
- let endTime = 0;
504
- let startTime = Infinity;
505
- for (const g of graphs) {
506
- startTime = Math.min(startTime, g.startTime);
507
- if (g.status === "failed") status = "failed";
508
- else if (g.status === "running" && status !== "failed") status = "running";
509
- if (g.endTime === null) endTime = null;
510
- else if (endTime !== null) endTime = Math.max(endTime, g.endTime);
511
- }
512
- const frozenChildMap = /* @__PURE__ */ new Map();
513
- for (const [k, v] of childMap) frozenChildMap.set(k, Object.freeze([...v]));
514
- return Object.freeze({
515
- traceId,
516
- graphs: graphsBySpan,
517
- rootGraph,
518
- childMap: frozenChildMap,
519
- startTime,
520
- endTime,
521
- status
522
- });
523
- }
524
- function getTraceTree(trace) {
525
- const result = [];
526
- function walk(spanId) {
527
- const graph = trace.graphs.get(spanId);
528
- if (graph) result.push(graph);
529
- const children = trace.childMap.get(spanId) ?? [];
530
- for (const childSpan of children) walk(childSpan);
531
- }
532
- if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
533
- else result.push(trace.rootGraph);
534
- return result;
535
- }
474
+ // src/live.ts
475
+ var import_node_fs2 = require("fs");
476
+ var import_node_path2 = require("path");
536
477
 
537
478
  // src/graph-query.ts
538
479
  function getNode(graph, nodeId) {
@@ -672,6 +613,520 @@ function getStats(graph) {
672
613
  hungCount
673
614
  };
674
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 = { tracesDir: ".", refreshMs: 3e3, recursive: false };
697
+ const args = argv.slice(0);
698
+ if (args[0] === "live") args.shift();
699
+ let i = 0;
700
+ while (i < args.length) {
701
+ const arg = args[i];
702
+ if (arg === "--help" || arg === "-h") {
703
+ printUsage();
704
+ process.exit(0);
705
+ } else if (arg === "--refresh" || arg === "-r") {
706
+ i++;
707
+ const v = parseInt(args[i] ?? "", 10);
708
+ if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
709
+ i++;
710
+ } else if (arg === "--recursive" || arg === "-R") {
711
+ config.recursive = true;
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
+ Auto-detects agent traces, state files, job schedulers, and session logs
732
+ from any JSON/JSONL files in the watched directory.
733
+
734
+ Usage:
735
+ agentflow live [directory] [options]
736
+
737
+ Arguments:
738
+ directory Directory to watch (default: current directory)
739
+
740
+ Options:
741
+ -r, --refresh <secs> Refresh interval in seconds (default: 3)
742
+ -R, --recursive Scan subdirectories (1 level deep)
743
+ -h, --help Show this help message
744
+
745
+ Examples:
746
+ agentflow live ./data
747
+ agentflow live ./traces --refresh 5
748
+ agentflow live /var/lib/myagent -R
749
+ `.trim());
750
+ }
751
+ function scanFiles(dir, recursive) {
752
+ const results = [];
753
+ function scanDir(d) {
754
+ try {
755
+ for (const f of (0, import_node_fs2.readdirSync)(d)) {
756
+ if (f.startsWith(".")) continue;
757
+ const fp = (0, import_node_path2.join)(d, f);
758
+ let stat;
759
+ try {
760
+ stat = (0, import_node_fs2.statSync)(fp);
761
+ } catch {
762
+ continue;
763
+ }
764
+ if (stat.isDirectory() && recursive && d === dir) {
765
+ scanDir(fp);
766
+ continue;
767
+ }
768
+ if (!stat.isFile()) continue;
769
+ if (f.endsWith(".json")) {
770
+ results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" });
771
+ } else if (f.endsWith(".jsonl")) {
772
+ results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" });
773
+ }
774
+ }
775
+ } catch {
776
+ }
777
+ }
778
+ scanDir(dir);
779
+ results.sort((a, b) => b.mtime - a.mtime);
780
+ return results;
781
+ }
782
+ function safeReadJson(fp) {
783
+ try {
784
+ return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
785
+ } catch {
786
+ return null;
787
+ }
788
+ }
789
+ function nameFromFile(filename) {
790
+ return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
791
+ }
792
+ function normalizeStatus(val) {
793
+ if (typeof val !== "string") return "unknown";
794
+ const s = val.toLowerCase();
795
+ if (["ok", "success", "completed", "done", "passed", "healthy", "good"].includes(s)) return "ok";
796
+ if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s)) return "error";
797
+ if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s)) return "running";
798
+ return "unknown";
799
+ }
800
+ function findStatus(obj) {
801
+ for (const key of ["status", "state", "lastRunStatus", "lastStatus", "health", "result"]) {
802
+ if (key in obj) {
803
+ const val = obj[key];
804
+ if (typeof val === "string") return normalizeStatus(val);
805
+ if (typeof val === "object" && val !== null && "status" in val) {
806
+ return normalizeStatus(val.status);
807
+ }
808
+ }
809
+ }
810
+ return "unknown";
811
+ }
812
+ function findTimestamp(obj) {
813
+ for (const key of ["ts", "timestamp", "lastRunAtMs", "last_run", "lastExecution", "updated_at", "started_at", "endTime", "startTime"]) {
814
+ const val = obj[key];
815
+ if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
816
+ if (typeof val === "string") {
817
+ const d = Date.parse(val);
818
+ if (!isNaN(d)) return d;
819
+ }
820
+ }
821
+ return 0;
822
+ }
823
+ function extractDetail(obj) {
824
+ const parts = [];
825
+ for (const key of ["summary", "message", "description", "lastError", "error", "name", "jobId", "id"]) {
826
+ const val = obj[key];
827
+ if (typeof val === "string" && val.length > 0 && val.length < 200) {
828
+ parts.push(val.slice(0, 80));
829
+ break;
830
+ }
831
+ }
832
+ for (const key of ["totalExecutions", "runs", "count", "processed", "consecutiveErrors"]) {
833
+ const val = obj[key];
834
+ if (typeof val === "number") {
835
+ parts.push(`${key}: ${val}`);
836
+ break;
837
+ }
838
+ }
839
+ return parts.join(" | ") || "";
840
+ }
841
+ function tryLoadTrace(fp, raw) {
842
+ if (typeof raw !== "object" || raw === null) return null;
843
+ const obj = raw;
844
+ if (!("nodes" in obj)) return null;
845
+ if (!("agentId" in obj) && !("rootNodeId" in obj) && !("rootId" in obj)) return null;
846
+ try {
847
+ return loadGraph(obj);
848
+ } catch {
849
+ return null;
850
+ }
851
+ }
852
+ function processJsonFile(file) {
853
+ const raw = safeReadJson(file.path);
854
+ if (raw === null) return [];
855
+ const records = [];
856
+ const trace = tryLoadTrace(file.path, raw);
857
+ if (trace) {
858
+ try {
859
+ const fails = getFailures(trace);
860
+ const hung = getHungNodes(trace);
861
+ const stats = getStats(trace);
862
+ records.push({
863
+ id: trace.agentId,
864
+ source: "trace",
865
+ status: fails.length === 0 && hung.length === 0 ? "ok" : "error",
866
+ lastActive: file.mtime,
867
+ detail: `${stats.totalNodes} nodes [${trace.trigger}]`,
868
+ file: file.filename,
869
+ traceData: trace
870
+ });
871
+ } catch {
872
+ }
873
+ return records;
874
+ }
875
+ if (typeof raw !== "object") return records;
876
+ const arr = Array.isArray(raw) ? raw : Array.isArray(raw.jobs) ? raw.jobs : Array.isArray(raw.tasks) ? raw.tasks : Array.isArray(raw.items) ? raw.items : null;
877
+ if (arr && arr.length > 0 && typeof arr[0] === "object" && arr[0] !== null) {
878
+ for (const item of arr.slice(0, 50)) {
879
+ const name = item.name ?? item.id ?? item.jobId ?? item.agentId;
880
+ if (!name) continue;
881
+ const state = typeof item.state === "object" && item.state !== null ? item.state : item;
882
+ const status2 = findStatus(state);
883
+ const ts2 = findTimestamp(state) || file.mtime;
884
+ const detail2 = extractDetail(state);
885
+ records.push({ id: String(name), source: "jobs", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
886
+ }
887
+ return records;
888
+ }
889
+ const obj = raw;
890
+ for (const containerKey of ["tools", "workers", "services", "agents", "daemons"]) {
891
+ const container = obj[containerKey];
892
+ if (typeof container === "object" && container !== null && !Array.isArray(container)) {
893
+ for (const [name, info] of Object.entries(container)) {
894
+ if (typeof info !== "object" || info === null) continue;
895
+ const w = info;
896
+ const status2 = findStatus(w);
897
+ const ts2 = findTimestamp(w) || findTimestamp(obj) || file.mtime;
898
+ const pid = w.pid;
899
+ const detail2 = pid ? `pid: ${pid}` : extractDetail(w);
900
+ records.push({ id: name, source: "workers", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
901
+ }
902
+ return records;
903
+ }
904
+ }
905
+ const status = findStatus(obj);
906
+ const ts = findTimestamp(obj) || file.mtime;
907
+ const detail = extractDetail(obj);
908
+ records.push({ id: nameFromFile(file.filename), source: "state", status, lastActive: ts, detail, file: file.filename });
909
+ return records;
910
+ }
911
+ function processJsonlFile(file) {
912
+ try {
913
+ const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
914
+ if (!content) return [];
915
+ const lines = content.split("\n");
916
+ const lastLine = lines[lines.length - 1];
917
+ const obj = JSON.parse(lastLine);
918
+ const name = obj.jobId ?? obj.agentId ?? obj.name ?? obj.id ?? nameFromFile(file.filename);
919
+ const status = findStatus(obj);
920
+ const ts = findTimestamp(obj) || file.mtime;
921
+ const action = obj.action;
922
+ const detail = action ? `${action} (${lines.length} entries)` : `${lines.length} entries`;
923
+ return [{ id: String(name), source: "session", status, lastActive: ts, detail, file: file.filename }];
924
+ } catch {
925
+ return [];
926
+ }
927
+ }
928
+ var prevFileCount = 0;
929
+ var newExecCount = 0;
930
+ var sessionStart = Date.now();
931
+ function render(config) {
932
+ const files = scanFiles(config.tracesDir, config.recursive);
933
+ if (files.length > prevFileCount && prevFileCount > 0) {
934
+ newExecCount += files.length - prevFileCount;
935
+ }
936
+ prevFileCount = files.length;
937
+ const allRecords = [];
938
+ const allTraces = [];
939
+ for (const f of files.slice(0, 300)) {
940
+ const records = f.ext === ".jsonl" ? processJsonlFile(f) : processJsonFile(f);
941
+ for (const r of records) {
942
+ allRecords.push(r);
943
+ if (r.traceData) allTraces.push(r.traceData);
944
+ }
945
+ }
946
+ const agents = {};
947
+ for (const r of allRecords) {
948
+ if (!agents[r.id]) {
949
+ agents[r.id] = { name: r.id, total: 0, ok: 0, fail: 0, running: 0, lastTs: 0, source: r.source, detail: "" };
950
+ }
951
+ const ag = agents[r.id];
952
+ ag.total++;
953
+ if (r.status === "ok") ag.ok++;
954
+ else if (r.status === "error") ag.fail++;
955
+ else if (r.status === "running") ag.running++;
956
+ if (r.lastActive > ag.lastTs) {
957
+ ag.lastTs = r.lastActive;
958
+ ag.detail = r.detail;
959
+ ag.source = r.source;
960
+ }
961
+ }
962
+ const agentList = Object.values(agents).sort((a, b) => b.lastTs - a.lastTs);
963
+ const totExec = agentList.reduce((s, a) => s + a.total, 0);
964
+ const totFail = agentList.reduce((s, a) => s + a.fail, 0);
965
+ const totRunning = agentList.reduce((s, a) => s + a.running, 0);
966
+ const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
967
+ const now = Date.now();
968
+ const buckets = new Array(12).fill(0);
969
+ const failBuckets = new Array(12).fill(0);
970
+ for (const r of allRecords) {
971
+ const age = now - r.lastActive;
972
+ if (age > 36e5 || age < 0) continue;
973
+ const idx = 11 - Math.floor(age / 3e5);
974
+ if (idx >= 0 && idx < 12) {
975
+ buckets[idx]++;
976
+ if (r.status === "error") failBuckets[idx]++;
977
+ }
978
+ }
979
+ const maxBucket = Math.max(...buckets, 1);
980
+ const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
981
+ const spark = buckets.map((v, i) => {
982
+ const level = Math.round(v / maxBucket * 8);
983
+ return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
984
+ }).join("");
985
+ const distributedTraces = [];
986
+ if (allTraces.length > 1) {
987
+ const traceGroups = groupByTraceId(allTraces);
988
+ for (const [_tid, graphs] of traceGroups) {
989
+ if (graphs.length > 1) {
990
+ try {
991
+ distributedTraces.push(stitchTrace(graphs));
992
+ } catch {
993
+ }
994
+ }
995
+ }
996
+ distributedTraces.sort((a, b) => b.startTime - a.startTime);
997
+ }
998
+ const upSec = Math.floor((Date.now() - sessionStart) / 1e3);
999
+ const upMin = Math.floor(upSec / 60);
1000
+ const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
1001
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
1002
+ const sourceTag = (s) => {
1003
+ switch (s) {
1004
+ case "trace":
1005
+ return `${C.cyan}trace${C.reset}`;
1006
+ case "jobs":
1007
+ return `${C.blue}job${C.reset}`;
1008
+ case "workers":
1009
+ return `${C.magenta}worker${C.reset}`;
1010
+ case "session":
1011
+ return `${C.yellow}session${C.reset}`;
1012
+ case "state":
1013
+ return `${C.dim}state${C.reset}`;
1014
+ }
1015
+ };
1016
+ process.stdout.write("\x1B[2J\x1B[H");
1017
+ console.log(`${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
1018
+ 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}`);
1019
+ const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
1020
+ const pad1 = Math.max(0, 64 - metaLine.length);
1021
+ console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
1022
+ console.log(`${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
1023
+ const sc = totFail === 0 ? C.green : C.yellow;
1024
+ console.log("");
1025
+ console.log(` ${C.bold}Agents${C.reset} ${sc}${agentList.length}${C.reset} ${C.bold}Records${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Running${C.reset} ${C.green}${totRunning}${C.reset} ${C.bold}Errors${C.reset} ${totFail > 0 ? C.red : C.dim}${totFail}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset}`);
1026
+ console.log("");
1027
+ console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
1028
+ console.log("");
1029
+ console.log(` ${C.bold}${C.under}Agent Type Status Last Active Detail${C.reset}`);
1030
+ for (const ag of agentList.slice(0, 30)) {
1031
+ const lastTime = ag.lastTs > 0 ? new Date(ag.lastTs).toLocaleTimeString() : "n/a";
1032
+ const isRecent = Date.now() - ag.lastTs < 3e5;
1033
+ let statusIcon;
1034
+ let statusText;
1035
+ if (ag.fail > 0 && ag.ok === 0 && ag.running === 0) {
1036
+ statusIcon = `${C.red}\u25CF${C.reset}`;
1037
+ statusText = `${C.red}error${C.reset}`;
1038
+ } else if (ag.running > 0) {
1039
+ statusIcon = `${C.green}\u25CF${C.reset}`;
1040
+ statusText = `${C.green}running${C.reset}`;
1041
+ } else if (ag.fail > 0) {
1042
+ statusIcon = `${C.yellow}\u25CF${C.reset}`;
1043
+ statusText = `${C.yellow}${ag.ok}ok/${ag.fail}err${C.reset}`;
1044
+ } else if (ag.ok > 0) {
1045
+ statusIcon = isRecent ? `${C.green}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
1046
+ statusText = ag.total > 1 ? `${C.green}${ag.ok}/${ag.total}${C.reset}` : `${C.green}ok${C.reset}`;
1047
+ } else {
1048
+ statusIcon = `${C.dim}\u25CB${C.reset}`;
1049
+ statusText = `${C.dim}idle${C.reset}`;
1050
+ }
1051
+ const name = ag.name.length > 23 ? ag.name.slice(0, 22) + "\u2026" : ag.name.padEnd(23);
1052
+ const src = sourceTag(ag.source).padEnd(16);
1053
+ const active = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
1054
+ const detail = ag.detail.length > 30 ? ag.detail.slice(0, 29) + "\u2026" : ag.detail;
1055
+ console.log(` ${statusIcon} ${name} ${src} ${statusText.padEnd(18)} ${active.padEnd(20)} ${C.dim}${detail}${C.reset}`);
1056
+ }
1057
+ if (distributedTraces.length > 0) {
1058
+ console.log("");
1059
+ console.log(` ${C.bold}${C.under}Distributed Traces${C.reset}`);
1060
+ for (const dt of distributedTraces.slice(0, 3)) {
1061
+ const traceTime = new Date(dt.startTime).toLocaleTimeString();
1062
+ const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1063
+ const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
1064
+ const tid = dt.traceId.slice(0, 8);
1065
+ console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
1066
+ const tree = getTraceTree(dt);
1067
+ for (let i = 0; i < Math.min(tree.length, 6); i++) {
1068
+ const g = tree[i];
1069
+ const depth = getDistDepth(dt, g.spanId);
1070
+ const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
1071
+ const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
1072
+ const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1073
+ const gs = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1074
+ const gd = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
1075
+ console.log(`${indent}${conn}${gs} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gd}${C.reset}`);
1076
+ }
1077
+ }
1078
+ }
1079
+ const recentRecords = allRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 8);
1080
+ if (recentRecords.length > 0) {
1081
+ console.log("");
1082
+ console.log(` ${C.bold}${C.under}Recent Activity${C.reset}`);
1083
+ for (const r of recentRecords) {
1084
+ const icon = r.status === "ok" ? `${C.green}\u2713${C.reset}` : r.status === "error" ? `${C.red}\u2717${C.reset}` : r.status === "running" ? `${C.green}\u25B6${C.reset}` : `${C.dim}\u25CB${C.reset}`;
1085
+ const t = new Date(r.lastActive).toLocaleTimeString();
1086
+ const agent = r.id.length > 26 ? r.id.slice(0, 25) + "\u2026" : r.id.padEnd(26);
1087
+ const age = Math.floor((Date.now() - r.lastActive) / 1e3);
1088
+ const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
1089
+ const detail = r.detail.length > 25 ? r.detail.slice(0, 24) + "\u2026" : r.detail;
1090
+ console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${detail}${C.reset}`);
1091
+ }
1092
+ }
1093
+ if (files.length === 0) {
1094
+ console.log("");
1095
+ console.log(` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
1096
+ console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
1097
+ }
1098
+ console.log("");
1099
+ console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
1100
+ console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
1101
+ }
1102
+ function getDistDepth(dt, spanId) {
1103
+ if (!spanId) return 0;
1104
+ const g = dt.graphs.get(spanId);
1105
+ if (!g || !g.parentSpanId) return 0;
1106
+ return 1 + getDistDepth(dt, g.parentSpanId);
1107
+ }
1108
+ function startLive(argv) {
1109
+ const config = parseArgs(argv);
1110
+ if (!(0, import_node_fs2.existsSync)(config.tracesDir)) {
1111
+ console.error(`Directory does not exist: ${config.tracesDir}`);
1112
+ console.error("Specify a directory containing JSON/JSONL files: agentflow live <dir>");
1113
+ process.exit(1);
1114
+ }
1115
+ render(config);
1116
+ let debounce = null;
1117
+ try {
1118
+ (0, import_node_fs2.watch)(config.tracesDir, { recursive: config.recursive }, () => {
1119
+ if (debounce) clearTimeout(debounce);
1120
+ debounce = setTimeout(() => render(config), 500);
1121
+ });
1122
+ } catch {
1123
+ }
1124
+ setInterval(() => render(config), config.refreshMs);
1125
+ process.on("SIGINT", () => {
1126
+ console.log("\n" + C.dim + "Monitor stopped." + C.reset);
1127
+ process.exit(0);
1128
+ });
1129
+ }
675
1130
  // Annotate the CommonJS export names for ESM import in node:
676
1131
  0 && (module.exports = {
677
1132
  createGraphBuilder,
@@ -691,5 +1146,6 @@ function getStats(graph) {
691
1146
  groupByTraceId,
692
1147
  loadGraph,
693
1148
  runTraced,
1149
+ startLive,
694
1150
  stitchTrace
695
1151
  });
package/dist/index.d.cts CHANGED
@@ -377,6 +377,24 @@ interface RunResult {
377
377
  */
378
378
  declare function runTraced(config: RunConfig): Promise<RunResult>;
379
379
 
380
+ /**
381
+ * AgentFlow Live Monitor — real-time terminal dashboard for any agent system.
382
+ *
383
+ * Auto-detects and displays data from any JSON/JSONL files in the watched
384
+ * directory. Works with agentflow traces, generic state files, job
385
+ * schedulers, session logs — no configuration needed.
386
+ *
387
+ * File detection:
388
+ * .json with `nodes` + `agentId` → AgentFlow trace (full analysis)
389
+ * .json with array of objects with `state` → Job/task list (per-item status)
390
+ * .json with `status`/`pid`/`tools` → Process/worker state
391
+ * .json with any other structure → Generic state (mtime-based)
392
+ * .jsonl → Session log (last entry status)
393
+ *
394
+ * @module
395
+ */
396
+ declare function startLive(argv: string[]): void;
397
+
380
398
  declare function groupByTraceId(graphs: ExecutionGraph[]): Map<string, ExecutionGraph[]>;
381
399
  declare function stitchTrace(graphs: ExecutionGraph[]): DistributedTrace;
382
400
  declare function getTraceTree(trace: DistributedTrace): ExecutionGraph[];
@@ -497,4 +515,4 @@ declare function getDepth(graph: ExecutionGraph): number;
497
515
  */
498
516
  declare function getStats(graph: ExecutionGraph): GraphStats;
499
517
 
500
- 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, stitchTrace };
518
+ 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 };
package/dist/index.d.ts CHANGED
@@ -377,6 +377,24 @@ interface RunResult {
377
377
  */
378
378
  declare function runTraced(config: RunConfig): Promise<RunResult>;
379
379
 
380
+ /**
381
+ * AgentFlow Live Monitor — real-time terminal dashboard for any agent system.
382
+ *
383
+ * Auto-detects and displays data from any JSON/JSONL files in the watched
384
+ * directory. Works with agentflow traces, generic state files, job
385
+ * schedulers, session logs — no configuration needed.
386
+ *
387
+ * File detection:
388
+ * .json with `nodes` + `agentId` → AgentFlow trace (full analysis)
389
+ * .json with array of objects with `state` → Job/task list (per-item status)
390
+ * .json with `status`/`pid`/`tools` → Process/worker state
391
+ * .json with any other structure → Generic state (mtime-based)
392
+ * .jsonl → Session log (last entry status)
393
+ *
394
+ * @module
395
+ */
396
+ declare function startLive(argv: string[]): void;
397
+
380
398
  declare function groupByTraceId(graphs: ExecutionGraph[]): Map<string, ExecutionGraph[]>;
381
399
  declare function stitchTrace(graphs: ExecutionGraph[]): DistributedTrace;
382
400
  declare function getTraceTree(trace: DistributedTrace): ExecutionGraph[];
@@ -497,4 +515,4 @@ declare function getDepth(graph: ExecutionGraph): number;
497
515
  */
498
516
  declare function getStats(graph: ExecutionGraph): GraphStats;
499
517
 
500
- 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, stitchTrace };
518
+ 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 };