@animalabs/agent-framework 0.23.3 → 0.24.1

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 (355) hide show
  1. package/README.md +212 -42
  2. package/dist/src/agent.d.ts +65 -9
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +98 -12
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +14 -12
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/clock-rows.d.ts +115 -0
  10. package/dist/src/clock-rows.d.ts.map +1 -0
  11. package/dist/src/clock-rows.js +222 -0
  12. package/dist/src/clock-rows.js.map +1 -0
  13. package/dist/src/code-execution/engine.d.ts +177 -0
  14. package/dist/src/code-execution/engine.d.ts.map +1 -0
  15. package/dist/src/code-execution/engine.js +494 -0
  16. package/dist/src/code-execution/engine.js.map +1 -0
  17. package/dist/src/code-execution/index.d.ts +5 -10
  18. package/dist/src/code-execution/index.d.ts.map +1 -1
  19. package/dist/src/code-execution/index.js +5 -9
  20. package/dist/src/code-execution/index.js.map +1 -1
  21. package/dist/src/code-execution/isolate/calls.d.ts +114 -0
  22. package/dist/src/code-execution/isolate/calls.d.ts.map +1 -0
  23. package/dist/src/code-execution/isolate/calls.js +145 -0
  24. package/dist/src/code-execution/isolate/calls.js.map +1 -0
  25. package/dist/src/code-execution/isolate/child-entry.d.ts +2 -0
  26. package/dist/src/code-execution/isolate/child-entry.d.ts.map +1 -0
  27. package/dist/src/code-execution/isolate/child-entry.js +5 -0
  28. package/dist/src/code-execution/isolate/child-entry.js.map +1 -0
  29. package/dist/src/code-execution/isolate/child-main.d.ts +26 -0
  30. package/dist/src/code-execution/isolate/child-main.d.ts.map +1 -0
  31. package/dist/src/code-execution/isolate/child-main.js +132 -0
  32. package/dist/src/code-execution/isolate/child-main.js.map +1 -0
  33. package/dist/src/code-execution/isolate/child.d.ts +44 -0
  34. package/dist/src/code-execution/isolate/child.d.ts.map +1 -0
  35. package/dist/src/code-execution/isolate/child.js +315 -0
  36. package/dist/src/code-execution/isolate/child.js.map +1 -0
  37. package/dist/src/code-execution/isolate/frames.d.ts +80 -0
  38. package/dist/src/code-execution/isolate/frames.d.ts.map +1 -0
  39. package/dist/src/code-execution/isolate/frames.js +210 -0
  40. package/dist/src/code-execution/isolate/frames.js.map +1 -0
  41. package/dist/src/code-execution/isolate/output.d.ts +68 -0
  42. package/dist/src/code-execution/isolate/output.d.ts.map +1 -0
  43. package/dist/src/code-execution/isolate/output.js +143 -0
  44. package/dist/src/code-execution/isolate/output.js.map +1 -0
  45. package/dist/src/code-execution/isolate/prelude.d.ts +109 -0
  46. package/dist/src/code-execution/isolate/prelude.d.ts.map +1 -0
  47. package/dist/src/code-execution/isolate/prelude.js +401 -0
  48. package/dist/src/code-execution/isolate/prelude.js.map +1 -0
  49. package/dist/src/code-execution/isolate/protocol.d.ts +151 -0
  50. package/dist/src/code-execution/isolate/protocol.d.ts.map +1 -0
  51. package/dist/src/code-execution/isolate/protocol.js +150 -0
  52. package/dist/src/code-execution/isolate/protocol.js.map +1 -0
  53. package/dist/src/code-execution/isolate/render.d.ts +57 -0
  54. package/dist/src/code-execution/isolate/render.d.ts.map +1 -0
  55. package/dist/src/code-execution/isolate/render.js +214 -0
  56. package/dist/src/code-execution/isolate/render.js.map +1 -0
  57. package/dist/src/code-execution/isolate/runner.d.ts +250 -0
  58. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -0
  59. package/dist/src/code-execution/isolate/runner.js +854 -0
  60. package/dist/src/code-execution/isolate/runner.js.map +1 -0
  61. package/dist/src/code-execution/isolate/scripts.d.ts +175 -0
  62. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -0
  63. package/dist/src/code-execution/isolate/scripts.js +357 -0
  64. package/dist/src/code-execution/isolate/scripts.js.map +1 -0
  65. package/dist/src/code-execution/quickjs.wasm +0 -0
  66. package/dist/src/code-execution/tool-definition.d.ts +93 -43
  67. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  68. package/dist/src/code-execution/tool-definition.js +176 -142
  69. package/dist/src/code-execution/tool-definition.js.map +1 -1
  70. package/dist/src/deadline-timer.d.ts +3 -0
  71. package/dist/src/deadline-timer.d.ts.map +1 -1
  72. package/dist/src/deadline-timer.js +1 -1
  73. package/dist/src/deadline-timer.js.map +1 -1
  74. package/dist/src/framework.d.ts +1120 -457
  75. package/dist/src/framework.d.ts.map +1 -1
  76. package/dist/src/framework.js +4635 -2505
  77. package/dist/src/framework.js.map +1 -1
  78. package/dist/src/gate/event-gate.d.ts +28 -4
  79. package/dist/src/gate/event-gate.d.ts.map +1 -1
  80. package/dist/src/gate/event-gate.js +89 -13
  81. package/dist/src/gate/event-gate.js.map +1 -1
  82. package/dist/src/held-writes.d.ts +16 -3
  83. package/dist/src/held-writes.d.ts.map +1 -1
  84. package/dist/src/held-writes.js +16 -3
  85. package/dist/src/held-writes.js.map +1 -1
  86. package/dist/src/index.d.ts +8 -6
  87. package/dist/src/index.d.ts.map +1 -1
  88. package/dist/src/index.js +10 -8
  89. package/dist/src/index.js.map +1 -1
  90. package/dist/src/mcpl/channel-registry.d.ts +483 -115
  91. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  92. package/dist/src/mcpl/channel-registry.js +1735 -425
  93. package/dist/src/mcpl/channel-registry.js.map +1 -1
  94. package/dist/src/mcpl/conversation-router.d.ts +19 -39
  95. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  96. package/dist/src/mcpl/conversation-router.js +27 -59
  97. package/dist/src/mcpl/conversation-router.js.map +1 -1
  98. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  99. package/dist/src/mcpl/convert-block.js +9 -5
  100. package/dist/src/mcpl/convert-block.js.map +1 -1
  101. package/dist/src/mcpl/inbound-source.d.ts +50 -0
  102. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  103. package/dist/src/mcpl/inbound-source.js +130 -0
  104. package/dist/src/mcpl/inbound-source.js.map +1 -1
  105. package/dist/src/mcpl/index.d.ts +2 -1
  106. package/dist/src/mcpl/index.d.ts.map +1 -1
  107. package/dist/src/mcpl/index.js +3 -2
  108. package/dist/src/mcpl/index.js.map +1 -1
  109. package/dist/src/mcpl/log-value.d.ts +6 -0
  110. package/dist/src/mcpl/log-value.d.ts.map +1 -1
  111. package/dist/src/mcpl/log-value.js +8 -0
  112. package/dist/src/mcpl/log-value.js.map +1 -1
  113. package/dist/src/mcpl/modern-connection.d.ts +15 -6
  114. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  115. package/dist/src/mcpl/modern-connection.js +168 -53
  116. package/dist/src/mcpl/modern-connection.js.map +1 -1
  117. package/dist/src/mcpl/prose-grammar.d.ts +4 -5
  118. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  119. package/dist/src/mcpl/prose-grammar.js +4 -5
  120. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  121. package/dist/src/mcpl/protocol-family.d.ts +97 -10
  122. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  123. package/dist/src/mcpl/protocol-family.js +174 -17
  124. package/dist/src/mcpl/protocol-family.js.map +1 -1
  125. package/dist/src/mcpl/push-coalescer.d.ts +132 -14
  126. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  127. package/dist/src/mcpl/push-coalescer.js +227 -50
  128. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  129. package/dist/src/mcpl/push-handler.d.ts +9 -20
  130. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  131. package/dist/src/mcpl/push-handler.js +7 -49
  132. package/dist/src/mcpl/push-handler.js.map +1 -1
  133. package/dist/src/mcpl/reference-fetcher.d.ts +2 -2
  134. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  135. package/dist/src/mcpl/reference-fetcher.js +4 -4
  136. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  137. package/dist/src/mcpl/server-connection.d.ts +50 -21
  138. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  139. package/dist/src/mcpl/server-connection.js +158 -74
  140. package/dist/src/mcpl/server-connection.js.map +1 -1
  141. package/dist/src/mcpl/tags.d.ts +24 -0
  142. package/dist/src/mcpl/tags.d.ts.map +1 -1
  143. package/dist/src/mcpl/tags.js +33 -0
  144. package/dist/src/mcpl/tags.js.map +1 -1
  145. package/dist/src/mcpl/tool-result-normalize.d.ts +2 -1
  146. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  147. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  148. package/dist/src/mcpl/transport.d.ts +55 -7
  149. package/dist/src/mcpl/transport.d.ts.map +1 -1
  150. package/dist/src/mcpl/transport.js +163 -34
  151. package/dist/src/mcpl/transport.js.map +1 -1
  152. package/dist/src/mcpl/types.d.ts +22 -6
  153. package/dist/src/mcpl/types.d.ts.map +1 -1
  154. package/dist/src/mcpl/types.js.map +1 -1
  155. package/dist/src/module-registry.d.ts +4 -2
  156. package/dist/src/module-registry.d.ts.map +1 -1
  157. package/dist/src/module-registry.js +100 -13
  158. package/dist/src/module-registry.js.map +1 -1
  159. package/dist/src/modules/discord/index.d.ts.map +1 -1
  160. package/dist/src/modules/discord/index.js +8 -13
  161. package/dist/src/modules/discord/index.js.map +1 -1
  162. package/dist/src/modules/history/index.d.ts.map +1 -1
  163. package/dist/src/modules/history/index.js +27 -4
  164. package/dist/src/modules/history/index.js.map +1 -1
  165. package/dist/src/modules/history/semantic.d.ts +15 -3
  166. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  167. package/dist/src/modules/history/semantic.js +27 -5
  168. package/dist/src/modules/history/semantic.js.map +1 -1
  169. package/dist/src/modules/index.d.ts +1 -1
  170. package/dist/src/modules/index.d.ts.map +1 -1
  171. package/dist/src/modules/index.js.map +1 -1
  172. package/dist/src/modules/unknown-tool.d.ts +4 -1
  173. package/dist/src/modules/unknown-tool.d.ts.map +1 -1
  174. package/dist/src/modules/unknown-tool.js +4 -1
  175. package/dist/src/modules/unknown-tool.js.map +1 -1
  176. package/dist/src/modules/workspace/branch-intent.d.ts +3 -1
  177. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  178. package/dist/src/modules/workspace/branch-intent.js +3 -1
  179. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  180. package/dist/src/modules/workspace/edit-plan.d.ts +265 -0
  181. package/dist/src/modules/workspace/edit-plan.d.ts.map +1 -0
  182. package/dist/src/modules/workspace/edit-plan.js +794 -0
  183. package/dist/src/modules/workspace/edit-plan.js.map +1 -0
  184. package/dist/src/modules/workspace/edit-text.d.ts +117 -0
  185. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -0
  186. package/dist/src/modules/workspace/edit-text.js +584 -0
  187. package/dist/src/modules/workspace/edit-text.js.map +1 -0
  188. package/dist/src/modules/workspace/effects.d.ts +5 -4
  189. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  190. package/dist/src/modules/workspace/effects.js +78 -25
  191. package/dist/src/modules/workspace/effects.js.map +1 -1
  192. package/dist/src/modules/workspace/index.d.ts +493 -62
  193. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  194. package/dist/src/modules/workspace/index.js +2216 -652
  195. package/dist/src/modules/workspace/index.js.map +1 -1
  196. package/dist/src/modules/workspace/observe.d.ts +213 -38
  197. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  198. package/dist/src/modules/workspace/observe.js +416 -36
  199. package/dist/src/modules/workspace/observe.js.map +1 -1
  200. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  201. package/dist/src/modules/workspace/reconcile.js +15 -8
  202. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  203. package/dist/src/modules/workspace/text.d.ts +32 -0
  204. package/dist/src/modules/workspace/text.d.ts.map +1 -0
  205. package/dist/src/modules/workspace/text.js +67 -0
  206. package/dist/src/modules/workspace/text.js.map +1 -0
  207. package/dist/src/modules/workspace/types.d.ts +210 -6
  208. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  209. package/dist/src/modules/workspace/types.js.map +1 -1
  210. package/dist/src/modules/workspace/watcher.d.ts +3 -2
  211. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  212. package/dist/src/modules/workspace/watcher.js.map +1 -1
  213. package/dist/src/operator-log.d.ts +52 -24
  214. package/dist/src/operator-log.d.ts.map +1 -1
  215. package/dist/src/operator-log.js.map +1 -1
  216. package/dist/src/recovery/discord-awareness-outbox.d.ts +496 -72
  217. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  218. package/dist/src/recovery/discord-awareness-outbox.js +1196 -289
  219. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  220. package/dist/src/recovery/offline-branch.d.ts +26 -1
  221. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  222. package/dist/src/recovery/offline-branch.js +164 -39
  223. package/dist/src/recovery/offline-branch.js.map +1 -1
  224. package/dist/src/recovery/recover-cli.js +23 -4
  225. package/dist/src/recovery/recover-cli.js.map +1 -1
  226. package/dist/src/send-tools.d.ts +11 -0
  227. package/dist/src/send-tools.d.ts.map +1 -0
  228. package/dist/src/send-tools.js +11 -0
  229. package/dist/src/send-tools.js.map +1 -0
  230. package/dist/src/timezone.d.ts +4 -2
  231. package/dist/src/timezone.d.ts.map +1 -1
  232. package/dist/src/timezone.js +4 -2
  233. package/dist/src/timezone.js.map +1 -1
  234. package/dist/src/tool-image-ledger.d.ts +46 -7
  235. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  236. package/dist/src/tool-image-ledger.js +60 -7
  237. package/dist/src/tool-image-ledger.js.map +1 -1
  238. package/dist/src/tool-result-guard.d.ts +14 -11
  239. package/dist/src/tool-result-guard.d.ts.map +1 -1
  240. package/dist/src/tool-result-guard.js +17 -14
  241. package/dist/src/tool-result-guard.js.map +1 -1
  242. package/dist/src/tool-result-history.d.ts +3 -2
  243. package/dist/src/tool-result-history.d.ts.map +1 -1
  244. package/dist/src/tool-result-history.js +3 -2
  245. package/dist/src/tool-result-history.js.map +1 -1
  246. package/dist/src/tune-out/coordinator.d.ts +21 -0
  247. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  248. package/dist/src/tune-out/coordinator.js +142 -40
  249. package/dist/src/tune-out/coordinator.js.map +1 -1
  250. package/dist/src/types/agent.d.ts +31 -6
  251. package/dist/src/types/agent.d.ts.map +1 -1
  252. package/dist/src/types/events.d.ts +66 -9
  253. package/dist/src/types/events.d.ts.map +1 -1
  254. package/dist/src/types/framework.d.ts +69 -62
  255. package/dist/src/types/framework.d.ts.map +1 -1
  256. package/dist/src/types/module.d.ts +40 -8
  257. package/dist/src/types/module.d.ts.map +1 -1
  258. package/dist/src/types/trace.d.ts +111 -10
  259. package/dist/src/types/trace.d.ts.map +1 -1
  260. package/dist/src/usage/types.d.ts +16 -0
  261. package/dist/src/usage/types.d.ts.map +1 -1
  262. package/dist/src/usage/usage-tracker.d.ts +14 -1
  263. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  264. package/dist/src/usage/usage-tracker.js +81 -41
  265. package/dist/src/usage/usage-tracker.js.map +1 -1
  266. package/package.json +6 -8
  267. package/src/agent.ts +99 -12
  268. package/src/api/server.ts +14 -12
  269. package/src/clock-rows.ts +302 -0
  270. package/src/code-execution/engine.ts +595 -0
  271. package/src/code-execution/index.ts +5 -17
  272. package/src/code-execution/isolate/calls.ts +199 -0
  273. package/src/code-execution/isolate/child-entry.ts +5 -0
  274. package/src/code-execution/isolate/child-main.ts +132 -0
  275. package/src/code-execution/isolate/child.ts +327 -0
  276. package/src/code-execution/isolate/frames.ts +209 -0
  277. package/src/code-execution/isolate/output.ts +161 -0
  278. package/src/code-execution/isolate/prelude.ts +409 -0
  279. package/src/code-execution/isolate/protocol.ts +240 -0
  280. package/src/code-execution/isolate/render.ts +230 -0
  281. package/src/code-execution/isolate/runner.ts +911 -0
  282. package/src/code-execution/isolate/scripts.ts +479 -0
  283. package/src/code-execution/quickjs.wasm +0 -0
  284. package/src/code-execution/quickjs.wasm.LICENSES +149 -0
  285. package/src/code-execution/tool-definition.ts +236 -167
  286. package/src/deadline-timer.ts +1 -1
  287. package/src/framework.ts +4978 -2671
  288. package/src/gate/event-gate.ts +79 -17
  289. package/src/held-writes.ts +17 -3
  290. package/src/index.ts +32 -22
  291. package/src/mcpl/channel-registry.ts +1892 -471
  292. package/src/mcpl/conversation-router.ts +26 -72
  293. package/src/mcpl/convert-block.ts +9 -5
  294. package/src/mcpl/inbound-source.ts +131 -0
  295. package/src/mcpl/index.ts +4 -2
  296. package/src/mcpl/log-value.ts +9 -0
  297. package/src/mcpl/modern-connection.ts +162 -53
  298. package/src/mcpl/prose-grammar.ts +4 -5
  299. package/src/mcpl/protocol-family.ts +200 -22
  300. package/src/mcpl/push-coalescer.ts +306 -62
  301. package/src/mcpl/push-handler.ts +12 -54
  302. package/src/mcpl/reference-fetcher.ts +4 -4
  303. package/src/mcpl/server-connection.ts +180 -79
  304. package/src/mcpl/tags.ts +36 -0
  305. package/src/mcpl/tool-result-normalize.ts +2 -1
  306. package/src/mcpl/transport.ts +162 -33
  307. package/src/mcpl/types.ts +23 -6
  308. package/src/module-registry.ts +92 -13
  309. package/src/modules/discord/index.ts +8 -13
  310. package/src/modules/history/index.ts +25 -3
  311. package/src/modules/history/semantic.ts +29 -6
  312. package/src/modules/index.ts +1 -0
  313. package/src/modules/unknown-tool.ts +4 -1
  314. package/src/modules/workspace/branch-intent.ts +3 -1
  315. package/src/modules/workspace/edit-plan.ts +954 -0
  316. package/src/modules/workspace/edit-text.ts +618 -0
  317. package/src/modules/workspace/effects.ts +70 -19
  318. package/src/modules/workspace/index.ts +2293 -650
  319. package/src/modules/workspace/observe.ts +465 -56
  320. package/src/modules/workspace/reconcile.ts +14 -7
  321. package/src/modules/workspace/text.ts +68 -0
  322. package/src/modules/workspace/types.ts +211 -6
  323. package/src/modules/workspace/watcher.ts +3 -2
  324. package/src/operator-log.ts +47 -19
  325. package/src/recovery/discord-awareness-outbox.ts +1500 -356
  326. package/src/recovery/offline-branch.ts +189 -55
  327. package/src/recovery/recover-cli.ts +25 -4
  328. package/src/send-tools.ts +11 -0
  329. package/src/timezone.ts +4 -2
  330. package/src/tool-image-ledger.ts +62 -8
  331. package/src/tool-result-guard.ts +18 -15
  332. package/src/tool-result-history.ts +3 -2
  333. package/src/tune-out/coordinator.ts +166 -45
  334. package/src/types/agent.ts +32 -6
  335. package/src/types/events.ts +66 -9
  336. package/src/types/framework.ts +69 -62
  337. package/src/types/module.ts +40 -8
  338. package/src/types/trace.ts +121 -15
  339. package/src/usage/types.ts +16 -0
  340. package/src/usage/usage-tracker.ts +86 -43
  341. package/dist/src/code-execution/py-runner.d.ts +0 -191
  342. package/dist/src/code-execution/py-runner.d.ts.map +0 -1
  343. package/dist/src/code-execution/py-runner.js +0 -655
  344. package/dist/src/code-execution/py-runner.js.map +0 -1
  345. package/dist/src/code-execution/runtime-py.d.ts +0 -50
  346. package/dist/src/code-execution/runtime-py.d.ts.map +0 -1
  347. package/dist/src/code-execution/runtime-py.js +0 -677
  348. package/dist/src/code-execution/runtime-py.js.map +0 -1
  349. package/dist/src/mcpl/prose-stream-router.d.ts +0 -88
  350. package/dist/src/mcpl/prose-stream-router.d.ts.map +0 -1
  351. package/dist/src/mcpl/prose-stream-router.js +0 -291
  352. package/dist/src/mcpl/prose-stream-router.js.map +0 -1
  353. package/src/code-execution/py-runner.ts +0 -791
  354. package/src/code-execution/runtime-py.ts +0 -677
  355. package/src/mcpl/prose-stream-router.ts +0 -268
