@animalabs/agent-framework 0.21.1 → 0.22.1

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 (348) hide show
  1. package/README.md +54 -4
  2. package/dist/src/agent.d.ts +17 -4
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +105 -43
  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 +41 -4
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/code-execution/py-runner.d.ts +22 -1
  10. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  11. package/dist/src/code-execution/py-runner.js +64 -9
  12. package/dist/src/code-execution/py-runner.js.map +1 -1
  13. package/dist/src/code-execution/runtime-py.d.ts +4 -0
  14. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  15. package/dist/src/code-execution/runtime-py.js +215 -38
  16. package/dist/src/code-execution/runtime-py.js.map +1 -1
  17. package/dist/src/framework.d.ts +569 -59
  18. package/dist/src/framework.d.ts.map +1 -1
  19. package/dist/src/framework.js +3091 -542
  20. package/dist/src/framework.js.map +1 -1
  21. package/dist/src/gate/event-gate.d.ts +21 -0
  22. package/dist/src/gate/event-gate.d.ts.map +1 -1
  23. package/dist/src/gate/event-gate.js +40 -7
  24. package/dist/src/gate/event-gate.js.map +1 -1
  25. package/dist/src/index.d.ts +7 -2
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +4 -0
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  30. package/dist/src/mcpl/channel-registry.js +26 -3
  31. package/dist/src/mcpl/channel-registry.js.map +1 -1
  32. package/dist/src/mcpl/conversation-router.d.ts +28 -1
  33. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  34. package/dist/src/mcpl/conversation-router.js +17 -0
  35. package/dist/src/mcpl/conversation-router.js.map +1 -1
  36. package/dist/src/mcpl/feature-set-manager.d.ts +11 -0
  37. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  38. package/dist/src/mcpl/feature-set-manager.js +31 -7
  39. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  40. package/dist/src/mcpl/index.d.ts +4 -0
  41. package/dist/src/mcpl/index.d.ts.map +1 -1
  42. package/dist/src/mcpl/index.js +1 -0
  43. package/dist/src/mcpl/index.js.map +1 -1
  44. package/dist/src/mcpl/prose-stream-router.d.ts +27 -6
  45. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  46. package/dist/src/mcpl/prose-stream-router.js +85 -22
  47. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  48. package/dist/src/mcpl/push-coalescer.d.ts +32 -4
  49. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  50. package/dist/src/mcpl/push-coalescer.js +46 -13
  51. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  52. package/dist/src/mcpl/push-handler.d.ts +5 -3
  53. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  54. package/dist/src/mcpl/push-handler.js +41 -11
  55. package/dist/src/mcpl/push-handler.js.map +1 -1
  56. package/dist/src/mcpl/reference-fetcher.d.ts +2 -1
  57. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  58. package/dist/src/mcpl/reference-fetcher.js +20 -9
  59. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  60. package/dist/src/mcpl/references.d.ts +29 -6
  61. package/dist/src/mcpl/references.d.ts.map +1 -1
  62. package/dist/src/mcpl/references.js +57 -16
  63. package/dist/src/mcpl/references.js.map +1 -1
  64. package/dist/src/mcpl/server-connection.d.ts +32 -1
  65. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  66. package/dist/src/mcpl/server-connection.js +76 -11
  67. package/dist/src/mcpl/server-connection.js.map +1 -1
  68. package/dist/src/mcpl/server-registry.d.ts +5 -2
  69. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  70. package/dist/src/mcpl/server-registry.js +25 -7
  71. package/dist/src/mcpl/server-registry.js.map +1 -1
  72. package/dist/src/mcpl/tool-lifecycle.d.ts +52 -2
  73. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  74. package/dist/src/mcpl/tool-lifecycle.js +47 -1
  75. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  76. package/dist/src/mcpl/tool-pattern-check.d.ts +51 -0
  77. package/dist/src/mcpl/tool-pattern-check.d.ts.map +1 -0
  78. package/dist/src/mcpl/tool-pattern-check.js +79 -0
  79. package/dist/src/mcpl/tool-pattern-check.js.map +1 -0
  80. package/dist/src/mcpl/types.d.ts +15 -5
  81. package/dist/src/mcpl/types.d.ts.map +1 -1
  82. package/dist/src/mcpl/types.js.map +1 -1
  83. package/dist/src/mcpl/visible-content.d.ts +39 -0
  84. package/dist/src/mcpl/visible-content.d.ts.map +1 -0
  85. package/dist/src/mcpl/visible-content.js +54 -0
  86. package/dist/src/mcpl/visible-content.js.map +1 -0
  87. package/dist/src/module-registry.d.ts +90 -6
  88. package/dist/src/module-registry.d.ts.map +1 -1
  89. package/dist/src/module-registry.js +260 -18
  90. package/dist/src/module-registry.js.map +1 -1
  91. package/dist/src/modules/workspace/branch-intent.d.ts +72 -0
  92. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -0
  93. package/dist/src/modules/workspace/branch-intent.js +83 -0
  94. package/dist/src/modules/workspace/branch-intent.js.map +1 -0
  95. package/dist/src/modules/workspace/disk-agreement.d.ts +167 -0
  96. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -0
  97. package/dist/src/modules/workspace/disk-agreement.js +259 -0
  98. package/dist/src/modules/workspace/disk-agreement.js.map +1 -0
  99. package/dist/src/modules/workspace/effects.d.ts +84 -0
  100. package/dist/src/modules/workspace/effects.d.ts.map +1 -0
  101. package/dist/src/modules/workspace/effects.js +368 -0
  102. package/dist/src/modules/workspace/effects.js.map +1 -0
  103. package/dist/src/modules/workspace/index.d.ts +155 -17
  104. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  105. package/dist/src/modules/workspace/index.js +1101 -424
  106. package/dist/src/modules/workspace/index.js.map +1 -1
  107. package/dist/src/modules/workspace/observe.d.ts +288 -0
  108. package/dist/src/modules/workspace/observe.d.ts.map +1 -0
  109. package/dist/src/modules/workspace/observe.js +825 -0
  110. package/dist/src/modules/workspace/observe.js.map +1 -0
  111. package/dist/src/modules/workspace/reconcile.d.ts +238 -0
  112. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -0
  113. package/dist/src/modules/workspace/reconcile.js +909 -0
  114. package/dist/src/modules/workspace/reconcile.js.map +1 -0
  115. package/dist/src/modules/workspace/sync.d.ts +6 -59
  116. package/dist/src/modules/workspace/sync.d.ts.map +1 -1
  117. package/dist/src/modules/workspace/sync.js +6 -257
  118. package/dist/src/modules/workspace/sync.js.map +1 -1
  119. package/dist/src/modules/workspace/types.d.ts +77 -10
  120. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  121. package/dist/src/modules/workspace/types.js.map +1 -1
  122. package/dist/src/operator-log.d.ts +41 -0
  123. package/dist/src/operator-log.d.ts.map +1 -1
  124. package/dist/src/operator-log.js.map +1 -1
  125. package/dist/src/provider-waits.d.ts +216 -0
  126. package/dist/src/provider-waits.d.ts.map +1 -0
  127. package/dist/src/provider-waits.js +482 -0
  128. package/dist/src/provider-waits.js.map +1 -0
  129. package/dist/src/record-journal.d.ts +127 -0
  130. package/dist/src/record-journal.d.ts.map +1 -0
  131. package/dist/src/record-journal.js +202 -0
  132. package/dist/src/record-journal.js.map +1 -0
  133. package/dist/src/refusal-reactions.d.ts +14 -12
  134. package/dist/src/refusal-reactions.d.ts.map +1 -1
  135. package/dist/src/refusal-reactions.js +14 -12
  136. package/dist/src/refusal-reactions.js.map +1 -1
  137. package/dist/src/shutdown.d.ts +13 -0
  138. package/dist/src/shutdown.d.ts.map +1 -0
  139. package/dist/src/shutdown.js +46 -0
  140. package/dist/src/shutdown.js.map +1 -0
  141. package/dist/src/silent-heartbeat.d.ts +74 -0
  142. package/dist/src/silent-heartbeat.d.ts.map +1 -0
  143. package/dist/src/silent-heartbeat.js +77 -0
  144. package/dist/src/silent-heartbeat.js.map +1 -0
  145. package/dist/src/tool-presentation.d.ts +50 -0
  146. package/dist/src/tool-presentation.d.ts.map +1 -0
  147. package/dist/src/tool-presentation.js +235 -0
  148. package/dist/src/tool-presentation.js.map +1 -0
  149. package/dist/src/tool-result-guard.d.ts +142 -6
  150. package/dist/src/tool-result-guard.d.ts.map +1 -1
  151. package/dist/src/tool-result-guard.js +357 -36
  152. package/dist/src/tool-result-guard.js.map +1 -1
  153. package/dist/src/tool-result-history.d.ts +13 -0
  154. package/dist/src/tool-result-history.d.ts.map +1 -1
  155. package/dist/src/tool-result-history.js +21 -1
  156. package/dist/src/tool-result-history.js.map +1 -1
  157. package/dist/src/types/agent.d.ts +18 -2
  158. package/dist/src/types/agent.d.ts.map +1 -1
  159. package/dist/src/types/events.d.ts +16 -0
  160. package/dist/src/types/events.d.ts.map +1 -1
  161. package/dist/src/types/framework.d.ts +12 -0
  162. package/dist/src/types/framework.d.ts.map +1 -1
  163. package/dist/src/types/module.d.ts +26 -4
  164. package/dist/src/types/module.d.ts.map +1 -1
  165. package/dist/src/types/trace.d.ts +87 -3
  166. package/dist/src/types/trace.d.ts.map +1 -1
  167. package/dist/test/batched-wake-locus.test.d.ts +2 -0
  168. package/dist/test/batched-wake-locus.test.d.ts.map +1 -0
  169. package/dist/test/batched-wake-locus.test.js +166 -0
  170. package/dist/test/batched-wake-locus.test.js.map +1 -0
  171. package/dist/test/code-execution.test.js +386 -2
  172. package/dist/test/code-execution.test.js.map +1 -1
  173. package/dist/test/component-defaults.test.d.ts +2 -0
  174. package/dist/test/component-defaults.test.d.ts.map +1 -0
  175. package/dist/test/component-defaults.test.js +54 -0
  176. package/dist/test/component-defaults.test.js.map +1 -0
  177. package/dist/test/conversation-routing.test.js +57 -1
  178. package/dist/test/conversation-routing.test.js.map +1 -1
  179. package/dist/test/discord-awareness-outbox.test.js +3 -0
  180. package/dist/test/discord-awareness-outbox.test.js.map +1 -1
  181. package/dist/test/ephemeral-settle.test.js +3 -0
  182. package/dist/test/ephemeral-settle.test.js.map +1 -1
  183. package/dist/test/event-gate.test.js +118 -0
  184. package/dist/test/event-gate.test.js.map +1 -1
  185. package/dist/test/explicit-prose-routing.test.js +975 -0
  186. package/dist/test/explicit-prose-routing.test.js.map +1 -1
  187. package/dist/test/framework.test.js +160 -1
  188. package/dist/test/framework.test.js.map +1 -1
  189. package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
  190. package/dist/test/helpers/coalescing-fixture.js +3 -2
  191. package/dist/test/helpers/coalescing-fixture.js.map +1 -1
  192. package/dist/test/helpers/pytc-stop-rows.d.ts +11 -0
  193. package/dist/test/helpers/pytc-stop-rows.d.ts.map +1 -0
  194. package/dist/test/helpers/pytc-stop-rows.js +143 -0
  195. package/dist/test/helpers/pytc-stop-rows.js.map +1 -0
  196. package/dist/test/inference-exhaustion-rewind.test.js +142 -14
  197. package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
  198. package/dist/test/inference-failure-bounds.test.d.ts +2 -0
  199. package/dist/test/inference-failure-bounds.test.d.ts.map +1 -0
  200. package/dist/test/inference-failure-bounds.test.js +1201 -0
  201. package/dist/test/inference-failure-bounds.test.js.map +1 -0
  202. package/dist/test/late-events-after-stop.test.d.ts +2 -0
  203. package/dist/test/late-events-after-stop.test.d.ts.map +1 -0
  204. package/dist/test/late-events-after-stop.test.js +122 -0
  205. package/dist/test/late-events-after-stop.test.js.map +1 -0
  206. package/dist/test/mcpl-awareness-barrier.test.js +141 -0
  207. package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
  208. package/dist/test/mcpl-coalescing.test.js +97 -22
  209. package/dist/test/mcpl-coalescing.test.js.map +1 -1
  210. package/dist/test/mcpl-config-diagnostics.test.d.ts +2 -0
  211. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +1 -0
  212. package/dist/test/mcpl-config-diagnostics.test.js +452 -0
  213. package/dist/test/mcpl-config-diagnostics.test.js.map +1 -0
  214. package/dist/test/mcpl-empty-content.test.d.ts +2 -0
  215. package/dist/test/mcpl-empty-content.test.d.ts.map +1 -0
  216. package/dist/test/mcpl-empty-content.test.js +368 -0
  217. package/dist/test/mcpl-empty-content.test.js.map +1 -0
  218. package/dist/test/mcpl-manifest-changed.test.js +2 -1
  219. package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
  220. package/dist/test/mcpl-reference-fetcher.test.js +232 -4
  221. package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
  222. package/dist/test/mcpl-reference-fields.test.js +69 -2
  223. package/dist/test/mcpl-reference-fields.test.js.map +1 -1
  224. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +106 -1
  225. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
  226. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +2 -0
  227. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +1 -0
  228. package/dist/test/mcpl-tool-lifecycle-script.test.js +288 -0
  229. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -0
  230. package/dist/test/mcpl-ws-transport.test.js +103 -0
  231. package/dist/test/mcpl-ws-transport.test.js.map +1 -1
  232. package/dist/test/ops-alert.test.js +2 -1
  233. package/dist/test/ops-alert.test.js.map +1 -1
  234. package/dist/test/placed-reaction-baseline.test.d.ts +2 -0
  235. package/dist/test/placed-reaction-baseline.test.d.ts.map +1 -0
  236. package/dist/test/placed-reaction-baseline.test.js +312 -0
  237. package/dist/test/placed-reaction-baseline.test.js.map +1 -0
  238. package/dist/test/prose-stream-router.test.js +41 -0
  239. package/dist/test/prose-stream-router.test.js.map +1 -1
  240. package/dist/test/provider-acceleration-cooldown.test.js +5 -1
  241. package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
  242. package/dist/test/provider-waits.test.d.ts +2 -0
  243. package/dist/test/provider-waits.test.d.ts.map +1 -0
  244. package/dist/test/provider-waits.test.js +2196 -0
  245. package/dist/test/provider-waits.test.js.map +1 -0
  246. package/dist/test/reaction-baseline-platform.test.d.ts +2 -0
  247. package/dist/test/reaction-baseline-platform.test.d.ts.map +1 -0
  248. package/dist/test/reaction-baseline-platform.test.js +124 -0
  249. package/dist/test/reaction-baseline-platform.test.js.map +1 -0
  250. package/dist/test/record-journal.test.d.ts +6 -0
  251. package/dist/test/record-journal.test.d.ts.map +1 -0
  252. package/dist/test/record-journal.test.js +269 -0
  253. package/dist/test/record-journal.test.js.map +1 -0
  254. package/dist/test/refusal-reaction-baseline.test.js +10 -3
  255. package/dist/test/refusal-reaction-baseline.test.js.map +1 -1
  256. package/dist/test/save-recent-image.test.js +240 -2
  257. package/dist/test/save-recent-image.test.js.map +1 -1
  258. package/dist/test/shutdown-errors.test.d.ts +2 -0
  259. package/dist/test/shutdown-errors.test.d.ts.map +1 -0
  260. package/dist/test/shutdown-errors.test.js +662 -0
  261. package/dist/test/shutdown-errors.test.js.map +1 -0
  262. package/dist/test/silent-heartbeat-rows.test.d.ts +2 -0
  263. package/dist/test/silent-heartbeat-rows.test.d.ts.map +1 -0
  264. package/dist/test/silent-heartbeat-rows.test.js +451 -0
  265. package/dist/test/silent-heartbeat-rows.test.js.map +1 -0
  266. package/dist/test/silent-heartbeat.test.js +49 -38
  267. package/dist/test/silent-heartbeat.test.js.map +1 -1
  268. package/dist/test/stop-with-work-in-flight.test.d.ts +23 -0
  269. package/dist/test/stop-with-work-in-flight.test.d.ts.map +1 -0
  270. package/dist/test/stop-with-work-in-flight.test.js +672 -0
  271. package/dist/test/stop-with-work-in-flight.test.js.map +1 -0
  272. package/dist/test/surgery-marks-receipt.test.d.ts +2 -0
  273. package/dist/test/surgery-marks-receipt.test.d.ts.map +1 -0
  274. package/dist/test/surgery-marks-receipt.test.js +540 -0
  275. package/dist/test/surgery-marks-receipt.test.js.map +1 -0
  276. package/dist/test/tool-batch-hook.test.d.ts +8 -0
  277. package/dist/test/tool-batch-hook.test.d.ts.map +1 -0
  278. package/dist/test/tool-batch-hook.test.js +275 -0
  279. package/dist/test/tool-batch-hook.test.js.map +1 -0
  280. package/dist/test/tool-presentation.test.d.ts +2 -0
  281. package/dist/test/tool-presentation.test.d.ts.map +1 -0
  282. package/dist/test/tool-presentation.test.js +268 -0
  283. package/dist/test/tool-presentation.test.js.map +1 -0
  284. package/dist/test/tool-result-guard.test.js +2545 -19
  285. package/dist/test/tool-result-guard.test.js.map +1 -1
  286. package/dist/test/tool-result-history.test.js +2 -1
  287. package/dist/test/tool-result-history.test.js.map +1 -1
  288. package/dist/test/turn-trigger-provenance.test.js +35 -0
  289. package/dist/test/turn-trigger-provenance.test.js.map +1 -1
  290. package/dist/test/unconvertible-tool-result.test.d.ts +2 -0
  291. package/dist/test/unconvertible-tool-result.test.d.ts.map +1 -0
  292. package/dist/test/unconvertible-tool-result.test.js +358 -0
  293. package/dist/test/unconvertible-tool-result.test.js.map +1 -0
  294. package/dist/test/workspace-materialize-freshness.test.d.ts +16 -0
  295. package/dist/test/workspace-materialize-freshness.test.d.ts.map +1 -0
  296. package/dist/test/workspace-materialize-freshness.test.js +252 -0
  297. package/dist/test/workspace-materialize-freshness.test.js.map +1 -0
  298. package/dist/test/workspace-three-way.test.d.ts +12 -0
  299. package/dist/test/workspace-three-way.test.d.ts.map +1 -0
  300. package/dist/test/workspace-three-way.test.js +3194 -0
  301. package/dist/test/workspace-three-way.test.js.map +1 -0
  302. package/dist/tsconfig.tsbuildinfo +1 -1
  303. package/package.json +2 -2
  304. package/src/agent.ts +124 -45
  305. package/src/api/server.ts +43 -4
  306. package/src/code-execution/py-runner.ts +66 -9
  307. package/src/code-execution/runtime-py.ts +215 -38
  308. package/src/framework.ts +3191 -544
  309. package/src/gate/event-gate.ts +59 -7
  310. package/src/index.ts +18 -2
  311. package/src/mcpl/channel-registry.ts +24 -3
  312. package/src/mcpl/conversation-router.ts +34 -1
  313. package/src/mcpl/feature-set-manager.ts +36 -11
  314. package/src/mcpl/index.ts +4 -0
  315. package/src/mcpl/prose-stream-router.ts +79 -23
  316. package/src/mcpl/push-coalescer.ts +62 -13
  317. package/src/mcpl/push-handler.ts +41 -12
  318. package/src/mcpl/reference-fetcher.ts +18 -8
  319. package/src/mcpl/references.ts +57 -13
  320. package/src/mcpl/server-connection.ts +95 -13
  321. package/src/mcpl/server-registry.ts +24 -7
  322. package/src/mcpl/tool-lifecycle.ts +77 -2
  323. package/src/mcpl/tool-pattern-check.ts +106 -0
  324. package/src/mcpl/types.ts +15 -5
  325. package/src/mcpl/visible-content.ts +57 -0
  326. package/src/module-registry.ts +269 -25
  327. package/src/modules/workspace/branch-intent.ts +123 -0
  328. package/src/modules/workspace/disk-agreement.ts +325 -0
  329. package/src/modules/workspace/effects.ts +362 -0
  330. package/src/modules/workspace/index.ts +1098 -446
  331. package/src/modules/workspace/observe.ts +865 -0
  332. package/src/modules/workspace/reconcile.ts +1157 -0
  333. package/src/modules/workspace/sync.ts +6 -336
  334. package/src/modules/workspace/types.ts +75 -10
  335. package/src/operator-log.ts +30 -0
  336. package/src/provider-waits.ts +523 -0
  337. package/src/record-journal.ts +246 -0
  338. package/src/refusal-reactions.ts +14 -12
  339. package/src/shutdown.ts +49 -0
  340. package/src/silent-heartbeat.ts +141 -0
  341. package/src/tool-presentation.ts +188 -0
  342. package/src/tool-result-guard.ts +384 -34
  343. package/src/tool-result-history.ts +25 -1
  344. package/src/types/agent.ts +18 -2
  345. package/src/types/events.ts +16 -0
  346. package/src/types/framework.ts +12 -0
  347. package/src/types/module.ts +27 -4
  348. package/src/types/trace.ts +94 -3
