@bastani/atomic 0.9.5-alpha.6 → 0.9.5-alpha.8

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 (283) hide show
  1. package/CHANGELOG.md +35 -2
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/CHANGELOG.md +17 -0
  4. package/dist/builtin/intercom/README.md +27 -21
  5. package/dist/builtin/intercom/broker/broker.ts +3 -6
  6. package/dist/builtin/intercom/broker/paths.ts +42 -5
  7. package/dist/builtin/intercom/broker/spawn.ts +78 -10
  8. package/dist/builtin/intercom/config.ts +6 -3
  9. package/dist/builtin/intercom/contact-supervisor-tool.ts +6 -4
  10. package/dist/builtin/intercom/index-heavy.ts +1 -1
  11. package/dist/builtin/intercom/index.ts +162 -130
  12. package/dist/builtin/intercom/intercom-tool.ts +9 -3
  13. package/dist/builtin/intercom/package.json +10 -1
  14. package/dist/builtin/intercom/result-renderers.ts +1 -1
  15. package/dist/builtin/intercom/ui/compose.ts +2 -2
  16. package/dist/builtin/intercom/ui/inline-message.ts +2 -2
  17. package/dist/builtin/intercom/ui/session-list.ts +2 -2
  18. package/dist/builtin/mcp/package.json +1 -1
  19. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  20. package/dist/builtin/subagents/package.json +1 -1
  21. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +15 -48
  22. package/dist/builtin/subagents/src/tui/render-layout.ts +9 -0
  23. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +27 -25
  24. package/dist/builtin/subagents/src/tui/render-widget.ts +46 -69
  25. package/dist/builtin/subagents/src/tui/render.ts +1 -1
  26. package/dist/builtin/web-access/package.json +1 -1
  27. package/dist/builtin/workflows/CHANGELOG.md +11 -0
  28. package/dist/builtin/workflows/README.md +3 -1
  29. package/dist/builtin/workflows/builtin/goal-artifacts.ts +3 -1
  30. package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -2
  31. package/dist/builtin/workflows/builtin/goal-reducer.ts +21 -0
  32. package/dist/builtin/workflows/builtin/goal-review.ts +60 -6
  33. package/dist/builtin/workflows/builtin/goal-runner.ts +32 -21
  34. package/dist/builtin/workflows/builtin/goal-types.ts +7 -1
  35. package/dist/builtin/workflows/builtin/ralph-core.ts +52 -4
  36. package/dist/builtin/workflows/builtin/ralph-review-gate.ts +10 -3
  37. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +23 -2
  38. package/dist/builtin/workflows/builtin/ralph-runner.ts +38 -16
  39. package/dist/builtin/workflows/builtin/review-convergence.ts +111 -0
  40. package/dist/builtin/workflows/package.json +1 -1
  41. package/dist/builtin/workflows/skills/create-spec/SKILL.md +2 -0
  42. package/dist/builtin/workflows/src/durable/child-primitive.ts +12 -8
  43. package/dist/builtin/workflows/src/durable/dbos-backend.ts +47 -2
  44. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +28 -0
  45. package/dist/builtin/workflows/src/durable/stage-primitive.ts +165 -27
  46. package/dist/builtin/workflows/src/durable/types.ts +14 -1
  47. package/dist/builtin/workflows/src/engine/run.ts +12 -13
  48. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +2 -0
  49. package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +13 -3
  50. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +1 -1
  51. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +18 -19
  52. package/dist/builtin/workflows/src/runs/foreground/stage-runner-unresolved-overflow.ts +38 -0
  53. package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +18 -1
  54. package/dist/builtin/workflows/src/shared/persistence-restore.ts +23 -5
  55. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
  56. package/dist/bun/internal-intercom-broker.d.ts +6 -0
  57. package/dist/bun/internal-intercom-broker.d.ts.map +1 -0
  58. package/dist/bun/internal-intercom-broker.js +39 -0
  59. package/dist/bun/internal-intercom-broker.js.map +1 -0
  60. package/dist/bun/split-loader.d.ts +2 -0
  61. package/dist/bun/split-loader.d.ts.map +1 -0
  62. package/dist/bun/split-loader.js +50 -0
  63. package/dist/bun/split-loader.js.map +1 -0
  64. package/dist/config.d.ts.map +1 -1
  65. package/dist/config.js +6 -4
  66. package/dist/config.js.map +1 -1
  67. package/dist/core/agent-session-auto-compaction.d.ts +7 -7
  68. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  69. package/dist/core/agent-session-auto-compaction.js +62 -16
  70. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  71. package/dist/core/agent-session-events.d.ts.map +1 -1
  72. package/dist/core/agent-session-events.js +3 -1
  73. package/dist/core/agent-session-events.js.map +1 -1
  74. package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
  75. package/dist/core/agent-session-extension-bindings.js +1 -1
  76. package/dist/core/agent-session-extension-bindings.js.map +1 -1
  77. package/dist/core/agent-session-methods.d.ts +10 -4
  78. package/dist/core/agent-session-methods.d.ts.map +1 -1
  79. package/dist/core/agent-session-methods.js.map +1 -1
  80. package/dist/core/agent-session-models.d.ts +2 -2
  81. package/dist/core/agent-session-models.d.ts.map +1 -1
  82. package/dist/core/agent-session-models.js.map +1 -1
  83. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  84. package/dist/core/agent-session-prompt.js +1 -0
  85. package/dist/core/agent-session-prompt.js.map +1 -1
  86. package/dist/core/agent-session-retry.d.ts +2 -0
  87. package/dist/core/agent-session-retry.d.ts.map +1 -1
  88. package/dist/core/agent-session-retry.js +152 -4
  89. package/dist/core/agent-session-retry.js.map +1 -1
  90. package/dist/core/agent-session-services.d.ts +1 -0
  91. package/dist/core/agent-session-services.d.ts.map +1 -1
  92. package/dist/core/agent-session-services.js +1 -0
  93. package/dist/core/agent-session-services.js.map +1 -1
  94. package/dist/core/agent-session-types.d.ts +15 -1
  95. package/dist/core/agent-session-types.d.ts.map +1 -1
  96. package/dist/core/agent-session-types.js.map +1 -1
  97. package/dist/core/agent-session.d.ts +4 -0
  98. package/dist/core/agent-session.d.ts.map +1 -1
  99. package/dist/core/agent-session.js +4 -0
  100. package/dist/core/agent-session.js.map +1 -1
  101. package/dist/core/builtin-packages.d.ts.map +1 -1
  102. package/dist/core/builtin-packages.js +8 -4
  103. package/dist/core/builtin-packages.js.map +1 -1
  104. package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
  105. package/dist/core/compaction/context-compaction-runner.js +36 -20
  106. package/dist/core/compaction/context-compaction-runner.js.map +1 -1
  107. package/dist/core/copilot-anthropic-sse-repair.d.ts +23 -0
  108. package/dist/core/copilot-anthropic-sse-repair.d.ts.map +1 -0
  109. package/dist/core/copilot-anthropic-sse-repair.js +340 -0
  110. package/dist/core/copilot-anthropic-sse-repair.js.map +1 -0
  111. package/dist/core/copilot-gemini-reasoning.d.ts +16 -8
  112. package/dist/core/copilot-gemini-reasoning.d.ts.map +1 -1
  113. package/dist/core/copilot-gemini-reasoning.js +27 -22
  114. package/dist/core/copilot-gemini-reasoning.js.map +1 -1
  115. package/dist/core/copilot-hosts.d.ts +12 -0
  116. package/dist/core/copilot-hosts.d.ts.map +1 -0
  117. package/dist/core/copilot-hosts.js +33 -0
  118. package/dist/core/copilot-hosts.js.map +1 -0
  119. package/dist/core/copilot-model-catalog.d.ts.map +1 -1
  120. package/dist/core/copilot-model-catalog.js +3 -2
  121. package/dist/core/copilot-model-catalog.js.map +1 -1
  122. package/dist/core/extensions/agent-events.d.ts +1 -1
  123. package/dist/core/extensions/agent-events.d.ts.map +1 -1
  124. package/dist/core/extensions/agent-events.js.map +1 -1
  125. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  126. package/dist/core/extensions/loader-virtual-modules.js +8 -3
  127. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  128. package/dist/core/http-dispatcher.d.ts.map +1 -1
  129. package/dist/core/http-dispatcher.js +5 -5
  130. package/dist/core/http-dispatcher.js.map +1 -1
  131. package/dist/core/openai-responses-payload-sanitizer.d.ts +6 -0
  132. package/dist/core/openai-responses-payload-sanitizer.d.ts.map +1 -0
  133. package/dist/core/openai-responses-payload-sanitizer.js +59 -0
  134. package/dist/core/openai-responses-payload-sanitizer.js.map +1 -0
  135. package/dist/core/package-manager-auto-resources.d.ts +2 -2
  136. package/dist/core/package-manager-auto-resources.d.ts.map +1 -1
  137. package/dist/core/package-manager-auto-resources.js +32 -52
  138. package/dist/core/package-manager-auto-resources.js.map +1 -1
  139. package/dist/core/package-manager-resolver.d.ts.map +1 -1
  140. package/dist/core/package-manager-resolver.js +23 -14
  141. package/dist/core/package-manager-resolver.js.map +1 -1
  142. package/dist/core/package-manager-resource-collector.d.ts +3 -3
  143. package/dist/core/package-manager-resource-collector.d.ts.map +1 -1
  144. package/dist/core/package-manager-resource-collector.js +76 -73
  145. package/dist/core/package-manager-resource-collector.js.map +1 -1
  146. package/dist/core/package-manager-resource-files.d.ts +9 -8
  147. package/dist/core/package-manager-resource-files.d.ts.map +1 -1
  148. package/dist/core/package-manager-resource-files.js +92 -81
  149. package/dist/core/package-manager-resource-files.js.map +1 -1
  150. package/dist/core/prompt-templates-async.d.ts +3 -0
  151. package/dist/core/prompt-templates-async.d.ts.map +1 -0
  152. package/dist/core/prompt-templates-async.js +118 -0
  153. package/dist/core/prompt-templates-async.js.map +1 -0
  154. package/dist/core/provider-context-usage.d.ts +11 -0
  155. package/dist/core/provider-context-usage.d.ts.map +1 -0
  156. package/dist/core/provider-context-usage.js +58 -0
  157. package/dist/core/provider-context-usage.js.map +1 -0
  158. package/dist/core/resource-loader-assets.d.ts +3 -3
  159. package/dist/core/resource-loader-assets.d.ts.map +1 -1
  160. package/dist/core/resource-loader-assets.js +66 -83
  161. package/dist/core/resource-loader-assets.js.map +1 -1
  162. package/dist/core/resource-loader-core.d.ts +1 -1
  163. package/dist/core/resource-loader-core.d.ts.map +1 -1
  164. package/dist/core/resource-loader-core.js +5 -5
  165. package/dist/core/resource-loader-core.js.map +1 -1
  166. package/dist/core/resource-loader-reload.d.ts.map +1 -1
  167. package/dist/core/resource-loader-reload.js +43 -7
  168. package/dist/core/resource-loader-reload.js.map +1 -1
  169. package/dist/core/resource-loader-types.d.ts +7 -1
  170. package/dist/core/resource-loader-types.d.ts.map +1 -1
  171. package/dist/core/resource-loader-types.js.map +1 -1
  172. package/dist/core/sdk-types.d.ts +2 -0
  173. package/dist/core/sdk-types.d.ts.map +1 -1
  174. package/dist/core/sdk-types.js.map +1 -1
  175. package/dist/core/sdk.d.ts.map +1 -1
  176. package/dist/core/sdk.js +7 -4
  177. package/dist/core/sdk.js.map +1 -1
  178. package/dist/core/settings-manager-basic-accessors.d.ts +1 -0
  179. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  180. package/dist/core/settings-manager-basic-accessors.js +6 -0
  181. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  182. package/dist/core/settings-types.d.ts +1 -0
  183. package/dist/core/settings-types.d.ts.map +1 -1
  184. package/dist/core/settings-types.js.map +1 -1
  185. package/dist/core/skills-async.d.ts +3 -0
  186. package/dist/core/skills-async.d.ts.map +1 -0
  187. package/dist/core/skills-async.js +269 -0
  188. package/dist/core/skills-async.js.map +1 -0
  189. package/dist/index-extensions.d.ts +1 -1
  190. package/dist/index-extensions.d.ts.map +1 -1
  191. package/dist/index-extensions.js.map +1 -1
  192. package/dist/main-deferred-startup.d.ts +14 -0
  193. package/dist/main-deferred-startup.d.ts.map +1 -1
  194. package/dist/main-deferred-startup.js +23 -0
  195. package/dist/main-deferred-startup.js.map +1 -1
  196. package/dist/main-early-input.d.ts +41 -0
  197. package/dist/main-early-input.d.ts.map +1 -0
  198. package/dist/main-early-input.js +147 -0
  199. package/dist/main-early-input.js.map +1 -0
  200. package/dist/main.d.ts +0 -6
  201. package/dist/main.d.ts.map +1 -1
  202. package/dist/main.js +23 -17
  203. package/dist/main.js.map +1 -1
  204. package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/chat-session-host-events.js +15 -0
  206. package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
  207. package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
  208. package/dist/modes/interactive/interactive-agent-events.js +34 -1
  209. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  210. package/dist/modes/interactive/interactive-deferred-startup.d.ts +6 -0
  211. package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
  212. package/dist/modes/interactive/interactive-deferred-startup.js +27 -13
  213. package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
  214. package/dist/modes/interactive/interactive-editor-actions.d.ts.map +1 -1
  215. package/dist/modes/interactive/interactive-editor-actions.js +13 -13
  216. package/dist/modes/interactive/interactive-editor-actions.js.map +1 -1
  217. package/dist/modes/interactive/interactive-extension-runtime.d.ts.map +1 -1
  218. package/dist/modes/interactive/interactive-extension-runtime.js +16 -0
  219. package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
  220. package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
  221. package/dist/modes/interactive/interactive-input-handling.js +324 -178
  222. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  223. package/dist/modes/interactive/interactive-mode-base.d.ts +14 -0
  224. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  225. package/dist/modes/interactive/interactive-mode-base.js +41 -0
  226. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  227. package/dist/modes/interactive/interactive-mode-surface.d.ts +22 -6
  228. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  229. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  230. package/dist/modes/interactive/interactive-mode-types.d.ts +3 -0
  231. package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
  232. package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
  233. package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
  234. package/dist/modes/interactive/interactive-model-routing.js +3 -3
  235. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  236. package/dist/modes/interactive/interactive-onboarding.d.ts.map +1 -1
  237. package/dist/modes/interactive/interactive-onboarding.js +1 -0
  238. package/dist/modes/interactive/interactive-onboarding.js.map +1 -1
  239. package/dist/modes/interactive/interactive-process-lifecycle.d.ts.map +1 -1
  240. package/dist/modes/interactive/interactive-process-lifecycle.js +43 -6
  241. package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
  242. package/dist/modes/interactive/interactive-render-chat.d.ts.map +1 -1
  243. package/dist/modes/interactive/interactive-render-chat.js +78 -9
  244. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  245. package/dist/modes/interactive/interactive-resource-disclosure.d.ts.map +1 -1
  246. package/dist/modes/interactive/interactive-resource-disclosure.js +3 -2
  247. package/dist/modes/interactive/interactive-resource-disclosure.js.map +1 -1
  248. package/dist/modes/interactive/interactive-resource-rendering.d.ts.map +1 -1
  249. package/dist/modes/interactive/interactive-resource-rendering.js +10 -8
  250. package/dist/modes/interactive/interactive-resource-rendering.js.map +1 -1
  251. package/dist/modes/interactive/interactive-session-runtime.d.ts.map +1 -1
  252. package/dist/modes/interactive/interactive-session-runtime.js +1 -1
  253. package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
  254. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  255. package/dist/modes/interactive/interactive-startup.js +65 -53
  256. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  257. package/dist/modes/interactive/theme/theme-loading.d.ts +1 -0
  258. package/dist/modes/interactive/theme/theme-loading.d.ts.map +1 -1
  259. package/dist/modes/interactive/theme/theme-loading.js +3 -0
  260. package/dist/modes/interactive/theme/theme-loading.js.map +1 -1
  261. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  262. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  263. package/dist/modes/interactive/theme/theme.js +1 -1
  264. package/dist/modes/interactive/theme/theme.js.map +1 -1
  265. package/dist/utils/module-require.d.ts +11 -0
  266. package/dist/utils/module-require.d.ts.map +1 -1
  267. package/dist/utils/module-require.js +14 -1
  268. package/dist/utils/module-require.js.map +1 -1
  269. package/dist/utils/split-launcher.d.ts +38 -0
  270. package/dist/utils/split-launcher.d.ts.map +1 -0
  271. package/dist/utils/split-launcher.js +63 -0
  272. package/dist/utils/split-launcher.js.map +1 -0
  273. package/docs/compaction.md +7 -0
  274. package/docs/json.md +3 -1
  275. package/docs/keybindings.md +2 -2
  276. package/docs/providers.md +2 -1
  277. package/docs/rpc.md +3 -1
  278. package/docs/settings.md +28 -2
  279. package/docs/terminal-setup.md +4 -0
  280. package/docs/usage.md +3 -3
  281. package/docs/workflows.md +13 -3
  282. package/npm-shrinkwrap.json +513 -23
  283. package/package.json +5 -4
