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
|
@@ -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 = { dirs: [], 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,138 +658,276 @@ function parseArgs(argv) {
|
|
|
661
658
|
process.exit(0);
|
|
662
659
|
} else if (arg === "--refresh" || arg === "-r") {
|
|
663
660
|
i++;
|
|
664
|
-
const
|
|
665
|
-
if (!isNaN(
|
|
666
|
-
i++;
|
|
667
|
-
} else if (arg === "--traces-dir" || arg === "-t") {
|
|
661
|
+
const v = parseInt(args[i] ?? "", 10);
|
|
662
|
+
if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
668
663
|
i++;
|
|
669
|
-
|
|
664
|
+
} else if (arg === "--recursive" || arg === "-R") {
|
|
665
|
+
config.recursive = true;
|
|
670
666
|
i++;
|
|
671
667
|
} else if (!arg.startsWith("-")) {
|
|
672
|
-
config.
|
|
668
|
+
config.dirs.push(resolve2(arg));
|
|
673
669
|
i++;
|
|
674
670
|
} else {
|
|
675
671
|
i++;
|
|
676
672
|
}
|
|
677
673
|
}
|
|
678
|
-
config.
|
|
674
|
+
if (config.dirs.length === 0) config.dirs.push(resolve2("."));
|
|
679
675
|
return config;
|
|
680
676
|
}
|
|
681
677
|
function printUsage() {
|
|
682
678
|
console.log(`
|
|
683
679
|
AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
|
|
684
680
|
|
|
681
|
+
Auto-detects agent traces, state files, job schedulers, and session logs
|
|
682
|
+
from any JSON/JSONL files in the watched directories.
|
|
683
|
+
|
|
685
684
|
Usage:
|
|
686
|
-
agentflow live [
|
|
685
|
+
agentflow live [dir...] [options]
|
|
687
686
|
|
|
688
687
|
Arguments:
|
|
689
|
-
|
|
688
|
+
dir One or more directories to watch (default: .)
|
|
690
689
|
|
|
691
690
|
Options:
|
|
692
691
|
-r, --refresh <secs> Refresh interval in seconds (default: 3)
|
|
693
|
-
-
|
|
692
|
+
-R, --recursive Scan subdirectories (1 level deep)
|
|
694
693
|
-h, --help Show this help message
|
|
695
694
|
|
|
696
695
|
Examples:
|
|
697
|
-
agentflow live
|
|
698
|
-
agentflow live ./
|
|
699
|
-
agentflow live /var/
|
|
696
|
+
agentflow live ./data
|
|
697
|
+
agentflow live ./traces ./cron ./workers --refresh 5
|
|
698
|
+
agentflow live /var/lib/myagent -R
|
|
700
699
|
`.trim());
|
|
701
700
|
}
|
|
702
|
-
function
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
701
|
+
function scanFiles(dirs, recursive) {
|
|
702
|
+
const results = [];
|
|
703
|
+
const seen = /* @__PURE__ */ new Set();
|
|
704
|
+
function scanDir(d, topLevel) {
|
|
705
|
+
try {
|
|
706
|
+
for (const f of readdirSync2(d)) {
|
|
707
|
+
if (f.startsWith(".")) continue;
|
|
708
|
+
const fp = join2(d, f);
|
|
709
|
+
if (seen.has(fp)) continue;
|
|
710
|
+
let stat;
|
|
711
|
+
try {
|
|
712
|
+
stat = statSync2(fp);
|
|
713
|
+
} catch {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (stat.isDirectory() && recursive && topLevel) {
|
|
717
|
+
scanDir(fp, false);
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (!stat.isFile()) continue;
|
|
721
|
+
if (f.endsWith(".json")) {
|
|
722
|
+
seen.add(fp);
|
|
723
|
+
results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" });
|
|
724
|
+
} else if (f.endsWith(".jsonl")) {
|
|
725
|
+
seen.add(fp);
|
|
726
|
+
results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
} catch {
|
|
730
|
+
}
|
|
711
731
|
}
|
|
732
|
+
for (const dir of dirs) scanDir(dir, true);
|
|
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.dirs, 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,107 +936,153 @@ 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(
|
|
884
|
-
console.log(` ${C.dim}
|
|
1048
|
+
console.log("");
|
|
1049
|
+
console.log(` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
|
|
1050
|
+
for (const d of config.dirs) console.log(` ${C.dim} ${d}${C.reset}`);
|
|
885
1051
|
}
|
|
886
1052
|
console.log("");
|
|
887
|
-
|
|
1053
|
+
const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
|
|
1054
|
+
console.log(` ${C.dim}Watching: ${dirLabel}${C.reset}`);
|
|
888
1055
|
console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
889
1056
|
}
|
|
1057
|
+
function getDistDepth(dt, spanId) {
|
|
1058
|
+
if (!spanId) return 0;
|
|
1059
|
+
const g = dt.graphs.get(spanId);
|
|
1060
|
+
if (!g || !g.parentSpanId) return 0;
|
|
1061
|
+
return 1 + getDistDepth(dt, g.parentSpanId);
|
|
1062
|
+
}
|
|
890
1063
|
function startLive(argv) {
|
|
891
1064
|
const config = parseArgs(argv);
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
console.error(
|
|
1065
|
+
const valid = config.dirs.filter((d) => existsSync2(d));
|
|
1066
|
+
if (valid.length === 0) {
|
|
1067
|
+
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1068
|
+
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
895
1069
|
process.exit(1);
|
|
896
1070
|
}
|
|
1071
|
+
const invalid = config.dirs.filter((d) => !existsSync2(d));
|
|
1072
|
+
if (invalid.length > 0) {
|
|
1073
|
+
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1074
|
+
}
|
|
1075
|
+
config.dirs = valid;
|
|
897
1076
|
render(config);
|
|
898
1077
|
let debounce = null;
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1078
|
+
for (const dir of config.dirs) {
|
|
1079
|
+
try {
|
|
1080
|
+
watch(dir, { recursive: config.recursive }, () => {
|
|
1081
|
+
if (debounce) clearTimeout(debounce);
|
|
1082
|
+
debounce = setTimeout(() => render(config), 500);
|
|
1083
|
+
});
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
905
1086
|
}
|
|
906
1087
|
setInterval(() => render(config), config.refreshMs);
|
|
907
1088
|
process.on("SIGINT", () => {
|