@bastani/atomic 0.9.18-alpha.5 → 0.9.18-alpha.7

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 (376) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +3 -3
  3. package/dist/builtin/intercom/CHANGELOG.md +39 -0
  4. package/dist/builtin/intercom/README.md +22 -2
  5. package/dist/builtin/intercom/broker/broker.ts +676 -128
  6. package/dist/builtin/intercom/broker/client.ts +151 -35
  7. package/dist/builtin/intercom/broker/delivered-message-cache.ts +497 -37
  8. package/dist/builtin/intercom/broker/framing.ts +4 -2
  9. package/dist/builtin/intercom/broker/paths.ts +10 -0
  10. package/dist/builtin/intercom/broker/pending-send-registry.ts +1 -2
  11. package/dist/builtin/intercom/broker/send-handler.ts +311 -52
  12. package/dist/builtin/intercom/broker/send-signature.ts +4 -2
  13. package/dist/builtin/intercom/broker/socket-writes.ts +103 -0
  14. package/dist/builtin/intercom/broker/spawn.ts +9 -4
  15. package/dist/builtin/intercom/index.bundle.mjs +1410 -212
  16. package/dist/builtin/intercom/package.json +2 -2
  17. package/dist/builtin/intercom/recoverable-disconnect.ts +52 -0
  18. package/dist/builtin/intercom/retry-policy.ts +8 -0
  19. package/dist/builtin/intercom/skills/intercom/SKILL.md +17 -9
  20. package/dist/builtin/intercom/types.ts +27 -8
  21. package/dist/builtin/intercom/workflow-stage-path-matching.ts +90 -0
  22. package/dist/builtin/intercom/workflow-stage-target.ts +57 -0
  23. package/dist/builtin/mcp/index.bundle.mjs +427 -126
  24. package/dist/builtin/mcp/package.json +2 -2
  25. package/dist/builtin/subagents/CHANGELOG.md +19 -0
  26. package/dist/builtin/subagents/README.md +3 -3
  27. package/dist/builtin/subagents/agents/code-simplifier.md +2 -2
  28. package/dist/builtin/subagents/agents/codebase-analyzer.md +2 -2
  29. package/dist/builtin/subagents/agents/codebase-locator.md +2 -2
  30. package/dist/builtin/subagents/agents/codebase-online-researcher.md +11 -11
  31. package/dist/builtin/subagents/agents/codebase-pattern-finder.md +2 -2
  32. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +2 -2
  33. package/dist/builtin/subagents/agents/codebase-research-locator.md +2 -2
  34. package/dist/builtin/subagents/agents/debugger.md +3 -3
  35. package/dist/builtin/subagents/agents/worker.md +2 -2
  36. package/dist/builtin/subagents/package.json +3 -3
  37. package/dist/builtin/subagents/skills/qlty/SKILL.md +29 -101
  38. package/dist/builtin/subagents/skills/qlty/references/manual-configuration.md +52 -0
  39. package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
  40. package/dist/builtin/subagents/src/extension/index.bundle.mjs +49 -132
  41. package/dist/builtin/web-access/package.json +2 -2
  42. package/dist/builtin/workflows/CHANGELOG.md +46 -0
  43. package/dist/builtin/workflows/README.md +7 -7
  44. package/dist/builtin/workflows/builtin/adversarial-verification.js +2 -2
  45. package/dist/builtin/workflows/builtin/{chunk-szz1ajz9.js → chunk-42s7sw0b.js} +9 -2
  46. package/dist/builtin/workflows/builtin/{chunk-2dqb5s2q.js → chunk-79fjkcvh.js} +1 -1
  47. package/dist/builtin/workflows/builtin/{chunk-n58a7v26.js → chunk-b0v2xab1.js} +181 -10
  48. package/dist/builtin/workflows/builtin/{chunk-fghhy2a5.js → chunk-ed4jfn5t.js} +23 -5
  49. package/dist/builtin/workflows/builtin/{chunk-h3r2vkzc.js → chunk-hqpe2cyx.js} +60 -21
  50. package/dist/builtin/workflows/builtin/{chunk-hzzn6adg.js → chunk-k3w87x8y.js} +1 -1
  51. package/dist/builtin/workflows/builtin/{chunk-82ha8p41.js → chunk-n8h4yyew.js} +1 -1
  52. package/dist/builtin/workflows/builtin/{chunk-qwzvgxnq.js → chunk-reqr3sr3.js} +1 -1
  53. package/dist/builtin/workflows/builtin/{chunk-c53y8bdh.js → chunk-wgccch41.js} +1 -1
  54. package/dist/builtin/workflows/builtin/{chunk-0x6e303p.js → chunk-zbekjqew.js} +20 -7
  55. package/dist/builtin/workflows/builtin/{chunk-29wrp38a.js → chunk-ze5x3d1r.js} +1 -1
  56. package/dist/builtin/workflows/builtin/classify-and-act.js +2 -2
  57. package/dist/builtin/workflows/builtin/fan-out-and-synthesize.js +2 -2
  58. package/dist/builtin/workflows/builtin/generate-and-filter.js +2 -2
  59. package/dist/builtin/workflows/builtin/goal.js +3 -3
  60. package/dist/builtin/workflows/builtin/index.js +11 -11
  61. package/dist/builtin/workflows/builtin/loop-until-done.js +2 -2
  62. package/dist/builtin/workflows/builtin/open-claude-design.js +2 -2
  63. package/dist/builtin/workflows/builtin/ralph.js +3 -3
  64. package/dist/builtin/workflows/builtin/steering-context.js +1 -1
  65. package/dist/builtin/workflows/builtin/tournament.js +2 -2
  66. package/dist/builtin/workflows/package.json +2 -2
  67. package/dist/builtin/workflows/skills/prompt-engineer/SKILL.md +39 -52
  68. package/dist/builtin/workflows/skills/prompt-engineer/references/advanced_patterns.md +9 -37
  69. package/dist/builtin/workflows/skills/prompt-engineer/references/claude_fable_5.md +49 -0
  70. package/dist/builtin/workflows/skills/prompt-engineer/references/claude_fable_5_1.md +57 -0
  71. package/dist/builtin/workflows/skills/prompt-engineer/references/claude_opus_4_8.md +57 -0
  72. package/dist/builtin/workflows/skills/prompt-engineer/references/claude_opus_5.md +47 -0
  73. package/dist/builtin/workflows/skills/prompt-engineer/references/claude_sonnet_5.md +57 -0
  74. package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_5_5.md +60 -0
  75. package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_5_6.md +58 -0
  76. package/dist/builtin/workflows/skills/prompt-engineer/references/gpt_6_astra.md +58 -0
  77. package/dist/builtin/workflows/skills/prompt-engineer/references/quality_improvement.md +18 -4
  78. package/dist/builtin/workflows/src/extension/index.bundle.mjs +3194 -737
  79. package/dist/builtin/workflows/src/index.js +899 -436
  80. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +0 -10
  81. package/dist/cli/list-models.d.ts.map +1 -1
  82. package/dist/cli/list-models.js +4 -0
  83. package/dist/cli/list-models.js.map +1 -1
  84. package/dist/client/index.d.ts +1 -2
  85. package/dist/client/index.d.ts.map +1 -1
  86. package/dist/client/index.js +1 -2
  87. package/dist/client/index.js.map +1 -1
  88. package/dist/config.d.ts +0 -9
  89. package/dist/config.d.ts.map +1 -1
  90. package/dist/config.js +0 -45
  91. package/dist/config.js.map +1 -1
  92. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  93. package/dist/core/agent-session-compaction.js +8 -6
  94. package/dist/core/agent-session-compaction.js.map +1 -1
  95. package/dist/core/agent-session-methods.d.ts +1 -0
  96. package/dist/core/agent-session-methods.d.ts.map +1 -1
  97. package/dist/core/agent-session-methods.js.map +1 -1
  98. package/dist/core/agent-session-models.d.ts.map +1 -1
  99. package/dist/core/agent-session-models.js +3 -4
  100. package/dist/core/agent-session-models.js.map +1 -1
  101. package/dist/core/agent-session-queue-pause.d.ts.map +1 -1
  102. package/dist/core/agent-session-queue-pause.js +6 -0
  103. package/dist/core/agent-session-queue-pause.js.map +1 -1
  104. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  105. package/dist/core/agent-session-runtime.js +13 -5
  106. package/dist/core/agent-session-runtime.js.map +1 -1
  107. package/dist/core/agent-session-tool-hooks.js +1 -1
  108. package/dist/core/agent-session-tool-hooks.js.map +1 -1
  109. package/dist/core/agent-session-tree.d.ts.map +1 -1
  110. package/dist/core/agent-session-tree.js +8 -0
  111. package/dist/core/agent-session-tree.js.map +1 -1
  112. package/dist/core/agent-session.d.ts +2 -0
  113. package/dist/core/agent-session.d.ts.map +1 -1
  114. package/dist/core/agent-session.js +2 -0
  115. package/dist/core/agent-session.js.map +1 -1
  116. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  117. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  118. package/dist/core/compaction/branch-summarization.js +4 -4
  119. package/dist/core/compaction/branch-summarization.js.map +1 -1
  120. package/dist/core/extensions/api-types.d.ts +8 -2
  121. package/dist/core/extensions/api-types.d.ts.map +1 -1
  122. package/dist/core/extensions/api-types.js.map +1 -1
  123. package/dist/core/extensions/context-types.d.ts +12 -0
  124. package/dist/core/extensions/context-types.d.ts.map +1 -1
  125. package/dist/core/extensions/context-types.js.map +1 -1
  126. package/dist/core/extensions/runner-shortcuts.d.ts.map +1 -1
  127. package/dist/core/extensions/runner-shortcuts.js +0 -1
  128. package/dist/core/extensions/runner-shortcuts.js.map +1 -1
  129. package/dist/core/fast-model-routing-transport.d.ts +12 -0
  130. package/dist/core/fast-model-routing-transport.d.ts.map +1 -0
  131. package/dist/core/{codex-fast-mode-transport.js → fast-model-routing-transport.js} +29 -29
  132. package/dist/core/fast-model-routing-transport.js.map +1 -0
  133. package/dist/core/fast-model-routing.d.ts +57 -0
  134. package/dist/core/fast-model-routing.d.ts.map +1 -0
  135. package/dist/core/fast-model-routing.js +201 -0
  136. package/dist/core/fast-model-routing.js.map +1 -0
  137. package/dist/core/fast-model-variants.d.ts +98 -0
  138. package/dist/core/fast-model-variants.d.ts.map +1 -0
  139. package/dist/core/fast-model-variants.js +148 -0
  140. package/dist/core/fast-model-variants.js.map +1 -0
  141. package/dist/core/http-dispatcher.d.ts.map +1 -1
  142. package/dist/core/http-dispatcher.js +4 -2
  143. package/dist/core/http-dispatcher.js.map +1 -1
  144. package/dist/core/keybindings.d.ts +0 -10
  145. package/dist/core/keybindings.d.ts.map +1 -1
  146. package/dist/core/keybindings.js +0 -8
  147. package/dist/core/keybindings.js.map +1 -1
  148. package/dist/core/messages.d.ts +1 -1
  149. package/dist/core/messages.d.ts.map +1 -1
  150. package/dist/core/messages.js.map +1 -1
  151. package/dist/core/model-config.d.ts +15 -0
  152. package/dist/core/model-config.d.ts.map +1 -1
  153. package/dist/core/model-config.js +3 -0
  154. package/dist/core/model-config.js.map +1 -1
  155. package/dist/core/model-resolver-initial.js +1 -1
  156. package/dist/core/model-resolver-initial.js.map +1 -1
  157. package/dist/core/model-runtime-streaming.d.ts +14 -1
  158. package/dist/core/model-runtime-streaming.d.ts.map +1 -1
  159. package/dist/core/model-runtime-streaming.js +25 -3
  160. package/dist/core/model-runtime-streaming.js.map +1 -1
  161. package/dist/core/model-runtime.d.ts +39 -2
  162. package/dist/core/model-runtime.d.ts.map +1 -1
  163. package/dist/core/model-runtime.js +91 -7
  164. package/dist/core/model-runtime.js.map +1 -1
  165. package/dist/core/provider-composer-internal.d.ts.map +1 -1
  166. package/dist/core/provider-composer-internal.js +8 -2
  167. package/dist/core/provider-composer-internal.js.map +1 -1
  168. package/dist/core/sdk.d.ts.map +1 -1
  169. package/dist/core/sdk.js +18 -18
  170. package/dist/core/sdk.js.map +1 -1
  171. package/dist/core/session-manager-archive.d.ts.map +1 -1
  172. package/dist/core/session-manager-archive.js +25 -2
  173. package/dist/core/session-manager-archive.js.map +1 -1
  174. package/dist/core/settings-manager-core.d.ts +0 -2
  175. package/dist/core/settings-manager-core.d.ts.map +1 -1
  176. package/dist/core/settings-manager-core.js +2 -9
  177. package/dist/core/settings-manager-core.js.map +1 -1
  178. package/dist/core/settings-manager-internals.d.ts +0 -1
  179. package/dist/core/settings-manager-internals.d.ts.map +1 -1
  180. package/dist/core/settings-manager-internals.js.map +1 -1
  181. package/dist/core/settings-manager-ui-accessors.d.ts +0 -8
  182. package/dist/core/settings-manager-ui-accessors.d.ts.map +1 -1
  183. package/dist/core/settings-manager-ui-accessors.js +0 -53
  184. package/dist/core/settings-manager-ui-accessors.js.map +1 -1
  185. package/dist/core/settings-manager.d.ts +1 -1
  186. package/dist/core/settings-manager.d.ts.map +1 -1
  187. package/dist/core/settings-manager.js.map +1 -1
  188. package/dist/core/settings-types.d.ts +0 -5
  189. package/dist/core/settings-types.d.ts.map +1 -1
  190. package/dist/core/settings-types.js.map +1 -1
  191. package/dist/core/skills.d.ts +1 -1
  192. package/dist/core/skills.d.ts.map +1 -1
  193. package/dist/core/skills.js +4 -2
  194. package/dist/core/skills.js.map +1 -1
  195. package/dist/core/slash-commands.d.ts.map +1 -1
  196. package/dist/core/slash-commands.js +0 -1
  197. package/dist/core/slash-commands.js.map +1 -1
  198. package/dist/core/system-prompt.js +7 -7
  199. package/dist/core/system-prompt.js.map +1 -1
  200. package/dist/core/tools/edit.d.ts +1 -1
  201. package/dist/core/tools/edit.d.ts.map +1 -1
  202. package/dist/core/tools/edit.js +128 -3
  203. package/dist/core/tools/edit.js.map +1 -1
  204. package/dist/core/tools/hashline.d.ts +2 -2
  205. package/dist/core/tools/hashline.d.ts.map +1 -1
  206. package/dist/core/tools/hashline.js +40 -10
  207. package/dist/core/tools/hashline.js.map +1 -1
  208. package/dist/core/tools/write.d.ts.map +1 -1
  209. package/dist/core/tools/write.js +8 -8
  210. package/dist/core/tools/write.js.map +1 -1
  211. package/dist/core/workflow-stage-admission.d.ts +6 -0
  212. package/dist/core/workflow-stage-admission.d.ts.map +1 -1
  213. package/dist/core/workflow-stage-admission.js +14 -0
  214. package/dist/core/workflow-stage-admission.js.map +1 -1
  215. package/dist/index-extensions.d.ts +1 -1
  216. package/dist/index-extensions.d.ts.map +1 -1
  217. package/dist/index-extensions.js.map +1 -1
  218. package/dist/index.d.ts +5 -5
  219. package/dist/index.d.ts.map +1 -1
  220. package/dist/index.js +4 -5
  221. package/dist/index.js.map +1 -1
  222. package/dist/modes/interactive/components/atomic-working-status.d.ts +4 -0
  223. package/dist/modes/interactive/components/atomic-working-status.d.ts.map +1 -1
  224. package/dist/modes/interactive/components/atomic-working-status.js +26 -1
  225. package/dist/modes/interactive/components/atomic-working-status.js.map +1 -1
  226. package/dist/modes/interactive/components/custom-editor.d.ts +7 -0
  227. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/custom-editor.js +43 -0
  229. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  230. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/footer.js +0 -6
  232. package/dist/modes/interactive/components/footer.js.map +1 -1
  233. package/dist/modes/interactive/components/index.d.ts +1 -2
  234. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  235. package/dist/modes/interactive/components/index.js +0 -1
  236. package/dist/modes/interactive/components/index.js.map +1 -1
  237. package/dist/modes/interactive/components/model-selector.d.ts +1 -1
  238. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  239. package/dist/modes/interactive/components/model-selector.js +6 -11
  240. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  241. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -5
  242. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  243. package/dist/modes/interactive/components/scoped-models-selector.js +3 -19
  244. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  245. package/dist/modes/interactive/components/settings-selector-items.js +1 -1
  246. package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
  247. package/dist/modes/interactive/components/thinking-selector.d.ts +1 -2
  248. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  249. package/dist/modes/interactive/components/thinking-selector.js +4 -11
  250. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  251. package/dist/modes/interactive/components/transcript-follow-indicator.js +3 -3
  252. package/dist/modes/interactive/components/transcript-follow-indicator.js.map +1 -1
  253. package/dist/modes/interactive/interactive-agent-events.js +8 -8
  254. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  255. package/dist/modes/interactive/interactive-autocomplete.js +5 -16
  256. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  257. package/dist/modes/interactive/interactive-bash-compact.js +2 -9
  258. package/dist/modes/interactive/interactive-bash-compact.js.map +1 -1
  259. package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
  260. package/dist/modes/interactive/interactive-deferred-startup.js +3 -0
  261. package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
  262. package/dist/modes/interactive/interactive-editor-actions.js +2 -2
  263. package/dist/modes/interactive/interactive-editor-actions.js.map +1 -1
  264. package/dist/modes/interactive/interactive-extension-custom-ui.js +1 -0
  265. package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
  266. package/dist/modes/interactive/interactive-extension-dialogs.js +7 -0
  267. package/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
  268. package/dist/modes/interactive/interactive-extension-runtime.js +20 -10
  269. package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
  270. package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -5
  271. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  272. package/dist/modes/interactive/interactive-initial-session-binding.d.ts.map +1 -1
  273. package/dist/modes/interactive/interactive-initial-session-binding.js +3 -0
  274. package/dist/modes/interactive/interactive-initial-session-binding.js.map +1 -1
  275. package/dist/modes/interactive/interactive-input-handling.js +0 -8
  276. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  277. package/dist/modes/interactive/interactive-mode-base.d.ts +5 -1
  278. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  279. package/dist/modes/interactive/interactive-mode-base.js +15 -9
  280. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  281. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -3
  282. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  283. package/dist/modes/interactive/interactive-mode-deps.js +1 -3
  284. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  285. package/dist/modes/interactive/interactive-mode-surface.d.ts +9 -12
  286. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  287. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  288. package/dist/modes/interactive/interactive-model-routing.js +10 -13
  289. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  290. package/dist/modes/interactive/interactive-render-chat.js +29 -0
  291. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  292. package/dist/modes/interactive/interactive-selectors.js +2 -32
  293. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  294. package/dist/modes/interactive/interactive-session-routing.js +1 -5
  295. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  296. package/dist/modes/interactive/interactive-session-runtime.js +1 -5
  297. package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
  298. package/dist/modes/interactive/interactive-slash-commands.js +6 -13
  299. package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
  300. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  301. package/dist/modes/interactive/interactive-startup.js +19 -13
  302. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  303. package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
  304. package/dist/modes/interactive/interactive-tui.js +27 -3
  305. package/dist/modes/interactive/interactive-tui.js.map +1 -1
  306. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  307. package/dist/modes/interactive-engine/engine-custom-ui.js +1 -0
  308. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  309. package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
  310. package/dist/modes/interactive-engine/isolated-runtime.js +4 -1
  311. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  312. package/dist/utils/child-process.d.ts.map +1 -1
  313. package/dist/utils/child-process.js +13 -6
  314. package/dist/utils/child-process.js.map +1 -1
  315. package/dist/utils/exif-orientation.d.ts.map +1 -1
  316. package/dist/utils/exif-orientation.js +2 -3
  317. package/dist/utils/exif-orientation.js.map +1 -1
  318. package/dist/utils/tools-manager.d.ts +1 -0
  319. package/dist/utils/tools-manager.d.ts.map +1 -1
  320. package/dist/utils/tools-manager.js +33 -18
  321. package/dist/utils/tools-manager.js.map +1 -1
  322. package/docs/custom-provider.md +2 -0
  323. package/docs/development.md +17 -0
  324. package/docs/docs.json +14 -2
  325. package/docs/extensions.md +9 -4
  326. package/docs/intercom.md +41 -7
  327. package/docs/keybindings.md +8 -6
  328. package/docs/models/evals.md +394 -0
  329. package/docs/models/model-selection.md +54 -27
  330. package/docs/models/pareto-efficiency.md +33 -16
  331. package/docs/models.md +37 -1
  332. package/docs/packages.md +2 -2
  333. package/docs/providers.md +44 -20
  334. package/docs/quickstart.md +8 -6
  335. package/docs/rpc.md +3 -3
  336. package/docs/sdk.md +2 -16
  337. package/docs/settings.md +19 -22
  338. package/docs/skills.md +4 -2
  339. package/docs/subagents.md +9 -3
  340. package/docs/tools/edit.md +373 -0
  341. package/docs/tools.md +3 -7
  342. package/docs/tui.md +2 -1
  343. package/docs/usage.md +3 -5
  344. package/docs/workflows/api-reference.md +1395 -0
  345. package/docs/workflows/authoring.md +678 -0
  346. package/docs/workflows/builtins.md +245 -0
  347. package/docs/workflows/operations.md +811 -0
  348. package/docs/workflows/reliable-design.md +2099 -0
  349. package/docs/workflows/verification.md +62 -0
  350. package/docs/workflows.md +15 -5183
  351. package/npm-shrinkwrap.json +584 -69
  352. package/package.json +7 -7
  353. package/dist/client/remote-session.d.ts +0 -64
  354. package/dist/client/remote-session.d.ts.map +0 -1
  355. package/dist/client/remote-session.js +0 -351
  356. package/dist/client/remote-session.js.map +0 -1
  357. package/dist/client/transcript.d.ts +0 -12
  358. package/dist/client/transcript.d.ts.map +0 -1
  359. package/dist/client/transcript.js +0 -98
  360. package/dist/client/transcript.js.map +0 -1
  361. package/dist/core/codex-fast-mode-transport.d.ts +0 -12
  362. package/dist/core/codex-fast-mode-transport.d.ts.map +0 -1
  363. package/dist/core/codex-fast-mode-transport.js.map +0 -1
  364. package/dist/core/codex-fast-mode.d.ts +0 -53
  365. package/dist/core/codex-fast-mode.d.ts.map +0 -1
  366. package/dist/core/codex-fast-mode.js +0 -249
  367. package/dist/core/codex-fast-mode.js.map +0 -1
  368. package/dist/modes/interactive/components/fast-mode-selector.d.ts +0 -27
  369. package/dist/modes/interactive/components/fast-mode-selector.d.ts.map +0 -1
  370. package/dist/modes/interactive/components/fast-mode-selector.js +0 -107
  371. package/dist/modes/interactive/components/fast-mode-selector.js.map +0 -1
  372. package/dist/server/create-harness.d.ts +0 -53
  373. package/dist/server/create-harness.d.ts.map +0 -1
  374. package/dist/server/create-harness.js +0 -360
  375. package/dist/server/create-harness.js.map +0 -1
  376. package/docs/models/artificial-analysis-index.md +0 -83
