@animalabs/agent-framework 0.23.1 → 0.24.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 (338) hide show
  1. package/README.md +212 -42
  2. package/dist/src/agent.d.ts +60 -9
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +92 -12
  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 +14 -12
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/code-execution/engine.d.ts +177 -0
  10. package/dist/src/code-execution/engine.d.ts.map +1 -0
  11. package/dist/src/code-execution/engine.js +494 -0
  12. package/dist/src/code-execution/engine.js.map +1 -0
  13. package/dist/src/code-execution/index.d.ts +5 -10
  14. package/dist/src/code-execution/index.d.ts.map +1 -1
  15. package/dist/src/code-execution/index.js +5 -9
  16. package/dist/src/code-execution/index.js.map +1 -1
  17. package/dist/src/code-execution/isolate/calls.d.ts +114 -0
  18. package/dist/src/code-execution/isolate/calls.d.ts.map +1 -0
  19. package/dist/src/code-execution/isolate/calls.js +145 -0
  20. package/dist/src/code-execution/isolate/calls.js.map +1 -0
  21. package/dist/src/code-execution/isolate/child-entry.d.ts +2 -0
  22. package/dist/src/code-execution/isolate/child-entry.d.ts.map +1 -0
  23. package/dist/src/code-execution/isolate/child-entry.js +5 -0
  24. package/dist/src/code-execution/isolate/child-entry.js.map +1 -0
  25. package/dist/src/code-execution/isolate/child-main.d.ts +26 -0
  26. package/dist/src/code-execution/isolate/child-main.d.ts.map +1 -0
  27. package/dist/src/code-execution/isolate/child-main.js +132 -0
  28. package/dist/src/code-execution/isolate/child-main.js.map +1 -0
  29. package/dist/src/code-execution/isolate/child.d.ts +44 -0
  30. package/dist/src/code-execution/isolate/child.d.ts.map +1 -0
  31. package/dist/src/code-execution/isolate/child.js +315 -0
  32. package/dist/src/code-execution/isolate/child.js.map +1 -0
  33. package/dist/src/code-execution/isolate/frames.d.ts +80 -0
  34. package/dist/src/code-execution/isolate/frames.d.ts.map +1 -0
  35. package/dist/src/code-execution/isolate/frames.js +210 -0
  36. package/dist/src/code-execution/isolate/frames.js.map +1 -0
  37. package/dist/src/code-execution/isolate/output.d.ts +68 -0
  38. package/dist/src/code-execution/isolate/output.d.ts.map +1 -0
  39. package/dist/src/code-execution/isolate/output.js +143 -0
  40. package/dist/src/code-execution/isolate/output.js.map +1 -0
  41. package/dist/src/code-execution/isolate/prelude.d.ts +109 -0
  42. package/dist/src/code-execution/isolate/prelude.d.ts.map +1 -0
  43. package/dist/src/code-execution/isolate/prelude.js +401 -0
  44. package/dist/src/code-execution/isolate/prelude.js.map +1 -0
  45. package/dist/src/code-execution/isolate/protocol.d.ts +151 -0
  46. package/dist/src/code-execution/isolate/protocol.d.ts.map +1 -0
  47. package/dist/src/code-execution/isolate/protocol.js +150 -0
  48. package/dist/src/code-execution/isolate/protocol.js.map +1 -0
  49. package/dist/src/code-execution/isolate/render.d.ts +57 -0
  50. package/dist/src/code-execution/isolate/render.d.ts.map +1 -0
  51. package/dist/src/code-execution/isolate/render.js +214 -0
  52. package/dist/src/code-execution/isolate/render.js.map +1 -0
  53. package/dist/src/code-execution/isolate/runner.d.ts +250 -0
  54. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -0
  55. package/dist/src/code-execution/isolate/runner.js +854 -0
  56. package/dist/src/code-execution/isolate/runner.js.map +1 -0
  57. package/dist/src/code-execution/isolate/scripts.d.ts +175 -0
  58. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -0
  59. package/dist/src/code-execution/isolate/scripts.js +357 -0
  60. package/dist/src/code-execution/isolate/scripts.js.map +1 -0
  61. package/dist/src/code-execution/quickjs.wasm +0 -0
  62. package/dist/src/code-execution/tool-definition.d.ts +93 -43
  63. package/dist/src/code-execution/tool-definition.d.ts.map +1 -1
  64. package/dist/src/code-execution/tool-definition.js +176 -142
  65. package/dist/src/code-execution/tool-definition.js.map +1 -1
  66. package/dist/src/deadline-timer.d.ts +3 -0
  67. package/dist/src/deadline-timer.d.ts.map +1 -1
  68. package/dist/src/deadline-timer.js +1 -1
  69. package/dist/src/deadline-timer.js.map +1 -1
  70. package/dist/src/framework.d.ts +1036 -440
  71. package/dist/src/framework.d.ts.map +1 -1
  72. package/dist/src/framework.js +4131 -2228
  73. package/dist/src/framework.js.map +1 -1
  74. package/dist/src/gate/event-gate.d.ts +28 -1
  75. package/dist/src/gate/event-gate.d.ts.map +1 -1
  76. package/dist/src/gate/event-gate.js +88 -10
  77. package/dist/src/gate/event-gate.js.map +1 -1
  78. package/dist/src/held-writes.d.ts +16 -3
  79. package/dist/src/held-writes.d.ts.map +1 -1
  80. package/dist/src/held-writes.js +16 -3
  81. package/dist/src/held-writes.js.map +1 -1
  82. package/dist/src/index.d.ts +6 -6
  83. package/dist/src/index.d.ts.map +1 -1
  84. package/dist/src/index.js +9 -8
  85. package/dist/src/index.js.map +1 -1
  86. package/dist/src/mcpl/channel-registry.d.ts +471 -75
  87. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  88. package/dist/src/mcpl/channel-registry.js +1673 -337
  89. package/dist/src/mcpl/channel-registry.js.map +1 -1
  90. package/dist/src/mcpl/conversation-router.d.ts +12 -7
  91. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  92. package/dist/src/mcpl/conversation-router.js +12 -7
  93. package/dist/src/mcpl/conversation-router.js.map +1 -1
  94. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  95. package/dist/src/mcpl/convert-block.js +9 -5
  96. package/dist/src/mcpl/convert-block.js.map +1 -1
  97. package/dist/src/mcpl/inbound-source.d.ts +50 -0
  98. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  99. package/dist/src/mcpl/inbound-source.js +130 -0
  100. package/dist/src/mcpl/inbound-source.js.map +1 -1
  101. package/dist/src/mcpl/index.d.ts +2 -1
  102. package/dist/src/mcpl/index.d.ts.map +1 -1
  103. package/dist/src/mcpl/index.js +3 -2
  104. package/dist/src/mcpl/index.js.map +1 -1
  105. package/dist/src/mcpl/log-value.d.ts +6 -0
  106. package/dist/src/mcpl/log-value.d.ts.map +1 -1
  107. package/dist/src/mcpl/log-value.js +8 -0
  108. package/dist/src/mcpl/log-value.js.map +1 -1
  109. package/dist/src/mcpl/modern-connection.d.ts +15 -6
  110. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  111. package/dist/src/mcpl/modern-connection.js +168 -53
  112. package/dist/src/mcpl/modern-connection.js.map +1 -1
  113. package/dist/src/mcpl/prose-grammar.d.ts +4 -5
  114. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  115. package/dist/src/mcpl/prose-grammar.js +4 -5
  116. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  117. package/dist/src/mcpl/protocol-family.d.ts +97 -10
  118. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  119. package/dist/src/mcpl/protocol-family.js +173 -16
  120. package/dist/src/mcpl/protocol-family.js.map +1 -1
  121. package/dist/src/mcpl/push-coalescer.d.ts +132 -14
  122. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  123. package/dist/src/mcpl/push-coalescer.js +227 -50
  124. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  125. package/dist/src/mcpl/push-handler.d.ts +6 -1
  126. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  127. package/dist/src/mcpl/push-handler.js +4 -1
  128. package/dist/src/mcpl/push-handler.js.map +1 -1
  129. package/dist/src/mcpl/reference-fetcher.d.ts +2 -2
  130. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  131. package/dist/src/mcpl/reference-fetcher.js +4 -4
  132. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  133. package/dist/src/mcpl/server-connection.d.ts +50 -21
  134. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  135. package/dist/src/mcpl/server-connection.js +158 -74
  136. package/dist/src/mcpl/server-connection.js.map +1 -1
  137. package/dist/src/mcpl/tags.d.ts +24 -0
  138. package/dist/src/mcpl/tags.d.ts.map +1 -1
  139. package/dist/src/mcpl/tags.js +33 -0
  140. package/dist/src/mcpl/tags.js.map +1 -1
  141. package/dist/src/mcpl/tool-result-normalize.d.ts +2 -1
  142. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  143. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  144. package/dist/src/mcpl/transport.d.ts +55 -7
  145. package/dist/src/mcpl/transport.d.ts.map +1 -1
  146. package/dist/src/mcpl/transport.js +163 -34
  147. package/dist/src/mcpl/transport.js.map +1 -1
  148. package/dist/src/mcpl/types.d.ts +22 -6
  149. package/dist/src/mcpl/types.d.ts.map +1 -1
  150. package/dist/src/mcpl/types.js.map +1 -1
  151. package/dist/src/module-registry.d.ts +4 -2
  152. package/dist/src/module-registry.d.ts.map +1 -1
  153. package/dist/src/module-registry.js +100 -13
  154. package/dist/src/module-registry.js.map +1 -1
  155. package/dist/src/modules/discord/index.d.ts.map +1 -1
  156. package/dist/src/modules/discord/index.js +8 -13
  157. package/dist/src/modules/discord/index.js.map +1 -1
  158. package/dist/src/modules/history/index.d.ts.map +1 -1
  159. package/dist/src/modules/history/index.js +27 -4
  160. package/dist/src/modules/history/index.js.map +1 -1
  161. package/dist/src/modules/history/semantic.d.ts +15 -3
  162. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  163. package/dist/src/modules/history/semantic.js +27 -5
  164. package/dist/src/modules/history/semantic.js.map +1 -1
  165. package/dist/src/modules/index.d.ts +1 -1
  166. package/dist/src/modules/index.d.ts.map +1 -1
  167. package/dist/src/modules/index.js.map +1 -1
  168. package/dist/src/modules/unknown-tool.d.ts +4 -1
  169. package/dist/src/modules/unknown-tool.d.ts.map +1 -1
  170. package/dist/src/modules/unknown-tool.js +4 -1
  171. package/dist/src/modules/unknown-tool.js.map +1 -1
  172. package/dist/src/modules/workspace/branch-intent.d.ts +3 -1
  173. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -1
  174. package/dist/src/modules/workspace/branch-intent.js +3 -1
  175. package/dist/src/modules/workspace/branch-intent.js.map +1 -1
  176. package/dist/src/modules/workspace/edit-plan.d.ts +265 -0
  177. package/dist/src/modules/workspace/edit-plan.d.ts.map +1 -0
  178. package/dist/src/modules/workspace/edit-plan.js +794 -0
  179. package/dist/src/modules/workspace/edit-plan.js.map +1 -0
  180. package/dist/src/modules/workspace/edit-text.d.ts +117 -0
  181. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -0
  182. package/dist/src/modules/workspace/edit-text.js +584 -0
  183. package/dist/src/modules/workspace/edit-text.js.map +1 -0
  184. package/dist/src/modules/workspace/effects.d.ts +5 -4
  185. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  186. package/dist/src/modules/workspace/effects.js +78 -25
  187. package/dist/src/modules/workspace/effects.js.map +1 -1
  188. package/dist/src/modules/workspace/index.d.ts +484 -61
  189. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  190. package/dist/src/modules/workspace/index.js +2173 -645
  191. package/dist/src/modules/workspace/index.js.map +1 -1
  192. package/dist/src/modules/workspace/observe.d.ts +213 -38
  193. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  194. package/dist/src/modules/workspace/observe.js +416 -36
  195. package/dist/src/modules/workspace/observe.js.map +1 -1
  196. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  197. package/dist/src/modules/workspace/reconcile.js +15 -8
  198. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  199. package/dist/src/modules/workspace/text.d.ts +32 -0
  200. package/dist/src/modules/workspace/text.d.ts.map +1 -0
  201. package/dist/src/modules/workspace/text.js +67 -0
  202. package/dist/src/modules/workspace/text.js.map +1 -0
  203. package/dist/src/modules/workspace/types.d.ts +210 -6
  204. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  205. package/dist/src/modules/workspace/types.js.map +1 -1
  206. package/dist/src/modules/workspace/watcher.d.ts +3 -2
  207. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  208. package/dist/src/modules/workspace/watcher.js.map +1 -1
  209. package/dist/src/operator-log.d.ts +52 -24
  210. package/dist/src/operator-log.d.ts.map +1 -1
  211. package/dist/src/operator-log.js.map +1 -1
  212. package/dist/src/recovery/discord-awareness-outbox.d.ts +496 -72
  213. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  214. package/dist/src/recovery/discord-awareness-outbox.js +1196 -289
  215. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  216. package/dist/src/recovery/offline-branch.d.ts +26 -1
  217. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  218. package/dist/src/recovery/offline-branch.js +164 -39
  219. package/dist/src/recovery/offline-branch.js.map +1 -1
  220. package/dist/src/recovery/recover-cli.js +23 -4
  221. package/dist/src/recovery/recover-cli.js.map +1 -1
  222. package/dist/src/send-tools.d.ts +11 -0
  223. package/dist/src/send-tools.d.ts.map +1 -0
  224. package/dist/src/send-tools.js +11 -0
  225. package/dist/src/send-tools.js.map +1 -0
  226. package/dist/src/tool-result-guard.d.ts +4 -3
  227. package/dist/src/tool-result-guard.d.ts.map +1 -1
  228. package/dist/src/tool-result-guard.js +5 -4
  229. package/dist/src/tool-result-guard.js.map +1 -1
  230. package/dist/src/tool-result-history.d.ts +1 -1
  231. package/dist/src/tool-result-history.js +1 -1
  232. package/dist/src/tune-out/coordinator.d.ts +21 -0
  233. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  234. package/dist/src/tune-out/coordinator.js +139 -37
  235. package/dist/src/tune-out/coordinator.js.map +1 -1
  236. package/dist/src/types/agent.d.ts +4 -0
  237. package/dist/src/types/agent.d.ts.map +1 -1
  238. package/dist/src/types/events.d.ts +62 -9
  239. package/dist/src/types/events.d.ts.map +1 -1
  240. package/dist/src/types/framework.d.ts +62 -58
  241. package/dist/src/types/framework.d.ts.map +1 -1
  242. package/dist/src/types/module.d.ts +40 -8
  243. package/dist/src/types/module.d.ts.map +1 -1
  244. package/dist/src/types/trace.d.ts +111 -10
  245. package/dist/src/types/trace.d.ts.map +1 -1
  246. package/dist/src/usage/types.d.ts +16 -0
  247. package/dist/src/usage/types.d.ts.map +1 -1
  248. package/dist/src/usage/usage-tracker.d.ts +14 -1
  249. package/dist/src/usage/usage-tracker.d.ts.map +1 -1
  250. package/dist/src/usage/usage-tracker.js +81 -41
  251. package/dist/src/usage/usage-tracker.js.map +1 -1
  252. package/package.json +6 -5
  253. package/src/agent.ts +93 -12
  254. package/src/api/server.ts +14 -12
  255. package/src/code-execution/engine.ts +595 -0
  256. package/src/code-execution/index.ts +5 -17
  257. package/src/code-execution/isolate/calls.ts +199 -0
  258. package/src/code-execution/isolate/child-entry.ts +5 -0
  259. package/src/code-execution/isolate/child-main.ts +132 -0
  260. package/src/code-execution/isolate/child.ts +327 -0
  261. package/src/code-execution/isolate/frames.ts +209 -0
  262. package/src/code-execution/isolate/output.ts +161 -0
  263. package/src/code-execution/isolate/prelude.ts +409 -0
  264. package/src/code-execution/isolate/protocol.ts +240 -0
  265. package/src/code-execution/isolate/render.ts +230 -0
  266. package/src/code-execution/isolate/runner.ts +911 -0
  267. package/src/code-execution/isolate/scripts.ts +479 -0
  268. package/src/code-execution/quickjs.wasm +0 -0
  269. package/src/code-execution/quickjs.wasm.LICENSES +149 -0
  270. package/src/code-execution/tool-definition.ts +236 -167
  271. package/src/deadline-timer.ts +1 -1
  272. package/src/framework.ts +4507 -2406
  273. package/src/gate/event-gate.ts +78 -11
  274. package/src/held-writes.ts +17 -3
  275. package/src/index.ts +30 -22
  276. package/src/mcpl/channel-registry.ts +1825 -368
  277. package/src/mcpl/conversation-router.ts +12 -7
  278. package/src/mcpl/convert-block.ts +9 -5
  279. package/src/mcpl/inbound-source.ts +131 -0
  280. package/src/mcpl/index.ts +4 -2
  281. package/src/mcpl/log-value.ts +9 -0
  282. package/src/mcpl/modern-connection.ts +162 -53
  283. package/src/mcpl/prose-grammar.ts +4 -5
  284. package/src/mcpl/protocol-family.ts +199 -21
  285. package/src/mcpl/push-coalescer.ts +306 -62
  286. package/src/mcpl/push-handler.ts +10 -2
  287. package/src/mcpl/reference-fetcher.ts +4 -4
  288. package/src/mcpl/server-connection.ts +180 -79
  289. package/src/mcpl/tags.ts +36 -0
  290. package/src/mcpl/tool-result-normalize.ts +2 -1
  291. package/src/mcpl/transport.ts +162 -33
  292. package/src/mcpl/types.ts +23 -6
  293. package/src/module-registry.ts +92 -13
  294. package/src/modules/discord/index.ts +8 -13
  295. package/src/modules/history/index.ts +25 -3
  296. package/src/modules/history/semantic.ts +29 -6
  297. package/src/modules/index.ts +1 -0
  298. package/src/modules/unknown-tool.ts +4 -1
  299. package/src/modules/workspace/branch-intent.ts +3 -1
  300. package/src/modules/workspace/edit-plan.ts +954 -0
  301. package/src/modules/workspace/edit-text.ts +618 -0
  302. package/src/modules/workspace/effects.ts +70 -19
  303. package/src/modules/workspace/index.ts +2247 -642
  304. package/src/modules/workspace/observe.ts +465 -56
  305. package/src/modules/workspace/reconcile.ts +14 -7
  306. package/src/modules/workspace/text.ts +68 -0
  307. package/src/modules/workspace/types.ts +211 -6
  308. package/src/modules/workspace/watcher.ts +3 -2
  309. package/src/operator-log.ts +47 -19
  310. package/src/recovery/discord-awareness-outbox.ts +1500 -356
  311. package/src/recovery/offline-branch.ts +189 -55
  312. package/src/recovery/recover-cli.ts +25 -4
  313. package/src/send-tools.ts +11 -0
  314. package/src/tool-result-guard.ts +6 -5
  315. package/src/tool-result-history.ts +1 -1
  316. package/src/tune-out/coordinator.ts +163 -42
  317. package/src/types/agent.ts +4 -0
  318. package/src/types/events.ts +62 -9
  319. package/src/types/framework.ts +62 -58
  320. package/src/types/module.ts +40 -8
  321. package/src/types/trace.ts +121 -15
  322. package/src/usage/types.ts +16 -0
  323. package/src/usage/usage-tracker.ts +86 -43
  324. package/dist/src/code-execution/py-runner.d.ts +0 -191
  325. package/dist/src/code-execution/py-runner.d.ts.map +0 -1
  326. package/dist/src/code-execution/py-runner.js +0 -655
  327. package/dist/src/code-execution/py-runner.js.map +0 -1
  328. package/dist/src/code-execution/runtime-py.d.ts +0 -50
  329. package/dist/src/code-execution/runtime-py.d.ts.map +0 -1
  330. package/dist/src/code-execution/runtime-py.js +0 -677
  331. package/dist/src/code-execution/runtime-py.js.map +0 -1
  332. package/dist/src/mcpl/prose-stream-router.d.ts +0 -88
  333. package/dist/src/mcpl/prose-stream-router.d.ts.map +0 -1
  334. package/dist/src/mcpl/prose-stream-router.js +0 -291
  335. package/dist/src/mcpl/prose-stream-router.js.map +0 -1
  336. package/src/code-execution/py-runner.ts +0 -791
  337. package/src/code-execution/runtime-py.ts +0 -677
  338. package/src/mcpl/prose-stream-router.ts +0 -268