@@ -6,6 +6,7 @@
6
6
 
7
7
  import type { McplServerConfig, McplHostCapabilities } from './types.js';
8
8
  import { McplServerConnection } from './server-connection.js';
9
+ import { DEFAULT_SHUTDOWN_TIMEOUT_MS, validateShutdownTimeout, waitForShutdown } from '../shutdown.js';
9
10
 
10
11
  /**
11
12
  * Capability keys that can be queried via `getServersWithCapability`.
@@ -25,6 +26,7 @@ export type McplCapabilityQuery =
25
26
  */
26
27
  export class McplServerRegistry {
27
28
  private servers = new Map<string, McplServerConnection>();
29
+ private closing = new Map<McplServerConnection, Promise<void>>();
28
30
 
29
31
  /**
30
32
  * Connect to an MCPL server and register it.
@@ -70,8 +72,7 @@ export class McplServerRegistry {
70
72
  if (!connection) {
71
73
  return;
72
74
  }
73
- this.servers.delete(id);
74
- await connection.close();
75
+ await this.closeServer(id, connection);
75
76
  }
76
77
 
77
78
  /**
@@ -135,11 +136,27 @@ export class McplServerRegistry {
135
136
  }
136
137
 
137
138
  /**
138
- * Close all server connections and clear the registry.
139
+ * Close all server connections, retaining failed or pending attempts.
140
+ * An explicit timeout bounds the caller's wait without cancelling cleanup.
139
141
  */
140
- async closeAll(): Promise<void> {
141
- const connections = Array.from(this.servers.values());
142
- this.servers.clear();
143
- await Promise.all(connections.map((c) => c.close()));
142
+ async closeAll(timeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS): Promise<void> {
143
+ validateShutdownTimeout(timeoutMs);
144
+ await waitForShutdown(Array.from(this.servers, ([id, connection]) => ({
145
+ label: `mcpl:${id}`, promise: this.closeServer(id, connection),
146
+ })), timeoutMs);
147
+ }
148
+
149
+ private closeServer(id: string, connection: McplServerConnection): Promise<void> {
150
+ const existing = this.closing.get(connection);
151
+ if (existing) return existing;
152
+ const attempt = Promise.resolve().then(() => connection.close()).then(() => {
153
+ if (this.servers.get(id) === connection) this.servers.delete(id);
154
+ this.closing.delete(connection);
155
+ }, error => {
156
+ this.closing.delete(connection);
157
+ throw error;
158
+ });
159
+ this.closing.set(connection, attempt);
160
+ return attempt;
144
161
  }
145
162
  }
@@ -50,8 +50,20 @@ export interface ToolLifecycleParams {
50
50
  inputWithheld?: boolean;
51
51
  isError?: boolean;
52
52
  durationMs?: number;
53
+ /**
54
+ * MCP's extension slot. Carries only SCRIPT_PARENT_META_KEY, on calls a
55
+ * code_execution script made: RFC-007 has no field for a call's origin,
56
+ * and the `mcpl/` prefix is reserved for keys MCPL defines (RFC-008 §3).
57
+ */
58
+ _meta?: Record<string, unknown>;
53
59
  }
54
60
 
61
+ /**
62
+ * `_meta` key naming the `toolCallId` of the code_execution call whose script
63
+ * made this call. Host-specific: not defined by RFC-007.
64
+ */
65
+ export const SCRIPT_PARENT_META_KEY = 'agent-framework/parentToolCallId';
66
+
55
67
  export interface ToolObserveMatch {
56
68
  tool?: string;
57
69
  serverTool?: string;
@@ -78,7 +90,8 @@ export interface ToolObserveRule {
78
90
  * RFC-007 §6.2 grammar.
79
91
  */
80
92
  export interface ToolLifecycleNarrowing {
81
- /** Patterns over the model-facing tool name (`computer--*`). */
93
+ /** Patterns over the model-facing tool name (`computer--*`): for an MCPL
94
+ * tool `<toolPrefix>--<tool>`, toolPrefix defaulting to `mcpl--<serverId>`. */
82
95
  tools?: string[];
83
96
  /** The tool's effective class must intersect this set. `'default'` is
84
97
  * DEFAULT_INPUT_CLASSES (computer, shell, files, web, media, body). */
@@ -242,6 +255,8 @@ export interface ToolCallDescriptor {
242
255
  serverId?: string;
243
256
  serverTool?: string;
244
257
  input: unknown;
258
+ /** Script-inner calls: the code_execution call's `toolCallId`, when it had one. */
259
+ parentToolCallId?: string;
245
260
  }
246
261
 
247
262
  function anyGlob(patterns: readonly string[], subject: string): boolean {
@@ -466,6 +481,7 @@ function baseParams(call: ToolCallDescriptor, phase: ToolPhase): ToolLifecyclePa
466
481
  class: [...call.class],
467
482
  ...(call.serverId !== undefined ? { serverId: call.serverId, serverTool: call.serverTool } : {}),
468
483
  phase,
484
+ ...(call.parentToolCallId !== undefined ? { _meta: { [SCRIPT_PARENT_META_KEY]: call.parentToolCallId } } : {}),
469
485
  };
470
486
  }
471
487
 
@@ -537,10 +553,23 @@ export interface ToolLifecycleHost {
537
553
  describe(tool: string): { class: ToolClass[]; serverId?: string; serverTool?: string };
538
554
  }
539
555
 
556
+ /**
557
+ * What a call made from inside a code_execution script inherits from the
558
+ * code_execution call that ran the script (scriptOrigin()).
559
+ */
560
+ export interface ScriptCallOrigin {
561
+ /** The inference whose output requested the code_execution call. */
562
+ inferenceId: string;
563
+ /** That call's `toolCallId`. Absent when nothing observed it, so it has none. */
564
+ parentToolCallId?: string;
565
+ }
566
+
540
567
  interface TrackedCall {
541
568
  agentName: string;
542
569
  modelCallId: string;
543
570
  call: ToolCallDescriptor;
571
+ /** Made by a code_execution script, not by the model (registerScriptCall). */
572
+ scriptInner: boolean;
544
573
  /** Connection id → transport epoch the opening went to. */
545
574
  openedTo: Map<string, number>;
546
575
  /** `started` has been sent (open() ran and the call was not refused). */
@@ -569,6 +598,9 @@ export class ToolLifecycleEmitter {
569
598
  private readonly recentIds = new Set<string>();
570
599
  private readonly recentOrder: string[] = [];
571
600
  private mintSeq = 0;
601
+ /** The call between register() and open(), kept even when nothing observes,
602
+ * so a code_execution dispatch can learn its inference (scriptOrigin()). */
603
+ private dispatching: { agentName: string; callId: string; inferenceId: string } | null = null;
572
604
 
573
605
  constructor(host: ToolLifecycleHost, now: () => number = Date.now) {
574
606
  this.host = host;
@@ -608,6 +640,44 @@ export class ToolLifecycleEmitter {
608
640
  * host may still refuse it (refuse()) before it executes.
609
641
  */
610
642
  register(agentName: string, inferenceId: string, call: { id: string; name: string; input: unknown }): void {
643
+ this.dispatching = { agentName, callId: call.id, inferenceId };
644
+ this.track(agentName, inferenceId, call, false);
645
+ }
646
+
647
+ /**
648
+ * A call a code_execution script made, about to go through the same
649
+ * dispatch as a model call (then refuse() / open() / onResult() as for
650
+ * one). Its class, provider, grant narrowing and filter matching are the
651
+ * INNER tool's own; its inference is the one that issued the
652
+ * code_execution call. Unlike a model call it is not aborted when its
653
+ * stream ends: the tool keeps running after the turn or the script is
654
+ * gone, so its terminal is the result's, or `failed` when the host gives
655
+ * up waiting for one.
656
+ */
657
+ registerScriptCall(agentName: string, origin: ScriptCallOrigin, call: { id: string; name: string; input: unknown }): void {
658
+ this.track(agentName, origin.inferenceId, call, true, origin.parentToolCallId);
659
+ }
660
+
661
+ /**
662
+ * The origin a code_execution call passes to the calls its script makes.
663
+ * Valid only while that call is being dispatched (between register() and
664
+ * open()), which is where the framework asks.
665
+ */
666
+ scriptOrigin(agentName: string, callId: string): ScriptCallOrigin | undefined {
667
+ const tracked = this.calls.get(callKey(agentName, callId));
668
+ if (tracked) return { inferenceId: tracked.call.inferenceId, parentToolCallId: tracked.call.toolCallId };
669
+ const d = this.dispatching;
670
+ if (d && d.agentName === agentName && d.callId === callId) return { inferenceId: d.inferenceId };
671
+ return undefined;
672
+ }
673
+
674
+ private track(
675
+ agentName: string,
676
+ inferenceId: string,
677
+ call: { id: string; name: string; input: unknown },
678
+ scriptInner: boolean,
679
+ parentToolCallId?: string,
680
+ ): void {
611
681
  if (!this.anyObserver()) return;
612
682
  const described = this.host.describe(call.name);
613
683
  this.calls.set(callKey(agentName, call.id), {
@@ -623,7 +693,9 @@ export class ToolLifecycleEmitter {
623
693
  ? { serverId: described.serverId, serverTool: described.serverTool }
624
694
  : {}),
625
695
  input: call.input,
696
+ ...(parentToolCallId !== undefined ? { parentToolCallId } : {}),
626
697
  },
698
+ scriptInner,
627
699
  openedTo: new Map(),
628
700
  opened: false,
629
701
  // Taken before dispatch, so durationMs includes work a synchronous
@@ -652,6 +724,7 @@ export class ToolLifecycleEmitter {
652
724
  * run synchronously inside dispatch, before this.
653
725
  */
654
726
  open(agentName: string, callId: string): void {
727
+ this.dispatching = null;
655
728
  const key = callKey(agentName, callId);
656
729
  const tracked = this.calls.get(key);
657
730
  if (!tracked || tracked.opened) return;
@@ -725,12 +798,14 @@ export class ToolLifecycleEmitter {
725
798
  * A stream ended: its calls still open were cancelled before a result.
726
799
  * Scoped to the inference ids that stream minted, so a successor stream
727
800
  * for the same agent (a budget restart overlapping this one's teardown)
728
- * keeps its live calls. A result arriving later finds nothing.
801
+ * keeps its live calls. A result arriving later finds nothing. Calls a
802
+ * script made are left open: they end with their own result.
729
803
  */
730
804
  abortOpen(agentName: string, inferenceIds: Iterable<string>): void {
731
805
  if (this.calls.size === 0) return;
732
806
  const ids = new Set(inferenceIds);
733
807
  for (const tracked of [...this.calls.values()]) {
808
+ if (tracked.scriptInner) continue;
734
809
  if (tracked.agentName === agentName && ids.has(tracked.call.inferenceId)) {
735
810
  if (tracked.opened) this.terminate(tracked, 'aborted');
736
811
  else this.calls.delete(callKey(tracked.agentName, tracked.modelCallId));
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Zero-match diagnostics for operator tool-name patterns.
3
+ *
4
+ * `toolClassOverrides` keys and `toolLifecycle.{observe,inputs}.tools` are
5
+ * RFC-007 §6.2 patterns over the MODEL-FACING tool name. For an MCPL tool
6
+ * that is `<toolPrefix>--<tool>`, and `toolPrefix` defaults to
7
+ * `mcpl--<serverId>`. A pattern written against the bare server id
8
+ * (`search--*` for server `search` with no toolPrefix) is valid config that
9
+ * matches nothing, and nothing else reports it: the override classes no tool
10
+ * and the narrowing admits no call. This module decides, for one pattern and
11
+ * the current tool listing, whether that is the case and what was probably
12
+ * meant.
13
+ */
14
+
15
+ import { globMatch } from './tool-glob.js';
16
+
17
+ export interface PatternServer {
18
+ id: string;
19
+ /** Effective tool prefix: config `toolPrefix ?? mcpl--<id>`. */
20
+ prefix: string;
21
+ /** True when this server's tools are in the current listing. */
22
+ listed: boolean;
23
+ }
24
+
25
+ export type ToolPatternVerdict =
26
+ | { kind: 'matched' }
27
+ /** Could still match tools of servers whose listing is not in (yet). */
28
+ | { kind: 'pending'; servers: string[] }
29
+ | { kind: 'unmatched'; suggestion?: string; hint: string };
30
+
31
+ /**
32
+ * Could `pattern` match some name under `<prefix>--`? Decided on the literal
33
+ * text before the first `*`; anything after it is not consulted, so this
34
+ * errs toward "could" (which only defers a warning, never invents one).
35
+ */
36
+ export function couldMatchUnderPrefix(pattern: string, prefix: string): boolean {
37
+ const head = `${prefix}--`;
38
+ const star = pattern.indexOf('*');
39
+ if (star === -1) return pattern.startsWith(head);
40
+ const literal = pattern.slice(0, star);
41
+ return literal.startsWith(head) || head.startsWith(literal);
42
+ }
43
+
44
+ /**
45
+ * Check one pattern against the model-facing tool names offered right now.
46
+ *
47
+ * `pending` while a server whose tools the pattern could name has not
48
+ * listed them (not connected yet, reconnecting, or removed): its listing is
49
+ * the only evidence, and an early verdict would be a false alarm.
50
+ */
51
+ export function checkToolPattern(
52
+ pattern: string,
53
+ toolNames: Iterable<string>,
54
+ servers: readonly PatternServer[],
55
+ ): ToolPatternVerdict {
56
+ const names = [...toolNames];
57
+ for (const name of names) {
58
+ if (globMatch(pattern, name)) return { kind: 'matched' };
59
+ }
60
+ const pending = servers.filter((s) => !s.listed && couldMatchUnderPrefix(pattern, s.prefix));
61
+ if (pending.length > 0) return { kind: 'pending', servers: [...new Set(pending.map((s) => s.id))] };
62
+
63
+ // Written against the wrong namespace for a known server: the bare id
64
+ // (`<id>--…`) where the effective prefix differs, or the default form
65
+ // (`mcpl--<id>--…`) for a server that set its own toolPrefix. Never when
66
+ // the namespace as written is real — another server's toolPrefix or a
67
+ // module's tools — since the pattern then names that, not a mistake.
68
+ const inRealNamespace = (ns: string) =>
69
+ servers.some((o) => o.prefix === ns) || names.some((n) => n.startsWith(`${ns}--`));
70
+ for (const s of servers) {
71
+ let rest: string | undefined;
72
+ if (s.prefix !== s.id && pattern.startsWith(`${s.id}--`) && !inRealNamespace(s.id)) {
73
+ rest = pattern.slice(s.id.length + 2);
74
+ } else if (
75
+ s.prefix !== `mcpl--${s.id}` && pattern.startsWith(`mcpl--${s.id}--`) && !inRealNamespace(`mcpl--${s.id}`)
76
+ ) {
77
+ rest = pattern.slice(s.id.length + 8);
78
+ }
79
+ if (rest === undefined) continue;
80
+ const why = s.prefix === `mcpl--${s.id}` ? ' (no toolPrefix set; the default is "mcpl--<serverId>")' : '';
81
+ return {
82
+ kind: 'unmatched',
83
+ suggestion: `${s.prefix}--${rest}`,
84
+ hint: `server "${s.id}" names its tools "${s.prefix}--<tool>"${why}`,
85
+ };
86
+ }
87
+
88
+ if (pattern.includes('--') && !servers.some((s) => couldMatchUnderPrefix(pattern, s.prefix))) {
89
+ return { kind: 'unmatched', hint: 'no connected MCPL server has a toolPrefix it could match' };
90
+ }
91
+ return { kind: 'unmatched', hint: 'no tool the framework offers has a matching name' };
92
+ }
93
+
94
+ /** The operator-facing line for an `unmatched` verdict. */
95
+ export function describeUnmatchedPattern(
96
+ where: string,
97
+ pattern: string,
98
+ verdict: Extract<ToolPatternVerdict, { kind: 'unmatched' }>,
99
+ ): string {
100
+ const meant = verdict.suggestion ? ` — did you mean ${JSON.stringify(verdict.suggestion)}?` : '';
101
+ return (
102
+ `[mcpl] ${where} pattern ${JSON.stringify(pattern)} matches no tool${meant} ` +
103
+ `(${verdict.hint}). Patterns match the full model-facing tool name, ` +
104
+ `"<toolPrefix>--<tool>" for MCPL tools.`
105
+ );
106
+ }
package/src/mcpl/types.ts CHANGED
@@ -282,10 +282,16 @@ export interface McplServerConfig {
282
282
  */
283
283
  allowHostCommands?: boolean;
284
284
 
285
- /** Feature sets to enable on connect */
285
+ /**
286
+ * Feature sets to enable on connect (`.`-segment wildcards). Omitted:
287
+ * every declared set is a candidate. `[]`: none. Either way a set is
288
+ * enabled only if its declared `uses` are recognized capabilities the
289
+ * grant covers (§6.4) — a set declaring no `uses` stays disabled even when
290
+ * listed here (logged, and traced as `mcpl:feature-set-disabled`).
291
+ */
286
292
  enabledFeatureSets?: string[];
287
293
 
288
- /** Feature sets to explicitly disable on connect */
294
+ /** Feature sets to explicitly disable on connect (wins over enabledFeatureSets). */
289
295
  disabledFeatureSets?: string[];
290
296
 
291
297
  /**
@@ -388,11 +394,15 @@ export interface McplServerConfig {
388
394
 
389
395
  /**
390
396
  * Tool name prefix for this server's tools.
391
- * Tools are exposed as `{toolPrefix}:{toolName}`.
397
+ * Tools are exposed as `{toolPrefix}--{toolName}`.
392
398
  *
393
- * Default: `mcpl:{id}` (e.g., `mcpl:zk:lobby_start_game`).
399
+ * Default: `mcpl--{id}` (e.g., `mcpl--zk--lobby_start_game`).
394
400
  * Set to the server ID for cleaner names (e.g., `toolPrefix: 'zk'`
395
- * gives `zk:lobby_start_game`).
401
+ * gives `zk--lobby_start_game`).
402
+ *
403
+ * Tool-name patterns elsewhere (`toolClassOverrides` keys,
404
+ * `toolLifecycle.*.tools`) match these full names; `enabledTools` /
405
+ * `disabledTools` use the bare server-side names.
396
406
  *
397
407
  * Must not collide with any module name or another server's prefix.
398
408
  */
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Visibly-empty MCPL content, and the one empty push that is allowed.
3
+ *
4
+ * A push event or channel message whose content gives the model nothing to
5
+ * read cannot be shown: the request sanitizer strips empty text blocks and
6
+ * drops the message they leave empty, so a wake queued for it presents the
7
+ * model with `[Continue]` or with an older message re-presented as the newest.
8
+ * That is a wake with no visible cause, and it invites the model to invent
9
+ * one. Such content is rejected at the MCPL boundary, and the store/wake site
10
+ * drops it as a backstop.
11
+ *
12
+ * The exception is the silent-heartbeat marker: the heartbeat server's own
13
+ * scheduled tick, which wakes with ephemeral host-supplied context instead of
14
+ * a stored message.
15
+ */
16
+
17
+ /**
18
+ * True when `content` holds nothing the model would see: no non-text block,
19
+ * and no text block whose text has a non-whitespace character. Mirrors the
20
+ * request sanitizer, which drops text that is not a string or trims to ''.
21
+ * Accepts MCPL wire blocks and converted membrane blocks alike.
22
+ */
23
+ export function isVisiblyEmptyContent(content: readonly unknown[] | null | undefined): boolean {
24
+ if (!Array.isArray(content)) return true;
25
+ for (const block of content) {
26
+ if (!block || typeof block !== 'object') continue;
27
+ const b = block as { type?: unknown; text?: unknown };
28
+ if (b.type !== 'text') return false;
29
+ if (typeof b.text === 'string' && b.text.trim() !== '') return false;
30
+ }
31
+ return true;
32
+ }
33
+
34
+ /** The server id and feature set the marker must carry. */
35
+ export const SILENT_HEARTBEAT_SERVER_ID = 'heartbeat';
36
+ export const SILENT_HEARTBEAT_FEATURE_SET = 'heartbeat';
37
+
38
+ /**
39
+ * The exact silent-heartbeat marker: an empty push from the `heartbeat`
40
+ * server on its `heartbeat` feature set, with `origin.source === 'heartbeat'`
41
+ * and `origin.silent === true`. Anything else that is empty is not a silent
42
+ * tick — arbitrary servers cannot claim the silent path by setting
43
+ * `origin.silent`, and a marker that carries content is ordinary content.
44
+ */
45
+ export function isSilentHeartbeatMarker(event: {
46
+ serverId: string;
47
+ featureSet?: string;
48
+ origin?: Record<string, unknown> | null;
49
+ content: readonly unknown[] | null | undefined;
50
+ }): boolean {
51
+ return event.serverId === SILENT_HEARTBEAT_SERVER_ID
52
+ && event.featureSet === SILENT_HEARTBEAT_FEATURE_SET
53
+ && event.origin?.source === 'heartbeat'
54
+ && event.origin?.silent === true
55
+ && Array.isArray(event.content)
56
+ && event.content.length === 0;
57
+ }