@bastani/atomic 0.8.21 → 0.8.22

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 +66 -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 +20 -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 +113 -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
@@ -7,7 +7,10 @@
7
7
  */
8
8
 
9
9
  import type { Store } from "./store.js";
10
- import type { RunSnapshot, StageSnapshot, StageStatus } from "./store-types.js";
10
+ import type { RunSnapshot, StageSnapshot, StageStatus, WorkflowChildReplaySnapshot } from "./store-types.js";
11
+ import type { WorkflowInputValues, WorkflowOutputValues } from "./types.js";
12
+ import { workflowSerializableObjectSchema } from "./serializable.js";
13
+ import { Value } from "typebox/value";
11
14
  import { isWorkflowFailureKind } from "./workflow-failures.js";
12
15
 
13
16
  // ---------------------------------------------------------------------------
@@ -40,7 +43,7 @@ export interface SessionManager {
40
43
  export interface InFlightRun {
41
44
  readonly runId: string;
42
45
  readonly name: string;
43
- readonly inputs: Readonly<Record<string, unknown>>;
46
+ readonly inputs: Readonly<WorkflowInputValues>;
44
47
  readonly startTs: number;
45
48
  /** Stage IDs that were started (in order) but may or may not have ended. */
46
49
  readonly stageIds: readonly string[];
@@ -57,7 +60,7 @@ export interface InFlightRun {
57
60
  * Pure function — does not mutate anything.
58
61
  */
59
62
  export function scanInFlightRuns(entries: readonly SessionEntry[]): InFlightRun[] {
60
- const started = new Map<string, { name: string; inputs: Record<string, unknown>; startTs: number; stageIds: string[] }>();
63
+ const started = new Map<string, { name: string; inputs: WorkflowInputValues; startTs: number; stageIds: string[] }>();
61
64
  const ended = new Set<string>();
62
65
 
63
66
  for (const entry of entries) {
@@ -73,9 +76,7 @@ export function scanInFlightRuns(entries: readonly SessionEntry[]): InFlightRun[
73
76
  ) {
74
77
  started.set(runId, {
75
78
  name,
76
- inputs: (inputs !== null && typeof inputs === "object" && !Array.isArray(inputs))
77
- ? (inputs as Record<string, unknown>)
78
- : {},
79
+ inputs: serializableObjectOrEmpty(inputs),
79
80
  startTs: ts,
80
81
  stageIds: [],
81
82
  });
@@ -180,6 +181,9 @@ export function restoreOnSessionStart(
180
181
  status: "running",
181
182
  stages,
182
183
  startedAt: run.startTs,
184
+ ...(runMeta.parentRunId !== undefined ? { parentRunId: runMeta.parentRunId } : {}),
185
+ ...(runMeta.parentStageId !== undefined ? { parentStageId: runMeta.parentStageId } : {}),
186
+ ...(runMeta.rootRunId !== undefined ? { rootRunId: runMeta.rootRunId } : {}),
183
187
  ...(runMeta.resumedFromRunId !== undefined ? { resumedFromRunId: runMeta.resumedFromRunId } : {}),
184
188
  ...(runMeta.resumeFromStageId !== undefined ? { resumeFromStageId: runMeta.resumeFromStageId } : {}),
185
189
  };
@@ -198,6 +202,9 @@ export function restoreOnSessionStart(
198
202
  error: "Run did not complete — process was interrupted.",
199
203
  failureKind: "unknown",
200
204
  resumable: false,
205
+ ...(runMeta.parentRunId !== undefined ? { parentRunId: runMeta.parentRunId } : {}),
206
+ ...(runMeta.parentStageId !== undefined ? { parentStageId: runMeta.parentStageId } : {}),
207
+ ...(runMeta.rootRunId !== undefined ? { rootRunId: runMeta.rootRunId } : {}),
201
208
  ...(runMeta.resumedFromRunId !== undefined ? { resumedFromRunId: runMeta.resumedFromRunId } : {}),
202
209
  ...(runMeta.resumeFromStageId !== undefined ? { resumeFromStageId: runMeta.resumeFromStageId } : {}),
203
210
  };
@@ -262,7 +269,7 @@ function _buildStageSnapshots(
262
269
  if (typeof failureKind === "string" && isWorkflowFailureKind(failureKind)) snap.failureKind = failureKind;
263
270
  if (typeof failureMessage === "string") snap.failureMessage = failureMessage;
264
271
  if (typeof skippedReason === "string") snap.skippedReason = skippedReason;
265
- Object.assign(snap, replayMetadata(entry.payload));
272
+ Object.assign(snap, replayMetadata(entry.payload), workflowChildMetadata(entry.payload));
266
273
  }
267
274
  }
268
275
  }
@@ -289,6 +296,66 @@ function replayMetadata(payload: Record<string, unknown>): Pick<StageSnapshot, "
289
296
  };
290
297
  }
291
298
 
299
+ function isRecord(value: unknown): value is Record<string, unknown> {
300
+ return value !== null && typeof value === "object" && !Array.isArray(value);
301
+ }
302
+
303
+ function serializableObject(value: unknown): WorkflowOutputValues | undefined {
304
+ return Value.Check(workflowSerializableObjectSchema, value)
305
+ ? (value as WorkflowOutputValues)
306
+ : undefined;
307
+ }
308
+
309
+ function serializableObjectOrEmpty(value: unknown): WorkflowOutputValues {
310
+ return serializableObject(value) ?? {};
311
+ }
312
+
313
+ function isWorkflowChildReplayStatus(status: unknown): status is WorkflowChildReplaySnapshot["status"] {
314
+ return status === "completed";
315
+ }
316
+
317
+ function workflowChildMetadata(payload: Record<string, unknown>): Pick<StageSnapshot, "workflowChild"> {
318
+ const workflowChild = payload["workflowChild"];
319
+ if (!isRecord(workflowChild)) return {};
320
+ const alias = workflowChild["alias"];
321
+ const workflow = workflowChild["workflow"];
322
+ const childRunId = workflowChild["runId"];
323
+ const status = workflowChild["status"];
324
+ const outputs = workflowChild["outputs"];
325
+ if (
326
+ typeof alias !== "string" ||
327
+ typeof workflow !== "string" ||
328
+ typeof childRunId !== "string" ||
329
+ !isWorkflowChildReplayStatus(status) ||
330
+ !isRecord(outputs)
331
+ ) {
332
+ return {};
333
+ }
334
+
335
+ // `structuredClone` detaches the restored snapshot from the parsed JSONL
336
+ // payload with a guaranteed deep copy, independent of the TypeBox
337
+ // serializable check. Declared `outputs` are the child contract, so a
338
+ // non-serializable value bails the whole child snapshot.
339
+ let clonedOutputs: WorkflowOutputValues;
340
+ try {
341
+ const serializableOutputs = serializableObject(outputs);
342
+ if (serializableOutputs === undefined) return {};
343
+ clonedOutputs = structuredClone(serializableOutputs);
344
+ } catch {
345
+ return {};
346
+ }
347
+
348
+ return {
349
+ workflowChild: {
350
+ alias,
351
+ workflow,
352
+ runId: childRunId,
353
+ status,
354
+ outputs: clonedOutputs,
355
+ },
356
+ };
357
+ }
358
+
292
359
  function restoreStageStatus(status: unknown): StageStatus {
293
360
  switch (status) {
294
361
  case "completed":
@@ -301,7 +368,7 @@ function restoreStageStatus(status: unknown): StageStatus {
301
368
  }
302
369
 
303
370
  function restoreTerminalRuns(entries: readonly SessionEntry[], store: Store): void {
304
- const started = new Map<string, { readonly name: string; readonly inputs: Readonly<Record<string, unknown>>; readonly startTs: number }>();
371
+ const started = new Map<string, { readonly name: string; readonly inputs: Readonly<WorkflowInputValues>; readonly startTs: number }>();
305
372
  const ended = new Map<string, Record<string, unknown>>();
306
373
 
307
374
  for (const entry of entries) {
@@ -313,9 +380,7 @@ function restoreTerminalRuns(entries: readonly SessionEntry[], store: Store): vo
313
380
  if (typeof runId === "string" && typeof name === "string" && typeof ts === "number") {
314
381
  started.set(runId, {
315
382
  name,
316
- inputs: (inputs !== null && typeof inputs === "object" && !Array.isArray(inputs))
317
- ? (inputs as Record<string, unknown>)
318
- : {},
383
+ inputs: serializableObjectOrEmpty(inputs),
319
384
  startTs: ts,
320
385
  });
321
386
  }
@@ -342,6 +407,9 @@ function restoreTerminalRuns(entries: readonly SessionEntry[], store: Store): vo
342
407
  status: "running",
343
408
  stages,
344
409
  startedAt: start.startTs,
410
+ ...(runMeta.parentRunId !== undefined ? { parentRunId: runMeta.parentRunId } : {}),
411
+ ...(runMeta.parentStageId !== undefined ? { parentStageId: runMeta.parentStageId } : {}),
412
+ ...(runMeta.rootRunId !== undefined ? { rootRunId: runMeta.rootRunId } : {}),
345
413
  ...(runMeta.resumedFromRunId !== undefined ? { resumedFromRunId: runMeta.resumedFromRunId } : {}),
346
414
  ...(runMeta.resumeFromStageId !== undefined ? { resumeFromStageId: runMeta.resumeFromStageId } : {}),
347
415
  });
@@ -380,12 +448,24 @@ function restoreTerminalRunStatus(status: unknown): "completed" | "failed" | "ki
380
448
  function findRunStartMetadata(
381
449
  entries: readonly SessionEntry[],
382
450
  runId: string,
383
- ): { readonly resumedFromRunId?: string; readonly resumeFromStageId?: string } {
451
+ ): {
452
+ readonly parentRunId?: string;
453
+ readonly parentStageId?: string;
454
+ readonly rootRunId?: string;
455
+ readonly resumedFromRunId?: string;
456
+ readonly resumeFromStageId?: string;
457
+ } {
384
458
  for (const entry of entries) {
385
459
  if (entry.type !== "workflow.run.start" || entry.payload["runId"] !== runId) continue;
460
+ const parentRunId = entry.payload["parentRunId"];
461
+ const parentStageId = entry.payload["parentStageId"];
462
+ const rootRunId = entry.payload["rootRunId"];
386
463
  const resumedFromRunId = entry.payload["resumedFromRunId"];
387
464
  const resumeFromStageId = entry.payload["resumeFromStageId"];
388
465
  return {
466
+ ...(typeof parentRunId === "string" ? { parentRunId } : {}),
467
+ ...(typeof parentStageId === "string" ? { parentStageId } : {}),
468
+ ...(typeof rootRunId === "string" ? { rootRunId } : {}),
389
469
  ...(typeof resumedFromRunId === "string" ? { resumedFromRunId } : {}),
390
470
  ...(typeof resumeFromStageId === "string" ? { resumeFromStageId } : {}),
391
471
  };
@@ -6,6 +6,8 @@
6
6
  * through gracefully when the runtime doesn't support the method.
7
7
  */
8
8
 
9
+ import type { WorkflowInputValues, WorkflowOutputValues } from "./types.js";
10
+
9
11
  // ---------------------------------------------------------------------------
10
12
  // Structural API type (subset of ExtensionAPI needed here)
11
13
  // ---------------------------------------------------------------------------
@@ -30,7 +32,10 @@ export interface PersistenceAPI {
30
32
  export interface RunStartPayload {
31
33
  readonly runId: string;
32
34
  readonly name: string;
33
- readonly inputs: Readonly<Record<string, unknown>>;
35
+ readonly inputs: Readonly<WorkflowInputValues>;
36
+ readonly parentRunId?: string;
37
+ readonly parentStageId?: string;
38
+ readonly rootRunId?: string;
34
39
  readonly resumedFromRunId?: string;
35
40
  readonly resumeFromStageId?: string;
36
41
  readonly ts: number;
@@ -55,6 +60,14 @@ export interface StageProgressPayload {
55
60
  readonly payload: unknown;
56
61
  }
57
62
 
63
+ export interface WorkflowChildReplayPayload {
64
+ readonly alias: string;
65
+ readonly workflow: string;
66
+ readonly runId: string;
67
+ readonly status: "completed";
68
+ readonly outputs: WorkflowOutputValues;
69
+ }
70
+
58
71
  export interface StageEndPayload {
59
72
  readonly runId: string;
60
73
  readonly stageId: string;
@@ -68,12 +81,13 @@ export interface StageEndPayload {
68
81
  readonly replayKey?: string;
69
82
  readonly replayedFromStageId?: string;
70
83
  readonly replayed?: boolean;
84
+ readonly workflowChild?: WorkflowChildReplayPayload;
71
85
  }
72
86
 
73
87
  export interface RunEndPayload {
74
88
  readonly runId: string;
75
89
  readonly status: string;
76
- readonly result?: unknown;
90
+ readonly result?: WorkflowOutputValues;
77
91
  readonly error?: string;
78
92
  readonly failureKind?: string;
79
93
  readonly failureMessage?: string;
@@ -96,6 +110,9 @@ export function appendRunStart(api: PersistenceAPI, payload: RunStartPayload): v
96
110
  runId: payload.runId,
97
111
  name: payload.name,
98
112
  inputs: payload.inputs,
113
+ ...(payload.parentRunId !== undefined ? { parentRunId: payload.parentRunId } : {}),
114
+ ...(payload.parentStageId !== undefined ? { parentStageId: payload.parentStageId } : {}),
115
+ ...(payload.rootRunId !== undefined ? { rootRunId: payload.rootRunId } : {}),
99
116
  ...(payload.resumedFromRunId !== undefined ? { resumedFromRunId: payload.resumedFromRunId } : {}),
100
117
  ...(payload.resumeFromStageId !== undefined ? { resumeFromStageId: payload.resumeFromStageId } : {}),
101
118
  ts: payload.ts,
@@ -153,6 +170,7 @@ export function appendStageEnd(
153
170
  ...(payload.replayKey !== undefined ? { replayKey: payload.replayKey } : {}),
154
171
  ...(payload.replayedFromStageId !== undefined ? { replayedFromStageId: payload.replayedFromStageId } : {}),
155
172
  ...(payload.replayed !== undefined ? { replayed: payload.replayed } : {}),
173
+ ...(payload.workflowChild !== undefined ? { workflowChild: payload.workflowChild } : {}),
156
174
  });
157
175
  if (opts?.emitMessage === true && payload.summary && typeof api.appendCustomMessageEntry === "function") {
158
176
  api.appendCustomMessageEntry(
@@ -168,7 +186,7 @@ export function appendRunEnd(api: PersistenceAPI, payload: RunEndPayload): void
168
186
  api.appendEntry("workflow.run.end", {
169
187
  runId: payload.runId,
170
188
  status: payload.status,
171
- ...(payload.result !== undefined ? { result: payload.result as Record<string, unknown> } : {}),
189
+ ...(payload.result !== undefined ? { result: payload.result } : {}),
172
190
  ...(payload.error !== undefined ? { error: payload.error } : {}),
173
191
  ...(payload.failureKind !== undefined ? { failureKind: payload.failureKind } : {}),
174
192
  ...(payload.failureMessage !== undefined ? { failureMessage: payload.failureMessage } : {}),
@@ -19,7 +19,7 @@
19
19
  * and for non-TTY consumers (LLM tool results, logfiles, --help in
20
20
  * redirected output).
21
21
  *
22
- * The layout in pretty mode mirrors flora131/atomic's `renderInputsText` so a
22
+ * The layout in pretty mode mirrors bastani-inc/atomic's `renderInputsText` so a
23
23
  * dev moving between the atomic CLI and the pi extension feels at home:
24
24
  *
25
25
  * INPUTS FOR ralph
@@ -42,7 +42,6 @@
42
42
  import type { GraphTheme } from "../tui/graph-theme.js";
43
43
  import { paint } from "../tui/color-utils.js";
44
44
  import { renderRoundedBox, chatWidth } from "../tui/chat-surface.js";
45
- import { truncateToWidth } from "../tui/text-helpers.js";
46
45
  import type { WorkflowInputEntry } from "../extension/render-result.js";
47
46
 
48
47
  export interface RenderInputsSchemaOptions {
@@ -0,0 +1,9 @@
1
+ import type { RunSnapshot } from "./store-types.js";
2
+
3
+ export function isTopLevelWorkflowRun(run: Pick<RunSnapshot, "parentRunId">): boolean {
4
+ return run.parentRunId === undefined;
5
+ }
6
+
7
+ export function topLevelWorkflowRuns(runs: readonly RunSnapshot[]): RunSnapshot[] {
8
+ return runs.filter(isTopLevelWorkflowRun);
9
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * schema-introspection — single adapter between TypeBox input/output schemas
3
+ * and the legacy normalized field descriptor (`WorkflowInputEntry`) consumed by
4
+ * the inputs picker UI, validation, dispatch, and render paths.
5
+ *
6
+ * Authors declare inputs/outputs with TypeBox schemas (`Type.String`,
7
+ * `Type.Number`, `Type.Union([...literals])`, …). The many UI/render/dispatch
8
+ * surfaces still want a flat `{ type, choices?, default?, required?,
9
+ * description? }` view; rather than rewrite all of them, they derive that view
10
+ * from a TypeBox schema through this module.
11
+ */
12
+
13
+ import {
14
+ IsAny,
15
+ IsArray,
16
+ IsBoolean,
17
+ IsInteger,
18
+ IsLiteralString,
19
+ IsNumber,
20
+ IsObject,
21
+ IsOptional,
22
+ IsString,
23
+ IsUnion,
24
+ type TSchema,
25
+ } from "typebox";
26
+ import type { WorkflowInputEntry } from "../extension/render-result.js";
27
+
28
+ /** Field-kind label expected by the legacy descriptor consumers. */
29
+ export type SchemaFieldKind =
30
+ | "text"
31
+ | "number"
32
+ | "integer"
33
+ | "boolean"
34
+ | "select"
35
+ | "object"
36
+ | "array"
37
+ | "unknown";
38
+
39
+ interface SchemaMeta {
40
+ readonly default?: unknown;
41
+ readonly description?: string;
42
+ readonly anyOf?: readonly TSchema[];
43
+ readonly const?: unknown;
44
+ }
45
+
46
+ function meta(schema: TSchema): SchemaMeta {
47
+ return schema as unknown as SchemaMeta;
48
+ }
49
+
50
+ /** True when every member of a union is a string literal. */
51
+ function isStringLiteralUnion(schema: TSchema): boolean {
52
+ if (!IsUnion(schema)) return false;
53
+ const members = meta(schema).anyOf ?? [];
54
+ return members.length > 0 && members.every((m) => IsLiteralString(m));
55
+ }
56
+
57
+ /** Map a TypeBox schema to the legacy field-kind label. */
58
+ export function schemaFieldKind(schema: TSchema): SchemaFieldKind {
59
+ if (isStringLiteralUnion(schema)) return "select";
60
+ if (IsLiteralString(schema)) return "text";
61
+ if (IsString(schema)) return "text";
62
+ if (IsInteger(schema)) return "integer";
63
+ if (IsNumber(schema)) return "number";
64
+ if (IsBoolean(schema)) return "boolean";
65
+ if (IsObject(schema)) return "object";
66
+ if (IsArray(schema)) return "array";
67
+ if (IsAny(schema)) return "unknown";
68
+ return "unknown";
69
+ }
70
+
71
+ /** Declared default value (`schema.default`), or undefined when none. */
72
+ export function schemaDefault(schema: TSchema): unknown {
73
+ return meta(schema).default;
74
+ }
75
+
76
+ /** Declared description, or undefined. */
77
+ export function schemaDescription(schema: TSchema): string | undefined {
78
+ return meta(schema).description;
79
+ }
80
+
81
+ /** Allowed string values for a string-literal union, else undefined. */
82
+ export function schemaChoices(schema: TSchema): readonly string[] | undefined {
83
+ if (!isStringLiteralUnion(schema)) return undefined;
84
+ const members = meta(schema).anyOf ?? [];
85
+ return members.map((m) => String(meta(m).const));
86
+ }
87
+
88
+ /**
89
+ * A field is "required" in the picker/validation sense — i.e. the caller MUST
90
+ * supply it — when it is neither wrapped in `Type.Optional(...)` nor carries a
91
+ * `default`. A defaulted input is a required KEY at the type level (it is always
92
+ * present after defaults are applied) but the user need not provide it, so it is
93
+ * reported as not-required here, matching the legacy descriptor semantics where
94
+ * `{ default }` implied `required: false`.
95
+ */
96
+ export function schemaIsRequired(schema: TSchema): boolean {
97
+ return !IsOptional(schema) && schemaDefault(schema) === undefined;
98
+ }
99
+
100
+ /** Derive the legacy normalized input descriptor for a single named field. */
101
+ export function deriveInputField(name: string, schema: TSchema): WorkflowInputEntry {
102
+ const choices = schemaChoices(schema);
103
+ const def = schemaDefault(schema);
104
+ const description = schemaDescription(schema);
105
+ const entry: WorkflowInputEntry = {
106
+ name,
107
+ type: schemaFieldKind(schema),
108
+ required: schemaIsRequired(schema),
109
+ };
110
+ if (description !== undefined) entry.description = description;
111
+ if (def !== undefined) entry.default = def;
112
+ if (choices !== undefined) entry.choices = choices;
113
+ return entry;
114
+ }
115
+
116
+ /** Derive descriptors for an entire declared input map (preserving order). */
117
+ export function deriveInputFields(
118
+ schema: Readonly<Record<string, TSchema>>,
119
+ ): WorkflowInputEntry[] {
120
+ return Object.entries(schema).map(([name, s]) => deriveInputField(name, s));
121
+ }
@@ -0,0 +1,132 @@
1
+ import { Refine, Type, type TSchema } from "typebox";
2
+
3
+ /**
4
+ * A JSON-serializable object must be a *plain* object (or array, handled
5
+ * separately) — class instances such as `Date`, `Map`, or `RegExp` structurally
6
+ * look like empty records to the schema checker but are not JSON round-trippable,
7
+ * so they are rejected here.
8
+ */
9
+ function isPlainObjectValue(value: unknown): boolean {
10
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return true;
11
+ const proto = Object.getPrototypeOf(value);
12
+ return proto === Object.prototype || proto === null;
13
+ }
14
+ import { Value } from "typebox/value";
15
+ import type {
16
+ WorkflowOutputValues,
17
+ WorkflowSerializableValue,
18
+ } from "./types.js";
19
+
20
+ export const WORKFLOW_SERIALIZABLE_DESCRIPTION =
21
+ "JSON-serializable (string, finite number, boolean, null, array, or object)";
22
+
23
+ /**
24
+ * Recursive TypeBox schema describing a JSON-serializable value: string, finite
25
+ * number, boolean, null, array of serializable values, or an object whose
26
+ * values are serializable. `Type.Number` already rejects NaN/Infinity in
27
+ * TypeBox's value checker, matching the previous `z.number().finite()`.
28
+ */
29
+ export const workflowSerializableValueSchema: TSchema = Type.Cyclic(
30
+ {
31
+ Serializable: Type.Union([
32
+ Type.String(),
33
+ Type.Number(),
34
+ Type.Boolean(),
35
+ Type.Null(),
36
+ Type.Array(Type.Ref("Serializable")),
37
+ Refine(
38
+ Type.Record(Type.String(), Type.Ref("Serializable")),
39
+ isPlainObjectValue,
40
+ "must be a plain JSON object",
41
+ ),
42
+ ]),
43
+ },
44
+ "Serializable",
45
+ );
46
+
47
+ export const workflowSerializableObjectSchema: TSchema = Type.Record(
48
+ Type.String(),
49
+ workflowSerializableValueSchema,
50
+ );
51
+
52
+ export function workflowSerializableTypeName(value: unknown): string {
53
+ if (value === null) return "null";
54
+ if (Array.isArray(value)) return "array";
55
+ if (typeof value === "number" && Number.isNaN(value)) return "NaN";
56
+ if (typeof value === "number" && !Number.isFinite(value)) return String(value);
57
+ return typeof value;
58
+ }
59
+
60
+ /** Convert a TypeBox `instancePath` ("/a/0/b") to the legacy "a[0].b" form. */
61
+ function formatInstancePath(instancePath: string): string {
62
+ if (!instancePath) return "";
63
+ const segments = instancePath.split("/").filter((s) => s.length > 0);
64
+ return segments
65
+ .map((segment) =>
66
+ /^\d+$/.test(segment)
67
+ ? `[${segment}]`
68
+ : /^[A-Za-z_$][\w$]*$/.test(segment)
69
+ ? `.${segment}`
70
+ : `[${JSON.stringify(segment)}]`,
71
+ )
72
+ .join("")
73
+ .replace(/^\./, "");
74
+ }
75
+
76
+ /** Resolve the value located at a TypeBox instance path within `root`. */
77
+ function valueAtInstancePath(root: unknown, instancePath: string): unknown {
78
+ if (!instancePath) return root;
79
+ let current: unknown = root;
80
+ for (const segment of instancePath.split("/").filter((s) => s.length > 0)) {
81
+ if (current === null || typeof current !== "object") return current;
82
+ current = (current as Record<string, unknown>)[segment];
83
+ }
84
+ return current;
85
+ }
86
+
87
+ function firstError(schema: TSchema, value: unknown): { instancePath: string } | undefined {
88
+ for (const error of Value.Errors(schema, value)) {
89
+ return { instancePath: error.instancePath };
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ export function workflowSerializableValidationError(
95
+ value: unknown,
96
+ label: string,
97
+ ): string | undefined {
98
+ if (Value.Check(workflowSerializableValueSchema, value)) return undefined;
99
+ const issue = firstError(workflowSerializableValueSchema, value);
100
+ const issuePath = issue === undefined ? "" : formatInstancePath(issue.instancePath);
101
+ const location = issuePath.length > 0 ? ` at ${issuePath}` : "";
102
+ const offending = issue === undefined ? value : valueAtInstancePath(value, issue.instancePath);
103
+ return `${label}${location} must be ${WORKFLOW_SERIALIZABLE_DESCRIPTION}, got ${workflowSerializableTypeName(offending)}`;
104
+ }
105
+
106
+ export function workflowSerializableObjectValidationError(
107
+ value: unknown,
108
+ label: string,
109
+ ): string | undefined {
110
+ if (Value.Check(workflowSerializableObjectSchema, value)) return undefined;
111
+ const issue = firstError(workflowSerializableObjectSchema, value);
112
+ const issuePath = issue === undefined ? "" : formatInstancePath(issue.instancePath);
113
+ const location = issuePath.length > 0 ? ` at ${issuePath}` : "";
114
+ const offending = issue === undefined ? value : valueAtInstancePath(value, issue.instancePath);
115
+ return `${label}${location} must be a ${WORKFLOW_SERIALIZABLE_DESCRIPTION} object, got ${workflowSerializableTypeName(offending)}`;
116
+ }
117
+
118
+ export function assertWorkflowSerializableValue(
119
+ value: unknown,
120
+ label: string,
121
+ ): asserts value is WorkflowSerializableValue {
122
+ const error = workflowSerializableValidationError(value, label);
123
+ if (error !== undefined) throw new Error(`atomic-workflows: ${error}`);
124
+ }
125
+
126
+ export function assertWorkflowSerializableObject(
127
+ value: unknown,
128
+ label: string,
129
+ ): asserts value is WorkflowOutputValues {
130
+ const error = workflowSerializableObjectValidationError(value, label);
131
+ if (error !== undefined) throw new Error(`atomic-workflows: ${error}`);
132
+ }