flowneer 0.9.2 → 0.9.4

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.
Files changed (34) hide show
  1. package/dist/{FlowBuilder-G67AbbRt.d.ts → FlowBuilder-8kwREeyD.d.ts} +37 -11
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +13 -4
  4. package/dist/plugins/agent/index.d.ts +7 -1
  5. package/dist/plugins/dev/index.d.ts +103 -24
  6. package/dist/plugins/dev/index.js +117 -27
  7. package/dist/plugins/eval/index.d.ts +1 -1
  8. package/dist/plugins/graph/index.d.ts +1 -1
  9. package/dist/plugins/graph/index.js +7 -2
  10. package/dist/plugins/index.d.ts +2 -2
  11. package/dist/plugins/index.js +120 -77
  12. package/dist/plugins/llm/index.d.ts +29 -1
  13. package/dist/plugins/memory/index.d.ts +5 -1
  14. package/dist/plugins/messaging/index.d.ts +9 -1
  15. package/dist/plugins/observability/index.d.ts +13 -1
  16. package/dist/plugins/output/index.d.ts +1 -1
  17. package/dist/plugins/persistence/index.d.ts +168 -34
  18. package/dist/plugins/persistence/index.js +201 -62
  19. package/dist/plugins/resilience/index.d.ts +14 -1
  20. package/dist/plugins/telemetry/index.d.ts +1 -1
  21. package/dist/plugins/tools/index.d.ts +8 -1
  22. package/dist/presets/agent/index.d.ts +7 -1
  23. package/dist/presets/agent/index.js +13 -4
  24. package/dist/presets/config/index.d.ts +1 -1
  25. package/dist/presets/config/index.js +13 -4
  26. package/dist/presets/index.d.ts +1 -1
  27. package/dist/presets/index.js +13 -4
  28. package/dist/presets/pipeline/index.d.ts +1 -1
  29. package/dist/presets/pipeline/index.js +13 -4
  30. package/dist/presets/rag/index.d.ts +1 -1
  31. package/dist/presets/rag/index.js +13 -4
  32. package/dist/src/index.d.ts +2 -2
  33. package/dist/src/index.js +13 -4
  34. package/package.json +1 -1
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Extendable shared-state interface augmented by each plugin via declaration
3
+ * merging. Extend your state type with this to get all plugin-provided keys
4
+ * typed and documented automatically — no manual `__*` declarations needed.
5
+ *
6
+ * @example
7
+ * import type { AugmentedState } from "flowneer";
8
+ * interface MyState extends AugmentedState {
9
+ * topic: string;
10
+ * results: string[];
11
+ * }
12
+ */
13
+ interface AugmentedState {
14
+ }
1
15
  /**
2
16
  * Generic validator interface — structurally compatible with Zod, ArkType,
3
17
  * Valibot, or any custom implementation that exposes `.parse(input)`.
@@ -105,8 +119,18 @@ interface FlowHooks<S = any, P extends Record<string, unknown> = Record<string,
105
119
  * `fnIndex` is the position within the fns array.
106
120
  */
107
121
  wrapParallelFn?: (meta: StepMeta, fnIndex: number, next: () => Promise<void>, shared: S, params: P) => Promise<void>;
108
- onError?: (meta: StepMeta, error: unknown, shared: S, params: P) => void;
122
+ onError?: (meta: StepMeta, error: unknown, shared: S, params: P) => void | Promise<void>;
109
123
  afterFlow?: (shared: S, params: P) => void | Promise<void>;
124
+ /**
125
+ * Fires after each loop iteration completes. `iteration` is zero-based.
126
+ * Only fires for `.loop()` steps, not `.batch()` or `.parallel()`.
127
+ */
128
+ onLoopIteration?: (meta: StepMeta, iteration: number, shared: S, params: P) => void | Promise<void>;
129
+ /**
130
+ * Fires when a goto jump resolves to an anchor (i.e. a step returned `"#anchorName"`).
131
+ * `anchorName` is the anchor label without the `#` prefix.
132
+ */
133
+ onAnchorHit?: (anchorName: string, shared: S, params: P) => void | Promise<void>;
110
134
  }
