@bastani/atomic 0.9.0-alpha.1 → 0.9.0-alpha.2

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 (211) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +6 -0
  3. package/dist/builtin/cursor/package.json +2 -2
  4. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  5. package/dist/builtin/intercom/package.json +2 -2
  6. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  7. package/dist/builtin/mcp/package.json +3 -3
  8. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  9. package/dist/builtin/subagents/package.json +4 -4
  10. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  11. package/dist/builtin/web-access/package.json +2 -2
  12. package/dist/builtin/workflows/CHANGELOG.md +12 -0
  13. package/dist/builtin/workflows/README.md +189 -122
  14. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +30 -27
  15. package/dist/builtin/workflows/builtin/goal-runner.ts +10 -17
  16. package/dist/builtin/workflows/builtin/goal.ts +39 -44
  17. package/dist/builtin/workflows/builtin/index.d.ts +1 -0
  18. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +16 -17
  19. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +1 -0
  20. package/dist/builtin/workflows/builtin/open-claude-design.ts +42 -50
  21. package/dist/builtin/workflows/builtin/ralph.ts +44 -41
  22. package/dist/builtin/workflows/package.json +2 -2
  23. package/dist/builtin/workflows/src/authoring/typebox-defaults.d.ts +41 -0
  24. package/dist/builtin/workflows/src/authoring/typebox-defaults.ts +217 -0
  25. package/dist/builtin/workflows/src/authoring/workflow.ts +184 -0
  26. package/dist/builtin/workflows/src/authoring.d.ts +14 -66
  27. package/dist/builtin/workflows/src/engine/graph-inference.ts +100 -0
  28. package/dist/builtin/workflows/src/engine/options.ts +40 -0
  29. package/dist/builtin/workflows/src/engine/primitives/chain.ts +29 -0
  30. package/dist/builtin/workflows/src/engine/primitives/exit.ts +2 -0
  31. package/dist/builtin/workflows/src/engine/primitives/parallel.ts +47 -0
  32. package/dist/builtin/workflows/src/engine/primitives/task.ts +108 -0
  33. package/dist/builtin/workflows/src/engine/primitives/ui.ts +41 -0
  34. package/dist/builtin/workflows/src/engine/primitives/workflow.ts +159 -0
  35. package/dist/builtin/workflows/src/engine/replay.ts +8 -0
  36. package/dist/builtin/workflows/src/engine/run.ts +356 -0
  37. package/dist/builtin/workflows/src/engine/runtime.ts +160 -0
  38. package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +9 -3
  39. package/dist/builtin/workflows/src/extension/workflow-schema.ts +0 -18
  40. package/dist/builtin/workflows/src/index.ts +0 -2
  41. package/dist/builtin/workflows/src/runs/background/runner.ts +6 -3
  42. package/dist/builtin/workflows/src/runs/foreground/executor-child-boundary.ts +3 -3
  43. package/dist/builtin/workflows/src/runs/foreground/executor-child-helpers.ts +4 -4
  44. package/dist/builtin/workflows/src/runs/foreground/executor-child-workflow.ts +1 -158
  45. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +1 -1
  46. package/dist/builtin/workflows/src/runs/foreground/executor-outputs.ts +2 -2
  47. package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +1 -1
  48. package/dist/builtin/workflows/src/runs/foreground/executor-run.ts +1 -359
  49. package/dist/builtin/workflows/src/runs/foreground/executor-scheduler.ts +1 -1
  50. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +2 -5
  51. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +12 -4
  52. package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +4 -3
  53. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +9 -2
  54. package/dist/builtin/workflows/src/runs/foreground/executor-task-context.ts +2 -132
  55. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +2 -2
  56. package/dist/builtin/workflows/src/runs/shared/graph-inference.ts +2 -100
  57. package/dist/builtin/workflows/src/sdk-surface.ts +6 -9
  58. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +9 -3
  59. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +17 -3
  60. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +3 -33
  61. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +9 -81
  62. package/dist/builtin/workflows/src/shared/types.ts +25 -8
  63. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +49 -0
  64. package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +84 -0
  65. package/dist/builtin/workflows/src/workflows/registry.ts +7 -3
  66. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  67. package/dist/core/agent-session-auto-compaction.js +6 -1
  68. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  69. package/dist/core/agent-session-bash.d.ts.map +1 -1
  70. package/dist/core/agent-session-bash.js +0 -5
  71. package/dist/core/agent-session-bash.js.map +1 -1
  72. package/dist/core/agent-session-methods.d.ts +0 -2
  73. package/dist/core/agent-session-methods.d.ts.map +1 -1
  74. package/dist/core/agent-session-methods.js.map +1 -1
  75. package/dist/core/agent-session-services.d.ts +0 -1
  76. package/dist/core/agent-session-services.d.ts.map +1 -1
  77. package/dist/core/agent-session-services.js +0 -1
  78. package/dist/core/agent-session-services.js.map +1 -1
  79. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  80. package/dist/core/agent-session-tool-registry.js +0 -2
  81. package/dist/core/agent-session-tool-registry.js.map +1 -1
  82. package/dist/core/agent-session-types.d.ts +0 -2
  83. package/dist/core/agent-session-types.d.ts.map +1 -1
  84. package/dist/core/agent-session-types.js.map +1 -1
  85. package/dist/core/agent-session.d.ts +0 -2
  86. package/dist/core/agent-session.d.ts.map +1 -1
  87. package/dist/core/agent-session.js +0 -1
  88. package/dist/core/agent-session.js.map +1 -1
  89. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  90. package/dist/core/atomic-guide-command.js +1 -1
  91. package/dist/core/atomic-guide-command.js.map +1 -1
  92. package/dist/core/extensions/loader-core.d.ts +1 -3
  93. package/dist/core/extensions/loader-core.d.ts.map +1 -1
  94. package/dist/core/extensions/loader-core.js +13 -6
  95. package/dist/core/extensions/loader-core.js.map +1 -1
  96. package/dist/core/extensions/loader-virtual-modules.d.ts +7 -1
  97. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  98. package/dist/core/extensions/loader-virtual-modules.js +34 -2
  99. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  100. package/dist/core/extensions/loader.d.ts +2 -1
  101. package/dist/core/extensions/loader.d.ts.map +1 -1
  102. package/dist/core/extensions/loader.js +2 -1
  103. package/dist/core/extensions/loader.js.map +1 -1
  104. package/dist/core/index.d.ts +0 -1
  105. package/dist/core/index.d.ts.map +1 -1
  106. package/dist/core/index.js +0 -1
  107. package/dist/core/index.js.map +1 -1
  108. package/dist/core/model-registry-builtins.d.ts.map +1 -1
  109. package/dist/core/model-registry-builtins.js +6 -0
  110. package/dist/core/model-registry-builtins.js.map +1 -1
  111. package/dist/core/model-registry-schemas.d.ts +65 -13
  112. package/dist/core/model-registry-schemas.d.ts.map +1 -1
  113. package/dist/core/model-registry-schemas.js +10 -0
  114. package/dist/core/model-registry-schemas.js.map +1 -1
  115. package/dist/core/resource-loader-core.d.ts +1 -0
  116. package/dist/core/resource-loader-core.d.ts.map +1 -1
  117. package/dist/core/resource-loader-core.js +2 -0
  118. package/dist/core/resource-loader-core.js.map +1 -1
  119. package/dist/core/resource-loader-extensions.d.ts.map +1 -1
  120. package/dist/core/resource-loader-extensions.js +3 -3
  121. package/dist/core/resource-loader-extensions.js.map +1 -1
  122. package/dist/core/resource-loader-internals.d.ts +1 -0
  123. package/dist/core/resource-loader-internals.d.ts.map +1 -1
  124. package/dist/core/resource-loader-internals.js.map +1 -1
  125. package/dist/core/resource-loader-reload.d.ts.map +1 -1
  126. package/dist/core/resource-loader-reload.js +6 -2
  127. package/dist/core/resource-loader-reload.js.map +1 -1
  128. package/dist/core/sdk-exports.d.ts +1 -1
  129. package/dist/core/sdk-exports.d.ts.map +1 -1
  130. package/dist/core/sdk-exports.js.map +1 -1
  131. package/dist/core/sdk-types.d.ts +0 -3
  132. package/dist/core/sdk-types.d.ts.map +1 -1
  133. package/dist/core/sdk-types.js.map +1 -1
  134. package/dist/core/sdk.d.ts.map +1 -1
  135. package/dist/core/sdk.js +0 -1
  136. package/dist/core/sdk.js.map +1 -1
  137. package/dist/core/session-manager-history.d.ts.map +1 -1
  138. package/dist/core/session-manager-history.js +2 -1
  139. package/dist/core/session-manager-history.js.map +1 -1
  140. package/dist/core/tools/bash.d.ts +0 -5
  141. package/dist/core/tools/bash.d.ts.map +1 -1
  142. package/dist/core/tools/bash.js +10 -11
  143. package/dist/core/tools/bash.js.map +1 -1
  144. package/dist/core/tools/edit-diff-preserve.d.ts +18 -0
  145. package/dist/core/tools/edit-diff-preserve.d.ts.map +1 -0
  146. package/dist/core/tools/edit-diff-preserve.js +85 -0
  147. package/dist/core/tools/edit-diff-preserve.js.map +1 -0
  148. package/dist/core/tools/edit-diff.d.ts +3 -2
  149. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  150. package/dist/core/tools/edit-diff.js +15 -18
  151. package/dist/core/tools/edit-diff.js.map +1 -1
  152. package/dist/core/tools/index.d.ts +0 -1
  153. package/dist/core/tools/index.d.ts.map +1 -1
  154. package/dist/core/tools/index.js +0 -1
  155. package/dist/core/tools/index.js.map +1 -1
  156. package/dist/index.d.ts +2 -2
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +1 -1
  159. package/dist/index.js.map +1 -1
  160. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/model-selector.js +2 -2
  162. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  163. package/dist/modes/interactive/model-search.d.ts +5 -0
  164. package/dist/modes/interactive/model-search.d.ts.map +1 -1
  165. package/dist/modes/interactive/model-search.js +9 -0
  166. package/dist/modes/interactive/model-search.js.map +1 -1
  167. package/dist/utils/shell.d.ts +1 -0
  168. package/dist/utils/shell.d.ts.map +1 -1
  169. package/dist/utils/shell.js +12 -5
  170. package/dist/utils/shell.js.map +1 -1
  171. package/docs/custom-provider.md +4 -3
  172. package/docs/models.md +3 -2
  173. package/docs/packages.md +2 -2
  174. package/docs/quickstart.md +1 -1
  175. package/docs/sdk.md +2 -40
  176. package/docs/security.md +1 -1
  177. package/docs/workflows.md +238 -173
  178. package/package.json +5 -5
  179. package/dist/builtin/workflows/src/workflows/define-workflow.ts +0 -277
  180. package/dist/core/tools/bash-policy-compile.d.ts +0 -5
  181. package/dist/core/tools/bash-policy-compile.d.ts.map +0 -1
  182. package/dist/core/tools/bash-policy-compile.js +0 -241
  183. package/dist/core/tools/bash-policy-compile.js.map +0 -1
  184. package/dist/core/tools/bash-policy-evaluate.d.ts +0 -3
  185. package/dist/core/tools/bash-policy-evaluate.d.ts.map +0 -1
  186. package/dist/core/tools/bash-policy-evaluate.js +0 -92
  187. package/dist/core/tools/bash-policy-evaluate.js.map +0 -1
  188. package/dist/core/tools/bash-policy-format.d.ts +0 -5
  189. package/dist/core/tools/bash-policy-format.d.ts.map +0 -1
  190. package/dist/core/tools/bash-policy-format.js +0 -49
  191. package/dist/core/tools/bash-policy-format.js.map +0 -1
  192. package/dist/core/tools/bash-policy-parser.d.ts +0 -4
  193. package/dist/core/tools/bash-policy-parser.d.ts.map +0 -1
  194. package/dist/core/tools/bash-policy-parser.js +0 -155
  195. package/dist/core/tools/bash-policy-parser.js.map +0 -1
  196. package/dist/core/tools/bash-policy-segment.d.ts +0 -3
  197. package/dist/core/tools/bash-policy-segment.d.ts.map +0 -1
  198. package/dist/core/tools/bash-policy-segment.js +0 -275
  199. package/dist/core/tools/bash-policy-segment.js.map +0 -1
  200. package/dist/core/tools/bash-policy-shell.d.ts +0 -11
  201. package/dist/core/tools/bash-policy-shell.d.ts.map +0 -1
  202. package/dist/core/tools/bash-policy-shell.js +0 -267
  203. package/dist/core/tools/bash-policy-shell.js.map +0 -1
  204. package/dist/core/tools/bash-policy-types.d.ts +0 -146
  205. package/dist/core/tools/bash-policy-types.d.ts.map +0 -1
  206. package/dist/core/tools/bash-policy-types.js +0 -2
  207. package/dist/core/tools/bash-policy-types.js.map +0 -1
  208. package/dist/core/tools/bash-policy.d.ts +0 -6
  209. package/dist/core/tools/bash-policy.d.ts.map +0 -1
  210. package/dist/core/tools/bash-policy.js +0 -5
  211. package/dist/core/tools/bash-policy.js.map +0 -1
