@animalabs/agent-framework 0.26.2 → 0.28.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 (371) hide show
  1. package/README.md +99 -8
  2. package/dist/src/agent.d.ts +291 -23
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +596 -61
  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 +5 -4
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/auto-continue.d.ts +34 -0
  10. package/dist/src/auto-continue.d.ts.map +1 -0
  11. package/dist/src/auto-continue.js +36 -0
  12. package/dist/src/auto-continue.js.map +1 -0
  13. package/dist/src/code-execution/isolate/scripts.d.ts +11 -0
  14. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  15. package/dist/src/code-execution/isolate/scripts.js +5 -1
  16. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  17. package/dist/src/cut-off-round.d.ts +24 -0
  18. package/dist/src/cut-off-round.d.ts.map +1 -0
  19. package/dist/src/cut-off-round.js +87 -0
  20. package/dist/src/cut-off-round.js.map +1 -0
  21. package/dist/src/framework.d.ts +1038 -193
  22. package/dist/src/framework.d.ts.map +1 -1
  23. package/dist/src/framework.js +5967 -1514
  24. package/dist/src/framework.js.map +1 -1
  25. package/dist/src/gate/event-gate.d.ts +405 -46
  26. package/dist/src/gate/event-gate.d.ts.map +1 -1
  27. package/dist/src/gate/event-gate.js +2646 -399
  28. package/dist/src/gate/event-gate.js.map +1 -1
  29. package/dist/src/gate/index.d.ts +3 -2
  30. package/dist/src/gate/index.d.ts.map +1 -1
  31. package/dist/src/gate/index.js +2 -1
  32. package/dist/src/gate/index.js.map +1 -1
  33. package/dist/src/gate/recipe-reconcile.d.ts +103 -0
  34. package/dist/src/gate/recipe-reconcile.d.ts.map +1 -0
  35. package/dist/src/gate/recipe-reconcile.js +325 -0
  36. package/dist/src/gate/recipe-reconcile.js.map +1 -0
  37. package/dist/src/gate/reply-judge.d.ts +116 -0
  38. package/dist/src/gate/reply-judge.d.ts.map +1 -0
  39. package/dist/src/gate/reply-judge.js +467 -0
  40. package/dist/src/gate/reply-judge.js.map +1 -0
  41. package/dist/src/gate/types.d.ts +229 -8
  42. package/dist/src/gate/types.d.ts.map +1 -1
  43. package/dist/src/heartbeat/heartbeat.d.ts +184 -0
  44. package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
  45. package/dist/src/heartbeat/heartbeat.js +752 -0
  46. package/dist/src/heartbeat/heartbeat.js.map +1 -0
  47. package/dist/src/heartbeat/reminder-time.d.ts +5 -0
  48. package/dist/src/heartbeat/reminder-time.d.ts.map +1 -0
  49. package/dist/src/heartbeat/reminder-time.js +107 -0
  50. package/dist/src/heartbeat/reminder-time.js.map +1 -0
  51. package/dist/src/index.d.ts +10 -4
  52. package/dist/src/index.d.ts.map +1 -1
  53. package/dist/src/index.js +7 -2
  54. package/dist/src/index.js.map +1 -1
  55. package/dist/src/mcpl/capability-mask.d.ts.map +1 -1
  56. package/dist/src/mcpl/capability-mask.js +3 -2
  57. package/dist/src/mcpl/capability-mask.js.map +1 -1
  58. package/dist/src/mcpl/channel-registry.d.ts +433 -113
  59. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  60. package/dist/src/mcpl/channel-registry.js +1154 -435
  61. package/dist/src/mcpl/channel-registry.js.map +1 -1
  62. package/dist/src/mcpl/conversation-router.d.ts +16 -8
  63. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  64. package/dist/src/mcpl/conversation-router.js +29 -15
  65. package/dist/src/mcpl/conversation-router.js.map +1 -1
  66. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  67. package/dist/src/mcpl/convert-block.js +14 -2
  68. package/dist/src/mcpl/convert-block.js.map +1 -1
  69. package/dist/src/mcpl/hook-orchestrator.d.ts +7 -1
  70. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  71. package/dist/src/mcpl/hook-orchestrator.js +32 -7
  72. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  73. package/dist/src/mcpl/inbound-source.d.ts +26 -10
  74. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  75. package/dist/src/mcpl/inbound-source.js +40 -69
  76. package/dist/src/mcpl/inbound-source.js.map +1 -1
  77. package/dist/src/mcpl/index.d.ts +5 -1
  78. package/dist/src/mcpl/index.d.ts.map +1 -1
  79. package/dist/src/mcpl/index.js +4 -0
  80. package/dist/src/mcpl/index.js.map +1 -1
  81. package/dist/src/mcpl/inference-router.d.ts +14 -2
  82. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  83. package/dist/src/mcpl/inference-router.js +73 -28
  84. package/dist/src/mcpl/inference-router.js.map +1 -1
  85. package/dist/src/mcpl/prose-grammar.d.ts +4 -0
  86. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  87. package/dist/src/mcpl/prose-grammar.js +6 -0
  88. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  89. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  90. package/dist/src/mcpl/protocol-family.js +3 -0
  91. package/dist/src/mcpl/protocol-family.js.map +1 -1
  92. package/dist/src/mcpl/push-coalescer.d.ts +20 -2
  93. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  94. package/dist/src/mcpl/push-coalescer.js +33 -11
  95. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  96. package/dist/src/mcpl/reference-fetcher.d.ts +63 -42
  97. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  98. package/dist/src/mcpl/reference-fetcher.js +177 -145
  99. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  100. package/dist/src/mcpl/references.d.ts +380 -46
  101. package/dist/src/mcpl/references.d.ts.map +1 -1
  102. package/dist/src/mcpl/references.js +736 -77
  103. package/dist/src/mcpl/references.js.map +1 -1
  104. package/dist/src/mcpl/server-connection.d.ts +93 -4
  105. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  106. package/dist/src/mcpl/server-connection.js +176 -11
  107. package/dist/src/mcpl/server-connection.js.map +1 -1
  108. package/dist/src/mcpl/tags.d.ts +9 -2
  109. package/dist/src/mcpl/tags.d.ts.map +1 -1
  110. package/dist/src/mcpl/tags.js +12 -2
  111. package/dist/src/mcpl/tags.js.map +1 -1
  112. package/dist/src/mcpl/tool-lifecycle.d.ts +11 -0
  113. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  114. package/dist/src/mcpl/tool-lifecycle.js +21 -0
  115. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  116. package/dist/src/mcpl/tool-presence.d.ts +311 -0
  117. package/dist/src/mcpl/tool-presence.d.ts.map +1 -0
  118. package/dist/src/mcpl/tool-presence.js +697 -0
  119. package/dist/src/mcpl/tool-presence.js.map +1 -0
  120. package/dist/src/mcpl/tool-result-normalize.d.ts +9 -3
  121. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  122. package/dist/src/mcpl/tool-result-normalize.js +14 -6
  123. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  124. package/dist/src/mcpl/tool-signature.d.ts +46 -0
  125. package/dist/src/mcpl/tool-signature.d.ts.map +1 -0
  126. package/dist/src/mcpl/tool-signature.js +321 -0
  127. package/dist/src/mcpl/tool-signature.js.map +1 -0
  128. package/dist/src/mcpl/toolset-host.d.ts +251 -0
  129. package/dist/src/mcpl/toolset-host.d.ts.map +1 -0
  130. package/dist/src/mcpl/toolset-host.js +720 -0
  131. package/dist/src/mcpl/toolset-host.js.map +1 -0
  132. package/dist/src/mcpl/toolsets-module.d.ts +83 -0
  133. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -0
  134. package/dist/src/mcpl/toolsets-module.js +523 -0
  135. package/dist/src/mcpl/toolsets-module.js.map +1 -0
  136. package/dist/src/mcpl/transport.d.ts +2 -2
  137. package/dist/src/mcpl/transport.d.ts.map +1 -1
  138. package/dist/src/mcpl/transport.js +3 -2
  139. package/dist/src/mcpl/transport.js.map +1 -1
  140. package/dist/src/mcpl/types.d.ts +24 -2
  141. package/dist/src/mcpl/types.d.ts.map +1 -1
  142. package/dist/src/mcpl/types.js.map +1 -1
  143. package/dist/src/mcpl/visible-content.d.ts +7 -9
  144. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  145. package/dist/src/mcpl/visible-content.js +21 -9
  146. package/dist/src/mcpl/visible-content.js.map +1 -1
  147. package/dist/src/module-registry.d.ts +27 -9
  148. package/dist/src/module-registry.d.ts.map +1 -1
  149. package/dist/src/module-registry.js +77 -17
  150. package/dist/src/module-registry.js.map +1 -1
  151. package/dist/src/modules/discord/index.js +1 -1
  152. package/dist/src/modules/discord/index.js.map +1 -1
  153. package/dist/src/modules/history/index.d.ts +2 -1
  154. package/dist/src/modules/history/index.d.ts.map +1 -1
  155. package/dist/src/modules/history/index.js +73 -28
  156. package/dist/src/modules/history/index.js.map +1 -1
  157. package/dist/src/modules/history/semantic.d.ts +108 -6
  158. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  159. package/dist/src/modules/history/semantic.js +273 -57
  160. package/dist/src/modules/history/semantic.js.map +1 -1
  161. package/dist/src/modules/workspace/answers.d.ts +22 -0
  162. package/dist/src/modules/workspace/answers.d.ts.map +1 -0
  163. package/dist/src/modules/workspace/answers.js +123 -0
  164. package/dist/src/modules/workspace/answers.js.map +1 -0
  165. package/dist/src/modules/workspace/branch-intent.d.ts +5 -3
  166. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  167. package/dist/src/modules/workspace/branch-intent.js +6 -2
  168. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  169. package/dist/src/modules/workspace/direct-write.d.ts +184 -0
  170. package/dist/src/modules/workspace/direct-write.d.ts.map +1 -0
  171. package/dist/src/modules/workspace/direct-write.js +417 -0
  172. package/dist/src/modules/workspace/direct-write.js.map +1 -0
  173. package/dist/src/modules/workspace/disk-agreement.d.ts +33 -11
  174. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  175. package/dist/src/modules/workspace/disk-agreement.js +48 -17
  176. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  177. package/dist/src/modules/workspace/edit-text.d.ts +27 -12
  178. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
  179. package/dist/src/modules/workspace/edit-text.js +61 -29
  180. package/dist/src/modules/workspace/edit-text.js.map +1 -1
  181. package/dist/src/modules/workspace/effects.d.ts +10 -0
  182. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  183. package/dist/src/modules/workspace/effects.js +21 -14
  184. package/dist/src/modules/workspace/effects.js.map +1 -1
  185. package/dist/src/modules/workspace/index.d.ts +308 -78
  186. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  187. package/dist/src/modules/workspace/index.js +2103 -720
  188. package/dist/src/modules/workspace/index.js.map +1 -1
  189. package/dist/src/modules/workspace/observe.d.ts +75 -63
  190. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  191. package/dist/src/modules/workspace/observe.js +366 -180
  192. package/dist/src/modules/workspace/observe.js.map +1 -1
  193. package/dist/src/modules/workspace/read-page.d.ts +41 -0
  194. package/dist/src/modules/workspace/read-page.d.ts.map +1 -0
  195. package/dist/src/modules/workspace/read-page.js +219 -0
  196. package/dist/src/modules/workspace/read-page.js.map +1 -0
  197. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  198. package/dist/src/modules/workspace/reconcile.js +26 -16
  199. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  200. package/dist/src/modules/workspace/result-text.d.ts +68 -0
  201. package/dist/src/modules/workspace/result-text.d.ts.map +1 -0
  202. package/dist/src/modules/workspace/result-text.js +94 -0
  203. package/dist/src/modules/workspace/result-text.js.map +1 -0
  204. package/dist/src/modules/workspace/sync.d.ts +9 -0
  205. package/dist/src/modules/workspace/sync.d.ts.map +1 -1
  206. package/dist/src/modules/workspace/sync.js +14 -0
  207. package/dist/src/modules/workspace/sync.js.map +1 -1
  208. package/dist/src/modules/workspace/text.d.ts +29 -12
  209. package/dist/src/modules/workspace/text.d.ts.map +1 -1
  210. package/dist/src/modules/workspace/text.js +35 -33
  211. package/dist/src/modules/workspace/text.js.map +1 -1
  212. package/dist/src/modules/workspace/types.d.ts +137 -60
  213. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  214. package/dist/src/modules/workspace/types.js.map +1 -1
  215. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  216. package/dist/src/modules/workspace/watcher.js +8 -3
  217. package/dist/src/modules/workspace/watcher.js.map +1 -1
  218. package/dist/src/prose-drafts.d.ts +4 -3
  219. package/dist/src/prose-drafts.d.ts.map +1 -1
  220. package/dist/src/prose-drafts.js +3 -2
  221. package/dist/src/prose-drafts.js.map +1 -1
  222. package/dist/src/reasoning-loss-notice.d.ts +23 -0
  223. package/dist/src/reasoning-loss-notice.d.ts.map +1 -0
  224. package/dist/src/reasoning-loss-notice.js +50 -0
  225. package/dist/src/reasoning-loss-notice.js.map +1 -0
  226. package/dist/src/reasoning-loss.d.ts +24 -0
  227. package/dist/src/reasoning-loss.d.ts.map +1 -0
  228. package/dist/src/reasoning-loss.js +102 -0
  229. package/dist/src/reasoning-loss.js.map +1 -0
  230. package/dist/src/send-tools.d.ts +3 -2
  231. package/dist/src/send-tools.d.ts.map +1 -1
  232. package/dist/src/send-tools.js +3 -2
  233. package/dist/src/send-tools.js.map +1 -1
  234. package/dist/src/sent-rows.d.ts +59 -0
  235. package/dist/src/sent-rows.d.ts.map +1 -0
  236. package/dist/src/sent-rows.js +145 -0
  237. package/dist/src/sent-rows.js.map +1 -0
  238. package/dist/src/silent-heartbeat.d.ts +17 -10
  239. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  240. package/dist/src/silent-heartbeat.js.map +1 -1
  241. package/dist/src/stable-hash.d.ts +3 -0
  242. package/dist/src/stable-hash.d.ts.map +1 -0
  243. package/dist/src/stable-hash.js +15 -0
  244. package/dist/src/stable-hash.js.map +1 -0
  245. package/dist/src/stored-injections.d.ts +99 -0
  246. package/dist/src/stored-injections.d.ts.map +1 -0
  247. package/dist/src/stored-injections.js +106 -0
  248. package/dist/src/stored-injections.js.map +1 -0
  249. package/dist/src/text-as-stored.d.ts +4 -3
  250. package/dist/src/text-as-stored.d.ts.map +1 -1
  251. package/dist/src/text-as-stored.js +4 -3
  252. package/dist/src/text-as-stored.js.map +1 -1
  253. package/dist/src/thinking-drops.d.ts +74 -0
  254. package/dist/src/thinking-drops.d.ts.map +1 -0
  255. package/dist/src/thinking-drops.js +96 -0
  256. package/dist/src/thinking-drops.js.map +1 -0
  257. package/dist/src/tool-args.d.ts +12 -0
  258. package/dist/src/tool-args.d.ts.map +1 -1
  259. package/dist/src/tool-args.js +35 -1
  260. package/dist/src/tool-args.js.map +1 -1
  261. package/dist/src/tool-image-ledger.d.ts +9 -8
  262. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  263. package/dist/src/tool-image-ledger.js +9 -8
  264. package/dist/src/tool-image-ledger.js.map +1 -1
  265. package/dist/src/tool-presentation.d.ts.map +1 -1
  266. package/dist/src/tool-presentation.js +3 -1
  267. package/dist/src/tool-presentation.js.map +1 -1
  268. package/dist/src/tool-result-guard.d.ts +12 -6
  269. package/dist/src/tool-result-guard.d.ts.map +1 -1
  270. package/dist/src/tool-result-guard.js +44 -17
  271. package/dist/src/tool-result-guard.js.map +1 -1
  272. package/dist/src/tool-result-history.d.ts +176 -24
  273. package/dist/src/tool-result-history.d.ts.map +1 -1
  274. package/dist/src/tool-result-history.js +411 -91
  275. package/dist/src/tool-result-history.js.map +1 -1
  276. package/dist/src/tune-out/coordinator.d.ts +17 -9
  277. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  278. package/dist/src/tune-out/coordinator.js +84 -37
  279. package/dist/src/tune-out/coordinator.js.map +1 -1
  280. package/dist/src/tune-out/tools.d.ts +4 -1
  281. package/dist/src/tune-out/tools.d.ts.map +1 -1
  282. package/dist/src/types/agent.d.ts +217 -7
  283. package/dist/src/types/agent.d.ts.map +1 -1
  284. package/dist/src/types/events.d.ts +33 -9
  285. package/dist/src/types/events.d.ts.map +1 -1
  286. package/dist/src/types/framework.d.ts +39 -3
  287. package/dist/src/types/framework.d.ts.map +1 -1
  288. package/dist/src/types/module.d.ts +95 -9
  289. package/dist/src/types/module.d.ts.map +1 -1
  290. package/dist/src/types/trace.d.ts +91 -3
  291. package/dist/src/types/trace.d.ts.map +1 -1
  292. package/package.json +5 -4
  293. package/src/agent.ts +661 -67
  294. package/src/api/server.ts +5 -4
  295. package/src/auto-continue.ts +43 -0
  296. package/src/code-execution/isolate/scripts.ts +11 -3
  297. package/src/cut-off-round.ts +98 -0
  298. package/src/framework.ts +6168 -1540
  299. package/src/gate/event-gate.ts +2895 -405
  300. package/src/gate/index.ts +10 -1
  301. package/src/gate/recipe-reconcile.ts +355 -0
  302. package/src/gate/reply-judge.ts +512 -0
  303. package/src/gate/types.ts +234 -9
  304. package/src/heartbeat/heartbeat.ts +804 -0
  305. package/src/heartbeat/reminder-time.ts +107 -0
  306. package/src/index.ts +11 -4
  307. package/src/mcpl/capability-mask.ts +3 -2
  308. package/src/mcpl/channel-registry.ts +1222 -468
  309. package/src/mcpl/conversation-router.ts +34 -16
  310. package/src/mcpl/convert-block.ts +14 -1
  311. package/src/mcpl/hook-orchestrator.ts +33 -6
  312. package/src/mcpl/inbound-source.ts +42 -69
  313. package/src/mcpl/index.ts +6 -1
  314. package/src/mcpl/inference-router.ts +79 -34
  315. package/src/mcpl/prose-grammar.ts +7 -0
  316. package/src/mcpl/protocol-family.ts +2 -0
  317. package/src/mcpl/push-coalescer.ts +51 -13
  318. package/src/mcpl/reference-fetcher.ts +201 -162
  319. package/src/mcpl/references.ts +851 -77
  320. package/src/mcpl/server-connection.ts +189 -11
  321. package/src/mcpl/tags.ts +13 -2
  322. package/src/mcpl/tool-lifecycle.ts +21 -0
  323. package/src/mcpl/tool-presence.ts +716 -0
  324. package/src/mcpl/tool-result-normalize.ts +18 -6
  325. package/src/mcpl/tool-signature.ts +316 -0
  326. package/src/mcpl/toolset-host.ts +727 -0
  327. package/src/mcpl/toolsets-module.ts +545 -0
  328. package/src/mcpl/transport.ts +3 -2
  329. package/src/mcpl/types.ts +25 -2
  330. package/src/mcpl/visible-content.ts +22 -9
  331. package/src/module-registry.ts +85 -26
  332. package/src/modules/discord/index.ts +1 -1
  333. package/src/modules/history/index.ts +70 -30
  334. package/src/modules/history/semantic.ts +308 -54
  335. package/src/modules/workspace/answers.ts +133 -0
  336. package/src/modules/workspace/branch-intent.ts +10 -5
  337. package/src/modules/workspace/direct-write.ts +460 -0
  338. package/src/modules/workspace/disk-agreement.ts +52 -21
  339. package/src/modules/workspace/edit-text.ts +84 -35
  340. package/src/modules/workspace/effects.ts +22 -13
  341. package/src/modules/workspace/index.ts +2142 -723
  342. package/src/modules/workspace/observe.ts +381 -177
  343. package/src/modules/workspace/read-page.ts +228 -0
  344. package/src/modules/workspace/reconcile.ts +27 -15
  345. package/src/modules/workspace/result-text.ts +121 -0
  346. package/src/modules/workspace/sync.ts +16 -0
  347. package/src/modules/workspace/text.ts +58 -32
  348. package/src/modules/workspace/types.ts +137 -60
  349. package/src/modules/workspace/watcher.ts +8 -3
  350. package/src/prose-drafts.ts +4 -3
  351. package/src/reasoning-loss-notice.ts +62 -0
  352. package/src/reasoning-loss.ts +120 -0
  353. package/src/send-tools.ts +3 -2
  354. package/src/sent-rows.ts +162 -0
  355. package/src/silent-heartbeat.ts +17 -10
  356. package/src/stable-hash.ts +14 -0
  357. package/src/stored-injections.ts +131 -0
  358. package/src/text-as-stored.ts +4 -3
  359. package/src/thinking-drops.ts +135 -0
  360. package/src/tool-args.ts +34 -1
  361. package/src/tool-image-ledger.ts +9 -8
  362. package/src/tool-presentation.ts +3 -1
  363. package/src/tool-result-guard.ts +45 -17
  364. package/src/tool-result-history.ts +471 -90
  365. package/src/tune-out/coordinator.ts +80 -37
  366. package/src/tune-out/tools.ts +4 -1
  367. package/src/types/agent.ts +220 -7
  368. package/src/types/events.ts +33 -9
  369. package/src/types/framework.ts +36 -3
  370. package/src/types/module.ts +91 -9
  371. package/src/types/trace.ts +94 -3
