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.
@@ -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].traceId ?? "";
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["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";
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
- `ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`,
335
- { encoding: "utf8", timeout: 5e3 }
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) problems.push(`High systemd restart count: ${systemd.restarts}`);
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) problems.push(`${orphans.length} orphan process(es) not tracked by PID file or workers registry`);
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("\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");
446
- lines.push("\u2551 \u{1F50D} P R O C E S S A U D I T \u2551");
447
- 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");
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
- Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`);
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(` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`);
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(` 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)}`);
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(` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed}`);
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 = { filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" };
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 = { filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" };
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(fp, raw) {
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") + "\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 firstRender = true;
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].source,
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(`[agentflow] process audit error: ${err instanceof Error ? err.message : err}
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) + "\u2026" : s;
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) sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
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(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}`}`);
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(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}`);
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 = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
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 + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
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
- console.log("\n" + C.dim + "Monitor stopped." + C.reset);
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(command) {
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 + "/") && resolvedOut !== resolvedTracesDir) {
1745
- throw new Error(`Path traversal detected: agentId "${graph.agentId}" escapes traces directory`);
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(`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`);
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 + "/") && resolvedPath !== 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
- renderNode(children[i].id, childPrefix, i === children.length - 1, false);
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].toLowerCase()) {
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 + ".tmp";
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["AGENTFLOW_TELEGRAM_BOT_TOKEN"] ?? "";
2358
- const chatId = process.env["AGENTFLOW_TELEGRAM_CHAT_ID"] ?? "";
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 ? ", " + channelLabels.join(", ") : ""}`
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}`);