@bastani/atomic 0.8.21-0 → 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 +46 -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 +101 -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
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { Component, Focusable, TUI } from "@earendil-works/pi-tui";
3
- import type { Store } from "./store.js";
3
+ import type { StagePromptAnswerSource, Store } from "./store.js";
4
4
  import { store as defaultStore } from "./store.js";
5
5
  import type { StageInputRequest } from "./store-types.js";
6
6
  import type { StageInputAnswer, StagePromptAdapter } from "./stage-prompt.js";
@@ -22,6 +22,22 @@ export interface StageCustomUiHost {
22
22
  hideCustomUi?(request: StageCustomUiRequest, reason: unknown): void;
23
23
  }
24
24
 
25
+ export interface StagePromptResolvedEvent {
26
+ readonly runId: string;
27
+ readonly stageId: string;
28
+ readonly prompt: StageInputRequest;
29
+ readonly answer: unknown;
30
+ readonly answeredAt: number;
31
+ readonly answerSource?: StagePromptAnswerSource;
32
+ }
33
+
34
+ export interface AnswerStagePromptOptions {
35
+ /** Identifies who answered the prompt so notification code can avoid echoing workflow-tool answers. */
36
+ readonly answerSource?: StagePromptAnswerSource;
37
+ }
38
+
39
+ export type StagePromptResolvedListener = (event: StagePromptResolvedEvent) => void;
40
+
25
41
  function key(runId: string, stageId: string): string {
26
42
  return `${runId}\0${stageId}`;
27
43
  }
