agentflow-core 0.2.0 → 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/{chunk-TRKBUPIN.js → chunk-FJVQYJFB.js} +295 -123
- package/dist/cli.cjs +297 -125
- package/dist/cli.js +5 -5
- package/dist/index.cjs +294 -122
- 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
|
@@ -632,7 +632,7 @@ function getTraceTree(trace) {
|
|
|
632
632
|
|
|
633
633
|
// src/live.ts
|
|
634
634
|
import { readdirSync as readdirSync2, readFileSync, statSync as statSync2, watch, existsSync as existsSync2 } from "fs";
|
|
635
|
-
import { join as join2, resolve as resolve2 } from "path";
|
|
635
|
+
import { join as join2, resolve as resolve2, basename as basename2 } from "path";
|
|
636
636
|
var C = {
|
|
637
637
|
reset: "\x1B[0m",
|
|
638
638
|
bold: "\x1B[1m",
|
|
@@ -647,10 +647,7 @@ var C = {
|
|
|
647
647
|
white: "\x1B[37m"
|
|
648
648
|
};
|
|
649
649
|
function parseArgs(argv) {
|
|
650
|
-
const config = {
|
|
651
|
-
tracesDir: "./traces",
|
|
652
|
-
refreshMs: 3e3
|
|
653
|
-
};
|
|
650
|
+
const config = { tracesDir: ".", refreshMs: 3e3, recursive: false };
|
|
654
651
|
const args = argv.slice(0);
|
|
655
652
|
if (args[0] === "live") args.shift();
|
|
656
653
|
let i = 0;
|
|
@@ -661,8 +658,11 @@ function parseArgs(argv) {
|
|
|
661
658
|
process.exit(0);
|
|
662
659
|
} else if (arg === "--refresh" || arg === "-r") {
|
|
663
660
|
i++;
|
|
664
|
-
const
|
|
665
|
-
if (!isNaN(
|
|
661
|
+
const v = parseInt(args[i] ?? "", 10);
|
|
662
|
+
if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
663
|
+
i++;
|
|
664
|
+
} else if (arg === "--recursive" || arg === "-R") {
|
|
665
|
+
config.recursive = true;
|
|
666
666
|
i++;
|
|
667
667
|
} else if (arg === "--traces-dir" || arg === "-t") {
|
|
668
668
|
i++;
|
|
@@ -682,117 +682,252 @@ function printUsage() {
|
|
|
682
682
|
console.log(`
|
|
683
683
|
AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
|
|
684
684
|
|
|
685
|
+
Auto-detects agent traces, state files, job schedulers, and session logs
|
|
686
|
+
from any JSON/JSONL files in the watched directory.
|
|
687
|
+
|
|
685
688
|
Usage:
|
|
686
|
-
agentflow live [
|
|
689
|
+
agentflow live [directory] [options]
|
|
687
690
|
|
|
688
691
|
Arguments:
|
|
689
|
-
|
|
692
|
+
directory Directory to watch (default: current directory)
|
|
690
693
|
|
|
691
694
|
Options:
|
|
692
695
|
-r, --refresh <secs> Refresh interval in seconds (default: 3)
|
|
693
|
-
-
|
|
696
|
+
-R, --recursive Scan subdirectories (1 level deep)
|
|
694
697
|
-h, --help Show this help message
|
|
695
698
|
|
|
696
699
|
Examples:
|
|
697
|
-
agentflow live
|
|
698
|
-
agentflow live ./
|
|
699
|
-
agentflow live /var/
|
|
700
|
+
agentflow live ./data
|
|
701
|
+
agentflow live ./traces --refresh 5
|
|
702
|
+
agentflow live /var/lib/myagent -R
|
|
700
703
|
`.trim());
|
|
701
704
|
}
|
|
702
|
-
function
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
705
|
+
function scanFiles(dir, recursive) {
|
|
706
|
+
const results = [];
|
|
707
|
+
function scanDir(d) {
|
|
708
|
+
try {
|
|
709
|
+
for (const f of readdirSync2(d)) {
|
|
710
|
+
if (f.startsWith(".")) continue;
|
|
711
|
+
const fp = join2(d, f);
|
|
712
|
+
let stat;
|
|
713
|
+
try {
|
|
714
|
+
stat = statSync2(fp);
|
|
715
|
+
} catch {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (stat.isDirectory() && recursive && d === dir) {
|
|
719
|
+
scanDir(fp);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (!stat.isFile()) continue;
|
|
723
|
+
if (f.endsWith(".json")) {
|
|
724
|
+
results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" });
|
|
725
|
+
} else if (f.endsWith(".jsonl")) {
|
|
726
|
+
results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
} catch {
|
|
730
|
+
}
|
|
711
731
|
}
|
|
732
|
+
scanDir(dir);
|
|
733
|
+
results.sort((a, b) => b.mtime - a.mtime);
|
|
734
|
+
return results;
|
|
712
735
|
}
|
|
713
|
-
function
|
|
736
|
+
function safeReadJson(fp) {
|
|
714
737
|
try {
|
|
715
|
-
return
|
|
738
|
+
return JSON.parse(readFileSync(fp, "utf8"));
|
|
716
739
|
} catch {
|
|
717
740
|
return null;
|
|
718
741
|
}
|
|
719
742
|
}
|
|
720
|
-
function
|
|
743
|
+
function nameFromFile(filename) {
|
|
744
|
+
return basename2(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
745
|
+
}
|
|
746
|
+
function normalizeStatus(val) {
|
|
747
|
+
if (typeof val !== "string") return "unknown";
|
|
748
|
+
const s = val.toLowerCase();
|
|
749
|
+
if (["ok", "success", "completed", "done", "passed", "healthy", "good"].includes(s)) return "ok";
|
|
750
|
+
if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s)) return "error";
|
|
751
|
+
if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s)) return "running";
|
|
752
|
+
return "unknown";
|
|
753
|
+
}
|
|
754
|
+
function findStatus(obj) {
|
|
755
|
+
for (const key of ["status", "state", "lastRunStatus", "lastStatus", "health", "result"]) {
|
|
756
|
+
if (key in obj) {
|
|
757
|
+
const val = obj[key];
|
|
758
|
+
if (typeof val === "string") return normalizeStatus(val);
|
|
759
|
+
if (typeof val === "object" && val !== null && "status" in val) {
|
|
760
|
+
return normalizeStatus(val.status);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return "unknown";
|
|
765
|
+
}
|
|
766
|
+
function findTimestamp(obj) {
|
|
767
|
+
for (const key of ["ts", "timestamp", "lastRunAtMs", "last_run", "lastExecution", "updated_at", "started_at", "endTime", "startTime"]) {
|
|
768
|
+
const val = obj[key];
|
|
769
|
+
if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
|
|
770
|
+
if (typeof val === "string") {
|
|
771
|
+
const d = Date.parse(val);
|
|
772
|
+
if (!isNaN(d)) return d;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
function extractDetail(obj) {
|
|
778
|
+
const parts = [];
|
|
779
|
+
for (const key of ["summary", "message", "description", "lastError", "error", "name", "jobId", "id"]) {
|
|
780
|
+
const val = obj[key];
|
|
781
|
+
if (typeof val === "string" && val.length > 0 && val.length < 200) {
|
|
782
|
+
parts.push(val.slice(0, 80));
|
|
783
|
+
break;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
for (const key of ["totalExecutions", "runs", "count", "processed", "consecutiveErrors"]) {
|
|
787
|
+
const val = obj[key];
|
|
788
|
+
if (typeof val === "number") {
|
|
789
|
+
parts.push(`${key}: ${val}`);
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return parts.join(" | ") || "";
|
|
794
|
+
}
|
|
795
|
+
function tryLoadTrace(fp, raw) {
|
|
796
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
797
|
+
const obj = raw;
|
|
798
|
+
if (!("nodes" in obj)) return null;
|
|
799
|
+
if (!("agentId" in obj) && !("rootNodeId" in obj) && !("rootId" in obj)) return null;
|
|
721
800
|
try {
|
|
722
|
-
|
|
723
|
-
const fails = getFailures(trace);
|
|
724
|
-
const hung = getHungNodes(trace);
|
|
725
|
-
return {
|
|
726
|
-
agentId: trace.agentId,
|
|
727
|
-
trigger: trace.trigger,
|
|
728
|
-
traceId: trace.traceId,
|
|
729
|
-
spanId: trace.spanId,
|
|
730
|
-
parentSpanId: trace.parentSpanId,
|
|
731
|
-
nodes: stats.totalNodes,
|
|
732
|
-
success: fails.length === 0 && hung.length === 0,
|
|
733
|
-
failures: fails.length,
|
|
734
|
-
hung: hung.length
|
|
735
|
-
};
|
|
801
|
+
return loadGraph(obj);
|
|
736
802
|
} catch {
|
|
737
803
|
return null;
|
|
738
804
|
}
|
|
739
805
|
}
|
|
740
|
-
function
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
806
|
+
function processJsonFile(file) {
|
|
807
|
+
const raw = safeReadJson(file.path);
|
|
808
|
+
if (raw === null) return [];
|
|
809
|
+
const records = [];
|
|
810
|
+
const trace = tryLoadTrace(file.path, raw);
|
|
811
|
+
if (trace) {
|
|
812
|
+
try {
|
|
813
|
+
const fails = getFailures(trace);
|
|
814
|
+
const hung = getHungNodes(trace);
|
|
815
|
+
const stats = getStats(trace);
|
|
816
|
+
records.push({
|
|
817
|
+
id: trace.agentId,
|
|
818
|
+
source: "trace",
|
|
819
|
+
status: fails.length === 0 && hung.length === 0 ? "ok" : "error",
|
|
820
|
+
lastActive: file.mtime,
|
|
821
|
+
detail: `${stats.totalNodes} nodes [${trace.trigger}]`,
|
|
822
|
+
file: file.filename,
|
|
823
|
+
traceData: trace
|
|
824
|
+
});
|
|
825
|
+
} catch {
|
|
826
|
+
}
|
|
827
|
+
return records;
|
|
828
|
+
}
|
|
829
|
+
if (typeof raw !== "object") return records;
|
|
830
|
+
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;
|
|
831
|
+
if (arr && arr.length > 0 && typeof arr[0] === "object" && arr[0] !== null) {
|
|
832
|
+
for (const item of arr.slice(0, 50)) {
|
|
833
|
+
const name = item.name ?? item.id ?? item.jobId ?? item.agentId;
|
|
834
|
+
if (!name) continue;
|
|
835
|
+
const state = typeof item.state === "object" && item.state !== null ? item.state : item;
|
|
836
|
+
const status2 = findStatus(state);
|
|
837
|
+
const ts2 = findTimestamp(state) || file.mtime;
|
|
838
|
+
const detail2 = extractDetail(state);
|
|
839
|
+
records.push({ id: String(name), source: "jobs", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
|
|
840
|
+
}
|
|
841
|
+
return records;
|
|
842
|
+
}
|
|
843
|
+
const obj = raw;
|
|
844
|
+
for (const containerKey of ["tools", "workers", "services", "agents", "daemons"]) {
|
|
845
|
+
const container = obj[containerKey];
|
|
846
|
+
if (typeof container === "object" && container !== null && !Array.isArray(container)) {
|
|
847
|
+
for (const [name, info] of Object.entries(container)) {
|
|
848
|
+
if (typeof info !== "object" || info === null) continue;
|
|
849
|
+
const w = info;
|
|
850
|
+
const status2 = findStatus(w);
|
|
851
|
+
const ts2 = findTimestamp(w) || findTimestamp(obj) || file.mtime;
|
|
852
|
+
const pid = w.pid;
|
|
853
|
+
const detail2 = pid ? `pid: ${pid}` : extractDetail(w);
|
|
854
|
+
records.push({ id: name, source: "workers", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
|
|
855
|
+
}
|
|
856
|
+
return records;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
const status = findStatus(obj);
|
|
860
|
+
const ts = findTimestamp(obj) || file.mtime;
|
|
861
|
+
const detail = extractDetail(obj);
|
|
862
|
+
records.push({ id: nameFromFile(file.filename), source: "state", status, lastActive: ts, detail, file: file.filename });
|
|
863
|
+
return records;
|
|
864
|
+
}
|
|
865
|
+
function processJsonlFile(file) {
|
|
866
|
+
try {
|
|
867
|
+
const content = readFileSync(file.path, "utf8").trim();
|
|
868
|
+
if (!content) return [];
|
|
869
|
+
const lines = content.split("\n");
|
|
870
|
+
const lastLine = lines[lines.length - 1];
|
|
871
|
+
const obj = JSON.parse(lastLine);
|
|
872
|
+
const name = obj.jobId ?? obj.agentId ?? obj.name ?? obj.id ?? nameFromFile(file.filename);
|
|
873
|
+
const status = findStatus(obj);
|
|
874
|
+
const ts = findTimestamp(obj) || file.mtime;
|
|
875
|
+
const action = obj.action;
|
|
876
|
+
const detail = action ? `${action} (${lines.length} entries)` : `${lines.length} entries`;
|
|
877
|
+
return [{ id: String(name), source: "session", status, lastActive: ts, detail, file: file.filename }];
|
|
878
|
+
} catch {
|
|
879
|
+
return [];
|
|
880
|
+
}
|
|
745
881
|
}
|
|
746
882
|
var prevFileCount = 0;
|
|
747
883
|
var newExecCount = 0;
|
|
748
884
|
var sessionStart = Date.now();
|
|
749
885
|
function render(config) {
|
|
750
|
-
const files =
|
|
886
|
+
const files = scanFiles(config.tracesDir, config.recursive);
|
|
751
887
|
if (files.length > prevFileCount && prevFileCount > 0) {
|
|
752
888
|
newExecCount += files.length - prevFileCount;
|
|
753
889
|
}
|
|
754
890
|
prevFileCount = files.length;
|
|
891
|
+
const allRecords = [];
|
|
755
892
|
const allTraces = [];
|
|
893
|
+
for (const f of files.slice(0, 300)) {
|
|
894
|
+
const records = f.ext === ".jsonl" ? processJsonlFile(f) : processJsonFile(f);
|
|
895
|
+
for (const r of records) {
|
|
896
|
+
allRecords.push(r);
|
|
897
|
+
if (r.traceData) allTraces.push(r.traceData);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
756
900
|
const agents = {};
|
|
757
|
-
for (const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const
|
|
762
|
-
if (!a) continue;
|
|
763
|
-
if (!agents[a.agentId]) {
|
|
764
|
-
agents[a.agentId] = { name: a.agentId, total: 0, ok: 0, fail: 0, lastTs: 0 };
|
|
765
|
-
}
|
|
766
|
-
const ag = agents[a.agentId];
|
|
901
|
+
for (const r of allRecords) {
|
|
902
|
+
if (!agents[r.id]) {
|
|
903
|
+
agents[r.id] = { name: r.id, total: 0, ok: 0, fail: 0, running: 0, lastTs: 0, source: r.source, detail: "" };
|
|
904
|
+
}
|
|
905
|
+
const ag = agents[r.id];
|
|
767
906
|
ag.total++;
|
|
768
|
-
|
|
769
|
-
if (
|
|
907
|
+
if (r.status === "ok") ag.ok++;
|
|
908
|
+
else if (r.status === "error") ag.fail++;
|
|
909
|
+
else if (r.status === "running") ag.running++;
|
|
910
|
+
if (r.lastActive > ag.lastTs) {
|
|
911
|
+
ag.lastTs = r.lastActive;
|
|
912
|
+
ag.detail = r.detail;
|
|
913
|
+
ag.source = r.source;
|
|
914
|
+
}
|
|
770
915
|
}
|
|
771
|
-
const agentList = Object.values(agents).sort((a, b) => b.
|
|
916
|
+
const agentList = Object.values(agents).sort((a, b) => b.lastTs - a.lastTs);
|
|
772
917
|
const totExec = agentList.reduce((s, a) => s + a.total, 0);
|
|
773
918
|
const totFail = agentList.reduce((s, a) => s + a.fail, 0);
|
|
919
|
+
const totRunning = agentList.reduce((s, a) => s + a.running, 0);
|
|
774
920
|
const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
|
|
775
|
-
const recent = [];
|
|
776
|
-
for (const f of files.slice(0, 15)) {
|
|
777
|
-
const trace = safeLoadTrace(f.path);
|
|
778
|
-
if (!trace) continue;
|
|
779
|
-
const a = analyze(trace);
|
|
780
|
-
if (a) recent.push({ ...a, ts: f.mtime });
|
|
781
|
-
}
|
|
782
921
|
const now = Date.now();
|
|
783
922
|
const buckets = new Array(12).fill(0);
|
|
784
923
|
const failBuckets = new Array(12).fill(0);
|
|
785
|
-
for (const
|
|
786
|
-
const age = now -
|
|
787
|
-
if (age > 36e5) continue;
|
|
924
|
+
for (const r of allRecords) {
|
|
925
|
+
const age = now - r.lastActive;
|
|
926
|
+
if (age > 36e5 || age < 0) continue;
|
|
788
927
|
const idx = 11 - Math.floor(age / 3e5);
|
|
789
928
|
if (idx >= 0 && idx < 12) {
|
|
790
|
-
const trace = safeLoadTrace(f.path);
|
|
791
|
-
if (!trace) continue;
|
|
792
|
-
const a = analyze(trace);
|
|
793
|
-
if (!a) continue;
|
|
794
929
|
buckets[idx]++;
|
|
795
|
-
if (
|
|
930
|
+
if (r.status === "error") failBuckets[idx]++;
|
|
796
931
|
}
|
|
797
932
|
}
|
|
798
933
|
const maxBucket = Math.max(...buckets, 1);
|
|
@@ -801,103 +936,140 @@ function render(config) {
|
|
|
801
936
|
const level = Math.round(v / maxBucket * 8);
|
|
802
937
|
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
803
938
|
}).join("");
|
|
804
|
-
const traceGroups = groupByTraceId(allTraces);
|
|
805
939
|
const distributedTraces = [];
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
940
|
+
if (allTraces.length > 1) {
|
|
941
|
+
const traceGroups = groupByTraceId(allTraces);
|
|
942
|
+
for (const [_tid, graphs] of traceGroups) {
|
|
943
|
+
if (graphs.length > 1) {
|
|
944
|
+
try {
|
|
945
|
+
distributedTraces.push(stitchTrace(graphs));
|
|
946
|
+
} catch {
|
|
947
|
+
}
|
|
811
948
|
}
|
|
812
949
|
}
|
|
950
|
+
distributedTraces.sort((a, b) => b.startTime - a.startTime);
|
|
813
951
|
}
|
|
814
|
-
distributedTraces.sort((a, b) => b.startTime - a.startTime);
|
|
815
952
|
const upSec = Math.floor((Date.now() - sessionStart) / 1e3);
|
|
816
953
|
const upMin = Math.floor(upSec / 60);
|
|
817
954
|
const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
|
|
818
955
|
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
956
|
+
const sourceTag = (s) => {
|
|
957
|
+
switch (s) {
|
|
958
|
+
case "trace":
|
|
959
|
+
return `${C.cyan}trace${C.reset}`;
|
|
960
|
+
case "jobs":
|
|
961
|
+
return `${C.blue}job${C.reset}`;
|
|
962
|
+
case "workers":
|
|
963
|
+
return `${C.magenta}worker${C.reset}`;
|
|
964
|
+
case "session":
|
|
965
|
+
return `${C.yellow}session${C.reset}`;
|
|
966
|
+
case "state":
|
|
967
|
+
return `${C.dim}state${C.reset}`;
|
|
968
|
+
}
|
|
969
|
+
};
|
|
819
970
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
820
|
-
console.log(`${C.bold}${C.cyan}\u2554\u2550\
|
|
971
|
+
console.log(`${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
|
|
821
972
|
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}`);
|
|
822
|
-
const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr}`;
|
|
973
|
+
const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
|
|
823
974
|
const pad1 = Math.max(0, 64 - metaLine.length);
|
|
824
975
|
console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
|
|
825
|
-
console.log(`${C.bold}${C.cyan}\u255A\u2550\
|
|
976
|
+
console.log(`${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
|
|
826
977
|
const sc = totFail === 0 ? C.green : C.yellow;
|
|
827
978
|
console.log("");
|
|
828
|
-
console.log(` ${C.bold}Agents${C.reset} ${sc}${agentList.length}${C.reset} ${C.bold}
|
|
979
|
+
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}`);
|
|
829
980
|
console.log("");
|
|
830
981
|
console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
831
982
|
console.log("");
|
|
832
|
-
console.log(` ${C.bold}${C.under}Agent
|
|
833
|
-
for (const ag of agentList) {
|
|
834
|
-
const
|
|
835
|
-
const lastTime = new Date(ag.lastTs).toLocaleTimeString();
|
|
983
|
+
console.log(` ${C.bold}${C.under}Agent Type Status Last Active Detail${C.reset}`);
|
|
984
|
+
for (const ag of agentList.slice(0, 30)) {
|
|
985
|
+
const lastTime = ag.lastTs > 0 ? new Date(ag.lastTs).toLocaleTimeString() : "n/a";
|
|
836
986
|
const isRecent = Date.now() - ag.lastTs < 3e5;
|
|
837
|
-
let
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
987
|
+
let statusIcon;
|
|
988
|
+
let statusText;
|
|
989
|
+
if (ag.fail > 0 && ag.ok === 0 && ag.running === 0) {
|
|
990
|
+
statusIcon = `${C.red}\u25CF${C.reset}`;
|
|
991
|
+
statusText = `${C.red}error${C.reset}`;
|
|
992
|
+
} else if (ag.running > 0) {
|
|
993
|
+
statusIcon = `${C.green}\u25CF${C.reset}`;
|
|
994
|
+
statusText = `${C.green}running${C.reset}`;
|
|
995
|
+
} else if (ag.fail > 0) {
|
|
996
|
+
statusIcon = `${C.yellow}\u25CF${C.reset}`;
|
|
997
|
+
statusText = `${C.yellow}${ag.ok}ok/${ag.fail}err${C.reset}`;
|
|
998
|
+
} else if (ag.ok > 0) {
|
|
999
|
+
statusIcon = isRecent ? `${C.green}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
|
|
1000
|
+
statusText = ag.total > 1 ? `${C.green}${ag.ok}/${ag.total}${C.reset}` : `${C.green}ok${C.reset}`;
|
|
1001
|
+
} else {
|
|
1002
|
+
statusIcon = `${C.dim}\u25CB${C.reset}`;
|
|
1003
|
+
statusText = `${C.dim}idle${C.reset}`;
|
|
1004
|
+
}
|
|
1005
|
+
const name = ag.name.length > 23 ? ag.name.slice(0, 22) + "\u2026" : ag.name.padEnd(23);
|
|
1006
|
+
const src = sourceTag(ag.source).padEnd(16);
|
|
1007
|
+
const active = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
|
|
1008
|
+
const detail = ag.detail.length > 30 ? ag.detail.slice(0, 29) + "\u2026" : ag.detail;
|
|
1009
|
+
console.log(` ${statusIcon} ${name} ${src} ${statusText.padEnd(18)} ${active.padEnd(20)} ${C.dim}${detail}${C.reset}`);
|
|
848
1010
|
}
|
|
849
1011
|
if (distributedTraces.length > 0) {
|
|
850
1012
|
console.log("");
|
|
851
|
-
console.log(` ${C.bold}${C.under}Distributed Traces
|
|
852
|
-
for (const dt of distributedTraces.slice(0,
|
|
1013
|
+
console.log(` ${C.bold}${C.under}Distributed Traces${C.reset}`);
|
|
1014
|
+
for (const dt of distributedTraces.slice(0, 3)) {
|
|
853
1015
|
const traceTime = new Date(dt.startTime).toLocaleTimeString();
|
|
854
1016
|
const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
855
1017
|
const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
|
|
856
1018
|
const tid = dt.traceId.slice(0, 8);
|
|
857
|
-
console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime}
|
|
1019
|
+
console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
|
|
858
1020
|
const tree = getTraceTree(dt);
|
|
859
|
-
for (let i = 0; i < tree.length; i++) {
|
|
1021
|
+
for (let i = 0; i < Math.min(tree.length, 6); i++) {
|
|
860
1022
|
const g = tree[i];
|
|
861
|
-
const depth =
|
|
1023
|
+
const depth = getDistDepth(dt, g.spanId);
|
|
862
1024
|
const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
|
|
863
|
-
const isLast = i === tree.length - 1 ||
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
const
|
|
867
|
-
console.log(`${indent}${
|
|
1025
|
+
const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
|
|
1026
|
+
const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1027
|
+
const gs = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
1028
|
+
const gd = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
|
|
1029
|
+
console.log(`${indent}${conn}${gs} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gd}${C.reset}`);
|
|
868
1030
|
}
|
|
869
1031
|
}
|
|
870
1032
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1033
|
+
const recentRecords = allRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 8);
|
|
1034
|
+
if (recentRecords.length > 0) {
|
|
1035
|
+
console.log("");
|
|
1036
|
+
console.log(` ${C.bold}${C.under}Recent Activity${C.reset}`);
|
|
1037
|
+
for (const r of recentRecords) {
|
|
1038
|
+
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}`;
|
|
1039
|
+
const t = new Date(r.lastActive).toLocaleTimeString();
|
|
1040
|
+
const agent = r.id.length > 26 ? r.id.slice(0, 25) + "\u2026" : r.id.padEnd(26);
|
|
1041
|
+
const age = Math.floor((Date.now() - r.lastActive) / 1e3);
|
|
1042
|
+
const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
|
|
1043
|
+
const detail = r.detail.length > 25 ? r.detail.slice(0, 24) + "\u2026" : r.detail;
|
|
1044
|
+
console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${detail}${C.reset}`);
|
|
1045
|
+
}
|
|
881
1046
|
}
|
|
882
1047
|
if (files.length === 0) {
|
|
883
|
-
console.log(
|
|
1048
|
+
console.log("");
|
|
1049
|
+
console.log(` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
|
|
884
1050
|
console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
|
|
885
1051
|
}
|
|
886
1052
|
console.log("");
|
|
887
1053
|
console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
|
|
888
1054
|
console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
889
1055
|
}
|
|
1056
|
+
function getDistDepth(dt, spanId) {
|
|
1057
|
+
if (!spanId) return 0;
|
|
1058
|
+
const g = dt.graphs.get(spanId);
|
|
1059
|
+
if (!g || !g.parentSpanId) return 0;
|
|
1060
|
+
return 1 + getDistDepth(dt, g.parentSpanId);
|
|
1061
|
+
}
|
|
890
1062
|
function startLive(argv) {
|
|
891
1063
|
const config = parseArgs(argv);
|
|
892
1064
|
if (!existsSync2(config.tracesDir)) {
|
|
893
|
-
console.error(`
|
|
894
|
-
console.error("
|
|
1065
|
+
console.error(`Directory does not exist: ${config.tracesDir}`);
|
|
1066
|
+
console.error("Specify a directory containing JSON/JSONL files: agentflow live <dir>");
|
|
895
1067
|
process.exit(1);
|
|
896
1068
|
}
|
|
897
1069
|
render(config);
|
|
898
1070
|
let debounce = null;
|
|
899
1071
|
try {
|
|
900
|
-
watch(config.tracesDir, () => {
|
|
1072
|
+
watch(config.tracesDir, { recursive: config.recursive }, () => {
|
|
901
1073
|
if (debounce) clearTimeout(debounce);
|
|
902
1074
|
debounce = setTimeout(() => render(config), 500);
|
|
903
1075
|
});
|