@bastani/atomic 0.9.11-alpha.9 → 0.9.11

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 (285) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +1 -1
  3. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  4. package/dist/builtin/intercom/package.json +1 -1
  5. package/dist/builtin/intercom/subagent-relay.ts +8 -5
  6. package/dist/builtin/mcp/CHANGELOG.md +10 -0
  7. package/dist/builtin/mcp/package.json +1 -1
  8. package/dist/builtin/subagents/CHANGELOG.md +20 -0
  9. package/dist/builtin/subagents/agents/code-simplifier.md +2 -2
  10. package/dist/builtin/subagents/agents/codebase-analyzer.md +2 -2
  11. package/dist/builtin/subagents/agents/codebase-locator.md +2 -2
  12. package/dist/builtin/subagents/agents/codebase-online-researcher.md +2 -2
  13. package/dist/builtin/subagents/agents/codebase-pattern-finder.md +2 -2
  14. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +2 -2
  15. package/dist/builtin/subagents/agents/codebase-research-locator.md +2 -2
  16. package/dist/builtin/subagents/agents/worker.md +2 -2
  17. package/dist/builtin/subagents/package.json +1 -1
  18. package/dist/builtin/subagents/src/extension/notification-content.ts +14 -9
  19. package/dist/builtin/subagents/src/runs/background/notify.ts +20 -4
  20. package/dist/builtin/web-access/CHANGELOG.md +20 -0
  21. package/dist/builtin/web-access/content-tools.ts +1 -0
  22. package/dist/builtin/web-access/flat-string.ts +37 -0
  23. package/dist/builtin/web-access/github-api.ts +2 -1
  24. package/dist/builtin/web-access/github-extract.ts +3 -2
  25. package/dist/builtin/web-access/package.json +2 -2
  26. package/dist/builtin/workflows/CHANGELOG.md +57 -0
  27. package/dist/builtin/workflows/builtin/goal-ledger.ts +6 -4
  28. package/dist/builtin/workflows/builtin/goal-runner.ts +2 -1
  29. package/dist/builtin/workflows/builtin/goal.ts +1 -1
  30. package/dist/builtin/workflows/builtin/ralph-core.ts +8 -9
  31. package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +2 -4
  32. package/dist/builtin/workflows/builtin/ralph-models.ts +4 -4
  33. package/dist/builtin/workflows/builtin/ralph-runner.ts +5 -8
  34. package/dist/builtin/workflows/builtin/ralph.ts +2 -1
  35. package/dist/builtin/workflows/package.json +1 -1
  36. package/dist/builtin/workflows/src/durable/completed-catalog.ts +14 -8
  37. package/dist/builtin/workflows/src/durable/completed-inspection.ts +10 -11
  38. package/dist/builtin/workflows/src/durable/resume-catalog.ts +1 -1
  39. package/dist/builtin/workflows/src/durable/resume-eligibility.ts +42 -0
  40. package/dist/builtin/workflows/src/durable/resume-runtime.ts +36 -37
  41. package/dist/builtin/workflows/src/durable/tool-primitive.ts +10 -2
  42. package/dist/builtin/workflows/src/engine/run.ts +1 -0
  43. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +12 -8
  44. package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +5 -2
  45. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1966 -880
  46. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +6 -3
  47. package/dist/builtin/workflows/src/extension/postmortem-deps.ts +3 -2
  48. package/dist/builtin/workflows/src/extension/render-result.ts +4 -11
  49. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +17 -20
  50. package/dist/builtin/workflows/src/extension/runtime.ts +3 -3
  51. package/dist/builtin/workflows/src/extension/workflow-command-completions.ts +1 -1
  52. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +3 -7
  53. package/dist/builtin/workflows/src/extension/workflow-durable-resume-command.ts +21 -24
  54. package/dist/builtin/workflows/src/extension/workflow-resume-picker-rows.ts +38 -26
  55. package/dist/builtin/workflows/src/extension/workflow-resume-shadow.ts +2 -1
  56. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +56 -64
  57. package/dist/builtin/workflows/src/extension/workflow-schema.ts +2 -2
  58. package/dist/builtin/workflows/src/extension/workflow-status-summary.ts +0 -5
  59. package/dist/builtin/workflows/src/extension/workflow-targets.ts +17 -20
  60. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +1 -1
  61. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +24 -51
  62. package/dist/builtin/workflows/src/extension/workflow-tool-inspection.ts +7 -29
  63. package/dist/builtin/workflows/src/extension/workflow-tool-send.ts +5 -13
  64. package/dist/builtin/workflows/src/extension/workflow-tool.ts +4 -13
  65. package/dist/builtin/workflows/src/runs/background/quit.ts +4 -3
  66. package/dist/builtin/workflows/src/runs/background/run-inspect.ts +8 -8
  67. package/dist/builtin/workflows/src/runs/background/status.ts +2 -1
  68. package/dist/builtin/workflows/src/runs/background/workflow-lifecycle-aggregate.ts +2 -1
  69. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +5 -5
  70. package/dist/builtin/workflows/src/runs/foreground/executor-lifecycle.ts +4 -0
  71. package/dist/builtin/workflows/src/runs/foreground/executor-scheduler.ts +2 -0
  72. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +23 -8
  73. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +6 -2
  74. package/dist/builtin/workflows/src/runs/foreground/executor-task-prompts.ts +5 -0
  75. package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +56 -10
  76. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +6 -0
  77. package/dist/builtin/workflows/src/runs/foreground/stage-runner-output.ts +137 -6
  78. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +2 -0
  79. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +2 -0
  80. package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +159 -10
  81. package/dist/builtin/workflows/src/shared/flat-string.ts +33 -0
  82. package/dist/builtin/workflows/src/shared/graph-store-snapshot.ts +144 -0
  83. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +2 -3
  84. package/dist/builtin/workflows/src/shared/run-id.ts +36 -0
  85. package/dist/builtin/workflows/src/shared/store-internal.ts +32 -3
  86. package/dist/builtin/workflows/src/shared/store-observation.ts +10 -0
  87. package/dist/builtin/workflows/src/shared/store-public-types.ts +8 -0
  88. package/dist/builtin/workflows/src/shared/store-run-methods.ts +14 -0
  89. package/dist/builtin/workflows/src/shared/store-types.ts +4 -0
  90. package/dist/builtin/workflows/src/shared/types.ts +2 -0
  91. package/dist/builtin/workflows/src/shared/workflow-artifacts.ts +261 -0
  92. package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +39 -23
  93. package/dist/builtin/workflows/src/tui/graph-canvas.ts +26 -9
  94. package/dist/builtin/workflows/src/tui/graph-view-constants.ts +3 -0
  95. package/dist/builtin/workflows/src/tui/graph-view-graph-render.ts +95 -98
  96. package/dist/builtin/workflows/src/tui/graph-view-render.ts +7 -20
  97. package/dist/builtin/workflows/src/tui/graph-view-state.ts +160 -13
  98. package/dist/builtin/workflows/src/tui/layout.ts +8 -2
  99. package/dist/builtin/workflows/src/tui/node-card.ts +57 -45
  100. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +4 -2
  101. package/dist/builtin/workflows/src/tui/prompt-card-render.ts +291 -21
  102. package/dist/builtin/workflows/src/tui/prompt-card.ts +1 -1
  103. package/dist/builtin/workflows/src/tui/renderers.ts +0 -1
  104. package/dist/builtin/workflows/src/tui/run-detail.ts +52 -35
  105. package/dist/builtin/workflows/src/tui/run-identity-rows.ts +97 -0
  106. package/dist/builtin/workflows/src/tui/session-overlays.ts +24 -3
  107. package/dist/builtin/workflows/src/tui/session-picker.ts +94 -41
  108. package/dist/builtin/workflows/src/tui/stage-chat-view-archive-history.ts +190 -26
  109. package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +52 -18
  110. package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +9 -5
  111. package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +44 -4
  112. package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +3 -1
  113. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +3 -2
  114. package/dist/builtin/workflows/src/tui/status-list.ts +67 -45
  115. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +7 -6
  116. package/dist/builtin/workflows/src/tui/widget.ts +49 -40
  117. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +15 -10
  118. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +21 -8
  119. package/dist/core/agent-session-accessors.d.ts.map +1 -1
  120. package/dist/core/agent-session-accessors.js +5 -0
  121. package/dist/core/agent-session-accessors.js.map +1 -1
  122. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  123. package/dist/core/agent-session-auto-compaction.js +5 -0
  124. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  125. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  126. package/dist/core/agent-session-compaction.js +3 -0
  127. package/dist/core/agent-session-compaction.js.map +1 -1
  128. package/dist/core/agent-session-events.d.ts.map +1 -1
  129. package/dist/core/agent-session-events.js +2 -1
  130. package/dist/core/agent-session-events.js.map +1 -1
  131. package/dist/core/agent-session-message-queue.d.ts +3 -7
  132. package/dist/core/agent-session-message-queue.d.ts.map +1 -1
  133. package/dist/core/agent-session-message-queue.js +27 -13
  134. package/dist/core/agent-session-message-queue.js.map +1 -1
  135. package/dist/core/agent-session-methods.d.ts +5 -3
  136. package/dist/core/agent-session-methods.d.ts.map +1 -1
  137. package/dist/core/agent-session-methods.js.map +1 -1
  138. package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -1
  139. package/dist/core/agent-session-persistent-custom-messages.js +6 -3
  140. package/dist/core/agent-session-persistent-custom-messages.js.map +1 -1
  141. package/dist/core/agent-session-post-tool-compaction.d.ts.map +1 -1
  142. package/dist/core/agent-session-post-tool-compaction.js +11 -2
  143. package/dist/core/agent-session-post-tool-compaction.js.map +1 -1
  144. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  145. package/dist/core/agent-session-runtime.js +5 -3
  146. package/dist/core/agent-session-runtime.js.map +1 -1
  147. package/dist/core/agent-session-tree.d.ts.map +1 -1
  148. package/dist/core/agent-session-tree.js +4 -0
  149. package/dist/core/agent-session-tree.js.map +1 -1
  150. package/dist/core/agent-session-types.d.ts +4 -0
  151. package/dist/core/agent-session-types.d.ts.map +1 -1
  152. package/dist/core/agent-session-types.js.map +1 -1
  153. package/dist/core/agent-session.d.ts +2 -1
  154. package/dist/core/agent-session.d.ts.map +1 -1
  155. package/dist/core/agent-session.js +1 -0
  156. package/dist/core/agent-session.js.map +1 -1
  157. package/dist/core/auth-storage-backends.d.ts.map +1 -1
  158. package/dist/core/auth-storage-backends.js +12 -1
  159. package/dist/core/auth-storage-backends.js.map +1 -1
  160. package/dist/core/bounded-model-refresh.d.ts +14 -0
  161. package/dist/core/bounded-model-refresh.d.ts.map +1 -0
  162. package/dist/core/bounded-model-refresh.js +38 -0
  163. package/dist/core/bounded-model-refresh.js.map +1 -0
  164. package/dist/core/context-tool-pairing.d.ts +4 -4
  165. package/dist/core/context-tool-pairing.js +4 -4
  166. package/dist/core/context-tool-pairing.js.map +1 -1
  167. package/dist/core/http-dispatcher.d.ts.map +1 -1
  168. package/dist/core/http-dispatcher.js +26 -1
  169. package/dist/core/http-dispatcher.js.map +1 -1
  170. package/dist/core/messages.d.ts +18 -6
  171. package/dist/core/messages.d.ts.map +1 -1
  172. package/dist/core/messages.js +46 -18
  173. package/dist/core/messages.js.map +1 -1
  174. package/dist/core/model-refresh-timeout.d.ts +9 -0
  175. package/dist/core/model-refresh-timeout.d.ts.map +1 -0
  176. package/dist/core/model-refresh-timeout.js +9 -0
  177. package/dist/core/model-refresh-timeout.js.map +1 -0
  178. package/dist/core/model-runtime-snapshot.d.ts +3 -0
  179. package/dist/core/model-runtime-snapshot.d.ts.map +1 -1
  180. package/dist/core/model-runtime-snapshot.js +59 -0
  181. package/dist/core/model-runtime-snapshot.js.map +1 -1
  182. package/dist/core/model-runtime.d.ts +7 -1
  183. package/dist/core/model-runtime.d.ts.map +1 -1
  184. package/dist/core/model-runtime.js +72 -17
  185. package/dist/core/model-runtime.js.map +1 -1
  186. package/dist/core/sdk.d.ts.map +1 -1
  187. package/dist/core/sdk.js +4 -3
  188. package/dist/core/sdk.js.map +1 -1
  189. package/dist/core/session-manager-core.d.ts +1 -1
  190. package/dist/core/session-manager-core.d.ts.map +1 -1
  191. package/dist/core/session-manager-core.js +2 -2
  192. package/dist/core/session-manager-core.js.map +1 -1
  193. package/dist/core/session-manager-entries.d.ts +1 -1
  194. package/dist/core/session-manager-entries.d.ts.map +1 -1
  195. package/dist/core/session-manager-entries.js +2 -1
  196. package/dist/core/session-manager-entries.js.map +1 -1
  197. package/dist/core/session-manager-history.d.ts.map +1 -1
  198. package/dist/core/session-manager-history.js +2 -2
  199. package/dist/core/session-manager-history.js.map +1 -1
  200. package/dist/core/session-manager-types.d.ts +3 -0
  201. package/dist/core/session-manager-types.d.ts.map +1 -1
  202. package/dist/core/session-manager-types.js.map +1 -1
  203. package/dist/index.d.ts +1 -1
  204. package/dist/index.d.ts.map +1 -1
  205. package/dist/index.js.map +1 -1
  206. package/dist/main-app-mode.d.ts +1 -0
  207. package/dist/main-app-mode.d.ts.map +1 -1
  208. package/dist/main-app-mode.js +3 -0
  209. package/dist/main-app-mode.js.map +1 -1
  210. package/dist/main.d.ts.map +1 -1
  211. package/dist/main.js +8 -3
  212. package/dist/main.js.map +1 -1
  213. package/dist/modes/interactive/components/chat-session-host-actions.d.ts +7 -2
  214. package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/chat-session-host-actions.js +13 -4
  216. package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
  217. package/dist/modes/interactive/components/chat-session-host-editor.d.ts +3 -1
  218. package/dist/modes/interactive/components/chat-session-host-editor.d.ts.map +1 -1
  219. package/dist/modes/interactive/components/chat-session-host-editor.js +2 -2
  220. package/dist/modes/interactive/components/chat-session-host-editor.js.map +1 -1
  221. package/dist/modes/interactive/components/chat-session-host-events.d.ts +3 -0
  222. package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
  223. package/dist/modes/interactive/components/chat-session-host-events.js +5 -4
  224. package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
  225. package/dist/modes/interactive/components/chat-session-host.d.ts +16 -2
  226. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  227. package/dist/modes/interactive/components/chat-session-host.js +35 -5
  228. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  229. package/dist/modes/interactive/components/index.d.ts +1 -0
  230. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/index.js.map +1 -1
  232. package/dist/modes/interactive/components/model-selector.d.ts +0 -1
  233. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/model-selector.js +25 -35
  235. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  236. package/dist/modes/interactive/interactive-editor-actions.js +2 -1
  237. package/dist/modes/interactive/interactive-editor-actions.js.map +1 -1
  238. package/dist/modes/interactive/interactive-input-handling.js +1 -1
  239. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  240. package/dist/modes/interactive/interactive-mode-surface.d.ts +4 -1
  241. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  242. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  243. package/dist/modes/interactive/interactive-model-routing.js +4 -1
  244. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  245. package/dist/modes/interactive/interactive-pause.d.ts +3 -1
  246. package/dist/modes/interactive/interactive-pause.d.ts.map +1 -1
  247. package/dist/modes/interactive/interactive-pause.js +5 -1
  248. package/dist/modes/interactive/interactive-pause.js.map +1 -1
  249. package/dist/modes/interactive/interactive-queueing.js +3 -3
  250. package/dist/modes/interactive/interactive-queueing.js.map +1 -1
  251. package/dist/modes/interactive-engine/engine-health.d.ts +6 -0
  252. package/dist/modes/interactive-engine/engine-health.d.ts.map +1 -1
  253. package/dist/modes/interactive-engine/engine-health.js +15 -1
  254. package/dist/modes/interactive-engine/engine-health.js.map +1 -1
  255. package/dist/modes/interactive-engine/isolated-auth.js +1 -1
  256. package/dist/modes/interactive-engine/isolated-auth.js.map +1 -1
  257. package/dist/modes/interactive-engine/isolated-runtime.d.ts +1 -0
  258. package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
  259. package/dist/modes/interactive-engine/isolated-runtime.js +7 -1
  260. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  261. package/dist/modes/interactive-engine/remote-model-catalog.d.ts.map +1 -1
  262. package/dist/modes/interactive-engine/remote-model-catalog.js +5 -1
  263. package/dist/modes/interactive-engine/remote-model-catalog.js.map +1 -1
  264. package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
  265. package/dist/modes/rpc/rpc-command-handler.js +23 -7
  266. package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
  267. package/dist/modes/rpc/rpc-input-scheduler.js +1 -1
  268. package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
  269. package/dist/modes/rpc/rpc-types.d.ts +2 -1
  270. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  271. package/dist/modes/rpc/rpc-types.js.map +1 -1
  272. package/docs/custom-provider.md +3 -3
  273. package/docs/extensions.md +3 -1
  274. package/docs/index.md +10 -0
  275. package/docs/keybindings.md +1 -1
  276. package/docs/models.md +2 -0
  277. package/docs/providers.md +7 -3
  278. package/docs/quickstart.md +7 -1
  279. package/docs/session-format.md +2 -0
  280. package/docs/termux.md +3 -1
  281. package/docs/usage.md +3 -2
  282. package/docs/workflows.md +82 -46
  283. package/npm-shrinkwrap.json +59 -27
  284. package/package.json +3 -3
  285. package/dist/builtin/workflows/src/tui/toast.ts +0 -107
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Truncation that actually releases the untruncated source.
3
+ *
4
+ * This local web-access copy mirrors the original helper in
5
+ * `packages/workflows/src/shared/flat-string.ts`; web-access is a standalone package and
6
+ * cannot import that workflows module.
7
+ *
8
+ * V8 does not copy characters for `String.prototype.slice`. For a flat parent of
9
+ * 13 characters or more it allocates a **SlicedString**, which is a length, an
10
+ * offset, and a pointer to the parent. Template interpolation then wraps that in
11
+ * a **ConsString**, which keeps the same pointer. So the obvious way to bound a
12
+ * value for storage:
13
+ *
14
+ * ```ts
15
+ * `${JSON.stringify(value).slice(0, 237)}...`
16
+ * ```
17
+ *
18
+ * produces a 240-character string that reports `length === 240` while holding
19
+ * the entire serialized payload alive. The bound is on what you can read, not on
20
+ * what you retain. An 8 MiB tool result summarized to 240 characters still costs
21
+ * 8 MiB for as long as anything keeps the summary.
22
+ *
23
+ * That is invisible in every ordinary test: the value is correct, its length is
24
+ * correct, and equality holds. It only shows up as heap that never comes back.
25
+ *
26
+ * `split("").join("")` copies the code units into a fresh flat string with no
27
+ * parent pointer. It is code-unit exact, including lone surrogates, because
28
+ * `split("")` splits on UTF-16 code units rather than code points.
29
+ *
30
+ * Use this at any truncation whose result outlives the value it came from —
31
+ * memoized projections, durable checkpoints, catalogs, persisted summaries.
32
+ * A truncation that is consumed and dropped within the same turn does not need
33
+ * it, and paying for a copy there is waste.
34
+ */
35
+ export function flattenTruncatedString(value: string): string {
36
+ return value.split("").join("");
37
+ }
@@ -1,5 +1,6 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import type { ExtractedContent } from "./extract.js";
3
+ import { flattenTruncatedString } from "./flat-string.js";
3
4
  import type { GitHubUrlInfo } from "./github-extract.js";
