flowneer 0.9.2 → 0.9.5

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-Bvf_nDeb.d.ts} +48 -11
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +25 -10
  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 +19 -8
  10. package/dist/plugins/index.d.ts +2 -2
  11. package/dist/plugins/index.js +132 -83
  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 +174 -2
  23. package/dist/presets/agent/index.js +142 -10
  24. package/dist/presets/config/index.d.ts +1 -1
  25. package/dist/presets/config/index.js +25 -10
  26. package/dist/presets/index.d.ts +2 -2
  27. package/dist/presets/index.js +142 -10
  28. package/dist/presets/pipeline/index.d.ts +1 -1
  29. package/dist/presets/pipeline/index.js +25 -10
  30. package/dist/presets/rag/index.d.ts +1 -1
  31. package/dist/presets/rag/index.js +25 -10
  32. package/dist/src/index.d.ts +2 -2
  33. package/dist/src/index.js +25 -10
  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)`.
@@ -72,6 +86,11 @@ interface StepMeta {
72
86
  * Entries are exact matches unless they contain `*`, which acts as a
73
87
  * wildcard matching any substring (glob-style).
74
88
  * e.g. `["llm:*"]` matches `"llm:summarise"`, `"llm:embed"`, etc.
89
+ * - **Negation** — prefix an entry with `!` to exclude matching steps.
90
+ * Negation veto always wins over a positive match in the same array.
91
+ * A negation-only array matches all labelled steps that are not excluded.
92
+ * e.g. `["!human:*"]` fires on every step _except_ `human:*` steps.
93
+ * e.g. `["!human:*", "llm:*"]` fires on `llm:*` steps only, never `human:*`.
75
94
  * - **Predicate** — full control; return `true` to match.
76
95
  *
77
96
  * Unmatched `wrapStep`/`wrapParallelFn` hooks still call `next()` automatically
@@ -84,6 +103,12 @@ interface StepMeta {
84
103
  * // Wildcard — any step whose label starts with "llm:"
85
104
  * flow.withRateLimit({ intervalMs: 1000 }, ["llm:*"]);
86
105
  *
106
+ * // Negation-only — apply everywhere except human-in-loop steps
107
+ * flow.withRateLimit({ intervalMs: 1000 }, ["!human:*"]);
108
+ *
109
+ * // Mixed — apply to llm steps, but never human steps
110
+ * flow.withRateLimit({ intervalMs: 1000 }, ["!human:*", "llm:*"]);
111
+ *
87
112
  * // Custom predicate
88
113
  * flow.addHooks({ beforeStep: log }, (meta) => meta.type === "fn");
89
114
  */
@@ -105,8 +130,18 @@ interface FlowHooks<S = any, P extends Record<string, unknown> = Record<string,
105
130
  * `fnIndex` is the position within the fns array.
106
131
  */
107
132
  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;
133
+ onError?: (meta: StepMeta, error: unknown, shared: S, params: P) => void | Promise<void>;
109
134
  afterFlow?: (shared: S, params: P) => void | Promise<void>;
135
+ /**
136
+ * Fires after each loop iteration completes. `iteration` is zero-based.
137
+ * Only fires for `.loop()` steps, not `.batch()` or `.parallel()`.
138
+ */
139
+ onLoopIteration?: (meta: StepMeta, iteration: number, shared: S, params: P) => void | Promise<void>;
140
+ /**
141
+ * Fires when a goto jump resolves to an anchor (i.e. a step returned `"#anchorName"`).
142
+ * `anchorName` is the anchor label without the `#` prefix.
143
+ */
144
+ onAnchorHit?: (anchorName: string, shared: S, params: P) => void | Promise<void>;
110
145
  }
111
146
  /**
112
147
  * A plugin is an object whose keys become methods on a `FlowBuilder.extend()` subclass prototype.
@@ -120,6 +155,17 @@ interface FlowHooks<S = any, P extends Record<string, unknown> = Record<string,
120
155
  * ```
121
156
  */
122
157
  type FlowneerPlugin = Record<string, (this: FlowBuilder<any, any>, ...args: any[]) => any>;
