@bastani/atomic 0.9.8 → 0.9.9-alpha.1

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 (348) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +6 -0
  3. package/dist/builtin/cursor/package.json +2 -2
  4. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  7. package/dist/builtin/mcp/package.json +1 -1
  8. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  9. package/dist/builtin/subagents/package.json +1 -1
  10. package/dist/builtin/subagents/src/runs/background/async-execution-chain.ts +2 -0
  11. package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +7 -1
  12. package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +2 -0
  13. package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +8 -2
  14. package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +2 -0
  15. package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +2 -0
  16. package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +2 -1
  17. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +2 -0
  18. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +2 -0
  19. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +2 -0
  20. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +2 -0
  21. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +6 -0
  22. package/dist/builtin/subagents/src/shared/types-config.ts +4 -0
  23. package/dist/builtin/subagents/src/shared/types-depth.ts +29 -2
  24. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  25. package/dist/builtin/web-access/package.json +1 -1
  26. package/dist/builtin/workflows/CHANGELOG.md +22 -0
  27. package/dist/builtin/workflows/README.md +17 -8
  28. package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -2
  29. package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +26 -9
  30. package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +1 -1
  31. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +8 -3
  32. package/dist/builtin/workflows/builtin/ralph-runner.ts +2 -0
  33. package/dist/builtin/workflows/builtin/shared-prompts.ts +24 -1
  34. package/dist/builtin/workflows/package.json +1 -1
  35. package/dist/builtin/workflows/src/durable/backend.ts +53 -15
  36. package/dist/builtin/workflows/src/durable/completed-catalog.ts +245 -0
  37. package/dist/builtin/workflows/src/durable/completed-inspection.ts +213 -0
  38. package/dist/builtin/workflows/src/durable/dbos-backend.ts +120 -170
  39. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +84 -8
  40. package/dist/builtin/workflows/src/durable/dbos-metadata.ts +98 -0
  41. package/dist/builtin/workflows/src/durable/dbos-tombstone.ts +27 -0
  42. package/dist/builtin/workflows/src/durable/factory.ts +1 -1
  43. package/dist/builtin/workflows/src/durable/file-backend.ts +247 -241
  44. package/dist/builtin/workflows/src/durable/file-lock.ts +153 -0
  45. package/dist/builtin/workflows/src/durable/file-state.ts +104 -0
  46. package/dist/builtin/workflows/src/durable/format-version.ts +12 -0
  47. package/dist/builtin/workflows/src/durable/index.ts +12 -0
  48. package/dist/builtin/workflows/src/durable/resume-catalog.ts +16 -27
  49. package/dist/builtin/workflows/src/durable/resume-eligibility.ts +20 -0
  50. package/dist/builtin/workflows/src/durable/resume-runtime.ts +29 -29
  51. package/dist/builtin/workflows/src/durable/scoped-backend.ts +20 -3
  52. package/dist/builtin/workflows/src/durable/stage-primitive.ts +29 -8
  53. package/dist/builtin/workflows/src/durable/types.ts +3 -0
  54. package/dist/builtin/workflows/src/engine/primitives/task.ts +1 -0
  55. package/dist/builtin/workflows/src/engine/run.ts +14 -14
  56. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +39 -9
  57. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +10 -0
  58. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +133 -0
  59. package/dist/builtin/workflows/src/extension/runtime.ts +15 -40
  60. package/dist/builtin/workflows/src/extension/wiring.ts +9 -6
  61. package/dist/builtin/workflows/src/extension/workflow-durable-resume-command.ts +226 -0
  62. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +1 -0
  63. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +66 -112
  64. package/dist/builtin/workflows/src/extension/workflow-schema.ts +5 -5
  65. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +16 -1
  66. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +50 -43
  67. package/dist/builtin/workflows/src/runs/foreground/executor-direct-output.ts +91 -0
  68. package/dist/builtin/workflows/src/runs/foreground/executor-direct.ts +95 -46
  69. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +3 -1
  70. package/dist/builtin/workflows/src/runs/foreground/executor-task-prompts.ts +1 -1
  71. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +3 -0
  72. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +1 -1
  73. package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +36 -7
  74. package/dist/builtin/workflows/src/runs/shared/worktree-cache-lifecycle.ts +25 -0
  75. package/dist/builtin/workflows/src/runs/shared/worktree-cwd.ts +103 -0
  76. package/dist/builtin/workflows/src/runs/shared/worktree-generation.ts +57 -0
  77. package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +134 -8
  78. package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -0
  79. package/dist/builtin/workflows/src/shared/resumable-workflow-notices.ts +43 -0
  80. package/dist/builtin/workflows/src/shared/timing.ts +4 -0
  81. package/dist/builtin/workflows/src/shared/types.ts +5 -3
  82. package/dist/builtin/workflows/src/tui/graph-view-constants.ts +1 -0
  83. package/dist/builtin/workflows/src/tui/graph-view-input.ts +24 -14
  84. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +43 -13
  85. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  86. package/dist/core/agent-session-auto-compaction.js +5 -7
  87. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  88. package/dist/core/agent-session-compaction.d.ts +6 -34
  89. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  90. package/dist/core/agent-session-compaction.js +95 -233
  91. package/dist/core/agent-session-compaction.js.map +1 -1
  92. package/dist/core/agent-session-message-queue.d.ts +0 -4
  93. package/dist/core/agent-session-message-queue.d.ts.map +1 -1
  94. package/dist/core/agent-session-message-queue.js +1 -5
  95. package/dist/core/agent-session-message-queue.js.map +1 -1
  96. package/dist/core/agent-session-methods.d.ts +5 -6
  97. package/dist/core/agent-session-methods.d.ts.map +1 -1
  98. package/dist/core/agent-session-methods.js.map +1 -1
  99. package/dist/core/agent-session-types.d.ts +2 -12
  100. package/dist/core/agent-session-types.d.ts.map +1 -1
  101. package/dist/core/agent-session-types.js.map +1 -1
  102. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  103. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  104. package/dist/core/compaction/branch-summarization.js +1 -2
  105. package/dist/core/compaction/branch-summarization.js.map +1 -1
  106. package/dist/core/compaction/compaction-boundary.d.ts +9 -0
  107. package/dist/core/compaction/compaction-boundary.d.ts.map +1 -0
  108. package/dist/core/compaction/compaction-boundary.js +115 -0
  109. package/dist/core/compaction/compaction-boundary.js.map +1 -0
  110. package/dist/core/compaction/compaction-parameters.d.ts +5 -0
  111. package/dist/core/compaction/compaction-parameters.d.ts.map +1 -0
  112. package/dist/core/compaction/compaction-parameters.js +27 -0
  113. package/dist/core/compaction/compaction-parameters.js.map +1 -0
  114. package/dist/core/compaction/compaction-runner.d.ts +14 -0
  115. package/dist/core/compaction/compaction-runner.d.ts.map +1 -0
  116. package/dist/core/compaction/compaction-runner.js +29 -0
  117. package/dist/core/compaction/compaction-runner.js.map +1 -0
  118. package/dist/core/compaction/compaction-types.d.ts +79 -0
  119. package/dist/core/compaction/compaction-types.d.ts.map +1 -0
  120. package/dist/core/compaction/compaction-types.js +6 -0
  121. package/dist/core/compaction/compaction-types.js.map +1 -0
  122. package/dist/core/compaction/deleted-ranges.d.ts +6 -0
  123. package/dist/core/compaction/deleted-ranges.d.ts.map +1 -0
  124. package/dist/core/compaction/deleted-ranges.js +134 -0
  125. package/dist/core/compaction/deleted-ranges.js.map +1 -0
  126. package/dist/core/compaction/index.d.ts +7 -1
  127. package/dist/core/compaction/index.d.ts.map +1 -1
  128. package/dist/core/compaction/index.js +7 -1
  129. package/dist/core/compaction/index.js.map +1 -1
  130. package/dist/core/compaction/range-planner.d.ts +21 -0
  131. package/dist/core/compaction/range-planner.d.ts.map +1 -0
  132. package/dist/core/compaction/range-planner.js +145 -0
  133. package/dist/core/compaction/range-planner.js.map +1 -0
  134. package/dist/core/compaction/transcript-serialization.d.ts +11 -0
  135. package/dist/core/compaction/transcript-serialization.d.ts.map +1 -0
  136. package/dist/core/compaction/transcript-serialization.js +104 -0
  137. package/dist/core/compaction/transcript-serialization.js.map +1 -0
  138. package/dist/core/extensions/context-types.d.ts +2 -2
  139. package/dist/core/extensions/context-types.d.ts.map +1 -1
  140. package/dist/core/extensions/context-types.js.map +1 -1
  141. package/dist/core/extensions/event-results.d.ts +2 -2
  142. package/dist/core/extensions/event-results.d.ts.map +1 -1
  143. package/dist/core/extensions/event-results.js.map +1 -1
  144. package/dist/core/extensions/session-events.d.ts +7 -7
  145. package/dist/core/extensions/session-events.d.ts.map +1 -1
  146. package/dist/core/extensions/session-events.js.map +1 -1
  147. package/dist/core/index.d.ts +1 -1
  148. package/dist/core/index.d.ts.map +1 -1
  149. package/dist/core/index.js.map +1 -1
  150. package/dist/core/messages.d.ts +4 -0
  151. package/dist/core/messages.d.ts.map +1 -1
  152. package/dist/core/messages.js +11 -0
  153. package/dist/core/messages.js.map +1 -1
  154. package/dist/core/provider-context-usage.d.ts.map +1 -1
  155. package/dist/core/provider-context-usage.js +1 -2
  156. package/dist/core/provider-context-usage.js.map +1 -1
  157. package/dist/core/session-manager-archive.d.ts +2 -1
  158. package/dist/core/session-manager-archive.d.ts.map +1 -1
  159. package/dist/core/session-manager-archive.js +2 -2
  160. package/dist/core/session-manager-archive.js.map +1 -1
  161. package/dist/core/session-manager-classification.d.ts +9 -0
  162. package/dist/core/session-manager-classification.d.ts.map +1 -0
  163. package/dist/core/session-manager-classification.js +37 -0
  164. package/dist/core/session-manager-classification.js.map +1 -0
  165. package/dist/core/session-manager-core.d.ts +4 -4
  166. package/dist/core/session-manager-core.d.ts.map +1 -1
  167. package/dist/core/session-manager-core.js +13 -8
  168. package/dist/core/session-manager-core.js.map +1 -1
  169. package/dist/core/session-manager-entries.d.ts +4 -3
  170. package/dist/core/session-manager-entries.d.ts.map +1 -1
  171. package/dist/core/session-manager-entries.js +12 -10
  172. package/dist/core/session-manager-entries.js.map +1 -1
  173. package/dist/core/session-manager-history.d.ts +6 -23
  174. package/dist/core/session-manager-history.d.ts.map +1 -1
  175. package/dist/core/session-manager-history.js +23 -256
  176. package/dist/core/session-manager-history.js.map +1 -1
  177. package/dist/core/session-manager-list.d.ts.map +1 -1
  178. package/dist/core/session-manager-list.js +3 -3
  179. package/dist/core/session-manager-list.js.map +1 -1
  180. package/dist/core/session-manager-storage.d.ts +1 -1
  181. package/dist/core/session-manager-storage.d.ts.map +1 -1
  182. package/dist/core/session-manager-storage.js +3 -2
  183. package/dist/core/session-manager-storage.js.map +1 -1
  184. package/dist/core/session-manager-types.d.ts +14 -15
  185. package/dist/core/session-manager-types.d.ts.map +1 -1
  186. package/dist/core/session-manager-types.js.map +1 -1
  187. package/dist/core/session-manager.d.ts +3 -2
  188. package/dist/core/session-manager.d.ts.map +1 -1
  189. package/dist/core/session-manager.js +1 -1
  190. package/dist/core/session-manager.js.map +1 -1
  191. package/dist/core/slash-commands.d.ts.map +1 -1
  192. package/dist/core/slash-commands.js +1 -1
  193. package/dist/core/slash-commands.js.map +1 -1
  194. package/dist/index.d.ts +3 -2
  195. package/dist/index.d.ts.map +1 -1
  196. package/dist/index.js +2 -1
  197. package/dist/index.js.map +1 -1
  198. package/dist/main-session.d.ts +1 -0
  199. package/dist/main-session.d.ts.map +1 -1
  200. package/dist/main-session.js +7 -0
  201. package/dist/main-session.js.map +1 -1
  202. package/dist/main.d.ts.map +1 -1
  203. package/dist/main.js +2 -2
  204. package/dist/main.js.map +1 -1
  205. package/dist/modes/interactive/components/chat-message-renderer.d.ts +4 -1
  206. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  207. package/dist/modes/interactive/components/chat-message-renderer.js +12 -22
  208. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  209. package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
  210. package/dist/modes/interactive/components/chat-session-host-events.js +5 -11
  211. package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
  212. package/dist/modes/interactive/components/chat-session-host-rendering.d.ts.map +1 -1
  213. package/dist/modes/interactive/components/chat-session-host-rendering.js +3 -0
  214. package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
  215. package/dist/modes/interactive/components/chat-session-host-state.d.ts +1 -0
  216. package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
  217. package/dist/modes/interactive/components/chat-session-host-state.js +1 -0
  218. package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
  219. package/dist/modes/interactive/components/chat-session-host-utils.d.ts.map +1 -1
  220. package/dist/modes/interactive/components/chat-session-host-utils.js +1 -1
  221. package/dist/modes/interactive/components/chat-session-host-utils.js.map +1 -1
  222. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  223. package/dist/modes/interactive/components/chat-session-host.js +1 -0
  224. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  225. package/dist/modes/interactive/components/compaction-boundary-message.d.ts +20 -0
  226. package/dist/modes/interactive/components/compaction-boundary-message.d.ts.map +1 -0
  227. package/dist/modes/interactive/components/compaction-boundary-message.js +48 -0
  228. package/dist/modes/interactive/components/compaction-boundary-message.js.map +1 -0
  229. package/dist/modes/interactive/components/index.d.ts +1 -1
  230. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/index.js +1 -1
  232. package/dist/modes/interactive/components/index.js.map +1 -1
  233. package/dist/modes/interactive/components/session-selector-list.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/session-selector-list.js +3 -0
  235. package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
  236. package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
  237. package/dist/modes/interactive/interactive-agent-events.js +3 -47
  238. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  239. package/dist/modes/interactive/interactive-mode-deps.d.ts +2 -2
  240. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  241. package/dist/modes/interactive/interactive-mode-deps.js +1 -1
  242. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  243. package/dist/modes/interactive/interactive-mode-surface.d.ts +5 -3
  244. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  245. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  246. package/dist/modes/interactive/interactive-render-chat.d.ts.map +1 -1
  247. package/dist/modes/interactive/interactive-render-chat.js +21 -12
  248. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  249. package/dist/modes/rpc/rpc-client.d.ts +3 -5
  250. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  251. package/dist/modes/rpc/rpc-client.js +1 -6
  252. package/dist/modes/rpc/rpc-client.js.map +1 -1
  253. package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
  254. package/dist/modes/rpc/rpc-command-handler.js +0 -4
  255. package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
  256. package/dist/modes/rpc/rpc-types.d.ts +2 -11
  257. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  258. package/dist/modes/rpc/rpc-types.js.map +1 -1
  259. package/docs/compaction.md +97 -772
  260. package/docs/extensions.md +25 -29
  261. package/docs/json.md +2 -2
  262. package/docs/rpc.md +32 -24
  263. package/docs/sdk.md +3 -3
  264. package/docs/session-format.md +49 -30
  265. package/docs/sessions.md +5 -3
  266. package/docs/settings.md +14 -4
  267. package/docs/usage.md +1 -1
  268. package/docs/workflows.md +30 -15
  269. package/examples/extensions/custom-compaction.ts +14 -58
  270. package/examples/extensions/handoff.ts +2 -3
  271. package/npm-shrinkwrap.json +23 -23
  272. package/package.json +2 -2
  273. package/dist/core/compaction/context-assistant-turns.d.ts +0 -42
  274. package/dist/core/compaction/context-assistant-turns.d.ts.map +0 -1
  275. package/dist/core/compaction/context-assistant-turns.js +0 -87
  276. package/dist/core/compaction/context-assistant-turns.js.map +0 -1
  277. package/dist/core/compaction/context-compaction-critical.d.ts +0 -15
  278. package/dist/core/compaction/context-compaction-critical.d.ts.map +0 -1
  279. package/dist/core/compaction/context-compaction-critical.js +0 -57
  280. package/dist/core/compaction/context-compaction-critical.js.map +0 -1
  281. package/dist/core/compaction/context-compaction-eviction-alternates.d.ts +0 -18
  282. package/dist/core/compaction/context-compaction-eviction-alternates.d.ts.map +0 -1
  283. package/dist/core/compaction/context-compaction-eviction-alternates.js +0 -186
  284. package/dist/core/compaction/context-compaction-eviction-alternates.js.map +0 -1
  285. package/dist/core/compaction/context-compaction-eviction.d.ts +0 -12
  286. package/dist/core/compaction/context-compaction-eviction.d.ts.map +0 -1
  287. package/dist/core/compaction/context-compaction-eviction.js +0 -222
  288. package/dist/core/compaction/context-compaction-eviction.js.map +0 -1
  289. package/dist/core/compaction/context-compaction-metrics.d.ts +0 -28
  290. package/dist/core/compaction/context-compaction-metrics.d.ts.map +0 -1
  291. package/dist/core/compaction/context-compaction-metrics.js +0 -107
  292. package/dist/core/compaction/context-compaction-metrics.js.map +0 -1
  293. package/dist/core/compaction/context-compaction-prompt.d.ts +0 -9
  294. package/dist/core/compaction/context-compaction-prompt.d.ts.map +0 -1
  295. package/dist/core/compaction/context-compaction-prompt.js +0 -163
  296. package/dist/core/compaction/context-compaction-prompt.js.map +0 -1
  297. package/dist/core/compaction/context-compaction-runner.d.ts +0 -11
  298. package/dist/core/compaction/context-compaction-runner.d.ts.map +0 -1
  299. package/dist/core/compaction/context-compaction-runner.js +0 -273
  300. package/dist/core/compaction/context-compaction-runner.js.map +0 -1
  301. package/dist/core/compaction/context-compaction-strategy.d.ts +0 -5
  302. package/dist/core/compaction/context-compaction-strategy.d.ts.map +0 -1
  303. package/dist/core/compaction/context-compaction-strategy.js +0 -27
  304. package/dist/core/compaction/context-compaction-strategy.js.map +0 -1
  305. package/dist/core/compaction/context-compaction-types.d.ts +0 -75
  306. package/dist/core/compaction/context-compaction-types.d.ts.map +0 -1
  307. package/dist/core/compaction/context-compaction-types.js +0 -6
  308. package/dist/core/compaction/context-compaction-types.js.map +0 -1
  309. package/dist/core/compaction/context-compaction.d.ts +0 -10
  310. package/dist/core/compaction/context-compaction.d.ts.map +0 -1
  311. package/dist/core/compaction/context-compaction.js +0 -8
  312. package/dist/core/compaction/context-compaction.js.map +0 -1
  313. package/dist/core/compaction/context-deletion-application.d.ts +0 -14
  314. package/dist/core/compaction/context-deletion-application.d.ts.map +0 -1
  315. package/dist/core/compaction/context-deletion-application.js +0 -261
  316. package/dist/core/compaction/context-deletion-application.js.map +0 -1
  317. package/dist/core/compaction/context-deletion-store.d.ts +0 -78
  318. package/dist/core/compaction/context-deletion-store.d.ts.map +0 -1
  319. package/dist/core/compaction/context-deletion-store.js +0 -162
  320. package/dist/core/compaction/context-deletion-store.js.map +0 -1
  321. package/dist/core/compaction/context-deletion-targets.d.ts +0 -43
  322. package/dist/core/compaction/context-deletion-targets.d.ts.map +0 -1
  323. package/dist/core/compaction/context-deletion-targets.js +0 -292
  324. package/dist/core/compaction/context-deletion-targets.js.map +0 -1
  325. package/dist/core/compaction/context-deletion-tool-definitions.d.ts +0 -193
  326. package/dist/core/compaction/context-deletion-tool-definitions.d.ts.map +0 -1
  327. package/dist/core/compaction/context-deletion-tool-definitions.js +0 -98
  328. package/dist/core/compaction/context-deletion-tool-definitions.js.map +0 -1
  329. package/dist/core/compaction/context-deletion-tool-helpers.d.ts +0 -19
  330. package/dist/core/compaction/context-deletion-tool-helpers.d.ts.map +0 -1
  331. package/dist/core/compaction/context-deletion-tool-helpers.js +0 -210
  332. package/dist/core/compaction/context-deletion-tool-helpers.js.map +0 -1
  333. package/dist/core/compaction/context-deletion-tools.d.ts +0 -4
  334. package/dist/core/compaction/context-deletion-tools.d.ts.map +0 -1
  335. package/dist/core/compaction/context-deletion-tools.js +0 -403
  336. package/dist/core/compaction/context-deletion-tools.js.map +0 -1
  337. package/dist/core/compaction/context-transcript-analysis.d.ts +0 -11
  338. package/dist/core/compaction/context-transcript-analysis.d.ts.map +0 -1
  339. package/dist/core/compaction/context-transcript-analysis.js +0 -228
  340. package/dist/core/compaction/context-transcript-analysis.js.map +0 -1
  341. package/dist/core/session-manager-tool-dependencies.d.ts +0 -10
  342. package/dist/core/session-manager-tool-dependencies.d.ts.map +0 -1
  343. package/dist/core/session-manager-tool-dependencies.js +0 -133
  344. package/dist/core/session-manager-tool-dependencies.js.map +0 -1
  345. package/dist/modes/interactive/components/context-compaction-summary-message.d.ts +0 -17
  346. package/dist/modes/interactive/components/context-compaction-summary-message.d.ts.map +0 -1
  347. package/dist/modes/interactive/components/context-compaction-summary-message.js +0 -83
  348. package/dist/modes/interactive/components/context-compaction-summary-message.js.map +0 -1
