@animalabs/agent-framework 0.22.2 → 0.22.3

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 (935) hide show
  1. package/README.md +3 -1
  2. package/dist/src/agent.d.ts +77 -12
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +136 -40
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/mcp-server-core.js +1 -1
  7. package/dist/src/api/mcp-server-core.js.map +1 -1
  8. package/dist/src/api/server.d.ts.map +1 -1
  9. package/dist/src/api/server.js +5 -1
  10. package/dist/src/api/server.js.map +1 -1
  11. package/dist/src/api/types.d.ts +1 -1
  12. package/dist/src/api/types.d.ts.map +1 -1
  13. package/dist/src/branch-names.d.ts +14 -0
  14. package/dist/src/branch-names.d.ts.map +1 -0
  15. package/dist/src/branch-names.js +22 -0
  16. package/dist/src/branch-names.js.map +1 -0
  17. package/dist/src/closest-names.d.ts +28 -0
  18. package/dist/src/closest-names.d.ts.map +1 -0
  19. package/dist/src/closest-names.js +98 -0
  20. package/dist/src/closest-names.js.map +1 -0
  21. package/dist/src/code-execution/py-runner.d.ts +16 -5
  22. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  23. package/dist/src/code-execution/py-runner.js +186 -74
  24. package/dist/src/code-execution/py-runner.js.map +1 -1
  25. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  26. package/dist/src/code-execution/runtime-py.js +49 -3
  27. package/dist/src/code-execution/runtime-py.js.map +1 -1
  28. package/dist/src/code-execution/tool-definition.d.ts +12 -1
  29. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  30. package/dist/src/code-execution/tool-definition.js +21 -3
  31. package/dist/src/code-execution/tool-definition.js.map +1 -1
  32. package/dist/src/config-checks.d.ts +28 -0
  33. package/dist/src/config-checks.d.ts.map +1 -0
  34. package/dist/src/config-checks.js +52 -0
  35. package/dist/src/config-checks.js.map +1 -0
  36. package/dist/src/deadline-timer.d.ts +21 -0
  37. package/dist/src/deadline-timer.d.ts.map +1 -0
  38. package/dist/src/deadline-timer.js +43 -0
  39. package/dist/src/deadline-timer.js.map +1 -0
  40. package/dist/src/error-message.d.ts +30 -0
  41. package/dist/src/error-message.d.ts.map +1 -0
  42. package/dist/src/error-message.js +50 -0
  43. package/dist/src/error-message.js.map +1 -0
  44. package/dist/src/framework.d.ts +828 -88
  45. package/dist/src/framework.d.ts.map +1 -1
  46. package/dist/src/framework.js +5011 -1326
  47. package/dist/src/framework.js.map +1 -1
  48. package/dist/src/gate/event-gate.d.ts +8 -0
  49. package/dist/src/gate/event-gate.d.ts.map +1 -1
  50. package/dist/src/gate/event-gate.js +27 -3
  51. package/dist/src/gate/event-gate.js.map +1 -1
  52. package/dist/src/index.d.ts +4 -2
  53. package/dist/src/index.d.ts.map +1 -1
  54. package/dist/src/index.js +4 -2
  55. package/dist/src/index.js.map +1 -1
  56. package/dist/src/mcpl/channel-registry.d.ts +144 -6
  57. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  58. package/dist/src/mcpl/channel-registry.js +424 -263
  59. package/dist/src/mcpl/channel-registry.js.map +1 -1
  60. package/dist/src/mcpl/checkpoint-manager.d.ts +21 -1
  61. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  62. package/dist/src/mcpl/checkpoint-manager.js +91 -30
  63. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  64. package/dist/src/mcpl/conversation-router.d.ts +19 -3
  65. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  66. package/dist/src/mcpl/conversation-router.js +23 -8
  67. package/dist/src/mcpl/conversation-router.js.map +1 -1
  68. package/dist/src/mcpl/convert-block.d.ts +20 -0
  69. package/dist/src/mcpl/convert-block.d.ts.map +1 -0
  70. package/dist/src/mcpl/convert-block.js +89 -0
  71. package/dist/src/mcpl/convert-block.js.map +1 -0
  72. package/dist/src/mcpl/errors.d.ts +13 -0
  73. package/dist/src/mcpl/errors.d.ts.map +1 -1
  74. package/dist/src/mcpl/errors.js +28 -0
  75. package/dist/src/mcpl/errors.js.map +1 -1
  76. package/dist/src/mcpl/hook-orchestrator.d.ts +23 -0
  77. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  78. package/dist/src/mcpl/hook-orchestrator.js +83 -72
  79. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  80. package/dist/src/mcpl/inbound-source.d.ts +114 -0
  81. package/dist/src/mcpl/inbound-source.d.ts.map +1 -0
  82. package/dist/src/mcpl/inbound-source.js +83 -0
  83. package/dist/src/mcpl/inbound-source.js.map +1 -0
  84. package/dist/src/mcpl/index.d.ts +3 -1
  85. package/dist/src/mcpl/index.d.ts.map +1 -1
  86. package/dist/src/mcpl/index.js +3 -1
  87. package/dist/src/mcpl/index.js.map +1 -1
  88. package/dist/src/mcpl/inference-router.d.ts +15 -11
  89. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  90. package/dist/src/mcpl/inference-router.js +153 -84
  91. package/dist/src/mcpl/inference-router.js.map +1 -1
  92. package/dist/src/mcpl/push-coalescer.d.ts +33 -11
  93. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  94. package/dist/src/mcpl/push-coalescer.js +52 -20
  95. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  96. package/dist/src/mcpl/push-handler.d.ts +16 -9
  97. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  98. package/dist/src/mcpl/push-handler.js +18 -83
  99. package/dist/src/mcpl/push-handler.js.map +1 -1
  100. package/dist/src/mcpl/reference-fetcher.d.ts +14 -2
  101. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  102. package/dist/src/mcpl/reference-fetcher.js +72 -30
  103. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  104. package/dist/src/mcpl/references.d.ts +47 -12
  105. package/dist/src/mcpl/references.d.ts.map +1 -1
  106. package/dist/src/mcpl/references.js +74 -21
  107. package/dist/src/mcpl/references.js.map +1 -1
  108. package/dist/src/mcpl/server-connection.d.ts +89 -10
  109. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  110. package/dist/src/mcpl/server-connection.js +405 -129
  111. package/dist/src/mcpl/server-connection.js.map +1 -1
  112. package/dist/src/mcpl/server-registry.d.ts +10 -1
  113. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  114. package/dist/src/mcpl/server-registry.js +14 -3
  115. package/dist/src/mcpl/server-registry.js.map +1 -1
  116. package/dist/src/mcpl/skip-reply-wake.d.ts +36 -0
  117. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -0
  118. package/dist/src/mcpl/skip-reply-wake.js +55 -0
  119. package/dist/src/mcpl/skip-reply-wake.js.map +1 -0
  120. package/dist/src/mcpl/transport.d.ts +51 -2
  121. package/dist/src/mcpl/transport.d.ts.map +1 -1
  122. package/dist/src/mcpl/transport.js +114 -20
  123. package/dist/src/mcpl/transport.js.map +1 -1
  124. package/dist/src/mcpl/types.d.ts +14 -3
  125. package/dist/src/mcpl/types.d.ts.map +1 -1
  126. package/dist/src/mcpl/types.js.map +1 -1
  127. package/dist/src/mcpl/visible-content.d.ts +15 -0
  128. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  129. package/dist/src/mcpl/visible-content.js +25 -0
  130. package/dist/src/mcpl/visible-content.js.map +1 -1
  131. package/dist/src/module-registry.d.ts +19 -1
  132. package/dist/src/module-registry.d.ts.map +1 -1
  133. package/dist/src/module-registry.js +54 -25
  134. package/dist/src/module-registry.js.map +1 -1
  135. package/dist/src/modules/api/index.js +1 -1
  136. package/dist/src/modules/api/index.js.map +1 -1
  137. package/dist/src/modules/discord/discord-js-client.d.ts +1 -0
  138. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  139. package/dist/src/modules/discord/discord-js-client.js +25 -9
  140. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  141. package/dist/src/modules/discord/index.d.ts +29 -3
  142. package/dist/src/modules/discord/index.d.ts.map +1 -1
  143. package/dist/src/modules/discord/index.js +379 -143
  144. package/dist/src/modules/discord/index.js.map +1 -1
  145. package/dist/src/modules/discord/types.d.ts +14 -1
  146. package/dist/src/modules/discord/types.d.ts.map +1 -1
  147. package/dist/src/modules/health/index.d.ts.map +1 -1
  148. package/dist/src/modules/health/index.js +2 -1
  149. package/dist/src/modules/health/index.js.map +1 -1
  150. package/dist/src/modules/history/index.d.ts +3 -1
  151. package/dist/src/modules/history/index.d.ts.map +1 -1
  152. package/dist/src/modules/history/index.js +359 -127
  153. package/dist/src/modules/history/index.js.map +1 -1
  154. package/dist/src/modules/history/semantic.d.ts +28 -1
  155. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  156. package/dist/src/modules/history/semantic.js +177 -32
  157. package/dist/src/modules/history/semantic.js.map +1 -1
  158. package/dist/src/modules/unknown-tool.d.ts +10 -0
  159. package/dist/src/modules/unknown-tool.d.ts.map +1 -0
  160. package/dist/src/modules/unknown-tool.js +17 -0
  161. package/dist/src/modules/unknown-tool.js.map +1 -0
  162. package/dist/src/modules/workspace/disk-agreement.d.ts +32 -2
  163. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  164. package/dist/src/modules/workspace/disk-agreement.js +30 -2
  165. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  166. package/dist/src/modules/workspace/effects.d.ts +64 -6
  167. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  168. package/dist/src/modules/workspace/effects.js +291 -44
  169. package/dist/src/modules/workspace/effects.js.map +1 -1
  170. package/dist/src/modules/workspace/index.d.ts +119 -23
  171. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  172. package/dist/src/modules/workspace/index.js +793 -247
  173. package/dist/src/modules/workspace/index.js.map +1 -1
  174. package/dist/src/modules/workspace/observe.d.ts +75 -8
  175. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  176. package/dist/src/modules/workspace/observe.js +174 -14
  177. package/dist/src/modules/workspace/observe.js.map +1 -1
  178. package/dist/src/modules/workspace/reconcile.d.ts +58 -7
  179. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  180. package/dist/src/modules/workspace/reconcile.js +521 -56
  181. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  182. package/dist/src/modules/workspace/types.d.ts +18 -4
  183. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  184. package/dist/src/modules/workspace/types.js.map +1 -1
  185. package/dist/src/modules/workspace/watcher.d.ts +7 -2
  186. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  187. package/dist/src/modules/workspace/watcher.js +28 -4
  188. package/dist/src/modules/workspace/watcher.js.map +1 -1
  189. package/dist/src/prose-drafts.d.ts +240 -0
  190. package/dist/src/prose-drafts.d.ts.map +1 -0
  191. package/dist/src/prose-drafts.js +362 -0
  192. package/dist/src/prose-drafts.js.map +1 -0
  193. package/dist/src/prose-segments.d.ts +21 -2
  194. package/dist/src/prose-segments.d.ts.map +1 -1
  195. package/dist/src/prose-segments.js +46 -9
  196. package/dist/src/prose-segments.js.map +1 -1
  197. package/dist/src/recovery/offline-branch.d.ts +1 -0
  198. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  199. package/dist/src/recovery/offline-branch.js +3 -1
  200. package/dist/src/recovery/offline-branch.js.map +1 -1
  201. package/dist/src/safe-slice.d.ts +5 -1
  202. package/dist/src/safe-slice.d.ts.map +1 -1
  203. package/dist/src/safe-slice.js +9 -1
  204. package/dist/src/safe-slice.js.map +1 -1
  205. package/dist/src/tool-image-ledger.d.ts +87 -19
  206. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  207. package/dist/src/tool-image-ledger.js +135 -32
  208. package/dist/src/tool-image-ledger.js.map +1 -1
  209. package/dist/src/tool-presentation.d.ts +9 -0
  210. package/dist/src/tool-presentation.d.ts.map +1 -1
  211. package/dist/src/tool-presentation.js +257 -60
  212. package/dist/src/tool-presentation.js.map +1 -1
  213. package/dist/src/tool-result-guard.d.ts.map +1 -1
  214. package/dist/src/tool-result-guard.js +7 -10
  215. package/dist/src/tool-result-guard.js.map +1 -1
  216. package/dist/src/tool-result-history.d.ts +32 -3
  217. package/dist/src/tool-result-history.d.ts.map +1 -1
  218. package/dist/src/tool-result-history.js +106 -19
  219. package/dist/src/tool-result-history.js.map +1 -1
  220. package/dist/src/tune-out/coordinator.d.ts +25 -1
  221. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  222. package/dist/src/tune-out/coordinator.js +260 -65
  223. package/dist/src/tune-out/coordinator.js.map +1 -1
  224. package/dist/src/types/agent.d.ts +17 -5
  225. package/dist/src/types/agent.d.ts.map +1 -1
  226. package/dist/src/types/events.d.ts +17 -0
  227. package/dist/src/types/events.d.ts.map +1 -1
  228. package/dist/src/types/framework.d.ts +21 -5
  229. package/dist/src/types/framework.d.ts.map +1 -1
  230. package/dist/src/types/inference-log.d.ts +1 -1
  231. package/dist/src/types/inference-log.d.ts.map +1 -1
  232. package/dist/src/types/module.d.ts +23 -4
  233. package/dist/src/types/module.d.ts.map +1 -1
  234. package/dist/src/types/trace.d.ts +95 -6
  235. package/dist/src/types/trace.d.ts.map +1 -1
  236. package/dist/src/usage/usage-tracker.d.ts +5 -0
  237. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  238. package/dist/src/usage/usage-tracker.js +35 -8
  239. package/dist/src/usage/usage-tracker.js.map +1 -1
  240. package/package.json +3 -4
  241. package/src/agent.ts +162 -40
  242. package/src/api/mcp-server-core.ts +1 -1
  243. package/src/api/server.ts +4 -1
  244. package/src/api/types.ts +1 -1
  245. package/src/branch-names.ts +19 -0
  246. package/src/closest-names.ts +94 -0
  247. package/src/code-execution/py-runner.ts +200 -76
  248. package/src/code-execution/runtime-py.ts +49 -3
  249. package/src/code-execution/tool-definition.ts +28 -4
  250. package/src/config-checks.ts +48 -0
  251. package/src/deadline-timer.ts +44 -0
  252. package/src/error-message.ts +48 -0
  253. package/src/framework.ts +5352 -1388
  254. package/src/gate/event-gate.ts +28 -3
  255. package/src/index.ts +11 -2
  256. package/src/mcpl/channel-registry.ts +517 -286
  257. package/src/mcpl/checkpoint-manager.ts +104 -29
  258. package/src/mcpl/conversation-router.ts +28 -8
  259. package/src/mcpl/convert-block.ts +97 -0
  260. package/src/mcpl/errors.ts +28 -0
  261. package/src/mcpl/hook-orchestrator.ts +84 -77
  262. package/src/mcpl/inbound-source.ts +156 -0
  263. package/src/mcpl/index.ts +10 -1
  264. package/src/mcpl/inference-router.ts +174 -99
  265. package/src/mcpl/push-coalescer.ts +60 -24
  266. package/src/mcpl/push-handler.ts +20 -90
  267. package/src/mcpl/reference-fetcher.ts +71 -26
  268. package/src/mcpl/references.ts +81 -22
  269. package/src/mcpl/server-connection.ts +427 -132
  270. package/src/mcpl/server-registry.ts +15 -4
  271. package/src/mcpl/skip-reply-wake.ts +73 -0
  272. package/src/mcpl/transport.ts +120 -18
  273. package/src/mcpl/types.ts +14 -3
  274. package/src/mcpl/visible-content.ts +27 -0
  275. package/src/module-registry.ts +58 -23
  276. package/src/modules/api/index.ts +1 -1
  277. package/src/modules/discord/discord-js-client.ts +26 -10
  278. package/src/modules/discord/index.ts +391 -158
  279. package/src/modules/discord/types.ts +15 -2
  280. package/src/modules/health/index.ts +2 -1
  281. package/src/modules/history/index.ts +410 -120
  282. package/src/modules/history/semantic.ts +189 -32
  283. package/src/modules/unknown-tool.ts +16 -0
  284. package/src/modules/workspace/disk-agreement.ts +51 -3
  285. package/src/modules/workspace/effects.ts +279 -35
  286. package/src/modules/workspace/index.ts +810 -260
  287. package/src/modules/workspace/observe.ts +194 -20
  288. package/src/modules/workspace/reconcile.ts +619 -64
  289. package/src/modules/workspace/types.ts +18 -4
  290. package/src/modules/workspace/watcher.ts +27 -4
  291. package/src/prose-drafts.ts +481 -0
  292. package/src/prose-segments.ts +51 -8
  293. package/src/recovery/offline-branch.ts +4 -1
  294. package/src/safe-slice.ts +9 -1
  295. package/src/tool-image-ledger.ts +153 -34
  296. package/src/tool-presentation.ts +224 -40
  297. package/src/tool-result-guard.ts +7 -8
  298. package/src/tool-result-history.ts +124 -20
  299. package/src/tune-out/coordinator.ts +259 -65
  300. package/src/types/agent.ts +17 -7
  301. package/src/types/events.ts +17 -0
  302. package/src/types/framework.ts +21 -5
  303. package/src/types/inference-log.ts +1 -1
  304. package/src/types/module.ts +24 -4
  305. package/src/types/trace.ts +101 -6
  306. package/src/usage/usage-tracker.ts +32 -8
  307. package/dist/test/activation-request-sanitize.test.d.ts +0 -2
  308. package/dist/test/activation-request-sanitize.test.d.ts.map +0 -1
  309. package/dist/test/activation-request-sanitize.test.js +0 -89
  310. package/dist/test/activation-request-sanitize.test.js.map +0 -1
  311. package/dist/test/agent-cache-ttl.test.d.ts +0 -2
  312. package/dist/test/agent-cache-ttl.test.d.ts.map +0 -1
  313. package/dist/test/agent-cache-ttl.test.js +0 -41
  314. package/dist/test/agent-cache-ttl.test.js.map +0 -1
  315. package/dist/test/agent-runtime-settings.test.d.ts +0 -2
  316. package/dist/test/agent-runtime-settings.test.d.ts.map +0 -1
  317. package/dist/test/agent-runtime-settings.test.js +0 -1598
  318. package/dist/test/agent-runtime-settings.test.js.map +0 -1
  319. package/dist/test/api-admin-gate.test.d.ts +0 -2
  320. package/dist/test/api-admin-gate.test.d.ts.map +0 -1
  321. package/dist/test/api-admin-gate.test.js +0 -116
  322. package/dist/test/api-admin-gate.test.js.map +0 -1
  323. package/dist/test/assistant-persist-refusal.test.d.ts +0 -2
  324. package/dist/test/assistant-persist-refusal.test.d.ts.map +0 -1
  325. package/dist/test/assistant-persist-refusal.test.js +0 -63
  326. package/dist/test/assistant-persist-refusal.test.js.map +0 -1
  327. package/dist/test/batched-wake-locus.test.d.ts +0 -2
  328. package/dist/test/batched-wake-locus.test.d.ts.map +0 -1
  329. package/dist/test/batched-wake-locus.test.js +0 -166
  330. package/dist/test/batched-wake-locus.test.js.map +0 -1
  331. package/dist/test/busy-agent-scheduler.test.d.ts +0 -2
  332. package/dist/test/busy-agent-scheduler.test.d.ts.map +0 -1
  333. package/dist/test/busy-agent-scheduler.test.js +0 -92
  334. package/dist/test/busy-agent-scheduler.test.js.map +0 -1
  335. package/dist/test/channel-incoming-targeting.test.d.ts +0 -2
  336. package/dist/test/channel-incoming-targeting.test.d.ts.map +0 -1
  337. package/dist/test/channel-incoming-targeting.test.js +0 -81
  338. package/dist/test/channel-incoming-targeting.test.js.map +0 -1
  339. package/dist/test/channel-label-history.test.d.ts +0 -2
  340. package/dist/test/channel-label-history.test.d.ts.map +0 -1
  341. package/dist/test/channel-label-history.test.js +0 -424
  342. package/dist/test/channel-label-history.test.js.map +0 -1
  343. package/dist/test/channel-lifecycle-provenance.test.d.ts +0 -2
  344. package/dist/test/channel-lifecycle-provenance.test.d.ts.map +0 -1
  345. package/dist/test/channel-lifecycle-provenance.test.js +0 -47
  346. package/dist/test/channel-lifecycle-provenance.test.js.map +0 -1
  347. package/dist/test/channel-registry-routing.test.d.ts +0 -2
  348. package/dist/test/channel-registry-routing.test.d.ts.map +0 -1
  349. package/dist/test/channel-registry-routing.test.js +0 -446
  350. package/dist/test/channel-registry-routing.test.js.map +0 -1
  351. package/dist/test/channel-registry-typing.test.d.ts +0 -2
  352. package/dist/test/channel-registry-typing.test.d.ts.map +0 -1
  353. package/dist/test/channel-registry-typing.test.js +0 -91
  354. package/dist/test/channel-registry-typing.test.js.map +0 -1
  355. package/dist/test/channels-changed-respond-first.test.d.ts +0 -2
  356. package/dist/test/channels-changed-respond-first.test.d.ts.map +0 -1
  357. package/dist/test/channels-changed-respond-first.test.js +0 -74
  358. package/dist/test/channels-changed-respond-first.test.js.map +0 -1
  359. package/dist/test/checkpoint-attribution.test.d.ts +0 -2
  360. package/dist/test/checkpoint-attribution.test.d.ts.map +0 -1
  361. package/dist/test/checkpoint-attribution.test.js +0 -200
  362. package/dist/test/checkpoint-attribution.test.js.map +0 -1
  363. package/dist/test/code-execution.test.d.ts +0 -11
  364. package/dist/test/code-execution.test.d.ts.map +0 -1
  365. package/dist/test/code-execution.test.js +0 -1272
  366. package/dist/test/code-execution.test.js.map +0 -1
  367. package/dist/test/component-defaults.test.d.ts +0 -2
  368. package/dist/test/component-defaults.test.d.ts.map +0 -1
  369. package/dist/test/component-defaults.test.js +0 -54
  370. package/dist/test/component-defaults.test.js.map +0 -1
  371. package/dist/test/conversation-fork-injection-scoping.test.d.ts +0 -2
  372. package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +0 -1
  373. package/dist/test/conversation-fork-injection-scoping.test.js +0 -200
  374. package/dist/test/conversation-fork-injection-scoping.test.js.map +0 -1
  375. package/dist/test/conversation-router.test.d.ts +0 -2
  376. package/dist/test/conversation-router.test.d.ts.map +0 -1
  377. package/dist/test/conversation-router.test.js +0 -174
  378. package/dist/test/conversation-router.test.js.map +0 -1
  379. package/dist/test/conversation-routing.test.d.ts +0 -7
  380. package/dist/test/conversation-routing.test.d.ts.map +0 -1
  381. package/dist/test/conversation-routing.test.js +0 -621
  382. package/dist/test/conversation-routing.test.js.map +0 -1
  383. package/dist/test/deferred-wake-integrity.test.d.ts +0 -29
  384. package/dist/test/deferred-wake-integrity.test.d.ts.map +0 -1
  385. package/dist/test/deferred-wake-integrity.test.js +0 -320
  386. package/dist/test/deferred-wake-integrity.test.js.map +0 -1
  387. package/dist/test/discord-awareness-outbox.test.d.ts +0 -2
  388. package/dist/test/discord-awareness-outbox.test.d.ts.map +0 -1
  389. package/dist/test/discord-awareness-outbox.test.js +0 -371
  390. package/dist/test/discord-awareness-outbox.test.js.map +0 -1
  391. package/dist/test/discord-history-delete-window.test.d.ts +0 -2
  392. package/dist/test/discord-history-delete-window.test.d.ts.map +0 -1
  393. package/dist/test/discord-history-delete-window.test.js +0 -81
  394. package/dist/test/discord-history-delete-window.test.js.map +0 -1
  395. package/dist/test/discord-history-sync.test.d.ts +0 -2
  396. package/dist/test/discord-history-sync.test.d.ts.map +0 -1
  397. package/dist/test/discord-history-sync.test.js +0 -56
  398. package/dist/test/discord-history-sync.test.js.map +0 -1
  399. package/dist/test/discord-typing-keepalive.test.d.ts +0 -2
  400. package/dist/test/discord-typing-keepalive.test.d.ts.map +0 -1
  401. package/dist/test/discord-typing-keepalive.test.js +0 -77
  402. package/dist/test/discord-typing-keepalive.test.js.map +0 -1
  403. package/dist/test/ephemeral-settle.test.d.ts +0 -2
  404. package/dist/test/ephemeral-settle.test.d.ts.map +0 -1
  405. package/dist/test/ephemeral-settle.test.js +0 -354
  406. package/dist/test/ephemeral-settle.test.js.map +0 -1
  407. package/dist/test/event-gate-passthrough.test.d.ts +0 -15
  408. package/dist/test/event-gate-passthrough.test.d.ts.map +0 -1
  409. package/dist/test/event-gate-passthrough.test.js +0 -289
  410. package/dist/test/event-gate-passthrough.test.js.map +0 -1
  411. package/dist/test/event-gate-policy-mutation.test.d.ts +0 -2
  412. package/dist/test/event-gate-policy-mutation.test.d.ts.map +0 -1
  413. package/dist/test/event-gate-policy-mutation.test.js +0 -137
  414. package/dist/test/event-gate-policy-mutation.test.js.map +0 -1
  415. package/dist/test/event-gate-rate-limit.test.d.ts +0 -2
  416. package/dist/test/event-gate-rate-limit.test.d.ts.map +0 -1
  417. package/dist/test/event-gate-rate-limit.test.js +0 -627
  418. package/dist/test/event-gate-rate-limit.test.js.map +0 -1
  419. package/dist/test/event-gate-script.test.d.ts +0 -2
  420. package/dist/test/event-gate-script.test.d.ts.map +0 -1
  421. package/dist/test/event-gate-script.test.js +0 -95
  422. package/dist/test/event-gate-script.test.js.map +0 -1
  423. package/dist/test/event-gate-selfwake.test.d.ts +0 -15
  424. package/dist/test/event-gate-selfwake.test.d.ts.map +0 -1
  425. package/dist/test/event-gate-selfwake.test.js +0 -125
  426. package/dist/test/event-gate-selfwake.test.js.map +0 -1
  427. package/dist/test/event-gate-tags.test.d.ts +0 -2
  428. package/dist/test/event-gate-tags.test.d.ts.map +0 -1
  429. package/dist/test/event-gate-tags.test.js +0 -76
  430. package/dist/test/event-gate-tags.test.js.map +0 -1
  431. package/dist/test/event-gate.test.d.ts +0 -2
  432. package/dist/test/event-gate.test.d.ts.map +0 -1
  433. package/dist/test/event-gate.test.js +0 -919
  434. package/dist/test/event-gate.test.js.map +0 -1
  435. package/dist/test/explicit-prose-routing.test.d.ts +0 -11
  436. package/dist/test/explicit-prose-routing.test.d.ts.map +0 -1
  437. package/dist/test/explicit-prose-routing.test.js +0 -1715
  438. package/dist/test/explicit-prose-routing.test.js.map +0 -1
  439. package/dist/test/framework.test.d.ts +0 -5
  440. package/dist/test/framework.test.d.ts.map +0 -1
  441. package/dist/test/framework.test.js +0 -1048
  442. package/dist/test/framework.test.js.map +0 -1
  443. package/dist/test/gather-context-timeout.test.d.ts +0 -2
  444. package/dist/test/gather-context-timeout.test.d.ts.map +0 -1
  445. package/dist/test/gather-context-timeout.test.js +0 -120
  446. package/dist/test/gather-context-timeout.test.js.map +0 -1
  447. package/dist/test/health-snapshot.test.d.ts +0 -2
  448. package/dist/test/health-snapshot.test.d.ts.map +0 -1
  449. package/dist/test/health-snapshot.test.js +0 -74
  450. package/dist/test/health-snapshot.test.js.map +0 -1
  451. package/dist/test/helpers/coalescing-fixture.d.ts +0 -74
  452. package/dist/test/helpers/coalescing-fixture.d.ts.map +0 -1
  453. package/dist/test/helpers/coalescing-fixture.js +0 -152
  454. package/dist/test/helpers/coalescing-fixture.js.map +0 -1
  455. package/dist/test/helpers/coalescing-kill-child.d.ts +0 -2
  456. package/dist/test/helpers/coalescing-kill-child.d.ts.map +0 -1
  457. package/dist/test/helpers/coalescing-kill-child.js +0 -24
  458. package/dist/test/helpers/coalescing-kill-child.js.map +0 -1
  459. package/dist/test/helpers/mock-membrane.d.ts +0 -53
  460. package/dist/test/helpers/mock-membrane.d.ts.map +0 -1
  461. package/dist/test/helpers/mock-membrane.js +0 -168
  462. package/dist/test/helpers/mock-membrane.js.map +0 -1
  463. package/dist/test/helpers/pytc-stop-rows.d.ts +0 -11
  464. package/dist/test/helpers/pytc-stop-rows.d.ts.map +0 -1
  465. package/dist/test/helpers/pytc-stop-rows.js +0 -143
  466. package/dist/test/helpers/pytc-stop-rows.js.map +0 -1
  467. package/dist/test/helpers/quiesce-crash-child.d.ts +0 -2
  468. package/dist/test/helpers/quiesce-crash-child.d.ts.map +0 -1
  469. package/dist/test/helpers/quiesce-crash-child.js +0 -156
  470. package/dist/test/helpers/quiesce-crash-child.js.map +0 -1
  471. package/dist/test/hide-then-turn.test.d.ts +0 -2
  472. package/dist/test/hide-then-turn.test.d.ts.map +0 -1
  473. package/dist/test/hide-then-turn.test.js +0 -101
  474. package/dist/test/hide-then-turn.test.js.map +0 -1
  475. package/dist/test/history-module-overview.test.d.ts +0 -2
  476. package/dist/test/history-module-overview.test.d.ts.map +0 -1
  477. package/dist/test/history-module-overview.test.js +0 -711
  478. package/dist/test/history-module-overview.test.js.map +0 -1
  479. package/dist/test/history-module-realstore.test.d.ts +0 -2
  480. package/dist/test/history-module-realstore.test.d.ts.map +0 -1
  481. package/dist/test/history-module-realstore.test.js +0 -363
  482. package/dist/test/history-module-realstore.test.js.map +0 -1
  483. package/dist/test/history-module-semantic.test.d.ts +0 -2
  484. package/dist/test/history-module-semantic.test.d.ts.map +0 -1
  485. package/dist/test/history-module-semantic.test.js +0 -213
  486. package/dist/test/history-module-semantic.test.js.map +0 -1
  487. package/dist/test/history-module-ux.test.d.ts +0 -2
  488. package/dist/test/history-module-ux.test.d.ts.map +0 -1
  489. package/dist/test/history-module-ux.test.js +0 -304
  490. package/dist/test/history-module-ux.test.js.map +0 -1
  491. package/dist/test/history-module.test.d.ts +0 -2
  492. package/dist/test/history-module.test.d.ts.map +0 -1
  493. package/dist/test/history-module.test.js +0 -619
  494. package/dist/test/history-module.test.js.map +0 -1
  495. package/dist/test/history-semantic-review-findings.test.d.ts +0 -2
  496. package/dist/test/history-semantic-review-findings.test.d.ts.map +0 -1
  497. package/dist/test/history-semantic-review-findings.test.js +0 -338
  498. package/dist/test/history-semantic-review-findings.test.js.map +0 -1
  499. package/dist/test/history-semantic-sync-converges.test.d.ts +0 -2
  500. package/dist/test/history-semantic-sync-converges.test.d.ts.map +0 -1
  501. package/dist/test/history-semantic-sync-converges.test.js +0 -109
  502. package/dist/test/history-semantic-sync-converges.test.js.map +0 -1
  503. package/dist/test/host-quiesce.test.d.ts +0 -2
  504. package/dist/test/host-quiesce.test.d.ts.map +0 -1
  505. package/dist/test/host-quiesce.test.js +0 -988
  506. package/dist/test/host-quiesce.test.js.map +0 -1
  507. package/dist/test/inference-exhaustion-rewind.test.d.ts +0 -2
  508. package/dist/test/inference-exhaustion-rewind.test.d.ts.map +0 -1
  509. package/dist/test/inference-exhaustion-rewind.test.js +0 -387
  510. package/dist/test/inference-exhaustion-rewind.test.js.map +0 -1
  511. package/dist/test/inference-failure-bounds.test.d.ts +0 -2
  512. package/dist/test/inference-failure-bounds.test.d.ts.map +0 -1
  513. package/dist/test/inference-failure-bounds.test.js +0 -1202
  514. package/dist/test/inference-failure-bounds.test.js.map +0 -1
  515. package/dist/test/inference-failure-observability.test.d.ts +0 -2
  516. package/dist/test/inference-failure-observability.test.d.ts.map +0 -1
  517. package/dist/test/inference-failure-observability.test.js +0 -84
  518. package/dist/test/inference-failure-observability.test.js.map +0 -1
  519. package/dist/test/journal-tool.test.d.ts +0 -2
  520. package/dist/test/journal-tool.test.d.ts.map +0 -1
  521. package/dist/test/journal-tool.test.js +0 -33
  522. package/dist/test/journal-tool.test.js.map +0 -1
  523. package/dist/test/kv-unified-open-flight-retry.test.d.ts +0 -2
  524. package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +0 -1
  525. package/dist/test/kv-unified-open-flight-retry.test.js +0 -189
  526. package/dist/test/kv-unified-open-flight-retry.test.js.map +0 -1
  527. package/dist/test/kv-unified-wiring.test.d.ts +0 -2
  528. package/dist/test/kv-unified-wiring.test.d.ts.map +0 -1
  529. package/dist/test/kv-unified-wiring.test.js +0 -95
  530. package/dist/test/kv-unified-wiring.test.js.map +0 -1
  531. package/dist/test/late-events-after-stop.test.d.ts +0 -2
  532. package/dist/test/late-events-after-stop.test.d.ts.map +0 -1
  533. package/dist/test/late-events-after-stop.test.js +0 -122
  534. package/dist/test/late-events-after-stop.test.js.map +0 -1
  535. package/dist/test/liveness-watchdog.test.d.ts +0 -2
  536. package/dist/test/liveness-watchdog.test.d.ts.map +0 -1
  537. package/dist/test/liveness-watchdog.test.js +0 -94
  538. package/dist/test/liveness-watchdog.test.js.map +0 -1
  539. package/dist/test/machine-close-provenance.test.d.ts +0 -2
  540. package/dist/test/machine-close-provenance.test.d.ts.map +0 -1
  541. package/dist/test/machine-close-provenance.test.js +0 -116
  542. package/dist/test/machine-close-provenance.test.js.map +0 -1
  543. package/dist/test/maintenance-timer.test.d.ts +0 -2
  544. package/dist/test/maintenance-timer.test.d.ts.map +0 -1
  545. package/dist/test/maintenance-timer.test.js +0 -1827
  546. package/dist/test/maintenance-timer.test.js.map +0 -1
  547. package/dist/test/mcp-server-bin.test.d.ts +0 -2
  548. package/dist/test/mcp-server-bin.test.d.ts.map +0 -1
  549. package/dist/test/mcp-server-bin.test.js +0 -142
  550. package/dist/test/mcp-server-bin.test.js.map +0 -1
  551. package/dist/test/mcpl-access-provider.test.d.ts +0 -2
  552. package/dist/test/mcpl-access-provider.test.d.ts.map +0 -1
  553. package/dist/test/mcpl-access-provider.test.js +0 -20
  554. package/dist/test/mcpl-access-provider.test.js.map +0 -1
  555. package/dist/test/mcpl-awareness-barrier.test.d.ts +0 -2
  556. package/dist/test/mcpl-awareness-barrier.test.d.ts.map +0 -1
  557. package/dist/test/mcpl-awareness-barrier.test.js +0 -769
  558. package/dist/test/mcpl-awareness-barrier.test.js.map +0 -1
  559. package/dist/test/mcpl-capability-grant.test.d.ts +0 -10
  560. package/dist/test/mcpl-capability-grant.test.d.ts.map +0 -1
  561. package/dist/test/mcpl-capability-grant.test.js +0 -111
  562. package/dist/test/mcpl-capability-grant.test.js.map +0 -1
  563. package/dist/test/mcpl-capability-scoping.test.d.ts +0 -2
  564. package/dist/test/mcpl-capability-scoping.test.d.ts.map +0 -1
  565. package/dist/test/mcpl-capability-scoping.test.js +0 -266
  566. package/dist/test/mcpl-capability-scoping.test.js.map +0 -1
  567. package/dist/test/mcpl-child-env.test.d.ts +0 -2
  568. package/dist/test/mcpl-child-env.test.d.ts.map +0 -1
  569. package/dist/test/mcpl-child-env.test.js +0 -158
  570. package/dist/test/mcpl-child-env.test.js.map +0 -1
  571. package/dist/test/mcpl-coalesced-content-parity.test.d.ts +0 -2
  572. package/dist/test/mcpl-coalesced-content-parity.test.d.ts.map +0 -1
  573. package/dist/test/mcpl-coalesced-content-parity.test.js +0 -532
  574. package/dist/test/mcpl-coalesced-content-parity.test.js.map +0 -1
  575. package/dist/test/mcpl-coalescing.test.d.ts +0 -2
  576. package/dist/test/mcpl-coalescing.test.d.ts.map +0 -1
  577. package/dist/test/mcpl-coalescing.test.js +0 -1125
  578. package/dist/test/mcpl-coalescing.test.js.map +0 -1
  579. package/dist/test/mcpl-config-diagnostics.test.d.ts +0 -2
  580. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +0 -1
  581. package/dist/test/mcpl-config-diagnostics.test.js +0 -452
  582. package/dist/test/mcpl-config-diagnostics.test.js.map +0 -1
  583. package/dist/test/mcpl-empty-content.test.d.ts +0 -2
  584. package/dist/test/mcpl-empty-content.test.d.ts.map +0 -1
  585. package/dist/test/mcpl-empty-content.test.js +0 -368
  586. package/dist/test/mcpl-empty-content.test.js.map +0 -1
  587. package/dist/test/mcpl-gate-roundtrip.test.d.ts +0 -2
  588. package/dist/test/mcpl-gate-roundtrip.test.d.ts.map +0 -1
  589. package/dist/test/mcpl-gate-roundtrip.test.js +0 -285
  590. package/dist/test/mcpl-gate-roundtrip.test.js.map +0 -1
  591. package/dist/test/mcpl-list-status.test.d.ts +0 -2
  592. package/dist/test/mcpl-list-status.test.d.ts.map +0 -1
  593. package/dist/test/mcpl-list-status.test.js +0 -82
  594. package/dist/test/mcpl-list-status.test.js.map +0 -1
  595. package/dist/test/mcpl-manifest-changed.test.d.ts +0 -2
  596. package/dist/test/mcpl-manifest-changed.test.d.ts.map +0 -1
  597. package/dist/test/mcpl-manifest-changed.test.js +0 -178
  598. package/dist/test/mcpl-manifest-changed.test.js.map +0 -1
  599. package/dist/test/mcpl-push-convert.test.d.ts +0 -2
  600. package/dist/test/mcpl-push-convert.test.d.ts.map +0 -1
  601. package/dist/test/mcpl-push-convert.test.js +0 -42
  602. package/dist/test/mcpl-push-convert.test.js.map +0 -1
  603. package/dist/test/mcpl-reconnect-events.test.d.ts +0 -2
  604. package/dist/test/mcpl-reconnect-events.test.d.ts.map +0 -1
  605. package/dist/test/mcpl-reconnect-events.test.js +0 -144
  606. package/dist/test/mcpl-reconnect-events.test.js.map +0 -1
  607. package/dist/test/mcpl-reconnect-reregistration.test.d.ts +0 -2
  608. package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +0 -1
  609. package/dist/test/mcpl-reconnect-reregistration.test.js +0 -238
  610. package/dist/test/mcpl-reconnect-reregistration.test.js.map +0 -1
  611. package/dist/test/mcpl-reference-fetcher.test.d.ts +0 -2
  612. package/dist/test/mcpl-reference-fetcher.test.d.ts.map +0 -1
  613. package/dist/test/mcpl-reference-fetcher.test.js +0 -427
  614. package/dist/test/mcpl-reference-fetcher.test.js.map +0 -1
  615. package/dist/test/mcpl-reference-fields.test.d.ts +0 -2
  616. package/dist/test/mcpl-reference-fields.test.d.ts.map +0 -1
  617. package/dist/test/mcpl-reference-fields.test.js +0 -149
  618. package/dist/test/mcpl-reference-fields.test.js.map +0 -1
  619. package/dist/test/mcpl-rejection-log-lines.test.d.ts +0 -2
  620. package/dist/test/mcpl-rejection-log-lines.test.d.ts.map +0 -1
  621. package/dist/test/mcpl-rejection-log-lines.test.js +0 -349
  622. package/dist/test/mcpl-rejection-log-lines.test.js.map +0 -1
  623. package/dist/test/mcpl-request-timeout.test.d.ts +0 -2
  624. package/dist/test/mcpl-request-timeout.test.d.ts.map +0 -1
  625. package/dist/test/mcpl-request-timeout.test.js +0 -138
  626. package/dist/test/mcpl-request-timeout.test.js.map +0 -1
  627. package/dist/test/mcpl-runtime-lifecycle.test.d.ts +0 -7
  628. package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +0 -1
  629. package/dist/test/mcpl-runtime-lifecycle.test.js +0 -142
  630. package/dist/test/mcpl-runtime-lifecycle.test.js.map +0 -1
  631. package/dist/test/mcpl-stderr-tail.test.d.ts +0 -2
  632. package/dist/test/mcpl-stderr-tail.test.d.ts.map +0 -1
  633. package/dist/test/mcpl-stderr-tail.test.js +0 -210
  634. package/dist/test/mcpl-stderr-tail.test.js.map +0 -1
  635. package/dist/test/mcpl-tool-class-listing.test.d.ts +0 -2
  636. package/dist/test/mcpl-tool-class-listing.test.d.ts.map +0 -1
  637. package/dist/test/mcpl-tool-class-listing.test.js +0 -147
  638. package/dist/test/mcpl-tool-class-listing.test.js.map +0 -1
  639. package/dist/test/mcpl-tool-classes.test.d.ts +0 -7
  640. package/dist/test/mcpl-tool-classes.test.d.ts.map +0 -1
  641. package/dist/test/mcpl-tool-classes.test.js +0 -80
  642. package/dist/test/mcpl-tool-classes.test.js.map +0 -1
  643. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +0 -2
  644. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +0 -1
  645. package/dist/test/mcpl-tool-lifecycle-e2e.test.js +0 -237
  646. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +0 -1
  647. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +0 -11
  648. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +0 -1
  649. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +0 -412
  650. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +0 -1
  651. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +0 -2
  652. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +0 -1
  653. package/dist/test/mcpl-tool-lifecycle-script.test.js +0 -288
  654. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +0 -1
  655. package/dist/test/mcpl-tool-lifecycle.test.d.ts +0 -11
  656. package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +0 -1
  657. package/dist/test/mcpl-tool-lifecycle.test.js +0 -270
  658. package/dist/test/mcpl-tool-lifecycle.test.js.map +0 -1
  659. package/dist/test/mcpl-transport-close.test.d.ts +0 -2
  660. package/dist/test/mcpl-transport-close.test.d.ts.map +0 -1
  661. package/dist/test/mcpl-transport-close.test.js +0 -373
  662. package/dist/test/mcpl-transport-close.test.js.map +0 -1
  663. package/dist/test/mcpl-transport-spawn.test.d.ts +0 -2
  664. package/dist/test/mcpl-transport-spawn.test.d.ts.map +0 -1
  665. package/dist/test/mcpl-transport-spawn.test.js +0 -58
  666. package/dist/test/mcpl-transport-spawn.test.js.map +0 -1
  667. package/dist/test/mcpl-ws-transport.test.d.ts +0 -2
  668. package/dist/test/mcpl-ws-transport.test.d.ts.map +0 -1
  669. package/dist/test/mcpl-ws-transport.test.js +0 -259
  670. package/dist/test/mcpl-ws-transport.test.js.map +0 -1
  671. package/dist/test/module-context-integration.test.d.ts +0 -18
  672. package/dist/test/module-context-integration.test.d.ts.map +0 -1
  673. package/dist/test/module-context-integration.test.js +0 -181
  674. package/dist/test/module-context-integration.test.js.map +0 -1
  675. package/dist/test/offline-recovery-branch.test.d.ts +0 -2
  676. package/dist/test/offline-recovery-branch.test.d.ts.map +0 -1
  677. package/dist/test/offline-recovery-branch.test.js +0 -570
  678. package/dist/test/offline-recovery-branch.test.js.map +0 -1
  679. package/dist/test/operator-surgery.test.d.ts +0 -2
  680. package/dist/test/operator-surgery.test.d.ts.map +0 -1
  681. package/dist/test/operator-surgery.test.js +0 -595
  682. package/dist/test/operator-surgery.test.js.map +0 -1
  683. package/dist/test/ops-alert.test.d.ts +0 -2
  684. package/dist/test/ops-alert.test.d.ts.map +0 -1
  685. package/dist/test/ops-alert.test.js +0 -153
  686. package/dist/test/ops-alert.test.js.map +0 -1
  687. package/dist/test/overbudget-drain-breaker.test.d.ts +0 -2
  688. package/dist/test/overbudget-drain-breaker.test.d.ts.map +0 -1
  689. package/dist/test/overbudget-drain-breaker.test.js +0 -198
  690. package/dist/test/overbudget-drain-breaker.test.js.map +0 -1
  691. package/dist/test/pack-installs-alone.test.d.ts +0 -2
  692. package/dist/test/pack-installs-alone.test.d.ts.map +0 -1
  693. package/dist/test/pack-installs-alone.test.js +0 -284
  694. package/dist/test/pack-installs-alone.test.js.map +0 -1
  695. package/dist/test/per-agent-delivery.test.d.ts +0 -2
  696. package/dist/test/per-agent-delivery.test.d.ts.map +0 -1
  697. package/dist/test/per-agent-delivery.test.js +0 -108
  698. package/dist/test/per-agent-delivery.test.js.map +0 -1
  699. package/dist/test/physical-window-restart.test.d.ts +0 -16
  700. package/dist/test/physical-window-restart.test.d.ts.map +0 -1
  701. package/dist/test/physical-window-restart.test.js +0 -487
  702. package/dist/test/physical-window-restart.test.js.map +0 -1
  703. package/dist/test/placed-reaction-baseline.test.d.ts +0 -2
  704. package/dist/test/placed-reaction-baseline.test.d.ts.map +0 -1
  705. package/dist/test/placed-reaction-baseline.test.js +0 -312
  706. package/dist/test/placed-reaction-baseline.test.js.map +0 -1
  707. package/dist/test/present-while-acting.test.d.ts +0 -19
  708. package/dist/test/present-while-acting.test.d.ts.map +0 -1
  709. package/dist/test/present-while-acting.test.js +0 -1649
  710. package/dist/test/present-while-acting.test.js.map +0 -1
  711. package/dist/test/prose-segments.test.d.ts +0 -2
  712. package/dist/test/prose-segments.test.d.ts.map +0 -1
  713. package/dist/test/prose-segments.test.js +0 -52
  714. package/dist/test/prose-segments.test.js.map +0 -1
  715. package/dist/test/prose-stream-router.test.d.ts +0 -2
  716. package/dist/test/prose-stream-router.test.d.ts.map +0 -1
  717. package/dist/test/prose-stream-router.test.js +0 -171
  718. package/dist/test/prose-stream-router.test.js.map +0 -1
  719. package/dist/test/provider-acceleration-cooldown.test.d.ts +0 -2
  720. package/dist/test/provider-acceleration-cooldown.test.d.ts.map +0 -1
  721. package/dist/test/provider-acceleration-cooldown.test.js +0 -435
  722. package/dist/test/provider-acceleration-cooldown.test.js.map +0 -1
  723. package/dist/test/provider-waits.test.d.ts +0 -2
  724. package/dist/test/provider-waits.test.d.ts.map +0 -1
  725. package/dist/test/provider-waits.test.js +0 -2259
  726. package/dist/test/provider-waits.test.js.map +0 -1
  727. package/dist/test/puppet-tool-call.test.d.ts +0 -2
  728. package/dist/test/puppet-tool-call.test.d.ts.map +0 -1
  729. package/dist/test/puppet-tool-call.test.js +0 -226
  730. package/dist/test/puppet-tool-call.test.js.map +0 -1
  731. package/dist/test/puppet-turn-reservation.test.d.ts +0 -2
  732. package/dist/test/puppet-turn-reservation.test.d.ts.map +0 -1
  733. package/dist/test/puppet-turn-reservation.test.js +0 -139
  734. package/dist/test/puppet-turn-reservation.test.js.map +0 -1
  735. package/dist/test/reaction-baseline-platform.test.d.ts +0 -2
  736. package/dist/test/reaction-baseline-platform.test.d.ts.map +0 -1
  737. package/dist/test/reaction-baseline-platform.test.js +0 -124
  738. package/dist/test/reaction-baseline-platform.test.js.map +0 -1
  739. package/dist/test/record-journal.test.d.ts +0 -6
  740. package/dist/test/record-journal.test.d.ts.map +0 -1
  741. package/dist/test/record-journal.test.js +0 -269
  742. package/dist/test/record-journal.test.js.map +0 -1
  743. package/dist/test/refusal-reaction-baseline.test.d.ts +0 -2
  744. package/dist/test/refusal-reaction-baseline.test.d.ts.map +0 -1
  745. package/dist/test/refusal-reaction-baseline.test.js +0 -81
  746. package/dist/test/refusal-reaction-baseline.test.js.map +0 -1
  747. package/dist/test/refusal-rewind.test.d.ts +0 -2
  748. package/dist/test/refusal-rewind.test.d.ts.map +0 -1
  749. package/dist/test/refusal-rewind.test.js +0 -117
  750. package/dist/test/refusal-rewind.test.js.map +0 -1
  751. package/dist/test/refusal-single-call.test.d.ts +0 -2
  752. package/dist/test/refusal-single-call.test.d.ts.map +0 -1
  753. package/dist/test/refusal-single-call.test.js +0 -177
  754. package/dist/test/refusal-single-call.test.js.map +0 -1
  755. package/dist/test/release-bump.test.d.ts +0 -2
  756. package/dist/test/release-bump.test.d.ts.map +0 -1
  757. package/dist/test/release-bump.test.js +0 -248
  758. package/dist/test/release-bump.test.js.map +0 -1
  759. package/dist/test/release-changelog.test.d.ts +0 -2
  760. package/dist/test/release-changelog.test.d.ts.map +0 -1
  761. package/dist/test/release-changelog.test.js +0 -159
  762. package/dist/test/release-changelog.test.js.map +0 -1
  763. package/dist/test/release-holds.test.d.ts +0 -2
  764. package/dist/test/release-holds.test.d.ts.map +0 -1
  765. package/dist/test/release-holds.test.js +0 -456
  766. package/dist/test/release-holds.test.js.map +0 -1
  767. package/dist/test/same-round-think-policy.test.d.ts +0 -2
  768. package/dist/test/same-round-think-policy.test.d.ts.map +0 -1
  769. package/dist/test/same-round-think-policy.test.js +0 -853
  770. package/dist/test/same-round-think-policy.test.js.map +0 -1
  771. package/dist/test/save-recent-image.test.d.ts +0 -26
  772. package/dist/test/save-recent-image.test.d.ts.map +0 -1
  773. package/dist/test/save-recent-image.test.js +0 -974
  774. package/dist/test/save-recent-image.test.js.map +0 -1
  775. package/dist/test/shutdown-errors.test.d.ts +0 -2
  776. package/dist/test/shutdown-errors.test.d.ts.map +0 -1
  777. package/dist/test/shutdown-errors.test.js +0 -662
  778. package/dist/test/shutdown-errors.test.js.map +0 -1
  779. package/dist/test/silent-heartbeat-rows.test.d.ts +0 -2
  780. package/dist/test/silent-heartbeat-rows.test.d.ts.map +0 -1
  781. package/dist/test/silent-heartbeat-rows.test.js +0 -451
  782. package/dist/test/silent-heartbeat-rows.test.js.map +0 -1
  783. package/dist/test/silent-heartbeat.test.d.ts +0 -2
  784. package/dist/test/silent-heartbeat.test.d.ts.map +0 -1
  785. package/dist/test/silent-heartbeat.test.js +0 -165
  786. package/dist/test/silent-heartbeat.test.js.map +0 -1
  787. package/dist/test/state-scaling-e2e.test.d.ts +0 -22
  788. package/dist/test/state-scaling-e2e.test.d.ts.map +0 -1
  789. package/dist/test/state-scaling-e2e.test.js +0 -206
  790. package/dist/test/state-scaling-e2e.test.js.map +0 -1
  791. package/dist/test/state-scaling.test.d.ts +0 -12
  792. package/dist/test/state-scaling.test.d.ts.map +0 -1
  793. package/dist/test/state-scaling.test.js +0 -251
  794. package/dist/test/state-scaling.test.js.map +0 -1
  795. package/dist/test/stop-with-host-operations.test.d.ts +0 -20
  796. package/dist/test/stop-with-host-operations.test.d.ts.map +0 -1
  797. package/dist/test/stop-with-host-operations.test.js +0 -1174
  798. package/dist/test/stop-with-host-operations.test.js.map +0 -1
  799. package/dist/test/stop-with-work-in-flight.test.d.ts +0 -23
  800. package/dist/test/stop-with-work-in-flight.test.d.ts.map +0 -1
  801. package/dist/test/stop-with-work-in-flight.test.js +0 -784
  802. package/dist/test/stop-with-work-in-flight.test.js.map +0 -1
  803. package/dist/test/surgery-marks-receipt.test.d.ts +0 -2
  804. package/dist/test/surgery-marks-receipt.test.d.ts.map +0 -1
  805. package/dist/test/surgery-marks-receipt.test.js +0 -540
  806. package/dist/test/surgery-marks-receipt.test.js.map +0 -1
  807. package/dist/test/thinking-token-stamp.test.d.ts +0 -12
  808. package/dist/test/thinking-token-stamp.test.d.ts.map +0 -1
  809. package/dist/test/thinking-token-stamp.test.js +0 -129
  810. package/dist/test/thinking-token-stamp.test.js.map +0 -1
  811. package/dist/test/timezone.test.d.ts +0 -2
  812. package/dist/test/timezone.test.d.ts.map +0 -1
  813. package/dist/test/timezone.test.js +0 -18
  814. package/dist/test/timezone.test.js.map +0 -1
  815. package/dist/test/tool-batch-hook.test.d.ts +0 -8
  816. package/dist/test/tool-batch-hook.test.d.ts.map +0 -1
  817. package/dist/test/tool-batch-hook.test.js +0 -275
  818. package/dist/test/tool-batch-hook.test.js.map +0 -1
  819. package/dist/test/tool-policy.test.d.ts +0 -2
  820. package/dist/test/tool-policy.test.d.ts.map +0 -1
  821. package/dist/test/tool-policy.test.js +0 -51
  822. package/dist/test/tool-policy.test.js.map +0 -1
  823. package/dist/test/tool-presentation.test.d.ts +0 -2
  824. package/dist/test/tool-presentation.test.d.ts.map +0 -1
  825. package/dist/test/tool-presentation.test.js +0 -268
  826. package/dist/test/tool-presentation.test.js.map +0 -1
  827. package/dist/test/tool-result-content.test.d.ts +0 -2
  828. package/dist/test/tool-result-content.test.d.ts.map +0 -1
  829. package/dist/test/tool-result-content.test.js +0 -37
  830. package/dist/test/tool-result-content.test.js.map +0 -1
  831. package/dist/test/tool-result-guard.test.d.ts +0 -2
  832. package/dist/test/tool-result-guard.test.d.ts.map +0 -1
  833. package/dist/test/tool-result-guard.test.js +0 -3729
  834. package/dist/test/tool-result-guard.test.js.map +0 -1
  835. package/dist/test/tool-result-history.test.d.ts +0 -7
  836. package/dist/test/tool-result-history.test.d.ts.map +0 -1
  837. package/dist/test/tool-result-history.test.js +0 -156
  838. package/dist/test/tool-result-history.test.js.map +0 -1
  839. package/dist/test/tool-result-spill.test.d.ts +0 -13
  840. package/dist/test/tool-result-spill.test.d.ts.map +0 -1
  841. package/dist/test/tool-result-spill.test.js +0 -586
  842. package/dist/test/tool-result-spill.test.js.map +0 -1
  843. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +0 -2
  844. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +0 -1
  845. package/dist/test/tool-wrapper-prose-guard-integration.test.js +0 -485
  846. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +0 -1
  847. package/dist/test/tool-wrapper-prose-guard.test.d.ts +0 -2
  848. package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +0 -1
  849. package/dist/test/tool-wrapper-prose-guard.test.js +0 -34
  850. package/dist/test/tool-wrapper-prose-guard.test.js.map +0 -1
  851. package/dist/test/tools-observe-parser-conformance.test.d.ts +0 -2
  852. package/dist/test/tools-observe-parser-conformance.test.d.ts.map +0 -1
  853. package/dist/test/tools-observe-parser-conformance.test.js +0 -129
  854. package/dist/test/tools-observe-parser-conformance.test.js.map +0 -1
  855. package/dist/test/trunk-channel-routing.test.d.ts +0 -19
  856. package/dist/test/trunk-channel-routing.test.d.ts.map +0 -1
  857. package/dist/test/trunk-channel-routing.test.js +0 -652
  858. package/dist/test/trunk-channel-routing.test.js.map +0 -1
  859. package/dist/test/tune-out-e2e.test.d.ts +0 -2
  860. package/dist/test/tune-out-e2e.test.d.ts.map +0 -1
  861. package/dist/test/tune-out-e2e.test.js +0 -142
  862. package/dist/test/tune-out-e2e.test.js.map +0 -1
  863. package/dist/test/tune-out-gate-composition.test.d.ts +0 -2
  864. package/dist/test/tune-out-gate-composition.test.d.ts.map +0 -1
  865. package/dist/test/tune-out-gate-composition.test.js +0 -221
  866. package/dist/test/tune-out-gate-composition.test.js.map +0 -1
  867. package/dist/test/tune-out-lifecycle.test.d.ts +0 -2
  868. package/dist/test/tune-out-lifecycle.test.d.ts.map +0 -1
  869. package/dist/test/tune-out-lifecycle.test.js +0 -121
  870. package/dist/test/tune-out-lifecycle.test.js.map +0 -1
  871. package/dist/test/turn-trigger-provenance.test.d.ts +0 -2
  872. package/dist/test/turn-trigger-provenance.test.d.ts.map +0 -1
  873. package/dist/test/turn-trigger-provenance.test.js +0 -137
  874. package/dist/test/turn-trigger-provenance.test.js.map +0 -1
  875. package/dist/test/unconvertible-tool-result.test.d.ts +0 -2
  876. package/dist/test/unconvertible-tool-result.test.d.ts.map +0 -1
  877. package/dist/test/unconvertible-tool-result.test.js +0 -358
  878. package/dist/test/unconvertible-tool-result.test.js.map +0 -1
  879. package/dist/test/unsent-compiles.test.d.ts +0 -2
  880. package/dist/test/unsent-compiles.test.d.ts.map +0 -1
  881. package/dist/test/unsent-compiles.test.js +0 -139
  882. package/dist/test/unsent-compiles.test.js.map +0 -1
  883. package/dist/test/usage-accounting.test.d.ts +0 -2
  884. package/dist/test/usage-accounting.test.d.ts.map +0 -1
  885. package/dist/test/usage-accounting.test.js +0 -13
  886. package/dist/test/usage-accounting.test.js.map +0 -1
  887. package/dist/test/usage-tracker.test.d.ts +0 -2
  888. package/dist/test/usage-tracker.test.d.ts.map +0 -1
  889. package/dist/test/usage-tracker.test.js +0 -129
  890. package/dist/test/usage-tracker.test.js.map +0 -1
  891. package/dist/test/utils-surface.test.d.ts +0 -2
  892. package/dist/test/utils-surface.test.d.ts.map +0 -1
  893. package/dist/test/utils-surface.test.js +0 -145
  894. package/dist/test/utils-surface.test.js.map +0 -1
  895. package/dist/test/workspace-branch-continuation.test.d.ts +0 -15
  896. package/dist/test/workspace-branch-continuation.test.d.ts.map +0 -1
  897. package/dist/test/workspace-branch-continuation.test.js +0 -183
  898. package/dist/test/workspace-branch-continuation.test.js.map +0 -1
  899. package/dist/test/workspace-materialize-freshness.test.d.ts +0 -16
  900. package/dist/test/workspace-materialize-freshness.test.d.ts.map +0 -1
  901. package/dist/test/workspace-materialize-freshness.test.js +0 -252
  902. package/dist/test/workspace-materialize-freshness.test.js.map +0 -1
  903. package/dist/test/workspace-path-containment.test.d.ts +0 -19
  904. package/dist/test/workspace-path-containment.test.d.ts.map +0 -1
  905. package/dist/test/workspace-path-containment.test.js +0 -91
  906. package/dist/test/workspace-path-containment.test.js.map +0 -1
  907. package/dist/test/workspace-read-file-from-disk.test.d.ts +0 -17
  908. package/dist/test/workspace-read-file-from-disk.test.d.ts.map +0 -1
  909. package/dist/test/workspace-read-file-from-disk.test.js +0 -174
  910. package/dist/test/workspace-read-file-from-disk.test.js.map +0 -1
  911. package/dist/test/workspace-read-image.test.d.ts +0 -2
  912. package/dist/test/workspace-read-image.test.d.ts.map +0 -1
  913. package/dist/test/workspace-read-image.test.js +0 -597
  914. package/dist/test/workspace-read-image.test.js.map +0 -1
  915. package/dist/test/workspace-read-text.test.d.ts +0 -2
  916. package/dist/test/workspace-read-text.test.d.ts.map +0 -1
  917. package/dist/test/workspace-read-text.test.js +0 -101
  918. package/dist/test/workspace-read-text.test.js.map +0 -1
  919. package/dist/test/workspace-restore-order.test.d.ts +0 -16
  920. package/dist/test/workspace-restore-order.test.d.ts.map +0 -1
  921. package/dist/test/workspace-restore-order.test.js +0 -140
  922. package/dist/test/workspace-restore-order.test.js.map +0 -1
  923. package/dist/test/workspace-three-way.test.d.ts +0 -12
  924. package/dist/test/workspace-three-way.test.d.ts.map +0 -1
  925. package/dist/test/workspace-three-way.test.js +0 -4094
  926. package/dist/test/workspace-three-way.test.js.map +0 -1
  927. package/dist/test/workspace-watcher.test.d.ts +0 -2
  928. package/dist/test/workspace-watcher.test.d.ts.map +0 -1
  929. package/dist/test/workspace-watcher.test.js +0 -281
  930. package/dist/test/workspace-watcher.test.js.map +0 -1
  931. package/dist/test/xml-round-persist.test.d.ts +0 -19
  932. package/dist/test/xml-round-persist.test.d.ts.map +0 -1
  933. package/dist/test/xml-round-persist.test.js +0 -235
  934. package/dist/test/xml-round-persist.test.js.map +0 -1
  935. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -14,8 +14,9 @@