@@ -68,10 +68,10 @@ import {
68
68
 
69
69
  import { StdioTransport, type McplTransport } from './transport.js';
70
70
  import { McplProtocolVersionError, McplRequestError, McplUnreapedLaunchError } from './server-connection.js';
71
- import { LEGACY_MCP_PROTOCOL_VERSION, MAX_TIMER_MS, MODERN_MCP_PROTOCOL_VERSION, checkServerConfig, describeCredentialError, noteDialSent, redactServerText, serverTextRedactor } from './protocol-family.js';
71
+ import { LEGACY_MCP_PROTOCOL_VERSION, MAX_TIMER_MS, MODERN_MCP_PROTOCOL_VERSION, checkServerConfig, describeCredentialError, maskedError, noteDialSent, redactServerText, redactedData, serverTextRedactor, type ServerTextRedactor } from './protocol-family.js';
72
72
  import type { McplServerConfig, McpToolDefinition } from './types.js';
73
73
  import { boundedForLog } from './errors.js';
74
- import { logDetail, logValue, logValueRedacted } from './log-value.js';
74
+ import { logDetail, logValue, logValueRedacted, loggedCode } from './log-value.js';
75
75
 
76
76
  const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
77
77
  /** Bound on establishing the connection (`server/discover`), like the legacy
@@ -132,6 +132,12 @@ class NotWrittenError extends Error {
132
132
  * transport inside the caller's async chain, so the scope reaches `send`. */
133
133
  const wireScope = new AsyncLocalStorage<WireRecord>();
134
134
 
135
+ /** A send in flight, its transport, and what that transport reported
136
+ * meanwhile, held until the send settles (ObservedTransport.send). A
137
+ * scope that names no transport, a notification's or a settled send's,
138
+ * holds nothing. */
139
+ const sendScope = new AsyncLocalStorage<{ wire: ObservedTransport | null; held: Error[] }>();
140
+
135
141
  /**
136
142
  * The boundary between the SDK and the actual transport. It records, for the
137
143
  * logical request in scope, each request of the observed method that it
@@ -172,15 +178,35 @@ class ObservedTransport implements Transport {
172
178
  if (record) wireScope.run(record, () => this.onmessage?.(message, extra));
173
179
  else this.onmessage?.(message, extra);
174
180
  };
175
- this.inner.onerror = (error) => this.onerror?.(error);
181
+ // An error this transport reports while one of its sends is in flight
182
+ // waits for that send to settle: the SDK's HTTP send reports a failure
183
+ // here and then throws it, and whoever sent it reports what it throws
184
+ // (#556). Anything else is reported at once, another transport's report
185
+ // made in a send's async context too.
186
+ this.inner.onerror = (error) => {
187
+ const sending = sendScope.getStore();
188
+ if (sending?.wire === this) sending.held.push(error);
189
+ else this.onerror?.(error);
190
+ };
176
191
  this.inner.onclose = () => this.onclose?.();
177
192
  await this.inner.start();
178
193
  }
179
194
 
180
195
  async send(message: JSONRPCMessage, options?: TransportSendOptions): Promise<void> {
196
+ if ('method' in message && !('id' in message)) {
197
+ // A notification's sender can swallow what its send throws (the SDK's
198
+ // listen teardown does), so what its transport reports is reported at
199
+ // once, whatever send it was made in.
200
+ await sendScope.run({ wire: null, held: [] }, () => this.inner.send(message, options));
201
+ return;
202
+ }
181
203
  const record = wireScope.getStore();
182
204
  let undo: (() => void) | undefined;
183
205
  if (record && 'method' in message && 'id' in message && message.method === record.method) {
206
+ // Every transport writes a message as JSON, so one JSON can't write (a
207
+ // BigInt, a cycle) never leaves this host: it's refused here, before
208
+ // it's marked handed off (#556).
209
+ JSON.stringify(message);
184
210
  const prior = { handedOff: record.handedOff, currentId: record.currentId, answer: record.answer, error: record.error };
185
211
  // Handed off from here on: past this point nothing proves it unsent,
186
212
  // unless the transport says it wrote nothing.
@@ -194,14 +220,29 @@ class ObservedTransport implements Transport {
194
220
  Object.assign(record, prior);
195
221
  };
196
222
  }
223
+ // A request's or a response's sender reports what its send throws: a
224
+ // call through its outcome, the connect through its failure, the SDK
225
+ // through its own report. So what the transport reports meanwhile is held
226
+ // here, and the failure the send threw isn't reported a second time (#556).
227
+ const sending: { wire: ObservedTransport | null; held: Error[] } = { wire: this, held: [] };
228
+ let thrown: unknown;
197
229
  try {
198
- await this.inner.send(message, options);
230
+ await sendScope.run(sending, () => this.inner.send(message, options));
199
231
  } catch (error) {
232
+ thrown = error;
200
233
  // Refused before any write: this leg never reached the server, so the
201
234
  // record is as it was before it. Any other throw (an HTTP send can fail
202
235
  // after part of the request went out) leaves it handed off.
203
236
  if (error instanceof NotWrittenError) undo?.();
204
237
  throw error;
238
+ } finally {
239
+ // Settled: what was held goes on, but for the failure the send threw,
240
+ // and the scope lets go of this transport, which work the send started
241
+ // still carries.
242
+ const { held } = sending;
243
+ sending.wire = null;
244
+ sending.held = [];
245
+ for (const error of held) if (error !== thrown) this.onerror?.(error);
205
246
  }
206
247
  }
207
248
 
@@ -278,8 +319,8 @@ class SpawnerStdioTransport implements Transport {
278
319
  private readonly onStderr: (line: string) => void,
279
320
  /** The child's pipe failed and it outlived SIGKILL too, so the line
280
321
  * stays open, owning it (the line's 'unreaped'): `reason` names the
281
- * pipe's error. */
282
- private readonly onUnreaped: (reason: string) => void,
322
+ * pipe's error, and `failure` is the reap's. */
323
+ private readonly onUnreaped: (reason: string, failure: Error) => void,
283
324
  ) {
284
325
  this.recorded = serverTextRedactor(config);
285
326
  }
