agentflow-core 0.8.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.
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # agentflow-core
2
+
3
+ **v0.8.0** — Monitor any AI agent system. Auto-detects failures, sends alerts, audits OS processes. Zero config, zero dependencies.
4
+
5
+ Works with any agent framework: OpenAI, Anthropic, LangChain, CrewAI, AutoGen, or hand-rolled agents.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install agentflow-core
11
+ ```
12
+
13
+ Requires Node.js >= 20.
14
+
15
+ ## Quick start
16
+
17
+ ### Step 1 — Build an execution graph
18
+
19
+ Wrap your agent's work with `createGraphBuilder`. Each logical unit of work is a node; the graph captures the full execution tree.
20
+
21
+ ```ts
22
+ import { createGraphBuilder, getStats } from 'agentflow-core';
23
+
24
+ const builder = createGraphBuilder({ agentId: 'my-agent' });
25
+
26
+ const rootId = builder.startNode({ type: 'agent', name: 'main' });
27
+ const toolId = builder.startNode({ type: 'tool', name: 'fetch', parentId: rootId });
28
+ builder.endNode(toolId);
29
+ builder.endNode(rootId);
30
+
31
+ const graph = builder.build();
32
+ console.log(getStats(graph));
33
+ // { totalNodes: 2, failedNodes: 0, duration: 42, status: 'completed' }
34
+ ```
35
+
36
+ ### Step 2 — Mine patterns across runs
37
+
38
+ Accumulate graphs over time and use process mining to find variants, bottlenecks, and conformance drift.
39
+
40
+ ```ts
41
+ import { discoverProcess, findVariants, getBottlenecks, checkConformance } from 'agentflow-core';
42
+
43
+ const model = discoverProcess(graphs); // build a process model from observed runs
44
+ const variants = findVariants(graphs); // group runs by their execution path
45
+ const bottlenecks = getBottlenecks(graphs); // rank nodes by cumulative wait time
46
+ const report = checkConformance(graph, model); // score a new run against the baseline
47
+ ```
48
+
49
+ ### Step 3 — Add guards
50
+
51
+ Guards detect runaway loops, spawn explosions, and policy violations at runtime. Wrap your builder with `withGuards` to activate them.
52
+
53
+ ```ts
54
+ import { createGraphBuilder, withGuards, createSomaPolicySource } from 'agentflow-core';
55
+
56
+ const raw = createGraphBuilder({ agentId: 'my-agent' });
57
+ const guarded = withGuards(raw, {
58
+ maxDepth: 8,
59
+ maxReasoningSteps: 20,
60
+ onViolation: 'warn', // 'warn' | 'error' | 'abort'
61
+ policySource: myPolicySource, // optional: adaptive thresholds from Soma
62
+ });
63
+ ```
64
+
65
+ ## API highlights
66
+
67
+ | Export | Kind | Description |
68
+ |---|---|---|
69
+ | `createGraphBuilder` | factory | Build and mutate an execution graph during a run |
70
+ | `withGuards` | wrapper | Add runtime guard checks to any GraphBuilder |
71
+ | `checkGuards` | fn | Pure guard check on a graph snapshot |
72
+ | `getStats` | fn | Summary stats: node counts, status, duration |
73
+ | `getCriticalPath` | fn | Longest path through the graph by duration |
74
+ | `getFailures` | fn | All failed nodes with error metadata |
75
+ | `getHungNodes` | fn | Nodes that are running beyond their timeout |
76
+ | `discoverProcess` | fn | Build a process model from a run corpus |
77
+ | `findVariants` | fn | Group runs by execution path signature |
78
+ | `getBottlenecks` | fn | Rank nodes by cumulative elapsed time |
79
+ | `checkConformance` | fn | Score a run against a reference process model |
80
+ | `createInsightEngine` | factory | Tier-2 LLM analysis: anomaly, failure, and fix prompts |
81
+ | `createTraceStore` | factory | Persist and load graphs from disk |
82
+ | `createEventEmitter` | factory | Emit structured events during execution |
83
+ | `createJsonEventWriter` | factory | Write events to newline-delimited JSON |
84
+ | `createSomaEventWriter` | factory | Write events to a Soma inbox for ingestion |
85
+ | `createKnowledgeStore` | factory | Lightweight in-process key/value knowledge store |
86
+ | `createPolicySource` | factory | Static policy source for guard thresholds |
87
+ | `stitchTrace` | fn | Reconstruct a distributed trace from span events |
88
+ | `startLive` | fn | Live terminal monitor for a running agent |
89
+ | `startWatch` | fn | Headless watcher with alerting via notify channels |
90
+ | `auditProcesses` | fn | Audit OS processes, PIDs, and systemd units |
91
+ | `runTraced` | fn | Run a shell command with full execution tracing |
92
+ | `toAsciiTree` | fn | Render a graph as an ASCII tree |
93
+ | `toTimeline` | fn | Render a graph as a text timeline |
94
+
95
+ Full type definitions are bundled. All functions are pure unless noted as factory.
96
+
97
+ ## Docs
98
+
99
+ [https://github.com/ClemenceChee/AgentFlow#readme](https://github.com/ClemenceChee/AgentFlow#readme)
@@ -25,7 +25,8 @@ function loadGraph(input) {
25
25
  events: raw.events ?? [],
26
26
  traceId: raw.traceId,
27
27
  spanId: raw.spanId,
28
- parentSpanId: raw.parentSpanId
28
+ parentSpanId: raw.parentSpanId,
29
+ metadata: raw.metadata ?? {}
29
30
  };
30
31
  }
31
32
  function graphToJson(graph) {
@@ -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) {
@@ -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) {
@@ -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 = {};
@@ -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);
@@ -566,7 +603,7 @@ function parseArgs(argv) {
566
603
  if (args[0] === "live") args.shift();
567
604
  let i = 0;
568
605
  while (i < args.length) {
569
- const arg = args[i];
606
+ const arg = args[i] ?? "";
570
607
  if (arg === "--help" || arg === "-h") {
571
608
  printUsage();
572
609
  process.exit(0);
@@ -873,7 +910,7 @@ function processJsonlFile(file) {
873
910
  if (!content) return [];
874
911
  const lines = content.split("\n");
875
912
  const lineCount = lines.length;
876
- const lastObj = JSON.parse(lines[lines.length - 1]);
913
+ const lastObj = JSON.parse(lines[lines.length - 1] ?? "{}");
877
914
  const name = lastObj.jobId ?? lastObj.agentId ?? lastObj.name ?? lastObj.id ?? nameFromFile(file.filename);
878
915
  if (lastObj.action !== void 0 || lastObj.jobId !== void 0) {
879
916
  const status2 = findStatus(lastObj);
@@ -969,7 +1006,7 @@ function processJsonlFile(file) {
969
1006
  }
970
1007
  const parts = [];
971
1008
  if (model) {
972
- const shortModel = model.includes("/") ? model.split("/").pop() : model;
1009
+ const shortModel = model.includes("/") ? model.split("/").pop() ?? model : model;
973
1010
  parts.push(shortModel.slice(0, 20));
974
1011
  }
975
1012
  if (toolCalls.length > 0) {
@@ -1134,15 +1171,15 @@ function render(config) {
1134
1171
  if (age > 36e5 || age < 0) continue;
1135
1172
  const idx = 11 - Math.floor(age / 3e5);
1136
1173
  if (idx >= 0 && idx < 12) {
1137
- buckets[idx]++;
1138
- if (r.status === "error") failBuckets[idx]++;
1174
+ buckets[idx] = (buckets[idx] ?? 0) + 1;
1175
+ if (r.status === "error") failBuckets[idx] = (failBuckets[idx] ?? 0) + 1;
1139
1176
  }
1140
1177
  }
1141
1178
  const maxBucket = Math.max(...buckets, 1);
1142
1179
  const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
1143
1180
  const spark = buckets.map((v, i) => {
1144
1181
  const level = Math.round(v / maxBucket * 8);
1145
- 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;
1146
1183
  }).join("");
1147
1184
  let auditResult = null;
1148
1185
  if (now - lastAuditTime > 1e4) {
@@ -1393,7 +1430,7 @@ function render(config) {
1393
1430
  for (const d of config.dirs) writeLine(L, ` ${C.dim} ${d}${C.reset}`);
1394
1431
  }
1395
1432
  writeLine(L, "");
1396
- 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`;
1397
1434
  writeLine(L, ` ${C.dim}Watching: ${dirLabel}${C.reset}`);
