@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
@@ -20,25 +20,45 @@ export interface MountConfig {
20
20
  * Where the mount's tools get what they read (default: 'chronicle').
21
21
  * - 'chronicle': the workspace keeps the mount's files in its store, where
22
22
  * writes and edits land first, and takes in disk's changes under the
23
- * three-way rule as its listings, searches, syncs and scans reach them.
24
- * A read of a path the store doesn't hold takes in a new file there;
25
- * otherwise it reads the store's copy.
26
- * - 'direct': for a checkout an agent reads, not one it changes. Every
27
- * read, listing and search is disk's own copy as it is when it runs,
28
- * and none of its files go into the store. No scan or watcher
23
+ * three-way rule as `read`, listings, searches, syncs and scans reach
24
+ * them. Its tools read the store's copy.
25
+ * - 'direct': for a checkout, such as a git one, whose history is its
26
+ * own. Every read, listing and search is disk's own copy as it is when
27
+ * it runs, and none of its files go into the store. No scan or watcher
29
28
  * runs, so no call waits behind one, and `sync`, `materialize` and the
30
- * watch settings have nothing to act on. Only with `mode: 'read-only'`,
31
- * and not with `watch` other than 'never', `watchDebounceMs`,
32
- * `watchRootPollMs`, a `wakeOnChange` that would wake (true, or a
33
- * non-empty list) or `autoMaterialize: true`: each of those would never
34
- * act, and the workspace refuses the mount. Its `ignore`
29
+ * watch settings have nothing to act on. Not with `watch` other than
30
+ * 'never', `watchDebounceMs`, `watchRootPollMs`, a `wakeOnChange` that
31
+ * would wake (true, or a non-empty list) or `autoMaterialize: true`:
32
+ * each of those would never act, and the workspace refuses the mount.
33
+ * Writable, as any mount with no `mode` is, its `write`, `edit`,
34
+ * `edit_many` and `delete` land on disk when they answer, or don't
35
+ * happen: a file is staged beside itself in `.workspace-staged-<id>` and
36
+ * renamed over itself, keeping its mode, so no reader sees half of it;
37
+ * an edit is applied to disk's text as it is now, and again on top of a
38
+ * change another writer makes meanwhile; and `edit_many` checks every
39
+ * edit before writing its files one by one, naming each file's outcome
40
+ * if a write fails partway. A file with other hard links gets a file of
41
+ * its own at its path, and the others keep the old text; a file this
42
+ * process may not write is refused. Nothing is synced, so a power loss
43
+ * can lose a write its answer called landed. An undo or other move of
44
+ * the store's head leaves the files as they are, and at the agent's next
45
+ * start the framework tells it once that writes from turns its history
46
+ * no longer holds are still on disk. Where each agent's latest write
47
+ * lies is saved at a clean stop, in a record no branch switch rewinds,
48
+ * so a restart on another branch still tells it, and after a crash a
49
+ * move away from the writes since then isn't told. The framework's own
50
+ * files, its
51
+ * tool-result spills, saved payloads and fetched references, never go to
52
+ * a mount configured direct. Its `ignore`
35
53
  * has a default (see there). Paths, links, its `include`, other mounts'
36
54
  * directories, size limits and the walk cap hold as on a 'chronicle'
37
- * mount. Its tools read beneath whatever directory is at its
38
- * root, with no check against the one a 'chronicle' run of it last
39
- * agreed with. Where that root now lies elsewhere than the last start
40
- * placed it, each call nests in it, as a start would now, every mount
41
- * configured beneath its path, where that path leads beneath its root,
55
+ * mount. Its tools read and write beneath whatever directory is at its
56
+ * root when each call starts, with no check against the one a
57
+ * 'chronicle' run of it last agreed with, so a root repointed through a
58
+ * link is read and written where it leads now. Where that root now lies
59
+ * elsewhere than the last start placed it, each call nests in it, as a
60
+ * start would now, every mount
61
+ * configured on or beneath its path, where that path leads beneath its root,
42
62
  * and every other mount whose root, as that start placed it, lies
43
63
  * beneath where its root lies now, or whose configured path lies
44
64
  * beneath a directory that start found there. A mount configured
@@ -52,7 +72,10 @@ export interface MountConfig {
52
72
  * them pending, or an intent the workspace can't read, which may hold one
53
73
  * of those. They stay in view, and the workspace says so on stderr and
54
74
  * through the framework's ops alerts, naming them and the ways to start it
55
- * direct. It runs unwatched, and walks by the direct kind's default
75
+ * direct. Until a start finds none held, it takes no writes, edits or
76
+ * deletions, so what's held only shrinks: a `sync` of a held path takes
77
+ * disk's copy, and a `materialize` naming the mount writes the store's.
78
+ * It runs unwatched, and walks by the direct kind's default
56
79
  * ignore list where its config names none; `ls` without a path, and
57
80
  * `status`, give its `backend` as 'chronicle' and that list. A mount that
58
81
  * ran direct and runs as 'chronicle' again takes in what disk changed
@@ -78,10 +101,32 @@ export interface MountConfig {
78
101
  /** Poll interval in ms for checking watched root identity changes (default: 2000) */
79
102
  watchRootPollMs?: number;
80
103
  /**
81
- * Simple ignore patterns (NOT full gitignore syntax). Supported forms:
82
- * - Exact name match: ".git", "node_modules"
83
- * - Directory glob: "node_modules/**" (anything under node_modules)
84
- * - Extension glob: "*.pyc" (any file ending in .pyc)
104
+ * Patterns in these of gitignore's forms, each covering the files
105
+ * gitignore's reading of it covers, with three differences: names compare
106
+ * exactly, as git's do with `core.ignorecase` off (its default folds case
107
+ * on macOS and Windows); "?" matches one character where git's matches
108
+ * one byte, so the two differ on names outside ASCII; and on a mount that
109
+ * follows links, a link to a directory counts as a directory, where git
110
+ * reads every link as a file:
111
+ * - A name, matched at any depth: ".git", "node_modules". Within a name,
112
+ * "*" matches any run of characters and "?" any one: "*.pyc",
113
+ * "npm-debug.log*".
114
+ * - A trailing "/" matches only a directory: "build/" covers every
115
+ * directory named build, with what lies beneath it, and no file of that
116
+ * name.
117
+ * - A leading or inner "/" makes a path from the mount's root: "/dist"
118
+ * covers only the root's dist, and "a/b" only the root's a/b.
119
+ * - "**" as the first segment, before one name, matches that name at any
120
+ * depth, as the name alone does; with a trailing "/**" as well, it reads
121
+ * as the name with a trailing "/".
122
+ * - A trailing "/**" covers a directory from the root and what lies
123
+ * beneath it: "node_modules/**" covers only the root's node_modules, and
124
+ * no file of that name.
125
+ * The module refuses, when it's built, a pattern of any other form, such as
126
+ * a leading "!" or "#", a "[", a "\", a trailing space, "**" anywhere
127
+ * else, or an empty, "." or ".." segment, naming the pattern and why. A
128
+ * "?" can stand for a "[" or "\" in a name.
129
+ *
85
130
  * A sync without a path and the watcher pass over what the list covers,
86
131
  * and over entries with an editor's swap or backup file name whatever it
87
132
  * says, a directory with what lies beneath it: `.name.swp` and `.name.swx`
@@ -101,13 +146,17 @@ export interface MountConfig {
101
146
  ignore?: string[];
102
147
  /**
103
148
  * The files this mount holds, when it holds only some of what is under its
104
- * path: the same pattern forms as `ignore`, read for files. A name or an
105
- * extension glob covers a file by its own name, at any depth, and a directory
106
- * glob (`dir/**`) covers what lies beneath that directory, and not a file
107
- * named like it. Unlike `ignore`, a pattern that matches a directory's name
108
- * covers nothing beneath it, since each file is covered by its own name:
109
- * `["gate.json"]` holds every file named `gate.json`, and no other file
110
- * beneath a directory of that name. A subtree is named with `dir/**`.
149
+ * path: patterns in `ignore`'s forms, read for files, and a `[` read as
150
+ * itself, so `[id].tsx` names that file. A pattern that matches
151
+ * only directories, with a trailing `/` or `/**`, covers what lies beneath a
152
+ * directory it matches: `fixtures/` every file beneath a directory named
153
+ * fixtures, at any depth, and `docs/**` or `/docs/` every file beneath the
154
+ * root's docs. Any other pattern covers a file by its own name, at any
155
+ * depth, or by its path from the root (`/gate.json`, `src/*.ts`). Unlike
156
+ * `ignore`, such a pattern covers nothing beneath a directory it matches,
157
+ * since each file is covered by its own name or path: `["gate.json"]` holds
158
+ * every file named `gate.json`, and no other file beneath a directory of
159
+ * that name.
111
160
  *
112
161
  * It's the mount's boundary for content: no workspace path reads, stores,
113
162
  * serves or writes back a file it doesn't cover, and no tool lists,
@@ -176,31 +225,62 @@ export interface MountConfig {
176
225
  * A path in a nested mount's directory is that mount's, whatever this
177
226
  * include covers: every tool gives the answer that names the mount holding
178
227
  * it and the path to use there. Through a link a mount follows, a place in
179
- * that directory is named only where it's a directory, or a file that
180
- * mount's own include covers with no file on its way, and is otherwise
181
- * refused as leaving the mount. On a mount that doesn't follow links, a
182
- * link on a path's way is refused, as on any such mount (followSymlinks).
183
- * A nested mount's root is placed where disk has it at each start, and its
184
- * directory is matched by spelling, so four cases fall outside this, as
185
- * for any nested mount, and a fifth on a direct one: on a disk that folds
186
- * names, a mount configured
187
- * through a link that spells a directory otherwise than disk does isn't
188
- * found nested, and this mount holds its files as its own; on such a disk,
189
- * a path through a link that spells a nested mount's directory otherwise
190
- * than that mount's configured path does, whether the link or the
191
- * configured path is the one spelled otherwise than disk, isn't found to
192
- * lead there, and this mount reads, lists, searches and writes there as
193
- * its own, creating files too, though a walk from its root passes over the
194
- * directory; on such a disk, a place in a nested mount's directory that a
195
- * link does lead to is judged by that mount's include, and named, in the
196
- * spelling the link's target gives, so a refusal can name a file disk
197
- * spells outside that include, or leave a covered one unnamed; and a
198
- * nested mount whose root moves while running, as `acceptRoot` takes,
199
- * stays placed where it was until the next start; and where a direct
200
- * mount's root is repointed between a read's parse and its open, a race
201
- * left to whoever writes the disk, a mount nested in it by its
202
- * directories alone, since its real root holds that root, can have the
203
- * read serve a path in its directory.
228
+ * that directory is named only where it's a directory, or a file covered,
229
+ * with no file on its way, by that mount's own include and by the include
230
+ * of each other nested mount whose directory may hold it, from the
231
+ * innermost out to the first that surely holds it, and is otherwise
232
+ * refused as leaving the mount. A listing, or a pass, names a file there
233
+ * only where those includes cover it. On a mount that doesn't follow
234
+ * links, a link on a path's way is refused, as on any such mount
235
+ * (followSymlinks).
236
+ * Every check compares a path with a nested mount's directory level by
237
+ * level, as any disk may compare names: in either Unicode normalization
238
+ * form and in any case, as fully as a disk that folds names may compare
239
+ * them, at every level, whatever the disk does there, and reading nothing
240
+ * from the disk to do so. Start places each root the same way, so a mount
241
+ * configured through a link, or in a spelling, other than disk's is found
242
+ * nested. So a path, a walk or the workspace's history that names the
243
+ * directory in any spelling a disk may take for it reaches that mount,
244
+ * whether or not the directory is on disk yet, and so does a link whose
245
+ * target spells it otherwise. Where a link leads into the directory, the
246
+ * place beneath it is judged, and named, as disk spells it. A path that names the directory itself keeps what lies
247
+ * beneath as given, since that is the nested mount's to name. Where a disk
248
+ * keeps apart two names the comparison joins, it errs toward the nested
249
+ * mount, in where start places a mount as in every check: a name in
250
+ * another case, on a disk that tells case apart; a name in the other
251
+ * normalization form, on a disk that keeps the forms apart; and a name the
252
+ * comparison folds with it that a disk keeps apart, such as `ss` beside a
253
+ * nested `ß`, or one that differs from it only by a default-ignorable code
254
+ * point, such as a soft hyphen, on a disk that keeps those. Such a
255
+ * directory, or file, is no mount's, and every tool that names it is
256
+ * refused as a path that may lie in the nested mount, with the two
257
+ * spellings the workspace can't tell apart, and, for a link, where it
258
+ * leads, when that lies in this mount. The answer gives a step for each
259
+ * thing the caller may have meant: that mount's path to use; for a new
260
+ * file of this mount's, a path outside both spellings; and for one already
261
+ * there, whoever runs the workspace renaming one of the two spellings.
262
+ * Where start placed the nested mount only by a guess, comparing the real
263
+ * roots, as for two mounts' roots configured in spellings it can't tell
264
+ * apart, a path there is a guess too, however exactly it matches, and the
265
+ * answer names that directory as the path spells it, with the nested
266
+ * directory's spelling only in the step for a new file, names no host
267
+ * path, and asks for the configuration to be checked. Three cases fall
268
+ * outside this, as for any nested mount, and a fourth on a direct one: on a
269
+ * disk whose folding goes past the comparison's, which joins names that
270
+ * differ only by default-ignorable code points, in normalization form or in
271
+ * case, in full case folding, as Linux's casefold does, a name the
272
+ * comparison keeps apart can reach the directory; a second path to the
273
+ * directory that isn't a link, such as a bind mount, isn't found to lead
274
+ * there, though a walk passes over it, knowing each nested root by which
275
+ * directory it is; a nested mount whose root moves while running, as
276
+ * `acceptRoot` takes, stays placed where it was until the next start, but
277
+ * for the calls of a direct mount whose root has moved and on or beneath
278
+ * whose path it's configured, which place it where it lies; and where a
279
+ * direct mount's root is repointed between a read's parse and its open, or
280
+ * a write's parse and its staging, a race left to whoever writes the disk,
281
+ * a mount nested in it by its directories alone, since its real root holds
282
+ * that root, can have the read serve a path in its directory, or the write
283
+ * land there.
204
284
  *
205
285
  * Text the host registers for a path (`registerGeneratedTextFile`) is
206
286
  * served there whatever the include says, since it isn't one of the
@@ -214,12 +294,8 @@ export interface MountConfig {
214
294
  * include covers it.
215
295
  *
216
296
  * Left out, the mount holds everything under its path. Given, it's a list
217
- * of at least one string, each a name, an `*.ext` glob or a `dir/**` glob,
218
- * whose directory may be nested (`a/b/**`). The module refuses a pattern
219
- * with a `/` that isn't such a glob, a `*` anywhere but a leading `*.` or
220
- * that trailing `/**`, an empty, `.` or `..` segment, and, where `\` is the
221
- * platform's separator, a `\`. Any other pattern is a file name, matched
222
- * literally, so `[id].tsx` names the file of that name.
297
+ * of at least one pattern, and the module refuses one `ignore` would
298
+ * refuse but for a `[`.
223
299
  */
224
300
  include?: string[];
225
301
  /**
@@ -290,7 +366,8 @@ export interface MountConfig {
290
366
  /**
291
367
  * If true, writes/edits/deletes via the module's tools materialize to the
292
368
  * filesystem immediately (not just into Chronicle tree state). Required for
293
- * any mount shared across agents as a communication channel — without it,
369
+ * any store-backed mount shared across agents as a communication channel (a
370
+ * direct mount's writes reach disk without it, and it's refused there) — without it,
294
371
  * another agent's watcher on the same directory sees nothing. The watcher
295
372
  * suppresses its own echoes, so this doesn't cause self-wake loops.
296
373
  */
@@ -126,10 +126,15 @@ export class MountWatcher {
126
126
  // another mount's directory, the mount's ignore list and editors' swap
127
127
  // and backup files. chokidar 4
128
128
  // compares a string given here with the whole absolute path, so the
129
- // list itself would match nothing.
130
- ignored: (path: string) => {
129
+ // list itself would match nothing. It asks without stats before it
130
+ // stats a path, asking again with them before it watches the path, and
131
+ // when a path it watches is removed. So where it gives no kind, no
132
+ // directory-only pattern matches: a directory one matches never passed
133
+ // the check with its stats, so it is never watched, and its removal
134
+ // never reported.
135
+ ignored: (path: string, stats?: Stats) => {
131
136
  const relative = this.toRelative(path);
132
- return relative !== null && passedOverAt(relative, ignore, this.others) !== undefined;
137
+ return relative !== null && passedOverAt(relative, ignore, this.others, stats?.isDirectory() ?? false) !== undefined;
133
138
  },
134
139
  // Its filter for editors' files tested the whole path, so a directory
135
140
  // with a dot in its name hid every `*.swp` beneath it, and the walk
@@ -2,8 +2,9 @@
2
2
  * Held prose drafts: a resident's plain speech that was not sent.
3
3
  *
4
4
  * Plain speech is held back for reasons that are right in themselves: an
5
- * explicit send in the same round suppresses the round's prose (so a "sent
6
- * it" postscript is not posted twice), a turn may have no destination, and a
5
+ * explicit send suppresses plain speech aimed where it may have posted, in
6
+ * its round and the turn's later ones (so a "sent it" postscript is not
7
+ * posted twice), a turn may have no destination, and a
7
8
  * malformed or unresolvable routing prefix bounces. Before drafts, the held words were
8
9
  * kept only as a count (or, in explicit mode, as one in-memory latest-wins
9
10
  * clipboard): the law was right, and the penalty was amnesia. A draft keeps
@@ -53,7 +54,7 @@ export const PROSE_DRAFT_RECORD_TYPE = 'framework/prose-draft';
53
54
 
54
55
  /** Why words were held instead of sent. */
55
56
  export type DraftReason =
56
- /** An explicit send in the same round suppressed the round's plain speech. */
57
+ /** An explicit send in its turn may have posted where the speech was going. */
57
58
  | 'explicit-send'
58
59
  /** The speech had no destination (no route this turn, or a failed envelope). */
59
60
  | 'no-destination'
@@ -0,0 +1,62 @@
1
+ import { safeSlice } from './safe-slice.js';
2
+
3
+ export type ReasoningLossCause = 'tools' | 'system' | 'model';
4
+
5
+ export type ReasoningLossAnchor = { text: string } | { toolCall: string };
6
+
7
+ export interface ReasoningLossNoticeInput {
8
+ agentName: string;
9
+ scope:
10
+ | { kind: 'all'; through?: ReasoningLossAnchor; thisTurn: boolean }
11
+ | { kind: 'some'; count: number; first?: ReasoningLossAnchor; unplaced?: boolean; thisTurn: boolean };
12
+ causes: readonly ReasoningLossCause[];
13
+ }
14
+
15
+ const ANCHOR_CHARS = 80;
16
+
17
+ const CAUSE_WORDS: Record<ReasoningLossCause, string> = {
18
+ tools: 'your tools changed',
19
+ system: 'your system prompt changed',
20
+ model: 'your model changed',
21
+ };
22
+ const CAUSE_ORDER: readonly ReasoningLossCause[] = ['tools', 'system', 'model'];
23
+
24
+ export function reasoningLossNotice(input: ReasoningLossNoticeInput): string {
25
+ const causes = CAUSE_ORDER.filter((c) => input.causes.includes(c)).map((c) => CAUSE_WORDS[c]);
26
+ const because = causes.length > 0 ? `, because ${joinAnd(causes)}` : '';
27
+ const scope = input.scope;
28
+ let lost: string;
29
+ let reread: string;
30
+ if (scope.kind === 'all') {
31
+ const through = scope.through ? anchorWords(scope.through) : undefined;
32
+ const among = scope.thisTurn ? ", this turn's earlier steps among them," : '';
33
+ lost = through
34
+ ? `The reasoning behind your replies up to the one ${through}${among} is no longer in your context${because}.`
35
+ : `The reasoning behind your earlier replies${among} is no longer in your context${because}.`;
36
+ reread = 'Re-read before relying on an earlier decision.';
37
+ } else {
38
+ const one = scope.count === 1 && !scope.unplaced;
39
+ const replies = scope.unplaced
40
+ ? `at least ${scope.count} of your replies`
41
+ : one ? 'one of your replies' : `${scope.count} of your replies`;
42
+ const named = scope.first && !scope.unplaced ? anchorWords(scope.first) : undefined;
43
+ const first = named ? (one ? `, the one ${named}` : `, the first being the one ${named}`) : '';
44
+ const among = scope.thisTurn ? (one ? ', an earlier step of this turn' : ", this turn's earlier steps among them") : '';
45
+ lost = `Some of your earlier reasoning is no longer in your context: the reasoning behind ${replies}${first}${among}${because}.`;
46
+ reread = named ? 'Re-read before relying on a decision made since then.' : 'Re-read before relying on an earlier decision.';
47
+ }
48
+ return `[reasoning] ${input.agentName}: ${lost} Their words are still here. ${reread}`;
49
+ }
50
+
51
+ function anchorWords(anchor: ReasoningLossAnchor): string | undefined {
52
+ if ('toolCall' in anchor) return anchor.toolCall ? `that called \`${anchor.toolCall}\`` : undefined;
53
+ const text = anchor.text.replace(/\s+/g, ' ').trim();
54
+ if (!text) return undefined;
55
+ const clipped = text.length > ANCHOR_CHARS ? `${safeSlice(text, 0, ANCHOR_CHARS).trimEnd()}…` : text;
56
+ return `beginning "${clipped}"`;
57
+ }
58
+
59
+ function joinAnd(parts: readonly string[]): string {
60
+ if (parts.length <= 1) return parts.join('');
61
+ return `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}`;
62
+ }
@@ -0,0 +1,120 @@
1
+ import type { ContentBlock } from '@animalabs/membrane';
2
+ import type { MessageId, ThinkingStrip } from '@animalabs/context-manager';
3
+ import { ThinkingLossIncidents, type PlacedDrop } from './thinking-drops.js';
4
+ import {
5
+ reasoningLossNotice,
6
+ type ReasoningLossAnchor,
7
+ type ReasoningLossCause,
8
+ } from './reasoning-loss-notice.js';
9
+
10
+ export interface StoredReplyReader {
11
+ getMessage(id: MessageId): { sequence: number; content: readonly ContentBlock[] } | null;
12
+ }
13
+
14
+ export interface ReasoningLossTurn {
15
+ start?: { branch: string; sequence: number };
16
+ }
17
+
18
+ const HEAD_CAUSES: Partial<Record<NonNullable<ThinkingStrip['changed']>[number], ReasoningLossCause>> = {
19
+ tools: 'tools',
20
+ system: 'system',
21
+ model: 'model',
22
+ };
23
+
24
+ export class ReasoningLossTeller {
25
+ private readonly told = new Map<string, Map<string, Set<MessageId>>>();
26
+ private readonly drops = new ThinkingLossIncidents();
27
+
28
+ forStrip(
29
+ agentName: string,
30
+ branch: string,
31
+ strip: ThinkingStrip,
32
+ turn: ReasoningLossTurn,
33
+ store: StoredReplyReader,
34
+ ): string | undefined {
35
+ const told = this.toldFor(agentName, branch);
36
+ const taken = strip.newly
37
+ .map((id) => ({ id, sequence: store.getMessage(id)?.sequence }))
38
+ .sort((a, b) => (a.sequence ?? Infinity) - (b.sequence ?? Infinity));
39
+ const replies = taken.filter((r) => !told.has(r.id));
40
+ if (replies.length === 0) return undefined;
41
+ const thisTurn = replies.some((r) => isThisTurn(r.sequence, branch, turn));
42
+ if (strip.cause === 'head') {
43
+ const causes = (strip.changed ?? [])
44
+ .map((c) => HEAD_CAUSES[c])
45
+ .filter((c): c is ReasoningLossCause => c !== undefined);
46
+ const newest = taken.filter((r) => r.sequence !== undefined).at(-1);
47
+ const through = newest ? anchorOf(store.getMessage(newest.id)?.content) : undefined;
48
+ const amongThisTurn = taken.some((r) => isThisTurn(r.sequence, branch, turn));
49
+ for (const r of replies) told.add(r.id);
50
+ return reasoningLossNotice({ agentName, scope: { kind: 'all', ...(through ? { through } : {}), thisTurn: amongThisTurn }, causes });
51
+ }
52
+ if (strip.cause !== 'first' && !taken.some((r) => isThisTurn(r.sequence, branch, turn))) return undefined;
53
+ const first = anchorOf(store.getMessage(replies[0]!.id)?.content);
54
+ for (const r of replies) told.add(r.id);
55
+ return reasoningLossNotice({
56
+ agentName,
57
+ scope: { kind: 'some', count: replies.length, ...(first ? { first } : {}), thisTurn },
58
+ causes: [],
59
+ });
60
+ }
61
+
62
+ forDrops(
63
+ agentName: string,
64
+ branch: string,
65
+ placed: readonly PlacedDrop[],
66
+ turn: ReasoningLossTurn,
67
+ store: StoredReplyReader,
68
+ ): string | undefined {
69
+ const replies = new Map<MessageId, number>();
70
+ const rounds = new Set<number>();
71
+ let blocks = 0;
72
+ for (const drop of placed) {
73
+ if (drop.kind === 'unplaced') continue;
74
+ blocks++;
75
+ if (drop.kind === 'round') rounds.add(drop.round);
76
+ else for (const body of drop.bodies) replies.set(body.messageId, body.sequence);
77
+ }
78
+ const unplaced = blocks < placed.length;
79
+ const count = replies.size + rounds.size;
80
+ const ordered = [...replies].sort((a, b) => a[1] - b[1]);
81
+ const thisTurn = rounds.size > 0 || ordered.some(([, sequence]) => isThisTurn(sequence, branch, turn));
82
+ const first = ordered.length > 0 && !unplaced ? anchorOf(store.getMessage(ordered[0]![0])?.content) : undefined;
83
+ if (this.drops.observe(agentName, blocks, branch) === undefined) return undefined;
84
+ const told = this.toldFor(agentName, branch);
85
+ for (const id of replies.keys()) told.add(id);
86
+ return reasoningLossNotice({
87
+ agentName,
88
+ scope: { kind: 'some', count, ...(first ? { first } : {}), ...(unplaced ? { unplaced: true } : {}), thisTurn },
89
+ causes: [],
90
+ });
91
+ }
92
+
93
+ forget(agentName: string): void {
94
+ this.told.delete(agentName);
95
+ this.drops.forget(agentName);
96
+ }
97
+
98
+ private toldFor(agentName: string, branch: string): Set<MessageId> {
99
+ let branches = this.told.get(agentName);
100
+ if (!branches) this.told.set(agentName, (branches = new Map()));
101
+ let told = branches.get(branch);
102
+ if (!told) branches.set(branch, (told = new Set()));
103
+ return told;
104
+ }
105
+ }
106
+
107
+ function isThisTurn(sequence: number | undefined, branch: string, turn: ReasoningLossTurn): boolean {
108
+ return sequence !== undefined && turn.start?.branch === branch && sequence > turn.start.sequence;
109
+ }
110
+
111
+ function anchorOf(content: readonly ContentBlock[] | undefined): ReasoningLossAnchor | undefined {
112
+ if (!content) return undefined;
113
+ for (const block of content) {
114
+ if (block.type === 'text' && typeof block.text === 'string' && block.text.trim() !== '') return { text: block.text };
115
+ }
116
+ for (const block of content) {
117
+ if (block.type === 'tool_use' && typeof block.name === 'string' && block.name !== '') return { toolCall: block.name };
118
+ }
119
+ return undefined;
120
+ }
package/src/send-tools.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  * Tools whose call delivers the resident's message to a channel or person,
3
3
  * by bare name (an MCPL server's `<prefix>--` stripped). The framework reads
4
4
  * a call of one as an explicit send that silences the round's auto-routed
5
- * prose; the history module's semantic index reads its message argument as
6
- * what the resident wrote to someone, whether or not it was delivered.
5
+ * prose where it may have posted; the history module's semantic index reads
6
+ * its message argument as what the resident wrote to someone, whether or not
7
+ * it was delivered.
7
8
  */
8
9
  export const SEND_TOOLS = new Set(['channel_publish', 'send_message', 'reply_message', 'send_dm']);
9
10