@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
@@ -27,7 +27,7 @@
27
27
  */
28
28
  import { InteractionMode, ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
29
29
  import type { Config } from "../../config.js";
30
- import type { AgentResolution } from "./session-lifecycle.js";
30
+ import type { AgentResolution, AgentResolutionReason } from "./session-lifecycle.js";
31
31
  import { type ExecuteActivityInput } from "../../shared/activity-input.js";
32
32
  /**
33
33
  * Creates the activity functions bound to the runner config.
@@ -52,7 +52,11 @@ export interface BuildPromptInput {
52
52
  subAgents: import("@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb").SubAgent[];
53
53
  workspaceDirs: string[];
54
54
  workspaceFileRefs: string[];
55
- attachmentPaths: string[];
55
+ /**
56
+ * This turn's resolved attachments for the `<input_files>` section —
57
+ * final paths plus duplicate-rename disclosure (attachment-resolver.ts).
58
+ */
59
+ attachments: import("./prompt-builder.js").AttachmentPromptEntry[];
56
60
  /**
57
61
  * Vision facts for the input-files section (T04): which attachments the
58
62
  * model sees inline and which degraded to path-only. PER-TURN like the
@@ -103,6 +107,15 @@ export interface BuildPromptInput {
103
107
  * usually blank.
104
108
  */
105
109
  conversationCatchup?: string;
110
+ /**
111
+ * The turn's recorded transcript rendered as digest lines
112
+ * (turn-recovery.ts), composed from `status.messages` at the call site.
113
+ * Consumed ONLY by the HITL-recovery shape — a fresh agent that replaced
114
+ * a lost one mid-HITL needs the story of the work it no longer remembers
115
+ * (issue #366); every other shape either has native context or no prior
116
+ * work to tell.
117
+ */
118
+ turnRecoveryDigest?: string;
106
119
  }
107
120
  /**
108
121
  * Select and build the appropriate prompt based on resolution reason and
@@ -113,20 +126,47 @@ export interface BuildPromptInput {
113
126
  * volume, or cloud server-side state) — there is no separate continuation
114
127
  * store. The prompt therefore depends only on how the agent was resolved:
115
128
  *
116
- * 1. HITL reinvocation -> buildReinvocationPrompt (approval decisions;
117
- * the resumed agent's native context carries
118
- * the prior conversation)
119
- * 2. resumed_successfully -> raw userMessage (native context carries it)
120
- * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
129
+ * 1. HITL reinvocation, -> buildReinvocationPrompt (approval decisions
130
+ * resumed agent only; the resumed agent's native context
131
+ * carries the prior conversation)
132
+ * 2. HITL reinvocation, -> buildHitlRecoveryPrompt (full context +
133
+ * fresh agent after the turn's recorded transcript + decisions;
134
+ * resume failure the replacement agent's conversation is
135
+ * empty, and both fresh-agent crossings —
136
+ * resolution-time resume failure and mid-send
137
+ * poisoned-handle recovery — land here by
138
+ * keying on the reason, issue #366)
139
+ * 3. resumed_successfully -> raw userMessage (native context carries it)
140
+ * 4. first execution / fresh -> buildEnhancedPrompt (full instructions +
121
141
  * agent after resume failure skills; no prior conversation to inherit)
122
142
  */
123
143
  /**
124
144
  * Whether this activity invocation is a HITL re-invocation — the turn resumes
125
145
  * an agent purely to convey approval decisions, carrying NO user message.
126
- * The single discriminator for everything that must ride with the user's
127
- * message and nothing else: the reinvocation prompt shape (below) and the
128
- * vision payload (images accompany the message; a resumed agent already holds
129
- * them in its native conversation).
146
+ * Discriminates the two surfaces that depend on the agent already holding
147
+ * this turn's content natively the prompt shape (below) and the primary
148
+ * send's vision payload but never alone: both pair it with
149
+ * `resolution.reason`, because a FRESH agent mid-HITL holds nothing and
150
+ * needs the full re-delivery (issue #366).
130
151
  */
131
152
  export declare function isHitlReinvocation(approvalDecisions: Map<string, ApprovalAction> | undefined): approvalDecisions is Map<string, ApprovalAction>;
