@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
@@ -11,76 +11,159 @@
11
11
  * cross-ref: issue #1498 — durable fallback when DBOS/Postgres is unavailable.
12
12
  */
13
13
 
14
- import {
15
- chmodSync,
16
- existsSync,
17
- mkdirSync,
18
- readFileSync,
19
- readdirSync,
20
- renameSync,
21
- rmSync,
22
- statSync,
23
- writeFileSync,
24
- } from "node:fs";
25
- import { hostname } from "node:os";
26
- import { dirname } from "node:path";
14
+ import { readdirSync, rmSync } from "node:fs";
27
15
  import type { DurableCheckpoint, DurableWorkflowHandle, DurableWorkflowStatus } from "./types.js";
28
16
  import { InMemoryDurableBackend, type DurableWorkflowBackend } from "./backend.js";
17
+ import { DURABLE_FORMAT_VERSION } from "./format-version.js";
18
+ import { readDurableFileState, type FileDurableRecord, type FileDurableState } from "./file-state.js";
19
+ import { withDurableFileLock, writeDurableFileState } from "./file-lock.js";
29
20
 
30
- interface FileDurableRecord {
31
- readonly handle: DurableWorkflowHandle;
32
- readonly checkpoints: readonly DurableCheckpoint[];
33
- }
34
-
35
- interface FileDurableState {
36
- readonly version: number;
37
- readonly workflows: readonly FileDurableRecord[];
38
- }
39
-
40
- interface LockOwner {
41
- readonly pid: number;
42
- readonly host: string;
43
- readonly token: string;
44
- readonly acquiredAt: number;
45
- }
46
-
47
- const FILE_FORMAT_VERSION = 1;
48
- const LOCK_OWNER_FILE = "owner.json";
49
- const STALE_LOCK_MS = 30_000;
21
+ const FILE_FORMAT_VERSION = DURABLE_FORMAT_VERSION;
50
22
 
