@animalabs/agent-framework 0.27.0 → 0.29.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 (344) hide show
  1. package/README.md +145 -41
  2. package/dist/src/agent.d.ts +29 -2
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +73 -12
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/mcp-server-core.d.ts.map +1 -1
  7. package/dist/src/api/mcp-server-core.js +3 -12
  8. package/dist/src/api/mcp-server-core.js.map +1 -1
  9. package/dist/src/api/server.d.ts.map +1 -1
  10. package/dist/src/api/server.js +11 -5
  11. package/dist/src/api/server.js.map +1 -1
  12. package/dist/src/auto-continue.d.ts +5 -2
  13. package/dist/src/auto-continue.d.ts.map +1 -1
  14. package/dist/src/auto-continue.js +9 -5
  15. package/dist/src/auto-continue.js.map +1 -1
  16. package/dist/src/code-execution/isolate/runner.d.ts +7 -5
  17. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
  18. package/dist/src/code-execution/isolate/runner.js +7 -5
  19. package/dist/src/code-execution/isolate/runner.js.map +1 -1
  20. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  21. package/dist/src/code-execution/isolate/scripts.js +3 -2
  22. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  23. package/dist/src/cut-off-round.d.ts +24 -0
  24. package/dist/src/cut-off-round.d.ts.map +1 -0
  25. package/dist/src/cut-off-round.js +87 -0
  26. package/dist/src/cut-off-round.js.map +1 -0
  27. package/dist/src/framework.d.ts +468 -125
  28. package/dist/src/framework.d.ts.map +1 -1
  29. package/dist/src/framework.js +3651 -1111
  30. package/dist/src/framework.js.map +1 -1
  31. package/dist/src/gate/event-gate.d.ts +133 -13
  32. package/dist/src/gate/event-gate.d.ts.map +1 -1
  33. package/dist/src/gate/event-gate.js +654 -59
  34. package/dist/src/gate/event-gate.js.map +1 -1
  35. package/dist/src/gate/index.d.ts +3 -2
  36. package/dist/src/gate/index.d.ts.map +1 -1
  37. package/dist/src/gate/index.js +2 -1
  38. package/dist/src/gate/index.js.map +1 -1
  39. package/dist/src/gate/reply-judge.d.ts +116 -0
  40. package/dist/src/gate/reply-judge.d.ts.map +1 -0
  41. package/dist/src/gate/reply-judge.js +467 -0
  42. package/dist/src/gate/reply-judge.js.map +1 -0
  43. package/dist/src/gate/types.d.ts +173 -2
  44. package/dist/src/gate/types.d.ts.map +1 -1
  45. package/dist/src/heartbeat/heartbeat.d.ts +202 -0
  46. package/dist/src/heartbeat/heartbeat.d.ts.map +1 -0
  47. package/dist/src/heartbeat/heartbeat.js +778 -0
  48. package/dist/src/heartbeat/heartbeat.js.map +1 -0
  49. package/dist/src/heartbeat/reminder-time.d.ts +5 -0
  50. package/dist/src/heartbeat/reminder-time.d.ts.map +1 -0
  51. package/dist/src/heartbeat/reminder-time.js +107 -0
  52. package/dist/src/heartbeat/reminder-time.js.map +1 -0
  53. package/dist/src/held-writes.d.ts +3 -3
  54. package/dist/src/held-writes.js +3 -3
  55. package/dist/src/host-presentation.d.ts +170 -0
  56. package/dist/src/host-presentation.d.ts.map +1 -0
  57. package/dist/src/host-presentation.js +561 -0
  58. package/dist/src/host-presentation.js.map +1 -0
  59. package/dist/src/index.d.ts +5 -1
  60. package/dist/src/index.d.ts.map +1 -1
  61. package/dist/src/index.js +3 -1
  62. package/dist/src/index.js.map +1 -1
  63. package/dist/src/line-reader.d.ts +4 -0
  64. package/dist/src/line-reader.d.ts.map +1 -0
  65. package/dist/src/line-reader.js +59 -0
  66. package/dist/src/line-reader.js.map +1 -0
  67. package/dist/src/mcpl/capability-mask.d.ts.map +1 -1
  68. package/dist/src/mcpl/capability-mask.js +3 -2
  69. package/dist/src/mcpl/capability-mask.js.map +1 -1
  70. package/dist/src/mcpl/channel-registry.d.ts +34 -6
  71. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  72. package/dist/src/mcpl/channel-registry.js +133 -54
  73. package/dist/src/mcpl/channel-registry.js.map +1 -1
  74. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  75. package/dist/src/mcpl/conversation-router.js +2 -1
  76. package/dist/src/mcpl/conversation-router.js.map +1 -1
  77. package/dist/src/mcpl/convert-block.d.ts +1 -0
  78. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  79. package/dist/src/mcpl/convert-block.js +16 -4
  80. package/dist/src/mcpl/convert-block.js.map +1 -1
  81. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  82. package/dist/src/mcpl/hook-orchestrator.js +4 -3
  83. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  84. package/dist/src/mcpl/inbound-source.d.ts +29 -11
  85. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  86. package/dist/src/mcpl/inbound-source.js +43 -70
  87. package/dist/src/mcpl/inbound-source.js.map +1 -1
  88. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  89. package/dist/src/mcpl/modern-connection.js +11 -7
  90. package/dist/src/mcpl/modern-connection.js.map +1 -1
  91. package/dist/src/mcpl/protocol-family.d.ts +3 -2
  92. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  93. package/dist/src/mcpl/protocol-family.js +25 -3
  94. package/dist/src/mcpl/protocol-family.js.map +1 -1
  95. package/dist/src/mcpl/push-handler.js +2 -2
  96. package/dist/src/mcpl/push-handler.js.map +1 -1
  97. package/dist/src/mcpl/references.d.ts +12 -40
  98. package/dist/src/mcpl/references.d.ts.map +1 -1
  99. package/dist/src/mcpl/references.js +29 -23
  100. package/dist/src/mcpl/references.js.map +1 -1
  101. package/dist/src/mcpl/server-connection.d.ts +18 -8
  102. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  103. package/dist/src/mcpl/server-connection.js +70 -30
  104. package/dist/src/mcpl/server-connection.js.map +1 -1
  105. package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
  106. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
  107. package/dist/src/mcpl/skip-reply-wake.js +25 -0
  108. package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
  109. package/dist/src/mcpl/tags.d.ts +9 -2
  110. package/dist/src/mcpl/tags.d.ts.map +1 -1
  111. package/dist/src/mcpl/tags.js +12 -2
  112. package/dist/src/mcpl/tags.js.map +1 -1
  113. package/dist/src/mcpl/tool-presence.d.ts +66 -14
  114. package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
  115. package/dist/src/mcpl/tool-presence.js +150 -80
  116. package/dist/src/mcpl/tool-presence.js.map +1 -1
  117. package/dist/src/mcpl/tool-signature.d.ts +10 -3
  118. package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
  119. package/dist/src/mcpl/tool-signature.js +60 -30
  120. package/dist/src/mcpl/tool-signature.js.map +1 -1
  121. package/dist/src/mcpl/toolset-host.d.ts +88 -55
  122. package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
  123. package/dist/src/mcpl/toolset-host.js +220 -111
  124. package/dist/src/mcpl/toolset-host.js.map +1 -1
  125. package/dist/src/mcpl/toolsets-module.d.ts +27 -35
  126. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  127. package/dist/src/mcpl/toolsets-module.js +313 -91
  128. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  129. package/dist/src/mcpl/transport.d.ts +1 -1
  130. package/dist/src/mcpl/transport.d.ts.map +1 -1
  131. package/dist/src/mcpl/transport.js +16 -16
  132. package/dist/src/mcpl/transport.js.map +1 -1
  133. package/dist/src/mcpl/types.d.ts +31 -13
  134. package/dist/src/mcpl/types.d.ts.map +1 -1
  135. package/dist/src/mcpl/types.js.map +1 -1
  136. package/dist/src/module-registry.d.ts +10 -8
  137. package/dist/src/module-registry.d.ts.map +1 -1
  138. package/dist/src/module-registry.js +15 -9
  139. package/dist/src/module-registry.js.map +1 -1
  140. package/dist/src/modules/api/index.d.ts.map +1 -1
  141. package/dist/src/modules/api/index.js +4 -2
  142. package/dist/src/modules/api/index.js.map +1 -1
  143. package/dist/src/modules/discord/index.d.ts.map +1 -1
  144. package/dist/src/modules/discord/index.js +33 -25
  145. package/dist/src/modules/discord/index.js.map +1 -1
  146. package/dist/src/modules/history/index.d.ts.map +1 -1
  147. package/dist/src/modules/history/index.js +30 -17
  148. package/dist/src/modules/history/index.js.map +1 -1
  149. package/dist/src/modules/sender-name.d.ts +7 -0
  150. package/dist/src/modules/sender-name.d.ts.map +1 -0
  151. package/dist/src/modules/sender-name.js +10 -0
  152. package/dist/src/modules/sender-name.js.map +1 -0
  153. package/dist/src/modules/workspace/answers.d.ts +26 -0
  154. package/dist/src/modules/workspace/answers.d.ts.map +1 -0
  155. package/dist/src/modules/workspace/answers.js +138 -0
  156. package/dist/src/modules/workspace/answers.js.map +1 -0
  157. package/dist/src/modules/workspace/branch-intent.d.ts +5 -3
  158. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  159. package/dist/src/modules/workspace/branch-intent.js +6 -2
  160. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  161. package/dist/src/modules/workspace/direct-write.d.ts +3 -0
  162. package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
  163. package/dist/src/modules/workspace/direct-write.js +59 -24
  164. package/dist/src/modules/workspace/direct-write.js.map +1 -1
  165. package/dist/src/modules/workspace/disk-agreement.d.ts +33 -11
  166. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  167. package/dist/src/modules/workspace/disk-agreement.js +48 -17
  168. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  169. package/dist/src/modules/workspace/edit-text.d.ts +2 -1
  170. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
  171. package/dist/src/modules/workspace/edit-text.js +4 -2
  172. package/dist/src/modules/workspace/edit-text.js.map +1 -1
  173. package/dist/src/modules/workspace/effects.d.ts +4 -2
  174. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  175. package/dist/src/modules/workspace/effects.js +67 -50
  176. package/dist/src/modules/workspace/effects.js.map +1 -1
  177. package/dist/src/modules/workspace/index.d.ts +63 -51
  178. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  179. package/dist/src/modules/workspace/index.js +980 -547
  180. package/dist/src/modules/workspace/index.js.map +1 -1
  181. package/dist/src/modules/workspace/observe.d.ts +125 -82
  182. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  183. package/dist/src/modules/workspace/observe.js +465 -241
  184. package/dist/src/modules/workspace/observe.js.map +1 -1
  185. package/dist/src/modules/workspace/read-page.d.ts.map +1 -1
  186. package/dist/src/modules/workspace/read-page.js +7 -1
  187. package/dist/src/modules/workspace/read-page.js.map +1 -1
  188. package/dist/src/modules/workspace/reconcile.d.ts +7 -1
  189. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  190. package/dist/src/modules/workspace/reconcile.js +156 -50
  191. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  192. package/dist/src/modules/workspace/result-text.d.ts +1 -0
  193. package/dist/src/modules/workspace/result-text.d.ts.map +1 -1
  194. package/dist/src/modules/workspace/result-text.js.map +1 -1
  195. package/dist/src/modules/workspace/sync.d.ts +9 -0
  196. package/dist/src/modules/workspace/sync.d.ts.map +1 -1
  197. package/dist/src/modules/workspace/sync.js +14 -0
  198. package/dist/src/modules/workspace/sync.js.map +1 -1
  199. package/dist/src/modules/workspace/types.d.ts +96 -43
  200. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  201. package/dist/src/modules/workspace/types.js.map +1 -1
  202. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  203. package/dist/src/modules/workspace/watcher.js +8 -3
  204. package/dist/src/modules/workspace/watcher.js.map +1 -1
  205. package/dist/src/notice-tags.d.ts +79 -0
  206. package/dist/src/notice-tags.d.ts.map +1 -0
  207. package/dist/src/notice-tags.js +83 -0
  208. package/dist/src/notice-tags.js.map +1 -0
  209. package/dist/src/prose-drafts.d.ts +4 -3
  210. package/dist/src/prose-drafts.d.ts.map +1 -1
  211. package/dist/src/prose-drafts.js +3 -2
  212. package/dist/src/prose-drafts.js.map +1 -1
  213. package/dist/src/reasoning-loss-notice.d.ts +23 -0
  214. package/dist/src/reasoning-loss-notice.d.ts.map +1 -0
  215. package/dist/src/reasoning-loss-notice.js +52 -0
  216. package/dist/src/reasoning-loss-notice.js.map +1 -0
  217. package/dist/src/reasoning-loss.d.ts +24 -0
  218. package/dist/src/reasoning-loss.d.ts.map +1 -0
  219. package/dist/src/reasoning-loss.js +102 -0
  220. package/dist/src/reasoning-loss.js.map +1 -0
  221. package/dist/src/send-tools.d.ts +3 -2
  222. package/dist/src/send-tools.d.ts.map +1 -1
  223. package/dist/src/send-tools.js +3 -2
  224. package/dist/src/send-tools.js.map +1 -1
  225. package/dist/src/sent-rows.js +1 -1
  226. package/dist/src/silent-heartbeat.d.ts +1 -53
  227. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  228. package/dist/src/silent-heartbeat.js +56 -3
  229. package/dist/src/silent-heartbeat.js.map +1 -1
  230. package/dist/src/supplied-text.d.ts +35 -0
  231. package/dist/src/supplied-text.d.ts.map +1 -0
  232. package/dist/src/supplied-text.js +141 -0
  233. package/dist/src/supplied-text.js.map +1 -0
  234. package/dist/src/thinking-drops.d.ts +4 -11
  235. package/dist/src/thinking-drops.d.ts.map +1 -1
  236. package/dist/src/thinking-drops.js +7 -14
  237. package/dist/src/thinking-drops.js.map +1 -1
  238. package/dist/src/tool-args.d.ts +12 -0
  239. package/dist/src/tool-args.d.ts.map +1 -1
  240. package/dist/src/tool-args.js +39 -4
  241. package/dist/src/tool-args.js.map +1 -1
  242. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  243. package/dist/src/tool-image-ledger.js +2 -1
  244. package/dist/src/tool-image-ledger.js.map +1 -1
  245. package/dist/src/tool-presentation.d.ts.map +1 -1
  246. package/dist/src/tool-presentation.js +5 -4
  247. package/dist/src/tool-presentation.js.map +1 -1
  248. package/dist/src/tool-result-guard.d.ts +8 -4
  249. package/dist/src/tool-result-guard.d.ts.map +1 -1
  250. package/dist/src/tool-result-guard.js +23 -11
  251. package/dist/src/tool-result-guard.js.map +1 -1
  252. package/dist/src/tune-out/coordinator.d.ts +3 -2
  253. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  254. package/dist/src/tune-out/coordinator.js +24 -26
  255. package/dist/src/tune-out/coordinator.js.map +1 -1
  256. package/dist/src/tune-out/tools.d.ts +4 -1
  257. package/dist/src/tune-out/tools.d.ts.map +1 -1
  258. package/dist/src/types/agent.d.ts +63 -18
  259. package/dist/src/types/agent.d.ts.map +1 -1
  260. package/dist/src/types/events.d.ts +18 -8
  261. package/dist/src/types/events.d.ts.map +1 -1
  262. package/dist/src/types/framework.d.ts +58 -0
  263. package/dist/src/types/framework.d.ts.map +1 -1
  264. package/dist/src/types/module.d.ts +27 -13
  265. package/dist/src/types/module.d.ts.map +1 -1
  266. package/dist/src/types/trace.d.ts +34 -1
  267. package/dist/src/types/trace.d.ts.map +1 -1
  268. package/package.json +3 -3
  269. package/src/agent.ts +76 -12
  270. package/src/api/mcp-server-core.ts +3 -14
  271. package/src/api/server.ts +11 -5
  272. package/src/auto-continue.ts +12 -5
  273. package/src/code-execution/isolate/runner.ts +7 -5
  274. package/src/code-execution/isolate/scripts.ts +3 -2
  275. package/src/cut-off-round.ts +98 -0
  276. package/src/framework.ts +3772 -1138
  277. package/src/gate/event-gate.ts +705 -60
  278. package/src/gate/index.ts +9 -1
  279. package/src/gate/reply-judge.ts +512 -0
  280. package/src/gate/types.ts +176 -3
  281. package/src/heartbeat/heartbeat.ts +843 -0
  282. package/src/heartbeat/reminder-time.ts +107 -0
  283. package/src/held-writes.ts +3 -3
  284. package/src/host-presentation.ts +605 -0
  285. package/src/index.ts +5 -1
  286. package/src/line-reader.ts +58 -0
  287. package/src/mcpl/capability-mask.ts +3 -2
  288. package/src/mcpl/channel-registry.ts +139 -53
  289. package/src/mcpl/conversation-router.ts +2 -1
  290. package/src/mcpl/convert-block.ts +17 -4
  291. package/src/mcpl/hook-orchestrator.ts +4 -3
  292. package/src/mcpl/inbound-source.ts +45 -70
  293. package/src/mcpl/modern-connection.ts +11 -7
  294. package/src/mcpl/protocol-family.ts +24 -3
  295. package/src/mcpl/push-handler.ts +2 -2
  296. package/src/mcpl/references.ts +31 -23
  297. package/src/mcpl/server-connection.ts +76 -34
  298. package/src/mcpl/skip-reply-wake.ts +35 -0
  299. package/src/mcpl/tags.ts +13 -2
  300. package/src/mcpl/tool-presence.ts +149 -82
  301. package/src/mcpl/tool-signature.ts +64 -32
  302. package/src/mcpl/toolset-host.ts +212 -110
  303. package/src/mcpl/toolsets-module.ts +277 -91
  304. package/src/mcpl/transport.ts +18 -14
  305. package/src/mcpl/types.ts +32 -13
  306. package/src/module-registry.ts +14 -9
  307. package/src/modules/api/index.ts +4 -2
  308. package/src/modules/discord/index.ts +37 -25
  309. package/src/modules/history/index.ts +30 -18
  310. package/src/modules/sender-name.ts +9 -0
  311. package/src/modules/workspace/answers.ts +151 -0
  312. package/src/modules/workspace/branch-intent.ts +10 -5
  313. package/src/modules/workspace/direct-write.ts +59 -23
  314. package/src/modules/workspace/disk-agreement.ts +52 -21
  315. package/src/modules/workspace/edit-text.ts +7 -5
  316. package/src/modules/workspace/effects.ts +69 -50
  317. package/src/modules/workspace/index.ts +966 -556
  318. package/src/modules/workspace/observe.ts +497 -242
  319. package/src/modules/workspace/read-page.ts +7 -1
  320. package/src/modules/workspace/reconcile.ts +184 -49
  321. package/src/modules/workspace/result-text.ts +2 -0
  322. package/src/modules/workspace/sync.ts +16 -0
  323. package/src/modules/workspace/types.ts +96 -43
  324. package/src/modules/workspace/watcher.ts +8 -3
  325. package/src/notice-tags.ts +84 -0
  326. package/src/prose-drafts.ts +4 -3
  327. package/src/reasoning-loss-notice.ts +64 -0
  328. package/src/reasoning-loss.ts +120 -0
  329. package/src/send-tools.ts +3 -2
  330. package/src/sent-rows.ts +1 -1
  331. package/src/silent-heartbeat.ts +4 -3
  332. package/src/supplied-text.ts +154 -0
  333. package/src/thinking-drops.ts +10 -16
  334. package/src/tool-args.ts +38 -4
  335. package/src/tool-image-ledger.ts +2 -1
  336. package/src/tool-presentation.ts +5 -4
  337. package/src/tool-result-guard.ts +26 -11
  338. package/src/tune-out/coordinator.ts +25 -27
  339. package/src/tune-out/tools.ts +4 -1
  340. package/src/types/agent.ts +63 -18
  341. package/src/types/events.ts +18 -8
  342. package/src/types/framework.ts +59 -0
  343. package/src/types/module.ts +28 -13
  344. package/src/types/trace.ts +36 -1
