@bastani/atomic 0.8.21 → 0.8.22-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 (235) hide show
  1. package/CHANGELOG.md +40 -9
  2. package/dist/builtin/intercom/broker/broker.ts +3 -3
  3. package/dist/builtin/intercom/config.ts +3 -3
  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 +2 -2
  7. package/dist/builtin/mcp/host-html-template.ts +0 -3
  8. package/dist/builtin/mcp/package.json +1 -1
  9. package/dist/builtin/subagents/CHANGELOG.md +13 -4
  10. package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -9
  11. package/dist/builtin/subagents/agents/debugger.md +6 -6
  12. package/dist/builtin/subagents/package.json +1 -1
  13. package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +1 -1
  14. package/dist/builtin/subagents/skills/browser-use/SKILL.md +234 -0
  15. package/dist/builtin/subagents/skills/browser-use/references/cdp-python.md +76 -0
  16. package/dist/builtin/subagents/skills/browser-use/references/multi-session.md +92 -0
  17. package/dist/builtin/subagents/skills/subagent/SKILL.md +4 -4
  18. package/dist/builtin/subagents/src/agents/skills.ts +19 -1
  19. package/dist/builtin/subagents/src/extension/index.ts +24 -22
  20. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +7 -1
  21. package/dist/builtin/subagents/src/runs/background/async-execution.ts +23 -7
  22. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +98 -3
  23. package/dist/builtin/subagents/src/runs/background/async-status.ts +3 -1
  24. package/dist/builtin/subagents/src/runs/background/run-status.ts +1 -1
  25. package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +3 -0
  26. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +37 -12
  27. package/dist/builtin/subagents/src/runs/foreground/chain-clarify.ts +15 -15
  28. package/dist/builtin/subagents/src/runs/foreground/execution.ts +26 -2
  29. package/dist/builtin/subagents/src/runs/shared/nested-render.ts +1 -1
  30. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +7 -0
  31. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +28 -1
  32. package/dist/builtin/subagents/src/shared/fast-mode.ts +80 -0
  33. package/dist/builtin/subagents/src/shared/formatters.ts +4 -2
  34. package/dist/builtin/subagents/src/shared/types.ts +4 -2
  35. package/dist/builtin/subagents/src/shared/utils.ts +3 -61
  36. package/dist/builtin/subagents/src/tui/render.ts +303 -157
  37. package/dist/builtin/web-access/package.json +1 -1
  38. package/dist/builtin/workflows/CHANGELOG.md +95 -35
  39. package/dist/builtin/workflows/README.md +228 -41
  40. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +535 -541
  41. package/dist/builtin/workflows/builtin/goal.ts +39 -25
  42. package/dist/builtin/workflows/builtin/open-claude-design.ts +66 -69
  43. package/dist/builtin/workflows/builtin/ralph.ts +21 -21
  44. package/dist/builtin/workflows/package.json +6 -5
  45. package/dist/builtin/workflows/skills/research-codebase/SKILL.md +1 -1
  46. package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +2 -2
  47. package/dist/builtin/workflows/src/extension/discovery.ts +25 -146
  48. package/dist/builtin/workflows/src/extension/dispatcher.ts +72 -24
  49. package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +363 -0
  50. package/dist/builtin/workflows/src/extension/index.ts +690 -352
  51. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +99 -62
  52. package/dist/builtin/workflows/src/extension/render-call.ts +2 -1
  53. package/dist/builtin/workflows/src/extension/render-result.ts +9 -3
  54. package/dist/builtin/workflows/src/extension/renderers.ts +5 -3
  55. package/dist/builtin/workflows/src/extension/runtime.ts +68 -33
  56. package/dist/builtin/workflows/src/extension/status-writer.ts +1 -1
  57. package/dist/builtin/workflows/src/extension/wiring.ts +34 -13
  58. package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +142 -0
  59. package/dist/builtin/workflows/src/extension/workflow-schema.ts +4 -4
  60. package/dist/builtin/workflows/src/index.ts +2 -0
  61. package/dist/builtin/workflows/src/intercom/result-intercom.ts +1 -1
  62. package/dist/builtin/workflows/src/runs/background/runner.ts +6 -4
  63. package/dist/builtin/workflows/src/runs/background/status.ts +45 -21
  64. package/dist/builtin/workflows/src/runs/foreground/executor.ts +624 -52
  65. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +1 -1
  66. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +80 -24
  67. package/dist/builtin/workflows/src/runs/shared/validate-inputs.ts +61 -24
  68. package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +32 -10
  69. package/dist/builtin/workflows/src/sdk-surface.ts +6 -0
  70. package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +178 -0
  71. package/dist/builtin/workflows/src/shared/persistence-restore.ts +92 -12
  72. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +21 -3
  73. package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +1 -2
  74. package/dist/builtin/workflows/src/shared/run-visibility.ts +9 -0
  75. package/dist/builtin/workflows/src/shared/schema-introspection.ts +121 -0
  76. package/dist/builtin/workflows/src/shared/serializable.ts +132 -0
  77. package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +91 -9
  78. package/dist/builtin/workflows/src/shared/store-types.ts +31 -3
  79. package/dist/builtin/workflows/src/shared/store.ts +58 -14
  80. package/dist/builtin/workflows/src/shared/types.ts +105 -40
  81. package/dist/builtin/workflows/src/tui/chat-surface-message.ts +129 -13
  82. package/dist/builtin/workflows/src/tui/chat-surface.ts +6 -1
  83. package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +3 -2
  84. package/dist/builtin/workflows/src/tui/graph-canvas.ts +1 -1
  85. package/dist/builtin/workflows/src/tui/graph-view.ts +91 -65
  86. package/dist/builtin/workflows/src/tui/inline-form-card.ts +1 -1
  87. package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +3 -2
  88. package/dist/builtin/workflows/src/tui/inputs-overlay.ts +3 -2
  89. package/dist/builtin/workflows/src/tui/inputs-picker.ts +8 -7
  90. package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +2 -0
  91. package/dist/builtin/workflows/src/tui/node-card.ts +34 -8
  92. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +4 -11
  93. package/dist/builtin/workflows/src/tui/prompt-card.ts +98 -50
  94. package/dist/builtin/workflows/src/tui/session-list.ts +7 -2
  95. package/dist/builtin/workflows/src/tui/session-picker.ts +2 -0
  96. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +226 -55
  97. package/dist/builtin/workflows/src/tui/status-helpers.ts +2 -0
  98. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +37 -158
  99. package/dist/builtin/workflows/src/tui/toast.ts +2 -2
  100. package/dist/builtin/workflows/src/tui/widget.ts +53 -12
  101. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +270 -19
  102. package/dist/builtin/workflows/src/tui/workflow-notice-card.ts +184 -0
  103. package/dist/builtin/workflows/src/workflows/define-workflow.ts +138 -43
  104. package/dist/config.d.ts +9 -0
  105. package/dist/config.d.ts.map +1 -1
  106. package/dist/config.js +45 -0
  107. package/dist/config.js.map +1 -1
  108. package/dist/core/agent-session.d.ts +27 -9
  109. package/dist/core/agent-session.d.ts.map +1 -1
  110. package/dist/core/agent-session.js +196 -17
  111. package/dist/core/agent-session.js.map +1 -1
  112. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  113. package/dist/core/atomic-guide-command.js +2 -2
  114. package/dist/core/atomic-guide-command.js.map +1 -1
  115. package/dist/core/codex-fast-mode.d.ts +36 -0
  116. package/dist/core/codex-fast-mode.d.ts.map +1 -0
  117. package/dist/core/codex-fast-mode.js +117 -0
  118. package/dist/core/codex-fast-mode.js.map +1 -0
  119. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  120. package/dist/core/compaction/branch-summarization.js +1 -1
  121. package/dist/core/compaction/branch-summarization.js.map +1 -1
  122. package/dist/core/compaction/compaction.d.ts.map +1 -1
  123. package/dist/core/compaction/compaction.js +1 -1
  124. package/dist/core/compaction/compaction.js.map +1 -1
  125. package/dist/core/extensions/index.d.ts +4 -1
  126. package/dist/core/extensions/index.d.ts.map +1 -1
  127. package/dist/core/extensions/index.js +1 -0
  128. package/dist/core/extensions/index.js.map +1 -1
  129. package/dist/core/extensions/loader.d.ts +7 -2
  130. package/dist/core/extensions/loader.d.ts.map +1 -1
  131. package/dist/core/extensions/loader.js +23 -8
  132. package/dist/core/extensions/loader.js.map +1 -1
  133. package/dist/core/extensions/reactive-widget.d.ts +58 -0
  134. package/dist/core/extensions/reactive-widget.d.ts.map +1 -0
  135. package/dist/core/extensions/reactive-widget.js +182 -0
  136. package/dist/core/extensions/reactive-widget.js.map +1 -0
  137. package/dist/core/extensions/runner.d.ts.map +1 -1
  138. package/dist/core/extensions/runner.js +1 -0
  139. package/dist/core/extensions/runner.js.map +1 -1
  140. package/dist/core/extensions/types.d.ts +26 -12
  141. package/dist/core/extensions/types.d.ts.map +1 -1
  142. package/dist/core/extensions/types.js.map +1 -1
  143. package/dist/core/messages.d.ts +1 -1
  144. package/dist/core/messages.d.ts.map +1 -1
  145. package/dist/core/messages.js +8 -2
  146. package/dist/core/messages.js.map +1 -1
  147. package/dist/core/model-registry.d.ts +4 -0
  148. package/dist/core/model-registry.d.ts.map +1 -1
  149. package/dist/core/model-registry.js +11 -0
  150. package/dist/core/model-registry.js.map +1 -1
  151. package/dist/core/resource-loader.d.ts +9 -1
  152. package/dist/core/resource-loader.d.ts.map +1 -1
  153. package/dist/core/resource-loader.js +49 -21
  154. package/dist/core/resource-loader.js.map +1 -1
  155. package/dist/core/sdk.d.ts.map +1 -1
  156. package/dist/core/sdk.js +22 -13
  157. package/dist/core/sdk.js.map +1 -1
  158. package/dist/core/session-manager.d.ts +7 -5
  159. package/dist/core/session-manager.d.ts.map +1 -1
  160. package/dist/core/session-manager.js +5 -3
  161. package/dist/core/session-manager.js.map +1 -1
  162. package/dist/core/settings-manager.d.ts +16 -0
  163. package/dist/core/settings-manager.d.ts.map +1 -1
  164. package/dist/core/settings-manager.js +64 -5
  165. package/dist/core/settings-manager.js.map +1 -1
  166. package/dist/core/slash-commands.d.ts.map +1 -1
  167. package/dist/core/slash-commands.js +1 -0
  168. package/dist/core/slash-commands.js.map +1 -1
  169. package/dist/core/system-prompt.d.ts.map +1 -1
  170. package/dist/core/system-prompt.js +7 -4
  171. package/dist/core/system-prompt.js.map +1 -1
  172. package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
  173. package/dist/core/tools/ask-user-question/ask-user-question.js +2 -2
  174. package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
  175. package/dist/index.d.ts +4 -3
  176. package/dist/index.d.ts.map +1 -1
  177. package/dist/index.js +3 -2
  178. package/dist/index.js.map +1 -1
  179. package/dist/main.d.ts +3 -0
  180. package/dist/main.d.ts.map +1 -1
  181. package/dist/main.js +12 -0
  182. package/dist/main.js.map +1 -1
  183. package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -1
  184. package/dist/modes/interactive/chat-input-actions.js.map +1 -1
  185. package/dist/modes/interactive/components/diff.d.ts.map +1 -1
  186. package/dist/modes/interactive/components/diff.js +0 -1
  187. package/dist/modes/interactive/components/diff.js.map +1 -1
  188. package/dist/modes/interactive/components/fast-mode-selector.d.ts +27 -0
  189. package/dist/modes/interactive/components/fast-mode-selector.d.ts.map +1 -0
  190. package/dist/modes/interactive/components/fast-mode-selector.js +105 -0
  191. package/dist/modes/interactive/components/fast-mode-selector.js.map +1 -0
  192. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  193. package/dist/modes/interactive/components/footer.js +7 -12
  194. package/dist/modes/interactive/components/footer.js.map +1 -1
  195. package/dist/modes/interactive/components/index.d.ts +1 -0
  196. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  197. package/dist/modes/interactive/components/index.js +1 -0
  198. package/dist/modes/interactive/components/index.js.map +1 -1
  199. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  200. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  201. package/dist/modes/interactive/interactive-mode.js +132 -30
  202. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  203. package/dist/modes/print-mode.d.ts.map +1 -1
  204. package/dist/modes/print-mode.js +53 -6
  205. package/dist/modes/print-mode.js.map +1 -1
  206. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  207. package/dist/modes/rpc/rpc-mode.js +3 -0
  208. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  209. package/docs/compaction.md +1 -1
  210. package/docs/custom-provider.md +2 -2
  211. package/docs/development.md +2 -2
  212. package/docs/docs.json +2 -2
  213. package/docs/extensions.md +18 -13
  214. package/docs/providers.md +5 -1
  215. package/docs/quickstart.md +5 -3
  216. package/docs/rpc.md +5 -5
  217. package/docs/sdk.md +12 -12
  218. package/docs/settings.md +18 -0
  219. package/docs/themes.md +6 -6
  220. package/docs/tui.md +20 -18
  221. package/docs/usage.md +2 -0
  222. package/docs/workflows.md +403 -39
  223. package/examples/extensions/qna.ts +2 -2
  224. package/package.json +4 -4
  225. package/dist/builtin/subagents/skills/playwright-cli/SKILL.md +0 -392
  226. package/dist/builtin/subagents/skills/playwright-cli/references/element-attributes.md +0 -23
  227. package/dist/builtin/subagents/skills/playwright-cli/references/playwright-tests.md +0 -39
  228. package/dist/builtin/subagents/skills/playwright-cli/references/request-mocking.md +0 -87
  229. package/dist/builtin/subagents/skills/playwright-cli/references/running-code.md +0 -241
  230. package/dist/builtin/subagents/skills/playwright-cli/references/session-management.md +0 -225
  231. package/dist/builtin/subagents/skills/playwright-cli/references/spec-driven-testing.md +0 -305
  232. package/dist/builtin/subagents/skills/playwright-cli/references/storage-state.md +0 -275
  233. package/dist/builtin/subagents/skills/playwright-cli/references/test-generation.md +0 -134
  234. package/dist/builtin/subagents/skills/playwright-cli/references/tracing.md +0 -139
  235. package/dist/builtin/subagents/skills/playwright-cli/references/video-recording.md +0 -143
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { basename } from "node:path";
25
25
  import type { ChatMessageRenderOptions, CreateAgentSessionOptions } from "@bastani/atomic";
