@animalabs/agent-framework 0.22.1 → 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 (946) hide show
  1. package/README.md +4 -2
  2. package/dist/src/agent.d.ts +266 -28
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +539 -104
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/mcp-server-core.d.ts +41 -0
  7. package/dist/src/api/mcp-server-core.d.ts.map +1 -0
  8. package/dist/src/api/mcp-server-core.js +593 -0
  9. package/dist/src/api/mcp-server-core.js.map +1 -0
  10. package/dist/src/api/mcp-server.d.ts +2 -36
  11. package/dist/src/api/mcp-server.d.ts.map +1 -1
  12. package/dist/src/api/mcp-server.js +15 -595
  13. package/dist/src/api/mcp-server.js.map +1 -1
  14. package/dist/src/api/server.d.ts +9 -1
  15. package/dist/src/api/server.d.ts.map +1 -1
  16. package/dist/src/api/server.js +55 -27
  17. package/dist/src/api/server.js.map +1 -1
  18. package/dist/src/api/types.d.ts +10 -2
  19. package/dist/src/api/types.d.ts.map +1 -1
  20. package/dist/src/branch-names.d.ts +14 -0
  21. package/dist/src/branch-names.d.ts.map +1 -0
  22. package/dist/src/branch-names.js +22 -0
  23. package/dist/src/branch-names.js.map +1 -0
  24. package/dist/src/closest-names.d.ts +28 -0
  25. package/dist/src/closest-names.d.ts.map +1 -0
  26. package/dist/src/closest-names.js +98 -0
  27. package/dist/src/closest-names.js.map +1 -0
  28. package/dist/src/code-execution/py-runner.d.ts +16 -5
  29. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  30. package/dist/src/code-execution/py-runner.js +186 -74
  31. package/dist/src/code-execution/py-runner.js.map +1 -1
  32. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  33. package/dist/src/code-execution/runtime-py.js +49 -3
  34. package/dist/src/code-execution/runtime-py.js.map +1 -1
  35. package/dist/src/code-execution/tool-definition.d.ts +12 -1
  36. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  37. package/dist/src/code-execution/tool-definition.js +21 -3
  38. package/dist/src/code-execution/tool-definition.js.map +1 -1
  39. package/dist/src/config-checks.d.ts +28 -0
  40. package/dist/src/config-checks.d.ts.map +1 -0
  41. package/dist/src/config-checks.js +52 -0
  42. package/dist/src/config-checks.js.map +1 -0
  43. package/dist/src/deadline-timer.d.ts +21 -0
  44. package/dist/src/deadline-timer.d.ts.map +1 -0
  45. package/dist/src/deadline-timer.js +43 -0
  46. package/dist/src/deadline-timer.js.map +1 -0
  47. package/dist/src/error-message.d.ts +30 -0
  48. package/dist/src/error-message.d.ts.map +1 -0
  49. package/dist/src/error-message.js +50 -0
  50. package/dist/src/error-message.js.map +1 -0
  51. package/dist/src/framework.d.ts +1212 -192
  52. package/dist/src/framework.d.ts.map +1 -1
  53. package/dist/src/framework.js +6686 -1835
  54. package/dist/src/framework.js.map +1 -1
  55. package/dist/src/gate/event-gate.d.ts +8 -0
  56. package/dist/src/gate/event-gate.d.ts.map +1 -1
  57. package/dist/src/gate/event-gate.js +27 -3
  58. package/dist/src/gate/event-gate.js.map +1 -1
  59. package/dist/src/gate/gate-script.d.ts.map +1 -1
  60. package/dist/src/gate/gate-script.js.map +1 -1
  61. package/dist/src/index.d.ts +5 -3
  62. package/dist/src/index.d.ts.map +1 -1
  63. package/dist/src/index.js +5 -3
  64. package/dist/src/index.js.map +1 -1
  65. package/dist/src/mcpl/capability-grant.d.ts.map +1 -1
  66. package/dist/src/mcpl/channel-registry.d.ts +170 -13
  67. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  68. package/dist/src/mcpl/channel-registry.js +499 -258
  69. package/dist/src/mcpl/channel-registry.js.map +1 -1
  70. package/dist/src/mcpl/checkpoint-manager.d.ts +21 -1
  71. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  72. package/dist/src/mcpl/checkpoint-manager.js +91 -30
  73. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  74. package/dist/src/mcpl/conversation-router.d.ts +19 -3
  75. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  76. package/dist/src/mcpl/conversation-router.js +23 -8
  77. package/dist/src/mcpl/conversation-router.js.map +1 -1
  78. package/dist/src/mcpl/convert-block.d.ts +20 -0
  79. package/dist/src/mcpl/convert-block.d.ts.map +1 -0
  80. package/dist/src/mcpl/convert-block.js +89 -0
  81. package/dist/src/mcpl/convert-block.js.map +1 -0
  82. package/dist/src/mcpl/errors.d.ts +13 -0
  83. package/dist/src/mcpl/errors.d.ts.map +1 -1
  84. package/dist/src/mcpl/errors.js +28 -0
  85. package/dist/src/mcpl/errors.js.map +1 -1
  86. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  87. package/dist/src/mcpl/feature-set-manager.js +4 -3
  88. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  89. package/dist/src/mcpl/hook-orchestrator.d.ts +23 -0
  90. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  91. package/dist/src/mcpl/hook-orchestrator.js +83 -72
  92. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  93. package/dist/src/mcpl/inbound-source.d.ts +114 -0
  94. package/dist/src/mcpl/inbound-source.d.ts.map +1 -0
  95. package/dist/src/mcpl/inbound-source.js +83 -0
  96. package/dist/src/mcpl/inbound-source.js.map +1 -0
  97. package/dist/src/mcpl/index.d.ts +3 -1
  98. package/dist/src/mcpl/index.d.ts.map +1 -1
  99. package/dist/src/mcpl/index.js +3 -1
  100. package/dist/src/mcpl/index.js.map +1 -1
  101. package/dist/src/mcpl/inference-router.d.ts +15 -11
  102. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  103. package/dist/src/mcpl/inference-router.js +153 -84
  104. package/dist/src/mcpl/inference-router.js.map +1 -1
  105. package/dist/src/mcpl/log-value.d.ts +54 -0
  106. package/dist/src/mcpl/log-value.d.ts.map +1 -0
  107. package/dist/src/mcpl/log-value.js +109 -0
  108. package/dist/src/mcpl/log-value.js.map +1 -0
  109. package/dist/src/mcpl/prose-stream-router.d.ts +6 -6
  110. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  111. package/dist/src/mcpl/prose-stream-router.js +4 -4
  112. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  113. package/dist/src/mcpl/push-coalescer.d.ts +64 -7
  114. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  115. package/dist/src/mcpl/push-coalescer.js +121 -24
  116. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  117. package/dist/src/mcpl/push-handler.d.ts +38 -8
  118. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  119. package/dist/src/mcpl/push-handler.js +77 -78
  120. package/dist/src/mcpl/push-handler.js.map +1 -1
  121. package/dist/src/mcpl/reference-fetcher.d.ts +14 -2
  122. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  123. package/dist/src/mcpl/reference-fetcher.js +72 -30
  124. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  125. package/dist/src/mcpl/references.d.ts +49 -14
  126. package/dist/src/mcpl/references.d.ts.map +1 -1
  127. package/dist/src/mcpl/references.js +74 -21
  128. package/dist/src/mcpl/references.js.map +1 -1
  129. package/dist/src/mcpl/refusal-lines.d.ts +37 -0
  130. package/dist/src/mcpl/refusal-lines.d.ts.map +1 -0
  131. package/dist/src/mcpl/refusal-lines.js +61 -0
  132. package/dist/src/mcpl/refusal-lines.js.map +1 -0
  133. package/dist/src/mcpl/scope-manager.d.ts.map +1 -1
  134. package/dist/src/mcpl/server-connection.d.ts +107 -12
  135. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  136. package/dist/src/mcpl/server-connection.js +470 -139
  137. package/dist/src/mcpl/server-connection.js.map +1 -1
  138. package/dist/src/mcpl/server-registry.d.ts +10 -1
  139. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  140. package/dist/src/mcpl/server-registry.js +14 -3
  141. package/dist/src/mcpl/server-registry.js.map +1 -1
  142. package/dist/src/mcpl/skip-reply-wake.d.ts +36 -0
  143. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -0
  144. package/dist/src/mcpl/skip-reply-wake.js +55 -0
  145. package/dist/src/mcpl/skip-reply-wake.js.map +1 -0
  146. package/dist/src/mcpl/tool-classes.d.ts +1 -1
  147. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  148. package/dist/src/mcpl/tool-classes.js +3 -2
  149. package/dist/src/mcpl/tool-classes.js.map +1 -1
  150. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  151. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  152. package/dist/src/mcpl/transport.d.ts +74 -7
  153. package/dist/src/mcpl/transport.d.ts.map +1 -1
  154. package/dist/src/mcpl/transport.js +209 -22
  155. package/dist/src/mcpl/transport.js.map +1 -1
  156. package/dist/src/mcpl/types.d.ts +43 -28
  157. package/dist/src/mcpl/types.d.ts.map +1 -1
  158. package/dist/src/mcpl/types.js.map +1 -1
  159. package/dist/src/mcpl/visible-content.d.ts +15 -0
  160. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  161. package/dist/src/mcpl/visible-content.js +25 -0
  162. package/dist/src/mcpl/visible-content.js.map +1 -1
  163. package/dist/src/module-registry.d.ts +19 -1
  164. package/dist/src/module-registry.d.ts.map +1 -1
  165. package/dist/src/module-registry.js +54 -25
  166. package/dist/src/module-registry.js.map +1 -1
  167. package/dist/src/modules/api/index.d.ts.map +1 -1
  168. package/dist/src/modules/api/index.js +1 -1
  169. package/dist/src/modules/api/index.js.map +1 -1
  170. package/dist/src/modules/discord/discord-js-client.d.ts +1 -0
  171. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  172. package/dist/src/modules/discord/discord-js-client.js +25 -9
  173. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  174. package/dist/src/modules/discord/index.d.ts +29 -3
  175. package/dist/src/modules/discord/index.d.ts.map +1 -1
  176. package/dist/src/modules/discord/index.js +383 -145
  177. package/dist/src/modules/discord/index.js.map +1 -1
  178. package/dist/src/modules/discord/types.d.ts +14 -1
  179. package/dist/src/modules/discord/types.d.ts.map +1 -1
  180. package/dist/src/modules/health/index.d.ts.map +1 -1
  181. package/dist/src/modules/health/index.js +2 -1
  182. package/dist/src/modules/health/index.js.map +1 -1
  183. package/dist/src/modules/history/index.d.ts +3 -1
  184. package/dist/src/modules/history/index.d.ts.map +1 -1
  185. package/dist/src/modules/history/index.js +359 -127
  186. package/dist/src/modules/history/index.js.map +1 -1
  187. package/dist/src/modules/history/semantic.d.ts +28 -1
  188. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  189. package/dist/src/modules/history/semantic.js +177 -32
  190. package/dist/src/modules/history/semantic.js.map +1 -1
  191. package/dist/src/modules/unknown-tool.d.ts +10 -0
  192. package/dist/src/modules/unknown-tool.d.ts.map +1 -0
  193. package/dist/src/modules/unknown-tool.js +17 -0
  194. package/dist/src/modules/unknown-tool.js.map +1 -0
  195. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  196. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  197. package/dist/src/modules/workspace/disk-agreement.d.ts +54 -6
  198. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  199. package/dist/src/modules/workspace/disk-agreement.js +93 -27
  200. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  201. package/dist/src/modules/workspace/effects.d.ts +64 -6
  202. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  203. package/dist/src/modules/workspace/effects.js +291 -44
  204. package/dist/src/modules/workspace/effects.js.map +1 -1
  205. package/dist/src/modules/workspace/index.d.ts +127 -27
  206. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  207. package/dist/src/modules/workspace/index.js +821 -248
  208. package/dist/src/modules/workspace/index.js.map +1 -1
  209. package/dist/src/modules/workspace/observe.d.ts +117 -15
  210. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  211. package/dist/src/modules/workspace/observe.js +436 -51
  212. package/dist/src/modules/workspace/observe.js.map +1 -1
  213. package/dist/src/modules/workspace/reconcile.d.ts +75 -8
  214. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  215. package/dist/src/modules/workspace/reconcile.js +576 -68
  216. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  217. package/dist/src/modules/workspace/types.d.ts +42 -3
  218. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  219. package/dist/src/modules/workspace/types.js.map +1 -1
  220. package/dist/src/modules/workspace/watcher.d.ts +7 -2
  221. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  222. package/dist/src/modules/workspace/watcher.js +28 -4
  223. package/dist/src/modules/workspace/watcher.js.map +1 -1
  224. package/dist/src/operator-log.d.ts.map +1 -1
  225. package/dist/src/operator-log.js.map +1 -1
  226. package/dist/src/prose-drafts.d.ts +240 -0
  227. package/dist/src/prose-drafts.d.ts.map +1 -0
  228. package/dist/src/prose-drafts.js +362 -0
  229. package/dist/src/prose-drafts.js.map +1 -0
  230. package/dist/src/prose-segments.d.ts +21 -2
  231. package/dist/src/prose-segments.d.ts.map +1 -1
  232. package/dist/src/prose-segments.js +46 -9
  233. package/dist/src/prose-segments.js.map +1 -1
  234. package/dist/src/provider-waits.d.ts.map +1 -1
  235. package/dist/src/provider-waits.js.map +1 -1
  236. package/dist/src/queue.d.ts.map +1 -1
  237. package/dist/src/record-journal.d.ts.map +1 -1
  238. package/dist/src/record-journal.js.map +1 -1
  239. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  240. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  241. package/dist/src/recovery/offline-branch.d.ts +1 -0
  242. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  243. package/dist/src/recovery/offline-branch.js +32 -25
  244. package/dist/src/recovery/offline-branch.js.map +1 -1
  245. package/dist/src/recovery/recover-cli.js.map +1 -1
  246. package/dist/src/runtime/liveness-watchdog.d.ts.map +1 -1
  247. package/dist/src/runtime/liveness-watchdog.js.map +1 -1
  248. package/dist/src/safe-slice.d.ts +5 -1
  249. package/dist/src/safe-slice.d.ts.map +1 -1
  250. package/dist/src/safe-slice.js +9 -1
  251. package/dist/src/safe-slice.js.map +1 -1
  252. package/dist/src/shutdown.d.ts.map +1 -1
  253. package/dist/src/shutdown.js.map +1 -1
  254. package/dist/src/stored-removal.d.ts +73 -0
  255. package/dist/src/stored-removal.d.ts.map +1 -0
  256. package/dist/src/stored-removal.js +100 -0
  257. package/dist/src/stored-removal.js.map +1 -0
  258. package/dist/src/thinking-token-stamp.js.map +1 -1
  259. package/dist/src/tool-image-ledger.d.ts +87 -19
  260. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  261. package/dist/src/tool-image-ledger.js +135 -32
  262. package/dist/src/tool-image-ledger.js.map +1 -1
  263. package/dist/src/tool-presentation.d.ts +9 -0
  264. package/dist/src/tool-presentation.d.ts.map +1 -1
  265. package/dist/src/tool-presentation.js +257 -60
  266. package/dist/src/tool-presentation.js.map +1 -1
  267. package/dist/src/tool-result-guard.d.ts.map +1 -1
  268. package/dist/src/tool-result-guard.js +7 -10
  269. package/dist/src/tool-result-guard.js.map +1 -1
  270. package/dist/src/tool-result-history.d.ts +32 -3
  271. package/dist/src/tool-result-history.d.ts.map +1 -1
  272. package/dist/src/tool-result-history.js +106 -19
  273. package/dist/src/tool-result-history.js.map +1 -1
  274. package/dist/src/tune-out/coordinator.d.ts +25 -1
  275. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  276. package/dist/src/tune-out/coordinator.js +260 -65
  277. package/dist/src/tune-out/coordinator.js.map +1 -1
  278. package/dist/src/tune-out/tools.d.ts +1 -1
  279. package/dist/src/tune-out/tools.d.ts.map +1 -1
  280. package/dist/src/types/agent.d.ts +59 -11
  281. package/dist/src/types/agent.d.ts.map +1 -1
  282. package/dist/src/types/events.d.ts +17 -0
  283. package/dist/src/types/events.d.ts.map +1 -1
  284. package/dist/src/types/framework.d.ts +31 -5
  285. package/dist/src/types/framework.d.ts.map +1 -1
  286. package/dist/src/types/inference-log.d.ts +1 -1
  287. package/dist/src/types/inference-log.d.ts.map +1 -1
  288. package/dist/src/types/module.d.ts +30 -7
  289. package/dist/src/types/module.d.ts.map +1 -1
  290. package/dist/src/types/trace.d.ts +151 -11
  291. package/dist/src/types/trace.d.ts.map +1 -1
  292. package/dist/src/usage/usage-tracker.d.ts +5 -0
  293. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  294. package/dist/src/usage/usage-tracker.js +35 -8
  295. package/dist/src/usage/usage-tracker.js.map +1 -1
  296. package/package.json +10 -9
  297. package/src/agent.ts +572 -105
  298. package/src/api/mcp-server-core.ts +681 -0
  299. package/src/api/mcp-server.ts +16 -685
  300. package/src/api/server.ts +55 -30
  301. package/src/api/types.ts +10 -2
  302. package/src/branch-names.ts +19 -0
  303. package/src/closest-names.ts +94 -0
  304. package/src/code-execution/py-runner.ts +200 -76
  305. package/src/code-execution/runtime-py.ts +49 -3
  306. package/src/code-execution/tool-definition.ts +28 -4
  307. package/src/config-checks.ts +48 -0
  308. package/src/deadline-timer.ts +44 -0
  309. package/src/error-message.ts +48 -0
  310. package/src/framework.ts +7158 -1936
  311. package/src/gate/event-gate.ts +28 -3
  312. package/src/index.ts +12 -3
  313. package/src/mcpl/channel-registry.ts +615 -289
  314. package/src/mcpl/checkpoint-manager.ts +104 -29
  315. package/src/mcpl/conversation-router.ts +28 -8
  316. package/src/mcpl/convert-block.ts +97 -0
  317. package/src/mcpl/errors.ts +28 -0
  318. package/src/mcpl/feature-set-manager.ts +4 -3
  319. package/src/mcpl/hook-orchestrator.ts +84 -77
  320. package/src/mcpl/inbound-source.ts +156 -0
  321. package/src/mcpl/index.ts +10 -1
  322. package/src/mcpl/inference-router.ts +174 -99
  323. package/src/mcpl/log-value.ts +116 -0
  324. package/src/mcpl/prose-stream-router.ts +6 -6
  325. package/src/mcpl/push-coalescer.ts +124 -24
  326. package/src/mcpl/push-handler.ts +86 -84
  327. package/src/mcpl/reference-fetcher.ts +71 -26
  328. package/src/mcpl/references.ts +81 -22
  329. package/src/mcpl/refusal-lines.ts +59 -0
  330. package/src/mcpl/server-connection.ts +496 -142
  331. package/src/mcpl/server-registry.ts +15 -4
  332. package/src/mcpl/skip-reply-wake.ts +73 -0
  333. package/src/mcpl/tool-classes.ts +3 -2
  334. package/src/mcpl/transport.ts +219 -26
  335. package/src/mcpl/types.ts +18 -3
  336. package/src/mcpl/visible-content.ts +27 -0
  337. package/src/module-registry.ts +58 -23
  338. package/src/modules/api/index.ts +1 -1
  339. package/src/modules/discord/discord-js-client.ts +26 -10
  340. package/src/modules/discord/index.ts +395 -160
  341. package/src/modules/discord/types.ts +15 -2
  342. package/src/modules/health/index.ts +2 -1
  343. package/src/modules/history/index.ts +410 -120
  344. package/src/modules/history/semantic.ts +189 -32
  345. package/src/modules/unknown-tool.ts +16 -0
  346. package/src/modules/workspace/disk-agreement.ts +125 -21
  347. package/src/modules/workspace/effects.ts +279 -35
  348. package/src/modules/workspace/index.ts +841 -261
  349. package/src/modules/workspace/observe.ts +456 -56
  350. package/src/modules/workspace/reconcile.ts +688 -76
  351. package/src/modules/workspace/types.ts +41 -2
  352. package/src/modules/workspace/watcher.ts +27 -4
  353. package/src/prose-drafts.ts +481 -0
  354. package/src/prose-segments.ts +51 -8
  355. package/src/recovery/offline-branch.ts +36 -29
  356. package/src/safe-slice.ts +9 -1
  357. package/src/stored-removal.ts +126 -0
  358. package/src/tool-image-ledger.ts +153 -34
  359. package/src/tool-presentation.ts +224 -40
  360. package/src/tool-result-guard.ts +7 -8
  361. package/src/tool-result-history.ts +124 -20
  362. package/src/tune-out/coordinator.ts +259 -65
  363. package/src/types/agent.ts +59 -13
  364. package/src/types/events.ts +17 -0
  365. package/src/types/framework.ts +31 -5
  366. package/src/types/inference-log.ts +1 -1
  367. package/src/types/module.ts +31 -7
  368. package/src/types/trace.ts +158 -12
  369. package/src/usage/usage-tracker.ts +32 -8
  370. package/dist/test/activation-request-sanitize.test.d.ts +0 -2
  371. package/dist/test/activation-request-sanitize.test.d.ts.map +0 -1
  372. package/dist/test/activation-request-sanitize.test.js +0 -89
  373. package/dist/test/activation-request-sanitize.test.js.map +0 -1
  374. package/dist/test/agent-cache-ttl.test.d.ts +0 -2
  375. package/dist/test/agent-cache-ttl.test.d.ts.map +0 -1
  376. package/dist/test/agent-cache-ttl.test.js +0 -41
  377. package/dist/test/agent-cache-ttl.test.js.map +0 -1
  378. package/dist/test/agent-runtime-settings.test.d.ts +0 -2
  379. package/dist/test/agent-runtime-settings.test.d.ts.map +0 -1
  380. package/dist/test/agent-runtime-settings.test.js +0 -462
  381. package/dist/test/agent-runtime-settings.test.js.map +0 -1
  382. package/dist/test/api-admin-gate.test.d.ts +0 -2
  383. package/dist/test/api-admin-gate.test.d.ts.map +0 -1
  384. package/dist/test/api-admin-gate.test.js +0 -116
  385. package/dist/test/api-admin-gate.test.js.map +0 -1
  386. package/dist/test/assistant-persist-refusal.test.d.ts +0 -2
  387. package/dist/test/assistant-persist-refusal.test.d.ts.map +0 -1
  388. package/dist/test/assistant-persist-refusal.test.js +0 -63
  389. package/dist/test/assistant-persist-refusal.test.js.map +0 -1
  390. package/dist/test/batched-wake-locus.test.d.ts +0 -2
  391. package/dist/test/batched-wake-locus.test.d.ts.map +0 -1
  392. package/dist/test/batched-wake-locus.test.js +0 -166
  393. package/dist/test/batched-wake-locus.test.js.map +0 -1
  394. package/dist/test/busy-agent-scheduler.test.d.ts +0 -2
  395. package/dist/test/busy-agent-scheduler.test.d.ts.map +0 -1
  396. package/dist/test/busy-agent-scheduler.test.js +0 -92
  397. package/dist/test/busy-agent-scheduler.test.js.map +0 -1
  398. package/dist/test/channel-incoming-targeting.test.d.ts +0 -2
  399. package/dist/test/channel-incoming-targeting.test.d.ts.map +0 -1
  400. package/dist/test/channel-incoming-targeting.test.js +0 -81
  401. package/dist/test/channel-incoming-targeting.test.js.map +0 -1
  402. package/dist/test/channel-label-history.test.d.ts +0 -2
  403. package/dist/test/channel-label-history.test.d.ts.map +0 -1
  404. package/dist/test/channel-label-history.test.js +0 -424
  405. package/dist/test/channel-label-history.test.js.map +0 -1
  406. package/dist/test/channel-lifecycle-provenance.test.d.ts +0 -2
  407. package/dist/test/channel-lifecycle-provenance.test.d.ts.map +0 -1
  408. package/dist/test/channel-lifecycle-provenance.test.js +0 -47
  409. package/dist/test/channel-lifecycle-provenance.test.js.map +0 -1
  410. package/dist/test/channel-registry-routing.test.d.ts +0 -2
  411. package/dist/test/channel-registry-routing.test.d.ts.map +0 -1
  412. package/dist/test/channel-registry-routing.test.js +0 -446
  413. package/dist/test/channel-registry-routing.test.js.map +0 -1
  414. package/dist/test/channel-registry-typing.test.d.ts +0 -2
  415. package/dist/test/channel-registry-typing.test.d.ts.map +0 -1
  416. package/dist/test/channel-registry-typing.test.js +0 -91
  417. package/dist/test/channel-registry-typing.test.js.map +0 -1
  418. package/dist/test/channels-changed-respond-first.test.d.ts +0 -2
  419. package/dist/test/channels-changed-respond-first.test.d.ts.map +0 -1
  420. package/dist/test/channels-changed-respond-first.test.js +0 -74
  421. package/dist/test/channels-changed-respond-first.test.js.map +0 -1
  422. package/dist/test/checkpoint-attribution.test.d.ts +0 -2
  423. package/dist/test/checkpoint-attribution.test.d.ts.map +0 -1
  424. package/dist/test/checkpoint-attribution.test.js +0 -200
  425. package/dist/test/checkpoint-attribution.test.js.map +0 -1
  426. package/dist/test/code-execution.test.d.ts +0 -11
  427. package/dist/test/code-execution.test.d.ts.map +0 -1
  428. package/dist/test/code-execution.test.js +0 -1272
  429. package/dist/test/code-execution.test.js.map +0 -1
  430. package/dist/test/component-defaults.test.d.ts +0 -2
  431. package/dist/test/component-defaults.test.d.ts.map +0 -1
  432. package/dist/test/component-defaults.test.js +0 -54
  433. package/dist/test/component-defaults.test.js.map +0 -1
  434. package/dist/test/conversation-fork-injection-scoping.test.d.ts +0 -2
  435. package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +0 -1
  436. package/dist/test/conversation-fork-injection-scoping.test.js +0 -200
  437. package/dist/test/conversation-fork-injection-scoping.test.js.map +0 -1
  438. package/dist/test/conversation-router.test.d.ts +0 -2
  439. package/dist/test/conversation-router.test.d.ts.map +0 -1
  440. package/dist/test/conversation-router.test.js +0 -174
  441. package/dist/test/conversation-router.test.js.map +0 -1
  442. package/dist/test/conversation-routing.test.d.ts +0 -7
  443. package/dist/test/conversation-routing.test.d.ts.map +0 -1
  444. package/dist/test/conversation-routing.test.js +0 -387
  445. package/dist/test/conversation-routing.test.js.map +0 -1
  446. package/dist/test/deferred-wake-integrity.test.d.ts +0 -29
  447. package/dist/test/deferred-wake-integrity.test.d.ts.map +0 -1
  448. package/dist/test/deferred-wake-integrity.test.js +0 -320
  449. package/dist/test/deferred-wake-integrity.test.js.map +0 -1
  450. package/dist/test/discord-awareness-outbox.test.d.ts +0 -2
  451. package/dist/test/discord-awareness-outbox.test.d.ts.map +0 -1
  452. package/dist/test/discord-awareness-outbox.test.js +0 -267
  453. package/dist/test/discord-awareness-outbox.test.js.map +0 -1
  454. package/dist/test/discord-history-delete-window.test.d.ts +0 -2
  455. package/dist/test/discord-history-delete-window.test.d.ts.map +0 -1
  456. package/dist/test/discord-history-delete-window.test.js +0 -81
  457. package/dist/test/discord-history-delete-window.test.js.map +0 -1
  458. package/dist/test/discord-history-sync.test.d.ts +0 -2
  459. package/dist/test/discord-history-sync.test.d.ts.map +0 -1
  460. package/dist/test/discord-history-sync.test.js +0 -56
  461. package/dist/test/discord-history-sync.test.js.map +0 -1
  462. package/dist/test/ephemeral-settle.test.d.ts +0 -2
  463. package/dist/test/ephemeral-settle.test.d.ts.map +0 -1
  464. package/dist/test/ephemeral-settle.test.js +0 -354
  465. package/dist/test/ephemeral-settle.test.js.map +0 -1
  466. package/dist/test/event-gate-passthrough.test.d.ts +0 -15
  467. package/dist/test/event-gate-passthrough.test.d.ts.map +0 -1
  468. package/dist/test/event-gate-passthrough.test.js +0 -289
  469. package/dist/test/event-gate-passthrough.test.js.map +0 -1
  470. package/dist/test/event-gate-policy-mutation.test.d.ts +0 -2
  471. package/dist/test/event-gate-policy-mutation.test.d.ts.map +0 -1
  472. package/dist/test/event-gate-policy-mutation.test.js +0 -137
  473. package/dist/test/event-gate-policy-mutation.test.js.map +0 -1
  474. package/dist/test/event-gate-rate-limit.test.d.ts +0 -2
  475. package/dist/test/event-gate-rate-limit.test.d.ts.map +0 -1
  476. package/dist/test/event-gate-rate-limit.test.js +0 -627
  477. package/dist/test/event-gate-rate-limit.test.js.map +0 -1
  478. package/dist/test/event-gate-script.test.d.ts +0 -2
  479. package/dist/test/event-gate-script.test.d.ts.map +0 -1
  480. package/dist/test/event-gate-script.test.js +0 -95
  481. package/dist/test/event-gate-script.test.js.map +0 -1
  482. package/dist/test/event-gate-selfwake.test.d.ts +0 -15
  483. package/dist/test/event-gate-selfwake.test.d.ts.map +0 -1
  484. package/dist/test/event-gate-selfwake.test.js +0 -125
  485. package/dist/test/event-gate-selfwake.test.js.map +0 -1
  486. package/dist/test/event-gate-tags.test.d.ts +0 -2
  487. package/dist/test/event-gate-tags.test.d.ts.map +0 -1
  488. package/dist/test/event-gate-tags.test.js +0 -76
  489. package/dist/test/event-gate-tags.test.js.map +0 -1
  490. package/dist/test/event-gate.test.d.ts +0 -2
  491. package/dist/test/event-gate.test.d.ts.map +0 -1
  492. package/dist/test/event-gate.test.js +0 -919
  493. package/dist/test/event-gate.test.js.map +0 -1
  494. package/dist/test/explicit-prose-routing.test.d.ts +0 -11
  495. package/dist/test/explicit-prose-routing.test.d.ts.map +0 -1
  496. package/dist/test/explicit-prose-routing.test.js +0 -1394
  497. package/dist/test/explicit-prose-routing.test.js.map +0 -1
  498. package/dist/test/framework.test.d.ts +0 -5
  499. package/dist/test/framework.test.d.ts.map +0 -1
  500. package/dist/test/framework.test.js +0 -1048
  501. package/dist/test/framework.test.js.map +0 -1
  502. package/dist/test/gather-context-timeout.test.d.ts +0 -2
  503. package/dist/test/gather-context-timeout.test.d.ts.map +0 -1
  504. package/dist/test/gather-context-timeout.test.js +0 -120
  505. package/dist/test/gather-context-timeout.test.js.map +0 -1
  506. package/dist/test/health-snapshot.test.d.ts +0 -2
  507. package/dist/test/health-snapshot.test.d.ts.map +0 -1
  508. package/dist/test/health-snapshot.test.js +0 -74
  509. package/dist/test/health-snapshot.test.js.map +0 -1
  510. package/dist/test/helpers/coalescing-fixture.d.ts +0 -72
  511. package/dist/test/helpers/coalescing-fixture.d.ts.map +0 -1
  512. package/dist/test/helpers/coalescing-fixture.js +0 -146
  513. package/dist/test/helpers/coalescing-fixture.js.map +0 -1
  514. package/dist/test/helpers/coalescing-kill-child.d.ts +0 -2
  515. package/dist/test/helpers/coalescing-kill-child.d.ts.map +0 -1
  516. package/dist/test/helpers/coalescing-kill-child.js +0 -24
  517. package/dist/test/helpers/coalescing-kill-child.js.map +0 -1
  518. package/dist/test/helpers/mock-membrane.d.ts +0 -53
  519. package/dist/test/helpers/mock-membrane.d.ts.map +0 -1
  520. package/dist/test/helpers/mock-membrane.js +0 -168
  521. package/dist/test/helpers/mock-membrane.js.map +0 -1
  522. package/dist/test/helpers/pytc-stop-rows.d.ts +0 -11
  523. package/dist/test/helpers/pytc-stop-rows.d.ts.map +0 -1
  524. package/dist/test/helpers/pytc-stop-rows.js +0 -143
  525. package/dist/test/helpers/pytc-stop-rows.js.map +0 -1
  526. package/dist/test/helpers/quiesce-crash-child.d.ts +0 -2
  527. package/dist/test/helpers/quiesce-crash-child.d.ts.map +0 -1
  528. package/dist/test/helpers/quiesce-crash-child.js +0 -156
  529. package/dist/test/helpers/quiesce-crash-child.js.map +0 -1
  530. package/dist/test/history-module-overview.test.d.ts +0 -2
  531. package/dist/test/history-module-overview.test.d.ts.map +0 -1
  532. package/dist/test/history-module-overview.test.js +0 -711
  533. package/dist/test/history-module-overview.test.js.map +0 -1
  534. package/dist/test/history-module-realstore.test.d.ts +0 -2
  535. package/dist/test/history-module-realstore.test.d.ts.map +0 -1
  536. package/dist/test/history-module-realstore.test.js +0 -363
  537. package/dist/test/history-module-realstore.test.js.map +0 -1
  538. package/dist/test/history-module-semantic.test.d.ts +0 -2
  539. package/dist/test/history-module-semantic.test.d.ts.map +0 -1
  540. package/dist/test/history-module-semantic.test.js +0 -213
  541. package/dist/test/history-module-semantic.test.js.map +0 -1
  542. package/dist/test/history-module-ux.test.d.ts +0 -2
  543. package/dist/test/history-module-ux.test.d.ts.map +0 -1
  544. package/dist/test/history-module-ux.test.js +0 -304
  545. package/dist/test/history-module-ux.test.js.map +0 -1
  546. package/dist/test/history-module.test.d.ts +0 -2
  547. package/dist/test/history-module.test.d.ts.map +0 -1
  548. package/dist/test/history-module.test.js +0 -619
  549. package/dist/test/history-module.test.js.map +0 -1
  550. package/dist/test/history-semantic-review-findings.test.d.ts +0 -2
  551. package/dist/test/history-semantic-review-findings.test.d.ts.map +0 -1
  552. package/dist/test/history-semantic-review-findings.test.js +0 -338
  553. package/dist/test/history-semantic-review-findings.test.js.map +0 -1
  554. package/dist/test/history-semantic-sync-converges.test.d.ts +0 -2
  555. package/dist/test/history-semantic-sync-converges.test.d.ts.map +0 -1
  556. package/dist/test/history-semantic-sync-converges.test.js +0 -109
  557. package/dist/test/history-semantic-sync-converges.test.js.map +0 -1
  558. package/dist/test/host-quiesce.test.d.ts +0 -2
  559. package/dist/test/host-quiesce.test.d.ts.map +0 -1
  560. package/dist/test/host-quiesce.test.js +0 -863
  561. package/dist/test/host-quiesce.test.js.map +0 -1
  562. package/dist/test/inference-exhaustion-rewind.test.d.ts +0 -2
  563. package/dist/test/inference-exhaustion-rewind.test.d.ts.map +0 -1
  564. package/dist/test/inference-exhaustion-rewind.test.js +0 -309
  565. package/dist/test/inference-exhaustion-rewind.test.js.map +0 -1
  566. package/dist/test/inference-failure-bounds.test.d.ts +0 -2
  567. package/dist/test/inference-failure-bounds.test.d.ts.map +0 -1
  568. package/dist/test/inference-failure-bounds.test.js +0 -1201
  569. package/dist/test/inference-failure-bounds.test.js.map +0 -1
  570. package/dist/test/inference-failure-observability.test.d.ts +0 -2
  571. package/dist/test/inference-failure-observability.test.d.ts.map +0 -1
  572. package/dist/test/inference-failure-observability.test.js +0 -84
  573. package/dist/test/inference-failure-observability.test.js.map +0 -1
  574. package/dist/test/journal-tool.test.d.ts +0 -2
  575. package/dist/test/journal-tool.test.d.ts.map +0 -1
  576. package/dist/test/journal-tool.test.js +0 -33
  577. package/dist/test/journal-tool.test.js.map +0 -1
  578. package/dist/test/kv-unified-open-flight-retry.test.d.ts +0 -2
  579. package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +0 -1
  580. package/dist/test/kv-unified-open-flight-retry.test.js +0 -189
  581. package/dist/test/kv-unified-open-flight-retry.test.js.map +0 -1
  582. package/dist/test/kv-unified-wiring.test.d.ts +0 -2
  583. package/dist/test/kv-unified-wiring.test.d.ts.map +0 -1
  584. package/dist/test/kv-unified-wiring.test.js +0 -95
  585. package/dist/test/kv-unified-wiring.test.js.map +0 -1
  586. package/dist/test/late-events-after-stop.test.d.ts +0 -2
  587. package/dist/test/late-events-after-stop.test.d.ts.map +0 -1
  588. package/dist/test/late-events-after-stop.test.js +0 -122
  589. package/dist/test/late-events-after-stop.test.js.map +0 -1
  590. package/dist/test/liveness-watchdog.test.d.ts +0 -2
  591. package/dist/test/liveness-watchdog.test.d.ts.map +0 -1
  592. package/dist/test/liveness-watchdog.test.js +0 -94
  593. package/dist/test/liveness-watchdog.test.js.map +0 -1
  594. package/dist/test/machine-close-provenance.test.d.ts +0 -2
  595. package/dist/test/machine-close-provenance.test.d.ts.map +0 -1
  596. package/dist/test/machine-close-provenance.test.js +0 -116
  597. package/dist/test/machine-close-provenance.test.js.map +0 -1
  598. package/dist/test/maintenance-timer.test.d.ts +0 -2
  599. package/dist/test/maintenance-timer.test.d.ts.map +0 -1
  600. package/dist/test/maintenance-timer.test.js +0 -265
  601. package/dist/test/maintenance-timer.test.js.map +0 -1
  602. package/dist/test/mcpl-access-provider.test.d.ts +0 -2
  603. package/dist/test/mcpl-access-provider.test.d.ts.map +0 -1
  604. package/dist/test/mcpl-access-provider.test.js +0 -20
  605. package/dist/test/mcpl-access-provider.test.js.map +0 -1
  606. package/dist/test/mcpl-awareness-barrier.test.d.ts +0 -2
  607. package/dist/test/mcpl-awareness-barrier.test.d.ts.map +0 -1
  608. package/dist/test/mcpl-awareness-barrier.test.js +0 -769
  609. package/dist/test/mcpl-awareness-barrier.test.js.map +0 -1
  610. package/dist/test/mcpl-capability-grant.test.d.ts +0 -10
  611. package/dist/test/mcpl-capability-grant.test.d.ts.map +0 -1
  612. package/dist/test/mcpl-capability-grant.test.js +0 -111
  613. package/dist/test/mcpl-capability-grant.test.js.map +0 -1
  614. package/dist/test/mcpl-capability-scoping.test.d.ts +0 -2
  615. package/dist/test/mcpl-capability-scoping.test.d.ts.map +0 -1
  616. package/dist/test/mcpl-capability-scoping.test.js +0 -266
  617. package/dist/test/mcpl-capability-scoping.test.js.map +0 -1
  618. package/dist/test/mcpl-child-env.test.d.ts +0 -2
  619. package/dist/test/mcpl-child-env.test.d.ts.map +0 -1
  620. package/dist/test/mcpl-child-env.test.js +0 -158
  621. package/dist/test/mcpl-child-env.test.js.map +0 -1
  622. package/dist/test/mcpl-coalescing.test.d.ts +0 -2
  623. package/dist/test/mcpl-coalescing.test.d.ts.map +0 -1
  624. package/dist/test/mcpl-coalescing.test.js +0 -1125
  625. package/dist/test/mcpl-coalescing.test.js.map +0 -1
  626. package/dist/test/mcpl-config-diagnostics.test.d.ts +0 -2
  627. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +0 -1
  628. package/dist/test/mcpl-config-diagnostics.test.js +0 -452
  629. package/dist/test/mcpl-config-diagnostics.test.js.map +0 -1
  630. package/dist/test/mcpl-empty-content.test.d.ts +0 -2
  631. package/dist/test/mcpl-empty-content.test.d.ts.map +0 -1
  632. package/dist/test/mcpl-empty-content.test.js +0 -368
  633. package/dist/test/mcpl-empty-content.test.js.map +0 -1
  634. package/dist/test/mcpl-gate-roundtrip.test.d.ts +0 -2
  635. package/dist/test/mcpl-gate-roundtrip.test.d.ts.map +0 -1
  636. package/dist/test/mcpl-gate-roundtrip.test.js +0 -285
  637. package/dist/test/mcpl-gate-roundtrip.test.js.map +0 -1
  638. package/dist/test/mcpl-list-status.test.d.ts +0 -2
  639. package/dist/test/mcpl-list-status.test.d.ts.map +0 -1
  640. package/dist/test/mcpl-list-status.test.js +0 -82
  641. package/dist/test/mcpl-list-status.test.js.map +0 -1
  642. package/dist/test/mcpl-manifest-changed.test.d.ts +0 -2
  643. package/dist/test/mcpl-manifest-changed.test.d.ts.map +0 -1
  644. package/dist/test/mcpl-manifest-changed.test.js +0 -178
  645. package/dist/test/mcpl-manifest-changed.test.js.map +0 -1
  646. package/dist/test/mcpl-push-convert.test.d.ts +0 -2
  647. package/dist/test/mcpl-push-convert.test.d.ts.map +0 -1
  648. package/dist/test/mcpl-push-convert.test.js +0 -42
  649. package/dist/test/mcpl-push-convert.test.js.map +0 -1
  650. package/dist/test/mcpl-reconnect-events.test.d.ts +0 -2
  651. package/dist/test/mcpl-reconnect-events.test.d.ts.map +0 -1
  652. package/dist/test/mcpl-reconnect-events.test.js +0 -144
  653. package/dist/test/mcpl-reconnect-events.test.js.map +0 -1
  654. package/dist/test/mcpl-reconnect-reregistration.test.d.ts +0 -2
  655. package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +0 -1
  656. package/dist/test/mcpl-reconnect-reregistration.test.js +0 -236
  657. package/dist/test/mcpl-reconnect-reregistration.test.js.map +0 -1
  658. package/dist/test/mcpl-reference-fetcher.test.d.ts +0 -2
  659. package/dist/test/mcpl-reference-fetcher.test.d.ts.map +0 -1
  660. package/dist/test/mcpl-reference-fetcher.test.js +0 -427
  661. package/dist/test/mcpl-reference-fetcher.test.js.map +0 -1
  662. package/dist/test/mcpl-reference-fields.test.d.ts +0 -2
  663. package/dist/test/mcpl-reference-fields.test.d.ts.map +0 -1
  664. package/dist/test/mcpl-reference-fields.test.js +0 -149
  665. package/dist/test/mcpl-reference-fields.test.js.map +0 -1
  666. package/dist/test/mcpl-request-timeout.test.d.ts +0 -2
  667. package/dist/test/mcpl-request-timeout.test.d.ts.map +0 -1
  668. package/dist/test/mcpl-request-timeout.test.js +0 -138
  669. package/dist/test/mcpl-request-timeout.test.js.map +0 -1
  670. package/dist/test/mcpl-runtime-lifecycle.test.d.ts +0 -7
  671. package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +0 -1
  672. package/dist/test/mcpl-runtime-lifecycle.test.js +0 -142
  673. package/dist/test/mcpl-runtime-lifecycle.test.js.map +0 -1
  674. package/dist/test/mcpl-tool-class-listing.test.d.ts +0 -2
  675. package/dist/test/mcpl-tool-class-listing.test.d.ts.map +0 -1
  676. package/dist/test/mcpl-tool-class-listing.test.js +0 -147
  677. package/dist/test/mcpl-tool-class-listing.test.js.map +0 -1
  678. package/dist/test/mcpl-tool-classes.test.d.ts +0 -7
  679. package/dist/test/mcpl-tool-classes.test.d.ts.map +0 -1
  680. package/dist/test/mcpl-tool-classes.test.js +0 -80
  681. package/dist/test/mcpl-tool-classes.test.js.map +0 -1
  682. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts +0 -2
  683. package/dist/test/mcpl-tool-lifecycle-e2e.test.d.ts.map +0 -1
  684. package/dist/test/mcpl-tool-lifecycle-e2e.test.js +0 -237
  685. package/dist/test/mcpl-tool-lifecycle-e2e.test.js.map +0 -1
  686. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts +0 -11
  687. package/dist/test/mcpl-tool-lifecycle-emitter.test.d.ts.map +0 -1
  688. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +0 -412
  689. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +0 -1
  690. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +0 -2
  691. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +0 -1
  692. package/dist/test/mcpl-tool-lifecycle-script.test.js +0 -288
  693. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +0 -1
  694. package/dist/test/mcpl-tool-lifecycle.test.d.ts +0 -11
  695. package/dist/test/mcpl-tool-lifecycle.test.d.ts.map +0 -1
  696. package/dist/test/mcpl-tool-lifecycle.test.js +0 -270
  697. package/dist/test/mcpl-tool-lifecycle.test.js.map +0 -1
  698. package/dist/test/mcpl-ws-transport.test.d.ts +0 -2
  699. package/dist/test/mcpl-ws-transport.test.d.ts.map +0 -1
  700. package/dist/test/mcpl-ws-transport.test.js +0 -258
  701. package/dist/test/mcpl-ws-transport.test.js.map +0 -1
  702. package/dist/test/module-context-integration.test.d.ts +0 -18
  703. package/dist/test/module-context-integration.test.d.ts.map +0 -1
  704. package/dist/test/module-context-integration.test.js +0 -181
  705. package/dist/test/module-context-integration.test.js.map +0 -1
  706. package/dist/test/offline-recovery-branch.test.d.ts +0 -2
  707. package/dist/test/offline-recovery-branch.test.d.ts.map +0 -1
  708. package/dist/test/offline-recovery-branch.test.js +0 -226
  709. package/dist/test/offline-recovery-branch.test.js.map +0 -1
  710. package/dist/test/operator-surgery.test.d.ts +0 -2
  711. package/dist/test/operator-surgery.test.d.ts.map +0 -1
  712. package/dist/test/operator-surgery.test.js +0 -398
  713. package/dist/test/operator-surgery.test.js.map +0 -1
  714. package/dist/test/ops-alert.test.d.ts +0 -2
  715. package/dist/test/ops-alert.test.d.ts.map +0 -1
  716. package/dist/test/ops-alert.test.js +0 -153
  717. package/dist/test/ops-alert.test.js.map +0 -1
  718. package/dist/test/overbudget-drain-breaker.test.d.ts +0 -2
  719. package/dist/test/overbudget-drain-breaker.test.d.ts.map +0 -1
  720. package/dist/test/overbudget-drain-breaker.test.js +0 -197
  721. package/dist/test/overbudget-drain-breaker.test.js.map +0 -1
  722. package/dist/test/per-agent-delivery.test.d.ts +0 -2
  723. package/dist/test/per-agent-delivery.test.d.ts.map +0 -1
  724. package/dist/test/per-agent-delivery.test.js +0 -108
  725. package/dist/test/per-agent-delivery.test.js.map +0 -1
  726. package/dist/test/physical-window-restart.test.d.ts +0 -14
  727. package/dist/test/physical-window-restart.test.d.ts.map +0 -1
  728. package/dist/test/physical-window-restart.test.js +0 -222
  729. package/dist/test/physical-window-restart.test.js.map +0 -1
  730. package/dist/test/placed-reaction-baseline.test.d.ts +0 -2
  731. package/dist/test/placed-reaction-baseline.test.d.ts.map +0 -1
  732. package/dist/test/placed-reaction-baseline.test.js +0 -312
  733. package/dist/test/placed-reaction-baseline.test.js.map +0 -1
  734. package/dist/test/present-while-acting.test.d.ts +0 -19
  735. package/dist/test/present-while-acting.test.d.ts.map +0 -1
  736. package/dist/test/present-while-acting.test.js +0 -706
  737. package/dist/test/present-while-acting.test.js.map +0 -1
  738. package/dist/test/prose-segments.test.d.ts +0 -2
  739. package/dist/test/prose-segments.test.d.ts.map +0 -1
  740. package/dist/test/prose-segments.test.js +0 -52
  741. package/dist/test/prose-segments.test.js.map +0 -1
  742. package/dist/test/prose-stream-router.test.d.ts +0 -2
  743. package/dist/test/prose-stream-router.test.d.ts.map +0 -1
  744. package/dist/test/prose-stream-router.test.js +0 -171
  745. package/dist/test/prose-stream-router.test.js.map +0 -1
  746. package/dist/test/provider-acceleration-cooldown.test.d.ts +0 -2
  747. package/dist/test/provider-acceleration-cooldown.test.d.ts.map +0 -1
  748. package/dist/test/provider-acceleration-cooldown.test.js +0 -435
  749. package/dist/test/provider-acceleration-cooldown.test.js.map +0 -1
  750. package/dist/test/provider-waits.test.d.ts +0 -2
  751. package/dist/test/provider-waits.test.d.ts.map +0 -1
  752. package/dist/test/provider-waits.test.js +0 -2196
  753. package/dist/test/provider-waits.test.js.map +0 -1
  754. package/dist/test/puppet-tool-call.test.d.ts +0 -2
  755. package/dist/test/puppet-tool-call.test.d.ts.map +0 -1
  756. package/dist/test/puppet-tool-call.test.js +0 -224
  757. package/dist/test/puppet-tool-call.test.js.map +0 -1
  758. package/dist/test/puppet-turn-reservation.test.d.ts +0 -2
  759. package/dist/test/puppet-turn-reservation.test.d.ts.map +0 -1
  760. package/dist/test/puppet-turn-reservation.test.js +0 -139
  761. package/dist/test/puppet-turn-reservation.test.js.map +0 -1
  762. package/dist/test/reaction-baseline-platform.test.d.ts +0 -2
  763. package/dist/test/reaction-baseline-platform.test.d.ts.map +0 -1
  764. package/dist/test/reaction-baseline-platform.test.js +0 -124
  765. package/dist/test/reaction-baseline-platform.test.js.map +0 -1
  766. package/dist/test/record-journal.test.d.ts +0 -6
  767. package/dist/test/record-journal.test.d.ts.map +0 -1
  768. package/dist/test/record-journal.test.js +0 -269
  769. package/dist/test/record-journal.test.js.map +0 -1
  770. package/dist/test/refusal-reaction-baseline.test.d.ts +0 -2
  771. package/dist/test/refusal-reaction-baseline.test.d.ts.map +0 -1
  772. package/dist/test/refusal-reaction-baseline.test.js +0 -81
  773. package/dist/test/refusal-reaction-baseline.test.js.map +0 -1
  774. package/dist/test/refusal-rewind.test.d.ts +0 -2
  775. package/dist/test/refusal-rewind.test.d.ts.map +0 -1
  776. package/dist/test/refusal-rewind.test.js +0 -108
  777. package/dist/test/refusal-rewind.test.js.map +0 -1
  778. package/dist/test/refusal-single-call.test.d.ts +0 -2
  779. package/dist/test/refusal-single-call.test.d.ts.map +0 -1
  780. package/dist/test/refusal-single-call.test.js +0 -177
  781. package/dist/test/refusal-single-call.test.js.map +0 -1
  782. package/dist/test/release-changelog.test.d.ts +0 -2
  783. package/dist/test/release-changelog.test.d.ts.map +0 -1
  784. package/dist/test/release-changelog.test.js +0 -159
  785. package/dist/test/release-changelog.test.js.map +0 -1
  786. package/dist/test/same-round-think-policy.test.d.ts +0 -2
  787. package/dist/test/same-round-think-policy.test.d.ts.map +0 -1
  788. package/dist/test/same-round-think-policy.test.js +0 -853
  789. package/dist/test/same-round-think-policy.test.js.map +0 -1
  790. package/dist/test/save-recent-image.test.d.ts +0 -26
  791. package/dist/test/save-recent-image.test.d.ts.map +0 -1
  792. package/dist/test/save-recent-image.test.js +0 -974
  793. package/dist/test/save-recent-image.test.js.map +0 -1
  794. package/dist/test/shutdown-errors.test.d.ts +0 -2
  795. package/dist/test/shutdown-errors.test.d.ts.map +0 -1
  796. package/dist/test/shutdown-errors.test.js +0 -662
  797. package/dist/test/shutdown-errors.test.js.map +0 -1
  798. package/dist/test/silent-heartbeat-rows.test.d.ts +0 -2
  799. package/dist/test/silent-heartbeat-rows.test.d.ts.map +0 -1
  800. package/dist/test/silent-heartbeat-rows.test.js +0 -451
  801. package/dist/test/silent-heartbeat-rows.test.js.map +0 -1
  802. package/dist/test/silent-heartbeat.test.d.ts +0 -2
  803. package/dist/test/silent-heartbeat.test.d.ts.map +0 -1
  804. package/dist/test/silent-heartbeat.test.js +0 -165
  805. package/dist/test/silent-heartbeat.test.js.map +0 -1
  806. package/dist/test/state-scaling-e2e.test.d.ts +0 -22
  807. package/dist/test/state-scaling-e2e.test.d.ts.map +0 -1
  808. package/dist/test/state-scaling-e2e.test.js +0 -206
  809. package/dist/test/state-scaling-e2e.test.js.map +0 -1
  810. package/dist/test/state-scaling.test.d.ts +0 -12
  811. package/dist/test/state-scaling.test.d.ts.map +0 -1
  812. package/dist/test/state-scaling.test.js +0 -251
  813. package/dist/test/state-scaling.test.js.map +0 -1
  814. package/dist/test/stop-with-work-in-flight.test.d.ts +0 -23
  815. package/dist/test/stop-with-work-in-flight.test.d.ts.map +0 -1
  816. package/dist/test/stop-with-work-in-flight.test.js +0 -672
  817. package/dist/test/stop-with-work-in-flight.test.js.map +0 -1
  818. package/dist/test/surgery-marks-receipt.test.d.ts +0 -2
  819. package/dist/test/surgery-marks-receipt.test.d.ts.map +0 -1
  820. package/dist/test/surgery-marks-receipt.test.js +0 -540
  821. package/dist/test/surgery-marks-receipt.test.js.map +0 -1
  822. package/dist/test/thinking-token-stamp.test.d.ts +0 -12
  823. package/dist/test/thinking-token-stamp.test.d.ts.map +0 -1
  824. package/dist/test/thinking-token-stamp.test.js +0 -129
  825. package/dist/test/thinking-token-stamp.test.js.map +0 -1
  826. package/dist/test/timezone.test.d.ts +0 -2
  827. package/dist/test/timezone.test.d.ts.map +0 -1
  828. package/dist/test/timezone.test.js +0 -18
  829. package/dist/test/timezone.test.js.map +0 -1
  830. package/dist/test/tool-batch-hook.test.d.ts +0 -8
  831. package/dist/test/tool-batch-hook.test.d.ts.map +0 -1
  832. package/dist/test/tool-batch-hook.test.js +0 -275
  833. package/dist/test/tool-batch-hook.test.js.map +0 -1
  834. package/dist/test/tool-policy.test.d.ts +0 -2
  835. package/dist/test/tool-policy.test.d.ts.map +0 -1
  836. package/dist/test/tool-policy.test.js +0 -51
  837. package/dist/test/tool-policy.test.js.map +0 -1
  838. package/dist/test/tool-presentation.test.d.ts +0 -2
  839. package/dist/test/tool-presentation.test.d.ts.map +0 -1
  840. package/dist/test/tool-presentation.test.js +0 -268
  841. package/dist/test/tool-presentation.test.js.map +0 -1
  842. package/dist/test/tool-result-content.test.d.ts +0 -2
  843. package/dist/test/tool-result-content.test.d.ts.map +0 -1
  844. package/dist/test/tool-result-content.test.js +0 -37
  845. package/dist/test/tool-result-content.test.js.map +0 -1
  846. package/dist/test/tool-result-guard.test.d.ts +0 -2
  847. package/dist/test/tool-result-guard.test.d.ts.map +0 -1
  848. package/dist/test/tool-result-guard.test.js +0 -3607
  849. package/dist/test/tool-result-guard.test.js.map +0 -1
  850. package/dist/test/tool-result-history.test.d.ts +0 -7
  851. package/dist/test/tool-result-history.test.d.ts.map +0 -1
  852. package/dist/test/tool-result-history.test.js +0 -156
  853. package/dist/test/tool-result-history.test.js.map +0 -1
  854. package/dist/test/tool-result-spill.test.d.ts +0 -13
  855. package/dist/test/tool-result-spill.test.d.ts.map +0 -1
  856. package/dist/test/tool-result-spill.test.js +0 -586
  857. package/dist/test/tool-result-spill.test.js.map +0 -1
  858. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +0 -2
  859. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +0 -1
  860. package/dist/test/tool-wrapper-prose-guard-integration.test.js +0 -485
  861. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +0 -1
  862. package/dist/test/tool-wrapper-prose-guard.test.d.ts +0 -2
  863. package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +0 -1
  864. package/dist/test/tool-wrapper-prose-guard.test.js +0 -34
  865. package/dist/test/tool-wrapper-prose-guard.test.js.map +0 -1
  866. package/dist/test/tools-observe-parser-conformance.test.d.ts +0 -2
  867. package/dist/test/tools-observe-parser-conformance.test.d.ts.map +0 -1
  868. package/dist/test/tools-observe-parser-conformance.test.js +0 -129
  869. package/dist/test/tools-observe-parser-conformance.test.js.map +0 -1
  870. package/dist/test/trunk-channel-routing.test.d.ts +0 -19
  871. package/dist/test/trunk-channel-routing.test.d.ts.map +0 -1
  872. package/dist/test/trunk-channel-routing.test.js +0 -329
  873. package/dist/test/trunk-channel-routing.test.js.map +0 -1
  874. package/dist/test/tune-out-e2e.test.d.ts +0 -2
  875. package/dist/test/tune-out-e2e.test.d.ts.map +0 -1
  876. package/dist/test/tune-out-e2e.test.js +0 -142
  877. package/dist/test/tune-out-e2e.test.js.map +0 -1
  878. package/dist/test/tune-out-gate-composition.test.d.ts +0 -2
  879. package/dist/test/tune-out-gate-composition.test.d.ts.map +0 -1
  880. package/dist/test/tune-out-gate-composition.test.js +0 -221
  881. package/dist/test/tune-out-gate-composition.test.js.map +0 -1
  882. package/dist/test/tune-out-lifecycle.test.d.ts +0 -2
  883. package/dist/test/tune-out-lifecycle.test.d.ts.map +0 -1
  884. package/dist/test/tune-out-lifecycle.test.js +0 -121
  885. package/dist/test/tune-out-lifecycle.test.js.map +0 -1
  886. package/dist/test/turn-trigger-provenance.test.d.ts +0 -2
  887. package/dist/test/turn-trigger-provenance.test.d.ts.map +0 -1
  888. package/dist/test/turn-trigger-provenance.test.js +0 -137
  889. package/dist/test/turn-trigger-provenance.test.js.map +0 -1
  890. package/dist/test/unconvertible-tool-result.test.d.ts +0 -2
  891. package/dist/test/unconvertible-tool-result.test.d.ts.map +0 -1
  892. package/dist/test/unconvertible-tool-result.test.js +0 -358
  893. package/dist/test/unconvertible-tool-result.test.js.map +0 -1
  894. package/dist/test/usage-accounting.test.d.ts +0 -2
  895. package/dist/test/usage-accounting.test.d.ts.map +0 -1
  896. package/dist/test/usage-accounting.test.js +0 -13
  897. package/dist/test/usage-accounting.test.js.map +0 -1
  898. package/dist/test/usage-tracker.test.d.ts +0 -2
  899. package/dist/test/usage-tracker.test.d.ts.map +0 -1
  900. package/dist/test/usage-tracker.test.js +0 -129
  901. package/dist/test/usage-tracker.test.js.map +0 -1
  902. package/dist/test/utils-surface.test.d.ts +0 -2
  903. package/dist/test/utils-surface.test.d.ts.map +0 -1
  904. package/dist/test/utils-surface.test.js +0 -145
  905. package/dist/test/utils-surface.test.js.map +0 -1
  906. package/dist/test/workspace-branch-continuation.test.d.ts +0 -15
  907. package/dist/test/workspace-branch-continuation.test.d.ts.map +0 -1
  908. package/dist/test/workspace-branch-continuation.test.js +0 -183
  909. package/dist/test/workspace-branch-continuation.test.js.map +0 -1
  910. package/dist/test/workspace-materialize-freshness.test.d.ts +0 -16
  911. package/dist/test/workspace-materialize-freshness.test.d.ts.map +0 -1
  912. package/dist/test/workspace-materialize-freshness.test.js +0 -252
  913. package/dist/test/workspace-materialize-freshness.test.js.map +0 -1
  914. package/dist/test/workspace-path-containment.test.d.ts +0 -19
  915. package/dist/test/workspace-path-containment.test.d.ts.map +0 -1
  916. package/dist/test/workspace-path-containment.test.js +0 -91
  917. package/dist/test/workspace-path-containment.test.js.map +0 -1
  918. package/dist/test/workspace-read-file-from-disk.test.d.ts +0 -17
  919. package/dist/test/workspace-read-file-from-disk.test.d.ts.map +0 -1
  920. package/dist/test/workspace-read-file-from-disk.test.js +0 -174
  921. package/dist/test/workspace-read-file-from-disk.test.js.map +0 -1
  922. package/dist/test/workspace-read-image.test.d.ts +0 -2
  923. package/dist/test/workspace-read-image.test.d.ts.map +0 -1
  924. package/dist/test/workspace-read-image.test.js +0 -597
  925. package/dist/test/workspace-read-image.test.js.map +0 -1
  926. package/dist/test/workspace-read-text.test.d.ts +0 -2
  927. package/dist/test/workspace-read-text.test.d.ts.map +0 -1
  928. package/dist/test/workspace-read-text.test.js +0 -101
  929. package/dist/test/workspace-read-text.test.js.map +0 -1
  930. package/dist/test/workspace-restore-order.test.d.ts +0 -16
  931. package/dist/test/workspace-restore-order.test.d.ts.map +0 -1
  932. package/dist/test/workspace-restore-order.test.js +0 -140
  933. package/dist/test/workspace-restore-order.test.js.map +0 -1
  934. package/dist/test/workspace-three-way.test.d.ts +0 -12
  935. package/dist/test/workspace-three-way.test.d.ts.map +0 -1
  936. package/dist/test/workspace-three-way.test.js +0 -3194
  937. package/dist/test/workspace-three-way.test.js.map +0 -1
  938. package/dist/test/workspace-watcher.test.d.ts +0 -2
  939. package/dist/test/workspace-watcher.test.d.ts.map +0 -1
  940. package/dist/test/workspace-watcher.test.js +0 -281
  941. package/dist/test/workspace-watcher.test.js.map +0 -1
  942. package/dist/test/xml-round-persist.test.d.ts +0 -19
  943. package/dist/test/xml-round-persist.test.d.ts.map +0 -1
  944. package/dist/test/xml-round-persist.test.js +0 -235
  945. package/dist/test/xml-round-persist.test.js.map +0 -1
  946. 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,16 +31,20 @@ 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';