4
5
 
5
6
  const MAX_TREE_ENTRIES = 200;
@@ -97,7 +98,7 @@ async function fetchReadmeViaApi(owner: string, repo: string, ref: string): Prom
97
98
  }
98
99
  try {
99
100
  const decoded = Buffer.from(stdout.trim(), "base64").toString("utf-8");
100
- resolve(decoded.length > 8192 ? decoded.slice(0, 8192) + "\n\n[README truncated at 8K chars]" : decoded);
101
+ resolve(decoded.length > 8192 ? flattenTruncatedString(decoded.slice(0, 8192) + "\n\n[README truncated at 8K chars]") : decoded);
101
102
  } catch {
102
103
  resolve(null);
103
104
  }
@@ -3,6 +3,7 @@ import { execFile } from "node:child_process";
3
3
  import { extname, join, resolve as resolvePath, sep as pathSep } from "node:path";
4
4
  import { activityMonitor } from "./activity.js";
5
5
  import type { ExtractedContent } from "./extract.js";
6
+ import { flattenTruncatedString } from "./flat-string.js";
6
7
  import { checkGhAvailable, checkRepoSize, fetchViaApi, showGhHint } from "./github-api.js";
7
8
  import { BINARY_EXTENSIONS, MAX_INLINE_FILE_CHARS, MAX_TREE_ENTRIES, NOISE_DIRS, loadGitHubConfig, parseGitHubUrl, resetGitHubConfig, type GitHubCloneConfig, type GitHubUrlInfo } from "./github-config.js";
