@animalabs/agent-framework 0.23.2 → 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 +1027 -440
  71. package/dist/src/framework.d.ts.map +1 -1
  72. package/dist/src/framework.js +4093 -2238
  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 +4468 -2418
  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
@@ -15,12 +15,15 @@ import { openTransport, STDERR_END_WAIT_MS, type McplTransport, type TransportCl
15
15
  import { maskNegotiatedCapabilities } from './capability-mask.js';
16
16
  import { CapabilityGrant, expandAdvertisementShorthand } from './capability-grant.js';
17
17
  import { CAPABILITY_DISABLED, boundedForLog, describeCause } from './errors.js';
18
- import { logDetail, logValue, logValueRedacted } from './log-value.js';
18
+ import { logDetail, logValue, logValueRedacted, loggedCode } from './log-value.js';
19
19
  import {
20
20
  LEGACY_MCP_PROTOCOL_VERSION,
21
21
  MODERN_MCP_PROTOCOL_VERSION,
22
22
  checkServerConfig,
23
23
  redactServerText,
24
+ redactedData,
25
+ serverTextRedactor,
26
+ type ServerTextRedactor,
24
27
  } from './protocol-family.js';
25
28
 
26
29
  import type {
@@ -84,18 +87,30 @@ export interface McplConnectionCloseInfo {
84
87
  * Spring Boot + JDA servers can take 5-10s to boot, so 30s is safe. */
85
88
  const INITIALIZE_TIMEOUT_MS = 30_000;
86
89
 
90
+ /** How many errors down a `cause` chain stderrTailOf looks. */
91
+ const STDERR_TAIL_CAUSE_LINKS = 8;
92
+
87
93
  /** A failed connect's `stderrTail`, the last of the server's stderr that its
88
- * handshake's error keeps, when there is one. */
94
+ * handshake's error keeps, when there is one: the error's own, or the
95
+ * nearest one in its `cause` chain, since a launch that couldn't be reaped
96
+ * (McplUnreapedLaunchError) and the framework's refusals wrap the
97
+ * handshake's error. It looks at most STDERR_TAIL_CAUSE_LINKS causes down,
98
+ * so a chain that loops ends. */
89
99
  export function stderrTailOf(error: unknown): string | undefined {
90
- const tail = (error as { stderrTail?: unknown } | null | undefined)?.stderrTail;
91
- return typeof tail === 'string' && tail !== '' ? tail : undefined;
100
+ let at = error;
101
+ for (let link = 0; link <= STDERR_TAIL_CAUSE_LINKS && typeof at === 'object' && at !== null; link++) {
102
+ const { stderrTail, cause } = at as { stderrTail?: unknown; cause?: unknown };
103
+ if (typeof stderrTail === 'string' && stderrTail !== '') return stderrTail;
104
+ at = cause;
105
+ }
106
+ return undefined;
92
107
  }
93
108
 
94
109
  /** For the host's own log line about a failed connect: the server's stderr
95
- * that the error keeps as `stderrTail`, with the server's credentials
96
- * taken out by `redact` (its redactServerText), one marked line per line of
97
- * it, each through log-value, since it's the server's text. '' when there's
98
- * none. */
110
+ * that the error, or the nearest error it wraps, keeps as `stderrTail`
111
+ * (stderrTailOf), with the server's credentials taken out by `redact` (its
112
+ * redactServerText), one marked line per line of it, each through
113
+ * log-value, since it's the server's text. '' when there's none. */
99
114
  export function withStderr(error: unknown, redact: (text: string) => string): string {
100
115
  const tail = stderrTailOf(error);
101
116
  if (tail === undefined) return '';
@@ -156,11 +171,13 @@ export class McplProtocolVersionError extends Error {
156
171
  constructor(
157
172
  message: string,
158
173
  readonly kind: 'rejected' | 'mismatch',
159
- /** Revision the server answered with (`mismatch`; null when it gave none). */
174
+ /** Revision the server answered with (`mismatch`; null when it gave
175
+ * none), redacted as the server's words are. */
160
176
  readonly returned: string | null,
161
177
  /** Revisions the server says it supports (`rejected`, when stated). */
162
178
  readonly supported: readonly string[] | null,
163
- /** The JSON-RPC error's code and data, verbatim (`rejected`). */
179
+ /** The JSON-RPC error's code, and its data with each string in it
180
+ * redacted (`rejected`). */
164
181
  readonly code?: number,
165
182
  readonly data?: unknown,
166
183
  /** The revision this client offered: the legacy engine's, unless a
@@ -172,8 +189,13 @@ export class McplProtocolVersionError extends Error {
172
189
  }
173
190
  }
174
191
 
175
- function protocolVersionRejection(id: string, error: { code: number; message: string; data?: unknown }): McplProtocolVersionError {
176
- const data = error.data as { supported?: unknown } | undefined;
192
+ function protocolVersionRejection(config: McplServerConfig, error: { code: number; message: string; data?: unknown }): McplProtocolVersionError {
193
+ const id = config.id;
194
+ // The server's words and data, redacted before they're bounded or kept:
195
+ // the error goes to the host. One build for all of them, so a
196
+ // configuration that can't be read is read once.
197
+ const redact = serverTextRedactor(config).now();
198
+ const data = redactedData(error.data, redact) as { supported?: unknown } | undefined;
177
199
  const supported = Array.isArray(data?.supported) && data.supported.every((v) => typeof v === 'string')
178
200
  ? (data.supported as string[])
179
201
  : null;
@@ -183,14 +205,15 @@ function protocolVersionRejection(id: string, error: { code: number; message: st
183
205
  return new McplProtocolVersionError(
184
206
  `MCPL server "${id}" does not support MCP ${MCP_PROTOCOL_VERSION}` +
185
207
  // The server's message can be any JSON value, so it's read without
186
- // throwing (describeCause) and bounded, as the initialize error is.
187
- (supported ? ` (it supports: ${supported.join(', ') || 'none listed'}).` : ` (${boundedForLog(describeCause(error.message))}).`) +
208
+ // throwing (describeCause), and redacted, then bounded, as the
209
+ // initialize error is.
210
+ (supported ? ` (it supports: ${boundedForLog(supported.join(', ')) || 'none listed'}).` : ` (${boundedForLog(redact(describeCause(error.message)))}).`) +
188
211
  modernHint,
189
212
  'rejected',
190
213
  null,
191
214
  supported,
192
215
  error.code,
193
- error.data,
216
+ data,
194
217
  );
195
218
  }
196
219
 
@@ -218,16 +241,21 @@ class HandshakeCleanupError extends Error {
218
241
  * is confirmed, so the caller can still clean up, and should start nothing
219
242
  * else for this server until then. connect() and connectWithReconnect()
220
243
  * throw this, and the modern engine's ModernMcpConnection.connect() too.
221
- * `cause` is the connect's own failure.
244
+ * `cause` is the connect's own failure. `connection` isn't enumerable, so
245
+ * printing the error doesn't print the connection's configuration, its
246
+ * credentials included.
222
247
  */
223
248
  export class McplUnreapedLaunchError<Connection extends { close(): Promise<void> } = McplServerConnection> extends Error {
249
+ declare readonly connection: Connection;
250
+
224
251
  constructor(
225
- readonly connection: Connection,
252
+ connection: Connection,
226
253
  failure: Error,
227
254
  /** The cleanup's own failure: the child it couldn't confirm exited. */
228
255
  readonly cleanup: Error,
229
256
  ) {
230
257
  super(`${failure.message}; its cleanup also failed: ${cleanup.message}`, { cause: failure });
258
+ Object.defineProperty(this, 'connection', { value: connection, enumerable: false });
231
259
  this.name = 'McplUnreapedLaunchError';
232
260
  }
233
261
  }
@@ -250,8 +278,9 @@ export class McplRequestError extends Error {
250
278
  constructor(
251
279
  message: string,
252
280
  readonly outcome: 'not-sent' | 'error-response' | 'no-response',
253
- /** The JSON-RPC error's `data`, verbatim, when the server supplied one
254
- * (e.g. which parts of a multi-part send were posted). */
281
+ /** The JSON-RPC error's `data`, when the server supplied one (e.g. which
282
+ * parts of a multi-part send were posted), with each string in it
283
+ * redacted (redactedData): a host hands it on. */
255
284
  readonly data?: unknown,
256
285
  ) {
257
286
  super(message);
@@ -529,8 +558,8 @@ export class McplServerConnection extends EventEmitter {
529
558
 
530
559
  // Event buffering: control-plane traffic can be released independently from
531
560
  // inference-bearing data-plane traffic. This lets a server finish its own
532
- // registration (and answer host tools/call requests) while startup/reconnect
533
- // reconciliation keeps agent wakes behind a durable barrier.
561
+ // registration (and answer host tools/call requests) while its data plane
562
+ // stays held, as through a quiesce.
534
563
  private controlPlaneReady = false;
535
564
  private dataPlaneReady = false;
536
565
  private bufferedEvents: HeldItem[] = [];
@@ -568,6 +597,15 @@ export class McplServerConnection extends EventEmitter {
568
597
 
569
598
  // Reconnect state (adapted from Anarchid/agent-framework@mcpl-module-proto)
570
599
  private config: McplServerConfig | null = null;
600
+ /** The redactor of the server's text for `config`, made at its first error
601
+ * answer, by when `config` is set, and kept: a framework-launched child's
602
+ * `env` is a getter that reads the awareness ledger, so a redactor built
603
+ * per answer would read it per answer (#540's review, R1-2). An answer
604
+ * takes one build of it for its words, data and log line (`now()`), so
605
+ * it reads the ledger at most once. It builds again only after a build
606
+ * that failed, which withholds that answer's text, or once a dial notes
607
+ * something new (serverTextRedactor). */
608
+ private redactor: ServerTextRedactor | null = null;
571
609
  private hostCapabilities: McplHostCapabilities | null = null;
572
610
  private reconnectEnabled = false;
573
611
  private reconnectIntervalMs = 5000;
@@ -666,8 +704,8 @@ export class McplServerConnection extends EventEmitter {
666
704
  `(long pause backlog${dropped > 0 ? `; ${dropped} dropped at cap` : ''})`,
667
705
  );
668
706
  }
669
- // Re-enter the gate for every buffered item. A control handler (notably
670
- // tools/list_changed) may install a newer data-plane barrier while this
707
+ // Re-enter the gate for every buffered item. A control handler (a
708
+ // host/command quiesce, say) may pause the data plane while this
671
709
  // snapshot is being flushed; later data items must observe that pause.
672
710
  this.flushHeld(pending);
673
711
  }
@@ -831,9 +869,8 @@ export class McplServerConnection extends EventEmitter {
831
869
 
832
870
  /**
833
871
  * Close the data plane synchronously while leaving an already-released
834
- * control plane live. Used before list-change reconciliation so no new
835
- * inference event can beat barrier installation. A reconnect closes both
836
- * planes instead.
872
+ * control plane live. Used by quiesce, and by reconnectAfterFailure before
873
+ * its teardown. A reconnect closes both planes instead.
837
874
  */
838
875
  pauseDataPlane(): void {
839
876
  this.dataPlaneReady = false;
@@ -841,8 +878,8 @@ export class McplServerConnection extends EventEmitter {
841
878
 
842
879
  /**
843
880
  * Override emit to buffer server→host events until the corresponding plane
844
- * is ready. Lifecycle events always pass through so reconnect can install a
845
- * new data-plane gate before the fresh transport is exposed.
881
+ * is ready. Lifecycle events always pass through so the reconnect listener
882
+ * runs before the fresh transport's buffered traffic is released.
846
883
  */
847
884
  override emit(event: string | symbol, ...args: unknown[]): boolean {
848
885
  const name = typeof event === 'string' ? event : '';
@@ -1169,18 +1206,22 @@ export class McplServerConnection extends EventEmitter {
1169
1206
  },
1170
1207
  };
1171
1208
 
1209
+ // When the transport fails, the end of the server's stderr often says
1210
+ // why, such as a missing token or a crash at startup. It goes on the
1211
+ // error as `stderrTail`, never into the message: an error's message
1212
+ // travels to traces and tool results, and a server's stderr is its own
1213
+ // text, which can hold a credential.
1214
+ const failed = (failure: string): Error & { stderrTail?: string } => {
1215
+ // It goes to the host too, so it's redacted, from a window of the
1216
+ // stream longer than the tail, so a credential the tail's cut
1217
+ // would split is whole when it's matched (stderrTailRedacted).
1218
+ const tail = transport.stderrTailRedacted(serverTextRedactor(config)).trimEnd();
1219
+ return Object.assign(new Error(failure), tail ? { stderrTail: tail } : {});
1220
+ };
1221
+
1172
1222
  // Await the initialize response, racing an early transport close/error,
1173
1223
  // a timeout and the owner's close. Every path cleans up its listeners.
1174
1224
  const { mcpl: rawCapabilities, mcpToolsAdvertised, protocolVersion } = await new Promise<{ mcpl: McplCapabilities | null; mcpToolsAdvertised: boolean; protocolVersion: string }>((resolve, reject) => {
1175
- // When the transport fails, the end of the server's stderr often says
1176
- // why, such as a missing token or a crash at startup. It goes on the
1177
- // error as `stderrTail`, never into the message: an error's message
1178
- // travels to traces and tool results, and a server's stderr is its
1179
- // own text, which can hold a credential.
1180
- const failed = (failure: string): Error & { stderrTail?: string } => {
1181
- const tail = transport.stderrTail.trimEnd();
1182
- return Object.assign(new Error(failure), tail ? { stderrTail: tail } : {});
1183
- };
1184
1225
  const cleanup = () => {
1185
1226
  transport.off('line', onLine);
1186
1227
  transport.off('close', onClose);
@@ -1222,21 +1263,25 @@ export class McplServerConnection extends EventEmitter {
1222
1263
  // listener after cleanup() has cleared the timeout: an uncaught
1223
1264
  // exception, which ends a host that doesn't catch them, and a
1224
1265
  // handshake left to wait forever. It's the server's text in an
1225
- // error's message, so it's bounded as errors.ts says.
1266
+ // error the host gets, so it's redacted, then bounded as
1267
+ // errors.ts says: a cut or a replaced character first would
1268
+ // leave part of a credential the redactor can't match.
1226
1269
  reject(msg.error.code === UNSUPPORTED_PROTOCOL_VERSION_CODE
1227
- ? protocolVersionRejection(config.id, msg.error)
1228
- : new Error(`MCPL server "${config.id}" initialize error: ${boundedForLog(describeCause(msg.error.message))}`));
1270
+ ? protocolVersionRejection(config, msg.error)
1271
+ : new Error(`MCPL server "${config.id}" initialize error: ${boundedForLog(redactServerText(describeCause(msg.error.message), config))}`));
1229
1272
  return;
1230
1273
  }
1231
1274
  const result = msg.result as Record<string, unknown> | undefined;
1232
1275
  // The server's answer is the established revision. This engine
1233
1276
  // implements exactly one, so any other answer (or none) means the
1234
1277
  // two sides never agreed on what they're speaking.
1235
- const returned = typeof result?.protocolVersion === 'string' ? result.protocolVersion : null;
1236
- if (returned !== MCP_PROTOCOL_VERSION) {
1278
+ const answered = typeof result?.protocolVersion === 'string' ? result.protocolVersion : null;
1279
+ if (answered !== MCP_PROTOCOL_VERSION) {
1280
+ // The server's words go to the host, so they're redacted.
1281
+ const returned = answered === null ? null : redactServerText(answered, config);
1237
1282
  reject(new McplProtocolVersionError(
1238
1283
  `MCPL server "${config.id}" answered initialize with protocolVersion ` +
1239
- `${returned === null ? '(none)' : JSON.stringify(returned)}; this client's legacy engine speaks only ${MCP_PROTOCOL_VERSION}`,
1284
+ `${returned === null ? '(none)' : boundedForLog(JSON.stringify(returned))}; this client's legacy engine speaks only ${MCP_PROTOCOL_VERSION}`,
1240
1285
  'mismatch',
1241
1286
  returned,
1242
1287
  null,
@@ -1251,7 +1296,7 @@ export class McplServerConnection extends EventEmitter {
1251
1296
  resolve({
1252
1297
  mcpl: (experimental?.mcpl as McplCapabilities) ?? null,
1253
1298
  mcpToolsAdvertised: caps?.tools !== undefined,
1254
- protocolVersion: returned,
1299
+ protocolVersion: answered,
1255
1300
  });
1256
1301
  };
1257
1302
  const onClose = (info: TransportCloseInfo) => {
@@ -1281,6 +1326,22 @@ export class McplServerConnection extends EventEmitter {
1281
1326
  transport.writeLine(JSON.stringify(initRequest));
1282
1327
  });
1283
1328
 
1329
+ // The transport has found the child's stdin or stdout failed before the
1330
+ // handshake could hand it on, such as when this host's answer to a ping
1331
+ // the server sent met EPIPE. Nothing more is written to the child then,
1332
+ // so the connection couldn't serve. It's read here, once the answer has
1333
+ // passed its own checks, rather than as the answer arrives: a write's
1334
+ // EPIPE is reported only once the callback that wrote it has returned,
1335
+ // so a ping and the answer read in one callback would pass a check made
1336
+ // there. A child that outlived SIGKILL may already have said so with
1337
+ // nothing listening (the transport's 'unreaped' is wired only once the
1338
+ // handshake hands it on). So the handshake fails, and its cleanup holds
1339
+ // the launch if it can't reap the child. A failure of the write below
1340
+ // is reported only once the caller has wired the transport, so it
1341
+ // reaches the connection as a loss, like any later one.
1342
+ const pipeFailure = transport.pipeFailure;
1343
+ if (pipeFailure) throw failed(`MCPL server "${config.id}" stdio pipe failed with ${pipeFailure.message} during the handshake`);
1344
+
1284
1345
  // Send `initialized` notification (no id)
1285
1346
  transport.writeLine(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }));
1286
1347
 
@@ -1485,9 +1546,13 @@ export class McplServerConnection extends EventEmitter {
1485
1546
  return this.sendRequest(McplMethod.ChannelsOpen, params as unknown as Record<string, unknown>, { timeoutMs }) as Promise<ChannelsOpenResult>;
1486
1547
  }
1487
1548
 
1488
- /** Send `channels/close` request and await result. */
1549
+ /** Send `channels/close` request and await result. It keeps a deadline as
1550
+ * channels/open does: a delivery waits for a channel's queued lifecycle
1551
+ * work, closes included, so a close that is never answered would hold the
1552
+ * delivery, and the turn that waits for it, for good. */
1489
1553
  sendChannelsClose(params: ChannelsCloseParams): Promise<ChannelsCloseResult> {
1490
- return this.sendRequest(McplMethod.ChannelsClose, params as unknown as Record<string, unknown>) as Promise<ChannelsCloseResult>;
1554
+ const timeoutMs = this.requestTimeoutMs > 0 ? this.requestTimeoutMs : this.publishDeadlineMs;
1555
+ return this.sendRequest(McplMethod.ChannelsClose, params as unknown as Record<string, unknown>, { timeoutMs }) as Promise<ChannelsCloseResult>;
1491
1556
  }
1492
1557
 
1493
1558
  /** Send `channels/acknowledge` request and await result. */
@@ -1585,10 +1650,11 @@ export class McplServerConnection extends EventEmitter {
1585
1650
  return { ...rest, tools };
1586
1651
  }
1587
1652
  if (typeof result.nextCursor !== 'string') {
1588
- throw malformed(`nextCursor must be a string, got ${JSON.stringify(result.nextCursor)}`);
1653
+ // The cursor is the server's, so it's redacted, then bounded.
1654
+ throw malformed(`nextCursor must be a string, got ${boundedForLog(redactServerText(JSON.stringify(result.nextCursor), this.config ?? {}))}`);
1589
1655
  }
1590
1656
  if (seen.has(result.nextCursor)) {
1591
- throw new Error(`MCPL server "${this.id}" repeated tools/list cursor ${JSON.stringify(result.nextCursor)}`);
1657
+ throw new Error(`MCPL server "${this.id}" repeated tools/list cursor ${boundedForLog(redactServerText(JSON.stringify(result.nextCursor), this.config ?? {}))}`);
1592
1658
  }
1593
1659
  seen.add(result.nextCursor);
1594
1660
  cursor = result.nextCursor;
@@ -1895,9 +1961,8 @@ export class McplServerConnection extends EventEmitter {
1895
1961
  // boundary resets, so control traffic too (a server's channels/register
1896
1962
  // right after initialize) must wait for the new grant rather than be
1897
1963
  // refused against the empty one. The reconnect lifecycle event bypasses
1898
- // buffering; its framework listener installs the awareness barrier,
1899
- // re-establishes the grant (§5.3 policy responses are never buffered),
1900
- // and then opens the planes.
1964
+ // buffering; its framework listener re-establishes the grant (§5.3
1965
+ // policy responses are never buffered), and then opens the planes.
1901
1966
  this.controlPlaneReady = false;
1902
1967
  this.dataPlaneReady = false;
1903
1968
  this.resetPolicyForTransportBoundary();
@@ -1921,6 +1986,18 @@ export class McplServerConnection extends EventEmitter {
1921
1986
  // Private: message sending
1922
1987
  // ==========================================================================
1923
1988
 
1989
+ /**
1990
+ * A request refused before anything is kept for it, as `error`, with its
1991
+ * operator's line logged after the refusal: a host logger that throws then
1992
+ * loses that line, and can't turn the refusal into a throw from the call.
1993
+ */
1994
+ private static refused(error: McplRequestError, line: string): Promise<never> {
1995
+ return new Promise((_, reject) => {
1996
+ reject(error);
1997
+ console.error(line);
1998
+ });
1999
+ }
2000
+
1924
2001
  /**
1925
2002
  * Send a JSON-RPC request and return a promise that resolves with the result
1926
2003
  * or rejects with a JSON-RPC error.
@@ -1958,14 +2035,16 @@ export class McplServerConnection extends EventEmitter {
1958
2035
  // pipe's error are the operator's, so they go to the log.
1959
2036
  const pipeFailure = this.transport?.pipeFailure;
1960
2037
  if (pipeFailure) {
1961
- console.error(`MCPL server "${this.id}": ${method} not sent, since its stdio pipe failed: ${logDetail(pipeFailure.message)}`);
1962
- return Promise.reject(new McplRequestError(
1963
- `The request wasn't sent: this host can no longer write to the "${this.id}" server, so nothing ran. ` +
1964
- (this.willReconnect
1965
- ? 'This host is reconnecting to it; try again later.'
1966
- : "This host won't reconnect to it on its own. If you need it, ask whoever runs this host."),
1967
- 'not-sent',
1968
- ));
2038
+ return McplServerConnection.refused(
2039
+ new McplRequestError(
2040
+ `The request wasn't sent: this host can no longer write to the "${this.id}" server, so nothing ran. ` +
2041
+ (this.willReconnect
2042
+ ? 'This host is reconnecting to it; try again later.'
2043
+ : "This host won't reconnect to it on its own. If you need it, ask whoever runs this host."),
2044
+ 'not-sent',
2045
+ ),
2046
+ `MCPL server "${this.id}": ${method} not sent, since its stdio pipe failed: ${logDetail(pipeFailure.message)}`,
2047
+ );
1969
2048
  }
1970
2049
 
1971
2050
  const id = this.nextRequestId++;
@@ -1975,6 +2054,23 @@ export class McplServerConnection extends EventEmitter {
1975
2054
  id,
1976
2055
  params,
1977
2056
  };
2057
+ // A request JSON can't write (a BigInt, a cycle) never leaves this host,
2058
+ // and calling again fails the same way, so it's refused before anything
2059
+ // is kept for it, in the modern engine's words (#556). The method, id and
2060
+ // the serializer's words are the operator's.
2061
+ let line: string;
2062
+ try {
2063
+ line = JSON.stringify(request);
2064
+ } catch (err) {
2065
+ return McplServerConnection.refused(
2066
+ new McplRequestError(
2067
+ `The request wasn't sent: this host couldn't send it to the "${this.id}" server, so nothing ran. ` +
2068
+ 'If calling it again fails, ask whoever runs this host.',
2069
+ 'not-sent',
2070
+ ),
2071
+ `MCPL server "${this.id}": ${method} (id=${id}) not sent, since it can't be written as JSON: ${logDetail(err instanceof Error ? err.message : err)}`,
2072
+ );
2073
+ }
1978
2074
 
1979
2075
  return new Promise((resolve, reject) => {
1980
2076
  const pending: PendingRequest = { resolve, reject, method };
@@ -2017,20 +2113,21 @@ export class McplServerConnection extends EventEmitter {
2017
2113
  }
2018
2114
  this.pendingRequests.set(id, pending);
2019
2115
  try {
2020
- this.transport!.writeLine(JSON.stringify(request));
2116
+ this.transport!.writeLine(line);
2021
2117
  } catch (err) {
2022
2118
  // A synchronous write rejection sent no request. Remove its pending
2023
2119
  // state and watchdog before reporting that it is safe to retry. The
2024
- // method, id and the transport's words are the operator's.
2120
+ // method, id and the transport's words are the operator's, logged
2121
+ // after the refusal, as refused() logs.
2025
2122
  this.pendingRequests.delete(id);
2026
2123
  if (pending.timer) clearTimeout(pending.timer);
2027
- console.error(
2028
- `MCPL server "${this.id}": writing ${method} (id=${id}) failed: ${logDetail(err instanceof Error ? err.message : err)}`,
2029
- );
2030
2124
  reject(new McplRequestError(
2031
2125
  `The request wasn't sent: this host couldn't write it to the "${this.id}" server, so nothing ran. Try again later.`,
2032
2126
  'not-sent',
2033
2127
  ));
2128
+ console.error(
2129
+ `MCPL server "${this.id}": writing ${method} (id=${id}) failed: ${logDetail(err instanceof Error ? err.message : err)}`,
2130
+ );
2034
2131
  }
2035
2132
  });
2036
2133
  }
@@ -2389,16 +2486,18 @@ export class McplServerConnection extends EventEmitter {
2389
2486
  if (pending.timer) clearTimeout(pending.timer);
2390
2487
 
2391
2488
  if (response.error) {
2392
- // The server's own words, after a word on whose they are, bounded as
2393
- // errors.ts says, since they're the server's text. Only a string is
2394
- // words: a message of any other JSON value reads as none. The method,
2395
- // id and JSON-RPC code are the operator's, so they go to the log, which
2396
- // prints any value a server can send without throwing (logValue). The
2397
- // caller's text keeps the server's words unredacted, as a JSON-RPC
2398
- // answer passes (redactServerText's scope); the log is a record of the
2399
- // server's text, so the message is redacted there, whatever JSON value
2400
- // it is, as every other line this connection writes is.
2401
- const said = typeof response.error.message === 'string' ? boundedForLog(response.error.message.trim()) : '';
2489
+ // The server's own words, after a word on whose they are, redacted and
2490
+ // then bounded as errors.ts says, since they're the server's text in
2491
+ // an error the caller gets (redactServerText's scope), and its data's
2492
+ // strings redacted too. It's trimmed after it's redacted, so a
2493
+ // credential that ends in a space is whole when it's matched. Only a
2494
+ // string is words: a message of any other JSON value reads as none.
2495
+ // The method and id are the operator's, so they go to the log, and the
2496
+ // code (loggedCode) and the message, whatever JSON values they are, go
2497
+ // there redacted (logValueRedacted), as every other line this
2498
+ // connection writes is.
2499
+ const redact = (this.redactor ??= serverTextRedactor(this.config ?? {})).now();
2500
+ const said = typeof response.error.message === 'string' ? boundedForLog(redact(response.error.message).trim()) : '';
2402
2501
  // The call is settled before its log line: its entry is gone and its
2403
2502
  // timer cleared, so nothing else would settle it, and a host's logger
2404
2503
  // can throw. The dispatch's containment reports such a throw. (A
@@ -2410,11 +2509,11 @@ export class McplServerConnection extends EventEmitter {
2410
2509
  : `The "${this.id}" server answered with an error without saying what went wrong, so whether the request took effect is unknown. ` +
2411
2510
  'Check its effect before trying again.',
2412
2511
  'error-response',
2413
- (response.error as { data?: unknown }).data,
2512
+ redactedData((response.error as { data?: unknown }).data, redact),
2414
2513
  ));
2415
2514
  console.error(
2416
- `MCPL server "${this.id}" answered ${pending.method} (id=${response.id}) with error ${logValue(response.error.code)}: ` +
2417
- logValueRedacted(response.error.message, (text) => redactServerText(text, this.config ?? {})),
2515
+ `MCPL server "${this.id}" answered ${pending.method} (id=${response.id}) with error ${loggedCode(response.error.code, redact)}: ` +
2516
+ logValueRedacted(response.error.message, redact),
2418
2517
  );
2419
2518
  } else {
2420
2519
  pending.resolve(response.result);
@@ -2540,8 +2639,10 @@ export class McplServerConnection extends EventEmitter {
2540
2639
  // reconnecting, and is lost as on a close, with the permanent failure
2541
2640
  // that raises the alert and takes its tools off the list. Its id stays
2542
2641
  // held until close() confirms the exit. A close the host has begun
2543
- // reports the failure itself. During a handshake nothing listens yet:
2544
- // the handshake's own cleanup holds the launch, once the handshake fails.
2642
+ // reports the failure itself. During a handshake nothing listens yet: a
2643
+ // handshake whose pipe has failed fails, when its answer comes (it's
2644
+ // refused then), when the child's exit closes the transport, or at its
2645
+ // timeout, and its own cleanup holds the launch.
2545
2646
  transport.on('unreaped', (failure: Error) => {
2546
2647
  if (this.closed || this.transport !== transport) return;
2547
2648
  this.holdUnreaped(transport, failure);
package/src/mcpl/tags.ts CHANGED
@@ -18,6 +18,42 @@ const CORE_IMPLIES: Readonly<Record<string, readonly string[]>> = {
18
18
  'chat:dm': ['chat:addressed', 'chat:private'],
19
19
  };
20
20
 
21
+ /**
22
+ * RFC-001 core tags that mark a message as being ABOUT the conversation
23
+ * rather than part of it: reactions (added or removed), edits and deletions
24
+ * of an existing message. These are markers, not turns — they must not clear
25
+ * explicit-send suppression, count as a new conversational round, or
26
+ * retarget the default publish channel. An edit can also be the first
27
+ * version of a message the agent sees, and then counts as that message:
28
+ * isMarkerTagged reads the host's mark for the case the coalescer knows
29
+ * (`firstView`), for isConversationalInjection and for the channel
30
+ * registry's publish channel and reply anchor. Tags are
31
+ * still never authority (§16.6): this set only ever WITHHOLDS default
32
+ * conversational treatment, it never grants anything.
33
+ */
34
+ export const NON_CONVERSATIONAL_TAGS: ReadonlySet<string> = new Set([
35
+ 'chat:reaction',
36
+ 'chat:reaction-remove',
37
+ 'chat:edited',
38
+ 'chat:deleted',
39
+ ]);
40
+
41
+ /** True when a (possibly untyped) tag list carries a non-conversational marker tag. */
42
+ export function hasNonConversationalTag(tags: unknown): boolean {
43
+ return Array.isArray(tags) && tags.some((t) => typeof t === 'string' && NON_CONVERSATIONAL_TAGS.has(t));
44
+ }
45
+
46
+ /**
47
+ * True when a message is a marker by its tags. `firstView` is the host's own
48
+ * mark that the coalescer delivered the message as the agent's first view of
49
+ * it (a stored message's metadata `firstView`, or in the channel registry the
50
+ * admission's receipt), never a server's claim: an edit so delivered counts
51
+ * as the message it replaced, while a reaction or deletion stays a notice.
52
+ */
53
+ export function isMarkerTagged(tags: unknown, firstView: boolean): boolean {
54
+ return hasNonConversationalTag(firstView && Array.isArray(tags) ? tags.filter((t) => t !== 'chat:edited') : tags);
55
+ }
56
+
21
57
  /**
22
58
  * Expand a raw tag list over the normative core closure, then resolve the
23
59
  * §16.2 mutual exclusion: `chat:addressed` and `chat:ambient` cannot both
@@ -36,7 +36,8 @@ export const MALFORMED_CONTENT_NOTICE =
36
36
  '[malformed result: its content is not a list of blocks; the tool may already have completed]';
37
37
 
38
38
  /** Saves a payload under a host-chosen name; resolves to its workspace path,
39
- * or null when there is nowhere writable to put it. */
39
+ * or null when there is no writable workspace mount without an include to
40
+ * put it in. */
40
41
  export type SaveToolPayload = (fileName: string, bytes: Buffer, mimeType: string) => Promise<string | null>;
41
42
 
42
43
  /** Stub fields are bounded independently of anything the server sends. */