14
14
  */
15
15
 
16
16
  import type { ContentBlock } from '@animalabs/membrane';
17
- import { INLINE_WITHHELD_TEXT, isInlineContradiction, referenceStubOrNull } from './references.js';
17
+ import { convertBlock } from './convert-block.js';
18
18
  import type { JsStore } from '@animalabs/chronicle';
19
+ import { ensureStateRegistered } from '../module-registry.js';
19
20
 
20
21
  import type {
21
22
  ChannelDescriptor,
@@ -30,7 +31,6 @@ import type {
30
31
  ChannelsPublishParams,
31
32
  ChannelsOpenResult,
32
33
  ChannelHistoryRequest,
33
- McplContentBlock,
34
34
  } from './types.js';
35
35
 
36
36
  import type { McplServerRegistry } from './server-registry.js';
@@ -38,10 +38,13 @@ import type { FeatureSetManager } from './feature-set-manager.js';
38
38
  import type { ToolDefinition, ToolResult, ProcessEvent } from '../types/index.js';
39
39
  import { expandCoreTags } from './tags.js';
40
40
  import { coalesceRejectionReason, EmptyContentError, validateCoalescedContent } from './push-coalescer.js';
41
- import { isVisiblyEmptyContent } from './visible-content.js';
41
+ import { isVisiblyEmptyConversion } from './visible-content.js';
42
42
  import { logDetail, logValue } from './log-value.js';
43
43
  import { RefusalLines } from './refusal-lines.js';
44
44
  import { CapabilityGrant } from './capability-grant.js';
45
+ import type { InboundSource } from './inbound-source.js';
46
+ import { McplRequestError } from './server-connection.js';
47
+ import { readSkipReplyWake } from './skip-reply-wake.js';
45
48
 
46
49
  // ============================================================================
47
50
  // Typing indicator interval (Discord typing lasts ~10s, so 7s keeps it alive)
@@ -239,88 +242,10 @@ interface McplChannelIncomingEvent {
239
242
  tags?: string[];
240
243
  triggerInference?: boolean;
241
244
  targetAgents?: string[];
242
- }
243
-
244
- // ============================================================================
245
- // Content Conversion: McplContentBlock → membrane ContentBlock
246
- // ============================================================================
247
-
248
- /**
249
- * Convert a single MCPL wire-format content block to a membrane ContentBlock.
250
- */
251
- function convertBlock(block: McplContentBlock): ContentBlock {
252
- switch (block.type) {
253
- case 'text':
254
- // Text that isn't a string is no text, as the request sanitizer and
255
- // the visible-content check already read it.
256
- return { type: 'text', text: typeof block.text === 'string' ? block.text : '' };
257
-
258
- case 'image':
259
- if (isInlineContradiction(block)) {
260
- // RFC-005 vector 2: inline data claiming bulk disposition — fail
261
- // closed, withhold the data (checked BEFORE the data branch).
262
- return { type: 'text', text: INLINE_WITHHELD_TEXT };
263
- }
264
- if (isFilledString(block.uri) && block.disposition) {
265
- // RFC-005 uri-form media with a disposition claim: stub, do not
266
- // hand the URI to the provider or inline it.
267
- return { type: 'text', text: referenceStubOrNull(block, 'attachment on channel message') ?? '[reference]' };
268
- }
269
- if (isFilledString(block.data) && isFilledString(block.mimeType)) {
270
- return {
271
- type: 'image',
272
- source: { type: 'base64', data: block.data, mediaType: block.mimeType },
273
- } as ContentBlock;
274
- }
275
- if (isFilledString(block.uri)) {
276
- return {
277
- type: 'image',
278
- source: { type: 'url', url: block.uri },
279
- } as ContentBlock;
280
- }
281
- return { type: 'text', text: isFilledString(block.data) ? '[Image: no media type]' : '[Image: no data]' };
282
-
283
- case 'audio':
284
- if (isInlineContradiction(block)) {
285
- // RFC-005 vector 2: inline data claiming bulk disposition — fail
286
- // closed, withhold the data (checked BEFORE the data branch).
287
- return { type: 'text', text: INLINE_WITHHELD_TEXT };
288
- }
289
- if (isFilledString(block.uri) && block.disposition) {
290
- // RFC-005 uri-form media with a disposition claim: stub, do not
291
- // hand the URI to the provider or inline it.
292
- return { type: 'text', text: referenceStubOrNull(block, 'attachment on channel message') ?? '[reference]' };
293
- }
294
- if (isFilledString(block.data) && isFilledString(block.mimeType)) {
295
- return {
296
- type: 'audio',
297
- source: { type: 'base64', data: block.data, mediaType: block.mimeType },
298
- } as ContentBlock;
299
- }
300
- return { type: 'text', text: isFilledString(block.data) ? '[Audio: no media type]' : '[Audio: no data]' };
301
-
302
- case 'resource':
303
- // RFC-005: reference blocks become bounded stubs — never raw URIs
304
- // (a signed URL is a bearer credential that looks like a location).
305
- return { type: 'text', text: referenceStubOrNull(block, 'attachment on channel message') ?? '[reference]' };
306
-
307
- default: {
308
- // Unknown wire block types previously fell off the exhaustive switch
309
- // and propagated `undefined` into ContentBlock[]. Fail visibly. A type
310
- // that isn't a string is no type, and is never converted to one: an
311
- // object can throw when it is.
312
- const type: unknown = (block as { type?: unknown }).type;
313
- return { type: 'text', text: `[unrecognized content block: ${typeof type === 'string' ? type : 'untyped'}]` };
314
- }
315
- }
316
- }
317
-
318
- /** A string with something in it: the only data, mimeType or uri a block's
319
- * source is built from, and the only uri that makes it a reference. Anything
320
- * else a sender puts there leaves the block without it, so conversion never
321
- * throws and never copies a non-string. */
322
- function isFilledString(value: unknown): value is string {
323
- return typeof value === 'string' && value !== '';
245
+ /** Host acceptance time (epoch ms), stamped where the message is admitted. */
246
+ acceptedAt?: number;
247
+ /** Source envelope frozen at admission (mcpl/inbound-source.ts). */
248
+ inboundSource?: InboundSource;
324
249
  }
325
250
 
326
251
  // ============================================================================
@@ -336,9 +261,8 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
336
261
  {
337
262
  name: 'channel_open',
338
263
  description:
339
- 'Open a channel to start receiving its ordinary ongoing traffic. The MCPL ' +
340
- 'integration performs its own subscribe/join/attach operation. Optionally request ' +
341
- 'history preceding the message that invited you into the channel.',
264
+ 'Open a channel. What an open channel delivers to you depends on its MCPL server. ' +
265
+ 'Optionally request history preceding the message that invited you into the channel.',
342
266
  inputSchema: {
343
267
  type: 'object' as const,
344
268
  properties: {
@@ -378,7 +302,9 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
378
302
  },
379
303
  {
380
304
  name: 'channel_close',
381
- description: 'Close a channel to stop receiving messages',
305
+ description:
306
+ 'Close a channel. Messages that address you still reach you; what else stops reaching you ' +
307
+ 'depends on its server.',
382
308
  inputSchema: {
383
309
  type: 'object' as const,
384
310
  properties: {
@@ -457,8 +383,9 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
457
383
  type: 'number',
458
384
  description:
459
385
  'Optional self-wake: if nothing else wakes you first, you wake again after this ' +
460
- 'many seconds (1 = go again almost immediately; clamped to 1–3600). Any other ' +
461
- 'wake before then cancels it. Omit to stay idle until the next external wake.',
386
+ 'many seconds, from 1 to 3600; a larger number waits 3600 (1 = go again almost ' +
387
+ 'immediately). Any other wake before then cancels it. Omit to stay idle until the ' +
388
+ 'next external wake.',
462
389
  },
463
390
  },
464
391
  required: [],
@@ -470,7 +397,125 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
470
397
  // Constructor Options
471
398
  // ============================================================================
472
399
 
400
+ /** Bound connector values for notices, logs and an outcome's reason. A
401
+ * PublishOutcome keeps the connector's structured data (`detail`) whole. */
402
+ export function connectorText(value: unknown): string {
403
+ let text: string;
404
+ try {
405
+ text = typeof value === 'string' ? value : JSON.stringify(value) ?? String(value);
406
+ } catch {
407
+ return '[unserializable connector value]';
408
+ }
409
+ const limit = 2000;
410
+ const suffix = '… [truncated]';
411
+ return text.length > limit ? text.slice(0, limit - suffix.length) + suffix : text;
412
+ }
413
+
414
+ /** Where a publish was resolved to go — named by the registry, never by
415
+ * the caller's spelling. */
416
+ export interface PublishDestination {
417
+ serverId: string;
418
+ channelId: string;
419
+ /** The channel's registered label when the attempt was made. */
420
+ label?: string;
421
+ }
422
+
423
+ /**
424
+ * What one channels/publish attempt established (ChannelRegistry.publish).
425
+ * - `delivered`: the connector returned `delivered: true`. A historical
426
+ * receipt (confirmed at `at`, to `destination`, message `messageId` when
427
+ * the connector named one), not a claim the message still exists.
428
+ * - `failed`: nothing was posted: the request never left the host
429
+ * (unresolvable or shared channel id, missing grant, failed open, closed
430
+ * connection), or the connector answered `delivered: false` and named no
431
+ * message.
432
+ * - `unknown`: the request was dispatched and no valid receipt came back (an
433
+ * error response, a timeout, a lost connection, or a missing, malformed or
434
+ * contradictory receipt, such as `delivered: false` beside a message id).
435
+ * It may or may not have been posted.
436
+ */
437
+ export interface PublishOutcome {
438
+ status: 'delivered' | 'failed' | 'unknown';
439
+ /** Absent only when no destination could be resolved. */
440
+ destination?: PublishDestination;
441
+ messageId?: string;
442
+ /** Why it failed, or why its outcome is unknown — for a connector error,
443
+ * its message, bounded (which may say what was already posted). */
444
+ reason?: string;
445
+ /** The connector error's structured `data`, verbatim, when it sent one. */
446
+ detail?: unknown;
447
+ /** When the outcome was established (epoch ms). */
448
+ at: number;
449
+ }
450
+
451
+ /**
452
+ * What a channels/publish request that threw establishes. Only a request
453
+ * that never left the host proves nothing was posted. An error response may
454
+ * follow a partial post (a multi-part send whose later part failed), so it
455
+ * is unknown, with the connector's own words, bounded, and its data kept for
456
+ * the resident to judge. The publish executor and channel_publish both read
457
+ * a failed request this way.
458
+ */
459
+ function publishErrorOutcome(err: unknown, destination: PublishDestination, at: number): PublishOutcome {
460
+ const message = connectorText(err instanceof Error ? err.message : String(err));
461
+ const notSent = err instanceof McplRequestError && err.outcome === 'not-sent';
462
+ const detail = err instanceof McplRequestError ? err.data : undefined;
463
+ return {
464
+ status: notSent ? 'failed' : 'unknown',
465
+ destination,
466
+ reason: notSent ? message : `${message} (delivery uncertain)`,
467
+ ...(detail !== undefined ? { detail } : {}),
468
+ at,
469
+ };
470
+ }
471
+
472
+ /**
473
+ * What a channels/publish answer establishes. A final publish is a request,
474
+ * so success requires an explicit receipt; a missing or malformed one leaves
475
+ * delivery uncertain (anima-research/agent-framework#163's rule).
476
+ * `delivered: false` proves nothing went out only when the receipt names no
477
+ * message at all: a message id beside it, valid or malformed, contradicts it,
478
+ * since something may have been posted. The publish executor and
479
+ * channel_publish both read an answer this way.
480
+ */
481
+ function publishReceiptOutcome(result: unknown, destination: PublishDestination, at: number): PublishOutcome {
482
+ const receipt = (result ?? {}) as { delivered?: unknown; messageId?: unknown };
483
+ const messageId = typeof receipt.messageId === 'string' && receipt.messageId ? receipt.messageId : undefined;
484
+ if (receipt.delivered === true) {
485
+ return { status: 'delivered', destination, ...(messageId ? { messageId } : {}), at };
486
+ }
487
+ if (receipt.delivered === false) {
488
+ if (receipt.messageId === undefined || receipt.messageId === null) {
489
+ return { status: 'failed', destination, reason: `server "${destination.serverId}" reported delivered:false`, at };
490
+ }
491
+ return {
492
+ status: 'unknown',
493
+ destination,
494
+ ...(messageId ? { messageId } : {}),
495
+ reason: messageId
496
+ ? `server "${destination.serverId}" reported delivered:false but named posted message ${connectorText(messageId)} (delivery uncertain)`
497
+ : `server "${destination.serverId}" reported delivered:false beside a malformed message id ` +
498
+ `${connectorText(receipt.messageId)} (contradictory receipt; delivery uncertain)`,
499
+ at,
500
+ };
501
+ }
502
+ return {
503
+ status: 'unknown',
504
+ destination,
505
+ ...(messageId ? { messageId } : {}),
506
+ reason: `server "${destination.serverId}" returned no valid delivery receipt (delivery uncertain)`,
507
+ at,
508
+ };
509
+ }
510
+
473
511
  interface ChannelRegistryOptions {
512
+ /**
513
+ * An ordinary (uncoalesced) `channels/incoming` message has just been
514
+ * admitted, before it is queued or acknowledged: return its source
515
+ * envelope, frozen now (mcpl/inbound-source.ts). The framework observes the
516
+ * acceptance here; coalesced work is stamped and observed by its own path.
517
+ */
518
+ acceptInbound?: (event: McplChannelIncomingEvent) => InboundSource | undefined;
474
519
  /**
475
520
  * RFC-006: an admitted `channels/incoming` message carrying `coalesce`, with
476
521
  * the event the ordinary path would have queued. The handler decides
@@ -499,15 +544,21 @@ interface ChannelRegistryOptions {
499
544
  channelId: string | null;
500
545
  reason: string;
501
546
  textLen: number;
547
+ /** `failed`: nothing was posted; `unknown`: the request was dispatched
548
+ * and no valid receipt came back, so it may or may not have been. */
549
+ outcome?: 'failed' | 'unknown';
550
+ /** Bounded display of the connector's structured error data, when present. */
551
+ detail?: string;
502
552
  }) => void;
503
553
  /**
504
554
  * Called when channels were opened WITHOUT the agent asking (subscription
505
555
  * policy admitting a newly discovered channel, or delivery into a closed
506
556
  * locus). The host wires this to drop a durable notice into the agent's
507
- * window — the agent must always learn that new traffic will start
508
- * flowing, and that `channel_close` opts out (their decision outranks
509
- * policy). Fires ONCE per channel ever: the desired-state decision is
510
- * durable, so reboots do not re-announce. Must not trigger inference.
557
+ * window — the agent must always learn that the channel opened, and that
558
+ * `channel_close` opts out (their decision outranks policy). What an open
559
+ * channel delivers is the server's to say (#406). Fires ONCE per channel
560
+ * ever: the desired-state decision is durable, so reboots do not
561
+ * re-announce. Must not trigger inference.
511
562
  */
512
563
  onChannelAutoOpened?: (info: {
513
564
  /** Agent whose action caused the open (delivery); absent for policy opens. */
@@ -543,6 +594,8 @@ interface ChannelRegistryOptions {
543
594
  * `defaultPublishChannel`.
544
595
  */
545
596
  activeChannelResolver?: (agentName: string) => string | undefined;
597
+ /** Whether this framework has EventGate and can honor skip_reply self-wakes. */
598
+ eventGatePresent?: boolean;
546
599
  }
547
600
 
548
601
  // ============================================================================
@@ -576,12 +629,7 @@ export class ChannelRegistry {
576
629
  op?: 'start' | 'stop',
577
630
  ) => void;
578
631
  private shouldTriggerInference?: (content: string, metadata: Record<string, unknown>) => boolean;
579
- private onRouteFailure?: (info: {
580
- conversationId: string;
581
- channelId: string | null;
582
- reason: string;
583
- textLen: number;
584
- }) => void;
632
+ private onRouteFailure?: ChannelRegistryOptions['onRouteFailure'];
585
633
  private onChannelAutoOpened?: (info: {
586
634
  conversationId?: string;
587
635
  serverId: string;
@@ -680,6 +728,8 @@ export class ChannelRegistry {
680
728
  private legacyPolicies = new Map<string, 'auto' | 'manual' | string[]>();
681
729
  private migratedLegacyPolicies = new Set<string>();
682
730
  private handleCoalescedIncoming?: ChannelRegistryOptions['handleCoalescedIncoming'];
731
+ private acceptInbound?: ChannelRegistryOptions['acceptInbound'];
732
+ private channelTools: ToolDefinition[];
683
733
 
684
734
  constructor(
685
735
  serverRegistry: McplServerRegistry,
@@ -696,6 +746,24 @@ export class ChannelRegistry {
696
746
  },
697
747
  ) {
698
748
  this.handleCoalescedIncoming = options?.handleCoalescedIncoming;
749
+ this.acceptInbound = options?.acceptInbound;
750
+ this.channelTools = CHANNEL_TOOL_DEFINITIONS.map((tool) => {
751
+ if (tool.name !== 'skip_reply' || options?.eventGatePresent === true) return tool;
752
+ const schema = tool.inputSchema as {
753
+ type: 'object';
754
+ properties: NonNullable<ToolDefinition['inputSchema']['properties']>;
755
+ required?: string[];
756
+ };
757
+ const { wake_in_seconds: _wake, ...properties } = schema.properties;
758
+ return {
759
+ ...tool,
760
+ description: tool.description.replace(
761
+ ' To end this turn but come back on your own shortly, set wake_in_seconds.',
762
+ '',
763
+ ),
764
+ inputSchema: { ...schema, properties },
765
+ };
766
+ });
699
767
  this.serverRegistry = serverRegistry;
700
768
  this.featureSetManager = featureSetManager;
701
769
  this.pushEventFn = pushEventFn;
@@ -1031,8 +1099,8 @@ export class ChannelRegistry {
1031
1099
  }
1032
1100
  }
1033
1101
  if (message.tags) message.tags = expandCoreTags(message.tags);
1034
- const convertedContent: ContentBlock[] = message.content.map(convertBlock);
1035
- if (!emptyAllowed && isVisiblyEmptyContent(convertedContent)) {
1102
+ const convertedContent: ContentBlock[] = message.content.map((block) => convertBlock(block, 'attachment on channel message', serverId));
1103
+ if (!emptyAllowed && isVisiblyEmptyConversion(message.content, convertedContent)) {
1036
1104
  rejectEmpty();
1037
1105
  continue;
1038
1106
  }
@@ -1060,7 +1128,7 @@ export class ChannelRegistry {
1060
1128
  if (this.shouldTriggerInference) {
1061
1129
  // The text the agent's context gets, as the push lane's filter reads
1062
1130
  // it: the converted text blocks, a placeholder or reference stub
1063
- // among them, and a text block's non-string text as empty text.
1131
+ // among them, NON_STRING_TEXT_PLACEHOLDER included.
1064
1132
  const textContent = convertedContent
1065
1133
  .filter((b): b is ContentBlock & { type: 'text' } => b.type === 'text')
1066
1134
  .map((b) => b.text)
@@ -1093,6 +1161,7 @@ export class ChannelRegistry {
1093
1161
  metadata: message.metadata,
1094
1162
  ...(message.tags ? { tags: message.tags } : {}),
1095
1163
  triggerInference,
1164
+ acceptedAt: Date.now(),
1096
1165
  };
1097
1166
 
1098
1167
  if (coalesced) {
@@ -1118,6 +1187,12 @@ export class ChannelRegistry {
1118
1187
  continue;
1119
1188
  }
1120
1189
 
1190
+ // The source envelope is frozen here, at admission, before the message
1191
+ // is queued or acknowledged: a rename or rebind while it waits in the
1192
+ // queue cannot change where it says it came from.
1193
+ const inboundSource = this.acceptInbound?.(event);
1194
+ if (inboundSource) event.inboundSource = inboundSource;
1195
+
1121
1196
  // Push to the processing queue
1122
1197
  // Cast through unknown because McplChannelIncomingEvent matches the
1123
1198
  // CustomEvent `${string}:${string}` type pattern but lacks an index signature.
@@ -1171,20 +1246,34 @@ export class ChannelRegistry {
1171
1246
  (existing.descriptor.metadata as { lazyRegistered?: boolean } | undefined)?.lazyRegistered &&
1172
1247
  label && existing.descriptor.label === channelId
1173
1248
  ) {
1174
- existing.descriptor.label = label;
1175
- if (extraMetadata) {
1176
- existing.descriptor.metadata = { ...existing.descriptor.metadata, ...extraMetadata };
1177
- }
1249
+ const metadata = extraMetadata
1250
+ ? { ...existing.descriptor.metadata, ...extraMetadata }
1251
+ : existing.descriptor.metadata;
1178
1252
  // The channel now has a real label, not just the bare id — durable.
1179
- this.appendLabelSighting(
1180
- existing.descriptor.id,
1181
- existing.descriptor.label,
1182
- extractDmMeta(existing.descriptor.metadata),
1183
- );
1253
+ // Recorded before the live descriptor takes it, since this branch's
1254
+ // guard reads the descriptor: an append that throws leaves the
1255
+ // placeholder, so the next push tries again.
1256
+ this.appendLabelSighting(existing.descriptor.id, label, extractDmMeta(metadata));
1257
+ existing.descriptor.label = label;
1258
+ existing.descriptor.metadata = metadata;
1184
1259
  }
1185
1260
  return;
1186
1261
  }
1187
1262
 
1263
+ // Only persist a REAL label durably. Falling back to the bare
1264
+ // channelId as a placeholder (no server-supplied label at all — e.g. a
1265
+ // Discord DM push missing both channelName and an author name,
1266
+ // framework.ts's derivePushEventChannel) must never overwrite a real
1267
+ // label already on file from a previous boot: after a restart the live
1268
+ // `channels` map starts empty, so THIS is the very branch a
1269
+ // label-less post-restart event takes — recording the placeholder here
1270
+ // would clobber the good label initializeLabelHistoryStore() just
1271
+ // replayed, defeating the entire point of the durable log. The live
1272
+ // descriptor below still carries the placeholder for in-process
1273
+ // routing; only the durable write is skipped when there's no real
1274
+ // label to record. A real label is recorded before the entry exists:
1275
+ // an append that throws registers nothing, so the next push tries again.
1276
+ if (label) this.appendLabelSighting(channelId, label, extractDmMeta(extraMetadata));
1188
1277
  const key = `${serverId}:${channelId}`;
1189
1278
  this.channels.set(key, {
1190
1279
  serverId,
@@ -1197,19 +1286,6 @@ export class ChannelRegistry {
1197
1286
  },
1198
1287
  open: false,
1199
1288
  });
1200
- // Only persist a REAL label durably. Falling back to the bare
1201
- // channelId as a placeholder (no server-supplied label at all — e.g. a
1202
- // Discord DM push missing both channelName and an author name,
1203
- // framework.ts's derivePushEventChannel) must never overwrite a real
1204
- // label already on file from a previous boot: after a restart the live
1205
- // `channels` map starts empty, so THIS is the very branch a
1206
- // label-less post-restart event takes — recording the placeholder here
1207
- // would clobber the good label initializeLabelHistoryStore() just
1208
- // replayed, defeating the entire point of the durable log. The live
1209
- // descriptor above still carries the placeholder for in-process
1210
- // routing; only the durable write is skipped when there's no real
1211
- // label to record.
1212
- if (label) this.appendLabelSighting(channelId, label, extractDmMeta(extraMetadata));
1213
1289
  this.emitTraceFn({
1214
1290
  type: 'mcpl:channel-lazy-registered',
1215
1291
  serverId,
@@ -1344,6 +1420,13 @@ export class ChannelRegistry {
1344
1420
  return this.findChannelEntry(channelId)?.descriptor;
1345
1421
  }
1346
1422
 
1423
+ /** The registered label of `channelId` on exactly `serverId` (no
1424
+ * cross-server first match), or undefined when it has none. */
1425
+ getChannelLabel(serverId: string, channelId: string): string | undefined {
1426
+ const label = this.channels.get(`${serverId}:${channelId}`)?.descriptor.label;
1427
+ return typeof label === 'string' && label.length > 0 ? label : undefined;
1428
+ }
1429
+
1347
1430
  isChannelOpen(channelId: string): boolean {
1348
1431
  return this.findChannelEntry(channelId)?.open === true;
1349
1432
  }
@@ -1373,7 +1456,7 @@ export class ChannelRegistry {
1373
1456
  * Get synthesized tool definitions for channel operations.
1374
1457
  */
1375
1458
  getChannelTools(): ToolDefinition[] {
1376
- return CHANNEL_TOOL_DEFINITIONS;
1459
+ return this.channelTools;
1377
1460
  }
1378
1461
 
1379
1462
  /**
@@ -1434,7 +1517,7 @@ export class ChannelRegistry {
1434
1517
  return this.handleToolJournal(input as { content?: string });
1435
1518
 
1436
1519
  case 'skip_reply':
1437
- return this.handleToolSkipReply(input as { reason?: string; wake_in_seconds?: number });
1520
+ return this.handleToolSkipReply(input as { reason?: string; wake_in_seconds?: unknown });
1438
1521
 
1439
1522
  default:
1440
1523
  return { success: false, error: `Unknown channel tool: ${toolName}`, isError: true };
@@ -1549,13 +1632,7 @@ export class ChannelRegistry {
1549
1632
  private initializeLifecycleStore(): void {
1550
1633
  if (!this.store) return;
1551
1634
 
1552
- try {
1553
- this.store.registerState({ id: CHANNEL_LIFECYCLE_LOG_ID, strategy: 'append_log' });
1554
- } catch (error) {
1555
- if (!(error instanceof Error) || !error.message.includes('State already exists')) {
1556
- throw error;
1557
- }
1558
- }
1635
+ ensureStateRegistered(this.store, { id: CHANNEL_LIFECYCLE_LOG_ID, strategy: 'append_log' });
1559
1636
 
1560
1637
  const raw = this.store.getStateJson(CHANNEL_LIFECYCLE_LOG_ID);
1561
1638
  if (!Array.isArray(raw)) return;
@@ -1598,6 +1675,15 @@ export class ChannelRegistry {
1598
1675
  }
1599
1676
  }
1600
1677
 
1678
+ /**
1679
+ * Each writer appends its record here before it changes memory for it, so
1680
+ * an append that throws, as on a full disk, fails the operation with that
1681
+ * change unmade, and memory and the log still agree. A retry then records
1682
+ * the change rather than finding it in memory already, where
1683
+ * setDesiredState's same-state return would record nothing, and a restart
1684
+ * replays what memory held. appendLabelSighting keeps the same order for
1685
+ * the label history.
1686
+ */
1601
1687
  private appendLifecycleEvent(event: ChannelLifecycleEvent): void {
1602
1688
  this.store?.appendToStateJson(CHANNEL_LIFECYCLE_LOG_ID, event);
1603
1689
  }
@@ -1616,13 +1702,7 @@ export class ChannelRegistry {
1616
1702
  private initializeLabelHistoryStore(): void {
1617
1703
  if (!this.store) return;
1618
1704
 
1619
- try {
1620
- this.store.registerState({ id: CHANNEL_LABEL_HISTORY_LOG_ID, strategy: 'append_log' });
1621
- } catch (error) {
1622
- if (!(error instanceof Error) || !error.message.includes('State already exists')) {
1623
- throw error;
1624
- }
1625
- }
1705
+ ensureStateRegistered(this.store, { id: CHANNEL_LABEL_HISTORY_LOG_ID, strategy: 'append_log' });
1626
1706
 
1627
1707
  const raw = this.store.getStateJson(CHANNEL_LABEL_HISTORY_LOG_ID);
1628
1708
  if (!Array.isArray(raw)) return;
@@ -1703,6 +1783,16 @@ export class ChannelRegistry {
1703
1783
  const recipientNameUnchanged = recipientName === undefined || recipientName === this.dmRecipientNames.get(channelId);
1704
1784
  const isDmUnchanged = isDm === undefined || this.dmClassified.get(channelId) === true;
1705
1785
  if (labelUnchanged && recipientIdUnchanged && recipientNameUnchanged && isDmUnchanged) return;
1786
+ // The log first, as for lifecycle events: an append that throws leaves
1787
+ // the history in memory as it was, so the next sighting records it.
1788
+ this.store?.appendToStateJson(CHANNEL_LABEL_HISTORY_LOG_ID, {
1789
+ channelId,
1790
+ label,
1791
+ ts: Date.now(),
1792
+ ...(recipientId ? { recipientId } : {}),
1793
+ ...(recipientName ? { recipientName } : {}),
1794
+ ...(isDm ? { isDm } : {}),
1795
+ } satisfies ChannelLabelSightingEvent);
1706
1796
  this.labelHistory.set(channelId, label);
1707
1797
  let seen = this.allLabelsSeen.get(channelId);
1708
1798
  if (!seen) {
@@ -1713,14 +1803,6 @@ export class ChannelRegistry {
1713
1803
  if (recipientId) this.dmRecipientIds.set(channelId, recipientId);
1714
1804
  if (recipientName) this.dmRecipientNames.set(channelId, recipientName);
1715
1805
  if (isDm) this.dmClassified.set(channelId, true);
1716
- this.store?.appendToStateJson(CHANNEL_LABEL_HISTORY_LOG_ID, {
1717
- channelId,
1718
- label,
1719
- ts: Date.now(),
1720
- ...(recipientId ? { recipientId } : {}),
1721
- ...(recipientName ? { recipientName } : {}),
1722
- ...(isDm ? { isDm } : {}),
1723
- } satisfies ChannelLabelSightingEvent);
1724
1806
  }
1725
1807
 
1726
1808
  /**
@@ -1945,7 +2027,6 @@ export class ChannelRegistry {
1945
2027
  ): void {
1946
2028
  const key = this.lifecycleKey(serverId, channelId);
1947
2029
  if (this.desiredStates.get(key)?.state === desired) return;
1948
- this.desiredStates.set(key, { state: desired, source });
1949
2030
  this.appendLifecycleEvent({
1950
2031
  kind: 'desired-state',
1951
2032
  serverId,
@@ -1954,6 +2035,7 @@ export class ChannelRegistry {
1954
2035
  source,
1955
2036
  timestamp: new Date().toISOString(),
1956
2037
  });
2038
+ this.desiredStates.set(key, { state: desired, source });
1957
2039
  }
1958
2040
 
1959
2041
  // ==========================================================================
@@ -1973,12 +2055,6 @@ export class ChannelRegistry {
1973
2055
  source: string,
1974
2056
  ): void {
1975
2057
  const key = this.lifecycleKey(serverId, channelId);
1976
- this.desiredStates.set(key, {
1977
- state: 'tuned-out',
1978
- source,
1979
- tuneOut: params,
1980
- wakeCount: 0,
1981
- });
1982
2058
  this.appendLifecycleEvent({
1983
2059
  kind: 'desired-state',
1984
2060
  serverId,
@@ -1988,6 +2064,12 @@ export class ChannelRegistry {
1988
2064
  source,
1989
2065
  timestamp: new Date().toISOString(),
1990
2066
  });
2067
+ this.desiredStates.set(key, {
2068
+ state: 'tuned-out',
2069
+ source,
2070
+ tuneOut: params,
2071
+ wakeCount: 0,
2072
+ });
1991
2073
  }
1992
2074
 
1993
2075
  /**
@@ -2006,7 +2088,6 @@ export class ChannelRegistry {
2006
2088
  const current = this.desiredStates.get(key);
2007
2089
  if (current?.state !== 'tuned-out' || !current.tuneOut) return null;
2008
2090
  const ended = { params: current.tuneOut, wakeCount: current.wakeCount ?? 0 };
2009
- this.desiredStates.set(key, { state: nextState, source });
2010
2091
  this.appendLifecycleEvent({
2011
2092
  kind: 'desired-state',
2012
2093
  serverId,
@@ -2015,6 +2096,7 @@ export class ChannelRegistry {
2015
2096
  source,
2016
2097
  timestamp: new Date().toISOString(),
2017
2098
  });
2099
+ this.desiredStates.set(key, { state: nextState, source });
2018
2100
  return ended;
2019
2101
  }
2020
2102
 
@@ -2032,16 +2114,17 @@ export class ChannelRegistry {
2032
2114
  const key = this.lifecycleKey(serverId, channelId);
2033
2115
  const current = this.desiredStates.get(key);
2034
2116
  if (current?.state !== 'tuned-out' || !current.tuneOut) return null;
2035
- current.wakeCount = (current.wakeCount ?? 0) + 1;
2117
+ const wakeCount = (current.wakeCount ?? 0) + 1;
2036
2118
  this.appendLifecycleEvent({
2037
2119
  kind: 'tune-out-wake',
2038
2120
  serverId,
2039
2121
  channelId,
2040
2122
  epochId: current.tuneOut.epochId,
2041
- wakeCount: current.wakeCount,
2123
+ wakeCount,
2042
2124
  timestamp: new Date().toISOString(),
2043
2125
  });
2044
- return { params: current.tuneOut, wakeCount: current.wakeCount };
2126
+ current.wakeCount = wakeCount;
2127
+ return { params: current.tuneOut, wakeCount };
2045
2128
  }
2046
2129
 
2047
2130
  /** Active tune-out params + wake count for a channel, or null. */
@@ -2069,17 +2152,13 @@ export class ChannelRegistry {
2069
2152
  */
2070
2153
  readCoordinatorState(stateId: string): unknown {
2071
2154
  if (!this.store) return null;
2072
- try {
2073
- this.store.registerState({ id: stateId, strategy: 'snapshot' });
2074
- } catch { /* already registered */ }
2155
+ ensureStateRegistered(this.store, { id: stateId, strategy: 'snapshot' });
2075
2156
  return this.store.getStateJson(stateId);
2076
2157
  }
2077
2158
 
2078
2159
  writeCoordinatorState(stateId: string, value: unknown): void {
2079
2160
  if (!this.store) return;
2080
- try {
2081
- this.store.registerState({ id: stateId, strategy: 'snapshot' });
2082
- } catch { /* already registered */ }
2161
+ ensureStateRegistered(this.store, { id: stateId, strategy: 'snapshot' });
2083
2162
  this.store.setStateJson(stateId, value);
2084
2163
  }
2085
2164
 
@@ -2116,13 +2195,13 @@ export class ChannelRegistry {
2116
2195
  this.setDesiredState(serverId, channel.id, desired, 'legacy-recipe-migration');
2117
2196
  }
2118
2197
 
2119
- this.migratedLegacyPolicies.add(serverId);
2120
2198
  this.appendLifecycleEvent({
2121
2199
  kind: 'legacy-policy-migrated',
2122
2200
  serverId,
2123
2201
  source: Array.isArray(policy) ? 'allow-list' : policy,
2124
2202
  timestamp: new Date().toISOString(),
2125
2203
  });
2204
+ this.migratedLegacyPolicies.add(serverId);
2126
2205
  }
2127
2206
 
2128
2207
  /**
@@ -2251,9 +2330,9 @@ export class ChannelRegistry {
2251
2330
  }
2252
2331
  }
2253
2332
 
2254
- // Announce policy admissions to the agent — nothing may start flowing
2255
- // traffic into their window without them being told, and told how to
2256
- // opt out (channel_close; their decision outranks policy).
2333
+ // Announce policy admissions to the agent — no channel may open without
2334
+ // them being told, and told how to opt out (channel_close; their
2335
+ // decision outranks policy).
2257
2336
  if (policyOpened.length > 0) {
2258
2337
  try {
2259
2338
  this.onChannelAutoOpened?.({
@@ -2670,11 +2749,13 @@ export class ChannelRegistry {
2670
2749
  // A tuned-out channel is transport-open but attention-diverted; a plain
2671
2750
  // open would silently discard the epoch (and its pending backlog dump).
2672
2751
  // Cancelling is the tune-out coordinator's flow, reached via its own
2673
- // tool — refuse with the pointer rather than eat the state.
2752
+ // tool — refuse with the pointer rather than eat the state. As an error
2753
+ // result, the agent reads the pointer itself; `data` names the refusal
2754
+ // for an in-process caller.
2674
2755
  if (this.getDesiredState(entry.serverId, input.channelId) === 'tuned-out') {
2675
2756
  return {
2676
2757
  success: false,
2677
- isError: false,
2758
+ isError: true,
2678
2759
  error:
2679
2760
  `Channel ${input.channelId} is tuned out. Cancel the tune-out ` +
2680
2761
  `(tune_out with mode: 'cancel') to resume normal attention — ` +
@@ -2845,7 +2926,7 @@ export class ChannelRegistry {
2845
2926
  if (entry.open || this.getDesiredState(entry.serverId, input.channelId) === 'open') {
2846
2927
  return {
2847
2928
  success: false,
2848
- error: `Channel is already open: ${input.channelId}. Use channel_close to leave it.`,
2929
+ error: `Channel is already open: ${input.channelId}. Use channel_close to close it.`,
2849
2930
  isError: true,
2850
2931
  };
2851
2932
  }
@@ -2953,14 +3034,15 @@ export class ChannelRegistry {
2953
3034
  * tool, so any trailing prose this turn is NOT posted. Replaces the old
2954
3035
  * overloaded use of `think` for staying silent.
2955
3036
  */
2956
- private handleToolSkipReply(input: { reason?: string; wake_in_seconds?: number }): ToolResult {
3037
+ private handleToolSkipReply(input: { reason?: string; wake_in_seconds?: unknown }): ToolResult {
2957
3038
  // The self-wake itself is armed by the framework (which owns the
2958
- // EventGate) BEFORE this dispatch; when the gate is absent the framework
2959
- // strips the field so this confirmation stays truthful.
2960
- const wakeSecs = Number(input.wake_in_seconds);
2961
- const selfWake = Number.isFinite(wakeSecs) && wakeSecs > 0
2962
- ? Math.max(1, Math.min(3600, Math.floor(wakeSecs)))
2963
- : undefined;
3039
+ // EventGate) BEFORE its dispatch reaches this handler, from the same
3040
+ // reading, and the framework refuses a wake the host can't set before
3041
+ // dispatching. A caller of this registry's handleChannelToolCall
3042
+ // (AgentFramework.channels) goes past both, so its note names a wake
3043
+ // nothing armed.
3044
+ const wake = readSkipReplyWake(input);
3045
+ const selfWake = wake.kind === 'wake' ? wake.seconds : undefined;
2964
3046
  return {
2965
3047
  success: true,
2966
3048
  // The note says "ended the turn" — make it TRUE. Without endTurn the
@@ -3056,7 +3138,179 @@ export class ChannelRegistry {
3056
3138
  return home ?? this.activeChannelResolver?.(conversationId) ?? this.defaultPublishChannel ?? null;
3057
3139
  }
3058
3140
 
3059
- async routeSpeech(
3141
+ /**
3142
+ * Resolve a publish target to exactly one registered channel: `serverId`
3143
+ * plus `channelId` when the caller has the server, otherwise a channel id
3144
+ * registered by exactly one server. A shared id is refused, never resolved
3145
+ * to whichever server registered it first.
3146
+ */
3147
+ resolveDestination(
3148
+ target: { serverId?: string; channelId: string },
3149
+ ): { destination: PublishDestination } | { error: string } {
3150
+ const entry = this.findExactEntry(target);
3151
+ if ('error' in entry) return entry;
3152
+ return {
3153
+ destination: {
3154
+ serverId: entry.entry.serverId,
3155
+ channelId: entry.entry.descriptor.id,
3156
+ ...(entry.entry.descriptor.label ? { label: entry.entry.descriptor.label } : {}),
3157
+ },
3158
+ };
3159
+ }
3160
+
3161
+ private findExactEntry(
3162
+ target: { serverId?: string; channelId: string },
3163
+ ): { entry: ChannelEntry } | { error: string } {
3164
+ // A supplied selector is checked, never read as omission: an empty or
3165
+ // non-string serverId would otherwise widen to "any server".
3166
+ if (typeof target.channelId !== 'string' || !target.channelId) {
3167
+ return { error: 'the destination names no channel id' };
3168
+ }
3169
+ if (target.serverId !== undefined && (typeof target.serverId !== 'string' || !target.serverId)) {
3170
+ return { error: 'serverId must name a server (omit it to resolve the channel id alone)' };
3171
+ }
3172
+ const matches = [...this.channels.values()].filter(
3173
+ (e) => e.descriptor.id === target.channelId && (!target.serverId || e.serverId === target.serverId),
3174
+ );
3175
+ if (matches.length === 0) {
3176
+ return {
3177
+ error: target.serverId
3178
+ ? `no registered channel "${target.channelId}" on server "${target.serverId}"`
3179
+ : `no registered channel "${target.channelId}"`,
3180
+ };
3181
+ }
3182
+ if (matches.length > 1) {
3183
+ return {
3184
+ error: `channel id "${target.channelId}" is registered by more than one MCPL server; the destination must name its server`,
3185
+ };
3186
+ }
3187
+ return { entry: matches[0]! };
3188
+ }
3189
+
3190
+ /**
3191
+ * Publish `text` to one registered channel and report what the attempt
3192
+ * established (PublishOutcome). The delivery executor behind plain speech
3193
+ * (deliverSpeech / routeSpeech) and resident resends of held drafts.
3194
+ * channel_publish (and publishForAgent, which shares its handler) resolves
3195
+ * and sends on its own path, and reads its answer through the same
3196
+ * publishReceiptOutcome and publishErrorOutcome.
3197
+ *
3198
+ * Resolution is exact (resolveDestination): `serverId` plus `channelId`
3199
+ * when the caller has the server, otherwise the channel id must be
3200
+ * registered by exactly one server (a shared id is refused, never routed
3201
+ * through whichever server came first). An omitted `serverId` means "not
3202
+ * in use"; a supplied one must be a non-empty server id, and an empty or
3203
+ * non-string selector is refused rather than read as omission. It is not
3204
+ * possible to send into a closed channel: a closed but registered
3205
+ * destination is opened first (`openSource` names why), once its server
3206
+ * and that server's channels.publish grant are known to take the send, so
3207
+ * a refused send opens nothing; and a failed open sends nothing.
3208
+ *
3209
+ * Only the connector's `delivered: true` confirms a post. `failed` means
3210
+ * nothing was posted: the attempt stopped before the request was written,
3211
+ * or the connector answered `delivered: false` and named no message (no
3212
+ * `messageId`, or null). Everything else is `unknown`: once the request
3213
+ * has been handed to the transport, an error response, a timeout, a lost
3214
+ * connection, or a missing, malformed or contradictory receipt (such as
3215
+ * `delivered: false` beside a message id, valid or not) means the post may
3216
+ * or may not exist, and the caller must not treat it as safe to retry
3217
+ * blindly.
3218
+ */
3219
+ async publish(
3220
+ conversationId: string,
3221
+ text: string,
3222
+ target: {
3223
+ serverId?: string;
3224
+ channelId: string;
3225
+ /** Set by speech routing (deliverSpeech): opening a closed channel
3226
+ * for this publish is logged under `[routeSpeech]`, with speech
3227
+ * routing's other lines. */
3228
+ routed?: boolean;
3229
+ },
3230
+ openSource: 'opened-by-delivery' | 'opened-by-reply' = 'opened-by-delivery',
3231
+ ): Promise<PublishOutcome> {
3232
+ const at = (): number => Date.now();
3233
+ const found = this.findExactEntry(target);
3234
+ if ('error' in found) return { status: 'failed', reason: found.error, at: at() };
3235
+ const entry = found.entry;
3236
+ const destination: PublishDestination = {
3237
+ serverId: entry.serverId,
3238
+ channelId: entry.descriptor.id,
3239
+ ...(entry.descriptor.label ? { label: entry.descriptor.label } : {}),
3240
+ };
3241
+
3242
+ // A send that would be refused is refused before anything opens: a
3243
+ // closed channel opens only for words that can go out.
3244
+ const server = this.serverRegistry.getServer(entry.serverId);
3245
+ if (!server) {
3246
+ return { status: 'failed', destination, reason: `server "${entry.serverId}" not found`, at: at() };
3247
+ }
3248
+ // §14.1: channels/publish requires channels.publish in the grant. The
3249
+ // host not sending is the enforcement.
3250
+ if (!CapabilityGrant.of(server).has('channels.publish')) {
3251
+ return {
3252
+ status: 'failed',
3253
+ destination,
3254
+ reason: `channels.publish not in "${entry.serverId}"'s effective grant (§14.1)`,
3255
+ at: at(),
3256
+ };
3257
+ }
3258
+
3259
+ if (!entry.open) {
3260
+ try {
3261
+ await this.openChannelNow(entry, openSource);
3262
+ if (target.routed) {
3263
+ console.error(
3264
+ `[routeSpeech] ${conversationId}: locus ${logValue(entry.descriptor.id)} was closed — opened by delivery (speech implies engagement)`,
3265
+ );
3266
+ }
3267
+ this.emitTraceFn({
3268
+ type: 'mcpl:channel-opened-by-send',
3269
+ serverId: entry.serverId,
3270
+ channelId: entry.descriptor.id,
3271
+ });
3272
+ if (openSource === 'opened-by-delivery') {
3273
+ try {
3274
+ this.onChannelAutoOpened?.({
3275
+ conversationId,
3276
+ serverId: entry.serverId,
3277
+ source: 'opened-by-delivery',
3278
+ channels: [{ channelId: entry.descriptor.id, label: entry.descriptor.label }],
3279
+ });
3280
+ } catch (err) {
3281
+ console.error('onChannelAutoOpened (delivery) failed:', err);
3282
+ }
3283
+ }
3284
+ } catch (err) {
3285
+ return {
3286
+ status: 'failed',
3287
+ destination,
3288
+ reason: `channel is closed and open failed: ${connectorText(err instanceof Error ? err.message : String(err))}`,
3289
+ at: at(),
3290
+ };
3291
+ }
3292
+ }
3293
+
3294
+ let result: unknown;
3295
+ try {
3296
+ result = await server.sendChannelsPublish({
3297
+ conversationId,
3298
+ channelId: entry.descriptor.id,
3299
+ content: [{ type: 'text', text }],
3300
+ });
3301
+ } catch (err) {
3302
+ return publishErrorOutcome(err, destination, at());
3303
+ }
3304
+ return publishReceiptOutcome(result, destination, at());
3305
+ }
3306
+
3307
+ /**
3308
+ * Plain speech for one turn segment: publish `text` to the turn's frozen
3309
+ * locus and report the PublishOutcome. A failure (or an uncertain outcome)
3310
+ * is surfaced as a trace and through onRouteFailure, so the agent learns
3311
+ * its words did not (or may not have) reached anyone.
3312
+ */
3313
+ async deliverSpeech(
3060
3314
  conversationId: string,
3061
3315
  text: string,
3062
3316
  /** The turn's FROZEN locus, snapshotted once by the caller (resolveLocus
@@ -3067,118 +3321,82 @@ export class ChannelRegistry {
3067
3321
  * PR #32, and the 2026-07-22 Sol DM misroute). Explicit `null` means
3068
3322
  * "this turn is pinned to no locus": fail loudly rather than guess. */
3069
3323
  locusChannelId: string | null,
3070
- ): Promise<{ delivered: boolean; channelId: string; messageId?: string } | null> {
3071
- // Surface a routing failure: emit a trace AND notify the host (which drops
3072
- // a `[discord-send-failed]` marker into chronicle) so the agent learns her
3073
- // reply never reached the human, instead of it vanishing silently.
3074
- const fail = (channelId: string | null, reason: string): null => {
3075
- console.error(`[routeSpeech] ${conversationId}: ${reason} — speech NOT routed (${text.length} chars stay in chronicle)`);
3324
+ /** `heldOnFailure`: the caller holds a `failed` delivery's words as a
3325
+ * draft with its own notice, so no failure marker is written for it.
3326
+ * An `unknown` outcome still gets its marker. */
3327
+ opts?: { heldOnFailure?: boolean },
3328
+ ): Promise<PublishOutcome> {
3329
+ const fail = (channelId: string | null, reason: string, outcome: PublishOutcome): PublishOutcome => {
3330
+ const status = outcome.status === 'unknown' ? 'unknown' : 'failed';
3331
+ const detail = outcome.detail !== undefined ? connectorText(outcome.detail) : undefined;
3332
+ const detailText = detail !== undefined ? `; connector detail: ${detail}` : '';
3333
+ console.error(`[routeSpeech] ${conversationId}: ${reason}${detailText} — speech ${status === 'unknown' ? 'delivery NOT confirmed' : 'NOT routed'} (${text.length} chars stay in chronicle)`);
3076
3334
  this.emitTraceFn({
3077
3335
  type: 'mcpl:speech-route-failed',
3078
3336
  conversationId,
3079
3337
  channelId: channelId ?? '',
3080
3338
  reason,
3081
3339
  textLen: text.length,
3340
+ outcome: status,
3341
+ ...(detail !== undefined ? { detail } : {}),
3082
3342
  });
3083
- this.onRouteFailure?.({ conversationId, channelId, reason, textLen: text.length });
3084
- return null;
3343
+ if (status === 'unknown' || !opts?.heldOnFailure) {
3344
+ this.onRouteFailure?.({ conversationId, channelId, reason, textLen: text.length, outcome: status, ...(detail !== undefined ? { detail } : {}) });
3345
+ }
3346
+ return outcome;
3085
3347
  };
3086
3348
 
3087
3349
  // Runtime backstop for JS callers the compiler can't see: an omitted
3088
3350
  // locus is a routing bug at the call site, never something to paper over
3089
3351
  // with a live re-resolution.
3090
3352
  if (locusChannelId === undefined) {
3091
- return fail(null, 'caller passed no locus (routing bug: every speech path must snapshot the turn locus)');
3353
+ const reason = 'caller passed no locus (routing bug: every speech path must snapshot the turn locus)';
3354
+ return fail(null, reason, { status: 'failed', reason, at: Date.now() });
3092
3355
  }
3093
- const channelId = locusChannelId;
3094
- if (!channelId) {
3356
+ if (!locusChannelId) {
3095
3357
  // The turn froze with no locus (no home, no triggering channel, no
3096
3358
  // global inbound ever seen) — the agent was told its prose stays in
3097
3359
  // the archive; honor that.
3098
- return fail(null, 'turn has no locus (no home/trigger channel; nothing to deliver into)');
3099
- }
3100
-
3101
- const entry = this.findChannelEntry(channelId);
3102
- if (!entry) {
3103
- return fail(channelId, `no registered channel for locus "${channelId}"`);
3104
- }
3105
-
3106
- // INVARIANT: it is not possible to send into a closed channel. Speech
3107
- // routed into a closed-but-registered locus (the canonical case: a DM
3108
- // that woke the agent — DMs register closed) OPENS the channel first,
3109
- // so typing indicators, reaction machinery, and inbound forwarding come
3110
- // alive with the reply. If the open fails, the speech does NOT go out:
3111
- // a half-alive delivery is worse than a loud marker.
3112
- if (!entry.open) {
3113
- try {
3114
- await this.openChannelNow(entry, 'opened-by-delivery');
3115
- console.error(
3116
- `[routeSpeech] ${conversationId}: locus ${logValue(channelId)} was closed — opened by delivery (speech implies engagement)`,
3117
- );
3118
- this.emitTraceFn({
3119
- type: 'mcpl:channel-opened-by-send',
3120
- serverId: entry.serverId,
3121
- channelId,
3122
- });
3123
- try {
3124
- this.onChannelAutoOpened?.({
3125
- conversationId,
3126
- serverId: entry.serverId,
3127
- source: 'opened-by-delivery',
3128
- channels: [{ channelId, label: entry.descriptor.label }],
3129
- });
3130
- } catch (err) {
3131
- console.error('onChannelAutoOpened (delivery) failed:', err);
3132
- }
3133
- } catch (err) {
3134
- return fail(channelId, `locus channel is closed and open failed: ${(err as Error).message}`);
3135
- }
3360
+ const reason = 'turn has no locus (no home/trigger channel; nothing to deliver into)';
3361
+ return fail(null, reason, { status: 'failed', reason, at: Date.now() });
3136
3362
  }
3137
3363
 
3138
- const server = this.serverRegistry.getServer(entry.serverId);
3139
- if (!server) {
3140
- return fail(channelId, `server "${entry.serverId}" not found`);
3141
- }
3142
- // §14.1: channels/publish requires channels.publish in the grant. The
3143
- // host not sending is the enforcement — a send to an ungranted server
3144
- // would have it act on authority it was never told it has.
3145
- if (!CapabilityGrant.of(server).has('channels.publish')) {
3146
- return fail(channelId, `channels.publish not in "${entry.serverId}"'s effective grant (§14.1)`);
3364
+ const outcome = await this.publish(conversationId, text, { channelId: locusChannelId, routed: true });
3365
+ const channelId = outcome.destination?.channelId ?? locusChannelId;
3366
+ if (outcome.status !== 'delivered') {
3367
+ return fail(channelId, outcome.reason ?? 'delivery not confirmed', outcome);
3147
3368
  }
3148
3369
 
3149
- const publishParams: ChannelsPublishParams = {
3150
- conversationId,
3151
- channelId,
3152
- content: [{ type: 'text', text }],
3153
- };
3154
- const result = await server.sendChannelsPublish(publishParams);
3155
- const delivered = (result as { delivered?: boolean } | undefined)?.delivered ?? true;
3156
- // Surface the posted message's id (ChannelsPublishResult.messageId) so
3157
- // trace consumers can act on the just-posted message — e.g. a TTS-relay
3158
- // tap editing it down to the words actually voiced on interruption.
3159
- // Previously this was silently dropped here.
3160
- const messageId = (result as { messageId?: string } | undefined)?.messageId;
3161
-
3162
- // The server accepted the publish RPC but reported the message was not
3163
- // actually delivered (e.g. missing Send Messages permission). Previously
3164
- // this returned `{ delivered: true }`, masking the failure. Surface it.
3165
- if (delivered === false) {
3166
- return fail(channelId, `server "${entry.serverId}" reported delivered:false for "${channelId}"`);
3167
- }
3168
-
3169
- console.error(`[routeSpeech] ${conversationId}: routed ${text.length} chars -> ${logValue(channelId)} (server=${entry.serverId}, delivered=${delivered})`);
3370
+ console.error(`[routeSpeech] ${conversationId}: routed ${text.length} chars -> ${logValue(channelId)} (server=${outcome.destination!.serverId}, delivered=true)`);
3170
3371
  this.emitTraceFn({
3171
3372
  type: 'mcpl:speech-routed',
3172
3373
  conversationId,
3173
- serverId: entry.serverId,
3374
+ serverId: outcome.destination!.serverId,
3174
3375
  channelId,
3175
- delivered,
3376
+ delivered: true,
3176
3377
  textLen: text.length,
3177
3378
  text,
3178
- ...(messageId !== undefined ? { messageId } : {}),
3379
+ // Surface the posted message's id (ChannelsPublishResult.messageId) so
3380
+ // trace consumers can act on the just-posted message — e.g. a TTS-relay
3381
+ // tap editing it down to the words actually voiced on interruption.
3382
+ ...(outcome.messageId !== undefined ? { messageId: outcome.messageId } : {}),
3179
3383
  });
3384
+ return outcome;
3385
+ }
3180
3386
 
3181
- return { delivered, channelId, ...(messageId !== undefined ? { messageId } : {}) };
3387
+ /** deliverSpeech, reduced to the confirmed delivery (null when there was none). */
3388
+ async routeSpeech(
3389
+ conversationId: string,
3390
+ text: string,
3391
+ locusChannelId: string | null,
3392
+ ): Promise<{ delivered: boolean; channelId: string; messageId?: string } | null> {
3393
+ const outcome = await this.deliverSpeech(conversationId, text, locusChannelId);
3394
+ if (outcome.status !== 'delivered') return null;
3395
+ return {
3396
+ delivered: true,
3397
+ channelId: outcome.destination!.channelId,
3398
+ ...(outcome.messageId !== undefined ? { messageId: outcome.messageId } : {}),
3399
+ };
3182
3400
  }
3183
3401
 
3184
3402
  private async handleToolPublish(input: { channelId?: string; content?: string; text?: string }): Promise<ToolResult> {
@@ -3220,31 +3438,44 @@ export class ChannelRegistry {
3220
3438
  };
3221
3439
  }
3222
3440
 
3223
- try {
3224
- if (!CapabilityGrant.of(server).has('channels.publish')) {
3225
- return {
3226
- success: false,
3227
- error: `channels.publish not in "${server.id}"'s effective grant (§14.1)`,
3228
- isError: true,
3229
- };
3230
- }
3231
- const publishParams: ChannelsPublishParams = {
3232
- conversationId: '', // Framework will fill this when wired
3233
- channelId,
3234
- content: [{ type: 'text', text: messageText }],
3235
- };
3236
-
3237
- const result = await server.sendChannelsPublish(publishParams);
3238
- return {
3239
- success: true,
3240
- data: result ?? { delivered: true },
3241
- };
3242
- } catch (err) {
3441
+ if (!CapabilityGrant.of(server).has('channels.publish')) {
3243
3442
  return {
3244
3443
  success: false,
3245
- error: `Failed to publish to channel: ${(err as Error).message}`,
3444
+ error: `channels.publish not in "${server.id}"'s effective grant (§14.1)`,
3246
3445
  isError: true,
3247
3446
  };
3248
3447
  }
3448
+ const publishParams: ChannelsPublishParams = {
3449
+ conversationId: '', // Framework will fill this when wired
3450
+ channelId,
3451
+ content: [{ type: 'text', text: messageText }],
3452
+ };
3453
+ const destination: PublishDestination = {
3454
+ serverId: entry.serverId,
3455
+ channelId: entry.descriptor.id,
3456
+ ...(entry.descriptor.label ? { label: entry.descriptor.label } : {}),
3457
+ };
3458
+
3459
+ // The answer counts by the same rule as plain speech's: only the
3460
+ // connector's delivered:true confirms a post.
3461
+ let result: unknown;
3462
+ let outcome: PublishOutcome;
3463
+ try {
3464
+ result = await server.sendChannelsPublish(publishParams);
3465
+ outcome = publishReceiptOutcome(result, destination, Date.now());
3466
+ } catch (err) {
3467
+ outcome = publishErrorOutcome(err, destination, Date.now());
3468
+ }
3469
+ if (outcome.status === 'delivered') return { success: true, data: result };
3470
+ // The connector's data may say what a multi-part send already posted.
3471
+ const detail = outcome.status === 'unknown' && outcome.detail !== undefined ? ` Connector detail: ${connectorText(outcome.detail)}` : '';
3472
+ return {
3473
+ success: false,
3474
+ error: (outcome.status === 'failed'
3475
+ ? `Publish to ${channelId} failed: ${outcome.reason}. Nothing was posted.`
3476
+ : `Publish to ${channelId} wasn't confirmed: ${outcome.reason}. ` +
3477
+ 'It may or may not have been posted, so sending it again could post it twice.') + detail,
3478
+ isError: true,
3479
+ };
3249
3480
  }
3250
3481
  }