26
- import type { StageAdapters, StageSessionRuntime } from "../runs/foreground/stage-runner.js";
26
+ import type { StageAdapters, StageSessionCreateResult, StageSessionRuntime } from "../runs/foreground/stage-runner.js";
27
27
  import type { StageExecutionMeta, StageOptions } from "../shared/types.js";
28
28
  import { stageUiBroker, type StageUiBroker } from "../shared/stage-ui-broker.js";
29
29
 
@@ -56,12 +56,12 @@ export interface RuntimeWiringSurface {
56
56
  exec?: (command: string, args: string[], opts?: PiExecOpts) => Promise<PiExecResult>;
57
57
  ui?: PiUISurface;
58
58
  /** Test seam: inject a stub session factory instead of importing the SDK. */
59
- createAgentSession?: (options?: CreateAgentSessionOptions) => Promise<{ session: StageSessionRuntime }>;
59
+ createAgentSession?: (options?: CreateAgentSessionOptions) => Promise<StageSessionCreateResult>;
60
60
  }
61
61
 
62
62
  export interface RuntimeAdapterBuildOptions {
63
63
  /** Test seam for SDK session creation. */
64
- createAgentSession?: (options?: CreateAgentSessionOptions) => Promise<{ session: StageSessionRuntime }>;
64
+ createAgentSession?: (options?: CreateAgentSessionOptions) => Promise<StageSessionCreateResult>;
65
65
  /** Broker that routes stage-local custom UI into attached workflow nodes. */
66
66
  stageUiBroker?: StageUiBroker;
67
67
  }