@@ -313,14 +354,20 @@ class SpawnerStdioTransport implements Transport {
313
354
  line.on('stderr', (text: string) => this.onStderr(text));
314
355
  line.on('error', (error: Error) => this.onerror?.(error));
315
356
  line.on('close', () => this.onclose?.());
316
- line.on('unreaped', () => this.onUnreaped(`its stdio pipe failed with ${line.pipeFailure?.message ?? 'an error'}`));
357
+ line.on('unreaped', (failure: Error) => this.onUnreaped(`its stdio pipe failed with ${line.pipeFailure?.message ?? 'an error'}`, failure));
358
+ }
359
+
360
+ /** The first error on the child's stdin or stdout, once there is one: the
361
+ * line is ending the child then, and writes nothing more to it. */
362
+ get pipeFailure(): Error | undefined {
363
+ return this.line?.pipeFailure;
317
364
  }
318
365
 
319
366
  async send(message: JSONRPCMessage): Promise<void> {
320
367
  if (!this.line || this.line.closed) throw new NotWrittenError(`MCP server "${this.config.id}" stdio transport is closed`);
321
368
  // Once the child's stdin or stdout has failed, the line is ending it and
322
369
  // writes nothing more to it, so a message is refused here as not sent.
323
- const failed = this.line.pipeFailure;
370
+ const failed = this.pipeFailure;
324
371
  if (failed) throw new NotWrittenError(`MCP server "${this.config.id}" stdio pipe failed with ${failed.message}`, true);
325
372
  this.line.writeLine(JSON.stringify(message));
326
373
  }