37
37
  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
- import { EmptyContentError, validateCoalescedContent } from './push-coalescer.js';
41
- import { isVisiblyEmptyContent } from './visible-content.js';
40
+ import { coalesceRejectionReason, EmptyContentError, validateCoalescedContent } from './push-coalescer.js';
41
+ import { isVisiblyEmptyConversion } from './visible-content.js';
42
+ import { logDetail, logValue } from './log-value.js';
43
+ import { RefusalLines } from './refusal-lines.js';
42
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';
43
48
 
44
49
  // ============================================================================
45
50
  // Typing indicator interval (Discord typing lasts ~10s, so 7s keeps it alive)
@@ -237,74 +242,10 @@ interface McplChannelIncomingEvent {
237
242
  tags?: string[];
238
243
  triggerInference?: boolean;
239
244
  targetAgents?: string[];
240
- }
241
-
242
- // ============================================================================
243
- // Content Conversion: McplContentBlock → membrane ContentBlock
244
- // ============================================================================
245
-
246
- /**
247
- * Convert a single MCPL wire-format content block to a membrane ContentBlock.
248
- */
249
- function convertBlock(block: McplContentBlock): ContentBlock {
250
- switch (block.type) {
251
- case 'text':
252
- return { type: 'text', text: block.text };
253
-
254
- case 'image':
255
- if (isInlineContradiction(block)) {
256
- // RFC-005 vector 2: inline data claiming bulk disposition — fail
257
- // closed, withhold the data (checked BEFORE the data branch).
258
- return { type: 'text', text: INLINE_WITHHELD_TEXT };
259
- }
260
- if (block.uri && block.disposition) {
261
- // RFC-005 uri-form media with a disposition claim: stub, do not
262
- // hand the URI to the provider or inline it.
263
- return { type: 'text', text: referenceStubOrNull(block, 'attachment on channel message') ?? '[reference]' };
264
- }
265
- if (block.data && block.mimeType) {
266
- return {
267
- type: 'image',
268
- source: { type: 'base64', data: block.data, mediaType: block.mimeType },
269
- } as ContentBlock;
270
- }
271
- if (block.uri) {
272
- return {
273
- type: 'image',
274
- source: { type: 'url', url: block.uri },
275
- } as ContentBlock;
276
- }
277
- return { type: 'text', text: '[Image: no data]' };
278
-
279
- case 'audio':
280
- if (isInlineContradiction(block)) {
281
- // RFC-005 vector 2: inline data claiming bulk disposition — fail
282
- // closed, withhold the data (checked BEFORE the data branch).
283
- return { type: 'text', text: INLINE_WITHHELD_TEXT };
284
- }
285
- if (block.uri && block.disposition) {
286
- // RFC-005 uri-form media with a disposition claim: stub, do not
287
- // hand the URI to the provider or inline it.
288
- return { type: 'text', text: referenceStubOrNull(block, 'attachment on channel message') ?? '[reference]' };
289
- }
290
- if (block.data && block.mimeType) {
291
- return {
292
- type: 'audio',
293
- source: { type: 'base64', data: block.data, mediaType: block.mimeType },
294
- } as ContentBlock;
295
- }
296
- return { type: 'text', text: '[Audio: no data]' };
297
-
298
- case 'resource':
299
- // RFC-005: reference blocks become bounded stubs — never raw URIs
300
- // (a signed URL is a bearer credential that looks like a location).
301
- return { type: 'text', text: referenceStubOrNull(block, 'attachment on channel message') ?? '[reference]' };
302
-
303
- default:
304
- // Unknown wire block types previously fell off the exhaustive switch
305
- // and propagated `undefined` into ContentBlock[]. Fail visibly.
306
- return { type: 'text', text: `[unrecognized content block: ${(block as { type?: string }).type ?? 'untyped'}]` };
307
- }
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;
308
249
  }