@@ -90,7 +90,9 @@ function isTestContext(): boolean {
90
90
  * cross-ref: node_modules/@bastani/atomic/docs/sdk.md
91
91
  * node_modules/@bastani/atomic/dist/core/sdk.d.ts
92
92
  */
93
- export interface PiSdkSettingsManager {}
93
+ export interface PiSdkSettingsManager {
94
+ getCodexFastModeSettings(): { readonly chat: boolean; readonly workflow: boolean };
95
+ }
94
96
  export interface PiSdkResourceLoader {
95
97
  reload(): Promise<void>;
96
98
  }
@@ -176,17 +178,24 @@ function stageBuiltinPackagePaths(paths: readonly string[]): string[] {
176
178
 
177
179
  async function createPiSdkAgentSession(
178
180
  options?: CreateAgentSessionOptions,
179
- ): Promise<{ session: StageSessionRuntime }> {
181
+ ): Promise<StageSessionCreateResult> {
180
182
  const sdk = await import("@bastani/atomic") as PiCodingAgentSdk;
181
183
  const sessionOptions = await prepareAtomicStageSessionOptions(options, sdk);
182
184
  const result = await sdk.createAgentSession(sessionOptions);
183
185
  // `CreateAgentSessionResult` is `{ session, extensionsResult, modelFallbackMessage? }`;
184
186
  // workflow stages only consume `.session` (structurally an `AgentSession`,
185
187
  // which is a superset of our `StageSessionRuntime` projection).
186
- return { session: result.session };
188
+ const resultSettingsManager = result.session.settingsManager;
189
+ const settingsManager = sessionOptions?.settingsManager ?? resultSettingsManager;
190
+ return {
191
+ session: result.session,
192
+ ...(settingsManager?.getCodexFastModeSettings !== undefined
193
+ ? { settingsManager }
194
+ : {}),
195
+ };
187
196
  }
188
197
 
189
- async function createTestAgentSession(_options?: CreateAgentSessionOptions): Promise<{ session: StageSessionRuntime }> {
198
+ async function createTestAgentSession(_options?: CreateAgentSessionOptions): Promise<StageSessionCreateResult> {
190
199
  let lastAssistantText: string | undefined;
191
200
  const session: StageSessionRuntime = {
192
201
  async prompt(text: string): Promise<string> {
@@ -255,7 +264,14 @@ function withWorkflowStageSessionOptions(
255
264
  ): CreateAgentSessionOptions {
256
265
  // Workflow stage sessions should never see the workflow tool, even when older
257
266
  // meta-less callers cannot receive the richer runtime orchestration context.
258
- const excludedTools = Array.from(new Set([...(options.excludedTools ?? []), "workflow"]));
267
+ // Non-interactive workflow runs also remove ask_user_question so child agents
268
+ // cannot block unattended automation on a prompt that no user can answer.
269
+ const policyExcludedTools = meta?.executionMode === "non_interactive"
270
+ ? ["workflow", "ask_user_question"]
271
+ : ["workflow"];
272
+ const excludedTools = Array.from(
273
+ new Set([...(options.excludedTools ?? []), ...policyExcludedTools]),
274
+ );
259
275
  return {
260
276
  ...options,
261
277
  excludedTools,
@@ -263,6 +279,11 @@ function withWorkflowStageSessionOptions(
263
279
  };
264
280
  }
265
281
 
282
+ function shouldBindStageUiContext(pi: RuntimeWiringSurface, meta: StageExecutionMeta | undefined): boolean {
283
+ if (meta?.executionMode === "non_interactive") return false;
284
+ return pi.ui !== undefined || meta !== undefined;
285
+ }
286
+
266
287
  function makeStageExtensionUiContext(
267
288
  ui: PiUISurface,
268
289
  meta: StageExecutionMeta | undefined,
@@ -297,7 +318,7 @@ function makeStageExtensionUiContext(
297
318
  const result = await ui.custom(factory as PiCustomOverlayFactory, options ?? { overlay: true });
298
319
  return result as T;
299
320
  }
300
- throw new Error("pi-workflows: ask_user_question UI is unavailable");
321
+ throw new Error("atomic-workflows: ask_user_question UI is unavailable");
301
322
  },
302
323
  pasteToEditor: ui.pasteToEditor ?? (() => undefined),
303
324
  setEditorText: ui.setEditorText ?? (() => undefined),
@@ -309,7 +330,7 @@ function makeStageExtensionUiContext(
309
330
  theme: ui.theme,
310
331
  getAllThemes: ui.getAllThemes ?? (() => []),
311
332
  getTheme: ui.getTheme ?? (() => undefined),
312
- setTheme: ui.setTheme ?? (() => ({ success: false, error: "pi-workflows: theme UI is unavailable" })),
333
+ setTheme: ui.setTheme ?? (() => ({ success: false, error: "atomic-workflows: theme UI is unavailable" })),
313
334
  getToolsExpanded: ui.getToolsExpanded ?? (() => false),
314
335
  setToolsExpanded: ui.setToolsExpanded ?? (() => undefined),
315
336
  getChatRenderSettings: ui.getChatRenderSettings ?? (() => undefined),
@@ -343,7 +364,7 @@ export function buildRuntimeAdapters(
343
364
  const broker = options.stageUiBroker ?? stageUiBroker;
344
365
  const adapters: StageAdapters = {
345
366
  agentSession: {
346
- async create(stageOptions: CreateAgentSessionOptions & Pick<StageOptions, "mcp" | "fallbackModels">, meta?: StageExecutionMeta): Promise<StageSessionRuntime> {
367
+ async create(stageOptions: CreateAgentSessionOptions & Pick<StageOptions, "mcp" | "fallbackModels">, meta?: StageExecutionMeta): Promise<StageSessionRuntime | StageSessionCreateResult> {
347
368
  // Atomic's SDK handles extension / skills / prompt-template /
348
369
  // slash-command discovery via the SettingsManager / ResourceLoader.
349
370
  // The production default deliberately uses normal DefaultResourceLoader
@@ -357,12 +378,12 @@ export function buildRuntimeAdapters(
357
378
  );
358
379
  const result = await createSession(sessionOptions);
359
380
  const bindable = result.session as BindableStageSession;
360
- if ((pi.ui !== undefined || meta !== undefined) && typeof bindable.bindExtensions === "function") {
381
+ if (shouldBindStageUiContext(pi, meta) && typeof bindable.bindExtensions === "function") {
361
382
  await bindable.bindExtensions({
362
383
  uiContext: makeStageExtensionUiContext(pi.ui ?? {}, meta, broker),
363
384
  });
364
385
  }
365
- return result.session;
386
+ return result;
366
387
  },
367
388
  },
368
389
  };
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Shared workflow module loading helpers.
3
+ *
4
+ * Discovery loads user-authored workflow files through this jiti instance so
5
+ * TypeScript/ESM/CJS semantics and the @bastani/workflows virtual SDK alias
6
+ * stay consistent.
7
+ */
8
+
9
+ import { createJiti } from "jiti/static";
10
+ import * as workflowsSdkSurface from "../sdk-surface.js";
11
+ import deepResearchCodebase from "../../builtin/deep-research-codebase.js";
12
+ import goal from "../../builtin/goal.js";
13
+ import openClaudeDesign from "../../builtin/open-claude-design.js";
14
+ import ralph from "../../builtin/ralph.js";
15
+
16
+ type RunWorkflowFunction = typeof import("../runs/shared/workflow-runner.js").runWorkflow;
17
+
18
+ const runWorkflow: RunWorkflowFunction = async (...args) => {
19
+ const { runWorkflow: actualRunWorkflow } = await import("../runs/shared/workflow-runner.js");
20
+ return actualRunWorkflow(...args);
21
+ };
22
+
23
+ const WORKFLOWS_MODULE_SPECIFIER = "@bastani/workflows";
24
+ const WORKFLOWS_BUILTIN_MODULE_SPECIFIER = `${WORKFLOWS_MODULE_SPECIFIER}/builtin`;
25
+ // Keep this in sync with index.ts through sdk-surface.ts. runWorkflow stays as
26
+ // a lazy wrapper because the public re-export comes from workflow-runner.ts,
27
+ // which imports discovery.ts and would otherwise reintroduce a cycle.
28
+ const WORKFLOWS_SDK_MODULE: Record<string, unknown> = {
29
+ ...workflowsSdkSurface,
30
+ runWorkflow,
31
+ };
32
+ const WORKFLOWS_BUILTIN_MODULE: Record<string, unknown> = {
33
+ deepResearchCodebase,
34
+ goal,
35
+ openClaudeDesign,
36
+ ralph,
37
+ };
38
+ const WORKFLOWS_VIRTUAL_MODULES: Record<string, unknown> = {
39
+ [WORKFLOWS_MODULE_SPECIFIER]: WORKFLOWS_SDK_MODULE,
40
+ [WORKFLOWS_BUILTIN_MODULE_SPECIFIER]: WORKFLOWS_BUILTIN_MODULE,
41
+ [`${WORKFLOWS_BUILTIN_MODULE_SPECIFIER}/deep-research-codebase`]: { default: deepResearchCodebase },
42
+ [`${WORKFLOWS_BUILTIN_MODULE_SPECIFIER}/goal`]: { default: goal },
43
+ [`${WORKFLOWS_BUILTIN_MODULE_SPECIFIER}/open-claude-design`]: { default: openClaudeDesign },
44
+ [`${WORKFLOWS_BUILTIN_MODULE_SPECIFIER}/ralph`]: { default: ralph },
45
+ };
46
+
47
+ const workflowModuleLoader = createJiti(import.meta.url, {
48
+ moduleCache: false,
49
+ // Keep workflow-file import semantics deterministic: jiti owns .ts/.js/.mjs/.cjs
50
+ // resolution instead of handing some imports back to native import().
51
+ tryNative: false,
52
+ // Resolve the @bastani/workflows SDK (and its builtin submodules) to in-memory
53
+ // surfaces in every runtime. This mirrors the compiled bun binary path and
54
+ // keeps discovery fast: aliasing the SDK to its on-disk package re-evaluated
55
+ // the entire SDK module graph once per workflow file (moduleCache stays false),
56
+ // which scaled discovery to multiple seconds on projects with many workflow
57
+ // files. Workflow files themselves are still evaluated fresh from disk, so
58
+ // `/workflow reload` continues to observe edits.
59
+ virtualModules: WORKFLOWS_VIRTUAL_MODULES,
60
+ });
61
+
62
+ function materializeModuleObject(mod: object): Record<string, unknown> {
63
+ const materialized: Record<string, unknown> = {};
64
+
65
+ // jiti's callable API can return an interop namespace proxy. Its own property
66
+ // descriptors contain the authored export values, but property access may apply
67
+ // default-export conveniences (and even expose a throwing inherited `then`
68
+ // getter for `export default null`). Copy own descriptors into a plain object
69
+ // so candidate collection sees the exact authored exports.
70
+ for (const key of Object.getOwnPropertyNames(mod)) {
71
+ const descriptor = Object.getOwnPropertyDescriptor(mod, key);
72
+ if (descriptor === undefined) continue;
73
+
74
+ const value = "value" in descriptor ? descriptor.value : descriptor.get?.call(mod);
75
+ Object.defineProperty(materialized, key, {
76
+ value,
77
+ enumerable: descriptor.enumerable,
78
+ configurable: true,
79
+ writable: true,
80
+ });
81
+ }
82
+
83
+ return materialized;
84
+ }
85
+
86
+ function normalizeWorkflowModule(mod: unknown): Record<string, unknown> {
87
+ if (mod !== null && typeof mod === "object") {
88
+ return materializeModuleObject(mod);
89
+ }
90
+ // CJS/default interop can return the exported value directly; wrap it so the
91
+ // candidate collector can handle it the same way as an ESM default export.
92
+ return { default: mod };
93
+ }
94
+
95
+ export interface WorkflowModuleCandidate {
96
+ readonly value: unknown;
97
+ readonly exportKey: string;
98
+ }
99
+
100
+ export function validateWorkflowDefinitionShape(value: unknown): string | null {
101
+ if (value === null || typeof value !== "object") {
102
+ return "export is not an object";
103
+ }
104
+ const d = value as Record<string, unknown>;
105
+
106
+ if (d["__piWorkflow"] !== true) {
107
+ return "missing or incorrect __piWorkflow sentinel (expected true)";
108
+ }
109
+ if (typeof d["name"] !== "string" || (d["name"] as string).trim().length === 0) {
110
+ return "name must be a non-empty string";
111
+ }
112
+ if (typeof d["normalizedName"] !== "string" || (d["normalizedName"] as string).trim().length === 0) {
113
+ return "normalizedName must be a non-empty string";
114
+ }
115
+ if (typeof d["run"] !== "function") {
116
+ return "run must be a function";
117
+ }
118
+ return null;
119
+ }
120
+
121
+ export function loadWorkflowModule(filePath: string): Record<string, unknown> {
122
+ return normalizeWorkflowModule(workflowModuleLoader(filePath));
123
+ }
124
+
125
+ export function collectWorkflowModuleCandidates(mod: Record<string, unknown>): WorkflowModuleCandidate[] {
126
+ const candidates: WorkflowModuleCandidate[] = [];
127
+
128
+ // Default export first (RFC §5.12: check mod.default before named exports)
129
+ if ("default" in mod && mod["default"] !== undefined) {
130
+ candidates.push({ value: mod["default"], exportKey: "default" });
131
+ }
132
+
133
+ // Then all named exports (a file may export multiple workflow definitions)
134
+ for (const [key, val] of Object.entries(mod)) {
135
+ if (key === "default") continue;
136
+ if (val !== undefined) {
137
+ candidates.push({ value: val, exportKey: key });
138
+ }
139
+ }
140
+
141
+ return candidates;
142
+ }
@@ -113,7 +113,7 @@ export const WorkflowParametersSchema = Type.Object({
113
113
  Type.Literal("resume"),
114
114
  Type.Literal("reload"),
115
115
  ], {
116
- description: "Workflow action: run/list/get/inputs/status, inspect stages/transcripts, send messages or prompt answers, pause/resume/interrupt/kill runs, or reload workflow resources.",
116
+ description: "Workflow action: run/list/get/inputs/status, inspect stage metadata, send messages or prompt answers, pause/resume/interrupt/kill runs, or reload workflow resources. For transcript inspection, prefer status/stages/stage first to get sessionFile/transcriptPath, quote the exact path without rewriting separators (Windows backslashes are valid), then search it with rg/grep and read small ranges; transcript defaults to at most 5 recent entries and explicit tail/limit overrides that preview.",
117
117
  })),
118
118
  runId: Type.Optional(Type.String({
119
119
  description: "Run identifier or unique prefix for status/stages/stage/transcript/send/pause/resume/interrupt/kill. Use '--all' or all:true for supported bulk run-control actions.",
@@ -145,14 +145,14 @@ export const WorkflowParametersSchema = Type.Object({
145
145
  })),
146
146
  limit: Type.Optional(Type.Integer({
147
147
  minimum: 0,
148
- description: "Transcript-only: maximum number of most recent transcript entries to return; applied before tool output is serialized.",
148
+ description: "Transcript-only: explicitly inline at most this many recent entries. Omit both limit and tail to use the default 5-entry preview plus metadata/path; prefer rg/grep on the exact quoted sessionFile/transcriptPath for targeted lookup without rewriting platform path separators.",
149
149
  })),
150
150
  tail: Type.Optional(Type.Integer({
151
151
  minimum: 0,
152
- description: "Transcript-only: return only the last N transcript entries; overrides limit when both are provided.",
152
+ description: "Transcript-only: explicitly inline the last N entries; overrides limit. Use for quick recent-context checks after status/stages/stage expose the transcript path.",
153
153
  })),
154
154
  includeToolOutput: Type.Optional(Type.Boolean({
155
- description: "Transcript-only: include captured tool output entries when building results from stage snapshots; live session transcripts may not expose tool output.",
155
+ description: "Transcript-only: include captured tool output entries when building transcript results from stage snapshots; prefer rg/grep on the exact quoted sessionFile/transcriptPath for large outputs. Live session transcripts may not expose tool output.",
156
156
  })),
157
157
  text: Type.Optional(Type.String({
158
158
  description: "Text to send to a stage for prompt answers, steering, follow-ups, or resume messages.",
@@ -11,3 +11,5 @@ export * from "./sdk-surface.js";
11
11
  // discovery provides a lazy wrapper instead of importing this entry eagerly.
12
12
  export { runWorkflow } from "./runs/shared/workflow-runner.js";
13
13
  export type { WorkflowOptions, WorkflowRunOptions } from "./runs/shared/workflow-runner.js";
14
+
15
+ // Note: `Type` / `Static` / `TSchema` are re-exported via ./sdk-surface.js.
@@ -225,7 +225,7 @@ export function subscribeIntercomControl(
225
225
  // Surface errors without breaking the event loop.
226
226
  Promise.reject(
227
227
  new Error(
228
- `pi-workflows intercom callback error (type=${payload.type}): ${err instanceof Error ? err.message : String(err)}`,
228
+ `atomic-workflows: intercom callback error (type=${payload.type}): ${err instanceof Error ? err.message : String(err)}`,
229
229
  ),
230
230
  );
231
231
  });
@@ -14,7 +14,7 @@
14
14
  * cross-ref: spec detached-runner
15
15
  */
16
16
 
17
- import type { WorkflowDefinition } from "../../shared/types.js";
17
+ import type { WorkflowDefinition, WorkflowExecutionMode, WorkflowInputValues } from "../../shared/types.js";
18
18
  import type { RunOpts, RunResult } from "../foreground/executor.js";
19
19
  import type { CancellationRegistry } from "./cancellation-registry.js";
20
20
  import type { JobTracker } from "./job-tracker.js";
@@ -52,6 +52,8 @@ export interface DetachedRunOpts
52
52
  * Override JobTracker (default: singleton jobTracker).
53
53
  */
54
54
  jobs?: JobTracker;
55
+ /** Runtime execution mode for UI/prompt policy. Defaults to interactive. */
56
+ executionMode?: WorkflowExecutionMode;
55
57
  }
56
58
 
57
59
  // ---------------------------------------------------------------------------
@@ -83,9 +85,9 @@ export function buildDetachedAccepted(
83
85
  * the store remains source of truth for run status. Cancellation is wired
84
86
  * through the provided (or default) CancellationRegistry.
85
87
  */
86
- export function runDetached<TInputs extends Record<string, unknown>>(
88
+ export function runDetached<TInputs extends WorkflowInputValues>(
87
89
  def: WorkflowDefinition<TInputs>,
88
- inputs: Record<string, unknown>,
90
+ inputs: Readonly<Record<string, unknown>>,
89
91
  opts: DetachedRunOpts = {},
90
92
  ): DetachedAccepted {
91
93
  const registry = opts.cancellation ?? defaultCancellationRegistry;
@@ -122,7 +124,7 @@ export function runDetached<TInputs extends Record<string, unknown>>(
122
124
  signal: controller.signal,
123
125
  cancellation: registry,
124
126
  store,
125
- usePromptNodesForUi: true,
127
+ usePromptNodesForUi: opts.executionMode !== "non_interactive",
126
128
  deferWorkflowStart: true,
127
129
  };
128
130
 
@@ -10,12 +10,14 @@
10
10
 
11
11
  import type { Store } from "../../shared/store.js";
12
12
  import type { RunSnapshot, RunStatus, StageSnapshot } from "../../shared/store-types.js";
13
- import type { WorkflowPersistencePort } from "../../shared/types.js";
13
+ import type { WorkflowInputValues, WorkflowOutputValues, WorkflowPersistencePort } from "../../shared/types.js";
14
14
  import type { CancellationRegistry } from "./cancellation-registry.js";
15
15
  import type { StageControlRegistry } from "../foreground/stage-control-registry.js";
16
16
  import { store as defaultStore } from "../../shared/store.js";
17
17
  import { stageControlRegistry as defaultStageControlRegistry } from "../foreground/stage-control-registry.js";
18
18
  import { appendRunEnd } from "../../shared/persistence-session-entries.js";
19
+ import { expandWorkflowGraph } from "../../shared/expanded-workflow-graph.js";
20
+ import { topLevelWorkflowRuns } from "../../shared/run-visibility.js";
19
21
 
20
22
  // ---------------------------------------------------------------------------
21
23
  // Types
@@ -76,9 +78,9 @@ export interface RunDetail {
76
78
  readonly pausedDurationMs?: number;
77
79
  readonly pausedAt?: number;
78
80
  readonly resumedAt?: number;
79
- readonly inputs: Readonly<Record<string, unknown>>;
81
+ readonly inputs: Readonly<WorkflowInputValues>;
80
82
  readonly stages: readonly RunSnapshot["stages"][number][];
81
- readonly result?: Record<string, unknown>;
83
+ readonly result?: WorkflowOutputValues;
82
84
  readonly error?: string;
83
85
  }
84
86
 
@@ -99,13 +101,14 @@ export type InspectRunResult =
99
101
  export function statusRuns(opts?: { all?: boolean; store?: Store }): RunStatusEntry[] {
100
102
  const activeStore = opts?.store ?? defaultStore;
101
103
 
102
- return activeStore.runs().map((run) => ({
104
+ const snapshot = activeStore.snapshot();
105
+ return topLevelWorkflowRuns(snapshot.runs).map((run) => ({
103
106
  runId: run.id,
104
107
  name: run.name,
105
108
  status: run.status,
106
109
  startedAt: run.startedAt,
107
110
  durationMs: run.durationMs,
108
- stageCount: run.stages.length,
111
+ stageCount: expandWorkflowGraph(snapshot, run.id).stages.length,
109
112
  }));
110
113
  }
111
114
 
@@ -164,7 +167,7 @@ export function killAllRuns(opts?: {
164
167
  persistence?: WorkflowPersistencePort;
165
168
  }): KillResult[] {
166
169
  const activeStore = opts?.store ?? defaultStore;
167
- const inFlight = activeStore.runs().filter((r) => r.endedAt === undefined);
170
+ const inFlight = topLevelWorkflowRuns(activeStore.runs()).filter((r) => r.endedAt === undefined);
168
171
  return inFlight.map((r) =>
169
172
  killRun(r.id, { store: activeStore, cancellation: opts?.cancellation, persistence: opts?.persistence }),
170
173
  );
@@ -211,24 +214,32 @@ export function resumeRun(
211
214
  }
212
215
 
213
216
  const resumed: StageSnapshot[] = [];
214
- if (run.status === "paused" || run.stages.some((s) => s.status === "paused")) {
215
- const runHandle = registry.run(runId);
217
+ const controlRunIds = opts?.stageId ? [runId] : expandedControlRunIds(activeStore, runId);
218
+ const hasPausedState = controlRunIds.some((controlRunId) => {
219
+ const controlRun = runs.find((candidate) => candidate.id === controlRunId);
220
+ return controlRun?.status === "paused" || (controlRun?.stages.some((s) => s.status === "paused") ?? false);
221
+ });
222
+ if (hasPausedState) {
216
223
  const handles = opts?.stageId
217
224
  ? [registry.get(runId, opts.stageId)].filter(
218
225
  (h): h is NonNullable<typeof h> => h !== undefined,
219
- )
220
- : runHandle.pausedStages();
226
+ ).map((handle) => ({ controlRunId: runId, handle }))
227
+ : controlRunIds.flatMap((controlRunId) =>
228
+ registry.run(controlRunId).pausedStages().map((handle) => ({ controlRunId, handle })),
229
+ );
221
230
  // Fire-and-forget the resume promise — the executor will mark each
222
231
  // stage running once `__resume()` settles. The snapshot returned
223
232
  // below reflects the *current* paused state; subscribers see the
224
233
  // transition through the usual store notify path.
225
- for (const handle of handles) {
234
+ for (const { controlRunId, handle } of handles) {
226
235
  if (handle.status !== "paused") continue;
227
236
  void handle.resume(opts?.message);
228
- const stageSnap = run.stages.find((s) => s.id === handle.stageId);
237
+ const controlRun = runs.find((candidate) => candidate.id === controlRunId);
238
+ const stageSnap = controlRun?.stages.find((s) => s.id === handle.stageId);
229
239
  if (stageSnap) resumed.push(stageSnap);
240
+ activeStore.recordRunResumed(controlRunId);
230
241
  }
231
- if (run.status === "paused" && (!opts?.stageId || resumed.length > 0)) {
242
+ if (!opts?.stageId || resumed.length > 0) {
232
243
  activeStore.recordRunResumed(runId);
233
244
  }
234
245
  }
@@ -275,6 +286,13 @@ export function resumeRun(
275
286
  * stage-scoped pause when no matching handle exists with
276
287
  * `reason: "stage_not_found"`.
277
288
  */
289
+ function expandedControlRunIds(activeStore: Store, runId: string): string[] {
290
+ const graph = expandWorkflowGraph(activeStore.snapshot(), runId);
291
+ const ids = new Set<string>([runId]);
292
+ for (const stage of graph.stages) ids.add(stage.workflowGraphTarget.runId);
293
+ return [...ids];
294
+ }
295
+
278
296
  export function pauseRun(
279
297
  runId: string,
280
298
  opts?: {
@@ -315,17 +333,22 @@ export function pauseRun(
315
333
  return { ok: true, runId, paused };
316
334
  }
317
335
 
318
- const handles = registry.run(runId).stages().filter(
319
- (h) => h.status === "running" || h.status === "pending",
336
+ const controlRunIds = expandedControlRunIds(activeStore, runId);
337
+ const handles = controlRunIds.flatMap((controlRunId) =>
338
+ registry.run(controlRunId).stages().filter(
339
+ (h) => h.status === "running" || h.status === "pending",
340
+ ).map((handle) => ({ controlRunId, handle })),
320
341
  );
321
342
  if (handles.length === 0) {
322
343
  return { ok: false, runId, reason: "no_active_stages" };
323
344
  }
324
345
  const pausedSnaps: StageSnapshot[] = [];
325
- for (const handle of handles) {
346
+ for (const { controlRunId, handle } of handles) {
326
347
  void handle.pause();
327
- const stageSnap = run.stages.find((s) => s.id === handle.stageId);
348
+ const controlRun = activeStore.runs().find((candidate) => candidate.id === controlRunId);
349
+ const stageSnap = controlRun?.stages.find((s) => s.id === handle.stageId);
328
350
  if (stageSnap) pausedSnaps.push(structuredClone(stageSnap));
351
+ activeStore.recordRunPaused(controlRunId);
329
352
  }
330
353
  activeStore.recordRunPaused(runId);
331
354
  return { ok: true, runId, paused: pausedSnaps };
@@ -336,7 +359,7 @@ export function pauseAllRuns(opts?: {
336
359
  stageControlRegistry?: StageControlRegistry;
337
360
  }): PauseResult[] {
338
361
  const activeStore = opts?.store ?? defaultStore;
339
- const inFlight = activeStore.runs().filter((r) => r.endedAt === undefined);
362
+ const inFlight = topLevelWorkflowRuns(activeStore.runs()).filter((r) => r.endedAt === undefined);
340
363
  return inFlight.map((r) =>
341
364
  pauseRun(r.id, { store: activeStore, stageControlRegistry: opts?.stageControlRegistry }),
342
365
  );
@@ -368,7 +391,7 @@ export function interruptAllRuns(opts?: {
368
391
  stageControlRegistry?: StageControlRegistry;
369
392
  }): InterruptRunResult[] {
370
393
  const activeStore = opts?.store ?? defaultStore;
371
- const inFlight = activeStore.runs().filter((r) => r.endedAt === undefined);
394
+ const inFlight = topLevelWorkflowRuns(activeStore.runs()).filter((r) => r.endedAt === undefined);
372
395
  return inFlight.map((r) =>
373
396
  interruptRun(r.id, { store: activeStore, stageControlRegistry: opts?.stageControlRegistry }),
374
397
  );
@@ -401,12 +424,13 @@ export function inspectRun(
401
424
 
402
425
  // Deep copy so callers cannot mutate the store via the snapshot.
403
426
  const copy = structuredClone(candidate);
427
+ const expandedStages = expandWorkflowGraph(activeStore.snapshot(), copy.id).stages;
404
428
 
405
429
  const detail: RunDetail = {
406
430
  runId: copy.id,
407
431
  name: copy.name,
408
432
  status: copy.status,
409
- mode: copy.stages.length > 1 ? "chain" : "single",
433
+ mode: expandedStages.length > 1 ? "chain" : "single",
410
434
  startedAt: copy.startedAt,
411
435
  endedAt: copy.endedAt,
412
436
  durationMs: copy.durationMs,
@@ -414,7 +438,7 @@ export function inspectRun(
414
438
  pausedAt: copy.pausedAt,
415
439
  resumedAt: copy.resumedAt,
416
440
  inputs: copy.inputs,
417
- stages: copy.stages,
441
+ stages: expandedStages.map((stage) => structuredClone(stage)),
418
442
  result: copy.result,
419
443
  error: copy.error,
420
444
  };