agentflow-core 0.7.0 → 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,7 +1014,8 @@ 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;
@@ -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,7 +1215,7 @@ 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);
@@ -1197,7 +1256,8 @@ function render(config) {
1197
1256
  if (ar.systemd) {
1198
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}`;
1199
1258
  sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
1200
- 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}`;
1201
1261
  }
1202
1262
  let pidLabel = "";
1203
1263
  if (ar.pidFile?.pid) {
@@ -1206,7 +1266,10 @@ function render(config) {
1206
1266
  }
1207
1267
  writeLine(L, "");
1208
1268
  writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
1209
- 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
+ );
1210
1273
  if (workerParts.length > 0) {
1211
1274
  writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
1212
1275
  }
@@ -1218,7 +1281,10 @@ function render(config) {
1218
1281
  if (ar.orphans.length > 0) {
1219
1282
  for (const o of ar.orphans.slice(0, 5)) {
1220
1283
  const cmd = (o.cmdline || o.command).substring(0, detailWidth);
1221
- 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
+ );
1222
1288
  }
1223
1289
  if (ar.orphans.length > 5) {
1224
1290
  writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
@@ -1292,7 +1358,7 @@ function render(config) {
1292
1358
  for (let i = 0; i < Math.min(tree.length, 6); i++) {
1293
1359
  const tg = tree[i];
1294
1360
  const depth = getDistDepth(dt, tg.spanId);
1295
- const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
1361
+ const indent = ` ${"\u2502 ".repeat(Math.max(0, depth - 1))}`;
1296
1362
  const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
1297
1363
  const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1298
1364
  const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
@@ -1313,7 +1379,7 @@ function render(config) {
1313
1379
  const t = new Date(r.lastActive).toLocaleTimeString();
1314
1380
  const agent = truncate(r.id, 26).padEnd(26);
1315
1381
  const age = Math.floor((Date.now() - r.lastActive) / 1e3);
1316
- 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`;
1317
1383
  const det = truncate(r.detail, detailWidth);
1318
1384
  writeLine(
1319
1385
  L,
@@ -1378,7 +1444,7 @@ function startLive(argv) {
1378
1444
  };
1379
1445
  process.on("SIGINT", () => {
1380
1446
  cleanup();
1381
- console.log(C.dim + "Monitor stopped." + C.reset);
1447
+ console.log(`${C.dim}Monitor stopped.${C.reset}`);
1382
1448
  process.exit(0);
1383
1449
  });
1384
1450
  process.on("SIGTERM", () => {
@@ -1642,7 +1708,7 @@ function agentIdFromFilename(filePath) {
1642
1708
  const cleaned = base.replace(/-state$/, "");
1643
1709
  return `alfred-${cleaned}`;
1644
1710
  }
1645
- function deriveAgentId(command) {
1711
+ function deriveAgentId(_command) {
1646
1712
  return "orchestrator";
1647
1713
  }
1648
1714
  function fileTimestamp() {
@@ -1752,14 +1818,18 @@ async function runTraced(config) {
1752
1818
  const filename = `${graph.agentId}-${ts}.json`;
1753
1819
  const outPath = join3(resolvedTracesDir, filename);
1754
1820
  const resolvedOut = resolve2(outPath);
1755
- if (!resolvedOut.startsWith(resolvedTracesDir + "/") && resolvedOut !== resolvedTracesDir) {
1756
- 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
+ );
1757
1825
  }
1758
1826
  writeFileSync(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
1759
1827
  tracePaths.push(outPath);
1760
1828
  }
1761
1829
  if (tracePaths.length > 0) {
1762
- 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
+ );
1763
1833
  }
1764
1834
  return {
1765
1835
  exitCode,
@@ -1805,7 +1875,7 @@ function createTraceStore(dir) {
1805
1875
  const filePath = join4(dir, `${graph.id}.json`);
1806
1876
  const resolvedBase = resolve3(dir);
1807
1877
  const resolvedPath = resolve3(filePath);
1808
- if (!resolvedPath.startsWith(resolvedBase + "/") && resolvedPath !== resolvedBase) {
1878
+ if (!resolvedPath.startsWith(`${resolvedBase}/`) && resolvedPath !== resolvedBase) {
1809
1879
  throw new Error(`Path traversal detected: "${graph.id}" escapes base directory`);
1810
1880
  }
1811
1881
  await writeFile(filePath, JSON.stringify(json, null, 2), "utf-8");
@@ -1935,7 +2005,8 @@ function toAsciiTree(graph) {
1935
2005
  const children = getChildren(graph, nodeId);
1936
2006
  const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
1937
2007
  for (let i = 0; i < children.length; i++) {
1938
- 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);
1939
2010
  }
1940
2011
  }
1941
2012
  renderNode(graph.rootNodeId, "", true, true);
@@ -2134,10 +2205,10 @@ function parseDuration(input) {
2134
2205
  const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i);
2135
2206
  if (!match) {
2136
2207
  const n = parseInt(input, 10);
2137
- return isNaN(n) ? 0 : n * 1e3;
2208
+ return Number.isNaN(n) ? 0 : n * 1e3;
2138
2209
  }
2139
2210
  const value = parseFloat(match[1]);
2140
- switch (match[2].toLowerCase()) {
2211
+ switch (match[2]?.toLowerCase()) {
2141
2212
  case "s":
2142
2213
  return value * 1e3;
2143
2214
  case "m":
@@ -2164,7 +2235,7 @@ function loadWatchState(filePath) {
2164
2235
  }
2165
2236
  }
2166
2237
  function saveWatchState(filePath, state) {
2167
- const tmp = filePath + ".tmp";
2238
+ const tmp = `${filePath}.tmp`;
2168
2239
  try {
2169
2240
  writeFileSync2(tmp, JSON.stringify(state, null, 2), "utf8");
2170
2241
  renameSync(tmp, filePath);
@@ -2365,8 +2436,8 @@ function parseWatchArgs(argv) {
2365
2436
  i++;
2366
2437
  const val = args[i] ?? "";
2367
2438
  if (val === "telegram") {
2368
- const botToken = process.env["AGENTFLOW_TELEGRAM_BOT_TOKEN"] ?? "";
2369
- 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 ?? "";
2370
2441
  if (botToken && chatId) {
2371
2442
  notifyChannels.push({ type: "telegram", botToken, chatId });
2372
2443
  } else {
@@ -2383,7 +2454,7 @@ function parseWatchArgs(argv) {
2383
2454
  } else if (arg === "--poll") {
2384
2455
  i++;
2385
2456
  const v = parseInt(args[i] ?? "", 10);
2386
- if (!isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
2457
+ if (!Number.isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
2387
2458
  i++;
2388
2459
  } else if (arg === "--cooldown") {
2389
2460
  i++;
@@ -2499,7 +2570,7 @@ agentflow watch started`);
2499
2570
  console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
2500
2571
  console.log(` Alert on: ${condLabels.join(", ")}`);
2501
2572
  console.log(
2502
- ` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`
2573
+ ` Notify: stdout${channelLabels.length > 0 ? `, ${channelLabels.join(", ")}` : ""}`
2503
2574
  );
2504
2575
  console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
2505
2576
  console.log(` State: ${config.stateFilePath}`);