package/dist/src/agent.js CHANGED
@@ -1,38 +1,57 @@
1
+ import { heartbeatToolAlias } from './heartbeat/heartbeat.js';
1
2
  import { isAbortedResponse } from '@animalabs/membrane';
2
- import { createHash } from 'node:crypto';
3
3
  import { ToolResultGuard } from './tool-result-guard.js';
4
4
  import { withoutTokenEstimates } from './thinking-token-stamp.js';
5
+ import { placeThinkingDrops } from './thinking-drops.js';
6
+ import { carriedBlock } from './sent-rows.js';
5
7
  import { injectionsAsStored } from './text-as-stored.js';
6
- import { assertBooleanSetting, holdsSettings } from './config-checks.js';
8
+ import { stableHash } from './stable-hash.js';
9
+ import { hasRenderedContent, storedInjectionMarkOf } from './stored-injections.js';
10
+ import { assertBooleanSetting, holdsSettings, kindOf } from './config-checks.js';
7
11
  import { CONTINUE_ROW, continueTurn, indexRequestOnlyRows, renderRequestOnlyRows, silentHeartbeatSeparatorTurn, tickKey, toolLoopPrompt, } from './silent-heartbeat.js';
8
12
  import { randomUUID } from 'node:crypto';
9
13
  import { resolveClockConfig } from './clock-rows.js';
