@bastani/atomic 0.9.13-alpha.2 → 0.9.13-alpha.4

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 (317) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +1 -1
  3. package/dist/builtin/i-have-adhd/package.json +1 -1
  4. package/dist/builtin/intercom/CHANGELOG.md +14 -0
  5. package/dist/builtin/intercom/README.md +12 -12
  6. package/dist/builtin/intercom/incoming-message-delivery.ts +1 -1
  7. package/dist/builtin/intercom/index.ts +1 -1
  8. package/dist/builtin/intercom/intercom-tool.ts +1 -1
  9. package/dist/builtin/intercom/intercom-utils.ts +3 -6
  10. package/dist/builtin/intercom/package.json +1 -1
  11. package/dist/builtin/intercom/result-renderers.ts +1 -1
  12. package/dist/builtin/intercom/session-target.ts +1 -15
  13. package/dist/builtin/intercom/skills/intercom/SKILL.md +6 -6
  14. package/dist/builtin/intercom/terminal-ordering-barrier.ts +2 -2
  15. package/dist/builtin/intercom/ui/inline-message.ts +7 -5
  16. package/dist/builtin/intercom/ui/session-list.ts +23 -10
  17. package/dist/builtin/mcp/package.json +1 -1
  18. package/dist/builtin/subagents/CHANGELOG.md +19 -0
  19. package/dist/builtin/subagents/README.md +29 -73
  20. package/dist/builtin/subagents/package.json +2 -2
  21. package/dist/builtin/subagents/prompts/parallel-cleanup.md +1 -1
  22. package/dist/builtin/subagents/prompts/parallel-review.md +1 -1
  23. package/dist/builtin/subagents/prompts/review-loop.md +2 -2
  24. package/dist/builtin/subagents/skills/subagent/SKILL.md +23 -59
  25. package/dist/builtin/subagents/skills/tmux/SKILL.md +1 -1
  26. package/dist/builtin/subagents/src/extension/control-notices.ts +2 -3
  27. package/dist/builtin/subagents/src/extension/doctor.ts +2 -14
  28. package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -17
  29. package/dist/builtin/subagents/src/extension/index.ts +9 -70
  30. package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
  31. package/dist/builtin/subagents/src/extension/prompt-guidance.ts +2 -2
  32. package/dist/builtin/subagents/src/extension/schemas.ts +2 -8
  33. package/dist/builtin/subagents/src/extension/tool-description.ts +2 -3
  34. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
  35. package/dist/builtin/subagents/src/intercom/result-intercom.ts +5 -46
  36. package/dist/builtin/subagents/src/runs/{background → foreground}/completion-notification.ts +2 -2
  37. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +25 -88
  38. package/dist/builtin/subagents/src/runs/{background → foreground}/notify.ts +5 -5
  39. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +11 -28
  40. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +3 -0
  41. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +2 -0
  42. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +0 -5
  43. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +30 -20
  44. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +4 -4
  45. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +0 -13
  46. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +104 -48
  47. package/dist/builtin/subagents/src/runs/inprocess/index.ts +1 -1
  48. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +5 -5
  49. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-api.ts +0 -7
  50. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-core.ts +0 -10
  51. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-projection.ts +3 -108
  52. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-sanitize.ts +0 -4
  53. package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +1 -1
  54. package/dist/builtin/subagents/src/shared/formatters.ts +3 -5
  55. package/dist/builtin/subagents/src/shared/model-resolution.ts +78 -0
  56. package/dist/builtin/subagents/src/shared/status-format.ts +4 -11
  57. package/dist/builtin/subagents/src/shared/types-config.ts +10 -9
  58. package/dist/builtin/subagents/src/shared/types-nested.ts +127 -0
  59. package/dist/builtin/subagents/src/shared/types-results.ts +2 -8
  60. package/dist/builtin/subagents/src/shared/types-runtime.ts +0 -9
  61. package/dist/builtin/subagents/src/shared/types.ts +1 -1
  62. package/dist/builtin/subagents/src/shared/utils.ts +1 -109
  63. package/dist/builtin/subagents/src/slash/slash-commands.ts +7 -17
  64. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +2 -252
  65. package/dist/builtin/subagents/src/tui/render-layout.ts +4 -4
  66. package/dist/builtin/subagents/src/tui/render-result.ts +0 -9
  67. package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -34
  68. package/dist/builtin/subagents/src/tui/render.ts +0 -2
  69. package/dist/builtin/web-access/package.json +1 -1
  70. package/dist/builtin/workflows/CHANGELOG.md +23 -0
  71. package/dist/builtin/workflows/README.md +3 -3
  72. package/dist/builtin/workflows/package.json +1 -1
  73. package/dist/builtin/workflows/src/durable/backend.ts +11 -0
  74. package/dist/builtin/workflows/src/durable/completed-catalog-stage-groups.ts +63 -25
  75. package/dist/builtin/workflows/src/durable/completed-catalog.ts +166 -25
  76. package/dist/builtin/workflows/src/durable/dbos-backend.ts +42 -24
  77. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +2 -1
  78. package/dist/builtin/workflows/src/durable/run-timing.ts +28 -7
  79. package/dist/builtin/workflows/src/durable/scoped-backend.ts +4 -0
  80. package/dist/builtin/workflows/src/durable/targeted-inspection.ts +110 -0
  81. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +2 -2
  82. package/dist/builtin/workflows/src/engine/runtime.ts +19 -2
  83. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +3 -0
  84. package/dist/builtin/workflows/src/extension/index.bundle.mjs +750 -168
  85. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +6 -0
  86. package/dist/builtin/workflows/src/extension/ui-surface.ts +4 -0
  87. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +6 -1
  88. package/dist/builtin/workflows/src/extension/workflow-targets.ts +18 -10
  89. package/dist/builtin/workflows/src/extension/workflow-tool-inspection.ts +31 -16
  90. package/dist/builtin/workflows/src/extension/workflow-tool-send.ts +75 -3
  91. package/dist/builtin/workflows/src/extension/workflow-tool.ts +53 -6
  92. package/dist/builtin/workflows/src/runs/background/run-inspect.ts +5 -1
  93. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +94 -60
  94. package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +1 -1
  95. package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +7 -7
  96. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +25 -4
  97. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +4 -1
  98. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +2 -0
  99. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +142 -4
  100. package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +2 -0
  101. package/dist/builtin/workflows/src/runs/foreground/stage-session-heartbeat.ts +88 -0
  102. package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +1 -0
  103. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +1 -0
  104. package/dist/builtin/workflows/src/tui/run-detail.ts +25 -12
  105. package/dist/builtin/workflows/src/tui/stage-chat-view-archive-history.ts +27 -8
  106. package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +17 -1
  107. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +20 -3
  108. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +10 -1
  109. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +1 -0
  110. package/dist/core/agent-session-events.d.ts.map +1 -1
  111. package/dist/core/agent-session-events.js +22 -5
  112. package/dist/core/agent-session-events.js.map +1 -1
  113. package/dist/core/agent-session-export.d.ts.map +1 -1
  114. package/dist/core/agent-session-export.js +2 -1
  115. package/dist/core/agent-session-export.js.map +1 -1
  116. package/dist/core/agent-session-methods.d.ts +10 -4
  117. package/dist/core/agent-session-methods.d.ts.map +1 -1
  118. package/dist/core/agent-session-methods.js.map +1 -1
  119. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  120. package/dist/core/agent-session-prompt.js +68 -0
  121. package/dist/core/agent-session-prompt.js.map +1 -1
  122. package/dist/core/agent-session-queue-pause.d.ts.map +1 -1
  123. package/dist/core/agent-session-queue-pause.js +6 -0
  124. package/dist/core/agent-session-queue-pause.js.map +1 -1
  125. package/dist/core/agent-session-summary.d.ts +21 -0
  126. package/dist/core/agent-session-summary.d.ts.map +1 -0
  127. package/dist/core/agent-session-summary.js +156 -0
  128. package/dist/core/agent-session-summary.js.map +1 -0
  129. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  130. package/dist/core/agent-session-tool-registry.js +0 -4
  131. package/dist/core/agent-session-tool-registry.js.map +1 -1
  132. package/dist/core/agent-session-transfer.d.ts.map +1 -1
  133. package/dist/core/agent-session-transfer.js +0 -2
  134. package/dist/core/agent-session-transfer.js.map +1 -1
  135. package/dist/core/agent-session-tree.d.ts.map +1 -1
  136. package/dist/core/agent-session-tree.js +6 -0
  137. package/dist/core/agent-session-tree.js.map +1 -1
  138. package/dist/core/agent-session.d.ts +21 -3
  139. package/dist/core/agent-session.d.ts.map +1 -1
  140. package/dist/core/agent-session.js +22 -5
  141. package/dist/core/agent-session.js.map +1 -1
  142. package/dist/core/atomic-guide-command.js +2 -2
  143. package/dist/core/atomic-guide-command.js.map +1 -1
  144. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  145. package/dist/core/compaction/branch-summarization.js +1 -0
  146. package/dist/core/compaction/branch-summarization.js.map +1 -1
  147. package/dist/core/compaction/index.d.ts +1 -0
  148. package/dist/core/compaction/index.d.ts.map +1 -1
  149. package/dist/core/compaction/index.js +1 -0
  150. package/dist/core/compaction/index.js.map +1 -1
  151. package/dist/core/compaction/session-summarization.d.ts +41 -0
  152. package/dist/core/compaction/session-summarization.d.ts.map +1 -0
  153. package/dist/core/compaction/session-summarization.js +97 -0
  154. package/dist/core/compaction/session-summarization.js.map +1 -0
  155. package/dist/core/extensions/ui-types.d.ts +4 -0
  156. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  157. package/dist/core/extensions/ui-types.js.map +1 -1
  158. package/dist/core/keybindings.d.ts +0 -5
  159. package/dist/core/keybindings.d.ts.map +1 -1
  160. package/dist/core/keybindings.js +0 -2
  161. package/dist/core/keybindings.js.map +1 -1
  162. package/dist/core/session-manager-core.d.ts +3 -1
  163. package/dist/core/session-manager-core.d.ts.map +1 -1
  164. package/dist/core/session-manager-core.js +9 -1
  165. package/dist/core/session-manager-core.js.map +1 -1
  166. package/dist/core/session-manager-entries.d.ts +24 -1
  167. package/dist/core/session-manager-entries.d.ts.map +1 -1
  168. package/dist/core/session-manager-entries.js +48 -0
  169. package/dist/core/session-manager-entries.js.map +1 -1
  170. package/dist/core/session-manager-list.d.ts.map +1 -1
  171. package/dist/core/session-manager-list.js +10 -0
  172. package/dist/core/session-manager-list.js.map +1 -1
  173. package/dist/core/session-manager-types.d.ts +10 -1
  174. package/dist/core/session-manager-types.d.ts.map +1 -1
  175. package/dist/core/session-manager-types.js.map +1 -1
  176. package/dist/core/settings-manager-basic-accessors.d.ts +3 -0
  177. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  178. package/dist/core/settings-manager-basic-accessors.js +5 -0
  179. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  180. package/dist/core/settings-manager.d.ts +1 -1
  181. package/dist/core/settings-manager.d.ts.map +1 -1
  182. package/dist/core/settings-manager.js.map +1 -1
  183. package/dist/core/settings-types.d.ts +4 -0
  184. package/dist/core/settings-types.d.ts.map +1 -1
  185. package/dist/core/settings-types.js.map +1 -1
  186. package/dist/core/slash-commands.js +2 -2
  187. package/dist/core/slash-commands.js.map +1 -1
  188. package/dist/core/tools/bash.d.ts +0 -15
  189. package/dist/core/tools/bash.d.ts.map +1 -1
  190. package/dist/core/tools/bash.js +3 -81
  191. package/dist/core/tools/bash.js.map +1 -1
  192. package/dist/core/tools/fetch-url.d.ts +12 -0
  193. package/dist/core/tools/fetch-url.d.ts.map +1 -1
  194. package/dist/core/tools/fetch-url.js +76 -10
  195. package/dist/core/tools/fetch-url.js.map +1 -1
  196. package/dist/core/tools/index.js +4 -4
  197. package/dist/core/tools/index.js.map +1 -1
  198. package/dist/core/tools/session-temp-dir.d.ts +2 -2
  199. package/dist/core/tools/session-temp-dir.d.ts.map +1 -1
  200. package/dist/core/tools/session-temp-dir.js +6 -7
  201. package/dist/core/tools/session-temp-dir.js.map +1 -1
  202. package/dist/core/usage-totals.js +1 -1
  203. package/dist/core/usage-totals.js.map +1 -1
  204. package/dist/index.d.ts +1 -1
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js +1 -1
  207. package/dist/index.js.map +1 -1
  208. package/dist/modes/interactive/components/footer.js +1 -1
  209. package/dist/modes/interactive/components/footer.js.map +1 -1
  210. package/dist/modes/interactive/components/host-session-picker.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/host-session-picker.js +1 -0
  212. package/dist/modes/interactive/components/host-session-picker.js.map +1 -1
  213. package/dist/modes/interactive/components/index.d.ts +1 -0
  214. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/index.js +1 -0
  216. package/dist/modes/interactive/components/index.js.map +1 -1
  217. package/dist/modes/interactive/components/session-selector-list.d.ts.map +1 -1
  218. package/dist/modes/interactive/components/session-selector-list.js +29 -4
  219. package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
  220. package/dist/modes/interactive/components/session-selector-search.js +1 -1
  221. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  222. package/dist/modes/interactive/components/transcript-follow-indicator.d.ts +14 -0
  223. package/dist/modes/interactive/components/transcript-follow-indicator.d.ts.map +1 -0
  224. package/dist/modes/interactive/components/transcript-follow-indicator.js +36 -0
  225. package/dist/modes/interactive/components/transcript-follow-indicator.js.map +1 -0
  226. package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
  227. package/dist/modes/interactive/components/tree-selector-content.js +6 -0
  228. package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
  229. package/dist/modes/interactive/interactive-extension-custom-ui.js +3 -0
  230. package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
  231. package/dist/modes/interactive/interactive-input-handling.js +0 -3
  232. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  233. package/dist/modes/interactive/interactive-mode-base.d.ts +1 -0
  234. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  235. package/dist/modes/interactive/interactive-mode-base.js +7 -1
  236. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  237. package/dist/modes/interactive/interactive-mode-surface.d.ts +3 -3
  238. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  239. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  240. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  241. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  242. package/dist/modes/interactive/interactive-mode.js +1 -0
  243. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  244. package/dist/modes/interactive/interactive-slash-commands.js +2 -5
  245. package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
  246. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  247. package/dist/modes/interactive/interactive-startup.js +6 -0
  248. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  249. package/dist/modes/interactive/interactive-transcript-follow.d.ts +2 -0
  250. package/dist/modes/interactive/interactive-transcript-follow.d.ts.map +1 -0
  251. package/dist/modes/interactive/interactive-transcript-follow.js +6 -0
  252. package/dist/modes/interactive/interactive-transcript-follow.js.map +1 -0
  253. package/dist/modes/interactive/interactive-tui.d.ts +13 -1
  254. package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
  255. package/dist/modes/interactive/interactive-tui.js +59 -1
  256. package/dist/modes/interactive/interactive-tui.js.map +1 -1
  257. package/dist/modes/interactive-engine/engine-custom-ui.d.ts +2 -0
  258. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  259. package/dist/modes/interactive-engine/engine-custom-ui.js +1 -0
  260. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  261. package/dist/modes/interactive-engine/protocol.d.ts +1 -0
  262. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  263. package/dist/modes/interactive-engine/protocol.js +5 -1
  264. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  265. package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
  266. package/dist/modes/interactive-engine/remote-component.js +21 -9
  267. package/dist/modes/interactive-engine/remote-component.js.map +1 -1
  268. package/docs/environment-variables.md +1 -1
  269. package/docs/intercom.md +20 -21
  270. package/docs/keybindings.md +2 -5
  271. package/docs/quickstart.md +5 -1
  272. package/docs/sdk.md +1 -1
  273. package/docs/session-format.md +15 -0
  274. package/docs/sessions.md +8 -0
  275. package/docs/settings.md +6 -0
  276. package/docs/subagents.md +19 -31
  277. package/docs/tools.md +3 -5
  278. package/docs/usage.md +3 -1
  279. package/docs/windows.md +1 -1
  280. package/docs/workflows.md +60 -32
  281. package/npm-shrinkwrap.json +29 -29
  282. package/package.json +2 -2
  283. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +0 -331
  284. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +0 -161
  285. package/dist/builtin/subagents/src/runs/inprocess/background-single.ts +0 -195
  286. package/dist/builtin/subagents/src/runs/inprocess/background.ts +0 -90
  287. package/dist/builtin/subagents/src/shared/types-async.ts +0 -247
  288. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +0 -217
  289. package/dist/builtin/subagents/src/tui/render-widget.ts +0 -366
  290. package/dist/core/async/format.d.ts +0 -3
  291. package/dist/core/async/format.d.ts.map +0 -1
  292. package/dist/core/async/format.js +0 -52
  293. package/dist/core/async/format.js.map +0 -1
  294. package/dist/core/async/job-manager.d.ts +0 -40
  295. package/dist/core/async/job-manager.d.ts.map +0 -1
  296. package/dist/core/async/job-manager.js +0 -293
  297. package/dist/core/async/job-manager.js.map +0 -1
  298. package/dist/core/async/session-manager.d.ts +0 -16
  299. package/dist/core/async/session-manager.d.ts.map +0 -1
  300. package/dist/core/async/session-manager.js +0 -34
  301. package/dist/core/async/session-manager.js.map +0 -1
  302. package/dist/core/async/types.d.ts +0 -23
  303. package/dist/core/async/types.d.ts.map +0 -1
  304. package/dist/core/async/types.js +0 -2
  305. package/dist/core/async/types.js.map +0 -1
  306. package/dist/core/tools/bash-async-execution.d.ts +0 -27
  307. package/dist/core/tools/bash-async-execution.d.ts.map +0 -1
  308. package/dist/core/tools/bash-async-execution.js +0 -81
  309. package/dist/core/tools/bash-async-execution.js.map +0 -1
  310. package/dist/core/tools/bash-async-jobs.d.ts +0 -30
  311. package/dist/core/tools/bash-async-jobs.d.ts.map +0 -1
  312. package/dist/core/tools/bash-async-jobs.js +0 -87
  313. package/dist/core/tools/bash-async-jobs.js.map +0 -1
  314. package/dist/core/tools/bash-async-output.d.ts +0 -13
  315. package/dist/core/tools/bash-async-output.d.ts.map +0 -1
  316. package/dist/core/tools/bash-async-output.js +0 -122
  317. package/dist/core/tools/bash-async-output.js.map +0 -1
