agentflow-core 0.3.2 → 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) {
@@ -1183,24 +1258,38 @@ function render(config) {
1183
1258
  function truncate(s, max) {
1184
1259
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1185
1260
  }
1261
+ const termWidth = process.stdout.columns || 120;
1262
+ const detailWidth = Math.max(20, termWidth - 60);
1186
1263
  if (firstRender) {
1187
1264
  process.stdout.write("\x1B[2J");
1188
1265
  firstRender = false;
1189
1266
  }
1190
1267
  const L = [];
1191
1268
  writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
1192
- 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
+ );
1193
1273
  const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
1194
1274
  const pad1 = Math.max(0, 64 - metaLine.length);
1195
- 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
+ );
1196
1279
  writeLine(L, `${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
1197
1280
  const sc = totFail === 0 ? C.green : C.yellow;
1198
1281
  writeLine(L, "");
1199
- 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
+ );
1200
1286
  writeLine(L, "");
1201
1287
  writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
1202
1288
  writeLine(L, "");
1203
- 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
+ );
1204
1293
  let lineCount = 0;
1205
1294
  for (const g of groups) {
1206
1295
  if (lineCount > 35) break;
@@ -1210,14 +1299,20 @@ function render(config) {
1210
1299
  if (g.children.length === 0) {
1211
1300
  const name = truncate(g.name, 26).padEnd(26);
1212
1301
  const st = statusText(g);
1213
- const det = truncate(g.detail, 30);
1214
- writeLine(L, ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`);
1302
+ const det = truncate(g.detail, detailWidth);
1303
+ writeLine(
1304
+ L,
1305
+ ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`
1306
+ );
1215
1307
  lineCount++;
1216
1308
  } else {
1217
1309
  const name = truncate(g.name, 24).padEnd(24);
1218
1310
  const st = statusText(g);
1219
1311
  const tag = sourceTag(g.source);
1220
- 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
+ );
1221
1316
  lineCount++;
1222
1317
  const kids = g.children.slice(0, 12);
1223
1318
  for (let i = 0; i < kids.length; i++) {
@@ -1228,8 +1323,11 @@ function render(config) {
1228
1323
  const cIcon = statusIcon(child.status, Date.now() - child.lastActive < 3e5);
1229
1324
  const cName = truncate(child.id, 22).padEnd(22);
1230
1325
  const cActive = `${C.dim}${timeStr(child.lastActive)}${C.reset}`;
1231
- const cDet = truncate(child.detail, 25);
1232
- writeLine(L, ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`);
1326
+ const cDet = truncate(child.detail, detailWidth - 5);
1327
+ writeLine(
1328
+ L,
1329
+ ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`
1330
+ );
1233
1331
  lineCount++;
1234
1332
  }
1235
1333
  if (g.children.length > 12) {
@@ -1246,7 +1344,10 @@ function render(config) {
1246
1344
  const si = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1247
1345
  const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
1248
1346
  const tid = dt.traceId.slice(0, 8);
1249
- 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
+ );
1250
1351
  const tree = getTraceTree(dt);
1251
1352
  for (let i = 0; i < Math.min(tree.length, 6); i++) {
1252
1353
  const tg = tree[i];
@@ -1256,7 +1357,10 @@ function render(config) {
1256
1357
  const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1257
1358
  const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1258
1359
  const gd = tg.endTime ? `${tg.endTime - tg.startTime}ms` : "running";
1259
- 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
+ );
1260
1364
  }
1261
1365
  }
1262
1366
  }
@@ -1270,8 +1374,11 @@ function render(config) {
1270
1374
  const agent = truncate(r.id, 26).padEnd(26);
1271
1375
  const age = Math.floor((Date.now() - r.lastActive) / 1e3);
1272
1376
  const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
1273
- const det = truncate(r.detail, 25);
1274
- writeLine(L, ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`);
1377
+ const det = truncate(r.detail, detailWidth);
1378
+ writeLine(
1379
+ L,
1380
+ ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`
1381
+ );
1275
1382
  }
1276
1383
  }
1277
1384
  if (files.length === 0) {
@@ -1293,39 +1400,540 @@ function getDistDepth(dt, spanId) {
1293
1400
  }
1294
1401
  function startLive(argv) {
1295
1402
  const config = parseArgs(argv);
1296
- 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));
1297
1404
  if (valid.length === 0) {
1298
1405
  console.error(`No valid directories found: ${config.dirs.join(", ")}`);
1299
1406
  console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
1300
1407
  process.exit(1);
1301
1408
  }
1302
- const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
1303
- if (invalid.length > 0) {
1304
- 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
+ }
1305
1792
  }
1306
- config.dirs = valid;
1307
- render(config);
1308
- let debounce = null;
1309
- for (const dir of config.dirs) {
1310
- try {
1311
- (0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
1312
- if (debounce) clearTimeout(debounce);
1313
- debounce = setTimeout(() => render(config), 500);
1314
- });
1315
- } 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
+ }
1316
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}`);
1317
1811
  }