153
+ /**
154
+ * Whether the PRIMARY send delivers the turn's vision payload. The invariant
155
+ * is "images accompany the user's turn message, wherever the conversation
156
+ * does not already hold them" — so the only send that skips them is a HITL
157
+ * re-invocation of a successfully RESUMED agent, whose native conversation
158
+ * carries the images from the original send. A fresh agent mid-HITL
159
+ * (resolution-time resume failure — issue #366's vision corollary) holds
160
+ * nothing and needs the re-delivery. The mid-send recovery retries always
161
+ * run on a fresh agent, so their send sites carry the payload
162
+ * unconditionally rather than consulting this.
163
+ */
164
+ export declare function primarySendCarriesImages(approvalDecisions: Map<string, ApprovalAction> | undefined, reason: AgentResolutionReason): boolean;
165
+ /**
166
+ * Append the structured-output contract to a prompt when the execution
167
+ * requests one. A per-turn directive (the buildFromPlan rule): it must ride
168
+ * every prompt this turn sends — the primary send AND the poisoned-handle
169
+ * recovery rebuild, which previously lost it (issue #366 ride-along).
170
+ */
171
+ export declare function appendStructuredOutputDirective(basePrompt: string, schema: Record<string, unknown> | undefined): string;
132
172
  export declare function buildPrompt(input: BuildPromptInput): string;
@@ -62,6 +62,7 @@ import { readChannelConversationId, synthesizeConversationAttachment, } from "..
62
62
  import { injectSynthesizedAttachment } from "../../shared/synthesized-attachment.js";
63
63
  import { mergeApprovalPolicies } from "./approval-policy.js";
64
64
  import { deriveActiveLeases, isUnattendedApprovalMode } from "../../shared/approval-policy.js";
65
+ import { enabledToolsBySlug } from "../../shared/mcp-enabled-tools.js";
65
66
  import { backfillMcpServersIfNeeded } from "./connect-backfill.js";
66
67
  import { resolveExecutionEnv } from "./env-resolver.js";
67
68
  import { resolveBlueprint } from "./blueprint-resolver.js";
@@ -69,7 +70,8 @@ import { buildCursorSubAgentDefinitions } from "./subagent-config.js";
69
70
  import { resolveSkills } from "./skill-resolver.js";
70
71
  import { removeStigmerSymlink } from "../../shared/workspace/stigmer-link.js";
71
72
  import { resolveAttachments } from "./attachment-resolver.js";
72
- import { buildEnhancedPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
73
+ import { buildEnhancedPrompt, buildHitlRecoveryPrompt, buildReinvocationPrompt, formatConversationCatchupSection, formatInputFiles, formatInteractionModePrefix, formatImplementPlanSection } from "./prompt-builder.js";
74
+ import { composeTurnRecoveryDigest } from "./turn-recovery.js";
73
75
  import { installHitlGate, removeHitlGate } from "./workspace-setup.js";
74
76
  import { ensureHitlDir } from "../../shared/workspace/platform-dir.js";
75
77
  import { acquireWorkspaceLock, WorkspaceLockCancelledError, WorkspaceLockTimeoutError, } from "../../shared/workspace/workspace-lock.js";
@@ -96,7 +98,7 @@ import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/
96
98
  import { activityStarted, activityFinished } from "../../idle-watchdog.js";
97
99
  import { normalizeActivityInput } from "../../shared/activity-input.js";
98
100
  import { getCapturedRejection, clearCapturedRejection } from "./rejection-capture.js";
99
- import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent } from "./error-classifier.js";
101
+ import { synthesizeError, formatClassifiedError, shouldRetryWithFreshAgent, extractRunErrorSources } from "./error-classifier.js";
100
102
  import { createAgent, createCloudAgent } from "./session-lifecycle.js";
101
103
  import { setMaxListeners } from "node:events";
102
104
  import { startHeartbeat } from "../../shared/heartbeat.js";
