@namzu/sdk 31.1.0 → 33.0.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 (355) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +4 -0
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  6. package/dist/agents/SupervisorAgent.js +4 -0
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/agents/runAgent.d.ts +4 -0
  9. package/dist/agents/runAgent.d.ts.map +1 -1
  10. package/dist/agents/runAgent.js +4 -0
  11. package/dist/agents/runAgent.js.map +1 -1
  12. package/dist/authorization/gate.d.ts +1 -1
  13. package/dist/authorization/gate.js +1 -1
  14. package/dist/connector/index.d.ts +1 -1
  15. package/dist/connector/index.d.ts.map +1 -1
  16. package/dist/connector/index.js +1 -1
  17. package/dist/connector/index.js.map +1 -1
  18. package/dist/connector/mcp/adapter.d.ts.map +1 -1
  19. package/dist/connector/mcp/adapter.js +71 -16
  20. package/dist/connector/mcp/adapter.js.map +1 -1
  21. package/dist/connector/mcp/client.d.ts.map +1 -1
  22. package/dist/connector/mcp/client.js +3 -2
  23. package/dist/connector/mcp/client.js.map +1 -1
  24. package/dist/connector/mcp/http-redirect.d.ts +17 -0
  25. package/dist/connector/mcp/http-redirect.d.ts.map +1 -0
  26. package/dist/connector/mcp/http-redirect.js +31 -0
  27. package/dist/connector/mcp/http-redirect.js.map +1 -0
  28. package/dist/connector/mcp/http-sse.d.ts.map +1 -1
  29. package/dist/connector/mcp/http-sse.js +5 -0
  30. package/dist/connector/mcp/http-sse.js.map +1 -1
  31. package/dist/connector/mcp/image-admission.d.ts +16 -0
  32. package/dist/connector/mcp/image-admission.d.ts.map +1 -0
  33. package/dist/connector/mcp/image-admission.js +288 -0
  34. package/dist/connector/mcp/image-admission.js.map +1 -0
  35. package/dist/connector/mcp/stdio.d.ts +13 -0
  36. package/dist/connector/mcp/stdio.d.ts.map +1 -1
  37. package/dist/connector/mcp/stdio.js +66 -23
  38. package/dist/connector/mcp/stdio.js.map +1 -1
  39. package/dist/connector/mcp/streamable-http.d.ts.map +1 -1
  40. package/dist/connector/mcp/streamable-http.js +3 -0
  41. package/dist/connector/mcp/streamable-http.js.map +1 -1
  42. package/dist/constants/tools/index.js +1 -1
  43. package/dist/constants/tools/index.js.map +1 -1
  44. package/dist/directory/derive-supervisor.d.ts.map +1 -1
  45. package/dist/directory/derive-supervisor.js +5 -0
  46. package/dist/directory/derive-supervisor.js.map +1 -1
  47. package/dist/directory/derive.d.ts.map +1 -1
  48. package/dist/directory/derive.js +5 -0
  49. package/dist/directory/derive.js.map +1 -1
  50. package/dist/directory/load.d.ts.map +1 -1
  51. package/dist/directory/load.js +1 -0
  52. package/dist/directory/load.js.map +1 -1
  53. package/dist/directory/types.d.ts +2 -0
  54. package/dist/directory/types.d.ts.map +1 -1
  55. package/dist/execution/base.d.ts +13 -0
  56. package/dist/execution/base.d.ts.map +1 -1
  57. package/dist/execution/base.js +103 -8
  58. package/dist/execution/base.js.map +1 -1
  59. package/dist/execution/code-runtime/types.d.ts +11 -1
  60. package/dist/execution/code-runtime/types.d.ts.map +1 -1
  61. package/dist/execution/code-runtime/types.js.map +1 -1
  62. package/dist/execution/code-runtime/worker.d.ts.map +1 -1
  63. package/dist/execution/code-runtime/worker.js +67 -9
  64. package/dist/execution/code-runtime/worker.js.map +1 -1
  65. package/dist/execution/errors.d.ts +12 -0
  66. package/dist/execution/errors.d.ts.map +1 -0
  67. package/dist/execution/errors.js +21 -0
  68. package/dist/execution/errors.js.map +1 -0
  69. package/dist/execution/factory.d.ts.map +1 -1
  70. package/dist/execution/factory.js +1 -0
  71. package/dist/execution/factory.js.map +1 -1
  72. package/dist/execution/hybrid.d.ts +5 -0
  73. package/dist/execution/hybrid.d.ts.map +1 -1
  74. package/dist/execution/hybrid.js +36 -16
  75. package/dist/execution/hybrid.js.map +1 -1
  76. package/dist/execution/index.d.ts +1 -0
  77. package/dist/execution/index.d.ts.map +1 -1
  78. package/dist/execution/index.js +1 -0
  79. package/dist/execution/index.js.map +1 -1
  80. package/dist/execution/local.d.ts +13 -0
  81. package/dist/execution/local.d.ts.map +1 -1
  82. package/dist/execution/local.js +145 -19
  83. package/dist/execution/local.js.map +1 -1
  84. package/dist/execution/output.d.ts +27 -0
  85. package/dist/execution/output.d.ts.map +1 -0
  86. package/dist/execution/output.js +105 -0
  87. package/dist/execution/output.js.map +1 -0
  88. package/dist/execution/remote.d.ts +26 -2
  89. package/dist/execution/remote.d.ts.map +1 -1
  90. package/dist/execution/remote.js +86 -15
  91. package/dist/execution/remote.js.map +1 -1
  92. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  93. package/dist/manager/agent/lifecycle.js +1 -0
  94. package/dist/manager/agent/lifecycle.js.map +1 -1
  95. package/dist/plugin/lifecycle.d.ts.map +1 -1
  96. package/dist/plugin/lifecycle.js +9 -3
  97. package/dist/plugin/lifecycle.js.map +1 -1
  98. package/dist/process/kill-tree.d.ts +11 -13
  99. package/dist/process/kill-tree.d.ts.map +1 -1
  100. package/dist/process/kill-tree.js +11 -13
  101. package/dist/process/kill-tree.js.map +1 -1
  102. package/dist/provider/errors.d.ts +1 -0
  103. package/dist/provider/errors.d.ts.map +1 -1
  104. package/dist/provider/errors.js +39 -1
  105. package/dist/provider/errors.js.map +1 -1
  106. package/dist/provider/fallback.d.ts.map +1 -1
  107. package/dist/provider/fallback.js +31 -0
  108. package/dist/provider/fallback.js.map +1 -1
  109. package/dist/provider/idle-timeout.d.ts.map +1 -1
  110. package/dist/provider/idle-timeout.js +5 -0
  111. package/dist/provider/idle-timeout.js.map +1 -1
  112. package/dist/provider/retry.d.ts.map +1 -1
  113. package/dist/provider/retry.js +5 -0
  114. package/dist/provider/retry.js.map +1 -1
  115. package/dist/public-runtime.d.ts +7 -5
  116. package/dist/public-runtime.d.ts.map +1 -1
  117. package/dist/public-runtime.js +9 -4
  118. package/dist/public-runtime.js.map +1 -1
  119. package/dist/public-types.d.ts +2 -1
  120. package/dist/public-types.d.ts.map +1 -1
  121. package/dist/registry/tool/execute.d.ts +7 -1
  122. package/dist/registry/tool/execute.d.ts.map +1 -1
  123. package/dist/registry/tool/execute.js +201 -54
  124. package/dist/registry/tool/execute.js.map +1 -1
  125. package/dist/run/command-gate.d.ts.map +1 -1
  126. package/dist/run/command-gate.js +11 -0
  127. package/dist/run/command-gate.js.map +1 -1
  128. package/dist/run/reporter.d.ts.map +1 -1
  129. package/dist/run/reporter.js +6 -1
  130. package/dist/run/reporter.js.map +1 -1
  131. package/dist/run/workspace-fingerprint.d.ts +4 -4
  132. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  133. package/dist/run/workspace-fingerprint.js +6 -4
  134. package/dist/run/workspace-fingerprint.js.map +1 -1
  135. package/dist/runtime/bidi/index.d.ts +1 -1
  136. package/dist/runtime/bidi/index.d.ts.map +1 -1
  137. package/dist/runtime/bidi/index.js +1 -1
  138. package/dist/runtime/bidi/index.js.map +1 -1
  139. package/dist/runtime/bidi/session.d.ts +19 -0
  140. package/dist/runtime/bidi/session.d.ts.map +1 -1
  141. package/dist/runtime/bidi/session.js +150 -26
  142. package/dist/runtime/bidi/session.js.map +1 -1
  143. package/dist/runtime/query/executor.d.ts +43 -1
  144. package/dist/runtime/query/executor.d.ts.map +1 -1
  145. package/dist/runtime/query/executor.js +824 -174
  146. package/dist/runtime/query/executor.js.map +1 -1
  147. package/dist/runtime/query/index.d.ts +5 -0
  148. package/dist/runtime/query/index.d.ts.map +1 -1
  149. package/dist/runtime/query/index.js +47 -4
  150. package/dist/runtime/query/index.js.map +1 -1
  151. package/dist/runtime/query/iteration/index.d.ts +1 -0
  152. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  153. package/dist/runtime/query/iteration/index.js +34 -14
  154. package/dist/runtime/query/iteration/index.js.map +1 -1
  155. package/dist/runtime/query/iteration/phases/advisory.js +2 -2
  156. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  157. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  158. package/dist/runtime/query/iteration/phases/tool-review.js +84 -22
  159. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  160. package/dist/runtime/query/iteration/provider-rejected-image.d.ts +13 -0
  161. package/dist/runtime/query/iteration/provider-rejected-image.d.ts.map +1 -0
  162. package/dist/runtime/query/iteration/provider-rejected-image.js +91 -0
  163. package/dist/runtime/query/iteration/provider-rejected-image.js.map +1 -0
  164. package/dist/runtime/query/iteration/stream-turn.d.ts +4 -1
  165. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  166. package/dist/runtime/query/iteration/stream-turn.js +7 -3
  167. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  168. package/dist/runtime/query/request-rich-content.d.ts +13 -0
  169. package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
  170. package/dist/runtime/query/request-rich-content.js +173 -17
  171. package/dist/runtime/query/request-rich-content.js.map +1 -1
  172. package/dist/runtime/query/result.d.ts.map +1 -1
  173. package/dist/runtime/query/result.js +1 -0
  174. package/dist/runtime/query/result.js.map +1 -1
  175. package/dist/runtime/query/resume-pending.d.ts +5 -1
  176. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  177. package/dist/runtime/query/resume-pending.js +66 -1
  178. package/dist/runtime/query/resume-pending.js.map +1 -1
  179. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  180. package/dist/runtime/query/tool-output-budget.js +45 -16
  181. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  182. package/dist/runtime/query/tooling.d.ts +6 -0
  183. package/dist/runtime/query/tooling.d.ts.map +1 -1
  184. package/dist/runtime/query/tooling.js +4 -0
  185. package/dist/runtime/query/tooling.js.map +1 -1
  186. package/dist/sandbox/provider/local.d.ts.map +1 -1
  187. package/dist/sandbox/provider/local.js +193 -41
  188. package/dist/sandbox/provider/local.js.map +1 -1
  189. package/dist/scheduler/delegating.d.ts +2 -1
  190. package/dist/scheduler/delegating.d.ts.map +1 -1
  191. package/dist/scheduler/delegating.js +6 -5
  192. package/dist/scheduler/delegating.js.map +1 -1
  193. package/dist/scheduler/local.d.ts +2 -1
  194. package/dist/scheduler/local.d.ts.map +1 -1
  195. package/dist/scheduler/local.js +2 -2
  196. package/dist/scheduler/local.js.map +1 -1
  197. package/dist/session/workspace/driver.d.ts +12 -4
  198. package/dist/session/workspace/driver.d.ts.map +1 -1
  199. package/dist/session/workspace/git-worktree.d.ts +17 -3
  200. package/dist/session/workspace/git-worktree.d.ts.map +1 -1
  201. package/dist/session/workspace/git-worktree.js +153 -34
  202. package/dist/session/workspace/git-worktree.js.map +1 -1
  203. package/dist/skills/registry.d.ts +15 -0
  204. package/dist/skills/registry.d.ts.map +1 -1
  205. package/dist/skills/registry.js +33 -0
  206. package/dist/skills/registry.js.map +1 -1
  207. package/dist/store/attachment/index.d.ts +24 -2
  208. package/dist/store/attachment/index.d.ts.map +1 -1
  209. package/dist/store/attachment/index.js +52 -5
  210. package/dist/store/attachment/index.js.map +1 -1
  211. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  212. package/dist/tools/builtins/computer-use.js +39 -2
  213. package/dist/tools/builtins/computer-use.js.map +1 -1
  214. package/dist/tools/builtins/lsp.d.ts +1 -1
  215. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  216. package/dist/tools/builtins/run-code.js +24 -16
  217. package/dist/tools/builtins/run-code.js.map +1 -1
  218. package/dist/tools/builtins/skill.d.ts +2 -1
  219. package/dist/tools/builtins/skill.d.ts.map +1 -1
  220. package/dist/tools/builtins/skill.js +250 -6
  221. package/dist/tools/builtins/skill.js.map +1 -1
  222. package/dist/tools/coordinator/agent.d.ts.map +1 -1
  223. package/dist/tools/coordinator/agent.js +100 -30
  224. package/dist/tools/coordinator/agent.js.map +1 -1
  225. package/dist/types/agent/base.d.ts +4 -0
  226. package/dist/types/agent/base.d.ts.map +1 -1
  227. package/dist/types/agent/factory.d.ts +1 -0
  228. package/dist/types/agent/factory.d.ts.map +1 -1
  229. package/dist/types/agent/manager.d.ts +1 -1
  230. package/dist/types/agent/manager.d.ts.map +1 -1
  231. package/dist/types/agent/scheduler.d.ts +10 -1
  232. package/dist/types/agent/scheduler.d.ts.map +1 -1
  233. package/dist/types/authorization/index.d.ts +12 -12
  234. package/dist/types/bidi/index.d.ts +19 -2
  235. package/dist/types/bidi/index.d.ts.map +1 -1
  236. package/dist/types/computer-use/index.d.ts +18 -0
  237. package/dist/types/computer-use/index.d.ts.map +1 -1
  238. package/dist/types/connector/execution-context.d.ts +3 -1
  239. package/dist/types/connector/execution-context.d.ts.map +1 -1
  240. package/dist/types/execution/index.d.ts +43 -1
  241. package/dist/types/execution/index.d.ts.map +1 -1
  242. package/dist/types/hitl/index.d.ts +11 -0
  243. package/dist/types/hitl/index.d.ts.map +1 -1
  244. package/dist/types/hitl/index.js.map +1 -1
  245. package/dist/types/message/index.d.ts +31 -1
  246. package/dist/types/message/index.d.ts.map +1 -1
  247. package/dist/types/message/index.js +43 -0
  248. package/dist/types/message/index.js.map +1 -1
  249. package/dist/types/plugin/index.d.ts +14 -1
  250. package/dist/types/plugin/index.d.ts.map +1 -1
  251. package/dist/types/plugin/index.js +1 -0
  252. package/dist/types/plugin/index.js.map +1 -1
  253. package/dist/types/provider/error.d.ts +4 -1
  254. package/dist/types/provider/error.d.ts.map +1 -1
  255. package/dist/types/provider/interface.d.ts +14 -0
  256. package/dist/types/provider/interface.d.ts.map +1 -1
  257. package/dist/types/run/events.d.ts +10 -2
  258. package/dist/types/run/events.d.ts.map +1 -1
  259. package/dist/types/run/events.js.map +1 -1
  260. package/dist/types/sandbox/index.d.ts +15 -10
  261. package/dist/types/sandbox/index.d.ts.map +1 -1
  262. package/dist/types/sandbox/index.js.map +1 -1
  263. package/dist/types/tool/index.d.ts +129 -12
  264. package/dist/types/tool/index.d.ts.map +1 -1
  265. package/dist/types/tool/index.js.map +1 -1
  266. package/dist/types/workspace/ref.d.ts +5 -0
  267. package/dist/types/workspace/ref.d.ts.map +1 -1
  268. package/dist/utils/abort.d.ts +8 -0
  269. package/dist/utils/abort.d.ts.map +1 -1
  270. package/dist/utils/abort.js +16 -0
  271. package/dist/utils/abort.js.map +1 -1
  272. package/package.json +4 -4
  273. package/src/agents/ReactiveAgent.ts +4 -0
  274. package/src/agents/SupervisorAgent.ts +4 -0
  275. package/src/agents/runAgent.ts +8 -0
  276. package/src/authorization/gate.ts +1 -1
  277. package/src/connector/index.ts +2 -0
  278. package/src/connector/mcp/adapter.ts +82 -21
  279. package/src/connector/mcp/client.ts +2 -1
  280. package/src/connector/mcp/http-redirect.ts +32 -0
  281. package/src/connector/mcp/http-sse.ts +5 -0
  282. package/src/connector/mcp/image-admission.ts +272 -0
  283. package/src/connector/mcp/stdio.ts +63 -21
  284. package/src/connector/mcp/streamable-http.ts +3 -0
  285. package/src/constants/tools/index.ts +1 -1
  286. package/src/directory/derive-supervisor.ts +5 -0
  287. package/src/directory/derive.ts +5 -0
  288. package/src/directory/load.ts +1 -0
  289. package/src/directory/types.ts +2 -0
  290. package/src/execution/base.ts +124 -7
  291. package/src/execution/code-runtime/types.ts +15 -1
  292. package/src/execution/code-runtime/worker.ts +72 -9
  293. package/src/execution/errors.ts +24 -0
  294. package/src/execution/factory.ts +1 -0
  295. package/src/execution/hybrid.ts +47 -16
  296. package/src/execution/index.ts +1 -0
  297. package/src/execution/local.ts +164 -19
  298. package/src/execution/output.ts +104 -0
  299. package/src/execution/remote.ts +122 -17
  300. package/src/manager/agent/lifecycle.ts +1 -0
  301. package/src/plugin/lifecycle.ts +9 -3
  302. package/src/process/kill-tree.ts +11 -13
  303. package/src/provider/errors.ts +40 -1
  304. package/src/provider/fallback.ts +39 -0
  305. package/src/provider/idle-timeout.ts +8 -0
  306. package/src/provider/retry.ts +8 -0
  307. package/src/public-runtime.ts +23 -2
  308. package/src/public-types.ts +11 -0
  309. package/src/registry/tool/execute.ts +230 -64
  310. package/src/run/command-gate.ts +12 -0
  311. package/src/run/reporter.ts +7 -1
  312. package/src/run/workspace-fingerprint.ts +5 -4
  313. package/src/runtime/bidi/index.ts +1 -1
  314. package/src/runtime/bidi/session.ts +177 -28
  315. package/src/runtime/query/executor.ts +987 -172
  316. package/src/runtime/query/index.ts +59 -6
  317. package/src/runtime/query/iteration/index.ts +69 -29
  318. package/src/runtime/query/iteration/phases/advisory.ts +2 -2
  319. package/src/runtime/query/iteration/phases/tool-review.ts +87 -23
  320. package/src/runtime/query/iteration/provider-rejected-image.ts +106 -0
  321. package/src/runtime/query/iteration/stream-turn.ts +12 -2
  322. package/src/runtime/query/request-rich-content.ts +213 -15
  323. package/src/runtime/query/result.ts +1 -0
  324. package/src/runtime/query/resume-pending.ts +83 -1
  325. package/src/runtime/query/tool-output-budget.ts +52 -17
  326. package/src/runtime/query/tooling.ts +10 -0
  327. package/src/sandbox/provider/local.ts +213 -45
  328. package/src/scheduler/delegating.ts +6 -5
  329. package/src/scheduler/local.ts +3 -2
  330. package/src/session/workspace/driver.ts +12 -4
  331. package/src/session/workspace/git-worktree.ts +173 -34
  332. package/src/skills/registry.ts +47 -0
  333. package/src/store/attachment/index.ts +100 -15
  334. package/src/tools/builtins/computer-use.ts +45 -2
  335. package/src/tools/builtins/run-code.ts +24 -16
  336. package/src/tools/builtins/skill.ts +325 -7
  337. package/src/tools/coordinator/agent.ts +108 -33
  338. package/src/types/agent/base.ts +4 -0
  339. package/src/types/agent/factory.ts +1 -0
  340. package/src/types/agent/manager.ts +1 -1
  341. package/src/types/agent/scheduler.ts +10 -1
  342. package/src/types/bidi/index.ts +19 -2
  343. package/src/types/computer-use/index.ts +19 -0
  344. package/src/types/connector/execution-context.ts +3 -0
  345. package/src/types/execution/index.ts +46 -1
  346. package/src/types/hitl/index.ts +11 -0
  347. package/src/types/message/index.ts +78 -1
  348. package/src/types/plugin/index.ts +15 -0
  349. package/src/types/provider/error.ts +4 -1
  350. package/src/types/provider/interface.ts +18 -0
  351. package/src/types/run/events.ts +18 -4
  352. package/src/types/sandbox/index.ts +15 -10
  353. package/src/types/tool/index.ts +159 -15
  354. package/src/types/workspace/ref.ts +5 -0
  355. package/src/utils/abort.ts +18 -0
