@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
@@ -41,7 +41,7 @@ import type { Config } from "../../config.js";
41
41
  import { StigmerClient } from "../../client/stigmer-client.js";
42
42
  import { describeExecutionError } from "../../shared/model-error.js";
43
43
  import { resolveAgentWithTransportRecovery } from "./session-lifecycle.js";
44
- import type { AgentResolution, CreateAgentOptions, CreateCloudAgentOptions } from "./session-lifecycle.js";
44
+ import type { AgentResolution, AgentResolutionReason, CreateAgentOptions, CreateCloudAgentOptions } from "./session-lifecycle.js";
45
45
  import { CursorMode } from "@stigmer/protos/ai/stigmer/agentic/session/v1/enum_pb";
46
46
  import { determineCursorMode, isCloudMode } from "./cursor-mode.js";
47
47
  import { MessageAccumulator, cancelInProgressSubAgentProtos, collapseRedundantToolCallTwins } from "./message-translator.js";
@@ -84,6 +84,7 @@ import {
84
84
  import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
85
85
  import { mergeApprovalPolicies } from "./approval-policy.js";
86
86
  import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
87
+ import { enabledToolsBySlug } from "../../shared/mcp-enabled-tools.js";
87
88
  import { backfillMcpServersIfNeeded } from "./connect-backfill.js";
88
89
  import { resolveExecutionEnv } from "./env-resolver.js";
89
90
  import { resolveBlueprint } from "./blueprint-resolver.js";
@@ -91,7 +92,8 @@ import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
91
92
  import { resolveSkills } from "./skill-resolver.js";
92
93
  import { removeStigmerSymlink } from "../../shared/workspace/stigmer-link.js";
93
94
  import { resolveAttachments } from "./attachment-resolver.js";
94
- import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
95
+ import { buildEnhancedPrompt, buildHitlRecoveryPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
96
+ import { composeTurnRecoveryDigest } from "./turn-recovery.js";
95
97
  import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
96
98
  import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
97
99
  import {
@@ -139,7 +141,7 @@ import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/
139
141
  import { activityStarted, activityFinished } from "../../idle-watchdog.js";
140
142
  import { normalizeActivityInput, type ExecuteActivityInput } from "../../shared/activity-input.js";
141
143
  import { getCapturedRejection, clearCapturedRejection } from "./rejection-capture.js";
142
- import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent } from "./error-classifier.js";
144
+ import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent, extractRunErrorSources } from "./error-classifier.js";
143
145
  import type { ClassifiedError } from "./error-classifier.js";
144
146
  import { createAgent, createCloudAgent } from "./session-lifecycle.js";
145
147
  import { setMaxListeners } from "node:events";
@@ -644,9 +646,21 @@ async function executeCursorInner(
644
646
  // the messaging reach; undefined lets a cloud sandbox runner's
645
647
  // ambient session-scoped token or OSS's no-auth apply). The
646
648
  // attachment header falls back to the ambient credential where no
647
- // exchange happens.
648
- const exchangedRunnerToken =
649
- await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
649
+ // exchange happens. Unlike the env read (which hard-fails on a broken
650
+ // exchange — secrets are load-bearing there), this exchange is
651
+ // opportunistic: every consumer below degrades to an empty answer by
652
+ // contract, and the server refuses the ambient fallback safely, so a
653
+ // failed exchange must not kill the run.
654
+ let exchangedRunnerToken: string | undefined;
655
+ try {
656
+ exchangedRunnerToken =
657
+ await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
658
+ } catch (err) {
659
+ console.warn(
660
+ "[execute-cursor] Scoped-token exchange failed for attachment/discovery " +
661
+ `reads; degrading to the ambient credential: ${err instanceof Error ? err.message : err}`,
662
+ );
663
+ }
650
664
  const attachmentCredential = exchangedRunnerToken
651
665
  ?? config.stigmerTokenRef?.current
652
666
  ?? config.stigmerToken;
@@ -734,11 +748,11 @@ async function executeCursorInner(
734
748
  // them as auto-approved); the global bypass empties the map entirely.
735
749
  const leases = deriveActiveLeases(execution);
736
750
  const globalBypass = leases.global;
737
- const agentOverrides = blueprint.mergedMcpServerUsages
738
- .flatMap((u) => u.toolApprovalOverrides ?? []);
751
+ // Layer-3 overrides ride each resolved server from its merged usage —
752
+ // see ResolvedMcpServer.toolApprovalOverrides (issue #349) — so there
753
+ // is no separate override input to pass here.
739
754
  const mergedPolicies = mergeApprovalPolicies(
740
755
  mcpResolution.resolvedServers,
741
- agentOverrides,
742
756
  leases,
743
757
  );
744
758
  heartbeat();
@@ -785,7 +799,10 @@ async function executeCursorInner(
785
799
  storage: artifactStorage,
786
800
  visionBudget,
787
801
  });
788
- const attachmentPaths = attachmentResults.map((a) => a.relativePath);
802
+ const attachmentEntries = attachmentResults.map((a) => ({
803
+ path: a.relativePath,
804
+ ...(a.renamedFrom !== undefined ? { renamedFrom: a.renamedFrom } : {}),
805
+ }));
789
806
  // Vision facts, derived once from the single resolution result: the
790
807
  // images the model will see inline (in attachment order) and the ones
791
808
  // that degraded to path-only, disclosed in the prompt.
@@ -906,6 +923,11 @@ async function executeCursorInner(
906
923
  captureIgnored,
907
924
  gitWorkspace,
908
925
  isUnattendedApprovalMode(execution),
926
+ // The enabled_tools capability manifest (issue #350): restricted
927
+ // servers' allow-lists, enforced by the hook's "disabled" arm ahead of
928
+ // every approval bypass. The Cursor SDK config cannot hide a server's
929
+ // tools, so this deny-at-call is the harness's enforcement.
930
+ enabledToolsBySlug(mcpResolution.resolvedServers),
909
931
  );
910
932
  const hitlGate = await installHitlGate({
911
933
  workspaceRoot: primaryWorkspaceDir,
@@ -1130,7 +1152,7 @@ async function executeCursorInner(
1130
1152
  subAgents: blueprint.subAgents,
1131
1153
  workspaceDirs: blueprint.workspaceDirs,
1132
1154
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
1133
- attachmentPaths,
1155
+ attachments: attachmentEntries,
1134
1156
  vision: visionPromptInfo,
1135
1157
  pendingApprovals: adjudicatedApprovals,
1136
1158
  appliedToolCallIds,
@@ -1140,28 +1162,43 @@ async function executeCursorInner(
1140
1162
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1141
1163
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1142
1164
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1165
+ // The turn's recorded transcript, seeded from the persisted execution
1166
+ // on a reinvocation (Phase 3). Consumed only by the HITL-recovery
1167
+ // shape — reached from HERE when the stored handle failed to resume
1168
+ // at resolution time (issue #366 crossing 2).
1169
+ turnRecoveryDigest: isReinvocation
1170
+ ? composeTurnRecoveryDigest(status.messages)
1171
+ : undefined,
1143
1172
  });
1144
1173
 
1145
- // Phase 10a: Inject structured output instruction for Cursor harness
1146
- let effectivePrompt = prompt;
1147
- if (structuredOutputSchema) {
1148
- const schemaStr = JSON.stringify(structuredOutputSchema, null, 2);
1149
- effectivePrompt += `\n\n---\nCRITICAL OUTPUT REQUIREMENT:\nYour final response MUST be a single valid JSON object (no markdown, no commentary, no code fences) that matches this schema:\n${schemaStr}\n\nRespond with ONLY the JSON object. Nothing else.`;
1150
- }
1174
+ // Phase 10a: Inject the structured output instruction for the Cursor
1175
+ // harness. A per-turn directive, so like buildFromPlan it must ride every
1176
+ // prompt this turn sends — the primary AND the poisoned-handle recovery
1177
+ // rebuild (the transport retry re-sends effectivePrompt and inherits it).
1178
+ const withStructuredOutputDirective = (basePrompt: string): string =>
1179
+ appendStructuredOutputDirective(basePrompt, structuredOutputSchema);
1180
+ const effectivePrompt = withStructuredOutputDirective(prompt);
1151
1181
 
1152
1182
  // Phase 10a1: The turn's vision payload. The invariant is "images
1153
- // accompany the user's turn message where the message goes, they go":
1154
- // every send that delivers this turn's message carries them (the primary
1155
- // send and both fresh-agent recovery retries, whose empty conversations
1156
- // genuinely need the re-send), while a HITL re-invocation whose prompt
1157
- // carries no user message and whose resumed agent already holds the
1158
- // images in its native conversation carries none. Computed ONCE here so
1159
- // all send sites agree by construction.
1160
- const turnImages = isHitlReinvocation(approvalDecisions)
1161
- ? []
1162
- : toCursorImages(visionImages);
1163
- const toSendMessage = (sendPrompt: string): string | SDKUserMessage =>
1164
- turnImages.length > 0 ? { text: sendPrompt, images: turnImages } : sendPrompt;
1183
+ // accompany the user's turn message, wherever the conversation does not
1184
+ // already hold them" (primarySendCarriesImages): the ONLY send that
1185
+ // skips them is a HITL re-invocation of a successfully RESUMED agent,
1186
+ // whose native conversation carries the images from the original send.
1187
+ // Every send that starts an empty conversation re-delivers them the
1188
+ // ordinary first/fresh-agent primary send, the HITL primary send after a
1189
+ // resolution-time resume failure, and both mid-send recovery retries
1190
+ // (which always run on a fresh agent, so their sites pass turnImages
1191
+ // unconditionally). Attachments re-resolve on every invocation
1192
+ // (Phase 5b), so the bytes are in hand even on a re-invocation.
1193
+ const turnImages = toCursorImages(visionImages);
1194
+ const primarySendImages = primarySendCarriesImages(approvalDecisions, resolution.reason)
1195
+ ? turnImages
1196
+ : [];
1197
+ const toSendMessage = (
1198
+ sendPrompt: string,
1199
+ images: { data: string; mimeType: string }[],
1200
+ ): string | SDKUserMessage =>
1201
+ images.length > 0 ? { text: sendPrompt, images } : sendPrompt;
1165
1202
 
1166
1203
  // Phase 10a2: Log Stigmer preamble size for context trimming diagnostics
1167
1204
  const promptChars = effectivePrompt.length;
@@ -1277,7 +1314,7 @@ async function executeCursorInner(
1277
1314
  // The stall watchdog is armed inside consumeCursorTurnStream (it needs the
1278
1315
  // run to cancel), stored on turnState.stallWatchdog so this shared onDelta can
1279
1316
  // reset it and the activity's finally can stop it as a backstop.
1280
- const run = await resolution.agent.send(toSendMessage(effectivePrompt), {
1317
+ const run = await resolution.agent.send(toSendMessage(effectivePrompt, primarySendImages), {
1281
1318
  onDelta: (event) => {
1282
1319
  if (!turnFirstEventEmitted) {
1283
1320
  turnFirstEventEmitted = true;
@@ -1648,10 +1685,11 @@ async function executeCursorInner(
1648
1685
  // poisoned-handle path leaked the fresh agent — it closed the stale one.)
1649
1686
  resolution = { ...resolution, agent: freshAgent, agentId: freshAgent.agentId, isNew: true };
1650
1687
  turnState.streamErrorMessage = undefined;
1651
- // The retry carries the turn's images too (same toSendMessage): the
1652
- // fresh agent's conversation is empty, so skipping them here would
1653
- // silently lose the user's photo on a recovered turn.
1654
- const retryRun = await freshAgent.send(toSendMessage(retryPrompt), {
1688
+ // The retry always carries the turn's full image payload never the
1689
+ // primary send's HITL-trimmed set: the fresh agent's conversation is
1690
+ // empty, so skipping them here would silently lose the user's photo on
1691
+ // a recovered turn (issue #366's vision corollary).
1692
+ const retryRun = await freshAgent.send(toSendMessage(retryPrompt, turnImages), {
1655
1693
  onDelta: makeCursorTurnOnDelta(onDeltaDeps),
1656
1694
  });
1657
1695
  await consumeCursorTurnStream(retryRun, streamDeps);
@@ -1721,12 +1759,10 @@ async function executeCursorInner(
1721
1759
  status.phase = ExecutionPhase.EXECUTION_COMPLETED;
1722
1760
  break;
1723
1761
  case "error": {
1724
- const resultAny = result as unknown as Record<string, unknown>;
1725
- const sdkError = result.result
1726
- ?? resultAny.error
1727
- ?? resultAny.message
1728
- ?? resultAny.reason;
1729
- const sdkErrorStr = sdkError ? String(sdkError) : undefined;
1762
+ // Shape-aware extraction, NOT String(): the result's error fields are
1763
+ // structured at runtime often enough that a bare coercion showed users
1764
+ // "[object Object]" and shadowed every fallback source below (oss#299).
1765
+ const runErrorSources = extractRunErrorSources(result);
1730
1766
 
1731
1767
  // The SDK frequently resolves run.wait() to a bare { status: "error" }
1732
1768
  // while the real reason (e.g. the original grpc-status 12 routing
@@ -1738,7 +1774,8 @@ async function executeCursorInner(
1738
1774
  if (capturedRejection) clearCapturedRejection(executionId);
1739
1775
 
1740
1776
  const classified = synthesizeError({
1741
- sdkResultFields: sdkErrorStr,
1777
+ sdkError: runErrorSources.sdkError,
1778
+ sdkResultFields: runErrorSources.sdkResultFields,
1742
1779
  streamErrorMessage: turnState.streamErrorMessage,
1743
1780
  capturedRejection,
1744
1781
  conversationErrorText,
@@ -1789,9 +1826,13 @@ async function executeCursorInner(
1789
1826
  subAgents: blueprint.subAgents,
1790
1827
  workspaceDirs: blueprint.workspaceDirs,
1791
1828
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
1792
- attachmentPaths,
1829
+ attachments: attachmentEntries,
1793
1830
  vision: visionPromptInfo,
1794
1831
  pendingApprovals: adjudicatedApprovals,
1832
+ // Without the applied set, the HITL-recovery prompt would tell
1833
+ // the fresh agent to carry out writes the runner already
1834
+ // exact-applied (the primary call at Phase 10 passes it too).
1835
+ appliedToolCallIds,
1795
1836
  interactionMode,
1796
1837
  // buildFromPlan was silently dropped here until T03 Sitting 3 —
1797
1838
  // a build turn that hit handle recovery lost its directive. The
@@ -1802,6 +1843,10 @@ async function executeCursorInner(
1802
1843
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1803
1844
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1804
1845
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1846
+ // Composed fresh (not reused from Phase 10): the failed primary
1847
+ // stream may have appended partial work onto status.messages,
1848
+ // and the replacement agent should know about that too.
1849
+ turnRecoveryDigest: composeTurnRecoveryDigest(status.messages),
1805
1850
  });
1806
1851
 
1807
1852
  console.log(
@@ -1817,7 +1862,14 @@ async function executeCursorInner(
1817
1862
  console.warn("Failed to update session with fresh agentId (non-fatal):", updateErr);
1818
1863
  }
1819
1864
 
1820
- const outcome = await runRecoveryStream(freshAgent, freshPrompt);
1865
+ // Same per-turn directive rule as buildFromPlan above: a
1866
+ // structured-output turn keeps its output contract on the rebuilt
1867
+ // prompt (the transport retry re-sends effectivePrompt and
1868
+ // inherits it without help).
1869
+ const outcome = await runRecoveryStream(
1870
+ freshAgent,
1871
+ withStructuredOutputDirective(freshPrompt),
1872
+ );
1821
1873
  if (!outcome.proceeded) {
1822
1874
  if (outcome.terminal.kind === "return") return slimStatus(status);
1823
1875
  throw new CancelledFailure(outcome.terminal.message);
@@ -1856,8 +1908,14 @@ async function executeCursorInner(
1856
1908
 
1857
1909
  const retryConversationErrorText = await introspectConversation(retryRun, executionId);
1858
1910
 
1911
+ // Same shape-aware extraction as the primary error arm — the retry
1912
+ // previously String()-coerced result.result alone, so a structured
1913
+ // retry failure both read "[object Object]" and ignored the
1914
+ // error/message/reason fields the primary arm consults.
1915
+ const retryErrorSources = extractRunErrorSources(retryResult);
1859
1916
  const retryClassified = synthesizeError({
1860
- sdkResultFields: retryResult.result ? String(retryResult.result) : undefined,
1917
+ sdkError: retryErrorSources.sdkError,
1918
+ sdkResultFields: retryErrorSources.sdkResultFields,
1861
1919
  streamErrorMessage: turnState.streamErrorMessage,
1862
1920
  capturedRejection: retryRejection,
1863
1921
  conversationErrorText: retryConversationErrorText,
@@ -1974,6 +2032,7 @@ async function executeCursorInner(
1974
2032
  `finalTextLength=${finalText.length}`,
1975
2033
  );
1976
2034
  try {
2035
+ const { extractStructuredOutput } = await import("./extract-structured-output.js");
1977
2036
  structuredOutput = await extractStructuredOutput(
1978
2037
  finalText, structuredOutputSchema, config, requestedModel,
1979
2038
  );
@@ -2298,53 +2357,6 @@ function seedCursorTranscriptFromExecution(
2298
2357
  return persisted.subAgentExecutions.map((sub) => clone(SubAgentExecutionSchema, sub));
2299
2358
  }
2300
2359
 
2301
- // ---------------------------------------------------------------------------
2302
- // Structured Output Extraction (Cursor Harness Tier 2)
2303
- // ---------------------------------------------------------------------------
2304
-
2305
- /**
2306
- * Extract structured data from an agent's free-text response using an
2307
- * economy-tier LLM with withStructuredOutput (function-calling).
2308
- * Guarantees schema-conformant JSON output via the API's tool-use mechanism.
2309
- *
2310
- * Construction (registry-id resolution, provider inference, proxy wiring) is
2311
- * delegated to the shared buildChatModel so the economy model's registry id is
2312
- * always resolved to a provider API id before the call.
2313
- */
2314
- async function extractStructuredOutput(
2315
- agentResponse: string,
2316
- schema: Record<string, unknown>,
2317
- config: Config,
2318
- primaryModel: string,
2319
- ): Promise<unknown | null> {
2320
- const { getEconomyModel } = await import("../../shared/model-registry.js");
2321
- const { buildChatModel } = await import("../../shared/model-client.js");
2322
-
2323
- const extractionModel = await getEconomyModel(primaryModel);
2324
- const proxyEndpoint = config.proxyEndpoint ?? config.stigmerBackendEndpoint;
2325
-
2326
- const { model: llm } = await buildChatModel({
2327
- modelName: extractionModel,
2328
- proxyEndpoint,
2329
- stigmerToken: config.stigmerToken ?? undefined,
2330
- maxTokens: 4096,
2331
- });
2332
-
2333
- const zodSchema = jsonSchemaToZod(schema);
2334
- const structured = llm.withStructuredOutput(zodSchema);
2335
-
2336
- const result = await structured.invoke([
2337
- { role: "system", content: "Extract the structured data from the agent's response. Return only the data that matches the schema." },
2338
- { role: "user", content: agentResponse },
2339
- ]);
2340
-
2341
- return result ?? null;
2342
- }
2343
-
2344
- // Re-export for use within this module; shared implementation eliminates
2345
- // the three duplicate converters that previously drifted independently.
2346
- import { jsonSchemaToZod } from "../../shared/json-schema-to-zod.js";
2347
-
2348
2360
  // ---------------------------------------------------------------------------
2349
2361
  // Prompt selection
2350
2362
  // ---------------------------------------------------------------------------
@@ -2365,7 +2377,11 @@ export interface BuildPromptInput {
2365
2377
  subAgents: import("@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb").SubAgent[];
2366
2378
  workspaceDirs: string[];
2367
2379
  workspaceFileRefs: string[];
2368
- attachmentPaths: string[];
2380
+ /**
2381
+ * This turn's resolved attachments for the `<input_files>` section —
2382
+ * final paths plus duplicate-rename disclosure (attachment-resolver.ts).
2383
+ */
2384
+ attachments: import("./prompt-builder.js").AttachmentPromptEntry[];
2369
2385
  /**
2370
2386
  * Vision facts for the input-files section (T04): which attachments the
2371
2387
  * model sees inline and which degraded to path-only. PER-TURN like the
@@ -2416,6 +2432,15 @@ export interface BuildPromptInput {
2416
2432
  * usually blank.
2417
2433
  */
2418
2434
  conversationCatchup?: string;
2435
+ /**
2436
+ * The turn's recorded transcript rendered as digest lines
2437
+ * (turn-recovery.ts), composed from `status.messages` at the call site.
2438
+ * Consumed ONLY by the HITL-recovery shape — a fresh agent that replaced
2439
+ * a lost one mid-HITL needs the story of the work it no longer remembers
2440
+ * (issue #366); every other shape either has native context or no prior
2441
+ * work to tell.
2442
+ */
2443
+ turnRecoveryDigest?: string;
2419
2444
  }
2420
2445
 
2421
2446
  /**
@@ -2427,20 +2452,28 @@ export interface BuildPromptInput {
2427
2452
  * volume, or cloud server-side state) — there is no separate continuation
2428
2453
  * store. The prompt therefore depends only on how the agent was resolved:
2429
2454
  *
2430
- * 1. HITL reinvocation -> buildReinvocationPrompt (approval decisions;
2431
- * the resumed agent's native context carries
2432
- * the prior conversation)
2433
- * 2. resumed_successfully -> raw userMessage (native context carries it)
2434
- * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
2455
+ * 1. HITL reinvocation, -> buildReinvocationPrompt (approval decisions
2456
+ * resumed agent only; the resumed agent's native context
2457
+ * carries the prior conversation)
2458
+ * 2. HITL reinvocation, -> buildHitlRecoveryPrompt (full context +
2459
+ * fresh agent after the turn's recorded transcript + decisions;
2460
+ * resume failure the replacement agent's conversation is
2461
+ * empty, and both fresh-agent crossings —
2462
+ * resolution-time resume failure and mid-send
2463
+ * poisoned-handle recovery — land here by
2464
+ * keying on the reason, issue #366)
2465
+ * 3. resumed_successfully -> raw userMessage (native context carries it)
2466
+ * 4. first execution / fresh -> buildEnhancedPrompt (full instructions +
2435
2467
  * agent after resume failure skills; no prior conversation to inherit)
2436
2468
  */
2437
2469
  /**
2438
2470
  * Whether this activity invocation is a HITL re-invocation — the turn resumes
2439
2471
  * an agent purely to convey approval decisions, carrying NO user message.
2440
- * The single discriminator for everything that must ride with the user's
2441
- * message and nothing else: the reinvocation prompt shape (below) and the
2442
- * vision payload (images accompany the message; a resumed agent already holds
2443
- * them in its native conversation).
2472
+ * Discriminates the two surfaces that depend on the agent already holding
2473
+ * this turn's content natively the prompt shape (below) and the primary
2474
+ * send's vision payload but never alone: both pair it with
2475
+ * `resolution.reason`, because a FRESH agent mid-HITL holds nothing and
2476
+ * needs the full re-delivery (issue #366).
2444
2477
  */
2445
2478
  export function isHitlReinvocation(
2446
2479
  approvalDecisions: Map<string, ApprovalAction> | undefined,
@@ -2448,6 +2481,39 @@ export function isHitlReinvocation(
2448
2481
  return approvalDecisions !== undefined && approvalDecisions.size > 0;
2449
2482
  }
2450
2483
 
2484
+ /**
2485
+ * Whether the PRIMARY send delivers the turn's vision payload. The invariant
2486
+ * is "images accompany the user's turn message, wherever the conversation
2487
+ * does not already hold them" — so the only send that skips them is a HITL
2488
+ * re-invocation of a successfully RESUMED agent, whose native conversation
2489
+ * carries the images from the original send. A fresh agent mid-HITL
2490
+ * (resolution-time resume failure — issue #366's vision corollary) holds
2491
+ * nothing and needs the re-delivery. The mid-send recovery retries always
2492
+ * run on a fresh agent, so their send sites carry the payload
2493
+ * unconditionally rather than consulting this.
2494
+ */
2495
+ export function primarySendCarriesImages(
2496
+ approvalDecisions: Map<string, ApprovalAction> | undefined,
2497
+ reason: AgentResolutionReason,
2498
+ ): boolean {
2499
+ return !(isHitlReinvocation(approvalDecisions) && reason === "resumed_successfully");
2500
+ }
2501
+
2502
+ /**
2503
+ * Append the structured-output contract to a prompt when the execution
2504
+ * requests one. A per-turn directive (the buildFromPlan rule): it must ride
2505
+ * every prompt this turn sends — the primary send AND the poisoned-handle
2506
+ * recovery rebuild, which previously lost it (issue #366 ride-along).
2507
+ */
2508
+ export function appendStructuredOutputDirective(
2509
+ basePrompt: string,
2510
+ schema: Record<string, unknown> | undefined,
2511
+ ): string {
2512
+ if (!schema) return basePrompt;
2513
+ const schemaStr = JSON.stringify(schema, null, 2);
2514
+ return basePrompt + `\n\n---\nCRITICAL OUTPUT REQUIREMENT:\nYour final response MUST be a single valid JSON object (no markdown, no commentary, no code fences) that matches this schema:\n${schemaStr}\n\nRespond with ONLY the JSON object. Nothing else.`;
2515
+ }
2516
+
2451
2517
  export function buildPrompt(input: BuildPromptInput): string {
2452
2518
  const {
2453
2519
  resolution,
@@ -2458,16 +2524,50 @@ export function buildPrompt(input: BuildPromptInput): string {
2458
2524
  subAgents,
2459
2525
  workspaceDirs,
2460
2526
  workspaceFileRefs,
2461
- attachmentPaths,
2527
+ attachments,
2462
2528
  interactionMode,
2463
2529
  buildFromPlan,
2464
2530
  conversationCatchup,
2465
2531
  } = input;
2466
2532
 
2467
- // HITL reinvocation: the agent is resumed, so its native context carries the
2468
- // prior conversation; the reinvocation prompt conveys the approval decisions
2469
- // (and which approved writes the runner already exact-applied).
2533
+ // HITL reinvocation: the decisions-only prompt is correct ONLY while the
2534
+ // agent's native context still carries the prior conversation which only
2535
+ // resumed_successfully guarantees. Any other reason means a fresh agent
2536
+ // mid-HITL (in practice created_after_resume_failure: the stored handle
2537
+ // failed to resume, or a poisoned handle was replaced mid-send), which
2538
+ // gets the full recovery shape instead — enhanced context + the turn's
2539
+ // recorded transcript + the same decisions — because the bare decisions on
2540
+ // an empty conversation strand the agent with instructions and no story,
2541
+ // and the session inherits that amnesia permanently (issue #366).
2470
2542
  if (isHitlReinvocation(approvalDecisions)) {
2543
+ if (resolution.reason !== "resumed_successfully") {
2544
+ return buildHitlRecoveryPrompt(
2545
+ {
2546
+ instructions,
2547
+ userMessage,
2548
+ skills,
2549
+ datastoreUsages: input.datastoreUsages ?? [],
2550
+ channelMessaging: input.channelMessaging ?? [],
2551
+ subAgents,
2552
+ workspaceDirs,
2553
+ workspaceFileRefs,
2554
+ attachments,
2555
+ vision: input.vision,
2556
+ interactionMode,
2557
+ buildFromPlan,
2558
+ contextBridge: input.contextBridge,
2559
+ senderIdentity: input.senderIdentity,
2560
+ sessionContext: input.sessionContext,
2561
+ conversationCatchup,
2562
+ },
2563
+ {
2564
+ turnDigest: input.turnRecoveryDigest,
2565
+ pendingApprovals: input.pendingApprovals,
2566
+ approvalDecisions,
2567
+ appliedToolCallIds: input.appliedToolCallIds,
2568
+ },
2569
+ );
2570
+ }
2471
2571
  return buildReinvocationPrompt(
2472
2572
  input.pendingApprovals,
2473
2573
  approvalDecisions,
@@ -2492,9 +2592,9 @@ export function buildPrompt(input: BuildPromptInput): string {
2492
2592
  if (resolution.reason === "resumed_successfully") {
2493
2593
  const prefixes = [
2494
2594
  formatInteractionModePrefix(interactionMode),
2495
- formatImplementPlanSection(buildFromPlan, attachmentPaths),
2496
- attachmentPaths.length > 0
2497
- ? formatInputFiles(attachmentPaths, input.vision)
2595
+ formatImplementPlanSection(buildFromPlan, attachments),
2596
+ attachments.length > 0
2597
+ ? formatInputFiles(attachments, input.vision)
2498
2598
  : undefined,
2499
2599
  conversationCatchup !== undefined
2500
2600
  ? formatConversationCatchupSection(conversationCatchup)
@@ -2517,7 +2617,7 @@ export function buildPrompt(input: BuildPromptInput): string {
2517
2617
  subAgents,
2518
2618
  workspaceDirs,
2519
2619
  workspaceFileRefs,
2520
- attachmentPaths,
2620
+ attachments,
2521
2621
  vision: input.vision,
2522
2622
  interactionMode,
2523
2623
  buildFromPlan,
@@ -14,7 +14,7 @@
14
14
  * same pipeline as the Python agent-runner's config_transformer.py.
15
15
  */
16
16
 
17
- import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
17
+ import type { McpServerUsage, ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
18
18
  import type { McpServer } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
19
19
  import type { ToolApprovalPolicy } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
20
20
  import type { StigmerClient } from "../../client/stigmer-client.js";
@@ -29,6 +29,7 @@ import {
29
29
  filterEnvToDeclaredKeys,
30
30
  PlaceholderResolutionError,
31
31
  } from "./placeholder-resolver.js";
32
+ import { effectiveEnabledTools } from "../../shared/mcp-enabled-tools.js";
32
33
 
33
34
  /**
34
35
  * Cursor SDK MCP server config shape (matches @cursor/sdk McpServerConfig).
@@ -70,6 +71,29 @@ export interface ResolvedMcpServer {
70
71
  pinnedToolApprovals: ToolApprovalPolicy[];
71
72
  /** True when the server has never been connected (no tool discovery yet). */
72
73
  discoveredCapabilitiesEmpty: boolean;
74
+ /**
75
+ * The EFFECTIVE tool allow-list for this server (issue #350): the usage's
76
+ * enabled_tools, falling back to the server's default_enabled_tools when
77
+ * the usage's list is empty (see shared/mcp-enabled-tools.ts). Absent when
78
+ * unrestricted. The Cursor SDK config cannot hide tools, so this harness
79
+ * enforces it in the HITL hook: the restricted map is written into the
80
+ * approval state (mcpServerEnabledTools) and non-enabled calls are denied
81
+ * with the non-pausing "disabled" kind (see hook-script.ts).
82
+ */
83
+ enabledTools?: string[];
84
+ /**
85
+ * Layer-3 per-agent approval overrides from the usage that resolved this
86
+ * server (issue #349): riding the resolved server is what SCOPES an
87
+ * override to its own server — a flat cross-server list is how an
88
+ * override once leaked onto (or silently un-gated) a same-named tool on
89
+ * another server. Deliberately REQUIRED, not optional: empty means "no
90
+ * overrides", and every construction site — including the shared
91
+ * resolver this one mirrors and the synthesized attachments, which have
92
+ * no usage and therefore no layer 3 — must say so explicitly, so a
93
+ * forgotten mirror cannot compile. Consumed by mergeApprovalPolicies
94
+ * (shared/approval-policy.ts).
95
+ */
96
+ toolApprovalOverrides: ToolApprovalOverride[];
73
97
  }
74
98
 
75
99
  /**
@@ -115,7 +139,13 @@ export async function resolveMcpServers(
115
139
  envVars,
116
140
  ref.slug,
117
141
  );
118
- const server = mcpServerToResolved(mcpServer, ref.slug, serverEnv);
142
+ const server = mcpServerToResolved(
143
+ mcpServer,
144
+ ref.slug,
145
+ serverEnv,
146
+ usage.enabledTools,
147
+ usage.toolApprovalOverrides ?? [],
148
+ );
119
149
  if (server) {
120
150
  assertTransportAllowed(server.slug, server.connectionType, transportPosture);
121
151
  resolved.push(server);
@@ -152,6 +182,8 @@ function mcpServerToResolved(
152
182
  server: McpServer,
153
183
  slug: string,
154
184
  envVars: Record<string, string>,
185
+ usageEnabledTools?: readonly string[],
186
+ usageToolApprovalOverrides: ToolApprovalOverride[] = [],
155
187
  ): ResolvedMcpServer | null {
156
188
  const spec = server.spec;
157
189
  if (!spec) return null;
@@ -167,6 +199,8 @@ function mcpServerToResolved(
167
199
  toolApprovals,
168
200
  pinnedToolApprovals,
169
201
  discoveredCapabilitiesEmpty,
202
+ enabledTools: effectiveEnabledTools(usageEnabledTools, spec.defaultEnabledTools),
203
+ toolApprovalOverrides: usageToolApprovalOverrides,
170
204
  };
171
205
 
172
206
  switch (spec.serverType.case) {
@@ -8,7 +8,12 @@
8
8
  * endpoint is unreachable.
9
9
  */
10
10
 
11
- import { resolveModelRegistryUrl, buildRegistryHeaders } from "../../shared/registry-endpoint.js";
11
+ import {
12
+ resolveModelRegistryUrl,
13
+ buildRegistryHeaders,
14
+ REGISTRY_RETRY_POLICY,
15
+ } from "../../shared/registry-endpoint.js";
16
+ import { fetchWithRetry } from "../../shared/http-retry.js";
12
17
 
13
18
  /** Per-million rates for a speed/mode variant (e.g. "fast") of a base model. */
14
19
  export interface CursorVariantPricing {
@@ -53,6 +58,10 @@ interface RegistryEntry {
53
58
  }
54
59
 
55
60
  const CACHE_TTL_MS = 3_600_000; // 1 hour
61
+ // Failed fetches are cached much shorter than successes (the model-registry.ts
62
+ // policy): a transient failure must not pin DEFAULT_PRICING — wrong rates for
63
+ // cost tracking — for a full hour.
64
+ const FAILURE_CACHE_TTL_MS = 60_000;
56
65
 
57
66
  const DEFAULT_PRICING: CursorModelPricing = {
58
67
  model: "unknown",
@@ -104,7 +113,11 @@ function parseVariants(
104
113
  }
105
114
 
106
115
  async function fetchFromApi(): Promise<readonly CursorModelPricing[]> {
107
- const res = await fetch(resolveModelRegistryUrl(), { headers: buildRegistryHeaders() });
116
+ const res = await fetchWithRetry(
117
+ resolveModelRegistryUrl(),
118
+ { headers: buildRegistryHeaders() },
119
+ REGISTRY_RETRY_POLICY,
120
+ );
108
121
  if (!res.ok) throw new Error(`Model registry fetch failed: ${res.status}`);
109
122
  const data: unknown = await res.json();
110
123
  const table = parsePricingTable(data);
@@ -138,7 +151,7 @@ export async function getPricingTable(): Promise<readonly CursorModelPricing[]>
138
151
  `Failed to fetch model registry from API, using default pricing: ${err}`,
139
152
  );
140
153
  const fallback = [DEFAULT_PRICING];
141
- cache = { data: fallback, expiresAt: Date.now() + CACHE_TTL_MS };
154
+ cache = { data: fallback, expiresAt: Date.now() + FAILURE_CACHE_TTL_MS };
142
155
  return fallback;
143
156
  })
144
157
  .finally(() => {
@@ -148,4 +161,10 @@ export async function getPricingTable(): Promise<readonly CursorModelPricing[]>
148
161
  return inflightFetch;
149
162
  }
150
163
 
164
+ /** Exposed for testing — resets the in-memory cache. */
165
+ export function _resetPricingCache(): void {
166
+ cache = null;
167
+ inflightFetch = null;
168
+ }
169
+
151
170
  export { DEFAULT_PRICING };