@bastani/atomic 0.9.18-alpha.4 → 0.9.18-alpha.6

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 (409) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +3 -3
  3. package/dist/builtin/intercom/CHANGELOG.md +41 -0
  4. package/dist/builtin/intercom/README.md +18 -3
  5. package/dist/builtin/intercom/broker/broker.ts +670 -127
  6. package/dist/builtin/intercom/broker/client.ts +149 -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 +1419 -213
  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 +25 -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 +44 -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 +15 -7
  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/anthropic-thinking-guard.d.ts.map +1 -1
  117. package/dist/core/anthropic-thinking-guard.js +71 -5
  118. package/dist/core/anthropic-thinking-guard.js.map +1 -1
  119. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  120. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  121. package/dist/core/compaction/branch-summarization.js +4 -4
  122. package/dist/core/compaction/branch-summarization.js.map +1 -1
  123. package/dist/core/extensions/api-types.d.ts +8 -2
  124. package/dist/core/extensions/api-types.d.ts.map +1 -1
  125. package/dist/core/extensions/api-types.js.map +1 -1
  126. package/dist/core/extensions/context-types.d.ts +12 -0
  127. package/dist/core/extensions/context-types.d.ts.map +1 -1
  128. package/dist/core/extensions/context-types.js.map +1 -1
  129. package/dist/core/extensions/provider-types.d.ts +2 -2
  130. package/dist/core/extensions/provider-types.d.ts.map +1 -1
  131. package/dist/core/extensions/provider-types.js.map +1 -1
  132. package/dist/core/extensions/runner-shortcuts.d.ts.map +1 -1
  133. package/dist/core/extensions/runner-shortcuts.js +0 -1
  134. package/dist/core/extensions/runner-shortcuts.js.map +1 -1
  135. package/dist/core/fast-model-routing-transport.d.ts +12 -0
  136. package/dist/core/fast-model-routing-transport.d.ts.map +1 -0
  137. package/dist/core/{codex-fast-mode-transport.js → fast-model-routing-transport.js} +29 -29
  138. package/dist/core/fast-model-routing-transport.js.map +1 -0
  139. package/dist/core/fast-model-routing.d.ts +57 -0
  140. package/dist/core/fast-model-routing.d.ts.map +1 -0
  141. package/dist/core/fast-model-routing.js +201 -0
  142. package/dist/core/fast-model-routing.js.map +1 -0
  143. package/dist/core/fast-model-variants.d.ts +98 -0
  144. package/dist/core/fast-model-variants.d.ts.map +1 -0
  145. package/dist/core/fast-model-variants.js +148 -0
  146. package/dist/core/fast-model-variants.js.map +1 -0
  147. package/dist/core/http-dispatcher.d.ts.map +1 -1
  148. package/dist/core/http-dispatcher.js +4 -2
  149. package/dist/core/http-dispatcher.js.map +1 -1
  150. package/dist/core/keybindings.d.ts +0 -10
  151. package/dist/core/keybindings.d.ts.map +1 -1
  152. package/dist/core/keybindings.js +0 -8
  153. package/dist/core/keybindings.js.map +1 -1
  154. package/dist/core/messages.d.ts +1 -1
  155. package/dist/core/messages.d.ts.map +1 -1
  156. package/dist/core/messages.js.map +1 -1
  157. package/dist/core/model-config.d.ts +24 -4
  158. package/dist/core/model-config.d.ts.map +1 -1
  159. package/dist/core/model-config.js +6 -2
  160. package/dist/core/model-config.js.map +1 -1
  161. package/dist/core/model-resolver-initial.js +1 -1
  162. package/dist/core/model-resolver-initial.js.map +1 -1
  163. package/dist/core/model-runtime-streaming.d.ts +14 -1
  164. package/dist/core/model-runtime-streaming.d.ts.map +1 -1
  165. package/dist/core/model-runtime-streaming.js +25 -3
  166. package/dist/core/model-runtime-streaming.js.map +1 -1
  167. package/dist/core/model-runtime.d.ts +39 -2
  168. package/dist/core/model-runtime.d.ts.map +1 -1
  169. package/dist/core/model-runtime.js +91 -7
  170. package/dist/core/model-runtime.js.map +1 -1
  171. package/dist/core/provider-composer-internal.d.ts +1 -1
  172. package/dist/core/provider-composer-internal.d.ts.map +1 -1
  173. package/dist/core/provider-composer-internal.js +8 -2
  174. package/dist/core/provider-composer-internal.js.map +1 -1
  175. package/dist/core/sdk.d.ts.map +1 -1
  176. package/dist/core/sdk.js +21 -18
  177. package/dist/core/sdk.js.map +1 -1
  178. package/dist/core/session-manager-archive.d.ts.map +1 -1
  179. package/dist/core/session-manager-archive.js +25 -2
  180. package/dist/core/session-manager-archive.js.map +1 -1
  181. package/dist/core/settings-manager-core.d.ts +0 -2
  182. package/dist/core/settings-manager-core.d.ts.map +1 -1
  183. package/dist/core/settings-manager-core.js +2 -9
  184. package/dist/core/settings-manager-core.js.map +1 -1
  185. package/dist/core/settings-manager-internals.d.ts +0 -1
  186. package/dist/core/settings-manager-internals.d.ts.map +1 -1
  187. package/dist/core/settings-manager-internals.js.map +1 -1
  188. package/dist/core/settings-manager-ui-accessors.d.ts +0 -8
  189. package/dist/core/settings-manager-ui-accessors.d.ts.map +1 -1
  190. package/dist/core/settings-manager-ui-accessors.js +0 -53
  191. package/dist/core/settings-manager-ui-accessors.js.map +1 -1
  192. package/dist/core/settings-manager.d.ts +1 -1
  193. package/dist/core/settings-manager.d.ts.map +1 -1
  194. package/dist/core/settings-manager.js.map +1 -1
  195. package/dist/core/settings-types.d.ts +0 -5
  196. package/dist/core/settings-types.d.ts.map +1 -1
  197. package/dist/core/settings-types.js.map +1 -1
  198. package/dist/core/skills.d.ts +1 -1
  199. package/dist/core/skills.d.ts.map +1 -1
  200. package/dist/core/skills.js +4 -2
  201. package/dist/core/skills.js.map +1 -1
  202. package/dist/core/slash-commands.d.ts.map +1 -1
  203. package/dist/core/slash-commands.js +0 -1
  204. package/dist/core/slash-commands.js.map +1 -1
  205. package/dist/core/system-prompt.js +7 -7
  206. package/dist/core/system-prompt.js.map +1 -1
  207. package/dist/core/tools/bash.d.ts.map +1 -1
  208. package/dist/core/tools/bash.js +10 -7
  209. package/dist/core/tools/bash.js.map +1 -1
  210. package/dist/core/tools/edit.d.ts +1 -1
  211. package/dist/core/tools/edit.d.ts.map +1 -1
  212. package/dist/core/tools/edit.js +153 -8
  213. package/dist/core/tools/edit.js.map +1 -1
  214. package/dist/core/tools/find.d.ts.map +1 -1
  215. package/dist/core/tools/find.js +9 -8
  216. package/dist/core/tools/find.js.map +1 -1
  217. package/dist/core/tools/grep.d.ts.map +1 -1
  218. package/dist/core/tools/grep.js +4 -3
  219. package/dist/core/tools/grep.js.map +1 -1
  220. package/dist/core/tools/hashline.d.ts +2 -2
  221. package/dist/core/tools/hashline.d.ts.map +1 -1
  222. package/dist/core/tools/hashline.js +40 -10
  223. package/dist/core/tools/hashline.js.map +1 -1
  224. package/dist/core/tools/ls.d.ts.map +1 -1
  225. package/dist/core/tools/ls.js +2 -2
  226. package/dist/core/tools/ls.js.map +1 -1
  227. package/dist/core/tools/read.d.ts.map +1 -1
  228. package/dist/core/tools/read.js +12 -11
  229. package/dist/core/tools/read.js.map +1 -1
  230. package/dist/core/tools/search.d.ts.map +1 -1
  231. package/dist/core/tools/search.js +20 -19
  232. package/dist/core/tools/search.js.map +1 -1
  233. package/dist/core/tools/write.d.ts.map +1 -1
  234. package/dist/core/tools/write.js +22 -21
  235. package/dist/core/tools/write.js.map +1 -1
  236. package/dist/core/workflow-stage-admission.d.ts +6 -0
  237. package/dist/core/workflow-stage-admission.d.ts.map +1 -1
  238. package/dist/core/workflow-stage-admission.js +14 -0
  239. package/dist/core/workflow-stage-admission.js.map +1 -1
  240. package/dist/index-extensions.d.ts +1 -1
  241. package/dist/index-extensions.d.ts.map +1 -1
  242. package/dist/index-extensions.js.map +1 -1
  243. package/dist/index.d.ts +5 -5
  244. package/dist/index.d.ts.map +1 -1
  245. package/dist/index.js +4 -5
  246. package/dist/index.js.map +1 -1
  247. package/dist/modes/interactive/components/atomic-working-status.d.ts +4 -0
  248. package/dist/modes/interactive/components/atomic-working-status.d.ts.map +1 -1
  249. package/dist/modes/interactive/components/atomic-working-status.js +26 -1
  250. package/dist/modes/interactive/components/atomic-working-status.js.map +1 -1
  251. package/dist/modes/interactive/components/custom-editor.d.ts +7 -0
  252. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  253. package/dist/modes/interactive/components/custom-editor.js +43 -0
  254. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  255. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  256. package/dist/modes/interactive/components/footer.js +0 -6
  257. package/dist/modes/interactive/components/footer.js.map +1 -1
  258. package/dist/modes/interactive/components/index.d.ts +1 -2
  259. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  260. package/dist/modes/interactive/components/index.js +0 -1
  261. package/dist/modes/interactive/components/index.js.map +1 -1
  262. package/dist/modes/interactive/components/model-selector.d.ts +1 -1
  263. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  264. package/dist/modes/interactive/components/model-selector.js +12 -25
  265. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  266. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -5
  267. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  268. package/dist/modes/interactive/components/scoped-models-selector.js +16 -34
  269. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  270. package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
  271. package/dist/modes/interactive/components/settings-selector-items.js +10 -5
  272. package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
  273. package/dist/modes/interactive/components/settings-selector-submenus.d.ts +1 -1
  274. package/dist/modes/interactive/components/settings-selector-submenus.d.ts.map +1 -1
  275. package/dist/modes/interactive/components/settings-selector-submenus.js +10 -7
  276. package/dist/modes/interactive/components/settings-selector-submenus.js.map +1 -1
  277. package/dist/modes/interactive/components/thinking-selector.d.ts +1 -2
  278. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  279. package/dist/modes/interactive/components/thinking-selector.js +6 -13
  280. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  281. package/dist/modes/interactive/components/transcript-follow-indicator.js +3 -3
  282. package/dist/modes/interactive/components/transcript-follow-indicator.js.map +1 -1
  283. package/dist/modes/interactive/components/trust-selector.js +2 -2
  284. package/dist/modes/interactive/components/trust-selector.js.map +1 -1
  285. package/dist/modes/interactive/interactive-agent-events.js +8 -8
  286. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  287. package/dist/modes/interactive/interactive-autocomplete.js +5 -16
  288. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  289. package/dist/modes/interactive/interactive-bash-compact.js +2 -9
  290. package/dist/modes/interactive/interactive-bash-compact.js.map +1 -1
  291. package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
  292. package/dist/modes/interactive/interactive-deferred-startup.js +3 -0
  293. package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
  294. package/dist/modes/interactive/interactive-editor-actions.js +2 -2
  295. package/dist/modes/interactive/interactive-editor-actions.js.map +1 -1
  296. package/dist/modes/interactive/interactive-extension-custom-ui.js +1 -0
  297. package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
  298. package/dist/modes/interactive/interactive-extension-dialogs.js +7 -0
  299. package/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
  300. package/dist/modes/interactive/interactive-extension-runtime.js +20 -10
  301. package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
  302. package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -5
  303. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  304. package/dist/modes/interactive/interactive-initial-session-binding.d.ts.map +1 -1
  305. package/dist/modes/interactive/interactive-initial-session-binding.js +3 -0
  306. package/dist/modes/interactive/interactive-initial-session-binding.js.map +1 -1
  307. package/dist/modes/interactive/interactive-input-handling.js +0 -8
  308. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  309. package/dist/modes/interactive/interactive-mode-base.d.ts +5 -1
  310. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  311. package/dist/modes/interactive/interactive-mode-base.js +15 -9
  312. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  313. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -3
  314. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  315. package/dist/modes/interactive/interactive-mode-deps.js +1 -3
  316. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  317. package/dist/modes/interactive/interactive-mode-surface.d.ts +9 -12
  318. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  319. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  320. package/dist/modes/interactive/interactive-model-routing.js +10 -13
  321. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  322. package/dist/modes/interactive/interactive-render-chat.js +29 -0
  323. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  324. package/dist/modes/interactive/interactive-selectors.js +2 -32
  325. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  326. package/dist/modes/interactive/interactive-session-routing.js +1 -5
  327. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  328. package/dist/modes/interactive/interactive-session-runtime.js +1 -5
  329. package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
  330. package/dist/modes/interactive/interactive-slash-commands.js +6 -13
  331. package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
  332. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  333. package/dist/modes/interactive/interactive-startup.js +19 -13
  334. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  335. package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
  336. package/dist/modes/interactive/interactive-tui.js +27 -3
  337. package/dist/modes/interactive/interactive-tui.js.map +1 -1
  338. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  339. package/dist/modes/interactive-engine/engine-custom-ui.js +1 -0
  340. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  341. package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
  342. package/dist/modes/interactive-engine/isolated-runtime.js +4 -1
  343. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  344. package/dist/utils/child-process.d.ts.map +1 -1
  345. package/dist/utils/child-process.js +13 -6
  346. package/dist/utils/child-process.js.map +1 -1
  347. package/dist/utils/exif-orientation.d.ts.map +1 -1
  348. package/dist/utils/exif-orientation.js +2 -3
  349. package/dist/utils/exif-orientation.js.map +1 -1
  350. package/dist/utils/tools-manager.d.ts +1 -0
  351. package/dist/utils/tools-manager.d.ts.map +1 -1
  352. package/dist/utils/tools-manager.js +33 -18
  353. package/dist/utils/tools-manager.js.map +1 -1
  354. package/docs/compaction.md +2 -0
  355. package/docs/custom-provider.md +4 -1
  356. package/docs/docs.json +14 -2
  357. package/docs/extensions.md +11 -4
  358. package/docs/intercom.md +33 -8
  359. package/docs/keybindings.md +8 -6
  360. package/docs/models/evals.md +394 -0
  361. package/docs/models/model-selection.md +60 -25
  362. package/docs/models/pareto-efficiency.md +33 -15
  363. package/docs/models.md +80 -1
  364. package/docs/packages.md +2 -2
  365. package/docs/providers.md +44 -20
  366. package/docs/quickstart.md +8 -6
  367. package/docs/rpc.md +3 -3
  368. package/docs/sdk.md +2 -16
  369. package/docs/settings.md +19 -22
  370. package/docs/skills.md +4 -2
  371. package/docs/subagents.md +9 -3
  372. package/docs/terminal-setup.md +15 -0
  373. package/docs/tools/edit.md +373 -0
  374. package/docs/tools.md +3 -7
  375. package/docs/tui.md +2 -1
  376. package/docs/usage.md +3 -5
  377. package/docs/workflows/api-reference.md +1395 -0
  378. package/docs/workflows/authoring.md +678 -0
  379. package/docs/workflows/builtins.md +245 -0
  380. package/docs/workflows/operations.md +811 -0
  381. package/docs/workflows/reliable-design.md +2099 -0
  382. package/docs/workflows/verification.md +62 -0
  383. package/docs/workflows.md +15 -5183
  384. package/npm-shrinkwrap.json +584 -69
  385. package/package.json +7 -7
  386. package/dist/client/remote-session.d.ts +0 -64
  387. package/dist/client/remote-session.d.ts.map +0 -1
  388. package/dist/client/remote-session.js +0 -351
  389. package/dist/client/remote-session.js.map +0 -1
  390. package/dist/client/transcript.d.ts +0 -12
  391. package/dist/client/transcript.d.ts.map +0 -1
  392. package/dist/client/transcript.js +0 -98
  393. package/dist/client/transcript.js.map +0 -1
  394. package/dist/core/codex-fast-mode-transport.d.ts +0 -12
  395. package/dist/core/codex-fast-mode-transport.d.ts.map +0 -1
  396. package/dist/core/codex-fast-mode-transport.js.map +0 -1
  397. package/dist/core/codex-fast-mode.d.ts +0 -53
  398. package/dist/core/codex-fast-mode.d.ts.map +0 -1
  399. package/dist/core/codex-fast-mode.js +0 -249
  400. package/dist/core/codex-fast-mode.js.map +0 -1
  401. package/dist/modes/interactive/components/fast-mode-selector.d.ts +0 -27
  402. package/dist/modes/interactive/components/fast-mode-selector.d.ts.map +0 -1
  403. package/dist/modes/interactive/components/fast-mode-selector.js +0 -107
  404. package/dist/modes/interactive/components/fast-mode-selector.js.map +0 -1
  405. package/dist/server/create-harness.d.ts +0 -53
  406. package/dist/server/create-harness.d.ts.map +0 -1
  407. package/dist/server/create-harness.js +0 -360
  408. package/dist/server/create-harness.js.map +0 -1
  409. 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;
