pipeai 0.8.2 → 0.8.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.
package/dist/index.js CHANGED
@@ -355,6 +355,847 @@ var Agent = class {
355
355
  import {
356
356
  createUIMessageStream
357
357
  } from "ai";
358
+
359
+ // src/steps/step.ts
360
+ var Step = class {
361
+ // Note: `type: "step"` subclasses (agent / transform / nested / branch /
362
+ // foreach / repeat / parallel) also carry a `readonly category` that the run
363
+ // loop reads (`getObservabilityType`) to type observability events. It is not
364
+ // declared on this base — the `StepNode` union in `workflow.ts` redeclares the
365
+ // node shape, so `category` is part of that structural contract rather than
366
+ // this class. Subclasses add it directly.
367
+ /**
368
+ * Precedence source tag a kind writes to `state.pendingError` when it
369
+ * captures a thrown body error. Defaults to `"step"`; kinds with a distinct
370
+ * error-precedence bucket (e.g. `finally`, `catch`, `gate`) override it.
371
+ */
372
+ errorSource = "step";
373
+ /**
374
+ * The step's body — the only method the run loop invokes. Each kind overrides
375
+ * it to do its work, run its own skip checks, and capture errors onto state.
376
+ * `state.output` is the input on entry and becomes the output on exit;
377
+ * `state.writer` is present in stream mode. The base implementation is a
378
+ * no-op so kinds that carry no body of their own need not override it.
379
+ */
380
+ async execute(_state) {
381
+ }
382
+ /**
383
+ * Run-policy gate: return `true` when this step should be skipped silently
384
+ * (no output change). The default is the "normal" policy — skip while the
385
+ * flow is suspended or already in error. Overridden by kinds with inverted
386
+ * policies: `catch` runs only when there's an error, `finally` always runs.
387
+ */
388
+ shouldSkip(state) {
389
+ return !!state.suspension || !!state.pendingError;
390
+ }
391
+ /**
392
+ * Apply `when` / `otherwise` conditional-skip options. Returns `true` when
393
+ * the body should be skipped — i.e. `when` returned false. On skip,
394
+ * `otherwise` (if present) produces the output; without it the input passes
395
+ * through unchanged. Distinct from {@link shouldSkip}: this is the body-level
396
+ * `when` / `otherwise` decision a kind applies after the policy gate passes.
397
+ */
398
+ async applyConditionalSkip(state, options) {
399
+ if (!options?.when) return false;
400
+ const params = { ctx: state.ctx, input: state.output };
401
+ if (await options.when(params)) return false;
402
+ if (options.otherwise) {
403
+ state.output = await options.otherwise(params);
404
+ }
405
+ return true;
406
+ }
407
+ };
408
+
409
+ // src/steps/transform-step.ts
410
+ var TransformStep = class extends Step {
411
+ type = "step";
412
+ id;
413
+ fn;
414
+ options;
415
+ constructor(id, fn, options) {
416
+ super();
417
+ this.id = id;
418
+ this.fn = fn;
419
+ this.options = options;
420
+ }
421
+ async execute(state) {
422
+ if (this.shouldSkip(state)) return;
423
+ try {
424
+ if (await this.applyConditionalSkip(state, this.options)) return;
425
+ state.output = await this.fn({
426
+ ctx: state.ctx,
427
+ input: state.output,
428
+ // Present in stream mode (undefined in generate mode), letting the
429
+ // inline step emit UIMessageChunk parts onto the workflow's stream.
430
+ writer: state.writer
431
+ });
432
+ } catch (error) {
433
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
434
+ }
435
+ }
436
+ };
437
+
438
+ // src/steps/agent-step.ts
439
+ var AgentStep = class _AgentStep extends Step {
440
+ type = "step";
441
+ id;
442
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
443
+ agent;
444
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
445
+ options;
446
+ constructor(id, agent, options) {
447
+ super();
448
+ this.id = id;
449
+ this.agent = agent;
450
+ this.options = options;
451
+ }
452
+ async execute(state) {
453
+ if (this.shouldSkip(state)) return;
454
+ try {
455
+ if (await this.applyConditionalSkip(state, this.options)) return;
456
+ await _AgentStep.runAgent(state, this.agent, state.ctx, this.options);
457
+ } catch (error) {
458
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
459
+ }
460
+ }
461
+ /**
462
+ * Run an agent against the current state, writing its result to
463
+ * `state.output`. In stream mode, output extraction awaits the full stream
464
+ * before returning — streaming benefits the client (incremental output), not
465
+ * pipeline throughput, since each step still runs sequentially.
466
+ *
467
+ * Static (does not touch instance state) so the still-literal foreach /
468
+ * parallel / branch combinators can share it. `itemIndex` identifies the
469
+ * execution to `handleStream` inside a multi-execution combinator (numeric
470
+ * index, record key, or matched case); `undefined` for a plain single
471
+ * `.step(agent)`.
472
+ */
473
+ static async runAgent(state, agent, ctx, options, itemIndex) {
474
+ const input = state.output;
475
+ const hasStructuredOutput = agent.hasOutput;
476
+ const abortSignal = state.abortSignal;
477
+ const agentCallOpts = abortSignal ? { abortSignal } : void 0;
478
+ if (state.mode === "stream" && state.writer) {
479
+ const writer = state.writer;
480
+ await runWithWriter(writer, async () => {
481
+ const result = await agent.stream(ctx, state.output, agentCallOpts);
482
+ if (options?.handleStream) {
483
+ await options.handleStream({ result, writer, ctx, input, itemIndex });
484
+ } else {
485
+ writer.merge(result.toUIMessageStream());
486
+ }
487
+ const hookParams = {
488
+ mode: "stream",
489
+ result,
490
+ ctx,
491
+ input
492
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
493
+ };
494
+ if (options?.onResult) {
495
+ await options.onResult(hookParams);
496
+ }
497
+ if (options?.mapResult) {
498
+ state.output = await options.mapResult(hookParams);
499
+ } else {
500
+ state.output = await extractOutput(result, hasStructuredOutput, agent.validateOutput);
501
+ }
502
+ });
503
+ } else {
504
+ const result = await agent.generate(ctx, state.output, agentCallOpts);
505
+ const hookParams = {
506
+ mode: "generate",
507
+ result,
508
+ ctx,
509
+ input
510
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
511
+ };
512
+ if (options?.onResult) {
513
+ await options.onResult(hookParams);
514
+ }
515
+ if (options?.mapResult) {
516
+ state.output = await options.mapResult(hookParams);
517
+ } else {
518
+ state.output = await extractOutput(result, hasStructuredOutput, agent.validateOutput);
519
+ }
520
+ }
521
+ }
522
+ };
523
+
524
+ // src/steps/branch-step.ts
525
+ var PredicateBranchStep = class extends Step {
526
+ type = "step";
527
+ category = "branch";
528
+ id;
529
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
530
+ cases;
531
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
532
+ constructor(id, cases) {
533
+ super();
534
+ this.id = id;
535
+ this.cases = cases;
536
+ }
537
+ async execute(state) {
538
+ if (this.shouldSkip(state)) return;
539
+ try {
540
+ const ctx = state.ctx;
541
+ const input = state.output;
542
+ for (let caseIndex = 0; caseIndex < this.cases.length; caseIndex++) {
543
+ const branchCase = this.cases[caseIndex];
544
+ if (branchCase.when) {
545
+ const match = await branchCase.when({ ctx, input });
546
+ if (!match) continue;
547
+ }
548
+ await AgentStep.runAgent(state, branchCase.agent, ctx, branchCase, caseIndex);
549
+ return;
550
+ }
551
+ let inputRepr;
552
+ try {
553
+ inputRepr = JSON.stringify(input);
554
+ if (inputRepr === void 0) inputRepr = String(input);
555
+ } catch {
556
+ inputRepr = `[unserializable ${typeof input}]`;
557
+ }
558
+ throw new WorkflowBranchError("predicate", `No branch matched and no default branch (a case without \`when\`) was provided. Input: ${inputRepr}`);
559
+ } catch (error) {
560
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
561
+ }
562
+ }
563
+ };
564
+ var SelectBranchStep = class extends Step {
565
+ type = "step";
566
+ category = "branch";
567
+ id;
568
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
569
+ config;
570
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
571
+ constructor(id, config) {
572
+ super();
573
+ this.id = id;
574
+ this.config = config;
575
+ }
576
+ async execute(state) {
577
+ if (this.shouldSkip(state)) return;
578
+ try {
579
+ const ctx = state.ctx;
580
+ const input = state.output;
581
+ const config = this.config;
582
+ const key = await config.select({ ctx, input });
583
+ const keyDeclared = Object.prototype.hasOwnProperty.call(config.agents, key);
584
+ if (keyDeclared && config.agents[key] === void 0) {
585
+ throw new WorkflowBranchError(
586
+ "select",
587
+ `Agent for key "${key}" was declared but the value is undefined. This usually means a conditional spread set the value to undefined. Available keys: ${Object.keys(config.agents).join(", ")}`
588
+ );
589
+ }
590
+ let agent = keyDeclared ? config.agents[key] : void 0;
591
+ if (!agent) {
592
+ if (config.onUnknownKey) {
593
+ config.onUnknownKey({
594
+ key,
595
+ availableKeys: Object.keys(config.agents),
596
+ ctx
597
+ });
598
+ }
599
+ if (config.fallback) {
600
+ agent = config.fallback;
601
+ } else {
602
+ throw new WorkflowBranchError("select", `No agent found for key "${key}" and no fallback provided. Available keys: ${Object.keys(config.agents).join(", ")}`);
603
+ }
604
+ }
605
+ await AgentStep.runAgent(state, agent, ctx, config, key);
606
+ } catch (error) {
607
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
608
+ }
609
+ }
610
+ };
611
+
612
+ // src/steps/semaphore.ts
613
+ var Semaphore = class {
614
+ available;
615
+ waiters = [];
616
+ constructor(permits) {
617
+ this.available = permits;
618
+ }
619
+ acquire() {
620
+ if (this.available > 0) {
621
+ this.available--;
622
+ return Promise.resolve();
623
+ }
624
+ return new Promise((resolve) => this.waiters.push(resolve));
625
+ }
626
+ release() {
627
+ const next = this.waiters.shift();
628
+ if (next) {
629
+ next();
630
+ } else {
631
+ this.available++;
632
+ }
633
+ }
634
+ async run(fn) {
635
+ await this.acquire();
636
+ try {
637
+ return await fn();
638
+ } finally {
639
+ this.release();
640
+ }
641
+ }
642
+ };
643
+
644
+ // src/steps/concurrent.ts
645
+ function reconcileUnits(state, id, failures, count, keyAt, unitStates, signal) {
646
+ for (let i = 0; i < count; i++) {
647
+ const us = unitStates[i];
648
+ if (!us) continue;
649
+ if (us.suspension) {
650
+ throw new Error(
651
+ `internal: gate "${us.suspension.gateId}" suspended inside concurrent unit ${id}[${keyAt(i)}]. Gates are forbidden in foreach / parallel targets \u2014 a cast must have bypassed the build-time guard.`
652
+ );
653
+ }
654
+ if (!us.warnings) continue;
655
+ for (const w of us.warnings) {
656
+ pushWarning(state, w.source, `${id}[${keyAt(i)}]:${w.stepId}`, w.error);
657
+ }
658
+ }
659
+ if (signal?.aborted) {
660
+ for (const f of failures) {
661
+ pushWarning(state, "foreach-sibling", `${id}[${f.key}]`, f.error);
662
+ }
663
+ throw signal.reason ?? new Error("Workflow aborted");
664
+ }
665
+ return failures;
666
+ }
667
+
668
+ // src/steps/foreach-step.ts
669
+ var ForeachStep = class extends Step {
670
+ type = "step";
671
+ category = "foreach";
672
+ id;
673
+ nestedWorkflow;
674
+ target;
675
+ concurrency;
676
+ onError;
677
+ handleStream;
678
+ isWorkflow;
679
+ inheritStreaming;
680
+ observability;
681
+ constructor(target, options, observability) {
682
+ super();
683
+ if (options?.concurrency !== void 0 && !(Number.isInteger(options.concurrency) && options.concurrency >= 1 || options.concurrency === Infinity)) {
684
+ throw new Error(`foreach: concurrency must be a positive integer or Infinity, got ${options.concurrency}`);
685
+ }
686
+ this.target = target;
687
+ this.concurrency = options?.concurrency ?? Infinity;
688
+ this.onError = options?.onError;
689
+ this.handleStream = options?.handleStream;
690
+ this.observability = observability;
691
+ this.isWorkflow = target instanceof SealedWorkflow;
692
+ this.inheritStreaming = this.isWorkflow || this.handleStream !== void 0;
693
+ const defaultId = this.isWorkflow ? target.id ?? "foreach" : `foreach:${target.id}`;
694
+ this.id = options?.id ?? defaultId;
695
+ this.nestedWorkflow = this.isWorkflow ? target : void 0;
696
+ }
697
+ async execute(state) {
698
+ if (this.shouldSkip(state)) return;
699
+ try {
700
+ const items = state.output;
701
+ if (!Array.isArray(items)) {
702
+ throw new Error(`foreach "${this.id}": expected array input, got ${typeof items}`);
703
+ }
704
+ const results = new Array(items.length);
705
+ const skipped = /* @__PURE__ */ new Set();
706
+ const itemStates = new Array(items.length);
707
+ const wantItemHooks = hasItemHooks(this.observability);
708
+ const executeItem = async (item, index) => {
709
+ const itemState = {
710
+ ctx: state.ctx,
711
+ output: item,
712
+ mode: this.inheritStreaming ? state.mode : "generate",
713
+ writer: this.inheritStreaming ? state.writer : void 0,
714
+ abortSignal: state.abortSignal
715
+ };
716
+ itemStates[index] = itemState;
717
+ const itemStart = wantItemHooks ? performance.now() : 0;
718
+ if (wantItemHooks) {
719
+ await fireHook(this.observability, state, "onItemStart", {
720
+ stepId: this.id,
721
+ type: "foreach",
722
+ itemIndex: index,
723
+ ctx: state.ctx,
724
+ input: item
725
+ });
726
+ }
727
+ try {
728
+ if (this.isWorkflow) {
729
+ await this.target.executeAsNested(itemState);
730
+ } else {
731
+ await AgentStep.runAgent(
732
+ itemState,
733
+ this.target,
734
+ state.ctx,
735
+ this.handleStream ? { handleStream: this.handleStream } : void 0,
736
+ index
737
+ );
738
+ }
739
+ results[index] = itemState.output;
740
+ if (wantItemHooks) {
741
+ await fireHook(this.observability, state, "onItemFinish", {
742
+ stepId: this.id,
743
+ type: "foreach",
744
+ itemIndex: index,
745
+ ctx: state.ctx,
746
+ output: itemState.output,
747
+ durationMs: performance.now() - itemStart
748
+ });
749
+ }
750
+ } catch (error) {
751
+ if (wantItemHooks) {
752
+ await fireHook(this.observability, state, "onItemError", {
753
+ stepId: this.id,
754
+ type: "foreach",
755
+ itemIndex: index,
756
+ ctx: state.ctx,
757
+ error,
758
+ durationMs: performance.now() - itemStart
759
+ });
760
+ }
761
+ throw error;
762
+ }
763
+ };
764
+ const sem = new Semaphore(this.concurrency);
765
+ const failures = [];
766
+ const inflight = /* @__PURE__ */ new Set();
767
+ for (let i = 0; i < items.length; i++) {
768
+ if (state.abortSignal?.aborted) break;
769
+ await sem.acquire();
770
+ if (state.abortSignal?.aborted) {
771
+ sem.release();
772
+ break;
773
+ }
774
+ const index = i;
775
+ const unit = (async () => {
776
+ try {
777
+ await executeItem(items[index], index);
778
+ } catch (error) {
779
+ failures.push({ key: index, index, error });
780
+ } finally {
781
+ sem.release();
782
+ }
783
+ })();
784
+ inflight.add(unit);
785
+ void unit.finally(() => inflight.delete(unit));
786
+ }
787
+ await Promise.all(inflight);
788
+ failures.sort((a, b) => a.index - b.index);
789
+ const nonGateFailures = reconcileUnits(state, this.id, failures, items.length, (i) => i, itemStates, state.abortSignal);
790
+ for (const { index, error } of nonGateFailures) {
791
+ if (!this.onError) throw error;
792
+ const recovered = await this.onError({ error, item: items[index], index, ctx: state.ctx });
793
+ if (recovered === Workflow.SKIP) {
794
+ skipped.add(index);
795
+ } else {
796
+ results[index] = recovered;
797
+ }
798
+ }
799
+ state.output = skipped.size === 0 ? results : results.filter((_, i) => !skipped.has(i));
800
+ } catch (error) {
801
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
802
+ }
803
+ }
804
+ };
805
+
806
+ // src/steps/parallel-step.ts
807
+ var ParallelStep = class extends Step {
808
+ type = "step";
809
+ category = "parallel";
810
+ id;
811
+ entries;
812
+ isTuple;
813
+ branchCount;
814
+ concurrency;
815
+ onError;
816
+ handleStream;
817
+ observability;
818
+ constructor(branches, options, observability) {
819
+ super();
820
+ this.isTuple = Array.isArray(branches);
821
+ this.entries = this.isTuple ? branches.map((target, i) => ({ key: i, index: i, target })) : Object.entries(branches).map(([k, t], i) => ({ key: k, index: i, target: t }));
822
+ this.branchCount = this.entries.length;
823
+ const requestedConcurrency = options?.concurrency;
824
+ if (requestedConcurrency !== void 0 && !(Number.isInteger(requestedConcurrency) && requestedConcurrency >= 1 || requestedConcurrency === Infinity)) {
825
+ throw new Error(`parallel: concurrency must be a positive integer or Infinity, got ${requestedConcurrency}`);
826
+ }
827
+ this.concurrency = requestedConcurrency ?? Infinity;
828
+ this.onError = options?.onError;
829
+ this.handleStream = options?.handleStream;
830
+ this.observability = observability;
831
+ this.id = options?.id ?? (this.isTuple ? "parallel:tuple" : "parallel:record");
832
+ }
833
+ async execute(state) {
834
+ if (this.shouldSkip(state)) return;
835
+ try {
836
+ const input = state.output;
837
+ const results = this.isTuple ? new Array(this.branchCount) : {};
838
+ const branchStates = new Array(this.branchCount);
839
+ const wantItemHooks = hasItemHooks(this.observability);
840
+ const executeBranch = async ({ key, index, target }) => {
841
+ const isWorkflowBranch = target instanceof SealedWorkflow;
842
+ const inheritStreaming = isWorkflowBranch || this.handleStream !== void 0;
843
+ const branchState = {
844
+ ctx: state.ctx,
845
+ output: input,
846
+ mode: inheritStreaming ? state.mode : "generate",
847
+ writer: inheritStreaming ? state.writer : void 0,
848
+ abortSignal: state.abortSignal
849
+ };
850
+ branchStates[index] = branchState;
851
+ const branchStart = wantItemHooks ? performance.now() : 0;
852
+ const itemIndex = this.isTuple ? index : key;
853
+ if (wantItemHooks) {
854
+ await fireHook(this.observability, state, "onItemStart", {
855
+ stepId: this.id,
856
+ type: "parallel",
857
+ itemIndex,
858
+ ctx: state.ctx,
859
+ input
860
+ });
861
+ }
862
+ try {
863
+ if (isWorkflowBranch) {
864
+ await target.executeAsNested(branchState);
865
+ } else {
866
+ await AgentStep.runAgent(
867
+ branchState,
868
+ target,
869
+ state.ctx,
870
+ this.handleStream ? { handleStream: this.handleStream } : void 0,
871
+ itemIndex
872
+ );
873
+ }
874
+ results[key] = branchState.output;
875
+ if (wantItemHooks) {
876
+ await fireHook(this.observability, state, "onItemFinish", {
877
+ stepId: this.id,
878
+ type: "parallel",
879
+ itemIndex,
880
+ ctx: state.ctx,
881
+ output: branchState.output,
882
+ durationMs: performance.now() - branchStart
883
+ });
884
+ }
885
+ } catch (error) {
886
+ if (wantItemHooks) {
887
+ await fireHook(this.observability, state, "onItemError", {
888
+ stepId: this.id,
889
+ type: "parallel",
890
+ itemIndex,
891
+ ctx: state.ctx,
892
+ error,
893
+ durationMs: performance.now() - branchStart
894
+ });
895
+ }
896
+ throw error;
897
+ }
898
+ };
899
+ const keyAt = (i) => this.entries[i].key;
900
+ const sem = new Semaphore(this.concurrency);
901
+ const failures = [];
902
+ const inflight = /* @__PURE__ */ new Set();
903
+ for (let i = 0; i < this.branchCount; i++) {
904
+ if (state.abortSignal?.aborted) break;
905
+ await sem.acquire();
906
+ if (state.abortSignal?.aborted) {
907
+ sem.release();
908
+ break;
909
+ }
910
+ const index = i;
911
+ const unit = (async () => {
912
+ try {
913
+ await executeBranch(this.entries[index]);
914
+ } catch (error) {
915
+ failures.push({ key: keyAt(index), index, error });
916
+ } finally {
917
+ sem.release();
918
+ }
919
+ })();
920
+ inflight.add(unit);
921
+ void unit.finally(() => inflight.delete(unit));
922
+ }
923
+ await Promise.all(inflight);
924
+ failures.sort((a, b) => a.index - b.index);
925
+ const nonGateFailures = reconcileUnits(state, this.id, failures, this.branchCount, keyAt, branchStates, state.abortSignal);
926
+ for (const { key, index, error } of nonGateFailures) {
927
+ if (!this.onError) throw error;
928
+ const recovered = await this.onError({
929
+ error,
930
+ key: this.isTuple ? void 0 : key,
931
+ index: this.isTuple ? index : void 0,
932
+ ctx: state.ctx
933
+ });
934
+ if (recovered === Workflow.SKIP) {
935
+ results[key] = void 0;
936
+ } else {
937
+ results[key] = recovered;
938
+ }
939
+ }
940
+ state.output = results;
941
+ } catch (error) {
942
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
943
+ }
944
+ }
945
+ };
946
+
947
+ // src/steps/gate-step.ts
948
+ var GateStep = class extends Step {
949
+ type = "gate";
950
+ id;
951
+ errorSource = "gate";
952
+ /** Read by `loadState` to validate the resumed gate response. */
953
+ schema;
954
+ /** Read by `loadState` to merge the response with the suspended output. */
955
+ merge;
956
+ payload;
957
+ condition;
958
+ constructor(id, payload, schema, condition, merge) {
959
+ super();
960
+ this.id = id;
961
+ this.payload = payload;
962
+ this.schema = schema;
963
+ this.condition = condition;
964
+ this.merge = merge;
965
+ }
966
+ async execute(state) {
967
+ if (this.shouldSkip(state)) return;
968
+ try {
969
+ if (this.condition && !await this.condition(state)) return;
970
+ const snapshot = {
971
+ version: 2,
972
+ kind: "gate",
973
+ resumeFromIndex: state.stepIndex ?? -1,
974
+ output: state.output,
975
+ gateId: this.id,
976
+ gatePayload: await this.payload(state)
977
+ };
978
+ state.suspension = snapshot;
979
+ if (resolveFreezeSnapshots(state)) deepFreeze(snapshot);
980
+ } catch (error) {
981
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
982
+ }
983
+ }
984
+ };
985
+
986
+ // src/steps/catch-step.ts
987
+ var CatchStep = class extends Step {
988
+ type = "catch";
989
+ id;
990
+ errorSource = "catch";
991
+ catchFn;
992
+ constructor(id, catchFn) {
993
+ super();
994
+ this.id = id;
995
+ this.catchFn = catchFn;
996
+ }
997
+ // Runs only on a pending error; skipped on suspension and checkpoint failure.
998
+ shouldSkip(state) {
999
+ return !!state.suspension || !state.pendingError || !!state.checkpointFailed;
1000
+ }
1001
+ async execute(state) {
1002
+ if (this.shouldSkip(state)) return;
1003
+ const handled = state.pendingError;
1004
+ state.output = await this.catchFn({
1005
+ error: handled.error,
1006
+ ctx: state.ctx,
1007
+ lastOutput: state.output,
1008
+ stepId: handled.stepId
1009
+ });
1010
+ state.pendingError = void 0;
1011
+ }
1012
+ };
1013
+
1014
+ // src/steps/finally-step.ts
1015
+ var FinallyStep = class extends Step {
1016
+ type = "finally";
1017
+ id;
1018
+ errorSource = "finally";
1019
+ fn;
1020
+ constructor(id, fn) {
1021
+ super();
1022
+ this.id = id;
1023
+ this.fn = fn;
1024
+ }
1025
+ // Always runs — cleanup must fire regardless of suspension / error state.
1026
+ shouldSkip(_state) {
1027
+ return false;
1028
+ }
1029
+ async execute(state) {
1030
+ if (this.shouldSkip(state)) return;
1031
+ await this.fn({ ctx: state.ctx });
1032
+ }
1033
+ };
1034
+
1035
+ // src/steps/nested-workflow-step.ts
1036
+ var NestedWorkflowStep = class extends Step {
1037
+ type = "step";
1038
+ category = "nested";
1039
+ id;
1040
+ nestedWorkflow;
1041
+ options;
1042
+ constructor(id, workflow, options) {
1043
+ super();
1044
+ this.id = id;
1045
+ this.nestedWorkflow = workflow;
1046
+ this.options = options;
1047
+ }
1048
+ async execute(state) {
1049
+ const descent = state.resumeDescent;
1050
+ if (descent) {
1051
+ state.resumeDescent = void 0;
1052
+ const [childStart, ...rest] = descent.remaining;
1053
+ const myIndex2 = state.stepIndex ?? -1;
1054
+ try {
1055
+ if (rest.length === 0) {
1056
+ state.output = descent.seedOutput;
1057
+ } else {
1058
+ state.resumeDescent = { remaining: rest, seedOutput: descent.seedOutput };
1059
+ }
1060
+ await this.nestedWorkflow.executeAsNested(state, childStart);
1061
+ if (state.suspension) state.suspension = prependNestedPath(state.suspension, myIndex2, state);
1062
+ } catch (error) {
1063
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
1064
+ }
1065
+ return;
1066
+ }
1067
+ if (this.shouldSkip(state)) return;
1068
+ const myIndex = state.stepIndex ?? -1;
1069
+ try {
1070
+ if (await this.applyConditionalSkip(state, this.options)) return;
1071
+ await this.nestedWorkflow.executeAsNested(state);
1072
+ if (state.suspension) state.suspension = prependNestedPath(state.suspension, myIndex, state);
1073
+ } catch (error) {
1074
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
1075
+ }
1076
+ }
1077
+ };
1078
+
1079
+ // src/steps/repeat-step.ts
1080
+ var RepeatStep = class extends Step {
1081
+ type = "step";
1082
+ category = "repeat";
1083
+ id;
1084
+ nestedWorkflow;
1085
+ target;
1086
+ predicate;
1087
+ maxIterations;
1088
+ isWorkflow;
1089
+ constructor(id, target, predicate, maxIterations, isWorkflow) {
1090
+ super();
1091
+ this.id = id;
1092
+ this.target = target;
1093
+ this.predicate = predicate;
1094
+ this.maxIterations = maxIterations;
1095
+ this.isWorkflow = isWorkflow;
1096
+ this.nestedWorkflow = isWorkflow ? target : void 0;
1097
+ }
1098
+ async execute(state) {
1099
+ if (this.shouldSkip(state)) return;
1100
+ try {
1101
+ const ctx = state.ctx;
1102
+ for (let i = 1; i <= this.maxIterations; i++) {
1103
+ if (state.abortSignal?.aborted) {
1104
+ throw state.abortSignal.reason ?? new Error("Workflow aborted");
1105
+ }
1106
+ if (this.isWorkflow) {
1107
+ await this.target.executeAsNested(state);
1108
+ } else {
1109
+ await AgentStep.runAgent(state, this.target, ctx);
1110
+ }
1111
+ const done = await this.predicate({ output: state.output, ctx, iterations: i });
1112
+ if (done) return;
1113
+ }
1114
+ throw new WorkflowLoopError(this.maxIterations, this.maxIterations);
1115
+ } catch (error) {
1116
+ state.pendingError = { error, stepId: this.id, source: this.errorSource };
1117
+ }
1118
+ }
1119
+ };
1120
+
1121
+ // src/runtime.ts
1122
+ function resolveFreezeSnapshots(state) {
1123
+ return state.runOptions?.freezeSnapshots ? true : false;
1124
+ }
1125
+ function pendingErrorSourceToStepType(source) {
1126
+ switch (source) {
1127
+ case "step":
1128
+ return "step";
1129
+ case "gate":
1130
+ return "gate";
1131
+ case "finally":
1132
+ return "finally";
1133
+ case "catch":
1134
+ return "catch";
1135
+ case "onCheckpoint":
1136
+ return "step";
1137
+ }
1138
+ }
1139
+ async function emitCheckpoint(state, opts, resumeFromIndex, stepShapeHash) {
1140
+ if (!opts.onCheckpoint) return;
1141
+ const willFreeze = resolveFreezeSnapshots(state);
1142
+ const snap = {
1143
+ version: 2,
1144
+ kind: "checkpoint",
1145
+ resumeFromIndex,
1146
+ output: willFreeze ? structuredClone(state.output) : state.output,
1147
+ stepShapeHash
1148
+ };
1149
+ if (willFreeze) deepFreeze(snap);
1150
+ await opts.onCheckpoint(snap, { signal: state.abortSignal });
1151
+ }
1152
+ var warnedStreamOnErrorOnSuspend = false;
1153
+ function pushWarning(state, source, stepId, error) {
1154
+ (state.warnings ??= []).push({ source, stepId, error });
1155
+ }
1156
+ function fireHook(observability, state, name, event) {
1157
+ const hook = observability?.[name];
1158
+ if (!hook) return void 0;
1159
+ return fireHookSlow(state, name, event, hook);
1160
+ }
1161
+ async function fireHookSlow(state, name, event, hook) {
1162
+ try {
1163
+ await hook(event);
1164
+ return void 0;
1165
+ } catch (e) {
1166
+ if (name !== "onStepError") {
1167
+ const stepId = event.stepId;
1168
+ pushWarning(state, name, stepId, e);
1169
+ console.error(`pipeai: ${name} hook threw for stepId "${stepId}":`, e);
1170
+ }
1171
+ return e;
1172
+ }
1173
+ }
1174
+ function hasItemHooks(observability) {
1175
+ return !!observability && !!(observability.onItemStart || observability.onItemFinish || observability.onItemError);
1176
+ }
1177
+ function demotePendingError(state, pe) {
1178
+ pushWarning(state, pe.source, pe.stepId, pe.error);
1179
+ }
1180
+ function maybeWarnStreamOnErrorOnSuspend(result, options) {
1181
+ if (result.status !== "suspended" || !options?.onError || warnedStreamOnErrorOnSuspend) return;
1182
+ warnedStreamOnErrorOnSuspend = true;
1183
+ console.warn(
1184
+ "pipeai: stream() with options.onError suspended at a gate \u2014 onError will NOT be invoked for suspension. Discriminate via the resolved output Promise."
1185
+ );
1186
+ }
1187
+ function makeRuntimeState(ctx, output, mode, opts, writer) {
1188
+ return {
1189
+ ctx,
1190
+ output,
1191
+ mode,
1192
+ ...writer ? { writer } : {},
1193
+ runOptions: opts,
1194
+ abortSignal: opts?.abortSignal
1195
+ };
1196
+ }
1197
+
1198
+ // src/workflow.ts
358
1199
  var WorkflowBranchError = class extends Error {
359
1200
  constructor(branchType, message) {
360
1201
  super(message);
@@ -370,33 +1211,13 @@ var WorkflowLoopError = class extends Error {
370
1211
  this.name = "WorkflowLoopError";
371
1212
  }
372
1213
  };
373
- var NestedGateUnsupportedError = class extends Error {
374
- gateId;
375
- workflowId;
376
- // Always present; non-gate rejections from concurrent foreach.
377
- siblingErrors;
378
- // Always present; OTHER suspending items in concurrent foreach.
379
- siblingSuspensions;
380
- constructor(gateId, workflowId, siblingErrors = [], siblingSuspensions = []) {
381
- super(`Gate "${gateId}" hit inside nested workflow "${workflowId ?? "(anonymous)"}". Nested gates are not yet supported.`);
382
- this.name = "NestedGateUnsupportedError";
383
- this.gateId = gateId;
384
- this.workflowId = workflowId;
385
- this.siblingErrors = siblingErrors;
386
- this.siblingSuspensions = siblingSuspensions;
387
- }
388
- };
389
1214
  var CHECKPOINT_STEP_ID = "::pipeai::onCheckpoint";
390
- var CheckpointTimeoutError = class extends Error {
391
- timeoutMs;
392
- constructor(timeoutMs) {
393
- super(`onCheckpoint exceeded ${timeoutMs}ms timeout`);
394
- this.name = "CheckpointTimeoutError";
395
- this.timeoutMs = timeoutMs;
396
- }
397
- };
398
- function resolveFreezeSnapshots(state) {
399
- return state.runOptions?.freezeSnapshots ? true : false;
1215
+ var ABORT_STEP_ID = "::pipeai::abort";
1216
+ var GATE_RESUME_STEP_ID = "::pipeai::gate:resume";
1217
+ function prependNestedPath(snapshot, index, state) {
1218
+ const next = { ...snapshot, nestedPath: [index, ...snapshot.nestedPath ?? []] };
1219
+ if (resolveFreezeSnapshots(state)) deepFreeze(next);
1220
+ return next;
400
1221
  }
401
1222
  function migrateSnapshot(legacy) {
402
1223
  if (legacy.version !== 1) {
@@ -425,67 +1246,6 @@ function getNestedWorkflows(node) {
425
1246
  return [];
426
1247
  }
427
1248
  }
428
- function pendingErrorSourceToStepType(source) {
429
- switch (source) {
430
- case "step":
431
- return "step";
432
- case "finally":
433
- return "finally";
434
- case "catch":
435
- return "catch";
436
- case "onCheckpoint":
437
- return "step";
438
- }
439
- }
440
- async function emitCheckpoint(state, opts, resumeFromIndex, stepShapeHash) {
441
- if (!opts.onCheckpoint) return;
442
- const snap = {
443
- version: 2,
444
- kind: "checkpoint",
445
- resumeFromIndex,
446
- output: state.output,
447
- stepShapeHash
448
- };
449
- if (resolveFreezeSnapshots(state)) deepFreeze(snap);
450
- const controller = new AbortController();
451
- if (opts.checkpointTimeout !== void 0) {
452
- const timeoutMs = opts.checkpointTimeout;
453
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
454
- try {
455
- const callPromise = Promise.resolve(opts.onCheckpoint(snap, { signal: controller.signal }));
456
- const timeoutPromise = new Promise((_, reject) => {
457
- controller.signal.addEventListener(
458
- "abort",
459
- () => reject(new CheckpointTimeoutError(timeoutMs)),
460
- { once: true }
461
- );
462
- });
463
- callPromise.catch(() => {
464
- });
465
- timeoutPromise.catch(() => {
466
- });
467
- await Promise.race([callPromise, timeoutPromise]);
468
- } finally {
469
- clearTimeout(timeoutId);
470
- }
471
- } else {
472
- await opts.onCheckpoint(snap, { signal: controller.signal });
473
- }
474
- }
475
- var warnedStreamOnErrorOnSuspend = false;
476
- function pushWarning(state, source, stepId, error) {
477
- (state.warnings ??= []).push({ source, stepId, error });
478
- }
479
- function demotePendingError(state, pe) {
480
- pushWarning(state, pe.source, pe.stepId, pe.error);
481
- }
482
- function maybeWarnStreamOnErrorOnSuspend(result, options) {
483
- if (result.status !== "suspended" || !options?.onError || warnedStreamOnErrorOnSuspend) return;
484
- warnedStreamOnErrorOnSuspend = true;
485
- console.warn(
486
- "pipeai: stream() with options.onError suspended at a gate \u2014 onError will NOT be invoked for suspension. Discriminate via the resolved output Promise."
487
- );
488
- }
489
1249
  var SealedWorkflow = class _SealedWorkflow {
490
1250
  id;
491
1251
  steps;
@@ -495,6 +1255,7 @@ var SealedWorkflow = class _SealedWorkflow {
495
1255
  // Memoized lazily per terminal instance — build pipelines once at module
496
1256
  // load and re-run via generate() to amortize.
497
1257
  _cachedExecutableStepCount;
1258
+ _cachedCheckpointableStepCount;
498
1259
  _cachedStepShapeHash;
499
1260
  constructor(steps, id, observability) {
500
1261
  this.steps = steps;
@@ -545,6 +1306,24 @@ var SealedWorkflow = class _SealedWorkflow {
545
1306
  this._cachedExecutableStepCount = n;
546
1307
  return n;
547
1308
  }
1309
+ /**
1310
+ * Count of *checkpointable* nodes — `type === "step"` only (this includes
1311
+ * `branch`/`foreach`/`repeat`/`parallel`/`nested`, all internally `step`).
1312
+ * Drives the checkpoint auto-cadence denominator. Distinct from
1313
+ * {@link cachedExecutableStepCount}, which also counts `gate` nodes: gates
1314
+ * suspend/skip and never reach the checkpoint block, so the runtime
1315
+ * `executableStepsSeen` counter never advances on them. Counting gates in
1316
+ * the denominator would dilute the "~4 checkpoints across the run" target.
1317
+ */
1318
+ get cachedCheckpointableStepCount() {
1319
+ if (this._cachedCheckpointableStepCount !== void 0) return this._cachedCheckpointableStepCount;
1320
+ let n = 0;
1321
+ for (const s of this.steps) {
1322
+ if (s.type === "step") n++;
1323
+ }
1324
+ this._cachedCheckpointableStepCount = n;
1325
+ return n;
1326
+ }
548
1327
  /** @internal — used by `computeStepShapeHash` to descend nested workflows. */
549
1328
  getStepsForShapeHash() {
550
1329
  return this.steps;
@@ -575,11 +1354,8 @@ var SealedWorkflow = class _SealedWorkflow {
575
1354
  if (opts.checkpointEvery !== void 0 && (!Number.isInteger(opts.checkpointEvery) || opts.checkpointEvery < 1)) {
576
1355
  throw new Error(`RunOptions: checkpointEvery must be a positive integer, got ${opts.checkpointEvery}`);
577
1356
  }
578
- if (opts.checkpointTimeout !== void 0 && (!Number.isFinite(opts.checkpointTimeout) || opts.checkpointTimeout < 1)) {
579
- throw new Error(`RunOptions: checkpointTimeout must be a finite positive number (ms), got ${opts.checkpointTimeout}`);
580
- }
581
1357
  const length = this.cachedExecutableStepCount;
582
- const cadence = opts.checkpointEvery ?? Math.max(1, Math.ceil(length / 4));
1358
+ const cadence = opts.checkpointEvery ?? Math.max(1, Math.ceil(this.cachedCheckpointableStepCount / 4));
583
1359
  if (opts.freezeSnapshots && opts.freezeSnapshots !== "iAcceptThePerformanceCost" && cadence === 1 && length >= 8) {
584
1360
  throw new Error(
585
1361
  `freezeSnapshots+checkpointEvery:1 on a ${length}-step workflow is reliably catastrophic. Set checkpointEvery >= 5, freezeSnapshots: false, or pass "iAcceptThePerformanceCost".`
@@ -607,47 +1383,82 @@ var SealedWorkflow = class _SealedWorkflow {
607
1383
  * `await` the result — `await undefined` is sync, so the no-hook path
608
1384
  * stays allocation-free.
609
1385
  */
1386
+ // Thin delegates to the free `fireHook` / `hasItemHooks` (which take an
1387
+ // explicit observability). Kept as methods so the loop's many `this.fireHook`
1388
+ // call sites stay unchanged; bare `fireHook` / `hasItemHooks` below resolve to
1389
+ // the module-level functions, not these members.
610
1390
  fireHook(state, name, event) {
611
- const hook = this.observability?.[name];
612
- if (!hook) return void 0;
613
- return this.fireHookSlow(state, name, event, hook);
1391
+ return fireHook(this.observability, state, name, event);
614
1392
  }
615
- async fireHookSlow(state, name, event, hook) {
616
- try {
617
- await hook(event);
618
- return void 0;
619
- } catch (e) {
620
- if (name !== "onStepError") {
621
- const stepId = event.stepId;
622
- pushWarning(state, name, stepId, e);
623
- console.error(`pipeai: ${name} hook threw for stepId "${stepId}":`, e);
1393
+ hasItemHooks() {
1394
+ return hasItemHooks(this.observability);
1395
+ }
1396
+ /**
1397
+ * Fire `onStepError` for a step-body failure and honor the documented
1398
+ * cause-attachment contract uniformly across every firing path (step, gate,
1399
+ * catch, finally, checkpoint). When the hook itself throws, its error is
1400
+ * attached as `cause` on the ORIGINAL error so the original still reaches the
1401
+ * caller with the failure trail attached. If the original error is frozen /
1402
+ * non-extensible (cause assignment throws) or is not an object, the hook
1403
+ * error is recorded as a warning instead — so an `onStepError` throw is never
1404
+ * silently lost. (The suspension-wins tail fires `onStepError` separately, on
1405
+ * its own demotion path.)
1406
+ */
1407
+ async fireStepErrorAndAttachCause(state, event) {
1408
+ const obsError = await this.fireHook(state, "onStepError", event);
1409
+ if (obsError === void 0) return;
1410
+ const e = event.error;
1411
+ if (typeof e === "object" && e !== null) {
1412
+ try {
1413
+ e.cause = obsError;
1414
+ return;
1415
+ } catch {
624
1416
  }
625
- return e;
626
1417
  }
1418
+ pushWarning(state, "onStepError", event.stepId, obsError);
627
1419
  }
628
1420
  // ── Execution ─────────────────────────────────────────────────
629
1421
  async generate(ctx, ...args) {
630
1422
  this.ensureDuplicateCheck();
631
1423
  const input = args[0];
632
1424
  const opts = args[1];
633
- this.validateRunOptions(opts);
634
- const state = {
635
- ctx,
636
- output: input,
637
- mode: "generate",
638
- runOptions: opts,
639
- abortSignal: opts?.abortSignal
640
- };
641
- await this.execute(state, 0, opts);
642
- return this.buildResult(state);
1425
+ return this.runGenerate(ctx, 0, opts, () => ({ output: input, initialError: null }));
643
1426
  }
644
1427
  stream(ctx, ...args) {
645
1428
  this.ensureDuplicateCheck();
646
1429
  const input = args[0];
647
1430
  const options = args[1];
648
1431
  const opts = args[2];
1432
+ return this.runStream(ctx, 0, opts, options, () => ({ output: input, initialError: null }));
1433
+ }
1434
+ // Helper — converts terminal RuntimeState into a WorkflowResult; freezes
1435
+ // snapshot + warnings if requested via runOptions.
1436
+ buildResult(state) {
1437
+ const warnings = state.warnings ?? [];
1438
+ if (resolveFreezeSnapshots(state)) {
1439
+ deepFreeze(warnings);
1440
+ }
1441
+ if (state.suspension) {
1442
+ return { status: "suspended", snapshot: state.suspension, warnings };
1443
+ }
1444
+ return { status: "complete", output: state.output, warnings };
1445
+ }
1446
+ // ── Shared run drivers (generate / stream) ────────────────────
1447
+ // Every public entry point — base generate/stream plus gate- and
1448
+ // checkpoint-resume — differs only in (a) how it seeds the initial output /
1449
+ // pre-execute error and (b) the start index. Both drivers take a `seed`
1450
+ // thunk for (a) and a `startIndex` for (b); the rest (validation, state
1451
+ // construction, execute, result building, stream plumbing) is identical.
1452
+ async runGenerate(ctx, startIndex, opts, seed) {
1453
+ this.validateRunOptions(opts);
1454
+ const seeded = await seed();
1455
+ const state = makeRuntimeState(ctx, seeded.output, "generate", opts);
1456
+ if (seeded.resumeDescent) state.resumeDescent = seeded.resumeDescent;
1457
+ await this.execute(state, startIndex, opts, seeded.initialError);
1458
+ return this.buildResult(state);
1459
+ }
1460
+ runStream(ctx, startIndex, opts, options, seed) {
649
1461
  this.validateRunOptions(opts);
650
- const abortSignal = opts?.abortSignal;
651
1462
  let resolveOutput;
652
1463
  let rejectOutput;
653
1464
  const outputPromise = new Promise((res, rej) => {
@@ -658,16 +1469,11 @@ var SealedWorkflow = class _SealedWorkflow {
658
1469
  });
659
1470
  const stream = createUIMessageStream({
660
1471
  execute: async ({ writer }) => {
661
- const state = {
662
- ctx,
663
- output: input,
664
- mode: "stream",
665
- writer,
666
- runOptions: opts,
667
- abortSignal
668
- };
669
1472
  try {
670
- await this.execute(state, 0, opts);
1473
+ const seeded = await seed();
1474
+ const state = makeRuntimeState(ctx, seeded.output, "stream", opts, writer);
1475
+ if (seeded.resumeDescent) state.resumeDescent = seeded.resumeDescent;
1476
+ await this.execute(state, startIndex, opts, seeded.initialError);
671
1477
  const result = this.buildResult(state);
672
1478
  maybeWarnStreamOnErrorOnSuspend(result, options);
673
1479
  resolveOutput(result);
@@ -681,22 +1487,7 @@ var SealedWorkflow = class _SealedWorkflow {
681
1487
  ...options?.originalMessages ? { originalMessages: options.originalMessages } : {},
682
1488
  ...options?.generateId ? { generateId: options.generateId } : {}
683
1489
  });
684
- return {
685
- stream,
686
- output: outputPromise
687
- };
688
- }
689
- // Helper — converts terminal RuntimeState into a WorkflowResult; freezes
690
- // snapshot + warnings if requested via runOptions.
691
- buildResult(state) {
692
- const warnings = state.warnings ?? [];
693
- if (state.suspension && resolveFreezeSnapshots(state)) {
694
- deepFreeze(warnings);
695
- }
696
- if (state.suspension) {
697
- return { status: "suspended", snapshot: state.suspension, warnings };
698
- }
699
- return { status: "complete", output: state.output, warnings };
1490
+ return { stream, output: outputPromise };
700
1491
  }
701
1492
  // ── Internal: execute pipeline ────────────────────────────────
702
1493
  async execute(state, startIndex = 0, opts, initialError = null) {
@@ -706,12 +1497,13 @@ var SealedWorkflow = class _SealedWorkflow {
706
1497
  if (opts !== void 0 && state.runOptions === void 0) {
707
1498
  state.runOptions = opts;
708
1499
  }
709
- const ckptCadence = opts?.onCheckpoint && opts.checkpointWhen === void 0 ? opts.checkpointEvery ?? Math.max(1, Math.ceil(this.cachedExecutableStepCount / 4)) : 0;
710
- let pendingError = initialError;
1500
+ const ckptCadence = opts?.onCheckpoint && opts.checkpointWhen === void 0 ? opts.checkpointEvery ?? Math.max(1, Math.ceil(this.cachedCheckpointableStepCount / 4)) : 0;
1501
+ let executableStepsSeen = 0;
1502
+ state.pendingError = initialError ?? void 0;
711
1503
  let abortPromoted = false;
712
1504
  const makeAbortError = (signal) => ({
713
1505
  error: signal.reason ?? new Error("Workflow aborted"),
714
- stepId: "abort",
1506
+ stepId: ABORT_STEP_ID,
715
1507
  source: "step"
716
1508
  });
717
1509
  for (let i = startIndex; i < this.steps.length; i++) {
@@ -719,152 +1511,61 @@ var SealedWorkflow = class _SealedWorkflow {
719
1511
  if (!abortPromoted) {
720
1512
  abortPromoted = true;
721
1513
  state.suspension = void 0;
722
- if (pendingError) demotePendingError(state, pendingError);
723
- pendingError = makeAbortError(state.abortSignal);
724
- } else if (!pendingError) {
725
- pendingError = makeAbortError(state.abortSignal);
1514
+ if (state.pendingError) demotePendingError(state, state.pendingError);
1515
+ state.pendingError = makeAbortError(state.abortSignal);
1516
+ } else if (!state.pendingError) {
1517
+ state.pendingError = makeAbortError(state.abortSignal);
726
1518
  }
727
1519
  }
728
1520
  const node = this.steps[i];
729
- if (node.type === "finally") {
730
- const stepId2 = node.id;
731
- const finStart = performance.now();
732
- await this.fireHook(state, "onStepStart", { stepId: stepId2, type: "finally", ctx: state.ctx, input: state.output });
733
- try {
734
- await node.execute(state);
735
- await this.fireHook(state, "onStepFinish", {
736
- stepId: stepId2,
737
- type: "finally",
738
- ctx: state.ctx,
739
- output: state.output,
740
- durationMs: performance.now() - finStart,
741
- suspended: false
742
- });
743
- } catch (e) {
744
- await this.fireHook(state, "onStepError", {
745
- stepId: stepId2,
746
- type: "finally",
747
- ctx: state.ctx,
748
- error: e,
749
- durationMs: performance.now() - finStart
750
- });
751
- if (pendingError) demotePendingError(state, pendingError);
752
- pendingError = { error: e, stepId: stepId2, source: "finally" };
753
- }
754
- continue;
755
- }
756
- if (node.type === "catch") {
757
- if (state.suspension || !pendingError || state.checkpointFailed) continue;
758
- const stepId2 = node.id;
759
- const cStart = performance.now();
760
- await this.fireHook(state, "onStepStart", { stepId: stepId2, type: "catch", ctx: state.ctx, input: state.output });
761
- try {
762
- state.output = await node.catchFn({
763
- error: pendingError.error,
764
- ctx: state.ctx,
765
- lastOutput: state.output,
766
- stepId: pendingError.stepId
767
- });
768
- pendingError = null;
769
- await this.fireHook(state, "onStepFinish", {
770
- stepId: stepId2,
771
- type: "catch",
772
- ctx: state.ctx,
773
- output: state.output,
774
- durationMs: performance.now() - cStart,
775
- suspended: false
776
- });
777
- } catch (e) {
778
- await this.fireHook(state, "onStepError", {
779
- stepId: stepId2,
780
- type: "catch",
781
- ctx: state.ctx,
782
- error: e,
783
- durationMs: performance.now() - cStart
784
- });
785
- if (pendingError) demotePendingError(state, pendingError);
786
- pendingError = { error: e, stepId: stepId2, source: "catch" };
787
- }
788
- continue;
789
- }
790
- if (state.suspension || pendingError) continue;
791
- if (node.type === "gate") {
792
- const stepId2 = node.id;
793
- const gStart = performance.now();
794
- await this.fireHook(state, "onStepStart", { stepId: stepId2, type: "gate", ctx: state.ctx, input: state.output });
795
- try {
796
- if (node.condition && !await node.condition(state)) {
797
- await this.fireHook(state, "onStepFinish", {
798
- stepId: stepId2,
799
- type: "gate",
800
- ctx: state.ctx,
801
- output: state.output,
802
- durationMs: performance.now() - gStart,
803
- suspended: false
804
- });
805
- continue;
806
- }
807
- const snapshot = {
808
- version: 2,
809
- kind: "gate",
810
- resumeFromIndex: i,
811
- output: state.output,
812
- gateId: node.id,
813
- gatePayload: await node.payload(state)
814
- };
815
- state.suspension = snapshot;
816
- if (resolveFreezeSnapshots(state)) deepFreeze(snapshot);
817
- await this.fireHook(state, "onStepFinish", {
818
- stepId: stepId2,
819
- type: "gate",
820
- ctx: state.ctx,
821
- output: state.output,
822
- durationMs: performance.now() - gStart,
823
- suspended: true
824
- });
825
- } catch (e) {
826
- pendingError = { error: e, stepId: node.id, source: "step" };
827
- }
828
- continue;
829
- }
1521
+ const skip = node.type === "finally" ? false : node.type === "catch" ? !!state.suspension || !state.pendingError || !!state.checkpointFailed : !!state.suspension || !!state.pendingError;
1522
+ if (skip) continue;
830
1523
  const obsType = getObservabilityType(node);
831
1524
  const stepId = node.id;
832
1525
  const sStart = performance.now();
833
- const stepInput = state.output;
834
- await this.fireHook(state, "onStepStart", { stepId, type: obsType, ctx: state.ctx, input: stepInput });
1526
+ const errBefore = state.pendingError;
1527
+ const suspendedBefore = !!state.suspension;
1528
+ state.stepIndex = i;
1529
+ await this.fireHook(state, "onStepStart", { stepId, type: obsType, ctx: state.ctx, input: state.output });
835
1530
  try {
836
1531
  await node.execute(state);
837
- await this.fireHook(state, "onStepFinish", {
1532
+ } catch (e) {
1533
+ await this.fireStepErrorAndAttachCause(state, {
838
1534
  stepId,
839
1535
  type: obsType,
840
1536
  ctx: state.ctx,
841
- output: state.output,
842
- durationMs: performance.now() - sStart,
843
- suspended: false
1537
+ error: e,
1538
+ durationMs: performance.now() - sStart
844
1539
  });
845
- } catch (e) {
846
- pendingError = { error: e, stepId: node.id, source: "step" };
847
- const obsError = await this.fireHook(state, "onStepError", {
1540
+ throw e;
1541
+ }
1542
+ const newError = state.pendingError && state.pendingError !== errBefore ? state.pendingError : null;
1543
+ if (newError) {
1544
+ await this.fireStepErrorAndAttachCause(state, {
848
1545
  stepId,
849
1546
  type: obsType,
850
1547
  ctx: state.ctx,
851
- error: e,
1548
+ error: newError.error,
852
1549
  durationMs: performance.now() - sStart
853
1550
  });
854
- if (obsError !== void 0 && typeof e === "object" && e !== null) {
855
- try {
856
- e.cause = obsError;
857
- } catch {
858
- }
859
- }
1551
+ } else {
1552
+ await this.fireHook(state, "onStepFinish", {
1553
+ stepId,
1554
+ type: obsType,
1555
+ ctx: state.ctx,
1556
+ output: state.output,
1557
+ durationMs: performance.now() - sStart,
1558
+ suspended: !suspendedBefore && !!state.suspension
1559
+ });
860
1560
  }
861
- const leaked = state.suspension;
862
- if (leaked) {
1561
+ if (node.type === "step" && node.category !== "nested" && state.suspension) {
1562
+ const leaked = state.suspension;
863
1563
  state.suspension = void 0;
864
1564
  throw new Error(`internal: suspension bubbled from non-gate step "${node.id}" (gate "${leaked.gateId}").`);
865
1565
  }
866
- if (!pendingError && !state.suspension && opts?.onCheckpoint) {
867
- const shouldCheckpoint = opts.checkpointWhen ? opts.checkpointWhen({ stepIndex: i, stepId: node.id, ctx: state.ctx }) : (i + 1) % ckptCadence === 0;
1566
+ if (node.type === "step" && !state.pendingError && !state.suspension && opts?.onCheckpoint) {
1567
+ executableStepsSeen++;
1568
+ const shouldCheckpoint = opts.checkpointWhen ? opts.checkpointWhen({ stepIndex: i, stepId: node.id, ctx: state.ctx }) : executableStepsSeen % ckptCadence === 0;
868
1569
  if (shouldCheckpoint) {
869
1570
  const ckptStart = performance.now();
870
1571
  try {
@@ -875,127 +1576,76 @@ var SealedWorkflow = class _SealedWorkflow {
875
1576
  this.cachedStepShapeHash
876
1577
  );
877
1578
  } catch (e) {
878
- pendingError = { error: e, stepId: CHECKPOINT_STEP_ID, source: "onCheckpoint" };
1579
+ state.pendingError = { error: e, stepId: CHECKPOINT_STEP_ID, source: "onCheckpoint" };
879
1580
  state.checkpointFailed = true;
880
- await this.fireHook(state, "onStepError", {
1581
+ await this.fireStepErrorAndAttachCause(state, {
881
1582
  stepId: CHECKPOINT_STEP_ID,
882
1583
  type: "step",
883
1584
  ctx: state.ctx,
884
1585
  error: e,
885
1586
  durationMs: performance.now() - ckptStart
886
1587
  });
887
- }
888
- }
889
- }
890
- }
891
- if (pendingError && !state.suspension) {
892
- if (state.checkpointFailed) {
893
- const warningsArr = state.warnings ?? [];
894
- const checkpointError = pendingError.source === "onCheckpoint" ? pendingError.error : warningsArr.find((w) => w.source === "onCheckpoint")?.error;
895
- const finallyErrors = warningsArr.filter((w) => w.source === "finally").map((w) => w.error);
896
- const all = pendingError.source === "finally" ? [...finallyErrors, pendingError.error] : finallyErrors;
897
- if (all.length > 0) {
898
- console.warn(
899
- `pipeai: ${all.length} .finally() error(s) suppressed by checkpoint-failure precedence:`,
900
- all
901
- );
902
- }
903
- throw checkpointError ?? pendingError.error;
904
- }
905
- const isFinallyPath = pendingError.source === "finally" || (state.warnings?.some((w) => w.source === "finally") ?? false);
906
- if (isFinallyPath) {
907
- const all = [...(state.warnings ?? []).map((w) => w.error), pendingError.error];
908
- throw new AggregateError(all, `Workflow failed with ${all.length} error(s) from .finally() bodies`);
909
- }
910
- throw pendingError.error;
911
- } else if (pendingError && state.suspension) {
912
- demotePendingError(state, pendingError);
913
- try {
914
- await this.observability?.onStepError?.({
915
- stepId: pendingError.stepId,
916
- type: pendingErrorSourceToStepType(pendingError.source),
917
- ctx: state.ctx,
918
- error: pendingError.error,
919
- durationMs: 0
920
- });
921
- } catch (obsError) {
922
- pushWarning(state, "onStepError", pendingError.stepId, obsError);
923
- }
924
- pendingError = null;
925
- }
926
- }
927
- // ── Internal: execute a nested workflow within a step/loop ─────
928
- // Defined on SealedWorkflow (not Workflow) because TypeScript's protected
929
- // access rules only allow calling workflow.execute() from the same class.
930
- //
931
- // Contract: clears any inner suspension before re-throwing as
932
- // NestedGateUnsupportedError. The outer execute() therefore never observes
933
- // a leaked `state.suspension` from non-gate nodes (defensive invariant).
934
- async executeNestedWorkflow(state, workflow) {
935
- const savedRunOptions = state.runOptions;
936
- state.runOptions = void 0;
937
- try {
938
- await workflow.execute(state);
939
- } finally {
940
- state.runOptions = savedRunOptions;
941
- }
942
- if (state.suspension) {
943
- const gateId = state.suspension.gateId;
944
- state.suspension = void 0;
945
- throw new NestedGateUnsupportedError(gateId, workflow.id);
946
- }
947
- }
948
- // ── Internal: execute an agent within a step/branch ───────────
949
- // In stream mode, output extraction awaits the full stream before returning.
950
- // Streaming benefits the client (incremental output), not pipeline throughput —
951
- // each step still runs sequentially.
952
- async executeAgent(state, agent, ctx, options) {
953
- const input = state.output;
954
- const hasStructuredOutput = agent.hasOutput;
955
- const abortSignal = state.abortSignal;
956
- const agentCallOpts = abortSignal ? { abortSignal } : void 0;
957
- if (state.mode === "stream" && state.writer) {
958
- const writer = state.writer;
959
- await runWithWriter(writer, async () => {
960
- const result = await agent.stream(ctx, state.output, agentCallOpts);
961
- if (options?.handleStream) {
962
- await options.handleStream({ result, writer, ctx, input });
963
- } else {
964
- writer.merge(result.toUIMessageStream());
965
- }
966
- const hookParams = {
967
- mode: "stream",
968
- result,
969
- ctx,
970
- input
971
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
972
- };
973
- if (options?.onResult) {
974
- await options.onResult(hookParams);
975
- }
976
- if (options?.mapResult) {
977
- state.output = await options.mapResult(hookParams);
978
- } else {
979
- state.output = await extractOutput(result, hasStructuredOutput, agent.validateOutput);
980
- }
981
- });
982
- } else {
983
- const result = await agent.generate(ctx, state.output, agentCallOpts);
984
- const hookParams = {
985
- mode: "generate",
986
- result,
987
- ctx,
988
- input
989
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
990
- };
991
- if (options?.onResult) {
992
- await options.onResult(hookParams);
1588
+ }
1589
+ }
993
1590
  }
994
- if (options?.mapResult) {
995
- state.output = await options.mapResult(hookParams);
996
- } else {
997
- state.output = await extractOutput(result, hasStructuredOutput, agent.validateOutput);
1591
+ }
1592
+ if (abortPromoted && !state.pendingError && !state.suspension && state.abortSignal?.aborted) {
1593
+ state.pendingError = makeAbortError(state.abortSignal);
1594
+ }
1595
+ if (state.pendingError && !state.suspension) {
1596
+ const pe = state.pendingError;
1597
+ if (state.checkpointFailed) {
1598
+ const warningsArr = state.warnings ?? [];
1599
+ const checkpointError = pe.source === "onCheckpoint" ? pe.error : warningsArr.find((w) => w.source === "onCheckpoint")?.error;
1600
+ const suppressed = warningsArr.filter((w) => w.error !== checkpointError).map((w) => w.error);
1601
+ if (pe.source !== "onCheckpoint" && pe.error !== checkpointError) {
1602
+ suppressed.push(pe.error);
1603
+ }
1604
+ if (suppressed.length > 0) {
1605
+ console.warn(
1606
+ `pipeai: ${suppressed.length} error(s) suppressed by checkpoint-failure precedence:`,
1607
+ suppressed
1608
+ );
1609
+ }
1610
+ throw checkpointError ?? pe.error;
1611
+ }
1612
+ throw pe.error;
1613
+ } else if (state.pendingError && state.suspension) {
1614
+ const pe = state.pendingError;
1615
+ demotePendingError(state, pe);
1616
+ try {
1617
+ await this.observability?.onStepError?.({
1618
+ stepId: pe.stepId,
1619
+ type: pendingErrorSourceToStepType(pe.source),
1620
+ ctx: state.ctx,
1621
+ error: pe.error,
1622
+ durationMs: 0
1623
+ });
1624
+ } catch (obsError) {
1625
+ pushWarning(state, "onStepError", pe.stepId, obsError);
998
1626
  }
1627
+ state.pendingError = void 0;
1628
+ }
1629
+ }
1630
+ /**
1631
+ * Run THIS sealed workflow as a nested step on the caller's run `state`.
1632
+ * Public (internal; not re-exported from index) so `Step` subclasses —
1633
+ * `nested` / `repeat` / `foreach` / `parallel` with `SealedWorkflow` targets
1634
+ * — can run a sub-workflow without reaching the protected `execute`.
1635
+ *
1636
+ * Contract: RunOptions is run-scoped, so the child never inherits the
1637
+ * parent's (`state.warnings` IS propagated — telemetry > config). A gate
1638
+ * inside the child leaves `state.suspension` set so it propagates up (only a
1639
+ * `.step(workflow)` ever does this — concurrent/looped combinators forbid
1640
+ * gated targets at build time).
1641
+ */
1642
+ async executeAsNested(state, startIndex = 0) {
1643
+ const savedRunOptions = state.runOptions;
1644
+ state.runOptions = void 0;
1645
+ try {
1646
+ await this.execute(state, startIndex);
1647
+ } finally {
1648
+ state.runOptions = savedRunOptions;
999
1649
  }
1000
1650
  }
1001
1651
  // ── Gate: load persisted state for resumption ──────────────────
@@ -1010,6 +1660,32 @@ var SealedWorkflow = class _SealedWorkflow {
1010
1660
  );
1011
1661
  }
1012
1662
  this.ensureDuplicateCheck();
1663
+ const nestedPath = gateLike.nestedPath;
1664
+ if (nestedPath && nestedPath.length > 0) {
1665
+ let steps = this.steps;
1666
+ for (const idx of nestedPath) {
1667
+ const node = steps[idx];
1668
+ const child = node?.type === "step" ? node.nestedWorkflow : void 0;
1669
+ if (!child) {
1670
+ throw new Error(`loadState: nested gate "${gateId}" path is stale \u2014 step ${idx} is not a nested workflow.`);
1671
+ }
1672
+ steps = child.getStepsForShapeHash();
1673
+ }
1674
+ const innerGate = steps[gateLike.resumeFromIndex];
1675
+ if (innerGate?.type !== "gate" || innerGate.id !== gateId) {
1676
+ throw new Error(`loadState: nested gate "${gateId}" not found at the recorded path.`);
1677
+ }
1678
+ const remaining = [...nestedPath.slice(1), gateLike.resumeFromIndex + 1];
1679
+ return new ResumedWorkflow(this.steps, nestedPath[0], {
1680
+ mode: "gate",
1681
+ schema: innerGate.schema,
1682
+ mergeFn: innerGate.merge,
1683
+ priorOutput: gateLike.output,
1684
+ snapshot: gateLike,
1685
+ observability: this.observability,
1686
+ nestedRemaining: remaining
1687
+ });
1688
+ }
1013
1689
  const gateIndex = this.findGateIndex(gateLike);
1014
1690
  const gateNode = this.steps[gateIndex];
1015
1691
  return new ResumedWorkflow(this.steps, gateIndex + 1, {
@@ -1069,16 +1745,12 @@ var SealedWorkflow = class _SealedWorkflow {
1069
1745
  /**
1070
1746
  * Append a `.finally()` body to a sealed workflow, returning another sealed
1071
1747
  * workflow. Allows multi-finally chains (`.finally().finally()`). A throwing
1072
- * `.finally` body does NOT abort subsequent ones they all run.
1748
+ * `.finally` body bubbles straight out of the run: it is non-recoverable, does
1749
+ * NOT aggregate with a prior error, and subsequent `.finally()` bodies do not
1750
+ * run. (See {@link FinallyStep} for the full contract.)
1073
1751
  */
1074
1752
  finally(id, fn) {
1075
- const node = {
1076
- type: "finally",
1077
- id,
1078
- execute: async (state) => {
1079
- await fn({ ctx: state.ctx });
1080
- }
1081
- };
1753
+ const node = new FinallyStep(id, fn);
1082
1754
  return new _SealedWorkflow([...this.steps, node], this.id, this.observability);
1083
1755
  }
1084
1756
  findGateIndex(snapshot) {
@@ -1108,6 +1780,7 @@ var ResumedWorkflow = class extends SealedWorkflow {
1108
1780
  schema;
1109
1781
  mergeFn;
1110
1782
  priorOutput;
1783
+ nestedRemaining;
1111
1784
  /** @internal */
1112
1785
  constructor(steps, startIndex, config) {
1113
1786
  super(steps, void 0, config.observability);
@@ -1115,6 +1788,7 @@ var ResumedWorkflow = class extends SealedWorkflow {
1115
1788
  this.schema = config.schema;
1116
1789
  this.mergeFn = config.mergeFn;
1117
1790
  this.priorOutput = config.priorOutput;
1791
+ this.nestedRemaining = config.nestedRemaining;
1118
1792
  }
1119
1793
  validateResponse(response) {
1120
1794
  if (this.schema) {
@@ -1122,77 +1796,39 @@ var ResumedWorkflow = class extends SealedWorkflow {
1122
1796
  }
1123
1797
  return response;
1124
1798
  }
1125
- async generate(ctx, ...args) {
1126
- const rawResponse = args[0];
1127
- const opts = args[1];
1128
- let output = this.priorOutput;
1129
- let initialError = null;
1799
+ /**
1800
+ * Seed the run by validating the gate response and merging it with the
1801
+ * suspended output. Runs schema.parse + mergeFn inside a try so a failure
1802
+ * becomes a pre-execute `initialError` (routed through `.catch()`) rather
1803
+ * than escaping the run synchronously. On error the output falls back to the
1804
+ * prior (pre-gate) output.
1805
+ */
1806
+ async seedFromResponse(rawResponse) {
1130
1807
  try {
1131
1808
  const response = this.validateResponse(rawResponse);
1132
- output = this.mergeFn ? await this.mergeFn({ priorOutput: this.priorOutput, response }) : response;
1809
+ const merged = this.mergeFn ? await this.mergeFn({ priorOutput: this.priorOutput, response }) : response;
1810
+ if (this.nestedRemaining) {
1811
+ return {
1812
+ output: this.priorOutput,
1813
+ initialError: null,
1814
+ resumeDescent: { remaining: this.nestedRemaining, seedOutput: merged }
1815
+ };
1816
+ }
1817
+ return { output: merged, initialError: null };
1133
1818
  } catch (error) {
1134
- initialError = { error, stepId: "gate:resume", source: "step" };
1135
- }
1136
- const state = {
1137
- ctx,
1138
- output,
1139
- mode: "generate",
1140
- runOptions: opts,
1141
- abortSignal: opts?.abortSignal
1142
- };
1143
- await this.execute(state, this.startIndex, opts, initialError);
1144
- return this.buildResult(state);
1819
+ return { output: this.priorOutput, initialError: { error, stepId: GATE_RESUME_STEP_ID, source: "step" } };
1820
+ }
1821
+ }
1822
+ async generate(ctx, ...args) {
1823
+ const rawResponse = args[0];
1824
+ const opts = args[1];
1825
+ return this.runGenerate(ctx, this.startIndex, opts, () => this.seedFromResponse(rawResponse));
1145
1826
  }
1146
1827
  stream(ctx, ...args) {
1147
1828
  const rawResponse = args[0];
1148
1829
  const options = args[1];
1149
1830
  const opts = args[2];
1150
- const abortSignal = opts?.abortSignal;
1151
- let resolveOutput;
1152
- let rejectOutput;
1153
- const outputPromise = new Promise((res, rej) => {
1154
- resolveOutput = res;
1155
- rejectOutput = rej;
1156
- });
1157
- outputPromise.catch(() => {
1158
- });
1159
- const mergeFn = this.mergeFn;
1160
- const priorOutput = this.priorOutput;
1161
- const startIndex = this.startIndex;
1162
- const stream = createUIMessageStream({
1163
- execute: async ({ writer }) => {
1164
- let output = priorOutput;
1165
- let initialError = null;
1166
- try {
1167
- const response = this.validateResponse(rawResponse);
1168
- output = mergeFn ? await mergeFn({ priorOutput, response }) : response;
1169
- } catch (error) {
1170
- initialError = { error, stepId: "gate:resume", source: "step" };
1171
- }
1172
- const state = {
1173
- ctx,
1174
- output,
1175
- mode: "stream",
1176
- writer,
1177
- runOptions: opts,
1178
- abortSignal
1179
- };
1180
- try {
1181
- await this.execute(state, startIndex, opts, initialError);
1182
- const result = this.buildResult(state);
1183
- maybeWarnStreamOnErrorOnSuspend(result, options);
1184
- resolveOutput(result);
1185
- } catch (error) {
1186
- rejectOutput(error);
1187
- throw error;
1188
- }
1189
- },
1190
- ...options?.onError ? { onError: options.onError } : {},
1191
- ...options?.onFinish ? { onFinish: options.onFinish } : {},
1192
- ...options?.originalMessages ? { originalMessages: options.originalMessages } : {},
1193
- ...options?.generateId ? { generateId: options.generateId } : {}
1194
- });
1195
- return { stream, output: outputPromise };
1831
+ return this.runStream(ctx, this.startIndex, opts, options, () => this.seedFromResponse(rawResponse));
1196
1832
  }
1197
1833
  };
1198
1834
  var CheckpointResumedWorkflow = class extends SealedWorkflow {
@@ -1208,55 +1844,12 @@ var CheckpointResumedWorkflow = class extends SealedWorkflow {
1208
1844
  // Inputs are ignored — state is seeded from the snapshot's `output` field.
1209
1845
  async generate(ctx, ...args) {
1210
1846
  const opts = args[1];
1211
- this.validateRunOptions(opts);
1212
- const state = {
1213
- ctx,
1214
- output: this.priorOutput,
1215
- mode: "generate",
1216
- runOptions: opts
1217
- };
1218
- await this.execute(state, this.startIndex, opts);
1219
- return this.buildResult(state);
1847
+ return this.runGenerate(ctx, this.startIndex, opts, () => ({ output: this.priorOutput, initialError: null }));
1220
1848
  }
1221
1849
  stream(ctx, ...args) {
1222
1850
  const options = args[1];
1223
1851
  const opts = args[2];
1224
- this.validateRunOptions(opts);
1225
- let resolveOutput;
1226
- let rejectOutput;
1227
- const outputPromise = new Promise((res, rej) => {
1228
- resolveOutput = res;
1229
- rejectOutput = rej;
1230
- });
1231
- outputPromise.catch(() => {
1232
- });
1233
- const priorOutput = this.priorOutput;
1234
- const startIndex = this.startIndex;
1235
- const stream = createUIMessageStream({
1236
- execute: async ({ writer }) => {
1237
- const state = {
1238
- ctx,
1239
- output: priorOutput,
1240
- mode: "stream",
1241
- writer,
1242
- runOptions: opts
1243
- };
1244
- try {
1245
- await this.execute(state, startIndex, opts);
1246
- const result = this.buildResult(state);
1247
- maybeWarnStreamOnErrorOnSuspend(result, options);
1248
- resolveOutput(result);
1249
- } catch (error) {
1250
- rejectOutput(error);
1251
- throw error;
1252
- }
1253
- },
1254
- ...options?.onError ? { onError: options.onError } : {},
1255
- ...options?.onFinish ? { onFinish: options.onFinish } : {},
1256
- ...options?.originalMessages ? { originalMessages: options.originalMessages } : {},
1257
- ...options?.generateId ? { generateId: options.generateId } : {}
1258
- });
1259
- return { stream, output: outputPromise };
1852
+ return this.runStream(ctx, this.startIndex, opts, options, () => ({ output: this.priorOutput, initialError: null }));
1260
1853
  }
1261
1854
  };
1262
1855
  var Workflow = class _Workflow extends SealedWorkflow {
@@ -1282,20 +1875,16 @@ var Workflow = class _Workflow extends SealedWorkflow {
1282
1875
  return new _Workflow([...this.steps, node], this.id, this.observability);
1283
1876
  }
1284
1877
  // ── step: implementation ──────────────────────────────────────
1285
- step(target, optionsOrFn) {
1878
+ step(target, optionsOrFn, inlineOptions) {
1286
1879
  if (target instanceof SealedWorkflow) {
1287
1880
  const workflow = target;
1288
- const node2 = {
1289
- type: "step",
1290
- id: workflow.id ?? "nested-workflow",
1291
- nestedWorkflow: workflow,
1292
- // Feeds the recursive stepShapeHash walk.
1293
- category: "nested",
1294
- // Observability event type.
1295
- execute: async (state) => {
1296
- await this.executeNestedWorkflow(state, workflow);
1297
- }
1298
- };
1881
+ const options2 = optionsOrFn;
1882
+ const node2 = new NestedWorkflowStep(
1883
+ options2?.id ?? workflow.id ?? "nested-workflow",
1884
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1885
+ workflow,
1886
+ options2
1887
+ );
1299
1888
  return this.appendStep(node2);
1300
1889
  }
1301
1890
  if (typeof target === "string") {
@@ -1303,31 +1892,22 @@ var Workflow = class _Workflow extends SealedWorkflow {
1303
1892
  throw new Error(`Workflow step("${target}"): second argument must be a function`);
1304
1893
  }
1305
1894
  const fn = optionsOrFn;
1306
- const node2 = {
1307
- type: "step",
1308
- id: target,
1309
- execute: async (state) => {
1310
- state.output = await fn({
1311
- ctx: state.ctx,
1312
- input: state.output,
1313
- // Present in stream mode (undefined in generate mode), letting the
1314
- // inline step emit UIMessageChunk parts onto the workflow's stream.
1315
- writer: state.writer
1316
- });
1317
- }
1318
- };
1895
+ const node2 = new TransformStep(
1896
+ target,
1897
+ fn,
1898
+ inlineOptions
1899
+ );
1319
1900
  return this.appendStep(node2);
1320
1901
  }
1321
1902
  const agent = target;
1322
1903
  const options = optionsOrFn;
1323
- const node = {
1324
- type: "step",
1325
- id: options?.id ?? agent.id,
1326
- execute: async (state) => {
1327
- const ctx = state.ctx;
1328
- await this.executeAgent(state, agent, ctx, options);
1329
- }
1330
- };
1904
+ const node = new AgentStep(
1905
+ options?.id ?? agent.id,
1906
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1907
+ agent,
1908
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1909
+ options
1910
+ );
1331
1911
  return this.appendStep(node);
1332
1912
  }
1333
1913
  // ── gate: human-in-the-loop suspension point ────────────────
@@ -1335,16 +1915,9 @@ var Workflow = class _Workflow extends SealedWorkflow {
1335
1915
  if (this.steps.some((s) => s.type === "gate" && s.id === id)) {
1336
1916
  throw new Error(`Workflow: duplicate gate ID "${id}". Each gate must have a unique identifier.`);
1337
1917
  }
1338
- const node = {
1339
- type: "gate",
1918
+ const node = new GateStep(
1340
1919
  id,
1341
- schema: options?.schema,
1342
- condition: options?.condition ? async (state) => options.condition({
1343
- ctx: state.ctx,
1344
- input: state.output
1345
- }) : void 0,
1346
- merge: options?.merge ? (params) => options.merge(params) : void 0,
1347
- payload: async (state) => {
1920
+ async (state) => {
1348
1921
  if (options?.payload) {
1349
1922
  return options.payload({
1350
1923
  ctx: state.ctx,
@@ -1352,8 +1925,14 @@ var Workflow = class _Workflow extends SealedWorkflow {
1352
1925
  });
1353
1926
  }
1354
1927
  return state.output;
1355
- }
1356
- };
1928
+ },
1929
+ options?.schema,
1930
+ options?.condition ? async (state) => options.condition({
1931
+ ctx: state.ctx,
1932
+ input: state.output
1933
+ }) : void 0,
1934
+ options?.merge ? (params) => options.merge(params) : void 0
1935
+ );
1357
1936
  return this.appendStep(node);
1358
1937
  }
1359
1938
  // ── branch: implementation ────────────────────────────────────
@@ -1364,67 +1943,11 @@ var Workflow = class _Workflow extends SealedWorkflow {
1364
1943
  return this.branchSelect(casesOrConfig, options?.id);
1365
1944
  }
1366
1945
  branchPredicate(cases, explicitId) {
1367
- const node = {
1368
- type: "step",
1369
- id: explicitId ?? "branch:predicate",
1370
- category: "branch",
1371
- execute: async (state) => {
1372
- const ctx = state.ctx;
1373
- const input = state.output;
1374
- for (const branchCase of cases) {
1375
- if (branchCase.when) {
1376
- const match = await branchCase.when({ ctx, input });
1377
- if (!match) continue;
1378
- }
1379
- await this.executeAgent(state, branchCase.agent, ctx, branchCase);
1380
- return;
1381
- }
1382
- let inputRepr;
1383
- try {
1384
- inputRepr = JSON.stringify(input);
1385
- if (inputRepr === void 0) inputRepr = String(input);
1386
- } catch {
1387
- inputRepr = `[unserializable ${typeof input}]`;
1388
- }
1389
- throw new WorkflowBranchError("predicate", `No branch matched and no default branch (a case without \`when\`) was provided. Input: ${inputRepr}`);
1390
- }
1391
- };
1946
+ const node = new PredicateBranchStep(explicitId ?? "branch:predicate", cases);
1392
1947
  return this.appendStep(node);
1393
1948
  }
1394
1949
  branchSelect(config, explicitId) {
1395
- const node = {
1396
- type: "step",
1397
- id: explicitId ?? "branch:select",
1398
- category: "branch",
1399
- execute: async (state) => {
1400
- const ctx = state.ctx;
1401
- const input = state.output;
1402
- const key = await config.select({ ctx, input });
1403
- const keyDeclared = Object.prototype.hasOwnProperty.call(config.agents, key);
1404
- if (keyDeclared && config.agents[key] === void 0) {
1405
- throw new WorkflowBranchError(
1406
- "select",
1407
- `Agent for key "${key}" was declared but the value is undefined. This usually means a conditional spread set the value to undefined. Available keys: ${Object.keys(config.agents).join(", ")}`
1408
- );
1409
- }
1410
- let agent = keyDeclared ? config.agents[key] : void 0;
1411
- if (!agent) {
1412
- if (config.onUnknownKey) {
1413
- config.onUnknownKey({
1414
- key,
1415
- availableKeys: Object.keys(config.agents),
1416
- ctx
1417
- });
1418
- }
1419
- if (config.fallback) {
1420
- agent = config.fallback;
1421
- } else {
1422
- throw new WorkflowBranchError("select", `No agent found for key "${key}" and no fallback provided. Available keys: ${Object.keys(config.agents).join(", ")}`);
1423
- }
1424
- }
1425
- await this.executeAgent(state, agent, ctx, config);
1426
- }
1427
- };
1950
+ const node = new SelectBranchStep(explicitId ?? "branch:select", config);
1428
1951
  return this.appendStep(node);
1429
1952
  }
1430
1953
  // ── foreach: array iteration ─────────────────────────────────
@@ -1435,367 +1958,63 @@ var Workflow = class _Workflow extends SealedWorkflow {
1435
1958
  * @param options.id Override the default step id (`foreach:<agentId>` or
1436
1959
  * the workflow's id). Required when chaining multiple foreach over the same
1437
1960
  * target — the construction-time `(type, id)` walk rejects duplicates.
1438
- * @param options.concurrency Max items in flight at any moment (default 1).
1439
- * Backed by a semaphore: as soon as one item completes, the next launches —
1961
+ * @param options.concurrency Max items in flight at any moment. **Default:
1962
+ * unbounded** (`Infinity` every item runs concurrently, clamped only by
1963
+ * item count). Pass an integer to throttle against provider rate limits.
1964
+ * Backed by a worker pool: as soon as one item completes, the next launches —
1440
1965
  * no lockstep batching.
1441
1966
  * @param options.onError Per-iteration error handler. **Bypassed entirely on
1442
- * the suspension path** (when any item hits a nested gate) see the
1967
+ * the suspension path** (when any item hits a nested gate) **and on the
1968
+ * cancellation path** (the run was aborted — pre-abort failures become
1969
+ * `foreach-sibling` warnings and the abort reason rethrows) — see the
1443
1970
  * foreach concurrency hazards in the README. Otherwise: return a
1444
1971
  * `TNextOutput` value to substitute, return `Workflow.SKIP` to omit, throw
1445
1972
  * to abort. Invoked sequentially in index order after all items settle.
1973
+ * A throw (or rethrow) from `onError` aborts the foreach immediately:
1974
+ * failures at indices AFTER the throwing one are neither recovered nor
1975
+ * surfaced as warnings.
1446
1976
  */
1447
1977
  foreach(target, options) {
1448
- const concurrency = options?.concurrency ?? 1;
1449
- const onError = options?.onError;
1450
- const isWorkflow = target instanceof SealedWorkflow;
1451
- const defaultId = isWorkflow ? target.id ?? "foreach" : `foreach:${target.id}`;
1452
- const id = options?.id ?? defaultId;
1453
- const node = {
1454
- type: "step",
1455
- id,
1456
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1457
- nestedWorkflow: isWorkflow ? target : void 0,
1458
- category: "foreach",
1459
- execute: async (state) => {
1460
- const items = state.output;
1461
- if (!Array.isArray(items)) {
1462
- throw new Error(`foreach "${id}": expected array input, got ${typeof items}`);
1463
- }
1464
- const ctx = state.ctx;
1465
- const results = new Array(items.length);
1466
- const skipped = /* @__PURE__ */ new Set();
1467
- const itemStates = new Array(items.length);
1468
- const executeItem = async (item, index) => {
1469
- const itemState = {
1470
- ctx: state.ctx,
1471
- output: item,
1472
- mode: "generate",
1473
- abortSignal: state.abortSignal
1474
- };
1475
- itemStates[index] = itemState;
1476
- const itemStart = performance.now();
1477
- await this.fireHook(state, "onItemStart", {
1478
- stepId: id,
1479
- type: "foreach",
1480
- itemIndex: index,
1481
- ctx: state.ctx,
1482
- input: item
1483
- });
1484
- try {
1485
- if (isWorkflow) {
1486
- await this.executeNestedWorkflow(itemState, target);
1487
- } else {
1488
- await this.executeAgent(itemState, target, ctx);
1489
- }
1490
- results[index] = itemState.output;
1491
- await this.fireHook(state, "onItemFinish", {
1492
- stepId: id,
1493
- type: "foreach",
1494
- itemIndex: index,
1495
- ctx: state.ctx,
1496
- output: itemState.output,
1497
- durationMs: performance.now() - itemStart
1498
- });
1499
- } catch (error) {
1500
- await this.fireHook(state, "onItemError", {
1501
- stepId: id,
1502
- type: "foreach",
1503
- itemIndex: index,
1504
- ctx: state.ctx,
1505
- error,
1506
- durationMs: performance.now() - itemStart
1507
- });
1508
- throw error;
1509
- }
1510
- };
1511
- const mergeItemWarnings = () => {
1512
- for (let idx = 0; idx < items.length; idx++) {
1513
- const its = itemStates[idx];
1514
- if (!its?.warnings) continue;
1515
- for (const w of its.warnings) {
1516
- pushWarning(state, w.source, `${id}[${idx}]:${w.stepId}`, w.error);
1517
- }
1518
- }
1519
- };
1520
- const handleRejection = async (error, item, index) => {
1521
- if (!onError) throw error;
1522
- const recovered = await onError({
1523
- error,
1524
- item,
1525
- index,
1526
- ctx: state.ctx
1527
- });
1528
- if (recovered === _Workflow.SKIP) {
1529
- skipped.add(index);
1530
- } else {
1531
- results[index] = recovered;
1532
- }
1533
- };
1534
- const failures = [];
1535
- const signal = state.abortSignal;
1536
- if (concurrency <= 1) {
1537
- for (let i = 0; i < items.length; i++) {
1538
- if (signal?.aborted) {
1539
- failures.push({ index: i, error: signal.reason ?? new Error("Workflow aborted") });
1540
- continue;
1541
- }
1542
- try {
1543
- await executeItem(items[i], i);
1544
- } catch (error) {
1545
- failures.push({ index: i, error });
1546
- }
1547
- }
1548
- } else {
1549
- let nextIndex = 0;
1550
- const worker = async () => {
1551
- while (true) {
1552
- const i = nextIndex++;
1553
- if (i >= items.length) return;
1554
- if (signal?.aborted) {
1555
- failures.push({ index: i, error: signal.reason ?? new Error("Workflow aborted") });
1556
- continue;
1557
- }
1558
- try {
1559
- await executeItem(items[i], i);
1560
- } catch (error) {
1561
- failures.push({ index: i, error });
1562
- }
1563
- }
1564
- };
1565
- const workers = Array.from(
1566
- { length: Math.min(concurrency, items.length) },
1567
- () => worker()
1568
- );
1569
- await Promise.all(workers);
1570
- }
1571
- failures.sort((a, b) => a.index - b.index);
1572
- const gateFailures = [];
1573
- const nonGateFailures = [];
1574
- for (const f of failures) {
1575
- if (f.error instanceof NestedGateUnsupportedError) {
1576
- gateFailures.push({ index: f.index, error: f.error });
1577
- } else {
1578
- nonGateFailures.push(f);
1579
- }
1580
- }
1581
- mergeItemWarnings();
1582
- if (gateFailures.length > 0) {
1583
- for (const nr of nonGateFailures) {
1584
- pushWarning(state, "foreach-sibling", `${id}[${nr.index}]`, nr.error);
1585
- }
1586
- const lowest = gateFailures[0];
1587
- const otherSuspensions = gateFailures.slice(1).map((g) => ({
1588
- index: g.index,
1589
- gateId: g.error.gateId
1590
- }));
1591
- const siblingErrors = nonGateFailures.map((nr) => nr.error);
1592
- throw new NestedGateUnsupportedError(
1593
- lowest.error.gateId,
1594
- lowest.error.workflowId,
1595
- siblingErrors,
1596
- otherSuspensions
1597
- );
1598
- }
1599
- for (const { index, error } of nonGateFailures) {
1600
- await handleRejection(error, items[index], index);
1601
- }
1602
- state.output = skipped.size === 0 ? results : results.filter((_, i) => !skipped.has(i));
1603
- }
1604
- };
1978
+ const node = new ForeachStep(target, options, this.observability);
1605
1979
  return this.appendStep(node);
1606
1980
  }
1607
1981
  // Implementation
1608
1982
  parallel(branches, options) {
1609
- const isTuple = Array.isArray(branches);
1610
- const entries = isTuple ? branches.map((target, i) => ({ key: i, index: i, target })) : Object.entries(branches).map(([k, t], i) => ({ key: k, index: i, target: t }));
1611
- const branchCount = entries.length;
1612
- const requestedConcurrency = options?.concurrency;
1613
- let effectiveConcurrency;
1614
- if (requestedConcurrency === void 0) {
1615
- effectiveConcurrency = Math.min(branchCount, 5);
1616
- } else {
1617
- effectiveConcurrency = requestedConcurrency;
1618
- }
1619
- if (requestedConcurrency === void 0 && branchCount > 5) {
1620
- warnOnce(
1621
- "pipeai:parallel-cap",
1622
- `pipeai: parallel() with ${branchCount} branches capped at concurrency 5 by default. Pass { concurrency: ${branchCount} } (or Infinity) to opt in, or set { concurrency: N } if you want fewer.`
1623
- );
1624
- }
1625
- const onError = options?.onError;
1626
- const id = options?.id ?? (isTuple ? "parallel:tuple" : "parallel:record");
1627
- const node = {
1628
- type: "step",
1629
- id,
1630
- category: "parallel",
1631
- execute: async (state) => {
1632
- const ctx = state.ctx;
1633
- const input = state.output;
1634
- const results = isTuple ? new Array(branchCount) : {};
1635
- const branchStates = new Array(branchCount);
1636
- const executeBranch = async ({ key, index, target }) => {
1637
- const branchState = { ctx: state.ctx, output: input, mode: "generate" };
1638
- branchStates[index] = branchState;
1639
- const branchStart = performance.now();
1640
- const itemIndex = isTuple ? index : key;
1641
- await this.fireHook(state, "onItemStart", {
1642
- stepId: id,
1643
- type: "parallel",
1644
- itemIndex,
1645
- ctx: state.ctx,
1646
- input
1647
- });
1648
- try {
1649
- if (target instanceof SealedWorkflow) {
1650
- await this.executeNestedWorkflow(branchState, target);
1651
- } else {
1652
- await this.executeAgent(branchState, target, ctx);
1653
- }
1654
- results[key] = branchState.output;
1655
- await this.fireHook(state, "onItemFinish", {
1656
- stepId: id,
1657
- type: "parallel",
1658
- itemIndex,
1659
- ctx: state.ctx,
1660
- output: branchState.output,
1661
- durationMs: performance.now() - branchStart
1662
- });
1663
- } catch (error) {
1664
- await this.fireHook(state, "onItemError", {
1665
- stepId: id,
1666
- type: "parallel",
1667
- itemIndex,
1668
- ctx: state.ctx,
1669
- error,
1670
- durationMs: performance.now() - branchStart
1671
- });
1672
- throw error;
1673
- }
1674
- };
1675
- const failures = [];
1676
- const eff = Number.isFinite(effectiveConcurrency) ? Math.max(1, effectiveConcurrency) : branchCount;
1677
- if (eff <= 1) {
1678
- for (const e of entries) {
1679
- try {
1680
- await executeBranch(e);
1681
- } catch (error) {
1682
- failures.push({ key: e.key, index: e.index, error });
1683
- }
1684
- }
1685
- } else {
1686
- let nextIndex = 0;
1687
- const worker = async () => {
1688
- while (true) {
1689
- const i = nextIndex++;
1690
- if (i >= branchCount) return;
1691
- const e = entries[i];
1692
- try {
1693
- await executeBranch(e);
1694
- } catch (error) {
1695
- failures.push({ key: e.key, index: e.index, error });
1696
- }
1697
- }
1698
- };
1699
- const workers = Array.from(
1700
- { length: Math.min(eff, branchCount) },
1701
- () => worker()
1702
- );
1703
- await Promise.all(workers);
1704
- }
1705
- for (let idx = 0; idx < branchCount; idx++) {
1706
- const bs = branchStates[idx];
1707
- if (!bs?.warnings) continue;
1708
- for (const w of bs.warnings) {
1709
- pushWarning(state, w.source, `${id}[${entries[idx].key}]:${w.stepId}`, w.error);
1710
- }
1711
- }
1712
- const gateFailures = [];
1713
- const nonGateFailures = [];
1714
- for (const f of failures) {
1715
- if (f.error instanceof NestedGateUnsupportedError) gateFailures.push({ key: f.key, index: f.index, error: f.error });
1716
- else nonGateFailures.push(f);
1717
- }
1718
- gateFailures.sort((a, b) => a.index - b.index);
1719
- nonGateFailures.sort((a, b) => a.index - b.index);
1720
- if (gateFailures.length > 0) {
1721
- for (const nr of nonGateFailures) {
1722
- pushWarning(state, "foreach-sibling", `${id}[${nr.key}]`, nr.error);
1723
- }
1724
- const lowest = gateFailures[0];
1725
- const otherSuspensions = gateFailures.slice(1).map((g) => ({ index: g.index, gateId: g.error.gateId }));
1726
- const siblingErrors = nonGateFailures.map((nr) => nr.error);
1727
- throw new NestedGateUnsupportedError(
1728
- lowest.error.gateId,
1729
- lowest.error.workflowId,
1730
- siblingErrors,
1731
- otherSuspensions
1732
- );
1733
- }
1734
- for (const { key, index, error } of nonGateFailures) {
1735
- if (!onError) throw error;
1736
- const recovered = await onError({
1737
- error,
1738
- key: isTuple ? void 0 : key,
1739
- index: isTuple ? index : void 0,
1740
- ctx: state.ctx
1741
- });
1742
- if (recovered === _Workflow.SKIP) {
1743
- results[key] = void 0;
1744
- } else {
1745
- results[key] = recovered;
1746
- }
1747
- }
1748
- state.output = results;
1749
- }
1750
- };
1983
+ const node = new ParallelStep(branches, options, this.observability);
1751
1984
  return this.appendStep(node);
1752
1985
  }
1753
1986
  // ── repeat: conditional loop ─────────────────────────────────
1754
1987
  repeat(target, options) {
1988
+ if (options.maxIterations !== void 0 && (!Number.isInteger(options.maxIterations) || options.maxIterations < 1)) {
1989
+ throw new Error(`repeat: maxIterations must be a positive integer, got ${options.maxIterations}`);
1990
+ }
1991
+ if (options.until === void 0 === (options.while === void 0)) {
1992
+ throw new Error("repeat: requires exactly one of `until` or `while`");
1993
+ }
1755
1994
  const maxIterations = options.maxIterations ?? 10;
1756
1995
  const isWorkflow = target instanceof SealedWorkflow;
1757
1996
  const defaultId = isWorkflow ? target.id ?? "repeat" : `repeat:${target.id}`;
1758
1997
  const id = options.id ?? defaultId;
1759
1998
  const predicate = options.until ?? (async (p) => !await options.while(p));
1760
- const node = {
1761
- type: "step",
1999
+ const node = new RepeatStep(
1762
2000
  id,
1763
2001
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1764
- nestedWorkflow: isWorkflow ? target : void 0,
1765
- category: "repeat",
1766
- execute: async (state) => {
1767
- const ctx = state.ctx;
1768
- for (let i = 1; i <= maxIterations; i++) {
1769
- if (state.abortSignal?.aborted) {
1770
- throw state.abortSignal.reason ?? new Error("Workflow aborted");
1771
- }
1772
- if (isWorkflow) {
1773
- await this.executeNestedWorkflow(state, target);
1774
- } else {
1775
- await this.executeAgent(state, target, ctx);
1776
- }
1777
- const done = await predicate({
1778
- output: state.output,
1779
- ctx,
1780
- iterations: i
1781
- });
1782
- if (done) return;
1783
- }
1784
- throw new WorkflowLoopError(maxIterations, maxIterations);
1785
- }
1786
- };
2002
+ target,
2003
+ predicate,
2004
+ maxIterations,
2005
+ isWorkflow
2006
+ );
1787
2007
  return this.appendStep(node);
1788
2008
  }
1789
2009
  // ── catch ─────────────────────────────────────────────────────
1790
2010
  catch(id, fn) {
1791
- if (!this.steps.some((s) => s.type === "step")) {
1792
- throw new Error(`Workflow: catch("${id}") requires at least one preceding step.`);
2011
+ if (!this.steps.some((s) => s.type === "step" || s.type === "gate")) {
2012
+ throw new Error(`Workflow: catch("${id}") requires at least one preceding step or gate.`);
1793
2013
  }
1794
- const node = {
1795
- type: "catch",
2014
+ const node = new CatchStep(
1796
2015
  id,
1797
- catchFn: fn
1798
- };
2016
+ fn
2017
+ );
1799
2018
  return this.appendStep(node);
1800
2019
  }
1801
2020
  // `.finally()` is inherited from SealedWorkflow now (it lives there so
@@ -1805,10 +2024,10 @@ var Workflow = class _Workflow extends SealedWorkflow {
1805
2024
  // src/index.ts
1806
2025
  var SKIP = Workflow.SKIP;
1807
2026
  export {
2027
+ ABORT_STEP_ID,
1808
2028
  Agent,
1809
2029
  CHECKPOINT_STEP_ID,
1810
- CheckpointTimeoutError,
1811
- NestedGateUnsupportedError,
2030
+ GATE_RESUME_STEP_ID,
1812
2031
  SKIP,
1813
2032
  TOOL_PROVIDER_BRAND,
1814
2033
  ToolProvider,
@@ -1816,7 +2035,6 @@ export {
1816
2035
  WorkflowBranchError,
1817
2036
  WorkflowLoopError,
1818
2037
  defineTool,
1819
- getActiveWriter,
1820
2038
  isToolProvider,
1821
2039
  migrateSnapshot
1822
2040
  };