158
+ type ResolvedHooks<S, P extends Record<string, unknown>> = {
159
+ beforeFlow: NonNullable<FlowHooks<S, P>["beforeFlow"]>[];
160
+ beforeStep: NonNullable<FlowHooks<S, P>["beforeStep"]>[];
161
+ wrapStep: NonNullable<FlowHooks<S, P>["wrapStep"]>[];
162
+ afterStep: NonNullable<FlowHooks<S, P>["afterStep"]>[];
163
+ wrapParallelFn: NonNullable<FlowHooks<S, P>["wrapParallelFn"]>[];
164
+ onError: NonNullable<FlowHooks<S, P>["onError"]>[];
165
+ afterFlow: NonNullable<FlowHooks<S, P>["afterFlow"]>[];
166
+ onLoopIteration: NonNullable<FlowHooks<S, P>["onLoopIteration"]>[];
167
+ onAnchorHit: NonNullable<FlowHooks<S, P>["onAnchorHit"]>[];
168
+ };
123
169
 
124
170
  interface FnStep<S, P extends Record<string, unknown>> {
125
171
  type: "fn";
@@ -196,15 +242,6 @@ interface DagStep<S, P extends Record<string, unknown>> {
196
242
  }
197
243
  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
244
 
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
245
  /**
209
246
  * Runtime context passed to every registered step handler.
210
247
  * Full access to the builder so handlers can run sub-flows etc.
@@ -395,4 +432,4 @@ declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<st
395
432
  private _addFn;
396
433
  }
397
434
 
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 };
435
+ 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-Bvf_nDeb.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
@@ -21,15 +21,21 @@ var InterruptError = class extends Error {
21
21
  };
22
22
 
23
23
  // src/core/utils.ts
24
+ function matchesPattern(pattern, label) {
25
+ return pattern.includes("*") ? new RegExp(
26
+ "^" + pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
27
+ ).test(label) : pattern === label;
28
+ }
24
29
  function matchesFilter(filter, meta) {
25
30
  if (!Array.isArray(filter)) return filter(meta);
31
+ const negations = filter.filter((p) => p.startsWith("!"));
32
+ const positives = filter.filter((p) => !p.startsWith("!"));
26
33
  const label = meta.label;
27
- if (label === void 0) return false;
28
- return filter.some(
29
- (p) => p.includes("*") ? new RegExp(
30
- "^" + p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
31
- ).test(label) : p === label
32
- );
34
+ if (label !== void 0 && negations.some((p) => matchesPattern(p.slice(1), label)))
35
+ return false;
36
+ if (positives.length > 0)
37
+ return label !== void 0 && positives.some((p) => matchesPattern(p, label));
38
+ return true;
33
39
  }
34
40
  function resolveNumber(val, fallback, shared, params) {
35
41
  if (val === void 0) return fallback;
@@ -97,6 +103,7 @@ function applyStepFilter(hooks, filter) {
97
103
  wrap("onError");
98
104
  wrap("wrapStep", (_m, next) => next());
99
105
  wrap("wrapParallelFn", (_m, _fi, next) => next());
106
+ wrap("onLoopIteration");
100
107
  return hooks;
101
108
  }
102
109
  function buildHookCache(list) {
@@ -108,7 +115,9 @@ function buildHookCache(list) {
108
115
  afterStep: pick("afterStep"),
109
116
  wrapParallelFn: pick("wrapParallelFn"),
110
117
  onError: pick("onError"),
111
- afterFlow: pick("afterFlow")
118
+ afterFlow: pick("afterFlow"),
119
+ onLoopIteration: pick("onLoopIteration"),
120
+ onAnchorHit: pick("onAnchorHit")
112
121
  };
113
122
  }
114
123
  var CoreFlowBuilder = class _CoreFlowBuilder {
@@ -325,10 +334,12 @@ var CoreFlowBuilder = class _CoreFlowBuilder {
325
334
  );
326
335
  }
327
336
  i = target;
337
+ for (const h of hooks.onAnchorHit)
338
+ await h(gotoTarget, shared, params);
328
339
  }
329
340
  } catch (err) {
330
341
  if (err instanceof InterruptError) throw err;
331
- for (const h of hooks.onError) h(meta, err, shared, params);
342
+ for (const h of hooks.onError) await h(meta, err, shared, params);
332
343
  if (err instanceof FlowError) throw err;
333
344
  const labelPart = stepLabel ? `"${stepLabel}" ` : "";
334
345
  const label = step.type === "fn" ? `${labelPart}step ${i}` : `${labelPart}${step.type} (step ${i})`;
@@ -416,12 +427,16 @@ var branchHandler = async (step, { shared, params }) => {
416
427
  };
417
428
 
418
429
  // src/core/steps/loop.ts
419
- var loopHandler = async (step, { shared, params, signal, meta, builder }) => {
420
- while (await step.condition(shared, params))
430
+ var loopHandler = async (step, { shared, params, signal, meta, hooks, builder }) => {
431
+ let iteration = 0;
432
+ while (await step.condition(shared, params)) {
421
433
  await builder._runSub(
422
434
  `loop (step ${meta.index})`,
423
435
  () => step.body._execute(shared, params, signal)
424
436
  );
437
+ for (const h of hooks.onLoopIteration)
438
+ await h(meta, iteration++, shared, params);
439
+ }
425
440
  return void 0;
426
441
  };
427
442
 
@@ -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-Bvf_nDeb.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-Bvf_nDeb.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-Bvf_nDeb.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-Bvf_nDeb.js';
2
2
 
3
3
  /** A single node entry in the exported graph. */
4
4
  interface GraphNodeExport {
@@ -21,15 +21,21 @@ var InterruptError = class extends Error {
21
21
  };
22
22
 
23
23
  // src/core/utils.ts
24
+ function matchesPattern(pattern, label) {
25
+ return pattern.includes("*") ? new RegExp(
26
+ "^" + pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
27
+ ).test(label) : pattern === label;
28
+ }
24
29
  function matchesFilter(filter, meta) {
25
30
  if (!Array.isArray(filter)) return filter(meta);
31
+ const negations = filter.filter((p) => p.startsWith("!"));
32
+ const positives = filter.filter((p) => !p.startsWith("!"));
26
33
  const label = meta.label;
27
- if (label === void 0) return false;
28
- return filter.some(
29
- (p) => p.includes("*") ? new RegExp(
30
- "^" + p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
31
- ).test(label) : p === label
32
- );
34
+ if (label !== void 0 && negations.some((p) => matchesPattern(p.slice(1), label)))
35
+ return false;
36
+ if (positives.length > 0)
37
+ return label !== void 0 && positives.some((p) => matchesPattern(p, label));
38
+ return true;
33
39
  }
34
40
  function resolveNumber(val, fallback, shared, params) {
35
41
  if (val === void 0) return fallback;
@@ -97,6 +103,7 @@ function applyStepFilter(hooks, filter) {
97
103
  wrap("onError");
98
104
  wrap("wrapStep", (_m, next) => next());
99
105
  wrap("wrapParallelFn", (_m, _fi, next) => next());
106
+ wrap("onLoopIteration");
100
107
  return hooks;
101
108
  }
102
109
  function buildHookCache(list) {
@@ -108,7 +115,9 @@ function buildHookCache(list) {
108
115
  afterStep: pick("afterStep"),
109
116
  wrapParallelFn: pick("wrapParallelFn"),
110
117
  onError: pick("onError"),
111
- afterFlow: pick("afterFlow")
118
+ afterFlow: pick("afterFlow"),
119
+ onLoopIteration: pick("onLoopIteration"),
120
+ onAnchorHit: pick("onAnchorHit")
112
121
  };
113
122
  }
114
123
  var CoreFlowBuilder = class _CoreFlowBuilder {
@@ -325,10 +334,12 @@ var CoreFlowBuilder = class _CoreFlowBuilder {
325
334
  );
326
335
  }
327
336
  i = target;
337
+ for (const h of hooks.onAnchorHit)
338
+ await h(gotoTarget, shared, params);
328
339
  }
329
340
  } catch (err) {
330
341
  if (err instanceof InterruptError) throw err;
331
- for (const h of hooks.onError) h(meta, err, shared, params);
342
+ for (const h of hooks.onError) await h(meta, err, shared, params);
332
343
  if (err instanceof FlowError) throw err;
333
344
  const labelPart = stepLabel ? `"${stepLabel}" ` : "";
334
345
  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-Bvf_nDeb.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';