@@ -0,0 +1,153 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { hostname } from "node:os";
3
+ import { dirname } from "node:path";
4
+ import type { FileDurableState } from "./file-state.js";
5
+
6
+ interface LockOwner {
7
+ readonly pid: number;
8
+ readonly host: string;
9
+ readonly token: string;
10
+ readonly acquiredAt: number;
11
+ }
12
+
13
+ const LOCK_OWNER_FILE = "owner.json";
14
+ const STALE_LOCK_MS = 30_000;
15
+
16
+ export function writeDurableFileState(filePath: string, state: FileDurableState): void {
17
+ const dir = dirname(filePath);
18
+ ensureSecureDir(dir);
19
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
20
+ writeFileSync(tmp, JSON.stringify(state), { encoding: "utf-8", mode: 0o600 });
21
+ chmodBestEffort(tmp, 0o600);
22
+ renameSync(tmp, filePath);
23
+ chmodBestEffort(filePath, 0o600);
24
+ }
25
+
26
+ export function withDurableFileLock<T>(filePath: string, fn: () => T): T {
27
+ const dir = dirname(filePath);
28
+ ensureSecureDir(dir);
29
+ const lockDir = `${filePath}.lock`;
30
+ const deadline = Date.now() + 5000;
31
+ while (true) {
32
+ try {
33
+ mkdirSync(lockDir, { mode: 0o700 });
34
+ try {
35
+ chmodBestEffort(lockDir, 0o700);
36
+ writeLockOwner(lockDir);
37
+ } catch (ownerErr) {
38
+ rmSync(lockDir, { recursive: true, force: true });
39
+ throw ownerErr;
40
+ }
41
+ break;
42
+ } catch (err) {
43
+ if (errorCode(err) !== "EEXIST") throw err;
44
+ if (tryReclaimStaleLock(lockDir, STALE_LOCK_MS)) continue;
45
+ if (Date.now() > deadline) throw new Error(`Timed out acquiring durable workflow state lock: ${lockDir}`);
46
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
47
+ }
48
+ }
49
+ try {
50
+ return fn();
51
+ } finally {
52
+ rmSync(lockDir, { recursive: true, force: true });
53
+ }
54
+ }
55
+
56
+ function tryReclaimStaleLock(lockDir: string, staleMs: number): boolean {
57
+ if (!isStaleLock(lockDir, staleMs)) return false;
58
+ const owner = readLockOwner(lockDir);
59
+ if (owner === undefined || !isLockOwnerAbandoned(owner)) return false;
60
+ const quarantine = `${lockDir}.stale.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
61
+ try {
62
+ renameSync(lockDir, quarantine);
63
+ } catch {
64
+ return false;
65
+ }
66
+ const quarantinedOwner = readLockOwner(quarantine);
67
+ if (!sameLockOwner(owner, quarantinedOwner)) {
68
+ restoreQuarantinedLock(lockDir, quarantine);
69
+ return false;
70
+ }
71
+ try {
72
+ rmSync(quarantine, { recursive: true, force: true });
73
+ } catch {
74
+ // The stale lock has already been moved away from the active lock path.
75
+ }
76
+ return true;
77
+ }
78
+
79
+ function isStaleLock(lockDir: string, staleMs: number): boolean {
80
+ try {
81
+ return Date.now() - statSync(lockDir).mtimeMs > staleMs;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ function writeLockOwner(lockDir: string): void {
88
+ const owner: LockOwner = {
89
+ pid: process.pid,
90
+ host: hostname(),
91
+ token: `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
92
+ acquiredAt: Date.now(),
93
+ };
94
+ const ownerPath = `${lockDir}/${LOCK_OWNER_FILE}`;
95
+ writeFileSync(ownerPath, JSON.stringify(owner), { encoding: "utf-8", mode: 0o600 });
96
+ chmodBestEffort(ownerPath, 0o600);
97
+ }
98
+
99
+ function readLockOwner(lockDir: string): LockOwner | undefined {
100
+ try {
101
+ const parsed = JSON.parse(readFileSync(`${lockDir}/${LOCK_OWNER_FILE}`, "utf-8"));
102
+ return isLockOwner(parsed) ? parsed : undefined;
103
+ } catch {
104
+ return undefined;
105
+ }
106
+ }
107
+
108
+ function isLockOwner(value: unknown): value is LockOwner {
109
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
110
+ const record = value as Partial<LockOwner>;
111
+ return typeof record.pid === "number" && typeof record.host === "string"
112
+ && typeof record.token === "string" && typeof record.acquiredAt === "number";
113
+ }
114
+
115
+ function isLockOwnerAbandoned(owner: LockOwner): boolean {
116
+ if (owner.host !== hostname() || !Number.isInteger(owner.pid) || owner.pid <= 0) return false;
117
+ try {
118
+ process.kill(owner.pid, 0);
119
+ return false;
120
+ } catch (err) {
121
+ return errorCode(err) === "ESRCH";
122
+ }
123
+ }
124
+
125
+ function sameLockOwner(a: LockOwner, b: LockOwner | undefined): boolean {
126
+ return b !== undefined && a.pid === b.pid && a.host === b.host && a.token === b.token;
127
+ }
128
+
129
+ function restoreQuarantinedLock(lockDir: string, quarantine: string): void {
130
+ try {
131
+ if (!existsSync(lockDir)) renameSync(quarantine, lockDir);
132
+ else rmSync(quarantine, { recursive: true, force: true });
133
+ } catch {
134
+ // Best-effort: never delete the active lock path after a failed compare.
135
+ }
136
+ }
137
+
138
+ function errorCode(err: unknown): string | undefined {
139
+ return typeof err === "object" && err !== null && "code" in err ? String(err.code) : undefined;
140
+ }
141
+
142
+ function ensureSecureDir(dir: string): void {
143
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
144
+ chmodBestEffort(dir, 0o700);
145
+ }
146
+
147
+ function chmodBestEffort(path: string, mode: number): void {
148
+ try {
149
+ chmodSync(path, mode);
150
+ } catch {
151
+ // chmod is unavailable or unsupported on some filesystems/platforms.
152
+ }
153
+ }
@@ -0,0 +1,104 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import type { WorkflowSerializableValue } from "../shared/types.js";
3
+ import type { DurableCheckpoint, DurableWorkflowHandle } from "./types.js";
4
+ import { classifyDurableFormatVersion, DURABLE_FORMAT_VERSION } from "./format-version.js";
5
+
6
+ export interface FileDurableRecord {
7
+ readonly handle: DurableWorkflowHandle;
8
+ readonly checkpoints: readonly DurableCheckpoint[];
9
+ }
10
+
11
+ export interface FileDurableState {
12
+ readonly version: number;
13
+ readonly workflows: readonly FileDurableRecord[];
14
+ readonly deletedWorkflowIds: readonly string[];
15
+ }
16
+
17
+ export type FileStateReadResult =
18
+ | { readonly kind: "missing" }
19
+ | { readonly kind: "current"; readonly state: FileDurableState }
20
+ | { readonly kind: "legacy"; readonly workflowIds: readonly string[] }
21
+ | { readonly kind: "unknown" };
22
+
23
+ export function readDurableFileState(filePath: string): FileStateReadResult {
24
+ if (!existsSync(filePath)) return { kind: "missing" };
25
+ try {
26
+ const parsed = JSON.parse(readFileSync(filePath, "utf-8")) as Partial<FileDurableState> | null;
27
+ if (parsed === null || !Array.isArray(parsed.workflows)) return { kind: "unknown" };
28
+ const compatibility = classifyDurableFormatVersion(parsed.version);
29
+ if (compatibility === "legacy") {
30
+ const validRecords = parsed.workflows.filter(isFileDurableRecord);
31
+ return {
32
+ kind: "legacy",
33
+ workflowIds: validRecords.length === parsed.workflows.length
34
+ ? validRecords.map((record) => record.handle.workflowId)
35
+ : [],
36
+ };
37
+ }
38
+ const deleted = parsed.deletedWorkflowIds ?? [];
39
+ if (compatibility === "unknown" || !isStringArray(deleted) || !parsed.workflows.every(isFileDurableRecord)) return { kind: "unknown" };
40
+ return { kind: "current", state: { version: DURABLE_FORMAT_VERSION, workflows: parsed.workflows, deletedWorkflowIds: deleted } };
41
+ } catch {
42
+ return { kind: "unknown" };
43
+ }
44
+ }
45
+
46
+ function isFileDurableRecord(value: unknown): value is FileDurableRecord {
47
+ if (!isObject(value)) return false;
48
+ const handle = value["handle"];
49
+ const checkpoints = value["checkpoints"];
50
+ if (!isHandle(handle) || !Array.isArray(checkpoints)) return false;
51
+ return checkpoints.every((checkpoint) => isCheckpoint(checkpoint) && checkpoint.workflowId === handle.workflowId);
52
+ }
53
+
54
+ function isHandle(value: unknown): value is DurableWorkflowHandle {
55
+ if (!isObject(value) || typeof value["workflowId"] !== "string" || typeof value["name"] !== "string"
56
+ || !isSerializableObject(value["inputs"]) || typeof value["createdAt"] !== "number"
57
+ || typeof value["updatedAt"] !== "number" || !isStatus(value["status"])
58
+ || typeof value["completedCheckpoints"] !== "number" || typeof value["pendingPrompts"] !== "number") return false;
59
+ return optionalString(value, "invocationCwd") && optionalString(value, "workflowCwd")
60
+ && optionalString(value, "repositoryRoot") && optionalString(value, "gitWorktreeRoot")
61
+ && optionalString(value, "sessionFile") && optionalString(value, "label")
62
+ && optionalString(value, "rootWorkflowId") && optionalBoolean(value, "resumable");
63
+ }
64
+
65
+ function isCheckpoint(value: unknown): value is DurableCheckpoint {
66
+ if (!isObject(value) || typeof value["workflowId"] !== "string" || typeof value["checkpointId"] !== "string"
67
+ || typeof value["completedAt"] !== "number") return false;
68
+ if (value["kind"] === "tool") return typeof value["name"] === "string" && typeof value["argsHash"] === "string" && isSerializable(value["output"]);
69
+ if (value["kind"] === "ui") return typeof value["promptKind"] === "string" && typeof value["message"] === "string"
70
+ && typeof value["promptHash"] === "string" && isSerializable(value["response"]);
71
+ return value["kind"] === "stage" && typeof value["name"] === "string" && typeof value["replayKey"] === "string"
72
+ && (!("output" in value) || isSerializable(value["output"]));
73
+ }
74
+
75
+ function isStringArray(value: unknown): value is readonly string[] {
76
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
77
+ }
78
+ function isStatus(value: unknown): boolean {
79
+ return value === "running" || value === "paused" || value === "completed"
80
+ || value === "failed" || value === "cancelled" || value === "blocked";
81
+ }
82
+
83
+ function isSerializableObject(value: unknown): value is Readonly<Record<string, WorkflowSerializableValue>> {
84
+ return isObject(value) && Object.values(value).every(isSerializable);
85
+ }
86
+
87
+ function isSerializable(value: unknown): value is WorkflowSerializableValue {
88
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
89
+ if (typeof value === "number") return Number.isFinite(value);
90
+ if (Array.isArray(value)) return value.every(isSerializable);
91
+ return isSerializableObject(value);
92
+ }
93
+
94
+ function isObject(value: unknown): value is Record<string, unknown> {
95
+ return typeof value === "object" && value !== null && !Array.isArray(value);
96
+ }
97
+
98
+ function optionalString(value: Record<string, unknown>, key: string): boolean {
99
+ return value[key] === undefined || typeof value[key] === "string";
100
+ }
101
+
102
+ function optionalBoolean(value: Record<string, unknown>, key: string): boolean {
103
+ return value[key] === undefined || typeof value[key] === "boolean";
104
+ }
@@ -0,0 +1,12 @@
1
+ /** Shared schema version for durable workflow state and discovery metadata. */
2
+ export const DURABLE_FORMAT_VERSION = 2 as const;
3
+ export const LEGACY_DURABLE_FORMAT_VERSION = 1 as const;
4
+
5
+ export type DurableFormatCompatibility = "current" | "legacy" | "unknown";
6
+
7
+ /** Classify an explicit durable format version without guessing about malformed data. */
8
+ export function classifyDurableFormatVersion(value: unknown): DurableFormatCompatibility {
9
+ if (value === DURABLE_FORMAT_VERSION) return "current";
10
+ if (value === LEGACY_DURABLE_FORMAT_VERSION) return "legacy";
11
+ return "unknown";
12
+ }
@@ -49,6 +49,18 @@ export {
49
49
  persistDurableCacheEntry,
50
50
  formatResumableWorkflowList,
51
51
  } from "./resume-catalog.js";