@@ -170,13 +170,14 @@ export class MCPClient {
170
170
  async disconnect(): Promise<void> {
171
171
  const reason = new Error('MCPClient disconnecting')
172
172
  this.abortCancellations(reason)
173
- if (this.status === 'disconnected') return
173
+ const alreadyDisconnected = this.status === 'disconnected'
174
174
 
175
175
  this.rejectAllPending('MCPClient disconnecting')
176
176
 
177
177
  await this.transport.close()
178
178
  this.status = 'disconnected'
179
179
  this.connectedAt = undefined
180
+ if (alreadyDisconnected) return
180
181
  this.log.info('MCP client disconnected')
181
182
  this.emitLifecycle({ type: 'mcp_client_disconnected', clientId: this.id })
182
183
  }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A request reached the configured MCP server, but no response from the
3
+ * requested operation was received because that server redirected it.
4
+ *
5
+ * This stays an internal transport-to-adapter signal. The public tool result
6
+ * carries the stable, serializable outcome fields instead of exposing an
7
+ * Error subclass as API.
8
+ */
9
+ export class MCPHttpRedirectError extends Error {
10
+ readonly code = 'mcp_http_redirect_refused'
11
+ readonly remoteOutcomeUnknown: boolean
12
+
13
+ constructor(
14
+ readonly status: number,
15
+ readonly requestMethod?: string,
16
+ ) {
17
+ const base = `MCP HTTP ${status} redirect was not followed because authenticated requests must remain at their configured endpoint. Configure the final MCP endpoint directly.`
18
+ const outcome =
19
+ requestMethod === 'tools/call'
20
+ ? ' The configured MCP server received the tool call, so the remote outcome is unknown; do not automatically retry.'
21
+ : ''
22
+ super(base + outcome)
23
+ this.name = 'MCPHttpRedirectError'
24
+ this.remoteOutcomeUnknown = requestMethod === 'tools/call'
25
+ }
26
+ }
27
+
28
+ export function refuseMcpHttpRedirect(response: Response, requestMethod?: string): void {
29
+ if (response.status >= 300 && response.status < 400) {
30
+ throw new MCPHttpRedirectError(response.status, requestMethod)
31
+ }
32
+ }
@@ -7,6 +7,7 @@ import type {
7
7
  import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js'
8
8
  import { type Logger, resolveLogger } from '../../utils/logger.js'
9
9
  import { ConnectorHttpOperation, validateConnectorTimeoutMs } from '../http-operation.js'
10
+ import { refuseMcpHttpRedirect } from './http-redirect.js'
10
11
 
11
12
  const DEFAULT_TIMEOUT_MS = 30_000
12
13
 
@@ -111,10 +112,12 @@ export class HttpSseTransport implements MCPTransport {
111
112
  ...this.config.headers,
112
113
  },
113
114
  body: JSON.stringify(message),
115
+ redirect: 'manual',
114
116
  signal: operation.signal,
115
117
  }),
