agentflow-core 0.3.3 → 0.5.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.d.ts CHANGED
@@ -276,6 +276,224 @@ interface MutableExecutionNode {
276
276
  */
277
277
  declare function createGraphBuilder(config?: AgentFlowConfig): GraphBuilder;
278
278
 
279
+ /**
280
+ * Pure query functions for interrogating a built `ExecutionGraph`.
281
+ * Every function takes a frozen graph and returns derived data without mutation.
282
+ * @module
283
+ */
284
+
285
+ /**
286
+ * Find a node by its ID.
287
+ *
288
+ * @param graph - The execution graph to search.
289
+ * @param nodeId - The node ID to look up.
290
+ * @returns The node, or `undefined` if not found.
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * const node = getNode(graph, 'node_002');
295
+ * if (node) console.log(node.name);
296
+ * ```
297
+ */
298
+ declare function getNode(graph: ExecutionGraph, nodeId: string): ExecutionNode | undefined;
299
+ /**
300
+ * Get the direct children of a node.
301
+ *
302
+ * @param graph - The execution graph to search.
303
+ * @param nodeId - The parent node ID.
304
+ * @returns Array of child nodes (may be empty).
305
+ *
306
+ * @example
307
+ * ```ts
308
+ * const children = getChildren(graph, rootId);
309
+ * ```
310
+ */
311
+ declare function getChildren(graph: ExecutionGraph, nodeId: string): ExecutionNode[];
312
+ /**
313
+ * Get the parent of a node.
314
+ *
315
+ * @param graph - The execution graph to search.
316
+ * @param nodeId - The child node ID.
317
+ * @returns The parent node, or `undefined` if root or not found.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * const parent = getParent(graph, toolId);
322
+ * ```
323
+ */
324
+ declare function getParent(graph: ExecutionGraph, nodeId: string): ExecutionNode | undefined;
325
+ /**
326
+ * Find all nodes with a failure-category status: `failed`, `hung`, or `timeout`.
327
+ *
328
+ * @param graph - The execution graph to search.
329
+ * @returns Array of nodes with failure statuses (may be empty).
330
+ */
331
+ declare function getFailures(graph: ExecutionGraph): ExecutionNode[];
332
+ /**
333
+ * Find all nodes that are still running (status `'running'`, no endTime).
334
+ *
335
+ * @param graph - The execution graph to search.
336
+ * @returns Array of running/hung nodes.
337
+ */
338
+ declare function getHungNodes(graph: ExecutionGraph): ExecutionNode[];
339
+ /**
340
+ * Find the critical path: the longest-duration path from the root to any leaf node.
341
+ * Uses node duration (endTime - startTime) as the weight.
342
+ * Running nodes use `Date.now()` as a provisional endTime.
343
+ *
344
+ * @param graph - The execution graph to analyse.
345
+ * @returns Nodes ordered from root to the deepest leaf on the longest path.
346
+ */
347
+ declare function getCriticalPath(graph: ExecutionGraph): ExecutionNode[];
348
+ /**
349
+ * Find what a node is waiting on.
350
+ * Returns nodes connected via `waited_on` edges where the given nodeId is the `from` side.
351
+ *
352
+ * @param graph - The execution graph to search.
353
+ * @param nodeId - The node that is doing the waiting.
354
+ * @returns Array of nodes that are being waited on.
355
+ */
356
+ declare function findWaitingOn(graph: ExecutionGraph, nodeId: string): ExecutionNode[];
357
+ /**
358
+ * Get all descendants of a node (children, grandchildren, etc.) in breadth-first order.
359
+ * The given node itself is NOT included.
360
+ *
361
+ * @param graph - The execution graph to search.
362
+ * @param nodeId - The ancestor node ID.
363
+ * @returns All descendant nodes in BFS order.
364
+ */
365
+ declare function getSubtree(graph: ExecutionGraph, nodeId: string): ExecutionNode[];
366
+ /**
367
+ * Total wall-clock duration of the graph in milliseconds.
368
+ * If the graph is still running, uses `Date.now()` as the provisional end.
369
+ *
370
+ * @param graph - The execution graph.
371
+ * @returns Duration in milliseconds.
372
+ */
373
+ declare function getDuration(graph: ExecutionGraph): number;
374
+ /**
375
+ * Maximum nesting depth of the graph. The root node is depth 0.
376
+ *
377
+ * @param graph - The execution graph.
378
+ * @returns The maximum depth (0 for a single-node graph, -1 for empty).
379
+ */
380
+ declare function getDepth(graph: ExecutionGraph): number;
381
+ /**
382
+ * Compute aggregate statistics for the execution graph.
383
+ *
384
+ * @param graph - The execution graph to analyse.
385
+ * @returns Statistics including node counts by type and status, depth, duration, and failure counts.
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * const stats = getStats(graph);
390
+ * console.log(`${stats.totalNodes} nodes, ${stats.failureCount} failures`);
391
+ * ```
392
+ */
393
+ declare function getStats(graph: ExecutionGraph): GraphStats;
394
+
395
+ declare function groupByTraceId(graphs: ExecutionGraph[]): Map<string, ExecutionGraph[]>;
396
+ declare function stitchTrace(graphs: ExecutionGraph[]): DistributedTrace;
397
+ declare function getTraceTree(trace: DistributedTrace): ExecutionGraph[];
398
+
399
+ /**
400
+ * Runtime guards for detecting problematic agent behaviors during graph construction.
401
+ *
402
+ * Guards operate on ExecutionGraph snapshots and detect three types of violations:
403
+ * - Long-running spans: nodes that exceed timeout thresholds for their type
404
+ * - Reasoning loops: consecutive nodes of the same type in a parent-child chain
405
+ * - Spawn explosion: excessive depth or agent/subagent spawn counts
406
+ *
407
+ * @module
408
+ */
409
+
410
+ /**
411
+ * Configuration for runtime guard detection.
412
+ */
413
+ interface GuardConfig {
414
+ /** Timeout thresholds per node type in milliseconds. */
415
+ readonly timeouts?: Partial<Record<NodeType, number>>;
416
+ /** Maximum consecutive same-type nodes before flagging reasoning loop (default: 25). */
417
+ readonly maxReasoningSteps?: number;
418
+ /** Maximum graph depth before flagging spawn explosion (default: 10). */
419
+ readonly maxDepth?: number;
420
+ /** Maximum total agent/subagent nodes before flagging spawn explosion (default: 50). */
421
+ readonly maxAgentSpawns?: number;
422
+ /** Action to take when guard violations are detected. */
423
+ readonly onViolation?: 'warn' | 'error' | 'abort';
424
+ /** Custom logger for warnings (defaults to console.warn). */
425
+ readonly logger?: (message: string) => void;
426
+ }
427
+ /**
428
+ * A detected guard violation.
429
+ */
430
+ interface GuardViolation {
431
+ readonly type: 'timeout' | 'reasoning-loop' | 'spawn-explosion';
432
+ readonly nodeId: string;
433
+ readonly message: string;
434
+ readonly timestamp: number;
435
+ }
436
+ /**
437
+ * Check an execution graph for guard violations.
438
+ *
439
+ * This is a pure function that analyzes a graph snapshot and returns detected violations
440
+ * without modifying the graph or producing side effects.
441
+ *
442
+ * @param graph - The execution graph to analyze.
443
+ * @param config - Optional guard configuration.
444
+ * @returns Array of detected violations (may be empty).
445
+ *
446
+ * @example
447
+ * ```ts
448
+ * const violations = checkGuards(graph, { maxDepth: 5 });
449
+ * if (violations.length > 0) {
450
+ * console.log(`Found ${violations.length} violations`);
451
+ * }
452
+ * ```
453
+ */
454
+ declare function checkGuards(graph: ExecutionGraph, config?: GuardConfig): readonly GuardViolation[];
455
+ /**
456
+ * Create a guard-aware wrapper around a GraphBuilder.
457
+ *
458
+ * The returned builder has an identical interface to the original but intercepts
459
+ * `endNode` and `build` calls to check for guard violations. Violations are handled
460
+ * according to the `onViolation` configuration.
461
+ *
462
+ * @param builder - The original GraphBuilder to wrap.
463
+ * @param config - Guard configuration.
464
+ * @returns A GraphBuilder with identical interface but guard protection.
465
+ *
466
+ * @example
467
+ * ```ts
468
+ * const raw = createGraphBuilder({ agentId: 'test' });
469
+ * const guarded = withGuards(raw, { maxDepth: 5, onViolation: 'abort' });
470
+ *
471
+ * // Use exactly like a normal builder
472
+ * const root = guarded.startNode({ type: 'agent', name: 'main' });
473
+ * guarded.endNode(root); // Will check for violations
474
+ * ```
475
+ */
476
+ declare function withGuards(builder: GraphBuilder, config?: GuardConfig): GraphBuilder;
477
+
478
+ /**
479
+ * AgentFlow Live Monitor — real-time terminal dashboard for any agent system.
480
+ *
481
+ * Auto-detects and displays data from any JSON/JSONL files in the watched
482
+ * directory. Works with agentflow traces, generic state files, job
483
+ * schedulers, session logs — no configuration needed.
484
+ *
485
+ * File detection:
486
+ * .json with `nodes` + `agentId` → AgentFlow trace (full analysis)
487
+ * .json with array of objects with `state` → Job/task list (per-item status)
488
+ * .json with `status`/`pid`/`tools` → Process/worker state
489
+ * .json with any other structure → Generic state (mtime-based)
490
+ * .jsonl → Session log (last entry status)
491
+ *
492
+ * @module
493
+ */
494
+
495
+ declare function startLive(argv: string[]): void;
496
+
279
497
  /**
280
498
  * Load and deserialize execution graphs from JSON.
281
499
  *
@@ -378,23 +596,89 @@ interface RunResult {
378
596
  declare function runTraced(config: RunConfig): Promise<RunResult>;
379
597
 
380
598
  /**
381
- * AgentFlow Live Monitor — real-time terminal dashboard for any agent system.
599
+ * JSON file-based trace storage for ExecutionGraphs.
382
600
  *
383
- * Auto-detects and displays data from any JSON/JSONL files in the watched
384
- * directory. Works with agentflow traces, generic state files, job
385
- * schedulers, session logs — no configuration needed.
601
+ * One JSON file per graph, using existing graphToJson/loadGraph for serialization.
602
+ * Compatible with `agentflow watch` auto-detection.
386
603
  *
387
- * File detection:
388
- * .json with `nodes` + `agentId` → AgentFlow trace (full analysis)
389
- * .json with array of objects with `state` → Job/task list (per-item status)
390
- * .json with `status`/`pid`/`tools` → Process/worker state
391
- * .json with any other structure → Generic state (mtime-based)
392
- * .jsonl → Session log (last entry status)
604
+ * @module
605
+ */
606
+
607
+ /**
608
+ * Trace storage interface for saving, loading, and querying execution graphs.
609
+ */
610
+ interface TraceStore {
611
+ /** Save a graph to disk. Returns the file path. */
612
+ save(graph: ExecutionGraph): Promise<string>;
613
+ /** Load a graph by ID. Returns null if not found. */
614
+ get(graphId: string): Promise<ExecutionGraph | null>;
615
+ /** List all stored graphs, optionally filtered by status. */
616
+ list(opts?: {
617
+ status?: GraphStatus;
618
+ limit?: number;
619
+ }): Promise<ExecutionGraph[]>;
620
+ /** Find all nodes with stuck status (running/hung/timeout) across all stored traces. */
621
+ getStuckSpans(): Promise<ExecutionNode[]>;
622
+ /** Find reasoning loops: consecutive same-type node sequences exceeding threshold. */
623
+ getReasoningLoops(threshold?: number): Promise<ReadonlyArray<{
624
+ graphId: string;
625
+ nodes: ExecutionNode[];
626
+ }>>;
627
+ }
628
+ /**
629
+ * Create a JSON file-based trace store.
630
+ *
631
+ * @param dir - Directory to store trace JSON files.
632
+ * @returns A TraceStore instance.
633
+ *
634
+ * @example
635
+ * ```ts
636
+ * const store = createTraceStore('./traces');
637
+ * await store.save(graph);
638
+ * const loaded = await store.get(graph.id);
639
+ * ```
640
+ */
641
+ declare function createTraceStore(dir: string): TraceStore;
642
+
643
+ /**
644
+ * Trace visualization: ASCII tree and timeline rendering for ExecutionGraphs.
645
+ *
646
+ * All functions are pure — they take an ExecutionGraph and return formatted strings.
393
647
  *
394
648
  * @module
395
649
  */
396
650
 
397
- declare function startLive(argv: string[]): void;
651
+ /**
652
+ * Render an ExecutionGraph as an ASCII tree showing parent-child hierarchy.
653
+ *
654
+ * @param graph - The execution graph to render.
655
+ * @returns A multi-line string showing the tree with status icons, durations, and metadata.
656
+ *
657
+ * @example
658
+ * ```ts
659
+ * console.log(toAsciiTree(graph));
660
+ * // ✓ main (agent) 4.2s
661
+ * // ├─ ✓ search (tool) 1.1s
662
+ * // └─ ✗ analyze (tool) 0.5s — Error: rate limit
663
+ * ```
664
+ */
665
+ declare function toAsciiTree(graph: ExecutionGraph): string;
666
+ /**
667
+ * Render an ExecutionGraph as a horizontal timeline/waterfall.
668
+ *
669
+ * @param graph - The execution graph to render.
670
+ * @returns A multi-line string showing spans as horizontal bars relative to graph start.
671
+ *
672
+ * @example
673
+ * ```ts
674
+ * console.log(toTimeline(graph));
675
+ * // 0s 1s 2s 3s
676
+ * // ├─────────┼─────────┼─────────┤
677
+ * // ████████████████████████████████ main (4.2s)
678
+ * // ██████████ search (1.1s)
679
+ * ```
680
+ */
681
+ declare function toTimeline(graph: ExecutionGraph): string;
398
682
 
399
683
  /**
400
684
  * AgentFlow Watch — headless alert system for agent infrastructure.
@@ -459,124 +743,4 @@ interface AlertPayload {
459
743
  readonly dirs: readonly string[];
460
744
  }
461
745
 
462
- declare function groupByTraceId(graphs: ExecutionGraph[]): Map<string, ExecutionGraph[]>;
463
- declare function stitchTrace(graphs: ExecutionGraph[]): DistributedTrace;
464
- declare function getTraceTree(trace: DistributedTrace): ExecutionGraph[];
465
-
466
- /**
467
- * Pure query functions for interrogating a built `ExecutionGraph`.
468
- * Every function takes a frozen graph and returns derived data without mutation.
469
- * @module
470
- */
471
-
472
- /**
473
- * Find a node by its ID.
474
- *
475
- * @param graph - The execution graph to search.
476
- * @param nodeId - The node ID to look up.
477
- * @returns The node, or `undefined` if not found.
478
- *
479
- * @example
480
- * ```ts
481
- * const node = getNode(graph, 'node_002');
482
- * if (node) console.log(node.name);
483
- * ```
484
- */
485
- declare function getNode(graph: ExecutionGraph, nodeId: string): ExecutionNode | undefined;
486
- /**
487
- * Get the direct children of a node.
488
- *
489
- * @param graph - The execution graph to search.
490
- * @param nodeId - The parent node ID.
491
- * @returns Array of child nodes (may be empty).
492
- *
493
- * @example
494
- * ```ts
495
- * const children = getChildren(graph, rootId);
496
- * ```
497
- */
498
- declare function getChildren(graph: ExecutionGraph, nodeId: string): ExecutionNode[];
499
- /**
500
- * Get the parent of a node.
501
- *
502
- * @param graph - The execution graph to search.
503
- * @param nodeId - The child node ID.
504
- * @returns The parent node, or `undefined` if root or not found.
505
- *
506
- * @example
507
- * ```ts
508
- * const parent = getParent(graph, toolId);
509
- * ```
510
- */
511
- declare function getParent(graph: ExecutionGraph, nodeId: string): ExecutionNode | undefined;
512
- /**
513
- * Find all nodes with a failure-category status: `failed`, `hung`, or `timeout`.
514
- *
515
- * @param graph - The execution graph to search.
516
- * @returns Array of nodes with failure statuses (may be empty).
517
- */
518
- declare function getFailures(graph: ExecutionGraph): ExecutionNode[];
519
- /**
520
- * Find all nodes that are still running (status `'running'`, no endTime).
521
- *
522
- * @param graph - The execution graph to search.
523
- * @returns Array of running/hung nodes.
524
- */
525
- declare function getHungNodes(graph: ExecutionGraph): ExecutionNode[];
526
- /**
527
- * Find the critical path: the longest-duration path from the root to any leaf node.
528
- * Uses node duration (endTime - startTime) as the weight.
529
- * Running nodes use `Date.now()` as a provisional endTime.
530
- *
531
- * @param graph - The execution graph to analyse.
532
- * @returns Nodes ordered from root to the deepest leaf on the longest path.
533
- */
534
- declare function getCriticalPath(graph: ExecutionGraph): ExecutionNode[];
535
- /**
536
- * Find what a node is waiting on.
537
- * Returns nodes connected via `waited_on` edges where the given nodeId is the `from` side.
538
- *
539
- * @param graph - The execution graph to search.
540
- * @param nodeId - The node that is doing the waiting.
541
- * @returns Array of nodes that are being waited on.
542
- */
543
- declare function findWaitingOn(graph: ExecutionGraph, nodeId: string): ExecutionNode[];
544
- /**
545
- * Get all descendants of a node (children, grandchildren, etc.) in breadth-first order.
546
- * The given node itself is NOT included.
547
- *
548
- * @param graph - The execution graph to search.
549
- * @param nodeId - The ancestor node ID.
550
- * @returns All descendant nodes in BFS order.
551
- */
552
- declare function getSubtree(graph: ExecutionGraph, nodeId: string): ExecutionNode[];
553
- /**
554
- * Total wall-clock duration of the graph in milliseconds.
555
- * If the graph is still running, uses `Date.now()` as the provisional end.
556
- *
557
- * @param graph - The execution graph.
558
- * @returns Duration in milliseconds.
559
- */
560
- declare function getDuration(graph: ExecutionGraph): number;
561
- /**
562
- * Maximum nesting depth of the graph. The root node is depth 0.
563
- *
564
- * @param graph - The execution graph.
565
- * @returns The maximum depth (0 for a single-node graph, -1 for empty).
566
- */
567
- declare function getDepth(graph: ExecutionGraph): number;
568
- /**
569
- * Compute aggregate statistics for the execution graph.
570
- *
571
- * @param graph - The execution graph to analyse.
572
- * @returns Statistics including node counts by type and status, depth, duration, and failure counts.
573
- *
574
- * @example
575
- * ```ts
576
- * const stats = getStats(graph);
577
- * console.log(`${stats.totalNodes} nodes, ${stats.failureCount} failures`);
578
- * ```
579
- */
580
- declare function getStats(graph: ExecutionGraph): GraphStats;
581
-
582
- export { type Adapter, type AgentFlowConfig, type AlertCondition, type AlertPayload, type DistributedTrace, type EdgeType, type ExecutionEdge, type ExecutionGraph, type ExecutionNode, type GraphBuilder, type GraphStats, type GraphStatus, type MutableExecutionNode, type NodeStatus, type NodeType, type NotifyChannel, type RunConfig, type RunResult, type StartNodeOptions, type TraceEvent, type TraceEventType, type WatchConfig, type Writer, createGraphBuilder, findWaitingOn, getChildren, getCriticalPath, getDepth, getDuration, getFailures, getHungNodes, getNode, getParent, getStats, getSubtree, getTraceTree, graphToJson, groupByTraceId, loadGraph, runTraced, startLive, startWatch, stitchTrace };
746
+ export { type Adapter, type AgentFlowConfig, type AlertCondition, type AlertPayload, type DistributedTrace, type EdgeType, type ExecutionEdge, type ExecutionGraph, type ExecutionNode, type GraphBuilder, type GraphStats, type GraphStatus, type GuardConfig, type GuardViolation, type MutableExecutionNode, type NodeStatus, type NodeType, type NotifyChannel, type RunConfig, type RunResult, type StartNodeOptions, type TraceEvent, type TraceEventType, type TraceStore, type WatchConfig, type Writer, checkGuards, createGraphBuilder, createTraceStore, findWaitingOn, getChildren, getCriticalPath, getDepth, getDuration, getFailures, getHungNodes, getNode, getParent, getStats, getSubtree, getTraceTree, graphToJson, groupByTraceId, loadGraph, runTraced, startLive, startWatch, stitchTrace, toAsciiTree, toTimeline, withGuards };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  createGraphBuilder,
3
+ createTraceStore,
3
4
  findWaitingOn,
4
5
  getChildren,
5
6
  getCriticalPath,
@@ -12,16 +13,180 @@ import {
12
13
  getStats,
13
14
  getSubtree,
14
15
  getTraceTree,
15
- graphToJson,
16
16
  groupByTraceId,
17
- loadGraph,
18
17
  runTraced,
19
18
  startLive,
20
19
  startWatch,
21
- stitchTrace
22
- } from "./chunk-M5CGDXAO.js";
20
+ stitchTrace,
21
+ toAsciiTree,
22
+ toTimeline
23
+ } from "./chunk-T2BYYGA4.js";
24
+ import {
25
+ graphToJson,
26
+ loadGraph
27
+ } from "./chunk-DY7YHFIB.js";
28
+
29
+ // src/guards.ts
30
+ var DEFAULT_TIMEOUTS = {
31
+ tool: 3e4,
32
+ // 30s
33
+ agent: 3e5,
34
+ // 5m
35
+ subagent: 3e5,
36
+ // 5m
37
+ wait: 6e5,
38
+ // 10m
39
+ decision: 3e4,
40
+ // 30s
41
+ custom: 3e4
42
+ // 30s
43
+ };
44
+ function checkGuards(graph, config) {
45
+ const violations = [];
46
+ const now = Date.now();
47
+ const timeouts = { ...DEFAULT_TIMEOUTS, ...config?.timeouts };
48
+ const maxReasoningSteps = config?.maxReasoningSteps ?? 25;
49
+ const maxDepth = config?.maxDepth ?? 10;
50
+ const maxAgentSpawns = config?.maxAgentSpawns ?? 50;
51
+ for (const node of graph.nodes.values()) {
52
+ if (node.status === "running" && node.endTime === null) {
53
+ const timeoutThreshold = timeouts[node.type];
54
+ const elapsed = now - node.startTime;
55
+ if (elapsed > timeoutThreshold) {
56
+ violations.push({
57
+ type: "timeout",
58
+ nodeId: node.id,
59
+ message: `Node ${node.id} (${node.type}: ${node.name}) has been running for ${elapsed}ms, exceeding timeout of ${timeoutThreshold}ms`,
60
+ timestamp: now
61
+ });
62
+ }
63
+ }
64
+ }
65
+ const depth = getDepth(graph);
66
+ if (depth > maxDepth) {
67
+ violations.push({
68
+ type: "spawn-explosion",
69
+ nodeId: graph.rootNodeId,
70
+ message: `Graph depth ${depth} exceeds maximum depth of ${maxDepth}`,
71
+ timestamp: now
72
+ });
73
+ }
74
+ let agentCount = 0;
75
+ for (const node of graph.nodes.values()) {
76
+ if (node.type === "agent" || node.type === "subagent") {
77
+ agentCount++;
78
+ }
79
+ }
80
+ if (agentCount > maxAgentSpawns) {
81
+ violations.push({
82
+ type: "spawn-explosion",
83
+ nodeId: graph.rootNodeId,
84
+ message: `Total agent/subagent count ${agentCount} exceeds maximum of ${maxAgentSpawns}`,
85
+ timestamp: now
86
+ });
87
+ }
88
+ violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
89
+ return violations;
90
+ }
91
+ function detectReasoningLoops(graph, maxSteps, timestamp) {
92
+ const violations = [];
93
+ const reported = /* @__PURE__ */ new Set();
94
+ function walk(nodeId, consecutiveCount, consecutiveType) {
95
+ const node = getNode(graph, nodeId);
96
+ if (!node) return;
97
+ let newCount;
98
+ let newType;
99
+ if (node.type === consecutiveType) {
100
+ newCount = consecutiveCount + 1;
101
+ newType = node.type;
102
+ } else {
103
+ newCount = 1;
104
+ newType = node.type;
105
+ }
106
+ if (newCount > maxSteps && !reported.has(newType)) {
107
+ reported.add(newType);
108
+ violations.push({
109
+ type: "reasoning-loop",
110
+ nodeId: node.id,
111
+ message: `Detected ${newCount} consecutive ${newType} nodes along path to ${node.name}`,
112
+ timestamp
113
+ });
114
+ }
115
+ const children = getChildren(graph, nodeId);
116
+ for (const child of children) {
117
+ walk(child.id, newCount, newType);
118
+ }
119
+ }
120
+ walk(graph.rootNodeId, 0, null);
121
+ return violations;
122
+ }
123
+ function withGuards(builder, config) {
124
+ const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
125
+ const onViolation = config?.onViolation ?? "warn";
126
+ function handleViolations(violations) {
127
+ if (violations.length === 0) return;
128
+ for (const violation of violations) {
129
+ const message = `Guard violation: ${violation.message}`;
130
+ switch (onViolation) {
131
+ case "warn":
132
+ logger(message);
133
+ break;
134
+ case "error":
135
+ logger(message);
136
+ builder.pushEvent({
137
+ eventType: "custom",
138
+ nodeId: violation.nodeId,
139
+ data: {
140
+ guardViolation: violation.type,
141
+ message: violation.message,
142
+ severity: "error"
143
+ }
144
+ });
145
+ break;
146
+ case "abort":
147
+ throw new Error(`AgentFlow guard violation: ${violation.message}`);
148
+ default:
149
+ logger(message);
150
+ }
151
+ }
152
+ }
153
+ return {
154
+ get graphId() {
155
+ return builder.graphId;
156
+ },
157
+ get traceContext() {
158
+ return builder.traceContext;
159
+ },
160
+ startNode: (opts) => builder.startNode(opts),
161
+ endNode: (nodeId, status) => {
162
+ builder.endNode(nodeId, status);
163
+ const snapshot = builder.getSnapshot();
164
+ const violations = checkGuards(snapshot, config);
165
+ handleViolations(violations);
166
+ },
167
+ failNode: (nodeId, error) => {
168
+ builder.failNode(nodeId, error);
169
+ const snapshot = builder.getSnapshot();
170
+ const violations = checkGuards(snapshot, config);
171
+ handleViolations(violations);
172
+ },
173
+ addEdge: (from, to, type) => builder.addEdge(from, to, type),
174
+ pushEvent: (event) => builder.pushEvent(event),
175
+ updateState: (nodeId, state) => builder.updateState(nodeId, state),
176
+ withParent: (parentId, fn) => builder.withParent(parentId, fn),
177
+ getSnapshot: () => builder.getSnapshot(),
178
+ build: () => {
179
+ const snapshot = builder.getSnapshot();
180
+ const violations = checkGuards(snapshot, config);
181
+ handleViolations(violations);
182
+ return builder.build();
183
+ }
184
+ };
185
+ }
23
186
  export {
187
+ checkGuards,
24
188
  createGraphBuilder,
189
+ createTraceStore,
25
190
  findWaitingOn,
26
191
  getChildren,
27
192
  getCriticalPath,
@@ -40,5 +205,8 @@ export {
40
205
  runTraced,
41
206
  startLive,
42
207
  startWatch,
43
- stitchTrace
208
+ stitchTrace,
209
+ toAsciiTree,
210
+ toTimeline,
211
+ withGuards
44
212
  };
@@ -0,0 +1,8 @@
1
+ import {
2
+ graphToJson,
3
+ loadGraph
4
+ } from "./chunk-DY7YHFIB.js";
5
+ export {
6
+ graphToJson,
7
+ loadGraph
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentflow-core",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "description": "Monitor any AI agent system. Auto-detects failures, sends alerts. Zero config, zero dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",