111
135
  /**
112
136
  * A plugin is an object whose keys become methods on a `FlowBuilder.extend()` subclass prototype.
@@ -120,6 +144,17 @@ interface FlowHooks<S = any, P extends Record<string, unknown> = Record<string,
120
144
  * ```
121
145
  */
122
146
  type FlowneerPlugin = Record<string, (this: FlowBuilder<any, any>, ...args: any[]) => any>;
147
+ type ResolvedHooks<S, P extends Record<string, unknown>> = {
148
+ beforeFlow: NonNullable<FlowHooks<S, P>["beforeFlow"]>[];
149
+ beforeStep: NonNullable<FlowHooks<S, P>["beforeStep"]>[];
150
+ wrapStep: NonNullable<FlowHooks<S, P>["wrapStep"]>[];
151
+ afterStep: NonNullable<FlowHooks<S, P>["afterStep"]>[];
152
+ wrapParallelFn: NonNullable<FlowHooks<S, P>["wrapParallelFn"]>[];
153
+ onError: NonNullable<FlowHooks<S, P>["onError"]>[];
154
+ afterFlow: NonNullable<FlowHooks<S, P>["afterFlow"]>[];
155
+ onLoopIteration: NonNullable<FlowHooks<S, P>["onLoopIteration"]>[];
156
+ onAnchorHit: NonNullable<FlowHooks<S, P>["onAnchorHit"]>[];
157
+ };
123
158
 
