agentflow-core 0.5.2 → 0.6.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-YTMQBL3T.js → chunk-BOSYI5YM.js} +374 -48
- package/dist/cli.cjs +480 -58
- package/dist/cli.js +103 -3
- package/dist/index.cjs +382 -53
- package/dist/index.d.cts +109 -1
- package/dist/index.d.ts +109 -1
- package/dist/index.js +7 -1
- package/package.json +7 -3
package/dist/index.cjs
CHANGED
|
@@ -20,10 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
auditProcesses: () => auditProcesses,
|
|
23
24
|
checkGuards: () => checkGuards,
|
|
24
25
|
createGraphBuilder: () => createGraphBuilder,
|
|
25
26
|
createTraceStore: () => createTraceStore,
|
|
27
|
+
discoverProcessConfig: () => discoverProcessConfig,
|
|
26
28
|
findWaitingOn: () => findWaitingOn,
|
|
29
|
+
formatAuditReport: () => formatAuditReport,
|
|
27
30
|
getChildren: () => getChildren,
|
|
28
31
|
getCriticalPath: () => getCriticalPath,
|
|
29
32
|
getDepth: () => getDepth,
|
|
@@ -636,9 +639,8 @@ function withGuards(builder, config) {
|
|
|
636
639
|
}
|
|
637
640
|
|
|
638
641
|
// src/live.ts
|
|
639
|
-
var
|
|
640
|
-
var
|
|
641
|
-
var import_node_child_process = require("child_process");
|
|
642
|
+
var import_node_fs2 = require("fs");
|
|
643
|
+
var import_node_path2 = require("path");
|
|
642
644
|
|
|
643
645
|
// src/loader.ts
|
|
644
646
|
function toNodesMap(raw) {
|
|
@@ -692,6 +694,262 @@ function graphToJson(graph) {
|
|
|
692
694
|
};
|
|
693
695
|
}
|
|
694
696
|
|
|
697
|
+
// src/process-audit.ts
|
|
698
|
+
var import_node_child_process = require("child_process");
|
|
699
|
+
var import_node_fs = require("fs");
|
|
700
|
+
var import_node_path = require("path");
|
|
701
|
+
function isPidAlive(pid) {
|
|
702
|
+
try {
|
|
703
|
+
process.kill(pid, 0);
|
|
704
|
+
return true;
|
|
705
|
+
} catch {
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function pidMatchesName(pid, name) {
|
|
710
|
+
try {
|
|
711
|
+
const cmdline = (0, import_node_fs.readFileSync)(`/proc/${pid}/cmdline`, "utf8");
|
|
712
|
+
return cmdline.includes(name);
|
|
713
|
+
} catch {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function readPidFile(path) {
|
|
718
|
+
try {
|
|
719
|
+
const pid = parseInt((0, import_node_fs.readFileSync)(path, "utf8").trim(), 10);
|
|
720
|
+
return isNaN(pid) ? null : pid;
|
|
721
|
+
} catch {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function auditPidFile(config) {
|
|
726
|
+
if (!config.pidFile) return null;
|
|
727
|
+
const pid = readPidFile(config.pidFile);
|
|
728
|
+
if (pid === null) {
|
|
729
|
+
return {
|
|
730
|
+
path: config.pidFile,
|
|
731
|
+
pid: null,
|
|
732
|
+
alive: false,
|
|
733
|
+
matchesProcess: false,
|
|
734
|
+
stale: !(0, import_node_fs.existsSync)(config.pidFile),
|
|
735
|
+
reason: (0, import_node_fs.existsSync)(config.pidFile) ? "PID file exists but content is invalid" : "No PID file found"
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
const alive = isPidAlive(pid);
|
|
739
|
+
const matchesProcess = alive ? pidMatchesName(pid, config.processName) : false;
|
|
740
|
+
const stale = !alive || alive && !matchesProcess;
|
|
741
|
+
let reason;
|
|
742
|
+
if (alive && matchesProcess) {
|
|
743
|
+
reason = `PID ${pid} alive and matches ${config.processName}`;
|
|
744
|
+
} else if (alive && !matchesProcess) {
|
|
745
|
+
reason = `PID ${pid} alive but is NOT ${config.processName} (PID reused by another process)`;
|
|
746
|
+
} else {
|
|
747
|
+
reason = `PID ${pid} no longer exists`;
|
|
748
|
+
}
|
|
749
|
+
return { path: config.pidFile, pid, alive, matchesProcess, stale, reason };
|
|
750
|
+
}
|
|
751
|
+
function auditSystemd(config) {
|
|
752
|
+
if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
|
|
753
|
+
const unit = config.systemdUnit;
|
|
754
|
+
try {
|
|
755
|
+
const raw = (0, import_node_child_process.execSync)(
|
|
756
|
+
`systemctl --user show ${unit} --property=ActiveState,SubState,MainPID,NRestarts,Result --no-pager 2>/dev/null`,
|
|
757
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
758
|
+
);
|
|
759
|
+
const props = {};
|
|
760
|
+
for (const line of raw.trim().split("\n")) {
|
|
761
|
+
const [k, ...v] = line.split("=");
|
|
762
|
+
if (k) props[k.trim()] = v.join("=").trim();
|
|
763
|
+
}
|
|
764
|
+
const activeState = props["ActiveState"] ?? "unknown";
|
|
765
|
+
const subState = props["SubState"] ?? "unknown";
|
|
766
|
+
const mainPid = parseInt(props["MainPID"] ?? "0", 10);
|
|
767
|
+
const restarts = parseInt(props["NRestarts"] ?? "0", 10);
|
|
768
|
+
const result = props["Result"] ?? "unknown";
|
|
769
|
+
return {
|
|
770
|
+
unit,
|
|
771
|
+
activeState,
|
|
772
|
+
subState,
|
|
773
|
+
mainPid,
|
|
774
|
+
restarts,
|
|
775
|
+
result,
|
|
776
|
+
crashLooping: activeState === "activating" && subState === "auto-restart",
|
|
777
|
+
failed: activeState === "failed"
|
|
778
|
+
};
|
|
779
|
+
} catch {
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function auditWorkers(config) {
|
|
784
|
+
if (!config.workersFile || !(0, import_node_fs.existsSync)(config.workersFile)) return null;
|
|
785
|
+
try {
|
|
786
|
+
const data = JSON.parse((0, import_node_fs.readFileSync)(config.workersFile, "utf8"));
|
|
787
|
+
const orchPid = data.pid ?? null;
|
|
788
|
+
const orchAlive = orchPid ? isPidAlive(orchPid) : false;
|
|
789
|
+
const workers = [];
|
|
790
|
+
for (const [name, info] of Object.entries(data.tools ?? {})) {
|
|
791
|
+
const w = info;
|
|
792
|
+
const wPid = w.pid ?? null;
|
|
793
|
+
const wAlive = wPid ? isPidAlive(wPid) : false;
|
|
794
|
+
workers.push({
|
|
795
|
+
name,
|
|
796
|
+
pid: wPid,
|
|
797
|
+
declaredStatus: w.status ?? "unknown",
|
|
798
|
+
alive: wAlive,
|
|
799
|
+
stale: w.status === "running" && !wAlive
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return {
|
|
803
|
+
orchestratorPid: orchPid,
|
|
804
|
+
orchestratorAlive: orchAlive,
|
|
805
|
+
startedAt: data.started_at ?? "",
|
|
806
|
+
workers
|
|
807
|
+
};
|
|
808
|
+
} catch {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function getOsProcesses(processName) {
|
|
813
|
+
try {
|
|
814
|
+
const raw = (0, import_node_child_process.execSync)(`ps aux`, { encoding: "utf8", timeout: 5e3 });
|
|
815
|
+
return raw.split("\n").filter((line) => line.includes(processName) && !line.includes("process-audit") && !line.includes("grep")).map((line) => {
|
|
816
|
+
const parts = line.trim().split(/\s+/);
|
|
817
|
+
return {
|
|
818
|
+
pid: parseInt(parts[1] ?? "0", 10),
|
|
819
|
+
cpu: parts[2] ?? "0",
|
|
820
|
+
mem: parts[3] ?? "0",
|
|
821
|
+
command: parts.slice(10).join(" ")
|
|
822
|
+
};
|
|
823
|
+
}).filter((p) => !isNaN(p.pid) && p.pid > 0);
|
|
824
|
+
} catch {
|
|
825
|
+
return [];
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
function discoverProcessConfig(dirs) {
|
|
829
|
+
let pidFile;
|
|
830
|
+
let workersFile;
|
|
831
|
+
let processName = "";
|
|
832
|
+
for (const dir of dirs) {
|
|
833
|
+
if (!(0, import_node_fs.existsSync)(dir)) continue;
|
|
834
|
+
let entries;
|
|
835
|
+
try {
|
|
836
|
+
entries = (0, import_node_fs.readdirSync)(dir);
|
|
837
|
+
} catch {
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
for (const f of entries) {
|
|
841
|
+
const fp = (0, import_node_path.join)(dir, f);
|
|
842
|
+
try {
|
|
843
|
+
if (!(0, import_node_fs.statSync)(fp).isFile()) continue;
|
|
844
|
+
} catch {
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (f.endsWith(".pid") && !pidFile) {
|
|
848
|
+
pidFile = fp;
|
|
849
|
+
if (!processName) {
|
|
850
|
+
processName = (0, import_node_path.basename)(f, ".pid");
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
|
|
854
|
+
workersFile = fp;
|
|
855
|
+
if (!processName && f !== "workers.json") {
|
|
856
|
+
processName = (0, import_node_path.basename)(f, "-workers.json");
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (!processName && !pidFile && !workersFile) return null;
|
|
862
|
+
if (!processName) processName = "agent";
|
|
863
|
+
return { processName, pidFile, workersFile };
|
|
864
|
+
}
|
|
865
|
+
function auditProcesses(config) {
|
|
866
|
+
const pidFile = auditPidFile(config);
|
|
867
|
+
const systemd = auditSystemd(config);
|
|
868
|
+
const workers = auditWorkers(config);
|
|
869
|
+
const osProcesses = getOsProcesses(config.processName);
|
|
870
|
+
const knownPids = /* @__PURE__ */ new Set();
|
|
871
|
+
if (pidFile?.pid && !pidFile.stale) knownPids.add(pidFile.pid);
|
|
872
|
+
if (workers) {
|
|
873
|
+
if (workers.orchestratorPid) knownPids.add(workers.orchestratorPid);
|
|
874
|
+
for (const w of workers.workers) {
|
|
875
|
+
if (w.pid) knownPids.add(w.pid);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
879
|
+
const orphans = osProcesses.filter((p) => !knownPids.has(p.pid));
|
|
880
|
+
const problems = [];
|
|
881
|
+
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
882
|
+
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
883
|
+
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
884
|
+
if (systemd && systemd.restarts > 10) problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
885
|
+
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
886
|
+
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
887
|
+
}
|
|
888
|
+
if (workers) {
|
|
889
|
+
for (const w of workers.workers) {
|
|
890
|
+
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (orphans.length > 0) problems.push(`${orphans.length} orphan process(es) not tracked by PID file or workers registry`);
|
|
894
|
+
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
895
|
+
}
|
|
896
|
+
function formatAuditReport(result) {
|
|
897
|
+
const lines = [];
|
|
898
|
+
lines.push("");
|
|
899
|
+
lines.push("\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\u2557");
|
|
900
|
+
lines.push("\u2551 \u{1F50D} P R O C E S S A U D I T \u2551");
|
|
901
|
+
lines.push("\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\u255D");
|
|
902
|
+
if (result.pidFile) {
|
|
903
|
+
const pf = result.pidFile;
|
|
904
|
+
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
905
|
+
lines.push(`
|
|
906
|
+
PID File: ${pf.path}`);
|
|
907
|
+
lines.push(` ${icon} ${pf.reason}`);
|
|
908
|
+
}
|
|
909
|
+
if (result.systemd) {
|
|
910
|
+
const sd = result.systemd;
|
|
911
|
+
const icon = sd.activeState === "active" ? "\u{1F7E2}" : sd.crashLooping ? "\u{1F7E1}" : sd.failed ? "\u{1F534}" : "\u26AA";
|
|
912
|
+
lines.push(`
|
|
913
|
+
Systemd: ${sd.unit}`);
|
|
914
|
+
lines.push(` ${icon} State: ${sd.activeState} (${sd.subState}) Result: ${sd.result}`);
|
|
915
|
+
lines.push(` Main PID: ${sd.mainPid || "none"} Restarts: ${sd.restarts}`);
|
|
916
|
+
}
|
|
917
|
+
if (result.workers) {
|
|
918
|
+
const w = result.workers;
|
|
919
|
+
lines.push(`
|
|
920
|
+
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`);
|
|
921
|
+
for (const worker of w.workers) {
|
|
922
|
+
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
923
|
+
lines.push(` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (result.osProcesses.length > 0) {
|
|
927
|
+
lines.push(`
|
|
928
|
+
OS Processes (${result.osProcesses.length} total)`);
|
|
929
|
+
for (const p of result.osProcesses) {
|
|
930
|
+
lines.push(` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} ${p.command.substring(0, 55)}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (result.orphans.length > 0) {
|
|
934
|
+
lines.push(`
|
|
935
|
+
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
936
|
+
for (const p of result.orphans) {
|
|
937
|
+
lines.push(` PID ${p.pid} \u2014 not tracked by PID file or workers registry`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
lines.push("");
|
|
941
|
+
if (result.problems.length === 0) {
|
|
942
|
+
lines.push(" \u2705 All checks passed \u2014 no process issues detected.");
|
|
943
|
+
} else {
|
|
944
|
+
lines.push(` \u26A0\uFE0F ${result.problems.length} issue(s):`);
|
|
945
|
+
for (const p of result.problems) {
|
|
946
|
+
lines.push(` \u2022 ${p}`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
lines.push("");
|
|
950
|
+
return lines.join("\n");
|
|
951
|
+
}
|
|
952
|
+
|
|
695
953
|
// src/live.ts
|
|
696
954
|
var C = {
|
|
697
955
|
reset: "\x1B[0m",
|
|
@@ -725,13 +983,13 @@ function parseArgs(argv) {
|
|
|
725
983
|
config.recursive = true;
|
|
726
984
|
i++;
|
|
727
985
|
} else if (!arg.startsWith("-")) {
|
|
728
|
-
config.dirs.push((0,
|
|
986
|
+
config.dirs.push((0, import_node_path2.resolve)(arg));
|
|
729
987
|
i++;
|
|
730
988
|
} else {
|
|
731
989
|
i++;
|
|
732
990
|
}
|
|
733
991
|
}
|
|
734
|
-
if (config.dirs.length === 0) config.dirs.push((0,
|
|
992
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
|
|
735
993
|
return config;
|
|
736
994
|
}
|
|
737
995
|
function printUsage() {
|
|
@@ -767,7 +1025,7 @@ function scanFiles(dirs, recursive) {
|
|
|
767
1025
|
const seen = /* @__PURE__ */ new Set();
|
|
768
1026
|
function scanDir(d, topLevel) {
|
|
769
1027
|
try {
|
|
770
|
-
const dirStat = (0,
|
|
1028
|
+
const dirStat = (0, import_node_fs2.statSync)(d);
|
|
771
1029
|
const dirMtime = dirStat.mtime.getTime();
|
|
772
1030
|
const cachedMtime = dirMtimeCache.get(d);
|
|
773
1031
|
if (cachedMtime === dirMtime) {
|
|
@@ -783,13 +1041,13 @@ function scanFiles(dirs, recursive) {
|
|
|
783
1041
|
}
|
|
784
1042
|
}
|
|
785
1043
|
const dirResults = [];
|
|
786
|
-
for (const f of (0,
|
|
1044
|
+
for (const f of (0, import_node_fs2.readdirSync)(d)) {
|
|
787
1045
|
if (f.startsWith(".")) continue;
|
|
788
|
-
const fp = (0,
|
|
1046
|
+
const fp = (0, import_node_path2.join)(d, f);
|
|
789
1047
|
if (seen.has(fp)) continue;
|
|
790
1048
|
let stat;
|
|
791
1049
|
try {
|
|
792
|
-
stat = (0,
|
|
1050
|
+
stat = (0, import_node_fs2.statSync)(fp);
|
|
793
1051
|
} catch {
|
|
794
1052
|
continue;
|
|
795
1053
|
}
|
|
@@ -821,13 +1079,13 @@ function scanFiles(dirs, recursive) {
|
|
|
821
1079
|
}
|
|
822
1080
|
function safeReadJson(fp) {
|
|
823
1081
|
try {
|
|
824
|
-
return JSON.parse((0,
|
|
1082
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
|
|
825
1083
|
} catch {
|
|
826
1084
|
return null;
|
|
827
1085
|
}
|
|
828
1086
|
}
|
|
829
1087
|
function nameFromFile(filename) {
|
|
830
|
-
return (0,
|
|
1088
|
+
return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
831
1089
|
}
|
|
832
1090
|
function normalizeStatus(val) {
|
|
833
1091
|
if (typeof val !== "string") return "unknown";
|
|
@@ -963,18 +1221,20 @@ function processJsonFile(file) {
|
|
|
963
1221
|
const w = info;
|
|
964
1222
|
const status2 = findStatus(w);
|
|
965
1223
|
const ts2 = findTimestamp(w) || findTimestamp(obj) || file.mtime;
|
|
966
|
-
const
|
|
1224
|
+
const rawPid = w.pid;
|
|
1225
|
+
const pid = typeof rawPid === "number" ? rawPid : Number(rawPid);
|
|
1226
|
+
const validPid = Number.isFinite(pid) && pid > 0;
|
|
967
1227
|
let validatedStatus = status2;
|
|
968
1228
|
let pidAlive = true;
|
|
969
|
-
if (
|
|
1229
|
+
if (validPid && (status2 === "running" || status2 === "ok")) {
|
|
970
1230
|
try {
|
|
971
|
-
(
|
|
1231
|
+
process.kill(pid, 0);
|
|
972
1232
|
} catch {
|
|
973
1233
|
pidAlive = false;
|
|
974
1234
|
validatedStatus = "error";
|
|
975
1235
|
}
|
|
976
1236
|
}
|
|
977
|
-
const pidLabel =
|
|
1237
|
+
const pidLabel = validPid ? pidAlive ? `pid: ${pid}` : `pid: ${pid} (dead)` : "";
|
|
978
1238
|
const detail2 = pidLabel || extractDetail(w);
|
|
979
1239
|
records.push({
|
|
980
1240
|
id: name,
|
|
@@ -1003,7 +1263,7 @@ function processJsonFile(file) {
|
|
|
1003
1263
|
}
|
|
1004
1264
|
function processJsonlFile(file) {
|
|
1005
1265
|
try {
|
|
1006
|
-
const content = (0,
|
|
1266
|
+
const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
|
|
1007
1267
|
if (!content) return [];
|
|
1008
1268
|
const lines = content.split("\n");
|
|
1009
1269
|
const lineCount = lines.length;
|
|
@@ -1155,6 +1415,9 @@ var prevFileCount = 0;
|
|
|
1155
1415
|
var newExecCount = 0;
|
|
1156
1416
|
var sessionStart = Date.now();
|
|
1157
1417
|
var firstRender = true;
|
|
1418
|
+
var cachedAuditConfig = null;
|
|
1419
|
+
var cachedAuditResult = null;
|
|
1420
|
+
var lastAuditTime = 0;
|
|
1158
1421
|
var fileCache = /* @__PURE__ */ new Map();
|
|
1159
1422
|
function getRecordsCached(f) {
|
|
1160
1423
|
const cached = fileCache.get(f.path);
|
|
@@ -1274,6 +1537,22 @@ function render(config) {
|
|
|
1274
1537
|
const level = Math.round(v / maxBucket * 8);
|
|
1275
1538
|
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
1276
1539
|
}).join("");
|
|
1540
|
+
let auditResult = null;
|
|
1541
|
+
if (now - lastAuditTime > 1e4) {
|
|
1542
|
+
if (!cachedAuditConfig) {
|
|
1543
|
+
cachedAuditConfig = discoverProcessConfig(config.dirs);
|
|
1544
|
+
}
|
|
1545
|
+
if (cachedAuditConfig) {
|
|
1546
|
+
try {
|
|
1547
|
+
auditResult = auditProcesses(cachedAuditConfig);
|
|
1548
|
+
cachedAuditResult = auditResult;
|
|
1549
|
+
lastAuditTime = now;
|
|
1550
|
+
} catch {
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
} else {
|
|
1554
|
+
auditResult = cachedAuditResult;
|
|
1555
|
+
}
|
|
1277
1556
|
const distributedTraces = [];
|
|
1278
1557
|
if (allTraces.length > 1) {
|
|
1279
1558
|
const traceGroups = groupByTraceId(allTraces);
|
|
@@ -1354,6 +1633,41 @@ function render(config) {
|
|
|
1354
1633
|
);
|
|
1355
1634
|
writeLine(L, "");
|
|
1356
1635
|
writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
1636
|
+
if (auditResult) {
|
|
1637
|
+
const ar = auditResult;
|
|
1638
|
+
const healthy = ar.problems.length === 0;
|
|
1639
|
+
const healthIcon = healthy ? `${C.green}\u25CF${C.reset}` : `${C.red}\u25CF${C.reset}`;
|
|
1640
|
+
const healthLabel = healthy ? `${C.green}healthy${C.reset}` : `${C.red}${ar.problems.length} issue(s)${C.reset}`;
|
|
1641
|
+
const workerParts = [];
|
|
1642
|
+
if (ar.workers) {
|
|
1643
|
+
for (const w of ar.workers.workers) {
|
|
1644
|
+
const wIcon = w.declaredStatus === "running" && w.alive ? `${C.green}\u25CF${C.reset}` : w.stale ? `${C.red}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
|
|
1645
|
+
workerParts.push(`${wIcon} ${w.name}`);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
let sysdLabel = "";
|
|
1649
|
+
if (ar.systemd) {
|
|
1650
|
+
const si = ar.systemd.activeState === "active" ? `${C.green}\u25CF${C.reset}` : ar.systemd.crashLooping ? `${C.yellow}\u25CF${C.reset}` : ar.systemd.failed ? `${C.red}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
|
|
1651
|
+
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1652
|
+
if (ar.systemd.restarts > 0) sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1653
|
+
}
|
|
1654
|
+
let pidLabel = "";
|
|
1655
|
+
if (ar.pidFile?.pid) {
|
|
1656
|
+
const pi = ar.pidFile.alive && ar.pidFile.matchesProcess ? `${C.green}\u25CF${C.reset}` : `${C.red}\u25CF${C.reset}`;
|
|
1657
|
+
pidLabel = ` ${C.bold}PID${C.reset} ${pi} ${ar.pidFile.pid}`;
|
|
1658
|
+
}
|
|
1659
|
+
writeLine(L, "");
|
|
1660
|
+
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1661
|
+
writeLine(L, ` ${healthIcon} ${healthLabel}${pidLabel}${sysdLabel} ${C.bold}Procs${C.reset} ${C.dim}${ar.osProcesses.length}${C.reset} ${ar.orphans.length > 0 ? `${C.red}Orphans ${ar.orphans.length}${C.reset}` : `${C.dim}Orphans 0${C.reset}`}`);
|
|
1662
|
+
if (workerParts.length > 0) {
|
|
1663
|
+
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1664
|
+
}
|
|
1665
|
+
if (!healthy) {
|
|
1666
|
+
for (const p of ar.problems.slice(0, 3)) {
|
|
1667
|
+
writeLine(L, ` ${C.red}\u2022${C.reset} ${C.dim}${p}${C.reset}`);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1357
1671
|
writeLine(L, "");
|
|
1358
1672
|
writeLine(
|
|
1359
1673
|
L,
|
|
@@ -1461,21 +1775,24 @@ function render(config) {
|
|
|
1461
1775
|
writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
1462
1776
|
flushLines(L);
|
|
1463
1777
|
}
|
|
1464
|
-
function getDistDepth(dt, spanId) {
|
|
1778
|
+
function getDistDepth(dt, spanId, visited) {
|
|
1465
1779
|
if (!spanId) return 0;
|
|
1780
|
+
const seen = visited ?? /* @__PURE__ */ new Set();
|
|
1781
|
+
if (seen.has(spanId)) return 0;
|
|
1782
|
+
seen.add(spanId);
|
|
1466
1783
|
const g = dt.graphs.get(spanId);
|
|
1467
1784
|
if (!g || !g.parentSpanId) return 0;
|
|
1468
|
-
return 1 + getDistDepth(dt, g.parentSpanId);
|
|
1785
|
+
return 1 + getDistDepth(dt, g.parentSpanId, seen);
|
|
1469
1786
|
}
|
|
1470
1787
|
function startLive(argv) {
|
|
1471
1788
|
const config = parseArgs(argv);
|
|
1472
|
-
const valid = config.dirs.filter((d) => (0,
|
|
1789
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
|
|
1473
1790
|
if (valid.length === 0) {
|
|
1474
1791
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1475
1792
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
1476
1793
|
process.exit(1);
|
|
1477
1794
|
}
|
|
1478
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
1795
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
|
|
1479
1796
|
if (invalid.length > 0) {
|
|
1480
1797
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1481
1798
|
}
|
|
@@ -1484,7 +1801,7 @@ function startLive(argv) {
|
|
|
1484
1801
|
let debounce = null;
|
|
1485
1802
|
for (const dir of config.dirs) {
|
|
1486
1803
|
try {
|
|
1487
|
-
(0,
|
|
1804
|
+
(0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
|
|
1488
1805
|
if (debounce) clearTimeout(debounce);
|
|
1489
1806
|
debounce = setTimeout(() => render(config), 500);
|
|
1490
1807
|
});
|
|
@@ -1500,20 +1817,20 @@ function startLive(argv) {
|
|
|
1500
1817
|
|
|
1501
1818
|
// src/runner.ts
|
|
1502
1819
|
var import_node_child_process2 = require("child_process");
|
|
1503
|
-
var
|
|
1504
|
-
var
|
|
1820
|
+
var import_node_fs3 = require("fs");
|
|
1821
|
+
var import_node_path3 = require("path");
|
|
1505
1822
|
function globToRegex(pattern) {
|
|
1506
1823
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1507
1824
|
return new RegExp(`^${escaped}$`);
|
|
1508
1825
|
}
|
|
1509
1826
|
function snapshotDir(dir, patterns) {
|
|
1510
1827
|
const result = /* @__PURE__ */ new Map();
|
|
1511
|
-
if (!(0,
|
|
1512
|
-
for (const entry of (0,
|
|
1828
|
+
if (!(0, import_node_fs3.existsSync)(dir)) return result;
|
|
1829
|
+
for (const entry of (0, import_node_fs3.readdirSync)(dir)) {
|
|
1513
1830
|
if (!patterns.some((re) => re.test(entry))) continue;
|
|
1514
|
-
const full = (0,
|
|
1831
|
+
const full = (0, import_node_path3.join)(dir, entry);
|
|
1515
1832
|
try {
|
|
1516
|
-
const stat = (0,
|
|
1833
|
+
const stat = (0, import_node_fs3.statSync)(full);
|
|
1517
1834
|
if (stat.isFile()) {
|
|
1518
1835
|
result.set(full, stat.mtimeMs);
|
|
1519
1836
|
}
|
|
@@ -1523,7 +1840,7 @@ function snapshotDir(dir, patterns) {
|
|
|
1523
1840
|
return result;
|
|
1524
1841
|
}
|
|
1525
1842
|
function agentIdFromFilename(filePath) {
|
|
1526
|
-
const base = (0,
|
|
1843
|
+
const base = (0, import_node_path3.basename)(filePath, ".json");
|
|
1527
1844
|
const cleaned = base.replace(/-state$/, "");
|
|
1528
1845
|
return `alfred-${cleaned}`;
|
|
1529
1846
|
}
|
|
@@ -1545,7 +1862,7 @@ async function runTraced(config) {
|
|
|
1545
1862
|
if (command.length === 0) {
|
|
1546
1863
|
throw new Error("runTraced: command must not be empty");
|
|
1547
1864
|
}
|
|
1548
|
-
const resolvedTracesDir = (0,
|
|
1865
|
+
const resolvedTracesDir = (0, import_node_path3.resolve)(tracesDir);
|
|
1549
1866
|
const patterns = watchPatterns.map(globToRegex);
|
|
1550
1867
|
const orchestrator = createGraphBuilder({ agentId, trigger });
|
|
1551
1868
|
const { traceId, spanId } = orchestrator.traceContext;
|
|
@@ -1628,15 +1945,19 @@ async function runTraced(config) {
|
|
|
1628
1945
|
childBuilder.endNode(childRootId);
|
|
1629
1946
|
allGraphs.push(childBuilder.build());
|
|
1630
1947
|
}
|
|
1631
|
-
if (!(0,
|
|
1632
|
-
(0,
|
|
1948
|
+
if (!(0, import_node_fs3.existsSync)(resolvedTracesDir)) {
|
|
1949
|
+
(0, import_node_fs3.mkdirSync)(resolvedTracesDir, { recursive: true });
|
|
1633
1950
|
}
|
|
1634
1951
|
const ts = fileTimestamp();
|
|
1635
1952
|
const tracePaths = [];
|
|
1636
1953
|
for (const graph of allGraphs) {
|
|
1637
1954
|
const filename = `${graph.agentId}-${ts}.json`;
|
|
1638
|
-
const outPath = (0,
|
|
1639
|
-
(0,
|
|
1955
|
+
const outPath = (0, import_node_path3.join)(resolvedTracesDir, filename);
|
|
1956
|
+
const resolvedOut = (0, import_node_path3.resolve)(outPath);
|
|
1957
|
+
if (!resolvedOut.startsWith(resolvedTracesDir + "/") && resolvedOut !== resolvedTracesDir) {
|
|
1958
|
+
throw new Error(`Path traversal detected: agentId "${graph.agentId}" escapes traces directory`);
|
|
1959
|
+
}
|
|
1960
|
+
(0, import_node_fs3.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
|
|
1640
1961
|
tracePaths.push(outPath);
|
|
1641
1962
|
}
|
|
1642
1963
|
if (tracePaths.length > 0) {
|
|
@@ -1684,6 +2005,11 @@ function createTraceStore(dir) {
|
|
|
1684
2005
|
await ensureDir();
|
|
1685
2006
|
const json = graphToJson(graph);
|
|
1686
2007
|
const filePath = (0, import_path.join)(dir, `${graph.id}.json`);
|
|
2008
|
+
const resolvedBase = (0, import_path.resolve)(dir);
|
|
2009
|
+
const resolvedPath = (0, import_path.resolve)(filePath);
|
|
2010
|
+
if (!resolvedPath.startsWith(resolvedBase + "/") && resolvedPath !== resolvedBase) {
|
|
2011
|
+
throw new Error(`Path traversal detected: "${graph.id}" escapes base directory`);
|
|
2012
|
+
}
|
|
1687
2013
|
await (0, import_promises.writeFile)(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
1688
2014
|
return filePath;
|
|
1689
2015
|
},
|
|
@@ -1882,9 +2208,9 @@ function toTimeline(graph) {
|
|
|
1882
2208
|
}
|
|
1883
2209
|
|
|
1884
2210
|
// src/watch.ts
|
|
1885
|
-
var
|
|
2211
|
+
var import_node_fs5 = require("fs");
|
|
1886
2212
|
var import_node_os = require("os");
|
|
1887
|
-
var
|
|
2213
|
+
var import_node_path4 = require("path");
|
|
1888
2214
|
|
|
1889
2215
|
// src/watch-alerts.ts
|
|
1890
2216
|
var import_node_child_process3 = require("child_process");
|
|
@@ -1942,7 +2268,7 @@ function sendTelegram(payload, botToken, chatId) {
|
|
|
1942
2268
|
text: formatTelegram(payload),
|
|
1943
2269
|
parse_mode: "Markdown"
|
|
1944
2270
|
});
|
|
1945
|
-
return new Promise((
|
|
2271
|
+
return new Promise((resolve5, reject) => {
|
|
1946
2272
|
const req = (0, import_node_https.request)(
|
|
1947
2273
|
`https://api.telegram.org/bot${botToken}/sendMessage`,
|
|
1948
2274
|
{
|
|
@@ -1951,7 +2277,7 @@ function sendTelegram(payload, botToken, chatId) {
|
|
|
1951
2277
|
},
|
|
1952
2278
|
(res) => {
|
|
1953
2279
|
res.resume();
|
|
1954
|
-
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300)
|
|
2280
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve5();
|
|
1955
2281
|
else reject(new Error(`Telegram API returned ${res.statusCode}`));
|
|
1956
2282
|
}
|
|
1957
2283
|
);
|
|
@@ -1964,7 +2290,7 @@ function sendWebhook(payload, url) {
|
|
|
1964
2290
|
const body = JSON.stringify(payload);
|
|
1965
2291
|
const isHttps = url.startsWith("https");
|
|
1966
2292
|
const doRequest = isHttps ? import_node_https.request : import_node_http.request;
|
|
1967
|
-
return new Promise((
|
|
2293
|
+
return new Promise((resolve5, reject) => {
|
|
1968
2294
|
const req = doRequest(
|
|
1969
2295
|
url,
|
|
1970
2296
|
{
|
|
@@ -1973,7 +2299,7 @@ function sendWebhook(payload, url) {
|
|
|
1973
2299
|
},
|
|
1974
2300
|
(res) => {
|
|
1975
2301
|
res.resume();
|
|
1976
|
-
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300)
|
|
2302
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve5();
|
|
1977
2303
|
else reject(new Error(`Webhook returned ${res.statusCode}`));
|
|
1978
2304
|
}
|
|
1979
2305
|
);
|
|
@@ -1986,7 +2312,7 @@ function sendWebhook(payload, url) {
|
|
|
1986
2312
|
});
|
|
1987
2313
|
}
|
|
1988
2314
|
function sendCommand(payload, cmd) {
|
|
1989
|
-
return new Promise((
|
|
2315
|
+
return new Promise((resolve5, reject) => {
|
|
1990
2316
|
const env = {
|
|
1991
2317
|
...process.env,
|
|
1992
2318
|
AGENTFLOW_ALERT_AGENT: payload.agentId,
|
|
@@ -1999,13 +2325,13 @@ function sendCommand(payload, cmd) {
|
|
|
1999
2325
|
};
|
|
2000
2326
|
(0, import_node_child_process3.exec)(cmd, { env, timeout: 3e4 }, (err) => {
|
|
2001
2327
|
if (err) reject(err);
|
|
2002
|
-
else
|
|
2328
|
+
else resolve5();
|
|
2003
2329
|
});
|
|
2004
2330
|
});
|
|
2005
2331
|
}
|
|
2006
2332
|
|
|
2007
2333
|
// src/watch-state.ts
|
|
2008
|
-
var
|
|
2334
|
+
var import_node_fs4 = require("fs");
|
|
2009
2335
|
function parseDuration(input) {
|
|
2010
2336
|
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i);
|
|
2011
2337
|
if (!match) {
|
|
@@ -2030,9 +2356,9 @@ function emptyState() {
|
|
|
2030
2356
|
return { version: 1, agents: {}, lastPollTime: 0 };
|
|
2031
2357
|
}
|
|
2032
2358
|
function loadWatchState(filePath) {
|
|
2033
|
-
if (!(0,
|
|
2359
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) return emptyState();
|
|
2034
2360
|
try {
|
|
2035
|
-
const raw = JSON.parse((0,
|
|
2361
|
+
const raw = JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf8"));
|
|
2036
2362
|
if (raw.version !== 1 || typeof raw.agents !== "object") return emptyState();
|
|
2037
2363
|
return raw;
|
|
2038
2364
|
} catch {
|
|
@@ -2042,11 +2368,11 @@ function loadWatchState(filePath) {
|
|
|
2042
2368
|
function saveWatchState(filePath, state) {
|
|
2043
2369
|
const tmp = filePath + ".tmp";
|
|
2044
2370
|
try {
|
|
2045
|
-
(0,
|
|
2046
|
-
(0,
|
|
2371
|
+
(0, import_node_fs4.writeFileSync)(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
2372
|
+
(0, import_node_fs4.renameSync)(tmp, filePath);
|
|
2047
2373
|
} catch {
|
|
2048
2374
|
try {
|
|
2049
|
-
(0,
|
|
2375
|
+
(0, import_node_fs4.writeFileSync)(filePath, JSON.stringify(state, null, 2), "utf8");
|
|
2050
2376
|
} catch {
|
|
2051
2377
|
}
|
|
2052
2378
|
}
|
|
@@ -2274,20 +2600,20 @@ function parseWatchArgs(argv) {
|
|
|
2274
2600
|
recursive = true;
|
|
2275
2601
|
i++;
|
|
2276
2602
|
} else if (!arg.startsWith("-")) {
|
|
2277
|
-
dirs.push((0,
|
|
2603
|
+
dirs.push((0, import_node_path4.resolve)(arg));
|
|
2278
2604
|
i++;
|
|
2279
2605
|
} else {
|
|
2280
2606
|
i++;
|
|
2281
2607
|
}
|
|
2282
2608
|
}
|
|
2283
|
-
if (dirs.length === 0) dirs.push((0,
|
|
2609
|
+
if (dirs.length === 0) dirs.push((0, import_node_path4.resolve)("."));
|
|
2284
2610
|
if (alertConditions.length === 0) {
|
|
2285
2611
|
alertConditions.push({ type: "error" });
|
|
2286
2612
|
alertConditions.push({ type: "recovery" });
|
|
2287
2613
|
}
|
|
2288
2614
|
notifyChannels.unshift({ type: "stdout" });
|
|
2289
2615
|
if (!stateFilePath) {
|
|
2290
|
-
stateFilePath = (0,
|
|
2616
|
+
stateFilePath = (0, import_node_path4.join)(dirs[0], ".agentflow-watch-state.json");
|
|
2291
2617
|
}
|
|
2292
2618
|
return {
|
|
2293
2619
|
dirs,
|
|
@@ -2295,7 +2621,7 @@ function parseWatchArgs(argv) {
|
|
|
2295
2621
|
pollIntervalMs,
|
|
2296
2622
|
alertConditions,
|
|
2297
2623
|
notifyChannels,
|
|
2298
|
-
stateFilePath: (0,
|
|
2624
|
+
stateFilePath: (0, import_node_path4.resolve)(stateFilePath),
|
|
2299
2625
|
cooldownMs
|
|
2300
2626
|
};
|
|
2301
2627
|
}
|
|
@@ -2349,12 +2675,12 @@ Examples:
|
|
|
2349
2675
|
}
|
|
2350
2676
|
function startWatch(argv) {
|
|
2351
2677
|
const config = parseWatchArgs(argv);
|
|
2352
|
-
const valid = config.dirs.filter((d) => (0,
|
|
2678
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs5.existsSync)(d));
|
|
2353
2679
|
if (valid.length === 0) {
|
|
2354
2680
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
2355
2681
|
process.exit(1);
|
|
2356
2682
|
}
|
|
2357
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
2683
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs5.existsSync)(d));
|
|
2358
2684
|
if (invalid.length > 0) {
|
|
2359
2685
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
2360
2686
|
}
|
|
@@ -2433,10 +2759,13 @@ agentflow watch started`);
|
|
|
2433
2759
|
}
|
|
2434
2760
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2435
2761
|
0 && (module.exports = {
|
|
2762
|
+
auditProcesses,
|
|
2436
2763
|
checkGuards,
|
|
2437
2764
|
createGraphBuilder,
|
|
2438
2765
|
createTraceStore,
|
|
2766
|
+
discoverProcessConfig,
|
|
2439
2767
|
findWaitingOn,
|
|
2768
|
+
formatAuditReport,
|
|
2440
2769
|
getChildren,
|
|
2441
2770
|
getCriticalPath,
|
|
2442
2771
|
getDepth,
|