@stigmer/runner 3.10.0 → 3.11.1-dev.20260812192248

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/README.md +14 -1
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/activities/call-llm.js +9 -10
  4. package/dist/activities/call-llm.js.map +1 -1
  5. package/dist/activities/classify-tool-approvals.d.ts +2 -1
  6. package/dist/activities/classify-tool-approvals.js +28 -2
  7. package/dist/activities/classify-tool-approvals.js.map +1 -1
  8. package/dist/activities/discover-mcp-server.d.ts +39 -0
  9. package/dist/activities/discover-mcp-server.js +165 -28
  10. package/dist/activities/discover-mcp-server.js.map +1 -1
  11. package/dist/activities/emit-event.d.ts +14 -2
  12. package/dist/activities/emit-event.js +52 -17
  13. package/dist/activities/emit-event.js.map +1 -1
  14. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +8 -0
  15. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
  17. package/dist/activities/execute-cursor/approval-state.d.ts +28 -2
  18. package/dist/activities/execute-cursor/approval-state.js +7 -1
  19. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  20. package/dist/activities/execute-cursor/attachment-resolver.d.ts +14 -0
  21. package/dist/activities/execute-cursor/attachment-resolver.js +18 -4
  22. package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
  23. package/dist/activities/execute-cursor/blueprint-resolver.d.ts +1 -9
  24. package/dist/activities/execute-cursor/blueprint-resolver.js +6 -22
  25. package/dist/activities/execute-cursor/blueprint-resolver.js.map +1 -1
  26. package/dist/activities/execute-cursor/env-resolver.js +3 -1
  27. package/dist/activities/execute-cursor/env-resolver.js.map +1 -1
  28. package/dist/activities/execute-cursor/error-classifier.d.ts +59 -3
  29. package/dist/activities/execute-cursor/error-classifier.js +115 -6
  30. package/dist/activities/execute-cursor/error-classifier.js.map +1 -1
  31. package/dist/activities/execute-cursor/extract-structured-output.d.ts +29 -0
  32. package/dist/activities/execute-cursor/extract-structured-output.js +58 -0
  33. package/dist/activities/execute-cursor/extract-structured-output.js.map +1 -0
  34. package/dist/activities/execute-cursor/hook-script.d.ts +14 -3
  35. package/dist/activities/execute-cursor/hook-script.js +72 -10
  36. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  37. package/dist/activities/execute-cursor/index.d.ts +51 -11
  38. package/dist/activities/execute-cursor/index.js +177 -93
  39. package/dist/activities/execute-cursor/index.js.map +1 -1
  40. package/dist/activities/execute-cursor/mcp-resolver.d.ts +24 -1
  41. package/dist/activities/execute-cursor/mcp-resolver.js +5 -2
  42. package/dist/activities/execute-cursor/mcp-resolver.js.map +1 -1
  43. package/dist/activities/execute-cursor/model-pricing-data.d.ts +2 -0
  44. package/dist/activities/execute-cursor/model-pricing-data.js +13 -3
  45. package/dist/activities/execute-cursor/model-pricing-data.js.map +1 -1
  46. package/dist/activities/execute-cursor/prompt-builder.d.ts +51 -4
  47. package/dist/activities/execute-cursor/prompt-builder.js +49 -7
  48. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  49. package/dist/activities/execute-cursor/turn-recovery.d.ts +52 -0
  50. package/dist/activities/execute-cursor/turn-recovery.js +193 -0
  51. package/dist/activities/execute-cursor/turn-recovery.js.map +1 -0
  52. package/dist/activities/execute-cursor/turn-stream.js +4 -1
  53. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  54. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +18 -1
  55. package/dist/activities/execute-deep-agent/attachment-injector.js +68 -23
  56. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  57. package/dist/activities/execute-deep-agent/environment.js +3 -1
  58. package/dist/activities/execute-deep-agent/environment.js.map +1 -1
  59. package/dist/activities/execute-deep-agent/index.js +15 -0
  60. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  61. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +7 -7
  62. package/dist/activities/execute-deep-agent/prompt-builder.js +8 -2
  63. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  64. package/dist/activities/execute-deep-agent/setup.d.ts +10 -0
  65. package/dist/activities/execute-deep-agent/setup.js +65 -24
  66. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/shell-env.d.ts +5 -1
  68. package/dist/activities/execute-deep-agent/shell-env.js +7 -9
  69. package/dist/activities/execute-deep-agent/shell-env.js.map +1 -1
  70. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +18 -1
  71. package/dist/activities/execute-deep-agent/subagent-transformer.js +15 -2
  72. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  73. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +27 -6
  74. package/dist/activities/execute-deep-agent/subagent-wiring.js +26 -5
  75. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  76. package/dist/activities/hydrate-workflow-execution.js +8 -3
  77. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  78. package/dist/activities/run-command.d.ts +5 -2
  79. package/dist/activities/run-command.js +22 -10
  80. package/dist/activities/run-command.js.map +1 -1
  81. package/dist/activities/run-env.d.ts +40 -0
  82. package/dist/activities/run-env.js +66 -0
  83. package/dist/activities/run-env.js.map +1 -0
  84. package/dist/activities/workflow-event-activities.d.ts +28 -10
  85. package/dist/activities/workflow-event-activities.js +87 -58
  86. package/dist/activities/workflow-event-activities.js.map +1 -1
  87. package/dist/bootstrap.js +5 -0
  88. package/dist/bootstrap.js.map +1 -1
  89. package/dist/claimcheck/payload-codec.js +21 -1
  90. package/dist/claimcheck/payload-codec.js.map +1 -1
  91. package/dist/client/stigmer-client.d.ts +83 -21
  92. package/dist/client/stigmer-client.js +106 -29
  93. package/dist/client/stigmer-client.js.map +1 -1
  94. package/dist/encryption/config.d.ts +64 -0
  95. package/dist/encryption/config.js +108 -0
  96. package/dist/encryption/config.js.map +1 -0
  97. package/dist/encryption/index.d.ts +3 -0
  98. package/dist/encryption/index.js +3 -0
  99. package/dist/encryption/index.js.map +1 -0
  100. package/dist/encryption/payload-codec.d.ts +41 -0
  101. package/dist/encryption/payload-codec.js +130 -0
  102. package/dist/encryption/payload-codec.js.map +1 -0
  103. package/dist/middleware/index.d.ts +3 -0
  104. package/dist/middleware/index.js +7 -0
  105. package/dist/middleware/index.js.map +1 -1
  106. package/dist/middleware/path-normalization.d.ts +57 -0
  107. package/dist/middleware/path-normalization.js +109 -0
  108. package/dist/middleware/path-normalization.js.map +1 -0
  109. package/dist/middleware/types.d.ts +8 -0
  110. package/dist/payload-codecs.d.ts +17 -0
  111. package/dist/payload-codecs.js +42 -0
  112. package/dist/payload-codecs.js.map +1 -0
  113. package/dist/preflight.d.ts +31 -0
  114. package/dist/preflight.js +43 -0
  115. package/dist/preflight.js.map +1 -1
  116. package/dist/runner-manager.js +21 -17
  117. package/dist/runner-manager.js.map +1 -1
  118. package/dist/runner.js +11 -18
  119. package/dist/runner.js.map +1 -1
  120. package/dist/shared/approval-policy.d.ts +9 -3
  121. package/dist/shared/approval-policy.js +15 -6
  122. package/dist/shared/approval-policy.js.map +1 -1
  123. package/dist/shared/artifact-storage.d.ts +23 -1
  124. package/dist/shared/artifact-storage.js +51 -10
  125. package/dist/shared/artifact-storage.js.map +1 -1
  126. package/dist/shared/attachment-naming.d.ts +53 -0
  127. package/dist/shared/attachment-naming.js +59 -0
  128. package/dist/shared/attachment-naming.js.map +1 -0
  129. package/dist/shared/caller-identity.d.ts +23 -2
  130. package/dist/shared/caller-identity.js +36 -5
  131. package/dist/shared/caller-identity.js.map +1 -1
  132. package/dist/shared/channel-attachment.js +1 -0
  133. package/dist/shared/channel-attachment.js.map +1 -1
  134. package/dist/shared/checkpointer/http-saver.d.ts +26 -1
  135. package/dist/shared/checkpointer/http-saver.js +54 -8
  136. package/dist/shared/checkpointer/http-saver.js.map +1 -1
  137. package/dist/shared/conversation-attachment.js +1 -0
  138. package/dist/shared/conversation-attachment.js.map +1 -1
  139. package/dist/shared/conversation-catchup.d.ts +9 -2
  140. package/dist/shared/conversation-catchup.js +39 -6
  141. package/dist/shared/conversation-catchup.js.map +1 -1
  142. package/dist/shared/datastore-attachment.d.ts +50 -7
  143. package/dist/shared/datastore-attachment.js +93 -11
  144. package/dist/shared/datastore-attachment.js.map +1 -1
  145. package/dist/shared/grpc-retry.d.ts +5 -1
  146. package/dist/shared/grpc-retry.js +5 -1
  147. package/dist/shared/grpc-retry.js.map +1 -1
  148. package/dist/shared/http-retry.d.ts +85 -0
  149. package/dist/shared/http-retry.js +101 -0
  150. package/dist/shared/http-retry.js.map +1 -0
  151. package/dist/shared/llm-backend.d.ts +275 -0
  152. package/dist/shared/llm-backend.js +425 -0
  153. package/dist/shared/llm-backend.js.map +1 -0
  154. package/dist/shared/llm-proxy.d.ts +8 -0
  155. package/dist/shared/llm-proxy.js +15 -0
  156. package/dist/shared/llm-proxy.js.map +1 -1
  157. package/dist/shared/mcp-enabled-tools.d.ts +57 -0
  158. package/dist/shared/mcp-enabled-tools.js +86 -0
  159. package/dist/shared/mcp-enabled-tools.js.map +1 -0
  160. package/dist/shared/mcp-manager.d.ts +11 -5
  161. package/dist/shared/mcp-manager.js +47 -8
  162. package/dist/shared/mcp-manager.js.map +1 -1
  163. package/dist/shared/mcp-resolver.d.ts +39 -2
  164. package/dist/shared/mcp-resolver.js +38 -2
  165. package/dist/shared/mcp-resolver.js.map +1 -1
  166. package/dist/shared/mcp-schema-sanitizer.d.ts +70 -0
  167. package/dist/shared/mcp-schema-sanitizer.js +197 -0
  168. package/dist/shared/mcp-schema-sanitizer.js.map +1 -0
  169. package/dist/shared/model-client.d.ts +18 -5
  170. package/dist/shared/model-client.js +149 -18
  171. package/dist/shared/model-client.js.map +1 -1
  172. package/dist/shared/model-error.js +198 -5
  173. package/dist/shared/model-error.js.map +1 -1
  174. package/dist/shared/model-pricing-data.d.ts +2 -0
  175. package/dist/shared/model-pricing-data.js +13 -3
  176. package/dist/shared/model-pricing-data.js.map +1 -1
  177. package/dist/shared/model-registry.js +3 -2
  178. package/dist/shared/model-registry.js.map +1 -1
  179. package/dist/shared/plan-mode-permissions.d.ts +33 -0
  180. package/dist/shared/plan-mode-permissions.js +35 -0
  181. package/dist/shared/plan-mode-permissions.js.map +1 -0
  182. package/dist/shared/registry-endpoint.d.ts +20 -0
  183. package/dist/shared/registry-endpoint.js +25 -0
  184. package/dist/shared/registry-endpoint.js.map +1 -1
  185. package/dist/shared/runner-credential-keys.d.ts +24 -0
  186. package/dist/shared/runner-credential-keys.js +47 -0
  187. package/dist/shared/runner-credential-keys.js.map +1 -0
  188. package/dist/worker.d.ts +2 -1
  189. package/dist/worker.js +2 -4
  190. package/dist/worker.js.map +1 -1
  191. package/dist/workflow-engine/resolve.d.ts +19 -1
  192. package/dist/workflow-engine/resolve.js +37 -2
  193. package/dist/workflow-engine/resolve.js.map +1 -1
  194. package/dist/workflow-engine/types.d.ts +18 -0
  195. package/dist/workflow-engine/types.js.map +1 -1
  196. package/dist/workflows/call-agent-orchestrator.d.ts +9 -0
  197. package/dist/workflows/call-agent-orchestrator.js +1 -0
  198. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  199. package/dist/workflows/connect-mcp-server.js +9 -0
  200. package/dist/workflows/connect-mcp-server.js.map +1 -1
  201. package/dist/workflows/engine-core.js +23 -2
  202. package/dist/workflows/engine-core.js.map +1 -1
  203. package/dist/workflows/execute-from-execution.d.ts +1 -1
  204. package/dist/workflows/execute-from-execution.js +11 -1
  205. package/dist/workflows/execute-from-execution.js.map +1 -1
  206. package/dist/workflows/types.d.ts +8 -0
  207. package/package.json +9 -4
  208. package/src/__test-utils__/__tests__/vitest-global-setup.test.ts +37 -0
  209. package/src/__test-utils__/vitest-global-setup.ts +50 -0
  210. package/src/__tests__/bootstrap.test.ts +43 -0
  211. package/src/__tests__/claimcheck-codec.test.ts +36 -0
  212. package/src/__tests__/encryption-codec.test.ts +287 -0
  213. package/src/__tests__/fixtures/encrypted-payload-fixture.json +15 -0
  214. package/src/__tests__/history-encryption-e2e.test.ts +243 -0
  215. package/src/__tests__/preflight.test.ts +65 -2
  216. package/src/activities/__tests__/call-llm.test.ts +75 -0
  217. package/src/activities/__tests__/classify-tool-approvals.test.ts +117 -1
  218. package/src/activities/__tests__/discover-mcp-server.hang.test.ts +103 -0
  219. package/src/activities/__tests__/discover-mcp-server.test.ts +242 -1
  220. package/src/activities/__tests__/error-classifier.test.ts +94 -2
  221. package/src/activities/__tests__/run-command.test.ts +221 -0
  222. package/src/activities/__tests__/workflow-event-activities.test.ts +107 -8
  223. package/src/activities/call-llm.ts +9 -16
  224. package/src/activities/classify-tool-approvals.ts +34 -4
  225. package/src/activities/discover-mcp-server.ts +203 -31
  226. package/src/activities/emit-event.ts +65 -21
  227. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +9 -0
  228. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +14 -0
  229. package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +53 -0
  230. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +151 -16
  231. package/src/activities/execute-cursor/__tests__/error-classifier-extraction.test.ts +208 -0
  232. package/src/activities/execute-cursor/__tests__/extract-structured-output.test.ts +120 -0
  233. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +93 -0
  234. package/src/activities/execute-cursor/__tests__/mcp-resolver.test.ts +125 -0
  235. package/src/activities/execute-cursor/__tests__/model-pricing.test.ts +62 -1
  236. package/src/activities/execute-cursor/__tests__/prompt-builder-delegation.test.ts +1 -1
  237. package/src/activities/execute-cursor/__tests__/turn-recovery.test.ts +160 -0
  238. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +13 -0
  239. package/src/activities/execute-cursor/approval-state.ts +30 -1
  240. package/src/activities/execute-cursor/attachment-resolver.ts +31 -3
  241. package/src/activities/execute-cursor/blueprint-resolver.ts +7 -27
  242. package/src/activities/execute-cursor/env-resolver.ts +3 -1
  243. package/src/activities/execute-cursor/error-classifier.ts +131 -7
  244. package/src/activities/execute-cursor/extract-structured-output.ts +72 -0
  245. package/src/activities/execute-cursor/hook-script.ts +74 -10
  246. package/src/activities/execute-cursor/index.ts +209 -109
  247. package/src/activities/execute-cursor/mcp-resolver.ts +36 -2
  248. package/src/activities/execute-cursor/model-pricing-data.ts +22 -3
  249. package/src/activities/execute-cursor/prompt-builder.ts +85 -9
  250. package/src/activities/execute-cursor/turn-recovery.ts +208 -0
  251. package/src/activities/execute-cursor/turn-stream.ts +5 -2
  252. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +110 -8
  253. package/src/activities/execute-deep-agent/__tests__/datastore-degradation.test.ts +104 -0
  254. package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +2 -0
  255. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +1 -0
  256. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +1 -0
  257. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +174 -0
  258. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +34 -5
  259. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +1 -0
  260. package/src/activities/execute-deep-agent/__tests__/shell-env.test.ts +24 -10
  261. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +178 -0
  262. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +12 -7
  263. package/src/activities/execute-deep-agent/attachment-injector.ts +94 -30
  264. package/src/activities/execute-deep-agent/environment.ts +3 -1
  265. package/src/activities/execute-deep-agent/index.ts +20 -0
  266. package/src/activities/execute-deep-agent/prompt-builder.ts +20 -10
  267. package/src/activities/execute-deep-agent/setup.ts +85 -29
  268. package/src/activities/execute-deep-agent/shell-env.ts +8 -9
  269. package/src/activities/execute-deep-agent/subagent-transformer.ts +30 -2
  270. package/src/activities/execute-deep-agent/subagent-wiring.ts +39 -6
  271. package/src/activities/hydrate-workflow-execution.ts +8 -3
  272. package/src/activities/run-command.ts +25 -12
  273. package/src/activities/run-env.ts +79 -0
  274. package/src/activities/workflow-event-activities.ts +96 -69
  275. package/src/bootstrap.ts +5 -0
  276. package/src/claimcheck/payload-codec.ts +33 -1
  277. package/src/client/__tests__/stigmer-client.test.ts +110 -11
  278. package/src/client/stigmer-client.ts +150 -37
  279. package/src/encryption/config.ts +155 -0
  280. package/src/encryption/index.ts +3 -0
  281. package/src/encryption/payload-codec.ts +152 -0
  282. package/src/middleware/__tests__/path-normalization.test.ts +140 -0
  283. package/src/middleware/index.ts +8 -0
  284. package/src/middleware/path-normalization.ts +125 -0
  285. package/src/middleware/types.ts +11 -0
  286. package/src/payload-codecs.ts +62 -0
  287. package/src/preflight.ts +45 -0
  288. package/src/runner-manager.ts +26 -26
  289. package/src/runner.ts +12 -27
  290. package/src/shared/__tests__/approval-policy.test.ts +82 -39
  291. package/src/shared/__tests__/artifact-storage.test.ts +145 -2
  292. package/src/shared/__tests__/attachment-naming.test.ts +159 -0
  293. package/src/shared/__tests__/bedrock-adapter.test.ts +213 -0
  294. package/src/shared/__tests__/bedrock-seam.test.ts +390 -0
  295. package/src/shared/__tests__/caller-identity.test.ts +25 -0
  296. package/src/shared/__tests__/channel-attachment.test.ts +1 -1
  297. package/src/shared/__tests__/connect-backfill.test.ts +1 -0
  298. package/src/shared/__tests__/conversation-attachment.test.ts +1 -1
  299. package/src/shared/__tests__/conversation-catchup.test.ts +24 -0
  300. package/src/shared/__tests__/datastore-attachment.test.ts +129 -1
  301. package/src/shared/__tests__/foundry-adapter.test.ts +276 -0
  302. package/src/shared/__tests__/foundry-seam.test.ts +482 -0
  303. package/src/shared/__tests__/http-retry.test.ts +68 -0
  304. package/src/shared/__tests__/llm-backend.test.ts +616 -0
  305. package/src/shared/__tests__/mcp-enabled-tools.test.ts +86 -0
  306. package/src/shared/__tests__/mcp-manager.test.ts +137 -15
  307. package/src/shared/__tests__/mcp-resolver.test.ts +146 -3
  308. package/src/shared/__tests__/mcp-schema-sanitizer.test.ts +267 -0
  309. package/src/shared/__tests__/model-client.test.ts +200 -0
  310. package/src/shared/__tests__/model-error.test.ts +289 -1
  311. package/src/shared/__tests__/model-pricing.test.ts +62 -2
  312. package/src/shared/__tests__/model-registry.test.ts +22 -0
  313. package/src/shared/__tests__/synthesized-attachment.test.ts +1 -0
  314. package/src/shared/__tests__/vertex-adapter.test.ts +169 -0
  315. package/src/shared/__tests__/vertex-seam.test.ts +295 -0
  316. package/src/shared/approval-policy.ts +14 -7
  317. package/src/shared/artifact-storage.ts +75 -10
  318. package/src/shared/attachment-naming.ts +78 -0
  319. package/src/shared/caller-identity.ts +40 -5
  320. package/src/shared/channel-attachment.ts +1 -0
  321. package/src/shared/checkpointer/__tests__/http-saver.test.ts +196 -1
  322. package/src/shared/checkpointer/http-saver.ts +71 -8
  323. package/src/shared/conversation-attachment.ts +1 -0
  324. package/src/shared/conversation-catchup.ts +39 -6
  325. package/src/shared/datastore-attachment.ts +106 -11
  326. package/src/shared/grpc-retry.ts +5 -1
  327. package/src/shared/http-retry.ts +139 -0
  328. package/src/shared/llm-backend.ts +544 -0
  329. package/src/shared/llm-proxy.ts +15 -0
  330. package/src/shared/mcp-enabled-tools.ts +105 -0
  331. package/src/shared/mcp-manager.ts +56 -8
  332. package/src/shared/mcp-resolver.ts +73 -2
  333. package/src/shared/mcp-schema-sanitizer.ts +224 -0
  334. package/src/shared/model-client.ts +179 -19
  335. package/src/shared/model-error.ts +222 -4
  336. package/src/shared/model-pricing-data.ts +22 -3
  337. package/src/shared/model-registry.ts +7 -2
  338. package/src/shared/plan-mode-permissions.ts +37 -0
  339. package/src/shared/registry-endpoint.ts +27 -0
  340. package/src/shared/runner-credential-keys.ts +46 -0
  341. package/src/worker.ts +4 -5
  342. package/src/workflow-engine/__tests__/tasks/emit-event.test.ts +127 -49
  343. package/src/workflow-engine/resolve.ts +48 -2
  344. package/src/workflow-engine/types.ts +18 -0
  345. package/src/workflows/__tests__/connect-mcp-server.test.ts +25 -0
  346. package/src/workflows/__tests__/execute-serverless-workflow.test.ts +68 -2
  347. package/src/workflows/call-agent-orchestrator.ts +10 -0
  348. package/src/workflows/connect-mcp-server.ts +9 -0
  349. package/src/workflows/engine-core.ts +23 -2
  350. package/src/workflows/execute-from-execution.ts +12 -2
  351. package/src/workflows/types.ts +8 -0
  352. package/dist/activities/execute-cursor/mcp-config.d.ts +0 -30
  353. package/dist/activities/execute-cursor/mcp-config.js +0 -39
  354. package/dist/activities/execute-cursor/mcp-config.js.map +0 -1
  355. package/src/activities/execute-cursor/mcp-config.ts +0 -66
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Adapter test for the vertex backend branch of buildChatModel — the
3
+ * complement of vertex-seam.test.ts.
4
+ *
5
+ * The seam test pins the REAL AnthropicVertex client's wire behavior under
6
+ * a hand-built ChatAnthropic. This file pins OUR production wiring: a real
7
+ * `buildChatModel` and a real `ChatAnthropic` drive a mocked
8
+ * `@anthropic-ai/vertex-sdk`, so LangChain's lazy `createClient` invocation
9
+ * path (client constructed on first request, once per cached client) is
10
+ * exercised exactly as production does it. Together the two files cover the
11
+ * whole chain without any test-only injection seam in production code.
12
+ *
13
+ * Pinned here:
14
+ * - the factory forwards LangChain's `maxRetries: 0` to the client (a
15
+ * factory that drops it nests SDK retries inside LangChain's own loop),
16
+ * - the factory forwards the request timeout (clientOptions.timeout ->
17
+ * factory options -> SDK constructor; how STIGMER_LLM_REQUEST_TIMEOUT_MS
18
+ * bounds this backend),
19
+ * - construction and invocation succeed with NO ANTHROPIC_API_KEY (Vertex
20
+ * auth is Google's, and ChatAnthropic waives the key when createClient
21
+ * is provided),
22
+ * - the translated `@date` id is what crosses the wire while the returned
23
+ * apiModelId stays canonical (the canonical-id invariant),
24
+ * - usage_metadata survives the adapter (what billing reads).
25
+ */
26
+
27
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
28
+ import { HumanMessage, AIMessage } from "@langchain/core/messages";
29
+
30
+ const { vertexCtorArgs, createRequests } = vi.hoisted(() => ({
31
+ vertexCtorArgs: [] as Array<{ maxRetries?: number; timeout?: number }>,
32
+ createRequests: [] as Array<Record<string, unknown>>,
33
+ }));
34
+
35
+ vi.mock("@anthropic-ai/vertex-sdk", () => ({
36
+ AnthropicVertex: class {
37
+ readonly maxRetries: number | undefined;
38
+ readonly messages = {
39
+ create: async (request: Record<string, unknown>) => {
40
+ createRequests.push(request);
41
+ return {
42
+ id: "msg_vertex_adapter_01",
43
+ type: "message",
44
+ role: "assistant",
45
+ model: request.model,
46
+ content: [{ type: "text", text: "Namaste from Vertex." }],
47
+ stop_reason: "end_turn",
48
+ stop_sequence: null,
49
+ usage: { input_tokens: 7, output_tokens: 5 },
50
+ };
51
+ },
52
+ };
53
+
54
+ constructor(opts: { maxRetries?: number; timeout?: number }) {
55
+ vertexCtorArgs.push(opts);
56
+ this.maxRetries = opts.maxRetries;
57
+ }
58
+ },
59
+ }));
60
+
61
+ import { ChatAnthropic } from "@langchain/anthropic";
62
+
63
+ import { buildChatModel } from "../model-client.js";
64
+ import { _resetRegistryCache } from "../model-registry.js";
65
+ import { toVertexModelId } from "../llm-backend.js";
66
+
67
+ describe("buildChatModel vertex adapter", () => {
68
+ const savedEnv = { ...process.env };
69
+
70
+ beforeEach(() => {
71
+ _resetRegistryCache();
72
+ vertexCtorArgs.length = 0;
73
+ createRequests.length = 0;
74
+ delete process.env.ANTHROPIC_API_KEY;
75
+ process.env.STIGMER_ANTHROPIC_BACKEND = "vertex";
76
+ process.env.CLOUD_ML_REGION = "asia-south1";
77
+ // Registry offline → resolveToApiModelId passes the id through, keeping
78
+ // this test free of registry fixtures (that path has its own tests).
79
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("registry offline"));
80
+ });
81
+
82
+ afterEach(() => {
83
+ _resetRegistryCache();
84
+ vi.restoreAllMocks();
85
+ process.env = { ...savedEnv };
86
+ });
87
+
88
+ it("drives the mocked client through the real createClient path with the invariants intact", async () => {
89
+ const { model, apiModelId } = await buildChatModel({
90
+ modelName: "claude-sonnet-4-5-20250929",
91
+ maxTokens: 1024,
92
+ });
93
+
94
+ // Canonical id returned; the client is not constructed yet (lazy factory).
95
+ expect(apiModelId).toBe("claude-sonnet-4-5-20250929");
96
+ expect(vertexCtorArgs).toHaveLength(0);
97
+
98
+ const result = await model.invoke([new HumanMessage("Weather in Chennai?")]);
99
+
100
+ // First request constructed exactly one (batch) client, with LangChain's
101
+ // maxRetries: 0 honored — LangChain owns retrying.
102
+ expect(vertexCtorArgs).toHaveLength(1);
103
+ expect(vertexCtorArgs[0].maxRetries).toBe(0);
104
+
105
+ // The wire carries the Vertex-translated id; billing already got the
106
+ // canonical one above.
107
+ expect(createRequests).toHaveLength(1);
108
+ expect(createRequests[0].model).toBe("claude-sonnet-4-5@20250929");
109
+ expect(createRequests[0].max_tokens).toBe(1024);
110
+
111
+ // Response and usage flow back through LangChain untouched.
112
+ expect(result).toBeInstanceOf(AIMessage);
113
+ expect(result.text).toBe("Namaste from Vertex.");
114
+ expect((result as AIMessage).usage_metadata).toMatchObject({
115
+ input_tokens: 7,
116
+ output_tokens: 5,
117
+ total_tokens: 12,
118
+ });
119
+ });
120
+
121
+ it("translated ids inherit the same default maxTokens as canonical ids", () => {
122
+ // setup.ts deliberately omits maxTokens, so the LangChain per-model
123
+ // default applies. That default prefix-matches the model string — and the
124
+ // vertex branch hands ChatAnthropic the TRANSLATED string. This pins the
125
+ // equality the zero-behavior-change criterion rests on (verified 16384 ==
126
+ // 16384 for 4.5-generation ids at @langchain/anthropic 1.4.0); a future
127
+ // bump that diverges the two forms fails here instead of silently capping
128
+ // vertex deployments differently from public ones.
129
+ for (const canonical of [
130
+ "claude-sonnet-4-5-20250929",
131
+ "claude-haiku-4-5-20251001",
132
+ "claude-sonnet-4-6",
133
+ ]) {
134
+ const publicModel = new ChatAnthropic({ model: canonical, apiKey: "probe" });
135
+ const vertexModel = new ChatAnthropic({ model: toVertexModelId(canonical), apiKey: "probe" });
136
+ expect(vertexModel.maxTokens, canonical).toBe(publicModel.maxTokens);
137
+ }
138
+ });
139
+
140
+ it("forwards the request timeout to the SDK client (STIGMER_LLM_REQUEST_TIMEOUT_MS path)", async () => {
141
+ // The timeout rides clientOptions -> factory options -> SDK constructor.
142
+ // A factory that drops it silently unbounds the operator's timeout on
143
+ // this backend (the regression T06 repaired for the public path).
144
+ const { model } = await buildChatModel({
145
+ modelName: "claude-sonnet-4-6",
146
+ maxTokens: 256,
147
+ timeoutMs: 5000,
148
+ });
149
+ await model.invoke([new HumanMessage("hi")]);
150
+
151
+ expect(vertexCtorArgs).toHaveLength(1);
152
+ expect(vertexCtorArgs[0].timeout).toBe(5000);
153
+ expect(vertexCtorArgs[0].maxRetries).toBe(0);
154
+ });
155
+
156
+ it("constructs and invokes with no ANTHROPIC_API_KEY anywhere in the environment", async () => {
157
+ expect(process.env.ANTHROPIC_API_KEY).toBeUndefined();
158
+
159
+ const { model } = await buildChatModel({
160
+ modelName: "claude-sonnet-4-6",
161
+ maxTokens: 256,
162
+ });
163
+ const result = await model.invoke([new HumanMessage("hi")]);
164
+
165
+ expect(result).toBeInstanceOf(AIMessage);
166
+ // Dateless 4.6-generation id crossed the wire untranslated.
167
+ expect(createRequests.at(-1)?.model).toBe("claude-sonnet-4-6");
168
+ });
169
+ });
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Characterization test for the ChatAnthropic `createClient` -> AnthropicVertex
3
+ * seam — the integration T02's vertex backend adapter will be built on.
4
+ *
5
+ * This is NOT a unit test of our code (there is no production vertex code
6
+ * yet). It pins the exact cross-package behavior production will rely on:
7
+ * the REAL `ChatAnthropic` (@langchain/anthropic, bundling @anthropic-ai/sdk
8
+ * 0.95.x) driving the REAL `AnthropicVertex` client (@anthropic-ai/vertex-sdk,
9
+ * bundling its own nested @anthropic-ai/sdk >=0.115). If a future bump of
10
+ * either side changes request shaping, streaming event handling, tool-call
11
+ * assembly, or usage accounting across this seam, this suite fails in CI
12
+ * instead of production. See scripts/check-langchain-deps.sh for why two
13
+ * @anthropic-ai/sdk copies coexist and when they collapse to one.
14
+ *
15
+ * Determinism: zero credentials, zero network. Google auth is bypassed by
16
+ * injecting a fake `authClient` (the SDK's supported constructor option —
17
+ * `accessToken` alone does NOT skip the auth client, adaptRequest always
18
+ * awaits it), and transport is a recording `fetch` injected through the
19
+ * SDK's own `fetch` option. No global patching, no SDK mocks.
20
+ */
21
+
22
+ import { describe, it, expect } from "vitest";
23
+ import { ChatAnthropic } from "@langchain/anthropic";
24
+ import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
25
+ import type { AuthClient } from "google-auth-library";
26
+ import { HumanMessage, AIMessage, AIMessageChunk } from "@langchain/core/messages";
27
+
28
+ // ─── Fixtures ────────────────────────────────────────────────────────────────
29
+
30
+ /** Dated pre-4.6 id in Vertex's `@date` form (see llm-backend.ts translation). */
31
+ const VERTEX_MODEL_ID = "claude-sonnet-4-5@20250929";
32
+ const PROJECT_ID = "test-project";
33
+ const REGION = "asia-south1";
34
+
35
+ const WEATHER_TOOL = {
36
+ name: "get_weather",
37
+ description: "Get the current weather for a city.",
38
+ input_schema: {
39
+ type: "object" as const,
40
+ properties: { city: { type: "string" } },
41
+ required: ["city"],
42
+ },
43
+ };
44
+
45
+ /** Non-streaming (`:rawPredict`) response: text + tool_use + usage. */
46
+ const MESSAGE_RESPONSE = {
47
+ id: "msg_vertex_test_01",
48
+ type: "message",
49
+ role: "assistant",
50
+ model: VERTEX_MODEL_ID,
51
+ content: [
52
+ { type: "text", text: "I'll check the weather." },
53
+ { type: "tool_use", id: "toolu_test_01", name: "get_weather", input: { city: "Chennai" } },
54
+ ],
55
+ stop_reason: "tool_use",
56
+ stop_sequence: null,
57
+ usage: { input_tokens: 25, output_tokens: 17 },
58
+ };
59
+
60
+ /**
61
+ * Streaming (`:streamRawPredict`) response as Anthropic SSE: a text block,
62
+ * a tool_use block assembled from input_json_delta, then cumulative usage in
63
+ * message_delta — the exact event grammar the nested SDK parses.
64
+ */
65
+ const SSE_EVENTS: ReadonlyArray<[string, object]> = [
66
+ ["message_start", {
67
+ type: "message_start",
68
+ message: {
69
+ id: "msg_vertex_test_02", type: "message", role: "assistant",
70
+ model: VERTEX_MODEL_ID, content: [], stop_reason: null, stop_sequence: null,
71
+ usage: { input_tokens: 25, output_tokens: 1 },
72
+ },
73
+ }],
74
+ ["content_block_start", { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }],
75
+ ["content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "I'll check the weather." } }],
76
+ ["content_block_stop", { type: "content_block_stop", index: 0 }],
77
+ ["content_block_start", { type: "content_block_start", index: 1, content_block: { type: "tool_use", id: "toolu_test_02", name: "get_weather", input: {} } }],
78
+ ["content_block_delta", { type: "content_block_delta", index: 1, delta: { type: "input_json_delta", partial_json: '{"city":"Chennai"}' } }],
79
+ ["content_block_stop", { type: "content_block_stop", index: 1 }],
80
+ ["message_delta", { type: "message_delta", delta: { stop_reason: "tool_use", stop_sequence: null }, usage: { output_tokens: 17 } }],
81
+ ["message_stop", { type: "message_stop" }],
82
+ ];
83
+
84
+ function sseBody(): string {
85
+ return SSE_EVENTS
86
+ .map(([event, data]) => `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`)
87
+ .join("");
88
+ }
89
+
90
+ // ─── Harness ─────────────────────────────────────────────────────────────────
91
+
92
+ interface RecordedRequest {
93
+ url: string;
94
+ method: string;
95
+ headers: Record<string, string>;
96
+ body: Record<string, unknown>;
97
+ }
98
+
99
+ interface SeamHarness {
100
+ model: ChatAnthropic;
101
+ requests: RecordedRequest[];
102
+ /** Options ChatAnthropic passed to the createClient factory. */
103
+ factoryOptions: Array<{ maxRetries?: number }>;
104
+ /** The AnthropicVertex instances the factory constructed. */
105
+ clients: AnthropicVertex[];
106
+ }
107
+
108
+ /**
109
+ * Build a real ChatAnthropic wired to a real AnthropicVertex through the
110
+ * `createClient` seam, with transport replaced by a recording fetch.
111
+ *
112
+ * The factory honors `maxRetries` from the incoming options: LangChain owns
113
+ * retrying (its AsyncCaller wraps every request) and passes `maxRetries: 0`
114
+ * so the underlying SDK must not retry underneath it. A factory that ignored
115
+ * this would nest the Vertex SDK's default 2 retries inside LangChain's loop,
116
+ * multiplying every transient failure — the T02 adapter must preserve this.
117
+ */
118
+ function buildSeamHarness(): SeamHarness {
119
+ const requests: RecordedRequest[] = [];
120
+ const factoryOptions: SeamHarness["factoryOptions"] = [];
121
+ const clients: AnthropicVertex[] = [];
122
+
123
+ const recordingFetch: typeof fetch = async (input, init) => {
124
+ const headers: Record<string, string> = {};
125
+ new Headers(init?.headers).forEach((value, key) => {
126
+ headers[key] = value;
127
+ });
128
+ requests.push({
129
+ url: String(input),
130
+ method: init?.method ?? "GET",
131
+ headers,
132
+ body: JSON.parse(String(init?.body)) as Record<string, unknown>,
133
+ });
134
+ const streaming = String(input).includes(":streamRawPredict");
135
+ return streaming
136
+ ? new Response(sseBody(), {
137
+ status: 200,
138
+ headers: { "Content-Type": "text/event-stream" },
139
+ })
140
+ : new Response(JSON.stringify(MESSAGE_RESPONSE), {
141
+ status: 200,
142
+ headers: { "Content-Type": "application/json" },
143
+ });
144
+ };
145
+
146
+ // Minimal structural fake of google-auth-library's AuthClient — the Vertex
147
+ // SDK's adaptRequest calls exactly `getRequestHeaders()` (merged into the
148
+ // outbound request) and reads `projectId`. The cast is test-only: building
149
+ // a real AuthClient would require credentials, defeating determinism.
150
+ const fakeAuthClient = {
151
+ projectId: PROJECT_ID,
152
+ getRequestHeaders: async () => new Headers({ authorization: "Bearer test-token" }),
153
+ } as unknown as AuthClient;
154
+
155
+ const model = new ChatAnthropic({
156
+ model: VERTEX_MODEL_ID,
157
+ temperature: 0,
158
+ maxTokens: 1024,
159
+ createClient: (options) => {
160
+ factoryOptions.push({ maxRetries: options.maxRetries });
161
+ const client = new AnthropicVertex({
162
+ projectId: PROJECT_ID,
163
+ region: REGION,
164
+ authClient: fakeAuthClient,
165
+ fetch: recordingFetch,
166
+ maxRetries: options.maxRetries,
167
+ });
168
+ clients.push(client);
169
+ return client;
170
+ },
171
+ });
172
+
173
+ return { model, requests, factoryOptions, clients };
174
+ }
175
+
176
+ const expectedUrl = (specifier: "rawPredict" | "streamRawPredict") =>
177
+ `https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}` +
178
+ `/locations/${REGION}/publishers/anthropic/models/${VERTEX_MODEL_ID}:${specifier}`;
179
+
180
+ // ─── Tests ───────────────────────────────────────────────────────────────────
181
+
182
+ describe("ChatAnthropic createClient -> AnthropicVertex seam", () => {
183
+ it("constructs without an Anthropic API key when createClient is provided", () => {
184
+ // chat_models.js waives the "Anthropic API key not found" check for
185
+ // factory-constructed clients — the waiver the vertex backend depends on.
186
+ expect(() => buildSeamHarness()).not.toThrow();
187
+ });
188
+
189
+ it("shapes a non-streaming request into Vertex wire form (:rawPredict)", async () => {
190
+ const h = buildSeamHarness();
191
+
192
+ const result = await h.model.invoke([new HumanMessage("Weather in Chennai?")]);
193
+
194
+ expect(h.requests).toHaveLength(1);
195
+ const req = h.requests[0];
196
+
197
+ // The model id rides in the URL path — with `@` intact — not in the body.
198
+ expect(req.url).toBe(expectedUrl("rawPredict"));
199
+ expect(req.method).toBe("POST");
200
+ expect(req.body).not.toHaveProperty("model");
201
+ expect(req.body.anthropic_version).toBe("vertex-2023-10-16");
202
+ expect(req.body.max_tokens).toBe(1024);
203
+
204
+ // The fake auth client's OAuth header reached the wire.
205
+ expect(req.headers.authorization).toBe("Bearer test-token");
206
+
207
+ expect(result).toBeInstanceOf(AIMessage);
208
+ });
209
+
210
+ it("round-trips tool definitions and tool_use blocks into tool_calls", async () => {
211
+ const h = buildSeamHarness();
212
+ const withTools = h.model.bindTools([WEATHER_TOOL]);
213
+
214
+ const result = (await withTools.invoke([
215
+ new HumanMessage("Weather in Chennai?"),
216
+ ])) as AIMessage;
217
+
218
+ // Tool definition survived request shaping through the Vertex adapter.
219
+ const tools = h.requests[0].body.tools as Array<{ name: string }>;
220
+ expect(tools).toHaveLength(1);
221
+ expect(tools[0].name).toBe("get_weather");
222
+
223
+ // The tool_use response block became a LangChain tool_call with its id —
224
+ // the identity the HITL approval flow keys on.
225
+ expect(result.tool_calls).toHaveLength(1);
226
+ expect(result.tool_calls?.[0]).toMatchObject({
227
+ id: "toolu_test_01",
228
+ name: "get_weather",
229
+ args: { city: "Chennai" },
230
+ });
231
+ });
232
+
233
+ it("reports usage_metadata on non-streaming responses (what billing reads)", async () => {
234
+ const h = buildSeamHarness();
235
+
236
+ const result = (await h.model.invoke([
237
+ new HumanMessage("Weather in Chennai?"),
238
+ ])) as AIMessage;
239
+
240
+ expect(result.usage_metadata).toMatchObject({
241
+ input_tokens: 25,
242
+ output_tokens: 17,
243
+ total_tokens: 42,
244
+ });
245
+ });
246
+
247
+ it("streams via :streamRawPredict, assembling tool_calls and usage from SSE", async () => {
248
+ const h = buildSeamHarness();
249
+
250
+ let final: AIMessageChunk | undefined;
251
+ for await (const chunk of await h.model.stream([
252
+ new HumanMessage("Weather in Chennai?"),
253
+ ])) {
254
+ final = final === undefined ? chunk : final.concat(chunk);
255
+ }
256
+
257
+ expect(h.requests).toHaveLength(1);
258
+ expect(h.requests[0].url).toBe(expectedUrl("streamRawPredict"));
259
+ expect(h.requests[0].body.stream).toBe(true);
260
+ expect(h.requests[0].body).not.toHaveProperty("model");
261
+
262
+ expect(final).toBeDefined();
263
+ expect(final?.text).toBe("I'll check the weather.");
264
+ expect(final?.tool_calls).toHaveLength(1);
265
+ expect(final?.tool_calls?.[0]).toMatchObject({
266
+ id: "toolu_test_02",
267
+ name: "get_weather",
268
+ args: { city: "Chennai" },
269
+ });
270
+
271
+ // input from message_start; output accumulated across message events.
272
+ // Pinned to the observed accumulation so a LangChain bump that changes
273
+ // usage math (billing input) fails here first.
274
+ expect(final?.usage_metadata).toMatchObject({
275
+ input_tokens: 25,
276
+ output_tokens: 18,
277
+ });
278
+ });
279
+
280
+ it("passes maxRetries: 0 to the factory and the honored value reaches the client", async () => {
281
+ const h = buildSeamHarness();
282
+
283
+ await h.model.invoke([new HumanMessage("hi")]);
284
+ for await (const chunk of await h.model.stream([new HumanMessage("hi")])) {
285
+ void chunk;
286
+ }
287
+
288
+ // LangChain owns retrying: it must hand the factory maxRetries: 0, once
289
+ // per cached client (batch + streaming are constructed independently).
290
+ expect(h.factoryOptions).toHaveLength(2);
291
+ expect(h.factoryOptions.every((o) => o.maxRetries === 0)).toBe(true);
292
+ expect(h.clients).toHaveLength(2);
293
+ expect(h.clients.every((c) => c.maxRetries === 0)).toBe(true);
294
+ });
295
+ });
@@ -16,7 +16,6 @@
16
16
  */
17
17
 
18
18
  import type { ToolApprovalPolicy } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
19
- import type { ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
20
19
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
21
20
  import { ApprovalAction, ApprovalMode, ApprovalPolicySource } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
22
21
  import { toolApprovalCategory, type ToolApprovalCategory } from "./tool-kind.js";
@@ -321,9 +320,16 @@ export interface MergedToolPolicy {
321
320
  * Policy chain (each level overrides the previous):
322
321
  * 1. status.toolApprovals — system-generated defaults; presence = requires approval
323
322
  * 2. spec.pinnedToolApprovals — manual overrides; presence = requires approval
324
- * 3. agent tool_approval_overrides — explicit boolean per tool (enable OR disable)
323
+ * 3. usage tool_approval_overrides — explicit boolean per tool (enable OR disable)
325
324
  * 4. active leases — runtime bypass (highest priority), now scoped
326
325
  *
326
+ * Layer 3 is read from {@link ResolvedMcpServer.toolApprovalOverrides} — the
327
+ * overrides ride each server from the usage that resolved it (issue #349), so
328
+ * an override is STRUCTURALLY scoped to its own server. There is deliberately
329
+ * no cross-server override input: a flat list applied inside this per-server
330
+ * loop is how an override once leaked onto — or silently un-gated — a
331
+ * same-named tool on another server.
332
+ *
327
333
  * The map carries ONLY the tools that require approval — a tool's absence means
328
334
  * "auto-approved". Leases shape that absence:
329
335
  * - On a global pre-arm ({@link ActiveLeases.global}) the map is empty.
@@ -341,7 +347,6 @@ export interface MergedToolPolicy {
341
347
  */
342
348
  export function mergeApprovalPolicies(
343
349
  resolvedServers: ResolvedMcpServer[],
344
- agentOverrides: ToolApprovalOverride[],
345
350
  leases: ActiveLeases,
346
351
  ): Map<string, MergedToolPolicy> {
347
352
  const merged = new Map<string, MergedToolPolicy>();
@@ -382,10 +387,12 @@ export function mergeApprovalPolicies(
382
387
  });
383
388
  }
384
389
 
385
- // Layer 3: per-agent overrides (explicit boolean, can enable or disable).
386
- // Touching a tool here makes the per-agent layer the responsible source,
387
- // whether it enables, disables, or re-messages the gate.
388
- for (const override of agentOverrides) {
390
+ // Layer 3: this usage's overrides (explicit boolean, can enable or
391
+ // disable) scoped to THIS server because they arrived on it (see the
392
+ // function doc). Touching a tool here makes the per-agent layer the
393
+ // responsible source, whether it enables, disables, or re-messages the
394
+ // gate.
395
+ for (const override of server.toolApprovalOverrides) {
389
396
  if (!override.toolName) continue;
390
397
  const existing = serverPolicies.get(override.toolName);
391
398
  if (existing) {
@@ -13,6 +13,11 @@
13
13
  * The runner never holds R2/S3 credentials — in cloud mode it calls the proxy
14
14
  * to obtain a presigned upload URL, then PUTs content over plain HTTPS.
15
15
  *
16
+ * Every proxy-backend request rides the shared bounded-backoff loop
17
+ * (http-retry.ts) with a per-request abort timeout, so a momentary
18
+ * stigmer-service interruption is retried and a hung connection cannot
19
+ * stall an agent turn unboundedly (stigmer/stigmer#468).
20
+ *
16
21
  * DD-6: No direct R2 backend. Local + Proxy only.
17
22
  */
18
23
 
@@ -20,6 +25,8 @@ import { mkdir, writeFile, readFile, access, rm } from "node:fs/promises";
20
25
  import { dirname, join, resolve, sep } from "node:path";
21
26
  import { homedir } from "node:os";
22
27
  import type { Config } from "../config.js";
28
+ import type { RetryOptions } from "./grpc-retry.js";
29
+ import { fetchWithRetry, type FetchRetryPolicy } from "./http-retry.js";
23
30
 
24
31
  // ── Interface ────────────────────────────────────────────────────────
25
32
 
@@ -117,13 +124,64 @@ export class LocalArtifactStorage implements ArtifactStorage {
117
124
 
118
125
  // ── Proxy Backend ────────────────────────────────────────────────────
119
126
 
127
+ /** Retry policy plus the two per-request bounds; every field is defaulted. */
128
+ export interface ProxyArtifactStorageOptions extends RetryOptions {
129
+ /** Bound for proxy presign calls and the 1-byte exists probe. Default 30 s. */
130
+ readonly requestTimeoutMs?: number;
131
+ /**
132
+ * Bound for the presigned R2 PUT/GET, which move whole artifact payloads
133
+ * (claim-check offloads, tool outputs, file-review captures — MB scale).
134
+ * Deliberately wider than requestTimeoutMs so a legitimate slow transfer
135
+ * is never aborted by a bound sized for JSON round-trips. Default 120 s.
136
+ */
137
+ readonly transferTimeoutMs?: number;
138
+ }
139
+
140
+ /**
141
+ * Default transient-retry policy (stigmer/stigmer#468): the checkpointer's
142
+ * budget — sized to span a pod-restart reroute, ~7.75 s of delays across
143
+ * 5 retries. Retrying is safe at every site: the presign POSTs are
144
+ * idempotent mints, the PUT re-sends identical bytes to the same key (a
145
+ * keyed overwrite), and the GETs are reads.
146
+ */
147
+ const DEFAULT_RETRY = {
148
+ baseDelayMs: 250,
149
+ backoffFactor: 2,
150
+ maxRetries: 5,
151
+ requestTimeoutMs: 30_000,
152
+ transferTimeoutMs: 120_000,
153
+ } as const;
154
+
120
155
  export class ProxyArtifactStorage implements ArtifactStorage {
121
156
  private readonly baseUrl: string;
122
157
  private readonly authTokenSource: ProxyAuthTokenSource;
123
-
124
- constructor(proxyEndpoint: string, authToken: ProxyAuthTokenSource) {
158
+ /** Governs the proxy presign calls and the exists probe. */
159
+ private readonly requestPolicy: FetchRetryPolicy;
160
+ /** Governs the presigned R2 transfers; same budget, wider timeout. */
161
+ private readonly transferPolicy: FetchRetryPolicy;
162
+
163
+ constructor(
164
+ proxyEndpoint: string,
165
+ authToken: ProxyAuthTokenSource,
166
+ options: ProxyArtifactStorageOptions = {},
167
+ ) {
125
168
  this.baseUrl = `${proxyEndpoint.replace(/\/+$/, "")}/v1/proxy/artifacts`;
126
169
  this.authTokenSource = authToken;
170
+ const shared = {
171
+ label: "ProxyArtifactStorage",
172
+ baseDelayMs: options.baseDelayMs ?? DEFAULT_RETRY.baseDelayMs,
173
+ backoffFactor: options.backoffFactor ?? DEFAULT_RETRY.backoffFactor,
174
+ maxRetries: options.maxRetries ?? DEFAULT_RETRY.maxRetries,
175
+ delayFn: options.delayFn,
176
+ };
177
+ this.requestPolicy = {
178
+ ...shared,
179
+ requestTimeoutMs: options.requestTimeoutMs ?? DEFAULT_RETRY.requestTimeoutMs,
180
+ };
181
+ this.transferPolicy = {
182
+ ...shared,
183
+ requestTimeoutMs: options.transferTimeoutMs ?? DEFAULT_RETRY.transferTimeoutMs,
184
+ };
127
185
  }
128
186
 
129
187
  /**
@@ -141,14 +199,14 @@ export class ProxyArtifactStorage implements ArtifactStorage {
141
199
  async upload(key: string, content: Buffer, contentType?: string): Promise<string> {
142
200
  const ct = contentType ?? "application/octet-stream";
143
201
 
144
- const presignResp = await fetch(`${this.baseUrl}/presigned-upload-url`, {
202
+ const presignResp = await fetchWithRetry(`${this.baseUrl}/presigned-upload-url`, {
145
203
  method: "POST",
146
204
  headers: {
147
205
  "Authorization": `Bearer ${this.authToken}`,
148
206
  "Content-Type": "application/json",
149
207
  },
150
208
  body: JSON.stringify({ key, content_type: ct }),
151
- });
209
+ }, this.requestPolicy);
152
210
  if (!presignResp.ok) {
153
211
  throw new Error(
154
212
  `Failed to get presigned upload URL (HTTP ${presignResp.status}): ` +
@@ -180,11 +238,14 @@ export class ProxyArtifactStorage implements ArtifactStorage {
180
238
  uploadHeaders["Content-Type"] = ct;
181
239
  }
182
240
 
183
- const putResp = await fetch(data.url, {
241
+ // Retrying the PUT alone (not the presign+PUT pair) is deliberate: the
242
+ // retry budget's few seconds of delays sit far inside presign validity,
243
+ // and re-sending the same Buffer to the same key is a keyed overwrite.
244
+ const putResp = await fetchWithRetry(data.url, {
184
245
  method: "PUT",
185
246
  headers: uploadHeaders,
186
247
  body: content,
187
- });
248
+ }, this.transferPolicy);
188
249
  if (!putResp.ok) {
189
250
  throw new Error(
190
251
  `Presigned upload failed (HTTP ${putResp.status}): ` +
@@ -196,14 +257,14 @@ export class ProxyArtifactStorage implements ArtifactStorage {
196
257
  }
197
258
 
198
259
  async getDownloadUrl(key: string): Promise<string> {
199
- const resp = await fetch(`${this.baseUrl}/presigned-download-url`, {
260
+ const resp = await fetchWithRetry(`${this.baseUrl}/presigned-download-url`, {
200
261
  method: "POST",
201
262
  headers: {
202
263
  "Authorization": `Bearer ${this.authToken}`,
203
264
  "Content-Type": "application/json",
204
265
  },
205
266
  body: JSON.stringify({ key }),
206
- });
267
+ }, this.requestPolicy);
207
268
  if (!resp.ok) {
208
269
  throw new Error(
209
270
  `Failed to get presigned download URL (HTTP ${resp.status}): ` +
@@ -216,7 +277,7 @@ export class ProxyArtifactStorage implements ArtifactStorage {
216
277
 
217
278
  async download(key: string): Promise<Buffer> {
218
279
  const url = await this.getDownloadUrl(key);
219
- const resp = await fetch(url);
280
+ const resp = await fetchWithRetry(url, undefined, this.transferPolicy);
220
281
  if (!resp.ok) {
221
282
  throw new Error(
222
283
  `Artifact download failed (HTTP ${resp.status}) for key '${key}': ` +
@@ -242,7 +303,11 @@ export class ProxyArtifactStorage implements ArtifactStorage {
242
303
  // addressed CAS blob dedup) then re-uploads, which is idempotent.
243
304
  return false;
244
305
  }
245
- const resp = await fetch(url, { headers: { Range: "bytes=0-0" } });
306
+ const resp = await fetchWithRetry(
307
+ url,
308
+ { headers: { Range: "bytes=0-0" } },
309
+ this.requestPolicy,
310
+ );
246
311
  await resp.arrayBuffer().catch(() => undefined); // drain the <=1-byte body
247
312
  if (resp.status === 404) return false;
248
313
  if (resp.status === 200 || resp.status === 206 || resp.status === 416) return true;