124
159
  interface FnStep<S, P extends Record<string, unknown>> {
125
160
  type: "fn";
@@ -196,15 +231,6 @@ interface DagStep<S, P extends Record<string, unknown>> {
196
231
  }
197
232
  type Step<S, P extends Record<string, unknown>> = FnStep<S, P> | BranchStep<S, P> | LoopStep<S, P> | BatchStep<S, P> | ParallelStep<S, P> | AnchorStep | DagStep<S, P>;
198
233
 
199
- type ResolvedHooks<S, P extends Record<string, unknown>> = {
200
- beforeFlow: NonNullable<FlowHooks<S, P>["beforeFlow"]>[];
201
- beforeStep: NonNullable<FlowHooks<S, P>["beforeStep"]>[];
202
- wrapStep: NonNullable<FlowHooks<S, P>["wrapStep"]>[];
203
- afterStep: NonNullable<FlowHooks<S, P>["afterStep"]>[];
204
- wrapParallelFn: NonNullable<FlowHooks<S, P>["wrapParallelFn"]>[];
205
- onError: NonNullable<FlowHooks<S, P>["onError"]>[];
206
- afterFlow: NonNullable<FlowHooks<S, P>["afterFlow"]>[];
207
- };
208
234
  /**
209
235
  * Runtime context passed to every registered step handler.
210
236
  * Full access to the builder so handlers can run sub-flows etc.
@@ -395,4 +421,4 @@ declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<st
395
421
  private _addFn;
396
422
  }
397
423
 
398
- export { type AnchorStep as A, type BatchStep as B, CoreFlowBuilder as C, type DagStep as D, FlowBuilder as F, type LoopStep as L, type NodeFn as N, type ParallelStep as P, type ResolvedHooks as R, type StepFilter as S, type Validator as V, type FlowneerPlugin as a, type NodeOptions as b, type FlowHooks as c, type StepMeta as d, type BranchStep as e, type FnStep as f, type NumberOrFn as g, type RunOptions as h, type Step as i, type StepContext as j, type StepHandler as k, type StreamEvent as l };
424
+ export { type AnchorStep as A, type BatchStep as B, CoreFlowBuilder as C, type DagStep as D, FlowBuilder as F, type LoopStep as L, type NodeFn as N, type ParallelStep as P, type ResolvedHooks as R, type StepFilter as S, type Validator as V, type FlowneerPlugin as a, type NodeOptions as b, type FlowHooks as c, type StepMeta as d, type AugmentedState as e, type BranchStep as f, type FnStep as g, type NumberOrFn as h, type RunOptions as i, type Step as j, type StepContext as k, type StepHandler as l, type StreamEvent as m };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { F as FlowBuilder, c as FlowHooks, a as FlowneerPlugin, N as NodeFn, b as NodeOptions, g as NumberOrFn, h as RunOptions, d as StepMeta, l as StreamEvent, V as Validator } from './FlowBuilder-G67AbbRt.js';
1
+ export { F as FlowBuilder, c as FlowHooks, a as FlowneerPlugin, N as NodeFn, b as NodeOptions, h as NumberOrFn, i as RunOptions, d as StepMeta, m as StreamEvent, V as Validator } from './FlowBuilder-8kwREeyD.js';
2
2
  export { Fragment, fragment } from './src/index.js';
3
3
  export { F as FlowError, I as InterruptError } from './errors-u-hq7p5N.js';
package/dist/index.js CHANGED
@@ -97,6 +97,7 @@ function applyStepFilter(hooks, filter) {
97
97
  wrap("onError");
98
98
  wrap("wrapStep", (_m, next) => next());
99
99
  wrap("wrapParallelFn", (_m, _fi, next) => next());
100
+ wrap("onLoopIteration");
100
101
  return hooks;
101
102
  }
102
103
  function buildHookCache(list) {
@@ -108,7 +109,9 @@ function buildHookCache(list) {
108
109
  afterStep: pick("afterStep"),
109
110
  wrapParallelFn: pick("wrapParallelFn"),
110
111
  onError: pick("onError"),
111
- afterFlow: pick("afterFlow")
112
+ afterFlow: pick("afterFlow"),
113
+ onLoopIteration: pick("onLoopIteration"),
114
+ onAnchorHit: pick("onAnchorHit")
112
115
  };
113
116
  }
114
117
  var CoreFlowBuilder = class _CoreFlowBuilder {
@@ -325,10 +328,12 @@ var CoreFlowBuilder = class _CoreFlowBuilder {
325
328
  );
326
329
  }
327
330
  i = target;
331
+ for (const h of hooks.onAnchorHit)
332
+ await h(gotoTarget, shared, params);
328
333
  }
329
334
  } catch (err) {
330
335
  if (err instanceof InterruptError) throw err;
331
- for (const h of hooks.onError) h(meta, err, shared, params);
336
+ for (const h of hooks.onError) await h(meta, err, shared, params);
332
337
  if (err instanceof FlowError) throw err;
333
338
  const labelPart = stepLabel ? `"${stepLabel}" ` : "";
334
339
  const label = step.type === "fn" ? `${labelPart}step ${i}` : `${labelPart}${step.type} (step ${i})`;
@@ -416,12 +421,16 @@ var branchHandler = async (step, { shared, params }) => {
416
421
  };
417
422
 
418
423
  // src/core/steps/loop.ts
419
- var loopHandler = async (step, { shared, params, signal, meta, builder }) => {
420
- while (await step.condition(shared, params))
424
+ var loopHandler = async (step, { shared, params, signal, meta, hooks, builder }) => {
425
+ let iteration = 0;
426
+ while (await step.condition(shared, params)) {
421
427
  await builder._runSub(
422
428
  `loop (step ${meta.index})`,
423
429
  () => step.body._execute(shared, params, signal)
424
430
  );
431
+ for (const h of hooks.onLoopIteration)
432
+ await h(meta, iteration++, shared, params);
433
+ }
425
434
  return void 0;
426
435
  };
427
436
 
@@ -1,4 +1,4 @@
1
- import { F as FlowBuilder, a as FlowneerPlugin } from '../../FlowBuilder-G67AbbRt.js';
1
+ import { F as FlowBuilder, a as FlowneerPlugin } from '../../FlowBuilder-8kwREeyD.js';
2
2
 
3
3
  interface HumanNodeOptions<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
4
4
  /**
@@ -45,6 +45,12 @@ declare module "../../Flowneer" {
45
45
  */
46
46
  humanNode(options?: HumanNodeOptions<S, P>): this;
47
47
  }