@@ -52,7 +52,7 @@ export function createStageContext(input: {
52
52
  try {
53
53
  runtime.mcpScope.clear();
54
54
  } finally {
55
- runtime.captureStageSessionMeta();
55
+ void runtime.captureStageSessionMeta();
56
56
  runtime.applyModelFallbackMeta(innerCtx.__modelFallbackMeta());
57
57
  }
58
58
  }
@@ -28,7 +28,7 @@ export function createStageControlHandle(runtime: LiveStageRuntime): StageContro
28
28
  const sessionFile = messagePreparation().sessionFile;
29
29
  if (sessionFile === undefined) return;
30
30
  await runtime.innerCtx.__ensureSessionFromFile(sessionFile);
31
- runtime.captureStageSessionMeta();
31
+ await runtime.captureStageSessionMeta({ awaitDurable: true });
32
32
  };
33
33
  const toolExecutions = new StageToolExecutionBuffer();
34
34
  const queuedUserMessages = new StageQueuedUserMessageBuffer();
@@ -75,7 +75,7 @@ export function createStageControlHandle(runtime: LiveStageRuntime): StageContro
75
75
  await ensureMessagingSession();
76
76
  await runtime.innerCtx.__ensureSession();
77
77
  runtime.throwIfStageMutationBlocked();