@@ -1,158 +1 @@
1
- import type {
2
- WorkflowChildResult,
3
- WorkflowDefinition,
4
- WorkflowInputValues,
5
- WorkflowOutputValues,
6
- WorkflowRunChildOptions,
7
- } from "../../shared/types.js";
8
- import type { WorkflowChildRunRef } from "../../shared/store-types.js";
9
- import type { RunOpts, RunResult } from "./executor-types.js";
10
- import type { WorkflowExitManager } from "./executor-exit-manager.js";
11
- import type { WorkflowBoundaryStage } from "./executor-child-boundary.js";
12
- import { findWorkflowExitSignal, isWorkflowExitStatus, makeParentWorkflowExitAbortReason } from "./executor-abort.js";
13
- import { selectWorkflowOutputs } from "./executor-outputs.js";
14
- import { resolveAndValidateInputs } from "./executor-inputs.js";
15
- import {
16
- isWorkflowDefinition,
17
- workflowChildReplaySnapshot,
18
- workflowDefinitionRequirementMessage,
19
- } from "./executor-child-helpers.js";
20
-
21
- export function createChildWorkflowRunner(input: {
22
- readonly runId: string;
23
- readonly depth: number;
24
- readonly opts: RunOpts;
25
- readonly exit: WorkflowExitManager;
26
- readonly ownController: AbortController;
27
- readonly resolveWorkflowCwd: () => string;
28
- readonly nextWorkflowBoundaryReplayKey: (name: string) => string;
29
- readonly startWorkflowBoundaryStage: (name: string, replayKey: string) => WorkflowBoundaryStage;
30
- readonly runWorkflow: <TInputs extends WorkflowInputValues>(
31
- def: WorkflowDefinition<TInputs>,
32
- inputs: Readonly<Record<string, unknown>>,
33
- opts?: RunOpts,
34
- ) => Promise<RunResult>;
35
- }): <TChildInputs extends WorkflowInputValues, TChildOutputs extends WorkflowOutputValues>(
36
- child: WorkflowDefinition<TChildInputs, TChildOutputs>,
37
- options?: WorkflowRunChildOptions<TChildInputs>,
38
- ) => Promise<WorkflowChildResult<TChildOutputs>> {
39
- return async <TChildInputs extends WorkflowInputValues, TChildOutputs extends WorkflowOutputValues>(
40
- child: WorkflowDefinition<TChildInputs, TChildOutputs>,
41
- options: WorkflowRunChildOptions<TChildInputs> = {},
42
- ): Promise<WorkflowChildResult<TChildOutputs>> => {
43
- input.exit.throwIfWorkflowExitSelected();
44
- if (!isWorkflowDefinition(child)) throw new Error(workflowDefinitionRequirementMessage("ctx.workflow(definition)", child));
45
- const childName = child.normalizedName;
46
- const boundaryName = options.stageName ?? `workflow:${childName}`;
47
- const boundaryReplayKey = input.nextWorkflowBoundaryReplayKey(boundaryName);
48
- const boundary = input.startWorkflowBoundaryStage(boundaryName, boundaryReplayKey);
49
- let childRunId: string | undefined;
50
- let detachParentAbort: (() => void) | undefined;
51
- try {
52
- if (boundary.replayedChild !== undefined) {
53
- await Promise.resolve();
54
- input.exit.throwIfWorkflowExitSelected();
55
- boundary.finalizeReplay();
56
- return boundary.replayedChild as WorkflowChildResult<TChildOutputs>;
57
- }
58
-
59
- const childInputs = resolveAndValidateInputs(child.inputs, options.inputs ?? {}, `child workflow "${childName}" (${child.name})`);
60
- input.exit.throwIfWorkflowExitSelected();
61
-
62
- childRunId = crypto.randomUUID();
63
- const childController = new AbortController();
64
- const childRef: WorkflowChildRunRef = { alias: childName, workflow: child.normalizedName, runId: childRunId };
65
- boundary.linkChildRun(childRef, childController);
66
-
67
- const abortChildFromParent = (): void => {
68
- const parentExit = findWorkflowExitSignal(input.ownController.signal.reason, input.exit.exitScope);
69
- childController.abort(parentExit !== undefined ? makeParentWorkflowExitAbortReason(parentExit.reason) : input.ownController.signal.reason);
70
- };
71
- if (input.ownController.signal.aborted) abortChildFromParent();
72
- else {
73
- input.ownController.signal.addEventListener("abort", abortChildFromParent, { once: true });
74
- detachParentAbort = () => input.ownController.signal.removeEventListener("abort", abortChildFromParent);
75
- }
76
- input.exit.throwIfWorkflowExitSelected();
77
- input.opts.cancellation?.register(childRunId, childController);
78
- input.exit.throwIfWorkflowExitSelected();
79
-
80
- const {
81
- runId: _parentRunId,
82
- continuation: _parentContinuation,
83
- deferWorkflowStart: _parentDeferWorkflowStart,
84
- parentRun: _parentRun,
85
- onRunStart: _parentOnRunStart,
86
- onRunEnd: _parentOnRunEnd,
87
- ...childBaseOpts
88
- } = input.opts;
89
- const childRunPromise = input.runWorkflow(child, childInputs, {
90
- ...childBaseOpts,
91
- runId: childRunId,
92
- cwd: input.resolveWorkflowCwd(),
93
- depth: input.depth + 1,
94
- ...(input.opts.registry !== undefined ? { registry: input.opts.registry } : {}),
95
- parentRun: {
96
- runId: input.runId,
97
- stageId: boundary.id,
98
- rootRunId: input.opts.parentRun?.rootRunId ?? input.runId,
99
- },
100
- signal: childController.signal,
101
- deferWorkflowStart: false,
102
- });
103
- boundary.observeChildRun(childRunPromise);
104
- const childRun = await childRunPromise;
105
- input.exit.throwIfWorkflowExitSelected();
106
-
107
- if (!isWorkflowExitStatus(childRun.status)) {
108
- const failedChildStage = childRun.stages.find((stage) => stage.failureKind !== undefined);
109
- throw new Error(
110
- `atomic-workflows: child workflow "${childName}" (${child.name}) failed with status ${childRun.status}${childRun.error !== undefined ? `: ${childRun.error}` : ""}`,
111
- {
112
- cause: {
113
- ...(failedChildStage?.failureKind !== undefined ? { code: failedChildStage.failureKind } : {}),
114
- ...(failedChildStage?.failureMessage !== undefined ? { message: failedChildStage.failureMessage } : {}),
115
- },
116
- },
117
- );
118
- }
119
-
120
- const outputs = selectWorkflowOutputs(child, childRun.result);
121
- const childExited = childRun.exited === true || childRun.status !== "completed";
122
- const childResult: WorkflowChildResult<TChildOutputs> = childExited
123
- ? {
124
- workflow: child.normalizedName,
125
- runId: childRun.runId,
126
- status: childRun.status,
127
- exited: true,
128
- outputs: outputs as Partial<TChildOutputs>,
129
- ...(childRun.exitReason !== undefined ? { exitReason: childRun.exitReason } : {}),
130
- }
131
- : {
132
- workflow: child.normalizedName,
133
- runId: childRun.runId,
134
- status: "completed",
135
- exited: false,
136
- outputs: outputs as TChildOutputs,
137
- };
138
- const workflowChild = workflowChildReplaySnapshot(childName, childResult);
139
- const outputKeys = Object.keys(outputs);
140
- boundary.complete(
141
- `Workflow "${child.name}" ${childRun.status} (runId: ${childRun.runId}; outputs: ${outputKeys.length > 0 ? outputKeys.join(", ") : "(none)"})`,
142
- workflowChild,
143
- );
144
- return childResult;
145
- } catch (err) {
146
- const exit = findWorkflowExitSignal(err, input.exit.exitScope) ?? findWorkflowExitSignal(input.ownController.signal.reason, input.exit.exitScope);
147
- if (exit !== undefined) {
148
- await boundary.skipForWorkflowExit(exit.reason);
149
- throw exit;
150
- }
151
- boundary.fail(err);
152
- throw err;
153
- } finally {
154
- detachParentAbort?.();
155
- if (childRunId !== undefined) input.opts.cancellation?.unregister(childRunId);
156
- }
157
- };
158
- }
1
+ export { createChildWorkflowRunner } from "../../engine/primitives/workflow.js";
@@ -15,7 +15,7 @@ import type {
15
15
  WorkflowTaskResult,
16
16
  WorkflowTaskStep,
17
17
  } from "../../shared/types.js";
