@bastani/atomic 0.8.27 → 0.8.28-alpha.2

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 (397) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +120 -118
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +2 -2
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/package.json +1 -1
  7. package/dist/builtin/workflows/CHANGELOG.md +22 -0
  8. package/dist/builtin/workflows/README.md +11 -9
  9. package/dist/builtin/workflows/builtin/open-claude-design.ts +150 -13
  10. package/dist/builtin/workflows/package.json +1 -1
  11. package/dist/builtin/workflows/src/authoring.d.ts +5 -2
  12. package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +3 -1
  13. package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +17 -25
  14. package/dist/builtin/workflows/src/extension/index.ts +133 -18
  15. package/dist/builtin/workflows/src/extension/render-result.ts +22 -2
  16. package/dist/builtin/workflows/src/extension/workflow-schema.ts +3 -3
  17. package/dist/builtin/workflows/src/runs/foreground/executor.ts +210 -16
  18. package/dist/builtin/workflows/src/sdk-surface.ts +1 -1
  19. package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +42 -5
  20. package/dist/builtin/workflows/src/shared/store-types.ts +8 -2
  21. package/dist/builtin/workflows/src/shared/store.ts +51 -0
  22. package/dist/builtin/workflows/src/shared/types.ts +14 -4
  23. package/dist/builtin/workflows/src/tui/chat-surface.ts +32 -33
  24. package/dist/builtin/workflows/src/tui/graph-view.ts +4 -1
  25. package/dist/builtin/workflows/src/tui/prompt-card.ts +6 -0
  26. package/dist/builtin/workflows/src/tui/run-detail.ts +11 -4
  27. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +11 -1
  28. package/dist/builtin/workflows/src/tui/status-list.ts +32 -2
  29. package/dist/cli/args.d.ts +4 -0
  30. package/dist/cli/args.d.ts.map +1 -1
  31. package/dist/cli/args.js +35 -0
  32. package/dist/cli/args.js.map +1 -1
  33. package/dist/cli/project-trust.d.ts +10 -0
  34. package/dist/cli/project-trust.d.ts.map +1 -0
  35. package/dist/cli/project-trust.js +36 -0
  36. package/dist/cli/project-trust.js.map +1 -0
  37. package/dist/cli/startup-ui.d.ts +7 -0
  38. package/dist/cli/startup-ui.d.ts.map +1 -0
  39. package/dist/cli/startup-ui.js +57 -0
  40. package/dist/cli/startup-ui.js.map +1 -0
  41. package/dist/config.d.ts.map +1 -1
  42. package/dist/config.js +24 -3
  43. package/dist/config.js.map +1 -1
  44. package/dist/core/agent-session-runtime.d.ts +3 -1
  45. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  46. package/dist/core/agent-session-runtime.js +1 -0
  47. package/dist/core/agent-session-runtime.js.map +1 -1
  48. package/dist/core/agent-session-services.d.ts +2 -1
  49. package/dist/core/agent-session-services.d.ts.map +1 -1
  50. package/dist/core/agent-session-services.js +2 -2
  51. package/dist/core/agent-session-services.js.map +1 -1
  52. package/dist/core/agent-session.d.ts +9 -5
  53. package/dist/core/agent-session.d.ts.map +1 -1
  54. package/dist/core/agent-session.js +205 -51
  55. package/dist/core/agent-session.js.map +1 -1
  56. package/dist/core/auth-guidance.d.ts +10 -1
  57. package/dist/core/auth-guidance.d.ts.map +1 -1
  58. package/dist/core/auth-guidance.js +26 -1
  59. package/dist/core/auth-guidance.js.map +1 -1
  60. package/dist/core/auth-storage.d.ts.map +1 -1
  61. package/dist/core/auth-storage.js +4 -3
  62. package/dist/core/auth-storage.js.map +1 -1
  63. package/dist/core/compaction/branch-summarization.d.ts +5 -3
  64. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  65. package/dist/core/compaction/branch-summarization.js +16 -10
  66. package/dist/core/compaction/branch-summarization.js.map +1 -1
  67. package/dist/core/compaction/compaction.d.ts +4 -84
  68. package/dist/core/compaction/compaction.d.ts.map +1 -1
  69. package/dist/core/compaction/compaction.js +20 -502
  70. package/dist/core/compaction/compaction.js.map +1 -1
  71. package/dist/core/compaction/context-compaction.d.ts.map +1 -1
  72. package/dist/core/compaction/context-compaction.js +39 -82
  73. package/dist/core/compaction/context-compaction.js.map +1 -1
  74. package/dist/core/compaction/index.d.ts +1 -1
  75. package/dist/core/compaction/index.d.ts.map +1 -1
  76. package/dist/core/compaction/index.js +1 -1
  77. package/dist/core/compaction/index.js.map +1 -1
  78. package/dist/core/compaction/utils.d.ts +1 -1
  79. package/dist/core/compaction/utils.d.ts.map +1 -1
  80. package/dist/core/compaction/utils.js +1 -1
  81. package/dist/core/compaction/utils.js.map +1 -1
  82. package/dist/core/experimental.d.ts +2 -0
  83. package/dist/core/experimental.d.ts.map +1 -0
  84. package/dist/core/experimental.js +5 -0
  85. package/dist/core/experimental.js.map +1 -0
  86. package/dist/core/export-html/template.js +19 -6
  87. package/dist/core/extensions/index.d.ts +1 -1
  88. package/dist/core/extensions/index.d.ts.map +1 -1
  89. package/dist/core/extensions/index.js.map +1 -1
  90. package/dist/core/extensions/loader.d.ts +1 -1
  91. package/dist/core/extensions/loader.d.ts.map +1 -1
  92. package/dist/core/extensions/loader.js +6 -4
  93. package/dist/core/extensions/loader.js.map +1 -1
  94. package/dist/core/extensions/runner.d.ts +11 -4
  95. package/dist/core/extensions/runner.d.ts.map +1 -1
  96. package/dist/core/extensions/runner.js +53 -3
  97. package/dist/core/extensions/runner.js.map +1 -1
  98. package/dist/core/extensions/types.d.ts +44 -12
  99. package/dist/core/extensions/types.d.ts.map +1 -1
  100. package/dist/core/extensions/types.js.map +1 -1
  101. package/dist/core/footer-data-provider.d.ts +2 -0
  102. package/dist/core/footer-data-provider.d.ts.map +1 -1
  103. package/dist/core/footer-data-provider.js +27 -1
  104. package/dist/core/footer-data-provider.js.map +1 -1
  105. package/dist/core/index.d.ts +2 -1
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js +1 -0
  108. package/dist/core/index.js.map +1 -1
  109. package/dist/core/messages.d.ts +1 -11
  110. package/dist/core/messages.d.ts.map +1 -1
  111. package/dist/core/messages.js +10 -25
  112. package/dist/core/messages.js.map +1 -1
  113. package/dist/core/model-registry.d.ts.map +1 -1
  114. package/dist/core/model-registry.js +64 -7
  115. package/dist/core/model-registry.js.map +1 -1
  116. package/dist/core/model-resolver.d.ts.map +1 -1
  117. package/dist/core/model-resolver.js +1 -0
  118. package/dist/core/model-resolver.js.map +1 -1
  119. package/dist/core/output-guard.d.ts +1 -0
  120. package/dist/core/output-guard.d.ts.map +1 -1
  121. package/dist/core/output-guard.js +52 -22
  122. package/dist/core/output-guard.js.map +1 -1
  123. package/dist/core/package-manager.d.ts +1 -0
  124. package/dist/core/package-manager.d.ts.map +1 -1
  125. package/dist/core/package-manager.js +20 -8
  126. package/dist/core/package-manager.js.map +1 -1
  127. package/dist/core/project-trust.d.ts +15 -0
  128. package/dist/core/project-trust.d.ts.map +1 -0
  129. package/dist/core/project-trust.js +58 -0
  130. package/dist/core/project-trust.js.map +1 -0
  131. package/dist/core/prompt-templates.d.ts +5 -4
  132. package/dist/core/prompt-templates.d.ts.map +1 -1
  133. package/dist/core/prompt-templates.js +30 -29
  134. package/dist/core/prompt-templates.js.map +1 -1
  135. package/dist/core/provider-attribution.d.ts +4 -0
  136. package/dist/core/provider-attribution.d.ts.map +1 -0
  137. package/dist/core/provider-attribution.js +73 -0
  138. package/dist/core/provider-attribution.js.map +1 -0
  139. package/dist/core/provider-display-names.d.ts.map +1 -1
  140. package/dist/core/provider-display-names.js +3 -0
  141. package/dist/core/provider-display-names.js.map +1 -1
  142. package/dist/core/resolve-config-value.d.ts +9 -1
  143. package/dist/core/resolve-config-value.d.ts.map +1 -1
  144. package/dist/core/resolve-config-value.js +134 -11
  145. package/dist/core/resolve-config-value.js.map +1 -1
  146. package/dist/core/resource-loader.d.ts +12 -2
  147. package/dist/core/resource-loader.d.ts.map +1 -1
  148. package/dist/core/resource-loader.js +108 -18
  149. package/dist/core/resource-loader.js.map +1 -1
  150. package/dist/core/sdk.d.ts.map +1 -1
  151. package/dist/core/sdk.js +12 -42
  152. package/dist/core/sdk.js.map +1 -1
  153. package/dist/core/session-manager.d.ts +11 -15
  154. package/dist/core/session-manager.d.ts.map +1 -1
  155. package/dist/core/session-manager.js +111 -111
  156. package/dist/core/session-manager.js.map +1 -1
  157. package/dist/core/settings-manager.d.ts +15 -5
  158. package/dist/core/settings-manager.d.ts.map +1 -1
  159. package/dist/core/settings-manager.js +69 -14
  160. package/dist/core/settings-manager.js.map +1 -1
  161. package/dist/core/slash-commands.d.ts.map +1 -1
  162. package/dist/core/slash-commands.js +1 -0
  163. package/dist/core/slash-commands.js.map +1 -1
  164. package/dist/core/system-prompt.d.ts.map +1 -1
  165. package/dist/core/system-prompt.js +0 -3
  166. package/dist/core/system-prompt.js.map +1 -1
  167. package/dist/core/tools/bash.d.ts.map +1 -1
  168. package/dist/core/tools/bash.js +2 -1
  169. package/dist/core/tools/bash.js.map +1 -1
  170. package/dist/core/tools/edit.d.ts.map +1 -1
  171. package/dist/core/tools/edit.js +7 -10
  172. package/dist/core/tools/edit.js.map +1 -1
  173. package/dist/core/tools/find.d.ts.map +1 -1
  174. package/dist/core/tools/find.js +1 -1
  175. package/dist/core/tools/find.js.map +1 -1
  176. package/dist/core/tools/grep.d.ts.map +1 -1
  177. package/dist/core/tools/grep.js +1 -1
  178. package/dist/core/tools/grep.js.map +1 -1
  179. package/dist/core/tools/ls.d.ts.map +1 -1
  180. package/dist/core/tools/ls.js +1 -1
  181. package/dist/core/tools/ls.js.map +1 -1
  182. package/dist/core/tools/oversized-tool-result.d.ts +53 -0
  183. package/dist/core/tools/oversized-tool-result.d.ts.map +1 -0
  184. package/dist/core/tools/oversized-tool-result.js +206 -0
  185. package/dist/core/tools/oversized-tool-result.js.map +1 -0
  186. package/dist/core/tools/read.d.ts +12 -0
  187. package/dist/core/tools/read.d.ts.map +1 -1
  188. package/dist/core/tools/read.js +99 -34
  189. package/dist/core/tools/read.js.map +1 -1
  190. package/dist/core/tools/render-utils.d.ts +6 -0
  191. package/dist/core/tools/render-utils.d.ts.map +1 -1
  192. package/dist/core/tools/render-utils.js +17 -1
  193. package/dist/core/tools/render-utils.js.map +1 -1
  194. package/dist/core/tools/tool-definition-wrapper.d.ts +6 -0
  195. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  196. package/dist/core/tools/tool-definition-wrapper.js +2 -0
  197. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  198. package/dist/core/tools/tool-limits.d.ts +25 -0
  199. package/dist/core/tools/tool-limits.d.ts.map +1 -0
  200. package/dist/core/tools/tool-limits.js +25 -0
  201. package/dist/core/tools/tool-limits.js.map +1 -0
  202. package/dist/core/tools/write.d.ts.map +1 -1
  203. package/dist/core/tools/write.js +1 -1
  204. package/dist/core/tools/write.js.map +1 -1
  205. package/dist/core/trust-manager.d.ts +31 -0
  206. package/dist/core/trust-manager.d.ts.map +1 -0
  207. package/dist/core/trust-manager.js +196 -0
  208. package/dist/core/trust-manager.js.map +1 -0
  209. package/dist/index.d.ts +12 -7
  210. package/dist/index.d.ts.map +1 -1
  211. package/dist/index.js +8 -4
  212. package/dist/index.js.map +1 -1
  213. package/dist/main.d.ts.map +1 -1
  214. package/dist/main.js +142 -30
  215. package/dist/main.js.map +1 -1
  216. package/dist/migrations.d.ts +3 -1
  217. package/dist/migrations.d.ts.map +1 -1
  218. package/dist/migrations.js +325 -7
  219. package/dist/migrations.js.map +1 -1
  220. package/dist/modes/index.d.ts +1 -1
  221. package/dist/modes/index.d.ts.map +1 -1
  222. package/dist/modes/index.js.map +1 -1
  223. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  224. package/dist/modes/interactive/components/bash-execution.js +2 -2
  225. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  226. package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -5
  227. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/chat-message-renderer.js +5 -9
  229. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  230. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/chat-session-host.js +0 -3
  232. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  233. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/footer.js +6 -0
  235. package/dist/modes/interactive/components/footer.js.map +1 -1
  236. package/dist/modes/interactive/components/index.d.ts +1 -1
  237. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  238. package/dist/modes/interactive/components/index.js +1 -1
  239. package/dist/modes/interactive/components/index.js.map +1 -1
  240. package/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  241. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  242. package/dist/modes/interactive/components/login-dialog.js +9 -16
  243. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  244. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  245. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  246. package/dist/modes/interactive/components/settings-selector.js +20 -0
  247. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  248. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  249. package/dist/modes/interactive/components/tool-execution.js +22 -0
  250. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  251. package/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  252. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
  253. package/dist/modes/interactive/components/trust-selector.js +85 -0
  254. package/dist/modes/interactive/components/trust-selector.js.map +1 -0
  255. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  256. package/dist/modes/interactive/components/user-message.js +1 -1
  257. package/dist/modes/interactive/components/user-message.js.map +1 -1
  258. package/dist/modes/interactive/interactive-mode.d.ts +9 -0
  259. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  260. package/dist/modes/interactive/interactive-mode.js +134 -36
  261. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  262. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  263. package/dist/modes/interactive/theme/theme.js +10 -0
  264. package/dist/modes/interactive/theme/theme.js.map +1 -1
  265. package/dist/modes/print-mode.d.ts.map +1 -1
  266. package/dist/modes/print-mode.js +1 -0
  267. package/dist/modes/print-mode.js.map +1 -1
  268. package/dist/modes/rpc/rpc-client.d.ts +4 -1
  269. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  270. package/dist/modes/rpc/rpc-client.js +52 -8
  271. package/dist/modes/rpc/rpc-client.js.map +1 -1
  272. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  273. package/dist/modes/rpc/rpc-mode.js +24 -5
  274. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  275. package/dist/modes/rpc/rpc-types.d.ts +1 -1
  276. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  277. package/dist/modes/rpc/rpc-types.js.map +1 -1
  278. package/dist/package-manager-cli.d.ts +6 -2
  279. package/dist/package-manager-cli.d.ts.map +1 -1
  280. package/dist/package-manager-cli.js +104 -10
  281. package/dist/package-manager-cli.js.map +1 -1
  282. package/dist/utils/changelog.d.ts +1 -0
  283. package/dist/utils/changelog.d.ts.map +1 -1
  284. package/dist/utils/changelog.js +72 -0
  285. package/dist/utils/changelog.js.map +1 -1
  286. package/dist/utils/deprecation.d.ts +4 -0
  287. package/dist/utils/deprecation.d.ts.map +1 -0
  288. package/dist/utils/deprecation.js +13 -0
  289. package/dist/utils/deprecation.js.map +1 -0
  290. package/dist/utils/git.d.ts.map +1 -1
  291. package/dist/utils/git.js +54 -22
  292. package/dist/utils/git.js.map +1 -1
  293. package/dist/utils/json.d.ts +3 -0
  294. package/dist/utils/json.d.ts.map +1 -0
  295. package/dist/utils/json.js +7 -0
  296. package/dist/utils/json.js.map +1 -0
  297. package/dist/utils/open-browser.d.ts +9 -0
  298. package/dist/utils/open-browser.d.ts.map +1 -0
  299. package/dist/utils/open-browser.js +22 -0
  300. package/dist/utils/open-browser.js.map +1 -0
  301. package/docs/compaction.md +210 -181
  302. package/docs/containerization.md +111 -0
  303. package/docs/custom-provider.md +9 -9
  304. package/docs/development.md +1 -1
  305. package/docs/docs.json +2 -0
  306. package/docs/extensions.md +71 -24
  307. package/docs/index.md +2 -0
  308. package/docs/json.md +3 -4
  309. package/docs/models.md +10 -10
  310. package/docs/packages.md +1 -1
  311. package/docs/prompt-templates.md +9 -2
  312. package/docs/providers.md +18 -5
  313. package/docs/quickstart.md +1 -0
  314. package/docs/rpc.md +3 -2
  315. package/docs/sdk.md +5 -0
  316. package/docs/security.md +56 -0
  317. package/docs/session-format.md +14 -23
  318. package/docs/sessions.md +11 -1
  319. package/docs/settings.md +23 -9
  320. package/docs/skills.md +1 -1
  321. package/docs/terminal-setup.md +44 -2
  322. package/docs/themes.md +1 -1
  323. package/docs/tmux.md +4 -2
  324. package/docs/tui.md +14 -5
  325. package/docs/usage.md +17 -3
  326. package/docs/workflows.md +11 -9
  327. package/examples/README.md +1 -1
  328. package/examples/extensions/README.md +9 -6
  329. package/examples/extensions/bash-spawn-hook.ts +1 -1
  330. package/examples/extensions/built-in-tool-renderer.ts +1 -1
  331. package/examples/extensions/claude-rules.ts +1 -1
  332. package/examples/extensions/commands.ts +1 -1
  333. package/examples/extensions/custom-compaction.ts +43 -106
  334. package/examples/extensions/custom-header.ts +1 -1
  335. package/examples/extensions/custom-provider-anthropic/index.ts +3 -3
  336. package/examples/extensions/custom-provider-anthropic/package-lock.json +4 -4
  337. package/examples/extensions/custom-provider-anthropic/package.json +6 -6
  338. package/examples/extensions/custom-provider-gitlab-duo/index.ts +55 -4
  339. package/examples/extensions/custom-provider-gitlab-duo/package.json +3 -3
  340. package/examples/extensions/doom-overlay/README.md +1 -1
  341. package/examples/extensions/doom-overlay/index.ts +2 -2
  342. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  343. package/examples/extensions/gondolin/index.ts +523 -0
  344. package/examples/extensions/gondolin/package-lock.json +185 -0
  345. package/examples/extensions/gondolin/package.json +19 -0
  346. package/examples/extensions/handoff.ts +7 -45
  347. package/examples/extensions/hidden-thinking-label.ts +1 -1
  348. package/examples/extensions/inline-bash.ts +2 -2
  349. package/examples/extensions/input-transform-streaming.ts +39 -0
  350. package/examples/extensions/input-transform.ts +3 -3
  351. package/examples/extensions/interactive-shell.ts +2 -2
  352. package/examples/extensions/mac-system-theme.ts +2 -2
  353. package/examples/extensions/minimal-mode.ts +1 -1
  354. package/examples/extensions/modal-editor.ts +1 -1
  355. package/examples/extensions/model-status.ts +1 -1
  356. package/examples/extensions/overlay-qa-tests.ts +198 -179
  357. package/examples/extensions/overlay-test.ts +1 -1
  358. package/examples/extensions/pirate.ts +1 -1
  359. package/examples/extensions/preset.ts +14 -12
  360. package/examples/extensions/project-trust.ts +64 -0
  361. package/examples/extensions/prompt-customizer.ts +1 -1
  362. package/examples/extensions/qna.ts +1 -1
  363. package/examples/extensions/question.ts +1 -1
  364. package/examples/extensions/questionnaire.ts +1 -1
  365. package/examples/extensions/rainbow-editor.ts +1 -1
  366. package/examples/extensions/sandbox/index.ts +16 -14
  367. package/examples/extensions/sandbox/package-lock.json +90 -90
  368. package/examples/extensions/sandbox/package.json +17 -17
  369. package/examples/extensions/snake.ts +1 -1
  370. package/examples/extensions/space-invaders.ts +1 -1
  371. package/examples/extensions/ssh.ts +2 -2
  372. package/examples/extensions/subagent/README.md +13 -13
  373. package/examples/extensions/subagent/agents.ts +4 -2
  374. package/examples/extensions/subagent/index.ts +6 -6
  375. package/examples/extensions/summarize.ts +1 -1
  376. package/examples/extensions/tic-tac-toe.ts +1 -1
  377. package/examples/extensions/titlebar-spinner.ts +1 -1
  378. package/examples/extensions/todo.ts +1 -1
  379. package/examples/extensions/tool-override.ts +1 -1
  380. package/examples/extensions/tools.ts +6 -1
  381. package/examples/extensions/trigger-compact.ts +5 -4
  382. package/examples/extensions/with-deps/package-lock.json +4 -4
  383. package/examples/extensions/with-deps/package.json +7 -7
  384. package/examples/extensions/working-indicator.ts +4 -4
  385. package/examples/extensions/working-message-test.ts +1 -1
  386. package/examples/sdk/01-minimal.ts +1 -1
  387. package/examples/sdk/03-custom-prompt.ts +1 -1
  388. package/examples/sdk/04-skills.ts +1 -1
  389. package/examples/sdk/06-extensions.ts +2 -2
  390. package/examples/sdk/08-prompt-templates.ts +1 -1
  391. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  392. package/examples/sdk/README.md +2 -2
  393. package/package.json +8 -8
  394. package/dist/modes/interactive/components/compaction-summary-message.d.ts +0 -16
  395. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +0 -1
  396. package/dist/modes/interactive/components/compaction-summary-message.js +0 -43
  397. package/dist/modes/interactive/components/compaction-summary-message.js.map +0 -1
