@animalabs/agent-framework 0.23.2 → 0.24.0

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 (338) hide show
  1. package/README.md +212 -42
  2. package/dist/src/agent.d.ts +60 -9
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +92 -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/code-execution/engine.d.ts +177 -0
  10. package/dist/src/code-execution/engine.d.ts.map +1 -0
  11. package/dist/src/code-execution/engine.js +494 -0
  12. package/dist/src/code-execution/engine.js.map +1 -0
  13. package/dist/src/code-execution/index.d.ts +5 -10
  14. package/dist/src/code-execution/index.d.ts.map +1 -1
  15. package/dist/src/code-execution/index.js +5 -9
  16. package/dist/src/code-execution/index.js.map +1 -1
  17. package/dist/src/code-execution/isolate/calls.d.ts +114 -0
  18. package/dist/src/code-execution/isolate/calls.d.ts.map +1 -0
  19. package/dist/src/code-execution/isolate/calls.js +145 -0
  20. package/dist/src/code-execution/isolate/calls.js.map +1 -0
  21. package/dist/src/code-execution/isolate/child-entry.d.ts +2 -0
  22. package/dist/src/code-execution/isolate/child-entry.d.ts.map +1 -0
  23. package/dist/src/code-execution/isolate/child-entry.js +5 -0
  24. package/dist/src/code-execution/isolate/child-entry.js.map +1 -0
  25. package/dist/src/code-execution/isolate/child-main.d.ts +26 -0
  26. package/dist/src/code-execution/isolate/child-main.d.ts.map +1 -0
  27. package/dist/src/code-execution/isolate/child-main.js +132 -0
  28. package/dist/src/code-execution/isolate/child-main.js.map +1 -0
  29. package/dist/src/code-execution/isolate/child.d.ts +44 -0
  30. package/dist/src/code-execution/isolate/child.d.ts.map +1 -0
  31. package/dist/src/code-execution/isolate/child.js +315 -0
  32. package/dist/src/code-execution/isolate/child.js.map +1 -0
  33. package/dist/src/code-execution/isolate/frames.d.ts +80 -0
  34. package/dist/src/code-execution/isolate/frames.d.ts.map +1 -0
  35. package/dist/src/code-execution/isolate/frames.js +210 -0
  36. package/dist/src/code-execution/isolate/frames.js.map +1 -0
  37. package/dist/src/code-execution/isolate/output.d.ts +68 -0
  38. package/dist/src/code-execution/isolate/output.d.ts.map +1 -0
  39. package/dist/src/code-execution/isolate/output.js +143 -0
  40. package/dist/src/code-execution/isolate/output.js.map +1 -0
  41. package/dist/src/code-execution/isolate/prelude.d.ts +109 -0
  42. package/dist/src/code-execution/isolate/prelude.d.ts.map +1 -0
  43. package/dist/src/code-execution/isolate/prelude.js +401 -0
  44. package/dist/src/code-execution/isolate/prelude.js.map +1 -0
  45. package/dist/src/code-execution/isolate/protocol.d.ts +151 -0
  46. package/dist/src/code-execution/isolate/protocol.d.ts.map +1 -0
  47. package/dist/src/code-execution/isolate/protocol.js +150 -0
  48. package/dist/src/code-execution/isolate/protocol.js.map +1 -0
  49. package/dist/src/code-execution/isolate/render.d.ts +57 -0
  50. package/dist/src/code-execution/isolate/render.d.ts.map +1 -0
  51. package/dist/src/code-execution/isolate/render.js +214 -0
  52. package/dist/src/code-execution/isolate/render.js.map +1 -0
  53. package/dist/src/code-execution/isolate/runner.d.ts +250 -0
  54. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -0
  55. package/dist/src/code-execution/isolate/runner.js +854 -0
  56. package/dist/src/code-execution/isolate/runner.js.map +1 -0
  57. package/dist/src/code-execution/isolate/scripts.d.ts +175 -0
  58. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -0
  59. package/dist/src/code-execution/isolate/scripts.js +357 -0
  60. package/dist/src/code-execution/isolate/scripts.js.map +1 -0
  61. package/dist/src/code-execution/quickjs.wasm +0 -0
  62. package/dist/src/code-execution/tool-definition.d.ts +93 -43
  63. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  64. package/dist/src/code-execution/tool-definition.js +176 -142
  65. package/dist/src/code-execution/tool-definition.js.map +1 -1
  66. package/dist/src/deadline-timer.d.ts +3 -0
  67. package/dist/src/deadline-timer.d.ts.map +1 -1
  68. package/dist/src/deadline-timer.js +1 -1
  69. package/dist/src/deadline-timer.js.map +1 -1
  70. package/dist/src/framework.d.ts +1027 -440
  71. package/dist/src/framework.d.ts.map +1 -1
  72. package/dist/src/framework.js +4093 -2238
  73. package/dist/src/framework.js.map +1 -1
  74. package/dist/src/gate/event-gate.d.ts +28 -1
  75. package/dist/src/gate/event-gate.d.ts.map +1 -1
  76. package/dist/src/gate/event-gate.js +88 -10
  77. package/dist/src/gate/event-gate.js.map +1 -1
  78. package/dist/src/held-writes.d.ts +16 -3
  79. package/dist/src/held-writes.d.ts.map +1 -1
  80. package/dist/src/held-writes.js +16 -3
  81. package/dist/src/held-writes.js.map +1 -1
  82. package/dist/src/index.d.ts +6 -6
  83. package/dist/src/index.d.ts.map +1 -1
  84. package/dist/src/index.js +9 -8
  85. package/dist/src/index.js.map +1 -1
  86. package/dist/src/mcpl/channel-registry.d.ts +471 -75
  87. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  88. package/dist/src/mcpl/channel-registry.js +1673 -337
  89. package/dist/src/mcpl/channel-registry.js.map +1 -1
  90. package/dist/src/mcpl/conversation-router.d.ts +12 -7
  91. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  92. package/dist/src/mcpl/conversation-router.js +12 -7
  93. package/dist/src/mcpl/conversation-router.js.map +1 -1
  94. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  95. package/dist/src/mcpl/convert-block.js +9 -5
  96. package/dist/src/mcpl/convert-block.js.map +1 -1
  97. package/dist/src/mcpl/inbound-source.d.ts +50 -0
  98. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  99. package/dist/src/mcpl/inbound-source.js +130 -0
  100. package/dist/src/mcpl/inbound-source.js.map +1 -1
  101. package/dist/src/mcpl/index.d.ts +2 -1
  102. package/dist/src/mcpl/index.d.ts.map +1 -1
  103. package/dist/src/mcpl/index.js +3 -2
  104. package/dist/src/mcpl/index.js.map +1 -1
  105. package/dist/src/mcpl/log-value.d.ts +6 -0
  106. package/dist/src/mcpl/log-value.d.ts.map +1 -1
  107. package/dist/src/mcpl/log-value.js +8 -0
  108. package/dist/src/mcpl/log-value.js.map +1 -1
  109. package/dist/src/mcpl/modern-connection.d.ts +15 -6
  110. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  111. package/dist/src/mcpl/modern-connection.js +168 -53
  112. package/dist/src/mcpl/modern-connection.js.map +1 -1
  113. package/dist/src/mcpl/prose-grammar.d.ts +4 -5
  114. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  115. package/dist/src/mcpl/prose-grammar.js +4 -5
  116. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  117. package/dist/src/mcpl/protocol-family.d.ts +97 -10
  118. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  119. package/dist/src/mcpl/protocol-family.js +173 -16
  120. package/dist/src/mcpl/protocol-family.js.map +1 -1
  121. package/dist/src/mcpl/push-coalescer.d.ts +132 -14
  122. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  123. package/dist/src/mcpl/push-coalescer.js +227 -50
  124. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  125. package/dist/src/mcpl/push-handler.d.ts +6 -1
  126. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  127. package/dist/src/mcpl/push-handler.js +4 -1
  128. package/dist/src/mcpl/push-handler.js.map +1 -1
  129. package/dist/src/mcpl/reference-fetcher.d.ts +2 -2
  130. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  131. package/dist/src/mcpl/reference-fetcher.js +4 -4
  132. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  133. package/dist/src/mcpl/server-connection.d.ts +50 -21
  134. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  135. package/dist/src/mcpl/server-connection.js +158 -74
  136. package/dist/src/mcpl/server-connection.js.map +1 -1
  137. package/dist/src/mcpl/tags.d.ts +24 -0
  138. package/dist/src/mcpl/tags.d.ts.map +1 -1
  139. package/dist/src/mcpl/tags.js +33 -0
  140. package/dist/src/mcpl/tags.js.map +1 -1
  141. package/dist/src/mcpl/tool-result-normalize.d.ts +2 -1
  142. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  143. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  144. package/dist/src/mcpl/transport.d.ts +55 -7
  145. package/dist/src/mcpl/transport.d.ts.map +1 -1
  146. package/dist/src/mcpl/transport.js +163 -34
  147. package/dist/src/mcpl/transport.js.map +1 -1
  148. package/dist/src/mcpl/types.d.ts +22 -6
  149. package/dist/src/mcpl/types.d.ts.map +1 -1
  150. package/dist/src/mcpl/types.js.map +1 -1
  151. package/dist/src/module-registry.d.ts +4 -2
  152. package/dist/src/module-registry.d.ts.map +1 -1
  153. package/dist/src/module-registry.js +100 -13
  154. package/dist/src/module-registry.js.map +1 -1
  155. package/dist/src/modules/discord/index.d.ts.map +1 -1
  156. package/dist/src/modules/discord/index.js +8 -13
  157. package/dist/src/modules/discord/index.js.map +1 -1
  158. package/dist/src/modules/history/index.d.ts.map +1 -1
  159. package/dist/src/modules/history/index.js +27 -4
  160. package/dist/src/modules/history/index.js.map +1 -1
  161. package/dist/src/modules/history/semantic.d.ts +15 -3
  162. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  163. package/dist/src/modules/history/semantic.js +27 -5
  164. package/dist/src/modules/history/semantic.js.map +1 -1
  165. package/dist/src/modules/index.d.ts +1 -1
  166. package/dist/src/modules/index.d.ts.map +1 -1
  167. package/dist/src/modules/index.js.map +1 -1
  168. package/dist/src/modules/unknown-tool.d.ts +4 -1
  169. package/dist/src/modules/unknown-tool.d.ts.map +1 -1
  170. package/dist/src/modules/unknown-tool.js +4 -1
  171. package/dist/src/modules/unknown-tool.js.map +1 -1
  172. package/dist/src/modules/workspace/branch-intent.d.ts +3 -1
  173. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  174. package/dist/src/modules/workspace/branch-intent.js +3 -1
  175. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  176. package/dist/src/modules/workspace/edit-plan.d.ts +265 -0
  177. package/dist/src/modules/workspace/edit-plan.d.ts.map +1 -0
  178. package/dist/src/modules/workspace/edit-plan.js +794 -0
  179. package/dist/src/modules/workspace/edit-plan.js.map +1 -0
  180. package/dist/src/modules/workspace/edit-text.d.ts +117 -0
  181. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -0
  182. package/dist/src/modules/workspace/edit-text.js +584 -0
  183. package/dist/src/modules/workspace/edit-text.js.map +1 -0
  184. package/dist/src/modules/workspace/effects.d.ts +5 -4
  185. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  186. package/dist/src/modules/workspace/effects.js +78 -25
  187. package/dist/src/modules/workspace/effects.js.map +1 -1
  188. package/dist/src/modules/workspace/index.d.ts +484 -61
  189. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  190. package/dist/src/modules/workspace/index.js +2173 -645
  191. package/dist/src/modules/workspace/index.js.map +1 -1
  192. package/dist/src/modules/workspace/observe.d.ts +213 -38
  193. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  194. package/dist/src/modules/workspace/observe.js +416 -36
  195. package/dist/src/modules/workspace/observe.js.map +1 -1
  196. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  197. package/dist/src/modules/workspace/reconcile.js +15 -8
  198. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  199. package/dist/src/modules/workspace/text.d.ts +32 -0
  200. package/dist/src/modules/workspace/text.d.ts.map +1 -0
  201. package/dist/src/modules/workspace/text.js +67 -0
  202. package/dist/src/modules/workspace/text.js.map +1 -0
  203. package/dist/src/modules/workspace/types.d.ts +210 -6
  204. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  205. package/dist/src/modules/workspace/types.js.map +1 -1
  206. package/dist/src/modules/workspace/watcher.d.ts +3 -2
  207. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  208. package/dist/src/modules/workspace/watcher.js.map +1 -1
  209. package/dist/src/operator-log.d.ts +52 -24
  210. package/dist/src/operator-log.d.ts.map +1 -1
  211. package/dist/src/operator-log.js.map +1 -1
  212. package/dist/src/recovery/discord-awareness-outbox.d.ts +496 -72
  213. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  214. package/dist/src/recovery/discord-awareness-outbox.js +1196 -289
  215. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  216. package/dist/src/recovery/offline-branch.d.ts +26 -1
  217. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  218. package/dist/src/recovery/offline-branch.js +164 -39
  219. package/dist/src/recovery/offline-branch.js.map +1 -1
  220. package/dist/src/recovery/recover-cli.js +23 -4
  221. package/dist/src/recovery/recover-cli.js.map +1 -1
  222. package/dist/src/send-tools.d.ts +11 -0
  223. package/dist/src/send-tools.d.ts.map +1 -0
  224. package/dist/src/send-tools.js +11 -0
  225. package/dist/src/send-tools.js.map +1 -0
  226. package/dist/src/tool-result-guard.d.ts +4 -3
  227. package/dist/src/tool-result-guard.d.ts.map +1 -1
  228. package/dist/src/tool-result-guard.js +5 -4
  229. package/dist/src/tool-result-guard.js.map +1 -1
  230. package/dist/src/tool-result-history.d.ts +1 -1
  231. package/dist/src/tool-result-history.js +1 -1
  232. package/dist/src/tune-out/coordinator.d.ts +21 -0
  233. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  234. package/dist/src/tune-out/coordinator.js +139 -37
  235. package/dist/src/tune-out/coordinator.js.map +1 -1
  236. package/dist/src/types/agent.d.ts +4 -0
  237. package/dist/src/types/agent.d.ts.map +1 -1
  238. package/dist/src/types/events.d.ts +62 -9
  239. package/dist/src/types/events.d.ts.map +1 -1
  240. package/dist/src/types/framework.d.ts +62 -58
  241. package/dist/src/types/framework.d.ts.map +1 -1
  242. package/dist/src/types/module.d.ts +40 -8
  243. package/dist/src/types/module.d.ts.map +1 -1
  244. package/dist/src/types/trace.d.ts +111 -10
  245. package/dist/src/types/trace.d.ts.map +1 -1
  246. package/dist/src/usage/types.d.ts +16 -0
  247. package/dist/src/usage/types.d.ts.map +1 -1
  248. package/dist/src/usage/usage-tracker.d.ts +14 -1
  249. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  250. package/dist/src/usage/usage-tracker.js +81 -41
  251. package/dist/src/usage/usage-tracker.js.map +1 -1
  252. package/package.json +6 -5
  253. package/src/agent.ts +93 -12
  254. package/src/api/server.ts +14 -12
  255. package/src/code-execution/engine.ts +595 -0
  256. package/src/code-execution/index.ts +5 -17
  257. package/src/code-execution/isolate/calls.ts +199 -0
  258. package/src/code-execution/isolate/child-entry.ts +5 -0
  259. package/src/code-execution/isolate/child-main.ts +132 -0
  260. package/src/code-execution/isolate/child.ts +327 -0
  261. package/src/code-execution/isolate/frames.ts +209 -0
  262. package/src/code-execution/isolate/output.ts +161 -0
  263. package/src/code-execution/isolate/prelude.ts +409 -0
  264. package/src/code-execution/isolate/protocol.ts +240 -0
  265. package/src/code-execution/isolate/render.ts +230 -0
  266. package/src/code-execution/isolate/runner.ts +911 -0
  267. package/src/code-execution/isolate/scripts.ts +479 -0
  268. package/src/code-execution/quickjs.wasm +0 -0
  269. package/src/code-execution/quickjs.wasm.LICENSES +149 -0
  270. package/src/code-execution/tool-definition.ts +236 -167
  271. package/src/deadline-timer.ts +1 -1
  272. package/src/framework.ts +4468 -2418
  273. package/src/gate/event-gate.ts +78 -11
  274. package/src/held-writes.ts +17 -3
  275. package/src/index.ts +30 -22
  276. package/src/mcpl/channel-registry.ts +1825 -368
  277. package/src/mcpl/conversation-router.ts +12 -7
  278. package/src/mcpl/convert-block.ts +9 -5
  279. package/src/mcpl/inbound-source.ts +131 -0
  280. package/src/mcpl/index.ts +4 -2
  281. package/src/mcpl/log-value.ts +9 -0
  282. package/src/mcpl/modern-connection.ts +162 -53
  283. package/src/mcpl/prose-grammar.ts +4 -5
  284. package/src/mcpl/protocol-family.ts +199 -21
  285. package/src/mcpl/push-coalescer.ts +306 -62
  286. package/src/mcpl/push-handler.ts +10 -2
  287. package/src/mcpl/reference-fetcher.ts +4 -4
  288. package/src/mcpl/server-connection.ts +180 -79
  289. package/src/mcpl/tags.ts +36 -0
  290. package/src/mcpl/tool-result-normalize.ts +2 -1
  291. package/src/mcpl/transport.ts +162 -33
  292. package/src/mcpl/types.ts +23 -6
  293. package/src/module-registry.ts +92 -13
  294. package/src/modules/discord/index.ts +8 -13
  295. package/src/modules/history/index.ts +25 -3
  296. package/src/modules/history/semantic.ts +29 -6
  297. package/src/modules/index.ts +1 -0
  298. package/src/modules/unknown-tool.ts +4 -1
  299. package/src/modules/workspace/branch-intent.ts +3 -1
  300. package/src/modules/workspace/edit-plan.ts +954 -0
  301. package/src/modules/workspace/edit-text.ts +618 -0
  302. package/src/modules/workspace/effects.ts +70 -19
  303. package/src/modules/workspace/index.ts +2247 -642
  304. package/src/modules/workspace/observe.ts +465 -56
  305. package/src/modules/workspace/reconcile.ts +14 -7
  306. package/src/modules/workspace/text.ts +68 -0
  307. package/src/modules/workspace/types.ts +211 -6
  308. package/src/modules/workspace/watcher.ts +3 -2
  309. package/src/operator-log.ts +47 -19
  310. package/src/recovery/discord-awareness-outbox.ts +1500 -356
  311. package/src/recovery/offline-branch.ts +189 -55
  312. package/src/recovery/recover-cli.ts +25 -4
  313. package/src/send-tools.ts +11 -0
  314. package/src/tool-result-guard.ts +6 -5
  315. package/src/tool-result-history.ts +1 -1
  316. package/src/tune-out/coordinator.ts +163 -42
  317. package/src/types/agent.ts +4 -0
  318. package/src/types/events.ts +62 -9
  319. package/src/types/framework.ts +62 -58
  320. package/src/types/module.ts +40 -8
  321. package/src/types/trace.ts +121 -15
  322. package/src/usage/types.ts +16 -0
  323. package/src/usage/usage-tracker.ts +86 -43
  324. package/dist/src/code-execution/py-runner.d.ts +0 -191
  325. package/dist/src/code-execution/py-runner.d.ts.map +0 -1
  326. package/dist/src/code-execution/py-runner.js +0 -655
  327. package/dist/src/code-execution/py-runner.js.map +0 -1
  328. package/dist/src/code-execution/runtime-py.d.ts +0 -50
  329. package/dist/src/code-execution/runtime-py.d.ts.map +0 -1
  330. package/dist/src/code-execution/runtime-py.js +0 -677
  331. package/dist/src/code-execution/runtime-py.js.map +0 -1
  332. package/dist/src/mcpl/prose-stream-router.d.ts +0 -88
  333. package/dist/src/mcpl/prose-stream-router.d.ts.map +0 -1
  334. package/dist/src/mcpl/prose-stream-router.js +0 -291
  335. package/dist/src/mcpl/prose-stream-router.js.map +0 -1
  336. package/src/code-execution/py-runner.ts +0 -791
  337. package/src/code-execution/runtime-py.ts +0 -677
  338. 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,
@@ -264,8 +264,10 @@ export class ConversationRouter {
264
264
  return Array.from(this.bindings.values());
265
265
  }
266
266
 
267
- /** Bindings whose idle TTL has elapsed. Does not unbind — the framework
268
- * 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). */
269
271
  expired(now = Date.now()): ConversationBinding[] {
270
272
  const ttl = this.idleTtlMs;
271
273
  return this.getBindings().filter((b) => now - b.lastActivity >= ttl);
@@ -301,7 +303,10 @@ export class ConversationRouter {
301
303
  return Object.fromEntries(this.generations);
302
304
  }
303
305
 
304
- /** 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. */
305
310
  hydrateGenerations(generations: Record<string, number>): void {
306
311
  for (const [channelId, generation] of Object.entries(generations)) {
307
312
  if (typeof generation === 'number' && generation > (this.generations.get(channelId) ?? 0)) {
@@ -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