116
118
  )
117
119
 
120
+ refuseMcpHttpRedirect(response, message.method)
118
121
  if (!response.ok) {
119
122
  throw new Error(`HTTP ${response.status}: ${response.statusText}`)
120
123
  }
@@ -202,9 +205,11 @@ export class HttpSseTransport implements MCPTransport {
202
205
  Accept: 'text/event-stream',
203
206
  ...this.config.headers,
204
207
  },
208
+ redirect: 'manual',
205
209
  signal,
206
210
  })
207
211
 
212
+ refuseMcpHttpRedirect(response)
208
213
  if (!response.ok || !response.body) {
209
214
  throw new Error(`SSE connection failed: HTTP ${response.status}`)
210
215
  }
@@ -0,0 +1,272 @@
1
+ import type { MCPContentBlock } from '../../types/connector/index.js'
2
+
3
+ const MAX_ENCODED_BYTES = 24 * 1024 * 1024
4
+ const MAX_DIMENSION = 16_384
5
+ const MAX_PIXELS = 40_000_000
6
+
7
+ type MCPImageBlock = Extract<MCPContentBlock, { type: 'image' }>
8
+ type ImageMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif'
9
+
10
+ const IMAGE_MEDIA_TYPES = new Set<ImageMediaType>([
11
+ 'image/png',
12
+ 'image/jpeg',
13
+ 'image/webp',
14
+ 'image/gif',
15
+ ])
16
+
17
+ function isImageMediaType(value: string): value is ImageMediaType {
18
+ return IMAGE_MEDIA_TYPES.has(value as ImageMediaType)
19
+ }
20
+
21
+ function decodeCanonicalBase64(value: string): Uint8Array | null {
22
+ if (value.length === 0 || value.length > MAX_ENCODED_BYTES) return null
23
+ if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
24
+ return null
25
+ }
26
+ const decoded = Buffer.from(value, 'base64')
27
+ return decoded.toString('base64') === value ? decoded : null
28
+ }
29
+
30
+ function dimensionsAreSafe(width: number, height: number): boolean {
31
+ return (
32
+ Number.isSafeInteger(width) &&
33
+ Number.isSafeInteger(height) &&
34
+ width > 0 &&
35
+ height > 0 &&
36
+ width <= MAX_DIMENSION &&
37
+ height <= MAX_DIMENSION &&
38
+ width * height <= MAX_PIXELS
39
+ )
40
+ }
41
+
42
+ const crcTable = (() => {
43
+ const table = new Uint32Array(256)
44
+ for (let n = 0; n < 256; n += 1) {
45
+ let value = n
46
+ for (let bit = 0; bit < 8; bit += 1) {
47
+ value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1
48
+ }
49
+ table[n] = value >>> 0
50
+ }
51
+ return table
52
+ })()
53
+
54
+ function crc32(bytes: Uint8Array, start: number, end: number): number {
55
+ let crc = 0xffffffff
56
+ for (let offset = start; offset < end; offset += 1) {
57
+ crc = (crcTable[(crc ^ (bytes[offset] ?? 0)) & 0xff] ?? 0) ^ (crc >>> 8)
58
+ }
59
+ return (crc ^ 0xffffffff) >>> 0
60
+ }
61
+
62
+ function isPng(bytes: Uint8Array): boolean {
63
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
64
+ if (bytes.length < 45 || !buffer.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex'))) {
65
+ return false
66
+ }
67
+ let offset = 8
68
+ let sawHeader = false
69
+ let sawData = false
70
+ while (offset + 12 <= bytes.length) {
71
+ const length = buffer.readUInt32BE(offset)
72
+ const typeStart = offset + 4
73
+ const dataStart = typeStart + 4
74
+ const dataEnd = dataStart + length
75
+ const chunkEnd = dataEnd + 4
76
+ if (dataEnd < dataStart || chunkEnd > bytes.length) return false
77
+ const type = buffer.toString('ascii', typeStart, dataStart)
78
+ if (buffer.readUInt32BE(dataEnd) !== crc32(bytes, typeStart, dataEnd)) return false
79
+ if (!sawHeader) {
80
+ if (type !== 'IHDR' || length !== 13) return false
81
+ const width = buffer.readUInt32BE(dataStart)
82
+ const height = buffer.readUInt32BE(dataStart + 4)
83
+ if (!dimensionsAreSafe(width, height)) return false
84
+ sawHeader = true
85
+ } else if (type === 'IHDR') return false
86
+ if (type === 'IDAT') sawData = true
87
+ if (type === 'IEND') return length === 0 && sawHeader && sawData && chunkEnd === bytes.length
88
+ offset = chunkEnd
89
+ }
90
+ return false
91
+ }
92
+
93
+ const JPEG_SOF_MARKERS = new Set([
94
+ 0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
95
+ ])
96
+
97
+ function isJpeg(bytes: Uint8Array): boolean {
98
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
99
+ if (bytes.length < 11 || bytes[0] !== 0xff || bytes[1] !== 0xd8) return false
100
+ let offset = 2
101
+ let sawFrame = false
102
+ let sawScan = false
103
+ let inScan = false
104
+ while (offset < bytes.length) {
105
+ if (inScan && bytes[offset] !== 0xff) {
106
+ offset += 1
107
+ continue
108
+ }
109
+ if (bytes[offset] !== 0xff) return false
110
+ while (offset < bytes.length && bytes[offset] === 0xff) offset += 1
111
+ if (offset >= bytes.length) return false
112
+ const marker = bytes[offset] ?? 0
113
+ offset += 1
114
+ if (inScan && (marker === 0x00 || (marker >= 0xd0 && marker <= 0xd7))) continue
115
+ inScan = false
116
+ if (marker === 0xd9) return sawFrame && sawScan && offset === bytes.length
117
+ if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) continue
118
+ if (offset + 2 > bytes.length) return false
119
+ const length = buffer.readUInt16BE(offset)
120
+ if (length < 2 || offset + length > bytes.length) return false
121
+ if (JPEG_SOF_MARKERS.has(marker)) {
122
+ if (length < 8) return false
123
+ const height = buffer.readUInt16BE(offset + 3)
124
+ const width = buffer.readUInt16BE(offset + 5)
125
+ if (!dimensionsAreSafe(width, height)) return false
126
+ sawFrame = true
127
+ }
128
+ offset += length
129
+ if (marker === 0xda) {
130
+ sawScan = true
131
+ inScan = true
132
+ }
133
+ }
134
+ return false
135
+ }
136
+
137
+ function skipGifSubBlocks(bytes: Uint8Array, start: number): number | null {
138
+ let offset = start
139
+ while (offset < bytes.length) {
140
+ const length = bytes[offset] ?? 0
141
+ offset += 1
142
+ if (length === 0) return offset
143
+ if (offset + length > bytes.length) return null
144
+ offset += length
145
+ }
146
+ return null
147
+ }
148
+
149
+ function isGif(bytes: Uint8Array): boolean {
150
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
151
+ if (bytes.length < 14) return false
152
+ const signature = buffer.toString('ascii', 0, 6)
153
+ if (signature !== 'GIF87a' && signature !== 'GIF89a') return false
154
+ if (!dimensionsAreSafe(buffer.readUInt16LE(6), buffer.readUInt16LE(8))) return false
155
+ let offset = 13
156
+ const packed = bytes[10] ?? 0
157
+ if ((packed & 0x80) !== 0) offset += 3 * 2 ** ((packed & 0x07) + 1)
158
+ if (offset > bytes.length) return false
159
+ let sawImage = false
160
+ while (offset < bytes.length) {
161
+ const marker = bytes[offset] ?? 0
162
+ offset += 1
163
+ if (marker === 0x3b) return sawImage && offset === bytes.length
164
+ if (marker === 0x21) {
165
+ if (offset >= bytes.length) return false
166
+ offset += 1
167
+ const next = skipGifSubBlocks(bytes, offset)
168
+ if (next === null) return false
169
+ offset = next
170
+ continue
171
+ }
172
+ if (marker !== 0x2c || offset + 9 > bytes.length) return false
173
+ const width = buffer.readUInt16LE(offset + 4)
174
+ const height = buffer.readUInt16LE(offset + 6)
175
+ if (!dimensionsAreSafe(width, height)) return false
176
+ const imagePacked = bytes[offset + 8] ?? 0
177
+ offset += 9
178
+ if ((imagePacked & 0x80) !== 0) offset += 3 * 2 ** ((imagePacked & 0x07) + 1)
179
+ if (offset >= bytes.length) return false
180
+ offset += 1
181
+ const next = skipGifSubBlocks(bytes, offset)
182
+ if (next === null) return false
183
+ offset = next
184
+ sawImage = true
185
+ }
186
+ return false
187
+ }
188
+
189
+ function readUInt24LE(bytes: Uint8Array, offset: number): number {
190
+ return (bytes[offset] ?? 0) | ((bytes[offset + 1] ?? 0) << 8) | ((bytes[offset + 2] ?? 0) << 16)
191
+ }
192
+
193
+ function webpDimensions(type: string, data: Uint8Array): readonly [number, number] | null {
194
+ const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength)
195
+ if (type === 'VP8X') {
196
+ if (data.length < 10) return null
197
+ return [readUInt24LE(data, 4) + 1, readUInt24LE(data, 7) + 1]
198
+ }
199
+ if (type === 'VP8L') {
200
+ if (data.length < 5 || data[0] !== 0x2f) return null
201
+ const bits = buffer.readUInt32LE(1)
202
+ return [(bits & 0x3fff) + 1, ((bits >>> 14) & 0x3fff) + 1]
203
+ }
204
+ if (type === 'VP8 ') {
205
+ if (data.length < 10 || data[3] !== 0x9d || data[4] !== 0x01 || data[5] !== 0x2a) {
206
+ return null
207
+ }
208
+ return [buffer.readUInt16LE(6) & 0x3fff, buffer.readUInt16LE(8) & 0x3fff]
209
+ }
210
+ return null
211
+ }
212
+
213
+ function isWebp(bytes: Uint8Array): boolean {
214
+ const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
215
+ if (
216
+ bytes.length < 20 ||
217
+ buffer.toString('ascii', 0, 4) !== 'RIFF' ||
218
+ buffer.readUInt32LE(4) + 8 !== bytes.length ||
219
+ buffer.toString('ascii', 8, 12) !== 'WEBP'
220
+ ) {
221
+ return false
222
+ }
223
+ let offset = 12
224
+ let dimensions: readonly [number, number] | null = null
225
+ let sawImagePayload = false
226
+ while (offset + 8 <= bytes.length) {
227
+ const type = buffer.toString('ascii', offset, offset + 4)
228
+ const length = buffer.readUInt32LE(offset + 4)
229
+ const dataStart = offset + 8
230
+ const dataEnd = dataStart + length
231
+ if (dataEnd < dataStart || dataEnd > bytes.length) return false
232
+ const current = webpDimensions(type, bytes.subarray(dataStart, dataEnd))
233
+ if (current && dimensions === null) dimensions = current
234
+ if (type === 'VP8 ' || type === 'VP8L') sawImagePayload = current !== null
235
+ offset = dataEnd + (length & 1)
236
+ }
237
+ return (
238
+ offset === bytes.length &&
239
+ dimensions !== null &&
240
+ sawImagePayload &&
241
+ dimensionsAreSafe(dimensions[0], dimensions[1])
242
+ )
243
+ }
244
+
245
+ function imageIsAdmitted(block: MCPImageBlock): boolean {
246
+ if (!isImageMediaType(block.mimeType)) return false
247
+ const bytes = decodeCanonicalBase64(block.data)
248
+ if (!bytes) return false
249
+ switch (block.mimeType) {
250
+ case 'image/png':
251
+ return isPng(bytes)
252
+ case 'image/jpeg':
253
+ return isJpeg(bytes)
254
+ case 'image/gif':
255
+ return isGif(bytes)
256
+ case 'image/webp':
257
+ return isWebp(bytes)
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Admit an MCP image batch atomically before any member becomes model input.
263
+ *
264
+ * A remote server controls the declared MIME and bytes. Canonical base64 is
265
+ * only an encoding property; the decoded value must also be a complete,
266
+ * bounded raster container whose format agrees with that declaration. One bad
267
+ * member withholds the complete batch so a model is never shown a partial
268
+ * result while the host retains every raw block for inspection.
269
+ */
270
+ export function admitMcpImageBatch(images: readonly MCPImageBlock[]): boolean {
271
+ return images.length === 0 || images.every(imageIsAdmitted)
272
+ }
@@ -95,6 +95,10 @@ export class StdioTransport implements MCPTransport {
95
95
  private closeHandlers: Array<() => void> = []
96
96
  private errorHandlers: Array<(error: Error) => void> = []
97
97
  private connected = false
98
+ /** The response channel ended for this process generation. */
99
+ private retired = false
100
+ /** Prevents a late event from an old child retiring its replacement. */
101
+ private generation = 0
98
102
  /** True between kill and the process's own `close` event. */
99
103
  private exitPending = false
100
104
  private buffer = ''
@@ -109,38 +113,47 @@ export class StdioTransport implements MCPTransport {
109
113
 
110
114
  async connect(): Promise<void> {
111
115
  if (this.connected) return
116
+ // A protocol channel can end while its process stays alive. Reconnect
117
+ // must reap that old owner before publishing a new generation; otherwise
118
+ // both children remain live and teardown owns only the newest one.
119
+ if (this.process) await this.close()
120
+ const generation = ++this.generation
121
+ this.retired = false
122
+ this.buffer = ''
112
123
 
113
- this.process = spawn(this.config.command, this.config.args ?? [], {
124
+ const child = spawn(this.config.command, this.config.args ?? [], {
114
125
  env: buildChildEnv(this.config),
115
126
  cwd: this.config.cwd,
116
127
  stdio: ['pipe', 'pipe', 'pipe'],
117
128
  })
129
+ this.process = child
118
130
 
119
- this.process.stdout?.on('data', (chunk: Buffer) => {
131
+ child.stdout?.on('data', (chunk: Buffer) => {
132
+ if (generation !== this.generation || this.retired) return
120
133
  this.buffer += chunk.toString('utf-8')
121
134
  this.processBuffer()
122
135
  })
136
+ child.stdout?.once('end', () => this.retire(generation, 'close'))
137
+ child.stdout?.once('close', () => this.retire(generation, 'close'))
138
+ child.stdout?.once('error', (err) => this.retire(generation, 'error', err))
123
139
 
124
- this.process.stderr?.on('data', (chunk: Buffer) => {
140
+ child.stderr?.on('data', (chunk: Buffer) => {
125
141
  this.log.warn('MCP server stderr', {
126
142
  'namzu.mcp.stderr': chunk.toString('utf-8').trim(),
127
143
  })
128
144
  })
129
145
 
130
- this.process.on('close', (code) => {
131
- this.connected = false
132
- this.log.info('MCP server process exited', { 'namzu.mcp.exit_code': code })
133
- for (const handler of this.closeHandlers) handler()
134
- // After the notification, not before it: this event is what tells
135
- // `MCPClient` the session ended.
146
+ child.stdin?.once('close', () => this.retire(generation, 'close'))
147
+ child.stdin?.once('error', (err) => this.retire(generation, 'error', err))
148
+
149
+ child.on('close', (code) => {
150
+ if (this.process === child) this.process = null
136
151
  this.exitPending = false
137
- this.clearHandlers()
152
+ this.log.info('MCP server process exited', { 'namzu.mcp.exit_code': code })
153
+ this.retire(generation, 'close')
138
154
  })
139
155
 
140
- this.process.on('error', (err) => {
141
- this.connected = false
142
- for (const handler of this.errorHandlers) handler(err)
143
- })
156
+ child.on('error', (err) => this.retire(generation, 'error', err))
144
157
 
145
158
  this.connected = true
146
159
  this.log.info('StdioTransport connected', {
@@ -175,7 +188,10 @@ export class StdioTransport implements MCPTransport {
175
188
  // so both settle this, and two timers make a hang impossible: the
176
189
  // first escalates to SIGKILL for a child ignoring SIGTERM, the second
177
190
  // gives up waiting. Neither holds the event loop open.
178
- if (child.pid === undefined) return
191
+ if (child.pid === undefined) {
192
+ this.exitPending = false
193
+ return
194
+ }
179
195
  await new Promise<void>((resolve) => {
180
196
  let settled = false
181
197
  const finish = (): void => {
@@ -185,18 +201,20 @@ export class StdioTransport implements MCPTransport {
185
201
  clearTimeout(giveUp)
186
202
  resolve()
187
203
  }
188
- child.once('exit', finish)
204
+ child.once('close', finish)
189
205
  child.once('error', finish)
190
- child.kill('SIGTERM')
206
+ if (child.exitCode === null && child.signalCode === null) {
207
+ child.kill('SIGTERM')
208
+ }
191
209
  const escalate = setTimeout(() => child.kill('SIGKILL'), TERMINATE_GRACE_MS)
192
210
  const giveUp = setTimeout(finish, TERMINATE_GRACE_MS * 2)
193
211
  escalate.unref?.()
194
212
  giveUp.unref?.()
195
213
  })
196
- // The handlers deliberately survive this call. `process.on('close')`
197
- // fires them when the process actually exits, and dropping them here
198
- // would leave `MCPClient` believing it is still connected — so its next
199
- // `connect()` would be refused with "already connected".
214
+ this.exitPending = false
215
+ // The active generation's terminal stream/process event owns handler
216
+ // retirement. A reconnect can register its handlers before `connect()`
217
+ // reaps an already-retired child, so `close()` must not clear them here.
200
218
  }
201
219
 
202
220
  /**
@@ -214,6 +232,30 @@ export class StdioTransport implements MCPTransport {
214
232
  this.errorHandlers = []
215
233
  }
216
234
 
235
+ /**
236
+ * Publish one terminal result for the current response channel.
237
+ *
238
+ * A child may close stdout and keep running. That is just as terminal for
239
+ * JSON-RPC as process exit: no pending or future request can receive a
240
+ * response. Keeping the child handle is intentional; `close()` still owns
241
+ * and reaps that process after the protocol session has been retired.
242
+ */
243
+ private retire(generation: number, kind: 'close' | 'error', error?: Error): void {
244
+ if (generation !== this.generation || this.retired) return
245
+ this.retired = true
246
+ this.connected = false
247
+ this.buffer = ''
248
+ const closeHandlers = this.closeHandlers
249
+ const errorHandlers = this.errorHandlers
250
+ this.clearHandlers()
251
+ if (kind === 'error') {
252
+ const reason = error ?? new Error('MCP stdio transport failed')
253
+ for (const handler of errorHandlers) handler(reason)
254
+ return
255
+ }
256
+ for (const handler of closeHandlers) handler()
257
+ }
258
+
217
259
  async send(message: MCPJsonRpcMessage, options?: MCPTransportSendOptions): Promise<void> {
218
260
  options?.signal?.throwIfAborted()
219
261
  if (!this.process?.stdin?.writable) {
@@ -7,6 +7,7 @@ import type {
7
7
  import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js'
8
8
  import { type Logger, resolveLogger } from '../../utils/logger.js'
9
9
  import { ConnectorHttpOperation, validateConnectorTimeoutMs } from '../http-operation.js'
10
+ import { refuseMcpHttpRedirect } from './http-redirect.js'
10
11
 
11
12
  const DEFAULT_TIMEOUT_MS = 30_000
12
13
 
@@ -83,10 +84,12 @@ export class StreamableHttpTransport implements MCPTransport {
83
84
  method: 'POST',
84
85
  headers: this.buildHeaders(),
85
86
  body: JSON.stringify(message),
87
+ redirect: 'manual',
86
88
  signal: operation.signal,
87
89
  }),
88
90
  )
89
91
 
92
+ refuseMcpHttpRedirect(response, message.method)
90
93
  if (!response.ok) {
91
94
  throw new Error(`StreamableHttpTransport: HTTP ${response.status}: ${response.statusText}`)
92
95
  }
@@ -1,4 +1,4 @@
1
- // Patterns the verification gate's `deny_dangerous_patterns` rule
1
+ // Patterns the authorization gate's `deny_dangerous_patterns` rule
2
2
  // matches against the JSON-serialised tool input. The list is
3
3
  // intentionally short and high-signal: the goal is to catch the
4
4
  // canonical "I will brick the host" mistakes (filesystem wipes,
@@ -143,6 +143,11 @@ export function deriveSupervisorOptions(
143
143
  maxRequestRichContentBytes: manifest.config.maxRequestRichContentBytes,
144
144
  }
145
145
  : {}),
146
+ ...(manifest.config.attachmentResolveTimeoutMs !== undefined
147
+ ? {
148
+ attachmentResolveTimeoutMs: manifest.config.attachmentResolveTimeoutMs,
149
+ }
150
+ : {}),
146
151
  ...(manifest.config.temperature !== undefined
147
152
  ? { temperature: manifest.config.temperature }
148
153
  : {}),
@@ -72,6 +72,11 @@ export function deriveRunOptions(
72
72
  maxRequestRichContentBytes: manifest.config.maxRequestRichContentBytes,
73
73
  }
74
74
  : {}),
75
+ ...(manifest.config.attachmentResolveTimeoutMs !== undefined
76
+ ? {
77
+ attachmentResolveTimeoutMs: manifest.config.attachmentResolveTimeoutMs,
78
+ }
79
+ : {}),
75
80
  ...(input.identity ?? {}),
76
81
  ...(input.overrides ?? {}),
77
82
  }
@@ -187,6 +187,7 @@ function readConfig(
187
187
  take('timeoutMs', isNumber, 'finite number')
188
188
  take('streamIdleTimeoutMs', isNumber, 'finite number')
189
189
  take('maxRequestRichContentBytes', isNumber, 'finite number')
190
+ take('attachmentResolveTimeoutMs', isNumber, 'finite number')
190
191
 
191
192
  // `metadata` is typed `Record<string, string>` and was admitted on
192
193
  // `typeof === 'object'` alone — which an array also satisfies, and which
@@ -170,6 +170,8 @@ export interface DirectoryConfig {
170
170
  readonly streamIdleTimeoutMs?: number
171
171
  /** Accumulated inline image/document bytes per provider request; `0` disables. */
172
172
  readonly maxRequestRichContentBytes?: number
173
+ /** Maximum stored-attachment materialization time; `0` disables the bound. */
174
+ readonly attachmentResolveTimeoutMs?: number
173
175
  /** Names the agent in traces and events when declared. */
174
176
  readonly name?: string
175
177
  /** Free-form labels for an inspector. Never interpreted by this package. */