851
+ }
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;
503
873
  }
504
- writeMessage(pending.senderSocket, {
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 }),
@@ -706,8 +1203,8 @@ class IntercomBroker {
706
1203
  ? this.supervisorChannel.claim(clientMessage.supervisor.capability, childName)
707
1204
  : undefined;
708
1205
  if (!claimedSupervisorId || !this.sessions.has(claimedSupervisorId)) {
709
- writeMessage(socket, { type: "registration_failed", reason: "Invalid supervisor authorization" });
710
- socket.end();
1206
+ writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid supervisor authorization" });
1207
+ this.endRefusedConnection(socket, currentId, setId);
711
1208
  return;
712
1209
  }
713
1210
  supervisorId = claimedSupervisorId;
@@ -743,7 +1240,7 @@ class IntercomBroker {
743
1240
  this.shutdownTimer = null;
744
1241
  }
745
1242
 
746
- writeMessage(socket, supervisorId
1243
+ writeMessageIfOpen(socket, supervisorId
747
1244
  ? { type: "registered", sessionId: id, supervisorSessionId: supervisorId }
748
1245
  : { type: "registered", sessionId: id });
749
1246
  this.broadcastToMemberships({ type: "session_joined", session: info }, sessionGroups(info), id);
@@ -773,11 +1270,12 @@ class IntercomBroker {
773
1270
  ? sessionsInGroup(this.sessions, clientMessage.group)
774
1271
  : []
775
1272
  : sessionsVisibleTo(this.sessions, requester.info);
776
- writeMessage(socket, {
1273
+ writeMessageIfOpen(socket, {
777
1274
  type: "sessions",
778
1275
  requestId: clientMessage.requestId,
779
1276
  sessions,
780
1277
  workflowStages: this.workflowStagesVisibleTo(requester, clientMessage.group),
1278
+ workflowFutureStages: this.workflowFutureStagesVisibleTo(requester, clientMessage.group),
781
1279
  });
782
1280
  break;
783
1281
  }
@@ -786,7 +1284,7 @@ class IntercomBroker {
786
1284
  if (typeof clientMessage.requestId !== "string") throw new Error("Invalid list_groups message");
787
1285
  const requester = currentId ? this.sessions.get(currentId) : undefined;
788
1286
  if (requester === undefined) throw new Error("Session not found");
789
- writeMessage(socket, {
1287
+ writeMessageIfOpen(socket, {
790
1288
  type: "groups",
791
1289
  requestId: clientMessage.requestId,
792
1290
  groups: knownGroupSummaries(this.sessions, requester.info),
@@ -808,7 +1306,7 @@ class IntercomBroker {
808
1306
  childName,
809
1307
  typeof clientMessage.capability === "string" ? clientMessage.capability : undefined,
810
1308
  );
811
- writeMessage(socket, {
1309
+ writeMessageIfOpen(socket, {
812
1310
  type: "supervisor_authorized",
813
1311
  requestId: clientMessage.requestId,
814
1312
  capability,
@@ -838,28 +1336,52 @@ class IntercomBroker {
838
1336
  (activeExisting !== undefined &&
839
1337
  (activeExisting.capability !== clientMessage.capability || activeExisting.group !== ownerGroup))
840
1338
  ) {
841
- writeMessage(socket, { type: "registration_failed", reason: "Pending-stage route is not authorized" });
842
- socket.end();
1339
+ writeMessageIfOpen(socket, { type: "registration_failed", reason: "Pending-stage route is not authorized" });
1340
+ this.endRefusedConnection(socket, currentId, setId);
843
1341
  return;
844
1342
  }
845
1343
  if (
846
1344
  clientMessage.stages !== undefined &&
847
- (!isWorkflowStageRosterAnnouncements(clientMessage.stages, clientMessage.runId) ||
1345
+ (!isWorkflowStageRosterAnnouncements(
1346
+ clientMessage.stages,
1347
+ clientMessage.runId,
1348
+ normalizeGroup(clientMessage.group),
1349
+ ) ||
848
1350
  !clientMessage.stages.every((stage) => invocationOwnsGroup(ownerGroup, stage.group)))
849
1351
  ) {
850
- writeMessage(socket, { type: "registration_failed", reason: "Invalid workflow-stage roster" });
851
- socket.end();
1352
+ writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid workflow-stage roster" });
1353
+ this.endRefusedConnection(socket, currentId, setId);
1354
+ return;
1355
+ }
1356
+ if (
1357
+ clientMessage.possibleStages !== undefined &&
1358
+ !isWorkflowPossibleStageAnnouncements(
1359
+ clientMessage.possibleStages,
1360
+ clientMessage.runId,
1361
+ normalizeGroup(clientMessage.group),
1362
+ )
1363
+ ) {
1364
+ writeMessageIfOpen(socket, { type: "registration_failed", reason: "Invalid workflow possible-stage roster" });
1365
+ this.endRefusedConnection(socket, currentId, setId);
852
1366
  return;
853
1367
  }
1368
+ // D7 (slice 4): presence replaces, absence keeps — a terminal root publishes `[]`
1369
+ // so the broker drops every future row, while nested runs' re-announcements (which
1370
+ // never carry the field) preserve the root's rows.
1371
+ const possibleStages =
1372
+ clientMessage.possibleStages !== undefined
1373
+ ? clientMessage.possibleStages
1374
+ : this.workflowRosters.get(clientMessage.runId)?.possibleStages;
854
1375
  if (activeExisting !== undefined && activeExisting.sessionId !== currentId) {
855
1376
  // A stage replays the process-shared owner announcement before registering its live aliases.
856
1377
  // It may publish the materialized roster, but the original workflow owner must continue to
857
1378
  // handle pending delivery and own roster cleanup.
858
- if (clientMessage.stages !== undefined) {
1379
+ if (clientMessage.stages !== undefined || possibleStages !== undefined) {
859
1380
  this.workflowRosters.set(clientMessage.runId, {
860
1381
  ownerSessionId: activeExisting.sessionId,
861
1382
  group: ownerGroup,
862
- stages: clientMessage.stages,
1383
+ stages: clientMessage.stages ?? [],
1384
+ ...(possibleStages === undefined ? {} : { possibleStages }),
863
1385
  });
864
1386
  }
865
1387
  break;
@@ -869,11 +1391,12 @@ class IntercomBroker {
869
1391
  group: ownerGroup,
870
1392
  capability: clientMessage.capability,
871
1393
  });
872
- if (clientMessage.stages !== undefined) {
1394
+ if (clientMessage.stages !== undefined || possibleStages !== undefined) {
873
1395
  this.workflowRosters.set(clientMessage.runId, {
874
1396
  ownerSessionId: currentId,
875
1397
  group: ownerGroup,
876
- stages: clientMessage.stages,
1398
+ stages: clientMessage.stages ?? [],
1399
+ ...(possibleStages === undefined ? {} : { possibleStages }),
877
1400
  });
878
1401
  }
879
1402
  break;
@@ -888,15 +1411,22 @@ class IntercomBroker {
888
1411
  !Array.isArray(clientMessage.stageKeys) ||
889
1412
  clientMessage.stageKeys.length < 1 ||
890
1413
  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
- )
1414
+ !clientMessage.stageKeys.every((stageKey) => typeof stageKey === "string" && stageKey.length > 0)
897
1415
  ) {
898
1416
  throw new Error("Invalid live workflow-stage route registration");
899
1417
  }
1418
+ if (!clientMessage.stageKeys.every((stageKey) => !stageKey.includes("/") && !stageKey.includes("*"))) {
1419
+ // A stage name containing "/" or "*" cannot be a canonical path segment, but throwing
1420
+ // here reaches the framing reader's onError and destroys the stage session's whole
1421
+ // broker connection. Refuse orderly like every neighbouring rejection; host clients
1422
+ // filter such name keys and register the stage-id key instead.
1423
+ writeMessageIfOpen(socket, {
1424
+ type: "registration_failed",
1425
+ reason: "Live workflow-stage route keys must be single path segments",
1426
+ });
1427
+ this.endRefusedConnection(socket, currentId, setId);
1428
+ return;
1429
+ }
900
1430
  const ownerRegistration = this.pendingStageRoutes.get(clientMessage.runId);
901
1431
  const registeringSession = this.sessions.get(currentId);
902
1432
  if (
@@ -915,11 +1445,11 @@ class IntercomBroker {
915
1445
  clientMessage.capability,
916
1446
  )
917
1447
  ) {
918
- writeMessage(socket, {
1448
+ writeMessageIfOpen(socket, {
919
1449
  type: "registration_failed",
920
1450
  reason: "Live workflow-stage route is owned by another active session",
921
1451
  });
922
- socket.end();
1452
+ this.endRefusedConnection(socket, currentId, setId);
923
1453
  return;
924
1454
  }
925
1455
  break;
@@ -933,8 +1463,14 @@ class IntercomBroker {
933
1463
  clientMessage.outcome !== "forward" &&
934
1464
  clientMessage.outcome !== "refused") ||
935
1465
  (clientMessage.position !== undefined && typeof clientMessage.position !== "number") ||
1466
+ (clientMessage.target !== undefined && typeof clientMessage.target !== "string") ||
1467
+ (clientMessage.outcome === "forward" && typeof clientMessage.target !== "string") ||
936
1468
  (clientMessage.reason !== undefined && typeof clientMessage.reason !== "string") ||
937
- (clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict")
1469
+ (clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict") ||
1470
+ (clientMessage.notInKnownSet !== undefined && clientMessage.notInKnownSet !== true) ||
1471
+ (clientMessage.forwardTargets !== undefined &&
1472
+ (!Array.isArray(clientMessage.forwardTargets) ||
1473
+ !clientMessage.forwardTargets.every((entry) => typeof entry === "string")))
938
1474
  ) {
939
1475
  throw new Error("Invalid pending-stage message result");
940
1476
  }
@@ -943,8 +1479,11 @@ class IntercomBroker {
943
1479
  clientMessage.requestId,
944
1480
  clientMessage.outcome,
945
1481
  clientMessage.position,
1482
+ clientMessage.target,
946
1483
  clientMessage.reason,
947
1484
  clientMessage.reasonCode,
1485
+ clientMessage.notInKnownSet === true ? true : undefined,
1486
+ Array.isArray(clientMessage.forwardTargets) ? [...clientMessage.forwardTargets] : undefined,
948
1487
  );
949
1488
  break;
950
1489
  }
@@ -957,12 +1496,14 @@ class IntercomBroker {
957
1496
  currentId,
958
1497
  this.sessions,
959
1498
  this.deliveredMessages,
960
- writeMessage,
1499
+ writeMessageIfOpen,
961
1500
  this.supervisorChannel,
962
1501
  this.pendingQuestions,
963
1502
  this.routePendingStage,
964
1503
  this.resolveLiveWorkflowStage,
965
1504
  this.canControlLiveWorkflowStage,
1505
+ this.resolveLegacyWorkflowStageTarget,
1506
+ writeMessageWithOutcome,
966
1507
  );
967
1508
  break;
968
1509
  }
@@ -988,7 +1529,7 @@ class IntercomBroker {
988
1529
  clientMessage,
989
1530
  currentId,
990
1531
  this.sessions,
991
- (target, message) => writeMessage(target, message),
1532
+ (target, message) => writeMessageIfOpen(target, message),
992
1533
  );
993
1534
  break;
994
1535
  }
@@ -1006,7 +1547,7 @@ class IntercomBroker {
1006
1547
  for (const question of this.pendingQuestions.takeForTarget(sessionId)) {
1007
1548
  const asker = this.sessions.get(question.senderSessionId);
1008
1549
  if (!asker) continue;
1009
- writeMessage(asker.socket, {
1550
+ writeMessageIfOpen(asker.socket, {
1010
1551
  type: "peer_disconnected",
1011
1552
  replyTo: question.messageId,
1012
1553
  peerSessionId: departed.info.id,
@@ -1024,7 +1565,7 @@ class IntercomBroker {
1024
1565
  clearTimeout(pending.timeout);
1025
1566
  this.pendingStageAcknowledgments.delete(requestId);
1026
1567
  this.releasePendingStageAcknowledgment(requestId);
1027
- writeMessage(pending.senderSocket, {
1568
+ writeMessageIfOpen(pending.senderSocket, {
1028
1569
  type: "delivery_failed",
1029
1570
  messageId: pending.messageId,
1030
1571
  ...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
@@ -1035,6 +1576,7 @@ class IntercomBroker {
1035
1576
  if (pending.ownerSessionId !== sessionId && pending.recipientSessionId !== sessionId) continue;
1036
1577
  clearTimeout(pending.timeout);
1037
1578
  this.pendingStageNotificationAcknowledgments.delete(requestId);
1579
+ this.deliveredMessages.forget(pending.messageId, pending.signature);
1038
1580
  if (pending.ownerSessionId === sessionId) continue;
1039
1581
  this.failPendingStageNotification(
1040
1582
  pending.senderSocket,
@@ -1056,7 +1598,7 @@ class IntercomBroker {
1056
1598
  private broadcastToMemberships(msg: BrokerMessage, groups: ReadonlySet<string>, exclude?: string): void {
1057
1599
  for (const [id, session] of this.sessions) {
1058
1600
  if (id !== exclude && [...groups].some((group) => hasGroup(sessionGroups(session.info), group))) {
1059
- writeMessage(session.socket, msg);
1601
+ writeMessageIfOpen(session.socket, msg);
1060
1602
  }
1061
1603
  }
1062
1604
  }
@@ -1093,6 +1635,7 @@ class IntercomBroker {
1093
1635
  this.liveWorkflowStageRouteActivations.clear();
1094
1636
  for (const pending of this.pendingStageNotificationAcknowledgments.values()) clearTimeout(pending.timeout);
1095
1637
  this.pendingStageNotificationAcknowledgments.clear();
1638
+ this.deliveredMessages.close();
1096
1639
  const ownsRuntime = this.readPidFile() === process.pid;
1097
1640
  this.unlinkRuntimeFilesIfOwned();
1098
1641
  // Node unlinks a Unix socket path on server.close() even after a successor