@bastani/atomic 0.8.4 → 0.8.5-0

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 (245) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +24 -23
  3. package/dist/builtin/intercom/README.md +5 -5
  4. package/dist/builtin/intercom/index.ts +1 -1
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/intercom/ui/compose.ts +19 -1
  7. package/dist/builtin/intercom/ui/session-list.ts +19 -1
  8. package/dist/builtin/mcp/README.md +3 -3
  9. package/dist/builtin/mcp/commands.ts +1 -1
  10. package/dist/builtin/mcp/host-html-template.ts +1 -1
  11. package/dist/builtin/mcp/mcp-panel.ts +14 -14
  12. package/dist/builtin/mcp/mcp-setup-panel.ts +4 -4
  13. package/dist/builtin/mcp/package.json +1 -1
  14. package/dist/builtin/mcp/tool-result-renderer.ts +1 -1
  15. package/dist/builtin/subagents/README.md +3 -3
  16. package/dist/builtin/subagents/package.json +1 -1
  17. package/dist/builtin/subagents/src/tui/render.ts +1844 -1062
  18. package/dist/builtin/web-access/README.md +1 -1
  19. package/dist/builtin/web-access/curator-page.ts +2 -2
  20. package/dist/builtin/web-access/index.ts +1 -1
  21. package/dist/builtin/web-access/package.json +1 -1
  22. package/dist/builtin/workflows/README.md +34 -7
  23. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +23 -4
  24. package/dist/builtin/workflows/builtin/ralph.ts +1 -1
  25. package/dist/builtin/workflows/package.json +1 -1
  26. package/dist/builtin/workflows/skills/workflow/SKILL.md +75 -16
  27. package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +34 -11
  28. package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +111 -20
  29. package/dist/builtin/workflows/src/extension/discovery.ts +32 -4
  30. package/dist/builtin/workflows/src/extension/index.ts +347 -63
  31. package/dist/builtin/workflows/src/extension/render-call.ts +3 -1
  32. package/dist/builtin/workflows/src/extension/render-result.ts +7 -0
  33. package/dist/builtin/workflows/src/extension/runtime.ts +4 -2
  34. package/dist/builtin/workflows/src/extension/wiring.ts +32 -8
  35. package/dist/builtin/workflows/src/extension/workflow-schema.ts +36 -14
  36. package/dist/builtin/workflows/src/runs/background/runner.ts +2 -2
  37. package/dist/builtin/workflows/src/runs/background/status.ts +89 -0
  38. package/dist/builtin/workflows/src/runs/foreground/executor.ts +338 -78
  39. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +2 -0
  40. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +55 -7
  41. package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +146 -10
  42. package/dist/builtin/workflows/src/shared/store.ts +29 -0
  43. package/dist/builtin/workflows/src/shared/types.ts +25 -4
  44. package/dist/builtin/workflows/src/tui/graph-canvas.ts +69 -2
  45. package/dist/builtin/workflows/src/tui/graph-view.ts +97 -182
  46. package/dist/builtin/workflows/src/tui/header.ts +36 -20
  47. package/dist/builtin/workflows/src/tui/inline-form-card.ts +129 -46
  48. package/dist/builtin/workflows/src/tui/inline-form-editor.ts +111 -36
  49. package/dist/builtin/workflows/src/tui/inputs-picker.ts +311 -91
  50. package/dist/builtin/workflows/src/tui/layout.ts +1 -1
  51. package/dist/builtin/workflows/src/tui/node-card.ts +66 -37
  52. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +20 -6
  53. package/dist/builtin/workflows/src/tui/prompt-card.ts +262 -85
  54. package/dist/builtin/workflows/src/tui/run-detail.ts +50 -31
  55. package/dist/builtin/workflows/src/tui/session-confirm.ts +21 -14
  56. package/dist/builtin/workflows/src/tui/session-picker.ts +35 -26
  57. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +531 -960
  58. package/dist/builtin/workflows/src/tui/status-helpers.ts +6 -0
  59. package/dist/builtin/workflows/src/tui/status-list.ts +8 -4
  60. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +7 -2
  61. package/dist/builtin/workflows/src/tui/switcher.ts +55 -25
  62. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +33 -1
  63. package/dist/builtin/workflows/src/tui/workflow-list.ts +10 -6
  64. package/dist/cli/args.d.ts.map +1 -1
  65. package/dist/cli/args.js +1 -1
  66. package/dist/cli/args.js.map +1 -1
  67. package/dist/config.d.ts.map +1 -1
  68. package/dist/config.js +20 -6
  69. package/dist/config.js.map +1 -1
  70. package/dist/core/agent-session-services.d.ts +3 -3
  71. package/dist/core/agent-session-services.d.ts.map +1 -1
  72. package/dist/core/agent-session-services.js.map +1 -1
  73. package/dist/core/agent-session.d.ts +7 -7
  74. package/dist/core/agent-session.d.ts.map +1 -1
  75. package/dist/core/agent-session.js.map +1 -1
  76. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  77. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  78. package/dist/core/compaction/branch-summarization.js.map +1 -1
  79. package/dist/core/compaction/compaction.d.ts +3 -3
  80. package/dist/core/compaction/compaction.d.ts.map +1 -1
  81. package/dist/core/compaction/compaction.js.map +1 -1
  82. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  83. package/dist/core/export-html/tool-renderer.js.map +1 -1
  84. package/dist/core/extensions/loader.d.ts +3 -2
  85. package/dist/core/extensions/loader.d.ts.map +1 -1
  86. package/dist/core/extensions/loader.js +24 -12
  87. package/dist/core/extensions/loader.js.map +1 -1
  88. package/dist/core/extensions/runner.d.ts.map +1 -1
  89. package/dist/core/extensions/runner.js +6 -0
  90. package/dist/core/extensions/runner.js.map +1 -1
  91. package/dist/core/extensions/types.d.ts +28 -17
  92. package/dist/core/extensions/types.d.ts.map +1 -1
  93. package/dist/core/extensions/types.js.map +1 -1
  94. package/dist/core/package-manager.d.ts +1 -0
  95. package/dist/core/package-manager.d.ts.map +1 -1
  96. package/dist/core/package-manager.js +65 -28
  97. package/dist/core/package-manager.js.map +1 -1
  98. package/dist/core/resource-loader.d.ts.map +1 -1
  99. package/dist/core/resource-loader.js +13 -5
  100. package/dist/core/resource-loader.js.map +1 -1
  101. package/dist/core/sdk.d.ts +3 -3
  102. package/dist/core/sdk.d.ts.map +1 -1
  103. package/dist/core/sdk.js.map +1 -1
  104. package/dist/core/session-manager.d.ts.map +1 -1
  105. package/dist/core/session-manager.js +1 -1
  106. package/dist/core/session-manager.js.map +1 -1
  107. package/dist/core/settings-manager.d.ts +2 -0
  108. package/dist/core/settings-manager.d.ts.map +1 -1
  109. package/dist/core/settings-manager.js.map +1 -1
  110. package/dist/core/slash-commands.d.ts.map +1 -1
  111. package/dist/core/slash-commands.js +1 -1
  112. package/dist/core/slash-commands.js.map +1 -1
  113. package/dist/core/system-prompt.d.ts.map +1 -1
  114. package/dist/core/system-prompt.js +5 -3
  115. package/dist/core/system-prompt.js.map +1 -1
  116. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +1 -1
  117. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
  118. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js +1 -1
  119. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
  120. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +8 -8
  121. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
  122. package/dist/core/tools/ask-user-question/view/dialog-builder.js +6 -6
  123. package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
  124. package/dist/core/tools/bash.d.ts.map +1 -1
  125. package/dist/core/tools/bash.js +1 -1
  126. package/dist/core/tools/bash.js.map +1 -1
  127. package/dist/core/tools/find.d.ts.map +1 -1
  128. package/dist/core/tools/find.js +1 -1
  129. package/dist/core/tools/find.js.map +1 -1
  130. package/dist/core/tools/grep.d.ts.map +1 -1
  131. package/dist/core/tools/grep.js +7 -4
  132. package/dist/core/tools/grep.js.map +1 -1
  133. package/dist/core/tools/index.d.ts +3 -2
  134. package/dist/core/tools/index.d.ts.map +1 -1
  135. package/dist/core/tools/index.js.map +1 -1
  136. package/dist/core/tools/ls.d.ts.map +1 -1
  137. package/dist/core/tools/ls.js +3 -2
  138. package/dist/core/tools/ls.js.map +1 -1
  139. package/dist/core/tools/read.d.ts.map +1 -1
  140. package/dist/core/tools/read.js +2 -2
  141. package/dist/core/tools/read.js.map +1 -1
  142. package/dist/core/tools/render-utils.d.ts +2 -1
  143. package/dist/core/tools/render-utils.d.ts.map +1 -1
  144. package/dist/core/tools/render-utils.js.map +1 -1
  145. package/dist/core/tools/todos.d.ts.map +1 -1
  146. package/dist/core/tools/todos.js +1 -1
  147. package/dist/core/tools/todos.js.map +1 -1
  148. package/dist/core/tools/tool-definition-wrapper.d.ts +4 -3
  149. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  150. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  151. package/dist/core/tools/write.d.ts.map +1 -1
  152. package/dist/core/tools/write.js +1 -1
  153. package/dist/core/tools/write.js.map +1 -1
  154. package/dist/index.d.ts +2 -1
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/index.js +2 -1
  157. package/dist/index.js.map +1 -1
  158. package/dist/main.d.ts.map +1 -1
  159. package/dist/main.js +2 -2
  160. package/dist/main.js.map +1 -1
  161. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  162. package/dist/modes/interactive/components/assistant-message.js +3 -3
  163. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  164. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/bash-execution.js +3 -3
  166. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  167. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  168. package/dist/modes/interactive/components/branch-summary-message.js +1 -1
  169. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  170. package/dist/modes/interactive/components/chat-message-renderer.d.ts +2 -1
  171. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  172. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  173. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  174. package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  175. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  176. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  177. package/dist/modes/interactive/components/config-selector.js +1 -1
  178. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  179. package/dist/modes/interactive/components/custom-editor.d.ts +3 -0
  180. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/custom-editor.js +13 -3
  182. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  183. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  184. package/dist/modes/interactive/components/footer.js +1 -1
  185. package/dist/modes/interactive/components/footer.js.map +1 -1
  186. package/dist/modes/interactive/components/index.d.ts +2 -1
  187. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  188. package/dist/modes/interactive/components/index.js +2 -1
  189. package/dist/modes/interactive/components/index.js.map +1 -1
  190. package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -0
  191. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  192. package/dist/modes/interactive/components/keybinding-hints.js +47 -5
  193. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  194. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  195. package/dist/modes/interactive/components/login-dialog.js +5 -5
  196. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  197. package/dist/modes/interactive/components/model-selector.d.ts +3 -3
  198. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  200. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
  201. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/scoped-models-selector.js +7 -7
  203. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  204. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/session-selector.js +8 -8
  206. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  207. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/settings-selector.js +3 -3
  209. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  210. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
  212. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  213. package/dist/modes/interactive/components/tool-execution.d.ts +10 -12
  214. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/tool-execution.js +3 -3
  216. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  217. package/dist/modes/interactive/components/working-status.d.ts +25 -0
  218. package/dist/modes/interactive/components/working-status.d.ts.map +1 -0
  219. package/dist/modes/interactive/components/working-status.js +28 -0
  220. package/dist/modes/interactive/components/working-status.js.map +1 -0
  221. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  222. package/dist/modes/interactive/interactive-mode.js +8 -7
  223. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  224. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  225. package/dist/modes/rpc/rpc-mode.js +8 -0
  226. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  227. package/dist/modes/rpc/rpc-types.d.ts +5 -5
  228. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  229. package/dist/modes/rpc/rpc-types.js.map +1 -1
  230. package/dist/utils/tools-manager.d.ts.map +1 -1
  231. package/dist/utils/tools-manager.js.map +1 -1
  232. package/docs/development.md +2 -2
  233. package/docs/extensions.md +7 -7
  234. package/docs/packages.md +11 -8
  235. package/docs/quickstart.md +2 -2
  236. package/docs/rpc.md +1 -1
  237. package/docs/sdk.md +14 -11
  238. package/docs/session-format.md +1 -1
  239. package/docs/sessions.md +10 -10
  240. package/docs/settings.md +1 -1
  241. package/docs/terminal-setup.md +9 -9
  242. package/docs/tmux.md +10 -10
  243. package/docs/tui.md +2 -2
  244. package/docs/usage.md +9 -9
  245. package/package.json +6 -1