@@ -16,10 +16,10 @@
16
16
  * - Everything else is left unrouted and dropped by the framework — the
17
17
  * trunk is a dormant template, not a listener.
18
18
  *
19
- * Bindings expire after an idle TTL. Expiry runs a final closure turn on the
20
- * fork (see framework wiring) and unbinds; the next qualifying message on
21
- * that channel spawns a *fresh* fork (generation + 1) from the current
22
- * trunk, so handbook/template updates propagate between engagements.
19
+ * Bindings expire after an idle TTL. Expiry unbinds the channel and runs a
20
+ * final closure turn on the fork (see framework wiring); the next qualifying
21
+ * message on that channel spawns a *fresh* fork (generation + 1) from the
22
+ * current trunk, so handbook/template updates propagate between engagements.
23
23
  *
24
24
  * The binding table is in-memory — bindings are re-established by the next
25
25
  * qualifying message after a restart. The generation counters, by contrast,
@@ -108,10 +108,6 @@ export interface ConversationRouterConfig {
108
108
 
109
109
  export interface ConversationBinding {
110
110
  channelId: string;
111
- /** The channel's MCPL server, where the binding was made with one: channel
112
- * ids are unique only within one server's connection (MCPL §14.2), so each
113
- * server's channel has its own binding. */
114
- serverId?: string;
115
111
  agentName: string;
116
112
  /** Engagement counter per channel — fresh forks get a new generation. */
117
113
  generation: number;
@@ -161,15 +157,11 @@ export const DEFAULT_CLOSURE_PROMPT =
161
157
  export class ConversationRouter {
162
158
  private config: ConversationRouterConfig;
163
159
 
164
- /** Active bindings, keyed by the channel's server and id (bindingKey):
165
- * two servers' channels with one id are two conversations. */
160
+ /** Active bindings, keyed by channelId. */
166
161
  private bindings = new Map<string, ConversationBinding>();
167
162
 
168
- /** Engagement counter per channel id — survives unbind, so a re-bound
169
- * channel gets a distinct fork agent name. Kept by bare id, as it's
170
- * persisted: two servers sharing an id share a counter, which still never
171
- * repeats a name, and their forks' names differ anyway, since each hashes
172
- * its server (forkAgentName). */
163
+ /** Engagement counter per channel — survives unbind, so a re-bound channel
164
+ * gets a distinct fork agent name. */
173
165
  private generations = new Map<string, number>();
174
166
 
175
167
  constructor(config: ConversationRouterConfig) {
@@ -199,7 +191,7 @@ export class ConversationRouter {
199
191
  * unchanged; a failed spawn doesn't leave a dangling route.
200
192
  */
201
193
  route(facts: IncomingMessageFacts): RoutingDecision {
202
- const existing = this.bindings.get(bindingKey(facts));
194
+ const existing = this.bindings.get(facts.channelId);
203
195
 
204
196
  if (existing) {
205
197
  return {
@@ -231,57 +223,33 @@ export class ConversationRouter {
231
223
  * Record a binding after the framework successfully spawned the fork.
232
224
  * Returns the new binding.
233
225
  */
234
- bind(channel: string | ConversationChannel, agentName: string, generation: number, now = Date.now()): ConversationBinding {
235
- const { channelId, serverId } = asChannel(channel);
236
- // The counter is kept by bare id, and two servers' spawns on one id can
237
- // run beside each other, so a bind can finish after a later spawn's: the
238
- // counter keeps the higher, or the next spawn would propose a fork name
239
- // already used.
240
- this.generations.set(channelId, Math.max(this.generations.get(channelId) ?? 0, generation));
226
+ bind(channelId: string, agentName: string, generation: number, now = Date.now()): ConversationBinding {
227
+ this.generations.set(channelId, generation);
241
228
  const binding: ConversationBinding = {
242
229
  channelId,
243
- ...(serverId !== undefined ? { serverId } : {}),
244
230
  agentName,
245
231
  generation,
246
232
  boundAt: now,
247
233
  lastActivity: now,
248
234
  };
249
- this.bindings.set(bindingKey({ channelId, serverId }), binding);
235
+ this.bindings.set(channelId, binding);
250
236
  return binding;
251
237
  }
252
238
 
253
239
  /** Record delivered activity on an existing binding. Does not bind a
254
240
  * channel or advance its generation. Call after successful delivery, not
255
241
  * when previewing a route. The optional clock supports deterministic use. */
256
- touch(channel: string | ConversationChannel, now = Date.now()): void {
257
- const found = this.findBinding(channel);
258
- if (found) found[1].lastActivity = now;
259
- }
260
-
261
- unbind(channel: string | ConversationChannel): void {
262
- const found = this.findBinding(channel);
263
- if (found) this.bindings.delete(found[0]);
242
+ touch(channelId: string, now = Date.now()): void {
243
+ const binding = this.bindings.get(channelId);
244
+ if (binding) binding.lastActivity = now;
264
245
  }
265
246
 
266
- getBinding(channel: string | ConversationChannel): ConversationBinding | undefined {
267
- return this.findBinding(channel)?.[1];
247
+ unbind(channelId: string): void {
248
+ this.bindings.delete(channelId);
268
249
  }
269
250
 
270
- /**
271
- * The binding a caller names, with its key. A channel given with its server
272
- * names that server's binding only. A bare id, as callers gave it before
273
- * bindings had servers, names the binding made with no server, else the one
274
- * binding for that id; where several servers' channels with that id are
275
- * bound, it names none.
276
- */
277
- private findBinding(channel: string | ConversationChannel): [string, ConversationBinding] | undefined {
278
- const named = asChannel(channel);
279
- const key = bindingKey(named);
280
- const exact = this.bindings.get(key);
281
- if (exact) return [key, exact];
282
- if (named.serverId !== undefined) return undefined;
283
- const matches = [...this.bindings.entries()].filter(([, b]) => b.channelId === named.channelId);
284
- return matches.length === 1 ? matches[0] : undefined;
251
+ getBinding(channelId: string): ConversationBinding | undefined {
252
+ return this.bindings.get(channelId);
285
253
  }
286
254
 
287
255
  /** Reverse lookup: the channel an agent is bound to, if any. */
@@ -296,8 +264,10 @@ export class ConversationRouter {
296
264
  return Array.from(this.bindings.values());
297
265
  }
298
266
 
299
- /** Bindings whose idle TTL has elapsed. Does not unbind — the framework
300
- * runs the closure turn first, then calls unbind(). */
267
+ /** Bindings whose idle TTL has elapsed. Does not unbind: the framework
268
+ * unbinds each one first, so the channel's next message goes to a newer
269
+ * fork, or nowhere, and then runs the old fork's closure turn
270
+ * (sweepExpiredConversations). */
301
271
  expired(now = Date.now()): ConversationBinding[] {
302
272
  const ttl = this.idleTtlMs;
303
273
  return this.getBindings().filter((b) => now - b.lastActivity >= ttl);
@@ -333,7 +303,10 @@ export class ConversationRouter {
333
303
  return Object.fromEntries(this.generations);
334
304
  }
335
305
 
336
- /** Restore persisted engagement counters (see exportGenerations). */
306
+ /** Raise each channel's engagement counter to at least the given
307
+ * generation, never lowering one: the persisted counters at startup (see
308
+ * exportGenerations), or a spawn's generation whose fork ended before the
309
+ * spawn could bind it. */
337
310
  hydrateGenerations(generations: Record<string, number>): void {
338
311
  for (const [channelId, generation] of Object.entries(generations)) {
339
312
  if (typeof generation === 'number' && generation > (this.generations.get(channelId) ?? 0)) {
@@ -349,25 +322,6 @@ export class ConversationRouter {
349
322
  }
350
323
  }
351
324
 
352
- /**
353
- * A channel as the router's bindings take it: its id, with the MCPL server it
354
- * belongs to where the caller has one. A bare id, as callers gave it before,
355
- * is the channel with no server.
356
- */
357
- export interface ConversationChannel {
358
- channelId: string;
359
- serverId?: string;
360
- }
361
-
362
- function asChannel(channel: string | ConversationChannel): ConversationChannel {
363
- return typeof channel === 'string' ? { channelId: channel } : channel;
364
- }
365
-
366
- /** A binding's key: its channel's server and id, unambiguous whatever either holds. */
367
- function bindingKey(channel: ConversationChannel): string {
368
- return JSON.stringify([channel.serverId ?? null, channel.channelId]);
369
- }
370
-
371
325
  /** The readable part of a fork's name stops here: uniqueness rides on the hash. */
372
326
  const FORK_NAME_READABLE_MAX = 64;
373
327
 
@@ -43,9 +43,12 @@ export function convertBlock(block: McplContentBlock, provenance: string, server
43
43
  // closed, withhold the data (checked BEFORE the data branch).
44
44
  return { type: 'text', text: INLINE_WITHHELD_TEXT };
45
45
  }
46
- if (isFilledString(block.uri) && block.disposition) {
46
+ if (isFilledString(block.uri) && block.disposition !== undefined) {
47
47
  // RFC-005 uri-form media with a disposition claim: stub, do not
48
- // hand the URI to the provider or inline it.
48
+ // hand the URI to the provider or inline it. Any disposition given
49
+ // is a claim, one the reference rejects included, as classifyBlock
50
+ // and isInlineContradiction read it: a falsy one such as 0, false
51
+ // or '' stubs too, rather than sending the URI as the image's source.
49
52
  return { type: 'text', text: referenceStubOrNull(block, provenance, serverId) ?? '[reference]' };
50
53
  }
51
54
  if (isFilledString(block.data) && isFilledString(block.mimeType)) {
@@ -68,9 +71,10 @@ export function convertBlock(block: McplContentBlock, provenance: string, server
68
71
  // closed, withhold the data (checked BEFORE the data branch).
69
72
  return { type: 'text', text: INLINE_WITHHELD_TEXT };
70
73
  }
71
- if (isFilledString(block.uri) && block.disposition) {
72
- // RFC-005 uri-form media with a disposition claim: stub, do not
73
- // hand the URI to the provider or inline it.
74
+ if (isFilledString(block.uri) && block.disposition !== undefined) {
75
+ // RFC-005 uri-form media with a disposition claim, a falsy one
76
+ // included, as for an image (above): stub, do not hand the URI to
77
+ // the provider or inline it.
74
78
  return { type: 'text', text: referenceStubOrNull(block, provenance, serverId) ?? '[reference]' };
75
79
  }
76
80
  if (isFilledString(block.data) && isFilledString(block.mimeType)) {
@@ -21,6 +21,8 @@
21
21
  * key: `eventId` is present only when the producer supplied one.
22
22
  */
23
23
 
24
+ import { createHash } from 'node:crypto';
25
+
24
26
  /** The MCPL admission lane that accepted an item. Its contract decides what
25
27
  * an `eventId` is worth: `push/event` deduplicates by eventId, and RFC-006
26
28
  * coalesced admission (`coalesced: true`) guarantees stable retries and
@@ -154,3 +156,132 @@ export function conversationKey(source: InboundSource): string | undefined {
154
156
  if (source.kind === 'surface') return JSON.stringify(['surface', source.surface]);
155
157
  return undefined;
156
158
  }
159
+
160
+ /**
161
+ * JSON with object keys sorted at every level, written as the store's append
162
+ * keeps a value (its native UTF-8 write, which the real-store digest table
163
+ * checks): an `undefined` object entry is dropped; a string is well-formed
164
+ * (each lone surrogate comes back as U+FFFD); and an entry whose key isn't
165
+ * well-formed is dropped (the store doesn't keep it). An `undefined` array
166
+ * element is written as `null`, as JSON writes it: the store's append
167
+ * refuses a body holding one, so no stored copy does. So a value
168
+ * hashes alike however its keys were ordered (content read back from the
169
+ * store has them in a different order than it was written), and alike
170
+ * before and after the store. editMessage writes JSON instead, so an edited
171
+ * copy can keep what an append wouldn't; it has changed anyway.
172
+ *
173
+ * It expects JSON-shaped values, as the MCPL lanes deliver them (parsed from
174
+ * JSON). Any other object is written by its own enumerable entries, unlike
175
+ * JSON.stringify: a Date becomes `{}`, as the store's own write keeps it, but
176
+ * a deferred write recovered from its JSON file brings it back as a string.
177
+ *
178
+ * sourceBodyDigest's serializer, kept out of the package API: serialization
179
+ * and framing both stay inside sourceBodyDigest, so consumers don't
180
+ * duplicate either (`canonicalJson(blocks)` alone hashes differently).
181
+ */
182
+ export function canonicalJson(value: unknown): string {
183
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
184
+ if (value && typeof value === 'object') {
185
+ const entries = Object.entries(value as Record<string, unknown>)
186
+ .filter(([k, v]) => v !== undefined && k.isWellFormed())
187
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
188
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
189
+ }
190
+ if (typeof value === 'string') return JSON.stringify(value.toWellFormed());
191
+ return JSON.stringify(value) ?? 'null';
192
+ }
193
+
194
+ /**
195
+ * A block as the store hands it back. Context-manager keeps inline media as
196
+ * blobs (blob-manager.ts): an image whose source isn't a URL, and every
197
+ * document, audio or video block, comes back as `{ type, source: { type:
198
+ * 'base64', data, mediaType } }` and nothing else, its data re-encoded from
199
+ * the decoded bytes, and an image's media type taken from its bytes'
200
+ * signature where they have one. Any other block comes back as it was given,
201
+ * except a store-internal `blob_ref` from an unresolved read, which resolves
202
+ * to inline media by default. It is digested as given here, so an unresolved
203
+ * read and its resolved form differ. Context-manager refuses caller-supplied
204
+ * blob_ref writes; the MCPL lanes never store one.
205
+ */
206
+ function storedBlock(block: unknown): unknown {
207
+ const media = block as { type?: unknown; source?: { type?: unknown; data?: unknown; mediaType?: unknown } } | null;
208
+ const blob = media?.type === 'image'
209
+ ? media.source?.type !== 'url'
210
+ : media?.type === 'document' || media?.type === 'audio' || media?.type === 'video';
211
+ const source = media?.source;
212
+ if (!blob || typeof source?.data !== 'string') return block;
213
+ const data = canonicalBase64(source.data);
214
+ const mediaType = media!.type === 'image'
215
+ ? sniffRasterImageMediaType(Buffer.from(data.slice(0, 32), 'base64')) ?? source.mediaType
216
+ : source.mediaType;
217
+ return { type: media!.type, source: { type: 'base64', data, mediaType } };
218
+ }
219
+
220
+ /**
221
+ * Base64 as the store writes it back from the decoded bytes: padded, with no
222
+ * whitespace, the standard alphabet and zero unused bits. Data already in
223
+ * that form, as every stored copy's is, is returned without decoding it all.
224
+ */
225
+ function canonicalBase64(data: string): string {
226
+ if (data.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(data)) {
227
+ const last = data.slice(-4);
228
+ if (Buffer.from(last, 'base64').toString('base64') === last) return data;
229
+ }
230
+ return Buffer.from(data, 'base64').toString('base64');
231
+ }
232
+
233
+ /**
234
+ * The store's own test for raster image types with an unambiguous byte
235
+ * signature, copied from context-manager's blob-manager.ts (it isn't
236
+ * exported): the store relabels an image whose bytes match one of these.
237
+ */
238
+ function sniffRasterImageMediaType(bytes: Uint8Array): string | null {
239
+ if (bytes.length >= 8 &&
240
+ bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47 &&
241
+ bytes[4] === 0x0d && bytes[5] === 0x0a && bytes[6] === 0x1a && bytes[7] === 0x0a) {
242
+ return 'image/png';
243
+ }
244
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
245
+ return 'image/jpeg';
246
+ }
247
+ if (bytes.length >= 6) {
248
+ const signature = Buffer.from(bytes.subarray(0, 6)).toString('ascii');
249
+ if (signature === 'GIF87a' || signature === 'GIF89a') return 'image/gif';
250
+ }
251
+ if (bytes.length >= 12 &&
252
+ Buffer.from(bytes.subarray(0, 4)).toString('ascii') === 'RIFF' &&
253
+ Buffer.from(bytes.subarray(8, 12)).toString('ascii') === 'WEBP') {
254
+ return 'image/webp';
255
+ }
256
+ return null;
257
+ }
258
+
259
+ /**
260
+ * The version identity of a delivered body (agreed with the receipts lane,
261
+ * room-220 #46752–#47210): SHA-256 hex of `canonicalJson([blocks])` over the
262
+ * ContentBlock[] the framework stores for the body — after MCPL conversion,
263
+ * before any host decoration is added and before storage shards it — with
264
+ * each block as the store keeps it (storedBlock, canonicalJson). So a body
265
+ * hashes alike as delivered, as handed to storage, and as read back from
266
+ * the store. The one-element array keeps the framing an undecorated,
267
+ * unsharded stored copy has always hashed to, so a stamped copy and an
268
+ * older copy of the same body share a version.
269
+ *
270
+ * Ingestion stamps it as `metadata.sourceBodyDigest`, and the same function
271
+ * over exactly the blocks handed to storage (decorations included) as
272
+ * `metadata.storedBodyDigest`, on both MCPL lanes. Both live outside the
273
+ * frozen admission envelope: they describe the body actually delivered (a
274
+ * materialization, a correction), not the admission. Neither proves later
275
+ * presence or completeness; an unsharded stored copy that no longer hashes
276
+ * to its storedBodyDigest was changed after delivery (editMessage keeps
277
+ * metadata). A body the store shards carries the whole body's two digests
278
+ * on every shard, and no shard hashes to them. Check a copy read back with
279
+ * its blobs resolved (getAllMessages, getMessage, or getMessageWindow
280
+ * without `resolveBlobs: false`): a blob reference never matches.
281
+ *
282
+ * Exported from the package root, so a consumer checks a copy against either
283
+ * field with this same function rather than a copy of it.
284
+ */
285
+ export function sourceBodyDigest(blocks: readonly unknown[]): string {
286
+ return createHash('sha256').update(canonicalJson([blocks.map(storedBlock)])).digest('hex');
287
+ }
package/src/mcpl/index.ts CHANGED
@@ -171,9 +171,11 @@ export { InferenceRouter } from './inference-router.js';
171
171
 
172
172
  // Channel registry (channel lifecycle, incoming messages, synthesized tools)
173
173
  export { ChannelRegistry } from './channel-registry.js';
174
+ export type { PublishRefusalReason } from './channel-registry.js';
174
175
 
175
- // Inbound source envelope: where each accepted item came from, stamped once
176
- export { INBOUND_SOURCE_KEY, readInboundSource, conversationKey } from './inbound-source.js';
176
+ // Inbound source envelope: where each accepted item came from, stamped once;
177
+ // and the digest of a delivered or stored body
178
+ export { INBOUND_SOURCE_KEY, readInboundSource, conversationKey, sourceBodyDigest } from './inbound-source.js';
177
179
  export type {
178
180
  InboundSource,
179
181
  InboundChannelSource,
@@ -55,6 +55,15 @@ export function logValueRedacted(value: unknown, redact: (text: string) => strin
55
55
  return printed(redact(textOf(value)), FIELD);
56
56
  }
57
57
 
58
+ /**
59
+ * A JSON-RPC error answer's code as a log line prints it: a number, as
60
+ * JSON-RPC says a code is, as logValue prints it; any other JSON value a
61
+ * server sends there redacted (logValueRedacted), since the server wrote it.
62
+ */
63
+ export function loggedCode(code: unknown, redact: (text: string) => string): string {
64
+ return typeof code === 'number' ? logValue(code) : logValueRedacted(code, redact);
65
+ }
66
+
58
67
  /**
59
68
  * The host's own message, as the free text that ends a log line after its
60
69
  * fields: `reason=coalesce-invalid field=payload.content: "content must be an