@@ -374,6 +421,8 @@ interface Session {
374
421
  readonly generation: number;
375
422
  readonly client: Client;
376
423
  readonly wire: ObservedTransport;
424
+ /** Its stdio transport, under `wire`; null over HTTP. */
425
+ readonly stdio: SpawnerStdioTransport | null;
377
426
  /** Its transport has closed, whether or not the session was adopted yet. */
378
427
  lost: boolean;
379
428
  }
@@ -411,6 +460,13 @@ export class ModernMcpConnection extends EventEmitter {
411
460
  * connect() returns), delivered to the first listener. Bounded: the
412
461
  * newest lines are kept. */
413
462
  private stderrBacklog: string[] = [];
463
+ private redactor?: ServerTextRedactor;
464
+
465
+ /** The server's text with its configuration's credentials replaced, for
466
+ * the errors this connection hands its caller and its records. */
467
+ private get recorded(): ServerTextRedactor {
468
+ return (this.redactor ??= serverTextRedactor(this.config));
469
+ }
414
470
 
415
471
  private constructor(private readonly config: McplServerConfig) {
416
472
  super();
@@ -470,7 +526,8 @@ export class ModernMcpConnection extends EventEmitter {
470
526
  * While it's true the connection owns that child, so whatever registered
471
527
  * it keeps its id held; close() retries the reap. It's settled only once
472
528
  * a reap has finished: while a lost session's reap or a close runs, it
473
- * reads false, so read it once lossReaped() or close() has settled. */
529
+ * may not read true yet, so read it once lossReaped() or close() has
530
+ * settled. */
474
531
  get holdsUnreapedChild(): boolean {
475
532
  return this.unreaped.length > 0;
476
533
  }
@@ -483,10 +540,15 @@ export class ModernMcpConnection extends EventEmitter {
483
540
  }
484
541
 
485
542
  /** A failed generation's cleanup couldn't reap its child: keep owning it,
486
- * and halt reconnecting until close() settles it. */
487
- private holdUnreaped(session: Session, failure: Error): void {
543
+ * and halt reconnecting until close() settles it. It's reported as an
544
+ * 'error' at once, unless the caller reports it later itself. */
545
+ private holdUnreaped(session: Session, failure: Error, report = true): void {
488
546
  if (this.unreaped.some((held) => held.session === session)) return;
489
547
  this.unreaped.push({ session, failure });
548
+ if (report) this.reportUnreaped(failure);
549
+ }
550
+
551
+ private reportUnreaped(failure: Error): void {
490
552
  this.emit('error', new Error(
491
553
  `MCP server "${this.id}": ${failure.message}. Not starting another launch while it may still be running; close the connection to retry the reap.`,
492
554
  ));
@@ -538,10 +600,16 @@ export class ModernMcpConnection extends EventEmitter {
538
600
  this.reconnectEnabled = false;
539
601
  throw error;
540
602
  }
603
+ // Any other failure is the SDK's or the transport's: it goes on as its
604
+ // words alone, redacted, since its message can quote the url and its
605
+ // other properties, such as Bun's fetch error's `path` or a wrapper's
606
+ // `cause`, can carry it whole (maskedError). The protocol refusal
607
+ // above was built from redacted words.
608
+ const masked = maskedError(error, this.recorded);
541
609
  // A launch that couldn't be reaped halts reconnecting, so no retry
542
610
  // will come: the first connect fails, as one without reconnect does.
543
- if (!this.reconnectEnabled || this.holdsUnreapedChild) throw error;
544
- console.error(`MCP server "${this.id}" initial connect failed, will retry:`, redactServerText((error as Error).message, this.config));
611
+ if (!this.reconnectEnabled || this.holdsUnreapedChild) throw masked;
612
+ console.error(`MCP server "${this.id}" initial connect failed, will retry:`, masked.message);
545
613
  this.reconnectAttempts = 1;
546
614
  this.emit('connect-failed', { error: (error as Error).message, attempt: 0 });
547
615
  this.scheduleReconnect();
@@ -580,17 +648,19 @@ export class ModernMcpConnection extends EventEmitter {
580
648
  // reopen backs off from the base interval, not from the previous
581
649
  // generation's failures.
582
650
  this.relistenAttempts = 0;
583
- const inner: Transport = this.transportKind === 'stdio'
651
+ const stdio = this.transportKind === 'stdio'
584
652
  ? new SpawnerStdioTransport(
585
653
  this.config,
586
654
  (line) => { if (this.isCurrent(generation)) this.deliverStderr(line); },
587
- // A child whose pipe failed and that outlived SIGKILL: a loss, whose
588
- // reap finds the child still running and holds the launch, as for any
589
- // launch it can't reap. During a connect there is no session to lose
590
- // yet: the connect's own cleanup holds it, once the connect fails.
591
- (reason) => { if (this.isCurrent(generation)) this.handleLost(generation, reason); },
655
+ // A child whose pipe failed and that outlived SIGKILL: a loss whose
656
+ // launch is already known unreaped, so it's held as the loss is
657
+ // handled (handleLost). During a connect there is no session to lose
658
+ // yet: a connect whose pipe has failed fails, whether or not the SDK
659
+ // resolves it (establish), and its cleanup holds the launch.
660
+ (reason, failure) => { if (this.isCurrent(generation)) this.handleLost(generation, reason, failure); },
592
661
  )
593
- : new StreamableHTTPClientTransport(new URL(this.config.url!), { authProvider: bearerAuth(this.config) });
662
+ : null;
663
+ const inner: Transport = stdio ?? new StreamableHTTPClientTransport(new URL(this.config.url!), { authProvider: bearerAuth(this.config) });
594
664
  const wire = new ObservedTransport(inner);
595
665
 
596
666
  const client = new Client(
@@ -609,7 +679,7 @@ export class ModernMcpConnection extends EventEmitter {
609
679
  },
610
680
  },
611
681
  );
612
- const session: Session = { generation, client, wire, lost: false };
682
+ const session: Session = { generation, client, wire, stdio, lost: false };
613
683
  client.onerror = (error: Error) => {
614
684
  // The SDK reports late responses to deadline-abandoned calls here
615
685
  // ("unknown message ID"), and a list-change subscription it could not
@@ -654,16 +724,23 @@ export class ModernMcpConnection extends EventEmitter {
654
724
  if (failure) this.holdUnreaped(session, failure);
655
725
  return;
656
726
  }
657
- if (session.lost) {
658
- // The transport closed while the client connected, such as a child
659
- // that exited at the connect-time list-change subscribe: the SDK
660
- // reports that subscribe's failure only as an error, and resolves the
661
- // connect anyway (R1-2). Adopted, the session would read as connected
662
- // with nothing behind it, and no later close would come to reconnect
663
- // it. So the connect failed.
727
+ // The transport closed while the client connected, such as a child that
728
+ // exited at the connect-time list-change subscribe, or the child's stdio
729
+ // pipe failed, such as that subscribe's write meeting EPIPE: the SDK
730
+ // reports the subscribe's failure only as an error, and resolves the
731
+ // connect anyway (R1-2). Adopted, the session would read as connected
732
+ // with nothing it can write to behind it, and from a child that outlived
733
+ // SIGKILL, whose 'unreaped' came while there was no session to lose, no
734
+ // later close would come to reconnect it. So the connect failed: its
735
+ // close reaps the child, and a child it can't reap is held. A failed
736
+ // pipe is named whether or not the child has exited by now.
737
+ const pipe = session.stdio?.pipeFailure;
738
+ if (session.lost || pipe) {
664
739
  const failure = await ModernMcpConnection.closeSession(session);
665
740
  if (failure) this.holdUnreaped(session, failure);
666
- throw new Error(`MCP server "${this.id}" closed its connection while connecting`);
741
+ throw new Error(pipe
742
+ ? `MCP server "${this.id}" stdio pipe failed with ${pipe.message} while connecting`
743
+ : `MCP server "${this.id}" closed its connection while connecting`);
667
744
  }
668
745
  this.session = session;
669
746
  this.protocolVersion = MODERN_MCP_PROTOCOL_VERSION;
@@ -679,7 +756,11 @@ export class ModernMcpConnection extends EventEmitter {
679
756
  */
680
757
  private versionVerdict(error: unknown): McplProtocolVersionError | null {
681
758
  if (!(error instanceof UnsupportedProtocolVersionError)) return null;
682
- const data = (error as { data?: unknown }).data as { supported?: unknown } | undefined;
759
+ // The server's words and data, redacted before they're bounded or kept:
760
+ // the error goes to the host. One build for all of them, so a
761
+ // configuration that can't be read is read once.
762
+ const redact = this.recorded.now();
763
+ const data = redactedData((error as { data?: unknown }).data, redact) as { supported?: unknown } | undefined;
683
764
  const supported = Array.isArray(data?.supported) && data.supported.every((v) => typeof v === 'string')
684
765
  ? (data.supported as string[])
685
766
  : null;
@@ -693,13 +774,13 @@ export class ModernMcpConnection extends EventEmitter {
693
774
  : ` It speaks only legacy MCP, which this client reaches over stdio or WebSocket (ws://, wss://), not http(s).`;
694
775
  return new McplProtocolVersionError(
695
776
  `MCP server "${this.id}" does not support MCP ${MODERN_MCP_PROTOCOL_VERSION}` +
696
- (supported ? ` (it supports: ${supported.join(', ') || 'none listed'}).` : ` (${error.message}).`) +
777
+ (supported ? ` (it supports: ${boundedForLog(supported.join(', ')) || 'none listed'}).` : ` (${boundedForLog(redact(error.message))}).`) +
697
778
  legacyHint,
698
779
  'rejected',
699
780
  null,
700
781
  supported,
701
782
  (error as { code?: unknown }).code === undefined ? undefined : Number((error as { code?: unknown }).code),
702
- (error as { data?: unknown }).data,
783
+ data,
703
784
  MODERN_MCP_PROTOCOL_VERSION,
704
785
  );
705
786
  }
@@ -762,8 +843,10 @@ export class ModernMcpConnection extends EventEmitter {
762
843
  }
763
844
  }
764
845
 
765
- /** The transport went away (child exit, or a closed HTTP client). */
766
- private handleLost(generation: number, reason: string): void {
846
+ /** The transport went away (child exit, or a closed HTTP client).
847
+ * `unreaped`: the child's pipe failed and it outlived SIGKILL, so its
848
+ * launch is already known unreaped. */
849
+ private handleLost(generation: number, reason: string, unreaped?: Error): void {
767
850
  const session = this.session;
768
851
  if (session?.generation !== generation) return;
769
852
  this.session = null;
@@ -775,18 +858,34 @@ export class ModernMcpConnection extends EventEmitter {
775
858
  // reconnect's does, and is a permanent failure, as that one is: no
776
859
  // later attempt will come. The reap starts, and is recorded, before
777
860
  // 'close' is emitted, so a listener that throws can't skip it.
861
+ const permanent = () => this.emit('reconnect-failed', { error: `${reason}; ${this.unreapedFailure()}`, attempt: Math.max(1, this.reconnectAttempts), permanent: true });
778
862
  const done = ModernMcpConnection.closeSession(session).then((failure) => {
779
863
  if (this.losing?.session === session) this.losing = null;
864
+ // A close() the host made meanwhile reports the failure itself.
865
+ if (unreaped) {
866
+ // Held as the loss was handled, whatever the reap found now.
867
+ if (!this.closedByHost) permanent();
868
+ return;
869
+ }
780
870
  if (failure) {
781
871
  this.holdUnreaped(session, failure);
782
- // A close() the host made meanwhile reports the failure itself.
783
- if (!this.closedByHost) {
784
- this.emit('reconnect-failed', { error: `${reason}; ${this.unreapedFailure()}`, attempt: Math.max(1, this.reconnectAttempts), permanent: true });
785
- }
872
+ if (!this.closedByHost) permanent();
786
873
  } else if (this.willReconnect) this.scheduleReconnect();
787
874
  });
788
875
  this.losing = { session, done };
789
- this.emit('close', { reason });
876
+ // A launch already known unreaped is held now, before 'close', so the
877
+ // calls the reap's close ends, which classify their failure only once
878
+ // this returns, and any made meanwhile, read that this host won't
879
+ // reconnect, as the legacy engine has it (#540's review, R1-1). It's
880
+ // reported after 'close', which is where the other route reports a hold
881
+ // too, whatever the 'close' listeners do, so a listener that throws can
882
+ // skip neither 'close' nor the report.
883
+ if (unreaped) this.holdUnreaped(session, unreaped, false);
884
+ try {
885
+ this.emit('close', { reason });
886
+ } finally {
887
+ if (unreaped) this.reportUnreaped(unreaped);
888
+ }
790
889
  }
791
890
 
792
891
  /** The legacy engine's backoff: doubling from the base interval, capped,
@@ -1013,9 +1112,9 @@ export class ModernMcpConnection extends EventEmitter {
1013
1112
  * The rules apply in this order. The call's answer is the server's final
1014
1113
  * one: an error or a `complete` result.
1015
1114
  * - nothing handed to the transport: `not-sent`, whatever was raised (a
1016
- * pre-dispatch validation, a signal already aborted, no transport, a send
1017
- * Connectome's stdio line refused before writing, an HTTP refusal before
1018
- * any request went out);
1115
+ * pre-dispatch validation, a signal already aborted, no transport, a
1116
+ * message JSON can't write, a send Connectome's stdio line refused
1117
+ * before writing);
1019
1118
  * - the most recent leg answered with a JSON-RPC error: `error-response`
1020
1119
  * with that error's data;
1021
1120
  * - the most recent leg answered with a complete result: the server
@@ -1051,7 +1150,10 @@ export class ModernMcpConnection extends EventEmitter {
1051
1150
  private requestFailure(what: string, record: WireRecord, error: unknown, timeoutMs?: number, lost = false): Error {
1052
1151
  const server = `the "${this.id}" server`;
1053
1152
  const Server = `The "${this.id}" server`;
1054
- const message = redactServerText(error instanceof Error ? error.message : String(error), this.config);
1153
+ // One build of the redactor for all of this failure's texts, so a
1154
+ // configuration that can't be read is read once.
1155
+ const redact = this.recorded.now();
1156
+ const message = redact(error instanceof Error ? error.message : String(error));
1055
1157
  const log = (line: string) => console.error(`MCP server "${this.id}" ${line}`);
1056
1158
  const listing = record.method === 'tools/list';
1057
1159
  // What a failure whose effect is in doubt says: for a tool call, that the
@@ -1075,18 +1177,21 @@ export class ModernMcpConnection extends EventEmitter {
1075
1177
  }
1076
1178
  if (record.answer === 'error' && record.error) {
1077
1179
  const { code, message: wireMessage, data } = record.error;
1078
- // A JSON-RPC answer reaches the caller unredacted (redactServerText's
1079
- // scope). The log is a record of the server's text, so the message is
1080
- // redacted there, whatever JSON value it is, as every other line this
1081
- // connection writes is.
1082
- log(`answered ${what} with error ${logValue(code)}: ${logValueRedacted(wireMessage, (text) => redactServerText(text, this.config))}`);
1083
- // The server's own words, bounded as errors.ts says. Only a string is
1084
- // words: a message of any other JSON value reads as none.
1085
- const said = typeof wireMessage === 'string' ? boundedForLog(wireMessage.trim()) : '';
1180
+ // The log is a record of the server's text, so the code (loggedCode)
1181
+ // and the message are redacted there, whatever JSON values they are,
1182
+ // as every other line this connection writes is.
1183
+ log(`answered ${what} with error ${loggedCode(code, redact)}: ${logValueRedacted(wireMessage, redact)}`);
1184
+ // The server's own words, redacted and then bounded as errors.ts says,
1185
+ // since they're in an error the caller gets (redactServerText's
1186
+ // scope), and its data's strings redacted too. It's trimmed after it's
1187
+ // redacted, so a credential that ends in a space is whole when it's
1188
+ // matched. Only a string is words: a message of any other JSON value
1189
+ // reads as none.
1190
+ const said = typeof wireMessage === 'string' ? boundedForLog(redact(wireMessage).trim()) : '';
1086
1191
  return new McplRequestError(
1087
1192
  said ? `${Server} answered with an error: ${said}` : unknown(`${Server} answered with an error without saying what went wrong`),
1088
1193
  'error-response',
1089
- data,
1194
+ redactedData(data, redact),
1090
1195
  );
1091
1196
  }
1092
1197
  if (record.answer === 'complete') {
@@ -1132,10 +1237,14 @@ export class ModernMcpConnection extends EventEmitter {
1132
1237
  // The response body (the SDK's `text`) is the server's text and can
1133
1238
  // echo the request's url, so it's left out of the data; the status stays.
1134
1239
  const { text: _body, ...data } = (error instanceof SdkHttpError && error.data && typeof error.data === 'object' ? error.data : {}) as Record<string, unknown>;
1240
+ const redacted = redactedData(data, redact);
1135
1241
  return new McplRequestError(
1136
1242
  `${Server} refused this host access, so the request didn't run. If you need it, ask whoever runs this host.`,
1137
1243
  'error-response',
1138
- { ...data, httpStatus: refusedWith },
1244
+ // The status is this host's own, so it's added after the server's
1245
+ // data is redacted, and it stays when a configuration that can't be
1246
+ // read withholds that data.
1247
+ { ...(redacted !== null && typeof redacted === 'object' ? redacted : {}), httpStatus: refusedWith },
1139
1248
  );
1140
1249
  }
1141
1250
  if (deadline) {
@@ -1,9 +1,8 @@
1
1
  /**
2
- * Explicit-prose routing grammar — the SINGLE definition shared by the
3
- * delivery path (framework.deliverProse*) and the outgoing-stream router
4
- * (prose-stream-router.ts). Extracted from framework.ts verbatim so the two
5
- * parsers cannot drift: a streamed chunk and its eventual delivery must agree
6
- * on what is a prefix, what is a modifier, and what is body.
2
+ * Explicit-prose routing grammar: the prefix parsers the delivery path uses
3
+ * (parseProsePrefix, parseHybridProsePrefix). The envelope splitters in
4
+ * framework.ts test a line's `>>` or `>>>` start on their own. The outgoing
5
+ * stream needs no parser of its own: it carries what delivery publishes.
7
6
  *
8
7
  * Grammar (docs/explicit-prose-routing.md):
9
8
  * `>>target [!] [body…]` — target = channel spec or `skip_reply`