1318
- setInterval(() => render(config), config.refreshMs);
1319
- process.on("SIGINT", () => {
1320
- console.log("\n" + C.dim + "Monitor stopped." + C.reset);
1321
- process.exit(0);
1322
- });
1812
+ return lines.join("\n");
1323
1813
  }
1324
1814
 
1325
1815
  // src/watch.ts
1326
1816
  var import_node_fs4 = require("fs");
1327
- var import_node_path3 = require("path");
1328
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
+ }
1329
1937
 
1330
1938
  // src/watch-state.ts
1331
1939
  var import_node_fs3 = require("fs");
@@ -1391,7 +1999,9 @@ function detectTransitions(previous, currentRecords, config, now) {
1391
1999
  const hasError = config.alertConditions.some((c) => c.type === "error");
1392
2000
  const hasRecovery = config.alertConditions.some((c) => c.type === "recovery");
1393
2001
  const staleConditions = config.alertConditions.filter((c) => c.type === "stale");
1394
- const consecutiveConditions = config.alertConditions.filter((c) => c.type === "consecutive-errors");
2002
+ const consecutiveConditions = config.alertConditions.filter(
2003
+ (c) => c.type === "consecutive-errors"
2004
+ );
1395
2005
  const byAgent = /* @__PURE__ */ new Map();
1396
2006
  for (const r of currentRecords) {
1397
2007
  const existing = byAgent.get(r.id);
@@ -1415,14 +2025,16 @@ function detectTransitions(previous, currentRecords, config, now) {
1415
2025
  for (const cond of consecutiveConditions) {
1416
2026
  if (newConsec === cond.threshold) {
1417
2027
  if (canAlert(prev, `consecutive-errors:${cond.threshold}`, config.cooldownMs, now)) {
1418
- alerts.push(makePayload(
1419
- agentId,
1420
- `consecutive-errors (${cond.threshold})`,
1421
- prevStatus,
1422
- currStatus,
1423
- { ...record, detail: `${newConsec} consecutive errors. ${record.detail}` },
1424
- config.dirs
1425
- ));
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
+ );
1426
2038
  }
1427
2039
  }
1428
2040
  }
@@ -1431,14 +2043,16 @@ function detectTransitions(previous, currentRecords, config, now) {
1431
2043
  if (sinceActive > cond.durationMs && record.lastActive > 0) {
1432
2044
  if (canAlert(prev, "stale", config.cooldownMs, now)) {
1433
2045
  const mins = Math.floor(sinceActive / 6e4);
1434
- alerts.push(makePayload(
1435
- agentId,
1436
- "stale",
1437
- prevStatus,
1438
- currStatus,
1439
- { ...record, detail: `No update for ${mins}m. ${record.detail}` },
1440
- config.dirs
1441
- ));
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
+ );
1442
2056
  }
1443
2057
  }
1444
2058
  }
@@ -1451,14 +2065,19 @@ function detectTransitions(previous, currentRecords, config, now) {
1451
2065
  if (canAlert(prev, "stale-auto", config.cooldownMs, now)) {
1452
2066
  const mins = Math.floor(sinceActive / 6e4);
1453
2067
  const expectedMins = Math.floor(expectedInterval / 6e4);
1454
- alerts.push(makePayload(
1455
- agentId,
1456
- "stale (auto)",
1457
- prevStatus,
1458
- currStatus,
1459
- { ...record, detail: `No update for ${mins}m (expected every ~${expectedMins}m). ${record.detail}` },
1460
- config.dirs
1461
- ));
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
+ );
1462
2081
  }
1463
2082
  }
