agentflow-core 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  graphToJson,
3
3
  loadGraph
4
- } from "./chunk-DY7YHFIB.js";
4
+ } from "./chunk-BYWLDTZK.js";
5
5
 
6
6
  // src/graph-query.ts
7
7
  function getNode(graph, nodeId) {
@@ -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;
@@ -207,7 +207,7 @@ function getTraceTree(trace) {
207
207
  }
208
208
 
209
209
  // src/process-audit.ts
210
- import { execSync } from "child_process";
210
+ import { execFileSync, execSync } from "child_process";
211
211
  import { existsSync, readdirSync, readFileSync, statSync } from "fs";
212
212
  import { basename, join } from "path";
213
213
  function isPidAlive(pid) {
@@ -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
  }
@@ -264,8 +264,15 @@ function auditSystemd(config) {
264
264
  if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
265
265
  const unit = config.systemdUnit;
266
266
  try {
267
- const raw = execSync(
268
- `systemctl --user show ${unit} --property=ActiveState,SubState,MainPID,NRestarts,Result --no-pager 2>/dev/null`,
267
+ const raw = execFileSync(
268
+ "systemctl",
269
+ [
270
+ "--user",
271
+ "show",
272
+ unit,
273
+ "--property=ActiveState,SubState,MainPID,NRestarts,Result",
274
+ "--no-pager"
275
+ ],
269
276
  { encoding: "utf8", timeout: 5e3 }
270
277
  );
271
278
  const props = {};
@@ -273,11 +280,11 @@ function auditSystemd(config) {
273
280
  const [k, ...v] = line.split("=");
274
281
  if (k) props[k.trim()] = v.join("=").trim();
275
282
  }
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";
283
+ const activeState = props.ActiveState ?? "unknown";
284
+ const subState = props.SubState ?? "unknown";
285
+ const mainPid = parseInt(props.MainPID ?? "0", 10);
286
+ const restarts = parseInt(props.NRestarts ?? "0", 10);
287
+ const result = props.Result ?? "unknown";
281
288
  return {
282
289
  unit,
283
290
  activeState,
@@ -330,10 +337,10 @@ function readCmdline(pid) {
330
337
  }
331
338
  function getOsProcesses(processName) {
332
339
  try {
333
- const raw = execSync(
334
- `ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`,
335
- { encoding: "utf8", timeout: 5e3 }
336
- );
340
+ const raw = execSync(`ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`, {
341
+ encoding: "utf8",
342
+ timeout: 5e3
343
+ });
337
344
  const results = [];
338
345
  for (const line of raw.split("\n")) {
339
346
  if (!line.includes(processName)) continue;
@@ -341,7 +348,7 @@ function getOsProcesses(processName) {
341
348
  const trimmed = line.trim();
342
349
  const parts = trimmed.split(/\s+/);
343
350
  const pid = parseInt(parts[0] ?? "0", 10);
344
- if (isNaN(pid) || pid <= 0) continue;
351
+ if (Number.isNaN(pid) || pid <= 0) continue;
345
352
  const cpu = parts[1] ?? "0";
346
353
  const mem = parts[2] ?? "0";
347
354
  const elapsed = parts[3] ?? "";
@@ -356,9 +363,11 @@ function getOsProcesses(processName) {
356
363
  }
357
364
  }
358
365
  function discoverProcessConfig(dirs) {
359
- let pidFile;
360
- let workersFile;
361
- let processName = "";
366
+ const configs = discoverAllProcessConfigs(dirs);
367
+ return configs.length > 0 ? configs[0] ?? null : null;
368
+ }
369
+ function discoverAllProcessConfigs(dirs) {
370
+ const configs = /* @__PURE__ */ new Map();
362
371
  for (const dir of dirs) {
363
372
  if (!existsSync(dir)) continue;
364
373
  let entries;
@@ -374,35 +383,63 @@ function discoverProcessConfig(dirs) {
374
383
  } catch {
375
384
  continue;
376
385
  }
377
- if (f.endsWith(".pid") && !pidFile) {
378
- pidFile = fp;
379
- if (!processName) {
380
- processName = basename(f, ".pid");
386
+ if (f.endsWith(".pid")) {
387
+ const name = basename(f, ".pid");
388
+ if (!configs.has(name)) {
389
+ configs.set(name, { processName: name });
381
390
  }
391
+ const cfg = configs.get(name) ?? { processName: name };
392
+ if (!cfg.pidFile) cfg.pidFile = fp;
382
393
  }
383
- if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
384
- workersFile = fp;
385
- if (!processName && f !== "workers.json") {
386
- processName = basename(f, "-workers.json");
394
+ if (f === "workers.json" || f.endsWith("-workers.json")) {
395
+ const name = f === "workers.json" ? "" : basename(f, "-workers.json");
396
+ if (name && !configs.has(name)) {
397
+ configs.set(name, { processName: name });
398
+ }
399
+ if (name) {
400
+ const cfg = configs.get(name) ?? { processName: name };
401
+ if (!cfg.workersFile) cfg.workersFile = fp;
387
402
  }
388
403
  }
389
404
  }
390
405
  }
391
- if (!processName && !pidFile && !workersFile) return null;
392
- if (!processName) processName = "agent";
393
- let systemdUnit;
394
406
  try {
395
- const unitName = `${processName}.service`;
396
- const result = execSync(
397
- `systemctl --user show ${unitName} --property=LoadState --no-pager 2>/dev/null`,
398
- { encoding: "utf8", timeout: 3e3 }
407
+ const raw = execSync(
408
+ "systemctl --user list-units --type=service --all --no-legend --no-pager 2>/dev/null",
409
+ { encoding: "utf8", timeout: 5e3 }
399
410
  );
400
- if (result.includes("LoadState=loaded")) {
401
- systemdUnit = unitName;
411
+ for (const line of raw.trim().split("\n")) {
412
+ if (!line.trim()) continue;
413
+ const parts = line.trim().split(/\s+/);
414
+ const unitName = parts[0] ?? "";
415
+ const loadState = parts[1] ?? "";
416
+ if (!unitName.endsWith(".service") || loadState !== "loaded") continue;
417
+ const name = unitName.replace(".service", "");
418
+ if (/^(dbus|gpg-agent|dirmngr|keyboxd|snapd\.|pk-|launchpadlib-)/.test(name)) continue;
419
+ if (!configs.has(name)) {
420
+ configs.set(name, { processName: name });
421
+ }
422
+ const cfg = configs.get(name) ?? { processName: name };
423
+ cfg.systemdUnit = unitName;
402
424
  }
403
425
  } catch {
404
426
  }
405
- return { processName, pidFile, workersFile, systemdUnit };
427
+ for (const cfg of configs.values()) {
428
+ if (cfg.systemdUnit !== void 0) continue;
429
+ try {
430
+ const unitName = `${cfg.processName}.service`;
431
+ const result = execFileSync(
432
+ "systemctl",
433
+ ["--user", "show", unitName, "--property=LoadState", "--no-pager"],
434
+ { encoding: "utf8", timeout: 3e3 }
435
+ );
436
+ if (result.includes("LoadState=loaded")) {
437
+ cfg.systemdUnit = unitName;
438
+ }
439
+ } catch {
440
+ }
441
+ }
442
+ return [...configs.values()];
406
443
  }
407
444
  function auditProcesses(config) {
408
445
  const pidFile = auditPidFile(config);
@@ -418,16 +455,45 @@ function auditProcesses(config) {
418
455
  }
419
456
  }
420
457
  if (systemd?.mainPid) knownPids.add(systemd.mainPid);
458
+ const childPids = /* @__PURE__ */ new Set();
459
+ for (const knownPid of knownPids) {
460
+ try {
461
+ const childrenRaw = readFileSync(
462
+ `/proc/${knownPid}/task/${knownPid}/children`,
463
+ "utf8"
464
+ ).trim();
465
+ if (childrenRaw) {
466
+ for (const c of childrenRaw.split(/\s+/)) {
467
+ const cp = parseInt(c, 10);
468
+ if (!Number.isNaN(cp)) childPids.add(cp);
469
+ }
470
+ }
471
+ } catch {
472
+ }
473
+ }
474
+ for (const p of osProcesses) {
475
+ if (knownPids.has(p.pid)) continue;
476
+ try {
477
+ const statusContent = readFileSync(`/proc/${p.pid}/status`, "utf8");
478
+ const ppidMatch = statusContent.match(/^PPid:\s+(\d+)/m);
479
+ if (ppidMatch) {
480
+ const ppid = parseInt(ppidMatch[1] ?? "0", 10);
481
+ if (knownPids.has(ppid)) childPids.add(p.pid);
482
+ }
483
+ } catch {
484
+ }
485
+ }
421
486
  const selfPid = process.pid;
422
487
  const selfPpid = process.ppid;
423
488
  const orphans = osProcesses.filter(
424
- (p) => !knownPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
489
+ (p) => !knownPids.has(p.pid) && !childPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
425
490
  );
426
491
  const problems = [];
427
492
  if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
428
493
  if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
429
494
  if (systemd?.failed) problems.push("Systemd unit has failed");
430
- if (systemd && systemd.restarts > 10) problems.push(`High systemd restart count: ${systemd.restarts}`);
495
+ if (systemd && systemd.restarts > 10)
496
+ problems.push(`High systemd restart count: ${systemd.restarts}`);
431
497
  if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
432
498
  problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
433
499
  }
@@ -436,15 +502,24 @@ function auditProcesses(config) {
436
502
  if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
437
503
  }
438
504
  }
439
- if (orphans.length > 0) problems.push(`${orphans.length} orphan process(es) not tracked by PID file or workers registry`);
505
+ if (orphans.length > 0)
506
+ problems.push(
507
+ `${orphans.length} orphan process(es) not tracked by PID file or workers registry`
508
+ );
440
509
  return { pidFile, systemd, workers, osProcesses, orphans, problems };
441
510
  }
442
511
  function formatAuditReport(result) {
443
512
  const lines = [];
444
513
  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");
514
+ lines.push(
515
+ "\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"
516
+ );
517
+ lines.push(
518
+ "\u2551 \u{1F50D} P R O C E S S A U D I T \u2551"
519
+ );
520
+ lines.push(
521
+ "\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"
522
+ );
448
523
  if (result.pidFile) {
449
524
  const pf = result.pidFile;
450
525
  const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
@@ -462,25 +537,33 @@ function formatAuditReport(result) {
462
537
  }
463
538
  if (result.workers) {
464
539
  const w = result.workers;
465
- lines.push(`
466
- Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`);
540
+ lines.push(
541
+ `
542
+ Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`
543
+ );
467
544
  for (const worker of w.workers) {
468
545
  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}`);
546
+ lines.push(
547
+ ` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`
548
+ );
470
549
  }
471
550
  }
472
551
  if (result.osProcesses.length > 0) {
473
552
  lines.push(`
474
553
  OS Processes (${result.osProcesses.length} total)`);
475
554
  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)}`);
555
+ lines.push(
556
+ ` 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)}`
557
+ );
477
558
  }
478
559
  }
479
560
  if (result.orphans.length > 0) {
480
561
  lines.push(`
481
562
  \u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
482
563
  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}`);
564
+ lines.push(
565
+ ` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed}`
566
+ );
484
567
  lines.push(` Started: ${p.started}`);
485
568
  lines.push(` Command: ${p.cmdline || p.command}`);
486
569
  }
@@ -520,14 +603,14 @@ function parseArgs(argv) {
520
603
  if (args[0] === "live") args.shift();
521
604
  let i = 0;
522
605
  while (i < args.length) {
523
- const arg = args[i];
606
+ const arg = args[i] ?? "";
524
607
  if (arg === "--help" || arg === "-h") {
525
608
  printUsage();
526
609
  process.exit(0);
527
610
  } else if (arg === "--refresh" || arg === "-r") {
528
611
  i++;
529
612
  const v = parseInt(args[i] ?? "", 10);
530
- if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
613
+ if (!Number.isNaN(v) && v > 0) config.refreshMs = v * 1e3;
531
614
  i++;
532
615
  } else if (arg === "--recursive" || arg === "-R") {
533
616
  config.recursive = true;
@@ -608,12 +691,22 @@ function scanFiles(dirs, recursive) {
608
691
  if (!stat.isFile()) continue;
609
692
  if (f.endsWith(".json")) {
610
693
  seen.add(fp);
611
- const entry = { filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" };
694
+ const entry = {
695
+ filename: f,
696
+ path: fp,
697
+ mtime: stat.mtime.getTime(),
698
+ ext: ".json"
699
+ };
612
700
  results.push(entry);
613
701
  dirResults.push(entry);
614
702
  } else if (f.endsWith(".jsonl")) {
615
703
  seen.add(fp);
616
- const entry = { filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" };
704
+ const entry = {
705
+ filename: f,
706
+ path: fp,
707
+ mtime: stat.mtime.getTime(),
708
+ ext: ".jsonl"
709
+ };
617
710
  results.push(entry);
618
711
  dirResults.push(entry);
619
712
  }
@@ -675,7 +768,7 @@ function findTimestamp(obj) {
675
768
  if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
676
769
  if (typeof val === "string") {
677
770
  const d = Date.parse(val);
678
- if (!isNaN(d)) return d;
771
+ if (!Number.isNaN(d)) return d;
679
772
  }
680
773
  }
681
774
  return 0;
@@ -707,7 +800,7 @@ function extractDetail(obj) {
707
800
  }
708
801
  return parts.join(" | ") || "";
709
802
  }
710
- function tryLoadTrace(fp, raw) {
803
+ function tryLoadTrace(_fp, raw) {
711
804
  if (typeof raw !== "object" || raw === null) return null;
712
805
  const obj = raw;
713
806
  if (!("nodes" in obj)) return null;
@@ -817,7 +910,7 @@ function processJsonlFile(file) {
817
910
  if (!content) return [];
818
911
  const lines = content.split("\n");
819
912
  const lineCount = lines.length;
820
- const lastObj = JSON.parse(lines[lines.length - 1]);
913
+ const lastObj = JSON.parse(lines[lines.length - 1] ?? "{}");
821
914
  const name = lastObj.jobId ?? lastObj.agentId ?? lastObj.name ?? lastObj.id ?? nameFromFile(file.filename);
822
915
  if (lastObj.action !== void 0 || lastObj.jobId !== void 0) {
823
916
  const status2 = findStatus(lastObj);
@@ -913,7 +1006,7 @@ function processJsonlFile(file) {
913
1006
  }
914
1007
  const parts = [];
915
1008
  if (model) {
916
- const shortModel = model.includes("/") ? model.split("/").pop() : model;
1009
+ const shortModel = model.includes("/") ? model.split("/").pop() ?? model : model;
917
1010
  parts.push(shortModel.slice(0, 20));
918
1011
  }
919
1012
  if (toolCalls.length > 0) {
@@ -958,7 +1051,8 @@ function writeLine(lines, text) {
958
1051
  }
959
1052
  function flushLines(lines) {
960
1053
  process.stdout.write("\x1B[H");
961
- process.stdout.write(lines.join("\n") + "\n");
1054
+ process.stdout.write(`${lines.join("\n")}
1055
+ `);
962
1056
  process.stdout.write("\x1B[J");
963
1057
  }
964
1058
  var prevFileCount = 0;
@@ -1052,7 +1146,7 @@ function render(config) {
1052
1146
  const status = fail > 0 ? "error" : running > 0 ? "running" : ok > 0 ? "ok" : "unknown";
1053
1147
  groups.push({
1054
1148
  name: groupName,
1055
- source: records[0].source,
1149
+ source: records[0]?.source ?? "trace",
1056
1150
  status,
1057
1151
  lastTs,
1058
1152
  detail: `${records.length} agents`,
@@ -1077,15 +1171,15 @@ function render(config) {
1077
1171
  if (age > 36e5 || age < 0) continue;
1078
1172
  const idx = 11 - Math.floor(age / 3e5);
1079
1173
  if (idx >= 0 && idx < 12) {
1080
- buckets[idx]++;
1081
- if (r.status === "error") failBuckets[idx]++;
1174
+ buckets[idx] = (buckets[idx] ?? 0) + 1;
1175
+ if (r.status === "error") failBuckets[idx] = (failBuckets[idx] ?? 0) + 1;
1082
1176
  }
1083
1177
  }
1084
1178
  const maxBucket = Math.max(...buckets, 1);
1085
1179
  const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
1086
1180
  const spark = buckets.map((v, i) => {
1087
1181
  const level = Math.round(v / maxBucket * 8);
1088
- return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
1182
+ return ((failBuckets[i] ?? 0) > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
1089
1183
  }).join("");
1090
1184
  let auditResult = null;
1091
1185
  if (now - lastAuditTime > 1e4) {
@@ -1098,8 +1192,10 @@ function render(config) {
1098
1192
  cachedAuditResult = auditResult;
1099
1193
  lastAuditTime = now;
1100
1194
  } catch (err) {
1101
- process.stderr.write(`[agentflow] process audit error: ${err instanceof Error ? err.message : err}
1102
- `);
1195
+ process.stderr.write(
1196
+ `[agentflow] process audit error: ${err instanceof Error ? err.message : err}
1197
+ `
1198
+ );
1103
1199
  }
1104
1200
  }
1105
1201
  } else {
@@ -1156,7 +1252,7 @@ function render(config) {
1156
1252
  return new Date(ts).toLocaleTimeString();
1157
1253
  }
1158
1254
  function truncate(s, max) {
1159
- return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1255
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
1160
1256
  }
1161
1257
  const termWidth = process.stdout.columns || 120;
1162
1258
  const detailWidth = Math.max(20, termWidth - 60);
@@ -1197,7 +1293,8 @@ function render(config) {
1197
1293
  if (ar.systemd) {
1198
1294
  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
1295
  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}`;
1296
+ if (ar.systemd.restarts > 0)
1297
+ sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
1201
1298
  }
1202
1299
  let pidLabel = "";
1203
1300
  if (ar.pidFile?.pid) {
@@ -1206,7 +1303,10 @@ function render(config) {
1206
1303
  }
1207
1304
  writeLine(L, "");
1208
1305
  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}`}`);
1306
+ writeLine(
1307
+ L,
1308
+ ` ${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}`}`
1309
+ );
1210
1310
  if (workerParts.length > 0) {
1211
1311
  writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
1212
1312
  }
@@ -1218,7 +1318,10 @@ function render(config) {
1218
1318
  if (ar.orphans.length > 0) {
1219
1319
  for (const o of ar.orphans.slice(0, 5)) {
1220
1320
  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}`);
1321
+ writeLine(
1322
+ L,
1323
+ ` ${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}`
1324
+ );
1222
1325
  }
1223
1326
  if (ar.orphans.length > 5) {
1224
1327
  writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
@@ -1292,7 +1395,7 @@ function render(config) {
1292
1395
  for (let i = 0; i < Math.min(tree.length, 6); i++) {
1293
1396
  const tg = tree[i];
1294
1397
  const depth = getDistDepth(dt, tg.spanId);
1295
- const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
1398
+ const indent = ` ${"\u2502 ".repeat(Math.max(0, depth - 1))}`;
1296
1399
  const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
1297
1400
  const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1298
1401
  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 +1416,7 @@ function render(config) {
1313
1416
  const t = new Date(r.lastActive).toLocaleTimeString();
1314
1417
  const agent = truncate(r.id, 26).padEnd(26);
1315
1418
  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";
1419
+ const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
1317
1420
  const det = truncate(r.detail, detailWidth);
1318
1421
  writeLine(
1319
1422
  L,
@@ -1327,7 +1430,7 @@ function render(config) {
1327
1430
  for (const d of config.dirs) writeLine(L, ` ${C.dim} ${d}${C.reset}`);
1328
1431
  }
1329
1432
  writeLine(L, "");
1330
- const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
1433
+ const dirLabel = config.dirs.length === 1 ? config.dirs[0] ?? "" : `${config.dirs.length} directories`;
1331
1434
  writeLine(L, ` ${C.dim}Watching: ${dirLabel}${C.reset}`);
1332
1435
  writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
1333
1436
  flushLines(L);
@@ -1378,7 +1481,7 @@ function startLive(argv) {
1378
1481
  };
1379
1482
  process.on("SIGINT", () => {
1380
1483
  cleanup();
1381
- console.log(C.dim + "Monitor stopped." + C.reset);
1484
+ console.log(`${C.dim}Monitor stopped.${C.reset}`);
1382
1485
  process.exit(0);
1383
1486
  });
1384
1487
  process.on("SIGTERM", () => {
@@ -1642,7 +1745,7 @@ function agentIdFromFilename(filePath) {
1642
1745
  const cleaned = base.replace(/-state$/, "");
1643
1746
  return `alfred-${cleaned}`;
1644
1747
  }
1645
- function deriveAgentId(command) {
1748
+ function deriveAgentId(_command) {
1646
1749
  return "orchestrator";
1647
1750
  }
1648
1751
  function fileTimestamp() {
@@ -1752,14 +1855,18 @@ async function runTraced(config) {
1752
1855
  const filename = `${graph.agentId}-${ts}.json`;
1753
1856
  const outPath = join3(resolvedTracesDir, filename);
1754
1857
  const resolvedOut = resolve2(outPath);
1755
- if (!resolvedOut.startsWith(resolvedTracesDir + "/") && resolvedOut !== resolvedTracesDir) {
1756
- throw new Error(`Path traversal detected: agentId "${graph.agentId}" escapes traces directory`);
1858
+ if (!resolvedOut.startsWith(`${resolvedTracesDir}/`) && resolvedOut !== resolvedTracesDir) {
1859
+ throw new Error(
1860
+ `Path traversal detected: agentId "${graph.agentId}" escapes traces directory`
1861
+ );
1757
1862
  }
1758
1863
  writeFileSync(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
1759
1864
  tracePaths.push(outPath);
1760
1865
  }
1761
1866
  if (tracePaths.length > 0) {
1762
- console.log(`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`);
1867
+ console.log(
1868
+ `\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`
1869
+ );
1763
1870
  }
1764
1871
  return {
1765
1872
  exitCode,
@@ -1805,7 +1912,7 @@ function createTraceStore(dir) {
1805
1912
  const filePath = join4(dir, `${graph.id}.json`);
1806
1913
  const resolvedBase = resolve3(dir);
1807
1914
  const resolvedPath = resolve3(filePath);
1808
- if (!resolvedPath.startsWith(resolvedBase + "/") && resolvedPath !== resolvedBase) {
1915
+ if (!resolvedPath.startsWith(`${resolvedBase}/`) && resolvedPath !== resolvedBase) {
1809
1916
  throw new Error(`Path traversal detected: "${graph.id}" escapes base directory`);
1810
1917
  }
1811
1918
  await writeFile(filePath, JSON.stringify(json, null, 2), "utf-8");
@@ -1935,7 +2042,8 @@ function toAsciiTree(graph) {
1935
2042
  const children = getChildren(graph, nodeId);
1936
2043
  const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
1937
2044
  for (let i = 0; i < children.length; i++) {
1938
- renderNode(children[i].id, childPrefix, i === children.length - 1, false);
2045
+ const childId = children[i]?.id;
2046
+ if (childId) renderNode(childId, childPrefix, i === children.length - 1, false);
1939
2047
  }
1940
2048
  }
1941
2049
  renderNode(graph.rootNodeId, "", true, true);
@@ -2134,10 +2242,10 @@ function parseDuration(input) {
2134
2242
  const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i);
2135
2243
  if (!match) {
2136
2244
  const n = parseInt(input, 10);
2137
- return isNaN(n) ? 0 : n * 1e3;
2245
+ return Number.isNaN(n) ? 0 : n * 1e3;
2138
2246
  }
2139
- const value = parseFloat(match[1]);
2140
- switch (match[2].toLowerCase()) {
2247
+ const value = parseFloat(match[1] ?? "0");
2248
+ switch (match[2]?.toLowerCase()) {
2141
2249
  case "s":
2142
2250
  return value * 1e3;
2143
2251
  case "m":
@@ -2164,7 +2272,7 @@ function loadWatchState(filePath) {
2164
2272
  }
2165
2273
  }
2166
2274
  function saveWatchState(filePath, state) {
2167
- const tmp = filePath + ".tmp";
2275
+ const tmp = `${filePath}.tmp`;
2168
2276
  try {
2169
2277
  writeFileSync2(tmp, JSON.stringify(state, null, 2), "utf8");
2170
2278
  renameSync(tmp, filePath);
@@ -2180,12 +2288,12 @@ function estimateInterval(history) {
2180
2288
  const sorted = [...history].sort((a, b) => a - b);
2181
2289
  const deltas = [];
2182
2290
  for (let i = 1; i < sorted.length; i++) {
2183
- const d = sorted[i] - sorted[i - 1];
2291
+ const d = (sorted[i] ?? 0) - (sorted[i - 1] ?? 0);
2184
2292
  if (d > 0) deltas.push(d);
2185
2293
  }
2186
2294
  if (deltas.length === 0) return 0;
2187
2295
  deltas.sort((a, b) => a - b);
2188
- return deltas[Math.floor(deltas.length / 2)];
2296
+ return deltas[Math.floor(deltas.length / 2)] ?? 0;
2189
2297
  }
2190
2298
  function detectTransitions(previous, currentRecords, config, now) {
2191
2299
  const alerts = [];
@@ -2342,7 +2450,7 @@ function parseWatchArgs(argv) {
2342
2450
  if (args[0] === "watch") args.shift();
2343
2451
  let i = 0;
2344
2452
  while (i < args.length) {
2345
- const arg = args[i];
2453
+ const arg = args[i] ?? "";
2346
2454
  if (arg === "--help" || arg === "-h") {
2347
2455
  printWatchUsage();
2348
2456
  process.exit(0);
@@ -2365,8 +2473,8 @@ function parseWatchArgs(argv) {
2365
2473
  i++;
2366
2474
  const val = args[i] ?? "";
2367
2475
  if (val === "telegram") {
2368
- const botToken = process.env["AGENTFLOW_TELEGRAM_BOT_TOKEN"] ?? "";
2369
- const chatId = process.env["AGENTFLOW_TELEGRAM_CHAT_ID"] ?? "";
2476
+ const botToken = process.env.AGENTFLOW_TELEGRAM_BOT_TOKEN ?? "";
2477
+ const chatId = process.env.AGENTFLOW_TELEGRAM_CHAT_ID ?? "";
2370
2478
  if (botToken && chatId) {
2371
2479
  notifyChannels.push({ type: "telegram", botToken, chatId });
2372
2480
  } else {
@@ -2383,7 +2491,7 @@ function parseWatchArgs(argv) {
2383
2491
  } else if (arg === "--poll") {
2384
2492
  i++;
2385
2493
  const v = parseInt(args[i] ?? "", 10);
2386
- if (!isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
2494
+ if (!Number.isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
2387
2495
  i++;
2388
2496
  } else if (arg === "--cooldown") {
2389
2497
  i++;
@@ -2411,7 +2519,7 @@ function parseWatchArgs(argv) {
2411
2519
  }
2412
2520
  notifyChannels.unshift({ type: "stdout" });
2413
2521
  if (!stateFilePath) {
2414
- stateFilePath = join5(dirs[0], ".agentflow-watch-state.json");
2522
+ stateFilePath = join5(dirs[0] ?? ".", ".agentflow-watch-state.json");
2415
2523
  }
2416
2524
  return {
2417
2525
  dirs,
@@ -2499,7 +2607,7 @@ agentflow watch started`);
2499
2607
  console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
2500
2608
  console.log(` Alert on: ${condLabels.join(", ")}`);
2501
2609
  console.log(
2502
- ` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`
2610
+ ` Notify: stdout${channelLabels.length > 0 ? `, ${channelLabels.join(", ")}` : ""}`
2503
2611
  );
2504
2612
  console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
2505
2613
  console.log(` State: ${config.stateFilePath}`);
@@ -2572,6 +2680,7 @@ export {
2572
2680
  stitchTrace,
2573
2681
  getTraceTree,
2574
2682
  discoverProcessConfig,
2683
+ discoverAllProcessConfigs,
2575
2684
  auditProcesses,
2576
2685
  formatAuditReport,
2577
2686
  startLive,