agentflow-core 0.2.0 → 0.2.2
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 → chunk-WOJEID7V.js} +317 -136
- package/dist/cli.cjs +320 -138
- package/dist/cli.js +6 -5
- package/dist/index.cjs +316 -135
- package/dist/index.d.cts +10 -11
- package/dist/index.d.ts +10 -11
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -693,10 +693,7 @@ var C = {
|
|
|
693
693
|
white: "\x1B[37m"
|
|
694
694
|
};
|
|
695
695
|
function parseArgs(argv) {
|
|
696
|
-
const config = {
|
|
697
|
-
tracesDir: "./traces",
|
|
698
|
-
refreshMs: 3e3
|
|
699
|
-
};
|
|
696
|
+
const config = { dirs: [], refreshMs: 3e3, recursive: false };
|
|
700
697
|
const args = argv.slice(0);
|
|
701
698
|
if (args[0] === "live") args.shift();
|
|
702
699
|
let i = 0;
|
|
@@ -707,138 +704,276 @@ function parseArgs(argv) {
|
|
|
707
704
|
process.exit(0);
|
|
708
705
|
} else if (arg === "--refresh" || arg === "-r") {
|
|
709
706
|
i++;
|
|
710
|
-
const
|
|
711
|
-
if (!isNaN(
|
|
712
|
-
i++;
|
|
713
|
-
} else if (arg === "--traces-dir" || arg === "-t") {
|
|
707
|
+
const v = parseInt(args[i] ?? "", 10);
|
|
708
|
+
if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
714
709
|
i++;
|
|
715
|
-
|
|
710
|
+
} else if (arg === "--recursive" || arg === "-R") {
|
|
711
|
+
config.recursive = true;
|
|
716
712
|
i++;
|
|
717
713
|
} else if (!arg.startsWith("-")) {
|
|
718
|
-
config.
|
|
714
|
+
config.dirs.push((0, import_node_path2.resolve)(arg));
|
|
719
715
|
i++;
|
|
720
716
|
} else {
|
|
721
717
|
i++;
|
|
722
718
|
}
|
|
723
719
|
}
|
|
724
|
-
config.
|
|
720
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
|
|
725
721
|
return config;
|
|
726
722
|
}
|
|
727
723
|
function printUsage() {
|
|
728
724
|
console.log(`
|
|
729
725
|
AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
|
|
730
726
|
|
|
727
|
+
Auto-detects agent traces, state files, job schedulers, and session logs
|
|
728
|
+
from any JSON/JSONL files in the watched directories.
|
|
729
|
+
|
|
731
730
|
Usage:
|
|
732
|
-
agentflow live [
|
|
731
|
+
agentflow live [dir...] [options]
|
|
733
732
|
|
|
734
733
|
Arguments:
|
|
735
|
-
|
|
734
|
+
dir One or more directories to watch (default: .)
|
|
736
735
|
|
|
737
736
|
Options:
|
|
738
737
|
-r, --refresh <secs> Refresh interval in seconds (default: 3)
|
|
739
|
-
-
|
|
738
|
+
-R, --recursive Scan subdirectories (1 level deep)
|
|
740
739
|
-h, --help Show this help message
|
|
741
740
|
|
|
742
741
|
Examples:
|
|
743
|
-
agentflow live
|
|
744
|
-
agentflow live ./
|
|
745
|
-
agentflow live /var/
|
|
742
|
+
agentflow live ./data
|
|
743
|
+
agentflow live ./traces ./cron ./workers --refresh 5
|
|
744
|
+
agentflow live /var/lib/myagent -R
|
|
746
745
|
`.trim());
|
|
747
746
|
}
|
|
748
|
-
function
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
747
|
+
function scanFiles(dirs, recursive) {
|
|
748
|
+
const results = [];
|
|
749
|
+
const seen = /* @__PURE__ */ new Set();
|
|
750
|
+
function scanDir(d, topLevel) {
|
|
751
|
+
try {
|
|
752
|
+
for (const f of (0, import_node_fs2.readdirSync)(d)) {
|
|
753
|
+
if (f.startsWith(".")) continue;
|
|
754
|
+
const fp = (0, import_node_path2.join)(d, f);
|
|
755
|
+
if (seen.has(fp)) continue;
|
|
756
|
+
let stat;
|
|
757
|
+
try {
|
|
758
|
+
stat = (0, import_node_fs2.statSync)(fp);
|
|
759
|
+
} catch {
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (stat.isDirectory() && recursive && topLevel) {
|
|
763
|
+
scanDir(fp, false);
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
if (!stat.isFile()) continue;
|
|
767
|
+
if (f.endsWith(".json")) {
|
|
768
|
+
seen.add(fp);
|
|
769
|
+
results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" });
|
|
770
|
+
} else if (f.endsWith(".jsonl")) {
|
|
771
|
+
seen.add(fp);
|
|
772
|
+
results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
} catch {
|
|
776
|
+
}
|
|
757
777
|
}
|
|
778
|
+
for (const dir of dirs) scanDir(dir, true);
|
|
779
|
+
results.sort((a, b) => b.mtime - a.mtime);
|
|
780
|
+
return results;
|
|
758
781
|
}
|
|
759
|
-
function
|
|
782
|
+
function safeReadJson(fp) {
|
|
760
783
|
try {
|
|
761
|
-
return
|
|
784
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
|
|
762
785
|
} catch {
|
|
763
786
|
return null;
|
|
764
787
|
}
|
|
765
788
|
}
|
|
766
|
-
function
|
|
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;
|
|
767
846
|
try {
|
|
768
|
-
|
|
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
|
-
};
|
|
847
|
+
return loadGraph(obj);
|
|
782
848
|
} catch {
|
|
783
849
|
return null;
|
|
784
850
|
}
|
|
785
851
|
}
|
|
786
|
-
function
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
+
}
|
|
791
927
|
}
|
|
792
928
|
var prevFileCount = 0;
|
|
793
929
|
var newExecCount = 0;
|
|
794
930
|
var sessionStart = Date.now();
|
|
795
931
|
function render(config) {
|
|
796
|
-
const files =
|
|
932
|
+
const files = scanFiles(config.dirs, config.recursive);
|
|
797
933
|
if (files.length > prevFileCount && prevFileCount > 0) {
|
|
798
934
|
newExecCount += files.length - prevFileCount;
|
|
799
935
|
}
|
|
800
936
|
prevFileCount = files.length;
|
|
937
|
+
const allRecords = [];
|
|
801
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
|
+
}
|
|
802
946
|
const agents = {};
|
|
803
|
-
for (const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
const
|
|
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];
|
|
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];
|
|
813
952
|
ag.total++;
|
|
814
|
-
|
|
815
|
-
if (
|
|
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
|
+
}
|
|
816
961
|
}
|
|
817
|
-
const agentList = Object.values(agents).sort((a, b) => b.
|
|
962
|
+
const agentList = Object.values(agents).sort((a, b) => b.lastTs - a.lastTs);
|
|
818
963
|
const totExec = agentList.reduce((s, a) => s + a.total, 0);
|
|
819
964
|
const totFail = agentList.reduce((s, a) => s + a.fail, 0);
|
|
965
|
+
const totRunning = agentList.reduce((s, a) => s + a.running, 0);
|
|
820
966
|
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
967
|
const now = Date.now();
|
|
829
968
|
const buckets = new Array(12).fill(0);
|
|
830
969
|
const failBuckets = new Array(12).fill(0);
|
|
831
|
-
for (const
|
|
832
|
-
const age = now -
|
|
833
|
-
if (age > 36e5) continue;
|
|
970
|
+
for (const r of allRecords) {
|
|
971
|
+
const age = now - r.lastActive;
|
|
972
|
+
if (age > 36e5 || age < 0) continue;
|
|
834
973
|
const idx = 11 - Math.floor(age / 3e5);
|
|
835
974
|
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
975
|
buckets[idx]++;
|
|
841
|
-
if (
|
|
976
|
+
if (r.status === "error") failBuckets[idx]++;
|
|
842
977
|
}
|
|
843
978
|
}
|
|
844
979
|
const maxBucket = Math.max(...buckets, 1);
|
|
@@ -847,107 +982,153 @@ function render(config) {
|
|
|
847
982
|
const level = Math.round(v / maxBucket * 8);
|
|
848
983
|
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
849
984
|
}).join("");
|
|
850
|
-
const traceGroups = groupByTraceId(allTraces);
|
|
851
985
|
const distributedTraces = [];
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
+
}
|
|
857
994
|
}
|
|
858
995
|
}
|
|
996
|
+
distributedTraces.sort((a, b) => b.startTime - a.startTime);
|
|
859
997
|
}
|
|
860
|
-
distributedTraces.sort((a, b) => b.startTime - a.startTime);
|
|
861
998
|
const upSec = Math.floor((Date.now() - sessionStart) / 1e3);
|
|
862
999
|
const upMin = Math.floor(upSec / 60);
|
|
863
1000
|
const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
|
|
864
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
|
+
};
|
|
865
1016
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
866
|
-
console.log(`${C.bold}${C.cyan}\u2554\u2550\
|
|
1017
|
+
console.log(`${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
|
|
867
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}`);
|
|
868
|
-
const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr}`;
|
|
1019
|
+
const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
|
|
869
1020
|
const pad1 = Math.max(0, 64 - metaLine.length);
|
|
870
1021
|
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\
|
|
1022
|
+
console.log(`${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
|
|
872
1023
|
const sc = totFail === 0 ? C.green : C.yellow;
|
|
873
1024
|
console.log("");
|
|
874
|
-
console.log(` ${C.bold}Agents${C.reset} ${sc}${agentList.length}${C.reset} ${C.bold}
|
|
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}`);
|
|
875
1026
|
console.log("");
|
|
876
1027
|
console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
877
1028
|
console.log("");
|
|
878
|
-
console.log(` ${C.bold}${C.under}Agent
|
|
879
|
-
for (const ag of agentList) {
|
|
880
|
-
const
|
|
881
|
-
const lastTime = new Date(ag.lastTs).toLocaleTimeString();
|
|
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";
|
|
882
1032
|
const isRecent = Date.now() - ag.lastTs < 3e5;
|
|
883
|
-
let
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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}`);
|
|
894
1056
|
}
|
|
895
1057
|
if (distributedTraces.length > 0) {
|
|
896
1058
|
console.log("");
|
|
897
|
-
console.log(` ${C.bold}${C.under}Distributed Traces
|
|
898
|
-
for (const dt of distributedTraces.slice(0,
|
|
1059
|
+
console.log(` ${C.bold}${C.under}Distributed Traces${C.reset}`);
|
|
1060
|
+
for (const dt of distributedTraces.slice(0, 3)) {
|
|
899
1061
|
const traceTime = new Date(dt.startTime).toLocaleTimeString();
|
|
900
1062
|
const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
901
1063
|
const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
|
|
902
1064
|
const tid = dt.traceId.slice(0, 8);
|
|
903
|
-
console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime}
|
|
1065
|
+
console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
|
|
904
1066
|
const tree = getTraceTree(dt);
|
|
905
|
-
for (let i = 0; i < tree.length; i++) {
|
|
1067
|
+
for (let i = 0; i < Math.min(tree.length, 6); i++) {
|
|
906
1068
|
const g = tree[i];
|
|
907
|
-
const depth =
|
|
1069
|
+
const depth = getDistDepth(dt, g.spanId);
|
|
908
1070
|
const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
|
|
909
|
-
const isLast = i === tree.length - 1 ||
|
|
910
|
-
const
|
|
911
|
-
const
|
|
912
|
-
const
|
|
913
|
-
console.log(`${indent}${
|
|
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}`);
|
|
914
1076
|
}
|
|
915
1077
|
}
|
|
916
1078
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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
|
+
}
|
|
927
1092
|
}
|
|
928
1093
|
if (files.length === 0) {
|
|
929
|
-
console.log(
|
|
930
|
-
console.log(` ${C.dim}
|
|
1094
|
+
console.log("");
|
|
1095
|
+
console.log(` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
|
|
1096
|
+
for (const d of config.dirs) console.log(` ${C.dim} ${d}${C.reset}`);
|
|
931
1097
|
}
|
|
932
1098
|
console.log("");
|
|
933
|
-
|
|
1099
|
+
const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
|
|
1100
|
+
console.log(` ${C.dim}Watching: ${dirLabel}${C.reset}`);
|
|
934
1101
|
console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
935
1102
|
}
|
|
1103
|
+
function getDistDepth(dt, spanId) {
|
|
1104
|
+
if (!spanId) return 0;
|
|
1105
|
+
const g = dt.graphs.get(spanId);
|
|
1106
|
+
if (!g || !g.parentSpanId) return 0;
|
|
1107
|
+
return 1 + getDistDepth(dt, g.parentSpanId);
|
|
1108
|
+
}
|
|
936
1109
|
function startLive(argv) {
|
|
937
1110
|
const config = parseArgs(argv);
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
console.error(
|
|
1111
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
|
|
1112
|
+
if (valid.length === 0) {
|
|
1113
|
+
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1114
|
+
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
941
1115
|
process.exit(1);
|
|
942
1116
|
}
|
|
1117
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
|
|
1118
|
+
if (invalid.length > 0) {
|
|
1119
|
+
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1120
|
+
}
|
|
1121
|
+
config.dirs = valid;
|
|
943
1122
|
render(config);
|
|
944
1123
|
let debounce = null;
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1124
|
+
for (const dir of config.dirs) {
|
|
1125
|
+
try {
|
|
1126
|
+
(0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
|
|
1127
|
+
if (debounce) clearTimeout(debounce);
|
|
1128
|
+
debounce = setTimeout(() => render(config), 500);
|
|
1129
|
+
});
|
|
1130
|
+
} catch {
|
|
1131
|
+
}
|
|
951
1132
|
}
|
|
952
1133
|
setInterval(() => render(config), config.refreshMs);
|
|
953
1134
|
process.on("SIGINT", () => {
|
package/dist/index.d.cts
CHANGED
|
@@ -380,17 +380,16 @@ declare function runTraced(config: RunConfig): Promise<RunResult>;
|
|
|
380
380
|
/**
|
|
381
381
|
* AgentFlow Live Monitor — real-time terminal dashboard for any agent system.
|
|
382
382
|
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
* - fs.watch auto-refresh on new traces
|
|
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)
|
|
394
393
|
*
|
|
395
394
|
* @module
|
|
396
395
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -380,17 +380,16 @@ declare function runTraced(config: RunConfig): Promise<RunResult>;
|
|
|
380
380
|
/**
|
|
381
381
|
* AgentFlow Live Monitor — real-time terminal dashboard for any agent system.
|
|
382
382
|
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
* - fs.watch auto-refresh on new traces
|
|
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)
|
|
394
393
|
*
|
|
395
394
|
* @module
|
|
396
395
|
*/
|