1398
1435
  writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
1399
1436
  flushLines(L);
@@ -2207,7 +2244,7 @@ function parseDuration(input) {
2207
2244
  const n = parseInt(input, 10);
2208
2245
  return Number.isNaN(n) ? 0 : n * 1e3;
2209
2246
  }
2210
- const value = parseFloat(match[1]);
2247
+ const value = parseFloat(match[1] ?? "0");
2211
2248
  switch (match[2]?.toLowerCase()) {
2212
2249
  case "s":
2213
2250
  return value * 1e3;
@@ -2251,12 +2288,12 @@ function estimateInterval(history) {
2251
2288
  const sorted = [...history].sort((a, b) => a - b);
2252
2289
  const deltas = [];
2253
2290
  for (let i = 1; i < sorted.length; i++) {
2254
- const d = sorted[i] - sorted[i - 1];
2291
+ const d = (sorted[i] ?? 0) - (sorted[i - 1] ?? 0);
2255
2292
  if (d > 0) deltas.push(d);
2256
2293
  }
2257
2294
  if (deltas.length === 0) return 0;
2258
2295
  deltas.sort((a, b) => a - b);
2259
- return deltas[Math.floor(deltas.length / 2)];
2296
+ return deltas[Math.floor(deltas.length / 2)] ?? 0;
2260
2297
  }
2261
2298
  function detectTransitions(previous, currentRecords, config, now) {
2262
2299
  const alerts = [];
@@ -2413,7 +2450,7 @@ function parseWatchArgs(argv) {
2413
2450
  if (args[0] === "watch") args.shift();
2414
2451
  let i = 0;
2415
2452
  while (i < args.length) {
2416
- const arg = args[i];
2453
+ const arg = args[i] ?? "";
2417
2454
  if (arg === "--help" || arg === "-h") {
2418
2455
  printWatchUsage();
2419
2456
  process.exit(0);
@@ -2482,7 +2519,7 @@ function parseWatchArgs(argv) {
2482
2519
  }
2483
2520
  notifyChannels.unshift({ type: "stdout" });
2484
2521
  if (!stateFilePath) {
2485
- stateFilePath = join5(dirs[0], ".agentflow-watch-state.json");
2522
+ stateFilePath = join5(dirs[0] ?? ".", ".agentflow-watch-state.json");
2486
2523
  }
2487
2524
  return {
2488
2525
  dirs,
@@ -2643,6 +2680,7 @@ export {
2643
2680
  stitchTrace,
2644
2681
  getTraceTree,
2645
2682
  discoverProcessConfig,
2683
+ discoverAllProcessConfigs,
2646
2684
  auditProcesses,
2647
2685
  formatAuditReport,
2648
2686
  startLive,
package/dist/cli.cjs CHANGED
@@ -62,7 +62,8 @@ function loadGraph(input) {
62
62
  events: raw.events ?? [],
63
63
  traceId: raw.traceId,
64
64
  spanId: raw.spanId,
65
- parentSpanId: raw.parentSpanId
65
+ parentSpanId: raw.parentSpanId,
66
+ metadata: raw.metadata ?? {}
66
67
  };
67
68
  }
68
69
  function graphToJson(graph) {
@@ -303,8 +304,15 @@ function auditSystemd(config) {
303
304
  if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
304
305
  const unit = config.systemdUnit;
305
306
  try {
306
- const raw = (0, import_node_child_process.execSync)(
307
- `systemctl --user show ${unit} --property=ActiveState,SubState,MainPID,NRestarts,Result --no-pager 2>/dev/null`,
307
+ const raw = (0, import_node_child_process.execFileSync)(
308
+ "systemctl",
309
+ [
310
+ "--user",
311
+ "show",
312
+ unit,
313
+ "--property=ActiveState,SubState,MainPID,NRestarts,Result",
314
+ "--no-pager"
315
+ ],
308
316
  { encoding: "utf8", timeout: 5e3 }
309
317
  );
310
318
  const props = {};
@@ -395,9 +403,11 @@ function getOsProcesses(processName) {
395
403
  }
396
404
  }
397
405
  function discoverProcessConfig(dirs) {
398
- let pidFile;
399
- let workersFile;
400
- let processName = "";
406
+ const configs = discoverAllProcessConfigs(dirs);
407
+ return configs.length > 0 ? configs[0] ?? null : null;
408
+ }
409
+ function discoverAllProcessConfigs(dirs) {
410
+ const configs = /* @__PURE__ */ new Map();
401
411
  for (const dir of dirs) {
402
412
  if (!(0, import_node_fs.existsSync)(dir)) continue;
403
413
  let entries;
@@ -413,35 +423,63 @@ function discoverProcessConfig(dirs) {
413
423
  } catch {
414
424
  continue;
415
425
  }
416
- if (f.endsWith(".pid") && !pidFile) {
417
- pidFile = fp;
418
- if (!processName) {
419
- processName = (0, import_node_path.basename)(f, ".pid");
426
+ if (f.endsWith(".pid")) {
427
+ const name = (0, import_node_path.basename)(f, ".pid");
428
+ if (!configs.has(name)) {
429
+ configs.set(name, { processName: name });
420
430
  }
431
+ const cfg = configs.get(name) ?? { processName: name };
432
+ if (!cfg.pidFile) cfg.pidFile = fp;
421
433
  }
422
- if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
423
- workersFile = fp;
424
- if (!processName && f !== "workers.json") {
425
- processName = (0, import_node_path.basename)(f, "-workers.json");
434
+ if (f === "workers.json" || f.endsWith("-workers.json")) {
435
+ const name = f === "workers.json" ? "" : (0, import_node_path.basename)(f, "-workers.json");
436
+ if (name && !configs.has(name)) {
437
+ configs.set(name, { processName: name });
438
+ }
439
+ if (name) {
440
+ const cfg = configs.get(name) ?? { processName: name };
441
+ if (!cfg.workersFile) cfg.workersFile = fp;
426
442
  }
427
443
  }
428
444
  }
429
445
  }
430
- if (!processName && !pidFile && !workersFile) return null;
431
- if (!processName) processName = "agent";
432
- let systemdUnit;
433
446
  try {
434
- const unitName = `${processName}.service`;
435
- const result = (0, import_node_child_process.execSync)(
436
- `systemctl --user show ${unitName} --property=LoadState --no-pager 2>/dev/null`,
437
- { encoding: "utf8", timeout: 3e3 }
447
+ const raw = (0, import_node_child_process.execSync)(
448
+ "systemctl --user list-units --type=service --all --no-legend --no-pager 2>/dev/null",
449
+ { encoding: "utf8", timeout: 5e3 }
438
450
  );
439
- if (result.includes("LoadState=loaded")) {
440
- systemdUnit = unitName;
451
+ for (const line of raw.trim().split("\n")) {
452
+ if (!line.trim()) continue;
453
+ const parts = line.trim().split(/\s+/);
454
+ const unitName = parts[0] ?? "";
455
+ const loadState = parts[1] ?? "";
456
+ if (!unitName.endsWith(".service") || loadState !== "loaded") continue;
457
+ const name = unitName.replace(".service", "");
458
+ if (/^(dbus|gpg-agent|dirmngr|keyboxd|snapd\.|pk-|launchpadlib-)/.test(name)) continue;
459
+ if (!configs.has(name)) {
460
+ configs.set(name, { processName: name });
461
+ }
462
+ const cfg = configs.get(name) ?? { processName: name };
463
+ cfg.systemdUnit = unitName;
441
464
  }
442
465
  } catch {
443
466
  }
444
- return { processName, pidFile, workersFile, systemdUnit };
467
+ for (const cfg of configs.values()) {
468
+ if (cfg.systemdUnit !== void 0) continue;
469
+ try {
470
+ const unitName = `${cfg.processName}.service`;
471
+ const result = (0, import_node_child_process.execFileSync)(
472
+ "systemctl",
473
+ ["--user", "show", unitName, "--property=LoadState", "--no-pager"],
474
+ { encoding: "utf8", timeout: 3e3 }
475
+ );
476
+ if (result.includes("LoadState=loaded")) {
477
+ cfg.systemdUnit = unitName;
478
+ }
479
+ } catch {
480
+ }
481
+ }
482
+ return [...configs.values()];
445
483
  }
446
484
  function auditProcesses(config) {
447
485
  const pidFile = auditPidFile(config);
@@ -603,7 +641,7 @@ function parseArgs(argv) {
603
641
  if (args[0] === "live") args.shift();
604
642
  let i = 0;
605
643
  while (i < args.length) {
606
- const arg = args[i];
644
+ const arg = args[i] ?? "";
607
645
  if (arg === "--help" || arg === "-h") {
608
646
  printUsage();
609
647
  process.exit(0);
@@ -910,7 +948,7 @@ function processJsonlFile(file) {
910
948
  if (!content) return [];
911
949
  const lines = content.split("\n");
912
950
  const lineCount = lines.length;
913
- const lastObj = JSON.parse(lines[lines.length - 1]);
951
+ const lastObj = JSON.parse(lines[lines.length - 1] ?? "{}");
914
952
  const name = lastObj.jobId ?? lastObj.agentId ?? lastObj.name ?? lastObj.id ?? nameFromFile(file.filename);
915
953
  if (lastObj.action !== void 0 || lastObj.jobId !== void 0) {
916
954
  const status2 = findStatus(lastObj);
@@ -1006,7 +1044,7 @@ function processJsonlFile(file) {
1006
1044
  }
1007
1045
  const parts = [];
1008
1046
  if (model) {
1009
- const shortModel = model.includes("/") ? model.split("/").pop() : model;
1047
+ const shortModel = model.includes("/") ? model.split("/").pop() ?? model : model;
1010
1048
  parts.push(shortModel.slice(0, 20));
1011
1049
  }
1012
1050
  if (toolCalls.length > 0) {
@@ -1171,15 +1209,15 @@ function render(config) {
1171
1209
  if (age > 36e5 || age < 0) continue;
1172
1210
  const idx = 11 - Math.floor(age / 3e5);
1173
1211
  if (idx >= 0 && idx < 12) {
1174
- buckets[idx]++;
1175
- if (r.status === "error") failBuckets[idx]++;
1212
+ buckets[idx] = (buckets[idx] ?? 0) + 1;
1213
+ if (r.status === "error") failBuckets[idx] = (failBuckets[idx] ?? 0) + 1;
1176
1214
  }
1177
1215
  }
1178
1216
  const maxBucket = Math.max(...buckets, 1);
1179
1217
  const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
1180
1218
  const spark = buckets.map((v, i) => {
1181
1219
  const level = Math.round(v / maxBucket * 8);
1182
- return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
1220
+ return ((failBuckets[i] ?? 0) > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
1183
1221
  }).join("");
1184
1222
  let auditResult = null;
1185
1223
  if (now - lastAuditTime > 1e4) {
@@ -1430,7 +1468,7 @@ function render(config) {
1430
1468
  for (const d of config.dirs) writeLine(L, ` ${C.dim} ${d}${C.reset}`);
1431
1469
  }
1432
1470
  writeLine(L, "");
1433
- const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
1471
+ const dirLabel = config.dirs.length === 1 ? config.dirs[0] ?? "" : `${config.dirs.length} directories`;
1434
1472
  writeLine(L, ` ${C.dim}Watching: ${dirLabel}${C.reset}`);
1435
1473
  writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
1436
1474
  flushLines(L);
@@ -2440,7 +2478,7 @@ function parseDuration(input) {
2440
2478
  const n = parseInt(input, 10);
2441
2479
  return Number.isNaN(n) ? 0 : n * 1e3;
2442
2480
  }
2443
- const value = parseFloat(match[1]);
2481
+ const value = parseFloat(match[1] ?? "0");
2444
2482
  switch (match[2]?.toLowerCase()) {
2445
2483
  case "s":
2446
2484
  return value * 1e3;
@@ -2484,12 +2522,12 @@ function estimateInterval(history) {
2484
2522
  const sorted = [...history].sort((a, b) => a - b);
2485
2523
  const deltas = [];
2486
2524
  for (let i = 1; i < sorted.length; i++) {
2487
- const d = sorted[i] - sorted[i - 1];
2525
+ const d = (sorted[i] ?? 0) - (sorted[i - 1] ?? 0);
2488
2526
  if (d > 0) deltas.push(d);
2489
2527
  }
2490
2528
  if (deltas.length === 0) return 0;
2491
2529
  deltas.sort((a, b) => a - b);
2492
- return deltas[Math.floor(deltas.length / 2)];
2530
+ return deltas[Math.floor(deltas.length / 2)] ?? 0;
2493
2531
  }
2494
2532
  function detectTransitions(previous, currentRecords, config, now) {
2495
2533
  const alerts = [];
@@ -2646,7 +2684,7 @@ function parseWatchArgs(argv) {
2646
2684
  if (args[0] === "watch") args.shift();
2647
2685
  let i = 0;
2648
2686
  while (i < args.length) {
2649
- const arg = args[i];
2687
+ const arg = args[i] ?? "";
2650
2688
  if (arg === "--help" || arg === "-h") {
2651
2689
  printWatchUsage();
2652
2690
  process.exit(0);
@@ -2715,7 +2753,7 @@ function parseWatchArgs(argv) {
2715
2753
  }
2716
2754
  notifyChannels.unshift({ type: "stdout" });
2717
2755
  if (!stateFilePath) {
2718
- stateFilePath = (0, import_node_path6.join)(dirs[0], ".agentflow-watch-state.json");
2756
+ stateFilePath = (0, import_node_path6.join)(dirs[0] ?? ".", ".agentflow-watch-state.json");
2719
2757
  }
2720
2758
  return {
2721
2759
  dirs,
@@ -3036,7 +3074,7 @@ function parseAuditArgs(argv) {
3036
3074
  if (args[0] === "audit") args.shift();
3037
3075
  let i = 0;
3038
3076
  while (i < args.length) {
3039
- const arg = args[i];
3077
+ const arg = args[i] ?? "";
3040
3078
  if (arg === "--help" || arg === "-h") {
3041
3079
  printAuditUsage();
3042
3080
  process.exit(0);
@@ -3126,7 +3164,7 @@ function runAudit(argv) {
3126
3164
  async function main() {
3127
3165
  const argv = process.argv.slice(2);
3128
3166
  const knownCommands = ["run", "live", "watch", "trace", "audit"];
3129
- if (argv.length === 0 || !knownCommands.includes(argv[0]) && (argv.includes("--help") || argv.includes("-h"))) {
3167
+ if (argv.length === 0 || !knownCommands.includes(argv[0] ?? "") && (argv.includes("--help") || argv.includes("-h"))) {
3130
3168
  printHelp();
3131
3169
  process.exit(0);
3132
3170
  }
package/dist/cli.js CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  startWatch,
10
10
  toAsciiTree,
11
11
  toTimeline
12
- } from "./chunk-6X5HU5LB.js";
13
- import "./chunk-DY7YHFIB.js";
12
+ } from "./chunk-NVFWBTAZ.js";
13
+ import "./chunk-BYWLDTZK.js";
14
14
 
15
15
  // src/cli.ts
16
16
  import { basename, resolve as resolve2 } from "path";
@@ -95,7 +95,7 @@ async function traceShow(argv) {
95
95
  const { join } = await import("path");
96
96
  const fname = graphId.endsWith(".json") ? graphId : `${graphId}.json`;
97
97
  try {
98
- const { loadGraph } = await import("./loader-LYRR6LMM.js");
98
+ const { loadGraph } = await import("./loader-JMFEFI3Q.js");
99
99
  const content = await readFile(join(dir, fname), "utf-8");
100
100
  graph = loadGraph(content);
101
101
  } catch {
@@ -124,7 +124,7 @@ async function traceTimeline(argv) {
124
124
  const { join } = await import("path");
125
125
  const fname = graphId.endsWith(".json") ? graphId : `${graphId}.json`;
126
126
  try {
127
- const { loadGraph } = await import("./loader-LYRR6LMM.js");
127
+ const { loadGraph } = await import("./loader-JMFEFI3Q.js");
128
128
  const content = await readFile(join(dir, fname), "utf-8");
129
129
  graph = loadGraph(content);
130
130
  } catch {
@@ -381,7 +381,7 @@ function parseAuditArgs(argv) {
381
381
  if (args[0] === "audit") args.shift();
382
382
  let i = 0;
383
383
  while (i < args.length) {
384
- const arg = args[i];
384
+ const arg = args[i] ?? "";
385
385
  if (arg === "--help" || arg === "-h") {
386
386
  printAuditUsage();
387
387
  process.exit(0);
@@ -471,7 +471,7 @@ function runAudit(argv) {
471
471
  async function main() {
472
472
  const argv = process.argv.slice(2);
473
473
  const knownCommands = ["run", "live", "watch", "trace", "audit"];
474
- if (argv.length === 0 || !knownCommands.includes(argv[0]) && (argv.includes("--help") || argv.includes("-h"))) {
474
+ if (argv.length === 0 || !knownCommands.includes(argv[0] ?? "") && (argv.includes("--help") || argv.includes("-h"))) {
475
475
  printHelp();
476
476
  process.exit(0);
477
477
  }