flowneer 0.9.1 → 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-CqWK42bA.d.ts → FlowBuilder-8kwREeyD.d.ts} +81 -14
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +45 -5
  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 +486 -44
  10. package/dist/plugins/index.d.ts +2 -2
  11. package/dist/plugins/index.js +584 -126
  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 +45 -5
  24. package/dist/presets/config/index.d.ts +1 -1
  25. package/dist/presets/config/index.js +45 -5
  26. package/dist/presets/index.d.ts +1 -1
  27. package/dist/presets/index.js +45 -5
  28. package/dist/presets/pipeline/index.d.ts +1 -1
  29. package/dist/presets/pipeline/index.js +45 -5
  30. package/dist/presets/rag/index.d.ts +1 -1
  31. package/dist/presets/rag/index.js +45 -5
  32. package/dist/src/index.d.ts +2 -2
  33. package/dist/src/index.js +45 -5
  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)`.
@@ -62,7 +76,7 @@ interface RunOptions {
62
76
  /** Metadata exposed to hooks — intentionally minimal to avoid coupling. */
63
77
  interface StepMeta {
64
78
  index: number;
65
- type: "fn" | "branch" | "loop" | "batch" | "parallel" | "anchor";
79
+ type: "fn" | "branch" | "loop" | "batch" | "parallel" | "anchor" | "dag";
66
80
  label?: string;
67
81
  }
68
82
  /**
@@ -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";
@@ -166,17 +201,36 @@ interface AnchorStep {
166
201
  /** Maximum number of times a goto may jump to this anchor per run. */
167
202
  maxVisits?: number;
168
203
  }
169
- type Step<S, P extends Record<string, unknown>> = FnStep<S, P> | BranchStep<S, P> | LoopStep<S, P> | BatchStep<S, P> | ParallelStep<S, P> | AnchorStep;
204
+ /**
205
+ * A compiled DAG step. Produced by the graph plugin's `.compile()` and
206
+ * executed by the registered "dag" step handler, which traverses nodes
207
+ * in topological order and fires per-node lifecycle hooks natively.
208
+ */
209
+ interface DagStep<S, P extends Record<string, unknown>> {
210
+ type: "dag";
211
+ nodes: Map<string, {
212
+ name: string;
213
+ fn: NodeFn<S, P>;
214
+ options?: NodeOptions<S, P>;
215
+ }>;
216
+ /** Topologically sorted node names. */
217
+ order: string[];
218
+ /** Conditional edges that point backwards (cycles). Always have a condition. */
219
+ backEdges: Array<{
220
+ from: string;
221
+ to: string;
222
+ condition: (shared: S, params: P) => boolean | Promise<boolean>;
223
+ }>;
224
+ /** Conditional edges that skip forward (not back-edges). Always have a condition. */
225
+ conditionalForward: Array<{
226
+ from: string;
227
+ to: string;
228
+ condition: (shared: S, params: P) => boolean | Promise<boolean>;
229
+ }>;
230
+ label?: string;
231
+ }
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>;
170
233
 
171
- type ResolvedHooks<S, P extends Record<string, unknown>> = {
172
- beforeFlow: NonNullable<FlowHooks<S, P>["beforeFlow"]>[];
173
- beforeStep: NonNullable<FlowHooks<S, P>["beforeStep"]>[];
174
- wrapStep: NonNullable<FlowHooks<S, P>["wrapStep"]>[];
175
- afterStep: NonNullable<FlowHooks<S, P>["afterStep"]>[];
176
- wrapParallelFn: NonNullable<FlowHooks<S, P>["wrapParallelFn"]>[];
177
- onError: NonNullable<FlowHooks<S, P>["onError"]>[];
178
- afterFlow: NonNullable<FlowHooks<S, P>["afterFlow"]>[];
179
- };
180
234
  /**
181
235
  * Runtime context passed to every registered step handler.
182
236
  * Full access to the builder so handlers can run sub-flows etc.
@@ -212,17 +266,30 @@ declare class CoreFlowBuilder<S = any, P extends Record<string, unknown> = Recor
212
266
  private _hooksList;
213
267
  private _hooksCache;
214
268
  private static _stepHandlers;
269
+ /**
270
+ * Step types registered with `{ transparent: true }` are invoked directly
271
+ * by `_execute` without wrapping in the outer beforeStep/wrapStep/afterStep
272
+ * lifecycle. Use this for step types that manage per-item lifecycle
273
+ * internally (e.g. a DAG handler that fires hooks once per graph node).
274
+ */
275
+ private static _transparentSteps;
215
276
  /**
216
277
  * Register a handler for a custom step type.
217
278
  * Called once at module load time from each step plugin.
218
279
  *
280
+ * Pass `{ transparent: true }` when the handler manages its own per-item
281
+ * lifecycle hooks internally and should not be wrapped by the outer
282
+ * beforeStep / wrapStep / afterStep guards.
283
+ *
219
284
  * @example
220
285
  * CoreFlowBuilder.registerStepType("myStep", async (step, ctx) => {
221
286
  * await doWork(ctx.shared);
222
287
  * return undefined;
223
288
  * });
224
289
  */
225
- static registerStepType(type: string, handler: StepHandler): void;
290
+ static registerStepType(type: string, handler: StepHandler, options?: {
291
+ transparent?: boolean;
292
+ }): void;
226
293
  private _hooks;
227
294
  /**
228
295
  * Register lifecycle hooks (called by plugin methods, not by consumers).
@@ -354,4 +421,4 @@ declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<st
354
421
  private _addFn;
355
422
  }
356
423
 
357
- export { type AnchorStep as A, type BatchStep as B, CoreFlowBuilder as C, 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-CqWK42bA.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 {
@@ -122,18 +125,30 @@ var CoreFlowBuilder = class _CoreFlowBuilder {
122
125
  // Step-type registry (static — shared across all instances)
123
126
  // -------------------------------------------------------------------------
124
127
  static _stepHandlers = /* @__PURE__ */ new Map();
128
+ /**
129
+ * Step types registered with `{ transparent: true }` are invoked directly
130
+ * by `_execute` without wrapping in the outer beforeStep/wrapStep/afterStep
131
+ * lifecycle. Use this for step types that manage per-item lifecycle
132
+ * internally (e.g. a DAG handler that fires hooks once per graph node).
133
+ */
134
+ static _transparentSteps = /* @__PURE__ */ new Set();
125
135
  /**
126
136
  * Register a handler for a custom step type.
127
137
  * Called once at module load time from each step plugin.
128
138
  *
139
+ * Pass `{ transparent: true }` when the handler manages its own per-item
140
+ * lifecycle hooks internally and should not be wrapped by the outer
141
+ * beforeStep / wrapStep / afterStep guards.
142
+ *
129
143
  * @example
130
144
  * CoreFlowBuilder.registerStepType("myStep", async (step, ctx) => {
131
145
  * await doWork(ctx.shared);
132
146
  * return undefined;
133
147
  * });
134
148
  */
135
- static registerStepType(type, handler) {
149
+ static registerStepType(type, handler, options) {
136
150
  _CoreFlowBuilder._stepHandlers.set(type, handler);
151
+ if (options?.transparent) _CoreFlowBuilder._transparentSteps.add(type);
137
152
  }
138
153
  // -------------------------------------------------------------------------
139
154
  // Hooks & plugins
@@ -263,6 +278,25 @@ var CoreFlowBuilder = class _CoreFlowBuilder {
263
278
  signal?.throwIfAborted();
264
279
  const step = this.steps[i];
265
280
  if (step.type === "anchor") continue;
281
+ if (_CoreFlowBuilder._transparentSteps.has(step.type)) {
282
+ const handler = _CoreFlowBuilder._stepHandlers.get(step.type);
283
+ if (handler) {
284
+ const meta2 = {
285
+ index: i,
286
+ type: step.type,
287
+ label: step.label
288
+ };
289
+ await handler(step, {
290
+ shared,
291
+ params,
292
+ signal,
293
+ hooks,
294
+ meta: meta2,
295
+ builder: this
296
+ });
297
+ }
298
+ continue;
299
+ }
266
300
  const stepLabel = step.label;
267
301
  const meta = {
268
302
  index: i,
@@ -294,10 +328,12 @@ var CoreFlowBuilder = class _CoreFlowBuilder {
294
328
  );
295
329
  }
296
330
  i = target;
331
+ for (const h of hooks.onAnchorHit)
332
+ await h(gotoTarget, shared, params);
297
333
  }
298
334
  } catch (err) {
299
335
  if (err instanceof InterruptError) throw err;
300
- for (const h of hooks.onError) h(meta, err, shared, params);
336
+ for (const h of hooks.onError) await h(meta, err, shared, params);
301
337
  if (err instanceof FlowError) throw err;
302
338
  const labelPart = stepLabel ? `"${stepLabel}" ` : "";
303
339
  const label = step.type === "fn" ? `${labelPart}step ${i}` : `${labelPart}${step.type} (step ${i})`;
@@ -385,12 +421,16 @@ var branchHandler = async (step, { shared, params }) => {
385
421
  };
386
422
 
387
423
  // src/core/steps/loop.ts
388
- var loopHandler = async (step, { shared, params, signal, meta, builder }) => {
389
- 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)) {
390
427
  await builder._runSub(
391
428
  `loop (step ${meta.index})`,
392
429
  () => step.body._execute(shared, params, signal)
393
430
  );
431
+ for (const h of hooks.onLoopIteration)
432
+ await h(meta, iteration++, shared, params);
433
+ }
394
434
  return void 0;
395
435
  };
396
436
 
@@ -1,4 +1,4 @@
1
- import { F as FlowBuilder, a as FlowneerPlugin } from '../../FlowBuilder-CqWK42bA.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-CqWK42bA.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-CqWK42bA.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-CqWK42bA.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 {