agentflow-core 0.6.3 → 0.8.0
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-R5SNE2HB.js → chunk-6X5HU5LB.js} +139 -57
- package/dist/cli.cjs +166 -80
- package/dist/cli.js +7 -3
- package/dist/index.cjs +1421 -124
- package/dist/index.d.cts +710 -37
- package/dist/index.d.ts +710 -37
- package/dist/index.js +1207 -1
- package/package.json +1 -1
|
@@ -155,7 +155,7 @@ function groupByTraceId(graphs) {
|
|
|
155
155
|
}
|
|
156
156
|
function stitchTrace(graphs) {
|
|
157
157
|
if (graphs.length === 0) throw new Error("No graphs to stitch");
|
|
158
|
-
const traceId = graphs[0]
|
|
158
|
+
const traceId = graphs[0]?.traceId ?? "";
|
|
159
159
|
const graphsBySpan = /* @__PURE__ */ new Map();
|
|
160
160
|
const childMap = /* @__PURE__ */ new Map();
|
|
161
161
|
let rootGraph = null;
|
|
@@ -229,7 +229,7 @@ function pidMatchesName(pid, name) {
|
|
|
229
229
|
function readPidFile(path) {
|
|
230
230
|
try {
|
|
231
231
|
const pid = parseInt(readFileSync(path, "utf8").trim(), 10);
|
|
232
|
-
return isNaN(pid) ? null : pid;
|
|
232
|
+
return Number.isNaN(pid) ? null : pid;
|
|
233
233
|
} catch {
|
|
234
234
|
return null;
|
|
235
235
|
}
|
|
@@ -273,11 +273,11 @@ function auditSystemd(config) {
|
|
|
273
273
|
const [k, ...v] = line.split("=");
|
|
274
274
|
if (k) props[k.trim()] = v.join("=").trim();
|
|
275
275
|
}
|
|
276
|
-
const activeState = props
|
|
277
|
-
const subState = props
|
|
278
|
-
const mainPid = parseInt(props
|
|
279
|
-
const restarts = parseInt(props
|
|
280
|
-
const result = props
|
|
276
|
+
const activeState = props.ActiveState ?? "unknown";
|
|
277
|
+
const subState = props.SubState ?? "unknown";
|
|
278
|
+
const mainPid = parseInt(props.MainPID ?? "0", 10);
|
|
279
|
+
const restarts = parseInt(props.NRestarts ?? "0", 10);
|
|
280
|
+
const result = props.Result ?? "unknown";
|
|
281
281
|
return {
|
|
282
282
|
unit,
|
|
283
283
|
activeState,
|
|
@@ -330,10 +330,10 @@ function readCmdline(pid) {
|
|
|
330
330
|
}
|
|
331
331
|
function getOsProcesses(processName) {
|
|
332
332
|
try {
|
|
333
|
-
const raw = execSync(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
);
|
|
333
|
+
const raw = execSync(`ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`, {
|
|
334
|
+
encoding: "utf8",
|
|
335
|
+
timeout: 5e3
|
|
336
|
+
});
|
|
337
337
|
const results = [];
|
|
338
338
|
for (const line of raw.split("\n")) {
|
|
339
339
|
if (!line.includes(processName)) continue;
|
|
@@ -341,7 +341,7 @@ function getOsProcesses(processName) {
|
|
|
341
341
|
const trimmed = line.trim();
|
|
342
342
|
const parts = trimmed.split(/\s+/);
|
|
343
343
|
const pid = parseInt(parts[0] ?? "0", 10);
|
|
344
|
-
if (isNaN(pid) || pid <= 0) continue;
|
|
344
|
+
if (Number.isNaN(pid) || pid <= 0) continue;
|
|
345
345
|
const cpu = parts[1] ?? "0";
|
|
346
346
|
const mem = parts[2] ?? "0";
|
|
347
347
|
const elapsed = parts[3] ?? "";
|
|
@@ -418,16 +418,45 @@ function auditProcesses(config) {
|
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
421
|
+
const childPids = /* @__PURE__ */ new Set();
|
|
422
|
+
for (const knownPid of knownPids) {
|
|
423
|
+
try {
|
|
424
|
+
const childrenRaw = readFileSync(
|
|
425
|
+
`/proc/${knownPid}/task/${knownPid}/children`,
|
|
426
|
+
"utf8"
|
|
427
|
+
).trim();
|
|
428
|
+
if (childrenRaw) {
|
|
429
|
+
for (const c of childrenRaw.split(/\s+/)) {
|
|
430
|
+
const cp = parseInt(c, 10);
|
|
431
|
+
if (!Number.isNaN(cp)) childPids.add(cp);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
for (const p of osProcesses) {
|
|
438
|
+
if (knownPids.has(p.pid)) continue;
|
|
439
|
+
try {
|
|
440
|
+
const statusContent = readFileSync(`/proc/${p.pid}/status`, "utf8");
|
|
441
|
+
const ppidMatch = statusContent.match(/^PPid:\s+(\d+)/m);
|
|
442
|
+
if (ppidMatch) {
|
|
443
|
+
const ppid = parseInt(ppidMatch[1] ?? "0", 10);
|
|
444
|
+
if (knownPids.has(ppid)) childPids.add(p.pid);
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
}
|
|
421
449
|
const selfPid = process.pid;
|
|
422
450
|
const selfPpid = process.ppid;
|
|
423
451
|
const orphans = osProcesses.filter(
|
|
424
|
-
(p) => !knownPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
452
|
+
(p) => !knownPids.has(p.pid) && !childPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
425
453
|
);
|
|
426
454
|
const problems = [];
|
|
427
455
|
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
428
456
|
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
429
457
|
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
430
|
-
if (systemd && systemd.restarts > 10)
|
|
458
|
+
if (systemd && systemd.restarts > 10)
|
|
459
|
+
problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
431
460
|
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
432
461
|
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
433
462
|
}
|
|
@@ -436,15 +465,24 @@ function auditProcesses(config) {
|
|
|
436
465
|
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
437
466
|
}
|
|
438
467
|
}
|
|
439
|
-
if (orphans.length > 0)
|
|
468
|
+
if (orphans.length > 0)
|
|
469
|
+
problems.push(
|
|
470
|
+
`${orphans.length} orphan process(es) not tracked by PID file or workers registry`
|
|
471
|
+
);
|
|
440
472
|
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
441
473
|
}
|
|
442
474
|
function formatAuditReport(result) {
|
|
443
475
|
const lines = [];
|
|
444
476
|
lines.push("");
|
|
445
|
-
lines.push(
|
|
446
|
-
|
|
447
|
-
|
|
477
|
+
lines.push(
|
|
478
|
+
"\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"
|
|
479
|
+
);
|
|
480
|
+
lines.push(
|
|
481
|
+
"\u2551 \u{1F50D} P R O C E S S A U D I T \u2551"
|
|
482
|
+
);
|
|
483
|
+
lines.push(
|
|
484
|
+
"\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"
|
|
485
|
+
);
|
|
448
486
|
if (result.pidFile) {
|
|
449
487
|
const pf = result.pidFile;
|
|
450
488
|
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
@@ -462,25 +500,33 @@ function formatAuditReport(result) {
|
|
|
462
500
|
}
|
|
463
501
|
if (result.workers) {
|
|
464
502
|
const w = result.workers;
|
|
465
|
-
lines.push(
|
|
466
|
-
|
|
503
|
+
lines.push(
|
|
504
|
+
`
|
|
505
|
+
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`
|
|
506
|
+
);
|
|
467
507
|
for (const worker of w.workers) {
|
|
468
508
|
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
469
|
-
lines.push(
|
|
509
|
+
lines.push(
|
|
510
|
+
` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`
|
|
511
|
+
);
|
|
470
512
|
}
|
|
471
513
|
}
|
|
472
514
|
if (result.osProcesses.length > 0) {
|
|
473
515
|
lines.push(`
|
|
474
516
|
OS Processes (${result.osProcesses.length} total)`);
|
|
475
517
|
for (const p of result.osProcesses) {
|
|
476
|
-
lines.push(
|
|
518
|
+
lines.push(
|
|
519
|
+
` 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)}`
|
|
520
|
+
);
|
|
477
521
|
}
|
|
478
522
|
}
|
|
479
523
|
if (result.orphans.length > 0) {
|
|
480
524
|
lines.push(`
|
|
481
525
|
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
482
526
|
for (const p of result.orphans) {
|
|
483
|
-
lines.push(
|
|
527
|
+
lines.push(
|
|
528
|
+
` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed}`
|
|
529
|
+
);
|
|
484
530
|
lines.push(` Started: ${p.started}`);
|
|
485
531
|
lines.push(` Command: ${p.cmdline || p.command}`);
|
|
486
532
|
}
|
|
@@ -527,7 +573,7 @@ function parseArgs(argv) {
|
|
|
527
573
|
} else if (arg === "--refresh" || arg === "-r") {
|
|
528
574
|
i++;
|
|
529
575
|
const v = parseInt(args[i] ?? "", 10);
|
|
530
|
-
if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
576
|
+
if (!Number.isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
531
577
|
i++;
|
|
532
578
|
} else if (arg === "--recursive" || arg === "-R") {
|
|
533
579
|
config.recursive = true;
|
|
@@ -608,12 +654,22 @@ function scanFiles(dirs, recursive) {
|
|
|
608
654
|
if (!stat.isFile()) continue;
|
|
609
655
|
if (f.endsWith(".json")) {
|
|
610
656
|
seen.add(fp);
|
|
611
|
-
const entry = {
|
|
657
|
+
const entry = {
|
|
658
|
+
filename: f,
|
|
659
|
+
path: fp,
|
|
660
|
+
mtime: stat.mtime.getTime(),
|
|
661
|
+
ext: ".json"
|
|
662
|
+
};
|
|
612
663
|
results.push(entry);
|
|
613
664
|
dirResults.push(entry);
|
|
614
665
|
} else if (f.endsWith(".jsonl")) {
|
|
615
666
|
seen.add(fp);
|
|
616
|
-
const entry = {
|
|
667
|
+
const entry = {
|
|
668
|
+
filename: f,
|
|
669
|
+
path: fp,
|
|
670
|
+
mtime: stat.mtime.getTime(),
|
|
671
|
+
ext: ".jsonl"
|
|
672
|
+
};
|
|
617
673
|
results.push(entry);
|
|
618
674
|
dirResults.push(entry);
|
|
619
675
|
}
|
|
@@ -675,7 +731,7 @@ function findTimestamp(obj) {
|
|
|
675
731
|
if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
|
|
676
732
|
if (typeof val === "string") {
|
|
677
733
|
const d = Date.parse(val);
|
|
678
|
-
if (!isNaN(d)) return d;
|
|
734
|
+
if (!Number.isNaN(d)) return d;
|
|
679
735
|
}
|
|
680
736
|
}
|
|
681
737
|
return 0;
|
|
@@ -707,7 +763,7 @@ function extractDetail(obj) {
|
|
|
707
763
|
}
|
|
708
764
|
return parts.join(" | ") || "";
|
|
709
765
|
}
|
|
710
|
-
function tryLoadTrace(
|
|
766
|
+
function tryLoadTrace(_fp, raw) {
|
|
711
767
|
if (typeof raw !== "object" || raw === null) return null;
|
|
712
768
|
const obj = raw;
|
|
713
769
|
if (!("nodes" in obj)) return null;
|
|
@@ -958,13 +1014,14 @@ function writeLine(lines, text) {
|
|
|
958
1014
|
}
|
|
959
1015
|
function flushLines(lines) {
|
|
960
1016
|
process.stdout.write("\x1B[H");
|
|
961
|
-
process.stdout.write(lines.join("\n")
|
|
1017
|
+
process.stdout.write(`${lines.join("\n")}
|
|
1018
|
+
`);
|
|
962
1019
|
process.stdout.write("\x1B[J");
|
|
963
1020
|
}
|
|
964
1021
|
var prevFileCount = 0;
|
|
965
1022
|
var newExecCount = 0;
|
|
966
1023
|
var sessionStart = Date.now();
|
|
967
|
-
var
|
|
1024
|
+
var liveRunning = false;
|
|
968
1025
|
var cachedAuditConfig = null;
|
|
969
1026
|
var cachedAuditResult = null;
|
|
970
1027
|
var lastAuditTime = 0;
|
|
@@ -1052,7 +1109,7 @@ function render(config) {
|
|
|
1052
1109
|
const status = fail > 0 ? "error" : running > 0 ? "running" : ok > 0 ? "ok" : "unknown";
|
|
1053
1110
|
groups.push({
|
|
1054
1111
|
name: groupName,
|
|
1055
|
-
source: records[0]
|
|
1112
|
+
source: records[0]?.source ?? "trace",
|
|
1056
1113
|
status,
|
|
1057
1114
|
lastTs,
|
|
1058
1115
|
detail: `${records.length} agents`,
|
|
@@ -1098,8 +1155,10 @@ function render(config) {
|
|
|
1098
1155
|
cachedAuditResult = auditResult;
|
|
1099
1156
|
lastAuditTime = now;
|
|
1100
1157
|
} catch (err) {
|
|
1101
|
-
process.stderr.write(
|
|
1102
|
-
`
|
|
1158
|
+
process.stderr.write(
|
|
1159
|
+
`[agentflow] process audit error: ${err instanceof Error ? err.message : err}
|
|
1160
|
+
`
|
|
1161
|
+
);
|
|
1103
1162
|
}
|
|
1104
1163
|
}
|
|
1105
1164
|
} else {
|
|
@@ -1156,14 +1215,10 @@ function render(config) {
|
|
|
1156
1215
|
return new Date(ts).toLocaleTimeString();
|
|
1157
1216
|
}
|
|
1158
1217
|
function truncate(s, max) {
|
|
1159
|
-
return s.length > max ? s.slice(0, max - 1)
|
|
1218
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
1160
1219
|
}
|
|
1161
1220
|
const termWidth = process.stdout.columns || 120;
|
|
1162
1221
|
const detailWidth = Math.max(20, termWidth - 60);
|
|
1163
|
-
if (firstRender) {
|
|
1164
|
-
process.stdout.write("\x1B[2J");
|
|
1165
|
-
firstRender = false;
|
|
1166
|
-
}
|
|
1167
1222
|
const L = [];
|
|
1168
1223
|
writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
|
|
1169
1224
|
writeLine(
|
|
@@ -1201,7 +1256,8 @@ function render(config) {
|
|
|
1201
1256
|
if (ar.systemd) {
|
|
1202
1257
|
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}`;
|
|
1203
1258
|
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1204
|
-
if (ar.systemd.restarts > 0)
|
|
1259
|
+
if (ar.systemd.restarts > 0)
|
|
1260
|
+
sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1205
1261
|
}
|
|
1206
1262
|
let pidLabel = "";
|
|
1207
1263
|
if (ar.pidFile?.pid) {
|
|
@@ -1210,7 +1266,10 @@ function render(config) {
|
|
|
1210
1266
|
}
|
|
1211
1267
|
writeLine(L, "");
|
|
1212
1268
|
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1213
|
-
writeLine(
|
|
1269
|
+
writeLine(
|
|
1270
|
+
L,
|
|
1271
|
+
` ${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}`}`
|
|
1272
|
+
);
|
|
1214
1273
|
if (workerParts.length > 0) {
|
|
1215
1274
|
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1216
1275
|
}
|
|
@@ -1222,7 +1281,10 @@ function render(config) {
|
|
|
1222
1281
|
if (ar.orphans.length > 0) {
|
|
1223
1282
|
for (const o of ar.orphans.slice(0, 5)) {
|
|
1224
1283
|
const cmd = (o.cmdline || o.command).substring(0, detailWidth);
|
|
1225
|
-
writeLine(
|
|
1284
|
+
writeLine(
|
|
1285
|
+
L,
|
|
1286
|
+
` ${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}`
|
|
1287
|
+
);
|
|
1226
1288
|
}
|
|
1227
1289
|
if (ar.orphans.length > 5) {
|
|
1228
1290
|
writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
|
|
@@ -1296,7 +1358,7 @@ function render(config) {
|
|
|
1296
1358
|
for (let i = 0; i < Math.min(tree.length, 6); i++) {
|
|
1297
1359
|
const tg = tree[i];
|
|
1298
1360
|
const depth = getDistDepth(dt, tg.spanId);
|
|
1299
|
-
const indent =
|
|
1361
|
+
const indent = ` ${"\u2502 ".repeat(Math.max(0, depth - 1))}`;
|
|
1300
1362
|
const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
|
|
1301
1363
|
const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1302
1364
|
const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
@@ -1317,7 +1379,7 @@ function render(config) {
|
|
|
1317
1379
|
const t = new Date(r.lastActive).toLocaleTimeString();
|
|
1318
1380
|
const agent = truncate(r.id, 26).padEnd(26);
|
|
1319
1381
|
const age = Math.floor((Date.now() - r.lastActive) / 1e3);
|
|
1320
|
-
const ageStr = age < 60 ? age
|
|
1382
|
+
const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
|
|
1321
1383
|
const det = truncate(r.detail, detailWidth);
|
|
1322
1384
|
writeLine(
|
|
1323
1385
|
L,
|
|
@@ -1358,6 +1420,9 @@ function startLive(argv) {
|
|
|
1358
1420
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1359
1421
|
}
|
|
1360
1422
|
config.dirs = valid;
|
|
1423
|
+
process.stdout.write("\x1B[?1049h");
|
|
1424
|
+
process.stdout.write("\x1B[?25l");
|
|
1425
|
+
liveRunning = true;
|
|
1361
1426
|
render(config);
|
|
1362
1427
|
let debounce = null;
|
|
1363
1428
|
for (const dir of config.dirs) {
|
|
@@ -1370,8 +1435,20 @@ function startLive(argv) {
|
|
|
1370
1435
|
}
|
|
1371
1436
|
}
|
|
1372
1437
|
setInterval(() => render(config), config.refreshMs);
|
|
1438
|
+
const cleanup = () => {
|
|
1439
|
+
if (liveRunning) {
|
|
1440
|
+
liveRunning = false;
|
|
1441
|
+
process.stdout.write("\x1B[?25h");
|
|
1442
|
+
process.stdout.write("\x1B[?1049l");
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1373
1445
|
process.on("SIGINT", () => {
|
|
1374
|
-
|
|
1446
|
+
cleanup();
|
|
1447
|
+
console.log(`${C.dim}Monitor stopped.${C.reset}`);
|
|
1448
|
+
process.exit(0);
|
|
1449
|
+
});
|
|
1450
|
+
process.on("SIGTERM", () => {
|
|
1451
|
+
cleanup();
|
|
1375
1452
|
process.exit(0);
|
|
1376
1453
|
});
|
|
1377
1454
|
}
|
|
@@ -1631,7 +1708,7 @@ function agentIdFromFilename(filePath) {
|
|
|
1631
1708
|
const cleaned = base.replace(/-state$/, "");
|
|
1632
1709
|
return `alfred-${cleaned}`;
|
|
1633
1710
|
}
|
|
1634
|
-
function deriveAgentId(
|
|
1711
|
+
function deriveAgentId(_command) {
|
|
1635
1712
|
return "orchestrator";
|
|
1636
1713
|
}
|
|
1637
1714
|
function fileTimestamp() {
|
|
@@ -1741,14 +1818,18 @@ async function runTraced(config) {
|
|
|
1741
1818
|
const filename = `${graph.agentId}-${ts}.json`;
|
|
1742
1819
|
const outPath = join3(resolvedTracesDir, filename);
|
|
1743
1820
|
const resolvedOut = resolve2(outPath);
|
|
1744
|
-
if (!resolvedOut.startsWith(resolvedTracesDir
|
|
1745
|
-
throw new Error(
|
|
1821
|
+
if (!resolvedOut.startsWith(`${resolvedTracesDir}/`) && resolvedOut !== resolvedTracesDir) {
|
|
1822
|
+
throw new Error(
|
|
1823
|
+
`Path traversal detected: agentId "${graph.agentId}" escapes traces directory`
|
|
1824
|
+
);
|
|
1746
1825
|
}
|
|
1747
1826
|
writeFileSync(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
|
|
1748
1827
|
tracePaths.push(outPath);
|
|
1749
1828
|
}
|
|
1750
1829
|
if (tracePaths.length > 0) {
|
|
1751
|
-
console.log(
|
|
1830
|
+
console.log(
|
|
1831
|
+
`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`
|
|
1832
|
+
);
|
|
1752
1833
|
}
|
|
1753
1834
|
return {
|
|
1754
1835
|
exitCode,
|
|
@@ -1794,7 +1875,7 @@ function createTraceStore(dir) {
|
|
|
1794
1875
|
const filePath = join4(dir, `${graph.id}.json`);
|
|
1795
1876
|
const resolvedBase = resolve3(dir);
|
|
1796
1877
|
const resolvedPath = resolve3(filePath);
|
|
1797
|
-
if (!resolvedPath.startsWith(resolvedBase
|
|
1878
|
+
if (!resolvedPath.startsWith(`${resolvedBase}/`) && resolvedPath !== resolvedBase) {
|
|
1798
1879
|
throw new Error(`Path traversal detected: "${graph.id}" escapes base directory`);
|
|
1799
1880
|
}
|
|
1800
1881
|
await writeFile(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
@@ -1924,7 +2005,8 @@ function toAsciiTree(graph) {
|
|
|
1924
2005
|
const children = getChildren(graph, nodeId);
|
|
1925
2006
|
const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
|
|
1926
2007
|
for (let i = 0; i < children.length; i++) {
|
|
1927
|
-
|
|
2008
|
+
const childId = children[i]?.id;
|
|
2009
|
+
if (childId) renderNode(childId, childPrefix, i === children.length - 1, false);
|
|
1928
2010
|
}
|
|
1929
2011
|
}
|
|
1930
2012
|
renderNode(graph.rootNodeId, "", true, true);
|
|
@@ -2123,10 +2205,10 @@ function parseDuration(input) {
|
|
|
2123
2205
|
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i);
|
|
2124
2206
|
if (!match) {
|
|
2125
2207
|
const n = parseInt(input, 10);
|
|
2126
|
-
return isNaN(n) ? 0 : n * 1e3;
|
|
2208
|
+
return Number.isNaN(n) ? 0 : n * 1e3;
|
|
2127
2209
|
}
|
|
2128
2210
|
const value = parseFloat(match[1]);
|
|
2129
|
-
switch (match[2]
|
|
2211
|
+
switch (match[2]?.toLowerCase()) {
|
|
2130
2212
|
case "s":
|
|
2131
2213
|
return value * 1e3;
|
|
2132
2214
|
case "m":
|
|
@@ -2153,7 +2235,7 @@ function loadWatchState(filePath) {
|
|
|
2153
2235
|
}
|
|
2154
2236
|
}
|
|
2155
2237
|
function saveWatchState(filePath, state) {
|
|
2156
|
-
const tmp = filePath
|
|
2238
|
+
const tmp = `${filePath}.tmp`;
|
|
2157
2239
|
try {
|
|
2158
2240
|
writeFileSync2(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
2159
2241
|
renameSync(tmp, filePath);
|
|
@@ -2354,8 +2436,8 @@ function parseWatchArgs(argv) {
|
|
|
2354
2436
|
i++;
|
|
2355
2437
|
const val = args[i] ?? "";
|
|
2356
2438
|
if (val === "telegram") {
|
|
2357
|
-
const botToken = process.env
|
|
2358
|
-
const chatId = process.env
|
|
2439
|
+
const botToken = process.env.AGENTFLOW_TELEGRAM_BOT_TOKEN ?? "";
|
|
2440
|
+
const chatId = process.env.AGENTFLOW_TELEGRAM_CHAT_ID ?? "";
|
|
2359
2441
|
if (botToken && chatId) {
|
|
2360
2442
|
notifyChannels.push({ type: "telegram", botToken, chatId });
|
|
2361
2443
|
} else {
|
|
@@ -2372,7 +2454,7 @@ function parseWatchArgs(argv) {
|
|
|
2372
2454
|
} else if (arg === "--poll") {
|
|
2373
2455
|
i++;
|
|
2374
2456
|
const v = parseInt(args[i] ?? "", 10);
|
|
2375
|
-
if (!isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
2457
|
+
if (!Number.isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
2376
2458
|
i++;
|
|
2377
2459
|
} else if (arg === "--cooldown") {
|
|
2378
2460
|
i++;
|
|
@@ -2488,7 +2570,7 @@ agentflow watch started`);
|
|
|
2488
2570
|
console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
|
|
2489
2571
|
console.log(` Alert on: ${condLabels.join(", ")}`);
|
|
2490
2572
|
console.log(
|
|
2491
|
-
` Notify: stdout${channelLabels.length > 0 ?
|
|
2573
|
+
` Notify: stdout${channelLabels.length > 0 ? `, ${channelLabels.join(", ")}` : ""}`
|
|
2492
2574
|
);
|
|
2493
2575
|
console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
|
|
2494
2576
|
console.log(` State: ${config.stateFilePath}`);
|