@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
@@ -112,6 +112,8 @@ export interface InternalStageContext extends StageContext {
112
112
  * `undefined` keys when the session has not yet been created.
113
113
  */
114
114
  __sessionMeta(): { sessionId: string | undefined; sessionFile: string | undefined };
115
+ /** Internal: live coding-agent session when the adapter returned one. */
116
+ __agentSession(): AgentSession | undefined;
115
117
  /** Internal: selected/effective model and fallback attempt metadata. */
116
118
  __modelFallbackMeta(): StageModelFallbackMeta;
117
119
  /**
@@ -194,6 +196,20 @@ function lastAssistantTextFromMessages(messages: AgentSession["messages"]): stri
194
196
  return undefined;
195
197
  }
196
198
 
199
+ function asAgentSession(activeSession: StageSessionRuntime | undefined): AgentSession | undefined {
200
+ if (!activeSession) return undefined;
201
+ const candidate = activeSession as StageSessionRuntime & Partial<Pick<AgentSession, "state" | "sessionManager" | "modelRegistry" | "getContextUsage">>;
202
+ if (
203
+ candidate.state !== undefined &&
204
+ candidate.sessionManager !== undefined &&
205
+ candidate.modelRegistry !== undefined &&
206
+ typeof candidate.getContextUsage === "function"
207
+ ) {
208
+ return candidate as AgentSession;
209
+ }
210
+ return undefined;
211
+ }
212
+
197
213
  function lastAssistantTextFromSession(
198
214
  activeSession: StageSessionRuntime | undefined,
199
215
  fallback: string | undefined,
@@ -369,17 +385,25 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
369
385
  deferred: PromiseWithResolvers<{ message?: string }>;
370
386
  } | null = null;
371
387
 
372
- // Wire the executor's abort signal to the pause deferred so a kill
373
- // (or other forced abort) doesn't leave a paused stage hanging on a
374
- // resume signal that will never arrive.
388
+ // Wire the executor's abort signal to the live SDK session and pause
389
+ // deferred so a kill (or other forced abort) doesn't leave a paused stage
390
+ // hanging on a resume signal that will never arrive. Re-use the abort
391
+ // reason instead of manufacturing a stage-specific error; shutdown/kill is
392
+ // expected cancellation and should not surface as a noisy per-stage failure.
375
393
  if (signal) {
394
+ const abortReason = (): Error | DOMException | string => {
395
+ const reason = signal.reason;
396
+ if (reason instanceof Error || reason instanceof DOMException || typeof reason === "string") {
397
+ return reason;
398
+ }
399
+ return new DOMException("workflow killed", "AbortError");
400
+ };
376
401
  const onAbort = (): void => {
402
+ void session?.abort().catch(() => {});
377
403
  if (!pauseRequest) return;
378
404
  const req = pauseRequest;
379
405
  pauseRequest = null;
380
- req.deferred.reject(
381
- new Error(`pi-workflows: stage "${stageName}" aborted while paused`),
382
- );
406
+ req.deferred.reject(abortReason());
383
407
  };
384
408
  if (signal.aborted) onAbort();
385
409
  else signal.addEventListener("abort", onAbort, { once: true });
@@ -475,8 +499,22 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
475
499
  // accumulated assistant text when resume carries no message.
476
500
  let nextText: string | undefined = initialText;
477
501
  while (nextText !== undefined) {
502
+ const pendingPauseBeforePrompt = pauseRequest;
503
+ if (pendingPauseBeforePrompt) {
504
+ const { message } = await pendingPauseBeforePrompt.deferred.promise;
505
+ nextText = message;
506
+ if (nextText === undefined) return;
507
+ continue;
508
+ }
478
509
  try {
479
510
  await activeSession.prompt(nextText, sdkOptions);
511
+ const pendingPauseAfterPrompt = pauseRequest;
512
+ if (pendingPauseAfterPrompt) {
513
+ const { message } = await pendingPauseAfterPrompt.deferred.promise;
514
+ nextText = message;
515
+ if (nextText === undefined) return;
516
+ continue;
517
+ }
480
518
  nextText = undefined;
481
519
  } catch (err) {
482
520
  if (pauseRequest) {
@@ -669,6 +707,10 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
669
707
  };
670
708
  },
671
709
 
710
+ __agentSession() {
711
+ return asAgentSession(session);
712
+ },
713
+
672
714
  __modelFallbackMeta() {
673
715
  const attemptedModels = modelAttempts.map((attempt) => attempt.model);
674
716
  const model = selectedModel ?? workflowModelId(session?.model);
@@ -682,7 +724,13 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
682
724
 
683
725
  async __requestPause() {
684
726
  if (pauseRequest) return;
685
- pauseRequest = { deferred: Promise.withResolvers<{ message?: string }>() };
727
+ const deferred = Promise.withResolvers<{ message?: string }>();
728
+ // A shutdown may reject this deferred when no prompt awaiter is actively
729
+ // observing it (for example a paused pending live stream). Keep the
730
+ // original promise for real waiters, but mark expected cancellation as
731
+ // observed so app-exit cleanup stays quiet.
732
+ void deferred.promise.catch(() => {});
733
+ pauseRequest = { deferred };
686
734
  // Best-effort: stop the current Pi op so the awaiter races abort.
687
735
  // The executor's `runTrackedStageCall` re-issues the call once
688
736
  // `__resume()` settles `pauseRequest.deferred`.
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Programmatic workflow runner.
3
3
  *
4
- * This helper executes a named workflow from an explicit definition object.
4
+ * This helper executes named workflows and direct workflow task definitions
5
+ * from an explicit object that mirrors the workflow tool surface.
5
6
  */
6
7
 
7
8
  import { homedir } from "node:os";
8
- import { run, type RunOpts as ExecutorRunOptions } from "../foreground/executor.js";
9
+ import { run, runChain, runParallel, runTask, type RunOpts as ExecutorRunOptions } from "../foreground/executor.js";
9
10
  import { buildRuntimeAdapters, type RuntimeAdapterBuildOptions, type RuntimeWiringSurface } from "../../extension/wiring.js";
10
11
  import { discoverWorkflows } from "../../extension/discovery.js";
11
12
  import { createStore } from "../../shared/store.js";
@@ -14,15 +15,38 @@ import { validateInputs, type ValidationError } from "./validate-inputs.js";
14
15
  import type { CreateAgentSessionOptions } from "@bastani/atomic";
15
16
  import type { StageSessionRuntime } from "../foreground/stage-runner.js";
16
17
  import type {
18
+ StageOptions,
19
+ WorkflowChainStep,
17
20
  WorkflowDetails,
18
21
  WorkflowDetailsStatus,
22
+ WorkflowDirectOptions,
23
+ WorkflowDirectTaskItem,
19
24
  WorkflowInputSchema,
25
+ WorkflowMaxOutput,
26
+ WorkflowOutputMode,
20
27
  } from "../../shared/types.js";
21
28
 
22
- export interface WorkflowDefinition {
23
- mode: "workflow";
24
- workflow: string;
29
+ export interface WorkflowDefinition extends StageOptions {
30
+ mode?: "workflow" | "named" | "single" | "parallel" | "chain";
31
+ workflow?: string;
25
32
  inputs?: Record<string, unknown>;
33
+ /** Direct single-task mode, or root task text for direct chain/parallel execution. */
34
+ task?: WorkflowDirectTaskItem | string;
35
+ /** Direct top-level parallel mode. */
36
+ tasks?: readonly WorkflowDirectTaskItem[];
37
+ /** Direct sequential/parallel chain mode. */
38
+ chain?: readonly WorkflowChainStep[];
39
+ chainName?: string;
40
+ concurrency?: number;
41
+ failFast?: boolean;
42
+ /** Chain-only shared artifact directory for relative reads, outputs, and worktree diffs. */
43
+ chainDir?: string;
44
+ reads?: readonly string[] | false;
45
+ output?: string | false;
46
+ outputMode?: WorkflowOutputMode;
47
+ worktree?: boolean;
48
+ maxOutput?: WorkflowMaxOutput;
49
+ artifacts?: boolean;
26
50
  }
27
51
 
28
52
  export type WorkflowRunOptions = Omit<ExecutorRunOptions, "adapters" | "store">;
@@ -37,7 +61,10 @@ export interface WorkflowOptions {
37
61
  stubAgent?: boolean;
38
62
  }
39
63
 
40
- function runOptionsWithAdapters(options: WorkflowOptions): ExecutorRunOptions {
64
+ function runOptionsWithAdapters(
65
+ options: WorkflowOptions,
66
+ definition?: WorkflowDefinition,
67
+ ): ExecutorRunOptions {
41
68
  const adapterOptions = options.stubAgent === true
42
69
  ? {
43
70
  ...options.adapterOptions,
@@ -45,8 +72,26 @@ function runOptionsWithAdapters(options: WorkflowOptions): ExecutorRunOptions {
45
72
  }
46
73
  : options.adapterOptions;
47
74
 
75
+ const argConcurrency =
76
+ typeof definition?.concurrency === "number" && Number.isFinite(definition.concurrency)
77
+ ? Math.max(1, Math.floor(definition.concurrency))
78
+ : undefined;
79
+ const config = argConcurrency === undefined
80
+ ? options.runOptions?.config
81
+ : {
82
+ maxDepth: options.runOptions?.config?.maxDepth ?? 4,
83
+ defaultConcurrency: argConcurrency,
84
+ persistRuns: options.runOptions?.config?.persistRuns ?? true,
85
+ statusFile: options.runOptions?.config?.statusFile ?? false,
86
+ ...(options.runOptions?.config?.statusFilePath !== undefined
87
+ ? { statusFilePath: options.runOptions.config.statusFilePath }
88
+ : {}),
89
+ resumeInFlight: options.runOptions?.config?.resumeInFlight ?? ("ask" as const),
90
+ };
91
+
48
92
  return {
49
93
  ...options.runOptions,
94
+ ...(config !== undefined ? { config } : {}),
50
95
  adapters: buildRuntimeAdapters(options.pi ?? {}, adapterOptions),
51
96
  store: createStore(),
52
97
  };
@@ -97,19 +142,77 @@ async function createStubAgentSession(
97
142
  return { session };
98
143
  }
99
144
 
145
+ function withoutUndefinedProperties<T extends object>(value: T): Partial<T> {
146
+ return Object.fromEntries(
147
+ Object.entries(value).filter(([, field]) => field !== undefined),
148
+ ) as Partial<T>;
149
+ }
150
+
151
+ function directOptions(definition: WorkflowDefinition): WorkflowDirectOptions {
152
+ const {
153
+ mode: _mode,
154
+ workflow: _workflow,
155
+ inputs: _inputs,
156
+ task,
157
+ tasks: _tasks,
158
+ chain: _chain,
159
+ chainName,
160
+ concurrency,
161
+ failFast,
162
+ chainDir,
163
+ reads,
164
+ output,
165
+ outputMode,
166
+ worktree,
167
+ maxOutput,
168
+ artifacts,
169
+ ...stageOptions
170
+ } = definition;
171
+
172
+ return {
173
+ ...withoutUndefinedProperties(stageOptions),
174
+ ...(typeof task === "string" ? { task } : {}),
175
+ ...(typeof chainName === "string" ? { chainName } : {}),
176
+ ...(typeof concurrency === "number" ? { concurrency } : {}),
177
+ ...(typeof failFast === "boolean" ? { failFast } : {}),
178
+ ...(typeof chainDir === "string" ? { chainDir } : {}),
179
+ ...(reads !== undefined ? { reads } : {}),
180
+ ...(output !== undefined ? { output } : {}),
181
+ ...(outputMode !== undefined ? { outputMode } : {}),
182
+ ...(typeof worktree === "boolean" ? { worktree } : {}),
183
+ ...(maxOutput !== undefined ? { maxOutput } : {}),
184
+ ...(typeof artifacts === "boolean" ? { artifacts } : {}),
185
+ };
186
+ }
187
+
188
+ function hasDirectExecutionMode(definition: WorkflowDefinition): boolean {
189
+ return (
190
+ definition.mode === "single" ||
191
+ definition.mode === "parallel" ||
192
+ definition.mode === "chain" ||
193
+ (definition.task !== undefined && typeof definition.task === "object") ||
194
+ Array.isArray(definition.tasks) ||
195
+ Array.isArray(definition.chain)
196
+ );
197
+ }
198
+
100
199
  async function runNamedWorkflow(
101
200
  definition: WorkflowDefinition,
102
201
  options: WorkflowOptions,
103
202
  runOptions: ExecutorRunOptions,
104
203
  ): Promise<WorkflowDetails> {
204
+ const workflowName = definition.workflow;
205
+ if (typeof workflowName !== "string" || workflowName.length === 0) {
206
+ throw new Error('Workflow definition must include "workflow" for named workflow execution.');
207
+ }
105
208
  const discovery = await discoverWorkflows({
106
209
  cwd: options.cwd ?? process.cwd(),
107
210
  homeDir: options.homeDir ?? homedir(),
108
211
  });
109
- const workflow = discovery.registry.get(definition.workflow);
212
+ const workflow = discovery.registry.get(workflowName);
110
213
  if (workflow === undefined) {
111
214
  const available = discovery.registry.names();
112
- throw new Error(`Workflow not found: "${definition.workflow}". Available: ${available.length > 0 ? available.join(", ") : "(none)"}`);
215
+ throw new Error(`Workflow not found: "${workflowName}". Available: ${available.length > 0 ? available.join(", ") : "(none)"}`);
113
216
  }
114
217
  const inputs = definition.inputs ?? {};
115
218
  const errors = validateInputs(workflow.inputs, inputs);
@@ -161,10 +264,43 @@ function toWorkflowDetailsStatus(status: string): WorkflowDetailsStatus {
161
264
  }
162
265
  }
163
266
 
267
+ async function runDirectWorkflow(
268
+ definition: WorkflowDefinition,
269
+ runOptions: ExecutorRunOptions,
270
+ ): Promise<WorkflowDetails> {
271
+ const options = directOptions(definition);
272
+
273
+ if (Array.isArray(definition.chain) || definition.mode === "chain") {
274
+ if (!Array.isArray(definition.chain)) {
275
+ throw new Error('Direct chain workflow definitions must include "chain".');
276
+ }
277
+ return runChain(definition.chain, options, runOptions);
278
+ }
279
+
280
+ if (Array.isArray(definition.tasks) || definition.mode === "parallel") {
281
+ if (!Array.isArray(definition.tasks)) {
282
+ throw new Error('Direct parallel workflow definitions must include "tasks".');
283
+ }
284
+ return runParallel(definition.tasks, options, runOptions);
285
+ }
286
+
287
+ if (typeof definition.task === "object") {
288
+ return runTask(definition.task, options, runOptions);
289
+ }
290
+
291
+ if (typeof definition.task === "string" && definition.mode === "single") {
292
+ return runTask({ name: "task", task: definition.task }, options, runOptions);
293
+ }
294
+
295
+ throw new Error('Direct workflow definitions must include "task", "tasks", or "chain".');
296
+ }
297
+
164
298
  export async function runWorkflow(
165
299
  definition: WorkflowDefinition,
166
300
  options: WorkflowOptions = {},
167
301
  ): Promise<WorkflowDetails> {
168
- const runOptions = runOptionsWithAdapters(options);
169
- return runNamedWorkflow(definition, options, runOptions);
302
+ const runOptions = runOptionsWithAdapters(options, definition);
303
+ return hasDirectExecutionMode(definition)
304
+ ? runDirectWorkflow(definition, runOptions)
305
+ : runNamedWorkflow(definition, options, runOptions);
170
306
  }
@@ -39,6 +39,12 @@ export interface Store {
39
39
  result?: Record<string, unknown>,
40
40
  error?: string,
41
41
  ): boolean;
42
+ /**
43
+ * Remove a run from live workflow history/status. Any pending HIL prompt
44
+ * waiter is rejected because the workflow will not resume through that path.
45
+ * Returns `true` when a run was removed, `false` when the id is unknown.
46
+ */
47
+ removeRun(runId: string): boolean;
42
48
  recordNotice(notice: WorkflowNotice): void;
43
49
  /**
44
50
  * Acknowledges a notice by id.
@@ -303,6 +309,29 @@ export function createStore(): Store {
303
309
  return true;
304
310
  },
305
311
 
312
+ removeRun(runId: string): boolean {
313
+ const index = _runs.findIndex((r) => r.id === runId);
314
+ if (index < 0) return false;
315
+ const run = _runs[index]!;
316
+ const pending = run.pendingPrompt;
317
+ if (pending) {
318
+ const entry = _resolvers.get(pending.id);
319
+ if (entry) {
320
+ _resolvers.delete(pending.id);
321
+ entry.reject(
322
+ new Error(`pi-workflows: run ${runId} was removed before prompt resolved`),
323
+ );
324
+ }
325
+ }
326
+ _runs.splice(index, 1);
327
+ for (let i = _notices.length - 1; i >= 0; i--) {
328
+ if (_notices[i]?.runId === runId) _notices.splice(i, 1);
329
+ }
330
+ _version++;
331
+ notify();
332
+ return true;
333
+ },
334
+
306
335
  recordNotice(notice: WorkflowNotice): void {
307
336
  _notices.push(notice);
308
337
  _version++;
@@ -264,14 +264,35 @@ export interface WorkflowTaskStep extends WorkflowTaskOptions {
264
264
  name: string;
265
265
  }
266
266
 
267
- export interface WorkflowChainOptions {
267
+ export interface WorkflowSharedTaskDefaults extends StageOptions {
268
+ /** Optional default output artifact path for steps that do not set one. */
269
+ output?: string | false;
270
+ /** Default output mode for steps that do not set one. */
271
+ outputMode?: WorkflowOutputMode;
272
+ /** Files the task should read before responding; relative paths resolve via chainDir for chains, otherwise cwd. */
273
+ reads?: readonly string[] | false;
274
+ /** Workflow-owned isolation flag; not forwarded to createAgentSession(). */
275
+ worktree?: boolean;
276
+ /** Default output truncation limits for steps that do not set one. */
277
+ maxOutput?: WorkflowMaxOutput;
278
+ /** Whether to include debug artifacts such as sessions and worktree diffs. */
279
+ artifacts?: boolean;
280
+ }
281
+
282
+ export interface WorkflowChainOptions extends WorkflowSharedTaskDefaults {
268
283
  /** Shared/root task used for `{task}` in chain steps. */
269
284
  task?: string;
285
+ /** Shared artifact directory for relative reads, outputs, and worktree diffs. */
286
+ chainDir?: string;
270
287
  }
271
288
 
272
- export interface WorkflowParallelOptions {
289
+ export interface WorkflowParallelOptions extends WorkflowSharedTaskDefaults {
273
290
  /** Shared fallback task for parallel steps without their own task. */
274
291
  task?: string;
292
+ /** Maximum number of parallel steps to schedule concurrently. */
293
+ concurrency?: number;
294
+ /** Stop scheduling additional steps after the first failure. Default: true. */
295
+ failFast?: boolean;
275
296
  }
276
297
 
277
298
  export type WorkflowOutputMode = "inline" | "file-only";
@@ -379,7 +400,6 @@ export interface WorkflowTaskSessionFields {
379
400
  output?: string | false;
380
401
  outputMode?: WorkflowOutputMode;
381
402
  reads?: readonly string[] | false;
382
- progress?: boolean;
383
403
  /** Workflow-owned isolation flag; not forwarded to createAgentSession(). */
384
404
  worktree?: boolean;
385
405
  maxOutput?: WorkflowMaxOutput;
@@ -412,10 +432,11 @@ export interface WorkflowDirectOptions extends StageOptions {
412
432
  chainName?: string;
413
433
  concurrency?: number;
414
434
  failFast?: boolean;
435
+ /** Chain-only shared artifact directory for relative reads, outputs, and worktree diffs. */
415
436
  chainDir?: string;
437
+ reads?: readonly string[] | false;
416
438
  output?: string | false;
417
439
  outputMode?: WorkflowOutputMode;
418
- progress?: boolean;
419
440
  worktree?: boolean;
420
441
  maxOutput?: WorkflowMaxOutput;
421
442
  artifacts?: boolean;
@@ -21,6 +21,59 @@ interface Cell {
21
21
  fg: string | null;
22
22
  }
23
23
 
24
+ type Dir = "u" | "d" | "l" | "r";
25
+
26
+ function dirsForGlyph(ch: string): Set<Dir> {
27
+ switch (ch) {
28
+ case "│":
29
+ return new Set(["u", "d"]);
30
+ case "─":
31
+ return new Set(["l", "r"]);
32
+ case "╭":
33
+ case "┌":
34
+ return new Set(["d", "r"]);
35
+ case "╮":
36
+ case "┐":
37
+ return new Set(["d", "l"]);
38
+ case "╰":
39
+ case "└":
40
+ return new Set(["u", "r"]);
41
+ case "╯":
42
+ case "┘":
43
+ return new Set(["u", "l"]);
44
+ case "├":
45
+ return new Set(["u", "d", "r"]);
46
+ case "┤":
47
+ return new Set(["u", "d", "l"]);
48
+ case "┬":
49
+ return new Set(["d", "l", "r"]);
50
+ case "┴":
51
+ return new Set(["u", "l", "r"]);
52
+ case "┼":
53
+ return new Set(["u", "d", "l", "r"]);
54
+ default:
55
+ return new Set();
56
+ }
57
+ }
58
+
59
+ function glyphForDirs(dirs: Set<Dir>): string {
60
+ const has = (...want: Dir[]) => want.every((d) => dirs.has(d));
61
+ if (has("u", "d", "l", "r")) return "┼";
62
+ if (has("u", "d", "r")) return "├";
63
+ if (has("u", "d", "l")) return "┤";
64
+ if (has("d", "l", "r")) return "┬";
65
+ if (has("u", "l", "r")) return "┴";
66
+ if (has("u", "d")) return "│";
67
+ if (has("l", "r")) return "─";
68
+ if (has("d", "r")) return "┌";
69
+ if (has("d", "l")) return "┐";
70
+ if (has("u", "r")) return "└";
71
+ if (has("u", "l")) return "┘";
72
+ if (has("u") || has("d")) return "│";
73
+ if (has("l") || has("r")) return "─";
74
+ return " ";
75
+ }
76
+
24
77
  export class GraphCanvas {
25
78
  /** rowIdx → colIdx → Cell. Sparse — empty cells render as a single space. */
26
79
  private rows: Map<number, Map<number, Cell>> = new Map();
@@ -39,6 +92,20 @@ export class GraphCanvas {
39
92
  if (col > this.maxCol) this.maxCol = col;
40
93
  }
41
94
 
95
+ getCell(row: number, col: number): string | null {
96
+ return this.rows.get(row)?.get(col)?.ch ?? null;
97
+ }
98
+
99
+ mergeCell(row: number, col: number, dirs: readonly Dir[], fg: string | null): void {
100
+ if (row < 0 || col < 0) return;
101
+ const existing = this.getCell(row, col);
102
+ const merged = new Set<Dir>([
103
+ ...(existing == null ? [] : dirsForGlyph(existing)),
104
+ ...dirs,
105
+ ]);
106
+ this.setCell(row, col, glyphForDirs(merged), fg);
107
+ }
108
+
42
109
  /**
43
110
  * Paint a single character cell, but only if the target cell is empty.
44
111
  * Used by the edge plotter so a corner glyph never clobbers a node-card
@@ -58,13 +125,13 @@ export class GraphCanvas {
58
125
  hline(row: number, fromCol: number, toCol: number, fg: string | null): void {
59
126
  const lo = Math.min(fromCol, toCol);
60
127
  const hi = Math.max(fromCol, toCol);
61
- for (let c = lo; c <= hi; c++) this.setCellIfEmpty(row, c, "", fg);
128
+ for (let c = lo; c <= hi; c++) this.mergeCell(row, c, ["l", "r"], fg);
62
129
  }
63
130
 
64
131
  vline(col: number, fromRow: number, toRow: number, fg: string | null): void {
65
132
  const lo = Math.min(fromRow, toRow);
66
133
  const hi = Math.max(fromRow, toRow);
67
- for (let r = lo; r <= hi; r++) this.setCellIfEmpty(r, col, "", fg);
134
+ for (let r = lo; r <= hi; r++) this.mergeCell(r, col, ["u", "d"], fg);
68
135
  }
69
136
 
70
137
  /** Materialise the canvas as one ANSI-styled string per row. */