309
250
 
310
251
  // ============================================================================
@@ -320,9 +261,8 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
320
261
  {
321
262
  name: 'channel_open',
322
263
  description:
323
- 'Open a channel to start receiving its ordinary ongoing traffic. The MCPL ' +
324
- 'integration performs its own subscribe/join/attach operation. Optionally request ' +
325
- '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.',
326
266
  inputSchema: {
327
267
  type: 'object' as const,
328
268
  properties: {
@@ -362,7 +302,9 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
362
302
  },
363
303
  {
364
304
  name: 'channel_close',
365
- 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.',
366
308
  inputSchema: {
367
309
  type: 'object' as const,
368
310
  properties: {
@@ -441,8 +383,9 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
441
383
  type: 'number',
442
384
  description:
443
385
  'Optional self-wake: if nothing else wakes you first, you wake again after this ' +
444
- 'many seconds (1 = go again almost immediately; clamped to 1–3600). Any other ' +
445
- '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.',
446
389
  },
447
390
  },
448
391
  required: [],
@@ -454,7 +397,125 @@ const CHANNEL_TOOL_DEFINITIONS: ToolDefinition[] = [
454
397
  // Constructor Options
455
398
  // ============================================================================
456
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
+
457
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;
458
519
  /**
459
520
  * RFC-006: an admitted `channels/incoming` message carrying `coalesce`, with
460
521
  * the event the ordinary path would have queued. The handler decides
@@ -483,15 +544,21 @@ interface ChannelRegistryOptions {
483
544
  channelId: string | null;
484
545
  reason: string;
485
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;
486
552
  }) => void;
487
553
  /**
488
554
  * Called when channels were opened WITHOUT the agent asking (subscription
489
555
  * policy admitting a newly discovered channel, or delivery into a closed
490
556
  * locus). The host wires this to drop a durable notice into the agent's
491
- * window — the agent must always learn that new traffic will start
492
- * flowing, and that `channel_close` opts out (their decision outranks
493
- * policy). Fires ONCE per channel ever: the desired-state decision is
494
- * 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.
495
562
  */
496
563
  onChannelAutoOpened?: (info: {
497
564
  /** Agent whose action caused the open (delivery); absent for policy opens. */
@@ -512,19 +579,23 @@ interface ChannelRegistryOptions {
512
579
  */
513
580
  homeChannelResolver?: (agentName: string) => string | undefined;
514
581
  /**
515
- * Resolve the channel that triggered an agent's CURRENT inference turn, by
516
- * agent name. This is the fix for single-TRUNK agents (the only mode
582
+ * Resolve an agent's channel for its CURRENT inference turn, by agent name:
583
+ * the turn's locus once it has one, else the channel that triggered the
584
+ * turn. This is the fix for single-TRUNK agents (the only mode
517
585
  * connectome-host runs — it never exposes conversation forks). A trunk agent
518
586
  * has no fork home, so without this its plain-text speech falls back to the
519
587
  * process-global `defaultPublishChannel`, which tracks the most-recent inbound
520
588
  * across ALL channels and misroutes a reply to whichever channel last spoke
521
- * under concurrency (item-3 redux). The framework tracks the triggering
522
- * channel of the live turn per-agent and exposes it here; resolves to
523
- * undefined for a heartbeat / no-trigger turn, which correctly keeps the
524
- * global fallback. Consulted AFTER `homeChannelResolver` (a fork's home always
525
- * wins), BEFORE `defaultPublishChannel`.
589
+ * under concurrency (item-3 redux). The framework keeps both on the live
590
+ * turn's routing state and exposes them here. For a heartbeat / no-trigger
591
+ * turn it resolves to undefined until the turn's locus is resolved, so that
592
+ * resolution takes the global fallback, which the turn then keeps.
593
+ * Consulted AFTER `homeChannelResolver` (a fork's home always wins), BEFORE
594
+ * `defaultPublishChannel`.
526
595
  */
527
596
  activeChannelResolver?: (agentName: string) => string | undefined;
597
+ /** Whether this framework has EventGate and can honor skip_reply self-wakes. */
598
+ eventGatePresent?: boolean;
528
599
  }
529
600
 
530
601
  // ============================================================================
@@ -558,12 +629,7 @@ export class ChannelRegistry {
558
629
  op?: 'start' | 'stop',
559
630
  ) => void;
560
631
  private shouldTriggerInference?: (content: string, metadata: Record<string, unknown>) => boolean;
561
- private onRouteFailure?: (info: {
562
- conversationId: string;
563
- channelId: string | null;
564
- reason: string;
565
- textLen: number;
566
- }) => void;
632
+ private onRouteFailure?: ChannelRegistryOptions['onRouteFailure'];
567
633
  private onChannelAutoOpened?: (info: {
568
634
  conversationId?: string;
569
635
  serverId: string;
@@ -577,6 +643,10 @@ export class ChannelRegistry {
577
643
  /** Registered channels, keyed by `{serverId}:{channelId}`. */
578
644
  private channels = new Map<string, ChannelEntry>();
579
645
 
646
+ /** Holds back repeats of the line for a message refused because its
647
+ * channel isn't registered (RefusalLines). */
648
+ private unknownChannelLines = new RefusalLines();
649
+
580
650
  /**
581
651
  * Most-recently-known label per channelId, replayed from
582
652
  * `CHANNEL_LABEL_HISTORY_LOG_ID` (last sighting wins) and kept current by
@@ -658,6 +728,8 @@ export class ChannelRegistry {
658
728
  private legacyPolicies = new Map<string, 'auto' | 'manual' | string[]>();
659
729
  private migratedLegacyPolicies = new Set<string>();
660
730
  private handleCoalescedIncoming?: ChannelRegistryOptions['handleCoalescedIncoming'];
731
+ private acceptInbound?: ChannelRegistryOptions['acceptInbound'];
732
+ private channelTools: ToolDefinition[];
661
733
 
662
734
  constructor(
663
735
  serverRegistry: McplServerRegistry,
@@ -674,6 +746,24 @@ export class ChannelRegistry {
674
746
  },
675
747
  ) {
676
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
+ });
677
767
  this.serverRegistry = serverRegistry;
678
768
  this.featureSetManager = featureSetManager;
679
769
  this.pushEventFn = pushEventFn;
@@ -848,6 +938,45 @@ export class ChannelRegistry {
848
938
  });
849
939
  }
850
940
 
941
+ /**
942
+ * A coalesced message the host refused. Until now the host said nothing, so
943
+ * a dropped message (agent-framework#266: every Discord message with a
944
+ * screenshot) left no trace where operators look. The sender gets the
945
+ * per-message result this lane gives: `coalesce_invalid` for a malformed
946
+ * item, or the thrown error's message for anything else. `answered` is the
947
+ * code that result stands for (-32602 for `coalesce_invalid`), and the
948
+ * reason follows it (coalesceRejectionReason): `coalesce-failed` when the
949
+ * host itself failed, `coalesce-invalid` when the sender's item was
950
+ * refused, with the failing `field` and the error's message as `detail`.
951
+ * The line prints the ids the sender sent, whatever their type; the trace
952
+ * carries them when they are strings.
953
+ */
954
+ private traceCoalesceRejection(
955
+ serverId: string,
956
+ message: { channelId?: unknown; messageId?: unknown } | null | undefined,
957
+ error: { field?: unknown; message: string },
958
+ answered: number,
959
+ ): void {
960
+ const reason = coalesceRejectionReason(answered);
961
+ const field = typeof error.field === 'string' && error.field ? error.field : undefined;
962
+ const channelId = typeof message?.channelId === 'string' ? message.channelId : '';
963
+ const messageId = typeof message?.messageId === 'string' && message.messageId ? message.messageId : undefined;
964
+ console.error(
965
+ `[channel-incoming-rejected] server=${serverId} channel=${logValue(message?.channelId ?? '')} ` +
966
+ `messageId=${logValue(message?.messageId ?? '')} ` +
967
+ `reason=${reason}${field ? ` field=${logValue(field)}` : ''}: ${logDetail(error.message)}`,
968
+ );
969
+ this.emitTraceFn({
970
+ type: 'mcpl:channel-incoming-rejected',
971
+ serverId,
972
+ channelId,
973
+ ...(messageId ? { messageId } : {}),
974
+ reason,
975
+ ...(field ? { field } : {}),
976
+ detail: error.message,
977
+ });
978
+ }
979
+
851
980
  /**
852
981
  * Handle `channels/incoming` from a server.
853
982
  *
@@ -864,10 +993,20 @@ export class ChannelRegistry {
864
993
  for (const message of params.messages) {
865
994
  if (!message || typeof message.channelId !== 'string' || !message.channelId
866
995
  || typeof message.messageId !== 'string' || !message.messageId) {
996
+ // The same gate as admission below: with no coalescer wired, a
997
+ // message carrying `coalesce` is handled uncoalesced, and refused as
998
+ // any uncoalesced message without an identity is.
999
+ const coalescedItem = message?.coalesce !== undefined && !!this.handleCoalescedIncoming;
1000
+ if (coalescedItem) {
1001
+ this.traceCoalesceRejection(serverId, message, {
1002
+ field: typeof message.channelId !== 'string' || !message.channelId ? 'channelId' : 'messageId',
1003
+ message: 'a coalesced message needs a channelId and a messageId',
1004
+ }, -32602);
1005
+ }
867
1006
  results.push({
868
1007
  messageId: typeof message?.messageId === 'string' ? message.messageId : '',
869
1008
  accepted: false,
870
- reason: message?.coalesce !== undefined ? 'coalesce_invalid' : 'invalid channel/message identity',
1009
+ reason: coalescedItem ? 'coalesce_invalid' : 'invalid channel/message identity',
871
1010
  });
872
1011
  continue;
873
1012
  }
@@ -899,11 +1038,24 @@ export class ChannelRegistry {
899
1038
  // routable entry rather than an open self-attested one.
900
1039
  const incomingKey = `${serverId}:${message.channelId}`;
901
1040
  if (!this.channels.has(incomingKey)) {
1041
+ // Loud, coalesced or not, like every other refusal of a message, but
1042
+ // at most once a minute per channel (RefusalLines): a server that
1043
+ // never registered a busy channel would otherwise print a line for
1044
+ // every message in it. Every refusal is traced.
1045
+ const held = this.unknownChannelLines.admit(serverId, message.channelId);
1046
+ if (held !== null) {
1047
+ console.error(
1048
+ `[channel-incoming-rejected] server=${serverId} channel=${logValue(message.channelId)} ` +
1049
+ `messageId=${logValue(message.messageId)} reason=unknown-channel ` +
1050
+ `(not registered, §14.5${held ? `; ${held} more since the last line` : ''})`,
1051
+ );
1052
+ }
902
1053
  this.emitTraceFn({
903
1054
  type: 'mcpl:channel-incoming-rejected',
904
1055
  serverId,
905
1056
  channelId: message.channelId,
906
- reason: 'unknown channel — not registered (§14.5)',
1057
+ messageId: message.messageId,
1058
+ reason: 'unknown-channel',
907
1059
  });
908
1060
  results.push({
909
1061
  messageId: message.messageId,
@@ -921,7 +1073,7 @@ export class ChannelRegistry {
921
1073
  // shows the model nothing would wake it with no visible cause.
922
1074
  const emptyAllowed = coalesced && (message.coalesce as { retract?: unknown } | null)?.retract === true;
923
1075
  const rejectEmpty = () => {
924
- console.error(`[channel-incoming-rejected] server=${serverId} channel=${message.channelId} messageId=${message.messageId} reason=empty-content`);
1076
+ console.error(`[channel-incoming-rejected] server=${serverId} channel=${logValue(message.channelId)} messageId=${logValue(message.messageId)} reason=empty-content`);
925
1077
  this.emitTraceFn({
926
1078
  type: 'mcpl:channel-incoming-rejected',
927
1079
  serverId,
@@ -935,16 +1087,20 @@ export class ChannelRegistry {
935
1087
  // RFC-006 §13: malformed content on a coalesced item is that item's
936
1088
  // failure, not the batch's — check the shape before converting.
937
1089
  try {
938
- validateCoalescedContent(message.content, undefined, { allowEmpty: emptyAllowed });
1090
+ validateCoalescedContent(message.content, { allowEmpty: emptyAllowed });
939
1091
  } catch (error) {
940
1092
  if (error instanceof EmptyContentError) rejectEmpty();
941
- else results.push({ messageId: message.messageId, accepted: false, reason: 'coalesce_invalid' });
1093
+ else {
1094
+ // Answered coalesce_invalid whatever the error's code: -32602.
1095
+ this.traceCoalesceRejection(serverId, message, error as Error & { field?: string }, -32602);
1096
+ results.push({ messageId: message.messageId, accepted: false, reason: 'coalesce_invalid' });
1097
+ }
942
1098
  continue;
943
1099
  }
944
1100
  }
945
1101
  if (message.tags) message.tags = expandCoreTags(message.tags);
946
- const convertedContent: ContentBlock[] = message.content.map(convertBlock);
947
- 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)) {
948
1104
  rejectEmpty();
949
1105
  continue;
950
1106
  }
@@ -970,8 +1126,11 @@ export class ChannelRegistry {
970
1126
  // Determine whether to trigger inference
971
1127
  let triggerInference = true;
972
1128
  if (this.shouldTriggerInference) {
973
- const textContent = message.content
974
- .filter((b): b is { type: 'text'; text: string } => b.type === 'text')
1129
+ // The text the agent's context gets, as the push lane's filter reads
1130
+ // it: the converted text blocks, a placeholder or reference stub
1131
+ // among them, NON_STRING_TEXT_PLACEHOLDER included.
1132
+ const textContent = convertedContent
1133
+ .filter((b): b is ContentBlock & { type: 'text' } => b.type === 'text')
975
1134
  .map((b) => b.text)
976
1135
  .join('\n');
977
1136
  triggerInference = this.shouldTriggerInference(
@@ -1002,6 +1161,7 @@ export class ChannelRegistry {
1002
1161
  metadata: message.metadata,
1003
1162
  ...(message.tags ? { tags: message.tags } : {}),
1004
1163
  triggerInference,
1164
+ acceptedAt: Date.now(),
1005
1165
  };
1006
1166
 
1007
1167
  if (coalesced) {
@@ -1014,7 +1174,10 @@ export class ChannelRegistry {
1014
1174
  if (result.accepted) markAccepted();
1015
1175
  results.push(result);
1016
1176
  } catch (error) {
1017
- const err = error as Error & { code?: number };
1177
+ const err = error as Error & { code?: number; field?: string };
1178
+ // -32602 is answered coalesce_invalid; anything else as its message,
1179
+ // a plain Error standing for an internal error (-32603).
1180
+ this.traceCoalesceRejection(serverId, message, err, typeof err.code === 'number' ? err.code : -32603);
1018
1181
  results.push({
1019
1182
  messageId: message.messageId,
1020
1183
  accepted: false,
@@ -1024,6 +1187,12 @@ export class ChannelRegistry {
1024
1187
  continue;
1025
1188
  }
1026
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
+
1027
1196
  // Push to the processing queue
1028
1197
  // Cast through unknown because McplChannelIncomingEvent matches the
1029
1198
  // CustomEvent `${string}:${string}` type pattern but lacks an index signature.
@@ -1077,20 +1246,34 @@ export class ChannelRegistry {
1077
1246
  (existing.descriptor.metadata as { lazyRegistered?: boolean } | undefined)?.lazyRegistered &&
1078
1247
  label && existing.descriptor.label === channelId
1079
1248
  ) {
1080
- existing.descriptor.label = label;
1081
- if (extraMetadata) {
1082
- existing.descriptor.metadata = { ...existing.descriptor.metadata, ...extraMetadata };
1083
- }
1249
+ const metadata = extraMetadata
1250
+ ? { ...existing.descriptor.metadata, ...extraMetadata }
1251
+ : existing.descriptor.metadata;
1084
1252
  // The channel now has a real label, not just the bare id — durable.
1085
- this.appendLabelSighting(
1086
- existing.descriptor.id,
1087
- existing.descriptor.label,
1088
- extractDmMeta(existing.descriptor.metadata),
1089
- );
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;
1090
1259
  }
1091
1260
  return;
1092
1261
  }
1093
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));
1094
1277
  const key = `${serverId}:${channelId}`;
1095
1278
  this.channels.set(key, {
1096
1279
  serverId,
@@ -1103,19 +1286,6 @@ export class ChannelRegistry {
1103
1286
  },
1104
1287
  open: false,
1105
1288
  });
1106
- // Only persist a REAL label durably. Falling back to the bare
1107
- // channelId as a placeholder (no server-supplied label at all — e.g. a
1108
- // Discord DM push missing both channelName and an author name,
1109
- // framework.ts's derivePushEventChannel) must never overwrite a real
1110
- // label already on file from a previous boot: after a restart the live
1111
- // `channels` map starts empty, so THIS is the very branch a
1112
- // label-less post-restart event takes — recording the placeholder here
1113
- // would clobber the good label initializeLabelHistoryStore() just
1114
- // replayed, defeating the entire point of the durable log. The live
1115
- // descriptor above still carries the placeholder for in-process
1116
- // routing; only the durable write is skipped when there's no real
1117
- // label to record.
1118
- if (label) this.appendLabelSighting(channelId, label, extractDmMeta(extraMetadata));
1119
1289
  this.emitTraceFn({
1120
1290
  type: 'mcpl:channel-lazy-registered',
1121
1291
  serverId,
@@ -1250,6 +1420,13 @@ export class ChannelRegistry {
1250
1420
  return this.findChannelEntry(channelId)?.descriptor;
1251
1421
  }
1252
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
+
1253
1430
  isChannelOpen(channelId: string): boolean {
1254
1431
  return this.findChannelEntry(channelId)?.open === true;
1255
1432
  }
@@ -1279,7 +1456,7 @@ export class ChannelRegistry {
1279
1456
  * Get synthesized tool definitions for channel operations.
1280
1457
  */
1281
1458
  getChannelTools(): ToolDefinition[] {
1282
- return CHANNEL_TOOL_DEFINITIONS;
1459
+ return this.channelTools;
1283
1460
  }
1284
1461
 
1285
1462
  /**
@@ -1340,7 +1517,7 @@ export class ChannelRegistry {
1340
1517
  return this.handleToolJournal(input as { content?: string });
1341
1518
 
1342
1519
  case 'skip_reply':
1343
- return this.handleToolSkipReply(input as { reason?: string; wake_in_seconds?: number });
1520
+ return this.handleToolSkipReply(input as { reason?: string; wake_in_seconds?: unknown });
1344
1521
 
1345
1522
  default:
1346
1523
  return { success: false, error: `Unknown channel tool: ${toolName}`, isError: true };
@@ -1361,8 +1538,9 @@ export class ChannelRegistry {
1361
1538
 
1362
1539
  // Resolve the outbound locus the SAME way routeSpeech does, so the agent is
1363
1540
  // told the channel its speech will actually land in: a conversation fork's
1364
- // home channel, else this turn's triggering channel (single trunk agent —
1365
- // item-3 redux), else the global default (heartbeats). Without this, a fork
1541
+ // home channel, else this turn's channel (single trunk agent — item-3
1542
+ // redux: its locus, else its triggering channel), else the global default
1543
+ // (heartbeats). Without this, a fork
1366
1544
  // was advertised the global locus but published somewhere else; and a trunk
1367
1545
  // agent was told the wrong channel under concurrency.
1368
1546
  const home = agentName ? this.homeChannelResolver?.(agentName) : undefined;
@@ -1454,13 +1632,7 @@ export class ChannelRegistry {
1454
1632
  private initializeLifecycleStore(): void {
1455
1633
  if (!this.store) return;
1456
1634
 
1457
- try {
1458
- this.store.registerState({ id: CHANNEL_LIFECYCLE_LOG_ID, strategy: 'append_log' });
1459
- } catch (error) {
1460
- if (!(error instanceof Error) || !error.message.includes('State already exists')) {
1461
- throw error;
1462
- }
1463
- }
1635
+ ensureStateRegistered(this.store, { id: CHANNEL_LIFECYCLE_LOG_ID, strategy: 'append_log' });
1464
1636
 
1465
1637
  const raw = this.store.getStateJson(CHANNEL_LIFECYCLE_LOG_ID);
1466
1638
  if (!Array.isArray(raw)) return;
@@ -1503,6 +1675,15 @@ export class ChannelRegistry {
1503
1675
  }
1504
1676
  }
1505
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
+ */
1506
1687
  private appendLifecycleEvent(event: ChannelLifecycleEvent): void {
1507
1688
  this.store?.appendToStateJson(CHANNEL_LIFECYCLE_LOG_ID, event);
1508
1689
  }
@@ -1521,13 +1702,7 @@ export class ChannelRegistry {
1521
1702
  private initializeLabelHistoryStore(): void {
1522
1703
  if (!this.store) return;
1523
1704
 
1524
- try {
1525
- this.store.registerState({ id: CHANNEL_LABEL_HISTORY_LOG_ID, strategy: 'append_log' });
1526
- } catch (error) {
1527
- if (!(error instanceof Error) || !error.message.includes('State already exists')) {
1528
- throw error;
1529
- }
1530
- }
1705
+ ensureStateRegistered(this.store, { id: CHANNEL_LABEL_HISTORY_LOG_ID, strategy: 'append_log' });
1531
1706
 
1532
1707
  const raw = this.store.getStateJson(CHANNEL_LABEL_HISTORY_LOG_ID);
1533
1708
  if (!Array.isArray(raw)) return;
@@ -1608,6 +1783,16 @@ export class ChannelRegistry {
1608
1783
  const recipientNameUnchanged = recipientName === undefined || recipientName === this.dmRecipientNames.get(channelId);
1609
1784
  const isDmUnchanged = isDm === undefined || this.dmClassified.get(channelId) === true;
1610
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);
1611
1796
  this.labelHistory.set(channelId, label);
1612
1797
  let seen = this.allLabelsSeen.get(channelId);
1613
1798
  if (!seen) {
@@ -1618,14 +1803,6 @@ export class ChannelRegistry {
1618
1803
  if (recipientId) this.dmRecipientIds.set(channelId, recipientId);
1619
1804
  if (recipientName) this.dmRecipientNames.set(channelId, recipientName);
1620
1805
  if (isDm) this.dmClassified.set(channelId, true);
1621
- this.store?.appendToStateJson(CHANNEL_LABEL_HISTORY_LOG_ID, {
1622
- channelId,
1623
- label,
1624
- ts: Date.now(),
1625
- ...(recipientId ? { recipientId } : {}),
1626
- ...(recipientName ? { recipientName } : {}),
1627
- ...(isDm ? { isDm } : {}),
1628
- } satisfies ChannelLabelSightingEvent);
1629
1806
  }
1630
1807
 
1631
1808
  /**
@@ -1850,7 +2027,6 @@ export class ChannelRegistry {
1850
2027
  ): void {
1851
2028
  const key = this.lifecycleKey(serverId, channelId);
1852
2029
  if (this.desiredStates.get(key)?.state === desired) return;
1853
- this.desiredStates.set(key, { state: desired, source });
1854
2030
  this.appendLifecycleEvent({
1855
2031
  kind: 'desired-state',
1856
2032
  serverId,
@@ -1859,6 +2035,7 @@ export class ChannelRegistry {
1859
2035
  source,
1860
2036
  timestamp: new Date().toISOString(),
1861
2037
  });
2038
+ this.desiredStates.set(key, { state: desired, source });
1862
2039
  }
1863
2040
 
1864
2041
  // ==========================================================================
@@ -1878,12 +2055,6 @@ export class ChannelRegistry {
1878
2055
  source: string,
1879
2056
  ): void {
1880
2057
  const key = this.lifecycleKey(serverId, channelId);
1881
- this.desiredStates.set(key, {
1882
- state: 'tuned-out',
1883
- source,
1884
- tuneOut: params,
1885
- wakeCount: 0,
1886
- });
1887
2058
  this.appendLifecycleEvent({
1888
2059
  kind: 'desired-state',
1889
2060
  serverId,
@@ -1893,6 +2064,12 @@ export class ChannelRegistry {
1893
2064
  source,
1894
2065
  timestamp: new Date().toISOString(),
1895
2066
  });
2067
+ this.desiredStates.set(key, {
2068
+ state: 'tuned-out',
2069
+ source,
2070
+ tuneOut: params,
2071
+ wakeCount: 0,
2072
+ });
1896
2073
  }
1897
2074
 
1898
2075
  /**
@@ -1911,7 +2088,6 @@ export class ChannelRegistry {
1911
2088
  const current = this.desiredStates.get(key);
1912
2089
  if (current?.state !== 'tuned-out' || !current.tuneOut) return null;
1913
2090
  const ended = { params: current.tuneOut, wakeCount: current.wakeCount ?? 0 };
1914
- this.desiredStates.set(key, { state: nextState, source });
1915
2091
  this.appendLifecycleEvent({
1916
2092
  kind: 'desired-state',
1917
2093
  serverId,
@@ -1920,6 +2096,7 @@ export class ChannelRegistry {
1920
2096
  source,
1921
2097
  timestamp: new Date().toISOString(),
1922
2098
  });
2099
+ this.desiredStates.set(key, { state: nextState, source });
1923
2100
  return ended;
1924
2101
  }
1925
2102
 
@@ -1937,16 +2114,17 @@ export class ChannelRegistry {
1937
2114
  const key = this.lifecycleKey(serverId, channelId);
1938
2115
  const current = this.desiredStates.get(key);
1939
2116
  if (current?.state !== 'tuned-out' || !current.tuneOut) return null;
1940
- current.wakeCount = (current.wakeCount ?? 0) + 1;
2117
+ const wakeCount = (current.wakeCount ?? 0) + 1;
1941
2118
  this.appendLifecycleEvent({
1942
2119
  kind: 'tune-out-wake',
1943
2120
  serverId,
1944
2121
  channelId,
1945
2122
  epochId: current.tuneOut.epochId,
1946
- wakeCount: current.wakeCount,
2123
+ wakeCount,
1947
2124
  timestamp: new Date().toISOString(),
1948
2125
  });
1949
- return { params: current.tuneOut, wakeCount: current.wakeCount };
2126
+ current.wakeCount = wakeCount;
2127
+ return { params: current.tuneOut, wakeCount };
1950
2128
  }
1951
2129
 
1952
2130
  /** Active tune-out params + wake count for a channel, or null. */
@@ -1974,17 +2152,13 @@ export class ChannelRegistry {
1974
2152
  */
1975
2153
  readCoordinatorState(stateId: string): unknown {
1976
2154
  if (!this.store) return null;
1977
- try {
1978
- this.store.registerState({ id: stateId, strategy: 'snapshot' });
1979
- } catch { /* already registered */ }
2155
+ ensureStateRegistered(this.store, { id: stateId, strategy: 'snapshot' });
1980
2156
  return this.store.getStateJson(stateId);
1981
2157
  }
1982
2158
 
1983
2159
  writeCoordinatorState(stateId: string, value: unknown): void {
1984
2160
  if (!this.store) return;
1985
- try {
1986
- this.store.registerState({ id: stateId, strategy: 'snapshot' });
1987
- } catch { /* already registered */ }
2161
+ ensureStateRegistered(this.store, { id: stateId, strategy: 'snapshot' });
1988
2162
  this.store.setStateJson(stateId, value);
1989
2163
  }
1990
2164
 
@@ -2021,13 +2195,13 @@ export class ChannelRegistry {
2021
2195
  this.setDesiredState(serverId, channel.id, desired, 'legacy-recipe-migration');
2022
2196
  }
2023
2197
 
2024
- this.migratedLegacyPolicies.add(serverId);
2025
2198
  this.appendLifecycleEvent({
2026
2199
  kind: 'legacy-policy-migrated',
2027
2200
  serverId,
2028
2201
  source: Array.isArray(policy) ? 'allow-list' : policy,
2029
2202
  timestamp: new Date().toISOString(),
2030
2203
  });
2204
+ this.migratedLegacyPolicies.add(serverId);
2031
2205
  }
2032
2206
 
2033
2207
  /**
@@ -2156,9 +2330,9 @@ export class ChannelRegistry {
2156
2330
  }
2157
2331
  }
2158
2332
 
2159
- // Announce policy admissions to the agent — nothing may start flowing
2160
- // traffic into their window without them being told, and told how to
2161
- // 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).
2162
2336
  if (policyOpened.length > 0) {
2163
2337
  try {
2164
2338
  this.onChannelAutoOpened?.({
@@ -2575,11 +2749,13 @@ export class ChannelRegistry {
2575
2749
  // A tuned-out channel is transport-open but attention-diverted; a plain
2576
2750
  // open would silently discard the epoch (and its pending backlog dump).
2577
2751
  // Cancelling is the tune-out coordinator's flow, reached via its own
2578
- // 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.
2579
2755
  if (this.getDesiredState(entry.serverId, input.channelId) === 'tuned-out') {
2580
2756
  return {
2581
2757
  success: false,
2582
- isError: false,
2758
+ isError: true,
2583
2759
  error:
2584
2760
  `Channel ${input.channelId} is tuned out. Cancel the tune-out ` +
2585
2761
  `(tune_out with mode: 'cancel') to resume normal attention — ` +
@@ -2750,7 +2926,7 @@ export class ChannelRegistry {
2750
2926
  if (entry.open || this.getDesiredState(entry.serverId, input.channelId) === 'open') {
2751
2927
  return {
2752
2928
  success: false,
2753
- 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.`,
2754
2930
  isError: true,
2755
2931
  };
2756
2932
  }
@@ -2858,14 +3034,15 @@ export class ChannelRegistry {
2858
3034
  * tool, so any trailing prose this turn is NOT posted. Replaces the old
2859
3035
  * overloaded use of `think` for staying silent.
2860
3036
  */
2861
- private handleToolSkipReply(input: { reason?: string; wake_in_seconds?: number }): ToolResult {
3037
+ private handleToolSkipReply(input: { reason?: string; wake_in_seconds?: unknown }): ToolResult {
2862
3038
  // The self-wake itself is armed by the framework (which owns the
2863
- // EventGate) BEFORE this dispatch; when the gate is absent the framework
2864
- // strips the field so this confirmation stays truthful.
2865
- const wakeSecs = Number(input.wake_in_seconds);
2866
- const selfWake = Number.isFinite(wakeSecs) && wakeSecs > 0
2867
- ? Math.max(1, Math.min(3600, Math.floor(wakeSecs)))
2868
- : 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;
2869
3046
  return {
2870
3047
  success: true,
2871
3048
  // The note says "ended the turn" — make it TRUE. Without endTurn the
@@ -2961,7 +3138,179 @@ export class ChannelRegistry {
2961
3138
  return home ?? this.activeChannelResolver?.(conversationId) ?? this.defaultPublishChannel ?? null;
2962
3139
  }
2963
3140
 
2964
- 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(
2965
3314
  conversationId: string,
2966
3315
  text: string,
2967
3316
  /** The turn's FROZEN locus, snapshotted once by the caller (resolveLocus
@@ -2972,118 +3321,82 @@ export class ChannelRegistry {
2972
3321
  * PR #32, and the 2026-07-22 Sol DM misroute). Explicit `null` means
2973
3322
  * "this turn is pinned to no locus": fail loudly rather than guess. */
2974
3323
  locusChannelId: string | null,
2975
- ): Promise<{ delivered: boolean; channelId: string; messageId?: string } | null> {
2976
- // Surface a routing failure: emit a trace AND notify the host (which drops
2977
- // a `[discord-send-failed]` marker into chronicle) so the agent learns her
2978
- // reply never reached the human, instead of it vanishing silently.
2979
- const fail = (channelId: string | null, reason: string): null => {
2980
- 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)`);
2981
3334
  this.emitTraceFn({
2982
3335
  type: 'mcpl:speech-route-failed',
2983
3336
  conversationId,
2984
3337
  channelId: channelId ?? '',
2985
3338
  reason,
2986
3339
  textLen: text.length,
3340
+ outcome: status,
3341
+ ...(detail !== undefined ? { detail } : {}),
2987
3342
  });
2988
- this.onRouteFailure?.({ conversationId, channelId, reason, textLen: text.length });
2989
- 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;
2990
3347
  };
2991
3348
 
2992
3349
  // Runtime backstop for JS callers the compiler can't see: an omitted
2993
3350
  // locus is a routing bug at the call site, never something to paper over
2994
3351
  // with a live re-resolution.
2995
3352
  if (locusChannelId === undefined) {
2996
- 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() });
2997
3355
  }
2998
- const channelId = locusChannelId;
2999
- if (!channelId) {
3356
+ if (!locusChannelId) {
3000
3357
  // The turn froze with no locus (no home, no triggering channel, no
3001
3358
  // global inbound ever seen) — the agent was told its prose stays in
3002
3359
  // the archive; honor that.
3003
- return fail(null, 'turn has no locus (no home/trigger channel; nothing to deliver into)');
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() });
3004
3362
  }
3005
3363
 
3006
- const entry = this.findChannelEntry(channelId);
3007
- if (!entry) {
3008
- return fail(channelId, `no registered channel for locus "${channelId}"`);
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);
3009
3368
  }
3010
3369
 
3011
- // INVARIANT: it is not possible to send into a closed channel. Speech
3012
- // routed into a closed-but-registered locus (the canonical case: a DM
3013
- // that woke the agent — DMs register closed) OPENS the channel first,
3014
- // so typing indicators, reaction machinery, and inbound forwarding come
3015
- // alive with the reply. If the open fails, the speech does NOT go out:
3016
- // a half-alive delivery is worse than a loud marker.
3017
- if (!entry.open) {
3018
- try {
3019
- await this.openChannelNow(entry, 'opened-by-delivery');
3020
- console.error(
3021
- `[routeSpeech] ${conversationId}: locus ${channelId} was closed — opened by delivery (speech implies engagement)`,
3022
- );
3023
- this.emitTraceFn({
3024
- type: 'mcpl:channel-opened-by-send',
3025
- serverId: entry.serverId,
3026
- channelId,
3027
- });
3028
- try {
3029
- this.onChannelAutoOpened?.({
3030
- conversationId,
3031
- serverId: entry.serverId,
3032
- source: 'opened-by-delivery',
3033
- channels: [{ channelId, label: entry.descriptor.label }],
3034
- });
3035
- } catch (err) {
3036
- console.error('onChannelAutoOpened (delivery) failed:', err);
3037
- }
3038
- } catch (err) {
3039
- return fail(channelId, `locus channel is closed and open failed: ${(err as Error).message}`);
3040
- }
3041
- }
3042
-
3043
- const server = this.serverRegistry.getServer(entry.serverId);
3044
- if (!server) {
3045
- return fail(channelId, `server "${entry.serverId}" not found`);
3046
- }
3047
- // §14.1: channels/publish requires channels.publish in the grant. The
3048
- // host not sending is the enforcement — a send to an ungranted server
3049
- // would have it act on authority it was never told it has.
3050
- if (!CapabilityGrant.of(server).has('channels.publish')) {
3051
- return fail(channelId, `channels.publish not in "${entry.serverId}"'s effective grant (§14.1)`);
3052
- }
3053
-
3054
- const publishParams: ChannelsPublishParams = {
3055
- conversationId,
3056
- channelId,
3057
- content: [{ type: 'text', text }],
3058
- };
3059
- const result = await server.sendChannelsPublish(publishParams);
3060
- const delivered = (result as { delivered?: boolean } | undefined)?.delivered ?? true;
3061
- // Surface the posted message's id (ChannelsPublishResult.messageId) so
3062
- // trace consumers can act on the just-posted message — e.g. a TTS-relay
3063
- // tap editing it down to the words actually voiced on interruption.
3064
- // Previously this was silently dropped here.
3065
- const messageId = (result as { messageId?: string } | undefined)?.messageId;
3066
-
3067
- // The server accepted the publish RPC but reported the message was not
3068
- // actually delivered (e.g. missing Send Messages permission). Previously
3069
- // this returned `{ delivered: true }`, masking the failure. Surface it.
3070
- if (delivered === false) {
3071
- return fail(channelId, `server "${entry.serverId}" reported delivered:false for "${channelId}"`);
3072
- }
3073
-
3074
- console.error(`[routeSpeech] ${conversationId}: routed ${text.length} chars -> ${channelId} (server=${entry.serverId}, delivered=${delivered})`);
3370
+ console.error(`[routeSpeech] ${conversationId}: routed ${text.length} chars -> ${logValue(channelId)} (server=${outcome.destination!.serverId}, delivered=true)`);
3075
3371
  this.emitTraceFn({
3076
3372
  type: 'mcpl:speech-routed',
3077
3373
  conversationId,
3078
- serverId: entry.serverId,
3374
+ serverId: outcome.destination!.serverId,
3079
3375
  channelId,
3080
- delivered,
3376
+ delivered: true,
3081
3377
  textLen: text.length,
3082
3378
  text,
3083
- ...(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 } : {}),
3084
3383
  });
3384
+ return outcome;
3385
+ }
3085
3386
 
3086
- 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
+ };
3087
3400
  }
3088
3401
 
3089
3402
  private async handleToolPublish(input: { channelId?: string; content?: string; text?: string }): Promise<ToolResult> {
@@ -3125,31 +3438,44 @@ export class ChannelRegistry {
3125
3438
  };
3126
3439
  }
3127
3440
 
3128
- try {
3129
- if (!CapabilityGrant.of(server).has('channels.publish')) {
3130
- return {
3131
- success: false,
3132
- error: `channels.publish not in "${server.id}"'s effective grant (§14.1)`,
3133
- isError: true,
3134
- };
3135
- }
3136
- const publishParams: ChannelsPublishParams = {
3137
- conversationId: '', // Framework will fill this when wired
3138
- channelId,
3139
- content: [{ type: 'text', text: messageText }],
3140
- };
3141
-
3142
- const result = await server.sendChannelsPublish(publishParams);
3143
- return {
3144
- success: true,
3145
- data: result ?? { delivered: true },
3146
- };
3147
- } catch (err) {
3441
+ if (!CapabilityGrant.of(server).has('channels.publish')) {
3148
3442
  return {
3149
3443
  success: false,
3150
- error: `Failed to publish to channel: ${(err as Error).message}`,
3444
+ error: `channels.publish not in "${server.id}"'s effective grant (§14.1)`,
3151
3445
  isError: true,
3152
3446
  };
3153
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
+ };
3154
3480
  }
3155
3481
  }