51
23
  export class FileDurableBackend implements DurableWorkflowBackend {
52
24
  public readonly persistent = true;
53
25
  private readonly mem = new InMemoryDurableBackend();
54
26
  private readonly filePath: string;
27
+ private readonly expectedWorkflowId?: string;
55
28
  private loaded = false;
56
-
57
- constructor(filePath: string) {
29
+ private unknownState = false;
30
+ private suppressedAll = false;
31
+ private readonly deletedWorkflowIds = new Set<string>();
32
+ private readonly revivedWorkflowIds = new Set<string>();
33
+
34
+ constructor(
35
+ filePath: string,
36
+ expectedWorkflowId?: string,
37
+ private readonly writeState: typeof writeDurableFileState = writeDurableFileState,
38
+ ) {
58
39
  this.filePath = filePath;
40
+ this.expectedWorkflowId = expectedWorkflowId;
59
41
  }
60
-
61
42
  private ensureLoaded(): void {
62
43
  if (this.loaded) return;
44
+ let result = readDurableFileState(this.filePath);
45
+ if (result.kind === "legacy" && this.expectedWorkflowId !== undefined
46
+ && (result.workflowIds.length === 0 || result.workflowIds.some((id) => id !== this.expectedWorkflowId))) {
47
+ this.unknownState = true;
48
+ this.suppressedAll = true;
49
+ this.loaded = true;
50
+ return;
51
+ }
52
+ if (result.kind === "legacy") {
53
+ this.replaceLegacyState();
54
+ result = readDurableFileState(this.filePath);
55
+ }
56
+ if (result.kind === "unknown" || (result.kind === "current" && !this.matchesExpectedId(result.state))) {
57
+ this.unknownState = true;
58
+ this.suppressedAll = true;
59
+ this.loaded = true;
60
+ return;
61
+ }
62
+ if (result.kind === "current") {
63
+ result.state.deletedWorkflowIds.forEach((id) => this.deletedWorkflowIds.add(id));
64
+ this.mem.importAll(result.state.workflows.filter((record) => !this.deletedWorkflowIds.has(record.handle.workflowId)));
65
+ }
63
66
  this.loaded = true;
64
- this.mem.importAll(readState(this.filePath).workflows);
67
+ }
68
+
69
+ private replaceLegacyState(): void {
70
+ withDurableFileLock(this.filePath, () => {
71
+ const latest = readDurableFileState(this.filePath);
72
+ if (latest.kind !== "legacy") return;
73
+ const ids = this.expectedWorkflowId === undefined
74
+ ? latest.workflowIds
75
+ : [this.expectedWorkflowId];
76
+ if (ids.length === 0) {
77
+ rmSync(this.filePath, { force: true });
78
+ this.suppressedAll = true;
79
+ return;
80
+ }
81
+ ids.forEach((id) => this.deletedWorkflowIds.add(id));
82
+ this.writeState(this.filePath, emptyState(ids));
83
+ });
84
+ }
85
+
86
+ private assertWritable(): void {
87
+ if (this.unknownState) throw new Error(`Cannot overwrite unknown durable workflow state format: ${this.filePath}`);
65
88
  }
66
89
 
67
90
  private persist(): void {
68
- withFileLock(this.filePath, () => {
69
- const merged = mergeRecords(readState(this.filePath).workflows, this.mem.exportAll());
70
- this.mem.reset();
71
- this.mem.importAll(merged);
72
- writeState(this.filePath, { version: FILE_FORMAT_VERSION, workflows: merged });
91
+ this.assertWritable();
92
+ withDurableFileLock(this.filePath, () => {
93
+ const result = readDurableFileState(this.filePath);
94
+ if (result.kind === "unknown" || (result.kind === "current" && !this.matchesExpectedId(result.state))) {
95
+ this.unknownState = true;
96
+ this.suppressedAll = true;
97
+ this.mem.reset();
98
+ throw new Error(`Cannot overwrite unknown durable workflow state format: ${this.filePath}`);
99
+ }
100
+ const stored = result.kind === "current" ? result.state.workflows : [];
101
+ const deleted = new Set(
102
+ result.kind === "current" ? result.state.deletedWorkflowIds
103
+ : result.kind === "legacy" ? result.workflowIds
104
+ : [],
105
+ );
106
+ this.deletedWorkflowIds.forEach((id) => deleted.add(id));
107
+ this.revivedWorkflowIds.forEach((id) => deleted.delete(id));
108
+ const merged = mergeRecords(stored, this.mem.exportAll()).filter((record) => !deleted.has(record.handle.workflowId));
109
+ const state = currentState(merged, deleted);
110
+ this.writeState(this.filePath, state);
111
+ this.replaceMirror(state);
112
+ this.revivedWorkflowIds.clear();
73
113
  });
74
114
  }
115
+ private refreshCompatibilityFromDisk(): void {
116
+ const result = readDurableFileState(this.filePath);
117
+ if (result.kind === "current" && this.matchesExpectedId(result.state)) {
118
+ this.unknownState = false;
119
+ this.suppressedAll = false;
120
+ this.replaceMirror(result.state);
121
+ return;
122
+ }
123
+ if (result.kind === "unknown"
124
+ || (result.kind === "current" && !this.matchesExpectedId(result.state))
125
+ || (result.kind === "legacy" && this.expectedWorkflowId !== undefined
126
+ && (result.workflowIds.length === 0 || result.workflowIds.some((id) => id !== this.expectedWorkflowId)))) {
127
+ this.unknownState = true;
128
+ this.suppressedAll = true;
129
+ this.mem.reset();
130
+ return;
131
+ }
132
+ if (result.kind === "legacy") {
133
+ this.loaded = false;
134
+ this.mem.reset();
135
+ this.deletedWorkflowIds.clear();
136
+ this.ensureLoaded();
137
+ }
138
+ }
139
+
140
+
141
+ private replaceMirror(state: FileDurableState): void {
142
+ this.mem.reset();
143
+ this.deletedWorkflowIds.clear();
144
+ state.deletedWorkflowIds.forEach((id) => this.deletedWorkflowIds.add(id));
145
+ this.mem.importAll(state.workflows.filter((record) => !this.deletedWorkflowIds.has(record.handle.workflowId)));
146
+ }
147
+
148
+ private matchesExpectedId(state: FileDurableState): boolean {
149
+ if (this.expectedWorkflowId === undefined) return true;
150
+ return state.workflows.every((record) => record.handle.workflowId === this.expectedWorkflowId)
151
+ && state.deletedWorkflowIds.every((id) => id === this.expectedWorkflowId);
152
+ }
75
153
 
76
154
  registerWorkflow(handle: Parameters<DurableWorkflowBackend["registerWorkflow"]>[0]): void {
77
155
  this.ensureLoaded();
156
+ this.assertWritable();
157
+ this.suppressedAll = false;
158
+ this.deletedWorkflowIds.delete(handle.workflowId);
159
+ this.revivedWorkflowIds.add(handle.workflowId);
78
160
  this.mem.registerWorkflow(handle);
79
161
  this.persist();
80
162
  }
81
163
 
82
164
  recordCheckpoint(checkpoint: DurableCheckpoint): void {
83
165
  this.ensureLoaded();
166
+ this.assertWritable();
84
167
  this.mem.recordCheckpoint(checkpoint);
85
168
  this.persist();
86
169
  }
@@ -117,6 +200,7 @@ export class FileDurableBackend implements DurableWorkflowBackend {
117
200
 
118
201
  setWorkflowStatus(workflowId: string, status: Parameters<DurableWorkflowBackend["setWorkflowStatus"]>[1], pendingPrompts?: number, resumable?: boolean): void {
119
202
  this.ensureLoaded();
203
+ this.assertWritable();
120
204
  this.mem.setWorkflowStatus(workflowId, status, pendingPrompts, resumable);
121
205
  this.persist();
122
206
  }
@@ -126,66 +210,81 @@ export class FileDurableBackend implements DurableWorkflowBackend {
126
210
  return this.mem.listResumableWorkflows();
127
211
  }
128
212
 
213
+ listCompletedWorkflows() {
214
+ this.ensureLoaded();
215
+ return this.mem.listCompletedWorkflows();
216
+ }
217
+
129
218
  toCacheEntry(workflowId: string) {
130
219
  this.ensureLoaded();
131
220
  return this.mem.toCacheEntry(workflowId);
132
221
  }
222
+ async deleteWorkflow(workflowId: string): Promise<void> {
223
+ this.ensureLoaded();
224
+ this.assertWritable();
225
+ withDurableFileLock(this.filePath, () => {
226
+ const result = readDurableFileState(this.filePath);
227
+ if (result.kind === "unknown" || (result.kind === "current" && !this.matchesExpectedId(result.state))) {
228
+ throw new Error(`Cannot overwrite unknown durable workflow state format: ${this.filePath}`);
229
+ }
230
+ const stored = result.kind === "current" ? result.state.workflows : [];
231
+ const deleted = new Set(
232
+ result.kind === "current" ? result.state.deletedWorkflowIds
233
+ : result.kind === "legacy" ? result.workflowIds
234
+ : [],
235
+ );
236
+ deleted.add(workflowId);
237
+ const merged = mergeRecords(stored, this.mem.exportAll())
238
+ .filter((record) => !deleted.has(record.handle.workflowId));
239
+ const state = currentState(merged, deleted);
240
+ this.writeState(this.filePath, state);
241
+ this.replaceMirror(state);
242
+ });
243
+ }
244
+
245
+ isWorkflowLoadable(workflowId: string): boolean {
246
+ this.ensureLoaded();
247
+ this.refreshCompatibilityFromDisk();
248
+ return !this.suppressedAll && !this.deletedWorkflowIds.has(workflowId);
249
+ }
133
250
 
134
251
  reset(): void {
135
252
  this.mem.reset();
136
- withFileLock(this.filePath, () => writeState(this.filePath, { version: FILE_FORMAT_VERSION, workflows: [] }));
253
+ this.unknownState = false;
254
+ this.suppressedAll = false;
255
+ this.deletedWorkflowIds.clear();
256
+ this.revivedWorkflowIds.clear();
257
+ withDurableFileLock(this.filePath, () => this.writeState(this.filePath, emptyState()));
137
258
  }
138
259
  }
139
260
 
140
- /**
141
- * Directory-backed durable backend that stores each root workflow in its own
142
- * JSON file while preserving the same DurableWorkflowBackend interface.
143
- */
144
261
  export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
145
262
  public readonly persistent = true;
146
263
  private readonly dir: string;
147
264
  private readonly fileBackends = new Map<string, FileDurableBackend>();
265
+ private readonly suppressedIds = new Set<string>();
148
266
 
149
- constructor(dir: string) {
150
- this.dir = dir;
151
- }
267
+ constructor(dir: string) { this.dir = dir; }
152
268
 
153
269
  registerWorkflow(handle: Parameters<DurableWorkflowBackend["registerWorkflow"]>[0]): void {
154
270
  this.backendFor(handle.workflowId).registerWorkflow(handle);
271
+ this.suppressedIds.delete(handle.workflowId);
155
272
  }
156
273
 
157
- recordCheckpoint(checkpoint: DurableCheckpoint): void {
158
- this.backendFor(checkpoint.workflowId).recordCheckpoint(checkpoint);
159
- }
160
-
161
- getToolOutput(workflowId: string, argsHash: string) {
162
- return this.backendFor(workflowId).getToolOutput(workflowId, argsHash);
163
- }
164
-
165
- getUiResponse(workflowId: string, promptHash: string) {
166
- return this.backendFor(workflowId).getUiResponse(workflowId, promptHash);
167
- }
168
-
169
- getStageOutput(workflowId: string, replayKey: string) {
170
- return this.backendFor(workflowId).getStageOutput(workflowId, replayKey);
171
- }
172
-
173
- getStageSession(workflowId: string, replayKey: string) {
174
- return this.backendFor(workflowId).getStageSession(workflowId, replayKey);
175
- }
176
-
177
- listCheckpoints(workflowId: string): readonly DurableCheckpoint[] {
178
- return this.backendFor(workflowId).listCheckpoints(workflowId);
179
- }
180
-
181
- getWorkflow(workflowId: string) {
182
- return this.backendFor(workflowId).getWorkflow(workflowId);
183
- }
274
+ recordCheckpoint(checkpoint: DurableCheckpoint): void { this.backendFor(checkpoint.workflowId).recordCheckpoint(checkpoint); }
275
+ getToolOutput(workflowId: string, argsHash: string) { return this.backendFor(workflowId).getToolOutput(workflowId, argsHash); }
276
+ getUiResponse(workflowId: string, promptHash: string) { return this.backendFor(workflowId).getUiResponse(workflowId, promptHash); }
277
+ getStageOutput(workflowId: string, replayKey: string) { return this.backendFor(workflowId).getStageOutput(workflowId, replayKey); }
278
+ getStageSession(workflowId: string, replayKey: string) { return this.backendFor(workflowId).getStageSession(workflowId, replayKey); }
279
+ listCheckpoints(workflowId: string): readonly DurableCheckpoint[] { return this.backendFor(workflowId).listCheckpoints(workflowId); }
280
+ getWorkflow(workflowId: string) { return this.backendFor(workflowId).getWorkflow(workflowId); }
184
281
 
185
282
  setWorkflowStatus(workflowId: string, status: DurableWorkflowStatus, pendingPrompts?: number, resumable?: boolean): void {
186
283
  const backend = this.backendFor(workflowId);
187
284
  backend.setWorkflowStatus(workflowId, status, pendingPrompts, resumable);
188
- if (isPrunableTerminalStatus(status, resumable)) this.removeWorkflowFile(workflowId);
285
+ if (isPrunableTerminalStatus(status, resumable)
286
+ && backend.isWorkflowLoadable(workflowId)
287
+ && backend.getWorkflow(workflowId) !== undefined) this.removeWorkflowFile(workflowId);
189
288
  }
190
289
 
191
290
  listResumableWorkflows() {
@@ -194,24 +293,49 @@ export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
194
293
  return mem.listResumableWorkflows();
195
294
  }
196
295
 
197
- toCacheEntry(workflowId: string) {
198
- return this.backendFor(workflowId).toCacheEntry(workflowId);
296
+ listCompletedWorkflows() {
297
+ const mem = new InMemoryDurableBackend();
298
+ mem.importAll(mergeRecords([], this.readAllRecords()));
299
+ return mem.listCompletedWorkflows();
300
+ }
301
+
302
+ toCacheEntry(workflowId: string) { return this.backendFor(workflowId).toCacheEntry(workflowId); }
303
+
304
+ async deleteWorkflow(workflowId: string): Promise<void> {
305
+ await this.backendFor(workflowId).deleteWorkflow(workflowId);
306
+ this.suppressedIds.add(workflowId);
307
+ }
308
+
309
+ isWorkflowLoadable(workflowId: string): boolean {
310
+ const filePath = durableStateFileFor(this.dir, workflowId);
311
+ const ownState = readDurableFileState(filePath);
312
+ if (ownState.kind === "current" && stateMatchesWorkflowId(ownState.state, workflowId)) {
313
+ const loadable = this.backendForFile(filePath, workflowId).isWorkflowLoadable(workflowId);
314
+ if (loadable) this.suppressedIds.delete(workflowId);
315
+ else this.suppressedIds.add(workflowId);
316
+ return loadable;
317
+ }
318
+ if (this.suppressedIds.has(workflowId)) return false;
319
+ const loadable = this.backendForFile(filePath, workflowId).isWorkflowLoadable(workflowId);
320
+ if (!loadable) this.suppressedIds.add(workflowId);
321
+ return loadable;
199
322
  }
200
323
 
201
324
  reset(): void {
202
325
  this.fileBackends.clear();
326
+ this.suppressedIds.clear();
203
327
  for (const filePath of this.stateFiles()) this.removeStateFile(filePath);
204
328
  for (const lockPath of this.lockDirs()) rmSync(lockPath, { recursive: true, force: true });
205
329
  }
206
330
 
207
331
  private backendFor(workflowId: string): FileDurableBackend {
208
- return this.backendForFile(durableStateFileFor(this.dir, workflowId));
332
+ return this.backendForFile(durableStateFileFor(this.dir, workflowId), workflowId);
209
333
  }
210
334
 
211
- private backendForFile(filePath: string): FileDurableBackend {
335
+ private backendForFile(filePath: string, expectedWorkflowId: string): FileDurableBackend {
212
336
  const existing = this.fileBackends.get(filePath);
213
337
  if (existing !== undefined) return existing;
214
- const backend = new FileDurableBackend(filePath);
338
+ const backend = new FileDurableBackend(filePath, expectedWorkflowId);
215
339
  this.fileBackends.set(filePath, backend);
216
340
  return backend;
217
341
  }
@@ -221,9 +345,7 @@ export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
221
345
  return readdirSync(this.dir, { withFileTypes: true })
222
346
  .filter((entry) => entry.isFile() && entry.name.startsWith("workflow-") && entry.name.endsWith(".json"))
223
347
  .map((entry) => `${this.dir}/${entry.name}`);
224
- } catch {
225
- return [];
226
- }
348
+ } catch { return []; }
227
349
  }
228
350
 
229
351
  private lockDirs(): readonly string[] {
@@ -231,18 +353,37 @@ export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
231
353
  return readdirSync(this.dir, { withFileTypes: true })
232
354
  .filter((entry) => entry.isDirectory() && entry.name.startsWith("workflow-") && entry.name.endsWith(".json.lock"))
233
355
  .map((entry) => `${this.dir}/${entry.name}`);
234
- } catch {
235
- return [];
236
- }
356
+ } catch { return []; }
237
357
  }
238
358
 
239
359
  private readAllRecords(): readonly FileDurableRecord[] {
240
- return this.stateFiles().flatMap((filePath) => readState(filePath).workflows);
360
+ return this.stateFiles().flatMap((filePath) => {
361
+ const workflowId = workflowIdFromStateFile(this.dir, filePath);
362
+ if (workflowId === undefined) return [];
363
+ const result = readDurableFileState(filePath);
364
+ const embeddedIds = result.kind === "current"
365
+ ? [...result.state.workflows.map((record) => record.handle.workflowId), ...result.state.deletedWorkflowIds]
366
+ : result.kind === "legacy" ? result.workflowIds : [];
367
+ const mismatched = embeddedIds.filter((id) => id !== workflowId);
368
+ if (mismatched.length > 0) {
369
+ this.suppressedIds.add(workflowId);
370
+ mismatched.forEach((id) => this.suppressedIds.add(id));
371
+ this.backendForFile(filePath, workflowId).isWorkflowLoadable(workflowId);
372
+ return [];
373
+ }
374
+ const backend = this.backendForFile(filePath, workflowId);
375
+ if (!backend.isWorkflowLoadable(workflowId)) {
376
+ this.suppressedIds.add(workflowId);
377
+ return [];
378
+ }
379
+ this.suppressedIds.delete(workflowId);
380
+ const current = readDurableFileState(filePath);
381
+ if (current.kind !== "current" || !stateMatchesWorkflowId(current.state, workflowId)) return [];
382
+ return current.state.workflows.filter((record) => !current.state.deletedWorkflowIds.includes(record.handle.workflowId));
383
+ });
241
384
  }
242
385
 
243
- private removeWorkflowFile(workflowId: string): void {
244
- this.removeStateFile(durableStateFileFor(this.dir, workflowId));
245
- }
386
+ private removeWorkflowFile(workflowId: string): void { this.removeStateFile(durableStateFileFor(this.dir, workflowId)); }
246
387
 
247
388
  private removeStateFile(filePath: string): void {
248
389
  this.fileBackends.delete(filePath);
@@ -251,163 +392,28 @@ export class WorkflowFileDurableBackend implements DurableWorkflowBackend {
251
392
  }
252
393
  }
253
394
 
254
- function readState(filePath: string): FileDurableState {
255
- if (!existsSync(filePath)) return { version: FILE_FORMAT_VERSION, workflows: [] };
256
- try {
257
- const parsed = JSON.parse(readFileSync(filePath, "utf-8")) as FileDurableState;
258
- return parsed && Array.isArray(parsed.workflows) ? parsed : { version: FILE_FORMAT_VERSION, workflows: [] };
259
- } catch {
260
- return { version: FILE_FORMAT_VERSION, workflows: [] };
261
- }
395
+ function workflowIdFromStateFile(dir: string, filePath: string): string | undefined {
396
+ const prefix = `${dir}/workflow-`;
397
+ if (!filePath.startsWith(prefix) || !filePath.endsWith(".json")) return undefined;
398
+ try { return decodeURIComponent(filePath.slice(prefix.length, -".json".length)); }
399
+ catch { return undefined; }
262
400
  }
263
401
 
264
- function writeState(filePath: string, state: FileDurableState): void {
265
- const dir = dirname(filePath);
266
- ensureSecureDir(dir);
267
- const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
268
- writeFileSync(tmp, JSON.stringify(state), { encoding: "utf-8", mode: 0o600 });
269
- chmodBestEffort(tmp, 0o600);
270
- renameSync(tmp, filePath);
271
- chmodBestEffort(filePath, 0o600);
272
- }
273
402
 
274
- function withFileLock<T>(filePath: string, fn: () => T): T {
275
- const dir = dirname(filePath);
276
- ensureSecureDir(dir);
277
- const lockDir = `${filePath}.lock`;
278
- const deadline = Date.now() + 5000;
279
- while (true) {
280
- try {
281
- mkdirSync(lockDir, { mode: 0o700 });
282
- try {
283
- chmodBestEffort(lockDir, 0o700);
284
- writeLockOwner(lockDir);
285
- } catch (ownerErr) {
286
- rmSync(lockDir, { recursive: true, force: true });
287
- throw ownerErr;
288
- }
289
- break;
290
- } catch (err) {
291
- if (errorCode(err) !== "EEXIST") throw err;
292
- if (tryReclaimStaleLock(lockDir, STALE_LOCK_MS)) continue;
293
- if (Date.now() > deadline) throw new Error(`Timed out acquiring durable workflow state lock: ${lockDir}`);
294
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
295
- }
296
- }
297
- try {
298
- return fn();
299
- } finally {
300
- rmSync(lockDir, { recursive: true, force: true });
301
- }
302
- }
303
-
304
- function tryReclaimStaleLock(lockDir: string, staleMs: number): boolean {
305
- if (!isStaleLock(lockDir, staleMs)) return false;
306
- const owner = readLockOwner(lockDir);
307
- if (owner === undefined || !isLockOwnerAbandoned(owner)) return false;
308
- const quarantine = `${lockDir}.stale.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
309
- try {
310
- renameSync(lockDir, quarantine);
311
- } catch {
312
- return false;
313
- }
314
- const quarantinedOwner = readLockOwner(quarantine);
315
- if (!sameLockOwner(owner, quarantinedOwner)) {
316
- restoreQuarantinedLock(lockDir, quarantine);
317
- return false;
318
- }
319
- try {
320
- rmSync(quarantine, { recursive: true, force: true });
321
- } catch {
322
- // The stale lock has already been moved away from the active lock path.
323
- }
324
- return true;
325
- }
326
-
327
- function isStaleLock(lockDir: string, staleMs: number): boolean {
328
- try {
329
- const stat = statSync(lockDir);
330
- return Date.now() - stat.mtimeMs > staleMs;
331
- } catch {
332
- return false;
333
- }
334
- }
335
-
336
- function writeLockOwner(lockDir: string): void {
337
- const owner: LockOwner = {
338
- pid: process.pid,
339
- host: hostname(),
340
- token: `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
341
- acquiredAt: Date.now(),
342
- };
343
- const ownerPath = `${lockDir}/${LOCK_OWNER_FILE}`;
344
- writeFileSync(ownerPath, JSON.stringify(owner), { encoding: "utf-8", mode: 0o600 });
345
- chmodBestEffort(ownerPath, 0o600);
403
+ function emptyState(deletedWorkflowIds: readonly string[] = []): FileDurableState {
404
+ return { version: FILE_FORMAT_VERSION, workflows: [], deletedWorkflowIds };
346
405
  }
347
406
 
348
- function readLockOwner(lockDir: string): LockOwner | undefined {
349
- try {
350
- const parsed = JSON.parse(readFileSync(`${lockDir}/${LOCK_OWNER_FILE}`, "utf-8"));
351
- if (!isLockOwner(parsed)) return undefined;
352
- return parsed;
353
- } catch {
354
- return undefined;
355
- }
407
+ function currentState(records: readonly FileDurableRecord[], deleted: ReadonlySet<string>): FileDurableState {
408
+ return { version: FILE_FORMAT_VERSION, workflows: records, deletedWorkflowIds: [...deleted] };
356
409
  }
357
410
 
358
- function isLockOwner(value: unknown): value is LockOwner {
359
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
360
- const record = value as Partial<LockOwner>;
361
- return typeof record.pid === "number"
362
- && typeof record.host === "string"
363
- && typeof record.token === "string"
364
- && typeof record.acquiredAt === "number";
365
- }
366
-
367
- function isLockOwnerAbandoned(owner: LockOwner): boolean {
368
- if (owner.host !== hostname()) return false;
369
- if (!Number.isInteger(owner.pid) || owner.pid <= 0) return false;
370
- try {
371
- process.kill(owner.pid, 0);
372
- return false;
373
- } catch (err) {
374
- const code = errorCode(err);
375
- return code === "ESRCH";
376
- }
411
+ function stateMatchesWorkflowId(state: FileDurableState, workflowId: string): boolean {
412
+ return state.workflows.every((record) => record.handle.workflowId === workflowId)
413
+ && state.deletedWorkflowIds.every((id) => id === workflowId);
377
414
  }
378
-
379
- function sameLockOwner(a: LockOwner, b: LockOwner | undefined): boolean {
380
- return b !== undefined && a.pid === b.pid && a.host === b.host && a.token === b.token;
381
- }
382
-
383
- function restoreQuarantinedLock(lockDir: string, quarantine: string): void {
384
- try {
385
- if (!existsSync(lockDir)) renameSync(quarantine, lockDir);
386
- else rmSync(quarantine, { recursive: true, force: true });
387
- } catch {
388
- // Best-effort: never delete the active lock path after a failed compare.
389
- }
390
- }
391
-
392
- function errorCode(err: unknown): string | undefined {
393
- return typeof err === "object" && err !== null && "code" in err ? String(err.code) : undefined;
394
- }
395
-
396
- function ensureSecureDir(dir: string): void {
397
- mkdirSync(dir, { recursive: true, mode: 0o700 });
398
- chmodBestEffort(dir, 0o700);
399
- }
400
-
401
- function chmodBestEffort(path: string, mode: number): void {
402
- try {
403
- chmodSync(path, mode);
404
- } catch {
405
- // chmod is unavailable or unsupported on some filesystems/platforms.
406
- }
407
- }
408
-
409
415
  function isPrunableTerminalStatus(status: DurableWorkflowStatus, resumable?: boolean): boolean {
410
- if (status === "completed" || status === "cancelled") return true;
416
+ if (status === "cancelled") return true;
411
417
  return (status === "failed" || status === "blocked") && resumable === false;
412
418
  }
413
419