@@ -39,6 +55,8 @@ export class StageUiBroker {
39
55
  // custom-UI prompt can be answered programmatically (e.g. via `workflow
40
56
  // send`) without a TUI host rendering the interactive component.
41
57
  private readonly adapters = new Map<string, StagePromptAdapter>();
58
+ private readonly resolvedPromptIds = new Map<string, string>();
59
+ private readonly resolvedListeners = new Set<StagePromptResolvedListener>();
42
60
 
43
61
  constructor(store: Store = defaultStore) {
44
62
  this.store = store;
@@ -52,7 +70,13 @@ export class StageUiBroker {
52
70
  * after the matching `requestCustomUi`; the (runId, stageId) key joins them.
53
71
  */
54
72
  provideStagePrompt(runId: string, stageId: string, adapter: StagePromptAdapter): void {
55
- this.adapters.set(key(runId, stageId), adapter);
73
+ const hostKey = key(runId, stageId);
74
+ this.adapters.set(hostKey, adapter);
75
+ // A newly provided adapter represents a fresh prompt instance, even when
76
+ // the caller reuses the same prompt id (readiness gates historically did).
77
+ // Clear the resolved marker unconditionally so a raced answer for this new
78
+ // request cannot be mistaken for a duplicate answer to the prior instance.
79
+ this.resolvedPromptIds.delete(hostKey);
56
80
  this.store.recordStageInputRequest(runId, stageId, adapter.prompt);
57
81
  }
58
82
 
@@ -63,6 +87,23 @@ export class StageUiBroker {
63
87
  }
64
88
  }
65
89
 
90
+ /**
91
+ * Settle and hide any brokered prompt that belongs to a stage whose workflow
92
+ * lifecycle has ended. Detaching a host intentionally does not cancel human
93
+ * input, but terminal stage/run cleanup must not leave stale HIL UI mounted.
94
+ */
95
+ cancelStagePrompt(runId: string, stageId: string, reason: unknown): void {
96
+ const hostKey = key(runId, stageId);
97
+ const request = this.pending.get(hostKey);
98
+ if (request) {
99
+ this.reject(request, reason);
100
+ return;
101
+ }
102
+ if (this.adapters.delete(hostKey)) {
103
+ this.store.clearStageInputRequest(runId, stageId);
104
+ }
105
+ }
106
+
66
107
  /**
67
108
  * Return the structured descriptor for a stage's brokered prompt when BOTH a
68
109
  * headless-answer adapter and a live pending request exist for it — i.e. when
@@ -80,15 +121,41 @@ export class StageUiBroker {
80
121
  * `ctx.ui.custom` promise with the adapter-built result. Returns `false` when
81
122
  * there is no adapter+request pair for the stage.
82
123
  */
83
- answerStagePrompt(runId: string, stageId: string, answer: StageInputAnswer): boolean {
124
+ answerStagePrompt(
125
+ runId: string,
126
+ stageId: string,
127
+ answer: StageInputAnswer,
128
+ options: AnswerStagePromptOptions = {},
129
+ ): boolean {
84
130
  const hostKey = key(runId, stageId);
85
131
  const adapter = this.adapters.get(hostKey);
86
132
  const request = this.pending.get(hostKey);
87
133
  if (!adapter || !request) return false;
88
- this.resolve(request, adapter.buildResult(answer));
134
+ this.resolve(request, adapter.buildResult(answer), options);
89
135
  return true;
90
136
  }
91
137
 
138
+ wasStagePromptResolved(runId: string, stageId: string, promptId: string): boolean {
139
+ return this.resolvedPromptIds.get(key(runId, stageId)) === promptId;
140
+ }
141
+
142
+ onStagePromptResolved(listener: StagePromptResolvedListener): () => void {
143
+ this.resolvedListeners.add(listener);
144
+ return () => {
145
+ this.resolvedListeners.delete(listener);
146
+ };
147
+ }
148
+
149
+ private notifyStagePromptAnswered(event: StagePromptResolvedEvent): void {
150
+ for (const listener of this.resolvedListeners) {
151
+ try {
152
+ listener(event);
153
+ } catch {
154
+ // Listener failures must not prevent the prompt from resolving.
155
+ }
156
+ }
157
+ }
158
+
92
159
  private hideHost(host: StageCustomUiHost | undefined, request: StageCustomUiRequest, reason: unknown): void {
93
160
  try {
94
161
  host?.hideCustomUi?.(request, reason);
@@ -113,7 +180,7 @@ export class StageUiBroker {
113
180
  this.hideHost(
114
181
  previousHost,
115
182
  request,
116
- new Error(`pi-workflows: stage ${stageId} custom UI host replaced`),
183
+ new Error(`atomic-workflows: stage ${stageId} custom UI host replaced`),
117
184
  );
118
185
  }
119
186
  this.hosts.set(hostKey, host);
@@ -139,12 +206,12 @@ export class StageUiBroker {
139
206
  signal?: AbortSignal,
140
207
  ): Promise<T> {
141
208
  if (signal?.aborted) {
142
- return Promise.reject(signal.reason ?? new Error("pi-workflows: stage UI request aborted"));
209
+ return Promise.reject(signal.reason ?? new Error("atomic-workflows: stage UI request aborted"));
143
210
  }
144
211
  const hostKey = key(runId, stageId);
145
212
  const existing = this.pending.get(hostKey);
146
213
  if (existing) {
147
- return Promise.reject(new Error(`pi-workflows: stage ${stageId} already has a pending custom UI request`));
214
+ return Promise.reject(new Error(`atomic-workflows: stage ${stageId} already has a pending custom UI request`));
148
215
  }
149
216
 
150
217
  let request!: StageCustomUiRequest<T>;
@@ -162,7 +229,7 @@ export class StageUiBroker {
162
229
  });
163
230
 
164
231
  const onAbort = (): void => {
165
- this.reject(request, signal?.reason ?? new Error("pi-workflows: stage UI request aborted"));
232
+ this.reject(request, signal?.reason ?? new Error("atomic-workflows: stage UI request aborted"));
166
233
  };
167
234
  signal?.addEventListener("abort", onAbort, { once: true });
168
235
 
@@ -179,14 +246,26 @@ export class StageUiBroker {
179
246
  });
180
247
  }
181
248
 
182
- resolve<T>(request: StageCustomUiRequest<T>, value: T): void {
249
+ resolve<T>(request: StageCustomUiRequest<T>, value: T, options: AnswerStagePromptOptions = {}): void {
183
250
  const hostKey = key(request.runId, request.stageId);
184
251
  if (this.pending.get(hostKey)?.id !== request.id) return;
252
+ const prompt = this.adapters.get(hostKey)?.prompt;
185
253
  this.pending.delete(hostKey);
186
254
  this.adapters.delete(hostKey);
255
+ if (prompt !== undefined) this.resolvedPromptIds.set(hostKey, prompt.id);
187
256
  this.store.clearStageInputRequest(request.runId, request.stageId);
188
257
  this.store.recordStageAwaitingInput(request.runId, request.stageId, false);
189
258
  this.hideHost(this.hosts.get(hostKey), request, undefined);
259
+ if (prompt !== undefined) {
260
+ this.notifyStagePromptAnswered({
261
+ runId: request.runId,
262
+ stageId: request.stageId,
263
+ prompt,
264
+ answer: value,
265
+ answeredAt: Date.now(),
266
+ ...(options.answerSource !== undefined ? { answerSource: options.answerSource } : {}),
267
+ });
268
+ }
190
269
  request.resolve(value);
191
270
  }
192
271
 
@@ -195,6 +274,7 @@ export class StageUiBroker {
195
274
  if (this.pending.get(hostKey)?.id !== request.id) return;
196
275
  this.pending.delete(hostKey);
197
276
  this.adapters.delete(hostKey);
277
+ this.resolvedPromptIds.delete(hostKey);
198
278
  this.store.clearStageInputRequest(request.runId, request.stageId);
199
279
  this.store.recordStageAwaitingInput(request.runId, request.stageId, false);
200
280
  this.hideHost(this.hosts.get(hostKey), request, reason);
@@ -214,12 +294,14 @@ export async function mountStageCustomUi(
214
294
  keybindings: PiKeybindings,
215
295
  broker: StageUiBroker,
216
296
  onDone?: () => void,
297
+ canResolve?: () => boolean,
217
298
  ): Promise<MountedStageCustomUi> {
218
299
  const rawComponent = await request.factory(
219
300
  tui as unknown as Parameters<StageCustomUiRequest["factory"]>[0],
220
301
  theme,
221
302
  keybindings,
222
303
  (result: unknown) => {
304
+ if (canResolve?.() === false) return;
223
305
  broker.resolve(request, result);
224
306
  onDone?.();
225
307
  },
@@ -3,6 +3,8 @@
3
3
  * cross-ref: spec §5.5
4
4
  */
5
5
 
6
+ import type { WorkflowInputValues, WorkflowOutputValues } from "./types.js";
7
+
6
8
  export type RunStatus = "pending" | "running" | "paused" | "completed" | "failed" | "killed";
7
9
  export type StageStatus =
8
10
  | "pending"
@@ -94,6 +96,20 @@ export interface StageNotice {
94
96
  readonly meta?: string;
95
97
  }
96
98
 
99
+ export interface WorkflowChildRunRef {
100
+ readonly alias: string;
101
+ readonly workflow: string;
102
+ readonly runId: string;
103
+ }
104
+
105
+ export interface WorkflowChildReplaySnapshot {
106
+ readonly alias: string;
107
+ readonly workflow: string;
108
+ readonly runId: string;
109
+ readonly status: "completed";
110
+ readonly outputs: WorkflowOutputValues;
111
+ }
112
+
97
113
  export interface StageSnapshot {
98
114
  readonly id: string;
99
115
  readonly name: string;
@@ -125,6 +141,10 @@ export interface StageSnapshot {
125
141
  replayedFromStageId?: string;
126
142
  /** True when provider work was skipped by continuation replay. */
127
143
  replayed?: boolean;
144
+ /** Live child workflow run metadata used to expand nested workflow graphs while the child is running. */
145
+ workflowChildRun?: WorkflowChildRunRef;
146
+ /** Snapshot-safe child workflow result metadata for continuation replay of import boundaries. */
147
+ workflowChild?: WorkflowChildReplaySnapshot;
128
148
  readonly toolEvents: ToolEvent[];
129
149
  /** True while an in-stage ask_user_question tool is waiting on the user. */
130
150
  awaitingInputSince?: number;
@@ -155,8 +175,10 @@ export interface StageSnapshot {
155
175
  */
156
176
  sessionId?: string;
157
177
  sessionFile?: string;
158
- /** Effective model selected for this stage after fallback resolution. */
178
+ /** Effective model id selected for this stage after fallback resolution. */
159
179
  model?: string;
180
+ /** True when Codex fast mode applied to this workflow stage. */
181
+ fastMode?: boolean;
160
182
  /** Ordered model ids attempted by fallback orchestration. */
161
183
  attemptedModels?: readonly string[];
162
184
  /** Per-model fallback attempt outcomes. */
@@ -180,7 +202,7 @@ export interface StageSnapshot {
180
202
  export interface RunSnapshot {
181
203
  readonly id: string;
182
204
  readonly name: string;
183
- readonly inputs: Readonly<Record<string, unknown>>;
205
+ readonly inputs: Readonly<WorkflowInputValues>;
184
206
  status: RunStatus;
185
207
  readonly stages: StageSnapshot[];
186
208
  startedAt: number;
@@ -192,7 +214,7 @@ export interface RunSnapshot {
192
214
  pausedAt?: number;
193
215
  /** Timestamp recorded on the most recent resume from a paused state. */
194
216
  resumedAt?: number;
195
- result?: Record<string, unknown>;
217
+ result?: WorkflowOutputValues;
196
218
  error?: string;
197
219
  /** Structured workflow failure category for failed runs. */
198
220
  failureKind?: WorkflowFailureKind;
@@ -200,6 +222,12 @@ export interface RunSnapshot {
200
222
  failureMessage?: string;
201
223
  failedStageId?: string;
202
224
  resumable?: boolean;
225
+ /** Parent workflow run when this snapshot is an internal child workflow run. Hidden from top-level status lists. */
226
+ parentRunId?: string;
227
+ /** Parent workflow boundary stage that launched this internal child workflow run. */
228
+ parentStageId?: string;
229
+ /** Top-level workflow run that owns this nested run tree. */
230
+ rootRunId?: string;
203
231
  /** Source failed run when this run is a continuation. */
204
232
  resumedFromRunId?: string;
205
233
  /** Source stage id where continuation resumes real execution. */
@@ -3,6 +3,7 @@
3
3
  * cross-ref: spec §5.5
4
4
  */
5
5
 
6
+ import type { WorkflowOutputValues } from "./types.js";
6
7
  import type {
7
8
  PendingPrompt,
8
9
  PromptKind,
@@ -16,8 +17,10 @@ import type {
16
17
  StageStatus,
17
18
  WorkflowFailureKind,
18
19
  WorkflowNotice,
20
+ WorkflowChildRunRef,
19
21
  } from "./store-types.js";
20
22
  import { accumulatePausedDurationMs, elapsedRunMs } from "./timing.js";
23
+ import { isTopLevelWorkflowRun } from "./run-visibility.js";
21
24
 
22
25
  /** Statuses that represent a terminal run state — cannot be overwritten. */
23
26
  const TERMINAL_STATUSES: ReadonlySet<RunStatus> = new Set(["completed", "failed", "killed"]);
@@ -45,6 +48,8 @@ export interface RunEndMetadata {
45
48
  readonly resumable?: boolean;
46
49
  }
47
50
 
51
+ export type StagePromptAnswerSource = "workflow_ui" | "workflow_tool";
52
+
48
53
  export interface PromptAnswerRecord {
49
54
  readonly runId: string;
50
55
  readonly stageId: string;
@@ -52,6 +57,7 @@ export interface PromptAnswerRecord {
52
57
  readonly kind: PromptKind;
53
58
  readonly value: unknown;
54
59
  readonly answeredAt: number;
60
+ readonly answerSource?: StagePromptAnswerSource;
55
61
  }
56
62
 
57
63
  export interface ResolveStagePendingPromptOptions {
@@ -60,6 +66,8 @@ export interface ResolveStagePendingPromptOptions {
60
66
  * continuation replay. Abort/default resolutions should set this to false.
61
67
  */
62
68
  readonly recordAnswer?: boolean;
69
+ /** Identifies who answered the prompt so notification code can avoid echoing workflow-tool answers. */
70
+ readonly answerSource?: StagePromptAnswerSource;
63
71
  }
64
72
 
65
73
  export interface Store {
@@ -68,6 +76,8 @@ export interface Store {
68
76
  activeRunId(): string | null;
69
77
  recordRunStart(run: RunSnapshot): void;
70
78
  recordStageStart(runId: string, stage: StageSnapshot): void;
79
+ /** Link a workflow boundary stage to its live child run before that child completes. */
80
+ recordStageWorkflowChildRun(runId: string, stageId: string, ref: WorkflowChildRunRef): boolean;
71
81
  recordToolStart(runId: string, stageId: string, evt: ToolEvent): void;
72
82
  recordToolEnd(runId: string, stageId: string, evt: ToolEvent): void;
73
83
  recordStageEnd(runId: string, stage: StageSnapshot): void;
@@ -81,7 +91,7 @@ export interface Store {
81
91
  recordRunEnd(
82
92
  runId: string,
83
93
  status: RunStatus,
84
- result?: Record<string, unknown>,
94
+ result?: WorkflowOutputValues,
85
95
  error?: string,
86
96
  metadata?: RunEndMetadata,
87
97
  ): boolean;
@@ -298,7 +308,19 @@ export function createStore(): Store {
298
308
  },
299
309
 
300
310
  activeRunId(): string | null {
301
- // Most recently started run that hasn't ended
311
+ // Most recently started top-level run that hasn't ended. Nested
312
+ // workflow runs stay in the store for live control/expanded graph
313
+ // rendering, but should not steal the active top-level workflow slot.
314
+ for (let i = _runs.length - 1; i >= 0; i--) {
315
+ const run = _runs[i];
316
+ if (run && isTopLevelWorkflowRun(run) && run.endedAt === undefined) {
317
+ return run.id;
318
+ }
319
+ }
320
+ // Fallback for the degraded "orphaned-nested-only" state: a child run is in
321
+ // flight but no top-level run is. This normally cannot happen (a parent
322
+ // stays in flight while awaiting `ctx.workflow(...)`), so callers that rely
323
+ // on a top-level id should treat a returned nested id as best-effort.
302
324
  for (let i = _runs.length - 1; i >= 0; i--) {
303
325
  const run = _runs[i];
304
326
  if (run && run.endedAt === undefined) {
@@ -325,6 +347,24 @@ export function createStore(): Store {
325
347
  notify();
326
348
  },
327
349
 
350
+ recordStageWorkflowChildRun(runId: string, stageId: string, ref: WorkflowChildRunRef): boolean {
351
+ const run = findRun(runId);
352
+ if (!run) return false;
353
+ const stage = findStage(run, stageId);
354
+ if (!stage) return false;
355
+ if (
356
+ stage.workflowChildRun?.runId === ref.runId &&
357
+ stage.workflowChildRun.alias === ref.alias &&
358
+ stage.workflowChildRun.workflow === ref.workflow
359
+ ) {
360
+ return false;
361
+ }
362
+ stage.workflowChildRun = { ...ref };
363
+ _version++;
364
+ notify();
365
+ return true;
366
+ },
367
+
328
368
  recordToolStart(runId: string, stageId: string, evt: ToolEvent): void {
329
369
  const run = findRun(runId);
330
370
  if (!run) return;
@@ -383,9 +423,11 @@ export function createStore(): Store {
383
423
  if (stage.promptAnswerState !== undefined) existing.promptAnswerState = stage.promptAnswerState;
384
424
  if (stage.replayedFromStageId !== undefined) existing.replayedFromStageId = stage.replayedFromStageId;
385
425
  if (stage.replayed !== undefined) existing.replayed = stage.replayed;
426
+ if (stage.workflowChildRun !== undefined) existing.workflowChildRun = { ...stage.workflowChildRun };
427
+ if (stage.workflowChild !== undefined) existing.workflowChild = structuredClone(stage.workflowChild);
386
428
  delete existing.awaitingInputSince;
387
429
  delete existing.inputRequest;
388
- rejectStagePrompt(existing, `pi-workflows: stage ${stage.id} ended before prompt resolved`);
430
+ rejectStagePrompt(existing, `atomic-workflows: stage ${stage.id} ended before prompt resolved`);
389
431
  _version++;
390
432
  notify();
391
433
  },
@@ -393,7 +435,7 @@ export function createStore(): Store {
393
435
  recordRunEnd(
394
436
  runId: string,
395
437
  status: RunStatus,
396
- result?: Record<string, unknown>,
438
+ result?: WorkflowOutputValues,
397
439
  error?: string,
398
440
  metadata?: RunEndMetadata,
399
441
  ): boolean {
@@ -430,9 +472,9 @@ export function createStore(): Store {
430
472
  const pending = run.pendingPrompt;
431
473
  if (pending) {
432
474
  run.pendingPrompt = undefined;
433
- rejectPrompt(pending.id, `pi-workflows: run ${runId} ended before prompt resolved`);
475
+ rejectPrompt(pending.id, `atomic-workflows: run ${runId} ended before prompt resolved`);
434
476
  }
435
- rejectAllStagePrompts(run, `pi-workflows: run ${runId} ended before prompt resolved`);
477
+ rejectAllStagePrompts(run, `atomic-workflows: run ${runId} ended before prompt resolved`);
436
478
  _version++;
437
479
  notify();
438
480
  return true;
@@ -444,9 +486,9 @@ export function createStore(): Store {
444
486
  const run = _runs[index]!;
445
487
  const pending = run.pendingPrompt;
446
488
  if (pending) {
447
- rejectPrompt(pending.id, `pi-workflows: run ${runId} was removed before prompt resolved`);
489
+ rejectPrompt(pending.id, `atomic-workflows: run ${runId} was removed before prompt resolved`);
448
490
  }
449
- rejectAllStagePrompts(run, `pi-workflows: run ${runId} was removed before prompt resolved`);
491
+ rejectAllStagePrompts(run, `atomic-workflows: run ${runId} was removed before prompt resolved`);
450
492
  for (const stage of run.stages) {
451
493
  _stagePromptAnswers.delete(stagePromptAnswerKey(runId, stage.id));
452
494
  }
@@ -511,14 +553,14 @@ export function createStore(): Store {
511
553
  return new Promise<unknown>((resolve, reject) => {
512
554
  const run = findRun(runId);
513
555
  if (!run) {
514
- reject(new Error(`pi-workflows: run "${runId}" not found`));
556
+ reject(new Error(`atomic-workflows: run "${runId}" not found`));
515
557
  return;
516
558
  }
517
559
  const pending = run.pendingPrompt;
518
560
  if (!pending || pending.id !== promptId) {
519
561
  reject(
520
562
  new Error(
521
- `pi-workflows: pending prompt "${promptId}" not registered on run "${runId}"`,
563
+ `atomic-workflows: pending prompt "${promptId}" not registered on run "${runId}"`,
522
564
  ),
523
565
  );
524
566
  return;
@@ -565,6 +607,7 @@ export function createStore(): Store {
565
607
  kind: pending.kind,
566
608
  value: response,
567
609
  answeredAt: Date.now(),
610
+ ...(options.answerSource !== undefined ? { answerSource: options.answerSource } : {}),
568
611
  });
569
612
  stage.promptAnswerState = "available";
570
613
  } else {
@@ -590,19 +633,19 @@ export function createStore(): Store {
590
633
  return new Promise<unknown>((resolve, reject) => {
591
634
  const run = findRun(runId);
592
635
  if (!run) {
593
- reject(new Error(`pi-workflows: run "${runId}" not found`));
636
+ reject(new Error(`atomic-workflows: run "${runId}" not found`));
594
637
  return;
595
638
  }
596
639
  const stage = findStage(run, stageId);
597
640
  if (!stage) {
598
- reject(new Error(`pi-workflows: stage "${stageId}" not found on run "${runId}"`));
641
+ reject(new Error(`atomic-workflows: stage "${stageId}" not found on run "${runId}"`));
599
642
  return;
600
643
  }
601
644
  const pending = stage.pendingPrompt;
602
645
  if (!pending || pending.id !== promptId) {
603
646
  reject(
604
647
  new Error(
605
- `pi-workflows: pending prompt "${promptId}" not registered on stage "${stageId}" in run "${runId}"`,
648
+ `atomic-workflows: pending prompt "${promptId}" not registered on stage "${stageId}" in run "${runId}"`,
606
649
  ),
607
650
  );
608
651
  return;
@@ -700,6 +743,7 @@ export function createStore(): Store {
700
743
  stage.status = "awaiting_input";
701
744
  stage.awaitingInputSince = ts ?? Date.now();
702
745
  } else {
746
+ if (stage.pendingPrompt !== undefined) return false;
703
747
  if (stage.status !== "awaiting_input") return false;
704
748
  stage.status = "running";
705
749
  delete stage.awaitingInputSince;
@@ -859,7 +903,7 @@ export function createStore(): Store {
859
903
  // instead of leaking. The error message is intentionally generic — the
860
904
  // caller already issued a session boundary, exact cause isn't needed.
861
905
  for (const entry of _resolvers.values()) {
862
- entry.reject(new Error("pi-workflows: store cleared"));
906
+ entry.reject(new Error("atomic-workflows: store cleared"));
863
907
  }
864
908
  _resolvers.clear();
865
909
  _stagePromptAnswers.clear();