78
- runtime.captureStageSessionMeta();
78
+ await runtime.captureStageSessionMeta({ awaitDurable: true });
79
79
  },
80
80
  async sendUserMessage(text, options, beforeDelivery) {
81
81
  runtime.throwIfStageMutationBlocked();
@@ -91,7 +91,7 @@ export function createStageControlHandle(runtime: LiveStageRuntime): StageContro
91
91
  }
92
92
  return action;
93
93
  } finally {
94
- runtime.captureStageSessionMeta();
94
+ void runtime.captureStageSessionMeta();
95
95
  }
96
96
  },
97
97
  async prompt(text: string) {
@@ -106,7 +106,7 @@ export function createStageControlHandle(runtime: LiveStageRuntime): StageContro
106
106
  preparation,
107
107
  );
108
108
  } finally {
109
- runtime.captureStageSessionMeta();
109
+ void runtime.captureStageSessionMeta();
110
110
  }
111
111
  if (action !== "handled") runtime.throwIfStageMutationBlocked();
112
112
  },
@@ -124,7 +124,7 @@ export function createStageControlHandle(runtime: LiveStageRuntime): StageContro
124
124
  await runtime.innerCtx.steer(text);
125
125
  if (queuedIntoInFlightTurn) runtime.state.resumeContinuationPending = "queued-user-message";