@@ -13,12 +13,14 @@ import type {
13
13
  WorkflowInputSchema,
14
14
  StageContext,
15
15
  StageOptions,
16
+ StagePromptOptions,
16
17
  WorkflowTaskContextInput,
17
18
  WorkflowTaskOptions,
18
19
  WorkflowTaskResult,
19
20
  WorkflowTaskStep,
20
21
  WorkflowArtifact,
21
22
  WorkflowMaxOutput,
23
+ WorkflowOutputMode,
22
24
  WorkflowChainOptions,
23
25
  WorkflowParallelOptions,
24
26
  WorkflowDetails,
@@ -149,6 +151,18 @@ export function resolveInputs(
149
151
  return resolved;
150
152
  }
151
153
 
154
+ function resolveInputConcurrency(
155
+ schema: Readonly<Record<string, WorkflowInputSchema>>,
156
+ resolvedInputs: ResolvedInputs,
157
+ ): number | undefined {
158
+ if (schema["max_concurrency"]?.type !== "number") return undefined;
159
+
160
+ const value = resolvedInputs["max_concurrency"];
161
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) return undefined;
162
+
163
+ return Math.floor(value);
164
+ }
165
+
152
166
  // ---------------------------------------------------------------------------
153
167
  // HIL unavailable fallback — rejects with precise per-primitive error