52
+ export {
53
+ completedWorkflowSnapshot,
54
+ listCompletedFromBackend,
55
+ listOpenableCompletedWorkflows,
56
+ resolveCompletedWorkflow,
57
+ type CompletedWorkflowResolution,
58
+ } from "./completed-catalog.js";
59
+ export {
60
+ openCompletedDurableWorkflow,
61
+ type OpenCompletedDurableDeps,
62
+ type OpenCompletedDurableResult,
63
+ } from "./completed-inspection.js";
52
64
  export {
53
65
  createToolPrimitive,
54
66
  createCheckpointIdGenerator,
@@ -20,6 +20,8 @@ import { join } from "node:path";
20
20
  import type { DurableCheckpointEntry, DurableWorkflowStatus, ResumableWorkflowEntry } from "./types.js";
21
21
  import type { DurableWorkflowBackend } from "./backend.js";
22
22
  import type { WorkflowSerializableValue } from "../shared/types.js";
23
+ import { isDurableWorkflowResumable } from "./resume-eligibility.js";
24
+ import { DURABLE_FORMAT_VERSION } from "./format-version.js";
23
25
 
24
26
  // ---------------------------------------------------------------------------
25
27
  // Session file scanning
@@ -52,7 +54,7 @@ export function scanResumableWorkflows(sessionDir: string): readonly ResumableWo
52
54
  }
