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/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 = { tracesDir: ".", 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,8 +704,11 @@ function parseArgs(argv) {
707
704
  process.exit(0);
708
705
  } else if (arg === "--refresh" || arg === "-r") {
709
706
  i++;
710
- const val = parseInt(args[i] ?? "", 10);
711
- if (!isNaN(val) && val > 0) config.refreshMs = val * 1e3;
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
712
  i++;
713
713
  } else if (arg === "--traces-dir" || arg === "-t") {
714
714
  i++;
@@ -728,117 +728,252 @@ function printUsage() {
728
728
  console.log(`
729
729
  AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
730
730
 
731
+ Auto-detects agent traces, state files, job schedulers, and session logs
732
+ from any JSON/JSONL files in the watched directory.
733
+
731
734
  Usage:
732
- agentflow live [traces-dir] [options]
735
+ agentflow live [directory] [options]
733
736
 
734
737
  Arguments:
735
- traces-dir Path to the traces directory (default: ./traces)
738
+ directory Directory to watch (default: current directory)
736
739
 
737
740
  Options:
738
741
  -r, --refresh <secs> Refresh interval in seconds (default: 3)
739
- -t, --traces-dir <path> Explicit traces directory path
742
+ -R, --recursive Scan subdirectories (1 level deep)
740
743
  -h, --help Show this help message
741
744
 
742
745
  Examples:
743
- agentflow live
744
- agentflow live ./my-traces --refresh 5
745
- agentflow live /var/log/agentflow/traces -r 10
746
+ agentflow live ./data
747
+ agentflow live ./traces --refresh 5
748
+ agentflow live /var/lib/myagent -R
746
749
  `.trim());
747
750
  }
748
- function listTraceFiles(tracesDir) {
749
- try {
750
- return (0, import_node_fs2.readdirSync)(tracesDir).filter((f) => f.endsWith(".json")).map((f) => {
751
- const fp = (0, import_node_path2.join)(tracesDir, f);
752
- const stat = (0, import_node_fs2.statSync)(fp);
753
- return { filename: f, path: fp, mtime: stat.mtime.getTime() };
754
- }).sort((a, b) => b.mtime - a.mtime);
755
- } catch {
756
- return [];
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
+ }
757
777
  }
778
+ scanDir(dir);
779
+ results.sort((a, b) => b.mtime - a.mtime);
780
+ return results;
758
781
  }
759
- function safeLoadTrace(fp) {
782
+ function safeReadJson(fp) {
760
783
  try {
761
- return loadGraph((0, import_node_fs2.readFileSync)(fp, "utf8"));
784
+ return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
762
785
  } catch {
763
786
  return null;
764
787
  }
765
788
  }
766
- function analyze(trace) {
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
- const stats = getStats(trace);
769
- const fails = getFailures(trace);
770
- const hung = getHungNodes(trace);
771
- return {
772
- agentId: trace.agentId,
773
- trigger: trace.trigger,
774
- traceId: trace.traceId,
775
- spanId: trace.spanId,
776
- parentSpanId: trace.parentSpanId,
777
- nodes: stats.totalNodes,
778
- success: fails.length === 0 && hung.length === 0,
779
- failures: fails.length,
780
- hung: hung.length
781
- };
847
+ return loadGraph(obj);
782
848
  } catch {
783
849
  return null;
784
850
  }
785
851
  }
786
- function getDistributedDepth(dt, spanId) {
787
- if (!spanId) return 0;
788
- const graph = dt.graphs.get(spanId);
789
- if (!graph || !graph.parentSpanId) return 0;
790
- return 1 + getDistributedDepth(dt, graph.parentSpanId);
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 = listTraceFiles(config.tracesDir);
932
+ const files = scanFiles(config.tracesDir, 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 f of files.slice(0, 200)) {
804
- const trace = safeLoadTrace(f.path);
805
- if (!trace) continue;
806
- allTraces.push(trace);
807
- const a = analyze(trace);
808
- if (!a) continue;
809
- if (!agents[a.agentId]) {
810
- agents[a.agentId] = { name: a.agentId, total: 0, ok: 0, fail: 0, lastTs: 0 };
811
- }
812
- const ag = agents[a.agentId];
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
- a.success ? ag.ok++ : ag.fail++;
815
- if (f.mtime > ag.lastTs) ag.lastTs = f.mtime;
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.total - a.total);
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 f of files) {
832
- const age = now - f.mtime;
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 (!a.success) failBuckets[idx]++;
976
+ if (r.status === "error") failBuckets[idx]++;
842
977
  }
843
978
  }
844
979
  const maxBucket = Math.max(...buckets, 1);