8
9
  export { parseGitHubUrl, type GitHubUrlInfo } from "./github-config.js";
@@ -225,14 +226,14 @@ function buildDirListing(rootPath: string, subPath: string): string {
225
226
  return lines.join("\n");
226
227
  }
227
228
 
228
- function readReadme(localPath: string): string | null {
229
+ export function readReadme(localPath: string): string | null {
229
230
  const candidates = ["README.md", "readme.md", "README", "README.txt", "README.rst"];
230
231
  for (const name of candidates) {
231
232
  const readmePath = join(localPath, name);
232
233
  if (existsSync(readmePath)) {
233
234
  try {
234
235
  const content = readFileSync(readmePath, "utf-8");
235
- return content.length > 8192 ? content.slice(0, 8192) + "\n\n[README truncated at 8K chars]" : content;
236
+ return content.length > 8192 ? flattenTruncatedString(content.slice(0, 8192) + "\n\n[README truncated at 8K chars]") : content;
236
237
  } catch {
237
238
  continue;
238
239
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.11-alpha.9",
3
+ "version": "0.9.11",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
6
6
  "contributors": [
@@ -46,6 +46,6 @@
46
46
  "linkedom": "^0.18.13",
47
47
  "p-limit": "^7.3.0",
48
48
  "turndown": "^7.2.0",
49
- "unpdf": "^1.6.2"
49
+ "unpdf": "1.7.0"
50
50
  }
51
51
  }
@@ -6,6 +6,63 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.11] - 2026-08-03
10
+
11
+ ### Added
12
+
13
+ - Added per-node cancellation and abort targeting to `ctx.tool`. Every callback receives a `WorkflowToolContext` (`{ signal }`) combining a per-invocation controller with the run signal, and `workflow({ action: "quit"|"interrupt", runId, stageId })` resolves a tool node by expanded id, local `tool:<argsHash>` id, or tool name. Aborting one node leaves siblings running; existing zero-argument callbacks are unchanged ([#2078](https://github.com/bastani-inc/atomic/issues/2078)).
14
+ - Added opt-in `ctx.tool(..., { failureMode: "return" })` for checks that may fail during repair flows, preserving bounded redacted `exitCode`, `stdout`, and `stderr` as typed durable data that replays without rerunning the callback ([#1993](https://github.com/bastani-inc/atomic/issues/1993)).
15
+ - Re-added the built-in Goal and Ralph workflows with their typed composition exports, command metadata, tests, and docs.
16
+ - Added a run-contract discipline to every builtin workflow: only the user may change a run's contract, mid-run steering and resume text are authoritative amendments, and each stage restates the amendments it received. Every bundled workflow wraps its context once through `withSteeringPropagationContext` (exported from `@bastani/workflows/builtin/steering-context`), so `ctx.task`, `ctx.chain`, and `ctx.parallel` prompts carry it automatically. Implementation stages also freeze acceptance criteria before writing code and keep a deferred list instead of a growing diff.
17
+ - Added a draft handoff for `ralph` runs that exhaust their review budget: with `create_pr` enabled, the run opens a draft pull request reproducing every unresolved blocking finding with priority and cited `file:line` instead of leaving the branch unpushed.
18
+ - Added the opt-in `autoAttach` workflow-definition field, which opens the graph overlay for interactive top-level named launches, and `ctx.models`, which optionally exposes the host session's model catalog to custom definitions.
19
+ - Embedded DBOS Postgres now works when Atomic runs as root on Linux (containers, CI sandboxes, eval harnesses) by resolving an unprivileged system account and keeping the cluster under `/var/lib/atomic-postgres`.
20
+
21
+ ### Changed
22
+
23
+ - Workflow invocations now assign a stable, non-`default` Intercom group from the top-level run identity and apply it to every Intercom-capable stage. Nested workflows, model fallback, pause/resume, durable replay, and subagent inheritance retain the group; separate runs stay isolated and explicit overrides still win, so authors no longer thread group names manually ([#1999](https://github.com/bastani-inc/atomic/issues/1999)).
24
+ - Adopted Claude Opus 5 as the primary model for the Ralph prompt-engineer, orchestrator, and reviewer-A stages, the Goal orchestrator and reviewer stages, and the Open Claude Design chain, with Copilot and OpenRouter mirrors added throughout. Ralph's research and reviewer-B chains keep GPT-5.6 primaries and gain Opus 5 fallbacks.
25
+ - Reworked the Goal, Ralph, Open Claude Design, and six pattern-workflow stage prompts, plus the bundled `prompt-engineer` skill (864 → 396 lines), for GPT-5.6, Claude Opus 5, and Claude Fable 5: artifacts before the final query, explicit output and stop contracts, evidence-grounded status claims, damped delegation, and no response prefilling or internal-reasoning requests. Schemas, convergence semantics, and exports are preserved.
26
+ - Changed the builtin Goal execution stage from a direct worker into a Ralph-style sub-agent orchestrator that delegates implementation, validation, and documentation through the `subagent` tool and writes `orchestrator-receipt.md`, while retaining Goal's ledger, reviewer, reducer, and optional PR flow.
27
+ - Strengthened reviewer prompts with a contract-probe playbook and a code-delta integrity contract: reviewers prove the delta exists in the review checkout before trusting receipts, treat claimed-but-absent work as blocking, and flag modified or deleted pre-existing tests ([#1973](https://github.com/bastani-inc/atomic/issues/1973)).
28
+ - Strengthened workflow-authoring and routing guidance to require DAG-only dynamic topologies with pre-launch cycle rejection, bounded loop unrolling, and topology sketches, and to triage multi-item queues into bounded concurrent top-level runs with separate worktrees ([#2010](https://github.com/bastani-inc/atomic/issues/2010), [#2012](https://github.com/bastani-inc/atomic/issues/2012)).
29
+ - Added a worktree-discipline contract to Goal and Ralph prompts (work stays in the invoking checkout), and made PR creation an explicitly authorized `create_pr=true` action rather than embedded task text.
30
+ - Documented deterministic delivery timing for stage messages: `steer` is consumed after the current response finishes its whole tool batch and before the next model request, `followUp` only when the agent would otherwise stop ([#2074](https://github.com/bastani-inc/atomic/issues/2074)).
31
+ - Workflow stage chat now renders Atomic's one-cell `∀` on the shared 88 ms luminance ramp, honours caller-supplied palettes through the detected terminal color mode, and drops redundant overlay status chrome.
32
+
33
+ ### Removed
34
+
35
+ - Removed the parenthesized context-window authoring token (`(1m)`, `(1.1m)`, `(936k)`, `(long)`) from workflow model strings along with the `contextWindow` / `contextWindowStrict` stage options. Every stage session uses its model's catalog context window.
36
+ - Removed Cursor model fallbacks and Cursor-branded Impeccable harness compatibility ([#1994](https://github.com/bastani-inc/atomic/issues/1994)), and removed the built-in `deep-research-codebase` workflow.
37
+
38
+ ### Fixed
39
+
40
+ - Fixed workflow graph rendering and memory on large fan-outs. Cards, edges, and composed rows clip to the viewport; store observation offers a synchronous invalidation channel plus one memoized immutable graph projection per store version, so a status update no longer `JSON.stringify`/`JSON.parse`-clones the full run payload; and truncated fields plus durable tool-result summaries copy into fresh flat strings, since a V8 SlicedString kept multi-megabyte results alive behind a 240-character summary ([#2100](https://github.com/bastani-inc/atomic/issues/2100)).
41
+ - Fixed graceful quit ignoring in-flight `ctx.tool` calls, which previously reported `No controllable stages on run <id>` and left callbacks running. Quit now counts running tool nodes as controllable work, closes the tool-admission boundary, aborts the whole root/nested set, and only then records the durable paused transition. Resume re-executes the cancelled call at the same node instead of replaying a cancellation as data, a caught cancellation cannot publish a completed run over quit's paused record, and a failed durable write still leaves the run controllable so a retried quit can upgrade the pause ([#2078](https://github.com/bastani-inc/atomic/issues/2078)).
42
+ - Fixed durable nested `ctx.workflow(...)` resume and completed hydration across fresh processes: boundary identity persists before child dispatch, ids stay stable under reversed parallel ordering, and stale, aliased, cyclic, or nonreciprocal records fail closed instead of flattening an unrelated run. Completed child work and `ctx.tool(...)` effects stay exactly-once, and ambiguous child-local stage ids no longer route to the first match.
43
+ - Fixed `ctx.tool()` calls durably completing side effects before a late empty-stage rejection. Tools now create ordered, inspectable, non-attachable graph nodes with live, failed, completed, cancelled, and cached states; tool-only workflows complete normally; and terminal publication closes admission so post-terminal calls reject before any callback runs ([#1991](https://github.com/bastani-inc/atomic/issues/1991)).
44
+ - Fixed host-session replacement (`/new`, `/resume`, `/fork`, `/reload`) leaving the process-global DBOS executor stopped, so every later run in the same process failed at its first durable checkpoint with `` `DBOS.launch()` must be called before running workflows ``. The executor lifetime is now process-scoped, and the backend factory never hands out a stopped backend ([#1957](https://github.com/bastani-inc/atomic/issues/1957)).
45
+ - Fixed stage message queues across detach and reattach: a typed Enter in an attached stage chat names steering explicitly instead of being demoted to a follow-up, the stage handle keeps its own projection of `queue_update` snapshots so pending rows survive detaching, the graph shows a `✉ N queued` badge, a session that arrives already holding queued messages replays the snapshot its listeners missed, and Escape restores queued steering and follow-ups into the editor ([#2074](https://github.com/bastani-inc/atomic/issues/2074)).
46
+ - Fixed controlled workflow pauses allowing queued or late-arriving work to run after a stage was visibly paused. A synchronous raw queue hold is established before one serialized abort boundary, and resume releases preserved steering, follow-up, custom, async, and user entries exactly once ([#1983](https://github.com/bastani-inc/atomic/issues/1983)).
47
+ - Fixed programmatic `workflow send` reopening retained stage conversations after the root workflow had terminated; terminal roots now return a structured `WORKFLOW_TERMINAL` failure before any session probing or delivery, while `/workflow attach` and Intercom keep their explicit post-mortem paths ([#2009](https://github.com/bastani-inc/atomic/issues/2009)).
48
+ - Fixed three `open-claude-design` faults: a failed `user-feedback-*` stage no longer converts into design approval and exports an unreviewed design ([#2123](https://github.com/bastani-inc/atomic/issues/2123)); research context now persists to `<artifact_dir>/design-context.md` and `references.md` and is read through `reads` instead of embedded inline, removing the `413 request_too_large` class ([#2121](https://github.com/bastani-inc/atomic/issues/2121)); and each refinement round raises a run-level prompt naming the preview path and `file://` URL instead of blocking on live review while reporting `running` ([#2060](https://github.com/bastani-inc/atomic/issues/2060)).
49
+ - Fixed the Ralph research stage destroying its own artifact by declaring the authored report path as its runner-owned `output`; research stages now return the full report as their final message. Fixed the Classify-and-act, Fan-out-and-synthesize, Generate-and-filter, Loop-until-done, and Tournament workflows returning whole terminal artifacts into the calling session's context — each `result` is now the compact artifact reference, with the full report on the existing path outputs.
50
+ - Fixed admitted durable `ctx.tool(...)` failures being misclassified as killed or cancelled, and fixed failed physical session appends leaving malformed JSONL tails, duplicate ids, or phantom parents after a partial write ([#2021](https://github.com/bastani-inc/atomic/issues/2021)).
51
+ - Fixed fresh-process resume and completed-run inspection losing nested child-workflow and parallel hierarchy, and fixed `workflow({ action: "resume", runId })` returning `Run not found` for eligible durably paused runs after session-local state was cleared ([#1924](https://github.com/bastani-inc/atomic/issues/1924)).
52
+ - Fixed session teardown crashing the process with exit code 1 after DBOS provisioning had failed, which turned otherwise-successful `--print` runs into nonzero exits. When no durable backend can be provisioned at all, workflows degrade to a loud non-durable in-memory backend instead of failing every action.
53
+ - Hardened the bundled `impeccable` live-mode scripts against four CodeQL findings (insecure randomness, resource exhaustion, regex injection, double escaping), preserving behaviour for all valid inputs.
54
+ - Fixed retained terminal stage chats dropping the Working indicator after Enter and before the follow-up turn started ([#1982](https://github.com/bastani-inc/atomic/issues/1982)).
55
+
56
+ ## [0.9.11-alpha.11] - 2026-08-03
57
+
58
+ ### Fixed
59
+
60
+ - Fixed workflow graph rendering for large, wide fan-outs by clipping cards, edges, and composed rows to the visible viewport, retaining cached topology and layout across status-only updates, and making idle animation eligibility constant-time ([#2100](https://github.com/bastani-inc/atomic/issues/2100)).
61
+ - Fixed every interactive store mutation cloning the full run payload before the graph could repaint. A status or question update used to build a complete `JSON.stringify`/`JSON.parse` snapshot, traversing workflow inputs, authored stage result bodies, child output values, and tool input/output bodies, so mutation cost scaled with payload size rather than with the graph. Store observation now offers a synchronous invalidation-only channel plus one immutable memoized graph projection per store version, and the graph, overlay, attach pane, stage chat, widget, lifecycle and HIL notifications, resume picker, and send admission all read that projection. In the default configuration (`statusFile: false`), mutations no longer build the legacy full snapshot; enabling `statusFile` retains that snapshot and status-file serialization cost. Topology, status, timing, prompts, attachment state, notices, bounded returned-status fields, durable tool summaries, and child output counts are preserved; the existing `Store.snapshot()` / `Store.subscribe(snapshot)` contract is unchanged for external consumers ([#2100](https://github.com/bastani-inc/atomic/issues/2100)).
62
+ - Fixed the memoized graph projection keeping whole run results in memory. Truncating a result field with `slice()` leaves a V8 SlicedString that points at its untruncated parent, so a 1 KiB projected field kept a multi-megabyte run result alive — inside the store's cached projection, and inside every long-lived holder of one — long after the run itself was removed. Truncated fields are now copied into fresh flat strings, so a session with large workflow results no longer grows heap ([#2100](https://github.com/bastani-inc/atomic/issues/2100)).
63
+ - Fixed durable tool-result summaries retaining the payloads they summarize. `summarizeToolResult` and `summarizeCompletedToolResult` bounded a serialized value with `` `${serialized.slice(0, 237)}...` ``, which leaves a V8 SlicedString behind a ConsString, both pointing at the untruncated parent — so a 240-character summary written into a durable checkpoint or held in a completed-run catalog row kept the entire `JSON.stringify` output alive (measured at 8.4 MiB for one large tool result). Both summaries now copy into fresh flat strings via a shared `flattenTruncatedString` helper, which the memoized graph projection also uses ([#2100](https://github.com/bastani-inc/atomic/issues/2100)).
64
+ - Fixed Escape in an attached workflow-stage chat restoring queued steering and follow-up messages into the editor instead of leaving them stuck in the pending-message queue.
65
+
9
66
  ## [0.9.11-alpha.9] - 2026-08-01
10
67
 
11
68
  ### Added
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdtemp, writeFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
2
+ import { writeFile } from "node:fs/promises";
4
3
  import { join } from "node:path";
4
+ import { createWorkflowArtifactDirectory } from "../src/shared/workflow-artifacts.js";
5
5
  import { LEDGER_FILENAME, type GoalLedger, type GoalLifecycleEvent } from "./goal-types.js";
6
6
 
7
7
  type ModelVisibleGoalLedger = Omit<
@@ -55,11 +55,13 @@ export function appendLifecycleEvent(
55
55
  export async function createGoalLedger(
56
56
  objective: string,
57
57
  acceptanceCriteria = objective,
58
+ runId?: string,
58
59
  ): Promise<{ ledger: GoalLedger; ledgerPath: string; artifactDir: string }> {
59
- const artifactDir = await mkdtemp(join(tmpdir(), "atomic-goal-runner-"));
60
+ const goalId = randomUUID();
61
+ const artifactDir = await createWorkflowArtifactDirectory(runId);
60
62
  const now = new Date().toISOString();
61
63
  const ledger: GoalLedger = {
62
- goal_id: randomUUID(),
64
+ goal_id: goalId,
63
65
  objective,
64
66
  acceptance_criteria: acceptanceCriteria,
65
67
  status: "active",
@@ -64,6 +64,7 @@ function normalizeBranchInput(
64
64
  }
65
65
  type GoalRunnerContext = {
66
66
  readonly inputs: GoalWorkflowInputs;
67
+ readonly runId?: string;
67
68
  task(name: string, options: WorkflowTaskOptions): Promise<WorkflowTaskResult>;
68
69
  parallel(steps: readonly WorkflowTaskStep[], options: WorkflowParallelOptions): Promise<WorkflowTaskResult[]>;
69
70
  };
@@ -109,7 +110,7 @@ export async function runGoalWorkflow(ctx: GoalRunnerContext, options: GoalWorkf
109
110
  const reviewQuorum = DEFAULT_REVIEW_QUORUM;
110
111
  const blockerThreshold = Math.min(DEFAULT_BLOCKER_THRESHOLD, maxTurns);
111
112
  const comparisonBaseBranch = normalizeBranchInput(inputs.base_branch, "origin/main");
112
- const { ledger, ledgerPath, artifactDir } = await createGoalLedger(objective, acceptanceCriteria);
113
+ const { ledger, ledgerPath, artifactDir } = await createGoalLedger(objective, acceptanceCriteria, ctx.runId);
113
114
 
114
115
  let latestReviews: ReviewRecord[] = [];
115
116
  let latestReviewArtifactPaths: string[] = [];
@@ -47,7 +47,7 @@ export default workflow({
47
47
  goal_id: Type.Optional(Type.String({ description: "Per-run goal identifier stored in the ledger." })),
48
48
  objective: Type.Optional(Type.String({ description: "Raw goal objective used by the run." })),
49
49
  acceptance_criteria: Type.Optional(Type.String({ description: "Immutable acceptance criteria used by the run." })),
50
- ledger_path: Type.Optional(Type.String({ description: "OS-temp path to goal-ledger.json with receipts, reviewer decisions, blockers, and lifecycle events." })),
50
+ ledger_path: Type.Optional(Type.String({ description: "Durable run-scoped path to goal-ledger.json with receipts, reviewer decisions, blockers, and lifecycle events." })),
51
51
  turns_completed: Type.Optional(Type.Number({ description: "Orchestrator/review turns completed." })),
52
52
  iterations_completed: Type.Optional(Type.Number({ description: "Orchestrator/review turns completed, retained for status summaries." })),
53
53
  receipts: Type.Optional(Type.Array(Type.Object({
@@ -1,8 +1,8 @@
1
- import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
1
+ import { mkdir, writeFile } from "node:fs/promises";
3
2
  import { dirname, join } from "node:path";
4
3
  import { Type } from "typebox";
5
4
  import type { WorkflowTaskResult } from "../src/shared/types.js";
5
+ import { createWorkflowArtifactDirectory } from "../src/shared/workflow-artifacts.js";
6
6
  import {
7
7
  E2E_VERIFICATION_GUIDANCE,
8
8
  LITERAL_OBJECTIVE_CONTRACT,
@@ -170,8 +170,8 @@ export function defaultResearchPath(prompt: string, now = new Date()): string {
170
170
  return join(DEFAULT_RESEARCH_DIR, `${date}-${slugifyResearchTopic(prompt)}.md`);
171
171
  }
172
172
 
173
- export async function createImplementationNotesFile(prompt: string): Promise<string> {
174
- const notesDir = await mkdtemp(join(tmpdir(), "atomic-ralph-notes-"));
173
+ export async function createImplementationNotesFile(prompt: string, runId?: string): Promise<string> {
174
+ const notesDir = await createWorkflowArtifactDirectory(runId);
175
175
  const notesPath = join(notesDir, IMPLEMENTATION_NOTES_FILENAME);
176
176
  const initialNotes = [
177
177
  "# Implementation Notes",
@@ -195,8 +195,8 @@ export async function createImplementationNotesFile(prompt: string): Promise<str
195
195
  // write to it; the video file itself is produced by the orchestrator's QA pass
196
196
  // (and overwritten each iteration so it always reflects the latest state). The
197
197
  // final pull-request stage attaches it when it exists.
198
- export async function createQaEvidenceVideoPath(): Promise<string> {
199
- const qaDir = await mkdtemp(join(tmpdir(), "atomic-ralph-qa-"));
198
+ export async function createQaEvidenceVideoPath(runId?: string): Promise<string> {
199
+ const qaDir = await createWorkflowArtifactDirectory(runId);
200
200
  return join(qaDir, QA_E2E_VIDEO_FILENAME);
201
201
  }
202
202
 
@@ -367,7 +367,6 @@ export function renderResearchPrompt(args: {
367
367
  readonly acceptanceCriteria: string;
368
368
  readonly workflowCwdContext: PromptSection;
369
369
  readonly latestReviewReportPath: string | undefined;
370
- readonly researchPath: string;
371
370
  }): string {
372
371
  return taggedPrompt([
373
372
  ["acceptance_criteria", args.acceptanceCriteria],
@@ -386,8 +385,7 @@ export function renderResearchPrompt(args: {
386
385
  [
387
386
  "research_artifact",
388
387
  [
389
- "Return the complete research report as your final message. This workflow saves that final message verbatim as the run's research artifact; downstream implementation and review stages read it from that file.",
390
- `Do not write ${args.researchPath} yourself. Anything written there during this stage is replaced by your final message, so a file you author is lost and a final message that only points at the path leaves later stages with no findings. Skill-owned notes under research/docs/ and research/web/ are unaffected.`,
388
+ "Return the complete research report as your final message. Downstream implementation and review stages read it from there.",
391
389
  "Produce a complete Markdown report with codebase and useful online/contextual findings, implementation guidance, relevant files/tests/docs, unresolved-finding analysis, and validation recommendations. Lead with conclusions; keep facts, caveats, and implementation-relevant next steps; drop background and repetition.",
392
390
  "Before reporting progress, audit each claim against a tool result from this session. Report only work you can point to evidence for; say so explicitly when something is unverified.",
393
391
  "Do not author an RFC/spec or implement code changes.",
@@ -417,6 +415,7 @@ export type RalphWorkflowOptions = {
417
415
  readonly comparisonBaseBranch: string;
418
416
  readonly workflowStartCwd: string;
419
417
  readonly createPr: boolean;
418
+ readonly runId?: string;
420
419
  };
421
420
 
422
421
  export type RalphWorkflowResult = {
@@ -28,7 +28,6 @@ export function renderForkedResearchPromptRefinementPrompt(args: {
28
28
  export function renderForkedResearchPrompt(args: {
29
29
  readonly transformedResearchQuestion: string;
30
30
  readonly latestReviewReportPath: string | undefined;
31
- readonly researchPath: string;
32
31
  }): string {
33
32
  return taggedPrompt([
34
33
  [
@@ -43,9 +42,8 @@ export function renderForkedResearchPrompt(args: {
43
42
  [
44
43
  "research_artifact",
45
44
  [
46
- "Return the rewritten research report for this iteration as your final message. This workflow saves that final message verbatim as the run's research artifact; downstream implementation and review stages read it from that file.",
47
- `Do not write ${args.researchPath} yourself. Anything written there during this stage is replaced by your final message, so a file you author is lost and a final message that only points at the path leaves later stages with no findings. Skill-owned notes under research/docs/ and research/web/ are unaffected.`,
48
- "Restate the still-applicable findings in full rather than referring back to the previous iteration's artifact; it is overwritten by this message.",
45
+ "Return the rewritten research report for this iteration as your final message.",
46
+ "Restate the still-applicable findings in full rather than referring back to the previous iteration's artifact; the current artifact and transcript are the authoritative records for this iteration.",
49
47
  "Do not author an RFC/spec or implement code changes.",
50
48
  ].join("\n"),
51
49
  ],
@@ -48,10 +48,10 @@ export const promptEngineerModelConfig = {
48
48
  };
49
49
 
50
50
  export const researchModelConfig = {
51
- model: "openai-codex/gpt-5.6-sol:medium",
51
+ model: "openai-codex/gpt-5.6-luna:max",
52
52
  fallbackModels: [
53
- "github-copilot/gpt-5.6-sol:medium",
54
- "openai/gpt-5.6-sol:medium",
53
+ "github-copilot/gpt-5.6-luna:max",
54
+ "openai/gpt-5.6-luna:max",
55
55
  "anthropic/claude-opus-5:low",
56
56
  "github-copilot/claude-opus-5:low",
57
57
  "anthropic/claude-fable-5:low",
@@ -64,7 +64,7 @@ export const researchModelConfig = {
64
64
  "xai/grok-4.5:high",
65
65
  "zai/glm-5.2:high",
66
66
  "zai-coding-cn/glm-5.2:high",
67
- "openrouter/openai/gpt-5.6-sol:medium",
67
+ "openrouter/openai/gpt-5.6-luna:max",
68
68
  "openrouter/anthropic/claude-opus-5:low",
69
69
  "openrouter/openai/gpt-5.5:medium",
70
70
  "openrouter/anthropic/claude-fable-5:low",
@@ -1,8 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
- import { mkdtemp } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
4
2
  import { join, resolve } from "node:path";
5
3
  import type { WorkflowRunContext, WorkflowTaskResult } from "../src/shared/types.js";
4
+ import { createWorkflowArtifactDirectory } from "../src/shared/workflow-artifacts.js";
6
5
  import {
7
6
  ACCEPTANCE_MATRIX_CONTRACT,
8
7
  CONTRACT_FIDELITY_AUDIT,
@@ -52,7 +51,7 @@ export async function runRalphWorkflow(
52
51
  ctx: WorkflowRunContext<RalphInputs>,
53
52
  options: RalphWorkflowOptions,
54
53
  ): Promise<RalphWorkflowResult> {
55
- const { prompt, acceptanceCriteria, maxLoops, comparisonBaseBranch, workflowStartCwd, createPr } = options;
54
+ const { prompt, acceptanceCriteria, maxLoops, comparisonBaseBranch, workflowStartCwd, createPr, runId } = options;
56
55
  let latestReviewReportPath: string | undefined;
57
56
  let finalPlan = "";
58
57
  let finalPlanPath = "";
@@ -63,9 +62,9 @@ export async function runRalphWorkflow(
63
62
  const workflowCwdContext = workflowCwdContextSection(workflowStartCwd);
64
63
  const workflowPrompt = prompt;
65
64
  const workflowResearchPath = resolve(workflowStartCwd, defaultResearchPath(workflowPrompt));
66
- const implementationNotesPath = await createImplementationNotesFile(workflowPrompt);
67
- const qaVideoPath = await createQaEvidenceVideoPath();
68
- const artifactDir = await mkdtemp(join(tmpdir(), "atomic-ralph-run-"));
65
+ const implementationNotesPath = await createImplementationNotesFile(workflowPrompt, runId);
66
+ const qaVideoPath = await createQaEvidenceVideoPath(runId);
67
+ const artifactDir = await createWorkflowArtifactDirectory(runId);
69
68
  let approved = false;
70
69
  let iterationsCompleted = 0;
71
70
  let previousResearchPromptRefinementSessionFile: string | undefined;
@@ -98,12 +97,10 @@ export async function runRalphWorkflow(
98
97
  acceptanceCriteria,
99
98
  workflowCwdContext,
100
99
  latestReviewReportPath,
101
- researchPath: workflowResearchPath,
102
100
  })
103
101
  : renderForkedResearchPrompt({
104
102
  transformedResearchQuestion: researchPromptRefinement.text,
105
103
  latestReviewReportPath,
106
- researchPath: workflowResearchPath,
107
104
  }),
108
105
  reads: latestReviewReportPath === undefined ? [] : [latestReviewReportPath],
109
106
  output: workflowResearchPath,
@@ -41,7 +41,7 @@ export default workflow({
41
41
  plan_path: Type.Optional(Type.String({ description: "Backward-compatible alias for research_path." })),
42
42
  research: Type.Optional(Type.String({ description: "Latest research report text or artifact reference." })),
43
43
  research_path: Type.Optional(Type.String({ description: "Path to the latest generated research artifact under research/." })),
44
- implementation_notes_path: Type.Optional(Type.String({ description: "OS-temp notes file containing decisions, deviations, blockers, and validation notes." })),
44
+ implementation_notes_path: Type.Optional(Type.String({ description: "Durable run-scoped notes file containing decisions, deviations, blockers, and validation notes." })),
45
45
  qa_video_path: Type.Optional(Type.String({ description: "Absolute path to the reviewable QA end-to-end proof video recorded with playwright-cli for UI-applicable changes, when one was produced." })),
46
46
  pr_report: Type.Optional(Type.String({ description: "Pull-request report emitted only when create_pr=true and the final pull-request stage runs." })),
47
47
  approved: Type.Optional(Type.Boolean({ description: "Whether the reviewer loop approved before completion or optional final handoff." })),
@@ -72,6 +72,7 @@ export default workflow({
72
72
  comparisonBaseBranch,
73
73
  workflowStartCwd,
74
74
  createPr,
75
+ runId: workflowCtx.runId,
75
76
  });
76
77
  },
77
78
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.11-alpha.9",
3
+ "version": "0.9.11",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -1,3 +1,4 @@
1
+ import { flattenTruncatedString } from "../shared/flat-string.js";
1
2
  import { isReopenableSessionTranscript } from "../shared/session-transcript.js";
2
3
  import type { RunSnapshot, StageSnapshot, ToolNodeSnapshot } from "../shared/store-types.js";
3
4
  import type { WorkflowInputValues, WorkflowSerializableValue } from "../shared/types.js";
@@ -31,7 +32,7 @@ import {
31
32
 
32
33
  export type CompletedWorkflowResolution =
33
34
  | { readonly kind: "found"; readonly entry: ResumableWorkflowEntry; readonly snapshot: RunSnapshot }
34
- | { readonly kind: "ambiguous"; readonly matches: readonly ResumableWorkflowEntry[] }
35
+ | { readonly kind: "malformed"; readonly message: string }
35
36
  | { readonly kind: "not_found" }
36
37
  | { readonly kind: "stale"; readonly entry: ResumableWorkflowEntry };
37
38
 
@@ -54,13 +55,13 @@ export function listOpenableCompletedWorkflows(backend: DurableWorkflowBackend):
54
55
  }
55
56
 
56
57
  export function resolveCompletedWorkflow(
57
- workflowIdOrPrefix: string,
58
+ workflowId: string,
58
59
  backend: DurableWorkflowBackend,
59
60
  openableCatalog: readonly ResumableWorkflowEntry[] = listOpenableCompletedWorkflows(backend),
60
61
  ): CompletedWorkflowResolution {
61
- const resolved = resolveDurableEntry(workflowIdOrPrefix, openableCatalog);
62
+ const resolved = resolveDurableEntry(workflowId, openableCatalog);
62
63
  if (resolved !== undefined) {
63
- if ("kind" in resolved) return { kind: "ambiguous", matches: resolved.matches };
64
+ if ("kind" in resolved) return { kind: "malformed", message: resolved.message };
64
65
  const snapshot = completedWorkflowSnapshot(backend, resolved);
65
66
  if (snapshot === undefined) {
66
67
  return { kind: "stale", entry: resolved };
@@ -68,9 +69,9 @@ export function resolveCompletedWorkflow(
68
69
  return { kind: "found", entry: resolved, snapshot };
69
70
  }
70
71
 
71
- const authoritative = resolveDurableEntry(workflowIdOrPrefix, listCompletedFromBackend(backend));
72
+ const authoritative = resolveDurableEntry(workflowId, listCompletedFromBackend(backend));
72
73
  if (authoritative === undefined) return { kind: "not_found" };
73
- if ("kind" in authoritative) return { kind: "ambiguous", matches: authoritative.matches };
74
+ if ("kind" in authoritative) return { kind: "malformed", message: authoritative.message };
74
75
  return { kind: "stale", entry: authoritative };
75
76
  }
76
77
 
@@ -208,10 +209,15 @@ function completedToolNodes(
208
209
  });
209
210
  }
210
211
 
212
+ /**
213
+ * Catalog rows are held for the lifetime of a completed-run listing, so this
214
+ * truncation must release the payload it summarizes rather than point into it.
215
+ * See `flattenTruncatedString`.
216
+ */
211
217
  function summarizeCompletedToolResult(value: WorkflowSerializableValue): string {
212
218
  const serialized = JSON.stringify(value);
213
- if (serialized === undefined) return String(value).slice(0, 240);
214
- return serialized.length <= 240 ? serialized : `${serialized.slice(0, 237)}...`;
219
+ if (serialized === undefined) return flattenTruncatedString(String(value).slice(0, 240));
220
+ return serialized.length <= 240 ? serialized : `${flattenTruncatedString(serialized.slice(0, 237))}...`;
215
221
  }
216
222
 
217
223
  function runSnapshotsFromCheckpoints(
@@ -23,7 +23,7 @@ export type OpenCompletedDurableResult =
23
23
  readonly name: string;
24
24
  readonly message: string;
25
25
  }
26
- | { readonly ok: false; readonly reason: "not_found" | "ambiguous" | "stale" | "active"; readonly message: string };
26
+ | { readonly ok: false; readonly reason: "not_found" | "malformed" | "stale" | "active"; readonly message: string };
27
27
 
28
28
  export interface OpenCompletedDurableDeps {
29
29
  readonly durableBackend: DurableWorkflowBackend;
@@ -49,22 +49,21 @@ const completedChatRegistrations = new WeakMap<StageControlRegistry, Map<string,
49
49
  * conversation to its retained Atomic session without dispatching the workflow.
50
50
  */
51
51
  export function openCompletedDurableWorkflow(
52
- workflowIdOrPrefix: string,
52
+ workflowId: string,
53
53
  deps: OpenCompletedDurableDeps,
54
54
  catalog: readonly ResumableWorkflowEntry[] = listOpenableCompletedWorkflows(deps.durableBackend),
55
55
  ): OpenCompletedDurableResult {
56
- const resolved = resolveCompletedWorkflow(workflowIdOrPrefix, deps.durableBackend, catalog);
56
+ const resolved = resolveCompletedWorkflow(workflowId, deps.durableBackend, catalog);
57
57
  if (resolved.kind === "not_found") {
58
- return failure("not_found", `No completed durable workflow found for id/prefix: ${workflowIdOrPrefix}`);
58
+ return failure("not_found", `No completed durable workflow found for id: ${workflowId}`);
59
59
  }
60
- if (resolved.kind === "ambiguous") {
61
- const matches = resolved.matches.map((entry) => `${entry.name} (${entry.workflowId.slice(0, 8)})`).join(", ");
62
- return failure("ambiguous", `Ambiguous completed workflow prefix "${workflowIdOrPrefix}" matches: ${matches}`);
60
+ if (resolved.kind === "malformed") {
61
+ return failure("malformed", resolved.message);
63
62
  }
64
63
  if (resolved.kind === "stale") {
65
64
  return failure(
66
65
  "stale",
67
- `Completed workflow ${resolved.entry.workflowId.slice(0, 8)} is stale or missing durable checkpoint/session data and cannot be opened.`,
66
+ `Completed workflow ${resolved.entry.workflowId} is stale or missing durable checkpoint/session data and cannot be opened.`,
68
67
  );
69
68
  }
70
69
 
@@ -72,7 +71,7 @@ export function openCompletedDurableWorkflow(
72
71
  if (existing !== undefined && existing.status !== "completed") {
73
72
  return failure(
74
73
  "active",
75
- `Workflow ${resolved.snapshot.id.slice(0, 8)} is already active in this session; attach with /workflow connect ${resolved.snapshot.id.slice(0, 8)} instead.`,
74
+ `Workflow ${resolved.snapshot.id} is already active in this session; attach with /workflow connect ${resolved.snapshot.id} instead.`,
76
75
  );
77
76
  }
78
77
  const snapshots = completedWorkflowRunSnapshots(deps.durableBackend, resolved.entry);
@@ -90,11 +89,11 @@ export function openCompletedDurableWorkflow(
90
89
  runId: snapshot.id,
91
90
  workflowId: snapshot.id,
92
91
  name: snapshot.name,
93
- message: `Opened completed durable workflow "${snapshot.name}" (${snapshot.id.slice(0, 8)}) for read-only inspection${hasReopenableStage ? " and follow-up chat" : ""}.`,
92
+ message: `Opened completed durable workflow "${snapshot.name}" (${snapshot.id}) for read-only inspection${hasReopenableStage ? " and follow-up chat" : ""}.`,
94
93
  };
95
94
  }
96
95
 
97
- function failure(reason: "not_found" | "ambiguous" | "stale" | "active", message: string): OpenCompletedDurableResult {
96
+ function failure(reason: "not_found" | "malformed" | "stale" | "active", message: string): OpenCompletedDurableResult {
98
97
  return { ok: false, reason, message };
99
98
  }
100
99
 
@@ -10,7 +10,7 @@ export function formatResumableWorkflowList(entries: readonly ResumableWorkflowE
10
10
  if (entries.length === 0) return "No resumable or completed workflows found.";
11
11
  const hasCompleted = entries.some((entry) => entry.status === "completed");
12
12
  const lines = entries.map((entry, index) => {
13
- const id = entry.workflowId.slice(0, 8);
13
+ const id = entry.workflowId;
14
14
  const status = entry.status === "completed" ? "✓ completed" : entry.status.padEnd(8);
15
15
  const checkpoints = `${entry.completedCheckpoints} checkpoint${entry.completedCheckpoints === 1 ? "" : "s"}`;
16
16
  const label = entry.label ? ` "${entry.label}"` : "";
@@ -1,3 +1,4 @@
1
+ import type { RunSnapshot, RunStatus, WorkflowFailureRecoverability } from "../shared/store-types.js";
1
2
  import type { DurableWorkflowStatus } from "./types.js";
2
3
 
3
4
  /** Metadata required to classify a current DBOS workflow as resumable. */
@@ -10,6 +11,47 @@ export interface DurableResumeCandidate {
10
11
  readonly resumable?: boolean;
11
12
  }
12
13
 
14
+ /** Live snapshot fields used by every workflow resume surface. */
15
+ export interface WorkflowRunResumeCandidate {
16
+ readonly status: RunStatus;
17
+ readonly endedAt?: number;
18
+ readonly resumable?: boolean;
19
+ readonly failureRecoverability?: WorkflowFailureRecoverability;
20
+ readonly exitReason?: string;
21
+ /** True when a descendant stage is paused/blocked even if the root is still running. */
22
+ readonly hasPausedState?: boolean;
23
+ /** Explicitly false when durable state or referenced artifacts are missing. */
24
+ readonly hasDurableCheckpoint?: boolean;
25
+ readonly artifactsIntact?: boolean;
26
+ }
27
+
28
+ /** Derive the paused/blocked state used by every workflow resume surface. */
29
+ export function workflowRunHasPausedState(run: Pick<RunSnapshot, "status" | "exitReason" | "stages">): boolean {
30
+ return (
31
+ run.status === "paused" ||
32
+ run.exitReason === "quit" ||
33
+ run.stages.some((stage) => stage.status === "paused" || stage.status === "blocked")
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Authoritative live-run resume rule shared by every workflow resume surface.
39
+ * Missing durable state or artifacts is never a resume target even when stale
40
+ * snapshot metadata still says resumable.
41
+ */
42
+ export function isWorkflowRunResumable(candidate: WorkflowRunResumeCandidate): boolean {
43
+ if (candidate.hasDurableCheckpoint === false || candidate.artifactsIntact === false) return false;
44
+ if (candidate.hasPausedState === true || candidate.status === "paused" || candidate.exitReason === "quit") {
45
+ return candidate.resumable !== false;
46
+ }
47
+ return (
48
+ (candidate.status === "failed" && candidate.endedAt !== undefined && candidate.resumable !== false) ||
49
+ (candidate.endedAt === undefined &&
50
+ candidate.resumable === true &&
51
+ candidate.failureRecoverability === "recoverable")
52
+ );
53
+ }
54
+
13
55
  /** Authoritative status/progress rules for durable workflow resume discovery. */
14
56
  export function isDurableWorkflowResumable(candidate: DurableResumeCandidate): boolean {
15
57
  const isRoot = candidate.rootWorkflowId === undefined || candidate.rootWorkflowId === candidate.workflowId;