agentflow-core 0.6.0 → 0.6.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-VF4FSBXR.js → chunk-BXZC5ZMJ.js} +357 -268
- package/dist/cli.cjs +361 -270
- package/dist/cli.js +1 -1
- package/dist/index.cjs +359 -270
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -639,8 +639,8 @@ function withGuards(builder, config) {
|
|
|
639
639
|
}
|
|
640
640
|
|
|
641
641
|
// src/live.ts
|
|
642
|
-
var
|
|
643
|
-
var
|
|
642
|
+
var import_node_fs2 = require("fs");
|
|
643
|
+
var import_node_path2 = require("path");
|
|
644
644
|
|
|
645
645
|
// src/loader.ts
|
|
646
646
|
function toNodesMap(raw) {
|
|
@@ -694,6 +694,286 @@ function graphToJson(graph) {
|
|
|
694
694
|
};
|
|
695
695
|
}
|
|
696
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 readCmdline(pid) {
|
|
813
|
+
try {
|
|
814
|
+
return (0, import_node_fs.readFileSync)(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").trim();
|
|
815
|
+
} catch {
|
|
816
|
+
return "";
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function getOsProcesses(processName) {
|
|
820
|
+
try {
|
|
821
|
+
const raw = (0, import_node_child_process.execSync)(
|
|
822
|
+
`ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`,
|
|
823
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
824
|
+
);
|
|
825
|
+
const results = [];
|
|
826
|
+
for (const line of raw.split("\n")) {
|
|
827
|
+
if (!line.includes(processName)) continue;
|
|
828
|
+
if (line.includes("process-audit") || line.includes(" grep ")) continue;
|
|
829
|
+
const trimmed = line.trim();
|
|
830
|
+
const parts = trimmed.split(/\s+/);
|
|
831
|
+
const pid = parseInt(parts[0] ?? "0", 10);
|
|
832
|
+
if (isNaN(pid) || pid <= 0) continue;
|
|
833
|
+
const cpu = parts[1] ?? "0";
|
|
834
|
+
const mem = parts[2] ?? "0";
|
|
835
|
+
const elapsed = parts[3] ?? "";
|
|
836
|
+
const started = parts.slice(4, 9).join(" ");
|
|
837
|
+
const command = parts.slice(9).join(" ");
|
|
838
|
+
const cmdline = readCmdline(pid);
|
|
839
|
+
results.push({ pid, cpu, mem, elapsed, started, command, cmdline });
|
|
840
|
+
}
|
|
841
|
+
return results;
|
|
842
|
+
} catch {
|
|
843
|
+
return [];
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function discoverProcessConfig(dirs) {
|
|
847
|
+
let pidFile;
|
|
848
|
+
let workersFile;
|
|
849
|
+
let processName = "";
|
|
850
|
+
for (const dir of dirs) {
|
|
851
|
+
if (!(0, import_node_fs.existsSync)(dir)) continue;
|
|
852
|
+
let entries;
|
|
853
|
+
try {
|
|
854
|
+
entries = (0, import_node_fs.readdirSync)(dir);
|
|
855
|
+
} catch {
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
for (const f of entries) {
|
|
859
|
+
const fp = (0, import_node_path.join)(dir, f);
|
|
860
|
+
try {
|
|
861
|
+
if (!(0, import_node_fs.statSync)(fp).isFile()) continue;
|
|
862
|
+
} catch {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (f.endsWith(".pid") && !pidFile) {
|
|
866
|
+
pidFile = fp;
|
|
867
|
+
if (!processName) {
|
|
868
|
+
processName = (0, import_node_path.basename)(f, ".pid");
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
|
|
872
|
+
workersFile = fp;
|
|
873
|
+
if (!processName && f !== "workers.json") {
|
|
874
|
+
processName = (0, import_node_path.basename)(f, "-workers.json");
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (!processName && !pidFile && !workersFile) return null;
|
|
880
|
+
if (!processName) processName = "agent";
|
|
881
|
+
return { processName, pidFile, workersFile };
|
|
882
|
+
}
|
|
883
|
+
function auditProcesses(config) {
|
|
884
|
+
const pidFile = auditPidFile(config);
|
|
885
|
+
const systemd = auditSystemd(config);
|
|
886
|
+
const workers = auditWorkers(config);
|
|
887
|
+
const osProcesses = getOsProcesses(config.processName);
|
|
888
|
+
const knownPids = /* @__PURE__ */ new Set();
|
|
889
|
+
if (pidFile?.pid && !pidFile.stale) knownPids.add(pidFile.pid);
|
|
890
|
+
if (workers) {
|
|
891
|
+
if (workers.orchestratorPid) knownPids.add(workers.orchestratorPid);
|
|
892
|
+
for (const w of workers.workers) {
|
|
893
|
+
if (w.pid) knownPids.add(w.pid);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
897
|
+
const selfPid = process.pid;
|
|
898
|
+
const selfPpid = process.ppid;
|
|
899
|
+
const orphans = osProcesses.filter(
|
|
900
|
+
(p) => !knownPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
901
|
+
);
|
|
902
|
+
const problems = [];
|
|
903
|
+
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
904
|
+
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
905
|
+
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
906
|
+
if (systemd && systemd.restarts > 10) problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
907
|
+
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
908
|
+
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
909
|
+
}
|
|
910
|
+
if (workers) {
|
|
911
|
+
for (const w of workers.workers) {
|
|
912
|
+
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if (orphans.length > 0) problems.push(`${orphans.length} orphan process(es) not tracked by PID file or workers registry`);
|
|
916
|
+
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
917
|
+
}
|
|
918
|
+
function formatAuditReport(result) {
|
|
919
|
+
const lines = [];
|
|
920
|
+
lines.push("");
|
|
921
|
+
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");
|
|
922
|
+
lines.push("\u2551 \u{1F50D} P R O C E S S A U D I T \u2551");
|
|
923
|
+
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");
|
|
924
|
+
if (result.pidFile) {
|
|
925
|
+
const pf = result.pidFile;
|
|
926
|
+
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
927
|
+
lines.push(`
|
|
928
|
+
PID File: ${pf.path}`);
|
|
929
|
+
lines.push(` ${icon} ${pf.reason}`);
|
|
930
|
+
}
|
|
931
|
+
if (result.systemd) {
|
|
932
|
+
const sd = result.systemd;
|
|
933
|
+
const icon = sd.activeState === "active" ? "\u{1F7E2}" : sd.crashLooping ? "\u{1F7E1}" : sd.failed ? "\u{1F534}" : "\u26AA";
|
|
934
|
+
lines.push(`
|
|
935
|
+
Systemd: ${sd.unit}`);
|
|
936
|
+
lines.push(` ${icon} State: ${sd.activeState} (${sd.subState}) Result: ${sd.result}`);
|
|
937
|
+
lines.push(` Main PID: ${sd.mainPid || "none"} Restarts: ${sd.restarts}`);
|
|
938
|
+
}
|
|
939
|
+
if (result.workers) {
|
|
940
|
+
const w = result.workers;
|
|
941
|
+
lines.push(`
|
|
942
|
+
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`);
|
|
943
|
+
for (const worker of w.workers) {
|
|
944
|
+
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
945
|
+
lines.push(` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (result.osProcesses.length > 0) {
|
|
949
|
+
lines.push(`
|
|
950
|
+
OS Processes (${result.osProcesses.length} total)`);
|
|
951
|
+
for (const p of result.osProcesses) {
|
|
952
|
+
lines.push(` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed.padEnd(10)} ${p.command.substring(0, 50)}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (result.orphans.length > 0) {
|
|
956
|
+
lines.push(`
|
|
957
|
+
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
958
|
+
for (const p of result.orphans) {
|
|
959
|
+
lines.push(` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed}`);
|
|
960
|
+
lines.push(` Started: ${p.started}`);
|
|
961
|
+
lines.push(` Command: ${p.cmdline || p.command}`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
lines.push("");
|
|
965
|
+
if (result.problems.length === 0) {
|
|
966
|
+
lines.push(" \u2705 All checks passed \u2014 no process issues detected.");
|
|
967
|
+
} else {
|
|
968
|
+
lines.push(` \u26A0\uFE0F ${result.problems.length} issue(s):`);
|
|
969
|
+
for (const p of result.problems) {
|
|
970
|
+
lines.push(` \u2022 ${p}`);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
lines.push("");
|
|
974
|
+
return lines.join("\n");
|
|
975
|
+
}
|
|
976
|
+
|
|
697
977
|
// src/live.ts
|
|
698
978
|
var C = {
|
|
699
979
|
reset: "\x1B[0m",
|
|
@@ -727,13 +1007,13 @@ function parseArgs(argv) {
|
|
|
727
1007
|
config.recursive = true;
|
|
728
1008
|
i++;
|
|
729
1009
|
} else if (!arg.startsWith("-")) {
|
|
730
|
-
config.dirs.push((0,
|
|
1010
|
+
config.dirs.push((0, import_node_path2.resolve)(arg));
|
|
731
1011
|
i++;
|
|
732
1012
|
} else {
|
|
733
1013
|
i++;
|
|
734
1014
|
}
|
|
735
1015
|
}
|
|
736
|
-
if (config.dirs.length === 0) config.dirs.push((0,
|
|
1016
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
|
|
737
1017
|
return config;
|
|
738
1018
|
}
|
|
739
1019
|
function printUsage() {
|
|
@@ -769,7 +1049,7 @@ function scanFiles(dirs, recursive) {
|
|
|
769
1049
|
const seen = /* @__PURE__ */ new Set();
|
|
770
1050
|
function scanDir(d, topLevel) {
|
|
771
1051
|
try {
|
|
772
|
-
const dirStat = (0,
|
|
1052
|
+
const dirStat = (0, import_node_fs2.statSync)(d);
|
|
773
1053
|
const dirMtime = dirStat.mtime.getTime();
|
|
774
1054
|
const cachedMtime = dirMtimeCache.get(d);
|
|
775
1055
|
if (cachedMtime === dirMtime) {
|
|
@@ -785,13 +1065,13 @@ function scanFiles(dirs, recursive) {
|
|
|
785
1065
|
}
|
|
786
1066
|
}
|
|
787
1067
|
const dirResults = [];
|
|
788
|
-
for (const f of (0,
|
|
1068
|
+
for (const f of (0, import_node_fs2.readdirSync)(d)) {
|
|
789
1069
|
if (f.startsWith(".")) continue;
|
|
790
|
-
const fp = (0,
|
|
1070
|
+
const fp = (0, import_node_path2.join)(d, f);
|
|
791
1071
|
if (seen.has(fp)) continue;
|
|
792
1072
|
let stat;
|
|
793
1073
|
try {
|
|
794
|
-
stat = (0,
|
|
1074
|
+
stat = (0, import_node_fs2.statSync)(fp);
|
|
795
1075
|
} catch {
|
|
796
1076
|
continue;
|
|
797
1077
|
}
|
|
@@ -823,13 +1103,13 @@ function scanFiles(dirs, recursive) {
|
|
|
823
1103
|
}
|
|
824
1104
|
function safeReadJson(fp) {
|
|
825
1105
|
try {
|
|
826
|
-
return JSON.parse((0,
|
|
1106
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
|
|
827
1107
|
} catch {
|
|
828
1108
|
return null;
|
|
829
1109
|
}
|
|
830
1110
|
}
|
|
831
1111
|
function nameFromFile(filename) {
|
|
832
|
-
return (0,
|
|
1112
|
+
return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
833
1113
|
}
|
|
834
1114
|
function normalizeStatus(val) {
|
|
835
1115
|
if (typeof val !== "string") return "unknown";
|
|
@@ -1007,7 +1287,7 @@ function processJsonFile(file) {
|
|
|
1007
1287
|
}
|
|
1008
1288
|
function processJsonlFile(file) {
|
|
1009
1289
|
try {
|
|
1010
|
-
const content = (0,
|
|
1290
|
+
const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
|
|
1011
1291
|
if (!content) return [];
|
|
1012
1292
|
const lines = content.split("\n");
|
|
1013
1293
|
const lineCount = lines.length;
|
|
@@ -1159,6 +1439,9 @@ var prevFileCount = 0;
|
|
|
1159
1439
|
var newExecCount = 0;
|
|
1160
1440
|
var sessionStart = Date.now();
|
|
1161
1441
|
var firstRender = true;
|
|
1442
|
+
var cachedAuditConfig = null;
|
|
1443
|
+
var cachedAuditResult = null;
|
|
1444
|
+
var lastAuditTime = 0;
|
|
1162
1445
|
var fileCache = /* @__PURE__ */ new Map();
|
|
1163
1446
|
function getRecordsCached(f) {
|
|
1164
1447
|
const cached = fileCache.get(f.path);
|
|
@@ -1278,6 +1561,24 @@ function render(config) {
|
|
|
1278
1561
|
const level = Math.round(v / maxBucket * 8);
|
|
1279
1562
|
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
1280
1563
|
}).join("");
|
|
1564
|
+
let auditResult = null;
|
|
1565
|
+
if (now - lastAuditTime > 1e4) {
|
|
1566
|
+
if (!cachedAuditConfig) {
|
|
1567
|
+
cachedAuditConfig = discoverProcessConfig(config.dirs);
|
|
1568
|
+
}
|
|
1569
|
+
if (cachedAuditConfig) {
|
|
1570
|
+
try {
|
|
1571
|
+
auditResult = auditProcesses(cachedAuditConfig);
|
|
1572
|
+
cachedAuditResult = auditResult;
|
|
1573
|
+
lastAuditTime = now;
|
|
1574
|
+
} catch (err) {
|
|
1575
|
+
process.stderr.write(`[agentflow] process audit error: ${err instanceof Error ? err.message : err}
|
|
1576
|
+
`);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
} else {
|
|
1580
|
+
auditResult = cachedAuditResult;
|
|
1581
|
+
}
|
|
1281
1582
|
const distributedTraces = [];
|
|
1282
1583
|
if (allTraces.length > 1) {
|
|
1283
1584
|
const traceGroups = groupByTraceId(allTraces);
|
|
@@ -1358,6 +1659,50 @@ function render(config) {
|
|
|
1358
1659
|
);
|
|
1359
1660
|
writeLine(L, "");
|
|
1360
1661
|
writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
1662
|
+
if (auditResult) {
|
|
1663
|
+
const ar = auditResult;
|
|
1664
|
+
const healthy = ar.problems.length === 0;
|
|
1665
|
+
const healthIcon = healthy ? `${C.green}\u25CF${C.reset}` : `${C.red}\u25CF${C.reset}`;
|
|
1666
|
+
const healthLabel = healthy ? `${C.green}healthy${C.reset}` : `${C.red}${ar.problems.length} issue(s)${C.reset}`;
|
|
1667
|
+
const workerParts = [];
|
|
1668
|
+
if (ar.workers) {
|
|
1669
|
+
for (const w of ar.workers.workers) {
|
|
1670
|
+
const wIcon = w.declaredStatus === "running" && w.alive ? `${C.green}\u25CF${C.reset}` : w.stale ? `${C.red}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
|
|
1671
|
+
workerParts.push(`${wIcon} ${w.name}`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
let sysdLabel = "";
|
|
1675
|
+
if (ar.systemd) {
|
|
1676
|
+
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}`;
|
|
1677
|
+
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1678
|
+
if (ar.systemd.restarts > 0) sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1679
|
+
}
|
|
1680
|
+
let pidLabel = "";
|
|
1681
|
+
if (ar.pidFile?.pid) {
|
|
1682
|
+
const pi = ar.pidFile.alive && ar.pidFile.matchesProcess ? `${C.green}\u25CF${C.reset}` : `${C.red}\u25CF${C.reset}`;
|
|
1683
|
+
pidLabel = ` ${C.bold}PID${C.reset} ${pi} ${ar.pidFile.pid}`;
|
|
1684
|
+
}
|
|
1685
|
+
writeLine(L, "");
|
|
1686
|
+
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1687
|
+
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}`}`);
|
|
1688
|
+
if (workerParts.length > 0) {
|
|
1689
|
+
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1690
|
+
}
|
|
1691
|
+
if (!healthy) {
|
|
1692
|
+
for (const p of ar.problems.slice(0, 3)) {
|
|
1693
|
+
writeLine(L, ` ${C.red}\u2022${C.reset} ${C.dim}${p}${C.reset}`);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
if (ar.orphans.length > 0) {
|
|
1697
|
+
for (const o of ar.orphans.slice(0, 5)) {
|
|
1698
|
+
const cmd = (o.cmdline || o.command).substring(0, detailWidth);
|
|
1699
|
+
writeLine(L, ` ${C.red}?${C.reset} ${C.dim}pid=${o.pid} cpu=${o.cpu} mem=${o.mem} up=${o.elapsed}${C.reset} ${C.dim}${cmd}${C.reset}`);
|
|
1700
|
+
}
|
|
1701
|
+
if (ar.orphans.length > 5) {
|
|
1702
|
+
writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1361
1706
|
writeLine(L, "");
|
|
1362
1707
|
writeLine(
|
|
1363
1708
|
L,
|
|
@@ -1476,13 +1821,13 @@ function getDistDepth(dt, spanId, visited) {
|
|
|
1476
1821
|
}
|
|
1477
1822
|
function startLive(argv) {
|
|
1478
1823
|
const config = parseArgs(argv);
|
|
1479
|
-
const valid = config.dirs.filter((d) => (0,
|
|
1824
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
|
|
1480
1825
|
if (valid.length === 0) {
|
|
1481
1826
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1482
1827
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
1483
1828
|
process.exit(1);
|
|
1484
1829
|
}
|
|
1485
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
1830
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
|
|
1486
1831
|
if (invalid.length > 0) {
|
|
1487
1832
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1488
1833
|
}
|
|
@@ -1491,7 +1836,7 @@ function startLive(argv) {
|
|
|
1491
1836
|
let debounce = null;
|
|
1492
1837
|
for (const dir of config.dirs) {
|
|
1493
1838
|
try {
|
|
1494
|
-
(0,
|
|
1839
|
+
(0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
|
|
1495
1840
|
if (debounce) clearTimeout(debounce);
|
|
1496
1841
|
debounce = setTimeout(() => render(config), 500);
|
|
1497
1842
|
});
|
|
@@ -1505,262 +1850,6 @@ function startLive(argv) {
|
|
|
1505
1850
|
});
|
|
1506
1851
|
}
|
|
1507
1852
|
|
|
1508
|
-
// src/process-audit.ts
|
|
1509
|
-
var import_node_child_process = require("child_process");
|
|
1510
|
-
var import_node_fs2 = require("fs");
|
|
1511
|
-
var import_node_path2 = require("path");
|
|
1512
|
-
function isPidAlive(pid) {
|
|
1513
|
-
try {
|
|
1514
|
-
process.kill(pid, 0);
|
|
1515
|
-
return true;
|
|
1516
|
-
} catch {
|
|
1517
|
-
return false;
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
function pidMatchesName(pid, name) {
|
|
1521
|
-
try {
|
|
1522
|
-
const cmdline = (0, import_node_fs2.readFileSync)(`/proc/${pid}/cmdline`, "utf8");
|
|
1523
|
-
return cmdline.includes(name);
|
|
1524
|
-
} catch {
|
|
1525
|
-
return false;
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
function readPidFile(path) {
|
|
1529
|
-
try {
|
|
1530
|
-
const pid = parseInt((0, import_node_fs2.readFileSync)(path, "utf8").trim(), 10);
|
|
1531
|
-
return isNaN(pid) ? null : pid;
|
|
1532
|
-
} catch {
|
|
1533
|
-
return null;
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
function auditPidFile(config) {
|
|
1537
|
-
if (!config.pidFile) return null;
|
|
1538
|
-
const pid = readPidFile(config.pidFile);
|
|
1539
|
-
if (pid === null) {
|
|
1540
|
-
return {
|
|
1541
|
-
path: config.pidFile,
|
|
1542
|
-
pid: null,
|
|
1543
|
-
alive: false,
|
|
1544
|
-
matchesProcess: false,
|
|
1545
|
-
stale: !(0, import_node_fs2.existsSync)(config.pidFile),
|
|
1546
|
-
reason: (0, import_node_fs2.existsSync)(config.pidFile) ? "PID file exists but content is invalid" : "No PID file found"
|
|
1547
|
-
};
|
|
1548
|
-
}
|
|
1549
|
-
const alive = isPidAlive(pid);
|
|
1550
|
-
const matchesProcess = alive ? pidMatchesName(pid, config.processName) : false;
|
|
1551
|
-
const stale = !alive || alive && !matchesProcess;
|
|
1552
|
-
let reason;
|
|
1553
|
-
if (alive && matchesProcess) {
|
|
1554
|
-
reason = `PID ${pid} alive and matches ${config.processName}`;
|
|
1555
|
-
} else if (alive && !matchesProcess) {
|
|
1556
|
-
reason = `PID ${pid} alive but is NOT ${config.processName} (PID reused by another process)`;
|
|
1557
|
-
} else {
|
|
1558
|
-
reason = `PID ${pid} no longer exists`;
|
|
1559
|
-
}
|
|
1560
|
-
return { path: config.pidFile, pid, alive, matchesProcess, stale, reason };
|
|
1561
|
-
}
|
|
1562
|
-
function auditSystemd(config) {
|
|
1563
|
-
if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
|
|
1564
|
-
const unit = config.systemdUnit;
|
|
1565
|
-
try {
|
|
1566
|
-
const raw = (0, import_node_child_process.execSync)(
|
|
1567
|
-
`systemctl --user show ${unit} --property=ActiveState,SubState,MainPID,NRestarts,Result --no-pager 2>/dev/null`,
|
|
1568
|
-
{ encoding: "utf8", timeout: 5e3 }
|
|
1569
|
-
);
|
|
1570
|
-
const props = {};
|
|
1571
|
-
for (const line of raw.trim().split("\n")) {
|
|
1572
|
-
const [k, ...v] = line.split("=");
|
|
1573
|
-
if (k) props[k.trim()] = v.join("=").trim();
|
|
1574
|
-
}
|
|
1575
|
-
const activeState = props["ActiveState"] ?? "unknown";
|
|
1576
|
-
const subState = props["SubState"] ?? "unknown";
|
|
1577
|
-
const mainPid = parseInt(props["MainPID"] ?? "0", 10);
|
|
1578
|
-
const restarts = parseInt(props["NRestarts"] ?? "0", 10);
|
|
1579
|
-
const result = props["Result"] ?? "unknown";
|
|
1580
|
-
return {
|
|
1581
|
-
unit,
|
|
1582
|
-
activeState,
|
|
1583
|
-
subState,
|
|
1584
|
-
mainPid,
|
|
1585
|
-
restarts,
|
|
1586
|
-
result,
|
|
1587
|
-
crashLooping: activeState === "activating" && subState === "auto-restart",
|
|
1588
|
-
failed: activeState === "failed"
|
|
1589
|
-
};
|
|
1590
|
-
} catch {
|
|
1591
|
-
return null;
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
function auditWorkers(config) {
|
|
1595
|
-
if (!config.workersFile || !(0, import_node_fs2.existsSync)(config.workersFile)) return null;
|
|
1596
|
-
try {
|
|
1597
|
-
const data = JSON.parse((0, import_node_fs2.readFileSync)(config.workersFile, "utf8"));
|
|
1598
|
-
const orchPid = data.pid ?? null;
|
|
1599
|
-
const orchAlive = orchPid ? isPidAlive(orchPid) : false;
|
|
1600
|
-
const workers = [];
|
|
1601
|
-
for (const [name, info] of Object.entries(data.tools ?? {})) {
|
|
1602
|
-
const w = info;
|
|
1603
|
-
const wPid = w.pid ?? null;
|
|
1604
|
-
const wAlive = wPid ? isPidAlive(wPid) : false;
|
|
1605
|
-
workers.push({
|
|
1606
|
-
name,
|
|
1607
|
-
pid: wPid,
|
|
1608
|
-
declaredStatus: w.status ?? "unknown",
|
|
1609
|
-
alive: wAlive,
|
|
1610
|
-
stale: w.status === "running" && !wAlive
|
|
1611
|
-
});
|
|
1612
|
-
}
|
|
1613
|
-
return {
|
|
1614
|
-
orchestratorPid: orchPid,
|
|
1615
|
-
orchestratorAlive: orchAlive,
|
|
1616
|
-
startedAt: data.started_at ?? "",
|
|
1617
|
-
workers
|
|
1618
|
-
};
|
|
1619
|
-
} catch {
|
|
1620
|
-
return null;
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
function getOsProcesses(processName) {
|
|
1624
|
-
try {
|
|
1625
|
-
const raw = (0, import_node_child_process.execSync)(`ps aux`, { encoding: "utf8", timeout: 5e3 });
|
|
1626
|
-
return raw.split("\n").filter((line) => line.includes(processName) && !line.includes("process-audit") && !line.includes("grep")).map((line) => {
|
|
1627
|
-
const parts = line.trim().split(/\s+/);
|
|
1628
|
-
return {
|
|
1629
|
-
pid: parseInt(parts[1] ?? "0", 10),
|
|
1630
|
-
cpu: parts[2] ?? "0",
|
|
1631
|
-
mem: parts[3] ?? "0",
|
|
1632
|
-
command: parts.slice(10).join(" ")
|
|
1633
|
-
};
|
|
1634
|
-
}).filter((p) => !isNaN(p.pid) && p.pid > 0);
|
|
1635
|
-
} catch {
|
|
1636
|
-
return [];
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
function discoverProcessConfig(dirs) {
|
|
1640
|
-
let pidFile;
|
|
1641
|
-
let workersFile;
|
|
1642
|
-
let processName = "";
|
|
1643
|
-
for (const dir of dirs) {
|
|
1644
|
-
if (!(0, import_node_fs2.existsSync)(dir)) continue;
|
|
1645
|
-
let entries;
|
|
1646
|
-
try {
|
|
1647
|
-
entries = (0, import_node_fs2.readdirSync)(dir);
|
|
1648
|
-
} catch {
|
|
1649
|
-
continue;
|
|
1650
|
-
}
|
|
1651
|
-
for (const f of entries) {
|
|
1652
|
-
const fp = (0, import_node_path2.join)(dir, f);
|
|
1653
|
-
try {
|
|
1654
|
-
if (!(0, import_node_fs2.statSync)(fp).isFile()) continue;
|
|
1655
|
-
} catch {
|
|
1656
|
-
continue;
|
|
1657
|
-
}
|
|
1658
|
-
if (f.endsWith(".pid") && !pidFile) {
|
|
1659
|
-
pidFile = fp;
|
|
1660
|
-
if (!processName) {
|
|
1661
|
-
processName = (0, import_node_path2.basename)(f, ".pid");
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
|
|
1665
|
-
workersFile = fp;
|
|
1666
|
-
if (!processName && f !== "workers.json") {
|
|
1667
|
-
processName = (0, import_node_path2.basename)(f, "-workers.json");
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
if (!processName && !pidFile && !workersFile) return null;
|
|
1673
|
-
if (!processName) processName = "agent";
|
|
1674
|
-
return { processName, pidFile, workersFile };
|
|
1675
|
-
}
|
|
1676
|
-
function auditProcesses(config) {
|
|
1677
|
-
const pidFile = auditPidFile(config);
|
|
1678
|
-
const systemd = auditSystemd(config);
|
|
1679
|
-
const workers = auditWorkers(config);
|
|
1680
|
-
const osProcesses = getOsProcesses(config.processName);
|
|
1681
|
-
const knownPids = /* @__PURE__ */ new Set();
|
|
1682
|
-
if (pidFile?.pid && !pidFile.stale) knownPids.add(pidFile.pid);
|
|
1683
|
-
if (workers) {
|
|
1684
|
-
if (workers.orchestratorPid) knownPids.add(workers.orchestratorPid);
|
|
1685
|
-
for (const w of workers.workers) {
|
|
1686
|
-
if (w.pid) knownPids.add(w.pid);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
1690
|
-
const orphans = osProcesses.filter((p) => !knownPids.has(p.pid));
|
|
1691
|
-
const problems = [];
|
|
1692
|
-
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
1693
|
-
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
1694
|
-
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
1695
|
-
if (systemd && systemd.restarts > 10) problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
1696
|
-
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
1697
|
-
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
1698
|
-
}
|
|
1699
|
-
if (workers) {
|
|
1700
|
-
for (const w of workers.workers) {
|
|
1701
|
-
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
if (orphans.length > 0) problems.push(`${orphans.length} orphan process(es) not tracked by PID file or workers registry`);
|
|
1705
|
-
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
1706
|
-
}
|
|
1707
|
-
function formatAuditReport(result) {
|
|
1708
|
-
const lines = [];
|
|
1709
|
-
lines.push("");
|
|
1710
|
-
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");
|
|
1711
|
-
lines.push("\u2551 \u{1F50D} P R O C E S S A U D I T \u2551");
|
|
1712
|
-
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");
|
|
1713
|
-
if (result.pidFile) {
|
|
1714
|
-
const pf = result.pidFile;
|
|
1715
|
-
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
1716
|
-
lines.push(`
|
|
1717
|
-
PID File: ${pf.path}`);
|
|
1718
|
-
lines.push(` ${icon} ${pf.reason}`);
|
|
1719
|
-
}
|
|
1720
|
-
if (result.systemd) {
|
|
1721
|
-
const sd = result.systemd;
|
|
1722
|
-
const icon = sd.activeState === "active" ? "\u{1F7E2}" : sd.crashLooping ? "\u{1F7E1}" : sd.failed ? "\u{1F534}" : "\u26AA";
|
|
1723
|
-
lines.push(`
|
|
1724
|
-
Systemd: ${sd.unit}`);
|
|
1725
|
-
lines.push(` ${icon} State: ${sd.activeState} (${sd.subState}) Result: ${sd.result}`);
|
|
1726
|
-
lines.push(` Main PID: ${sd.mainPid || "none"} Restarts: ${sd.restarts}`);
|
|
1727
|
-
}
|
|
1728
|
-
if (result.workers) {
|
|
1729
|
-
const w = result.workers;
|
|
1730
|
-
lines.push(`
|
|
1731
|
-
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`);
|
|
1732
|
-
for (const worker of w.workers) {
|
|
1733
|
-
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
1734
|
-
lines.push(` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
if (result.osProcesses.length > 0) {
|
|
1738
|
-
lines.push(`
|
|
1739
|
-
OS Processes (${result.osProcesses.length} total)`);
|
|
1740
|
-
for (const p of result.osProcesses) {
|
|
1741
|
-
lines.push(` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} ${p.command.substring(0, 55)}`);
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
if (result.orphans.length > 0) {
|
|
1745
|
-
lines.push(`
|
|
1746
|
-
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
1747
|
-
for (const p of result.orphans) {
|
|
1748
|
-
lines.push(` PID ${p.pid} \u2014 not tracked by PID file or workers registry`);
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
lines.push("");
|
|
1752
|
-
if (result.problems.length === 0) {
|
|
1753
|
-
lines.push(" \u2705 All checks passed \u2014 no process issues detected.");
|
|
1754
|
-
} else {
|
|
1755
|
-
lines.push(` \u26A0\uFE0F ${result.problems.length} issue(s):`);
|
|
1756
|
-
for (const p of result.problems) {
|
|
1757
|
-
lines.push(` \u2022 ${p}`);
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
lines.push("");
|
|
1761
|
-
return lines.join("\n");
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
1853
|
// src/runner.ts
|
|
1765
1854
|
var import_node_child_process2 = require("child_process");
|
|
1766
1855
|
var import_node_fs3 = require("fs");
|