10
14
  import { nonErrorResultText, truncateForHistory, DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS, } from './tool-result-history.js';
11
- function stableHash(value) {
12
- const encode = (item) => {
13
- if (Array.isArray(item))
14
- return `[${item.map(encode).join(',')}]`;
15
- if (item && typeof item === 'object') {
16
- const record = item;
17
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${encode(record[key])}`).join(',')}}`;
18
- }
19
- return JSON.stringify(item) ?? 'null';
20
- };
21
- return createHash('sha256').update(encode(value)).digest('hex');
15
+ /** The render stats of the dry compile behind each request a dry run
16
+ * assembled (`assembleActivationRequest` with `dryRun`), by that request.
17
+ * A request that is sent has none here. Read through the framework's
18
+ * `getRequestCompileStats`. */
19
+ const dryCompileStats = new WeakMap();
20
+ /** The render stats of the dry compile that built `request`, or null: see
21
+ * `AgentFramework.getRequestCompileStats`. */
22
+ export function dryCompileStatsOf(request) {
23
+ return dryCompileStats.get(request) ?? null;
24
+ }
25
+ /**
26
+ * Whether a request's messages, as sanitized, end on `agentName`'s own
27
+ * message, apart from `injectionTail` stored context injections after it
28
+ * (counted over the messages the sanitizer keeps).
29
+ */
30
+ function endsOnOwnMessage(messages, agentName, injectionTail) {
31
+ const own = messages[messages.length - 1 - injectionTail];
32
+ return own !== undefined && own.participant === agentName
33
+ && messages.slice(messages.length - injectionTail).every((m) => m.participant.startsWith('system_context:'));
34
+ }
35
+ /** A digest of a value as a request's JSON carries it (a field whose value is undefined left out, as the wire leaves
36
+ * it out), key order aside: the head digests the thinking binding compares, so a change on the wire changes one. */
37
+ function wireDigest(value) {
38
+ return stableHash(JSON.parse(JSON.stringify(value) ?? 'null'));
22
39
  }
23
40
  const DEFAULT_CONTEXT_BUDGET_TOKENS = 100_000;
24
41
  const DEFAULT_TRANSITION_PACE_TOKENS = 16_000;
25
42
  const DEFAULT_SAME_ROUND_THINK_TEXT_POLICY = 'public';
26
43
  /**
27
44
  * Refuse an agent config whose `promptCaching`, `toolResultGuard`,
28
- * `toolWrapperProseGuard`, `thinking.enabled` or `refusalHandling.autoRewind`
29
- * holds anything but true or false (assertBooleanSetting), or whose `thinking`
30
- * or `refusalHandling` is anything but an object, null or false
31
- * (holdsSettings). The constructor runs it first, so the rule holds wherever
32
- * an Agent is built. `create()` also runs it for each of its agents before
33
- * anything opens, and `createEphemeralAgent` before it reserves the name, so a
34
- * refusal there leaves nothing behind. Membrane reads `thinking.enabled`, and
35
- * the refusal is here because the setting is AgentConfig's.
45
+ * `toolWrapperProseGuard`, `thinking.enabled`, `thinkingBinding.dropBlock`,
46
+ * `thinkingBinding.binds` or `refusalHandling.autoRewind` holds anything but true or false
47
+ * (assertBooleanSetting, where null counts as left out), or whose `thinking`, `thinkingBinding` or
48
+ * `refusalHandling` is anything but an object, null or false (holdsSettings),
49
+ * or whose `autoContinue` resolveAutoContinue refuses. The constructor runs it
50
+ * first, so the rule holds wherever an Agent is built. `create()` also runs it
51
+ * for each of its agents before anything opens, and `createEphemeralAgent`
52
+ * before it reserves the name, so a refusal there leaves nothing behind.
53
+ * Membrane reads `thinking.enabled`, and the refusal is here because the
54
+ * setting is AgentConfig's.
36
55
  */
37
56
  export function assertAgentSwitches(config) {
38
57
  const agent = `Agent "${config.name}"`;
@@ -40,6 +59,12 @@ export function assertAgentSwitches(config) {
40
59
  if (holdsSettings(`${agent} thinking`, config.thinking, '{ enabled: true }')) {
41
60
  assertBooleanSetting(`${agent} thinking.enabled`, config.thinking.enabled, 'true asks the provider for extended thinking, and false or leaving it out doesn\'t');
42
61
  }
62
+ if (holdsSettings(`${agent} thinkingBinding`, config.thinkingBinding, '{ binds: true, dropBlock: true }')) {
63
+ assertBooleanSetting(`${agent} thinkingBinding.dropBlock`, config.thinkingBinding.dropBlock, 'true asks the provider to drop a signed thinking block whose earlier conversation changed rather than refuse the request, ' +
64
+ 'and false or leaving it out sends nothing, so the account\'s default applies');
65
+ assertBooleanSetting(`${agent} thinkingBinding.binds`, config.thinkingBinding.binds, 'true says the model binds its signed thinking to the conversation before it, so each compile tells the context manager so, ' +
66
+ 'and false or leaving it out compiles as before');
67
+ }
43
68
  if (holdsSettings(`${agent} refusalHandling`, config.refusalHandling, '{ autoRewind: true }')) {
44
69
  assertBooleanSetting(`${agent} refusalHandling.autoRewind`, config.refusalHandling.autoRewind, 'true redacts the turn that tripped a refusal and runs again, up to maxRewinds times, ' +
45
70
  'and false or leaving it out doesn\'t');
@@ -48,6 +73,45 @@ export function assertAgentSwitches(config) {
48
73
  'and false or leaving it out doesn\'t');
49
74
  assertBooleanSetting(`${agent} toolWrapperProseGuard`, config.toolWrapperProseGuard, 'true keeps a response whose whole prose is a tool-call-shaped wrapper from running or entering the agent\'s history, ' +
50
75
  'and false or leaving it out doesn\'t');
76
+ resolveAutoContinue(config);
77
+ }
78
+ /** `AutoContinueConfig.maxConsecutive` when left out. */
79
+ export const DEFAULT_AUTO_CONTINUE_MAX = 3;
80
+ /** The most continuations in a row a config may ask for. */
81
+ const MAX_AUTO_CONTINUE_MAX = 20;
82
+ /**
83
+ * `AgentConfig.autoContinue` resolved: undefined when off (left out, null or
84
+ * false), else its cap. `true` takes the default cap; an object takes
85
+ * `maxConsecutive`, a whole number from 1 to 20 (the default when left out).
86
+ * Any other value is refused.
87
+ */
88
+ export function resolveAutoContinue(config) {
89
+ const setting = `Agent "${config.name}" autoContinue`;
90
+ const value = config.autoContinue;
91
+ if (value === true)
92
+ return { maxConsecutive: DEFAULT_AUTO_CONTINUE_MAX };
93
+ if (value !== undefined && value !== null && value !== false && (typeof value !== 'object' || Array.isArray(value))) {
94
+ throw new Error(`${setting} must be true, false or an object such as { maxConsecutive: 3 }, not ${kindOf(value)}` +
95
+ (typeof value === 'string' ? ' (true and false go unquoted)' : ''));
96
+ }
97
+ if (!holdsSettings(setting, value, '{ maxConsecutive: 3 }'))
98
+ return undefined;
99
+ const max = value.maxConsecutive;
100
+ if (max === undefined)
101
+ return { maxConsecutive: DEFAULT_AUTO_CONTINUE_MAX };
102
+ if (typeof max !== 'number' || !Number.isInteger(max) || max < 1 || max > MAX_AUTO_CONTINUE_MAX) {
103
+ // The value's kind, never the value (config-checks): a `${VAR}` that
104
+ // lands here could be a credential.
105
+ const kind = typeof max !== 'number' ? kindOf(max)
106
+ : !Number.isInteger(max) ? 'a number that isn\'t whole'
107
+ : max < 1 ? 'a number below 1' : `a number above ${MAX_AUTO_CONTINUE_MAX}`;
108
+ throw new Error(`${setting}.maxConsecutive must be a whole number from 1 to ${MAX_AUTO_CONTINUE_MAX}, not ${kind}: ` +
109
+ 'the most turns in a row that continue without skip_reply before the agent waits for the next event');
110
+ }
111
+ return { maxConsecutive: max };
112
+ }
113
+ export function branchKey(branch) {
114
+ return `${branch.id}\u0000${branch.created}`;
51
115
  }
52
116
  /**
53
117
  * An agent wraps a context manager and manages inference state.
@@ -72,6 +136,12 @@ export class Agent {
72
136
  * blocks are preserved through Chronicle.
73
137
  */
74
138
  thinking;
139
+ /** Whether requests ask the provider to drop signed thinking whose earlier
140
+ * conversation changed (see AgentConfig.thinkingBinding.dropBlock). */
141
+ thinkingDropBlock;
142
+ /** Whether the model binds its signed thinking to the conversation before
143
+ * it (see AgentConfig.thinkingBinding.binds). */
144
+ thinkingBinds;
75
145
  /** Refusal auto-rewind policy (see AgentConfig.refusalHandling). */
76
146
  refusalHandling;
77
147
  toolResultGuard;
@@ -79,6 +149,9 @@ export class Agent {
79
149
  proseRouting;
80
150
  /** Exact whole-response known-tool wrapper containment (default off). */
81
151
  toolWrapperProseGuard;
152
+ /** Continuation after a turn the model ends itself (see
153
+ * AgentConfig.autoContinue); undefined when off. */
154
+ autoContinue;
82
155
  /** Clock rows' interval and precision (see AgentConfig.clock); undefined
83
156
  * when the agent writes none of its own (a resident still reads those
84
157
  * another resident writes into the shared slot). */
@@ -202,10 +275,13 @@ export class Agent {
202
275
  this.maxTokens = config.maxTokens ?? 4096;
203
276
  this.temperature = config.temperature;
204
277
  this.thinking = config.thinking;
278
+ this.thinkingDropBlock = config.thinkingBinding?.dropBlock === true;
279
+ this.thinkingBinds = config.thinkingBinding?.binds === true;
205
280
  this.refusalHandling = config.refusalHandling;
206
281
  this.toolResultGuard = new ToolResultGuard(config.name, contextManager, config.toolResultGuard === true);
207
282
  this.proseRouting = config.proseRouting ?? 'locus';
208
283
  this.toolWrapperProseGuard = config.toolWrapperProseGuard === true;
284
+ this.autoContinue = resolveAutoContinue(config);
209
285
  this.clock = resolveClockConfig(config.name, config.clock);
210
286
  this.cacheTtl = config.cacheTtl ?? '1h';
211
287
  this.promptCaching = config.promptCaching !== false;
@@ -213,7 +289,12 @@ export class Agent {
213
289
  this.providerParams = config.providerParams;
214
290
  this.sameRoundThinkTextPolicy = config.sameRoundThinkTextPolicy;
215
291
  this.maxStreamTokens = config.maxStreamTokens ?? 150_000;
216
- this.physicalWindowTokens = config.physicalWindowTokens;
292
+ // The window size converted as the projection's comparison (`projected > window`) converts it, so a string such
293
+ // as "200000" from a JSON or YAML config is its number, for every rule that reads it. A value whose number isn't
294
+ // finite is no window: null, NaN and Infinity, which the comparison never passed, and -Infinity, which it always
295
+ // did, a restart at every boundary.
296
+ const window = config.physicalWindowTokens == null ? NaN : Number(config.physicalWindowTokens);
297
+ this.physicalWindowTokens = Number.isFinite(window) ? window : undefined;
217
298
  this.contextBudgetTokens = config.contextBudgetTokens;
218
299
  this.configuredContextBudgetTokens = config.contextBudgetTokens;
219
300
  this.contextManager = contextManager;
@@ -253,7 +334,14 @@ export class Agent {
253
334
  if (this.allowedTools === 'all') {
254
335
  return true;
255
336
  }
256
- return this.allowedTools.includes(toolName);
337
+ if (this.allowedTools.includes(toolName))
338
+ return true;
339
+ // A heartbeat tool is one tool under either prefix: the framework's
340
+ // `heartbeat--x` and heartbeat-mcpl's `mcpl--heartbeat--x`, which an
341
+ // allowedTools written for the server names. Only one of them is ever
342
+ // offered, since the framework refuses the server beside its heartbeat.
343
+ const alias = heartbeatToolAlias(toolName);
344
+ return alias !== undefined && this.allowedTools.includes(alias);
257
345
  }
258
346
  /**
259
347
  * Check if a source can trigger inference for this agent.
@@ -845,6 +933,54 @@ export class Agent {
845
933
  getShownWatermark() {
846
934
  return this.shownWatermark;
847
935
  }
936
+ /**
937
+ * The newest consumed watermark a request carried that was answered: its
938
+ * round's tool calls, or its completion, came back. A message a boundary
939
+ * injected counts as heard only once a request carrying it is answered;
940
+ * the consumed watermark moves when the stream is handed it, before its
941
+ * request runs, so a request that fails, ending its stream with an error,
942
+ * leaves this as it was.
943
+ */
944
+ answeredWatermark = null;
945
+ getAnsweredWatermark() {
946
+ return this.answeredWatermark;
947
+ }
948
+ /** A request that carried `watermark` was answered. Requests are answered
949
+ * in the order they're sent, and the consumed watermark only advances
950
+ * within a branch, so the latest is the newest. */
951
+ markAnswered(watermark) {
952
+ if (watermark)
953
+ this.answeredWatermark = watermark;
954
+ }
955
+ /**
956
+ * Per destination (a server's channel, by its key), the newest consumed
957
+ * watermark the agent had read when it published there by a route the
958
+ * framework sees (its prose, a sleep's announcement, a channel_publish, a
959
+ * draft resend, or a send tool whose channel the framework records), on
960
+ * the branch it was taken on. A wake that carries a channel, unless it's
961
+ * marked ambient (InferenceRequest.ambient), keeps its turn unless the
962
+ * agent spoke there after reading the write the wake is for. It
963
+ * lives as long as this Agent: an agent made again starts it empty, and
964
+ * such a wake then stays.
965
+ */
966
+ publishedThrough = new Map();
967
+ /** Note a publish into `destination` (its key), made having read through
968
+ * `readThrough`: the consumed watermark when its words were written, or
969
+ * its call made. */
970
+ notePublished(destination, readThrough) {
971
+ if (!readThrough)
972
+ return;
973
+ const noted = this.publishedThrough.get(destination);
974
+ if (noted && noted.branch === readThrough.branch && noted.sequence >= readThrough.sequence)
975
+ return;
976
+ this.publishedThrough.set(destination, readThrough);
977
+ }
978
+ /** Whether the agent published into `destination` (its key) having read
979
+ * the row stored at `sequence` on `branch`. */
980
+ publishedAfterReading(destination, branch, sequence) {
981
+ const noted = this.publishedThrough.get(destination);
982
+ return !!noted && noted.branch === branch && noted.sequence >= sequence;
983
+ }
848
984
  /** Mark everything currently stored as consumed (the agent's creation, at
849
985
  * boot or later, and a tool boundary that injected messages into a live
850
986
  * stream declaring it carries them; see consumedWatermark). */
@@ -852,7 +988,9 @@ export class Agent {
852
988
  // Tolerates partial context-manager doubles (tests build Agents over
853
989
  // stubs). Without a watermark, a reader that looks for one takes its own
854
990
  // side: the coalescer counts the message as read, and the framework's
855
- // deferredWriteReadOrGone and unreadNoticesForPrimary count it as unread.
991
+ // unreadNoticesForPrimary counts it as unread. A wake for a deferred
992
+ // write or a stored message reads the answered watermark instead
993
+ // (deferredWriteDone, storedMessageDone).
856
994
  this.consumedWatermark = this.pendingWatermark();
857
995
  this.watermarkMarks++;
858
996
  this.shownWatermark = this.consumedWatermark;
@@ -895,6 +1033,22 @@ export class Agent {
895
1033
  this.consumedWatermark = watermark;
896
1034
  this.watermarkMarks++;
897
1035
  }
1036
+ /**
1037
+ * The branch now (`branchKey`) and its head, as `pendingWatermark` reads it: a message stored on that branch later
1038
+ * has a larger sequence. Sequences are numbered per branch, so one compares with another only on the same branch.
1039
+ */
1040
+ branchHead() {
1041
+ const head = this.pendingWatermark();
1042
+ const cm = this.contextManager;
1043
+ if (!head || typeof cm.currentBranchRef !== 'function')
1044
+ return undefined;
1045
+ try {
1046
+ return { branch: branchKey(cm.currentBranchRef()), sequence: head.sequence };
1047
+ }
1048
+ catch {
1049
+ return undefined;
1050
+ }
1051
+ }
898
1052
  /** The watermark a successful compile will establish (taken before, applied after). */
899
1053
  pendingWatermark() {
900
1054
  const cm = this.contextManager;
@@ -920,7 +1074,10 @@ export class Agent {
920
1074
  * manager commits what it commits for one, the consumed watermark moves,
921
1075
  * and a prepared budget change settles. To inspect what the agent would
922
1076
  * see, use the framework's `previewActivation`, which compiles as a dry
923
- * run. */
1077
+ * run. It isn't told the agent's thinking binding (`thinkingBinding.binds`),
1078
+ * so a context manager that keeps a thinking-binding record neither reads
1079
+ * nor writes the record here, and what it returns keeps thinking whose
1080
+ * earlier conversation changed. */
924
1081
  async compileContext(budget) {
925
1082
  // RFC-006 §3.3: consumption is determined at assembly — a compile that
926
1083
  // fails assembled nothing, so the watermark moves only on success.
@@ -942,6 +1099,211 @@ export class Agent {
942
1099
  this.applyCompileWatermark(watermark, marksBefore);
943
1100
  return result;
944
1101
  }
1102
+ /**
1103
+ * The stored context injections of this agent's that the last compile it
1104
+ * built to send rendered (stored-injections.ts), the framework's activation
1105
+ * request's or the direct API's: for each namespace and content, the parts
1106
+ * the compile held, and the branch it read. A compile built to send whose
1107
+ * request then isn't sent (a stop, a failure) still records: its rows stay
1108
+ * in the window all the same. Undefined before the first, as after a
1109
+ * restart. The framework's same-content skip reads it (renderedInjection).
1110
+ */
1111
+ renderedInjections;
1112
+ /**
1113
+ * Per branch (by id and created), the point a stored injection on it has
1114
+ * to come after to have been in no request yet (unsentInjection): the
1115
+ * newest row of this agent's window on that branch when the first plan
1116
+ * there since its last compile built to send (a start's, or a preview's,
1117
+ * which stores nothing) asked about a unit, before that plan's start
1118
+ * stored anything. Each compile built to send that records what it
1119
+ * held unsets its branch's, a restart unsets them all, and a plan asks
1120
+ * here before it stores a unit, so every one of this agent's stored
1121
+ * injections on a branch after its point was stored since that compile.
1122
+ * Kept per branch, so a start or preview on another branch doesn't move
1123
+ * this one's past a copy no request has had.
1124
+ */
1125
+ unsentSince = new Map();
1126
+ /**
1127
+ * A host's hook, called with the messages of each compile built to send
1128
+ * (never a dry run), as the compile is recorded: what the request carries,
1129
+ * after compression and the window have done their work. The framework
1130
+ * uses it to tell when an MCPL toolset's announcement has left the context
1131
+ * (RFC-014 §8.3 reminders). It must not change the messages; one that
1132
+ * throws is logged, and the start goes on.
1133
+ */
1134
+ onCompiledToSend;
1135
+ /**
1136
+ * A host's hook, called with each committing compile's `thinkingStrip` (never a dry run's) and the key of the
1137
+ * branch it read (`branchKey`), as the compile returns, whether or not its request is then sent, since the
1138
+ * context manager's binding record takes the strip either way. One that throws is logged. The framework sets it
1139
+ * at the agent's first stream unless a host has set one, so a host's own observer stands in for the framework's,
1140
+ * whose `[reasoning]` line for the agent then isn't told.
1141
+ */
1142
+ onThinkingStrip;
1143
+ /**
1144
+ * The newest content of `namespace` among this agent's rows stored since
1145
+ * its last compile built to send (unsentSince), and whether every part of
1146
+ * it is among them: rows a start stored whose compile then didn't record
1147
+ * (it threw, or the start failed before it), which no request has had
1148
+ * yet, and which are newer than anything renderedInjection knows. Undefined when there are none, as at every
1149
+ * start whose last compile was built and recorded. A fold between that
1150
+ * start and the next (a host's own committing compile, or the compression
1151
+ * an over-budget retry waits for) can summarize them all the same: the
1152
+ * next request then has the summary's word for them, and the start after
1153
+ * it stores them again, as after any fold. The window is read back 64
1154
+ * rows at a time from the newest, stopping at that point.
1155
+ */
1156
+ unsentInjection(namespace) {
1157
+ let newest;
1158
+ try {
1159
+ const branch = branchKey(this.contextManager.currentBranchRef());
1160
+ const since = this.unsentSince.get(branch);
1161
+ if (since === undefined) {
1162
+ const count = this.contextManager.getMessageCount();
1163
+ const top = count > 0 ? this.contextManager.getMessageWindow(count - 1, 1, { resolveBlobs: false }).messages[0]?.sequence ?? 0 : 0;
1164
+ this.unsentSince.set(branch, top);
1165
+ return undefined;
1166
+ }
1167
+ scan: for (let end = this.contextManager.getMessageCount(); end > 0;) {
1168
+ const span = Math.min(end, 64);
1169
+ const { messages } = this.contextManager.getMessageWindow(end - span, span, { resolveBlobs: false });
1170
+ for (let i = messages.length - 1; i >= 0; i--) {
1171
+ const row = messages[i];
1172
+ if (row.sequence <= since)
1173
+ break scan;
1174
+ const mark = storedInjectionMarkOf(row.metadata);
1175
+ if (mark?.agent !== this.name || mark.namespace !== namespace)
1176
+ continue;
1177
+ // Read from the newest back, the first copy met is the newest
1178
+ // content; its other parts pool with it.
1179
+ newest ??= { contentHash: mark.contentHash, parts: mark.parts, held: new Set() };
1180
+ if (mark.contentHash === newest.contentHash)
1181
+ newest.held.add(mark.part);
1182
+ }
1183
+ end -= span;
1184
+ }
1185
+ }
1186
+ catch (err) {
1187
+ // Where the rows read before the failure held a copy, the newest of
1188
+ // them is still the newest content (any unread row is older), and a
1189
+ // part it lacks may be unread, so it's stored again at worst. Where
1190
+ // they held none, the last request's record decides, as it did before
1191
+ // this check: a copy a failed start stored is then stored again, or,
1192
+ // where the source has changed back to what that request held, left
1193
+ // newest for one more request. Either way the start goes on, where
1194
+ // failing here would lose its turn.
1195
+ console.error(`[injection] ${this.name}: could not read its window for copies stored since its last request, so ${newest ? 'the rows it read decide' : "that request's record decides"}:`, err);
1196
+ if (!newest)
1197
+ return undefined;
1198
+ }
1199
+ return newest && { contentHash: newest.contentHash, whole: newest.held.size === newest.parts };
1200
+ }
1201
+ /** Record which of this agent's stored injections a compile built to send
1202
+ * rendered. Another agent's stored rows never reach this agent's compile,
1203
+ * since every compile of a shared store leaves them out by its view
1204
+ * filter (isAnotherAgentsInjection): a compile of a shared store without
1205
+ * that filter would mix them in here. */
1206
+ noteRenderedInjections(result) {
1207
+ // The host's look at what this request carries (onCompiledToSend).
1208
+ if (this.onCompiledToSend) {
1209
+ try {
1210
+ this.onCompiledToSend(result.messages);
1211
+ }
1212
+ catch (err) {
1213
+ console.error(`[agent] ${this.name}: the host's compile observer failed:`, err);
1214
+ }
1215
+ }
1216
+ const provenance = result.provenance;
1217
+ // The rows stored before this compile on its branch have been compiled
1218
+ // to send, whether the request held them or a fold or the strategy's
1219
+ // window took them (the record below says which): the first plan there
1220
+ // after it, a start's or a preview's, sets the point again
1221
+ // (unsentInjection). Without provenance, which branch isn't known, so
1222
+ // every branch's point goes, rather than one surviving a compile that
1223
+ // held its rows.
1224
+ if (provenance)
1225
+ this.unsentSince.delete(branchKey(provenance.branch));
1226
+ else
1227
+ this.unsentSince.clear();
1228
+ if (!provenance) {
1229
+ this.renderedInjections = undefined;
1230
+ return;
1231
+ }
1232
+ const byNamespace = new Map();
1233
+ for (const [i, source] of provenance.messages.entries()) {
1234
+ // Only a message rendered as an injection's participant can be one, so
1235
+ // the store is read for those alone.
1236
+ if (source.kind !== 'raw' || !result.messages[i]?.participant.startsWith('system_context:'))
1237
+ continue;
1238
+ for (const body of source.bodies) {
1239
+ const mark = this.storedMarkOf(body.messageId);
1240
+ if (!mark)
1241
+ continue;
1242
+ // A part counts as held when all its shards are there. One the
1243
+ // strategy altered (cut at its per-message cap, an image stripped)
1244
+ // counts too: a new copy would meet the same, so storing again
1245
+ // would only add copies, every start. A part missing shards still
1246
+ // says which content is newest.
1247
+ const held = body.complete || (body.missing !== undefined && !body.missing.includes('shards'));
1248
+ const contents = byNamespace.get(mark.namespace) ?? new Map();
1249
+ byNamespace.set(mark.namespace, contents);
1250
+ // Copies of the same content in view pool what they hold: the agent
1251
+ // has each part that one of them holds.
1252
+ const known = contents.get(mark.contentHash) ?? { parts: mark.parts, held: new Set(), sequence: body.sequence };
1253
+ if (held)
1254
+ known.held.add(mark.part);
1255
+ known.sequence = Math.max(known.sequence, body.sequence);
1256
+ contents.set(mark.contentHash, known);
1257
+ }
1258
+ }
1259
+ this.renderedInjections = { branch: provenance.branch, byNamespace };
1260
+ }
1261
+ /**
1262
+ * The newest content of `namespace` among this agent's stored injections
1263
+ * that the last compile built to send rendered, by its newest copy: its
1264
+ * content hash, and whether every part of that content was there in some
1265
+ * copy, every shard, however the strategy shaped it. Undefined when none
1266
+ * was, when this agent hasn't built a request since it started, or when
1267
+ * the window's branch has changed since.
1268
+ */
1269
+ renderedInjection(namespace) {
1270
+ const rendered = this.renderedInjections;
1271
+ const branch = this.contextManager.currentBranchRef();
1272
+ if (!rendered || rendered.branch.id !== branch.id || rendered.branch.created !== branch.created)
1273
+ return undefined;
1274
+ let newest;
1275
+ for (const entry of rendered.byNamespace.get(namespace) ?? []) {
1276
+ if (!newest || entry[1].sequence > newest[1].sequence)
1277
+ newest = entry;
1278
+ }
1279
+ return newest ? { contentHash: newest[0], whole: newest[1].held.size === newest[1].parts } : undefined;
1280
+ }
1281
+ /**
1282
+ * Whether compiled message `i` of `result` is a row of this agent's stored
1283
+ * context injections (stored-injections.ts): rendered as an injection's
1284
+ * participant, and each of its stored rows, by the compile's provenance,
1285
+ * carrying this agent's mark. A speaker merely named like an injection's
1286
+ * participant isn't one. Only such a message's rows are read, so a start
1287
+ * reads the store for those alone.
1288
+ */
1289
+ isStoredInjectionRow(result, i) {
1290
+ const source = result.provenance?.messages[i];
1291
+ return source?.kind === 'raw' && source.bodies.length > 0
1292
+ && !!result.messages[i]?.participant.startsWith('system_context:')
1293
+ && source.bodies.every((b) => this.storedMarkOf(b.messageId)?.agent === this.name);
1294
+ }
1295
+ /** A stored row's injection mark, or undefined for any other row, and for
1296
+ * one the store can't read just now, which is logged: a start doesn't fail
1297
+ * on it (the row then counts as no stored injection). */
1298
+ storedMarkOf(id) {
1299
+ try {
1300
+ return storedInjectionMarkOf(this.contextManager.getMessage(id)?.metadata);
1301
+ }
1302
+ catch (err) {
1303
+ console.error(`[injection] ${this.name}: could not read stored row ${id}, so it counts as no stored injection:`, err);
1304
+ return undefined;
1305
+ }
1306
+ }
945
1307
  /**
946
1308
  * Compile the context with injections (e.g. from MCPL servers), which the
947
1309
  * context manager merges into the compiled messages. Without
@@ -953,9 +1315,8 @@ export class Agent {
953
1315
  async compileWithInjections(budget, injections, opts) {
954
1316
  // A dry run is a compile that will never be sent. The context manager
955
1317
  // persists no fold resolutions for it and queues none of the compression
956
- // or merge work its fold plan calls for. Its thinking binding
957
- // (context-manager#160, not yet released) will commit stamps and add a
958
- // compile awaiting acceptance for a committing compile only. Here nothing
1318
+ // or merge work its fold plan calls for, and, with the thinking binding
1319
+ // (`thinkingBinding.binds`), writes no binding record. Here nothing
959
1320
  // moves either: the agent never read what a dry run assembled, so the
960
1321
  // consumed watermark stays. A dry run proves nothing about the budget
961
1322
  // either, so it goes straight to the compile, around
@@ -990,6 +1351,14 @@ export class Agent {
990
1351
  }
991
1352
  if (watermark)
992
1353
  this.applyCompileWatermark(watermark, marksBefore);
1354
+ if (!opts?.dryRun && result.thinkingStrip && this.onThinkingStrip) {
1355
+ try {
1356
+ this.onThinkingStrip(result.thinkingStrip, result.provenance ? branchKey(result.provenance.branch) : '');
1357
+ }
1358
+ catch (err) {
1359
+ console.error(`[agent] ${this.name}: the host's thinking-strip observer failed:`, err);
1360
+ }
1361
+ }
993
1362
  return result;
994
1363
  }
995
1364
  // ==========================================================================
@@ -997,14 +1366,22 @@ export class Agent {
997
1366
  // ==========================================================================
998
1367
  /**
999
1368
  * Run inference and return result with tool calls and speech content.
1000
- * Updates agent state during execution.
1369
+ * Updates agent state during execution. Its compile commits. With
1370
+ * `thinkingBinding.binds` it also writes the context manager's
1371
+ * thinking-binding record, so then call it only while none of this agent's
1372
+ * requests is streaming: a stream open meanwhile then has its later notes
1373
+ * write nothing, and its replies go without their thinking at the next
1374
+ * compile. Its own reply is never noted, so with `binds` its thinking is
1375
+ * left out at the compile after it's stored.
1001
1376
  */
1002
1377
  async runInference(availableTools, budget, options = {}) {
1003
1378
  return this.runInferenceWithInjections(availableTools, undefined, budget, options);
1004
1379
  }
1005
1380
  /**
1006
1381
  * Run inference with context injections.
1007
- * Same as runInference but passes injections through to compile.
1382
+ * Same as runInference but passes injections through to compile. Its
1383
+ * compile commits, and with `thinkingBinding.binds` writes the
1384
+ * thinking-binding record, as runInference's does (see there).
1008
1385
  */
1009
1386
  async runInferenceWithInjections(availableTools, injections, budget, options) {
1010
1387
  if (this._state.status === 'inferring') {
@@ -1037,8 +1414,13 @@ export class Agent {
1037
1414
  // been retried; one whose store still fails is compiled as the bare
1038
1415
  // notice.
1039
1416
  let unguardedResults;
1417
+ const binding = this.bindingCompileOptions(tools, (injections ?? [])
1418
+ .filter((injection) => injection.position === 'system')
1419
+ .flatMap((injection) => injection.content));
1040
1420
  const assemble = async () => {
1041
- const compiled = await this.toolResultGuard.afterAnnotations(() => this.compileWithInjections(budget, injections));
1421
+ const compiled = await this.toolResultGuard.afterAnnotations(() => this.compileWithInjections(budget, injections, binding.thinkingBinding ? binding : undefined));
1422
+ // What it holds of the stored injections is what the agent last saw.
1423
+ this.noteRenderedInjections(compiled);
1042
1424
  const { systemInjections } = compiled;
1043
1425
  // The framework's own pricing stamp never goes out (thinking-token-stamp.ts).
1044
1426
  const messages = withoutTokenEstimates(compiled.messages);
@@ -1154,7 +1536,10 @@ export class Agent {
1154
1536
  * watermark); a preview passes `options.dryRun`, so its compile commits
1155
1537
  * none of that (see `compileWithInjections` for what a dry run still
1156
1538
  * leaves). Either way the context manager is handed the compression tool
1157
- * definitions. Safe to call regardless of the agent's current status.
1539
+ * definitions. Safe to call regardless of the agent's current status,
1540
+ * except that with `thinkingBinding.binds` a compile that isn't a dry run
1541
+ * writes the context manager's binding record, so while one of the
1542
+ * agent's requests streams, a call to look passes `options.dryRun`.
1158
1543
  * It doesn't wait for the tool-result guard's annotations, so a preview
1159
1544
  * never waits on a workspace write (rollback, undo and redo report one), and
1160
1545
  * may show a withheld stub before its place is added; the activation waits
@@ -1185,12 +1570,63 @@ export class Agent {
1185
1570
  system: this.buildSystemPrompt(prospectiveSystemInjections),
1186
1571
  })
1187
1572
  : undefined;
1188
- let { messages, systemInjections } = await this.compileWithInjections(budget, injections, immutablePrefixHash || options.dryRun
1573
+ const binding = this.bindingCompileOptions(availableTools, prospectiveSystemInjections);
1574
+ const compiledResult = await this.compileWithInjections(budget, injections, immutablePrefixHash || options.dryRun || binding.thinkingBinding
1189
1575
  ? {
1190
1576
  ...(immutablePrefixHash ? { kvUnifiedImmutablePrefixHash: immutablePrefixHash } : {}),
1191
1577
  ...(options.dryRun ? { dryRun: true } : {}),
1578
+ ...binding,
1192
1579
  }
1193
1580
  : undefined);
1581
+ let { messages } = compiledResult;
1582
+ const { systemInjections } = compiledResult;
1583
+ // It counts what the compile selected, so a preview's stand-ins
1584
+ // (appendedMessages, below) aren't in it.
1585
+ const { renderStats } = compiledResult;
1586
+ // What the request is built from, as the compile returned it: a block the provider drops is found again in
1587
+ // it, through the copies below, by its signature (placeThinkingDrops).
1588
+ const recordedAfter = compiledResult.thinkingRecordedAfter;
1589
+ const compiled = {
1590
+ messages: compiledResult.messages,
1591
+ ...(compiledResult.provenance ? { provenance: compiledResult.provenance } : {}),
1592
+ ...(recordedAfter !== undefined ? { recordedAfter } : {}),
1593
+ };
1594
+ // Each message's place in the compile, or a preview's stand-in (null),
1595
+ // for the count of stored rows the request ends on, below.
1596
+ let origin = messages.map((_, i) => i);
1597
+ // A request that's sent says which stored injections the agent now has in
1598
+ // view, which the next start's same-content skip reads (stored-injections.ts).
1599
+ if (!options.dryRun)
1600
+ this.noteRenderedInjections(compiledResult);
1601
+ const appended = options.appendedMessages ?? [];
1602
+ if (appended.length > 0) {
1603
+ // Where a start's compile would render them, as the window's newest
1604
+ // rows: last, unless the window has no user message, where the compile
1605
+ // places the injections it's given at the end, after the window.
1606
+ const sources = compiledResult.provenance?.messages;
1607
+ let at = messages.length;
1608
+ if (sources && !messages.some((m, i) => sources[i]?.kind !== 'injection' && m.participant.toLowerCase() === 'user')) {
1609
+ while (at > 0 && sources[at - 1]?.kind === 'injection')
1610
+ at--;
1611
+ }
1612
+ origin = [...origin.slice(0, at), ...appended.map(() => null), ...origin.slice(at)];
1613
+ messages = [...messages.slice(0, at), ...appended, ...messages.slice(at)];
1614
+ }
1615
+ // The stored context injections the request ends on, counted over the
1616
+ // messages the sanitizer below keeps (hasRenderedContent), since it drops
1617
+ // a message it leaves empty, a placed blank injection among them.
1618
+ // [Continue] goes after them, and a tool loop's prompt is read from
1619
+ // before them: they're context, not what woke the agent. An injection
1620
+ // compile() places ends the tail, as on main, where every one was placed.
1621
+ let injectionTail = 0;
1622
+ for (let i = messages.length - 1; i >= 0; i--) {
1623
+ if (!hasRenderedContent(messages[i].content))
1624
+ continue;
1625
+ const from = origin[i] ?? null;
1626
+ if (from !== null && !this.isStoredInjectionRow(compiledResult, from))
1627
+ break;
1628
+ injectionTail++;
1629
+ }
1194
1630
  // Sanitize: strip empty/whitespace text blocks and drop messages left with
1195
1631
  // no content. The Anthropic API rejects empty text blocks with 400
1196
1632
  // "messages: text content blocks must be non-empty"; when such a block
@@ -1202,14 +1638,10 @@ export class Agent {
1202
1638
  // 2026-07-10 on a resident agent: one empty block muted the agent's live
1203
1639
  // path entirely; twin of context-manager's stripEmptyTextBlocks on the
1204
1640
  // compression path.)
1641
+ // The same pass a tool boundary applies to what it notes as sent
1642
+ // (sent-rows.ts), so the two can't drift.
1205
1643
  messages = messages
1206
- .map((m) => ({
1207
- ...m,
1208
- content: m.content.filter(
1209
- // typeof guard is deliberate runtime defense: history loaded from
1210
- // disk can carry a non-string `text` despite what the types claim.
1211
- (b) => !(b.type === "text" && (typeof b.text !== "string" || b.text.trim() === ""))),
1212
- }))
1644
+ .map((m) => ({ ...m, content: m.content.filter(carriedBlock) }))
1213
1645
  .filter((m) => m.content.length > 0);
1214
1646
  // The framework's own pricing stamp never goes out (thinking-token-stamp.ts).
1215
1647
  messages = withoutTokenEstimates(messages);
@@ -1233,23 +1665,31 @@ export class Agent {
1233
1665
  && !rendered.separatedTicks.has(tickKey(options.silentHeartbeat));
1234
1666
  // The [Continue] prompt this request's reply follows, read from the
1235
1667
  // request itself: a new one when it ends on [Continue]; else, when it
1236
- // ends on tool results, the prompt of the reply that called those tools,
1237
- // whose tool loop this reply continues; else none. A request that ends
1668
+ // ends on tool results (apart from stored context injections after
1669
+ // them), the prompt of the reply that called those tools, whose tool
1670
+ // loop this reply continues; else none. A request that ends
1238
1671
  // on anything else (a message that arrived during a cooldown, say) gets
1239
1672
  // a reply that follows that message and carries no prompt.
1240
1673
  let continuePrompt;
1241
1674
  if (openingTick) {
1242
1675
  messages = [...messages, silentHeartbeatSeparatorTurn()];
1243
1676
  }
1244
- else if (messages.length > 0 && messages[messages.length - 1].participant === this.name) {
1677
+ else if (endsOnOwnMessage(messages, this.name, injectionTail)) {
1245
1678
  // Safety: ensure messages don't end with an assistant message.
1246
1679
  // Some models reject trailing assistant messages ("prefill not supported"),
1247
1680
  // and after context compression a stale assistant turn can end up last.
1681
+ // Where stored context injections follow the agent's message, as a
1682
+ // changed one does on a wake with nothing new, [Continue] goes after
1683
+ // them, so the cue stays last: the block isn't taken for what woke
1684
+ // the agent. Its reply's promptedBy puts it there in later compiles too,
1685
+ // where they can recognise the reply (renderRequestOnlyRows).
1248
1686
  messages = [...messages, continueTurn()];
1249
1687
  continuePrompt = { turn: randomUUID(), text: CONTINUE_ROW };
1250
1688
  }
1251
1689
  else {
1252
- continuePrompt = toolLoopPrompt(messages.at(-1), index);
1690
+ // What the request ends on apart from the stored injections after it,
1691
+ // which aren't something the reply answers.
1692
+ continuePrompt = toolLoopPrompt(messages.at(-1 - injectionTail), index);
1253
1693
  }
1254
1694
  const request = {
1255
1695
  messages: this.toolResultGuard.prepareRequest(messages),
@@ -1258,23 +1698,26 @@ export class Agent {
1258
1698
  ...this.requestSettings(),
1259
1699
  ...(kvUnified ? { cacheMarkers: 'cm-owned' } : {}),
1260
1700
  };
1261
- return { request, ...(continuePrompt ? { continuePrompt } : {}) };
1701
+ if (options.dryRun)
1702
+ dryCompileStats.set(request, renderStats);
1703
+ return { request, ...(continuePrompt ? { continuePrompt } : {}), compiled, binding };
1262
1704
  }
1263
1705
  /**
1264
1706
  * The request fields that come from this agent's own settings: its model
1265
- * config, prompt caching and cache TTL, prefill message, provider
1266
- * parameters and participant name. Every request the agent sends takes
1267
- * them from here, a framework turn's (assembleActivationRequest) and the
1268
- * direct API's (runInferenceWithInjections), so a setting reaches both or
1269
- * neither.
1707
+ * config, thinking binding, prompt caching and cache TTL, prefill message,
1708
+ * provider parameters and participant name. Every request the agent sends
1709
+ * takes them from here, a framework turn's (assembleActivationRequest) and
1710
+ * the direct API's (runInferenceWithInjections), so a setting reaches both
1711
+ * or neither.
1270
1712
  */
1271
1713
  requestSettings() {
1714
+ const thinking = this.requestThinking();
1272
1715
  return {
1273
1716
  config: {
1274
1717
  model: this.model,
1275
1718
  maxTokens: this.maxTokens,
1276
1719
  ...(this.temperature !== undefined && { temperature: this.temperature }),
1277
- ...(this.thinking !== undefined && { thinking: this.thinking }),
1720
+ ...(thinking !== undefined && { thinking }),
1278
1721
  },
1279
1722
  promptCaching: this.promptCaching,
1280
1723
  cacheTtl: this.cacheTtl,
@@ -1283,6 +1726,70 @@ export class Agent {
1283
1726
  assistantParticipant: this.name,
1284
1727
  };
1285
1728
  }
1729
+ /**
1730
+ * The thinking config a request carries: the agent's own, with the binding
1731
+ * control added when `thinkingBinding.dropBlock` asks for it. A model whose
1732
+ * thinking is always on takes the control whatever `enabled` says, so an
1733
+ * agent with no thinking config of its own still sends one, holding only
1734
+ * the control and `enabled: false`. Membrane sends nothing from it to any
1735
+ * other model, since thinking is off there.
1736
+ */
1737
+ requestThinking() {
1738
+ if (!this.thinkingDropBlock)
1739
+ return this.thinking;
1740
+ const own = this.thinking !== null && typeof this.thinking === 'object' ? this.thinking : { enabled: false };
1741
+ return { ...own, blockBinding: { prefixMismatchBehavior: 'drop_block' } };
1742
+ }
1743
+ /** The stream's way to tell the context manager what its later rounds sent, with the head its compile was given:
1744
+ * where the agent's model binds its thinking (context-manager's `noteRequestSent`). */
1745
+ requestNotes(binding, compiled) {
1746
+ const identity = binding.identity;
1747
+ // Each note names the compile the stream was built from, so a note can't vouch for this stream under a record a
1748
+ // later committing compile with binds wrote. A compile that reports no provenance gives the stream nothing to name, and the
1749
+ // stream notes nothing: its replies go without their thinking at the next compile, never refused.
1750
+ const compileId = compiled.provenance?.compileId;
1751
+ if (!identity || compileId === undefined)
1752
+ return {};
1753
+ const cm = this.contextManager;
1754
+ return { noteRequestSent: (rows) => cm.noteRequestSent(rows, identity, compileId) };
1755
+ }
1756
+ /**
1757
+ * The binding a compile of this agent is told of (bindingCompileOptions), and a preview of its context too: the
1758
+ * participant whose messages are its replies is its own name. Undefined where the model doesn't bind.
1759
+ * @internal For the framework's preview and its budget rule; not a host API.
1760
+ */
1761
+ thinkingBindingOption() {
1762
+ return this.thinkingBinds ? { binds: true, dropBlock: this.thinkingDropBlock, participant: this.name } : undefined;
1763
+ }
1764
+ /**
1765
+ * What a compile the agent sends tells the context manager when the model binds its thinking
1766
+ * (`thinkingBinding.binds`): the binding as set, with the agent's own name as the participant whose messages
1767
+ * are its replies (an agent stores its replies under its name), and a digest of each part of the request ahead of
1768
+ * its messages, so a changed one can stand for a first difference where the context manager keeps a
1769
+ * thinking-binding record. The system text is the one the request will carry, built from the system-position
1770
+ * injections given to the compile as the kv-unified prefix hash builds it; the tools are the ones it will carry, in
1771
+ * their order; `request` covers the thinking config, `maxTokens` and `providerParams`, where a host puts its own
1772
+ * thinking config and `tool_choice`, and `prefillUserMessage`, which membrane sends as a leading user turn ahead
1773
+ * of a conversation that would begin with the assistant. Nothing without `binds`: the compile is as it was.
1774
+ */
1775
+ bindingCompileOptions(tools, systemInjections) {
1776
+ const thinkingBinding = this.thinkingBindingOption();
1777
+ if (!thinkingBinding)
1778
+ return {};
1779
+ const { config, providerParams, prefillUserMessage } = this.requestSettings();
1780
+ return {
1781
+ thinkingBinding,
1782
+ identity: {
1783
+ system: wireDigest(this.buildSystemPrompt(systemInjections)),
1784
+ tools: wireDigest(tools),
1785
+ model: wireDigest(this.model),
1786
+ request: wireDigest({
1787
+ thinking: config.thinking ?? null, maxTokens: config.maxTokens, providerParams: providerParams ?? null,
1788
+ prefillUserMessage: prefillUserMessage ?? null,
1789
+ }),
1790
+ },
1791
+ };
1792
+ }
1286
1793
  /** The request `assembleActivationRequest` builds (see there), for preview
1287
1794
  * and debug tooling: what this agent's next activation would send. */
1288
1795
  async buildActivationRequest(availableTools, injections, budget, compressionTools = availableTools, options = {}) {
@@ -1338,10 +1845,10 @@ export class Agent {
1338
1845
  // ran. The stream driver collects it on that stream's terminal event, or
1339
1846
  // at the loop's end when the iterator closed without one, while the
1340
1847
  // stream still owns the agent. This clear is for the records no driver
1341
- // collects: a dropped turn's, whose stream no driver iterates; one whose
1342
- // stream's terminal came after another stream took the agent; or a
1343
- // cancel after the stream's complete event. A fresh stream must not
1344
- // inherit one and read its own later error as a deliberate stop.
1848
+ // collects: a cancel of a stream no driver iterates; one whose stream's
1849
+ // terminal came after another stream took the agent; or a cancel after
1850
+ // the stream's complete event. A fresh stream must not inherit one and
1851
+ // read its own later error as a deliberate stop.
1345
1852
  this._pendingCancel = undefined;
1346
1853
  this._inferenceStartedAt = Date.now();
1347
1854
  this.lastStreamInputTokens = 0;
@@ -1359,7 +1866,7 @@ export class Agent {
1359
1866
  // Compiled only once each withheld stub's annotation has finished,
1360
1867
  // including one that began during the compile, and after a stub edit that
1361
1868
  // failed has been retried (see afterAnnotations).
1362
- const { request, continuePrompt } = await this.toolResultGuard.afterAnnotations(() => this.assembleActivationRequest(availableTools, injections, budget, compressionTools, options));
1869
+ const { request, continuePrompt, compiled, binding } = await this.toolResultGuard.afterAnnotations(() => this.assembleActivationRequest(availableTools, injections, budget, compressionTools, options));
1363
1870
  request.messages = this.toolResultGuard.prepareRequest(request.messages, true);
1364
1871
  const receiptAware = this.contextManager
1365
1872
  .getStrategy?.();
@@ -1385,6 +1892,12 @@ export class Agent {
1385
1892
  emitTokens: true,
1386
1893
  emitBlocks: false,
1387
1894
  emitUsage: true,
1895
+ // A caller that retries itself (the framework's turns: errorPolicy,
1896
+ // provider holds, stated waits, each attempt a fresh compile a stop can
1897
+ // cancel) has membrane send a failed round once and hand the error up:
1898
+ // since membrane 0.7 its own schedules, ten minutes at a 529, would
1899
+ // otherwise run inside each of the caller's attempts, out of its sight.
1900
+ ...(options.membraneRetries === false ? { retry: false } : {}),
1388
1901
  // Retry a content-policy refusal at the provider seam rather than at
1389
1902
  // the framework level: membrane replays the SAME request immediately
1390
1903
  // (cache-warm), where a framework-level requeue would recompile and
@@ -1403,6 +1916,14 @@ export class Agent {
1403
1916
  stream,
1404
1917
  request,
1405
1918
  ...(continuePrompt ? { continuePrompt } : {}),
1919
+ // Read against the messages the stream sends, which membrane's report indexes (RoundReport's coordinates).
1920
+ placeDrops: (entries) => placeThinkingDrops(entries, request.messages, compiled),
1921
+ ...this.requestNotes(binding, compiled),
1922
+ ...(compiled.provenance
1923
+ ? { carriedBodies: new Set(compiled.provenance.messages.flatMap((m) => m.kind === 'raw' ? m.bodies.map((b) => b.messageId) : [])) }
1924
+ : {}),
1925
+ ...(compiled.recordedAfter !== undefined ? { thinkingRecordedAfter: compiled.recordedAfter } : {}),
1926
+ ...(compiled.provenance ? { thinkingBranch: branchKey(compiled.provenance.branch) } : {}),
1406
1927
  ...(kvEnabled
1407
1928
  ? {
1408
1929
  takeKvSubmission: () => kvQueue.shift(),
@@ -1473,9 +1994,9 @@ export class Agent {
1473
1994
  console.error(`[assistant-persist] agent=${this.name}: refusing to store a thinking-only assistant ` +
1474
1995
  `message (${content.length} block(s), no text and no tool_use) — the turn produced no ` +
1475
1996
  `output. See the refusal/inference-failed record for why.`);
1476
- return;
1997
+ return undefined;
1477
1998
  }
1478
- this.contextManager.addMessage(this.name, content, metadata);
1999
+ return this.contextManager.addMessage(this.name, content, metadata);
1479
2000
  }
1480
2001
  /**
1481
2002
  * Transition back to streaming state after tool results are provided.
@@ -1506,12 +2027,25 @@ export class Agent {
1506
2027
  * one is still ahead; otherwise the next start drops it. `reason` is
1507
2028
  * provenance for the framework's inference:aborted trace and marker
1508
2029
  * metadata (e.g. 'zombie_reclaim', 'subagent_cancel'); it never reaches
1509
- * the provider.
2030
+ * the provider. When the stream's cancel() throws, the throw goes on to
2031
+ * the caller, and the agent still holds the stream, its cancel record as
2032
+ * it was before the call.
1510
2033
  */
1511
2034
  cancelStream(reason) {
1512
2035
  if ('stream' in this._state && this._state.stream) {
2036
+ // A cancel that throws leaves the stream going and the agent holding
2037
+ // it, so this call's record is taken back, leaving any earlier one: a
2038
+ // later end of that stream, an `error` the provider reports included,
2039
+ // is not read as this stop's.
2040
+ const earlier = this._pendingCancel;
1513
2041
  this._pendingCancel = { reason };
1514
- this._state.stream.cancel();
2042
+ try {
2043
+ this._state.stream.cancel();
2044
+ }
2045
+ catch (error) {
2046
+ this._pendingCancel = earlier;
2047
+ throw error;
2048
+ }
1515
2049
  }
1516
2050
  else {
1517
2051
  this._stopWithoutStream = { reason };
@@ -1665,6 +2199,7 @@ export class Agent {
1665
2199
  id: block.id,
1666
2200
  name: block.name,
1667
2201
  input: block.input,
2202
+ ...(block.unparseableInput !== undefined ? { unparseableInput: block.unparseableInput } : {}),
1668
2203
  });
1669
2204
  }
1670
2205
  else if (block.type === 'text') {
@@ -1693,7 +2228,7 @@ export class Agent {
1693
2228
  toolUseId: r.id,
1694
2229
  content: isError
1695
2230
  ? truncateForHistory(typeof error === 'string' ? error.toWellFormed() : error, DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS)
1696
- : nonErrorResultText(r.result, DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS),
2231
+ : nonErrorResultText(r.result, DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS, { toolName: r.name }),
1697
2232
  isError,
1698
2233
  };
1699
2234
  });