@@ -129,7 +129,7 @@ export const WORKFLOW_TOOL_DESCRIPTION =
129
129
  "For large stage handoffs, write context to files/artifacts, pass paths via reads, and prompt downstream agents to 'Read the file at <path>...' instead of injecting large previous text. " +
130
130
  "For transcripts, prefer status/stages/stage to get sessionFile/transcriptPath, " +
131
131
  "quote the exact path without rewriting separators (Windows backslashes are valid), " +
132
- "search it with rg/grep, and read small ranges; transcript defaults to at most 5 recent entries and explicit tail/limit overrides that preview.";
132
+ "then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview.";
133
133
 
134
134
  // ---------------------------------------------------------------------------
135
135
  // Minimal ExtensionAPI structural types
@@ -644,8 +644,10 @@ function renderTranscriptToolContent(
644
644
  if (result.transcriptPath) lines.push(`transcriptPathJson: ${JSON.stringify(result.transcriptPath)}`);
645
645
  if (result.entryCount !== undefined) lines.push(`availableEntries: ${result.entryCount}`);
646
646
  if (result.entryLimit !== undefined) lines.push(`entryLimit: ${result.entryLimit}`);
647
+ if (result.lazyReadPrompt) lines.push(`lazyReadPrompt: ${result.lazyReadPrompt}`);
648
+ if (result.fallbackNote) lines.push(`fallbackNote: ${result.fallbackNote}`);
647
649
  if (result.entries.length === 0) {
648
- lines.push("entries: none");
650
+ lines.push(result.inlineMode === "path_only" || result.lazyReadPrompt ? "entries: not inlined" : "entries: none");
649
651
  return lines.join("\n");
650
652
  }
651
653
  lines.push("entries:");
@@ -702,6 +704,10 @@ function renderStagesToolContent(
702
704
  lines.push("inputRequest:");
703
705
  lines.push(JSON.stringify(stage.inputRequest, null, 2));
704
706
  }
707
+ if (stage.promptFootprint !== undefined) {
708
+ lines.push("promptFootprint:");
709
+ lines.push(JSON.stringify(stage.promptFootprint, null, 2));
710
+ }
705
711
  });