@@ -1,31 +1,61 @@
1
+ /**
2
+ * The host's own interface to MCPL dynamic tools (RFC-014 §8.2): four tools,
3
+ * constant and static, aggregated across every server that presents
4
+ * toolsets.
5
+ *
6
+ * toolsets--open {server?, toolset?, instance?, options?}
7
+ * toolsets--act {server?, tool, instance?, args?}
8
+ * toolsets--look {server?, toolset?, instance?, tool?}
9
+ * toolsets--close {server?, toolset?, instance?}
10
+ *
11
+ * Dynamic tools never enter the API tool list: changing it rebuilds the
12
+ * prompt cache. They're announced in the conversation (in these tools'
13
+ * results, in the results of calls that changed them, in push events and in
14
+ * notices) and called through `toolsets--act`, which this module only
15
+ * resolves (resolveAct): the framework dispatches the call it resolves to
16
+ * down the same path as a direct call of the server's tool. The module is registered at create(),
17
+ * for every agent (the host's own toolsets use it too), unless a module given to create() holds
18
+ * the name; its tools never change after that.
19
+ *
20
+ * Its gatherContext publishes the RFC §8.3 reminder, as a stored `afterUser`
21
+ * injection, when an agent's earlier announcements of an open toolset have
22
+ * left its context (ToolsetHub.observeCompile).
23
+ *
24
+ * Ported from mcpl-cc-bridge's meta-tools.ts, the reference host.
25
+ */
26
+ import { nameToTypeBack, quotedValue, suppliedValue } from '../supplied-text.js';
1
27
  import { unknownToolMessage } from '../modules/unknown-tool.js';