@@ -536,8 +538,20 @@ turnSeq) {
536
538
  // the messaging reach; undefined lets a cloud sandbox runner's
537
539
  // ambient session-scoped token or OSS's no-auth apply). The
538
540
  // attachment header falls back to the ambient credential where no
539
- // exchange happens.
540
- const exchangedRunnerToken = await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
541
+ // exchange happens. Unlike the env read (which hard-fails on a broken
542
+ // exchange secrets are load-bearing there), this exchange is
543
+ // opportunistic: every consumer below degrades to an empty answer by
544
+ // contract, and the server refuses the ambient fallback safely, so a
545
+ // failed exchange must not kill the run.
546
+ let exchangedRunnerToken;
547
+ try {
548
+ exchangedRunnerToken =
549
+ await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
550
+ }
551
+ catch (err) {
552
+ console.warn("[execute-cursor] Scoped-token exchange failed for attachment/discovery " +
553
+ `reads; degrading to the ambient credential: ${err instanceof Error ? err.message : err}`);
554
+ }
541
555
  const attachmentCredential = exchangedRunnerToken
542
556
  ?? config.stigmerTokenRef?.current
543
557
  ?? config.stigmerToken;
@@ -612,9 +626,10 @@ turnSeq) {
612
626
  // them as auto-approved); the global bypass empties the map entirely.
613
627
  const leases = deriveActiveLeases(execution);
614
628
  const globalBypass = leases.global;
615
- const agentOverrides = blueprint.mergedMcpServerUsages
616
- .flatMap((u) => u.toolApprovalOverrides ?? []);
617
- const mergedPolicies = mergeApprovalPolicies(mcpResolution.resolvedServers, agentOverrides, leases);
629
+ // Layer-3 overrides ride each resolved server from its merged usage —
630
+ // see ResolvedMcpServer.toolApprovalOverrides (issue #349) — so there
631
+ // is no separate override input to pass here.
632
+ const mergedPolicies = mergeApprovalPolicies(mcpResolution.resolvedServers, leases);
618
633
  heartbeat();
619
634
  // Phase 4c: Validate MCP server env health (diagnostic, non-blocking)
620
635
  const mcpWarnings = validateMcpServerEnv(mcpResolution.resolvedServers, blueprint.mergedMcpServerUsages, envVars);
@@ -650,7 +665,10 @@ turnSeq) {
650
665
  storage: artifactStorage,
651
666
  visionBudget,
652
667
  });
653
- const attachmentPaths = attachmentResults.map((a) => a.relativePath);
668
+ const attachmentEntries = attachmentResults.map((a) => ({
669
+ path: a.relativePath,
670
+ ...(a.renamedFrom !== undefined ? { renamedFrom: a.renamedFrom } : {}),
671
+ }));
654
672
  // Vision facts, derived once from the single resolution result: the
655
673
  // images the model will see inline (in attachment order) and the ones
656
674
  // that degraded to path-only, disclosed in the prompt.
