agentflow-core 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ checkGuards: () => checkGuards,
23
24
  createGraphBuilder: () => createGraphBuilder,
25
+ createTraceStore: () => createTraceStore,
24
26
  findWaitingOn: () => findWaitingOn,
25
27
  getChildren: () => getChildren,
26
28
  getCriticalPath: () => getCriticalPath,
@@ -39,7 +41,10 @@ __export(index_exports, {
39
41
  runTraced: () => runTraced,
40
42
  startLive: () => startLive,
41
43
  startWatch: () => startWatch,
42
- stitchTrace: () => stitchTrace
44
+ stitchTrace: () => stitchTrace,
45
+ toAsciiTree: () => toAsciiTree,
46
+ toTimeline: () => toTimeline,
47
+ withGuards: () => withGuards
43
48
  });
44
49
  module.exports = __toCommonJS(index_exports);
45
50
 
@@ -269,213 +274,6 @@ function createGraphBuilder(config) {
269
274
  return builder;
270
275
  }
271
276
 
272
- // src/loader.ts
273
- function toNodesMap(raw) {
274
- if (raw instanceof Map) return raw;
275
- if (Array.isArray(raw)) {
276
- return new Map(raw);
277
- }
278
- if (raw !== null && typeof raw === "object") {
279
- return new Map(Object.entries(raw));
280
- }
281
- return /* @__PURE__ */ new Map();
282
- }
283
- function loadGraph(input) {
284
- const raw = typeof input === "string" ? JSON.parse(input) : input;
285
- const nodes = toNodesMap(raw.nodes);
286
- return {
287
- id: raw.id ?? "",
288
- rootNodeId: raw.rootNodeId ?? raw.rootId ?? "",
289
- nodes,
290
- edges: raw.edges ?? [],
291
- startTime: raw.startTime ?? 0,
292
- endTime: raw.endTime ?? null,
293
- status: raw.status ?? "completed",
294
- trigger: raw.trigger ?? "unknown",
295
- agentId: raw.agentId ?? "unknown",
296
- events: raw.events ?? [],
297
- traceId: raw.traceId,
298
- spanId: raw.spanId,
299
- parentSpanId: raw.parentSpanId
300
- };
301
- }
302
- function graphToJson(graph) {
303
- const nodesObj = {};
304
- for (const [id, node] of graph.nodes) {
305
- nodesObj[id] = node;
306
- }
307
- return {
308
- id: graph.id,
309
- rootNodeId: graph.rootNodeId,
310
- nodes: nodesObj,
311
- edges: graph.edges,
312
- startTime: graph.startTime,
313
- endTime: graph.endTime,
314
- status: graph.status,
315
- trigger: graph.trigger,
316
- agentId: graph.agentId,
317
- events: graph.events,
318
- traceId: graph.traceId,
319
- spanId: graph.spanId,
320
- parentSpanId: graph.parentSpanId
321
- };
322
- }
323
-
324
- // src/runner.ts
325
- var import_node_child_process = require("child_process");
326
- var import_node_fs = require("fs");
327
- var import_node_path = require("path");
328
- function globToRegex(pattern) {
329
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
330
- return new RegExp(`^${escaped}$`);
331
- }
332
- function snapshotDir(dir, patterns) {
333
- const result = /* @__PURE__ */ new Map();
334
- if (!(0, import_node_fs.existsSync)(dir)) return result;
335
- for (const entry of (0, import_node_fs.readdirSync)(dir)) {
336
- if (!patterns.some((re) => re.test(entry))) continue;
337
- const full = (0, import_node_path.join)(dir, entry);
338
- try {
339
- const stat = (0, import_node_fs.statSync)(full);
340
- if (stat.isFile()) {
341
- result.set(full, stat.mtimeMs);
342
- }
343
- } catch {
344
- }
345
- }
346
- return result;
347
- }
348
- function agentIdFromFilename(filePath) {
349
- const base = (0, import_node_path.basename)(filePath, ".json");
350
- const cleaned = base.replace(/-state$/, "");
351
- return `alfred-${cleaned}`;
352
- }
353
- function deriveAgentId(command) {
354
- return "orchestrator";
355
- }
356
- function fileTimestamp() {
357
- return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
358
- }
359
- async function runTraced(config) {
360
- const {
361
- command,
362
- agentId = deriveAgentId(command),
363
- trigger = "cli",
364
- tracesDir = "./traces",
365
- watchDirs = [],
366
- watchPatterns = ["*.json"]
367
- } = config;
368
- if (command.length === 0) {
369
- throw new Error("runTraced: command must not be empty");
370
- }
371
- const resolvedTracesDir = (0, import_node_path.resolve)(tracesDir);
372
- const patterns = watchPatterns.map(globToRegex);
373
- const orchestrator = createGraphBuilder({ agentId, trigger });
374
- const { traceId, spanId } = orchestrator.traceContext;
375
- const beforeSnapshots = /* @__PURE__ */ new Map();
376
- for (const dir of watchDirs) {
377
- beforeSnapshots.set(dir, snapshotDir(dir, patterns));
378
- }
379
- const rootId = orchestrator.startNode({ type: "agent", name: agentId });
380
- const dispatchId = orchestrator.startNode({
381
- type: "tool",
382
- name: "dispatch-command",
383
- parentId: rootId
384
- });
385
- orchestrator.updateState(dispatchId, { command: command.join(" ") });
386
- const monitorId = orchestrator.startNode({
387
- type: "tool",
388
- name: "state-monitor",
389
- parentId: rootId
390
- });
391
- orchestrator.updateState(monitorId, {
392
- watchDirs,
393
- watchPatterns
394
- });
395
- const startMs = Date.now();
396
- const execCmd = command[0] ?? "";
397
- const execArgs = command.slice(1);
398
- process.env.AGENTFLOW_TRACE_ID = traceId;
399
- process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
400
- const result = (0, import_node_child_process.spawnSync)(execCmd, execArgs, { stdio: "inherit" });
401
- delete process.env.AGENTFLOW_TRACE_ID;
402
- delete process.env.AGENTFLOW_PARENT_SPAN_ID;
403
- const exitCode = result.status ?? 1;
404
- const duration = (Date.now() - startMs) / 1e3;
405
- const stateChanges = [];
406
- for (const dir of watchDirs) {
407
- const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
408
- const after = snapshotDir(dir, patterns);
409
- for (const [filePath, mtime] of after) {
410
- const prevMtime = before.get(filePath);
411
- if (prevMtime === void 0 || mtime > prevMtime) {
412
- stateChanges.push(filePath);
413
- }
414
- }
415
- }
416
- orchestrator.updateState(monitorId, { stateChanges });
417
- orchestrator.endNode(monitorId);
418
- if (exitCode === 0) {
419
- orchestrator.endNode(dispatchId);
420
- } else {
421
- orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
422
- }
423
- orchestrator.updateState(rootId, {
424
- exitCode,
425
- duration,
426
- stateChanges
427
- });
428
- if (exitCode === 0) {
429
- orchestrator.endNode(rootId);
430
- } else {
431
- orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
432
- }
433
- const orchestratorGraph = orchestrator.build();
434
- const allGraphs = [orchestratorGraph];
435
- for (const filePath of stateChanges) {
436
- const childAgentId = agentIdFromFilename(filePath);
437
- const childBuilder = createGraphBuilder({
438
- agentId: childAgentId,
439
- trigger: "state-change",
440
- traceId,
441
- parentSpanId: spanId
442
- });
443
- const childRootId = childBuilder.startNode({
444
- type: "agent",
445
- name: childAgentId
446
- });
447
- childBuilder.updateState(childRootId, {
448
- stateFile: filePath,
449
- detectedBy: "runner-state-monitor"
450
- });
451
- childBuilder.endNode(childRootId);
452
- allGraphs.push(childBuilder.build());
453
- }
454
- if (!(0, import_node_fs.existsSync)(resolvedTracesDir)) {
455
- (0, import_node_fs.mkdirSync)(resolvedTracesDir, { recursive: true });
456
- }
457
- const ts = fileTimestamp();
458
- const tracePaths = [];
459
- for (const graph of allGraphs) {
460
- const filename = `${graph.agentId}-${ts}.json`;
461
- const outPath = (0, import_node_path.join)(resolvedTracesDir, filename);
462
- (0, import_node_fs.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
463
- tracePaths.push(outPath);
464
- }
465
- return {
466
- exitCode,
467
- traceId,
468
- spanId,
469
- tracePaths,
470
- stateChanges,
471
- duration
472
- };
473
- }
474
-
475
- // src/live.ts
476
- var import_node_fs2 = require("fs");
477
- var import_node_path2 = require("path");
478
-
479
277
  // src/graph-query.ts
480
278
  function getNode(graph, nodeId) {
481
279
  return graph.nodes.get(nodeId);
@@ -507,13 +305,13 @@ function getHungNodes(graph) {
507
305
  function getCriticalPath(graph) {
508
306
  const root = graph.nodes.get(graph.rootNodeId);
509
307
  if (!root) return [];
510
- function nodeDuration(node) {
308
+ function nodeDuration2(node) {
511
309
  const end = node.endTime ?? Date.now();
512
310
  return end - node.startTime;
513
311
  }
514
312
  function dfs(node) {
515
313
  if (node.children.length === 0) {
516
- return { duration: nodeDuration(node), path: [node] };
314
+ return { duration: nodeDuration2(node), path: [node] };
517
315
  }
518
316
  let bestChild = { duration: -1, path: [] };
519
317
  for (const childId of node.children) {
@@ -525,7 +323,7 @@ function getCriticalPath(graph) {
525
323
  }
526
324
  }
527
325
  return {
528
- duration: nodeDuration(node) + bestChild.duration,
326
+ duration: nodeDuration2(node) + bestChild.duration,
529
327
  path: [node, ...bestChild.path]
530
328
  };
531
329
  }
@@ -666,17 +464,231 @@ function stitchTrace(graphs) {
666
464
  status
667
465
  });
668
466
  }
669
- function getTraceTree(trace) {
670
- const result = [];
671
- function walk(spanId) {
672
- const graph = trace.graphs.get(spanId);
673
- if (graph) result.push(graph);
674
- const children = trace.childMap.get(spanId) ?? [];
675
- for (const childSpan of children) walk(childSpan);
467
+ function getTraceTree(trace) {
468
+ const result = [];
469
+ function walk(spanId) {
470
+ const graph = trace.graphs.get(spanId);
471
+ if (graph) result.push(graph);
472
+ const children = trace.childMap.get(spanId) ?? [];
473
+ for (const childSpan of children) walk(childSpan);
474
+ }
475
+ if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
476
+ else result.push(trace.rootGraph);
477
+ return result;
478
+ }
479
+
480
+ // src/guards.ts
481
+ var DEFAULT_TIMEOUTS = {
482
+ tool: 3e4,
483
+ // 30s
484
+ agent: 3e5,
485
+ // 5m
486
+ subagent: 3e5,
487
+ // 5m
488
+ wait: 6e5,
489
+ // 10m
490
+ decision: 3e4,
491
+ // 30s
492
+ custom: 3e4
493
+ // 30s
494
+ };
495
+ function checkGuards(graph, config) {
496
+ const violations = [];
497
+ const now = Date.now();
498
+ const timeouts = { ...DEFAULT_TIMEOUTS, ...config?.timeouts };
499
+ const maxReasoningSteps = config?.maxReasoningSteps ?? 25;
500
+ const maxDepth = config?.maxDepth ?? 10;
501
+ const maxAgentSpawns = config?.maxAgentSpawns ?? 50;
502
+ for (const node of graph.nodes.values()) {
503
+ if (node.status === "running" && node.endTime === null) {
504
+ const timeoutThreshold = timeouts[node.type];
505
+ const elapsed = now - node.startTime;
506
+ if (elapsed > timeoutThreshold) {
507
+ violations.push({
508
+ type: "timeout",
509
+ nodeId: node.id,
510
+ message: `Node ${node.id} (${node.type}: ${node.name}) has been running for ${elapsed}ms, exceeding timeout of ${timeoutThreshold}ms`,
511
+ timestamp: now
512
+ });
513
+ }
514
+ }
515
+ }
516
+ const depth = getDepth(graph);
517
+ if (depth > maxDepth) {
518
+ violations.push({
519
+ type: "spawn-explosion",
520
+ nodeId: graph.rootNodeId,
521
+ message: `Graph depth ${depth} exceeds maximum depth of ${maxDepth}`,
522
+ timestamp: now
523
+ });
524
+ }
525
+ let agentCount = 0;
526
+ for (const node of graph.nodes.values()) {
527
+ if (node.type === "agent" || node.type === "subagent") {
528
+ agentCount++;
529
+ }
530
+ }
531
+ if (agentCount > maxAgentSpawns) {
532
+ violations.push({
533
+ type: "spawn-explosion",
534
+ nodeId: graph.rootNodeId,
535
+ message: `Total agent/subagent count ${agentCount} exceeds maximum of ${maxAgentSpawns}`,
536
+ timestamp: now
537
+ });
538
+ }
539
+ violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
540
+ return violations;
541
+ }
542
+ function detectReasoningLoops(graph, maxSteps, timestamp) {
543
+ const violations = [];
544
+ const reported = /* @__PURE__ */ new Set();
545
+ function walk(nodeId, consecutiveCount, consecutiveType) {
546
+ const node = getNode(graph, nodeId);
547
+ if (!node) return;
548
+ let newCount;
549
+ let newType;
550
+ if (node.type === consecutiveType) {
551
+ newCount = consecutiveCount + 1;
552
+ newType = node.type;
553
+ } else {
554
+ newCount = 1;
555
+ newType = node.type;
556
+ }
557
+ if (newCount > maxSteps && !reported.has(newType)) {
558
+ reported.add(newType);
559
+ violations.push({
560
+ type: "reasoning-loop",
561
+ nodeId: node.id,
562
+ message: `Detected ${newCount} consecutive ${newType} nodes along path to ${node.name}`,
563
+ timestamp
564
+ });
565
+ }
566
+ const children = getChildren(graph, nodeId);
567
+ for (const child of children) {
568
+ walk(child.id, newCount, newType);
569
+ }
570
+ }
571
+ walk(graph.rootNodeId, 0, null);
572
+ return violations;
573
+ }
574
+ function withGuards(builder, config) {
575
+ const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
576
+ const onViolation = config?.onViolation ?? "warn";
577
+ function handleViolations(violations) {
578
+ if (violations.length === 0) return;
579
+ for (const violation of violations) {
580
+ const message = `Guard violation: ${violation.message}`;
581
+ switch (onViolation) {
582
+ case "warn":
583
+ logger(message);
584
+ break;
585
+ case "error":
586
+ logger(message);
587
+ builder.pushEvent({
588
+ eventType: "custom",
589
+ nodeId: violation.nodeId,
590
+ data: {
591
+ guardViolation: violation.type,
592
+ message: violation.message,
593
+ severity: "error"
594
+ }
595
+ });
596
+ break;
597
+ case "abort":
598
+ throw new Error(`AgentFlow guard violation: ${violation.message}`);
599
+ default:
600
+ logger(message);
601
+ }
602
+ }
603
+ }
604
+ return {
605
+ get graphId() {
606
+ return builder.graphId;
607
+ },
608
+ get traceContext() {
609
+ return builder.traceContext;
610
+ },
611
+ startNode: (opts) => builder.startNode(opts),
612
+ endNode: (nodeId, status) => {
613
+ builder.endNode(nodeId, status);
614
+ const snapshot = builder.getSnapshot();
615
+ const violations = checkGuards(snapshot, config);
616
+ handleViolations(violations);
617
+ },
618
+ failNode: (nodeId, error) => {
619
+ builder.failNode(nodeId, error);
620
+ const snapshot = builder.getSnapshot();
621
+ const violations = checkGuards(snapshot, config);
622
+ handleViolations(violations);
623
+ },
624
+ addEdge: (from, to, type) => builder.addEdge(from, to, type),
625
+ pushEvent: (event) => builder.pushEvent(event),
626
+ updateState: (nodeId, state) => builder.updateState(nodeId, state),
627
+ withParent: (parentId, fn) => builder.withParent(parentId, fn),
628
+ getSnapshot: () => builder.getSnapshot(),
629
+ build: () => {
630
+ const snapshot = builder.getSnapshot();
631
+ const violations = checkGuards(snapshot, config);
632
+ handleViolations(violations);
633
+ return builder.build();
634
+ }
635
+ };
636
+ }
637
+
638
+ // src/live.ts
639
+ var import_node_fs = require("fs");
640
+ var import_node_path = require("path");
641
+
642
+ // src/loader.ts
643
+ function toNodesMap(raw) {
644
+ if (raw instanceof Map) return raw;
645
+ if (Array.isArray(raw)) {
646
+ return new Map(raw);
647
+ }
648
+ if (raw !== null && typeof raw === "object") {
649
+ return new Map(Object.entries(raw));
650
+ }
651
+ return /* @__PURE__ */ new Map();
652
+ }
653
+ function loadGraph(input) {
654
+ const raw = typeof input === "string" ? JSON.parse(input) : input;
655
+ const nodes = toNodesMap(raw.nodes);
656
+ return {
657
+ id: raw.id ?? "",
658
+ rootNodeId: raw.rootNodeId ?? raw.rootId ?? "",
659
+ nodes,
660
+ edges: raw.edges ?? [],
661
+ startTime: raw.startTime ?? 0,
662
+ endTime: raw.endTime ?? null,
663
+ status: raw.status ?? "completed",
664
+ trigger: raw.trigger ?? "unknown",
665
+ agentId: raw.agentId ?? "unknown",
666
+ events: raw.events ?? [],
667
+ traceId: raw.traceId,
668
+ spanId: raw.spanId,
669
+ parentSpanId: raw.parentSpanId
670
+ };
671
+ }
672
+ function graphToJson(graph) {
673
+ const nodesObj = {};
674
+ for (const [id, node] of graph.nodes) {
675
+ nodesObj[id] = node;
676
676
  }
677
- if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
678
- else result.push(trace.rootGraph);
679
- return result;
677
+ return {
678
+ id: graph.id,
679
+ rootNodeId: graph.rootNodeId,
680
+ nodes: nodesObj,
681
+ edges: graph.edges,
682
+ startTime: graph.startTime,
683
+ endTime: graph.endTime,
684
+ status: graph.status,
685
+ trigger: graph.trigger,
686
+ agentId: graph.agentId,
687
+ events: graph.events,
688
+ traceId: graph.traceId,
689
+ spanId: graph.spanId,
690
+ parentSpanId: graph.parentSpanId
691
+ };
680
692
  }
681
693
 
682
694
  // src/live.ts
@@ -712,17 +724,18 @@ function parseArgs(argv) {
712
724
  config.recursive = true;
713
725
  i++;
714
726
  } else if (!arg.startsWith("-")) {
715
- config.dirs.push((0, import_node_path2.resolve)(arg));
727
+ config.dirs.push((0, import_node_path.resolve)(arg));
716
728
  i++;
717
729
  } else {
718
730
  i++;
719
731
  }
720
732
  }
721
- if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
733
+ if (config.dirs.length === 0) config.dirs.push((0, import_node_path.resolve)("."));
722
734
  return config;
723
735
  }
724
736
  function printUsage() {
725
- console.log(`
737
+ console.log(
738
+ `
726
739
  AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
727
740
 
728
741
  Auto-detects agent traces, state files, job schedulers, and session logs
@@ -743,20 +756,21 @@ Examples:
743
756
  agentflow live ./data
744
757
  agentflow live ./traces ./cron ./workers --refresh 5
745
758
  agentflow live /var/lib/myagent -R
746
- `.trim());
759
+ `.trim()
760
+ );
747
761
  }
748
762
  function scanFiles(dirs, recursive) {
749
763
  const results = [];
750
764
  const seen = /* @__PURE__ */ new Set();
751
765
  function scanDir(d, topLevel) {
752
766
  try {
753
- for (const f of (0, import_node_fs2.readdirSync)(d)) {
767
+ for (const f of (0, import_node_fs.readdirSync)(d)) {
754
768
  if (f.startsWith(".")) continue;
755
- const fp = (0, import_node_path2.join)(d, f);
769
+ const fp = (0, import_node_path.join)(d, f);
756
770
  if (seen.has(fp)) continue;
757
771
  let stat;
758
772
  try {
759
- stat = (0, import_node_fs2.statSync)(fp);
773
+ stat = (0, import_node_fs.statSync)(fp);
760
774
  } catch {
761
775
  continue;
762
776
  }
@@ -782,20 +796,22 @@ function scanFiles(dirs, recursive) {
782
796
  }
783
797
  function safeReadJson(fp) {
784
798
  try {
785
- return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
799
+ return JSON.parse((0, import_node_fs.readFileSync)(fp, "utf8"));
786
800
  } catch {
787
801
  return null;
788
802
  }
789
803
  }
790
804
  function nameFromFile(filename) {
791
- return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
805
+ return (0, import_node_path.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
792
806
  }
793
807
  function normalizeStatus(val) {
794
808
  if (typeof val !== "string") return "unknown";
795
809
  const s = val.toLowerCase();
796
810
  if (["ok", "success", "completed", "done", "passed", "healthy", "good"].includes(s)) return "ok";
797
- if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s)) return "error";
798
- if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s)) return "running";
811
+ if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s))
812
+ return "error";
813
+ if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s))
814
+ return "running";
799
815
  return "unknown";
800
816
  }
801
817
  function findStatus(obj) {
@@ -811,7 +827,17 @@ function findStatus(obj) {
811
827
  return "unknown";
812
828
  }
813
829
  function findTimestamp(obj) {
814
- for (const key of ["ts", "timestamp", "lastRunAtMs", "last_run", "lastExecution", "updated_at", "started_at", "endTime", "startTime"]) {
830
+ for (const key of [
831
+ "ts",
832
+ "timestamp",
833
+ "lastRunAtMs",
834
+ "last_run",
835
+ "lastExecution",
836
+ "updated_at",
837
+ "started_at",
838
+ "endTime",
839
+ "startTime"
840
+ ]) {
815
841
  const val = obj[key];
816
842
  if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
817
843
  if (typeof val === "string") {
@@ -823,7 +849,16 @@ function findTimestamp(obj) {
823
849
  }
824
850
  function extractDetail(obj) {
825
851
  const parts = [];
826
- for (const key of ["summary", "message", "description", "lastError", "error", "name", "jobId", "id"]) {
852
+ for (const key of [
853
+ "summary",
854
+ "message",
855
+ "description",
856
+ "lastError",
857
+ "error",
858
+ "name",
859
+ "jobId",
860
+ "id"
861
+ ]) {
827
862
  const val = obj[key];
828
863
  if (typeof val === "string" && val.length > 0 && val.length < 200) {
829
864
  parts.push(val.slice(0, 80));
@@ -883,7 +918,14 @@ function processJsonFile(file) {
883
918
  const status2 = findStatus(state);
884
919
  const ts2 = findTimestamp(state) || file.mtime;
885
920
  const detail2 = extractDetail(state);
886
- records.push({ id: String(name), source: "jobs", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
921
+ records.push({
922
+ id: String(name),
923
+ source: "jobs",
924
+ status: status2,
925
+ lastActive: ts2,
926
+ detail: detail2,
927
+ file: file.filename
928
+ });
887
929
  }
888
930
  return records;
889
931
  }
@@ -898,7 +940,14 @@ function processJsonFile(file) {
898
940
  const ts2 = findTimestamp(w) || findTimestamp(obj) || file.mtime;
899
941
  const pid = w.pid;
900
942
  const detail2 = pid ? `pid: ${pid}` : extractDetail(w);
901
- records.push({ id: name, source: "workers", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
943
+ records.push({
944
+ id: name,
945
+ source: "workers",
946
+ status: status2,
947
+ lastActive: ts2,
948
+ detail: detail2,
949
+ file: file.filename
950
+ });
902
951
  }
903
952
  return records;
904
953
  }
@@ -906,12 +955,19 @@ function processJsonFile(file) {
906
955
  const status = findStatus(obj);
907
956
  const ts = findTimestamp(obj) || file.mtime;
908
957
  const detail = extractDetail(obj);
909
- records.push({ id: nameFromFile(file.filename), source: "state", status, lastActive: ts, detail, file: file.filename });
958
+ records.push({
959
+ id: nameFromFile(file.filename),
960
+ source: "state",
961
+ status,
962
+ lastActive: ts,
963
+ detail,
964
+ file: file.filename
965
+ });
910
966
  return records;
911
967
  }
912
968
  function processJsonlFile(file) {
913
969
  try {
914
- const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
970
+ const content = (0, import_node_fs.readFileSync)(file.path, "utf8").trim();
915
971
  if (!content) return [];
916
972
  const lines = content.split("\n");
917
973
  const lineCount = lines.length;
@@ -922,13 +978,22 @@ function processJsonlFile(file) {
922
978
  const ts2 = findTimestamp(lastObj) || file.mtime;
923
979
  const action = lastObj.action;
924
980
  const detail2 = action ? `${action} (${lineCount} entries)` : `${lineCount} entries`;
925
- return [{ id: String(name), source: "session", status: status2, lastActive: ts2, detail: detail2, file: file.filename }];
981
+ return [
982
+ {
983
+ id: String(name),
984
+ source: "session",
985
+ status: status2,
986
+ lastActive: ts2,
987
+ detail: detail2,
988
+ file: file.filename
989
+ }
990
+ ];
926
991
  }
927
992
  const tail = lines.slice(Math.max(0, lineCount - 30));
928
993
  let model = "";
929
994
  let totalTokens = 0;
930
995
  let totalCost = 0;
931
- let toolCalls = [];
996
+ const toolCalls = [];
932
997
  let lastUserMsg = "";
933
998
  let lastAssistantMsg = "";
934
999
  let errorCount = 0;
@@ -1027,7 +1092,16 @@ function processJsonlFile(file) {
1027
1092
  const status = errorCount > lineCount / 4 ? "error" : lastRole === "assistant" ? "ok" : "running";
1028
1093
  const ts = findTimestamp(lastObj) || file.mtime;
1029
1094
  const sessionName = sessionId ? sessionId.slice(0, 8) : nameFromFile(file.filename);
1030
- return [{ id: String(name !== "unknown" ? name : sessionName), source: "session", status, lastActive: ts, detail, file: file.filename }];
1095
+ return [
1096
+ {
1097
+ id: String(name !== "unknown" ? name : sessionName),
1098
+ source: "session",
1099
+ status,
1100
+ lastActive: ts,
1101
+ detail,
1102
+ file: file.filename
1103
+ }
1104
+ ];
1031
1105
  } catch {
1032
1106
  return [];
1033
1107
  }
@@ -1159,7 +1233,8 @@ function render(config) {
1159
1233
  if (g.fail > 0 && g.ok === 0 && g.running === 0) return `${C.red}error${C.reset}`;
1160
1234
  if (g.running > 0) return `${C.green}running${C.reset}`;
1161
1235
  if (g.fail > 0) return `${C.yellow}${g.ok}ok/${g.fail}err${C.reset}`;
1162
- if (g.ok > 0) return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
1236
+ if (g.ok > 0)
1237
+ return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
1163
1238
  return `${C.dim}idle${C.reset}`;
1164
1239
  }
1165
1240
  function sourceTag(s) {
@@ -1191,18 +1266,30 @@ function render(config) {
1191
1266
  }
1192
1267
  const L = [];
1193
1268
  writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
1194
- writeLine(L, `${C.bold}${C.cyan}\u2551${C.reset} ${C.bold}${C.white}AGENTFLOW LIVE${C.reset} ${C.green}\u25CF LIVE${C.reset} ${C.dim}${time}${C.reset} ${C.bold}${C.cyan}\u2551${C.reset}`);
1269
+ writeLine(
1270
+ L,
1271
+ `${C.bold}${C.cyan}\u2551${C.reset} ${C.bold}${C.white}AGENTFLOW LIVE${C.reset} ${C.green}\u25CF LIVE${C.reset} ${C.dim}${time}${C.reset} ${C.bold}${C.cyan}\u2551${C.reset}`
1272
+ );
1195
1273
  const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
1196
1274
  const pad1 = Math.max(0, 64 - metaLine.length);
1197
- writeLine(L, `${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
1275
+ writeLine(
1276
+ L,
1277
+ `${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`
1278
+ );
1198
1279
  writeLine(L, `${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
1199
1280
  const sc = totFail === 0 ? C.green : C.yellow;
1200
1281
  writeLine(L, "");
1201
- writeLine(L, ` ${C.bold}Agents${C.reset} ${sc}${uniqueAgents}${C.reset} ${C.bold}Records${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Running${C.reset} ${C.green}${totRunning}${C.reset} ${C.bold}Errors${C.reset} ${totFail > 0 ? C.red : C.dim}${totFail}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset}`);
1282
+ writeLine(
1283
+ L,
1284
+ ` ${C.bold}Agents${C.reset} ${sc}${uniqueAgents}${C.reset} ${C.bold}Records${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Running${C.reset} ${C.green}${totRunning}${C.reset} ${C.bold}Errors${C.reset} ${totFail > 0 ? C.red : C.dim}${totFail}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset}`
1285
+ );
1202
1286
  writeLine(L, "");
1203
1287
  writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
1204
1288
  writeLine(L, "");
1205
- writeLine(L, ` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`);
1289
+ writeLine(
1290
+ L,
1291
+ ` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`
1292
+ );
1206
1293
  let lineCount = 0;
1207
1294
  for (const g of groups) {
1208
1295
  if (lineCount > 35) break;
@@ -1213,13 +1300,19 @@ function render(config) {
1213
1300
  const name = truncate(g.name, 26).padEnd(26);
1214
1301
  const st = statusText(g);
1215
1302
  const det = truncate(g.detail, detailWidth);
1216
- writeLine(L, ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`);
1303
+ writeLine(
1304
+ L,
1305
+ ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`
1306
+ );
1217
1307
  lineCount++;
1218
1308
  } else {
1219
1309
  const name = truncate(g.name, 24).padEnd(24);
1220
1310
  const st = statusText(g);
1221
1311
  const tag = sourceTag(g.source);
1222
- writeLine(L, ` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`);
1312
+ writeLine(
1313
+ L,
1314
+ ` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`
1315
+ );
1223
1316
  lineCount++;
1224
1317
  const kids = g.children.slice(0, 12);
1225
1318
  for (let i = 0; i < kids.length; i++) {
@@ -1231,7 +1324,10 @@ function render(config) {
1231
1324
  const cName = truncate(child.id, 22).padEnd(22);
1232
1325
  const cActive = `${C.dim}${timeStr(child.lastActive)}${C.reset}`;
1233
1326
  const cDet = truncate(child.detail, detailWidth - 5);
1234
- writeLine(L, ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`);
1327
+ writeLine(
1328
+ L,
1329
+ ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`
1330
+ );
1235
1331
  lineCount++;
1236
1332
  }
1237
1333
  if (g.children.length > 12) {
@@ -1248,7 +1344,10 @@ function render(config) {
1248
1344
  const si = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1249
1345
  const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
1250
1346
  const tid = dt.traceId.slice(0, 8);
1251
- writeLine(L, ` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
1347
+ writeLine(
1348
+ L,
1349
+ ` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`
1350
+ );
1252
1351
  const tree = getTraceTree(dt);
1253
1352
  for (let i = 0; i < Math.min(tree.length, 6); i++) {
1254
1353
  const tg = tree[i];
@@ -1258,7 +1357,10 @@ function render(config) {
1258
1357
  const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1259
1358
  const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1260
1359
  const gd = tg.endTime ? `${tg.endTime - tg.startTime}ms` : "running";
1261
- writeLine(L, `${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`);
1360
+ writeLine(
1361
+ L,
1362
+ `${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`
1363
+ );
1262
1364
  }
1263
1365
  }
1264
1366
  }
@@ -1273,7 +1375,10 @@ function render(config) {
1273
1375
  const age = Math.floor((Date.now() - r.lastActive) / 1e3);
1274
1376
  const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
1275
1377
  const det = truncate(r.detail, detailWidth);
1276
- writeLine(L, ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`);
1378
+ writeLine(
1379
+ L,
1380
+ ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`
1381
+ );
1277
1382
  }
1278
1383
  }
1279
1384
  if (files.length === 0) {
@@ -1295,39 +1400,540 @@ function getDistDepth(dt, spanId) {
1295
1400
  }
1296
1401
  function startLive(argv) {
1297
1402
  const config = parseArgs(argv);
1298
- const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
1403
+ const valid = config.dirs.filter((d) => (0, import_node_fs.existsSync)(d));
1299
1404
  if (valid.length === 0) {
1300
1405
  console.error(`No valid directories found: ${config.dirs.join(", ")}`);
1301
1406
  console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
1302
1407
  process.exit(1);
1303
1408
  }
1304
- const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
1305
- if (invalid.length > 0) {
1306
- console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
1409
+ const invalid = config.dirs.filter((d) => !(0, import_node_fs.existsSync)(d));
1410
+ if (invalid.length > 0) {
1411
+ console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
1412
+ }
1413
+ config.dirs = valid;
1414
+ render(config);
1415
+ let debounce = null;
1416
+ for (const dir of config.dirs) {
1417
+ try {
1418
+ (0, import_node_fs.watch)(dir, { recursive: config.recursive }, () => {
1419
+ if (debounce) clearTimeout(debounce);
1420
+ debounce = setTimeout(() => render(config), 500);
1421
+ });
1422
+ } catch {
1423
+ }
1424
+ }
1425
+ setInterval(() => render(config), config.refreshMs);
1426
+ process.on("SIGINT", () => {
1427
+ console.log("\n" + C.dim + "Monitor stopped." + C.reset);
1428
+ process.exit(0);
1429
+ });
1430
+ }
1431
+
1432
+ // src/runner.ts
1433
+ var import_node_child_process = require("child_process");
1434
+ var import_node_fs2 = require("fs");
1435
+ var import_node_path2 = require("path");
1436
+ function globToRegex(pattern) {
1437
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1438
+ return new RegExp(`^${escaped}$`);
1439
+ }
1440
+ function snapshotDir(dir, patterns) {
1441
+ const result = /* @__PURE__ */ new Map();
1442
+ if (!(0, import_node_fs2.existsSync)(dir)) return result;
1443
+ for (const entry of (0, import_node_fs2.readdirSync)(dir)) {
1444
+ if (!patterns.some((re) => re.test(entry))) continue;
1445
+ const full = (0, import_node_path2.join)(dir, entry);
1446
+ try {
1447
+ const stat = (0, import_node_fs2.statSync)(full);
1448
+ if (stat.isFile()) {
1449
+ result.set(full, stat.mtimeMs);
1450
+ }
1451
+ } catch {
1452
+ }
1453
+ }
1454
+ return result;
1455
+ }
1456
+ function agentIdFromFilename(filePath) {
1457
+ const base = (0, import_node_path2.basename)(filePath, ".json");
1458
+ const cleaned = base.replace(/-state$/, "");
1459
+ return `alfred-${cleaned}`;
1460
+ }
1461
+ function deriveAgentId(command) {
1462
+ return "orchestrator";
1463
+ }
1464
+ function fileTimestamp() {
1465
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
1466
+ }
1467
+ async function runTraced(config) {
1468
+ const {
1469
+ command,
1470
+ agentId = deriveAgentId(command),
1471
+ trigger = "cli",
1472
+ tracesDir = "./traces",
1473
+ watchDirs = [],
1474
+ watchPatterns = ["*.json"]
1475
+ } = config;
1476
+ if (command.length === 0) {
1477
+ throw new Error("runTraced: command must not be empty");
1478
+ }
1479
+ const resolvedTracesDir = (0, import_node_path2.resolve)(tracesDir);
1480
+ const patterns = watchPatterns.map(globToRegex);
1481
+ const orchestrator = createGraphBuilder({ agentId, trigger });
1482
+ const { traceId, spanId } = orchestrator.traceContext;
1483
+ const beforeSnapshots = /* @__PURE__ */ new Map();
1484
+ for (const dir of watchDirs) {
1485
+ beforeSnapshots.set(dir, snapshotDir(dir, patterns));
1486
+ }
1487
+ const rootId = orchestrator.startNode({ type: "agent", name: agentId });
1488
+ const dispatchId = orchestrator.startNode({
1489
+ type: "tool",
1490
+ name: "dispatch-command",
1491
+ parentId: rootId
1492
+ });
1493
+ orchestrator.updateState(dispatchId, { command: command.join(" ") });
1494
+ const monitorId = orchestrator.startNode({
1495
+ type: "tool",
1496
+ name: "state-monitor",
1497
+ parentId: rootId
1498
+ });
1499
+ orchestrator.updateState(monitorId, {
1500
+ watchDirs,
1501
+ watchPatterns
1502
+ });
1503
+ const startMs = Date.now();
1504
+ const execCmd = command[0] ?? "";
1505
+ const execArgs = command.slice(1);
1506
+ process.env.AGENTFLOW_TRACE_ID = traceId;
1507
+ process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
1508
+ const result = (0, import_node_child_process.spawnSync)(execCmd, execArgs, { stdio: "inherit" });
1509
+ delete process.env.AGENTFLOW_TRACE_ID;
1510
+ delete process.env.AGENTFLOW_PARENT_SPAN_ID;
1511
+ const exitCode = result.status ?? 1;
1512
+ const duration = (Date.now() - startMs) / 1e3;
1513
+ const stateChanges = [];
1514
+ for (const dir of watchDirs) {
1515
+ const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
1516
+ const after = snapshotDir(dir, patterns);
1517
+ for (const [filePath, mtime] of after) {
1518
+ const prevMtime = before.get(filePath);
1519
+ if (prevMtime === void 0 || mtime > prevMtime) {
1520
+ stateChanges.push(filePath);
1521
+ }
1522
+ }
1523
+ }
1524
+ orchestrator.updateState(monitorId, { stateChanges });
1525
+ orchestrator.endNode(monitorId);
1526
+ if (exitCode === 0) {
1527
+ orchestrator.endNode(dispatchId);
1528
+ } else {
1529
+ orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
1530
+ }
1531
+ orchestrator.updateState(rootId, {
1532
+ exitCode,
1533
+ duration,
1534
+ stateChanges
1535
+ });
1536
+ if (exitCode === 0) {
1537
+ orchestrator.endNode(rootId);
1538
+ } else {
1539
+ orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
1540
+ }
1541
+ const orchestratorGraph = orchestrator.build();
1542
+ const allGraphs = [orchestratorGraph];
1543
+ for (const filePath of stateChanges) {
1544
+ const childAgentId = agentIdFromFilename(filePath);
1545
+ const childBuilder = createGraphBuilder({
1546
+ agentId: childAgentId,
1547
+ trigger: "state-change",
1548
+ traceId,
1549
+ parentSpanId: spanId
1550
+ });
1551
+ const childRootId = childBuilder.startNode({
1552
+ type: "agent",
1553
+ name: childAgentId
1554
+ });
1555
+ childBuilder.updateState(childRootId, {
1556
+ stateFile: filePath,
1557
+ detectedBy: "runner-state-monitor"
1558
+ });
1559
+ childBuilder.endNode(childRootId);
1560
+ allGraphs.push(childBuilder.build());
1561
+ }
1562
+ if (!(0, import_node_fs2.existsSync)(resolvedTracesDir)) {
1563
+ (0, import_node_fs2.mkdirSync)(resolvedTracesDir, { recursive: true });
1564
+ }
1565
+ const ts = fileTimestamp();
1566
+ const tracePaths = [];
1567
+ for (const graph of allGraphs) {
1568
+ const filename = `${graph.agentId}-${ts}.json`;
1569
+ const outPath = (0, import_node_path2.join)(resolvedTracesDir, filename);
1570
+ (0, import_node_fs2.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
1571
+ tracePaths.push(outPath);
1572
+ }
1573
+ if (tracePaths.length > 0) {
1574
+ console.log(`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`);
1575
+ }
1576
+ return {
1577
+ exitCode,
1578
+ traceId,
1579
+ spanId,
1580
+ tracePaths,
1581
+ stateChanges,
1582
+ duration
1583
+ };
1584
+ }
1585
+
1586
+ // src/trace-store.ts
1587
+ var import_promises = require("fs/promises");
1588
+ var import_path = require("path");
1589
+ function createTraceStore(dir) {
1590
+ async function ensureDir() {
1591
+ await (0, import_promises.mkdir)(dir, { recursive: true });
1592
+ }
1593
+ async function loadAll() {
1594
+ await ensureDir();
1595
+ let files;
1596
+ try {
1597
+ files = await (0, import_promises.readdir)(dir);
1598
+ } catch {
1599
+ return [];
1600
+ }
1601
+ const graphs = [];
1602
+ for (const file of files) {
1603
+ if (!file.endsWith(".json")) continue;
1604
+ try {
1605
+ const content = await (0, import_promises.readFile)((0, import_path.join)(dir, file), "utf-8");
1606
+ const graph = loadGraph(content);
1607
+ graphs.push(graph);
1608
+ } catch {
1609
+ }
1610
+ }
1611
+ return graphs;
1612
+ }
1613
+ return {
1614
+ async save(graph) {
1615
+ await ensureDir();
1616
+ const json = graphToJson(graph);
1617
+ const filePath = (0, import_path.join)(dir, `${graph.id}.json`);
1618
+ await (0, import_promises.writeFile)(filePath, JSON.stringify(json, null, 2), "utf-8");
1619
+ return filePath;
1620
+ },
1621
+ async get(graphId) {
1622
+ await ensureDir();
1623
+ const filePath = (0, import_path.join)(dir, `${graphId}.json`);
1624
+ try {
1625
+ const content = await (0, import_promises.readFile)(filePath, "utf-8");
1626
+ return loadGraph(content);
1627
+ } catch {
1628
+ }
1629
+ const all = await loadAll();
1630
+ return all.find((g) => g.id === graphId) ?? null;
1631
+ },
1632
+ async list(opts) {
1633
+ let graphs = await loadAll();
1634
+ if (opts?.status) {
1635
+ graphs = graphs.filter((g) => g.status === opts.status);
1636
+ }
1637
+ graphs.sort((a, b) => b.startTime - a.startTime);
1638
+ if (opts?.limit && opts.limit > 0) {
1639
+ graphs = graphs.slice(0, opts.limit);
1640
+ }
1641
+ return graphs;
1642
+ },
1643
+ async getStuckSpans() {
1644
+ const graphs = await loadAll();
1645
+ const stuck = [];
1646
+ for (const graph of graphs) {
1647
+ for (const node of graph.nodes.values()) {
1648
+ if (node.status === "running" || node.status === "hung" || node.status === "timeout") {
1649
+ stuck.push(node);
1650
+ }
1651
+ }
1652
+ }
1653
+ return stuck;
1654
+ },
1655
+ async getReasoningLoops(threshold = 25) {
1656
+ const graphs = await loadAll();
1657
+ const results = [];
1658
+ for (const graph of graphs) {
1659
+ const loops = findLoopsInGraph(graph, threshold);
1660
+ if (loops.length > 0) {
1661
+ results.push({ graphId: graph.id, nodes: loops });
1662
+ }
1663
+ }
1664
+ return results;
1665
+ }
1666
+ };
1667
+ }
1668
+ function findLoopsInGraph(graph, threshold) {
1669
+ const loopNodes = [];
1670
+ function walk(nodeId, consecutiveCount, consecutiveType) {
1671
+ const node = graph.nodes.get(nodeId);
1672
+ if (!node) return;
1673
+ const newCount = node.type === consecutiveType ? consecutiveCount + 1 : 1;
1674
+ if (newCount > threshold) {
1675
+ loopNodes.push(node);
1676
+ }
1677
+ for (const childId of node.children) {
1678
+ walk(childId, newCount, node.type);
1679
+ }
1680
+ }
1681
+ walk(graph.rootNodeId, 0, null);
1682
+ return loopNodes;
1683
+ }
1684
+
1685
+ // src/visualize.ts
1686
+ var STATUS_ICONS = {
1687
+ completed: "\u2713",
1688
+ failed: "\u2717",
1689
+ running: "\u231B",
1690
+ hung: "\u231B",
1691
+ timeout: "\u231B"
1692
+ };
1693
+ function formatDuration(ms) {
1694
+ if (ms < 1e3) return `${ms}ms`;
1695
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
1696
+ if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
1697
+ return `${(ms / 36e5).toFixed(1)}h`;
1698
+ }
1699
+ function nodeDuration(node, graphEndTime) {
1700
+ const end = node.endTime ?? graphEndTime;
1701
+ return formatDuration(end - node.startTime);
1702
+ }
1703
+ function getGenAiInfo(node) {
1704
+ const parts = [];
1705
+ const meta = node.metadata;
1706
+ if (meta["gen_ai.request.model"]) {
1707
+ parts.push(String(meta["gen_ai.request.model"]));
1708
+ }
1709
+ const tokens = meta["gen_ai.usage.prompt_tokens"] ?? meta["gen_ai.usage.completion_tokens"];
1710
+ if (tokens !== void 0) {
1711
+ const prompt = meta["gen_ai.usage.prompt_tokens"] ?? 0;
1712
+ const completion = meta["gen_ai.usage.completion_tokens"] ?? 0;
1713
+ if (prompt || completion) {
1714
+ parts.push(`${prompt + completion} tok`);
1715
+ }
1716
+ }
1717
+ return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
1718
+ }
1719
+ function hasViolation(node, graph) {
1720
+ return graph.events.some(
1721
+ (e) => e.nodeId === node.id && e.eventType === "custom" && e.data.guardViolation !== void 0
1722
+ );
1723
+ }
1724
+ function toAsciiTree(graph) {
1725
+ if (graph.nodes.size === 0) return "(empty graph)";
1726
+ const now = Date.now();
1727
+ const endTime = graph.endTime ?? now;
1728
+ const lines = [];
1729
+ function renderNode(nodeId, prefix, isLast, isRoot) {
1730
+ const node = graph.nodes.get(nodeId);
1731
+ if (!node) return;
1732
+ const icon = STATUS_ICONS[node.status];
1733
+ const duration = nodeDuration(node, endTime);
1734
+ const genAi = getGenAiInfo(node);
1735
+ const violation = hasViolation(node, graph) ? " \u26A0" : "";
1736
+ const errorInfo = node.status === "failed" && node.metadata.error ? ` \u2014 ${node.metadata.error}` : "";
1737
+ const timeoutInfo = node.status === "timeout" ? " [TIMEOUT]" : "";
1738
+ const hungInfo = node.status === "hung" ? " [HUNG]" : "";
1739
+ const connector = isRoot ? "" : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1740
+ const line = `${prefix}${connector}${icon} ${node.name} (${node.type}) ${duration}${genAi}${violation}${timeoutInfo}${hungInfo}${errorInfo}`;
1741
+ lines.push(line);
1742
+ const children = getChildren(graph, nodeId);
1743
+ const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
1744
+ for (let i = 0; i < children.length; i++) {
1745
+ renderNode(children[i].id, childPrefix, i === children.length - 1, false);
1746
+ }
1747
+ }
1748
+ renderNode(graph.rootNodeId, "", true, true);
1749
+ return lines.join("\n");
1750
+ }
1751
+ function toTimeline(graph) {
1752
+ if (graph.nodes.size === 0) return "(empty graph)";
1753
+ const now = Date.now();
1754
+ const graphStart = graph.startTime;
1755
+ const graphEnd = graph.endTime ?? now;
1756
+ const totalDuration = graphEnd - graphStart;
1757
+ if (totalDuration <= 0) return "(zero duration)";
1758
+ const barWidth = 60;
1759
+ const lines = [];
1760
+ const scaleLabels = [];
1761
+ const tickCount = Math.min(5, Math.max(2, Math.floor(barWidth / 10)));
1762
+ for (let i = 0; i <= tickCount; i++) {
1763
+ const t = totalDuration * i / tickCount;
1764
+ scaleLabels.push(formatDuration(t));
1765
+ }
1766
+ let header = "";
1767
+ for (let i = 0; i < scaleLabels.length; i++) {
1768
+ const pos = Math.round(barWidth * i / tickCount);
1769
+ while (header.length < pos) header += " ";
1770
+ header += scaleLabels[i];
1771
+ }
1772
+ lines.push(header);
1773
+ let tickLine = "";
1774
+ for (let i = 0; i < barWidth; i++) {
1775
+ const tickPos = tickCount > 0 ? i * tickCount / barWidth : 0;
1776
+ if (Number.isInteger(Math.round(tickPos * 100) / 100) && Math.abs(tickPos - Math.round(tickPos)) < 0.01) {
1777
+ tickLine += "\u253C";
1778
+ } else {
1779
+ tickLine += "\u2500";
1780
+ }
1781
+ }
1782
+ lines.push(tickLine);
1783
+ const orderedNodes = [];
1784
+ function collectNodes(nodeId) {
1785
+ const node = graph.nodes.get(nodeId);
1786
+ if (!node) return;
1787
+ orderedNodes.push(node);
1788
+ const children = getChildren(graph, nodeId);
1789
+ for (const child of children) {
1790
+ collectNodes(child.id);
1791
+ }
1307
1792
  }
1308
- config.dirs = valid;
1309
- render(config);
1310
- let debounce = null;
1311
- for (const dir of config.dirs) {
1312
- try {
1313
- (0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
1314
- if (debounce) clearTimeout(debounce);
1315
- debounce = setTimeout(() => render(config), 500);
1316
- });
1317
- } catch {
1793
+ collectNodes(graph.rootNodeId);
1794
+ for (const node of orderedNodes) {
1795
+ const nodeStart = node.startTime - graphStart;
1796
+ const nodeEnd = (node.endTime ?? now) - graphStart;
1797
+ const startCol = Math.round(nodeStart / totalDuration * barWidth);
1798
+ const endCol = Math.max(startCol + 1, Math.round(nodeEnd / totalDuration * barWidth));
1799
+ let bar = "";
1800
+ for (let i = 0; i < barWidth; i++) {
1801
+ if (i >= startCol && i < endCol) {
1802
+ bar += "\u2588";
1803
+ } else {
1804
+ bar += " ";
1805
+ }
1318
1806
  }
1807
+ const icon = STATUS_ICONS[node.status];
1808
+ const duration = nodeDuration(node, graphEnd);
1809
+ const violation = hasViolation(node, graph) ? " \u26A0" : "";
1810
+ lines.push(`${bar} ${icon} ${node.name} (${duration})${violation}`);
1319
1811
  }
1320
- setInterval(() => render(config), config.refreshMs);
1321
- process.on("SIGINT", () => {
1322
- console.log("\n" + C.dim + "Monitor stopped." + C.reset);
1323
- process.exit(0);
1324
- });
1812
+ return lines.join("\n");
1325
1813
  }
1326
1814
 
1327
1815
  // src/watch.ts
1328
1816
  var import_node_fs4 = require("fs");
1329
- var import_node_path3 = require("path");
1330
1817
  var import_node_os = require("os");
1818
+ var import_node_path3 = require("path");
1819
+
1820
+ // src/watch-alerts.ts
1821
+ var import_node_child_process2 = require("child_process");
1822
+ var import_node_http = require("http");
1823
+ var import_node_https = require("https");
1824
+ function formatAlertMessage(payload) {
1825
+ const time = new Date(payload.timestamp).toISOString();
1826
+ const arrow = `${payload.previousStatus} \u2192 ${payload.currentStatus}`;
1827
+ return [
1828
+ `[ALERT] ${payload.condition}: "${payload.agentId}"`,
1829
+ ` Status: ${arrow}`,
1830
+ payload.detail ? ` Detail: ${payload.detail}` : null,
1831
+ ` File: ${payload.file}`,
1832
+ ` Time: ${time}`
1833
+ ].filter(Boolean).join("\n");
1834
+ }
1835
+ function formatTelegram(payload) {
1836
+ const icon = payload.condition === "recovery" ? "\u2705" : "\u26A0\uFE0F";
1837
+ const time = new Date(payload.timestamp).toLocaleTimeString();
1838
+ return [
1839
+ `${icon} *AgentFlow Alert*`,
1840
+ `*${payload.condition}*: \`${payload.agentId}\``,
1841
+ `Status: ${payload.previousStatus} \u2192 ${payload.currentStatus}`,
1842
+ payload.detail ? `Detail: ${payload.detail.slice(0, 200)}` : null,
1843
+ `Time: ${time}`
1844
+ ].filter(Boolean).join("\n");
1845
+ }
1846
+ async function sendAlert(payload, channel) {
1847
+ try {
1848
+ switch (channel.type) {
1849
+ case "stdout":
1850
+ sendStdout(payload);
1851
+ break;
1852
+ case "telegram":
1853
+ await sendTelegram(payload, channel.botToken, channel.chatId);
1854
+ break;
1855
+ case "webhook":
1856
+ await sendWebhook(payload, channel.url);
1857
+ break;
1858
+ case "command":
1859
+ await sendCommand(payload, channel.cmd);
1860
+ break;
1861
+ }
1862
+ } catch (err) {
1863
+ const msg = err instanceof Error ? err.message : String(err);
1864
+ console.error(`[agentflow] Failed to send ${channel.type} alert: ${msg}`);
1865
+ }
1866
+ }
1867
+ function sendStdout(payload) {
1868
+ console.log(formatAlertMessage(payload));
1869
+ }
1870
+ function sendTelegram(payload, botToken, chatId) {
1871
+ const body = JSON.stringify({
1872
+ chat_id: chatId,
1873
+ text: formatTelegram(payload),
1874
+ parse_mode: "Markdown"
1875
+ });
1876
+ return new Promise((resolve4, reject) => {
1877
+ const req = (0, import_node_https.request)(
1878
+ `https://api.telegram.org/bot${botToken}/sendMessage`,
1879
+ {
1880
+ method: "POST",
1881
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
1882
+ },
1883
+ (res) => {
1884
+ res.resume();
1885
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1886
+ else reject(new Error(`Telegram API returned ${res.statusCode}`));
1887
+ }
1888
+ );
1889
+ req.on("error", reject);
1890
+ req.write(body);
1891
+ req.end();
1892
+ });
1893
+ }
1894
+ function sendWebhook(payload, url) {
1895
+ const body = JSON.stringify(payload);
1896
+ const isHttps = url.startsWith("https");
1897
+ const doRequest = isHttps ? import_node_https.request : import_node_http.request;
1898
+ return new Promise((resolve4, reject) => {
1899
+ const req = doRequest(
1900
+ url,
1901
+ {
1902
+ method: "POST",
1903
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
1904
+ },
1905
+ (res) => {
1906
+ res.resume();
1907
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1908
+ else reject(new Error(`Webhook returned ${res.statusCode}`));
1909
+ }
1910
+ );
1911
+ req.on("error", reject);
1912
+ req.setTimeout(1e4, () => {
1913
+ req.destroy(new Error("Webhook timeout"));
1914
+ });
1915
+ req.write(body);
1916
+ req.end();
1917
+ });
1918
+ }
1919
+ function sendCommand(payload, cmd) {
1920
+ return new Promise((resolve4, reject) => {
1921
+ const env = {
1922
+ ...process.env,
1923
+ AGENTFLOW_ALERT_AGENT: payload.agentId,
1924
+ AGENTFLOW_ALERT_CONDITION: payload.condition,
1925
+ AGENTFLOW_ALERT_STATUS: payload.currentStatus,
1926
+ AGENTFLOW_ALERT_PREVIOUS_STATUS: payload.previousStatus,
1927
+ AGENTFLOW_ALERT_DETAIL: payload.detail,
1928
+ AGENTFLOW_ALERT_FILE: payload.file,
1929
+ AGENTFLOW_ALERT_TIMESTAMP: String(payload.timestamp)
1930
+ };
1931
+ (0, import_node_child_process2.exec)(cmd, { env, timeout: 3e4 }, (err) => {
1932
+ if (err) reject(err);
1933
+ else resolve4();
1934
+ });
1935
+ });
1936
+ }
1331
1937
 
1332
1938
  // src/watch-state.ts
1333
1939
  var import_node_fs3 = require("fs");
@@ -1393,7 +1999,9 @@ function detectTransitions(previous, currentRecords, config, now) {
1393
1999
  const hasError = config.alertConditions.some((c) => c.type === "error");
1394
2000
  const hasRecovery = config.alertConditions.some((c) => c.type === "recovery");
1395
2001
  const staleConditions = config.alertConditions.filter((c) => c.type === "stale");
1396
- const consecutiveConditions = config.alertConditions.filter((c) => c.type === "consecutive-errors");
2002
+ const consecutiveConditions = config.alertConditions.filter(
2003
+ (c) => c.type === "consecutive-errors"
2004
+ );
1397
2005
  const byAgent = /* @__PURE__ */ new Map();
1398
2006
  for (const r of currentRecords) {
1399
2007
  const existing = byAgent.get(r.id);
@@ -1417,14 +2025,16 @@ function detectTransitions(previous, currentRecords, config, now) {
1417
2025
  for (const cond of consecutiveConditions) {
1418
2026
  if (newConsec === cond.threshold) {
1419
2027
  if (canAlert(prev, `consecutive-errors:${cond.threshold}`, config.cooldownMs, now)) {
1420
- alerts.push(makePayload(
1421
- agentId,
1422
- `consecutive-errors (${cond.threshold})`,
1423
- prevStatus,
1424
- currStatus,
1425
- { ...record, detail: `${newConsec} consecutive errors. ${record.detail}` },
1426
- config.dirs
1427
- ));
2028
+ alerts.push(
2029
+ makePayload(
2030
+ agentId,
2031
+ `consecutive-errors (${cond.threshold})`,
2032
+ prevStatus,
2033
+ currStatus,
2034
+ { ...record, detail: `${newConsec} consecutive errors. ${record.detail}` },
2035
+ config.dirs
2036
+ )
2037
+ );
1428
2038
  }
1429
2039
  }
1430
2040
  }
@@ -1433,14 +2043,16 @@ function detectTransitions(previous, currentRecords, config, now) {
1433
2043
  if (sinceActive > cond.durationMs && record.lastActive > 0) {
1434
2044
  if (canAlert(prev, "stale", config.cooldownMs, now)) {
1435
2045
  const mins = Math.floor(sinceActive / 6e4);
1436
- alerts.push(makePayload(
1437
- agentId,
1438
- "stale",
1439
- prevStatus,
1440
- currStatus,
1441
- { ...record, detail: `No update for ${mins}m. ${record.detail}` },
1442
- config.dirs
1443
- ));
2046
+ alerts.push(
2047
+ makePayload(
2048
+ agentId,
2049
+ "stale",
2050
+ prevStatus,
2051
+ currStatus,
2052
+ { ...record, detail: `No update for ${mins}m. ${record.detail}` },
2053
+ config.dirs
2054
+ )
2055
+ );
1444
2056
  }
1445
2057
  }
1446
2058
  }
@@ -1453,14 +2065,19 @@ function detectTransitions(previous, currentRecords, config, now) {
1453
2065
  if (canAlert(prev, "stale-auto", config.cooldownMs, now)) {
1454
2066
  const mins = Math.floor(sinceActive / 6e4);
1455
2067
  const expectedMins = Math.floor(expectedInterval / 6e4);
1456
- alerts.push(makePayload(
1457
- agentId,
1458
- "stale (auto)",
1459
- prevStatus,
1460
- currStatus,
1461
- { ...record, detail: `No update for ${mins}m (expected every ~${expectedMins}m). ${record.detail}` },
1462
- config.dirs
1463
- ));
2068
+ alerts.push(
2069
+ makePayload(
2070
+ agentId,
2071
+ "stale (auto)",
2072
+ prevStatus,
2073
+ currStatus,
2074
+ {
2075
+ ...record,
2076
+ detail: `No update for ${mins}m (expected every ~${expectedMins}m). ${record.detail}`
2077
+ },
2078
+ config.dirs
2079
+ )
2080
+ );
1464
2081
  }
1465
2082
  }
1466
2083
  }
@@ -1519,118 +2136,6 @@ function makePayload(agentId, condition, previousStatus, currentStatus, record,
1519
2136
  };
1520
2137
  }
1521
2138
 
1522
- // src/watch-alerts.ts
1523
- var import_node_https = require("https");
1524
- var import_node_http = require("http");
1525
- var import_node_child_process2 = require("child_process");
1526
- function formatAlertMessage(payload) {
1527
- const time = new Date(payload.timestamp).toISOString();
1528
- const arrow = `${payload.previousStatus} \u2192 ${payload.currentStatus}`;
1529
- return [
1530
- `[ALERT] ${payload.condition}: "${payload.agentId}"`,
1531
- ` Status: ${arrow}`,
1532
- payload.detail ? ` Detail: ${payload.detail}` : null,
1533
- ` File: ${payload.file}`,
1534
- ` Time: ${time}`
1535
- ].filter(Boolean).join("\n");
1536
- }
1537
- function formatTelegram(payload) {
1538
- const icon = payload.condition === "recovery" ? "\u2705" : "\u26A0\uFE0F";
1539
- const time = new Date(payload.timestamp).toLocaleTimeString();
1540
- return [
1541
- `${icon} *AgentFlow Alert*`,
1542
- `*${payload.condition}*: \`${payload.agentId}\``,
1543
- `Status: ${payload.previousStatus} \u2192 ${payload.currentStatus}`,
1544
- payload.detail ? `Detail: ${payload.detail.slice(0, 200)}` : null,
1545
- `Time: ${time}`
1546
- ].filter(Boolean).join("\n");
1547
- }
1548
- async function sendAlert(payload, channel) {
1549
- try {
1550
- switch (channel.type) {
1551
- case "stdout":
1552
- sendStdout(payload);
1553
- break;
1554
- case "telegram":
1555
- await sendTelegram(payload, channel.botToken, channel.chatId);
1556
- break;
1557
- case "webhook":
1558
- await sendWebhook(payload, channel.url);
1559
- break;
1560
- case "command":
1561
- await sendCommand(payload, channel.cmd);
1562
- break;
1563
- }
1564
- } catch (err) {
1565
- const msg = err instanceof Error ? err.message : String(err);
1566
- console.error(`[agentflow] Failed to send ${channel.type} alert: ${msg}`);
1567
- }
1568
- }
1569
- function sendStdout(payload) {
1570
- console.log(formatAlertMessage(payload));
1571
- }
1572
- function sendTelegram(payload, botToken, chatId) {
1573
- const body = JSON.stringify({
1574
- chat_id: chatId,
1575
- text: formatTelegram(payload),
1576
- parse_mode: "Markdown"
1577
- });
1578
- return new Promise((resolve4, reject) => {
1579
- const req = (0, import_node_https.request)(
1580
- `https://api.telegram.org/bot${botToken}/sendMessage`,
1581
- { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
1582
- (res) => {
1583
- res.resume();
1584
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1585
- else reject(new Error(`Telegram API returned ${res.statusCode}`));
1586
- }
1587
- );
1588
- req.on("error", reject);
1589
- req.write(body);
1590
- req.end();
1591
- });
1592
- }
1593
- function sendWebhook(payload, url) {
1594
- const body = JSON.stringify(payload);
1595
- const isHttps = url.startsWith("https");
1596
- const doRequest = isHttps ? import_node_https.request : import_node_http.request;
1597
- return new Promise((resolve4, reject) => {
1598
- const req = doRequest(
1599
- url,
1600
- { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
1601
- (res) => {
1602
- res.resume();
1603
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1604
- else reject(new Error(`Webhook returned ${res.statusCode}`));
1605
- }
1606
- );
1607
- req.on("error", reject);
1608
- req.setTimeout(1e4, () => {
1609
- req.destroy(new Error("Webhook timeout"));
1610
- });
1611
- req.write(body);
1612
- req.end();
1613
- });
1614
- }
1615
- function sendCommand(payload, cmd) {
1616
- return new Promise((resolve4, reject) => {
1617
- const env = {
1618
- ...process.env,
1619
- AGENTFLOW_ALERT_AGENT: payload.agentId,
1620
- AGENTFLOW_ALERT_CONDITION: payload.condition,
1621
- AGENTFLOW_ALERT_STATUS: payload.currentStatus,
1622
- AGENTFLOW_ALERT_PREVIOUS_STATUS: payload.previousStatus,
1623
- AGENTFLOW_ALERT_DETAIL: payload.detail,
1624
- AGENTFLOW_ALERT_FILE: payload.file,
1625
- AGENTFLOW_ALERT_TIMESTAMP: String(payload.timestamp)
1626
- };
1627
- (0, import_node_child_process2.exec)(cmd, { env, timeout: 3e4 }, (err) => {
1628
- if (err) reject(err);
1629
- else resolve4();
1630
- });
1631
- });
1632
- }
1633
-
1634
2139
  // src/watch.ts
1635
2140
  function parseWatchArgs(argv) {
1636
2141
  const dirs = [];
@@ -1672,7 +2177,9 @@ function parseWatchArgs(argv) {
1672
2177
  if (botToken && chatId) {
1673
2178
  notifyChannels.push({ type: "telegram", botToken, chatId });
1674
2179
  } else {
1675
- console.error("Warning: --notify telegram requires AGENTFLOW_TELEGRAM_BOT_TOKEN and AGENTFLOW_TELEGRAM_CHAT_ID env vars");
2180
+ console.error(
2181
+ "Warning: --notify telegram requires AGENTFLOW_TELEGRAM_BOT_TOKEN and AGENTFLOW_TELEGRAM_CHAT_ID env vars"
2182
+ );
1676
2183
  }
1677
2184
  } else if (val.startsWith("webhook:")) {
1678
2185
  notifyChannels.push({ type: "webhook", url: val.slice(8) });
@@ -1724,7 +2231,8 @@ function parseWatchArgs(argv) {
1724
2231
  };
1725
2232
  }
1726
2233
  function printWatchUsage() {
1727
- console.log(`
2234
+ console.log(
2235
+ `
1728
2236
  AgentFlow Watch \u2014 headless alert system for agent infrastructure.
1729
2237
 
1730
2238
  Polls directories for JSON/JSONL files, detects failures and stale
@@ -1767,7 +2275,8 @@ Examples:
1767
2275
  agentflow watch ./data ./cron --notify telegram --poll 60
1768
2276
  agentflow watch ./traces --notify webhook:https://hooks.slack.com/... --alert-on consecutive-errors:3
1769
2277
  agentflow watch ./data --notify "command:curl -X POST https://my-pagerduty/alert"
1770
- `.trim());
2278
+ `.trim()
2279
+ );
1771
2280
  }
1772
2281
  function startWatch(argv) {
1773
2282
  const config = parseWatchArgs(argv);
@@ -1796,7 +2305,9 @@ agentflow watch started`);
1796
2305
  console.log(` Directories: ${valid.join(", ")}`);
1797
2306
  console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
1798
2307
  console.log(` Alert on: ${condLabels.join(", ")}`);
1799
- console.log(` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`);
2308
+ console.log(
2309
+ ` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`
2310
+ );
1800
2311
  console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
1801
2312
  console.log(` State: ${config.stateFilePath}`);
1802
2313
  console.log(` Hostname: ${(0, import_node_os.hostname)()}`);
@@ -1822,9 +2333,13 @@ agentflow watch started`);
1822
2333
  if (pollCount % 10 === 0) {
1823
2334
  const agentCount = Object.keys(state.agents).length;
1824
2335
  const errorCount = Object.values(state.agents).filter((a) => a.lastStatus === "error").length;
1825
- const runningCount = Object.values(state.agents).filter((a) => a.lastStatus === "running").length;
2336
+ const runningCount = Object.values(state.agents).filter(
2337
+ (a) => a.lastStatus === "running"
2338
+ ).length;
1826
2339
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
1827
- console.log(`[${time}] heartbeat: ${agentCount} agents, ${runningCount} running, ${errorCount} errors, ${files.length} files`);
2340
+ console.log(
2341
+ `[${time}] heartbeat: ${agentCount} agents, ${runningCount} running, ${errorCount} errors, ${files.length} files`
2342
+ );
1828
2343
  }
1829
2344
  }
1830
2345
  poll();
@@ -1841,7 +2356,9 @@ agentflow watch started`);
1841
2356
  }
1842
2357
  // Annotate the CommonJS export names for ESM import in node:
1843
2358
  0 && (module.exports = {
2359
+ checkGuards,
1844
2360
  createGraphBuilder,
2361
+ createTraceStore,
1845
2362
  findWaitingOn,
1846
2363
  getChildren,
1847
2364
  getCriticalPath,
@@ -1860,5 +2377,8 @@ agentflow watch started`);
1860
2377
  runTraced,
1861
2378
  startLive,
1862
2379
  startWatch,
1863
- stitchTrace
2380
+ stitchTrace,
2381
+ toAsciiTree,
2382
+ toTimeline,
2383
+ withGuards
1864
2384
  });