48
+ interface AugmentedState {
49
+ /** Prompt/question for the human reviewer. Written by `.humanNode()` before interrupting. */
50
+ __humanPrompt?: string;
51
+ /** Human-provided response. Write this onto the saved shared state before calling `resumeFlow()`. */
52
+ __humanFeedback?: string;
53
+ }
48
54
  }
49
55
  declare const withHumanNode: FlowneerPlugin;
50
56
  /**
@@ -1,4 +1,4 @@
1
- import { a as FlowneerPlugin, N as NodeFn, b as NodeOptions, S as StepFilter } from '../../FlowBuilder-G67AbbRt.js';
1
+ import { a as FlowneerPlugin, N as NodeFn, b as NodeOptions, S as StepFilter } from '../../FlowBuilder-8kwREeyD.js';
2
2
 
3
3
  declare module "../../Flowneer" {
4
4
  interface FlowBuilder<S, P> {
@@ -130,39 +130,118 @@ declare module "../../Flowneer" {
130
130
  }
131
131
  declare const withFlowAnalyzer: FlowneerPlugin;
132
132
 
133
- interface DebuggerHooks {
134
- /** Pause before a step runs. Default: `true`. */
135
- beforeStep?: boolean;
136
- /** Pause after a step completes. Default: `false`. */
137
- afterStep?: boolean;
138
- /** Pause when a step throws. Default: `false`. */
139
- onError?: boolean;
140
- /** Pause once before the step body and once after (inside the wrapper). Default: `false`. */
141
- wrapStep?: boolean;
133
+ /** Per-step performance snapshot recorded by `.withPerfAnalyzer()`. */
134
+ interface StepPerfStats {
135
+ /** Step index (0-based). */
136
+ index: number;
137
+ /** Step type: "fn" | "branch" | "loop" | "batch" | "parallel" | "dag". */
138
+ type: string;
139
+ /** Step label if set via `NodeOptions.label`. */
140
+ label?: string;
141
+ /** Wall-clock duration in ms (high-res via `performance.now()`). */
142
+ durationMs: number;
143
+ /** User-space CPU time consumed during this step (ms). 0 on non-Node runtimes. */
144
+ cpuUserMs: number;
145
+ /** Kernel CPU time consumed during this step (ms). 0 on non-Node runtimes. */
146
+ cpuSystemMs: number;
147
+ /** V8 heap used at step start (bytes). */
148
+ heapUsedBefore: number;
149
+ /** V8 heap used at step end (bytes). */
150
+ heapUsedAfter: number;
151
+ /**
152
+ * Net change in V8 heap usage (bytes, positive = allocated, negative = freed
153
+ * due to a GC cycle that completed during the step).
154
+ */
155
+ heapDeltaBytes: number;
156
+ /** Net change in Resident Set Size (bytes). */
157
+ rssDeltaBytes: number;
158
+ /** Net change in external (C++ / Buffer) memory bound to V8 (bytes). */
159
+ externalDeltaBytes: number;
160
+ /**
161
+ * Number of GC events accumulated since last step end. Best-effort; see
162
+ * module note regarding async PerformanceObserver delivery.
163
+ */
164
+ gcCount: number;
165
+ /** Total GC pause duration attributed to this step (ms). Best-effort. */
166
+ gcDurationMs: number;
167
+ /** `true` if the step threw an error (stats still recorded via finally). */
168
+ threw: boolean;
169
+ }
170
+ /** Flow-level performance summary written to `shared.__perfReport`. */
171
+ interface PerfReport {
172
+ /** Sum of all step `durationMs` (includes parallel overlap). */
173
+ totalDurationMs: number;
174
+ /** Sum of all step `cpuUserMs`. */
175
+ totalCpuUserMs: number;
176
+ /** Sum of all step `cpuSystemMs`. */
177
+ totalCpuSystemMs: number;
178
+ /** Sum of all GC pause durations across the flow (ms). */
179
+ totalGcDurationMs: number;
180
+ /** Total GC event count during the flow. */
181
+ totalGcCount: number;
182
+ /** Highest `heapUsedAfter` seen across all steps (bytes). */
183
+ peakHeapUsedBytes: number;
184
+ /** All per-step stats in execution order. */
185
+ steps: StepPerfStats[];
186
+ /** The step with the longest wall-clock duration, or `null` if no steps ran. */
187
+ slowest: StepPerfStats | null;
188
+ /** The step with the largest heap delta, or `null` if no steps ran. */
189
+ heaviest: StepPerfStats | null;
190
+ }
191
+ interface PerfAnalyzerOptions {
192
+ /**
193
+ * Track GC pause events via `PerformanceObserver`. Requires Node.js.
194
+ * @default true
195
+ */
196
+ trackGc?: boolean;
197
+ /**
198
+ * Called with the final `PerfReport` in `afterFlow`.
199
+ * Use this to log, persist, or ship metrics — formatting is left to the caller.
200
+ */
201
+ onReport?: (report: PerfReport) => void;
142
202
  }