@@ -751,7 +769,12 @@ turnSeq) {
751
769
  // approval denials with the non-pausing "unattended" kind, so the
752
770
  // first-denial stop never fires and the turn boundary settles the denied
753
771
  // calls as SKIPPED instead of pausing a turn nobody can approve.
754
- const approvalState = buildApprovalState(mergedPolicies, globalBypass, leases.categories, approvalGrants, captureMode, captureIgnored, gitWorkspace, isUnattendedApprovalMode(execution));
772
+ const approvalState = buildApprovalState(mergedPolicies, globalBypass, leases.categories, approvalGrants, captureMode, captureIgnored, gitWorkspace, isUnattendedApprovalMode(execution),
773
+ // The enabled_tools capability manifest (issue #350): restricted
774
+ // servers' allow-lists, enforced by the hook's "disabled" arm ahead of
775
+ // every approval bypass. The Cursor SDK config cannot hide a server's
776
+ // tools, so this deny-at-call is the harness's enforcement.
777
+ enabledToolsBySlug(mcpResolution.resolvedServers));
755
778
  const hitlGate = await installHitlGate({
756
779
  workspaceRoot: primaryWorkspaceDir,
757
780
  hitlDir,
@@ -944,7 +967,7 @@ turnSeq) {
944
967
  subAgents: blueprint.subAgents,
945
968
  workspaceDirs: blueprint.workspaceDirs,
946
969
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
947
- attachmentPaths,
970
+ attachments: attachmentEntries,
948
971
  vision: visionPromptInfo,
949
972
  pendingApprovals: adjudicatedApprovals,
950
973
  appliedToolCallIds,
@@ -954,25 +977,36 @@ turnSeq) {
954
977
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
955
978
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
956
979
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
980
+ // The turn's recorded transcript, seeded from the persisted execution
981
+ // on a reinvocation (Phase 3). Consumed only by the HITL-recovery
982
+ // shape — reached from HERE when the stored handle failed to resume
983
+ // at resolution time (issue #366 crossing 2).
984
+ turnRecoveryDigest: isReinvocation
985
+ ? composeTurnRecoveryDigest(status.messages)
986
+ : undefined,
957
987
  });
958
- // Phase 10a: Inject structured output instruction for Cursor harness
959
- let effectivePrompt = prompt;
960
- if (structuredOutputSchema) {
961
- const schemaStr = JSON.stringify(structuredOutputSchema, null, 2);
962
- 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.`;
963
- }
988
+ // Phase 10a: Inject the structured output instruction for the Cursor
989
+ // harness. A per-turn directive, so like buildFromPlan it must ride every
990
+ // prompt this turn sends — the primary AND the poisoned-handle recovery
991
+ // rebuild (the transport retry re-sends effectivePrompt and inherits it).
992
+ const withStructuredOutputDirective = (basePrompt) => appendStructuredOutputDirective(basePrompt, structuredOutputSchema);
993
+ const effectivePrompt = withStructuredOutputDirective(prompt);
964
994
  // Phase 10a1: The turn's vision payload. The invariant is "images
965
- // accompany the user's turn message where the message goes, they go":
966
- // every send that delivers this turn's message carries them (the primary
967
- // send and both fresh-agent recovery retries, whose empty conversations
968
- // genuinely need the re-send), while a HITL re-invocation whose prompt
969
- // carries no user message and whose resumed agent already holds the
970
- // images in its native conversation carries none. Computed ONCE here so
971
- // all send sites agree by construction.
972
- const turnImages = isHitlReinvocation(approvalDecisions)
973
- ? []
974
- : toCursorImages(visionImages);
975
- const toSendMessage = (sendPrompt) => turnImages.length > 0 ? { text: sendPrompt, images: turnImages } : sendPrompt;
995
+ // accompany the user's turn message, wherever the conversation does not
996
+ // already hold them" (primarySendCarriesImages): the ONLY send that
997
+ // skips them is a HITL re-invocation of a successfully RESUMED agent,
998
+ // whose native conversation carries the images from the original send.
999
+ // Every send that starts an empty conversation re-delivers them the
1000
+ // ordinary first/fresh-agent primary send, the HITL primary send after a
1001
+ // resolution-time resume failure, and both mid-send recovery retries
1002
+ // (which always run on a fresh agent, so their sites pass turnImages
1003
+ // unconditionally). Attachments re-resolve on every invocation
1004
+ // (Phase 5b), so the bytes are in hand even on a re-invocation.
1005
+ const turnImages = toCursorImages(visionImages);
1006
+ const primarySendImages = primarySendCarriesImages(approvalDecisions, resolution.reason)
1007
+ ? turnImages
1008
+ : [];
1009
+ const toSendMessage = (sendPrompt, images) => images.length > 0 ? { text: sendPrompt, images } : sendPrompt;
976
1010
  // Phase 10a2: Log Stigmer preamble size for context trimming diagnostics
977
1011
  const promptChars = effectivePrompt.length;
978
1012
  const promptEstimatedTokens = Math.ceil(promptChars / 4);
@@ -1073,7 +1107,7 @@ turnSeq) {
1073
1107
  // The stall watchdog is armed inside consumeCursorTurnStream (it needs the
1074
1108
  // run to cancel), stored on turnState.stallWatchdog so this shared onDelta can
1075
1109
  // reset it and the activity's finally can stop it as a backstop.
1076
- const run = await resolution.agent.send(toSendMessage(effectivePrompt), {
1110
+ const run = await resolution.agent.send(toSendMessage(effectivePrompt, primarySendImages), {
1077
1111
  onDelta: (event) => {
1078
1112
  if (!turnFirstEventEmitted) {
1079
1113
  turnFirstEventEmitted = true;
@@ -1390,10 +1424,11 @@ turnSeq) {
1390
1424
  // poisoned-handle path leaked the fresh agent — it closed the stale one.)
1391
1425
  resolution = { ...resolution, agent: freshAgent, agentId: freshAgent.agentId, isNew: true };
1392
1426
  turnState.streamErrorMessage = undefined;
1393
- // The retry carries the turn's images too (same toSendMessage): the
1394
- // fresh agent's conversation is empty, so skipping them here would
1395
- // silently lose the user's photo on a recovered turn.
1396
- const retryRun = await freshAgent.send(toSendMessage(retryPrompt), {
1427
+ // The retry always carries the turn's full image payload never the
1428
+ // primary send's HITL-trimmed set: the fresh agent's conversation is
1429
+ // empty, so skipping them here would silently lose the user's photo on
1430
+ // a recovered turn (issue #366's vision corollary).
1431
+ const retryRun = await freshAgent.send(toSendMessage(retryPrompt, turnImages), {
1397
1432
  onDelta: makeCursorTurnOnDelta(onDeltaDeps),
1398
1433
  });
1399
1434
  await consumeCursorTurnStream(retryRun, streamDeps);
@@ -1450,12 +1485,10 @@ turnSeq) {
1450
1485
  status.phase = ExecutionPhase.EXECUTION_COMPLETED;
1451
1486
  break;
1452
1487
  case "error": {
1453
- const resultAny = result;
1454
- const sdkError = result.result
1455
- ?? resultAny.error
1456
- ?? resultAny.message
1457
- ?? resultAny.reason;
1458
- const sdkErrorStr = sdkError ? String(sdkError) : undefined;
1488
+ // Shape-aware extraction, NOT String(): the result's error fields are
1489
+ // structured at runtime often enough that a bare coercion showed users
1490
+ // "[object Object]" and shadowed every fallback source below (oss#299).
1491
+ const runErrorSources = extractRunErrorSources(result);
1459
1492
  // The SDK frequently resolves run.wait() to a bare { status: "error" }
1460
1493
  // while the real reason (e.g. the original grpc-status 12 routing
1461
1494
  // failure) lives on the failing conversation turn. Capture it here so
@@ -1465,7 +1498,8 @@ turnSeq) {
1465
1498
  if (capturedRejection)
1466
1499
  clearCapturedRejection(executionId);
1467
1500
  const classified = synthesizeError({
1468
- sdkResultFields: sdkErrorStr,
1501
+ sdkError: runErrorSources.sdkError,
1502
+ sdkResultFields: runErrorSources.sdkResultFields,
1469
1503
  streamErrorMessage: turnState.streamErrorMessage,
1470
1504
  capturedRejection,
1471
1505
  conversationErrorText,
@@ -1508,9 +1542,13 @@ turnSeq) {
1508
1542
  subAgents: blueprint.subAgents,
1509
1543
  workspaceDirs: blueprint.workspaceDirs,
1510
1544
  workspaceFileRefs: spec.workspaceFileRefs ?? [],
1511
- attachmentPaths,
1545
+ attachments: attachmentEntries,
1512
1546
  vision: visionPromptInfo,
1513
1547
  pendingApprovals: adjudicatedApprovals,
1548
+ // Without the applied set, the HITL-recovery prompt would tell
1549
+ // the fresh agent to carry out writes the runner already
1550
+ // exact-applied (the primary call at Phase 10 passes it too).
1551
+ appliedToolCallIds,
1514
1552
  interactionMode,
1515
1553
  // buildFromPlan was silently dropped here until T03 Sitting 3 —
1516
1554
  // a build turn that hit handle recovery lost its directive. The
@@ -1521,6 +1559,10 @@ turnSeq) {
1521
1559
  senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
1522
1560
  sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
1523
1561
  conversationCatchup: readConversationCatchup(spec.conversationCatchup),
1562
+ // Composed fresh (not reused from Phase 10): the failed primary
1563
+ // stream may have appended partial work onto status.messages,
1564
+ // and the replacement agent should know about that too.
1565
+ turnRecoveryDigest: composeTurnRecoveryDigest(status.messages),
1524
1566
  });
1525
1567
  console.log(`ExecuteCursor retry with fresh agent: execution=${executionId}, ` +
1526
1568
  `newAgentId=${freshAgent.agentId}`);
@@ -1533,7 +1575,11 @@ turnSeq) {
1533
1575
  catch (updateErr) {
1534
1576
  console.warn("Failed to update session with fresh agentId (non-fatal):", updateErr);
1535
1577
  }
1536
- const outcome = await runRecoveryStream(freshAgent, freshPrompt);
1578
+ // Same per-turn directive rule as buildFromPlan above: a
1579
+ // structured-output turn keeps its output contract on the rebuilt
1580
+ // prompt (the transport retry re-sends effectivePrompt and
1581
+ // inherits it without help).
1582
+ const outcome = await runRecoveryStream(freshAgent, withStructuredOutputDirective(freshPrompt));
1537
1583
  if (!outcome.proceeded) {
1538
1584
  if (outcome.terminal.kind === "return")
1539
1585
  return slimStatus(status);
@@ -1564,8 +1610,14 @@ turnSeq) {
1564
1610
  if (retryRejection)
1565
1611
  clearCapturedRejection(executionId);
1566
1612
  const retryConversationErrorText = await introspectConversation(retryRun, executionId);
1613
+ // Same shape-aware extraction as the primary error arm — the retry
1614
+ // previously String()-coerced result.result alone, so a structured
1615
+ // retry failure both read "[object Object]" and ignored the
1616
+ // error/message/reason fields the primary arm consults.
1617
+ const retryErrorSources = extractRunErrorSources(retryResult);
1567
1618
  const retryClassified = synthesizeError({
1568
- sdkResultFields: retryResult.result ? String(retryResult.result) : undefined,
1619
+ sdkError: retryErrorSources.sdkError,
1620
+ sdkResultFields: retryErrorSources.sdkResultFields,
1569
1621
  streamErrorMessage: turnState.streamErrorMessage,
1570
1622
  capturedRejection: retryRejection,
1571
1623
  conversationErrorText: retryConversationErrorText,
@@ -1661,6 +1713,7 @@ turnSeq) {
1661
1713
  console.log(`ExecuteCursor text extraction failed, trying LLM extraction: execution=${executionId}, ` +
1662
1714
  `finalTextLength=${finalText.length}`);
1663
1715
  try {
1716
+ const { extractStructuredOutput } = await import("./extract-structured-output.js");
1664
1717
  structuredOutput = await extractStructuredOutput(finalText, structuredOutputSchema, config, requestedModel);
1665
1718
  if (structuredOutput !== undefined) {
1666
1719
  console.log(`ExecuteCursor structured output extracted (LLM): execution=${executionId}`);
@@ -1949,40 +2002,6 @@ function seedCursorTranscriptFromExecution(status, execution) {
1949
2002
  }
1950
2003
  return persisted.subAgentExecutions.map((sub) => clone(SubAgentExecutionSchema, sub));
1951
2004
  }
1952
- // ---------------------------------------------------------------------------
1953
- // Structured Output Extraction (Cursor Harness Tier 2)
1954
- // ---------------------------------------------------------------------------
1955
- /**
1956
- * Extract structured data from an agent's free-text response using an
1957
- * economy-tier LLM with withStructuredOutput (function-calling).
1958
- * Guarantees schema-conformant JSON output via the API's tool-use mechanism.
1959
- *
1960
- * Construction (registry-id resolution, provider inference, proxy wiring) is
1961
- * delegated to the shared buildChatModel so the economy model's registry id is
1962
- * always resolved to a provider API id before the call.
1963
- */
1964
- async function extractStructuredOutput(agentResponse, schema, config, primaryModel) {
1965
- const { getEconomyModel } = await import("../../shared/model-registry.js");
1966
- const { buildChatModel } = await import("../../shared/model-client.js");
1967
- const extractionModel = await getEconomyModel(primaryModel);
1968
- const proxyEndpoint = config.proxyEndpoint ?? config.stigmerBackendEndpoint;
1969
- const { model: llm } = await buildChatModel({
1970
- modelName: extractionModel,
1971
- proxyEndpoint,
1972
- stigmerToken: config.stigmerToken ?? undefined,
1973
- maxTokens: 4096,
1974
- });
1975
- const zodSchema = jsonSchemaToZod(schema);
1976
- const structured = llm.withStructuredOutput(zodSchema);
1977
- const result = await structured.invoke([
1978
- { role: "system", content: "Extract the structured data from the agent's response. Return only the data that matches the schema." },
1979
- { role: "user", content: agentResponse },
1980
- ]);
1981
- return result ?? null;
1982
- }
1983
- // Re-export for use within this module; shared implementation eliminates
1984
- // the three duplicate converters that previously drifted independently.
1985
- import { jsonSchemaToZod } from "../../shared/json-schema-to-zod.js";
1986
2005
  /**
1987
2006
  * Select and build the appropriate prompt based on resolution reason and
1988
2007
  * HITL state.
@@ -1992,30 +2011,95 @@ import { jsonSchemaToZod } from "../../shared/json-schema-to-zod.js";
1992
2011
  * volume, or cloud server-side state) — there is no separate continuation
1993
2012
  * store. The prompt therefore depends only on how the agent was resolved:
1994
2013
  *
1995
- * 1. HITL reinvocation -> buildReinvocationPrompt (approval decisions;
1996
- * the resumed agent's native context carries
1997
- * the prior conversation)
1998
- * 2. resumed_successfully -> raw userMessage (native context carries it)
1999
- * 3. first execution / fresh -> buildEnhancedPrompt (full instructions +
2014
+ * 1. HITL reinvocation, -> buildReinvocationPrompt (approval decisions
2015
+ * resumed agent only; the resumed agent's native context
2016
+ * carries the prior conversation)
2017
+ * 2. HITL reinvocation, -> buildHitlRecoveryPrompt (full context +
2018
+ * fresh agent after the turn's recorded transcript + decisions;
2019
+ * resume failure the replacement agent's conversation is
2020
+ * empty, and both fresh-agent crossings —
2021
+ * resolution-time resume failure and mid-send
2022
+ * poisoned-handle recovery — land here by
2023
+ * keying on the reason, issue #366)
2024
+ * 3. resumed_successfully -> raw userMessage (native context carries it)
2025
+ * 4. first execution / fresh -> buildEnhancedPrompt (full instructions +
2000
2026
  * agent after resume failure skills; no prior conversation to inherit)
2001
2027
  */
2002
2028
  /**
2003
2029
  * Whether this activity invocation is a HITL re-invocation — the turn resumes
2004
2030
  * an agent purely to convey approval decisions, carrying NO user message.
2005
- * The single discriminator for everything that must ride with the user's
2006
- * message and nothing else: the reinvocation prompt shape (below) and the
2007
- * vision payload (images accompany the message; a resumed agent already holds
2008
- * them in its native conversation).
2031
+ * Discriminates the two surfaces that depend on the agent already holding
2032
+ * this turn's content natively the prompt shape (below) and the primary
2033
+ * send's vision payload but never alone: both pair it with
2034
+ * `resolution.reason`, because a FRESH agent mid-HITL holds nothing and
2035
+ * needs the full re-delivery (issue #366).
2009
2036
  */
2010
2037
  export function isHitlReinvocation(approvalDecisions) {
2011
2038
  return approvalDecisions !== undefined && approvalDecisions.size > 0;
2012
2039
  }
2040
+ /**
2041
+ * Whether the PRIMARY send delivers the turn's vision payload. The invariant
2042
+ * is "images accompany the user's turn message, wherever the conversation
2043
+ * does not already hold them" — so the only send that skips them is a HITL
2044
+ * re-invocation of a successfully RESUMED agent, whose native conversation
2045
+ * carries the images from the original send. A fresh agent mid-HITL
2046
+ * (resolution-time resume failure — issue #366's vision corollary) holds
2047
+ * nothing and needs the re-delivery. The mid-send recovery retries always
2048
+ * run on a fresh agent, so their send sites carry the payload
2049
+ * unconditionally rather than consulting this.
2050
+ */
2051
+ export function primarySendCarriesImages(approvalDecisions, reason) {
2052
+ return !(isHitlReinvocation(approvalDecisions) && reason === "resumed_successfully");
2053
+ }
2054
+ /**
2055
+ * Append the structured-output contract to a prompt when the execution
2056
+ * requests one. A per-turn directive (the buildFromPlan rule): it must ride
2057
+ * every prompt this turn sends — the primary send AND the poisoned-handle
2058
+ * recovery rebuild, which previously lost it (issue #366 ride-along).
2059
+ */
2060
+ export function appendStructuredOutputDirective(basePrompt, schema) {
2061
+ if (!schema)
2062
+ return basePrompt;
2063
+ const schemaStr = JSON.stringify(schema, null, 2);
2064
+ 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.`;
2065
+ }
2013
2066
  export function buildPrompt(input) {
2014
- const { resolution, approvalDecisions, instructions, userMessage, skills, subAgents, workspaceDirs, workspaceFileRefs, attachmentPaths, interactionMode, buildFromPlan, conversationCatchup, } = input;
2015
- // HITL reinvocation: the agent is resumed, so its native context carries the
2016
- // prior conversation; the reinvocation prompt conveys the approval decisions
2017
- // (and which approved writes the runner already exact-applied).
2067
+ const { resolution, approvalDecisions, instructions, userMessage, skills, subAgents, workspaceDirs, workspaceFileRefs, attachments, interactionMode, buildFromPlan, conversationCatchup, } = input;
2068
+ // HITL reinvocation: the decisions-only prompt is correct ONLY while the
2069
+ // agent's native context still carries the prior conversation which only
2070
+ // resumed_successfully guarantees. Any other reason means a fresh agent
2071
+ // mid-HITL (in practice created_after_resume_failure: the stored handle
2072
+ // failed to resume, or a poisoned handle was replaced mid-send), which
2073
+ // gets the full recovery shape instead — enhanced context + the turn's
2074
+ // recorded transcript + the same decisions — because the bare decisions on
2075
+ // an empty conversation strand the agent with instructions and no story,
2076
+ // and the session inherits that amnesia permanently (issue #366).
2018
2077
  if (isHitlReinvocation(approvalDecisions)) {
2078
+ if (resolution.reason !== "resumed_successfully") {
2079
+ return buildHitlRecoveryPrompt({
2080
+ instructions,
2081
+ userMessage,
2082
+ skills,
2083
+ datastoreUsages: input.datastoreUsages ?? [],
2084
+ channelMessaging: input.channelMessaging ?? [],
2085
+ subAgents,
2086
+ workspaceDirs,
2087
+ workspaceFileRefs,
2088
+ attachments,
2089
+ vision: input.vision,
2090
+ interactionMode,
2091
+ buildFromPlan,
2092
+ contextBridge: input.contextBridge,
2093
+ senderIdentity: input.senderIdentity,
2094
+ sessionContext: input.sessionContext,
2095
+ conversationCatchup,
2096
+ }, {
2097
+ turnDigest: input.turnRecoveryDigest,
2098
+ pendingApprovals: input.pendingApprovals,
2099
+ approvalDecisions,
2100
+ appliedToolCallIds: input.appliedToolCallIds,
2101
+ });
2102
+ }
2019
2103
  return buildReinvocationPrompt(input.pendingApprovals, approvalDecisions, input.appliedToolCallIds);
2020
2104
  }
2021
2105
  // A successfully resumed agent carries its own conversation context via the
@@ -2035,9 +2119,9 @@ export function buildPrompt(input) {
2035
2119
  if (resolution.reason === "resumed_successfully") {
2036
2120
  const prefixes = [
2037
2121
  formatInteractionModePrefix(interactionMode),
2038
- formatImplementPlanSection(buildFromPlan, attachmentPaths),
2039
- attachmentPaths.length > 0
2040
- ? formatInputFiles(attachmentPaths, input.vision)
2122
+ formatImplementPlanSection(buildFromPlan, attachments),
2123
+ attachments.length > 0
2124
+ ? formatInputFiles(attachments, input.vision)
2041
2125
  : undefined,
2042
2126
  conversationCatchup !== undefined
2043
2127
  ? formatConversationCatchupSection(conversationCatchup)
@@ -2059,7 +2143,7 @@ export function buildPrompt(input) {
2059
2143
  subAgents,
2060
2144
  workspaceDirs,
2061
2145
  workspaceFileRefs,
2062
- attachmentPaths,
2146
+ attachments,
2063
2147
  vision: input.vision,
2064
2148
  interactionMode,
2065
2149
  buildFromPlan,