706
712
  return lines.join("\n");
707
713
  }
@@ -800,6 +806,7 @@ type WorkflowStageSummary = {
800
806
  awaitingInputSince?: number;
801
807
  pendingPrompt?: StageSnapshot["pendingPrompt"];
802
808
  inputRequest?: StageSnapshot["inputRequest"];
809
+ promptFootprint?: StageSnapshot["promptFootprint"];
803
810
  };
804
811
 
805
812
  type WorkflowTranscriptEntry = {
@@ -842,6 +849,9 @@ function summarizeStage(stage: StageSnapshot): WorkflowStageSummary {
842
849
  inputRequest: stage.inputRequest === undefined
843
850
  ? undefined
844
851
  : structuredClone(stage.inputRequest),
852
+ promptFootprint: stage.promptFootprint === undefined
853
+ ? undefined
854
+ : structuredClone(stage.promptFootprint),
845
855
  };
846
856
  }
847
857
 
@@ -854,6 +864,12 @@ type TranscriptEntrySelection = {
854
864
  entryLimit?: number;
855
865
  };
856
866
 
867
+ type WorkflowTranscriptResult = Extract<WorkflowToolResult, { action: "transcript" }>;
868
+
869
+ function isTranscriptPreviewExplicit(args: WorkflowToolArgs): boolean {
870
+ return args.tail !== undefined || args.limit !== undefined;
871
+ }
872
+
857
873
  function requestedTranscriptEntryLimit(args: WorkflowToolArgs): number {
858
874
  const raw = args.tail ?? args.limit;
859
875
  if (raw === undefined) return DEFAULT_TRANSCRIPT_LIMIT;
@@ -891,6 +907,80 @@ function selectTranscriptEntries(
891
907
  };
892
908
  }