1464
2083
  }
@@ -1517,118 +2136,6 @@ function makePayload(agentId, condition, previousStatus, currentStatus, record,
1517
2136
  };
1518
2137
  }
1519
2138
 
1520
- // src/watch-alerts.ts
1521
- var import_node_https = require("https");
1522
- var import_node_http = require("http");
1523
- var import_node_child_process2 = require("child_process");
1524
- function formatAlertMessage(payload) {
1525
- const time = new Date(payload.timestamp).toISOString();
1526
- const arrow = `${payload.previousStatus} \u2192 ${payload.currentStatus}`;
1527
- return [
1528
- `[ALERT] ${payload.condition}: "${payload.agentId}"`,
1529
- ` Status: ${arrow}`,
1530
- payload.detail ? ` Detail: ${payload.detail}` : null,
1531
- ` File: ${payload.file}`,
1532
- ` Time: ${time}`
1533
- ].filter(Boolean).join("\n");
1534
- }
1535
- function formatTelegram(payload) {
1536
- const icon = payload.condition === "recovery" ? "\u2705" : "\u26A0\uFE0F";
1537
- const time = new Date(payload.timestamp).toLocaleTimeString();
1538
- return [
1539
- `${icon} *AgentFlow Alert*`,
1540
- `*${payload.condition}*: \`${payload.agentId}\``,
1541
- `Status: ${payload.previousStatus} \u2192 ${payload.currentStatus}`,
1542
- payload.detail ? `Detail: ${payload.detail.slice(0, 200)}` : null,
1543
- `Time: ${time}`
1544
- ].filter(Boolean).join("\n");
1545
- }
1546
- async function sendAlert(payload, channel) {
1547
- try {
1548
- switch (channel.type) {
1549
- case "stdout":
1550
- sendStdout(payload);
1551
- break;
1552
- case "telegram":
1553
- await sendTelegram(payload, channel.botToken, channel.chatId);
1554
- break;
1555
- case "webhook":
1556
- await sendWebhook(payload, channel.url);
1557
- break;
1558
- case "command":
1559
- await sendCommand(payload, channel.cmd);
1560
- break;
1561
- }
1562
- } catch (err) {
1563
- const msg = err instanceof Error ? err.message : String(err);
1564
- console.error(`[agentflow] Failed to send ${channel.type} alert: ${msg}`);
1565
- }
1566
- }
1567
- function sendStdout(payload) {
1568
- console.log(formatAlertMessage(payload));
1569
- }
1570
- function sendTelegram(payload, botToken, chatId) {
1571
- const body = JSON.stringify({
1572
- chat_id: chatId,
1573
- text: formatTelegram(payload),
1574
- parse_mode: "Markdown"
1575
- });
1576
- return new Promise((resolve4, reject) => {
1577
- const req = (0, import_node_https.request)(
1578
- `https://api.telegram.org/bot${botToken}/sendMessage`,
1579
- { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
1580
- (res) => {
1581
- res.resume();
1582
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1583
- else reject(new Error(`Telegram API returned ${res.statusCode}`));
1584
- }
1585
- );
1586
- req.on("error", reject);
1587
- req.write(body);
1588
- req.end();
1589
- });
1590
- }
1591
- function sendWebhook(payload, url) {
1592
- const body = JSON.stringify(payload);
1593
- const isHttps = url.startsWith("https");
1594
- const doRequest = isHttps ? import_node_https.request : import_node_http.request;
1595
- return new Promise((resolve4, reject) => {
1596
- const req = doRequest(
1597
- url,
1598
- { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
1599
- (res) => {
1600
- res.resume();
1601
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1602
- else reject(new Error(`Webhook returned ${res.statusCode}`));
1603
- }
1604
- );
1605
- req.on("error", reject);
1606
- req.setTimeout(1e4, () => {
1607
- req.destroy(new Error("Webhook timeout"));
1608
- });
1609
- req.write(body);
1610
- req.end();
1611
- });
1612
- }
1613
- function sendCommand(payload, cmd) {
1614
- return new Promise((resolve4, reject) => {
1615
- const env = {
1616
- ...process.env,
1617
- AGENTFLOW_ALERT_AGENT: payload.agentId,
1618
- AGENTFLOW_ALERT_CONDITION: payload.condition,
1619
- AGENTFLOW_ALERT_STATUS: payload.currentStatus,
1620
- AGENTFLOW_ALERT_PREVIOUS_STATUS: payload.previousStatus,
1621
- AGENTFLOW_ALERT_DETAIL: payload.detail,
1622
- AGENTFLOW_ALERT_FILE: payload.file,
1623
- AGENTFLOW_ALERT_TIMESTAMP: String(payload.timestamp)
1624
- };
1625
- (0, import_node_child_process2.exec)(cmd, { env, timeout: 3e4 }, (err) => {
1626
- if (err) reject(err);
1627
- else resolve4();
1628
- });
1629
- });
1630
- }
1631
-
1632
2139
  // src/watch.ts
1633
2140
  function parseWatchArgs(argv) {
1634
2141
  const dirs = [];
@@ -1670,7 +2177,9 @@ function parseWatchArgs(argv) {
1670
2177
  if (botToken && chatId) {
1671
2178
  notifyChannels.push({ type: "telegram", botToken, chatId });
1672
2179
  } else {
1673
- 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
+ );
1674
2183
  }
1675
2184
  } else if (val.startsWith("webhook:")) {
1676
2185
  notifyChannels.push({ type: "webhook", url: val.slice(8) });
@@ -1722,7 +2231,8 @@ function parseWatchArgs(argv) {
1722
2231
  };
1723
2232
  }
1724
2233
  function printWatchUsage() {
1725
- console.log(`
2234
+ console.log(
2235
+ `
1726
2236
  AgentFlow Watch \u2014 headless alert system for agent infrastructure.
1727
2237
 
1728
2238
  Polls directories for JSON/JSONL files, detects failures and stale
@@ -1765,7 +2275,8 @@ Examples:
1765
2275
  agentflow watch ./data ./cron --notify telegram --poll 60
1766
2276
  agentflow watch ./traces --notify webhook:https://hooks.slack.com/... --alert-on consecutive-errors:3
1767
2277
  agentflow watch ./data --notify "command:curl -X POST https://my-pagerduty/alert"
1768
- `.trim());
2278
+ `.trim()
2279
+ );
1769
2280
  }
1770
2281
  function startWatch(argv) {
1771
2282
  const config = parseWatchArgs(argv);
@@ -1794,7 +2305,9 @@ agentflow watch started`);
1794
2305
  console.log(` Directories: ${valid.join(", ")}`);
1795
2306
  console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
1796
2307
  console.log(` Alert on: ${condLabels.join(", ")}`);
1797
- console.log(` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`);
2308
+ console.log(
2309
+ ` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`
2310
+ );
1798
2311
  console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
1799
2312
  console.log(` State: ${config.stateFilePath}`);
1800
2313
  console.log(` Hostname: ${(0, import_node_os.hostname)()}`);
@@ -1820,9 +2333,13 @@ agentflow watch started`);
1820
2333
  if (pollCount % 10 === 0) {
1821
2334
  const agentCount = Object.keys(state.agents).length;
1822
2335
  const errorCount = Object.values(state.agents).filter((a) => a.lastStatus === "error").length;
1823
- 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;
1824
2339
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
1825
- 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
+ );
1826
2343
  }
1827
2344
  }
1828
2345
  poll();
@@ -1839,7 +2356,9 @@ agentflow watch started`);
1839
2356
  }
1840
2357
  // Annotate the CommonJS export names for ESM import in node:
1841
2358
  0 && (module.exports = {
2359
+ checkGuards,
1842
2360
  createGraphBuilder,
2361
+ createTraceStore,
1843
2362
  findWaitingOn,
1844
2363
  getChildren,
1845
2364
  getCriticalPath,
@@ -1858,5 +2377,8 @@ agentflow watch started`);
1858
2377
  runTraced,
1859
2378
  startLive,
1860
2379
  startWatch,
1861
- stitchTrace
2380
+ stitchTrace,
2381
+ toAsciiTree,
2382
+ toTimeline,
2383
+ withGuards
1862
2384
  });