143
203
  declare module "../../Flowneer" {
144
204
  interface FlowBuilder<S, P> {
145
205
  /**
146
- * Drops a `debugger` statement at the selected lifecycle points.
147
- * Attach DevTools / `--inspect` breakpoints and step through live flow state.
206
+ * Profiles each step using Node.js built-in performance APIs — no external
207
+ * dependencies.
148
208
  *
149
- * @param filter Limit to specific steps by label or predicate (optional).
150
- * @param hooks Which lifecycle points to pause at. Defaults to `{ beforeStep: true }`.
209
+ * Per step, records:
210
+ * - Wall-clock duration (`performance.now()`)
211
+ * - CPU user + system time (`process.cpuUsage()`)
212
+ * - Heap used delta (`process.memoryUsage().heapUsed`)
213
+ * - RSS and external memory delta
214
+ * - GC pause count + duration (`PerformanceObserver`, best-effort)
215
+ *
216
+ * Results are written to `shared.__perfStats` (array, in execution order)
217
+ * and `shared.__perfReport` (flow summary) when the flow completes.
151
218
  *
152
219
  * @example
153
- * // Pause before every step
154
- * new AppFlow().withDebugger().then(myStep).run(shared);
220
+ * const flow = new AppFlow<State>()
221
+ * .withPerfAnalyzer({
222
+ * onReport: (r) => console.log(JSON.stringify(r, null, 2)),
223
+ * })
224
+ * .then(fetchData, { label: "fetch" })
225
+ * .then(callLlm, { label: "llm:generate" })
226
+ * .then(save, { label: "save" });
227
+ *
228
+ * await flow.run(shared);
229
+ * // shared.__perfReport.slowest → { label: "llm:generate", durationMs: ... }
230
+ * // shared.__perfStats[0] → { heapDeltaBytes: 2621440, cpuUserMs: 12 … }
155
231
  *
156
232
  * @example
157
- * // Pause only on "llm:*" steps, before and after
158
- * new AppFlow()
159
- * .withDebugger(["llm:*"], { beforeStep: true, afterStep: true })
160
- * .then(callLlm)
161
- * .run(shared);
233
+ * // Profile only LLM steps
234
+ * flow.withPerfAnalyzer({}, ["llm:*"])
162
235
  */
163
- withDebugger(filter?: StepFilter, hooks?: DebuggerHooks): this;
236
+ withPerfAnalyzer(options?: PerfAnalyzerOptions, filter?: StepFilter): this;
237
+ }
238
+ interface AugmentedState {
239
+ /** Per-step perf stats written by `.withPerfAnalyzer()`. In execution order. */
240
+ __perfStats?: StepPerfStats[];
241
+ /** Flow-level perf summary written by `.withPerfAnalyzer()` on flow completion. */
242
+ __perfReport?: PerfReport;
164
243
  }
165
244
  }