@@ -1,6 +1,6 @@
1
1
  /** Durable `ctx.stage` / `ctx.task` replay and checkpoint helpers. */
2
2
 
3
- import type { StageContext, StageOptions, WorkflowTaskOptions, WorkflowTaskResult } from "../shared/types.js";
3
+ import type { StageContext, StageOptions, WorkflowChildResult, WorkflowOutputValues, WorkflowTaskOptions, WorkflowTaskResult } from "../shared/types.js";
4
4
  import type { StageSnapshot } from "../shared/store-types.js";
5
5
  import type { WorkflowSerializableValue } from "../shared/types.js";
6
6
  import type { DurableWorkflowBackend } from "./backend.js";
@@ -8,6 +8,7 @@ import type { ParallelFailFastScope } from "../runs/foreground/executor-types.js
8
8
  import { durableHash } from "./backend.js";
9
9
  import { recordCheckpointDurably } from "./tool-primitive.js";
10
10
  import type { DurableStageCheckpoint } from "./types.js";
11
+ export type DurableCompletedStageCheckpoint = DurableStageCheckpoint & { readonly output: WorkflowSerializableValue };
11
12
 
12
13
  export interface DurableStageDeps {
13
14
  readonly workflowId: string;
@@ -20,16 +21,28 @@ export interface DurableStageDeps {
20
21
  export async function recordStageCheckpoint(deps: DurableStageDeps, stage: StageSnapshot): Promise<boolean> {
21
22
  if (stage.status !== "completed") return false;
22
23
  const replayKey = deps.replayKeyForCompletedStage?.(stage) ?? stage.replayKey ?? deps.nextReplayKey(stage.name);
23
- if (deps.backend.getStageOutput(deps.workflowId, replayKey) !== undefined) return false;
24
- const checkpoint: DurableStageCheckpoint = {
25
- kind: "stage",
26
- workflowId: deps.workflowId,
27
- checkpointId: stableCheckpointId("stage", replayKey),
28
- name: stage.name,
29
- replayKey,
30
- output: stageOutput(stage),
31
- completedAt: stage.endedAt ?? Date.now(),
32
- };
24
+ const metadata = checkpointMetadata(stage);
25
+ const hasExistingOutput = deps.backend.getStageOutput(deps.workflowId, replayKey) !== undefined;
26
+ const checkpoint: DurableStageCheckpoint = hasExistingOutput
27
+ ? {
28
+ kind: "stage",
29
+ workflowId: deps.workflowId,
30
+ checkpointId: stageMetadataCheckpointId(replayKey, stage),
31
+ name: stage.name,
32
+ replayKey,
33
+ completedAt: stage.endedAt ?? Date.now(),
34
+ ...metadata,
35
+ }
36
+ : {
37
+ kind: "stage",
38
+ workflowId: deps.workflowId,
39
+ checkpointId: stableCheckpointId("stage", replayKey),
40
+ name: stage.name,
41
+ replayKey,
42
+ output: stageOutput(stage),
43
+ completedAt: stage.endedAt ?? Date.now(),
44
+ ...metadata,
45
+ };
33
46
  await recordCheckpointDurably(deps.backend, checkpoint);
34
47
  return true;
35
48
  }
@@ -79,14 +92,14 @@ export function createDurableStagePrimitive(input: {
79
92
  readonly backend: DurableWorkflowBackend;
80
93
  readonly nextReplayKey: (stageName: string) => string;
81
94
  readonly stage: (name: string, options: StageOptions | undefined, replayKey: string) => StageContext;
82
- readonly recordCachedStage?: (name: string, replayKey: string, output: WorkflowSerializableValue) => void;
95
+ readonly recordCachedStage?: (name: string, replayKey: string, checkpoint: DurableCompletedStageCheckpoint) => void;
83
96
  }): (name: string, options?: StageOptions) => StageContext {
84
97
  return (name: string, options?: StageOptions): StageContext => {
85
98
  const replayKey = input.nextReplayKey(name);
86
- const cached = input.backend.getStageOutput(input.workflowId, replayKey);
99
+ const cached = stageCheckpointWithOutput(input.backend, input.workflowId, replayKey);
87
100
  if (cached !== undefined) {
88
101
  input.recordCachedStage?.(name, replayKey, cached);
89
- return createCachedStageContext(name, cached);
102
+ return createCachedStageContext(name, cached.output, cached.result);
90
103
  }
91
104
  const session = input.backend.getStageSession(input.workflowId, replayKey);
92
105
  const isMidSessionResume = session?.sessionFile !== undefined;
@@ -112,14 +125,14 @@ export function createDurableTaskPrimitive(input: {
112
125
  readonly backend: DurableWorkflowBackend;
113
126
  readonly nextReplayKey: (stageName: string) => string;
114
127
  readonly task: (name: string, options: WorkflowTaskOptions, stageFailFastScope?: ParallelFailFastScope) => Promise<WorkflowTaskResult>;
115
- readonly recordCachedTask?: (name: string, replayKey: string, output: WorkflowTaskResult) => void;
128
+ readonly recordCachedTask?: (name: string, replayKey: string, checkpoint: DurableCompletedStageCheckpoint, stageFailFastScope?: ParallelFailFastScope) => void;
116
129
  }): (name: string, options: WorkflowTaskOptions) => Promise<WorkflowTaskResult> {
117
130
  return async (name: string, options: WorkflowTaskOptions, stageFailFastScope?: ParallelFailFastScope): Promise<WorkflowTaskResult> => {
118
131
  const replayKey = input.nextReplayKey(`task:${name}`);
119
- const cached = input.backend.getStageOutput(input.workflowId, replayKey);
120
- if (cached !== undefined && isWorkflowTaskResult(cached)) {
121
- input.recordCachedTask?.(name, replayKey, cached);
122
- return cached;
132
+ const cached = stageCheckpointWithOutput(input.backend, input.workflowId, replayKey, isWorkflowTaskResult);
133
+ if (cached !== undefined && isWorkflowTaskResult(cached.output)) {
134
+ input.recordCachedTask?.(name, replayKey, cached, stageFailFastScope);
135
+ return cached.output;
123
136
  }
124
137
  const session = input.backend.getStageSession(input.workflowId, replayKey);
125
138
  const taskOptions: WorkflowTaskOptions = {
@@ -136,6 +149,7 @@ export function createDurableTaskPrimitive(input: {
136
149
  replayKey,
137
150
  output: result,
138
151
  completedAt: Date.now(),
152
+ ...taskCheckpointMetadata(result),
139
153
  });
140
154
  return result;
141
155
  };
@@ -174,8 +188,8 @@ function wrapSchemaStageForDurability(input: {
174
188
  return wrapped;
175
189
  }
176
190
 
177
- function createCachedStageContext(name: string, output: WorkflowSerializableValue): StageContext {
178
- const text = typeof output === "string" ? output : JSON.stringify(output);
191
+ function createCachedStageContext(name: string, output: WorkflowSerializableValue, result?: string): StageContext {
192
+ const text = result ?? (typeof output === "string" ? output : JSON.stringify(output));
179
193
  const unsupported = async (): Promise<never> => { throw new Error(`Stage "${name}" was replayed from a durable checkpoint; live session operations are unavailable.`); };
180
194
  const cached = {
181
195
  name,
@@ -213,6 +227,82 @@ function stageOutput(stage: StageSnapshot): WorkflowSerializableValue {
213
227
  return { status: stage.status, stageId: stage.id };
214
228
  }
215
229
 
230
+ function checkpointMetadata(stage: StageSnapshot): Partial<DurableStageCheckpoint> {
231
+ return {
232
+ ...(stage.startedAt !== undefined ? { startedAt: stage.startedAt } : {}),
233
+ ...(stage.endedAt !== undefined ? { endedAt: stage.endedAt } : {}),
234
+ ...(stage.durationMs !== undefined ? { durationMs: stage.durationMs } : {}),
235
+ ...(stage.result !== undefined ? { result: stage.result } : {}),
236
+ ...(stage.sessionId !== undefined ? { sessionId: stage.sessionId } : {}),
237
+ ...(stage.sessionFile !== undefined ? { sessionFile: stage.sessionFile } : {}),
238
+ ...(stage.model !== undefined ? { model: stage.model } : {}),
239
+ ...(stage.fastMode !== undefined ? { fastMode: stage.fastMode } : {}),
240
+ ...(stage.attemptedModels !== undefined ? { attemptedModels: [...stage.attemptedModels] } : {}),
241
+ ...(stage.modelAttempts !== undefined ? { modelAttempts: [...stage.modelAttempts] } : {}),
242
+ };
243
+ }
244
+
245
+ function taskCheckpointMetadata(result: WorkflowTaskResult): Partial<DurableStageCheckpoint> {
246
+ return {
247
+ result: result.text,
248
+ ...(result.sessionId !== undefined ? { sessionId: result.sessionId } : {}),
249
+ ...(result.sessionFile !== undefined ? { sessionFile: result.sessionFile } : {}),
250
+ ...(result.model !== undefined ? { model: result.model } : {}),
251
+ ...(result.fastMode !== undefined ? { fastMode: result.fastMode } : {}),
252
+ ...(result.attemptedModels !== undefined ? { attemptedModels: [...result.attemptedModels] } : {}),
253
+ ...(result.modelAttempts !== undefined ? { modelAttempts: [...result.modelAttempts] } : {}),
254
+ };
255
+ }
256
+
257
+ export function stageCheckpointWithOutput(
258
+ backend: DurableWorkflowBackend,
259
+ workflowId: string,
260
+ replayKey: string,
261
+ matchesOutput?: (value: WorkflowSerializableValue) => boolean,
262
+ ): DurableCompletedStageCheckpoint | undefined {
263
+ const checkpoints = backend.listCheckpoints(workflowId)
264
+ .filter((checkpoint): checkpoint is DurableStageCheckpoint =>
265
+ checkpoint.kind === "stage" && checkpoint.replayKey === replayKey,
266
+ );
267
+ const outputCheckpoints = checkpoints.filter((checkpoint): checkpoint is DurableCompletedStageCheckpoint =>
268
+ checkpoint.output !== undefined,
269
+ );
270
+ const replayValueCheckpoint = matchesOutput === undefined
271
+ ? outputCheckpoints[0]
272
+ : outputCheckpoints.find((checkpoint) => matchesOutput(checkpoint.output));
273
+ if (replayValueCheckpoint === undefined) return undefined;
274
+ return mergeCheckpointHydrationMetadata(replayValueCheckpoint, checkpoints);
275
+ }
276
+
277
+ function mergeCheckpointHydrationMetadata(
278
+ replayValueCheckpoint: DurableCompletedStageCheckpoint,
279
+ checkpoints: readonly DurableStageCheckpoint[],
280
+ ): DurableCompletedStageCheckpoint {
281
+ if (checkpoints.length === 0) return replayValueCheckpoint;
282
+ return {
283
+ ...replayValueCheckpoint,
284
+ ...(replayValueCheckpoint.startedAt === undefined ? metadataValue(checkpoints, "startedAt") : {}),
285
+ ...(replayValueCheckpoint.endedAt === undefined ? metadataValue(checkpoints, "endedAt") : {}),
286
+ ...(replayValueCheckpoint.durationMs === undefined ? metadataValue(checkpoints, "durationMs") : {}),
287
+ ...(replayValueCheckpoint.result === undefined ? metadataValue(checkpoints, "result") : {}),
288
+ ...(replayValueCheckpoint.sessionId === undefined ? metadataValue(checkpoints, "sessionId") : {}),
289
+ ...(replayValueCheckpoint.sessionFile === undefined ? metadataValue(checkpoints, "sessionFile") : {}),
290
+ ...(replayValueCheckpoint.model === undefined ? metadataValue(checkpoints, "model") : {}),
291
+ ...(replayValueCheckpoint.fastMode === undefined ? metadataValue(checkpoints, "fastMode") : {}),
292
+ ...(replayValueCheckpoint.attemptedModels === undefined ? metadataValue(checkpoints, "attemptedModels") : {}),
293
+ ...(replayValueCheckpoint.modelAttempts === undefined ? metadataValue(checkpoints, "modelAttempts") : {}),
294
+ };
295
+ }
296
+
297
+ function metadataValue<K extends keyof DurableStageCheckpoint>(
298
+ checkpoints: readonly DurableStageCheckpoint[],
299
+ key: K,
300
+ ): Pick<DurableStageCheckpoint, K> | Record<string, never> {
301
+ const value = checkpoints.find((checkpoint) => checkpoint[key] !== undefined)?.[key];
302
+ return value === undefined ? {} : { [key]: value } as Pick<DurableStageCheckpoint, K>;
303
+ }
304
+
305
+
216
306
  export function createStageReplayKeyGenerator(_workflowId: string): (stageName: string, stageId?: string) => string {
217
307
  const counts = new Map<string, number>();
218
308
  return (stageName: string, _stageId?: string): string => {
@@ -236,6 +326,15 @@ function stageSessionCheckpointId(replayKey: string, stage: StageSnapshot): stri
236
326
  export function cachedStageId(runId: string, replayKey: string): string {
237
327
  return `durable-${durableHash({ runId, replayKey })}`;
238
328
  }
329
+ function stageMetadataCheckpointId(replayKey: string, stage: StageSnapshot): string {
330
+ return `${stableCheckpointId("stage-meta", replayKey)}:${durableHash({
331
+ stageId: stage.id,
332
+ endedAt: stage.endedAt ?? 0,
333
+ durationMs: stage.durationMs ?? 0,
334
+ result: stage.result ?? "",
335
+ })}`;
336
+ }
337
+
239
338
 
240
339
  export function recordCachedStageIntoStore(
241
340
  store: import("../shared/store.js").Store,
@@ -245,13 +344,24 @@ export function recordCachedStageIntoStore(
245
344
  output: WorkflowSerializableValue,
246
345
  completedStageReplayKeys: Map<string, string>,
247
346
  parentIds?: readonly string[],
347
+ checkpoint?: DurableCompletedStageCheckpoint,
248
348
  ): void {
249
349
  const now = Date.now();
250
350
  const stageId = cachedStageId(runId, replayKey);
251
- const result = typeof output === "string" ? output : JSON.stringify(output);
351
+ const result = checkpoint?.result ?? (typeof output === "string" ? output : JSON.stringify(output));
352
+ const endedAt = checkpoint?.endedAt ?? checkpoint?.completedAt ?? now;
353
+ const workflowChild = isWorkflowChildResult(output) ? workflowChildSnapshotFromResult(output) : undefined;
252
354
  const snapshot: StageSnapshot = {
253
- id: stageId, name, status: "completed", parentIds: parentIds !== undefined ? Object.freeze([...parentIds]) : [], startedAt: now, endedAt: now, durationMs: 0, result,
355
+ id: stageId, name, status: "completed", parentIds: parentIds !== undefined ? Object.freeze([...parentIds]) : [],
356
+ startedAt: checkpoint?.startedAt ?? endedAt, endedAt, durationMs: checkpoint?.durationMs ?? 0, result,
254
357
  replayKey, replayed: true, skippedReason: "durable checkpoint replay", toolEvents: [], attachable: false,
358
+ ...(workflowChild !== undefined ? { workflowChild } : {}),
359
+ ...(checkpoint?.sessionId !== undefined ? { sessionId: checkpoint.sessionId } : {}),
360
+ ...(checkpoint?.sessionFile !== undefined ? { sessionFile: checkpoint.sessionFile } : {}),
361
+ ...(checkpoint?.model !== undefined ? { model: checkpoint.model } : {}),
362
+ ...(checkpoint?.fastMode !== undefined ? { fastMode: checkpoint.fastMode } : {}),
363
+ ...(checkpoint?.attemptedModels !== undefined ? { attemptedModels: checkpoint.attemptedModels } : {}),
364
+ ...(checkpoint?.modelAttempts !== undefined ? { modelAttempts: checkpoint.modelAttempts } : {}),
255
365
  };
256
366
  store.recordStageStart(runId, snapshot);
257
367
  store.recordStageEnd(runId, snapshot);
@@ -269,16 +379,44 @@ export function recordCachedStageWithTracker(
269
379
  runId: string,
270
380
  name: string,
271
381
  replayKey: string,
272
- output: WorkflowSerializableValue,
382
+ checkpoint: DurableCompletedStageCheckpoint,
273
383
  completedStageReplayKeys: Map<string, string>,
384
+ stageFailFastScope?: ParallelFailFastScope,
274
385
  ): void {
275
386
  const stageId = cachedStageId(runId, replayKey);
276
- const parentIds = tracker.onSpawn(stageId, name);
277
- recordCachedStageIntoStore(store, runId, name, replayKey, output, completedStageReplayKeys, parentIds);
387
+ let parentIds = tracker.onSpawn(stageId, name);
388
+ const scopeParentIds = stageFailFastScope?.parentIds ?? [];
389
+ if (stageFailFastScope !== undefined) {
390
+ tracker.replaceParents(stageId, scopeParentIds);
391
+ parentIds = [...scopeParentIds];
392
+ }
393
+ recordCachedStageIntoStore(store, runId, name, replayKey, checkpoint.output, completedStageReplayKeys, parentIds, checkpoint);
278
394
  tracker.onSettle(stageId);
279
395
  }
280
-
281
396
  function isWorkflowTaskResult(value: WorkflowSerializableValue): value is WorkflowTaskResult {
282
397
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
283
398
  return typeof (value as Record<string, WorkflowSerializableValue>)["text"] === "string";
284
399
  }
400
+
401
+ function isWorkflowChildResult(value: WorkflowSerializableValue): value is WorkflowChildResult<WorkflowOutputValues> {
402
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
403
+ const objectValue = value as Record<string, WorkflowSerializableValue | undefined>;
404
+ return typeof objectValue["workflow"] === "string"
405
+ && typeof objectValue["runId"] === "string"
406
+ && typeof objectValue["status"] === "string"
407
+ && typeof objectValue["outputs"] === "object"
408
+ && objectValue["outputs"] !== null
409
+ && !Array.isArray(objectValue["outputs"]);
410
+ }
411
+
412
+ function workflowChildSnapshotFromResult(result: WorkflowChildResult<WorkflowOutputValues>): StageSnapshot["workflowChild"] {
413
+ return {
414
+ alias: result.workflow,
415
+ workflow: result.workflow,
416
+ runId: result.runId,
417
+ status: result.status,
418
+ ...(result.exited !== undefined ? { exited: result.exited } : {}),
419
+ outputs: result.outputs,
420
+ ...(typeof result.exitReason === "string" ? { exitReason: result.exitReason } : {}),
421
+ };
422
+ }
@@ -9,7 +9,7 @@
9
9
  * cross-ref: issue #1498
10
10
  */
11
11
 
12
- import type { WorkflowSerializableValue } from "../shared/types.js";
12
+ import type { WorkflowModelAttempt, WorkflowSerializableValue } from "../shared/types.js";
13
13
 
14
14
  // ---------------------------------------------------------------------------
15
15
  // Top-level workflow identity
@@ -113,6 +113,19 @@ export interface DurableStageCheckpoint {
113
113
  readonly sessionId?: string;
114
114
  readonly sessionFile?: string;
115
115
  readonly completedAt: number;
116
+ /** Original stage start timestamp, when available. */
117
+ readonly startedAt?: number;
118
+ /** Original stage end timestamp, when available. */
119
+ readonly endedAt?: number;
120
+ /** Original stage duration, when available. */
121
+ readonly durationMs?: number;
122
+ /** Display/result text recorded for the completed stage. */
123
+ readonly result?: string;
124
+ /** Completed stage/task model metadata used to hydrate replayed snapshots. */
125
+ readonly model?: string;
126
+ readonly fastMode?: boolean;
127
+ readonly attemptedModels?: readonly string[];
128
+ readonly modelAttempts?: readonly WorkflowModelAttempt[];
116
129
  }
117
130
 
118
131
  export type UiPromptKind = "input" | "confirm" | "select" | "editor" | "custom";
@@ -1,11 +1,5 @@
1
- import type { RunSnapshot } from "../shared/store-types.js";
2
- import type {
3
- WorkflowDefinition,
4
- WorkflowInputValues,
5
- WorkflowOutputValues,
6
- WorkflowRunContext,
7
- WorkflowSerializableValue,
8
- } from "../shared/types.js";
1
+ import type { RunSnapshot, StageSnapshot } from "../shared/store-types.js";
2
+ import type { WorkflowDefinition, WorkflowInputValues, WorkflowOutputValues, WorkflowRunContext } from "../shared/types.js";
9
3
  import type { WorkflowFailure } from "../shared/workflow-failures.js";
10
4
  import { classifyWorkflowFailure } from "../shared/workflow-failures.js";
11
5
  import { store as defaultStore } from "../shared/store.js";
@@ -45,12 +39,12 @@ import { classifyReturnedRunStatus } from "./run-returned-status.js";
45
39
  import { createToolPrimitive, createCheckpointIdGenerator } from "../durable/tool-primitive.js";
46
40
  import { persistDurableCacheEntry } from "../durable/resume-catalog.js";
47
41
  import { createDurableStagePrimitive, createDurableTaskPrimitive, recordStageCheckpoint, createStageReplayKeyGenerator, recordCachedStageWithTracker } from "../durable/stage-primitive.js";
42
+ import type { DurableCompletedStageCheckpoint } from "../durable/stage-primitive.js";
48
43
  import { createDurableChildWorkflowPrimitive } from "../durable/child-primitive.js";
49
44
  import { ScopedDurableBackend, type DurableScope } from "../durable/scoped-backend.js";
50
45
  import { finalizeDurableTerminalStatus } from "./run-durable-finalize.js";
51
46
  import { createDurableStageSessionRecorder } from "./run-durable-stage-session.js";
52
47
  import type { DurableWorkflowBackend } from "../durable/backend.js";
53
- import type { StageSnapshot } from "../shared/store-types.js";
54
48
 
55
49
  function nextEventLoopTurn(): Promise<void> {
56
50
  return new Promise((resolve) => setTimeout(resolve, 0));
@@ -283,6 +277,8 @@ export async function run<
283
277
  });
284
278
  const workflowBoundaryReplayCounts = new Map<string, number>();
285
279
  const nextWorkflowBoundaryReplayKey = (name: string): string => {
280
+ const durableScopePrefix = pendingChildDurableScope?.scopePrefix;
281
+ if (durableScopePrefix !== undefined && durableScopePrefix.startsWith(`workflow:${name}:`)) return durableScopePrefix;
286
282
  const next = (workflowBoundaryReplayCounts.get(name) ?? 0) + 1;
287
283
  workflowBoundaryReplayCounts.set(name, next);
288
284
  return `workflow:${name}:${next}`;
@@ -346,12 +342,13 @@ export async function run<
346
342
 
347
343
  // Durable ctx.ui wrapper — caches completed user responses so a resumed workflow does not re-ask answered prompts.
348
344
  const durableUiDeps = { workflowId: runId, backend: durableBackend, nextCheckpointId: checkpointIdGenerator };
349
- const recordCachedStage = (name: string, replayKey: string, output: WorkflowSerializableValue): void =>
350
- recordCachedStageWithTracker(activeStore, tracker, runId, name, replayKey, output, completedStageReplayKeys);
345
+ const recordCachedStage = (name: string, replayKey: string, checkpoint: DurableCompletedStageCheckpoint): void =>
346
+ recordCachedStageWithTracker(activeStore, tracker, runId, name, replayKey, checkpoint, completedStageReplayKeys);
351
347
  const durableTask = createDurableTaskPrimitive({
352
348
  workflowId: runId, backend: durableBackend,
353
349
  nextReplayKey: (stageName) => stageReplayKeyGenerator(stageName), task: taskRunners.task,
354
- recordCachedTask: (name, replayKey, output) => recordCachedStage(name, replayKey, output),
350
+ recordCachedTask: (name, replayKey, checkpoint, scope) =>
351
+ recordCachedStageWithTracker(activeStore, tracker, runId, name, replayKey, checkpoint, completedStageReplayKeys, scope),
355
352
  });
356
353
  const durableWorkflow = createDurableChildWorkflowPrimitive({
357
354
  workflowId: runId, rootWorkflowId: opts.parentRun?.rootRunId ?? runId, backend: durableBackend,
@@ -426,7 +423,7 @@ export async function run<
426
423
  await durableBackend.flush?.();
427
424
  const returned = classifyReturnedRunStatus(result);
428
425
  const recorded = activeStore.recordRunEnd(runId, returned.status, result, returned.error, returned.metadata);
429
- appendRunEndWhenRecorded(opts.persistence, recorded, { runId, status: returned.status, result, ...(returned.error !== undefined ? { error: returned.error } : {}), ...(returned.metadata ?? {}), ts: Date.now() });
426
+ appendRunEndWhenRecorded(opts.persistence, recorded, { runId, status: returned.status, result, ...(returned.error !== undefined ? { error: returned.error } : {}), ...(returned.metadata ?? {}), ...(runSnapshot.endedAt !== undefined ? { endedAt: runSnapshot.endedAt } : {}), ...(runSnapshot.durationMs !== undefined ? { durationMs: runSnapshot.durationMs } : {}), ts: Date.now() });
430
427
  durableBackend.setWorkflowStatus(runId, returned.status, undefined, returned.metadata?.resumable);
431
428
  await durableBackend.flush?.();
432
429
  if (opts.persistence && durableBackend.persistent) {
@@ -480,6 +477,8 @@ export async function run<
480
477
  ...(metadata.failedStageId !== undefined ? { failedStageId: metadata.failedStageId } : {}),
481
478
  resumable: metadata.resumable,
482
479
  ...(metadata.retryAfterMs !== undefined ? { retryAfterMs: metadata.retryAfterMs } : {}),
480
+ ...(runSnapshot.endedAt !== undefined ? { endedAt: runSnapshot.endedAt } : {}),
481
+ ...(runSnapshot.durationMs !== undefined ? { durationMs: runSnapshot.durationMs } : {}),
483
482
  ts: Date.now(),
484
483
  });
485
484
  return reconcileTerminalRunResult(runId, runSnapshot, activeStore, { status: "failed", error: metadata.errorMessage }, opts.onRunEnd);
@@ -25,6 +25,8 @@ export const DEFAULT_PROMPT_GUIDANCE: string[] = [
25
25
  - For transcripts, avoid reading whole session transcripts at once. Use \`stages\` or \`stage\` to get \`sessionFile\`/\`transcriptPath\`, quote the exact path without rewriting separators (preserve Windows backslashes), search it with \`rg\`/\`grep\`, and read small relevant ranges; use \`transcript\` with explicit \`tail\` or \`limit\` only for quick recent-context checks.
26
26
  - If a user asks to create or edit a workflow, use the create-spec skill when available and ask detailed clarifying questions until you understand its purpose, inputs, stages, handoffs, validation, success criteria, and selected starter pattern. Then read the workflow docs/examples and implement the workflow from the created spec directly as a TypeScript definition. After you implement the workflow, reload it to access it and run it with test inputs to validate it works as intended before presenting it to the user.
27
27
  - Tip: when designing workflows, implement it in a way that you pass information from stage to stage by writing it to a file or artifact (either deterministic or model-driven), pass the path with \`reads\`, and explicitly prompt the downstream agent with wording like \`Read the file at <path>...\`; do not inject large \`previous\` payloads or session history into the next prompt unless explicitly requested to.
28
+ - When designing reviewer-gated workflows, distinguish implementation/review acceptance from post-approval final actions such as PR/MR/review creation, release tagging, deployment, or publication. Prompt reviewers/reducers to approve and stop the implementation loop when implementation and validation criteria are proven and only an explicitly authorized final action remains; carry that remainder as a next action or final-action field instead of another implementation turn.
29
+ - When authoring workflow, stage, reviewer, or reducer prompts, remember that a model stage sees its local prompt, artifacts, tools, and reads — not the workflow graph's name or surrounding implementation details unless you explicitly provide them. Describe the concrete action, available evidence, and success criteria directly (for example "review the current code delta" or "create/update the review request") instead of asking the model to reason from labels like "the create-PR workflow stage", "this Goal run", or "the Ralph reviewer". Use workflow or stage names only when the name itself is user-visible context or materially affects behavior.
28
30
  - Prefer the \`goal\` workflow for small-to-medium non-trivial one-off fixes, debugging, docs/code changes, scoped multi-file edits, or test/fix loops when you can name the work surface, outcome, and validation; prefer the \`ralph\` workflow for larger/riskier work, migrations, new features, broad refactors, multi-package changes, or tasks estimated over ~2K LoC.
29
31
  - Adjust \`goal.max_turns\` for Goal runs and \`ralph.max_loops\` for Ralph runs based on task complexity (estimated LoC, risk, validation effort, and number of unique files/areas touched).
30
32
  - Define an objective that includes tight scope, concrete and verifiable done criteria, and validation steps; then monitor progress as above instead of doing parallel implementation yourself.`,
@@ -68,14 +68,23 @@ export function createContinuationReplayIndex(continuation: RunContinuationOpts
68
68
  }
69
69
 
70
70
  const stagesByReplayIdentity = new Map<string, StageSnapshot[]>();
71
+ const stagesByDisplayName = new Map<string, StageSnapshot[]>();
71
72
  const promptDuplicateCounts = new Map<string, number>();
72
73
  for (const stage of continuation.source.stages) {
73
74
  const identity = stage.replayKey ?? stage.name;
74
75
  const stages = stagesByReplayIdentity.get(identity);
75
76
  if (stages === undefined) stagesByReplayIdentity.set(identity, [stage]);
76
77
  else stages.push(stage);
77
- const duplicateKey = `${identity}\u0001${sortedIdentity(stage.parentIds)}`;
78
- promptDuplicateCounts.set(duplicateKey, (promptDuplicateCounts.get(duplicateKey) ?? 0) + 1);
78
+ const namedStages = stagesByDisplayName.get(stage.name);
79
+ if (namedStages === undefined) stagesByDisplayName.set(stage.name, [stage]);
80
+ else namedStages.push(stage);
81
+ const duplicateKeys = [
82
+ `${identity}\u0001${sortedIdentity(stage.parentIds)}`,
83
+ ...(identity === stage.name ? [] : [`${stage.name}\u0001${sortedIdentity(stage.parentIds)}`]),
84
+ ];
85
+ for (const duplicateKey of duplicateKeys) {
86
+ promptDuplicateCounts.set(duplicateKey, (promptDuplicateCounts.get(duplicateKey) ?? 0) + 1);
87
+ }
79
88
  }
80
89
 
81
90
  const consumedSourceStageIds = new Set<string>();
@@ -126,7 +135,8 @@ export function createContinuationReplayIndex(continuation: RunContinuationOpts
126
135
  let candidates = stagesByReplayIdentity.get(replayKey)?.filter((stage) => !consumedSourceStageIds.has(stage.id)) ?? [];
127
136
  if (candidates.length === 0) {
128
137
  identity = displayName;
129
- candidates = stagesByReplayIdentity.get(displayName)?.filter((stage) => !consumedSourceStageIds.has(stage.id) && stage.replayKey === undefined) ?? [];
138
+ const namedCandidates = stagesByDisplayName.get(displayName)?.filter((stage) => !consumedSourceStageIds.has(stage.id)) ?? [];
139
+ candidates = kind === "prompt" ? namedCandidates.filter((stage) => stage.replayKey === undefined) : namedCandidates;
130
140
  }
131
141
  if (candidates.length === 0) return { kind: "execute", parentIds, answerReplay: "unavailable" };
132
142
 
@@ -54,7 +54,7 @@ export function createWorkflowStageFactory(input: {
54
54
  input.tracker.replaceParents(stageId, scopedParentIds);
55
55
  }
56
56
 
57
- const replayKey = `stage:${name}`;
57
+ const replayKey = options?.durableReplayKey ?? `stage:${name}`;
58
58
  const replayDecision = input.replayIndex.decide({
59
59
  displayName: name,
60
60
  replayKey,
@@ -6,6 +6,7 @@ import { missingAdapter, stripWorkflowOnlyOptions, unavailableSync } from "./sta
6
6
  import { asAgentSession, disposeStageSession, normalizeSessionCreateResult } from "./stage-runner-session.js";
7
7
  import { structuredOutputToolErrorFromEvent } from "./stage-runner-structured-output.js";
8
8
  import { sendStageUserMessage } from "./stage-runner-send-user-message.js";
9
+ import { nextResumedContextOverflowFallbackIndex, terminatingToolCallId, unresolvedContextOverflowFailure, unresolvedContextOverflowMessage } from "./stage-runner-unresolved-overflow.js";
9
10
  import type { AgentSessionConsumer, StageModelFallbackMeta, StageRunnerOpts, StageSessionCreateOptions, StageSessionCreateResult, StageSessionEvent, StageSessionRuntime, WorkflowFastModeSettingsManager } from "./stage-runner-types.js";
10
11
 
11
12
  type PauseRequest = {
@@ -19,6 +20,7 @@ export class StageSessionController {
19
20
  private readonly terminatingToolCallIds = new Set<string>();
20
21
  private latestStructuredOutputToolErrorValue: string | undefined;
21
22
  private unsubscribeTerminateWatcher: (() => void) | undefined;
23
+ private unresolvedContextOverflowMessage: string | undefined;
22
24
  private disposed = false;
23
25
  private pendingThinkingLevel: Parameters<StageContext["setThinkingLevel"]>[0] | undefined;
24
26
  private readonly pendingListeners = new Set<(event: StageSessionEvent) => void>();
@@ -325,7 +327,9 @@ export class StageSessionController {
325
327
  }
326
328
  this.unsubscribeTerminateWatcher?.();
327
329
  this.unsubscribeTerminateWatcher = result.session.subscribe((event) => {
328
- this.recordTerminatingToolCall(event);
330
+ const terminatingId = terminatingToolCallId(event);
331
+ if (terminatingId !== undefined) this.terminatingToolCallIds.add(terminatingId);
332
+ this.unresolvedContextOverflowMessage = unresolvedContextOverflowMessage(event) ?? this.unresolvedContextOverflowMessage;
329
333
  this.latestStructuredOutputToolErrorValue =
330
334
  structuredOutputToolErrorFromEvent(event) ?? this.latestStructuredOutputToolErrorValue;
331
335
  });
@@ -361,24 +365,29 @@ export class StageSessionController {
361
365
  continue;
362
366
  }
363
367
  const promptStartIndex = activeSession.messages.length;
368
+ this.unresolvedContextOverflowMessage = undefined;
364
369
  try {
365
370
  await activeSession.prompt(nextText, sdkOptions);
366
371
  const pendingPauseAfterPrompt = this.pauseRequest;
367
372
  if (pendingPauseAfterPrompt) {
368
373
  const { message } = await pendingPauseAfterPrompt.deferred.promise;
374
+ this.throwUnresolvedContextOverflowIfPresent();
369
375
  nextText = message;
370
376
  if (nextText === undefined) return { terminalScanStartIndex: activeSession.messages.length };
371
377
  continue;
372
378
  }
379
+ this.throwUnresolvedContextOverflowIfPresent();
373
380
  return { terminalScanStartIndex: promptStartIndex };
374
381
  } catch (err) {
375
382
  const pendingPauseAfterThrow = this.pauseRequest;
376
383
  if (pendingPauseAfterThrow) {
377
384
  const { message } = await pendingPauseAfterThrow.deferred.promise;
385
+ this.throwUnresolvedContextOverflowIfPresent();
378
386
  nextText = message;
379
387
  if (nextText === undefined) return { terminalScanStartIndex: activeSession.messages.length };
380
388
  continue;
381
389
  }
390
+ this.throwUnresolvedContextOverflowIfPresent();
382
391
  throw err;
383
392
  }
384
393
  }
@@ -420,10 +429,10 @@ export class StageSessionController {
420
429
  this.notifyModelFallbackMetaChange();
421
430
  throw err;
422
431
  }
423
- this.pendingFallbackWarnings.push(`[fallback] resume on ${resumedLabel} failed: ${message}. Restarting fallback from ${this.candidateLabel(candidates[0]!)}.`);
424
- await this.disposeCurrentSession();
425
- this.activeCandidateIndex = undefined;
426
- return false;
432
+ const resumedOverflowNextIndex = nextResumedContextOverflowFallbackIndex(err, this.activeCandidateIndex, candidates.length);
433
+ if (resumedOverflowNextIndex === "terminal") { this.modelWarnings.push(...this.pendingFallbackWarnings); this.pendingFallbackWarnings.length = 0; this.notifyModelFallbackMetaChange(); throw err; }
434
+ this.pendingFallbackWarnings.push(resumedOverflowNextIndex === undefined ? `[fallback] resume on ${resumedLabel} failed: ${message}. Restarting fallback from ${this.candidateLabel(candidates[0]!)}.` : `[fallback] resume on ${resumedLabel} failed: ${message}. Retrying with ${this.candidateLabel(candidates[resumedOverflowNextIndex]!)}.`);
435
+ await this.disposeCurrentSession(); this.activeCandidateIndex = resumedOverflowNextIndex; return false;
427
436
  }
428
437
  }
429
438
 
@@ -479,22 +488,12 @@ export class StageSessionController {
479
488
  private candidateLabel(candidate: WorkflowResolvedModelCandidate): string {
480
489
  return candidate.reasoningLevel !== undefined ? `${candidate.id}:${candidate.reasoningLevel}` : candidate.id;
481
490
  }
482
-
483
491
  private isWorkflowFastModeEnabled(): boolean | undefined {
484
492
  const model = this.session?.model;
485
493
  const settingsManager = this.sessionSettingsManager ?? this.effectiveStageOptions?.settingsManager;
486
- if (model === undefined || settingsManager === undefined) return undefined;
487
- return shouldApplyCodexFastModeForScope(model, settingsManager.getCodexFastModeSettings(), "workflow");
488
- }
489
-
490
- private recordTerminatingToolCall(event: unknown): void {
491
- if (event === null || typeof event !== "object") return;
492
- const record = event as Record<string, unknown>;
493
- if (record["type"] !== "tool_execution_end") return;
494
- const result = record["result"];
495
- if (result === null || typeof result !== "object") return;
496
- if ((result as Record<string, unknown>)["terminate"] !== true) return;
497
- const callId = record["toolCallId"];
498
- if (typeof callId === "string" && callId.length > 0) this.terminatingToolCallIds.add(callId);
494
+ return model === undefined || settingsManager === undefined
495
+ ? undefined
496
+ : shouldApplyCodexFastModeForScope(model, settingsManager.getCodexFastModeSettings(), "workflow");
499
497
  }
498
+ private throwUnresolvedContextOverflowIfPresent(): void { const message = this.unresolvedContextOverflowMessage; this.unresolvedContextOverflowMessage = undefined; if (message !== undefined) throw unresolvedContextOverflowFailure(message); }
500
499
  }
@@ -0,0 +1,38 @@
1
+ import { WorkflowPromptModelFailure } from "./stage-runner-messages.js";
2
+
3
+ export function unresolvedContextOverflowMessage(event: unknown): string | undefined {
4
+ if (event === null || typeof event !== "object") return undefined;
5
+ const record = event as Record<string, unknown>;
6
+ if (record["type"] !== "compaction_end" || record["unresolvedOverflow"] !== true) return undefined;
7
+ const message = record["errorMessage"];
8
+ return typeof message === "string" && message.trim().length > 0
9
+ ? message
10
+ : "Context overflow recovery was exhausted for the current model.";
11
+ }
12
+
13
+ export function unresolvedContextOverflowFailure(message: string): WorkflowPromptModelFailure {
14
+ return new WorkflowPromptModelFailure({ message, code: "context_length_exceeded", stopReason: "error" });
15
+ }
16
+
17
+ export function isUnresolvedContextOverflowFailure(error: unknown): boolean {
18
+ if (!(error instanceof WorkflowPromptModelFailure)) return false;
19
+ const cause = error.cause;
20
+ if (cause === null || typeof cause !== "object") return false;
21
+ return (cause as { readonly code?: unknown }).code === "context_length_exceeded";
22
+ }
23
+
24
+ export function nextResumedContextOverflowFallbackIndex(error: unknown, currentIndex: number | undefined, candidateCount: number): number | "terminal" | undefined {
25
+ if (!isUnresolvedContextOverflowFailure(error) || currentIndex === undefined) return undefined;
26
+ const nextIndex = currentIndex + 1;
27
+ return nextIndex >= candidateCount ? "terminal" : nextIndex;
28
+ }
29
+
30
+ export function terminatingToolCallId(event: unknown): string | undefined {
31
+ if (event === null || typeof event !== "object") return undefined;
32
+ const record = event as Record<string, unknown>;
33
+ const result = record["result"];
34
+ if (record["type"] !== "tool_execution_end" || result === null || typeof result !== "object") return undefined;
35
+ if ((result as Record<string, unknown>)["terminate"] !== true) return undefined;
36
+ const callId = record["toolCallId"];
37
+ return typeof callId === "string" && callId.length > 0 ? callId : undefined;
38
+ }
@@ -1,7 +1,7 @@
1
1
  import { Value } from "typebox/value";
2
2
  import { workflowSerializableObjectSchema } from "./serializable.js";
3
3
  import type { Store } from "./store.js";
4
- import type { SessionEntry } from "./persistence-restore.js";
4
+ import type { NormalizedSessionEntry as SessionEntry } from "./persistence-restore.js";
5
5
  import type {
6
6
  RunStatus,
7
7
  StageSnapshot,
@@ -255,6 +255,14 @@ function numericRetryAfterMs(value: unknown): number | undefined {
255
255
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
256
256
  }
257
257
 
258
+ function numericTimestamp(value: unknown): number | undefined {
259
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
260
+ }
261
+
262
+ function numericDuration(value: unknown): number | undefined {
263
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
264
+ }
265
+
258
266
  export function findRunBlockedMetadata(
259
267
  entries: readonly SessionEntry[],
260
268
  runId: string,
@@ -360,6 +368,8 @@ export function restoreTerminalRuns(entries: readonly SessionEntry[], store: Sto
360
368
  const retryAfterMs = numericRetryAfterMs(end["retryAfterMs"]);
361
369
  const failureMessage = end["failureMessage"];
362
370
  const failedStageId = end["failedStageId"];
371
+ const restoredEndedAt = numericTimestamp(end["endedAt"]);
372
+ const restoredDurationMs = numericDuration(end["durationMs"]);
363
373
  store.recordRunEnd(
364
374
  runId,
365
375
  status,
@@ -378,6 +388,13 @@ export function restoreTerminalRuns(entries: readonly SessionEntry[], store: Sto
378
388
  ...(typeof exitReason === "string" ? { exitReason } : {}),
379
389
  },
380
390
  );
391
+ const restoredRun = store.runs().find((run) => run.id === runId);
392
+ if (restoredRun !== undefined && (restoredEndedAt !== undefined || restoredDurationMs !== undefined)) {
393
+ Object.assign(restoredRun, {
394
+ ...(restoredEndedAt !== undefined ? { endedAt: restoredEndedAt } : {}),
395
+ ...(restoredDurationMs !== undefined ? { durationMs: restoredDurationMs } : {}),
396
+ });
397
+ }
381
398
  }
382
399
  }
383
400