126
126
  } finally {
127
- runtime.captureStageSessionMeta();
127
+ void runtime.captureStageSessionMeta();
128
128
  }
129
129
  },
130
130
  async followUp(text: string) {
@@ -137,7 +137,7 @@ export function createStageControlHandle(runtime: LiveStageRuntime): StageContro
137
137
  await runtime.innerCtx.followUp(text);
138
138
  if (queuedIntoInFlightTurn) runtime.state.resumeContinuationPending = "queued-user-message";
139
139
  } finally {
140
- runtime.captureStageSessionMeta();
140
+ void runtime.captureStageSessionMeta();
141
141
  }
142
142
  },
143
143
  async pause() {
@@ -206,7 +206,7 @@ export function createStageControlHandle(runtime: LiveStageRuntime): StageContro
206
206
  }
207
207
  throw err;
208
208
  } finally {
209
- runtime.captureStageSessionMeta();
209
+ void runtime.captureStageSessionMeta();
210
210
  }
211
211
  },
212
212
  subscribe(listener: AgentSessionEventListener) {
@@ -40,6 +40,7 @@ import {
40
40
  type InternalStageContext,
41
41
  type StageAdapters,
42
42
  } from "./stage-runner.js";
43
+ import { createStageSessionHeartbeat } from "./stage-session-heartbeat.js";
43
44
 
44
45
  export function createWorkflowStageFactory(input: {
45
46
  readonly runId: string;
@@ -151,6 +152,15 @@ export function createWorkflowStageFactory(input: {
151
152
  if (meta.modelAttempts !== undefined) stageSnapshot.modelAttempts = meta.modelAttempts;
152
153
  };
153
154
 
155
+ let runtime!: LiveStageRuntime;
156
+ const stageSessionHeartbeat = createStageSessionHeartbeat(async () => {
157
+ try {
158
+ await runtime.captureStageSessionMeta({ awaitDurable: true });
159
+ } catch (error) {
160
+ await runtime.innerCtx.abort().catch(() => {});
161
+ throw error;
162
+ }
163
+ });
154
164
  const innerCtx = createInnerStageContext({
155
165
  stageId,
156
166
  stageName: name,
@@ -166,6 +176,11 @@ export function createWorkflowStageFactory(input: {
166
176
  applyModelFallbackMeta(meta);
167
177
  if (stageSnapshot.status === "running") input.activeStore.recordStageStart(input.runId, stageSnapshot);
168
178
  },
179
+ async onSessionReady() {
180
+ if (stageSnapshot.status !== "running") return;
181
+ await runtime.captureStageSessionMeta({ awaitDurable: true });
182
+ runtime.startStageSessionHeartbeat();
183
+ },
169
184
  });
170
185
 
171
186
  const state: LiveStageMutableState = {
@@ -234,21 +249,23 @@ export function createWorkflowStageFactory(input: {
234
249
  await innerCtx.__dispose();
235
250
  };
236
251
 
237
- let runtime: LiveStageRuntime;
238
- const captureStageSessionMeta = (checkpointOptions?: StageSessionCheckpointOptions): unknown => {
252
+ const captureStageSessionMeta = async (checkpointOptions?: StageSessionCheckpointOptions): Promise<void> => {
239
253
  const meta = innerCtx.__sessionMeta();
240
254
  if (meta.sessionId !== undefined) stageSnapshot.sessionId = meta.sessionId;
241
255
  if (meta.sessionFile !== undefined) stageSnapshot.sessionFile = meta.sessionFile;
242
256
  if (meta.sessionId !== undefined || meta.sessionFile !== undefined)
243
257
  input.activeStore.recordStageSession(input.runId, stageId, meta);
244
258
  const pending = input.opts.onStageSession?.(input.runId, stageSnapshot, checkpointOptions);
245
- if (checkpointOptions?.forceDurable === true) return pending;
259
+ if (checkpointOptions?.forceDurable === true || checkpointOptions?.awaitDurable === true) {
260
+ await pending;
261
+ return;
262
+ }
246
263
  void Promise.resolve(pending).catch(() => {});
247
- return undefined;
248
264
  };
249
265
  const releaseLiveHandle = async (): Promise<void> => {
250
266
  if (state.liveHandleReleased) return;
251
267
  state.liveHandleReleased = true;
268
+ stageSessionHeartbeat.stop();
252
269
  runtime.dropStageControlHandle();
253
270
  runtime.unregisterStageHandle();
254
271
  await disposeInnerContext();
@@ -282,6 +299,7 @@ export function createWorkflowStageFactory(input: {
282
299
  };
283
300
 
284
301
  const finalizeStageSnapshot = async (): Promise<boolean> => {
302
+ stageSessionHeartbeat.stop();
285
303
  if (state.stageFinalized) return false;
286
304
  if (stageSnapshot.endedAt !== undefined && isTerminalStage(stageSnapshot)) {
287
305
  state.stageFinalized = true;
@@ -371,6 +389,9 @@ export function createWorkflowStageFactory(input: {
371
389
  dropStageControlHandle: () => {},
372
390
  unregisterWorkflowExitCleanup: () => {},
373
391
  captureStageSessionMeta,
392
+ startStageSessionHeartbeat: stageSessionHeartbeat.start,
393
+ drainStageSessionHeartbeat: stageSessionHeartbeat.drain,
394
+ raceStageSessionHeartbeat: stageSessionHeartbeat.race,
374
395
  applyModelFallbackMeta,
375
396
  appendStageStartOnce,
376
397
  finalizeStageSnapshot,
@@ -51,7 +51,10 @@ export interface LiveStageRuntime {
51
51
  unregisterStageHandle: () => void;
52
52
  dropStageControlHandle: () => void;
53
53
  unregisterWorkflowExitCleanup: () => void;
54
- readonly captureStageSessionMeta: (options?: StageSessionCheckpointOptions) => unknown;
54
+ readonly captureStageSessionMeta: (options?: StageSessionCheckpointOptions) => Promise<void>;
55
+ readonly startStageSessionHeartbeat: () => void;
56
+ readonly drainStageSessionHeartbeat: () => Promise<void>;
57
+ readonly raceStageSessionHeartbeat: <T>(work: Promise<T>) => Promise<T>;
55
58
  readonly applyModelFallbackMeta: (meta: ReturnType<InternalStageContext["__modelFallbackMeta"]>) => void;
56
59
  readonly appendStageStartOnce: () => void;
57
60
  readonly finalizeStageSnapshot: () => Promise<boolean>;
@@ -34,6 +34,8 @@ export interface RunContinuationOpts {
34
34
 
35
35
  export interface StageSessionCheckpointOptions {
36
36
  readonly forceDurable?: boolean;
37
+ /** Await and surface the persistence result without bypassing duration bucketing. */
38
+ readonly awaitDurable?: boolean;
37
39
  }
38
40
  export interface RunOpts
39
41
  extends Omit<
@@ -14,6 +14,7 @@ import type {
14
14
  StageUserMessageContent,
15
15
  WorkflowModelAttempt,
16
16
  WorkflowModelCatalogPort,
17
+ WorkflowModelUsage,
17
18
  } from "../../shared/types.js";
18
19
  import {
19
20
  buildModelCandidatesFromCatalog,
@@ -67,6 +68,58 @@ import {
67
68
 
68
69
  type RetryPauseResume = NonNullable<ReturnType<StageSessionPause["currentResume"]>>;
69
70
 
71
+ type StageMessage = StageSessionRuntime["messages"][number];
72
+ type StageAssistantMessage = Extract<StageMessage, { role: "assistant" }>;
73
+
74
+ type AttemptUsageTotals = {
75
+ input: number;
76
+ output: number;
77
+ cacheRead: number;
78
+ cacheWrite: number;
79
+ cost: number;
80
+ turns: number;
81
+ };
82
+
83
+ /**
84
+ * A present usage object is meaningful when any token bucket, the total token
85
+ * count, or the provider-reported total cost is positive. Reject the entire
86
+ * provider record when any aggregated value is non-finite or negative so
87
+ * malformed telemetry cannot poison a durable stage checkpoint.
88
+ */
89
+ function hasMeaningfulUsage(usage: StageAssistantMessage["usage"] | undefined): boolean {
90
+ if (usage === undefined) return false;
91
+ const values = [usage.input, usage.output, usage.cacheRead, usage.cacheWrite, usage.totalTokens, usage.cost.total];
92
+ return values.every((value) => Number.isFinite(value) && value >= 0) && values.some((value) => value > 0);
93
+ }
94
+
95
+ /**
96
+ * Aggregate the assistant-message usage admitted since the latched attempt
97
+ * boundary, plus usage accrued from error assistants that a retry restoration
98
+ * removed from the live transcript. Returns undefined when nothing meaningful
99
+ * was seen, so no `usage: undefined` enters the serializable attempt record.
100
+ */
101
+ function usageForAttempt(
102
+ messages: readonly StageMessage[],
103
+ start: number,
104
+ discarded: AttemptUsageTotals,
105
+ ): WorkflowModelUsage | undefined {
106
+ const totals = { ...discarded };
107
+ let meaningful = totals.turns > 0;
108
+
109
+ for (const message of messages.slice(start)) {
110
+ if (message.role !== "assistant" || !hasMeaningfulUsage(message.usage)) continue;
111
+ totals.input += message.usage.input;
112
+ totals.output += message.usage.output;
113
+ totals.cacheRead += message.usage.cacheRead;
114
+ totals.cacheWrite += message.usage.cacheWrite;
115
+ totals.cost += message.usage.cost.total;
116
+ totals.turns += 1;
117
+ meaningful = true;
118
+ }
119
+
120
+ return meaningful ? totals : undefined;
121
+ }
122
+
70
123
  interface ThrownErrorRetryState {
71
124
  readonly controller: AbortController;
72
125
  pauseResume?: RetryPauseResume;
@@ -159,6 +212,17 @@ export class StageSessionController {
159
212
  private sessionPromise: Promise<StageSessionRuntime> | undefined;
160
213
  private reattachSessionFile: string | undefined;
161
214
  private lastPromptStartIndex: number | undefined;
215
+ /** Message index latched once per high-level candidate prompt; never re-read later. */
216
+ private attemptUsageStartIndex: number | undefined;
217
+ /** Usage accrued from error assistants removed by retry restoration. */
218
+ private discardedAttemptUsage: AttemptUsageTotals = {
219
+ input: 0,
220
+ output: 0,
221
+ cacheRead: 0,
222
+ cacheWrite: 0,
223
+ cost: 0,
224
+ turns: 0,
225
+ };
162
226
  private readonly terminatingToolCallIds = new Set<string>();
163
227
  private latestStructuredOutputToolErrorValue: string | undefined;
164
228
  private unsubscribeTerminateWatcher: (() => void) | undefined;
@@ -418,6 +482,7 @@ export class StageSessionController {
418
482
  this.activeCandidateIndex = index;
419
483
  this.selectedModel = candidate.id;
420
484
  this.notifyModelFallbackMetaChange();
485
+ this.beginAttemptUsage(activeSession);
421
486
  const { terminalScanStartIndex } = await this.promptWithThrownErrorRetry(
422
487
  activeSession,
423
488
  promptText,
@@ -563,6 +628,49 @@ export class StageSessionController {
563
628
  }
564
629
  return undefined;
565
630
  }
631
+ /**
632
+ * Latch a fresh high-level usage window against the active session. The
633
+ * boundary is the message length before the candidate prompt, so retries,
634
+ * pause/re-prompts, and recording never re-read a later index.
635
+ */
636
+ private beginAttemptUsage(session: StageSessionRuntime): void {
637
+ this.attemptUsageStartIndex = session.messages.length;
638
+ this.discardedAttemptUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
639
+ }
640
+
641
+ /**
642
+ * Consume the current window exactly once. A session-creation failure has no
643
+ * window: clear the latch instead of reading a previous session's messages.
644
+ */
645
+ private takeAttemptUsage(session: StageSessionRuntime | undefined): WorkflowModelUsage | undefined {
646
+ const start = this.attemptUsageStartIndex;
647
+ const usage =
648
+ session === undefined || start === undefined
649
+ ? undefined
650
+ : usageForAttempt(session.messages, start, this.discardedAttemptUsage);
651
+ this.clearAttemptUsage();
652
+ return usage;
653
+ }
654
+
655
+ private clearAttemptUsage(): void {
656
+ this.attemptUsageStartIndex = undefined;
657
+ this.discardedAttemptUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
658
+ }
659
+
660
+ /**
661
+ * Keep the usage of an error assistant that a retry restoration is about to
662
+ * remove from the live transcript, so the final attempt total still counts it.
663
+ */
664
+ private accrueDiscardedAttemptUsage(message: StageAssistantMessage): void {
665
+ if (!hasMeaningfulUsage(message.usage)) return;
666
+ this.discardedAttemptUsage.input += message.usage.input;
667
+ this.discardedAttemptUsage.output += message.usage.output;
668
+ this.discardedAttemptUsage.cacheRead += message.usage.cacheRead;
669
+ this.discardedAttemptUsage.cacheWrite += message.usage.cacheWrite;
670
+ this.discardedAttemptUsage.cost += message.usage.cost.total;
671
+ this.discardedAttemptUsage.turns += 1;
672
+ }
673
+
566
674
  /**
567
675
  * Drop this attempt's failed input from live state before a same-candidate
568
676
  * retry, keeping unrelated concurrent messages. The durable transcript keeps
@@ -581,6 +689,12 @@ export class StageSessionController {
581
689
  ): StageSessionRuntime["messages"][number] | undefined {
582
690
  const snapshotMessages = new Set(snapshot);
583
691
  const admitted = session.messages.filter((message) => !snapshotMessages.has(message));
692
+ // Error assistants the filter below removes still carry real provider
693
+ // usage; accrue it before the splice so the retry total keeps it.
694
+ for (const message of admitted) {
695
+ if (message.role !== "assistant" || message.stopReason !== "error") continue;
696
+ this.accrueDiscardedAttemptUsage(message);
697
+ }
584
698
  const failedAssistantIndex = admitted.findLastIndex(
585
699
  (message) => message.role === "assistant" && message.stopReason === "error",
586
700
  );
@@ -1054,9 +1168,12 @@ export class StageSessionController {
1054
1168
  throw new Error(`atomic-workflows: stage "${this.opts.stageName}" session has been disposed`);
1055
1169
  throw this.staleCreationReason(startGeneration);
1056
1170
  }
1057
- return attachCreatedStageSession(created, this.disposed, this.opts.stageName, (result) =>
1171
+ const session = attachCreatedStageSession(created, this.disposed, this.opts.stageName, (result) =>
1058
1172
  this.attachSession(result),
1059
1173
  );
1174
+ if (session instanceof Promise) return await session;
1175
+ await this.opts.onSessionReady?.();
1176
+ return session;
1060
1177
  }
1061
1178
 
1062
1179
  private attachSession(created: StageSessionRuntime | StageSessionCreateResult): StageSessionRuntime {
@@ -1175,11 +1292,17 @@ export class StageSessionController {
1175
1292
  const resumedSession = this.session;
1176
1293
  const resumedLabel = this.selectedModel ?? workflowModelId(resumedSession.model) ?? candidates[0]!.id;
1177
1294
  this.notifyModelFallbackMetaChange();
1295
+ this.beginAttemptUsage(resumedSession);
1178
1296
  try {
1179
1297
  const { terminalScanStartIndex } = await this.promptWithThrownErrorRetry(resumedSession, text, sdkOptions);
1180
1298
  const terminalFailure = latestTerminalAssistantFailureSince(resumedSession.messages, terminalScanStartIndex);
1181
1299
  if (terminalFailure === undefined || this.capturedStructuredOutputForAttempt()) {
1182
- this.modelAttempts.push({ model: resumedLabel, success: true });
1300
+ const usage = this.takeAttemptUsage(resumedSession);
1301
+ this.modelAttempts.push({
1302
+ model: resumedLabel,
1303
+ success: true,
1304
+ ...(usage === undefined ? {} : { usage }),
1305
+ });
1183
1306
  this.pendingFallbackWarnings.length = 0;
1184
1307
  this.resumeCurrentSession = true;
1185
1308
  return true;
@@ -1187,13 +1310,24 @@ export class StageSessionController {
1187
1310
  throw new WorkflowPromptModelFailure(terminalFailure);
1188
1311
  } catch (err) {
1189
1312
  if (this.capturedStructuredOutputForAttempt() && isRetryableModelFailure(err)) {
1190
- this.modelAttempts.push({ model: resumedLabel, success: true });
1313
+ const usage = this.takeAttemptUsage(resumedSession);
1314
+ this.modelAttempts.push({
1315
+ model: resumedLabel,
1316
+ success: true,
1317
+ ...(usage === undefined ? {} : { usage }),
1318
+ });
1191
1319
  this.pendingFallbackWarnings.length = 0;
1192
1320
  this.resumeCurrentSession = true;
1193
1321
  return true;
1194
1322
  }
1195
1323
  const message = errorMessage(err);
1196
- this.modelAttempts.push({ model: resumedLabel, success: false, error: message });
1324
+ const usage = this.takeAttemptUsage(resumedSession);
1325
+ this.modelAttempts.push({
1326
+ model: resumedLabel,
1327
+ success: false,
1328
+ ...(usage === undefined ? {} : { usage }),
1329
+ error: message,
1330
+ });
1197
1331
  if (this.opts.signal?.aborted || !isRetryableModelFailure(err)) {
1198
1332
  this.modelWarnings.push(...this.pendingFallbackWarnings);
1199
1333
  this.pendingFallbackWarnings.length = 0;
@@ -1233,10 +1367,12 @@ export class StageSessionController {
1233
1367
  this.recordSuccessfulAttempt(candidate);
1234
1368
  return "handled";
1235
1369
  }
1370
+ const usage = this.takeAttemptUsage(this.session);
1236
1371
  this.modelAttempts.push({
1237
1372
  model: candidate.id,
1238
1373
  success: false,
1239
1374
  ...modelAttemptReasoning(candidate, this.effectiveStageOptions?.thinkingLevel),
1375
+ ...(usage === undefined ? {} : { usage }),
1240
1376
  error: message,
1241
1377
  });
1242
1378
  if (this.opts.signal?.aborted || !isRetryableModelFailure(err) || index === candidates.length - 1) {
@@ -1258,10 +1394,12 @@ export class StageSessionController {
1258
1394
  }
1259
1395
 
1260
1396
  private recordSuccessfulAttempt(candidate: WorkflowResolvedModelCandidate): void {
1397
+ const usage = this.takeAttemptUsage(this.session);
1261
1398
  this.modelAttempts.push({
1262
1399
  model: candidate.id,
1263
1400
  success: true,
1264
1401
  ...modelAttemptReasoning(candidate, this.effectiveStageOptions?.thinkingLevel),
1402
+ ...(usage === undefined ? {} : { usage }),
1265
1403
  });
1266
1404
  this.pendingFallbackWarnings.length = 0;
1267
1405
  this.resumeCurrentSession = true;
@@ -147,6 +147,8 @@ export interface StageRunnerOpts {
147
147
  defaultSessionDir?: string;
148
148
  /** Internal: notifies the executor when an in-flight fallback changes model/fast metadata. */
149
149
  onModelFallbackMetaChange?: (meta: StageModelFallbackMeta) => void;
150
+ /** Internal: persist stage-session identity once the SDK has created its path. */
151
+ onSessionReady?: () => void | Promise<void>;
150
152
  }
151
153
 
152
154
  export interface InternalStageContext extends StageContext {
@@ -0,0 +1,88 @@
1
+ export const STAGE_SESSION_HEARTBEAT_INTERVAL_MS = 30_000;
2
+
3
+ export interface StageSessionHeartbeat {
4
+ start(): void;
5
+ stop(): void;
6
+ /**
7
+ * Stop scheduling and await the checkpoint that is still in flight, so a late
8
+ * durable failure surfaces at shutdown instead of being discarded with the
9
+ * cancelled timer. Rejects with a failure recorded earlier via `fail()` too.
10
+ */
11
+ drain(): Promise<void>;
12
+ fail(error: unknown): void;
13
+ race<T>(work: Promise<T>): Promise<T>;
14
+ }
15
+
16
+ /** Run one non-overlapping, unref'd durability checkpoint every 30 seconds. */
17
+ export function createStageSessionHeartbeat(
18
+ checkpoint: () => Promise<void>,
19
+ intervalMs: number = STAGE_SESSION_HEARTBEAT_INTERVAL_MS,
20
+ ): StageSessionHeartbeat {
21
+ let active = false;
22
+ let timer: ReturnType<typeof setTimeout> | undefined;
23
+ let generation = 0;
24
+ let nextDeadline = 0;
25
+ let failure: unknown;
26
+ let inFlight: Promise<void> | undefined;
27
+ let rejectFailure!: (reason: unknown) => void;
28
+ const failureSignal = new Promise<never>((_resolve, reject) => {
29
+ rejectFailure = reject;
30
+ });
31
+ // A race attaches the normal consumer; this guard prevents a late checkpoint
32
+ // rejection from becoming an unhandled process error during teardown.
33
+ void failureSignal.catch(() => undefined);
34
+ const stopScheduling = (): void => {
35
+ active = false;
36
+ generation += 1;
37
+ if (timer !== undefined) clearTimeout(timer);
38
+ timer = undefined;
39
+ };
40
+ const fail = (error: unknown): void => {
41
+ if (failure !== undefined) return;
42
+ failure = error;
43
+ stopScheduling();
44
+ rejectFailure(error);
45
+ };
46
+
47
+ const schedule = (scheduledGeneration: number): void => {
48
+ if (!active || failure !== undefined || scheduledGeneration !== generation) return;
49
+ const delayMs = Math.max(0, nextDeadline - Date.now());
50
+ timer = setTimeout(() => {
51
+ timer = undefined;
52
+ const startedAt = Date.now();
53
+ do nextDeadline += intervalMs;
54
+ while (nextDeadline <= startedAt);
55
+ const running = checkpoint()
56
+ .catch(fail)
57
+ .finally(() => {
58
+ if (inFlight === running) inFlight = undefined;
59
+ schedule(scheduledGeneration);
60
+ });
61
+ inFlight = running;
62
+ }, delayMs);
63
+ (timer as { unref?: () => void }).unref?.();
64
+ };
65
+
66
+ const drain = async (): Promise<void> => {
67
+ stopScheduling();
68
+ await inFlight;
69
+ if (failure !== undefined) throw failure;
70
+ };
71
+
72
+ return {
73
+ start(): void {
74
+ if (active || failure !== undefined) return;
75
+ active = true;
76
+ generation += 1;
77
+ nextDeadline = Date.now() + intervalMs;
78
+ schedule(generation);
79
+ },
80
+ stop: stopScheduling,
81
+ drain,
82
+ fail,
83
+ race<T>(work: Promise<T>): Promise<T> {
84
+ if (failure !== undefined) return Promise.reject(failure);
85
+ return Promise.race([work, failureSignal]);
86
+ },
87
+ };
88
+ }
@@ -44,6 +44,7 @@ export const APP_ACTION = {
44
44
  } as const;
45
45
 
46
46
  export const TUI_ACTION = {
47
+ altScreenBottom: "tui.altScreen.bottom",
47
48
  editorCursorUp: "tui.editor.cursorUp",
48
49
  editorCursorDown: "tui.editor.cursorDown",
49
50
  editorCursorLeft: "tui.editor.cursorLeft",
@@ -412,6 +412,7 @@ export function buildGraphOverlayAdapter(
412
412
  // Ctrl+C (Skip / Close / cancel), so the host must forward it here
413
413
  // instead of closing this overlay on the first press.
414
414
  handlesCtrlC: true,
415
+ handlesInternalUiAction: true,
415
416
  overlayOptions: FULLSCREEN_OVERLAY_OPTIONS,
416
417
  onHandle: (handle) => {
417
418
  currentHandle = handle;
@@ -182,6 +182,7 @@ function summaryRows(detail: RunDetail, now: number): Array<[string, string | un
182
182
  if (detail.error) {
183
183
  rows.push(["error", detail.error.split("\n")[0] ?? ""]);
184
184
  }
185
+ if (detail.resumeGuidance) rows.push(["guidance", detail.resumeGuidance]);
185
186
  return rows;
186
187
  }
187
188
 
@@ -319,6 +320,8 @@ function stateBadges(detail: RunDetail, theme: GraphTheme): FlatBandBadge[] {
319
320
  switch (detail.status) {
320
321
  case "running":
321
322
  return [{ text: "● running", fg: theme.warning }];
323
+ case "crashed":
324
+ return [{ text: detail.resumable === true ? "✗ crashed · resumable" : "✗ crashed", fg: theme.error }];
322
325
  case "paused":
323
326
  return [{ text: "❚❚ paused", fg: theme.warning }];
324
327
  case "completed":
@@ -342,6 +345,8 @@ function stateLabel(detail: RunDetail): string {
342
345
  switch (detail.status) {
343
346
  case "running":
344
347
  return "● running";
348
+ case "crashed":
349
+ return detail.resumable === true ? "✗ crashed · resumable" : "✗ crashed";
345
350
  case "paused":
346
351
  return "❚❚ paused";
347
352
  case "completed":
@@ -387,18 +392,26 @@ function renderIdentifierRows(id: string, width: number, theme?: GraphTheme): st
387
392
  }
388
393
 
389
394
  function renderDetailHintRows(detail: RunDetail, width: number, theme?: GraphTheme): string[] {
390
- const prefix =
391
- detail.endedAt === undefined
392
- ? detail.status === "paused"
393
- ? " workflow resume id="
394
- : " ▸ workflow interrupt id="
395
- : " workflow resume id=";
396
- const suffix =
397
- detail.endedAt === undefined
398
- ? detail.status === "paused"
399
- ? " continue workflow "
400
- : " cancel "
401
- : " reopen graph ";
395
+ const resumable =
396
+ (detail.status === "paused" && detail.resumable !== false) ||
397
+ (detail.resumable === true &&
398
+ (detail.status === "crashed" || detail.status === "failed" || detail.status === "blocked"));
399
+ const inspectOnly =
400
+ detail.ownerActiveElsewhere === true ||
401
+ (detail.status === "crashed" && !resumable) ||
402
+ (detail.endedAt !== undefined && !resumable);
403
+ const prefix = inspectOnly
404
+ ? " workflow status id="
405
+ : resumable
406
+ ? " workflow resume id="
407
+ : " ▸ workflow interrupt id=";
408
+ const suffix = inspectOnly
409
+ ? detail.ownerActiveElsewhere === true
410
+ ? " inspect; owner active elsewhere "
411
+ : " inspect retained state "
412
+ : resumable
413
+ ? " continue workflow "
414
+ : " cancel ";
402
415
  const continuation = " ";
403
416
  const rows = wrapIdentifierLines(detail.runId, width, prefix, continuation);
404
417
  const last = rows[rows.length - 1]!;
@@ -38,6 +38,8 @@ export function renderReadOnlyArchiveBody(
38
38
  width: number,
39
39
  budget: number,
40
40
  stage: StageSnapshot | undefined,
41
+ indicatorLines: readonly string[] = [],
42
+ renderIndicator: () => readonly string[] = () => indicatorLines,
41
43
  ): string[] {
42
44
  if (stage?.promptFootprint) {
43
45
  return renderReadOnlyPromptArchiveBody(ctx, width, budget, stage);
@@ -69,8 +71,14 @@ export function renderReadOnlyArchiveBody(
69
71
  ).render(width),
70
72
  );
71
73
  const transcriptBudget = Math.max(0, budget - callout.length);
72
- const lines = transcriptBudget > 0 ? ctx.chatHost.renderBody(width, transcriptBudget) : [];
73
- lines.push(...callout);
74
+ const reservedIndicatorRows =
75
+ indicatorLines.length > 0 && transcriptBudget > indicatorLines.length ? indicatorLines.length : 0;
76
+ const transcriptRows = transcriptBudget - reservedIndicatorRows;
77
+ const lines = transcriptRows > 0 ? ctx.chatHost.renderBody(width, transcriptRows) : [];
78
+ const visibleIndicatorLines =
79
+ reservedIndicatorRows > 0 ? [...renderIndicator().slice(0, reservedIndicatorRows)] : [];
80
+ while (visibleIndicatorLines.length < reservedIndicatorRows) visibleIndicatorLines.push(blankLine(width));
81
+ lines.push(...visibleIndicatorLines, ...callout);
74
82
  while (lines.length < budget) lines.push(blankLine(width));
75
83
  if (lines.length > budget) lines.length = budget;
76
84
  return lines;
@@ -163,7 +171,13 @@ function formatReadOnlyPromptAnswer(value: unknown, kind: PendingPrompt["kind"])
163
171
  }
164
172
  }
165
173
 
166
- export function renderPausedBody(ctx: StageChatViewContext, width: number, budget: number): string[] {
174
+ export function renderPausedBody(
175
+ ctx: StageChatViewContext,
176
+ width: number,
177
+ budget: number,
178
+ indicatorLines: readonly string[] = [],
179
+ renderIndicator: () => readonly string[] = () => indicatorLines,
180
+ ): string[] {
167
181
  const t = ctx.theme;
168
182
  const callout: string[] = [];
169
183
  callout.push(blankLine(width));
@@ -175,11 +189,16 @@ export function renderPausedBody(ctx: StageChatViewContext, width: number, budge
175
189
  0,
176
190
  ).render(width),
177
191
  );
178
-
179
- const calloutRows = Math.min(callout.length, Math.max(0, budget - 1));
180
- const transcriptBudget = Math.max(1, budget - calloutRows);
181
- const lines = ctx.chatHost.renderBody(width, transcriptBudget);
182
- lines.push(...callout.slice(0, calloutRows));
192
+ const calloutRows = Math.min(callout.length, Math.max(0, budget));
193
+ const transcriptBudget = Math.max(0, budget - calloutRows);
194
+ const reservedIndicatorRows =
195
+ indicatorLines.length > 0 && transcriptBudget > indicatorLines.length ? indicatorLines.length : 0;
196
+ const transcriptRows = transcriptBudget - reservedIndicatorRows;
197
+ const lines = transcriptRows > 0 ? ctx.chatHost.renderBody(width, transcriptRows) : [];
198
+ const visibleIndicatorLines =
199
+ reservedIndicatorRows > 0 ? [...renderIndicator().slice(0, reservedIndicatorRows)] : [];
200
+ while (visibleIndicatorLines.length < reservedIndicatorRows) visibleIndicatorLines.push(blankLine(width));
201
+ lines.push(...visibleIndicatorLines, ...callout.slice(0, calloutRows));
183
202
  while (lines.length < budget) lines.push(blankLine(width));
184
203
  if (lines.length > budget) lines.length = budget;
185
204
  return lines;