@@ -847,103 +982,140 @@ 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
- for (const [_traceId, graphs] of traceGroups) {
853
- if (graphs.length > 1) {
854
- try {
855
- distributedTraces.push(stitchTrace(graphs));
856
- } catch {
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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${C.reset}`);
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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${C.reset}`);
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}Executions${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Traces${C.reset} ${sc}${files.length}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset} ${C.bold}Distributed${C.reset} ${C.magenta}${distributedTraces.length}${C.reset}`);
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 Runs OK Fail Rate Last Active${C.reset}`);
879
- for (const ag of agentList) {
880
- const rate = (ag.ok / ag.total * 100).toFixed(0);
881
- const lastTime = new Date(ag.lastTs).toLocaleTimeString();
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 status;
884
- if (ag.fail > 0) status = `${C.red}\u25CF${C.reset}`;
885
- else if (isRecent) status = `${C.green}\u25CF${C.reset}`;
886
- else status = `${C.dim}\u25CB${C.reset}`;
887
- const name = ag.name.padEnd(28);
888
- const runs = String(ag.total).padStart(5);
889
- const ok = String(ag.ok).padStart(5);
890
- const fail = ag.fail > 0 ? `${C.red}${String(ag.fail).padStart(4)}${C.reset}` : String(ag.fail).padStart(4);
891
- const rateStr = (rate + "%").padStart(5);
892
- const activeStr = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
893
- console.log(` ${status} ${name}${runs}${ok}${fail} ${rateStr} ${activeStr}`);
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 (multi-agent workflows)${C.reset}`);
898
- for (const dt of distributedTraces.slice(0, 5)) {
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}${C.reset} ${C.dim}${dur}${C.reset} ${C.dim}(${dt.graphs.size} agents)${C.reset}`);
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 = getDistributedDepth(dt, g.spanId);
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 || getDistributedDepth(dt, tree[i + 1]?.spanId) <= depth;
910
- const connector = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
911
- const gStatus = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
912
- const gDur = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
913
- console.log(`${indent}${connector}${gStatus} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gDur}${C.reset}`);
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
- console.log("");
918
- console.log(` ${C.bold}${C.under}Recent Executions${C.reset}`);
919
- for (const ex of recent.slice(0, 8)) {
920
- const icon = ex.success ? `${C.green}\u2713${C.reset}` : `${C.red}\u2717${C.reset}`;
921
- const t = new Date(ex.ts).toLocaleTimeString();
922
- const agent = ex.agentId.padEnd(28);
923
- const age = Math.floor((Date.now() - ex.ts) / 1e3);
924
- const ageStr = age < 60 ? age + "s ago" : Math.floor(age / 60) + "m ago";
925
- const traceTag = ex.traceId ? ` ${C.magenta}\u29EB${C.reset}` : "";
926
- console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)} ${ex.nodes} nodes${C.reset}${traceTag}`);
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(` ${C.dim}No trace files found. Waiting for traces in:${C.reset}`);
1094
+ console.log("");
1095
+ console.log(` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
930
1096
  console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
931
1097
  }
932
1098
  console.log("");
933
1099
  console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
934
1100
  console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
935
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
+ }
936
1108
  function startLive(argv) {
937
1109
  const config = parseArgs(argv);
938
1110
  if (!(0, import_node_fs2.existsSync)(config.tracesDir)) {
939
- console.error(`Traces directory does not exist: ${config.tracesDir}`);
940
- console.error("Create it or specify a different path: agentflow live <traces-dir>");
1111
+ console.error(`Directory does not exist: ${config.tracesDir}`);
1112
+ console.error("Specify a directory containing JSON/JSONL files: agentflow live <dir>");
941
1113
  process.exit(1);
942
1114
  }
943
1115
  render(config);
944
1116
  let debounce = null;
945
1117
  try {
946
- (0, import_node_fs2.watch)(config.tracesDir, () => {
1118
+ (0, import_node_fs2.watch)(config.tracesDir, { recursive: config.recursive }, () => {
947
1119
  if (debounce) clearTimeout(debounce);
948
1120
  debounce = setTimeout(() => render(config), 500);
949
1121
  });
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
- * Usage:
384
- * agentflow live [traces-dir] [options]
385
- * agentflow live ./traces --refresh 5
386
- *
387
- * Features:
388
- * - Auto-discovers agents from trace files
389
- * - Sparkline activity graph (1 hour)
390
- * - Per-agent success/failure table
391
- * - Distributed trace tree view
392
- * - Recent execution feed
393
- * - fs.watch auto-refresh on new traces
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
- * Usage:
384
- * agentflow live [traces-dir] [options]
385
- * agentflow live ./traces --refresh 5
386
- *
387
- * Features:
388
- * - Auto-discovers agents from trace files
389
- * - Sparkline activity graph (1 hour)
390
- * - Per-agent success/failure table
391
- * - Distributed trace tree view
392
- * - Recent execution feed
393
- * - fs.watch auto-refresh on new traces
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.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  runTraced,
19
19
  startLive,
20
20
  stitchTrace
21
- } from "./chunk-TRKBUPIN.js";
21
+ } from "./chunk-FJVQYJFB.js";
22
22
  export {
23
23
  createGraphBuilder,
24
24
  findWaitingOn,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentflow-core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Universal execution tracing for AI agent systems",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",