@@ -2,10 +2,16 @@
2
2
  // only position from which the stderr cap covers the other modules' own initialization.
3
3
  import "./bounded-stderr-install.js";
4
4
  import net from "net";
5
- import { writeFileSync, unlinkSync, mkdirSync, readFileSync } from "fs";
5
+ import { chmodSync, writeFileSync, unlinkSync, mkdirSync, readFileSync } from "fs";
6
6
  import { randomUUID } from "crypto";
7
- import { writeMessage, createMessageReader } from "./framing.js";
8
- import { getBrokerPidPath, getBrokerSocketPath, getIntercomDirPath } from "./paths.js";
7
+ import { createMessageReader } from "./framing.js";
8
+ import { writeMessageIfOpen, writeMessageWithOutcome } from "./socket-writes.js";
9
+ import {
10
+ getBrokerDeliveredMessagesPath,
11
+ getBrokerPidPath,
12
+ getBrokerSocketPath,
13
+ getIntercomDirPath,
14
+ } from "./paths.js";
9
15
  import type {
10
16
  SessionInfo,
11
17
  Message,
@@ -13,15 +19,23 @@ import type {
13
19
  SupervisorRegistration,
14
20
  WorkflowStageRosterAnnouncement,
15
21
  WorkflowStageRosterEntry,
22
+ WorkflowPossibleStageAnnouncement,
23
+ WorkflowFutureStageRosterEntry,
16
24
  } from "../types.js";
17
- import { DeliveredMessageCache } from "./delivered-message-cache.js";
25
+ import {
26
+ formatWorkflowStageTarget,
27
+ parseWorkflowStageTarget,
28
+ withWorkflowStageTargetFinalSegment,
29
+ } from "../workflow-stage-target.js";
30
+ import { DELIVERED_MESSAGE_MAX_ENTRIES, DeliveredMessageCache } from "./delivered-message-cache.js";
18
31
  import { isMessage } from "./client-message-validation.js";
19
32
  import { buildMessageSendSignature } from "./send-signature.js";
20
33
  import {
21
- handleBrokerSend,
22
- parsePendingStageTarget,
23
- type BrokerConnectedSession,
24
- type PendingStageRoute,
34
+ deliveryTargetIdentity,
35
+ handleBrokerSend,
36
+ type BrokerConnectedSession,
37
+ type PendingStageRoute,
38
+ senderGroupIdentity,
25
39
  } from "./send-handler.js";
26
40
  import { SupervisorChannelCache } from "./supervisor-channel.js";
27
41
  import { hasGroup, normalizeGroup, normalizeGroups } from "../group.js";
@@ -34,6 +48,8 @@ import {
34
48
  setSessionGroups,
35
49
  } from "./group-membership.js";
36
50
  import { PendingQuestionIndex } from "./pending-question-index.js";
51
+ import { matchStagePathSegments } from "../workflow-stage-path-matching.js";
52
+ import { DELIVERED_MESSAGE_TTL_MS } from "../retry-policy.js";
37
53
 
38
54
  const INTERCOM_DIR = getIntercomDirPath();
39
55
  const SOCKET_PATH = getBrokerSocketPath();
@@ -51,6 +67,7 @@ interface WorkflowRosterRegistration {
51
67
  readonly ownerSessionId: string;
52
68
  readonly group: string;
53
69
  readonly stages: WorkflowStageRosterAnnouncement[];
70
+ readonly possibleStages?: readonly WorkflowPossibleStageAnnouncement[];
54
71
  }
55
72
 
56
73
 
@@ -59,8 +76,8 @@ interface PendingStageAcknowledgment {
59
76
  readonly senderSocket: net.Socket;
60
77
  readonly messageId: string;
61
78
  readonly attemptId?: string;
62
- readonly runId: string;
63
- readonly stageKey: string;
79
+ readonly runId: string;
80
+ readonly target: string;
64
81
  readonly senderSessionId: string;
65
82
  readonly sender: SessionInfo;
66
83
  readonly message: Message;
@@ -82,6 +99,8 @@ interface PendingStageNotificationAcknowledgment {
82
99
  interface LiveWorkflowStageRouteRegistration {
83
100
  readonly sessionId: string;
84
101
  readonly capability: string;
102
+ /** Run whose route registration authorized this alias; capabilities are per run. */
103
+ readonly runId: string;
85
104
  }
86
105
 
87
106
  interface LiveWorkflowStageRouteActivation {
@@ -136,22 +155,61 @@ function invocationOwnsGroup(invocationGroup: string, candidateGroup: string): b
136
155
  return candidate === owner || candidate.startsWith(`${owner}/`);
137
156
  }
138
157
 
139
- function isWorkflowStageRosterAnnouncements(value: unknown, runId: string): value is WorkflowStageRosterAnnouncement[] {
158
+ function isWorkflowStageRosterAnnouncements(
159
+ value: unknown,
160
+ runId: string,
161
+ invocationGroup: string,
162
+ ): value is WorkflowStageRosterAnnouncement[] {
163
+ const groupRootRunId = invocationGroup.startsWith("workflow:")
164
+ ? invocationGroup.slice("workflow:".length)
165
+ : undefined;
166
+ return (
167
+ Array.isArray(value) &&
168
+ value.every((stage) => {
169
+ if (typeof stage !== "object" || stage === null) return false;
170
+ const announcement = stage as WorkflowStageRosterAnnouncement;
171
+ const parsed = parseWorkflowStageTarget(announcement.target);
172
+ return (
173
+ typeof announcement.stageId === "string" &&
174
+ typeof announcement.stageName === "string" &&
175
+ parsed?.kind === "path" &&
176
+ parsed.segments.at(-1) === announcement.stageId &&
177
+ // Depth-faithful targets are anchored at the invocation root even when the
178
+ // announcing run is a nested descendant whose run id appears in no segment
179
+ // position (boundary-name segments). The runId conditions keep the flat
180
+ // shortcut form valid for hosts that have not adopted the clarified form.
181
+ ((groupRootRunId !== undefined && groupRootRunId === parsed.rootRunId) ||
182
+ runId === parsed.rootRunId ||
183
+ parsed.segments.slice(0, -1).includes(runId)) &&
184
+ (announcement.lifecycle === "pending" || announcement.lifecycle === "running") &&
185
+ typeof announcement.routeEligible === "boolean" &&
186
+ typeof announcement.group === "string"
187
+ );
188
+ })
189
+ );
190
+ }
191
+ function isWorkflowPossibleStageAnnouncements(
192
+ value: unknown,
193
+ runId: string,
194
+ invocationGroup: string,
195
+ ): value is WorkflowPossibleStageAnnouncement[] {
196
+ const groupRootRunId = invocationGroup.startsWith("workflow:")
197
+ ? invocationGroup.slice("workflow:".length)
198
+ : undefined;
140
199
  return (
141
200
  Array.isArray(value) &&
142
- value.every(
143
- (stage) =>
144
- typeof stage === "object" &&
145
- stage !== null &&
146
- typeof (stage as WorkflowStageRosterAnnouncement).stageId === "string" &&
147
- typeof (stage as WorkflowStageRosterAnnouncement).stageName === "string" &&
148
- (stage as WorkflowStageRosterAnnouncement).target ===
149
- `${runId}:${(stage as WorkflowStageRosterAnnouncement).stageId}` &&
150
- ((stage as WorkflowStageRosterAnnouncement).lifecycle === "pending" ||
151
- (stage as WorkflowStageRosterAnnouncement).lifecycle === "running") &&
152
- typeof (stage as WorkflowStageRosterAnnouncement).routeEligible === "boolean" &&
153
- typeof (stage as WorkflowStageRosterAnnouncement).group === "string"
154
- )
201
+ value.every((row) => {
202
+ if (typeof row !== "object" || row === null) return false;
203
+ const announcement = row as WorkflowPossibleStageAnnouncement;
204
+ const parsed = parseWorkflowStageTarget(announcement.target);
205
+ return (
206
+ parsed !== undefined &&
207
+ ((groupRootRunId !== undefined && groupRootRunId === parsed.rootRunId) || runId === parsed.rootRunId) &&
208
+ typeof announcement.queuedCount === "number" &&
209
+ Number.isInteger(announcement.queuedCount) &&
210
+ announcement.queuedCount >= 0
211
+ );
212
+ })
155
213
  );
156
214
  }
157
215
 
@@ -160,7 +218,11 @@ class IntercomBroker {
160
218
  private sessions = new Map<string, ConnectedSession>();
161
219
  private server: net.Server;
162
220
  private shutdownTimer: NodeJS.Timeout | null = null;
163
- private deliveredMessages = new DeliveredMessageCache();
221
+ private deliveredMessages = new DeliveredMessageCache(
222
+ DELIVERED_MESSAGE_TTL_MS,
223
+ DELIVERED_MESSAGE_MAX_ENTRIES,
224
+ getBrokerDeliveredMessagesPath(),
225
+ );
164
226
  private supervisorChannel = new SupervisorChannelCache();
165
227
  private pendingQuestions = new PendingQuestionIndex();
166
228
  private pendingStageRoutes = new Map<string, PendingStageRouteRegistration>();
@@ -171,7 +233,8 @@ class IntercomBroker {
171
233
  private liveWorkflowStageRouteActivations = new Map<string, LiveWorkflowStageRouteActivation>();
172
234
 
173
235
  constructor() {
174
- mkdirSync(INTERCOM_DIR, { recursive: true });
236
+ mkdirSync(INTERCOM_DIR, { recursive: true, mode: 0o700 });
237
+ if (process.platform !== "win32") chmodSync(INTERCOM_DIR, 0o700);
175
238
  if (process.platform !== "win32") {
176
239
  try {
177
240
  unlinkSync(SOCKET_PATH);
@@ -206,13 +269,38 @@ class IntercomBroker {
206
269
 
207
270
  socket.on("data", reader);
208
271
 
272
+ // Retire the session as soon as its socket stops being able to accept a
273
+ // frame, not only on 'close'. A peer that half-closes -- or one whose
274
+ // writable side the broker itself ended after refusing a registration --
275
+ // can keep its read side open indefinitely, so 'close' may never arrive
276
+ // while the session is still in the fan-out map and every broadcast, ack
277
+ // and delivery targets a socket whose writable side is already gone.
278
+ //
279
+ // 'end' is a safe retirement point: Node runs its automatic end() after the
280
+ // user listeners, so the socket is still writable here and no final frame
281
+ // is lost. disconnectSession is idempotent, so 'end', 'error' and 'close'
282
+ // may each fire without duplicating the departure broadcast.
283
+ const retire = () => {
284
+ const departing = sessionId;
285
+ if (departing === null) return;
286
+ sessionId = null;
287
+ this.disconnectSession(departing);
288
+ };
289
+
290
+ socket.on("end", () => {
291
+ retire();
292
+ this.scheduleShutdownCheck();
293
+ });
294
+
209
295
  socket.on("close", () => {
210
- if (sessionId) this.disconnectSession(sessionId);
296
+ retire();
211
297
  this.scheduleShutdownCheck();
212
298
  });
213
299
 
214
300
  socket.on("error", (error) => {
215
301
  console.error("Socket error:", error);
302
+ retire();
303
+ this.scheduleShutdownCheck();
216
304
  });
217
305
  }
218
306
 
@@ -227,21 +315,87 @@ class IntercomBroker {
227
315
  }
228
316
  }, 5000);
229
317
  }
230
- private resolveLiveWorkflowStage = (target: string): ConnectedSession | undefined => {
231
- const registration = this.liveWorkflowStageRoutes.get(target);
232
- if (registration === undefined) return undefined;
233
- const parsedTarget = parsePendingStageTarget(target);
234
- const currentOwner = parsedTarget === undefined ? undefined : this.pendingStageRoutes.get(parsedTarget.runId);
235
- if (currentOwner === undefined) return undefined;
236
- if (currentOwner.capability !== registration.capability) {
318
+
319
+ /**
320
+ * End a connection the broker itself is refusing, and retire its session first.
321
+ *
322
+ * `socket.end()` only sends FIN. A peer opened with `allowHalfOpen` — or one
323
+ * that simply has not read yet — never has to close in response, so without
324
+ * this the refused session would stay in the fan-out map indefinitely: still
325
+ * advertised by `list`, still acked as `delivered`, and still written to by
326
+ * every broadcast even though its writable side is gone.
327
+ */
328
+ private endRefusedConnection(
329
+ socket: net.Socket,
330
+ currentId: string | null,
331
+ setId: (id: string | null) => void,
332
+ ): void {
333
+ if (currentId !== null) {
334
+ this.disconnectSession(currentId);
335
+ setId(null);
336
+ }
337
+ socket.end();
338
+ this.scheduleShutdownCheck();
339
+ }
340
+ private pendingStageOwnerForTarget(
341
+ target: string,
342
+ ): { readonly runId: string; readonly registration: PendingStageRouteRegistration } | undefined {
343
+ const parsed = parseWorkflowStageTarget(target);
344
+ if (parsed === undefined) return undefined;
345
+ const invocationGroup = normalizeGroup(`workflow:${parsed.rootRunId}`);
346
+ // Slice 3 (D3/D6): pattern and `**` targets are anchored at the invocation root
347
+ // registration — glob segments name no nested run, and the sticky entry lives in
348
+ // the root run's durable bucket.
349
+ if (parsed.kind !== "path") {
350
+ const rootRegistration = this.pendingStageRoutes.get(parsed.rootRunId);
351
+ return rootRegistration !== undefined && normalizeGroup(rootRegistration.group) === invocationGroup
352
+ ? { runId: parsed.rootRunId, registration: rootRegistration }
353
+ : undefined;
354
+ }
355
+ let ownerRunId = parsed.rootRunId;
356
+ let registration = this.pendingStageRoutes.get(ownerRunId);
357
+ for (const segment of parsed.segments.slice(0, -1)) {
358
+ const candidate = this.pendingStageRoutes.get(segment);
359
+ if (candidate !== undefined && normalizeGroup(candidate.group) === invocationGroup) {
360
+ ownerRunId = segment;
361
+ registration = candidate;
362
+ }
363
+ }
364
+ return registration === undefined ? undefined : { runId: ownerRunId, registration };
365
+ }
366
+
367
+ private resolveLegacyWorkflowStageTarget = (runId: string, stageKey: string): string | undefined => {
368
+ const route = this.pendingStageRoutes.get(runId);
369
+ const roster = this.workflowRosters.get(runId);
370
+ if (route === undefined || roster === undefined || !route.group.startsWith("workflow:")) return undefined;
371
+ const matches = roster.stages.filter((stage) => stage.stageId === stageKey || stage.stageName === stageKey);
372
+ return matches.length === 1 ? matches[0]!.target : undefined;
373
+ };
374
+
375
+ private resolveLiveWorkflowStage = (target: string): ConnectedSession | undefined => {
376
+ const registration = this.liveWorkflowStageRoutes.get(target);
377
+ if (registration === undefined) return undefined;
378
+ // The alias is owned by the run that registered it: route capabilities are per run,
379
+ // and for boundary-form targets the depth-first owner resolution selects the root
380
+ // registration, whose capability legitimately differs from the nested stage's own.
381
+ // Validate staleness against the alias's own run registration and require the alias
382
+ // to stay anchored inside the target's invocation.
383
+ const ownRegistration = this.pendingStageRoutes.get(registration.runId);
384
+ const parsed = parseWorkflowStageTarget(target);
385
+ if (
386
+ ownRegistration === undefined ||
387
+ ownRegistration.capability !== registration.capability ||
388
+ parsed === undefined ||
389
+ normalizeGroup(ownRegistration.group) !== normalizeGroup(`workflow:${parsed.rootRunId}`)
390
+ ) {
237
391
  this.liveWorkflowStageRoutes.delete(target);
238
392
  return undefined;
239
393
  }
240
- const session = this.sessions.get(registration.sessionId);
241
- if (session !== undefined) return session;
242
- this.liveWorkflowStageRoutes.delete(target);
243
- return undefined;
244
- };
394
+ const session = this.sessions.get(registration.sessionId);
395
+ if (session !== undefined) return session;
396
+ this.liveWorkflowStageRoutes.delete(target);
397
+ return undefined;
398
+ };
245
399
 
246
400
  private canInspectSelectedGroup(requester: ConnectedSession, selectedGroup: string): boolean {
247
401
  const selected = normalizeGroup(selectedGroup);
@@ -270,14 +424,16 @@ class IntercomBroker {
270
424
  target: ConnectedSession,
271
425
  logicalTarget: string,
272
426
  ): boolean => {
273
- const parsed = parsePendingStageTarget(logicalTarget);
274
- const owner = parsed === undefined ? undefined : this.pendingStageRoutes.get(parsed.runId);
427
+ const owner = this.pendingStageOwnerForTarget(logicalTarget)?.registration;
275
428
  const live = this.liveWorkflowStageRoutes.get(logicalTarget);
276
429
  if (owner === undefined || live?.sessionId !== target.info.id) return false;
277
430
  const targetGroup = target.registrationGroup ?? target.info.group ?? "default";
278
431
  return this.canControlWorkflowInvocation(sender, owner.group) && invocationOwnsGroup(owner.group, targetGroup);
279
432
  };
280
433
 
434
+ // `send`/`ask` always resolve this namespace as a stage target. `list({ group })`
435
+ // remains the only operation that interprets the same path as an owned subgroup.
436
+
281
437
  private workflowStagesVisibleTo(requester: ConnectedSession, selectedGroup?: string): WorkflowStageRosterEntry[] {
282
438
  const requesterGroups = sessionGroups(requester.info);
283
439
  const selected = selectedGroup === undefined ? undefined : normalizeGroup(selectedGroup);
@@ -311,12 +467,42 @@ class IntercomBroker {
311
467
  return entries;
312
468
  }
313
469
 
470
+ /**
471
+ * D7 (slice 4): possible-future rows published with the run's persisted scan. Visible
472
+ * only from inside the invocation — a membership in `workflow:<rootRunId>` or in an
473
+ * owned subgroup (`workflow:<rootRunId>/…`). The read-only peek filter keeps the
474
+ * invocation-group rows out of every other group's view.
475
+ */
476
+ private workflowFutureStagesVisibleTo(
477
+ requester: ConnectedSession,
478
+ selectedGroup?: string,
479
+ ): WorkflowFutureStageRosterEntry[] {
480
+ const requesterGroups = sessionGroups(requester.info);
481
+ const selected = selectedGroup === undefined ? undefined : normalizeGroup(selectedGroup);
482
+ const entries: WorkflowFutureStageRosterEntry[] = [];
483
+ for (const [runId, roster] of this.workflowRosters) {
484
+ if (roster.possibleStages === undefined || roster.possibleStages.length === 0) continue;
485
+ if (![...requesterGroups].some((group) => invocationOwnsGroup(roster.group, group))) continue;
486
+ if (selected !== undefined && selected !== roster.group) continue;
487
+ for (const row of roster.possibleStages) {
488
+ entries.push({
489
+ kind: "workflow-future-stage",
490
+ runId,
491
+ target: row.target,
492
+ queuedCount: row.queuedCount,
493
+ group: roster.group,
494
+ });
495
+ }
496
+ }
497
+ return entries;
498
+ }
499
+
314
500
  private acknowledgeLiveWorkflowStageRoute(requestId: string): void {
315
501
  const activation = this.liveWorkflowStageRouteActivations.get(requestId);
316
502
  if (activation === undefined || activation.pendingRequestIds.size > 0) return;
317
503
  this.liveWorkflowStageRouteActivations.delete(requestId);
318
504
  const stage = this.sessions.get(activation.sessionId);
319
- if (stage !== undefined) writeMessage(stage.socket, { type: "live_workflow_stage_route_registered", requestId });
505
+ if (stage !== undefined) writeMessageIfOpen(stage.socket, { type: "live_workflow_stage_route_registered", requestId });
320
506
  }
321
507
 
322
508
  private releasePendingStageAcknowledgment(requestId: string): void {
@@ -333,10 +519,29 @@ class IntercomBroker {
333
519
  stageKeys: readonly string[],
334
520
  capability: string,
335
521
  ): boolean {
336
- const uniqueStageKeys = [...new Set(stageKeys)];
337
- const targets = uniqueStageKeys.map((stageKey) => `${runId}:${stageKey}`);
338
- for (const target of targets) {
339
- const existing = this.liveWorkflowStageRoutes.get(target);
522
+ const uniqueStageKeys = [...new Set(stageKeys)];
523
+ const owner = this.pendingStageRoutes.get(runId);
524
+ if (owner === undefined || !owner.group.startsWith("workflow:")) return false;
525
+ const rootRunId = owner.group.slice("workflow:".length);
526
+ // D8 clarification: advertised targets are depth-faithful, so the live aliases must be
527
+ // depth-faithful too. The roster publishes the id-form target per stage; the name form
528
+ // swaps its final segment. Without a roster entry, fall back to the flat run-id prefix
529
+ // (still an accepted resolver input).
530
+ const roster = this.workflowRosters.get(runId);
531
+ const targets = uniqueStageKeys.map((stageKey) => {
532
+ const entry = roster?.stages.find((stage) => stage.stageId === stageKey || stage.stageName === stageKey);
533
+ const entryTarget = entry === undefined ? undefined : parseWorkflowStageTarget(entry.target);
534
+ if (entry !== undefined && entryTarget?.kind === "path") {
535
+ if (stageKey !== entry.stageName) return entry.target;
536
+ return (
537
+ withWorkflowStageTargetFinalSegment(entry.target, entry.stageName) ?? entry.target
538
+ );
539
+ }
540
+ const prefix = runId === rootRunId ? [] : [runId];
541
+ return formatWorkflowStageTarget(rootRunId, ...prefix, stageKey);
542
+ });
543
+ for (const target of targets) {
544
+ const existing = this.liveWorkflowStageRoutes.get(target);
340
545
  if (
341
546
  existing !== undefined &&
342
547
  existing.sessionId !== currentId &&
@@ -346,11 +551,32 @@ class IntercomBroker {
346
551
  }
347
552
  }
348
553
  for (const target of targets) {
349
- this.liveWorkflowStageRoutes.set(target, { sessionId: currentId, capability });
554
+ this.liveWorkflowStageRoutes.set(target, { sessionId: currentId, capability, runId });
350
555
  }
351
556
  const pendingRequestIds = new Set(
352
557
  [...this.pendingStageAcknowledgments]
353
- .filter(([, pending]) => pending.runId === runId && uniqueStageKeys.includes(pending.stageKey))
558
+ .filter(([, pending]) => {
559
+ // Match in-flight pendings of the whole invocation, not just this run's own:
560
+ // boundary-form pendings settle on the root registration while the going-live
561
+ // stage may register under its nested child run.
562
+ const parsedPending = parseWorkflowStageTarget(pending.target);
563
+ if (parsedPending === undefined || parsedPending.rootRunId !== rootRunId) return false;
564
+ if (parsedPending.kind !== "path") {
565
+ // Slice 3: sticky pattern pendings hold the going-live barrier when the
566
+ // stage's advertised depth-faithful target matches the pattern.
567
+ return uniqueStageKeys.some((stageKey) => {
568
+ const entry = roster?.stages.find(
569
+ (candidate) => candidate.stageId === stageKey || candidate.stageName === stageKey,
570
+ );
571
+ const parsedEntry = entry === undefined ? undefined : parseWorkflowStageTarget(entry.target);
572
+ if (parsedEntry === undefined) {
573
+ return matchStagePathSegments(parsedPending.segments, parsedPending.segments);
574
+ }
575
+ return matchStagePathSegments(parsedPending.segments, parsedEntry.segments);
576
+ });
577
+ }
578
+ return uniqueStageKeys.includes(parsedPending.segments.at(-1) ?? "");
579
+ })
354
580
  .map(([pendingRequestId]) => pendingRequestId),
355
581
  );
356
582
  this.liveWorkflowStageRouteActivations.set(requestId, { sessionId: currentId, pendingRequestIds });
@@ -358,16 +584,17 @@ class IntercomBroker {
358
584
  return true;
359
585
  }
360
586
 
361
- private routePendingStage = (route: PendingStageRoute): boolean => {
362
- const ownerRegistration = this.pendingStageRoutes.get(route.runId);
363
- if (ownerRegistration === undefined) return false;
364
- const owner = this.sessions.get(ownerRegistration.sessionId);
365
- if (owner === undefined) {
366
- this.pendingStageRoutes.delete(route.runId);
367
- return false;
368
- }
587
+ private routePendingStage = (route: PendingStageRoute): boolean => {
588
+ const ownerMatch = this.pendingStageOwnerForTarget(route.target);
589
+ if (ownerMatch === undefined) return false;
590
+ const { runId, registration: ownerRegistration } = ownerMatch;
591
+ const owner = this.sessions.get(ownerRegistration.sessionId);
592
+ if (owner === undefined) {
593
+ this.pendingStageRoutes.delete(runId);
594
+ return false;
595
+ }
369
596
  if (route.liveTargetId === undefined && !this.canControlWorkflowInvocation(route.from, ownerRegistration.group)) {
370
- writeMessage(route.socket, {
597
+ writeMessageIfOpen(route.socket, {
371
598
  type: "delivery_failed",
372
599
  messageId: route.message.id,
373
600
  ...(route.attemptId ? { attemptId: route.attemptId } : {}),
@@ -381,7 +608,7 @@ class IntercomBroker {
381
608
  if (pending === undefined) return;
382
609
  this.pendingStageAcknowledgments.delete(requestId);
383
610
  this.releasePendingStageAcknowledgment(requestId);
384
- writeMessage(pending.senderSocket, {
611
+ writeMessageIfOpen(pending.senderSocket, {
385
612
  type: "delivery_failed",
386
613
  messageId: pending.messageId,
387
614
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
@@ -393,8 +620,8 @@ class IntercomBroker {
393
620
  senderSocket: route.socket,
394
621
  messageId: route.message.id,
395
622
  ...(route.attemptId ? { attemptId: route.attemptId } : {}),
396
- runId: route.runId,
397
- stageKey: route.stageKey,
623
+ runId,
624
+ target: route.target,
398
625
  senderSessionId: route.from.info.id,
399
626
  sender: { ...route.from.info },
400
627
  message: route.message,
@@ -402,19 +629,31 @@ class IntercomBroker {
402
629
  ...(route.signature === undefined ? {} : { signature: route.signature }),
403
630
  timeout,
404
631
  });
405
- writeMessage(owner.socket, {
406
- type: "pending_stage_message",
407
- requestId,
408
- from: route.from.info,
632
+ if (
633
+ !writeMessageIfOpen(owner.socket, {
634
+ type: "pending_stage_message",
635
+ requestId,
636
+ from: route.from.info,
409
637
  ...(route.from.registrationName === undefined ? {} : { senderRegistrationName: route.from.registrationName }),
410
638
  ...(route.from.registrationReturnAddress === undefined
411
639
  ? {}
412
640
  : { senderReturnAddress: route.from.registrationReturnAddress }),
413
- runId: route.runId,
414
- stageKey: route.stageKey,
415
- message: route.message,
641
+ runId,
642
+ target: route.target,
643
+ message: route.message,
416
644
  ...(route.liveTargetId === undefined ? {} : { live: true }),
417
- });
645
+ })
646
+ ) {
647
+ // The owner's socket stopped accepting frames between resolving the route
648
+ // and this write. Unwind the acknowledgment just armed and report the
649
+ // route as not handled, so the caller answers with an honest
650
+ // delivery_failed now instead of stalling the sender for the full
651
+ // acknowledgment timeout on a message nobody ever received.
652
+ clearTimeout(timeout);
653
+ this.pendingStageAcknowledgments.delete(requestId);
654
+ this.releasePendingStageAcknowledgment(requestId);
655
+ return false;
656
+ }
418
657
  return true;
419
658
  };
420
659
 
@@ -423,8 +662,11 @@ class IntercomBroker {
423
662
  requestId: string,
424
663
  outcome: "queued" | "delivered" | "forward" | "refused",
425
664
  position: number | undefined,
665
+ forwardTarget: string | undefined,
426
666
  reason: string | undefined,
427
667
  reasonCode: "message_id_conflict" | undefined,
668
+ notInKnownSet?: true,
669
+ forwardTargets?: readonly string[],
428
670
  ): void {
429
671
  const pending = this.pendingStageAcknowledgments.get(requestId);
430
672
  if (pending === undefined || pending.ownerSessionId !== currentId) return;
@@ -432,11 +674,11 @@ class IntercomBroker {
432
674
  this.pendingStageAcknowledgments.delete(requestId);
433
675
  this.releasePendingStageAcknowledgment(requestId);
434
676
  if (outcome === "forward") {
435
- this.forwardValidatedLiveStageMessage(pending);
677
+ this.forwardValidatedLiveStageMessage(pending, forwardTarget);
436
678
  return;
437
679
  }
438
680
  if (outcome === "delivered") {
439
- writeMessage(pending.senderSocket, {
681
+ writeMessageIfOpen(pending.senderSocket, {
440
682
  type: "delivered",
441
683
  messageId: pending.messageId,
442
684
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
@@ -444,17 +686,34 @@ class IntercomBroker {
444
686
  return;
445
687
  }
446
688
  if (outcome === "queued" && typeof position === "number" && position > 0) {
447
- writeMessage(pending.senderSocket, {
689
+ // Slice 3 (D6/D9): a sticky pattern entry answers `queued` once while any
690
+ // matching live stages receive the message now, as ordinary inbound
691
+ // messages with the original sender identity. The owner records a live
692
+ // delivery only for the targets this write actually reached, so a stage
693
+ // that dropped between the owner's answer and this write is not marked
694
+ // delivered and a retried send re-forwards to it.
695
+ const deliveredTargets = this.deliverStickyBroadcast(pending, forwardTargets);
696
+ const ownerSocket = this.sessions.get(currentId)?.socket;
697
+ if (deliveredTargets.length > 0 && ownerSocket !== undefined) {
698
+ writeMessageIfOpen(ownerSocket, {
699
+ type: "sticky_live_delivered",
700
+ runId: pending.runId,
701
+ messageId: pending.messageId,
702
+ target: pending.target,
703
+ deliveredTargets,
704
+ });
705
+ }
706
+ writeMessageIfOpen(pending.senderSocket, {
448
707
  type: "queued",
449
708
  messageId: pending.messageId,
450
709
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
451
- runId: pending.runId,
452
- stageKey: pending.stageKey,
710
+ target: pending.target,
453
711
  position,
712
+ ...(notInKnownSet === true ? { notInKnownSet: true } : {}),
454
713
  });
455
714
  return;
456
715
  }
457
- writeMessage(pending.senderSocket, {
716
+ writeMessageIfOpen(pending.senderSocket, {
458
717
  type: "delivery_failed",
459
718
  messageId: pending.messageId,
460
719
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
@@ -463,16 +722,27 @@ class IntercomBroker {
463
722
  });
464
723
  }
465
724
 
466
- private forwardValidatedLiveStageMessage(pending: PendingStageAcknowledgment): void {
725
+ private forwardValidatedLiveStageMessage(pending: PendingStageAcknowledgment, forwardTarget?: string): void {
467
726
  const from = this.sessions.get(pending.senderSessionId);
468
- const target = this.resolveLiveWorkflowStage(`${pending.runId}:${pending.stageKey}`);
727
+ // The route owner chooses the live alias for boundary-form targets, so bind its
728
+ // forward answer to the sender's own invocation: a target under any other rootRunId
729
+ // could deliver a sender's message into a different invocation, bypassing the
730
+ // sender's group authorization on the ordinary send path.
731
+ const destination = forwardTarget ?? pending.target;
732
+ const parsedDestination = parseWorkflowStageTarget(destination);
733
+ const parsedPending = parseWorkflowStageTarget(pending.target);
734
+ const crossInvocation =
735
+ parsedDestination === undefined ||
736
+ parsedPending === undefined ||
737
+ parsedDestination.rootRunId !== parsedPending.rootRunId;
738
+ const target = crossInvocation ? undefined : this.resolveLiveWorkflowStage(destination);
469
739
  if (
470
740
  from === undefined ||
471
741
  target === undefined ||
472
- target.info.id !== pending.liveTargetId ||
742
+ (pending.liveTargetId !== undefined && target.info.id !== pending.liveTargetId) ||
473
743
  pending.signature === undefined
474
744
  ) {
475
- writeMessage(pending.senderSocket, {
745
+ writeMessageIfOpen(pending.senderSocket, {
476
746
  type: "delivery_failed",
477
747
  messageId: pending.messageId,
478
748
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
@@ -480,28 +750,144 @@ class IntercomBroker {
480
750
  });
481
751
  return;
482
752
  }
483
- const deliveredMatch = this.deliveredMessages.lookup(pending.messageId, pending.signature);
484
- if (deliveredMatch === "conflict") {
485
- writeMessage(pending.senderSocket, {
753
+ const targetIdentity = deliveryTargetIdentity(target);
754
+ const deliveredMatch = this.deliveredMessages.lookupForTarget(
755
+ pending.messageId,
756
+ pending.signature,
757
+ targetIdentity,
758
+ );
759
+ if (deliveredMatch !== "miss") {
760
+ if (deliveredMatch === "match") {
761
+ if (pending.message.expectsReply === true) {
762
+ const acceptedQuestionTarget = this.deliveredMessages.lookupQuestionTarget(
763
+ pending.messageId,
764
+ pending.signature,
765
+ senderGroupIdentity(from),
766
+ );
767
+ if (acceptedQuestionTarget === undefined) {
768
+ writeMessageIfOpen(pending.senderSocket, {
769
+ type: "delivery_failed",
770
+ messageId: pending.messageId,
771
+ ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
772
+ reason: "Accepted Intercom question route could not be securely rebound",
773
+ });
774
+ return;
775
+ }
776
+ this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
777
+ }
778
+ writeMessageIfOpen(pending.senderSocket, {
779
+ type: "delivered",
780
+ messageId: pending.messageId,
781
+ ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
782
+ });
783
+ return;
784
+ }
785
+ writeMessageIfOpen(pending.senderSocket, {
486
786
  type: "delivery_failed",
487
787
  messageId: pending.messageId,
488
788
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
489
- reason: `Intercom message ID '${pending.messageId}' was already delivered with a different target or payload`,
490
- reasonCode: "message_id_conflict",
789
+ reason:
790
+ deliveredMatch === "conflict"
791
+ ? `Intercom message ID '${pending.messageId}' was already delivered with a different target or payload`
792
+ : deliveredMatch === "uncertain"
793
+ ? "Intercom cannot prove whether this reserved operation reached its recipient; refusing redelivery"
794
+ : "Intercom accepted-delivery authority is invalid; refusing possible duplicate delivery",
795
+ ...(deliveredMatch === "conflict" ? { reasonCode: "message_id_conflict" as const } : {}),
491
796
  });
492
797
  return;
493
798
  }
494
- if (deliveredMatch === "miss") {
495
- writeMessage(target.socket, { type: "message", from: pending.sender, message: pending.message });
496
- this.deliveredMessages.record(pending.messageId, pending.signature);
799
+ const reservation =
800
+ pending.message.expectsReply === true
801
+ ? this.deliveredMessages.reserveQuestion(
802
+ pending.messageId,
803
+ pending.signature,
804
+ {
805
+ targetSessionId: target.info.id,
806
+ senderGroupIdentity: senderGroupIdentity(from),
807
+ },
808
+ Date.now(),
809
+ targetIdentity,
810
+ )
811
+ : this.deliveredMessages.reserve(pending.messageId, pending.signature, Date.now(), targetIdentity);
812
+ if (reservation === "match") {
497
813
  if (pending.message.expectsReply === true) {
814
+ const acceptedQuestionTarget = this.deliveredMessages.lookupQuestionTarget(
815
+ pending.messageId,
816
+ pending.signature,
817
+ senderGroupIdentity(from),
818
+ );
819
+ if (acceptedQuestionTarget === undefined) {
820
+ writeMessageIfOpen(pending.senderSocket, {
821
+ type: "delivery_failed",
822
+ messageId: pending.messageId,
823
+ ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
824
+ reason: "Accepted Intercom question route could not be securely rebound",
825
+ });
826
+ return;
827
+ }
498
828
  this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
499
829
  }
500
- if (pending.message.replyTo !== undefined) {
501
- this.pendingQuestions.clearReply(from.info.id, target.info.id, pending.message.replyTo);
502
- }
830
+ writeMessageIfOpen(pending.senderSocket, {
831
+ type: "delivered",
832
+ messageId: pending.messageId,
833
+ ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
834
+ });
835
+ return;
836
+ }
837
+ if (reservation !== "recorded") {
838
+ writeMessageIfOpen(pending.senderSocket, {
839
+ type: "delivery_failed",
840
+ messageId: pending.messageId,
841
+ ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
842
+ reason:
843
+ reservation === "capacity"
844
+ ? "Intercom accepted-delivery capacity is full; refusing delivery without evicting live retry authority"
845
+ : reservation === "uncertain"
846
+ ? "Intercom cannot prove whether this reserved operation reached its recipient; refusing redelivery"
847
+ : "Intercom accepted-delivery authority changed before forwarding; refusing possible duplicate delivery",
848
+ ...(reservation === "conflict" ? { reasonCode: "message_id_conflict" as const } : {}),
849
+ });
850
+ return;
503
851
  }
504
- writeMessage(pending.senderSocket, {
852
+ if (!writeMessageIfOpen(target.socket, { type: "message", from: pending.sender, message: pending.message })) {
853
+ this.deliveredMessages.forget(pending.messageId, pending.signature);
854
+ writeMessageIfOpen(pending.senderSocket, {
855
+ type: "delivery_failed",
856
+ messageId: pending.messageId,
857
+ ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
858
+ reason: "Session not found",
859
+ });
860
+ return;
861
+ }
862
+ let accepted: ReturnType<DeliveredMessageCache["accept"]>;
863
+ try {
864
+ accepted = this.deliveredMessages.accept(pending.messageId, pending.signature, targetIdentity);
865
+ } catch {
866
+ writeMessageIfOpen(pending.senderSocket, {
867
+ type: "delivery_failed",
868
+ messageId: pending.messageId,
869
+ ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
870
+ reason: "Intercom could not durably confirm the forwarded operation; refusing a possibly unsafe retry",
871
+ });
872
+ return;
873
+ }
874
+ if (accepted !== "accepted") {
875
+ writeMessageIfOpen(pending.senderSocket, {
876
+ type: "delivery_failed",
877
+ messageId: pending.messageId,
878
+ ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
879
+ reason: "Intercom could not durably confirm the forwarded operation; refusing a possibly unsafe retry",
880
+ ...(accepted === "conflict" ? { reasonCode: "message_id_conflict" as const } : {}),
881
+ });
882
+ return;
883
+ }
884
+ if (pending.message.expectsReply === true) {
885
+ this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
886
+ }
887
+ if (pending.message.replyTo !== undefined) {
888
+ this.pendingQuestions.clearReply(from.info.id, target.info.id, pending.message.replyTo);
889
+ }
890
+ writeMessageIfOpen(pending.senderSocket, {
505
891
  type: "delivered",
506
892
  messageId: pending.messageId,
507
893
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
@@ -509,6 +895,38 @@ class IntercomBroker {
509
895
  }
510
896
 
511
897
 
898
+ /**
899
+ * Slice 3 (D6/D9): deliver a sticky broadcast to the live stages the host validated,
900
+ * each as an ordinary inbound message frame carrying the original sender identity.
901
+ */
902
+ private deliverStickyBroadcast(
903
+ pending: PendingStageAcknowledgment,
904
+ forwardTargets: readonly string[] | undefined,
905
+ ): string[] {
906
+ if (forwardTargets === undefined || forwardTargets.length === 0) return [];
907
+ const parsedPending = parseWorkflowStageTarget(pending.target);
908
+ if (parsedPending === undefined) return [];
909
+ const invocationGroup = normalizeGroup(`workflow:${parsedPending.rootRunId}`);
910
+ const delivered: string[] = [];
911
+ for (const target of forwardTargets) {
912
+ const targetSession = this.resolveLiveWorkflowStage(target);
913
+ if (targetSession === undefined) continue;
914
+ const targetGroup = targetSession.registrationGroup ?? targetSession.info.group ?? "default";
915
+ if (!invocationOwnsGroup(invocationGroup, normalizeGroup(targetGroup))) continue;
916
+ // The write itself is the writability check, so a stage that dropped between
917
+ // the owner's answer and this loop is not reported as delivered and a retried
918
+ // send re-forwards to it.
919
+ if (!writeMessageIfOpen(targetSession.socket, { type: "message", from: pending.sender, message: pending.message }))
920
+ continue;
921
+ delivered.push(target);
922
+ }
923
+ // Deliberately NOT recorded in the delivered-message cache (round-1 review): the
924
+ // sticky ledger in the workflow host is the durable dedupe authority, so a retry
925
+ // re-routes to the host, dedupes there, and the sender's ack stays `queued`
926
+ // instead of flipping to `delivered` between attempts.
927
+ return delivered;
928
+ }
929
+
512
930
  private failPendingStageNotification(
513
931
  socket: net.Socket,
514
932
  messageId: string,
@@ -516,7 +934,7 @@ class IntercomBroker {
516
934
  reason: string,
517
935
  reasonCode?: "message_id_conflict",
518
936
  ): void {
519
- writeMessage(socket, {
937
+ writeMessageIfOpen(socket, {
520
938
  type: "delivery_failed",
521
939
  messageId,
522
940
  ...(attemptId === undefined ? {} : { attemptId }),
@@ -571,16 +989,20 @@ class IntercomBroker {
571
989
  const signature = buildMessageSendSignature(logicalTarget, message, clientMessage.capability);
572
990
  const deliveredMatch = this.deliveredMessages.lookup(message.id, signature);
573
991
  if (deliveredMatch === "match") {
574
- writeMessage(socket, { type: "delivered", messageId: message.id, ...(attemptId === undefined ? {} : { attemptId }) });
992
+ writeMessageIfOpen(socket, { type: "delivered", messageId: message.id, ...(attemptId === undefined ? {} : { attemptId }) });
575
993
  return;
576
994
  }
577
- if (deliveredMatch === "conflict") {
995
+ if (deliveredMatch !== "miss") {
578
996
  this.failPendingStageNotification(
579
997
  socket,
580
998
  message.id,
581
999
  attemptId,
582
- `Intercom message ID '${message.id}' was already delivered with a different target or payload`,
583
- "message_id_conflict",
1000
+ deliveredMatch === "conflict"
1001
+ ? `Intercom message ID '${message.id}' was already delivered with a different target or payload`
1002
+ : deliveredMatch === "uncertain"
1003
+ ? "Intercom cannot prove whether this reserved operation reached its recipient; refusing redelivery"
1004
+ : "Intercom accepted-delivery authority is invalid; refusing possible duplicate delivery",
1005
+ deliveredMatch === "conflict" ? "message_id_conflict" : undefined,
584
1006
  );
585
1007
  return;
586
1008
  }
@@ -616,10 +1038,48 @@ class IntercomBroker {
616
1038
  }
617
1039
 
618
1040
  const requestId = randomUUID();
1041
+ let reservation: ReturnType<DeliveredMessageCache["reserve"]>;
1042
+ try {
1043
+ reservation = this.deliveredMessages.reserve(message.id, signature);
1044
+ } catch {
1045
+ this.failPendingStageNotification(
1046
+ socket,
1047
+ message.id,
1048
+ attemptId,
1049
+ "Intercom accepted-delivery authority is invalid; refusing possible duplicate delivery",
1050
+ );
1051
+ return;
1052
+ }
1053
+ if (reservation === "match") {
1054
+ writeMessageIfOpen(socket, {
1055
+ type: "delivered",
1056
+ messageId: message.id,
1057
+ ...(attemptId === undefined ? {} : { attemptId }),
1058
+ });
1059
+ return;
1060
+ }
1061
+ if (reservation !== "recorded") {
1062
+ this.failPendingStageNotification(
1063
+ socket,
1064
+ message.id,
1065
+ attemptId,
1066
+ reservation === "capacity"
1067
+ ? "Intercom accepted-delivery capacity is full; refusing delivery without evicting live retry authority"
1068
+ : reservation === "conflict"
1069
+ ? `Intercom message ID '${message.id}' was already delivered with a different target or payload`
1070
+ : reservation === "uncertain"
1071
+ ? "Intercom cannot prove whether this reserved operation reached its recipient; refusing redelivery"
1072
+ : "Intercom accepted-delivery authority is invalid; refusing possible duplicate delivery",
1073
+ reservation === "conflict" ? "message_id_conflict" : undefined,
1074
+ );
1075
+ return;
1076
+ }
1077
+
619
1078
  const timeout = setTimeout(() => {
620
1079
  const pending = this.pendingStageNotificationAcknowledgments.get(requestId);
621
1080
  if (pending === undefined) return;
622
1081
  this.pendingStageNotificationAcknowledgments.delete(requestId);
1082
+ this.deliveredMessages.forget(pending.messageId, pending.signature);
623
1083
  this.failPendingStageNotification(
624
1084
  pending.senderSocket,
625
1085
  pending.messageId,
@@ -636,7 +1096,22 @@ class IntercomBroker {
636
1096
  signature,
637
1097
  timeout,
638
1098
  });
639
- writeMessage(target.socket, { type: "pending_stage_notification", requestId, from: owner.info, message });
1099
+ if (
1100
+ !writeMessageIfOpen(target.socket, {
1101
+ type: "pending_stage_notification",
1102
+ requestId,
1103
+ from: owner.info,
1104
+ message,
1105
+ })
1106
+ ) {
1107
+ // The recipient's socket stopped accepting frames between resolving it and
1108
+ // this write. Release the acknowledgment and fail now, so the owner retries a
1109
+ // message that was never handed over rather than waiting out the ack timeout.
1110
+ clearTimeout(timeout);
1111
+ this.pendingStageNotificationAcknowledgments.delete(requestId);
1112
+ this.failPendingStageNotification(socket, message.id, attemptId, "Session not found");
1113
+ this.deliveredMessages.forget(message.id, signature);
1114
+ }
640
1115
  }
641
1116
 
642
1117
  private settlePendingStageNotification(
@@ -649,6 +1124,7 @@ class IntercomBroker {
649
1124
  clearTimeout(pending.timeout);
650
1125
  this.pendingStageNotificationAcknowledgments.delete(requestId);
651
1126
  if (!delivered) {
1127
+ this.deliveredMessages.forget(pending.messageId, pending.signature);
652
1128
  this.failPendingStageNotification(
653
1129
  pending.senderSocket,
654
1130
  pending.messageId,
@@ -657,8 +1133,29 @@ class IntercomBroker {
657
1133
  );
658
1134
  return;
659
1135
  }
660
- this.deliveredMessages.record(pending.messageId, pending.signature);
661
- writeMessage(pending.senderSocket, {
1136
+ let accepted: ReturnType<DeliveredMessageCache["accept"]>;
1137
+ try {
1138
+ accepted = this.deliveredMessages.accept(pending.messageId, pending.signature);
1139
+ } catch {
1140
+ this.failPendingStageNotification(
1141
+ pending.senderSocket,
1142
+ pending.messageId,
1143
+ pending.attemptId,
1144
+ "Intercom could not durably confirm the forwarded operation; refusing a possibly unsafe retry",
1145
+ );
1146
+ return;
1147
+ }
1148
+ if (accepted !== "accepted") {
1149
+ this.failPendingStageNotification(
1150
+ pending.senderSocket,
1151
+ pending.messageId,
1152
+ pending.attemptId,
1153
+ "Intercom could not durably confirm the forwarded operation; refusing a possibly unsafe retry",
1154
+ accepted === "conflict" ? "message_id_conflict" : undefined,
1155
+ );
1156
+ return;
1157
+ }
1158
+ writeMessageIfOpen(pending.senderSocket, {
662
1159
  type: "delivered",
663
1160
  messageId: pending.messageId,
664
1161
  ...(pending.attemptId === undefined ? {} : { attemptId: pending.attemptId }),
@@ -686,6 +1183,10 @@ class IntercomBroker {
686
1183
  if (!isSessionRegistration(clientMessage.session)) {
687
1184
  throw new Error("Invalid register message");
688
1185
  }
1186
+ if (clientMessage.registrationGroup !== undefined &&
1187
+ (typeof clientMessage.registrationGroup !== "string" || clientMessage.registrationGroup.trim().length === 0)) {
1188
+ throw new Error("Invalid registration group");
1189
+ }
689
1190
  if (clientMessage.returnAddress !== undefined &&
690
1191
  (typeof clientMessage.returnAddress !== "string" || clientMessage.returnAddress.length === 0)) {
691
1192
  throw new Error("Invalid return address");
@@ -706,8 +1207,8 @@ class IntercomBroker {
706
1207
  ? this.supervisorChannel.claim(clientMessage.supervisor.capability, childName)
707
1208
  : undefined;
708
1209
  if (!claimedSupervisorId || !this.sessions.has(claimedSupervisorId)) {
709
- writeMessage(socket, { type: "registration_failed", reason: "Invalid supervisor authorization" });
710
- socket.end();
1210
+ writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid supervisor authorization" });
1211
+ this.endRefusedConnection(socket, currentId, setId);
711
1212
  return;
712
1213
  }
713
1214
  supervisorId = claimedSupervisorId;
@@ -725,7 +1226,8 @@ class IntercomBroker {
725
1226
  this.sessions.set(id, {
726
1227
  socket,
727
1228
  info,
728
- registrationGroup: legacyGroup,
1229
+ registrationGroup: typeof clientMessage.registrationGroup === "string"
1230
+ ? normalizeGroup(clientMessage.registrationGroup) : legacyGroup,
729
1231
  ...(typeof info.name === "string" && info.name.trim().length > 0
730
1232
  ? { registrationName: info.name.trim() }
731
1233
  : {}),
@@ -743,7 +1245,7 @@ class IntercomBroker {
743
1245
  this.shutdownTimer = null;
744
1246
  }
745
1247
 
746
- writeMessage(socket, supervisorId
1248
+ writeMessageIfOpen(socket, supervisorId
747
1249
  ? { type: "registered", sessionId: id, supervisorSessionId: supervisorId }
748
1250
  : { type: "registered", sessionId: id });
749
1251
  this.broadcastToMemberships({ type: "session_joined", session: info }, sessionGroups(info), id);
@@ -773,11 +1275,12 @@ class IntercomBroker {
773
1275
  ? sessionsInGroup(this.sessions, clientMessage.group)
774
1276
  : []
775
1277
  : sessionsVisibleTo(this.sessions, requester.info);
776
- writeMessage(socket, {
1278
+ writeMessageIfOpen(socket, {
777
1279
  type: "sessions",
778
1280
  requestId: clientMessage.requestId,
779
1281
  sessions,
780
1282
  workflowStages: this.workflowStagesVisibleTo(requester, clientMessage.group),
1283
+ workflowFutureStages: this.workflowFutureStagesVisibleTo(requester, clientMessage.group),
781
1284
  });
782
1285
  break;
783
1286
  }
@@ -786,7 +1289,7 @@ class IntercomBroker {
786
1289
  if (typeof clientMessage.requestId !== "string") throw new Error("Invalid list_groups message");
787
1290
  const requester = currentId ? this.sessions.get(currentId) : undefined;
788
1291
  if (requester === undefined) throw new Error("Session not found");
789
- writeMessage(socket, {
1292
+ writeMessageIfOpen(socket, {
790
1293
  type: "groups",
791
1294
  requestId: clientMessage.requestId,
792
1295
  groups: knownGroupSummaries(this.sessions, requester.info),
@@ -808,7 +1311,7 @@ class IntercomBroker {
808
1311
  childName,
809
1312
  typeof clientMessage.capability === "string" ? clientMessage.capability : undefined,
810
1313
  );
811
- writeMessage(socket, {
1314
+ writeMessageIfOpen(socket, {
812
1315
  type: "supervisor_authorized",
813
1316
  requestId: clientMessage.requestId,
814
1317
  capability,
@@ -838,28 +1341,52 @@ class IntercomBroker {
838
1341
  (activeExisting !== undefined &&
839
1342
  (activeExisting.capability !== clientMessage.capability || activeExisting.group !== ownerGroup))
840
1343
  ) {
841
- writeMessage(socket, { type: "registration_failed", reason: "Pending-stage route is not authorized" });
842
- socket.end();
1344
+ writeMessageIfOpen(socket, { type: "registration_failed", reason: "Pending-stage route is not authorized" });
1345
+ this.endRefusedConnection(socket, currentId, setId);
843
1346
  return;
844
1347
  }
845
1348
  if (
846
1349
  clientMessage.stages !== undefined &&
847
- (!isWorkflowStageRosterAnnouncements(clientMessage.stages, clientMessage.runId) ||
1350
+ (!isWorkflowStageRosterAnnouncements(
1351
+ clientMessage.stages,
1352
+ clientMessage.runId,
1353
+ normalizeGroup(clientMessage.group),
1354
+ ) ||
848
1355
  !clientMessage.stages.every((stage) => invocationOwnsGroup(ownerGroup, stage.group)))
849
1356
  ) {
850
- writeMessage(socket, { type: "registration_failed", reason: "Invalid workflow-stage roster" });
851
- socket.end();
1357
+ writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid workflow-stage roster" });
1358
+ this.endRefusedConnection(socket, currentId, setId);
1359
+ return;
1360
+ }
1361
+ if (
1362
+ clientMessage.possibleStages !== undefined &&
1363
+ !isWorkflowPossibleStageAnnouncements(
1364
+ clientMessage.possibleStages,
1365
+ clientMessage.runId,
1366
+ normalizeGroup(clientMessage.group),
1367
+ )
1368
+ ) {
1369
+ writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid workflow possible-stage roster" });
1370
+ this.endRefusedConnection(socket, currentId, setId);
852
1371
  return;
853
1372
  }
1373
+ // D7 (slice 4): presence replaces, absence keeps — a terminal root publishes `[]`
1374
+ // so the broker drops every future row, while nested runs' re-announcements (which
1375
+ // never carry the field) preserve the root's rows.
1376
+ const possibleStages =
1377
+ clientMessage.possibleStages !== undefined
1378
+ ? clientMessage.possibleStages
1379
+ : this.workflowRosters.get(clientMessage.runId)?.possibleStages;
854
1380
  if (activeExisting !== undefined && activeExisting.sessionId !== currentId) {
855
1381
  // A stage replays the process-shared owner announcement before registering its live aliases.
856
1382
  // It may publish the materialized roster, but the original workflow owner must continue to
857
1383
  // handle pending delivery and own roster cleanup.
858
- if (clientMessage.stages !== undefined) {
1384
+ if (clientMessage.stages !== undefined || possibleStages !== undefined) {
859
1385
  this.workflowRosters.set(clientMessage.runId, {
860
1386
  ownerSessionId: activeExisting.sessionId,
861
1387
  group: ownerGroup,
862
- stages: clientMessage.stages,
1388
+ stages: clientMessage.stages ?? [],
1389
+ ...(possibleStages === undefined ? {} : { possibleStages }),
863
1390
  });
864
1391
  }
865
1392
  break;
@@ -869,11 +1396,12 @@ class IntercomBroker {
869
1396
  group: ownerGroup,
870
1397
  capability: clientMessage.capability,
871
1398
  });
872
- if (clientMessage.stages !== undefined) {
1399
+ if (clientMessage.stages !== undefined || possibleStages !== undefined) {
873
1400
  this.workflowRosters.set(clientMessage.runId, {
874
1401
  ownerSessionId: currentId,
875
1402
  group: ownerGroup,
876
- stages: clientMessage.stages,
1403
+ stages: clientMessage.stages ?? [],
1404
+ ...(possibleStages === undefined ? {} : { possibleStages }),
877
1405
  });
878
1406
  }
879
1407
  break;
@@ -888,15 +1416,22 @@ class IntercomBroker {
888
1416
  !Array.isArray(clientMessage.stageKeys) ||
889
1417
  clientMessage.stageKeys.length < 1 ||
890
1418
  clientMessage.stageKeys.length > 2 ||
891
- !clientMessage.stageKeys.every(
892
- (stageKey) =>
893
- typeof stageKey === "string" &&
894
- stageKey.length > 0 &&
895
- parsePendingStageTarget(`${clientMessage.runId}:${stageKey}`) !== undefined,
896
- )
1419
+ !clientMessage.stageKeys.every((stageKey) => typeof stageKey === "string" && stageKey.length > 0)
897
1420
  ) {
898
1421
  throw new Error("Invalid live workflow-stage route registration");
899
1422
  }
1423
+ if (!clientMessage.stageKeys.every((stageKey) => !stageKey.includes("/") && !stageKey.includes("*"))) {
1424
+ // A stage name containing "/" or "*" cannot be a canonical path segment, but throwing
1425
+ // here reaches the framing reader's onError and destroys the stage session's whole
1426
+ // broker connection. Refuse orderly like every neighbouring rejection; host clients
1427
+ // filter such name keys and register the stage-id key instead.
1428
+ writeMessageIfOpen(socket, {
1429
+ type: "registration_failed",
1430
+ reason: "Live workflow-stage route keys must be single path segments",
1431
+ });
1432
+ this.endRefusedConnection(socket, currentId, setId);
1433
+ return;
1434
+ }
900
1435
  const ownerRegistration = this.pendingStageRoutes.get(clientMessage.runId);
901
1436
  const registeringSession = this.sessions.get(currentId);
902
1437
  if (
@@ -915,11 +1450,11 @@ class IntercomBroker {
915
1450
  clientMessage.capability,
916
1451
  )
917
1452
  ) {
918
- writeMessage(socket, {
1453
+ writeMessageIfOpen(socket, {
919
1454
  type: "registration_failed",
920
1455
  reason: "Live workflow-stage route is owned by another active session",
921
1456
  });
922
- socket.end();
1457
+ this.endRefusedConnection(socket, currentId, setId);
923
1458
  return;
924
1459
  }
925
1460
  break;
@@ -933,8 +1468,14 @@ class IntercomBroker {
933
1468
  clientMessage.outcome !== "forward" &&
934
1469
  clientMessage.outcome !== "refused") ||
935
1470
  (clientMessage.position !== undefined && typeof clientMessage.position !== "number") ||
1471
+ (clientMessage.target !== undefined && typeof clientMessage.target !== "string") ||
1472
+ (clientMessage.outcome === "forward" && typeof clientMessage.target !== "string") ||
936
1473
  (clientMessage.reason !== undefined && typeof clientMessage.reason !== "string") ||
937
- (clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict")
1474
+ (clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict") ||
1475
+ (clientMessage.notInKnownSet !== undefined && clientMessage.notInKnownSet !== true) ||
1476
+ (clientMessage.forwardTargets !== undefined &&
1477
+ (!Array.isArray(clientMessage.forwardTargets) ||
1478
+ !clientMessage.forwardTargets.every((entry) => typeof entry === "string")))
938
1479
  ) {
939
1480
  throw new Error("Invalid pending-stage message result");
940
1481
  }
@@ -943,8 +1484,11 @@ class IntercomBroker {
943
1484
  clientMessage.requestId,
944
1485
  clientMessage.outcome,
945
1486
  clientMessage.position,
1487
+ clientMessage.target,
946
1488
  clientMessage.reason,
947
1489
  clientMessage.reasonCode,
1490
+ clientMessage.notInKnownSet === true ? true : undefined,
1491
+ Array.isArray(clientMessage.forwardTargets) ? [...clientMessage.forwardTargets] : undefined,
948
1492
  );
949
1493
  break;
950
1494
  }
@@ -957,12 +1501,14 @@ class IntercomBroker {
957
1501
  currentId,
958
1502
  this.sessions,
959
1503
  this.deliveredMessages,
960
- writeMessage,
1504
+ writeMessageIfOpen,
961
1505
  this.supervisorChannel,
962
1506
  this.pendingQuestions,
963
1507
  this.routePendingStage,
964
1508
  this.resolveLiveWorkflowStage,
965
1509
  this.canControlLiveWorkflowStage,
1510
+ this.resolveLegacyWorkflowStageTarget,
1511
+ writeMessageWithOutcome,
966
1512
  );
967
1513
  break;
968
1514
  }
@@ -988,7 +1534,7 @@ class IntercomBroker {
988
1534
  clientMessage,
989
1535
  currentId,
990
1536
  this.sessions,
991
- (target, message) => writeMessage(target, message),
1537
+ (target, message) => writeMessageIfOpen(target, message),
992
1538
  );
993
1539
  break;
994
1540
  }
@@ -1006,7 +1552,7 @@ class IntercomBroker {
1006
1552
  for (const question of this.pendingQuestions.takeForTarget(sessionId)) {
1007
1553
  const asker = this.sessions.get(question.senderSessionId);
1008
1554
  if (!asker) continue;
1009
- writeMessage(asker.socket, {
1555
+ writeMessageIfOpen(asker.socket, {
1010
1556
  type: "peer_disconnected",
1011
1557
  replyTo: question.messageId,
1012
1558
  peerSessionId: departed.info.id,
@@ -1024,7 +1570,7 @@ class IntercomBroker {
1024
1570
  clearTimeout(pending.timeout);
1025
1571
  this.pendingStageAcknowledgments.delete(requestId);
1026
1572
  this.releasePendingStageAcknowledgment(requestId);
1027
- writeMessage(pending.senderSocket, {
1573
+ writeMessageIfOpen(pending.senderSocket, {
1028
1574
  type: "delivery_failed",
1029
1575
  messageId: pending.messageId,
1030
1576
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
@@ -1035,6 +1581,7 @@ class IntercomBroker {
1035
1581
  if (pending.ownerSessionId !== sessionId && pending.recipientSessionId !== sessionId) continue;
1036
1582
  clearTimeout(pending.timeout);
1037
1583
  this.pendingStageNotificationAcknowledgments.delete(requestId);
1584
+ this.deliveredMessages.forget(pending.messageId, pending.signature);
1038
1585
  if (pending.ownerSessionId === sessionId) continue;
1039
1586
  this.failPendingStageNotification(
1040
1587
  pending.senderSocket,
@@ -1056,7 +1603,7 @@ class IntercomBroker {
1056
1603
  private broadcastToMemberships(msg: BrokerMessage, groups: ReadonlySet<string>, exclude?: string): void {
1057
1604
  for (const [id, session] of this.sessions) {
1058
1605
  if (id !== exclude && [...groups].some((group) => hasGroup(sessionGroups(session.info), group))) {
1059
- writeMessage(session.socket, msg);
1606
+ writeMessageIfOpen(session.socket, msg);
1060
1607
  }
1061
1608
  }
1062
1609
  }
@@ -1093,6 +1640,7 @@ class IntercomBroker {
1093
1640
  this.liveWorkflowStageRouteActivations.clear();
1094
1641
  for (const pending of this.pendingStageNotificationAcknowledgments.values()) clearTimeout(pending.timeout);
1095
1642
  this.pendingStageNotificationAcknowledgments.clear();
1643
+ this.deliveredMessages.close();
1096
1644
  const ownsRuntime = this.readPidFile() === process.pid;
1097
1645
  this.unlinkRuntimeFilesIfOwned();
1098
1646
  // Node unlinks a Unix socket path on server.close() even after a successor