154
168
  // ---------------------------------------------------------------------------
@@ -250,15 +264,52 @@ function taskPrevious(options: WorkflowTaskOptions): WorkflowTaskOptions["previo
250
264
  return options.previous;
251
265
  }
252
266
 
253
- function taskStageOptions(options: WorkflowTaskOptions): StageOptions {
267
+ type WorkflowTaskExecutionOptions = WorkflowTaskOptions & { chainDir?: string };
268
+
269
+ function resolveWorkflowPath(filePath: string, baseDir: string | undefined): string {
270
+ if (isAbsolute(filePath)) return filePath;
271
+ return resolve(baseDir ?? process.cwd(), filePath);
272
+ }
273
+
274
+ function taskBaseDir(options: Pick<WorkflowTaskExecutionOptions, "chainDir" | "cwd">): string | undefined {
275
+ if (typeof options.chainDir === "string" && options.chainDir.length > 0) {
276
+ return resolveWorkflowPath(options.chainDir, process.cwd());
277
+ }
278
+ if (typeof options.cwd === "string" && options.cwd.length > 0) {
279
+ return resolveWorkflowPath(options.cwd, process.cwd());
280
+ }
281
+ return undefined;
282
+ }
283
+
284
+ function taskReadInstruction(options: WorkflowTaskExecutionOptions): string {
285
+ if (options.reads === false || options.reads === undefined || options.reads.length === 0) return "";
286
+ const baseDir = taskBaseDir(options);
287
+ const files = options.reads.map((file) => resolveWorkflowPath(file, baseDir));
288
+ return `[Read from: ${files.join(", ")}]\n\n`;
289
+ }
290
+
291
+ function taskPromptOptions(options: WorkflowTaskExecutionOptions): StagePromptOptions | undefined {
292
+ const baseDir = taskBaseDir(options);
293
+ const promptOptions: StagePromptOptions = {
294
+ ...(options.output !== undefined ? { output: options.output } : {}),
295
+ ...(options.outputMode !== undefined ? { outputMode: options.outputMode } : {}),
296
+ ...(baseDir !== undefined ? { cwd: baseDir } : options.cwd !== undefined ? { cwd: options.cwd } : {}),
297
+ ...(options.maxOutput !== undefined ? { maxOutput: options.maxOutput } : {}),
298
+ ...(options.artifacts !== undefined ? { artifacts: options.artifacts } : {}),
299
+ ...(options.sessionDir !== undefined ? { sessionDir: options.sessionDir } : {}),
300
+ };
301
+ return Object.keys(promptOptions).length === 0 ? undefined : promptOptions;
302
+ }
303
+
304
+ function taskStageOptions(options: WorkflowTaskExecutionOptions): StageOptions {
254
305
  const {
255
306
  prompt: _prompt,
256
307
  task: _task,
257
308
  previous: _previous,
309
+ chainDir: _chainDir,
258
310
  output: _output,
259
311
  outputMode: _outputMode,
260
312
  reads: _reads,
261
- progress: _progress,
262
313
  worktree: _worktree,
263
314
  maxOutput: _maxOutput,
264
315
  artifacts: _artifacts,
@@ -273,16 +324,11 @@ function taskOptionsFromStep(step: WorkflowTaskStep, prompt: string, previous?:
273
324
  prompt: _prompt,
274
325
  task: _task,
275
326
  previous: _previous,
276
- output: _output,
277
- outputMode: _outputMode,
278
- reads: _reads,
279
- progress: _progress,
280
- worktree: _worktree,
281
- ...stageOptions
327
+ ...stepOptions
282
328
  } = step;
283
329
  return previous === undefined
284
- ? { ...stageOptions, prompt }
285
- : { ...stageOptions, prompt, previous };
330
+ ? { ...stepOptions, prompt }
331
+ : { ...stepOptions, prompt, previous };
286
332
  }
287
333
 
288
334
  function replaceTaskPlaceholder(prompt: string, task: string): string {
@@ -382,6 +428,28 @@ function withoutUndefinedProperties<T extends object>(value: T): Partial<T> {
382
428
  ) as Partial<T>;
383
429
  }
384
430
 
431
+ function sharedTaskDefaultsFromOptions(
432
+ options: WorkflowChainOptions | WorkflowParallelOptions,
433
+ ): Partial<WorkflowTaskExecutionOptions> {
434
+ const {
435
+ task: _task,
436
+ concurrency: _concurrency,
437
+ failFast: _failFast,
438
+ ...taskDefaults
439
+ } = options as WorkflowParallelOptions;
440
+ return withoutUndefinedProperties(taskDefaults);
441
+ }
442
+
443
+ function taskWithSharedDefaults(
444
+ taskOptions: WorkflowTaskOptions,
445
+ options: WorkflowChainOptions | WorkflowParallelOptions,
446
+ ): WorkflowTaskExecutionOptions {
447
+ return {
448
+ ...sharedTaskDefaultsFromOptions(options),
449
+ ...withoutUndefinedProperties(taskOptions),
450
+ } as WorkflowTaskExecutionOptions;
451
+ }
452
+
385
453
  function directTaskWithDefaults(
386
454
  item: WorkflowDirectTaskItem,
387
455
  options: WorkflowDirectOptions,
@@ -392,9 +460,9 @@ function directTaskWithDefaults(
392
460
  concurrency: _concurrency,
393
461
  failFast: _failFast,
394
462
  chainDir: _chainDir,
463
+ reads,
395
464
  output,
396
465
  outputMode,
397
- progress,
398
466
  worktree,
399
467
  maxOutput,
400
468
  artifacts,
@@ -409,9 +477,9 @@ function directTaskWithDefaults(
409
477
 
410
478
  return {
411
479
  ...taskWithStageDefaults,
480
+ ...(item.reads === undefined && reads !== undefined ? { reads } : {}),
412
481
  ...(item.output === undefined && output !== undefined ? { output } : {}),
413
482
  ...(item.outputMode === undefined && outputMode !== undefined ? { outputMode } : {}),
414
- ...(item.progress === undefined && progress !== undefined ? { progress } : {}),
415
483
  ...(item.worktree === undefined && worktree !== undefined ? { worktree } : {}),
416
484
  ...(item.maxOutput === undefined && maxOutput !== undefined ? { maxOutput } : {}),
417
485
  ...(item.artifacts === undefined && artifacts !== undefined ? { artifacts } : {}),
@@ -427,8 +495,6 @@ function directTaskToStep(
427
495
  count: _count,
428
496
  output: _output,
429
497
  outputMode: _outputMode,
430
- reads: _reads,
431
- progress: _progress,
432
498
  worktree: _worktree,
433
499
  prompt,
434
500
  task,
@@ -442,6 +508,54 @@ function directTaskToStep(
442
508
  };
443
509
  }
444
510
 
511
+ function positiveConcurrency(value: number | undefined): number | undefined {
512
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) return undefined;
513
+ return Math.floor(value);
514
+ }
515
+
516
+ async function mapParallelSteps<T>(
517
+ steps: readonly WorkflowTaskStep[],
518
+ concurrency: number | undefined,
519
+ failFast: boolean | undefined,
520
+ mapper: (step: WorkflowTaskStep) => Promise<T>,
521
+ ): Promise<T[]> {
522
+ const limit = positiveConcurrency(concurrency) ?? steps.length;
523
+ const results = new Array<T>(steps.length);
524
+ const failures: Array<{ readonly index: number; readonly error: unknown }> = [];
525
+ let nextIndex = 0;
526
+ let firstFailure: unknown;
527
+
528
+ async function worker(): Promise<void> {
529
+ while (true) {
530
+ if (failFast !== false && firstFailure !== undefined) return;
531
+ const index = nextIndex;
532
+ nextIndex += 1;
533
+ const step = steps[index];
534
+ if (step === undefined) return;
535
+ try {
536
+ results[index] = await mapper(step);
537
+ } catch (err) {
538
+ failures.push({ index, error: err });
539
+ firstFailure ??= err;
540
+ if (failFast !== false) throw err;
541
+ }
542
+ }
543
+ }
544
+
545
+ await Promise.all(
546
+ Array.from({ length: Math.min(limit, steps.length) }, () => worker()),
547
+ );
548
+
549
+ if (failures.length > 0) {
550
+ throw new AggregateError(
551
+ failures.map((failure) => failure.error),
552
+ `pi-workflows: ${failures.length} parallel ${failures.length === 1 ? "step" : "steps"} failed`,
553
+ );
554
+ }
555
+
556
+ return results;
557
+ }
558
+
445
559
  function expandedParallelTasks(tasks: readonly WorkflowDirectTaskItem[]): WorkflowDirectTaskItem[] {
446
560
  const expanded: WorkflowDirectTaskItem[] = [];
447
561
  for (const task of tasks) {
@@ -584,13 +698,14 @@ function isRunOpts(value: WorkflowDirectOptions | RunOpts | undefined): value is
584
698
  }
585
699
 
586
700
  async function writeDirectOutput(
587
- item: Pick<WorkflowDirectTaskItem, "output" | "outputMode">,
701
+ item: { readonly chainDir?: string; readonly cwd?: string; readonly output?: string | false; readonly outputMode?: WorkflowOutputMode },
588
702
  result: WorkflowTaskResult,
589
703
  ): Promise<{ result: WorkflowTaskResult; artifact?: WorkflowArtifact }> {
590
704
  if (typeof item.output !== "string") return { result };
591
705
 
592
- await mkdir(dirname(item.output), { recursive: true });
593
- await writeFile(item.output, result.text, "utf8");
706
+ const outputPath = resolveWorkflowPath(item.output, taskBaseDir(item));
707
+ await mkdir(dirname(outputPath), { recursive: true });
708
+ await writeFile(outputPath, result.text, "utf8");
594
709
 
595
710
  const visibleResult =
596
711
  item.outputMode === "file-only"
@@ -601,7 +716,7 @@ async function writeDirectOutput(
601
716
  result: visibleResult,
602
717
  artifact: {
603
718
  kind: "output",
604
- path: item.output,
719
+ path: outputPath,
605
720
  taskName: result.name,
606
721
  },
607
722
  };
@@ -746,7 +861,11 @@ export async function runParallel(
746
861
  const direct = defineDirectWorkflow("direct-parallel", async (ctx) => {
747
862
  try {
748
863
  const steps = prepared.tasks.map((task) => directTaskToStep(task));
749
- const rawResults = await ctx.parallel(steps, { task: options.task });
864
+ const rawResults = await ctx.parallel(steps, {
865
+ task: options.task,
866
+ concurrency: options.concurrency,
867
+ failFast: options.failFast,
868
+ });
750
869
  const persisted = await Promise.all(
751
870
  rawResults.map((result, index) => writeDirectOutput(prepared.tasks[index]!, result)),
752
871
  );
@@ -788,9 +907,16 @@ async function runDirectChainStep(
788
907
  const steps = prepared.tasks.map((item) =>
789
908
  directTaskToStep(item, directTaskPrompt(item) ?? "{previous}", item.previous ?? prior),
790
909
  );
791
- const rawResults = await ctx.parallel(steps, { task: rootTask });
910
+ const rawResults = await ctx.parallel(steps, {
911
+ task: rootTask,
912
+ concurrency: step.concurrency ?? options.concurrency,
913
+ failFast: step.failFast ?? options.failFast,
914
+ ...(typeof options.chainDir === "string" ? { chainDir: options.chainDir } : {}),
915
+ } as WorkflowParallelOptions);
792
916
  const persisted = await Promise.all(
793
- rawResults.map((result, taskIndex) => writeDirectOutput(prepared.tasks[taskIndex]!, result)),
917
+ rawResults.map((result, taskIndex) =>
918
+ writeDirectOutput({ ...prepared.tasks[taskIndex]!, chainDir: options.chainDir }, result),
919
+ ),
794
920
  );
795
921
  const worktreeDiffs = collectWorktreeDiffs(prepared, stepOptions.artifacts !== false);
796
922
  return {
@@ -811,9 +937,12 @@ async function runDirectChainStep(
811
937
  try {
812
938
  const rawResult = await ctx.task(
813
939
  preparedStep.name,
814
- directTaskToStep(preparedStep, replaceTaskPlaceholder(prompt, rootTask), preparedStep.previous ?? prior),
940
+ {
941
+ ...directTaskToStep(preparedStep, replaceTaskPlaceholder(prompt, rootTask), preparedStep.previous ?? prior),
942
+ ...(typeof options.chainDir === "string" ? { chainDir: options.chainDir } : {}),
943
+ } as WorkflowTaskOptions,
815
944
  );
816
- const { result, artifact } = await writeDirectOutput(preparedStep, rawResult);
945
+ const { result, artifact } = await writeDirectOutput({ ...preparedStep, chainDir: options.chainDir }, rawResult);
817
946
  const worktreeDiffs = collectWorktreeDiffs(prepared, options.artifacts !== false);
818
947
  return {
819
948
  results: [result],
@@ -924,8 +1053,8 @@ function nextEventLoopTurn(): Promise<void> {
924
1053
  return new Promise((resolve) => setTimeout(resolve, 0));
925
1054
  }
926
1055
 
927
- export async function run(
928
- def: WorkflowDefinition,
1056
+ export async function run<TInputs extends Record<string, unknown>>(
1057
+ def: WorkflowDefinition<TInputs>,
929
1058
  inputs: Record<string, unknown>,
930
1059
  opts: RunOpts = {},
931
1060
  ): Promise<RunResult> {
@@ -996,16 +1125,23 @@ export async function run(
996
1125
 
997
1126
  // 4. Create GraphFrontierTracker and per-run ConcurrencyLimiter
998
1127
  const tracker = new GraphFrontierTracker();
999
- const limiter = createRunLimiter(opts.config?.defaultConcurrency);
1128
+ const inputConcurrency = resolveInputConcurrency(def.inputs, resolvedInputs);
1129
+ const limiter = createRunLimiter(inputConcurrency ?? opts.config?.defaultConcurrency);
1000
1130
  interface ReleaseBarrier {
1001
1131
  readonly promise: Promise<void>;
1002
1132
  readonly resolve: () => void;
1003
1133
  readonly reject: (reason?: unknown) => void;
1004
1134
  }
1005
1135
  const releaseBarriers = new Map<string, ReleaseBarrier>();
1136
+ const cascadePauseOwners = new Map<string, Set<string>>();
1006
1137
 
1007
1138
  const makeReleaseBarrier = (): ReleaseBarrier => {
1008
1139
  const resolver = Promise.withResolvers<void>();
1140
+ // Abort rejects release barriers during kill/shutdown. Some barriers are
1141
+ // only state markers for a paused root/current stage and have no active
1142
+ // waiter, so mark expected cancellation as observed while preserving the
1143
+ // same promise for callers that do await it.
1144
+ void resolver.promise.catch(() => {});
1009
1145
  return { promise: resolver.promise, resolve: resolver.resolve, reject: resolver.reject };
1010
1146
  };
1011
1147
 
@@ -1059,6 +1195,23 @@ export async function run(
1059
1195
  activeStore.recordStageBlocked(runId, stage.id, blockedBy);
1060
1196
  };
1061
1197
 
1198
+ const markCascadePaused = (stageId: string, ownerStageId: string): void => {
1199
+ let owners = cascadePauseOwners.get(stageId);
1200
+ if (!owners) {
1201
+ owners = new Set<string>();
1202
+ cascadePauseOwners.set(stageId, owners);
1203
+ }
1204
+ owners.add(ownerStageId);
1205
+ };
1206
+
1207
+ const releaseCascadePauseOwner = (stageId: string, ownerStageId: string): boolean => {
1208
+ const owners = cascadePauseOwners.get(stageId);
1209
+ if (!owners) return false;
1210
+ const changed = owners.delete(ownerStageId);
1211
+ if (owners.size === 0) cascadePauseOwners.delete(stageId);
1212
+ return changed;
1213
+ };
1214
+
1062
1215
  const releaseStageBarrier = (stageId: string): void => {
1063
1216
  const barrier = releaseBarriers.get(stageId);
1064
1217
  if (!barrier) return;
@@ -1067,12 +1220,14 @@ export async function run(
1067
1220
  };
1068
1221
 
1069
1222
  const cascadePauseFrom = async (pausedStageId: string): Promise<void> => {
1223
+ const stageRegistry = opts.stageControlRegistry ?? defaultStageControlRegistry;
1070
1224
  for (const descendant of descendantsOf(pausedStageId)) {
1071
1225
  if (isTerminalStage(descendant) || descendant.status === "paused" || descendant.status === "blocked") continue;
1072
- if (descendant.status === "running") {
1073
- const descendantHandle = (opts.stageControlRegistry ?? defaultStageControlRegistry).get(runId, descendant.id);
1074
- if (descendantHandle && descendantHandle.status === "running") {
1226
+ const descendantHandle = stageRegistry.get(runId, descendant.id);
1227
+ if (descendantHandle?.isStreaming || descendant.status === "running") {
1228
+ if (descendantHandle && (descendantHandle.status === "running" || descendantHandle.status === "pending")) {
1075
1229
  await descendantHandle.pause();
1230
+ markCascadePaused(descendant.id, pausedStageId);
1076
1231
  }
1077
1232
  continue;
1078
1233
  }
@@ -1080,17 +1235,32 @@ export async function run(
1080
1235
  }
1081
1236
  };
1082
1237
 
1083
- const cascadeResumeFrom = (resumedStageId: string): void => {
1238
+ const cascadeResumeFrom = async (resumedStageId: string): Promise<void> => {
1239
+ const stageRegistry = opts.stageControlRegistry ?? defaultStageControlRegistry;
1084
1240
  for (const descendant of descendantsOf(resumedStageId)) {
1085
- if (isTerminalStage(descendant) || descendant.status !== "blocked") continue;
1086
- if (blockingAncestorFor(descendant) !== undefined) continue;
1087
- if (activeStore.recordStageUnblocked(runId, descendant.id)) {
1088
- releaseStageBarrier(descendant.id);
1241
+ if (isTerminalStage(descendant)) continue;
1242
+ if (descendant.status === "blocked") {
1243
+ if (blockingAncestorFor(descendant) !== undefined) continue;
1244
+ if (activeStore.recordStageUnblocked(runId, descendant.id)) {
1245
+ releaseStageBarrier(descendant.id);
1246
+ }
1247
+ continue;
1248
+ }
1249
+ if (descendant.status === "paused") {
1250
+ const ownedByResumedStage = releaseCascadePauseOwner(descendant.id, resumedStageId);
1251
+ if (!ownedByResumedStage) continue;
1252
+ if (cascadePauseOwners.has(descendant.id)) continue;
1253
+ if (blockingAncestorFor(descendant) !== undefined) continue;
1254
+ const descendantHandle = stageRegistry.get(runId, descendant.id);
1255
+ if (descendantHandle?.status === "paused") {
1256
+ await descendantHandle.resume();
1257
+ }
1089
1258
  }
1090
1259
  }
1091
1260
  };
1092
1261
 
1093
1262
  const rejectReleaseBarriers = (reason: unknown): void => {
1263
+ cascadePauseOwners.clear();
1094
1264
  for (const [stageId, barrier] of releaseBarriers) {
1095
1265
  releaseBarriers.delete(stageId);
1096
1266
  activeStore.recordStageUnblocked(runId, stageId);
@@ -1105,8 +1275,8 @@ export async function run(
1105
1275
  );
1106
1276
 
1107
1277
  // 5. Build WorkflowRunContext
1108
- const ctx: WorkflowRunContext = {
1109
- inputs: resolvedInputs,
1278
+ const ctx: WorkflowRunContext<TInputs> = {
1279
+ inputs: resolvedInputs as TInputs,
1110
1280
  ui: opts.ui ?? makeUnavailableUIContext(),
1111
1281
 
1112
1282
  stage(name: string, options?: StageOptions) {
@@ -1168,6 +1338,15 @@ export async function run(
1168
1338
  activeStore.recordStageAwaitingInput(runId, stageId, false);
1169
1339
  await innerCtx.__dispose();
1170
1340
  };
1341
+ let unregisterStageHandle = (): void => {};
1342
+ let liveHandleReleased = false;
1343
+ const releaseLiveHandle = async (): Promise<void> => {
1344
+ if (liveHandleReleased) return;
1345
+ liveHandleReleased = true;
1346
+ activeStore.recordStageAttachable(runId, stageId, false);
1347
+ unregisterStageHandle();
1348
+ await disposeInnerContext();
1349
+ };
1171
1350
 
1172
1351
  // e. Register a live stage-control handle so attached panes can
1173
1352
  // prompt/steer/pause/resume the underlying Pi session lazily.
@@ -1194,6 +1373,9 @@ export async function run(
1194
1373
  get messages() {
1195
1374
  return innerCtx.messages;
1196
1375
  },
1376
+ get agentSession() {
1377
+ return innerCtx.__agentSession();
1378
+ },
1197
1379
  async ensureAttached() {
1198
1380
  await innerCtx.__ensureSession();
1199
1381
  const meta = innerCtx.__sessionMeta();
@@ -1215,20 +1397,29 @@ export async function run(
1215
1397
  await innerCtx.followUp(text);
1216
1398
  },
1217
1399
  async pause() {
1400
+ const statusBeforePause = stageSnapshot.status;
1218
1401
  const changed = activeStore.recordStagePaused(runId, stageId);
1219
- if (changed) await cascadePauseFrom(stageId);
1220
- await innerCtx.__requestPause();
1402
+ if (changed) {
1403
+ ensureReleaseBarrier(stageId);
1404
+ await cascadePauseFrom(stageId);
1405
+ }
1406
+ if (statusBeforePause === "pending" || statusBeforePause === "running" || innerCtx.isStreaming) {
1407
+ await innerCtx.__requestPause();
1408
+ }
1221
1409
  },
1222
1410
  async resume(message?: string) {
1223
1411
  const changed = activeStore.recordStageResumed(runId, stageId);
1224
- if (changed) cascadeResumeFrom(stageId);
1412
+ if (changed) {
1413
+ releaseStageBarrier(stageId);
1414
+ await cascadeResumeFrom(stageId);
1415
+ }
1225
1416
  await innerCtx.__resume(message);
1226
1417
  },
1227
1418
  subscribe(listener: AgentSessionEventListener) {
1228
1419
  return innerCtx.subscribe(listener);
1229
1420
  },
1230
1421
  };
1231
- const unregisterStageHandle = stageRegistry.register(handle);
1422
+ unregisterStageHandle = stageRegistry.register(handle);
1232
1423
 
1233
1424
  // f. Record stage start in store (as pending), call onStageStart.
1234
1425
  activeStore.recordStageStart(runId, stageSnapshot);
@@ -1239,9 +1430,10 @@ export async function run(
1239
1430
  }
1240
1431
 
1241
1432
 
1242
- const runTrackedStageCall = async (call: () => Promise<string>): Promise<string> => {
1243
- const barrier = releaseBarriers.get(stageId);
1244
- if (barrier) {
1433
+ const waitForStageRelease = async (): Promise<void> => {
1434
+ while (true) {
1435
+ const barrier = releaseBarriers.get(stageId);
1436
+ if (!barrier) return;
1245
1437
  try {
1246
1438
  await barrier.promise;
1247
1439
  } catch (err) {
@@ -1251,10 +1443,21 @@ export async function run(
1251
1443
  throw err;
1252
1444
  }
1253
1445
  }
1446
+ };
1447
+
1448
+ const runTrackedStageCall = async (call: () => Promise<string>): Promise<string> => {
1449
+ await waitForStageRelease();
1254
1450
 
1255
1451
  // Block here until a concurrency slot is available for this run.
1256
1452
  await limiter.acquire();
1257
1453
 
1454
+ try {
1455
+ await waitForStageRelease();
1456
+ } catch (err) {
1457
+ limiter.release();
1458
+ throw err;
1459
+ }
1460
+
1258
1461
  stageSnapshot.status = "running";
1259
1462
  stageSnapshot.startedAt = Date.now();
1260
1463
  activeStore.recordStageStart(runId, stageSnapshot);
@@ -1310,8 +1513,10 @@ export async function run(
1310
1513
  }
1311
1514
  return result;
1312
1515
  } catch (err) {
1313
- stageSnapshot.status = "failed";
1314
- stageSnapshot.error = err instanceof Error ? err.message : String(err);
1516
+ if (!ownController.signal.aborted) {
1517
+ stageSnapshot.status = "failed";
1518
+ stageSnapshot.error = err instanceof Error ? err.message : String(err);
1519
+ }
1315
1520
  throw err;
1316
1521
  } finally {
1317
1522
  stageSnapshot.endedAt = Date.now();
@@ -1343,12 +1548,39 @@ export async function run(
1343
1548
  }
1344
1549
 
1345
1550
  tracker.onSettle(stageId);
1346
- activeStore.recordStageAttachable(runId, stageId, false);
1347
- unregisterStageHandle();
1348
- try {
1349
- await disposeInnerContext();
1350
- } finally {
1551
+ if (stageSnapshot.attached === true) {
1552
+ let unsubscribeDetach: (() => void) | undefined;
1553
+ let abortListener: (() => void) | undefined;
1554
+ const releaseWhenDetached = (force = false): void => {
1555
+ const currentRun = activeStore.runs().find((r) => r.id === runId);
1556
+ const currentStage = currentRun?.stages.find((s) => s.id === stageId);
1557
+ if (!force && currentStage?.attached === true) return;
1558
+ unsubscribeDetach?.();
1559
+ unsubscribeDetach = undefined;
1560
+ if (abortListener) {
1561
+ ownController.signal.removeEventListener("abort", abortListener);
1562
+ abortListener = undefined;
1563
+ }
1564
+ void releaseLiveHandle().catch(() => {});
1565
+ };
1566
+ unsubscribeDetach = activeStore.subscribe(() => releaseWhenDetached());
1567
+ abortListener = () => releaseWhenDetached(true);
1568
+ if (ownController.signal.aborted) releaseWhenDetached(true);
1569
+ else {
1570
+ ownController.signal.addEventListener(
1571
+ "abort",
1572
+ abortListener,
1573
+ { once: true },
1574
+ );
1575
+ }
1576
+ releaseWhenDetached();
1351
1577
  limiter.release();
1578
+ } else {
1579
+ try {
1580
+ await releaseLiveHandle();
1581
+ } finally {
1582
+ limiter.release();
1583
+ }
1352
1584
  }
1353
1585
  }
1354
1586
  };
@@ -1438,28 +1670,52 @@ export async function run(
1438
1670
  },
1439
1671
 
1440
1672
  async task(name: string, options: WorkflowTaskOptions): Promise<WorkflowTaskResult> {
1441
- const stage = ctx.stage(name, taskStageOptions(options));
1442
- const rawText = await stage.prompt(applyTaskContext(taskPrompt(options), taskPrevious(options)));
1443
- const text = truncateTaskOutput(rawText, options.maxOutput);
1444
- const sessionId = (() => {
1445
- try {
1446
- return stage.sessionId;
1447
- } catch {
1448
- return undefined;
1449
- }
1450
- })();
1451
- const stageMeta = (stage as InternalStageContext).__modelFallbackMeta?.() ?? {};
1452
- return {
1453
- name,
1454
- stageName: name,
1455
- text,
1456
- ...(sessionId !== undefined ? { sessionId } : {}),
1457
- ...(stage.sessionFile !== undefined ? { sessionFile: stage.sessionFile } : {}),
1458
- ...(stageMeta.model !== undefined ? { model: stageMeta.model } : {}),
1459
- ...(stageMeta.attemptedModels !== undefined ? { attemptedModels: stageMeta.attemptedModels } : {}),
1460
- ...(stageMeta.modelAttempts !== undefined ? { modelAttempts: stageMeta.modelAttempts } : {}),
1461
- ...(stageMeta.warnings !== undefined ? { warnings: stageMeta.warnings } : {}),
1673
+ const runTaskOnce = async (taskOptions: WorkflowTaskOptions): Promise<WorkflowTaskResult> => {
1674
+ const stage = ctx.stage(name, taskStageOptions(taskOptions));
1675
+ const rawText = await stage.prompt(
1676
+ applyTaskContext(`${taskReadInstruction(taskOptions)}${taskPrompt(taskOptions)}`, taskPrevious(taskOptions)),
1677
+ taskPromptOptions(taskOptions),
1678
+ );
1679
+ const text = truncateTaskOutput(rawText, taskOptions.maxOutput);
1680
+ const sessionId = (() => {
1681
+ try {
1682
+ return stage.sessionId;
1683
+ } catch {
1684
+ return undefined;
1685
+ }
1686
+ })();
1687
+ const stageMeta = (stage as InternalStageContext).__modelFallbackMeta?.() ?? {};
1688
+ return {
1689
+ name,
1690
+ stageName: name,
1691
+ text,
1692
+ ...(sessionId !== undefined ? { sessionId } : {}),
1693
+ ...(stage.sessionFile !== undefined ? { sessionFile: stage.sessionFile } : {}),
1694
+ ...(stageMeta.model !== undefined ? { model: stageMeta.model } : {}),
1695
+ ...(stageMeta.attemptedModels !== undefined ? { attemptedModels: stageMeta.attemptedModels } : {}),
1696
+ ...(stageMeta.modelAttempts !== undefined ? { modelAttempts: stageMeta.modelAttempts } : {}),
1697
+ ...(stageMeta.warnings !== undefined ? { warnings: stageMeta.warnings } : {}),
1698
+ };
1462
1699
  };
1700
+
1701
+ if (options.worktree !== true) return runTaskOnce(options);
1702
+
1703
+ const prepared = prepareDirectWorktrees(
1704
+ [{ ...options, name }],
1705
+ { ...options, worktree: true },
1706
+ `${runId}-${name}-${crypto.randomUUID()}`,
1707
+ name,
1708
+ );
1709
+ const preparedTask = prepared.tasks[0]!;
1710
+ try {
1711
+ const result = await runTaskOnce(preparedTask);
1712
+ const worktreeDiffs = collectWorktreeDiffs(prepared, options.artifacts !== false);
1713
+ return worktreeDiffs.artifacts.length === 0
1714
+ ? result
1715
+ : { ...result, artifacts: [...(result.artifacts ?? []), ...worktreeDiffs.artifacts] };
1716
+ } finally {
1717
+ if (prepared.setup !== undefined) cleanupWorktrees(prepared.setup);
1718
+ }
1463
1719
  },
1464
1720
 
1465
1721
  async chain(steps: readonly WorkflowTaskStep[], options: WorkflowChainOptions = {}): Promise<WorkflowTaskResult[]> {
@@ -1469,19 +1725,23 @@ export async function run(
1469
1725
  const explicitPrevious = taskPrevious(step);
1470
1726
  const previous = explicitPrevious ?? (index > 0 ? results[index - 1] : undefined);
1471
1727
  const prompt = replaceTaskPlaceholder(chainStepPrompt(step, index), options.task ?? "");
1472
- results.push(await ctx.task(step.name, taskOptionsFromStep(step, prompt, previous)));
1728
+ results.push(await ctx.task(
1729
+ step.name,
1730
+ taskWithSharedDefaults(taskOptionsFromStep(step, prompt, previous), options),
1731
+ ));
1473
1732
  }
1474
1733
  return results;
1475
1734
  },
1476
1735
 
1477
1736
  async parallel(steps: readonly WorkflowTaskStep[], options: WorkflowParallelOptions = {}): Promise<WorkflowTaskResult[]> {
1478
1737
  const fallback = parallelFallbackTask(steps, options);
1479
- return Promise.all(
1480
- steps.map((step) => {
1481
- const prompt = replaceTaskPlaceholder(step.prompt ?? step.task ?? fallback, options.task ?? fallback);
1482
- return ctx.task(step.name, taskOptionsFromStep(step, prompt, taskPrevious(step)));
1483
- }),
1484
- );
1738
+ return mapParallelSteps(steps, options.concurrency, options.failFast, async (step) => {
1739
+ const prompt = replaceTaskPlaceholder(step.prompt ?? step.task ?? fallback, options.task ?? fallback);
1740
+ return ctx.task(
1741
+ step.name,
1742
+ taskWithSharedDefaults(taskOptionsFromStep(step, prompt, taskPrevious(step)), options),
1743
+ );
1744
+ });
1485
1745
  },
1486
1746
  };
1487
1747
 
@@ -50,6 +50,8 @@ export interface StageControlHandle {
50
50
  readonly sessionFile: string | undefined;
51
51
  readonly isStreaming: boolean;
52
52
  readonly messages: AgentSession["messages"];
53
+ /** Live coding-agent session when available, used by embedded chat/footer UI. */
54
+ readonly agentSession?: AgentSession;
53
55
  /** Ensure the SDK session exists. Cheap when already attached. */
54
56
  ensureAttached(): Promise<void>;
55
57
  /** Send a prompt. Use only when the stage is idle / not streaming. */