53
55
  }
54
56
  }
55
- return [...entries.values()].filter(isResumableEntry).sort((a, b) => b.updatedAt - a.updatedAt);
57
+ return [...entries.values()].filter(isDurableWorkflowResumable).sort((a, b) => b.updatedAt - a.updatedAt);
56
58
  }
57
59
 
58
60
  function readDurableEntriesFromFile(filePath: string): readonly DurableCheckpointEntry[] {
@@ -89,6 +91,8 @@ function durablePayloadFromJsonlEntry(parsed: Record<string, unknown>): Record<s
89
91
  }
90
92
 
91
93
  function parseDurableEntry(raw: Record<string, unknown>): DurableCheckpointEntry | undefined {
94
+ if (raw["formatVersion"] !== DURABLE_FORMAT_VERSION) return undefined;
95
+
92
96
  const workflowId = raw["workflowId"];
93
97
  const name = raw["name"];
94
98
  const inputs = raw["inputs"];
@@ -97,6 +101,7 @@ function parseDurableEntry(raw: Record<string, unknown>): DurableCheckpointEntry
97
101
  if (typeof workflowId !== "string" || typeof name !== "string" || typeof status !== "string" || typeof ts !== "number") return undefined;
98
102
  if (!isWorkflowSerializableObject(inputs)) return undefined;
99
103
  return {
104
+ formatVersion: DURABLE_FORMAT_VERSION,
100
105
  type: "workflow.durable.checkpoint",
101
106
  workflowId,
102
107
  name,
@@ -155,24 +160,6 @@ function entryToResumable(entry: DurableCheckpointEntry, sessionFile: string): R
155
160
  };
156
161
  }
