@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
@@ -0,0 +1,720 @@
1
+ /**
2
+ * MCPL RFC-014, host side: per server, the controller that feeds the
3
+ * server's tool presence (tool-presence.ts) from what its connection reads,
4
+ * sends toolsets/open, close and list, recovers from a lost rev, and renders
5
+ * what changed; and the hub that holds every server's controller for the
6
+ * meta-tools (toolsets-module.ts) and for reminders.
7
+ *
8
+ * Hint changes apply as their message is read (RFC §6.1); a static↔dynamic
9
+ * move takes effect when a tools/list states it (§4.1), and the model's tool
10
+ * list shows it at its next rebuild; until then the tool is called at either
11
+ * place (§4.2). The connection's read tap hands
12
+ * the controller every result of a hinted request (awaited or not, while no
13
+ * more than 1,000 hinted requests await an answer), each tools/list whole, every push/event and every tools/hints, of the
14
+ * current transport, synchronously and in arrival order, before anything
15
+ * routes the message or anything read after it is handled.
16
+ * What a message's changes did to the model's view is kept by the object
17
+ * that carried them (`readChanges`), so whoever later handles that object
18
+ * (the tool result, the push, the meta-tool) renders it next to the content.
19
+ *
20
+ * Reminders (RFC §8.3): after each compile built to send, the hub reads
21
+ * which open toolsets' markers (`[server/toolset:instance]`) the request
22
+ * carried. A marker an agent has had in view, gone from a later request
23
+ * (compressed, or out of the window), makes a reminder due for that agent,
24
+ * which the toolsets module gathers at its next start as a stored
25
+ * `afterUser` injection.
26
+ */
27
+ import { McplRequestError } from './server-connection.js';
28
+ import { isToolAllowed } from './tool-policy.js';
29
+ import { redactServerText } from './protocol-family.js';
30
+ import { logDetail, logValueRedacted } from './log-value.js';
31
+ import { ToolPresence, emptyChange, isEmptyChange, keyOf, } from './tool-presence.js';
32
+ /** The meta-tools' module, whose tools are `toolsets--open` and so on. */
33
+ export const TOOLSETS_MODULE = 'toolsets';
34
+ export const META_TOOL_NAMES = {
35
+ open: `${TOOLSETS_MODULE}--open`,
36
+ act: `${TOOLSETS_MODULE}--act`,
37
+ look: `${TOOLSETS_MODULE}--look`,
38
+ close: `${TOOLSETS_MODULE}--close`,
39
+ };
40
+ /** How long an open or close is waited for. A late result still applies when it's read. */
41
+ export const TOOLSET_REQUEST_TIMEOUT_MS = 60_000;
42
+ /** A toolsets/list after a (re)connect or in a recovery. */
43
+ const LIST_TIMEOUT_MS = 15_000;
44
+ /** A toolsets/list that only refreshes titles for look. */
45
+ const REFRESH_TIMEOUT_MS = 5_000;
46
+ /** A burst of anomalies is one recovery. */
47
+ const RECOVERY_DEBOUNCE_MS = 200;
48
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
49
+ const metaHints = (res) => (isObj(res._meta) ? res._meta['mcpl/hints'] : undefined);
50
+ /** One server's RFC-014 state and traffic. Lives as long as the server's
51
+ * configuration, across reconnects and the server's restarts (its `ended`
52
+ * ids and what the model was shown carry over), in memory only. */
53
+ export class ToolsetController {
54
+ serverId;
55
+ config;
56
+ deps;
57
+ presence;
58
+ connection = null;
59
+ /** The transport epoch the presence's revs belong to. */
60
+ epoch = -1;
61
+ /** What applying a message's hints changed, by the object that carried them. */
62
+ readChanges = new WeakMap();
63
+ /** Results whose change no one has taken yet, in the order they were read, with their method (tellUnlessClaimed). */
64
+ unclaimed = new Map();
65
+ /** eventIds of pushes read on this transport (bounded): a repeat's hints are ignored (RFC §6.1). */
66
+ seenEvents = new Set();
67
+ recoveryTimer = null;
68
+ recovering = false;
69
+ recoverAgain = false;
70
+ constructor(serverId, config, deps) {
71
+ this.serverId = serverId;
72
+ this.config = config;
73
+ this.deps = deps;
74
+ this.presence = new ToolPresence(serverId, META_TOOL_NAMES, (tool) => isToolAllowed(tool, this.config), (tool) => deps.listedName(serverId, tool), (tool) => deps.published?.(serverId, tool));
75
+ }
76
+ log(line) {
77
+ this.deps.log(`${this.serverId}: ${line}`);
78
+ }
79
+ field(value) {
80
+ return logValueRedacted(value, (text) => redactServerText(text, this.config));
81
+ }
82
+ failure(error) {
83
+ return logDetail(redactServerText(error instanceof Error ? error.message : String(error), this.config));
84
+ }
85
+ /** A restart can bring a new configuration under the same id. */
86
+ setConfig(config) {
87
+ this.config = config;
88
+ }
89
+ /** Tap this connection (a connect or a restart's new one). */
90
+ attach(connection) {
91
+ if (this.connection !== connection) {
92
+ this.connection?.setReadTap(null);
93
+ // A new connection numbers its transports from 0 again.
94
+ this.epoch = -1;
95
+ }
96
+ this.connection = connection;
97
+ connection.setReadTap((read, epoch) => this.onRead(connection, read, epoch));
98
+ }
99
+ release() {
100
+ if (this.connection)
101
+ this.detach(this.connection);
102
+ }
103
+ /** The server's connection is gone for good (disconnected, removed). */
104
+ detach(connection) {
105
+ if (this.connection !== connection)
106
+ return;
107
+ connection.setReadTap(null);
108
+ this.connection = null;
109
+ this.epoch = -1;
110
+ if (this.recoveryTimer)
111
+ clearTimeout(this.recoveryTimer);
112
+ this.recoveryTimer = null;
113
+ // Nothing of it presents any more; what the agent was told (ended ids,
114
+ // definitions shown) is kept for a next connection.
115
+ this.presence.deactivate();
116
+ }
117
+ /** Whether the meta-tools speak for this server now. */
118
+ get presents() {
119
+ return this.presence.presents;
120
+ }
121
+ /** The live connection, for a request. */
122
+ live() {
123
+ const c = this.connection;
124
+ if (!c || !c.isConnected)
125
+ throw new McplRequestError(`${this.serverId} is not connected right now; try again later`, 'not-sent');
126
+ this.sync(c.transportEpoch, c);
127
+ return c;
128
+ }
129
+ /**
130
+ * A new transport: revs start over (RFC §6.1), and its advertisement says
131
+ * whether hints are in effect. Read from the connection, which holds the
132
+ * advertisement before its transport can deliver anything.
133
+ */
134
+ sync(epoch, connection) {
135
+ if (epoch === this.epoch)
136
+ return;
137
+ this.epoch = epoch;
138
+ this.seenEvents = new Set();
139
+ this.presence.beginConnection(connection.toolsetDeclarations, true);
140
+ }
141
+ // ── RFC §6.1: apply on arrival ──
142
+ onRead(connection, read, epoch) {
143
+ if (connection !== this.connection)
144
+ return;
145
+ this.sync(epoch, connection);
146
+ if (read.kind === 'result') {
147
+ this.readResult(read.method, read.params, read.result, read.awaited, read.straddled === true);
148
+ return;
149
+ }
150
+ if (!this.presence.active || !isObj(read.params))
151
+ return;
152
+ const p = read.params;
153
+ if (read.kind === 'hints') {
154
+ const cs = this.applyHints(p, 'tools/hints');
155
+ // Carrier 3: a change with nothing to say, told on its own.
156
+ this.deliver(cs);
157
+ return;
158
+ }
159
+ if (p.hints === undefined)
160
+ return;
161
+ // RFC §7: a push carrying hints is never coalesced; when its changes
162
+ // would apply is undefined, so they're ignored and the state re-read.
163
+ if (p.coalesce !== undefined && p.coalesce !== null) {
164
+ this.log(`push ${this.field(p.eventId ?? '')} carries hints and coalesce (RFC-014 §7 forbids it): its hints are ignored; recovering`);
165
+ this.scheduleRecovery();
166
+ return;
167
+ }
168
+ // RFC §6.1: never retried, so a seen eventId is a duplicate whose hints applied once.
169
+ const eventId = typeof p.eventId === 'string' || typeof p.eventId === 'number' ? String(p.eventId) : '';
170
+ if (eventId) {
171
+ if (this.seenEvents.has(eventId)) {
172
+ this.log(`push ${this.field(eventId)} was read before: its hints are ignored`);
173
+ return;
174
+ }
175
+ this.seenEvents.add(eventId);
176
+ if (this.seenEvents.size > 1000)
177
+ this.seenEvents.delete(this.seenEvents.values().next().value);
178
+ }
179
+ // Applied now, before (and whatever) the host decides about the push.
180
+ const cs = this.applyHints(p.hints, 'push');
181
+ this.readChanges.set(p, cs);
182
+ this.tellUnlessClaimed(p, cs, 'push/event');
183
+ }
184
+ readResult(method, params, res, awaited, straddled) {
185
+ let cs;
186
+ switch (method) {
187
+ case 'tools/list': {
188
+ if (!Array.isArray(res.tools))
189
+ return;
190
+ cs = this.readToolList(res.tools, res);
191
+ if (straddled && !this.recovering) {
192
+ this.log('tools/list pages carried different revs, so its earlier pages are older than changes read between them: recovering');
193
+ this.scheduleRecovery();
194
+ }
195
+ this.deliver(cs);
196
+ return;
197
+ }
198
+ case 'toolsets/list':
199
+ cs = this.readToolsetList(res);
200
+ break;
201
+ case 'toolsets/open': {
202
+ if (!this.presence.active)
203
+ return;
204
+ cs = this.applyHints(metaHints(res), 'toolsets/open');
205
+ const before = this.presence.view();
206
+ const toolset = typeof res.toolset === 'string' ? res.toolset : String(params.toolset ?? '');
207
+ const instance = typeof res.instance === 'string' && res.instance ? res.instance : typeof params.instance === 'string' ? params.instance : undefined;
208
+ this.presence.noteOpened(toolset, instance, typeof res.title === 'string' && res.title ? res.title : undefined);
209
+ cs.changes.push(...this.presence.diff(before, this.presence.view()).changes);
210
+ break;
211
+ }
212
+ case 'toolsets/close': {
213
+ if (!this.presence.active)
214
+ return;
215
+ // RFC §5.2: the hint changes the close caused ride in its result, like an open's.
216
+ cs = this.applyHints(metaHints(res), 'toolsets/close');
217
+ const before = this.presence.view();
218
+ this.presence.noteClosed(String(params.toolset ?? ''), typeof params.instance === 'string' ? params.instance : undefined);
219
+ cs.changes.push(...this.presence.diff(before, this.presence.view()).changes);
220
+ break;
221
+ }
222
+ default:
223
+ if (!this.presence.active)
224
+ return;
225
+ cs = this.applyHints(metaHints(res), method);
226
+ }
227
+ if (awaited) {
228
+ this.readChanges.set(res, cs);
229
+ this.tellUnlessClaimed(res, cs, method);
230
+ }
231
+ else {
232
+ // RFC §6.1: a result nobody waits for any more (it timed out) applies
233
+ // all the same, and its change is told on its own.
234
+ this.log(`late ${method} result read: its hint changes applied`);
235
+ this.deliver(cs);
236
+ }
237
+ }
238
+ /**
239
+ * The one apply path (RFC §6.1) for a HintChanges from any carrier, as its
240
+ * message is read. Revs are consecutive: anything but the last + 1 means a
241
+ * message was lost or misordered, and the host recovers.
242
+ */
243
+ applyHints(h, source) {
244
+ if (h === undefined || !this.presence.active)
245
+ return emptyChange();
246
+ const before = this.presence.view();
247
+ const last = this.field(this.presence.rev);
248
+ const r = this.presence.apply(h);
249
+ const rev = this.field(h?.rev);
250
+ if (r.behind) {
251
+ this.log(`hint change rev ${rev} (${source}) is not after rev ${last}: ignored; recovering from tools/list and toolsets/list`);
252
+ this.scheduleRecovery();
253
+ }
254
+ else if (r.skipped) {
255
+ this.log(`hint change rev ${rev} (${source}) after rev ${last}: a rev was lost; applied, and recovering from tools/list and toolsets/list`);
256
+ this.scheduleRecovery();
257
+ }
258
+ else if (!r.applied) {
259
+ this.log(`a malformed hint change (${source}) was ignored`);
260
+ }
261
+ if (r.unlisted) {
262
+ this.log(`hint change rev ${rev} (${source}) names a tool tools/list hasn't listed: recovering from tools/list and toolsets/list`);
263
+ this.scheduleRecovery();
264
+ }
265
+ const cs = this.presence.diff(before, this.presence.view());
266
+ cs.suggest.push(...r.suggest);
267
+ return cs;
268
+ }
269
+ /** tools/list, as read: definitions and presence as of its `mcpl/rev`, which takes no rev of its own (RFC §4.1). */
270
+ readToolList(tools, last) {
271
+ const before = this.presence.view();
272
+ this.presence.setTools(tools);
273
+ if (this.presence.active && this.presence.noteListRev(isObj(last._meta) ? last._meta['mcpl/rev'] : undefined)) {
274
+ this.log(`tools/list was written after rev ${this.field(isObj(last._meta) ? last._meta['mcpl/rev'] : '')}, past the last rev read: a message was lost; recovering`);
275
+ this.scheduleRecovery();
276
+ }
277
+ return this.presence.diff(before, this.presence.view());
278
+ }
279
+ /** toolsets/list, as read: a full statement of dynamic-tool state under its own rev (RFC §5.3, §6.1). */
280
+ readToolsetList(res) {
281
+ if (!this.presence.active)
282
+ return emptyChange();
283
+ if (!Array.isArray(res.open)) {
284
+ this.log('a toolsets/list result with no open list was ignored');
285
+ return emptyChange();
286
+ }
287
+ const before = this.presence.view();
288
+ const { lost, ended, suggest, skipped, unlisted } = this.presence.adopt(res);
289
+ if (skipped && !this.recovering) {
290
+ // Per-instance state is restated in full; what else was lost (presence) comes from tools/list.
291
+ this.log('toolsets/list skipped a rev: re-reading tools/list');
292
+ this.scheduleRecovery();
293
+ }
294
+ if (unlisted && !this.recovering) {
295
+ this.log('toolsets/list names a tool tools/list hasn\'t listed: re-reading tools/list');
296
+ this.scheduleRecovery();
297
+ }
298
+ const cs = this.presence.diff(before, this.presence.view());
299
+ cs.suggest.push(...suggest);
300
+ // A lost toolset gets its note, not also a "closed" line.
301
+ cs.changes = cs.changes.filter((c) => !lost.some((o) => keyOf(o.toolset, o.instance) === c.key));
302
+ const told = this.presence.adoptionNotes(lost, ended);
303
+ cs.notes.push(...told.notes);
304
+ if (told.ended.length)
305
+ cs.ended = [...(cs.ended ?? []), ...told.ended];
306
+ return cs;
307
+ }
308
+ /** A change with nothing else to ride on, told as a notice, after every result's change still unclaimed. */
309
+ deliver(cs) {
310
+ this.tellUnclaimed();
311
+ this.notice(cs);
312
+ }
313
+ /**
314
+ * Unclaimed carriers' changes, told as notices in the order they were read,
315
+ * through `upTo` when given. When no notice can be written, a push keeps its
316
+ * change for the framework's push path, which takes it when the push gets
317
+ * there; a result's is dropped, whether or not its caller is still to
318
+ * resume.
319
+ */
320
+ tellUnclaimed(upTo) {
321
+ for (const [res, method] of [...this.unclaimed]) {
322
+ this.unclaimed.delete(res);
323
+ const cs = this.readChanges.get(res) ?? emptyChange();
324
+ const sent = this.notice(cs);
325
+ if (sent !== 'refused' || method !== 'push/event')
326
+ this.readChanges.delete(res);
327
+ if (sent === 'told')
328
+ this.log(`${method}'s tool change wasn't shown with it: told as a notice`);
329
+ if (res === upTo)
330
+ return;
331
+ }
332
+ }
333
+ /** Whether a notice was written, there was nothing left to tell (`empty`), or one couldn't be written (`refused`). */
334
+ notice(cs) {
335
+ if (isEmptyChange(cs))
336
+ return 'empty';
337
+ const text = this.presence.render(cs, { mark: false });
338
+ if (!text)
339
+ return 'empty';
340
+ if (!this.deps.notify(this.serverId, text, { invite: cs.suggest.some((s) => s.action === 'open') }))
341
+ return 'refused';
342
+ this.presence.markTold(cs.ended);
343
+ this.presence.markShown(cs);
344
+ return 'told';
345
+ }
346
+ /**
347
+ * A dynamic tool called by its namespaced name, as if it were listed (a
348
+ * model imitating an earlier call, a module): refused, so every call into
349
+ * a toolset goes through act, which knows its instance and gates closed
350
+ * groups (RFC §5.4, §5.5). Null for any other tool, including one the tool
351
+ * list last published or the server's latest tools/list puts as static
352
+ * (shownAs: RFC §4.2, until its move applies).
353
+ */
354
+ dynamicCallRefusal(tool, calledAs) {
355
+ if (!this.presence.active || !this.presence.tool(tool) || this.presence.shownAs(tool, 'static'))
356
+ return null;
357
+ return `There's no tool "${calledAs}" in your tool list, so nothing ran: ${tool} is a dynamic tool of ${this.serverId}, ` +
358
+ `called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool:"${tool}", args}. ${this.presence.toolStatus(tool)}.`;
359
+ }
360
+ /**
361
+ * An awaited result's change is told by whoever renders that result, when
362
+ * the result goes to the agent: the model's own call of a tool (its tool
363
+ * result), a meta-tool. One no one renders by the time the awaiting code has
364
+ * run (the next macrotask), such as the result of a call the framework makes
365
+ * itself (a reaction, a send, an accounted side effect), is told as a notice
366
+ * instead, as a standalone tools/hints is (RFC §6.1: every change reaches the
367
+ * agent once), or dropped as tellUnclaimed says when none can be written.
368
+ */
369
+ tellUnlessClaimed(res, cs, method) {
370
+ if (isEmptyChange(cs))
371
+ return;
372
+ this.unclaimed.set(res, method);
373
+ setImmediate(() => {
374
+ if (this.unclaimed.has(res))
375
+ this.tellUnclaimed(res);
376
+ });
377
+ }
378
+ /**
379
+ * The change a carrier's hints made (a tool result, a push's params), taken
380
+ * by a caller that shows it to an agent with the carrier: it isn't told as a
381
+ * notice, and the caller says when it has reached an agent (told). Call it
382
+ * synchronously as the carrier is read, before the next macrotask, when an
383
+ * unclaimed carrier's change goes out as a notice instead (tellUnclaimed
384
+ * says what happens when none can be written). A change already gone that
385
+ * way is empty here. Unclaimed carriers read before this one
386
+ * are told first, as notices, so no later change is shown before them.
387
+ */
388
+ takeChange(carrier) {
389
+ if (!isObj(carrier))
390
+ return emptyChange();
391
+ if (this.unclaimed.has(carrier)) {
392
+ let before;
393
+ for (const res of this.unclaimed.keys()) {
394
+ if (res === carrier)
395
+ break;
396
+ before = res;
397
+ }
398
+ if (before)
399
+ this.tellUnclaimed(before);
400
+ }
401
+ this.unclaimed.delete(carrier);
402
+ return this.readChanges.get(carrier) ?? emptyChange();
403
+ }
404
+ /** A change its taker showed to an agent: its ended records count as told, and the definitions it describes as shown, unless the taker showed it without its blocks (`blocksShown: false`). */
405
+ told(cs) {
406
+ this.presence.markTold(cs.ended);
407
+ if (cs.blocksShown !== false)
408
+ this.presence.markShown(cs);
409
+ }
410
+ /** A change its taker couldn't show, told as a notice instead, or dropped when none can be written. */
411
+ tell(cs) {
412
+ this.notice(cs);
413
+ }
414
+ changeOf(carrier) {
415
+ return this.readChanges.get(carrier) ?? emptyChange();
416
+ }
417
+ // ── recovery (RFC §6.1) ──
418
+ scheduleRecovery() {
419
+ if (!this.presence.active)
420
+ return;
421
+ if (this.recovering) {
422
+ this.recoverAgain = true;
423
+ return;
424
+ }
425
+ if (this.recoveryTimer)
426
+ return;
427
+ this.recoveryTimer = setTimeout(() => {
428
+ this.recoveryTimer = null;
429
+ void this.recover();
430
+ }, RECOVERY_DEBOUNCE_MS);
431
+ this.recoveryTimer.unref?.();
432
+ }
433
+ /**
434
+ * Recovering: re-read tools/list (presence, declared definitions), then
435
+ * toolsets/list (dynamic-tool state in full), so every tool the statement
436
+ * names is already known, unless tools/list failed: a tool the last list
437
+ * lacks then stays unknown until a later change or list starts a recovery.
438
+ * Each applies where it's read. What
439
+ * changed is told. (stdio and WebSocket deliver in order, so no early rev is
440
+ * held.)
441
+ */
442
+ async recover() {
443
+ const connection = this.connection;
444
+ if (!connection || !connection.isConnected)
445
+ return;
446
+ this.recovering = true;
447
+ try {
448
+ await this.ownToolList(connection);
449
+ // RFC §6.1: toolsets/list after tools/list, so every tool it names is known, unless tools/list failed.
450
+ if (connection !== this.connection)
451
+ return;
452
+ const lists = await this.ownToolsetList(connection, LIST_TIMEOUT_MS, 'recovery');
453
+ if (connection !== this.connection)
454
+ return;
455
+ this.deliver(lists);
456
+ }
457
+ finally {
458
+ this.recovering = false;
459
+ if (this.recoverAgain) {
460
+ this.recoverAgain = false;
461
+ this.scheduleRecovery();
462
+ }
463
+ }
464
+ }
465
+ /** The tap applies the list and tells its change as it's read. */
466
+ async ownToolList(connection) {
467
+ try {
468
+ await connection.sendToolsList();
469
+ }
470
+ catch (error) {
471
+ this.log(`tools/list failed: ${this.failure(error)}`);
472
+ }
473
+ }
474
+ async ownToolsetList(connection, timeoutMs, why) {
475
+ try {
476
+ const r = await connection.sendToolsetsList(timeoutMs);
477
+ return this.takeChange(r);
478
+ }
479
+ catch (error) {
480
+ this.log(`toolsets/list (${why}) failed: ${this.failure(error)}`);
481
+ return emptyChange();
482
+ }
483
+ }
484
+ /**
485
+ * After every (re)connect, once its policy has settled (RFC §5.3): tools/list
486
+ * then toolsets/list, the truth. What the server reports open is adopted
487
+ * (a game kept across the reconnect); what the host believed open and it
488
+ * doesn't report is closed, never re-opened here, since opening can start a
489
+ * new game. What ended meanwhile is told, each `ended` id once.
490
+ */
491
+ async connected(connection) {
492
+ if (connection !== this.connection || !connection.isConnected)
493
+ return;
494
+ this.sync(connection.transportEpoch, connection);
495
+ if (!this.presence.active)
496
+ return;
497
+ const epoch = connection.transportEpoch;
498
+ await this.ownToolList(connection);
499
+ if (connection !== this.connection || connection.transportEpoch !== epoch)
500
+ return;
501
+ const lists = await this.ownToolsetList(connection, LIST_TIMEOUT_MS, 'after connecting');
502
+ if (connection !== this.connection || connection.transportEpoch !== epoch)
503
+ return;
504
+ this.deliver(lists);
505
+ }
506
+ /**
507
+ * Re-read what the server holds open (titles, visibility) without telling
508
+ * it, for look. Returns what the re-read changed, taken from the notice it
509
+ * would otherwise get when `shown` (its result reaches an agent): look then
510
+ * either shows it (told) or hands it back (tell). Otherwise the change is
511
+ * left to its notice, and returned to be shown to the caller alone. Null
512
+ * when nothing was read.
513
+ */
514
+ async refreshOpen(opts) {
515
+ if (!this.presence.active)
516
+ return null;
517
+ const connection = this.connection;
518
+ if (!connection || !connection.isConnected)
519
+ return null;
520
+ try {
521
+ const r = await connection.sendToolsetsList(REFRESH_TIMEOUT_MS);
522
+ if (!isObj(r))
523
+ return null;
524
+ return opts.shown ? this.takeChange(r) : this.changeOf(r);
525
+ }
526
+ catch (error) {
527
+ this.log(`toolsets/list (look) failed: ${this.failure(error)}`);
528
+ return null;
529
+ }
530
+ }
531
+ // ── the host's acts ──
532
+ /** toolsets/open (RFC §5.2). The server's refusal rejects. Its change is
533
+ * taken when `shown` (its result reaches an agent), else left to its
534
+ * notice and returned for the caller alone; close's is the same. */
535
+ async open(toolset, instance, options, opts) {
536
+ const connection = this.live();
537
+ const r = await connection.sendToolsetsOpen({ toolset, ...(instance ? { instance } : {}), ...(options ? { options } : {}) }, TOOLSET_REQUEST_TIMEOUT_MS);
538
+ const res = isObj(r) ? r : {};
539
+ const inst = typeof res.instance === 'string' && res.instance ? res.instance : instance;
540
+ // RFC §5.2: a proposed name is used as given or the open refused.
541
+ if (instance && inst !== instance)
542
+ this.log(`proposed instance ${this.field(instance)} came back as ${this.field(inst)} (RFC-014 §5.2 forbids renaming): using the server's`);
543
+ const key = keyOf(toolset, this.presence.declared[toolset]?.instances ? inst : undefined);
544
+ const title = typeof res.title === 'string' && res.title ? res.title : this.presence.openRecords().find((o) => keyOf(o.toolset, o.instance) === key)?.title;
545
+ return {
546
+ key,
547
+ ...(title ? { title } : {}),
548
+ content: Array.isArray(res.content) ? res.content : [],
549
+ change: opts.shown ? this.takeChange(res) : this.changeOf(res),
550
+ adopted: res.adopted === true,
551
+ };
552
+ }
553
+ /** toolsets/close (RFC §5.2). */
554
+ async close(toolset, instance, opts) {
555
+ const connection = this.live();
556
+ const r = await connection.sendToolsetsClose({ toolset, ...(instance ? { instance } : {}) }, TOOLSET_REQUEST_TIMEOUT_MS);
557
+ const res = isObj(r) ? r : {};
558
+ return { content: Array.isArray(res.content) ? res.content : [], change: opts.shown ? this.takeChange(res) : this.changeOf(res) };
559
+ }
560
+ /** Open or close an undeclared group (RFC §5.4): the host's own act; the server isn't told. */
561
+ setGroupOpen(group, open) {
562
+ const before = this.presence.view();
563
+ if (open)
564
+ this.presence.openGroup(group);
565
+ else
566
+ this.presence.closeGroup(group);
567
+ return this.presence.diff(before, this.presence.view());
568
+ }
569
+ /**
570
+ * A manifest re-fetch (RFC §5.1): the `toolsets` domain's declarations, as
571
+ * the server sent them. A toolset removed ends its instances; a server
572
+ * that withdraws toolHints is hint-less from then on. What changed is told.
573
+ */
574
+ manifestChanged(declarations) {
575
+ const before = this.presence.view();
576
+ const wasActive = this.presence.active;
577
+ if (!declarations || declarations.toolHints !== true) {
578
+ if (!wasActive)
579
+ return;
580
+ this.presence.deactivate();
581
+ this.log('the server withdrew toolHints: every tool is static from now on');
582
+ const cs = this.presence.diff(before, this.presence.view());
583
+ cs.notes.push(`${this.serverId} no longer presents toolsets: its tools are called directly now, by name, not through ${META_TOOL_NAMES.act}.`);
584
+ this.deliver(cs);
585
+ return;
586
+ }
587
+ if (!wasActive) {
588
+ // Hints that arrive by a manifest change take effect at the next
589
+ // connection's handshake: revs belong to a connection's first message.
590
+ this.log('the server now advertises toolHints: in effect from its next connection');
591
+ return;
592
+ }
593
+ this.presence.setManifest(declarations);
594
+ this.deliver(this.presence.diff(before, this.presence.view()));
595
+ }
596
+ /** At a rebuild of the model's tool list (RFC §4.2), tell where the tools it moved went: `moves`, those still pending, or every one pending. A move a list has undone since the rebuild listed the server isn't told; the next rebuild publishes the undoing. */
597
+ applyMoves(moves) {
598
+ const moved = this.presence.applyMoves(moves);
599
+ if (!moved.length)
600
+ return;
601
+ const cs = emptyChange();
602
+ cs.notes.push(...moved.map(({ name, to }) => to === 'dynamic'
603
+ ? `${name} left your tool list: it's announced when available, and called with ${META_TOOL_NAMES.act} {server:"${this.serverId}", tool:"${name}", args}.`
604
+ : `${name} is in your tool list now, as ${this.deps.listedName(this.serverId, name)}.`));
605
+ this.deliver(cs);
606
+ }
607
+ }
608
+ /** Every server's controller, for the meta-tools and reminders. */
609
+ export class ToolsetHub {
610
+ deps;
611
+ controllers = new Map();
612
+ /** Per agent: the markers it has had in view (seen), and whether a reminder is due. */
613
+ seen = new Map();
614
+ due = new Set();
615
+ constructor(deps) {
616
+ this.deps = deps;
617
+ }
618
+ /** The controller for a server, made at its first connect. */
619
+ controllerFor(config) {
620
+ let c = this.controllers.get(config.id);
621
+ if (!c) {
622
+ c = new ToolsetController(config.id, config, this.deps);
623
+ this.controllers.set(config.id, c);
624
+ }
625
+ else {
626
+ c.setConfig(config);
627
+ }
628
+ return c;
629
+ }
630
+ get(serverId) {
631
+ return this.controllers.get(serverId);
632
+ }
633
+ /** A server's configuration went (removed): its controller lets go of the
634
+ * connection it holds, and is forgotten, so a server added later under
635
+ * the same id starts from nothing. */
636
+ forget(serverId) {
637
+ this.controllers.get(serverId)?.release();
638
+ this.controllers.delete(serverId);
639
+ }
640
+ all() {
641
+ return [...this.controllers.values()];
642
+ }
643
+ /** The servers the meta-tools speak for now. */
644
+ presenting() {
645
+ return this.all().filter((c) => c.presents);
646
+ }
647
+ /** Every open key's marker, across servers. */
648
+ openMarkers() {
649
+ return this.all().flatMap((c) => (c.presence.active ? c.presence.openKeys().map((k) => c.presence.label(k)) : []));
650
+ }
651
+ /**
652
+ * A compile built to send for `agentName` (RFC §8.3): which open toolsets'
653
+ * announcements the request carries. One the agent has had in view and the
654
+ * request no longer carries (compressed, or out of the window) makes a
655
+ * reminder due.
656
+ */
657
+ observeCompile(agentName, messages) {
658
+ const open = this.openMarkers();
659
+ if (open.length === 0) {
660
+ this.seen.delete(agentName);
661
+ this.due.delete(agentName);
662
+ return;
663
+ }
664
+ const text = compiledText(messages);
665
+ const seen = this.seen.get(agentName) ?? new Set();
666
+ let missing = false;
667
+ for (const marker of open) {
668
+ if (text.includes(marker))
669
+ seen.add(marker);
670
+ else if (seen.has(marker))
671
+ missing = true;
672
+ }
673
+ for (const marker of [...seen])
674
+ if (!open.includes(marker))
675
+ seen.delete(marker);
676
+ this.seen.set(agentName, seen);
677
+ // Due until a request carries every marker the agent has seen again: a
678
+ // gather that doesn't send (a preview) leaves it due for the next start.
679
+ if (missing)
680
+ this.due.add(agentName);
681
+ else
682
+ this.due.delete(agentName);
683
+ }
684
+ /** The reminder due for `agentName`, or null when none is. */
685
+ reminderFor(agentName) {
686
+ if (!this.due.has(agentName))
687
+ return null;
688
+ const parts = this.presenting().filter((c) => this.deps.canNotify(c.serverId)).map((c) => c.presence.reminder()).filter((r) => !!r);
689
+ if (parts.length === 0)
690
+ return null;
691
+ return `Open toolsets (a reminder: their earlier announcements have left your context):\n${parts.join('\n')}`;
692
+ }
693
+ }
694
+ /** The text a compiled request carries, for marker search: text blocks and tool results' text, at any depth. */
695
+ function compiledText(messages) {
696
+ const out = [];
697
+ const walk = (v, depth) => {
698
+ if (depth > 6)
699
+ return;
700
+ if (typeof v === 'string') {
701
+ out.push(v);
702
+ return;
703
+ }
704
+ if (Array.isArray(v)) {
705
+ for (const x of v)
706
+ walk(x, depth + 1);
707
+ return;
708
+ }
709
+ if (!isObj(v))
710
+ return;
711
+ if (typeof v.text === 'string')
712
+ out.push(v.text);
713
+ if (v.content !== undefined)
714
+ walk(v.content, depth + 1);
715
+ };
716
+ for (const m of messages)
717
+ walk(isObj(m) ? m.content : undefined, 0);
718
+ return out.join('\n');
719
+ }
720
+ //# sourceMappingURL=toolset-host.js.map