893
909
 
910
+ function transcriptLazyReadPrompt(path: string): string {
911
+ return `Transcript not inlined to protect context. Read it lazily from ${path} with your file read tools (read small ranges; rg/grep for targeted lookups).`;
912
+ }
913
+
914
+ function transcriptFallbackNote(limit: number): string {
915
+ return `No transcript file path is available for this stage; falling back to a bounded inline preview of up to ${limit} recent ${limit === 1 ? "entry" : "entries"}.`;
916
+ }
917
+
918
+ /**
919
+ * Shape a transcript tool result, keeping the context-safe path-only default
920
+ * the cheap hot path for the large runs #1314 protects against.
921
+ *
922
+ * `buildEntries` is a thunk so the default case (a transcript file path exists
923
+ * and no explicit `tail`/`limit` was requested) never materializes entry bodies
924
+ * just to discard them. Only the caller-provided `entryCount` is needed for the
925
+ * advisory count, which matches what `buildEntries()` would yield. The thunk is
926
+ * invoked solely for the explicit-preview and no-path fallback branches.
927
+ */
928
+ function shapeTranscriptResult(input: {
929
+ runId: string;
930
+ stageId: string;
931
+ source: "live" | "snapshot";
932
+ entryCount: number;
933
+ buildEntries: () => readonly WorkflowTranscriptEntry[];
934
+ args: WorkflowToolArgs;
935
+ sessionId?: string | undefined;
936
+ sessionFile?: string | undefined;
937
+ transcriptPath?: string | undefined;
938
+ }): WorkflowTranscriptResult {
939
+ // `transcriptPath` already falls back to `sessionFile`, so it is the single
940
+ // resolved path the agent should lazily read.
941
+ const transcriptPath = input.transcriptPath ?? input.sessionFile;
942
+ if (transcriptPath !== undefined && !isTranscriptPreviewExplicit(input.args)) {
943
+ const result: WorkflowTranscriptResult = {
944
+ action: "transcript",
945
+ runId: input.runId,
946
+ stageId: input.stageId,
947
+ source: input.source,
948
+ entries: [],
949
+ // `truncated` here means "more transcript exists on disk than was inlined"
950
+ // (everything, since the default inlines nothing), not "an explicit limit
951
+ // clipped results". It only drives the cosmetic "(truncated)" notice
952
+ // suffix; no consumer re-fetches on it.
953
+ truncated: input.entryCount > 0,
954
+ entryCount: input.entryCount,
955
+ entryLimit: 0,
956
+ lazyReadPrompt: transcriptLazyReadPrompt(transcriptPath),
957
+ inlineMode: "path_only",
958
+ };
959
+ if (input.sessionId !== undefined) result.sessionId = input.sessionId;
960
+ if (input.sessionFile !== undefined) result.sessionFile = input.sessionFile;
961
+ result.transcriptPath = transcriptPath;
962
+ return result;
963
+ }
964
+
965
+ const limited = selectTranscriptEntries(input.buildEntries(), input.args);
966
+ const result: WorkflowTranscriptResult = {
967
+ action: "transcript",
968
+ runId: input.runId,
969
+ stageId: input.stageId,
970
+ source: input.source,
971
+ entries: limited.entries,
972
+ truncated: limited.truncated,
973
+ entryCount: limited.entryCount,
974
+ entryLimit: limited.entryLimit,
975
+ inlineMode: transcriptPath === undefined ? "fallback_preview" : "preview",
976
+ };
977
+ if (input.sessionId !== undefined) result.sessionId = input.sessionId;
978
+ if (input.sessionFile !== undefined) result.sessionFile = input.sessionFile;
979
+ if (transcriptPath !== undefined) result.transcriptPath = transcriptPath;
980
+ if (transcriptPath === undefined) result.fallbackNote = transcriptFallbackNote(limited.entryLimit ?? DEFAULT_TRANSCRIPT_LIMIT);
981
+ return result;
982
+ }
983
+
894
984
  function messageText(content: MessageLike["content"]): string | undefined {
895
985
  if (typeof content === "string") return content;
896
986
  if (!Array.isArray(content)) return undefined;
@@ -1463,33 +1553,40 @@ export function makeExecuteWorkflowTool(
1463
1553
  if (liveHandle !== undefined) {
1464
1554
  const sessionFile = liveHandle.sessionFile ?? snapshot?.sessionFile;
1465
1555
  const sessionId = liveHandle.sessionId ?? snapshot?.sessionId;
1466
- const limited = selectTranscriptEntries(
1467
- liveHandle.messages.map((m) => transcriptEntryFromMessage(m as MessageLike)),
1468
- args,
1469
- );
1470
- return {
1471
- action: "transcript",
1556
+ return shapeTranscriptResult({
1472
1557
  runId: stageRunId,
1473
1558
  stageId: stage.stageId,
1474
1559
  source: "live",
1475
- ...limited,
1560
+ entryCount: liveHandle.messages.length,
1561
+ buildEntries: () =>
1562
+ liveHandle.messages.map((m) => transcriptEntryFromMessage(m as MessageLike)),
1563
+ args,
1476
1564
  sessionId,
1477
1565
  sessionFile,
1478
1566
  transcriptPath: sessionFile,
1479
- };
1567
+ });
1480
1568
  }
1481
- const fallback = snapshotTranscriptEntries(snapshot, args.includeToolOutput === true);
1482
- const limited = selectTranscriptEntries(fallback, args);
1483
- return {
1484
- action: "transcript",
1569
+ const snapshotSessionFile = snapshot?.sessionFile;
1570
+ const includeSnapshotOutput = args.includeToolOutput === true && (
1571
+ isTranscriptPreviewExplicit(args) || snapshotSessionFile === undefined
1572
+ );
1573
+ // Cheap count matches `snapshotTranscriptEntries(...).length` (one entry
1574
+ // per tool event plus the optional terminal result/error entries)
1575
+ // without building bodies for the path-only default.
1576
+ const snapshotEntryCount = (snapshot?.toolEvents?.length ?? 0)
1577
+ + (snapshot?.result !== undefined ? 1 : 0)
1578
+ + (snapshot?.error !== undefined ? 1 : 0);
1579
+ return shapeTranscriptResult({
1485
1580
  runId: stageRunId,
1486
1581
  stageId: stage.stageId,
1487
1582
  source: "snapshot",
1488
- ...limited,
1583
+ entryCount: snapshotEntryCount,
1584
+ buildEntries: () => snapshotTranscriptEntries(snapshot, includeSnapshotOutput),
1585
+ args,
1489
1586
  sessionId: snapshot?.sessionId,
1490
- sessionFile: snapshot?.sessionFile,
1491
- transcriptPath: snapshot?.sessionFile,
1492
- };
1587
+ sessionFile: snapshotSessionFile,
1588
+ transcriptPath: snapshotSessionFile,
1589
+ });
1493
1590
  }
1494
1591
 
1495
1592
  case "send": {
@@ -1543,6 +1640,24 @@ export function makeExecuteWorkflowTool(
1543
1640
  ok ? `Answered input request ${brokerPrompt.id}.` : `No matching pending input request ${brokerPrompt.id}.`,
1544
1641
  );
1545
1642
  }
1643
+ const customPrompt = snapshot?.status === "awaiting_input" && snapshot.promptFootprint?.kind === "custom"
1644
+ ? snapshot.promptFootprint
1645
+ : undefined;
1646
+ const targetsCustomPrompt =
1647
+ customPrompt !== undefined &&
1648
+ (args.promptId === undefined || args.promptId === customPrompt.id) &&
1649
+ (requestedDelivery === "answer" ||
1650
+ args.promptId !== undefined ||
1651
+ requestedDelivery === "auto");
1652
+ if (targetsCustomPrompt && customPrompt !== undefined) {
1653
+ return workflowSendResult(
1654
+ stageRunId,
1655
+ stage.stageId,
1656
+ "answer",
1657
+ "noop",
1658
+ `Custom UI prompt ${customPrompt.id} requires the interactive workflow graph; arbitrary ctx.ui.custom<T> results cannot be answered through workflow send.`,
1659
+ );
1660
+ }
1546
1661
  const targetsPrompt =
1547
1662
  requestedDelivery === "answer" ||
1548
1663
  args.promptId !== undefined ||
@@ -110,11 +110,13 @@ type StageListItem = {
110
110
  awaitingInputSince?: number;
111
111
  pendingPrompt?: PendingPrompt;
112
112
  inputRequest?: StageInputRequest;
113
+ promptFootprint?: PendingPrompt;
113
114
  };
114
115
  type StageListResult = { action: "stages"; runId: string; filter: string; stages: StageListItem[]; error?: string };
115
116
  type StageDetailItem = StageSnapshot & { transcriptPath?: string };
116
117
  type StageDetailResult = { action: "stage"; runId: string; stage?: StageDetailItem; error?: string };
117
118
  type TranscriptEntry = { role: string; text?: string; toolName?: string; output?: string; timestamp?: number };
119
+ type TranscriptInlineMode = "path_only" | "preview" | "fallback_preview";
118
120
  type TranscriptResult = {
119
121
  action: "transcript";
120
122
  runId: string;
@@ -127,6 +129,9 @@ type TranscriptResult = {
127
129
  sessionId?: string;
128
130
  sessionFile?: string;
129
131
  transcriptPath?: string;
132
+ lazyReadPrompt?: string;
133
+ fallbackNote?: string;
134
+ inlineMode?: TranscriptInlineMode;
130
135
  };
131
136
  type SendResult = { action: "send"; runId: string; stageId: string; delivery: string; status: "ok" | "noop"; message: string };
132
137
  type PauseResult = { action: "pause"; runId: string; status: string; message: string };
@@ -213,7 +218,7 @@ function renderNotice(
213
218
  const TRANSCRIPT_NOTICE_ENTRY_LIMIT = 5;
214
219
  const TRANSCRIPT_NOTICE_CHAR_LIMIT = 240;
215
220
 
216
- function transcriptNoticeText(entries: readonly TranscriptEntry[]): string {
221
+ function transcriptEntriesNoticeText(entries: readonly TranscriptEntry[]): string {
217
222
  if (entries.length === 0) return "no transcript entries";
218
223
  const shown = entries.slice(0, TRANSCRIPT_NOTICE_ENTRY_LIMIT);
219
224
  const text = shown
@@ -225,6 +230,21 @@ function transcriptNoticeText(entries: readonly TranscriptEntry[]): string {
225
230
  return fitLine(`${text}${entrySuffix}`, TRANSCRIPT_NOTICE_CHAR_LIMIT);
226
231
  }
227
232
 
233
+ function transcriptNoticeText(result: TranscriptResult): string {
234
+ if ((result.inlineMode === "path_only" || result.lazyReadPrompt !== undefined) && result.entries.length === 0) {
235
+ const path = result.transcriptPath ?? result.sessionFile ?? "transcript file";
236
+ const count = result.entryCount === undefined
237
+ ? ""
238
+ : ` (${result.entryCount} ${result.entryCount === 1 ? "entry" : "entries"})`;
239
+ return fitLine(`not inlined; read ${path}${count}`, TRANSCRIPT_NOTICE_CHAR_LIMIT);
240
+ }
241
+ const entriesText = transcriptEntriesNoticeText(result.entries);
242
+ if (result.inlineMode === "fallback_preview" || result.fallbackNote !== undefined) {
243
+ return fitLine(`no session file; preview: ${entriesText}`, TRANSCRIPT_NOTICE_CHAR_LIMIT);
244
+ }
245
+ return entriesText;
246
+ }
247
+
228
248
  export function renderResult(result: WorkflowToolResult, opts?: RenderResultOpts): string {
229
249
  const partial = opts?.isPartial === true;
230
250
  const themed = opts?.plain !== true;
@@ -350,7 +370,7 @@ export function renderResult(result: WorkflowToolResult, opts?: RenderResultOpts
350
370
 
351
371
  case "transcript": {
352
372
  const r = result as TranscriptResult;
353
- const text = transcriptNoticeText(r.entries);
373
+ const text = transcriptNoticeText(r);
354
374
  const suffix = r.truncated ? " (truncated)" : "";
355
375
  return renderNotice("WORKFLOW TRANSCRIPT", `${r.runId}/${r.stageId.slice(0, 12)} ${r.source}: ${text}${suffix}`, opts, themed);
356
376
  }
@@ -114,7 +114,7 @@ export const WorkflowParametersSchema = Type.Object({
114
114
  Type.Literal("resume"),
115
115
  Type.Literal("reload"),
116
116
  ], {
117
- description: "Workflow action: run/list/get/inputs/status, inspect stage metadata, send messages or prompt answers, pause/resume/interrupt/kill runs, or reload workflow resources. For transcript inspection, prefer status/stages/stage first to get sessionFile/transcriptPath, quote the exact path without rewriting separators (Windows backslashes are valid), then search it with rg/grep and read small ranges; transcript defaults to at most 5 recent entries and explicit tail/limit overrides that preview.",
117
+ description: "Workflow action: run/list/get/inputs/status, inspect stage metadata, send messages or prompt answers, pause/resume/interrupt/kill runs, or reload workflow resources. For transcript inspection, prefer status/stages/stage first to get sessionFile/transcriptPath, quote the exact path without rewriting separators (Windows backslashes are valid), then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview.",
118
118
  })),
119
119
  runId: Type.Optional(Type.String({
120
120
  description: "Run identifier or unique prefix for status/stages/stage/transcript/send/pause/resume/interrupt/kill. Use '--all' or all:true for supported bulk run-control actions.",
@@ -146,14 +146,14 @@ export const WorkflowParametersSchema = Type.Object({
146
146
  })),
147
147
  limit: Type.Optional(Type.Integer({
148
148
  minimum: 0,
149
- description: "Transcript-only: explicitly inline at most this many recent entries. Omit both limit and tail to use the default 5-entry preview plus metadata/path; prefer rg/grep on the exact quoted sessionFile/transcriptPath for targeted lookup without rewriting platform path separators.",
149
+ description: "Transcript-only: explicitly inline at most this many recent entries. Omit both limit and tail to use the path-only default when sessionFile/transcriptPath exists; prefer rg/grep on the exact quoted sessionFile/transcriptPath for targeted lookup without rewriting platform path separators.",
150
150
  })),
151
151
  tail: Type.Optional(Type.Integer({
152
152
  minimum: 0,
153
153
  description: "Transcript-only: explicitly inline the last N entries; overrides limit. Use for quick recent-context checks after status/stages/stage expose the transcript path.",
154
154
  })),
155
155
  includeToolOutput: Type.Optional(Type.Boolean({
156
- description: "Transcript-only: include captured tool output entries when building transcript results from stage snapshots; prefer rg/grep on the exact quoted sessionFile/transcriptPath for large outputs. Live session transcripts may not expose tool output.",
156
+ description: "Transcript-only: include captured tool output entries when building inlined snapshot previews; this does not bypass the path-only default. Prefer rg/grep on the exact quoted sessionFile/transcriptPath for large outputs. Live session transcripts may not expose tool output.",
157
157
  })),
158
158
  text: Type.Optional(Type.String({
159
159
  description: "Text to send to a stage for prompt answers, steering, follow-ups, or resume messages.",
@@ -14,6 +14,8 @@ import type {
14
14
  WorkflowRunContext,
15
15
  WorkflowUIContext,
16
16
  WorkflowUIAdapter,
17
+ WorkflowCustomUiFactory,
18
+ WorkflowCustomUiOptions,
17
19
  WorkflowInputSchema,
18
20
  StageContext,
19
21
  StageOptions,
@@ -56,7 +58,7 @@ import type {
56
58
  WorkflowFailureRecoverability,
57
59
  WorkflowFailureDisposition,
58
60
  PendingPrompt,
59
- PromptKind,
61
+ CustomPromptIdentitySource,
60
62
  WorkflowChildReplaySnapshot,
61
63
  WorkflowChildRunRef,
62
64
  } from "../../shared/store-types.js";
@@ -108,7 +110,7 @@ export interface RunContinuationOpts {
108
110
  readonly resumeFromStageId: string;
109
111
  }
110
112
 
111
- export interface RunOpts extends Omit<AuthoringContract.RunOpts, "adapters" | "store" | "cancellation" | "overlay" | "registry" | "stageControlRegistry" | "continuation" | "onRunStart" | "onStageStart" | "onStageEnd" | "onRunEnd"> {
113
+ export interface RunOpts extends Omit<AuthoringContract.RunOpts, "adapters" | "store" | "cancellation" | "overlay" | "registry" | "stageControlRegistry" | "continuation" | "onRunStart" | "onStageStart" | "onStageEnd" | "onRunEnd" | "ui"> {
112
114
  adapters?: StageAdapters;
113
115
  /** Invocation working directory exposed to workflow definitions as ctx.cwd. */
114
116
  cwd?: string;
@@ -271,14 +273,28 @@ function resolveInputRuntimeDefaults(
271
273
  // HIL unavailable fallback — rejects with precise per-primitive error
272
274
  // ---------------------------------------------------------------------------
273
275
 
274
- interface PromptDescriptor {
275
- readonly kind: PromptKind;
276
+ type PrimitivePromptDescriptor =
277
+ | { readonly kind: "input"; readonly message: string; readonly initial?: string }
278
+ | { readonly kind: "confirm"; readonly message: string }
279
+ | { readonly kind: "select"; readonly message: string; readonly choices: readonly string[] }
280
+ | { readonly kind: "editor"; readonly message: string; readonly initial?: string };
281
+
282
+ interface CustomPromptDescriptor<T> {
283
+ readonly kind: "custom";
276
284
  readonly message: string;
277
- readonly choices?: readonly string[];
278
- readonly initial?: string;
285
+ readonly factory: WorkflowCustomUiFactory<T>;
286
+ readonly options?: WorkflowCustomUiOptions;
287
+ readonly customIdentityHash: string;
288
+ readonly customIdentitySource: CustomPromptIdentitySource;
279
289
  }
280
290
 
281
- function fallbackForPromptDescriptor(descriptor: PromptDescriptor): unknown {
291
+ type PromptDescriptor<T = unknown> = PrimitivePromptDescriptor | CustomPromptDescriptor<T>;
292
+
293
+ function isCustomPromptDescriptor<T>(descriptor: PromptDescriptor<T>): descriptor is CustomPromptDescriptor<T> {
294
+ return descriptor.kind === "custom";
295
+ }
296
+
297
+ function fallbackForPromptDescriptor(descriptor: PrimitivePromptDescriptor): unknown {
282
298
  switch (descriptor.kind) {
283
299
  case "input":
284
300
  case "editor":
@@ -286,7 +302,7 @@ function fallbackForPromptDescriptor(descriptor: PromptDescriptor): unknown {
286
302
  case "confirm":
287
303
  return false;
288
304
  case "select":
289
- return descriptor.choices?.[0] ?? "";
305
+ return descriptor.choices[0] ?? "";
290
306
  }
291
307
  }
292
308
 
@@ -295,8 +311,12 @@ function makePrompt(descriptor: PromptDescriptor): PendingPrompt {
295
311
  id: `hil-${crypto.randomUUID()}`,
296
312
  kind: descriptor.kind,
297
313
  message: descriptor.message,
298
- ...(descriptor.choices !== undefined ? { choices: descriptor.choices } : {}),
299
- ...(descriptor.initial !== undefined ? { initial: descriptor.initial } : {}),
314
+ ...(!isCustomPromptDescriptor(descriptor) && descriptor.kind === "select" ? { choices: descriptor.choices } : {}),
315
+ ...(!isCustomPromptDescriptor(descriptor) && (descriptor.kind === "input" || descriptor.kind === "editor") && descriptor.initial !== undefined ? { initial: descriptor.initial } : {}),
316
+ ...(isCustomPromptDescriptor(descriptor) ? {
317
+ customIdentityHash: descriptor.customIdentityHash,
318
+ customIdentitySource: descriptor.customIdentitySource,
319
+ } : {}),
300
320
  createdAt: Date.now(),
301
321
  };
302
322
  }
@@ -307,13 +327,19 @@ function stableHash(value: unknown): string {
307
327
  }
308
328
 
309
329
  function promptDescriptorHash(descriptor: PromptDescriptor): string {
330
+ if (isCustomPromptDescriptor(descriptor)) {
331
+ return stableHash({
332
+ kind: "custom",
333
+ customIdentityHash: descriptor.customIdentityHash,
334
+ });
335
+ }
310
336
  return stableHash({
311
337
  kind: descriptor.kind,
312
338
  message: descriptor.message,
313
- choices: descriptor.choices ?? [],
339
+ choices: descriptor.kind === "select" ? descriptor.choices : [],
314
340
  // Include input/editor initial text because it is visible prompt context;
315
341
  // changing it should not replay a stale answer from the same callsite.
316
- initial: descriptor.initial ?? null,
342
+ initial: descriptor.kind === "input" || descriptor.kind === "editor" ? descriptor.initial ?? null : null,
317
343
  });
318
344
  }
319
345
 
@@ -334,6 +360,80 @@ function hilAbortError(signal: AbortSignal): Error {
334
360
  : new Error("atomic-workflows: HIL aborted");
335
361
  }
336
362
 
363
+ function resolveCustomPromptIdentity<T>(
364
+ factory: WorkflowCustomUiFactory<T>,
365
+ options: WorkflowCustomUiOptions | undefined,
366
+ ): Pick<CustomPromptDescriptor<T>, "customIdentityHash" | "customIdentitySource"> {
367
+ const replayIdentity = options?.replayIdentity?.trim();
368
+ if (replayIdentity !== undefined && replayIdentity.length > 0) {
369
+ return {
370
+ customIdentityHash: stableHash({ source: "caller", value: replayIdentity }),
371
+ customIdentitySource: "caller",
372
+ };
373
+ }
374
+ if (factory.name.trim().length > 0) {
375
+ return {
376
+ customIdentityHash: stableHash({ source: "factory", value: factory.name }),
377
+ customIdentitySource: "factory",
378
+ };
379
+ }
380
+ try {
381
+ const source = Function.prototype.toString.call(factory);
382
+ if (source.trim().length > 0) {
383
+ return {
384
+ customIdentityHash: stableHash({ source: "factory", value: source }),
385
+ customIdentitySource: "factory",
386
+ };
387
+ }
388
+ } catch {
389
+ // Fall through to callsite-only identity below.
390
+ }
391
+ return {
392
+ customIdentityHash: stableHash({ source: "callsite" }),
393
+ customIdentitySource: "callsite",
394
+ };
395
+ }
396
+
397
+ function customPromptDescriptor<T>(
398
+ factory: WorkflowCustomUiFactory<T>,
399
+ options: WorkflowCustomUiOptions | undefined,
400
+ ): CustomPromptDescriptor<T> {
401
+ const label = options?.label?.trim();
402
+ return {
403
+ kind: "custom",
404
+ message: label && label.length > 0 ? label : "Custom TUI prompt",
405
+ factory,
406
+ ...(options !== undefined ? { options } : {}),
407
+ ...resolveCustomPromptIdentity(factory, options),
408
+ };
409
+ }
410
+
411
+ interface MergedHilSignal {
412
+ readonly signal: AbortSignal;
413
+ readonly dispose: () => void;
414
+ }
415
+
416
+ function mergeHilSignals(primary: AbortSignal, secondary: AbortSignal | undefined): MergedHilSignal {
417
+ if (secondary === undefined) return { signal: primary, dispose: () => undefined };
418
+ const controller = new AbortController();
419
+ const abortFrom = (source: AbortSignal): void => {
420
+ if (!controller.signal.aborted) controller.abort(source.reason);
421
+ };
422
+ const onPrimaryAbort = (): void => abortFrom(primary);
423
+ const onSecondaryAbort = (): void => abortFrom(secondary);
424
+ primary.addEventListener("abort", onPrimaryAbort, { once: true });
425
+ secondary.addEventListener("abort", onSecondaryAbort, { once: true });
426
+ if (primary.aborted) abortFrom(primary);
427
+ else if (secondary.aborted) abortFrom(secondary);
428
+ return {
429
+ signal: controller.signal,
430
+ dispose: () => {
431
+ primary.removeEventListener("abort", onPrimaryAbort);
432
+ secondary.removeEventListener("abort", onSecondaryAbort);
433
+ },
434
+ };
435
+ }
436
+
337
437
  function makeUnavailableUIContext(): WorkflowUIContext {
338
438
  const msg = (primitive: string): string =>
339
439
  `atomic-workflows: HIL ctx.ui.${primitive} is unavailable because Atomic runtime did not provide a UI adapter`;
@@ -342,6 +442,31 @@ function makeUnavailableUIContext(): WorkflowUIContext {
342
442
  confirm: () => Promise.reject(new Error(msg("confirm"))),
343
443
  select: () => Promise.reject(new Error(msg("select"))),
344
444
  editor: () => Promise.reject(new Error(msg("editor"))),
445
+ custom: () => Promise.reject(new Error(msg("custom"))),
446
+ };
447
+ }
448
+
449
+ function normalizeUIContext(adapter: WorkflowUIAdapter | undefined): WorkflowUIContext {
450
+ const unavailable = makeUnavailableUIContext();
451
+ if (adapter === undefined) return unavailable;
452
+ return {
453
+ input(prompt) {
454
+ return adapter.input.call(adapter, prompt);
455
+ },
456
+ confirm(message) {
457
+ return adapter.confirm.call(adapter, message);
458
+ },
459
+ select<T extends string>(message: string, options: readonly T[]): Promise<T> {
460
+ return adapter.select.call(adapter, message, options) as Promise<T>;
461
+ },
462
+ editor(initial) {
463
+ return adapter.editor.call(adapter, initial);
464
+ },
465
+ custom<T>(factory: WorkflowCustomUiFactory<T>, options?: WorkflowCustomUiOptions): Promise<T> {
466
+ return adapter.custom !== undefined
467
+ ? adapter.custom.call(adapter, factory, options) as Promise<T>
468
+ : unavailable.custom(factory, options);
469
+ },
345
470
  };
346
471
  }
347
472
 
@@ -1400,8 +1525,19 @@ export async function runChain(
1400
1525
  return workflowDetailsFromRun("chain", runResult, results, options, validationWarnings);
1401
1526
  }
1402
1527
 
1403
- function raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
1528
+ export function raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
1404
1529
  if (signal.aborted) {
1530
+ // Callers invoke `raceAbort(call(), signal)`, so `call()` is evaluated —
1531
+ // and the underlying work (e.g. a stage prompt) is already in flight —
1532
+ // before this function observes an already-aborted signal. Attach a no-op
1533
+ // rejection handler so that in-flight promise can never surface as an
1534
+ // unhandled rejection. Without this, killing a workflow mid-prompt orphans
1535
+ // the prompt promise; its eventual rejection (commonly
1536
+ // "No API key found for ...") escapes every workflow error boundary and is
1537
+ // raised as a process-level uncaught exception that crashes the whole CLI.
1538
+ // The run is being aborted, so the orphaned settlement is intentionally
1539
+ // discarded here.
1540
+ void promise.catch(() => {});
1405
1541
  return Promise.reject(signal.reason ?? new DOMException("workflow killed", "AbortError"));
1406
1542
  }
1407
1543
  return new Promise<T>((resolve, reject) => {
@@ -2640,11 +2776,16 @@ export async function run<TInputs extends WorkflowInputValues>(
2640
2776
  };
2641
2777
  };
2642
2778
 
2643
- const buildPromptNodeUiAdapter = (): WorkflowUIAdapter => {
2644
- const ask = async (descriptor: PromptDescriptor): Promise<unknown> => {
2779
+ const buildPromptNodeUiAdapter = (): WorkflowUIContext => {
2780
+ const ask = async <T>(descriptor: PromptDescriptor<T>): Promise<unknown> => {
2781
+ const isCustom = isCustomPromptDescriptor(descriptor);
2645
2782
  if (ownController.signal.aborted) {
2783
+ if (isCustom) throw hilAbortError(ownController.signal);
2646
2784
  return fallbackForPromptDescriptor(descriptor);
2647
2785
  }
2786
+ if (isCustom && descriptor.options?.signal?.aborted) {
2787
+ throw hilAbortError(descriptor.options.signal);
2788
+ }
2648
2789
 
2649
2790
  const prompt = makePrompt(descriptor);
2650
2791
  const stageId = crypto.randomUUID();
@@ -2741,6 +2882,55 @@ export async function run<TInputs extends WorkflowInputValues>(
2741
2882
  finalizePromptStage("completed");
2742
2883
  return replayAnswer.value;
2743
2884
  }
2885
+
2886
+ if (isCustom) {
2887
+ if (descriptor.options?.overlay === true) {
2888
+ const error = new Error("atomic-workflows: ctx.ui.custom overlay mode is unavailable in the workflow graph viewer");
2889
+ applyFailureToStage(stageSnapshot, classifyExecutorFailure(error));
2890
+ finalizePromptStage("failed");
2891
+ throw error;
2892
+ }
2893
+
2894
+ const mergedSignal = mergeHilSignals(ownController.signal, descriptor.options?.signal);
2895
+ try {
2896
+ if (mergedSignal.signal.aborted) throw hilAbortError(mergedSignal.signal);
2897
+ const accepted = activeStore.recordStageAwaitingInput(runId, stageId, true, prompt.createdAt);
2898
+ if (!accepted) {
2899
+ const error = new Error("atomic-workflows: ctx.ui.custom prompt node is unavailable");
2900
+ stageSnapshot.skippedReason = "prompt-unavailable";
2901
+ finalizePromptStage("skipped");
2902
+ throw error;
2903
+ }
2904
+ const response = await stageUiBroker.requestCustomUi(
2905
+ runId,
2906
+ stageId,
2907
+ descriptor.factory as unknown as Parameters<typeof stageUiBroker.requestCustomUi>[2],
2908
+ descriptor.options as Parameters<typeof stageUiBroker.requestCustomUi>[3],
2909
+ mergedSignal.signal,
2910
+ );
2911
+ activeStore.recordStagePromptAnswer(runId, stageId, prompt, response, {
2912
+ answerSource: "workflow_ui",
2913
+ });
2914
+ finalizePromptStage("completed");
2915
+ return response;
2916
+ } catch (err) {
2917
+ activeStore.recordStageAwaitingInput(runId, stageId, false);
2918
+ stageUiBroker.cancelStagePrompt(runId, stageId, err);
2919
+ if (mergedSignal.signal.aborted) {
2920
+ stageSnapshot.skippedReason = ownController.signal.aborted ? "run-aborted" : "prompt-aborted";
2921
+ finalizePromptStage("skipped");
2922
+ throw hilAbortError(mergedSignal.signal);
2923
+ }
2924
+ if (!finalized) {
2925
+ applyFailureToStage(stageSnapshot, classifyExecutorFailure(err));
2926
+ finalizePromptStage("failed");
2927
+ }
2928
+ throw err;
2929
+ } finally {
2930
+ mergedSignal.dispose();
2931
+ }
2932
+ }
2933
+
2744
2934
  const accepted = activeStore.recordStagePendingPrompt(runId, stageId, prompt);
2745
2935
  if (!accepted) {
2746
2936
  stageSnapshot.skippedReason = "prompt-unavailable";
@@ -2818,6 +3008,10 @@ export async function run<TInputs extends WorkflowInputValues>(
2818
3008
  });
2819
3009
  return typeof response === "string" ? response : initial ?? "";
2820
3010
  },
3011
+ async custom<T>(factory: WorkflowCustomUiFactory<T>, options?: WorkflowCustomUiOptions): Promise<T> {
3012
+ const response = await ask(customPromptDescriptor(factory, options));
3013
+ return response as T;
3014
+ },
2821
3015
  };
2822
3016
  };
2823
3017
 
@@ -2827,7 +3021,7 @@ export async function run<TInputs extends WorkflowInputValues>(
2827
3021
  get cwd() { return resolveWorkflowCwd(); },
2828
3022
  // Prompt nodes and caller-provided UI adapters are mutually exclusive;
2829
3023
  // executor-owned prompt nodes intentionally take precedence when enabled.
2830
- ui: opts.usePromptNodesForUi === true ? buildPromptNodeUiAdapter() : opts.ui ?? makeUnavailableUIContext(),
3024
+ ui: opts.usePromptNodesForUi === true ? buildPromptNodeUiAdapter() : normalizeUIContext(opts.ui),
2831
3025
 
2832
3026
  stage(name: string, options?: StageOptions, stageFailFastScope?: ParallelFailFastScope) {
2833
3027
  options = stageOptionsWithGitWorktree(stageOptionsWithInputDefaults(options, inputRuntimeDefaults), workflowInvocationCwd);
@@ -37,7 +37,7 @@ export type { StageNode } from "./runs/shared/graph-inference.js";
37
37
  export { setupGitWorktree } from "./runs/shared/worktree.js";
38
38
  export type { GitWorktreeSetupOptions, GitWorktreeSetupResult } from "./runs/shared/worktree.js";
39
39
  export { createStore, store } from "./shared/store.js";
40
- export type { RunStatus, StageStatus, ToolEvent, StageSnapshot, RunSnapshot, StoreSnapshot, WorkflowNotice, NoticeLevel, WorkflowOverlayAdapter, PromptKind, PendingPrompt } from "./shared/store-types.js";
40
+ export type { RunStatus, StageStatus, ToolEvent, StageSnapshot, RunSnapshot, StoreSnapshot, WorkflowNotice, NoticeLevel, WorkflowOverlayAdapter, PromptKind, CustomPromptIdentitySource, PendingPrompt } from "./shared/store-types.js";
41
41
 
42
42
  // Phase D — cancellation registry
43
43
  export { createCancellationRegistry, cancellationRegistry } from "./runs/background/cancellation-registry.js";