166
- declare const withDebugger: FlowneerPlugin;
245
+ declare const withPerfAnalyzer: FlowneerPlugin;
167
246
 
168
- export { type DebuggerHooks, type PathMap, type PathNode, type TraceEvent, type TraceHandle, type TraceReport, withAtomicUpdates, withDebugger, withDryRun, withFlowAnalyzer, withMocks, withStepLimit };
247
+ export { type PathMap, type PathNode, type PerfAnalyzerOptions, type PerfReport, type StepPerfStats, type TraceEvent, type TraceHandle, type TraceReport, withAtomicUpdates, withDryRun, withFlowAnalyzer, withMocks, withPerfAnalyzer, withStepLimit };
@@ -180,41 +180,131 @@ var withFlowAnalyzer = {
180
180
  }
181
181
  };
182
182
 
183
- // plugins/dev/withDebugger.ts
184
- var withDebugger = {
185
- withDebugger(filter, hooks = { beforeStep: true }) {
186
- const registered = {};
187
- if (hooks.beforeStep) {
188
- registered.beforeStep = (meta, shared, params) => {
189
- debugger;
190
- };
191
- }
192
- if (hooks.afterStep) {
193
- registered.afterStep = (meta, shared, params) => {
194
- debugger;
195
- };
196
- }
197
- if (hooks.onError) {
198
- registered.onError = (meta, error, shared, params) => {
199
- debugger;
200
- };
201
- }
202
- if (hooks.wrapStep) {
203
- registered.wrapStep = async (meta, next, shared, params) => {
204
- debugger;
205
- await next();
206
- debugger;
207
- };
183
+ // plugins/dev/withPerfAnalyzer.ts
184
+ import { performance, PerformanceObserver } from "perf_hooks";
185
+ function cpuUsage() {
186
+ return process.cpuUsage?.() ?? {
187
+ user: 0,
188
+ system: 0
189
+ };
190
+ }
191
+ function cpuUsageDelta(start) {
192
+ return process.cpuUsage?.(start) ?? { user: 0, system: 0 };
193
+ }
194
+ function memUsage() {
195
+ return process.memoryUsage?.() ?? {
196
+ rss: 0,
197
+ heapTotal: 0,
198
+ heapUsed: 0,
199
+ external: 0,
200
+ arrayBuffers: 0
201
+ };
202
+ }
203
+ var withPerfAnalyzer = {
204
+ withPerfAnalyzer(options = {}, filter) {
205
+ const { trackGc = true, onReport } = options;
206
+ const gcLog = [];
207
+ let gcObserver = null;
208
+ if (trackGc) {
209
+ try {
210
+ gcObserver = new PerformanceObserver((list) => {
211
+ for (const entry of list.getEntries()) {
212
+ gcLog.push({
213
+ startTime: entry.startTime,
214
+ duration: entry.duration
215
+ });
216
+ }
217
+ });
218
+ gcObserver.observe({ entryTypes: ["gc"] });
219
+ } catch {
220
+ }
208
221
  }
209
- this._setHooks(registered, filter);
222
+ const snapshots = /* @__PURE__ */ new Map();
223
+ this._setHooks(
224
+ {
225
+ wrapStep: async (meta, next, shared) => {
226
+ const gcMsBefore = gcLog.reduce((a, e) => a + e.duration, 0);
227
+ const t0 = performance.now();
228
+ const cpu0 = cpuUsage();
229
+ const mem0 = memUsage();
230
+ snapshots.set(meta.index, {
231
+ t0,
232
+ cpu0,
233
+ mem0,
234
+ gcLenBefore: gcLog.length,
235
+ gcMsBefore
236
+ });
237
+ let threw = false;
238
+ try {
239
+ await next();
240
+ } catch (e) {
241
+ threw = true;
242
+ throw e;
243
+ } finally {
244
+ const durationMs = performance.now() - t0;
245
+ const cpuDelta = cpuUsageDelta(cpu0);
246
+ const mem1 = memUsage();
247
+ const snap = snapshots.get(meta.index);
248
+ snapshots.delete(meta.index);
249
+ const gcMsAfter = gcLog.reduce((a, e) => a + e.duration, 0);
250
+ const gcCountAfter = gcLog.length;
251
+ const gcLenBefore = snap?.gcLenBefore ?? gcLog.length;
252
+ const gcMsSnap = snap?.gcMsBefore ?? gcMsAfter;
253
+ const stat = {
254
+ index: meta.index,
255
+ type: meta.type,
256
+ label: meta.label,
257
+ durationMs,
258
+ cpuUserMs: cpuDelta.user / 1e3,
259
+ cpuSystemMs: cpuDelta.system / 1e3,
260
+ heapUsedBefore: mem0.heapUsed,
261
+ heapUsedAfter: mem1.heapUsed,
262
+ heapDeltaBytes: mem1.heapUsed - mem0.heapUsed,
263
+ rssDeltaBytes: mem1.rss - mem0.rss,
264
+ externalDeltaBytes: mem1.external - mem0.external,
265
+ gcCount: gcCountAfter - gcLenBefore,
266
+ gcDurationMs: gcMsAfter - gcMsSnap,
267
+ threw
268
+ };
269
+ if (!shared.__perfStats) shared.__perfStats = [];
270
+ shared.__perfStats.push(stat);
271
+ }
272
+ },
273
+ afterFlow: (shared) => {
274
+ gcObserver?.disconnect();
275
+ const stats = shared.__perfStats ?? [];
276
+ const totalGcDurationMs = gcLog.reduce((a, e) => a + e.duration, 0);
277
+ const totalGcCount = gcLog.length;
278
+ const report = {
279
+ totalDurationMs: stats.reduce((a, s) => a + s.durationMs, 0),
280
+ totalCpuUserMs: stats.reduce((a, s) => a + s.cpuUserMs, 0),
281
+ totalCpuSystemMs: stats.reduce((a, s) => a + s.cpuSystemMs, 0),
282
+ totalGcDurationMs,
283
+ totalGcCount,
284
+ peakHeapUsedBytes: stats.reduce(
285
+ (a, s) => Math.max(a, s.heapUsedAfter),
286
+ 0
287
+ ),
288
+ steps: stats,
289
+ slowest: stats.length > 0 ? stats.reduce((a, s) => s.durationMs > a.durationMs ? s : a) : null,
290
+ heaviest: stats.length > 0 ? stats.reduce(
291
+ (a, s) => s.heapDeltaBytes > a.heapDeltaBytes ? s : a
292
+ ) : null
293
+ };
294
+ shared.__perfReport = report;
295
+ onReport?.(report);
296
+ }
297
+ },
298
+ filter
299
+ );
210
300
  return this;
211
301
  }
212
302
  };
213
303
  export {
214
304
  withAtomicUpdates,
215
- withDebugger,
216
305
  withDryRun,
217
306
  withFlowAnalyzer,
218
307
  withMocks,
308
+ withPerfAnalyzer,
219
309
  withStepLimit
220
310
  };
@@ -1,4 +1,4 @@
1
- import { F as FlowBuilder } from '../../FlowBuilder-G67AbbRt.js';
1
+ import { F as FlowBuilder } from '../../FlowBuilder-8kwREeyD.js';
2
2
 
3
3
  /** Case-insensitive exact match. Returns 1.0 or 0.0. */
4
4
  declare function exactMatch(predicted: string, expected: string): number;
@@ -1,4 +1,4 @@
1
- import { a as FlowneerPlugin, N as NodeFn, b as NodeOptions } from '../../FlowBuilder-G67AbbRt.js';
1
+ import { a as FlowneerPlugin, N as NodeFn, b as NodeOptions } from '../../FlowBuilder-8kwREeyD.js';
2
2
 
3
3
  /** A single node entry in the exported graph. */
4
4
  interface GraphNodeExport {
@@ -97,6 +97,7 @@ function applyStepFilter(hooks, filter) {
97
97
  wrap("onError");
98
98
  wrap("wrapStep", (_m, next) => next());
99
99
  wrap("wrapParallelFn", (_m, _fi, next) => next());
100
+ wrap("onLoopIteration");
100
101
  return hooks;
101
102
  }
102
103
  function buildHookCache(list) {
@@ -108,7 +109,9 @@ function buildHookCache(list) {
108
109
  afterStep: pick("afterStep"),
109
110
  wrapParallelFn: pick("wrapParallelFn"),
110
111
  onError: pick("onError"),
111
- afterFlow: pick("afterFlow")
112
+ afterFlow: pick("afterFlow"),
113
+ onLoopIteration: pick("onLoopIteration"),
114
+ onAnchorHit: pick("onAnchorHit")
112
115
  };
113
116
  }
114
117
  var CoreFlowBuilder = class _CoreFlowBuilder {
@@ -325,10 +328,12 @@ var CoreFlowBuilder = class _CoreFlowBuilder {
325
328
  );
326
329
  }
327
330
  i = target;
331
+ for (const h of hooks.onAnchorHit)
332
+ await h(gotoTarget, shared, params);
328
333
  }
329
334
  } catch (err) {
330
335
  if (err instanceof InterruptError) throw err;
331
- for (const h of hooks.onError) h(meta, err, shared, params);
336
+ for (const h of hooks.onError) await h(meta, err, shared, params);
332
337
  if (err instanceof FlowError) throw err;
333
338
  const labelPart = stepLabel ? `"${stepLabel}" ` : "";
334
339
  const label = step.type === "fn" ? `${labelPart}step ${i}` : `${labelPart}${step.type} (step ${i})`;
@@ -1,6 +1,6 @@
1
1
  export { withHistory, withInterrupts, withTiming, withVerbose } from './observability/index.js';
2
2
  export { CircuitBreakerOptions, withCircuitBreaker, withCycles, withFallback, withTimeout } from './resilience/index.js';
3
- export { AuditEntry, AuditLogStore, CheckpointStore, VersionedCheckpointEntry, VersionedCheckpointStore, withAuditLog, withCheckpoint, withReplay, withVersionedCheckpoint } from './persistence/index.js';
3
+ export { AuditEntry, AuditLogStore, withAuditLog, withCheckpoint, withReplay } from './persistence/index.js';
4
4
  export { RateLimitOptions, withCostTracker, withRateLimit, withTokenBudget } from './llm/index.js';
5
5
  export { PathMap, PathNode, TraceEvent, TraceHandle, TraceReport, withAtomicUpdates, withDryRun, withFlowAnalyzer, withMocks, withStepLimit } from './dev/index.js';
6
6
  export { StreamSubscriber, emit, peekChannel, receiveFrom, sendTo, withChannels, withStream } from './messaging/index.js';
@@ -11,7 +11,7 @@ export { parseJsonOutput, parseListOutput, parseMarkdownTable, parseRegexOutput
11
11
  export { Span, TelemetryDaemon, TelemetryExporter, TelemetryOptions, consoleExporter, otlpExporter, withTelemetry } from './telemetry/index.js';
12
12
  export { EvalResult, EvalSummary, ScoreFn, answerRelevance, containsMatch, exactMatch, f1Score, retrievalPrecision, retrievalRecall, runEvalSuite } from './eval/index.js';
13
13
  export { GraphEdge, GraphNode, withGraph } from './graph/index.js';
14
- import { S as StepFilter, a as FlowneerPlugin, d as StepMeta, F as FlowBuilder } from '../FlowBuilder-G67AbbRt.js';
14
+ import { S as StepFilter, a as FlowneerPlugin, d as StepMeta, F as FlowBuilder } from '../FlowBuilder-8kwREeyD.js';
15
15
  import { F as FlowError } from '../errors-u-hq7p5N.js';
16
16
  export { validate } from './config/index.js';
17
17
  export { F as FlowConfig, a as FnRegistry, S as StepConfig, V as ValidationError, b as ValidationResult } from '../schema-CIqQAXqY.js';