2
28
  import { META_TOOL_NAMES, TOOLSETS_MODULE } from './toolset-host.js';
3
29
  import { keyOf } from './tool-presence.js';
4
- import { checkArgs, renderToolDefinition } from './tool-signature.js';
30
+ import { checkArgs, jsonText, renderToolDefinition } from './tool-signature.js';
31
+ import { McplRequestError } from './server-connection.js';
32
+ import { BUILTIN_TOOLSET } from '../host-presentation.js';
5
33
  /** The namespace a reminder's stored injection carries. */
6
34
  export const TOOLSETS_REMINDER_NAMESPACE = 'mcpl-toolsets';
7
35
  const META_TOOLS = [
8
36
  {
9
37
  name: 'open',
10
- description: 'Open a toolset of an MCPL server (a game, a building mode, a card table) so its tools can be called with toolsets--act. ' +
11
- 'Returns what the server shows on opening and the tools available now. server may be left out when one server has the toolset, ' +
12
- 'and toolset when the server has only one; instance names a separate copy where a toolset allows several (two games at once); ' +
38
+ description: 'Open a toolset so its tools can be called with toolsets--act: the host\'s own settings and controls (toolset "settings"), ' +
39
+ 'an MCPL server\'s tab (server alone: its tools without putting them in your tool list; a closed tab is connected again; ' +
40
+ 'full: true puts the server back in your tool list), or a toolset an MCPL server declares (a game, a building mode, a card table: ' +
41
+ 'server and toolset). Returns the tools available now. instance names a separate copy where a toolset allows several; ' +
13
42
  'options as toolsets--look lists them.',
14
43
  inputSchema: {
15
44
  type: 'object',
16
45
  properties: {
17
46
  server: { type: 'string', description: 'the MCPL server id' },
18
- toolset: { type: 'string' },
47
+ toolset: { type: 'string', description: '"settings" for the host\'s own; a server\'s toolset with server' },
19
48
  instance: { type: 'string', description: 'a name for this copy (toolsets with instances only)' },
20
49
  options: { type: 'object', description: 'toolset options, as toolsets--look shows them' },
50
+ full: { type: 'boolean', description: 'with server alone: put the server\'s tools back in your tool list' },
21
51
  },
22
52
  },
23
53
  },
24
54
  {
25
55
  name: 'act',
26
- description: 'Call a tool that was announced in the conversation (a tool of an open toolset, or another dynamic tool); they are not in your tool list. ' +
56
+ description: 'Call a tool of an open toolset (the host\'s "settings", a server\'s tab, or a server\'s own toolset) or another dynamic tool, as announced in the conversation; they are not in your tool list. ' +
27
57
  'args are the arguments its signature shows; instance picks the open copy when several are open; server is needed only when two servers have the tool. ' +
28
- 'Returns the server\'s result, then any change in which tools are available.',
58
+ 'Returns the server\'s result, then any change it made in which tools are available, unless that change already went out as a notice. toolsets--look shows what is available now.',
29
59
  inputSchema: {
30
60
  type: 'object',
31
61
  properties: {
@@ -39,7 +69,7 @@ const META_TOOLS = [
39
69
  },
40
70
  {
41
71
  name: 'look',
42
- description: 'Show what is open and what can be opened on MCPL servers, with the tools usable now: announcements earlier in the conversation may be out of date. ' +
72
+ description: 'Show what is open and what can be opened (the host\'s "settings", each MCPL server\'s tab, servers\' own toolsets), with the tools usable now: announcements earlier in the conversation may be out of date. ' +
43
73
  'With tool: that tool\'s full description and input schema. With toolset or instance: just that one.',
44
74
  inputSchema: {
45
75
  type: 'object',
@@ -53,18 +83,52 @@ const META_TOOLS = [
53
83
  },
54
84
  {
55
85
  name: 'close',
56
- description: 'Close an open toolset or instance: its tools and its events stop. Closing a game in progress may count as leaving it.',
86
+ description: 'Close an open toolset or instance: its tools stop, and for a toolset the server declares, the server is told and stops its events too. Closing a game in progress may count as leaving it. ' +
87
+ 'server alone moves a server out of your tool list into a tab (connected, its events still come), or hides its open tab; with disconnect: true it is disconnected until you open it again.',
57
88
  inputSchema: {
58
89
  type: 'object',
59
90
  properties: {
60
91
  server: { type: 'string', description: 'the MCPL server id (may be left out when one server has the toolset open)' },
61
- toolset: { type: 'string', description: 'may be left out when only one thing is open on the server' },
92
+ toolset: { type: 'string', description: '"settings" for the host\'s own; may be left out when only one thing is open on the server' },
62
93
  instance: { type: 'string' },
94
+ disconnect: { type: 'boolean', description: 'with server alone: disconnect it as a closed tab until it is opened again' },
63
95
  },
64
96
  },
65
97
  },
66
98
  ];
67
99
  const str = (v) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
100
+ const selector = (v, has) => typeof v === 'string' && v !== v.trim() && v.trim() !== '' && has(v) ? v : str(v);
101
+ const presentersOf = (hub, server) => {
102
+ const id = str(server);
103
+ if (!id)
104
+ return hub.presenting();
105
+ const c = hub.get(id);
106
+ return c ? [c] : [];
107
+ };
108
+ const toolNamed = (hub, server, v) => selector(v, (name) => presentersOf(hub, server).some((c) => !!c.presence.tool(name)));
109
+ const toolsetNamed = (hub, server, v) => selector(v, (name) => presentersOf(hub, server).some((c) => c.presence.isDeclared(name) || c.presence.undeclaredGroups.includes(name)));
110
+ const instanceNamed = (presenters, toolset, v) => selector(v, (name) => presenters.some((c) => c.presence.openRecords(toolset).some((o) => o.instance === name)));
111
+ const SELECTORS = ['server', 'toolset', 'instance', 'tool'];
112
+ function checkSelectors(args) {
113
+ for (const key of SELECTORS) {
114
+ const v = args[key];
115
+ if (v === undefined || v === null || str(v) !== undefined)
116
+ continue;
117
+ const got = typeof v === 'string' ? 'a blank string' : Array.isArray(v) ? 'a list' : typeof v === 'object' ? 'an object' : `a ${typeof v}`;
118
+ fail(`${key} must be a name, or left out; got ${got}, so nothing was done`);
119
+ }
120
+ }
121
+ /** A failure whose outcome is unknown, with how to find it out. */
122
+ export function withLookHint(serverId, text) {
123
+ const said = text.trimEnd();
124
+ return `${said}${/[.!?]$/.test(said) ? '' : '.'} ${META_TOOL_NAMES.look} {server:"${serverId}"} asks it what's open, or says it couldn't.`;
125
+ }
126
+ function requestFailure(serverId, did, unconfirmed, error) {
127
+ const message = suppliedValue(error instanceof Error ? error.message : String(error));
128
+ if (error instanceof McplRequestError && error.outcome === 'not-sent')
129
+ return fail(`${serverId} ${did}: ${message}`);
130
+ return fail(withLookHint(serverId, `${serverId} ${unconfirmed}: ${message}`));
131
+ }
68
132
  const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
69
133
  class MetaError extends Error {
70
134
  }
@@ -106,13 +170,13 @@ function pick(hub, server) {
106
170
  if (!id) {
107
171
  if (ids.length === 1)
108
172
  return hub.get(ids[0]);
109
- return fail(`name a server: ${ids.join(', ') || '(no MCPL server presents toolsets now)'}`);
173
+ return fail(ids.length ? `name a server: ${ids.join(', ')}` : NONE_PRESENTS);
110
174
  }
111
175
  const c = hub.get(id);
112
176
  if (!c)
113
177
  return fail(`no server "${id}" presents toolsets; the ones that do: ${ids.join(', ') || 'none'}`);
114
178
  if (!c.presence.active)
115
- return fail(`${id} sends no tool hints now: its tools are in your tool list`);
179
+ return fail(`${id} sends no tool hints now: its tools are called directly, by name, not through ${META_TOOL_NAMES.act}`);
116
180
  return c;
117
181
  }
118
182
  /**
@@ -125,59 +189,71 @@ function owner(hub, server, has, what) {
125
189
  const owners = hub.presenting().filter(has);
126
190
  if (owners.length === 1)
127
191
  return owners[0];
128
- return fail(owners.length
129
- ? `${what} is on several servers (${owners.map((c) => c.serverId).join(', ')}): add server`
130
- : `no server that presents toolsets has ${what}`);
192
+ if (owners.length)
193
+ return fail(`${what} is on several servers (${owners.map((c) => c.serverId).join(', ')}): add server`);
194
+ return fail(hub.presenting().length ? `no server that presents toolsets has ${what}: ${META_TOOL_NAMES.look} shows what they have` : NONE_PRESENTS);
195
+ }
196
+ const NONE_PRESENTS = 'no MCPL server presents toolsets now: MCPL tools are called directly, by name';
197
+ function noToolset(c, toolset) {
198
+ const names = [...Object.keys(c.presence.declared), ...c.presence.undeclaredGroups];
199
+ return `${c.serverId} has no toolset "${toolset}"; ${names.length ? `it has ${names.map(nameToTypeBack).join(', ')}` : 'it has none'}`;
131
200
  }
132
201
  /**
133
202
  * Resolve a `toolsets--act` call (RFC §8.2) to the dynamic tool it calls,
134
203
  * or the reason it can't be sent. The framework then dispatches it exactly as
135
204
  * a direct call of that tool, with `_meta["mcpl/instance"]` added (RFC §5.5).
136
205
  * Refused here, before anything is sent: a tool no presenting server has, a
137
- * static tool (it's in the tool list), an instance that isn't named where
206
+ * tool neither the published list nor the server's latest tools/list puts as dynamic (RFC §4.2), an instance that isn't named where
138
207
  * several are open, a group the host has closed (RFC §5.4), and arguments
139
208
  * that don't fit the definition in force for that instance (RFC §6.1).
140
209
  */
210
+ const hasInstances = (p, ts) => ts !== undefined && p.isDeclared(ts) && !!p.declared[ts].instances;
211
+ /** The key whose definition act checks a call of `tool` against (RFC §6.1): an instance's own, else the toolset's, else the listed one. */
212
+ function actKey(p, tool, instance) {
213
+ const ts = p.toolsetOf(tool);
214
+ return ts ? keyOf(ts, hasInstances(p, ts) ? instance : undefined) : keyOf('');
215
+ }
141
216
  export function resolveAct(hub, input) {
142
217
  try {
143
218
  const args = isObj(input) ? input : {};
144
- const tool = str(args.tool) ?? fail('name the tool');
219
+ checkSelectors(args);
220
+ const tool = toolNamed(hub, args.server, args.tool) ?? fail('name the tool');
145
221
  const c = owner(hub, args.server, (x) => !!x.presence.tool(tool), `a tool "${tool}"`);
146
222
  const p = c.presence;
147
223
  if (!p.tool(tool))
148
224
  fail(`${c.serverId} has no tool "${tool}": ${META_TOOL_NAMES.look} {server:"${c.serverId}"} shows what's available`);
149
- if (p.presenceOf(tool) === 'static')
150
- fail(`${tool} is not a dynamic tool: ${p.toolStatus(tool)}. Call it there.`);
225
+ if (!p.shownAs(tool, 'dynamic'))
226
+ fail(`${nameToTypeBack(tool)} is not a dynamic tool: ${p.toolStatus(tool)}. Call it there.`);
151
227
  if (args.args !== undefined && !isObj(args.args))
152
228
  fail('args must be an object of the tool\'s arguments');
153
229
  const callArgs = (args.args ?? {});
154
- let instance = str(args.instance);
155
230
  const ts = p.toolsetOf(tool);
231
+ let instance = instanceNamed([c], ts, args.instance);
156
232
  const declared = ts !== undefined && p.isDeclared(ts);
157
- const withInstances = declared && !!p.declared[ts].instances;
233
+ const withInstances = hasInstances(p, ts);
158
234
  if (withInstances && !instance) {
159
235
  // RFC §5.5: the instance rides in _meta; with exactly one open, it's that one.
160
236
  const open = p.openRecords(ts);
161
237
  if (open.length === 1)
162
238
  instance = open[0].instance;
163
239
  else if (!open.length)
164
- fail(`${tool} belongs to ${ts}, which isn't open: ${p.openHint(ts)}`);
240
+ fail(`${nameToTypeBack(tool)} belongs to ${nameToTypeBack(ts)}, which isn't open: ${p.openHint(ts)}`);
165
241
  else
166
- fail(`several ${ts} instances are open (${open.map((o) => o.instance).join(', ')}): say which with instance`);
242
+ fail(`several ${nameToTypeBack(ts)} instances are open (${open.map((o) => nameToTypeBack(o.instance)).join(', ')}): say which with instance`);
167
243
  }
168
244
  if (instance && !withInstances)
169
- fail(`${tool} ${ts ? `belongs to ${ts}, which has no instances` : 'belongs to no toolset'}: call it without instance`);
245
+ fail(`${nameToTypeBack(tool)} ${ts ? `belongs to ${nameToTypeBack(ts)}, which has no instances` : 'belongs to no toolset'}: call it without instance`);
170
246
  // RFC §5.4: the host gates calls into an undeclared group it has closed.
171
247
  if (ts && !declared && !p.isOpen(ts))
172
- fail(`${tool} is in ${ts}, which isn't open: ${p.openHint(ts)}`);
248
+ fail(`${nameToTypeBack(tool)} is in ${nameToTypeBack(ts)}, which isn't open: ${p.openHint(ts)}`);
173
249
  // RFC §6.1: arguments are checked against the definition in force for
174
250
  // that instance: its own from hints (the legal moves of one game), else
175
251
  // the toolset's, else the listed one.
176
- const key = ts ? keyOf(ts, withInstances ? instance : undefined) : keyOf('');
252
+ const key = actKey(p, tool, instance);
177
253
  const problem = checkArgs(p.defOf(key, tool).inputSchema, callArgs);
178
254
  if (problem) {
179
- fail(`Invalid arguments for ${tool} in ${p.label(key)}: ${problem}. Nothing was sent; ` +
180
- `${META_TOOL_NAMES.look} {server:"${c.serverId}", tool:"${tool}"${instance ? `, instance:"${instance}"` : ''}} shows its current definition.`);
255
+ fail(`Invalid arguments for ${nameToTypeBack(tool)} in ${p.label(key)}: ${problem}. Nothing was sent; ` +
256
+ `${META_TOOL_NAMES.look} {server:"${c.serverId}", tool:${quotedValue(tool)}${instance ? `, instance:${quotedValue(instance)}` : ''}} shows its current definition.`);
181
257
  }
182
258
  return { ok: true, serverId: c.serverId, tool, ...(instance ? { instance } : {}), args: callArgs };
183
259
  }
@@ -189,9 +265,18 @@ export function resolveAct(hub, input) {
189
265
  }
190
266
  export class ToolsetsModule {
191
267
  hub;
268
+ takerFor;
269
+ host;
192
270
  name = TOOLSETS_MODULE;
193
- constructor(hub) {
271
+ /** `takerFor`: who shows a call's result to an agent, taking the tool
272
+ * change rendered into it; null when the result may reach none, and its
273
+ * change is left to a notice (RFC-014 §6.1: told once). */
274
+ constructor(hub, takerFor,
275
+ /** The host's own toolsets (the built-in "settings" and server tabs), when the framework presents them. */
276
+ host = null) {
194
277
  this.hub = hub;
278
+ this.takerFor = takerFor;
279
+ this.host = host;
195
280
  }
196
281
  async start(_ctx) { }
197
282
  async stop() { }
@@ -211,11 +296,25 @@ export class ToolsetsModule {
211
296
  async handleToolCall(call) {
212
297
  const args = isObj(call.input) ? call.input : {};
213
298
  try {
299
+ checkSelectors(args);
300
+ const hosted = await this.hostCall(call.name, args, call.callerAgentName);
301
+ if (hosted)
302
+ return hosted;
303
+ const take = this.takerFor(call);
304
+ const lead = call.name === 'look' ? this.hostLookLead(args, call.callerAgentName) : null;
305
+ if (lead !== null) {
306
+ if (!this.hub.presenting().length)
307
+ return textResult(lead);
308
+ const theirs = await this.look(args, take);
309
+ if (theirs.isError || typeof theirs.data !== 'string')
310
+ return theirs;
311
+ return textResult(`${lead}\n\n${theirs.data}`);
312
+ }
214
313
  switch (call.name) {
215
- case 'open': return await this.open(args);
314
+ case 'open': return await this.open(args, take);
216
315
  case 'act': return this.act(args);
217
- case 'look': return await this.look(args);
218
- case 'close': return await this.close(args);
316
+ case 'look': return await this.look(args, take);
317
+ case 'close': return await this.close(args, take);
219
318
  default: return { success: false, isError: true, error: unknownToolMessage(this.name, call.name, META_TOOLS) };
220
319
  }
221
320
  }
@@ -226,16 +325,87 @@ export class ToolsetsModule {
226
325
  return { success: false, isError: true, error: message };
227
326
  }
228
327
  }
328
+ /**
329
+ * The host's own toolsets: `toolset: "settings"` alone, or `server` alone
330
+ * (a server's tab), for open, close and look; a tool a host toolset has, for
331
+ * look. Null for everything else, which the servers' toolsets answer.
332
+ */
333
+ async hostCall(name, args, agentName) {
334
+ const host = this.host;
335
+ if (!host || !agentName || !host.active(agentName))
336
+ return null;
337
+ const server = str(args.server);
338
+ const toolset = str(args.toolset);
339
+ const instance = str(args.instance);
340
+ const tool = str(args.tool);
341
+ const builtin = toolset === BUILTIN_TOOLSET && !server && !instance;
342
+ const tab = !!server && !toolset && !instance && args.options === undefined;
343
+ if (args.full !== undefined && typeof args.full !== 'boolean')
344
+ fail('full must be true or false');
345
+ if (args.disconnect !== undefined && typeof args.disconnect !== 'boolean')
346
+ fail('disconnect must be true or false');
347
+ if (tab && !host.knows(server) && !this.hub.get(server))
348
+ fail(`no MCPL server "${server}" is configured here`);
349
+ switch (name) {
350
+ case 'open':
351
+ if (builtin)
352
+ return textResult(host.openBuiltin(agentName));
353
+ if (tab)
354
+ return textResult(await host.openServer(agentName, server, { full: args.full === true }));
355
+ if (args.full !== undefined)
356
+ fail('full goes with server alone');
357
+ return null;
358
+ case 'close':
359
+ if (builtin)
360
+ return textResult(host.closeBuiltin(agentName));
361
+ if (tab)
362
+ return textResult(await host.closeServer(agentName, server, { disconnect: args.disconnect === true }));
363
+ if (args.disconnect !== undefined)
364
+ fail('disconnect goes with server alone');
365
+ return null;
366
+ case 'look': {
367
+ // A host toolset's tool, with or without its server named (as act takes it).
368
+ if (tool && !toolset && !instance) {
369
+ const shown = host.lookTool(agentName, tool, server);
370
+ if (shown)
371
+ return textResult(shown);
372
+ return null;
373
+ }
374
+ if (builtin)
375
+ return textResult(host.lookToolset(agentName, { builtin: true }));
376
+ // A server's tab, and every host toolset with no selector, go first in
377
+ // what the servers' own look says (handleToolCall adds them).
378
+ if (tab && !tool && !this.hub.get(server)?.presents)
379
+ return textResult(host.lookToolset(agentName, { serverId: server }));
380
+ return null;
381
+ }
382
+ default:
383
+ return null;
384
+ }
385
+ }
386
+ /** What the host's own toolsets add ahead of the servers' look: everything, or one presenting server's tab. */
387
+ hostLookLead(args, agentName) {
388
+ const host = this.host;
389
+ if (!host || !agentName || !host.active(agentName))
390
+ return null;
391
+ const server = str(args.server);
392
+ if (str(args.toolset) || str(args.instance) || str(args.tool))
393
+ return null;
394
+ if (!server)
395
+ return host.catalog(agentName).join('\n');
396
+ return host.knows(server) ? host.lookToolset(agentName, { serverId: server }) : null;
397
+ }
229
398
  toolsAt(c, key) {
230
399
  return [...(c.presence.view().get(key)?.tools.values() ?? [])];
231
400
  }
232
401
  /** A change set rendered without the key the caller already showed whole. */
233
- renderOthers(c, cs, key) {
234
- return c.presence.render({ ...cs, changes: cs.changes.filter((ch) => ch.key !== key) });
402
+ renderOthers(c, cs, key, shown) {
403
+ return c.presence.render({ ...cs, changes: cs.changes.filter((ch) => ch.key !== key) }, { mark: shown, describeAll: !shown });
235
404
  }
236
405
  // ── the tools ──
237
- async open(args) {
238
- let toolset = str(args.toolset);
406
+ async open(args, take) {
407
+ const shown = take !== null;
408
+ let toolset = toolsetNamed(this.hub, args.server, args.toolset);
239
409
  const c = toolset && !str(args.server)
240
410
  ? owner(this.hub, undefined, (x) => x.presence.isDeclared(toolset) || x.presence.undeclaredGroups.includes(toolset), `a toolset "${toolset}"`)
241
411
  : pick(this.hub, args.server);
@@ -243,55 +413,60 @@ export class ToolsetsModule {
243
413
  const openable = [...Object.keys(p.declared), ...p.undeclaredGroups];
244
414
  if (!toolset) {
245
415
  if (openable.length !== 1)
246
- fail(`${c.serverId} has ${openable.length ? `several toolsets: name one of ${openable.join(', ')}` : 'no toolsets'}`);
416
+ fail(`${c.serverId} has ${openable.length ? `several toolsets: name one of ${openable.map(nameToTypeBack).join(', ')}` : 'no toolsets'}`);
247
417
  toolset = openable[0];
248
418
  }
249
- const instance = str(args.instance);
419
+ const instance = instanceNamed([c], toolset, args.instance);
250
420
  if (args.options !== undefined && !isObj(args.options))
251
421
  fail('options must be an object');
252
422
  const options = args.options;
253
423
  if (p.isDeclared(toolset)) {
254
424
  if (instance && !p.declared[toolset].instances)
255
- fail(`${c.serverId}/${toolset} has no instances: open it without one`);
425
+ fail(`${c.serverId}/${nameToTypeBack(toolset)} has no instances: open it without one`);
256
426
  let opened;
257
427
  try {
258
- opened = await c.open(toolset, instance, options);
428
+ opened = await c.open(toolset, instance, options, { shown });
259
429
  }
260
430
  catch (error) {
261
- return fail(`${c.serverId} did not open ${toolset}: ${error instanceof Error ? error.message : String(error)}`);
431
+ return requestFailure(c.serverId, `did not open ${nameToTypeBack(toolset)}`, `didn't confirm opening ${nameToTypeBack(toolset)}`, error);
262
432
  }
263
433
  // RFC §5.2: opening what is open adopts it; its content says which options weren't applied.
264
434
  const blocks = [
265
- { type: 'text', text: `${p.label(opened.key)}${opened.title ? ` ${opened.title}` : ''}${opened.adopted ? ' (already open)' : ''}` },
435
+ { type: 'text', text: `${p.label(opened.key)}${opened.title ? ` ${suppliedValue(opened.title)}` : ''}${opened.adopted ? ' (already open)' : ''}` },
266
436
  ...asBlocks(opened.content),
267
437
  ];
268
- const tail = [...p.toolBlock(opened.key, this.toolsAt(c, opened.key), { headed: false })];
438
+ const tail = [...p.toolBlock(opened.key, this.toolsAt(c, opened.key), { headed: false, describeAll: !shown, seen: shown ? undefined : new Set() })];
269
439
  const gone = opened.change.changes.find((ch) => ch.key === opened.key)?.gone ?? [];
270
440
  if (gone.length)
271
- tail.push(`now unavailable: ${gone.join(', ')}`);
272
- const others = this.renderOthers(c, opened.change, opened.key);
441
+ tail.push(`now unavailable: ${gone.map(suppliedValue).join(', ')}`);
442
+ const others = this.renderOthers(c, opened.change, opened.key, shown);
273
443
  if (others)
274
444
  tail.push(others);
275
445
  const close = p.closeHint(opened.key);
276
446
  if (close)
277
447
  tail.push(`${close} closes it.`);
278
448
  blocks.push({ type: 'text', text: tail.join('\n') });
449
+ take?.({ toolsets: c, cs: opened.change });
279
450
  return blocksResult(blocks);
280
451
  }
281
452
  if (p.undeclaredGroups.includes(toolset)) {
282
453
  // RFC §5.4: a grouping the server offers but doesn't track: the host opens it, the server isn't told.
283
454
  if (instance || options)
284
- fail(`${c.serverId}/${toolset} takes no instance or options (the server doesn't declare it)`);
455
+ fail(`${c.serverId}/${nameToTypeBack(toolset)} takes no instance or options (the server doesn't declare it)`);
285
456
  const cs = c.setGroupOpen(toolset, true);
286
457
  const key = keyOf(toolset);
287
- const lines = [`${p.label(key)} open.`, ...p.toolBlock(key, this.toolsAt(c, key), { headed: false })];
288
- const others = this.renderOthers(c, cs, key);
458
+ const lines = [`${p.label(key)} open.`, ...p.toolBlock(key, this.toolsAt(c, key), { headed: false, describeAll: !shown, seen: shown ? undefined : new Set() })];
459
+ const others = this.renderOthers(c, cs, key, shown);
289
460
  if (others)
290
461
  lines.push(others);
291
462
  lines.push(`${p.closeHint(key)} closes it.`);
463
+ if (take)
464
+ take({ toolsets: c, cs });
465
+ else
466
+ c.tell(cs);
292
467
  return textResult(lines.join('\n'));
293
468
  }
294
- return fail(`${c.serverId} has no toolset "${toolset}"${openable.length ? `; it has ${openable.join(', ')}` : ''}`);
469
+ return fail(noToolset(c, toolset));
295
470
  }
296
471
  /**
297
472
  * act is dispatched by the framework, not here: it resolves the call
@@ -307,42 +482,80 @@ export class ToolsetsModule {
307
482
  return { success: false, isError: true, error: `${META_TOOL_NAMES.act} is dispatched by the framework; nothing was sent` };
308
483
  }
309
484
  /** look restates the present: titles and visibility are re-read from the servers first (toolsets/list). */
310
- async look(args) {
485
+ async look(args, take) {
311
486
  const server = str(args.server);
312
487
  const targets = server ? [this.hub.get(server)].filter((c) => !!c && c.presents) : this.hub.presenting();
313
- const notes = (await Promise.all(targets.map((c) => c.refreshOpen().catch(() => [])))).flat();
314
- const result = this.lookNow(args, server, str(args.tool));
315
- return notes.length && !result.isError && typeof result.data === 'string' ? textResult(`${result.data}\n${notes.join('\n')}`) : result;
488
+ const shown = take !== null;
489
+ const reads = await Promise.all(targets.map(async (c) => ({ c, cs: await c.refreshOpen({ shown }).catch(() => null) })));
490
+ const read = reads.filter((r) => r.cs !== null);
491
+ const unread = reads.filter((r) => r.cs === null).map(({ c }) => `${c.serverId} couldn't be asked just now: this is what the host last knew of it.`);
492
+ const giveBack = () => {
493
+ if (shown)
494
+ for (const { c, cs } of read)
495
+ c.tell(cs);
496
+ };
497
+ let result;
498
+ try {
499
+ result = this.lookNow(args, server, toolNamed(this.hub, server, args.tool), shown);
500
+ }
501
+ catch (error) {
502
+ giveBack();
503
+ if (error instanceof MetaError && unread.length)
504
+ fail([error.message, ...unread].join('\n'));
505
+ throw error;
506
+ }
507
+ if (result.isError || typeof result.data !== 'string') {
508
+ giveBack();
509
+ return result;
510
+ }
511
+ const restates = !str(args.tool) && !str(args.toolset) && !str(args.instance);
512
+ if (!restates)
513
+ giveBack();
514
+ const lines = restates
515
+ ? read.flatMap(({ c, cs }) => {
516
+ const said = [...cs.suggest.map((s) => c.presence.renderSuggestion(s)), ...c.presence.untoldNotes(cs), ...cs.notes];
517
+ // The catalog above counted what it showed; the change's own blocks weren't shown, but stay for a notice if its taker can't show the result.
518
+ take?.({ toolsets: c, cs: { ...cs, blocksShown: false } });
519
+ return said;
520
+ })
521
+ : [];
522
+ lines.push(...unread);
523
+ return lines.length ? textResult(`${result.data}\n${lines.join('\n')}`) : result;
316
524
  }
317
- lookNow(args, server, tool) {
525
+ lookNow(args, server, tool, mark) {
318
526
  if (!server) {
319
- if (tool)
320
- return this.lookTool(owner(this.hub, undefined, (c) => !!c.presence.tool(tool), `a tool "${tool}"`), tool, str(args.instance));
321
- const ts = str(args.toolset);
322
- const inst = str(args.instance);
527
+ if (tool) {
528
+ const c = owner(this.hub, undefined, (x) => !!x.presence.tool(tool), `a tool "${tool}"`);
529
+ return this.lookTool(c, tool, instanceNamed([c], c.presence.toolsetOf(tool), args.instance));
530
+ }
531
+ const ts = toolsetNamed(this.hub, undefined, args.toolset);
532
+ const inst = instanceNamed(this.hub.presenting(), ts, args.instance);
323
533
  if (ts || inst) {
324
- const c = owner(this.hub, undefined, (x) => (ts ? x.presence.isDeclared(ts) || x.presence.undeclaredGroups.includes(ts) : true) && (!inst || x.presence.openRecords().some((o) => o.instance === inst)), ts ? `a toolset "${ts}"${inst ? ` with instance "${inst}"` : ''}` : `an open instance "${inst}"`);
325
- return this.lookNow(args, c.serverId, tool);
534
+ const c = owner(this.hub, undefined, (x) => (ts ? x.presence.isDeclared(ts) || x.presence.undeclaredGroups.includes(ts) : true) && (!inst || x.presence.openRecords(ts).some((o) => o.instance === inst)), ts ? `a toolset "${ts}"${inst ? ` with instance "${inst}"` : ''}` : `an open instance "${inst}"`);
535
+ return this.lookNow(args, c.serverId, tool, mark);
326
536
  }
327
537
  const all = this.hub.presenting();
328
538
  if (!all.length)
329
- return textResult('No MCPL server presents toolsets or dynamic tools now; every tool is in your tool list.');
330
- return textResult(all.map((c) => c.presence.catalog()).join('\n\n'));
539
+ return textResult('No MCPL server presents toolsets or dynamic tools now; MCPL tools are called directly, by name.');
540
+ return textResult(all.map((c) => c.presence.catalog({ mark })).join('\n\n'));
331
541
  }
332
542
  const c = pick(this.hub, server);
333
543
  if (tool)
334
- return this.lookTool(c, tool, str(args.instance));
544
+ return this.lookTool(c, tool, instanceNamed([c], c.presence.toolsetOf(tool), args.instance));
335
545
  const p = c.presence;
336
- let toolset = str(args.toolset);
337
- const instance = str(args.instance);
338
- if (!toolset && instance)
339
- toolset = p.openRecords().find((o) => o.instance === instance)?.toolset ?? fail(`no open instance "${instance}" on ${c.serverId}`);
546
+ let toolset = toolsetNamed(this.hub, server, args.toolset);
547
+ const instance = instanceNamed([c], toolset, args.instance);
548
+ if (!toolset && instance) {
549
+ const open = p.openRecords().filter((o) => o.instance !== undefined);
550
+ toolset = open.find((o) => o.instance === instance)?.toolset
551
+ ?? fail(`no open instance "${instance}" on ${c.serverId}; ${open.length ? `open: ${open.map((o) => `${nameToTypeBack(o.toolset)}:${nameToTypeBack(String(o.instance))}`).join(', ')}` : 'no instance is open there'}`);
552
+ }
340
553
  if (!toolset)
341
- return textResult(p.catalog());
554
+ return textResult(p.catalog({ mark }));
342
555
  if (p.isOpen(toolset, instance) || (!instance && p.openRecords(toolset).length === 1)) {
343
556
  const rec = p.isDeclared(toolset) ? (instance ? p.openRecords(toolset).find((o) => o.instance === instance) : p.openRecords(toolset)[0]) : undefined;
344
557
  const key = keyOf(toolset, rec?.instance);
345
- const lines = p.toolBlock(key, this.toolsAt(c, key), { describeAll: true, title: rec?.title });
558
+ const lines = p.toolBlock(key, this.toolsAt(c, key), { describeAll: true, title: rec?.title, seen: mark ? undefined : new Set() });
346
559
  const close = p.closeHint(key);
347
560
  if (close)
348
561
  lines.push(`${close} closes it.`);
@@ -352,37 +565,39 @@ export class ToolsetsModule {
352
565
  if (d) {
353
566
  const open = p.openRecords(toolset);
354
567
  return textResult([
355
- `${c.serverId}/${toolset}${d.title ? ` — ${d.title}` : ''}${d.summary ? `: ${d.summary}` : ''}`,
356
- open.length ? `open: ${open.map((o) => o.instance ?? toolset).join(', ')}` : 'not open',
568
+ `${c.serverId}/${nameToTypeBack(toolset)}${d.title ? ` — ${suppliedValue(d.title)}` : ''}${d.summary ? `: ${suppliedValue(d.summary)}` : ''}`,
569
+ open.length ? `open: ${open.map((o) => nameToTypeBack(o.instance ?? toolset)).join(', ')}` : 'not open',
357
570
  ...(d.instances ? ['several can be open at once; name each with instance'] : []),
358
- ...(d.options ? [`options: ${JSON.stringify(d.options)}`] : []),
571
+ ...(d.options ? [`options: ${jsonText(d.options)}`] : []),
359
572
  `${p.openHint(toolset)} opens it.`,
360
573
  ].join('\n'));
361
574
  }
362
575
  if (p.undeclaredGroups.includes(toolset)) {
363
576
  const names = p.allTools.filter((t) => p.toolsetOf(t.name) === toolset).map((t) => t.name);
364
- return textResult(`${c.serverId}/${toolset}: ${names.join(', ')}; not open: ${p.openHint(toolset)} opens it.`);
577
+ return textResult(`${c.serverId}/${nameToTypeBack(toolset)}: ${names.map(nameToTypeBack).join(', ')}; not open: ${p.openHint(toolset)} opens it.`);
365
578
  }
366
- return fail(`${c.serverId} has no toolset "${toolset}"`);
579
+ return fail(noToolset(c, toolset));
367
580
  }
368
581
  lookTool(c, tool, instance) {
369
582
  const p = c.presence;
370
583
  if (!p.tool(tool))
371
- fail(`${c.serverId} has no tool "${tool}"`);
584
+ fail(`${c.serverId} has no tool "${tool}": ${META_TOOL_NAMES.look} {server:"${c.serverId}"} shows what's available`);
372
585
  const ts = p.toolsetOf(tool);
373
- const where = [...p.view().entries()].filter(([, e]) => e.tools.has(tool)).map(([k]) => k);
374
- const key = instance && ts ? keyOf(ts, instance) : where[0];
586
+ // The definition act would check, wherever act would take the tool.
587
+ const open = !instance && hasInstances(p, ts) ? p.openRecords(ts) : [];
588
+ const key = !p.shownAs(tool, 'dynamic') ? undefined : actKey(p, tool, instance ?? (open.length === 1 ? open[0].instance : undefined));
375
589
  const def = key ? p.defOf(key, tool) : p.tool(tool);
376
590
  return textResult(`${renderToolDefinition(def)}\n${p.toolStatus(tool)}`);
377
591
  }
378
- async close(args) {
379
- const named = str(args.toolset);
592
+ async close(args, take) {
593
+ const shown = take !== null;
594
+ const named = toolsetNamed(this.hub, args.server, args.toolset);
380
595
  const c = named && !str(args.server)
381
- ? owner(this.hub, undefined, (x) => x.presence.isOpen(named, str(args.instance)) || x.presence.openRecords(named).length > 0, `an open toolset "${named}"`)
596
+ ? owner(this.hub, undefined, (x) => x.presence.isOpen(named, instanceNamed([x], named, args.instance)) || x.presence.openRecords(named).length > 0, `an open toolset "${named}"`)
382
597
  : pick(this.hub, args.server);
383
598
  const p = c.presence;
384
599
  let toolset = named;
385
- let instance = str(args.instance);
600
+ let instance = instanceNamed([c], named, args.instance);
386
601
  if (!toolset) {
387
602
  const open = [
388
603
  ...p.openRecords().filter((o) => !instance || o.instance === instance),
@@ -390,7 +605,7 @@ export class ToolsetsModule {
390
605
  ];
391
606
  if (open.length !== 1) {
392
607
  fail(open.length
393
- ? `several are open on ${c.serverId}: name one of ${open.map((o) => `${o.toolset}${o.instance ? `:${o.instance}` : ''}`).join(', ')}`
608
+ ? `several are open on ${c.serverId}: name one of ${open.map((o) => `${nameToTypeBack(o.toolset)}${o.instance ? `:${nameToTypeBack(o.instance)}` : ''}`).join(', ')}`
394
609
  : `nothing is open on ${c.serverId}`);
395
610
  }
396
611
  toolset = open[0].toolset;
@@ -399,33 +614,40 @@ export class ToolsetsModule {
399
614
  if (p.isDeclared(toolset)) {
400
615
  // As open refuses one: a toolset without instances is closed without one.
401
616
  if (instance && !p.declared[toolset].instances)
402
- fail(`${c.serverId}/${toolset} has no instances: close it without one`);
617
+ fail(`${c.serverId}/${nameToTypeBack(toolset)} has no instances: close it without one`);
403
618
  if (p.declared[toolset].instances && !instance) {
404
619
  const open = p.openRecords(toolset);
405
620
  if (open.length !== 1)
406
- fail(open.length ? `several ${toolset} instances are open (${open.map((o) => o.instance).join(', ')}): say which` : `${toolset} is not open`);
621
+ fail(open.length ? `several ${nameToTypeBack(toolset)} instances are open (${open.map((o) => nameToTypeBack(String(o.instance))).join(', ')}): say which` : `${nameToTypeBack(toolset)} is not open`);
407
622
  instance = open[0].instance;
408
623
  }
409
624
  let closed;
410
625
  try {
411
- closed = await c.close(toolset, instance);
626
+ closed = await c.close(toolset, instance, { shown });
412
627
  }
413
628
  catch (error) {
414
- return fail(`${c.serverId} did not close ${toolset}${instance ? `:${instance}` : ''}: ${error instanceof Error ? error.message : String(error)}`);
629
+ const what = `${nameToTypeBack(toolset)}${instance ? `:${nameToTypeBack(instance)}` : ''}`;
630
+ return requestFailure(c.serverId, `did not close ${what}`, `didn't confirm closing ${what}`, error);
415
631
  }
416
632
  const key = keyOf(toolset, instance);
417
- const change = p.render(closed.change) || `${p.label(key)} closed.`;
633
+ const change = p.render(closed.change, { mark: shown, describeAll: !shown }) || `${p.label(key)} closed.`;
634
+ take?.({ toolsets: c, cs: closed.change });
418
635
  return blocksResult([...asBlocks(closed.content), { type: 'text', text: change }]);
419
636
  }
420
637
  if (p.undeclaredGroups.includes(toolset)) {
421
638
  if (instance)
422
- fail(`${c.serverId}/${toolset} takes no instance (the server doesn't declare it)`);
639
+ fail(`${c.serverId}/${nameToTypeBack(toolset)} takes no instance (the server doesn't declare it)`);
423
640
  if (!p.isOpen(toolset))
424
- fail(`${c.serverId}/${toolset} is not open`);
641
+ fail(`${c.serverId}/${nameToTypeBack(toolset)} is not open`);
425
642
  const cs = c.setGroupOpen(toolset, false);
426
- return textResult(p.render(cs) || `${p.label(keyOf(toolset))} closed.`);
643
+ const text = p.render(cs, { mark: shown, describeAll: !shown }) || `${p.label(keyOf(toolset))} closed.`;
644
+ if (take)
645
+ take({ toolsets: c, cs });
646
+ else
647
+ c.tell(cs);
648
+ return textResult(text);
427
649
  }
428
- return fail(`${c.serverId} has no toolset "${toolset}"`);
650
+ return fail(noToolset(c, toolset));
429
651
  }
430
652
  }
431
653
  //# sourceMappingURL=toolsets-module.js.map