18
- import { stampWorkflowDefinition } from "../../workflows/define-workflow.js";
18
+ import { stampWorkflowDefinition } from "../../authoring/workflow.js";
19
19
  import { classifyWorkflowFailure } from "../../shared/workflow-failures.js";
20
20
  import { buildModelCandidatesFromCatalog, validateWorkflowModels, workflowModelId } from "../shared/model-fallback.js";
21
21
  import {
@@ -31,7 +31,7 @@ function assertWorkflowOutputsExplicit(
31
31
  for (const key of Object.keys(sourceOutput)) {
32
32
  if (!hasOwnWorkflowOutput(declarations, key)) {
33
33
  throw new Error(
34
- `atomic-workflows: ${scope} returned undeclared output "${key}"; declare it with .output("${key}", Type....) or remove it from the .run() return`,
34
+ `atomic-workflows: ${scope} returned undeclared output "${key}"; declare it in outputs: { "${key}": Type.... } or remove it from the .run() return`,
35
35
  );
36
36
  }
37
37
  }
@@ -119,7 +119,7 @@ export function assertWorkflowExitOutputs(
119
119
  for (const key of Object.keys(sourceOutput)) {
120
120
  if (!hasOwnWorkflowOutput(declarations, key)) {
121
121
  throw new Error(
122
- `atomic-workflows: ${scope} provided undeclared output "${key}"; declare it with .output("${key}", Type....) or remove it from ctx.exit({ outputs })`,
122
+ `atomic-workflows: ${scope} provided undeclared output "${key}"; declare it in outputs: { "${key}": Type.... } or remove it from ctx.exit({ outputs })`,
123
123
  );
124
124
  }
125
125
  }
@@ -5,7 +5,7 @@ import { stageUiBroker } from "../../shared/stage-ui-broker.js";
5
5
  import { appendStageEnd, appendStageStart } from "../../shared/persistence-session-entries.js";
6
6
  import { elapsedStageMs } from "../../shared/timing.js";
7
7
  import type { WorkflowFailure } from "../../shared/workflow-failures.js";
8
- import type { GraphFrontierTracker } from "../shared/graph-inference.js";
8
+ import type { GraphFrontierTracker } from "../../engine/graph-inference.js";
9
9
  import type { RunOpts, WorkflowExitCleanup } from "./executor-types.js";
10
10
  import type { ContinuationReplayIndex } from "./executor-continuation.js";
11
11
  import { getPromptAnswerState, sameStringSet } from "./executor-continuation.js";
@@ -1,359 +1 @@
1
- import type { RunSnapshot } from "../../shared/store-types.js";
2
- import type {
3
- WorkflowDefinition,
4
- WorkflowInputValues,
5
- WorkflowCustomUiFactory,
6
- WorkflowCustomUiOptions,
7
- WorkflowRunContext,
8
- WorkflowUIContext,
9
- } from "../../shared/types.js";
10
- import type { WorkflowFailure } from "../../shared/workflow-failures.js";
11
- import { classifyWorkflowFailure } from "../../shared/workflow-failures.js";
12
- import { store as defaultStore } from "../../shared/store.js";
13
- import { appendRunStart } from "../../shared/persistence-session-entries.js";
14
- import { GraphFrontierTracker } from "../shared/graph-inference.js";
15
- import { createRunLimiter } from "../shared/concurrency.js";
16
- import { stageControlRegistry as defaultStageControlRegistry } from "./stage-control-registry.js";
17
- import type { RunOpts, RunResult } from "./executor-types.js";
18
- import { unknownErrorMessage, findWorkflowExitSignal, parentWorkflowExitAbortReason } from "./executor-abort.js";
19
- import { createWorkflowExitManager } from "./executor-exit-manager.js";
20
- import { resolveAndValidateInputs, resolveInputConcurrency, resolveInputRuntimeDefaults } from "./executor-inputs.js";
21
- import { workflowCwdWithInputWorktree } from "./executor-direct-helpers.js";
22
- import { createStageScheduler } from "./executor-scheduler.js";
23
- import { createRunFinalizers } from "./executor-run-finalizers.js";
24
- import { createWorkflowBoundaryFactory } from "./executor-child-boundary.js";
25
- import { createWorkflowStageFactory } from "./executor-stage-factory.js";
26
- import { createWorkflowTaskRunners } from "./executor-task-context.js";
27
- import { createChildWorkflowRunner } from "./executor-child-workflow.js";
28
- import { createContinuationReplayIndex } from "./executor-continuation.js";
29
- import { buildPromptNodeUiAdapter } from "./executor-prompt-nodes.js";
30
- import { makeHeadlessUnavailableUIContext, normalizeUIContext } from "./executor-hil.js";
31
- import {
32
- appendRunEndWhenRecorded,
33
- assertWorkflowCreatedStage,
34
- finalizeKilled,
35
- finalizeKilledByFailure,
36
- recordActiveBlockedFailure,
37
- reconcileTerminalRunResult,
38
- selectRunFailureDisposition,
39
- } from "./executor-lifecycle.js";
40
- import { assertWorkflowRunOutputs, normalizeWorkflowRunOutput } from "./executor-outputs.js";
41
- import { isWorkflowDefinition, workflowDefinitionRequirementMessage } from "./executor-child-helpers.js";
42
-
43
- function nextEventLoopTurn(): Promise<void> {
44
- return new Promise((resolve) => setTimeout(resolve, 0));
45
- }
46
-
47
- function buildExitGatedUiContext(input: {
48
- readonly opts: RunOpts;
49
- readonly baseFromPromptNodes: () => WorkflowUIContext;
50
- readonly throwIfWorkflowExitSelected: () => void;
51
- }): WorkflowUIContext {
52
- const base = input.opts.usePromptNodesForUi === true
53
- ? input.baseFromPromptNodes()
54
- : input.opts.executionMode === "non_interactive" && input.opts.ui === undefined
55
- ? makeHeadlessUnavailableUIContext()
56
- : normalizeUIContext(input.opts.ui);
57
- return {
58
- async input(promptText: string): Promise<string> {
59
- input.throwIfWorkflowExitSelected();
60
- return await base.input(promptText);
61
- },
62
- async confirm(message: string): Promise<boolean> {
63
- input.throwIfWorkflowExitSelected();
64
- return await base.confirm(message);
65
- },
66
- async select<T extends string>(message: string, options: readonly T[]): Promise<T> {
67
- input.throwIfWorkflowExitSelected();
68
- return await base.select(message, options);
69
- },
70
- async editor(initial?: string): Promise<string> {
71
- input.throwIfWorkflowExitSelected();
72
- return await base.editor(initial);
73
- },
74
- async custom<T>(factory: WorkflowCustomUiFactory<T>, options?: WorkflowCustomUiOptions): Promise<T> {
75
- input.throwIfWorkflowExitSelected();
76
- return await base.custom(factory, options);
77
- },
78
- };
79
- }
80
-
81
- export async function run<TInputs extends WorkflowInputValues>(
82
- def: WorkflowDefinition<TInputs>,
83
- inputs: Readonly<Record<string, unknown>>,
84
- opts: RunOpts = {},
85
- ): Promise<RunResult> {
86
- if (!isWorkflowDefinition(def)) throw new Error(workflowDefinitionRequirementMessage("run(definition, inputs)", def));
87
-
88
- const activeStore = opts.store ?? defaultStore;
89
- const adapters = opts.adapters ?? {};
90
- if (opts.usePromptNodesForUi === true && opts.ui !== undefined) {
91
- console.warn("atomic-workflows: usePromptNodesForUi ignores the provided RunOpts.ui adapter");
92
- }
93
-
94
- const depth = opts.depth ?? 0;
95
- const maxDepth = opts.config?.maxDepth ?? 4;
96
- if (depth >= maxDepth) {
97
- return {
98
- runId: opts.runId ?? crypto.randomUUID(),
99
- status: "failed",
100
- error: `atomic-workflows: maxDepth exceeded (max ${maxDepth})`,
101
- stages: [],
102
- };
103
- }
104
-
105
- const resolvedInputs = resolveAndValidateInputs(def.inputs, inputs, `workflow "${def.name}"`);
106
- const runId = opts.runId ?? crypto.randomUUID();
107
- const exitScope = Symbol(`workflow-exit:${runId}`);
108
- const ownController = new AbortController();
109
- const callerSignal = opts.signal;
110
- if (callerSignal) {
111
- if (callerSignal.aborted) ownController.abort(callerSignal.reason);
112
- else callerSignal.addEventListener("abort", () => { ownController.abort(callerSignal.reason); }, { once: true });
113
- }
114
- const exit = createWorkflowExitManager({ runId, exitScope, controller: ownController });
115
-
116
- const runSnapshot: RunSnapshot = {
117
- id: runId,
118
- name: def.name,
119
- inputs: Object.freeze(resolvedInputs),
120
- status: "running" as const,
121
- stages: [],
122
- startedAt: Date.now(),
123
- ...(opts.parentRun !== undefined ? {
124
- parentRunId: opts.parentRun.runId,
125
- parentStageId: opts.parentRun.stageId,
126
- rootRunId: opts.parentRun.rootRunId,
127
- } : {}),
128
- ...(opts.continuation !== undefined ? {
129
- resumedFromRunId: opts.continuation.source.id,
130
- resumeFromStageId: opts.continuation.resumeFromStageId,
131
- } : {}),
132
- };
133
-
134
- const classifiedFailures = new Map<unknown, WorkflowFailure>();
135
- const classifyExecutorFailure = (error: unknown): WorkflowFailure => {
136
- const cached = classifiedFailures.get(error);
137
- if (cached !== undefined) return cached;
138
- let classified: WorkflowFailure;
139
- try {
140
- classified = classifyWorkflowFailure(error);
141
- } catch {
142
- classified = classifyWorkflowFailure(new Error(unknownErrorMessage(error)));
143
- }
144
- classifiedFailures.set(error, classified);
145
- return classified;
146
- };
147
-
148
- activeStore.recordRunStart(runSnapshot);
149
- if (!opts.signal) opts.cancellation?.register(runId, ownController);
150
- opts.onRunStart?.(runSnapshot);
151
- if (opts.persistence) {
152
- appendRunStart(opts.persistence, {
153
- runId,
154
- name: def.name,
155
- inputs: resolvedInputs,
156
- ...(runSnapshot.parentRunId !== undefined ? { parentRunId: runSnapshot.parentRunId } : {}),
157
- ...(runSnapshot.parentStageId !== undefined ? { parentStageId: runSnapshot.parentStageId } : {}),
158
- ...(runSnapshot.rootRunId !== undefined ? { rootRunId: runSnapshot.rootRunId } : {}),
159
- ...(runSnapshot.resumedFromRunId !== undefined ? { resumedFromRunId: runSnapshot.resumedFromRunId } : {}),
160
- ...(runSnapshot.resumeFromStageId !== undefined ? { resumeFromStageId: runSnapshot.resumeFromStageId } : {}),
161
- ts: runSnapshot.startedAt,
162
- });
163
- }
164
-
165
- const tracker = new GraphFrontierTracker();
166
- const inputConcurrency = resolveInputConcurrency(def.inputs, resolvedInputs);
167
- const inputRuntimeDefaults = resolveInputRuntimeDefaults(def, resolvedInputs);
168
- const workflowInvocationCwd = opts.cwd ?? process.cwd();
169
- let workflowCwd: string | undefined;
170
- const resolveWorkflowCwd = (): string => {
171
- workflowCwd ??= workflowCwdWithInputWorktree(inputRuntimeDefaults, workflowInvocationCwd);
172
- return workflowCwd;
173
- };
174
- const limiter = createRunLimiter(inputConcurrency ?? opts.config?.defaultConcurrency);
175
- const stageRegistry = opts.stageControlRegistry ?? defaultStageControlRegistry;
176
- const replayIndex = createContinuationReplayIndex(opts.continuation);
177
- const scheduler = createStageScheduler({
178
- runId,
179
- runSnapshot,
180
- activeStore,
181
- tracker,
182
- stageRegistry: () => stageRegistry,
183
- });
184
- ownController.signal.addEventListener(
185
- "abort",
186
- () => scheduler.rejectReleaseBarriers(ownController.signal.reason ?? new Error("atomic-workflows: run aborted")),
187
- { once: true },
188
- );
189
-
190
- const finalizers = createRunFinalizers({
191
- def,
192
- runId,
193
- runSnapshot,
194
- activeStore,
195
- opts,
196
- classifyExecutorFailure,
197
- drainWorkflowExitCleanups: exit.drainWorkflowExitCleanups,
198
- });
199
- const startWorkflowBoundaryStage = createWorkflowBoundaryFactory({
200
- runId,
201
- runSnapshot,
202
- activeStore,
203
- opts,
204
- tracker,
205
- replayIndex,
206
- registerWorkflowExitCleanup: exit.registerWorkflowExitCleanup,
207
- workflowExitSkippedReason: exit.workflowExitSkippedReason,
208
- classifyExecutorFailure,
209
- });
210
- const workflowBoundaryReplayCounts = new Map<string, number>();
211
- const nextWorkflowBoundaryReplayKey = (name: string): string => {
212
- const next = (workflowBoundaryReplayCounts.get(name) ?? 0) + 1;
213
- workflowBoundaryReplayCounts.set(name, next);
214
- return `workflow:${name}:${next}`;
215
- };
216
- const stage = createWorkflowStageFactory({
217
- runId,
218
- activeStore,
219
- opts,
220
- adapters,
221
- signal: ownController.signal,
222
- tracker,
223
- scheduler,
224
- replayIndex,
225
- limiter,
226
- inputRuntimeDefaults,
227
- workflowInvocationCwd,
228
- stageRegistry,
229
- exit,
230
- classifyExecutorFailure,
231
- });
232
- const taskRunners = createWorkflowTaskRunners({
233
- runId,
234
- exit,
235
- tracker,
236
- inputRuntimeDefaults,
237
- workflowInvocationCwd,
238
- stage,
239
- });
240
- const workflow = createChildWorkflowRunner({
241
- runId,
242
- depth,
243
- opts,
244
- exit,
245
- ownController,
246
- resolveWorkflowCwd,
247
- nextWorkflowBoundaryReplayKey,
248
- startWorkflowBoundaryStage,
249
- runWorkflow: run,
250
- });
251
-
252
- const ctx: WorkflowRunContext<TInputs> = {
253
- inputs: resolvedInputs as TInputs,
254
- get cwd() { return resolveWorkflowCwd(); },
255
- exit: exit.exit,
256
- ui: buildExitGatedUiContext({
257
- opts,
258
- throwIfWorkflowExitSelected: exit.throwIfWorkflowExitSelected,
259
- baseFromPromptNodes: () => buildPromptNodeUiAdapter({
260
- runId,
261
- activeStore,
262
- opts,
263
- tracker,
264
- replayIndex,
265
- signal: ownController.signal,
266
- throwIfWorkflowExitSelected: exit.throwIfWorkflowExitSelected,
267
- registerWorkflowExitCleanup: exit.registerWorkflowExitCleanup,
268
- workflowExitSkippedReason: exit.workflowExitSkippedReason,
269
- preserveWorkflowExitSkippedReason: exit.preserveWorkflowExitSkippedReason,
270
- classifyExecutorFailure,
271
- }),
272
- }),
273
- stage,
274
- task: taskRunners.task,
275
- chain: taskRunners.chain,
276
- parallel: taskRunners.parallel,
277
- workflow,
278
- };
279
-
280
- try {
281
- if (opts.deferWorkflowStart === true) {
282
- await nextEventLoopTurn();
283
- if (ownController.signal.aborted) {
284
- const selectedExit = findWorkflowExitSignal(ownController.signal.reason, exitScope);
285
- if (selectedExit !== undefined) return await finalizers.finalizeWorkflowExit(selectedExit);
286
- const parentExit = parentWorkflowExitAbortReason(ownController.signal.reason);
287
- if (parentExit !== undefined) return await finalizers.finalizeParentWorkflowExitCancellation(parentExit);
288
- return finalizeKilled(runId, runSnapshot, activeStore, opts.persistence, opts.onRunEnd);
289
- }
290
- }
291
-
292
- const rawResult = await def.run(ctx);
293
- if (ownController.signal.aborted) {
294
- const selectedExit = findWorkflowExitSignal(ownController.signal.reason, exitScope);
295
- if (selectedExit !== undefined) return await finalizers.finalizeWorkflowExit(selectedExit);
296
- const parentExit = parentWorkflowExitAbortReason(ownController.signal.reason);
297
- if (parentExit !== undefined) return await finalizers.finalizeParentWorkflowExitCancellation(parentExit);
298
- return finalizeKilled(runId, runSnapshot, activeStore, opts.persistence, opts.onRunEnd);
299
- }
300
-
301
- const result = normalizeWorkflowRunOutput(def.name, rawResult);
302
- assertWorkflowRunOutputs(def.name, result, def.outputs);
303
- assertWorkflowCreatedStage(runSnapshot);
304
- const recorded = activeStore.recordRunEnd(runId, "completed", result);
305
- appendRunEndWhenRecorded(opts.persistence, recorded, { runId, status: "completed", result, ts: Date.now() });
306
- return reconcileTerminalRunResult(runId, runSnapshot, activeStore, { status: "completed", result }, opts.onRunEnd);
307
- } catch (err) {
308
- const selectedExit = findWorkflowExitSignal(err, exitScope) ?? findWorkflowExitSignal(ownController.signal.reason, exitScope);
309
- if (selectedExit !== undefined) return await finalizers.finalizeWorkflowExit(selectedExit);
310
-
311
- if (ownController.signal.aborted) {
312
- const parentExit = parentWorkflowExitAbortReason(ownController.signal.reason);
313
- if (parentExit !== undefined) return await finalizers.finalizeParentWorkflowExitCancellation(parentExit);
314
- return finalizeKilled(runId, runSnapshot, activeStore, opts.persistence, opts.onRunEnd);
315
- }
316
-
317
- const failure = classifyExecutorFailure(err);
318
- const metadata = selectRunFailureDisposition({
319
- outerFailure: failure,
320
- thrownError: err,
321
- stages: runSnapshot.stages,
322
- classifyFailure: classifyExecutorFailure,
323
- });
324
-
325
- if (metadata.failureDisposition === "terminal_killed") {
326
- for (const failedStageId of metadata.failedStageIds) scheduler.blockKnownNonTerminalDescendants(failedStageId);
327
- return finalizeKilledByFailure(runId, runSnapshot, activeStore, opts.persistence, opts.onRunEnd, { ...metadata, resumable: false });
328
- }
329
-
330
- if (metadata.failureDisposition === "active_blocked" && metadata.failedStageId !== undefined && metadata.failureRecoverability === "recoverable") {
331
- for (const failedStageId of metadata.failedStageIds) scheduler.blockKnownNonTerminalDescendants(failedStageId);
332
- return recordActiveBlockedFailure(runId, runSnapshot, activeStore, opts.persistence, {
333
- ...metadata,
334
- failureRecoverability: "recoverable",
335
- failedStageId: metadata.failedStageId,
336
- resumable: true,
337
- });
338
- }
339
-
340
- const recorded = activeStore.recordRunEnd(runId, "failed", undefined, metadata.errorMessage, metadata);
341
- appendRunEndWhenRecorded(opts.persistence, recorded, {
342
- runId,
343
- status: "failed",
344
- error: metadata.errorMessage,
345
- failureKind: metadata.failureKind,
346
- ...(metadata.failureCode !== undefined ? { failureCode: metadata.failureCode } : {}),
347
- ...(metadata.failureRecoverability !== undefined ? { failureRecoverability: metadata.failureRecoverability } : {}),
348
- ...(metadata.failureDisposition !== undefined ? { failureDisposition: metadata.failureDisposition } : {}),
349
- failureMessage: metadata.failureMessage,
350
- ...(metadata.failedStageId !== undefined ? { failedStageId: metadata.failedStageId } : {}),
351
- resumable: metadata.resumable,
352
- ...(metadata.retryAfterMs !== undefined ? { retryAfterMs: metadata.retryAfterMs } : {}),
353
- ts: Date.now(),
354
- });
355
- return reconcileTerminalRunResult(runId, runSnapshot, activeStore, { status: "failed", error: metadata.errorMessage }, opts.onRunEnd);
356
- } finally {
357
- opts.cancellation?.unregister(runId);
358
- }
359
- }
1
+ export { run } from "../../engine/run.js";
@@ -1,6 +1,6 @@
1
1
  import type { RunSnapshot, StageSnapshot } from "../../shared/store-types.js";
2
2
  import type { Store } from "../../shared/store.js";
3
- import type { GraphFrontierTracker } from "../shared/graph-inference.js";
3
+ import type { GraphFrontierTracker } from "../../engine/graph-inference.js";
4
4
  import type { StageControlRegistry } from "./stage-control-registry.js";
5
5
 
6
6
  interface ReleaseBarrier {
@@ -112,10 +112,7 @@ export function createTrackedStageCaller(input: {
112
112
  runtime.activeStore.recordStageStart(runtime.runId, runtime.stageSnapshot);
113
113
  runtime.appendStageStartOnce();
114
114
 
115
- const mcpAllow = input.options?.mcp?.allow ?? null;
116
- const mcpDeny = input.options?.mcp?.deny ?? null;
117
- const hasMcpScope = mcpAllow !== null || mcpDeny !== null;
118
- if (runtime.opts.mcp && hasMcpScope) runtime.opts.mcp.setScope(runtime.stageId, mcpAllow, mcpDeny);
115
+ runtime.mcpScope.apply();
119
116
 
120
117
  try {
121
118
  const abortSession = (): void => {
@@ -183,7 +180,7 @@ export function createTrackedStageCaller(input: {
183
180
  }
184
181
  throw err;
185
182
  } finally {
186
- if (runtime.opts.mcp && hasMcpScope) runtime.opts.mcp.clearScope(runtime.stageId);
183
+ runtime.mcpScope.clear();
187
184
  runtime.captureStageSessionMeta();
188
185
  runtime.finalizeStageSnapshot();
189
186
  if (runtime.state.stageClosedByWorkflowExit || runtime.exit.currentWorkflowExitAbortReason() !== undefined) {
@@ -7,10 +7,11 @@ import { appendStageEnd, appendStageStart } from "../../shared/persistence-sessi
7
7
  import { elapsedStageMs } from "../../shared/timing.js";
8
8
  import type { WorkflowFailure } from "../../shared/workflow-failures.js";
9
9
  import type { ConcurrencyLimiter } from "../shared/concurrency.js";
10
- import type { GraphFrontierTracker } from "../shared/graph-inference.js";
10
+ import type { GraphFrontierTracker } from "../../engine/graph-inference.js";
11
+ import type { EngineStageRuntimeOptions } from "../../engine/options.js";
11
12
  import { createStageContext as createInnerStageContext, type InternalStageContext, type StageAdapters } from "./stage-runner.js";
12
13
  import type { StageControlRegistry } from "./stage-control-registry.js";
13
- import type { RunOpts, ParallelFailFastScope } from "./executor-types.js";
14
+ import type { ParallelFailFastScope } from "./executor-types.js";
14
15
  import type { WorkflowExitManager } from "./executor-exit-manager.js";
15
16
  import type { ContinuationReplayIndex } from "./executor-continuation.js";
16
17
  import { sameStringSet } from "./executor-continuation.js";
@@ -19,7 +20,7 @@ import { isTerminalStage } from "./executor-scheduler.js";
19
20
  import { stageReplayFields } from "./executor-lifecycle.js";
20
21
  import { askUserQuestionToolEvent, toolResultHasChatAnswer } from "./executor-hil.js";
21
22
  import { createReplayStageContext } from "./executor-stage-replay.js";
22
- import type { LiveStageMutableState, LiveStageRuntime, StageContextWithMeta } from "./executor-stage-types.js";
23
+ import type { LiveStageMutableState, LiveStageRuntime, StageContextWithMeta, StageMcpScope } from "./executor-stage-types.js";
23
24
  import { createStageControlHandle } from "./executor-stage-control.js";
24
25
  import { createTrackedStageCaller } from "./executor-stage-call.js";
25
26
  import { createStageContext } from "./executor-stage-context.js";
@@ -28,7 +29,7 @@ import { stageOptionsWithGitWorktree, stageOptionsWithInputDefaults } from "./ex
28
29
  export function createWorkflowStageFactory(input: {
29
30
  readonly runId: string;
30
31
  readonly activeStore: Store;
31
- readonly opts: RunOpts;
32
+ readonly opts: EngineStageRuntimeOptions;
32
33
  readonly adapters: StageAdapters;
33
34
  readonly signal: AbortSignal;
34
35
  readonly tracker: GraphFrontierTracker;
@@ -40,6 +41,7 @@ export function createWorkflowStageFactory(input: {
40
41
  readonly stageRegistry: StageControlRegistry;
41
42
  readonly exit: WorkflowExitManager;
42
43
  readonly classifyExecutorFailure: (error: unknown) => WorkflowFailure;
44
+ readonly createMcpScope: (stageId: string, options: StageOptions | undefined) => StageMcpScope;
43
45
  }): (name: string, options?: StageOptions, stageFailFastScope?: ParallelFailFastScope) => StageContextWithMeta {
44
46
  return (name: string, options?: StageOptions, stageFailFastScope?: ParallelFailFastScope): StageContextWithMeta => {
45
47
  input.exit.throwIfWorkflowExitSelected();
@@ -287,6 +289,7 @@ export function createWorkflowStageFactory(input: {
287
289
  signal: input.signal,
288
290
  exit: input.exit,
289
291
  classifyExecutorFailure: input.classifyExecutorFailure,
292
+ mcpScope: input.createMcpScope(stageId, options),
290
293
  ...(stageFailFastScope !== undefined ? { stageFailFastScope } : {}),
291
294
  state,
292
295
  unregisterStageHandle: () => {},
@@ -317,6 +320,11 @@ export function createWorkflowStageFactory(input: {
317
320
  const blockedBy = input.scheduler.blockingAncestorFor(stageSnapshot);
318
321
  if (blockedBy !== undefined) input.scheduler.blockStageUntilCascadeRelease(stageSnapshot, blockedBy);
319
322
 
323
+ // Parallel fail-fast and workflow-exit cleanup can both target a live stage.
324
+ // The first terminal path owns the snapshot: finalization unregisters
325
+ // workflow-exit cleanup and removes the stage from the fail-fast active set.
326
+ // Later paths must not overwrite the terminal skippedReason; they only abort
327
+ // and release idempotent live handles.
320
328
  const skipForParallelFailFast = (): void => {
321
329
  if (isTerminalStage(stageSnapshot)) return;
322
330
  markSkippedForParallelFailFast();
@@ -2,9 +2,10 @@ import type { StageSnapshot } from "../../shared/store-types.js";
2
2
  import type { Store } from "../../shared/store.js";
3
3
  import { appendStageEnd, appendStageStart } from "../../shared/persistence-session-entries.js";
4
4
  import { elapsedStageMs } from "../../shared/timing.js";
5
- import type { GraphFrontierTracker } from "../shared/graph-inference.js";
5
+ import type { GraphFrontierTracker } from "../../engine/graph-inference.js";
6
+ import type { EngineStageRuntimeOptions } from "../../engine/options.js";
6
7
  import type { InternalStageContext } from "./stage-runner.js";
7
- import type { RunOpts, WorkflowExitCleanup } from "./executor-types.js";
8
+ import type { WorkflowExitCleanup } from "./executor-types.js";
8
9
  import { stageReplayFields } from "./executor-lifecycle.js";
9
10
 
10
11
  export function createReplayStageContext(input: {
@@ -14,7 +15,7 @@ export function createReplayStageContext(input: {
14
15
  readonly stageSnapshot: StageSnapshot;
15
16
  readonly replaySource: StageSnapshot;
16
17
  readonly activeStore: Store;
17
- readonly opts: RunOpts;
18
+ readonly opts: EngineStageRuntimeOptions;
18
19
  readonly tracker: GraphFrontierTracker;
19
20
  readonly registerWorkflowExitCleanup: (stageId: string, cleanup: WorkflowExitCleanup) => () => void;
20
21
  readonly workflowExitSkippedReason: (reason?: string) => string;