157
162
 
158
- function isResumableStatus(status: DurableWorkflowStatus): boolean {
159
- // `running`/`paused` are both resumable at the catalog level. A `running`
160
- // durable handle may be a crashed process (cross-session crash recovery);
161
- // same-session double-resume is filtered out by the command layer.
162
- return status === "running" || status === "paused";
163
- }
164
- function hasResumeProgress(entry: ResumableWorkflowEntry): boolean {
165
- return entry.completedCheckpoints > 0 || entry.pendingPrompts > 0;
166
- }
167
-
168
-
169
- function isResumableEntry(entry: ResumableWorkflowEntry): boolean {
170
- const isRoot = entry.rootWorkflowId === undefined || entry.rootWorkflowId === entry.workflowId;
171
- if (!isRoot) return false;
172
- if (entry.status === "failed" || entry.status === "blocked") return entry.resumable !== false;
173
- return isResumableStatus(entry.status) && hasResumeProgress(entry);
174
- }
175
-
176
163
  // ---------------------------------------------------------------------------
177
164
  // Backend-backed catalog
178
165
  // ---------------------------------------------------------------------------
@@ -196,6 +183,7 @@ export function persistDurableCacheEntry(
196
183
  ): void {
197
184
  if (typeof persistence.appendEntry !== "function") return;
198
185
  persistence.appendEntry("workflow.durable.checkpoint", {
186
+ formatVersion: entry.formatVersion,
199
187
  workflowId: entry.workflowId,
200
188
  name: entry.name,
201
189
  inputs: entry.inputs as Record<string, unknown>,
@@ -217,13 +205,14 @@ export function persistDurableCacheEntry(
217
205
  * Format the resumable workflow list for display in the selector.
218
206
  */
219
207
  export function formatResumableWorkflowList(entries: readonly ResumableWorkflowEntry[]): string {
220
- if (entries.length === 0) return "No resumable workflows found.";
221
- const lines = entries.map((e, i) => {
222
- const id = e.workflowId.slice(0, 8);
223
- const status = e.status.padEnd(8);
224
- const checkpoints = `${e.completedCheckpoints} checkpoint${e.completedCheckpoints === 1 ? "" : "s"}`;
225
- const label = e.label ? ` "${e.label}"` : "";
226
- return ` ${i + 1}. ${id} ${status} ${e.name}${label} (${checkpoints})`;
208
+ if (entries.length === 0) return "No resumable or completed workflows found.";
209
+ const hasCompleted = entries.some((entry) => entry.status === "completed");
210
+ const lines = entries.map((entry, index) => {
211
+ const id = entry.workflowId.slice(0, 8);
212
+ const status = entry.status === "completed" ? "✓ completed" : entry.status.padEnd(8);
213
+ const checkpoints = `${entry.completedCheckpoints} checkpoint${entry.completedCheckpoints === 1 ? "" : "s"}`;
214
+ const label = entry.label ? ` "${entry.label}"` : "";
215
+ return ` ${index + 1}. ${id} ${status} ${entry.name}${label} (${checkpoints})`;
227
216
  });
228
- return `Resumable workflows:\n${lines.join("\n")}`;
217
+ return `${hasCompleted ? "Workflow resume targets" : "Resumable workflows"}:\n${lines.join("\n")}`;
229
218
  }
@@ -0,0 +1,20 @@
1
+ import type { DurableWorkflowStatus } from "./types.js";
2
+
3
+ /** Metadata shared by durable handles and session-cache resume entries. */
4
+ export interface DurableResumeCandidate {
5
+ readonly workflowId: string;
6
+ readonly status: DurableWorkflowStatus;
7
+ readonly completedCheckpoints: number;
8
+ readonly pendingPrompts: number;
9
+ readonly rootWorkflowId?: string;
10
+ readonly resumable?: boolean;
11
+ }
12
+
13
+ /** Authoritative status/progress rules for durable workflow resume discovery. */
14
+ export function isDurableWorkflowResumable(candidate: DurableResumeCandidate): boolean {
15
+ const isRoot = candidate.rootWorkflowId === undefined || candidate.rootWorkflowId === candidate.workflowId;
16
+ if (!isRoot) return false;
17
+ if (candidate.status === "failed" || candidate.status === "blocked") return candidate.resumable !== false;
18
+ const hasResumeProgress = candidate.completedCheckpoints > 0 || candidate.pendingPrompts > 0;
19
+ return (candidate.status === "running" || candidate.status === "paused") && hasResumeProgress;
20
+ }
@@ -31,6 +31,7 @@ import { resolveAndValidateInputs } from "../runs/foreground/executor-inputs.js"
31
31
  import { getDurableBackend } from "./factory.js";
32
32
  import type { DurableWorkflowBackend } from "./backend.js";
33
33
  import type { ResumableWorkflowEntry } from "./types.js";
34
+ import { isDurableWorkflowResumable } from "./resume-eligibility.js";
34
35
  import { workflowDefinitionRequirementMessage } from "../runs/foreground/executor-child-helpers.js";
35
36
  import { isWorkflowDefinition } from "../runs/foreground/executor-child-helpers.js";
36
37
  import type { RunSnapshot } from "../shared/store-types.js";
@@ -109,6 +110,9 @@ export function resumeDurableWorkflow(
109
110
  const resolvedCatalog = catalog ?? backend.listResumableWorkflows();
110
111
  const resolved = resolveDurableEntry(workflowIdOrPrefix, resolvedCatalog);
111
112
  if (resolved === undefined) {
113
+ if (!backend.isWorkflowLoadable(workflowIdOrPrefix)) {
114
+ return { ok: false, reason: "not_registered", message: `Durable workflow ${workflowIdOrPrefix.slice(0, 8)} uses an incompatible or unavailable persisted format and cannot be resumed.` };
115
+ }
112
116
  // Not in the (filtered) resumable catalog. It may still be a workflow the
113
117
  // backend knows about. Only surface "already running" when there is a live,
114
118
  // actively-executing run for it in this session; otherwise a `running`
@@ -126,6 +130,9 @@ export function resumeDurableWorkflow(
126
130
  message: `Ambiguous workflow prefix "${workflowIdOrPrefix}" matches: ${resolved.matches.map((m) => `${m.name} (${m.workflowId.slice(0, 8)})`).join(", ")}`,
127
131
  };
128
132
  }
133
+ if (!backend.isWorkflowLoadable(resolved.workflowId)) {
134
+ return { ok: false, reason: "not_registered", message: `Durable workflow ${resolved.workflowId.slice(0, 8)} uses an incompatible or unavailable persisted format and cannot be resumed.` };
135
+ }
129
136
  // Authoritative backend-handle check: a cache-only entry (no handle) is
130
137
  // "stale" regardless of its cached status. A `running` handle is only
131
138
  // refused when there is a live, actively-executing run for it in THIS
@@ -140,15 +147,15 @@ export function resumeDurableWorkflow(
140
147
  };
141
148
  }
142
149
  if (handle.status === "running" && hasActiveLiveRun(deps.baseRunOpts.store, resolved.workflowId)) {
143
- return alreadyRunningResult(resolved.name, resolved.workflowId, deps.baseRunOpts.store);
150
+ return alreadyRunningResult(handle.name, resolved.workflowId, deps.baseRunOpts.store);
144
151
  }
145
- if (!isResumableEntry(resolved)) {
146
- return { ok: false, reason: "not_resumable", message: `Workflow ${resolved.workflowId.slice(0, 8)} is ${resolved.status}, not resumable.` };
152
+ if (!isDurableWorkflowResumable(handle)) {
153
+ return { ok: false, reason: "not_resumable", message: `Workflow ${resolved.workflowId.slice(0, 8)} is ${handle.status}, not resumable.` };
147
154
  }
148
155
 
149
- const def = deps.registry.get(resolved.name);
156
+ const def = deps.registry.get(handle.name);
150
157
  if (def === undefined) {
151
- return { ok: false, reason: "workflow_not_found", message: `Workflow definition not found: ${resolved.name}` };
158
+ return { ok: false, reason: "workflow_not_found", message: `Workflow definition not found: ${handle.name}` };
152
159
  }
153
160
  if (!isWorkflowDefinition(def)) {
154
161
  return { ok: false, reason: "workflow_not_found", message: workflowDefinitionRequirementMessage("resumeDurableWorkflow", def) };
@@ -180,8 +187,8 @@ export function resumeDurableWorkflow(
180
187
  ok: true,
181
188
  runId: accepted.runId,
182
189
  workflowId: resolved.workflowId,
183
- name: resolved.name,
184
- message: `Resuming durable workflow "${resolved.name}" (${resolved.workflowId.slice(0, 8)}) — completed checkpoints will be replayed.`,
190
+ name: handle.name,
191
+ message: `Resuming durable workflow "${handle.name}" (${resolved.workflowId.slice(0, 8)}) — completed checkpoints will be replayed.`,
185
192
  };
186
193
  }
187
194
 
@@ -209,20 +216,6 @@ function alreadyRunningResult(name: string, workflowId: string, store: RunOpts["
209
216
  };
210
217
  }
211
218
 
212
- function hasResumeProgress(entry: ResumableWorkflowEntry): boolean {
213
- return entry.completedCheckpoints > 0 || entry.pendingPrompts > 0;
214
- }
215
-
216
- function isResumableEntry(entry: ResumableWorkflowEntry): boolean {
217
- const isRoot = entry.rootWorkflowId === undefined || entry.rootWorkflowId === entry.workflowId;
218
- if (!isRoot) return false;
219
- if (entry.status === "failed" || entry.status === "blocked") return entry.resumable !== false;
220
- // `running` is resumable at this layer: a `running` durable handle may be a
221
- // crashed process. Same-session double-resume is blocked separately via
222
- // `hasActiveLiveRun` before dispatch.
223
- return (entry.status === "running" || entry.status === "paused") && hasResumeProgress(entry);
224
- }
225
-
226
219
  /**
227
220
  * True when the live run store has an actively-executing (not ended, not quit)
228
221
  * run for `workflowId`. This is the only reliable signal that a durable
@@ -251,9 +244,6 @@ export function isBackendTerminal(backend: DurableWorkflowBackend, workflowId: s
251
244
  return false;
252
245
  }
253
246
 
254
- function hasBackendResumeState(backend: DurableWorkflowBackend, workflowId: string): boolean {
255
- return backend.getWorkflow(workflowId) !== undefined;
256
- }
257
247
 
258
248
  /**
259
249
  * Runtime-facing async preparation: hydrate the durable backend from DBOS
@@ -266,6 +256,17 @@ function hasBackendResumeState(backend: DurableWorkflowBackend, workflowId: stri
266
256
  * terminal (completed/cancelled/non-resumable), stale cache rows for that id
267
257
  * are also suppressed. cross-ref: issue #1498.
268
258
  */
259
+ /** Remove restored/live snapshots whose authoritative durable format is non-loadable. */
260
+ export function purgeSuppressedWorkflowRuns(backend: DurableWorkflowBackend, store: RunOpts["store"]): readonly string[] {
261
+ if (store === undefined) return [];
262
+ const removed: string[] = [];
263
+ for (const run of store.runs()) {
264
+ if (backend.isWorkflowLoadable(run.id)) continue;
265
+ if (store.removeRun(run.id)) removed.push(run.id);
266
+ }
267
+ return removed;
268
+ }
269
+
269
270
  export async function prepareRuntimeDurableResumable(
270
271
  getBackend: () => DurableWorkflowBackend,
271
272
  resolveSessionDir: () => string | undefined,
@@ -293,10 +294,9 @@ export async function prepareRuntimeDurableResumable(
293
294
  // durable backend handle/checkpoint state, selecting the row can only fail
294
295
  // as stale (or risk re-running from scratch), so it should not clutter the
295
296
  // resume selector.
296
- const suppressed = scanned.filter((e) =>
297
- !liveIds.has(e.workflowId) &&
298
- hasBackendResumeState(backend, e.workflowId) &&
299
- !isBackendTerminal(backend, e.workflowId)
300
- );
297
+ const suppressed = scanned.filter((entry) => {
298
+ const handle = backend.getWorkflow(entry.workflowId);
299
+ return !liveIds.has(entry.workflowId) && handle !== undefined && isDurableWorkflowResumable(handle);
300
+ });
301
301
  return [...live, ...suppressed];
302
302
  }
@@ -13,9 +13,9 @@
13
13
  * child boundary key, so the same side effects are recovered on resume.
14
14
  *
15
15
  * Only checkpoint read/write methods are scoped. Lifecycle methods
16
- * (`registerWorkflow`, `setWorkflowStatus`, `listResumableWorkflows`,
16
+ * (`registerWorkflow`, `setWorkflowStatus`, completed/resumable listing,
17
17
  * `toCacheEntry`, `getWorkflow`) are no-ops for scoped children because child
18
- * runs are never independently resumable — only the root workflow is resumed.
18
+ * runs are never independently addressable — only the root workflow is.
19
19
  *
20
20
  * cross-ref: issue #1498 — child side effects under the root durable workflow.
21
21
  */
@@ -86,7 +86,12 @@ export class ScopedDurableBackend implements DurableWorkflowBackend {
86
86
  return this.inner.getStageOutput(this.scope.rootWorkflowId, this.scopeKey(replayKey));
87
87
  }
88
88
 
89
- getStageSession(_workflowId: string, replayKey: string): { sessionId?: string; sessionFile?: string } | undefined {
89
+ getStageSession(_workflowId: string, replayKey: string): {
90
+ sessionId?: string;
91
+ sessionFile?: string;
92
+ startedAt?: number;
93
+ durationMs?: number;
94
+ } | undefined {
90
95
  return this.inner.getStageSession(this.scope.rootWorkflowId, this.scopeKey(replayKey));
91
96
  }
92
97
 
@@ -114,10 +119,22 @@ export class ScopedDurableBackend implements DurableWorkflowBackend {
114
119
  return [];
115
120
  }
116
121
 
122
+ listCompletedWorkflows(): readonly ResumableWorkflowEntry[] {
123
+ return [];
124
+ }
125
+
117
126
  toCacheEntry(_workflowId: string): undefined {
118
127
  return undefined;
119
128
  }
120
129
 
130
+ async deleteWorkflow(_workflowId: string): Promise<void> {
131
+ // No-op: scoped children never own or delete root durable state.
132
+ }
133
+
134
+ isWorkflowLoadable(_workflowId: string): boolean {
135
+ return false;
136
+ }
137
+
121
138
  reset(): void {
122
139
  // No-op: scoped backends never own root state.
123
140
  }
@@ -7,6 +7,7 @@ import type { DurableWorkflowBackend } from "./backend.js";
7
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
+ import { elapsedStageMs } from "../shared/timing.js";
10
11
  import { RESUME_CONTINUATION_PROMPT } from "../shared/resume-continuation.js";
11
12
  import type { DurableStageCheckpoint } from "./types.js";
12
13
  export type DurableCompletedStageCheckpoint = DurableStageCheckpoint & { readonly output: WorkflowSerializableValue };
@@ -17,6 +18,7 @@ export interface DurableStageDeps {
17
18
  readonly nextCheckpointId: () => string;
18
19
  readonly nextReplayKey: (stageName: string) => string;
19
20
  readonly replayKeyForCompletedStage?: (stage: StageSnapshot) => string | undefined;
21
+ readonly now?: () => number;
20
22
  }
21
23
 
22
24
  export async function recordStageCheckpoint(deps: DurableStageDeps, stage: StageSnapshot): Promise<boolean> {
@@ -51,17 +53,25 @@ export async function recordStageCheckpoint(deps: DurableStageDeps, stage: Stage
51
53
  export async function recordStageSessionCheckpoint(deps: DurableStageDeps, stage: StageSnapshot): Promise<boolean> {
52
54
  const replayKey = deps.replayKeyForCompletedStage?.(stage) ?? stage.replayKey ?? deps.nextReplayKey(stage.name);
53
55
  if (stage.sessionFile === undefined) return false;
56
+ const checkpointNow = deps.now?.() ?? Date.now();
57
+ const durationMs = elapsedStageMs(stage, checkpointNow) ?? 0;
54
58
  const current = deps.backend.getStageSession(deps.workflowId, replayKey);
55
- if (current?.sessionFile === stage.sessionFile) return false;
59
+ if (current !== undefined
60
+ && current.sessionId === stage.sessionId
61
+ && current.sessionFile === stage.sessionFile
62
+ && current.startedAt === stage.startedAt
63
+ && current.durationMs === durationMs) return false;
56
64
  const checkpoint: DurableStageCheckpoint = {
57
65
  kind: "stage",
58
66
  workflowId: deps.workflowId,
59
- checkpointId: stageSessionCheckpointId(replayKey, stage),
67
+ checkpointId: stageSessionCheckpointId(replayKey, stage, durationMs),
60
68
  name: stage.name,
61
69
  replayKey,
62
70
  ...(stage.sessionId !== undefined ? { sessionId: stage.sessionId } : {}),
63
71
  sessionFile: stage.sessionFile,
64
- completedAt: Date.now(),
72
+ ...(stage.startedAt !== undefined ? { startedAt: stage.startedAt } : {}),
73
+ durationMs,
74
+ completedAt: checkpointNow,
65
75
  };
66
76
  await recordCheckpointDurably(deps.backend, checkpoint);
67
77
  return true;
@@ -107,7 +117,10 @@ export function createDurableStagePrimitive(input: {
107
117
  const liveOptions: StageOptions | undefined = {
108
118
  ...(options ?? {}),
109
119
  durableReplayKey: replayKey,
110
- ...(isMidSessionResume ? { resumeFromSessionFile: session.sessionFile } : {}),
120
+ ...(isMidSessionResume ? {
121
+ resumeFromSessionFile: session.sessionFile,
122
+ durableAccumulatedDurationMs: session.durationMs ?? 0,
123
+ } : {}),
111
124
  };
112
125
  const live = withMidSessionResumePrompt(input.stage(name, liveOptions, replayKey), isMidSessionResume);
113
126
  if (options?.schema === undefined) return live;
@@ -139,7 +152,10 @@ export function createDurableTaskPrimitive(input: {
139
152
  const taskOptions: WorkflowTaskOptions = {
140
153
  ...options,
141
154
  durableReplayKey: replayKey,
142
- ...(session?.sessionFile !== undefined ? { resumeFromSessionFile: session.sessionFile } : {}),
155
+ ...(session?.sessionFile !== undefined ? {
156
+ resumeFromSessionFile: session.sessionFile,
157
+ durableAccumulatedDurationMs: session.durationMs ?? 0,
158
+ } : {}),
143
159
  };
144
160
  const result = await input.task(name, taskOptions, stageFailFastScope);
145
161
  await recordCheckpointDurably(input.backend, {
@@ -299,8 +315,11 @@ function metadataValue<K extends keyof DurableStageCheckpoint>(
299
315
  checkpoints: readonly DurableStageCheckpoint[],
300
316
  key: K,
301
317
  ): Pick<DurableStageCheckpoint, K> | Record<string, never> {
302
- const value = checkpoints.find((checkpoint) => checkpoint[key] !== undefined)?.[key];
303
- return value === undefined ? {} : { [key]: value } as Pick<DurableStageCheckpoint, K>;
318
+ for (let index = checkpoints.length - 1; index >= 0; index -= 1) {
319
+ const value = checkpoints[index]?.[key];
320
+ if (value !== undefined) return { [key]: value } as Pick<DurableStageCheckpoint, K>;
321
+ }
322
+ return {};
304
323
  }
305
324
 
306
325
 
@@ -317,10 +336,12 @@ export function stableCheckpointId(kind: string, replayKey: string): string {
317
336
  return `${kind}:${replayKey}`;
318
337
  }
319
338
 
320
- function stageSessionCheckpointId(replayKey: string, stage: StageSnapshot): string {
339
+ function stageSessionCheckpointId(replayKey: string, stage: StageSnapshot, durationMs: number): string {
321
340
  return `${stableCheckpointId("stage-session", replayKey)}:${durableHash({
322
341
  sessionId: stage.sessionId ?? "",
323
342
  sessionFile: stage.sessionFile ?? "",
343
+ startedAt: stage.startedAt ?? 0,
344
+ durationMs,
324
345
  })}`;
325
346
  }
326
347