@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
@@ -53,6 +53,10 @@ describe("buildChatModel", () => {
53
53
  vi.clearAllMocks();
54
54
  delete process.env.ANTHROPIC_API_KEY;
55
55
  delete process.env.OPENAI_API_KEY;
56
+ delete process.env.STIGMER_ANTHROPIC_BACKEND;
57
+ delete process.env.STIGMER_OPENAI_BACKEND;
58
+ delete process.env.CLOUD_ML_REGION;
59
+ delete process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS;
56
60
  });
57
61
 
58
62
  afterEach(() => {
@@ -159,4 +163,200 @@ describe("buildChatModel", () => {
159
163
  expect(provider).toBe("anthropic");
160
164
  expect(apiModelId).toBe("claude-haiku-4.5");
161
165
  });
166
+
167
+ // ─── Request timeout (STIGMER_LLM_REQUEST_TIMEOUT_MS) ──────────────────────
168
+ //
169
+ // The timeout lives in a different slot per wrapper: ChatOpenAI reads it
170
+ // as a constructor field; ChatAnthropic only honors clientOptions.timeout.
171
+ // Putting it in the shared constructor spread was the regression that made
172
+ // the env var inert on every Anthropic path (T02 finding 2, repaired in
173
+ // T06). The backend factories' half of the chain is pinned in the
174
+ // vertex/bedrock/foundry adapter tests.
175
+
176
+ describe("request timeout", () => {
177
+ it("places the OpenAI timeout at the constructor level with maxRetries 0", async () => {
178
+ mockRegistryResponse([
179
+ { id: "gpt-4.1", apiModelId: "gpt-4.1", provider: "openai" },
180
+ ]);
181
+
182
+ await buildChatModel({ modelName: "gpt-4.1", timeoutMs: 5000 });
183
+
184
+ const args = mockOpenAICtor.mock.calls.at(-1)?.[0] as Record<string, unknown>;
185
+ expect(args).toMatchObject({ timeout: 5000, maxRetries: 0 });
186
+ });
187
+
188
+ it("places the Anthropic timeout in clientOptions, never the inert constructor slot", async () => {
189
+ mockRegistryResponse([
190
+ { id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic" },
191
+ ]);
192
+
193
+ await buildChatModel({ modelName: "claude-haiku-4.5", timeoutMs: 5000 });
194
+
195
+ const args = lastAnthropicArgs();
196
+ expect(args).toMatchObject({ clientOptions: { timeout: 5000 }, maxRetries: 0 });
197
+ expect(args).not.toHaveProperty("timeout");
198
+ });
199
+
200
+ it("merges the timeout with the proxy wiring inside one clientOptions block", async () => {
201
+ mockRegistryResponse([
202
+ { id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic" },
203
+ ]);
204
+
205
+ await buildChatModel({
206
+ modelName: "claude-haiku-4.5",
207
+ proxyEndpoint: "https://api.stigmer.ai",
208
+ stigmerToken: "tok-123",
209
+ timeoutMs: 5000,
210
+ });
211
+
212
+ expect(lastAnthropicArgs()).toMatchObject({
213
+ clientOptions: {
214
+ timeout: 5000,
215
+ baseURL: "https://api.stigmer.ai/v1/proxy/llm/anthropic",
216
+ },
217
+ });
218
+ });
219
+
220
+ // The env default resolves INSIDE buildChatModel (#468): before this,
221
+ // each caller parsed STIGMER_LLM_REQUEST_TIMEOUT_MS itself, and every
222
+ // caller except the deep-agent main path — including its own sub-agent
223
+ // modelFactory — dropped the bound.
224
+ it("defaults to STIGMER_LLM_REQUEST_TIMEOUT_MS when the caller passes no timeout", async () => {
225
+ process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS = "7000";
226
+ mockRegistryResponse([
227
+ { id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic" },
228
+ ]);
229
+
230
+ await buildChatModel({ modelName: "claude-haiku-4.5" });
231
+
232
+ expect(lastAnthropicArgs()).toMatchObject({
233
+ clientOptions: { timeout: 7000 },
234
+ maxRetries: 0,
235
+ });
236
+ });
237
+
238
+ it("lets an explicit caller timeout win over the env default", async () => {
239
+ process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS = "7000";
240
+ mockRegistryResponse([
241
+ { id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic" },
242
+ ]);
243
+
244
+ await buildChatModel({ modelName: "claude-haiku-4.5", timeoutMs: 5000 });
245
+
246
+ expect(lastAnthropicArgs()).toMatchObject({ clientOptions: { timeout: 5000 } });
247
+ });
248
+
249
+ it.each(["0", "-1", "not-a-number", ""])(
250
+ "normalizes a non-positive env value (%j) to no bound",
251
+ async (envValue) => {
252
+ process.env.STIGMER_LLM_REQUEST_TIMEOUT_MS = envValue;
253
+ mockRegistryResponse([
254
+ { id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic" },
255
+ ]);
256
+
257
+ await buildChatModel({ modelName: "claude-haiku-4.5" });
258
+
259
+ const args = lastAnthropicArgs();
260
+ expect(args).not.toHaveProperty("clientOptions");
261
+ expect(args).not.toHaveProperty("maxRetries");
262
+ },
263
+ );
264
+ });
265
+
266
+ // ─── Provider backends (STIGMER_ANTHROPIC_BACKEND) ─────────────────────────
267
+
268
+ describe("vertex backend", () => {
269
+ beforeEach(() => {
270
+ process.env.STIGMER_ANTHROPIC_BACKEND = "vertex";
271
+ process.env.CLOUD_ML_REGION = "asia-south1";
272
+ });
273
+
274
+ it("holds the canonical-id invariant: translated id on the wire, canonical id returned", async () => {
275
+ mockRegistryResponse([
276
+ { id: "claude-sonnet-4.5", apiModelId: "claude-sonnet-4-5-20250929", provider: "anthropic" },
277
+ ]);
278
+
279
+ const { apiModelId } = await buildChatModel({ modelName: "claude-sonnet-4.5" });
280
+
281
+ // Pricing and usage metrics key on the canonical id; only the
282
+ // constructor (the wire) sees the Vertex `@date` form.
283
+ expect(apiModelId).toBe("claude-sonnet-4-5-20250929");
284
+ const args = lastAnthropicArgs();
285
+ expect(args.model).toBe("claude-sonnet-4-5@20250929");
286
+ expect(typeof args.createClient).toBe("function");
287
+ });
288
+
289
+ it("passes dateless 4.6-generation ids to the wire untranslated", async () => {
290
+ mockRegistryResponse([
291
+ { id: "claude-sonnet-4.6", apiModelId: "claude-sonnet-4-6", provider: "anthropic" },
292
+ ]);
293
+
294
+ await buildChatModel({ modelName: "claude-sonnet-4.6" });
295
+
296
+ expect(lastAnthropicArgs().model).toBe("claude-sonnet-4-6");
297
+ });
298
+
299
+ it("constructs without ANTHROPIC_API_KEY (auth is Google's, not Anthropic's)", async () => {
300
+ mockRegistryResponse([
301
+ { id: "claude-sonnet-4.6", apiModelId: "claude-sonnet-4-6", provider: "anthropic" },
302
+ ]);
303
+
304
+ await buildChatModel({ modelName: "claude-sonnet-4.6" });
305
+
306
+ expect(lastAnthropicArgs().apiKey).toBe("");
307
+ });
308
+
309
+ it("yields to the proxy: a proxied call never consults the backend var", async () => {
310
+ mockRegistryResponse([
311
+ { id: "claude-sonnet-4.6", apiModelId: "claude-sonnet-4-6", provider: "anthropic" },
312
+ ]);
313
+
314
+ await buildChatModel({
315
+ modelName: "claude-sonnet-4.6",
316
+ proxyEndpoint: "https://api.stigmer.ai",
317
+ stigmerToken: "tok",
318
+ });
319
+
320
+ const args = lastAnthropicArgs();
321
+ expect(args).not.toHaveProperty("createClient");
322
+ expect(args.model).toBe("claude-sonnet-4-6");
323
+ expect(args).toMatchObject({
324
+ clientOptions: { baseURL: "https://api.stigmer.ai/v1/proxy/llm/anthropic" },
325
+ });
326
+ });
327
+
328
+ it("leaves the OpenAI path untouched by the Anthropic backend var", async () => {
329
+ mockRegistryResponse([
330
+ { id: "gpt-4.1", apiModelId: "gpt-4.1", provider: "openai" },
331
+ ]);
332
+
333
+ await buildChatModel({ modelName: "gpt-4.1" });
334
+
335
+ const args = mockOpenAICtor.mock.calls.at(-1)?.[0] as Record<string, unknown>;
336
+ expect(args).not.toHaveProperty("createClient");
337
+ expect(mockAnthropicCtor).not.toHaveBeenCalled();
338
+ });
339
+
340
+ it("fails at dispatch with the region message when CLOUD_ML_REGION is missing", async () => {
341
+ // Defense in depth for paths that bypass the factories' preflight.
342
+ delete process.env.CLOUD_ML_REGION;
343
+ mockRegistryResponse([
344
+ { id: "claude-sonnet-4.6", apiModelId: "claude-sonnet-4-6", provider: "anthropic" },
345
+ ]);
346
+
347
+ await expect(buildChatModel({ modelName: "claude-sonnet-4.6" }))
348
+ .rejects.toThrow(/CLOUD_ML_REGION/);
349
+ expect(mockAnthropicCtor).not.toHaveBeenCalled();
350
+ });
351
+ });
352
+
353
+ it("throws the parser's message on an invalid backend value (never a silent public fallback)", async () => {
354
+ process.env.STIGMER_ANTHROPIC_BACKEND = "verteks";
355
+ mockRegistryResponse([
356
+ { id: "claude-sonnet-4.6", apiModelId: "claude-sonnet-4-6", provider: "anthropic" },
357
+ ]);
358
+
359
+ await expect(buildChatModel({ modelName: "claude-sonnet-4.6" }))
360
+ .rejects.toThrow(/STIGMER_ANTHROPIC_BACKEND="verteks" is not a supported backend/);
361
+ });
162
362
  });
@@ -11,7 +11,7 @@
11
11
  * a non-model error.
12
12
  */
13
13
 
14
- import { describe, it, expect } from "vitest";
14
+ import { describe, it, expect, vi, afterEach } from "vitest";
15
15
  import {
16
16
  PLATFORM_CAPACITY_SENTINEL,
17
17
  classifyModelCallError,
@@ -211,6 +211,294 @@ describe("classifyModelCallError — connection heuristics and no-signal", () =>
211
211
  });
212
212
  });
213
213
 
214
+ describe("classifyModelCallError — vertex backend", () => {
215
+ // The Vertex arms activate only for direct-mode Anthropic calls under
216
+ // STIGMER_ANTHROPIC_BACKEND=vertex (resolved from env, deployment-static).
217
+ // Every other test in this file runs with the var unset and pins that the
218
+ // public wordings are untouched.
219
+ function stubVertexEnv() {
220
+ vi.stubEnv("STIGMER_ANTHROPIC_BACKEND", "vertex");
221
+ vi.stubEnv("CLOUD_ML_REGION", "asia-south1");
222
+ }
223
+
224
+ afterEach(() => {
225
+ vi.unstubAllEnvs();
226
+ });
227
+
228
+ const vertexCtx = {
229
+ proxyMode: false,
230
+ provider: "anthropic" as const,
231
+ modelId: "claude-sonnet-4-6",
232
+ };
233
+
234
+ it("classifies google-auth credential failures as non-retryable with the GCP fix", () => {
235
+ stubVertexEnv();
236
+ for (const raw of [
237
+ "Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.",
238
+ "Unable to detect a Project Id in the current environment.",
239
+ "No projectId was given and it could not be resolved from credentials. The client should be instantiated with the `projectId` option or the `ANTHROPIC_VERTEX_PROJECT_ID` environment variable should be set.",
240
+ "invalid_grant: Invalid JWT Signature.",
241
+ ]) {
242
+ const classified = classifyModelCallError(middlewareWrap(new Error(raw)), vertexCtx);
243
+ expect(classified?.code, raw).toBe("LLM_BACKEND_CREDENTIALS");
244
+ expect(classified?.retryable, raw).toBe(false);
245
+ expect(classified?.message, raw).toContain("GOOGLE_APPLICATION_CREDENTIALS");
246
+ }
247
+ });
248
+
249
+ it("does NOT classify credential prose when the backend is not vertex (no relabeling drift)", () => {
250
+ // Same message, backend unset: google-auth prose can only mean vertex —
251
+ // without it, there is no positive model-call signal, so no classification.
252
+ const classified = classifyModelCallError(
253
+ new Error("Could not load the default credentials"),
254
+ vertexCtx,
255
+ );
256
+ expect(classified).toBeUndefined();
257
+ });
258
+
259
+ it("words 401 around Google credentials, not an API key", () => {
260
+ stubVertexEnv();
261
+ const classified = classifyModelCallError(sdkError(401, "unauthorized"), vertexCtx);
262
+ expect(classified?.code).toBe("LLM_AUTHENTICATION_ERROR");
263
+ expect(classified?.message).toContain("Google rejected this Vertex AI call");
264
+ expect(classified?.message).not.toContain("API key");
265
+ });
266
+
267
+ it("words 403 around the service account's Vertex AI role", () => {
268
+ stubVertexEnv();
269
+ const classified = classifyModelCallError(sdkError(403, "forbidden"), vertexCtx);
270
+ expect(classified?.code).toBe("LLM_PERMISSION_DENIED");
271
+ expect(classified?.message).toContain("Vertex AI User");
272
+ expect(classified?.message).toContain("aiplatform.endpoints.predict");
273
+ });
274
+
275
+ it("words 404 around Model Garden enablement and the configured region", () => {
276
+ stubVertexEnv();
277
+ const classified = classifyModelCallError(sdkError(404, "not found"), vertexCtx);
278
+ expect(classified?.code).toBe("LLM_MODEL_NOT_FOUND");
279
+ expect(classified?.message).toContain("Model Garden");
280
+ expect(classified?.message).toContain('region "asia-south1"');
281
+ });
282
+
283
+ it("stays inert in proxy mode even with the backend var set (proxy owns routing)", () => {
284
+ stubVertexEnv();
285
+ const classified = classifyModelCallError(
286
+ sdkError(401, "unauthorized"),
287
+ { proxyMode: true, provider: "anthropic" },
288
+ );
289
+ expect(classified?.message).toContain("Stigmer platform");
290
+ expect(classified?.message).not.toContain("Vertex");
291
+ });
292
+
293
+ it("stays inert for OpenAI failures with the Anthropic backend var set", () => {
294
+ stubVertexEnv();
295
+ const classified = classifyModelCallError(
296
+ sdkError(404, "not found"),
297
+ { proxyMode: false, provider: "openai", modelId: "gpt-4.1" },
298
+ );
299
+ expect(classified?.message).not.toContain("Vertex");
300
+ });
301
+
302
+ it("never throws on an invalid backend value — classification reads it as public", () => {
303
+ vi.stubEnv("STIGMER_ANTHROPIC_BACKEND", "verteks");
304
+ const classified = classifyModelCallError(sdkError(401, "unauthorized"), vertexCtx);
305
+ expect(classified?.code).toBe("LLM_AUTHENTICATION_ERROR");
306
+ expect(classified?.message).toContain("API key");
307
+ });
308
+ });
309
+
310
+ describe("classifyModelCallError — bedrock backend", () => {
311
+ // The Bedrock arms activate only for direct-mode Anthropic calls under
312
+ // STIGMER_ANTHROPIC_BACKEND=bedrock, mirroring the vertex suite above.
313
+ function stubBedrockEnv() {
314
+ vi.stubEnv("STIGMER_ANTHROPIC_BACKEND", "bedrock");
315
+ vi.stubEnv("AWS_REGION", "ap-south-1");
316
+ }
317
+
318
+ afterEach(() => {
319
+ vi.unstubAllEnvs();
320
+ });
321
+
322
+ const bedrockCtx = {
323
+ proxyMode: false,
324
+ provider: "anthropic" as const,
325
+ modelId: "claude-sonnet-4-6",
326
+ };
327
+
328
+ it("classifies AWS credential-chain failures as non-retryable with the AWS fix", () => {
329
+ stubBedrockEnv();
330
+ for (const raw of [
331
+ "Could not load credentials from any providers",
332
+ "Resolved credential object is not valid",
333
+ ]) {
334
+ const classified = classifyModelCallError(middlewareWrap(new Error(raw)), bedrockCtx);
335
+ expect(classified?.code, raw).toBe("LLM_BACKEND_CREDENTIALS");
336
+ expect(classified?.retryable, raw).toBe(false);
337
+ expect(classified?.message, raw).toContain("AWS");
338
+ expect(classified?.message, raw).toContain("AWS_BEARER_TOKEN_BEDROCK");
339
+ }
340
+ });
341
+
342
+ it("does NOT classify credential prose when the backend is not bedrock (no relabeling drift)", () => {
343
+ const classified = classifyModelCallError(
344
+ new Error("Could not load credentials from any providers"),
345
+ bedrockCtx,
346
+ );
347
+ expect(classified).toBeUndefined();
348
+ });
349
+
350
+ it("translates the bare-id inference-profile rejection into the one-var remedy", () => {
351
+ stubBedrockEnv();
352
+ // AWS's actual ValidationException prose for newer Claude models
353
+ // invoked by bare model id.
354
+ const classified = classifyModelCallError(
355
+ sdkError(
356
+ 400,
357
+ "Invocation of model ID anthropic.claude-sonnet-4-6-v1:0 with on-demand throughput isn't supported. Retry your request with the ID or ARN of an inference profile that contains this model.",
358
+ ),
359
+ bedrockCtx,
360
+ );
361
+ expect(classified?.code).toBe("LLM_BACKEND_MODEL_ROUTING");
362
+ expect(classified?.retryable).toBe(false);
363
+ expect(classified?.message).toContain("STIGMER_BEDROCK_INFERENCE_PREFIX");
364
+ expect(classified?.message).toContain("STIGMER_BEDROCK_MODEL_MAP");
365
+ });
366
+
367
+ it("words 401 around AWS identity, not an API key", () => {
368
+ stubBedrockEnv();
369
+ const classified = classifyModelCallError(sdkError(401, "unauthorized"), bedrockCtx);
370
+ expect(classified?.code).toBe("LLM_AUTHENTICATION_ERROR");
371
+ expect(classified?.message).toContain("AWS rejected this Bedrock call");
372
+ expect(classified?.message).not.toContain("API key");
373
+ });
374
+
375
+ it("words 403 around Bedrock model access and IAM, not an API key", () => {
376
+ stubBedrockEnv();
377
+ const classified = classifyModelCallError(sdkError(403, "forbidden"), bedrockCtx);
378
+ expect(classified?.code).toBe("LLM_PERMISSION_DENIED");
379
+ expect(classified?.message).toContain("Model access");
380
+ expect(classified?.message).toContain("bedrock:InvokeModel");
381
+ expect(classified?.message).not.toContain("API key");
382
+ });
383
+
384
+ it("words 404 around region availability and the id-resolution knobs", () => {
385
+ stubBedrockEnv();
386
+ const classified = classifyModelCallError(sdkError(404, "not found"), bedrockCtx);
387
+ expect(classified?.code).toBe("LLM_MODEL_NOT_FOUND");
388
+ expect(classified?.message).toContain('region "ap-south-1"');
389
+ expect(classified?.message).toContain("STIGMER_BEDROCK_MODEL_MAP");
390
+ });
391
+
392
+ it("stays inert in proxy mode even with the backend var set (proxy owns routing)", () => {
393
+ stubBedrockEnv();
394
+ const classified = classifyModelCallError(
395
+ sdkError(401, "unauthorized"),
396
+ { proxyMode: true, provider: "anthropic" },
397
+ );
398
+ expect(classified?.message).toContain("Stigmer platform");
399
+ expect(classified?.message).not.toContain("Bedrock");
400
+ });
401
+
402
+ it("keeps vertex wordings and bedrock wordings from cross-contaminating", () => {
403
+ stubBedrockEnv();
404
+ const classified = classifyModelCallError(sdkError(403, "forbidden"), bedrockCtx);
405
+ expect(classified?.message).not.toContain("Vertex");
406
+ expect(classified?.message).not.toContain("Model Garden");
407
+ });
408
+ });
409
+
410
+ describe("classifyModelCallError — foundry backend", () => {
411
+ // The Foundry arms activate only for direct-mode Anthropic calls under
412
+ // STIGMER_ANTHROPIC_BACKEND=foundry, mirroring the vertex and bedrock
413
+ // suites above.
414
+ function stubFoundryEnv() {
415
+ vi.stubEnv("STIGMER_ANTHROPIC_BACKEND", "foundry");
416
+ vi.stubEnv("ANTHROPIC_FOUNDRY_RESOURCE", "my-foundry-resource");
417
+ }
418
+
419
+ afterEach(() => {
420
+ vi.unstubAllEnvs();
421
+ });
422
+
423
+ const foundryCtx = {
424
+ proxyMode: false,
425
+ provider: "anthropic" as const,
426
+ modelId: "claude-sonnet-4-6",
427
+ };
428
+
429
+ it("classifies Entra token-acquisition failures as non-retryable with the Azure fix", () => {
430
+ stubFoundryEnv();
431
+ // The Foundry SDK wraps every token-provider failure in this one
432
+ // prefix (pinned by foundry-seam.test.ts), so a single wording covers
433
+ // the whole @azure/identity credential-chain family.
434
+ const classified = classifyModelCallError(
435
+ middlewareWrap(
436
+ new Error(
437
+ "Failed to get token from azureADTokenProvider: " +
438
+ "ChainedTokenCredential authentication failed. CredentialUnavailableError: " +
439
+ "ManagedIdentityCredential: no managed identity endpoint found.",
440
+ ),
441
+ ),
442
+ foundryCtx,
443
+ );
444
+ expect(classified?.code).toBe("LLM_BACKEND_CREDENTIALS");
445
+ expect(classified?.retryable).toBe(false);
446
+ expect(classified?.message).toContain("Microsoft Entra ID");
447
+ expect(classified?.message).toContain("ANTHROPIC_FOUNDRY_API_KEY");
448
+ });
449
+
450
+ it("does NOT classify credential prose when the backend is not foundry (no relabeling drift)", () => {
451
+ const classified = classifyModelCallError(
452
+ new Error("Failed to get token from azureADTokenProvider: boom"),
453
+ foundryCtx,
454
+ );
455
+ expect(classified).toBeUndefined();
456
+ });
457
+
458
+ it("words 401 around the Foundry credential, not the Anthropic API key", () => {
459
+ stubFoundryEnv();
460
+ const classified = classifyModelCallError(sdkError(401, "unauthorized"), foundryCtx);
461
+ expect(classified?.code).toBe("LLM_AUTHENTICATION_ERROR");
462
+ expect(classified?.message).toContain("Azure rejected this Microsoft Foundry call");
463
+ expect(classified?.message).toContain("ANTHROPIC_FOUNDRY_API_KEY");
464
+ expect(classified?.message).not.toContain("your API key");
465
+ });
466
+
467
+ it("words 403 around the Foundry RBAC role", () => {
468
+ stubFoundryEnv();
469
+ const classified = classifyModelCallError(sdkError(403, "forbidden"), foundryCtx);
470
+ expect(classified?.code).toBe("LLM_PERMISSION_DENIED");
471
+ expect(classified?.message).toContain("Foundry User");
472
+ expect(classified?.message).not.toContain("API key");
473
+ });
474
+
475
+ it("words 404 around deployment names, the resource, and the deployment map", () => {
476
+ stubFoundryEnv();
477
+ const classified = classifyModelCallError(sdkError(404, "not found"), foundryCtx);
478
+ expect(classified?.code).toBe("LLM_MODEL_NOT_FOUND");
479
+ expect(classified?.message).toContain("deployment name");
480
+ expect(classified?.message).toContain('resource "my-foundry-resource"');
481
+ expect(classified?.message).toContain("STIGMER_FOUNDRY_DEPLOYMENT_MAP");
482
+ });
483
+
484
+ it("stays inert in proxy mode even with the backend var set (proxy owns routing)", () => {
485
+ stubFoundryEnv();
486
+ const classified = classifyModelCallError(
487
+ sdkError(401, "unauthorized"),
488
+ { proxyMode: true, provider: "anthropic" },
489
+ );
490
+ expect(classified?.message).toContain("Stigmer platform");
491
+ expect(classified?.message).not.toContain("Foundry");
492
+ });
493
+
494
+ it("keeps foundry wordings from cross-contaminating with the other backends", () => {
495
+ stubFoundryEnv();
496
+ const classified = classifyModelCallError(sdkError(404, "not found"), foundryCtx);
497
+ expect(classified?.message).not.toContain("Vertex");
498
+ expect(classified?.message).not.toContain("Bedrock");
499
+ });
500
+ });
501
+
214
502
  describe("describeExecutionError", () => {
215
503
  it("labels classified model errors with the stable code, not the wrapper class", () => {
216
504
  const { errorType, errorMessage } = describeExecutionError(
@@ -1,6 +1,11 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import { computeLlmCostMicros, computeTurnCost, getModelPricing, resolveModelId, ensureLoaded } from "../model-pricing.js";
3
- import type { ModelPricing } from "../model-pricing-data.js";
3
+ import {
4
+ getPricingTable,
5
+ _resetPricingCache,
6
+ DEFAULT_PRICING,
7
+ type ModelPricing,
8
+ } from "../model-pricing-data.js";
4
9
 
5
10
  describe("computeLlmCostMicros", () => {
6
11
  it("falls back to DEFAULT_PRICING when registry is not loaded", () => {
@@ -83,3 +88,58 @@ describe("getModelPricing", () => {
83
88
  expect(pricing.model).toBe("custom-model");
84
89
  });
85
90
  });
91
+
92
+ describe("getPricingTable failure caching (#468)", () => {
93
+ beforeEach(() => {
94
+ _resetPricingCache();
95
+ vi.restoreAllMocks();
96
+ });
97
+
98
+ afterEach(() => {
99
+ _resetPricingCache();
100
+ vi.useRealTimers();
101
+ });
102
+
103
+ const registryResponse = () =>
104
+ new Response(
105
+ JSON.stringify({
106
+ models: [
107
+ {
108
+ id: "claude-sonnet-4-6",
109
+ displayName: "Claude Sonnet",
110
+ costTier: "standard",
111
+ pricing: {
112
+ inputPricePerMillion: 3.0,
113
+ outputPricePerMillion: 15.0,
114
+ cacheWritePricePerMillion: 3.75,
115
+ cacheReadPricePerMillion: 0.3,
116
+ },
117
+ },
118
+ ],
119
+ }),
120
+ { status: 200, headers: { "Content-Type": "application/json" } },
121
+ );
122
+
123
+ it("retries after the short failure TTL instead of pinning DEFAULT_PRICING for an hour", async () => {
124
+ // The model-registry.ts failure-cache policy, applied here: wrong default
125
+ // rates for cost tracking must not persist a full success TTL.
126
+ vi.useFakeTimers();
127
+ const fetchSpy = vi
128
+ .spyOn(globalThis, "fetch")
129
+ .mockRejectedValueOnce(new Error("network error"))
130
+ .mockResolvedValueOnce(registryResponse());
131
+
132
+ // First call fails and degrades to the default table.
133
+ expect((await getPricingTable())[0]).toBe(DEFAULT_PRICING);
134
+
135
+ // Within the failure TTL the fallback stays cached (no refetch).
136
+ vi.advanceTimersByTime(30_000);
137
+ expect((await getPricingTable())[0]).toBe(DEFAULT_PRICING);
138
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
139
+
140
+ // Past the failure TTL the registry is refetched and real rates recover.
141
+ vi.advanceTimersByTime(31_000);
142
+ expect((await getPricingTable())[0]?.model).toBe("claude-sonnet-4-6");
143
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
144
+ });
145
+ });
@@ -223,6 +223,28 @@ describe("resolveToApiModelId", () => {
223
223
  expect(result).toBe("claude-haiku-4.5");
224
224
  });
225
225
 
226
+ it("recovers from a transient network failure without engaging the failure cache (#468)", async () => {
227
+ // undici's retryable shape (TypeError) — the shared bounded-backoff loop
228
+ // absorbs it, so resolution succeeds on the SAME call and the 60 s
229
+ // degraded window never opens. Pre-#468 code failed the fetch outright.
230
+ const fetchSpy = vi
231
+ .spyOn(globalThis, "fetch")
232
+ .mockRejectedValueOnce(new TypeError("fetch failed"))
233
+ .mockResolvedValueOnce(
234
+ new Response(
235
+ JSON.stringify({
236
+ models: [
237
+ { id: "claude-haiku-4.5", apiModelId: "claude-haiku-4-5-20251001", provider: "anthropic", costTier: "economy", harness: "native" },
238
+ ],
239
+ }),
240
+ { status: 200, headers: { "Content-Type": "application/json" } },
241
+ ),
242
+ );
243
+
244
+ expect(await resolveToApiModelId("claude-haiku-4.5")).toBe("claude-haiku-4-5-20251001");
245
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
246
+ });
247
+
226
248
  it("retries after the short failure TTL instead of caching the failure for an hour", async () => {
227
249
  vi.useFakeTimers();
228
250
  const fetchSpy = vi
@@ -40,6 +40,7 @@ const userServer: ResolvedMcpServer = {
40
40
  url: "https://example.com",
41
41
  toolApprovals: [],
42
42
  pinnedToolApprovals: [],
43
+ toolApprovalOverrides: [],
43
44
  discoveredCapabilitiesEmpty: false,
44
45
  };
45
46