@stigmer/runner 3.0.9-dev.20260616060535 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
  3. package/dist/__test-utils__/approval-contract/types.js +24 -0
  4. package/dist/__test-utils__/approval-contract/types.js.map +1 -0
  5. package/dist/activities/call-agent-status.d.ts +19 -1
  6. package/dist/activities/call-agent-status.js +64 -5
  7. package/dist/activities/call-agent-status.js.map +1 -1
  8. package/dist/activities/call-llm.js +19 -53
  9. package/dist/activities/call-llm.js.map +1 -1
  10. package/dist/activities/classify-tool-approvals.d.ts +24 -0
  11. package/dist/activities/classify-tool-approvals.js +69 -17
  12. package/dist/activities/classify-tool-approvals.js.map +1 -1
  13. package/dist/activities/discover-mcp-server.d.ts +7 -0
  14. package/dist/activities/discover-mcp-server.js +11 -1
  15. package/dist/activities/discover-mcp-server.js.map +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
  17. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
  18. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
  19. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
  20. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
  21. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
  22. package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
  23. package/dist/activities/execute-cursor/approval-policy.js +8 -120
  24. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  25. package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
  26. package/dist/activities/execute-cursor/approval-state.js +233 -17
  27. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  28. package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
  29. package/dist/activities/execute-cursor/capture-flow.js +234 -0
  30. package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
  31. package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
  32. package/dist/activities/execute-cursor/cas-observations.js +184 -0
  33. package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
  34. package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
  35. package/dist/activities/execute-cursor/command-provenance.js +128 -0
  36. package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
  37. package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
  38. package/dist/activities/execute-cursor/exact-apply.js +204 -0
  39. package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
  40. package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
  41. package/dist/activities/execute-cursor/hook-script.js +310 -47
  42. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  43. package/dist/activities/execute-cursor/index.d.ts +8 -1
  44. package/dist/activities/execute-cursor/index.js +500 -64
  45. package/dist/activities/execute-cursor/index.js.map +1 -1
  46. package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
  47. package/dist/activities/execute-cursor/message-translator.js +836 -90
  48. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  49. package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
  50. package/dist/activities/execute-cursor/prompt-builder.js +74 -15
  51. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  52. package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
  53. package/dist/activities/execute-cursor/workspace-setup.js +212 -33
  54. package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
  55. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
  56. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
  57. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
  58. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
  59. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
  60. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
  61. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
  62. package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
  63. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
  64. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
  65. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
  66. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
  68. package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
  69. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
  70. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
  71. package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
  72. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
  73. package/dist/activities/execute-deep-agent/hitl.d.ts +10 -0
  74. package/dist/activities/execute-deep-agent/hitl.js +5 -1
  75. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  76. package/dist/activities/execute-deep-agent/index.d.ts +2 -1
  77. package/dist/activities/execute-deep-agent/index.js +370 -56
  78. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  79. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
  80. package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
  81. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  82. package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
  83. package/dist/activities/execute-deep-agent/setup.js +149 -92
  84. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  85. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
  86. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
  87. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
  88. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
  89. package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
  90. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  91. package/dist/activities/execute-deep-agent/status-builder.d.ts +11 -5
  92. package/dist/activities/execute-deep-agent/status-builder.js +6 -2
  93. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  94. package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
  95. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/streaming.js +3 -15
  97. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  98. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
  99. package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
  100. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  101. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
  102. package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
  103. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  104. package/dist/activities/execute-deep-agent/v3-status-builder.js +6 -2
  105. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  106. package/dist/claimcheck/payload-codec.js +9 -5
  107. package/dist/claimcheck/payload-codec.js.map +1 -1
  108. package/dist/client/stigmer-client.d.ts +2 -0
  109. package/dist/client/stigmer-client.js +2 -0
  110. package/dist/client/stigmer-client.js.map +1 -1
  111. package/dist/middleware/approval-gate.d.ts +85 -4
  112. package/dist/middleware/approval-gate.js +165 -38
  113. package/dist/middleware/approval-gate.js.map +1 -1
  114. package/dist/middleware/types.d.ts +2 -5
  115. package/dist/shared/activity-input.d.ts +43 -0
  116. package/dist/shared/activity-input.js +17 -0
  117. package/dist/shared/activity-input.js.map +1 -0
  118. package/dist/shared/approval-canonicalize.d.ts +19 -0
  119. package/dist/shared/approval-canonicalize.js +119 -0
  120. package/dist/shared/approval-canonicalize.js.map +1 -0
  121. package/dist/shared/approval-fingerprint.d.ts +106 -0
  122. package/dist/shared/approval-fingerprint.js +113 -0
  123. package/dist/shared/approval-fingerprint.js.map +1 -0
  124. package/dist/shared/approval-policy.d.ts +182 -12
  125. package/dist/shared/approval-policy.js +213 -27
  126. package/dist/shared/approval-policy.js.map +1 -1
  127. package/dist/shared/args-preview.d.ts +52 -0
  128. package/dist/shared/args-preview.js +93 -0
  129. package/dist/shared/args-preview.js.map +1 -0
  130. package/dist/shared/artifact-storage.d.ts +19 -1
  131. package/dist/shared/artifact-storage.js +48 -11
  132. package/dist/shared/artifact-storage.js.map +1 -1
  133. package/dist/shared/file-change.d.ts +44 -0
  134. package/dist/shared/file-change.js +57 -0
  135. package/dist/shared/file-change.js.map +1 -0
  136. package/dist/shared/file-tools.d.ts +107 -0
  137. package/dist/shared/file-tools.js +168 -0
  138. package/dist/shared/file-tools.js.map +1 -0
  139. package/dist/shared/filereview/capture.d.ts +202 -0
  140. package/dist/shared/filereview/capture.js +498 -0
  141. package/dist/shared/filereview/capture.js.map +1 -0
  142. package/dist/shared/filereview/cas-substrate.d.ts +190 -0
  143. package/dist/shared/filereview/cas-substrate.js +284 -0
  144. package/dist/shared/filereview/cas-substrate.js.map +1 -0
  145. package/dist/shared/filereview/digest.d.ts +40 -0
  146. package/dist/shared/filereview/digest.js +66 -0
  147. package/dist/shared/filereview/digest.js.map +1 -0
  148. package/dist/shared/filereview/events.d.ts +170 -0
  149. package/dist/shared/filereview/events.js +298 -0
  150. package/dist/shared/filereview/events.js.map +1 -0
  151. package/dist/shared/filereview/git-substrate.d.ts +175 -0
  152. package/dist/shared/filereview/git-substrate.js +439 -0
  153. package/dist/shared/filereview/git-substrate.js.map +1 -0
  154. package/dist/shared/filereview/index.d.ts +11 -0
  155. package/dist/shared/filereview/index.js +12 -0
  156. package/dist/shared/filereview/index.js.map +1 -0
  157. package/dist/shared/filereview/secret-paths.d.ts +63 -0
  158. package/dist/shared/filereview/secret-paths.js +105 -0
  159. package/dist/shared/filereview/secret-paths.js.map +1 -0
  160. package/dist/shared/fingerprint-secret.d.ts +26 -0
  161. package/dist/shared/fingerprint-secret.js +47 -0
  162. package/dist/shared/fingerprint-secret.js.map +1 -0
  163. package/dist/shared/model-client.d.ts +51 -0
  164. package/dist/shared/model-client.js +77 -0
  165. package/dist/shared/model-client.js.map +1 -0
  166. package/dist/shared/plan-artifact.js +0 -2
  167. package/dist/shared/plan-artifact.js.map +1 -1
  168. package/dist/shared/status-offload.d.ts +83 -9
  169. package/dist/shared/status-offload.js +399 -79
  170. package/dist/shared/status-offload.js.map +1 -1
  171. package/dist/shared/status.js +14 -1
  172. package/dist/shared/status.js.map +1 -1
  173. package/dist/shared/tool-kind.d.ts +19 -0
  174. package/dist/shared/tool-kind.js +13 -0
  175. package/dist/shared/tool-kind.js.map +1 -1
  176. package/dist/shared/tool-row.d.ts +88 -0
  177. package/dist/shared/tool-row.js +127 -0
  178. package/dist/shared/tool-row.js.map +1 -0
  179. package/dist/shared/workspace/platform-dir.d.ts +25 -0
  180. package/dist/shared/workspace/platform-dir.js +38 -2
  181. package/dist/shared/workspace/platform-dir.js.map +1 -1
  182. package/dist/workflows/call-agent-orchestrator.js +56 -7
  183. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  184. package/dist/workflows/connect-mcp-server.d.ts +50 -0
  185. package/dist/workflows/connect-mcp-server.js +136 -15
  186. package/dist/workflows/connect-mcp-server.js.map +1 -1
  187. package/dist/workflows/types.d.ts +8 -0
  188. package/package.json +2 -2
  189. package/src/__test-utils__/approval-contract/contract.ts +224 -0
  190. package/src/__test-utils__/approval-contract/types.ts +179 -0
  191. package/src/__test-utils__/fake-artifact-storage.ts +72 -0
  192. package/src/__tests__/approval-gateway-contract.test.ts +29 -0
  193. package/src/__tests__/claimcheck-codec.test.ts +16 -53
  194. package/src/__tests__/golden-e2e.test.ts +2 -0
  195. package/src/__tests__/runner-token-coordinator.test.ts +3 -3
  196. package/src/activities/__tests__/call-agent-status.test.ts +135 -0
  197. package/src/activities/__tests__/call-llm.test.ts +1 -1
  198. package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
  199. package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
  200. package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
  201. package/src/activities/call-agent-status.ts +74 -4
  202. package/src/activities/call-llm.ts +18 -63
  203. package/src/activities/classify-tool-approvals.ts +101 -19
  204. package/src/activities/discover-mcp-server.ts +29 -1
  205. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
  206. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
  207. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
  208. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
  209. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
  210. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
  211. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
  212. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
  213. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
  214. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
  215. package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
  216. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
  217. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
  218. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
  219. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
  220. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
  221. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
  222. package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
  223. package/src/activities/execute-cursor/approval-policy.ts +28 -159
  224. package/src/activities/execute-cursor/approval-state.ts +366 -18
  225. package/src/activities/execute-cursor/capture-flow.ts +323 -0
  226. package/src/activities/execute-cursor/cas-observations.ts +204 -0
  227. package/src/activities/execute-cursor/command-provenance.ts +168 -0
  228. package/src/activities/execute-cursor/exact-apply.ts +253 -0
  229. package/src/activities/execute-cursor/hook-script.ts +317 -51
  230. package/src/activities/execute-cursor/index.ts +575 -67
  231. package/src/activities/execute-cursor/message-translator.ts +963 -89
  232. package/src/activities/execute-cursor/prompt-builder.ts +80 -14
  233. package/src/activities/execute-cursor/workspace-setup.ts +257 -42
  234. package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
  235. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
  236. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
  237. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
  238. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
  239. package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
  240. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
  241. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
  242. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +2 -5
  243. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
  244. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
  245. package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
  246. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +12 -11
  247. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
  248. package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
  249. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
  250. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
  251. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
  252. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
  253. package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
  254. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
  255. package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
  256. package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
  257. package/src/activities/execute-deep-agent/hitl.ts +15 -1
  258. package/src/activities/execute-deep-agent/index.ts +434 -64
  259. package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
  260. package/src/activities/execute-deep-agent/setup.ts +223 -125
  261. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
  262. package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
  263. package/src/activities/execute-deep-agent/status-builder.ts +19 -7
  264. package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
  265. package/src/activities/execute-deep-agent/streaming.ts +3 -13
  266. package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
  267. package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
  268. package/src/activities/execute-deep-agent/v3-status-builder.ts +8 -2
  269. package/src/claimcheck/payload-codec.ts +8 -8
  270. package/src/client/stigmer-client.ts +9 -1
  271. package/src/middleware/__tests__/approval-gate.test.ts +488 -4
  272. package/src/middleware/approval-gate.ts +247 -38
  273. package/src/middleware/types.ts +5 -5
  274. package/src/shared/__tests__/activity-input.test.ts +78 -0
  275. package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
  276. package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
  277. package/src/shared/__tests__/approval-policy.test.ts +274 -40
  278. package/src/shared/__tests__/args-preview.test.ts +78 -0
  279. package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
  280. package/src/shared/__tests__/artifact-storage.test.ts +123 -11
  281. package/src/shared/__tests__/file-change.test.ts +85 -0
  282. package/src/shared/__tests__/file-tools.test.ts +90 -0
  283. package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
  284. package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
  285. package/src/shared/__tests__/model-client.test.ts +162 -0
  286. package/src/shared/__tests__/plan-artifact.test.ts +11 -26
  287. package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
  288. package/src/shared/__tests__/status-offload.test.ts +573 -16
  289. package/src/shared/__tests__/status.test.ts +4 -5
  290. package/src/shared/__tests__/tool-kind.test.ts +24 -1
  291. package/src/shared/__tests__/tool-row.test.ts +221 -0
  292. package/src/shared/activity-input.ts +57 -0
  293. package/src/shared/approval-canonicalize.ts +159 -0
  294. package/src/shared/approval-fingerprint.ts +148 -0
  295. package/src/shared/approval-policy.ts +303 -27
  296. package/src/shared/args-preview.ts +98 -0
  297. package/src/shared/artifact-storage.ts +62 -11
  298. package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
  299. package/src/shared/file-change.ts +64 -0
  300. package/src/shared/file-tools.ts +169 -0
  301. package/src/shared/filereview/__tests__/capture.test.ts +856 -0
  302. package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
  303. package/src/shared/filereview/__tests__/digest.test.ts +100 -0
  304. package/src/shared/filereview/__tests__/events.test.ts +245 -0
  305. package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
  306. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
  307. package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
  308. package/src/shared/filereview/capture.ts +727 -0
  309. package/src/shared/filereview/cas-substrate.ts +401 -0
  310. package/src/shared/filereview/digest.ts +83 -0
  311. package/src/shared/filereview/events.ts +449 -0
  312. package/src/shared/filereview/git-substrate.ts +555 -0
  313. package/src/shared/filereview/index.ts +60 -0
  314. package/src/shared/filereview/secret-paths.ts +121 -0
  315. package/src/shared/fingerprint-secret.ts +53 -0
  316. package/src/shared/model-client.ts +122 -0
  317. package/src/shared/plan-artifact.ts +0 -2
  318. package/src/shared/status-offload.ts +433 -77
  319. package/src/shared/status.ts +13 -0
  320. package/src/shared/tool-kind.ts +33 -0
  321. package/src/shared/tool-row.ts +135 -0
  322. package/src/shared/workspace/platform-dir.ts +41 -2
  323. package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
  324. package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
  325. package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
  326. package/src/workflows/call-agent-orchestrator.ts +53 -6
  327. package/src/workflows/connect-mcp-server.ts +179 -24
  328. package/src/workflows/types.ts +8 -0
@@ -1,4 +1,6 @@
1
1
  /**
2
+ * @regression file-hitl-phase0 — pins file-edit HITL fix #3 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
3
+ *
2
4
  * Unit tests for MessageAccumulator tool call status transitions.
3
5
  *
4
6
  * Validates the indexed tool call tracking that prevents the
@@ -9,12 +11,16 @@
9
11
 
10
12
  import { describe, it, expect } from "vitest";
11
13
  import { create } from "@bufbuild/protobuf";
12
- import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
14
+ import {
15
+ AgentMessageSchema,
16
+ ToolCallSchema,
17
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
13
18
  import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
14
19
  import {
15
20
  MessageType,
16
21
  ToolCallStatus,
17
22
  SubAgentStatus,
23
+ ToolKind,
18
24
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
19
25
  import type { SDKMessage } from "@cursor/sdk";
20
26
  import {
@@ -106,6 +112,40 @@ describe("MessageAccumulator tool call status transitions", () => {
106
112
  expect(tc.completedAt).toBeTruthy();
107
113
  });
108
114
 
115
+ it("persists the Cursor shell {status,value} envelope verbatim on ToolCall.result", () => {
116
+ // The Cursor SDK's built-in Shell tool returns a structured envelope object;
117
+ // the runner stores it as a JSON string (toResultString → JSON.stringify),
118
+ // and the SDK view layer (normalizeShell) unwraps it for display. This locks
119
+ // the persisted shape so the cross-language fixture (result-views.json's
120
+ // cursor_shell_envelope) is grounded in what the runner actually writes,
121
+ // not a guess — the two halves of the bug fix stay in sync.
122
+ const messages: AgentMessage[] = [];
123
+ const acc = new MessageAccumulator(messages);
124
+
125
+ const envelope = {
126
+ status: "success",
127
+ value: {
128
+ exitCode: 0,
129
+ signal: "",
130
+ stdout: "total 8\n",
131
+ stderr: "",
132
+ executionTime: 1176,
133
+ },
134
+ };
135
+
136
+ acc.processEvent(assistantEvent("r1", "Running ls."));
137
+ acc.processEvent(toolCallEvent("tc-shell", "Shell", "running"));
138
+ acc.processEvent(
139
+ toolCallEvent("tc-shell", "Shell", "completed", "run-1", { result: envelope }),
140
+ );
141
+
142
+ const tc = findToolCallById(messages, "tc-shell")!;
143
+ expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
144
+ expect(tc.result).toBe(JSON.stringify(envelope));
145
+ // Round-trips to the original structure (the SDK normalizer reads .value).
146
+ expect(JSON.parse(tc.result)).toEqual(envelope);
147
+ });
148
+
109
149
  it("cross-message completion: tool call completes after new AI message is created", () => {
110
150
  const messages: AgentMessage[] = [];
111
151
  const acc = new MessageAccumulator(messages);
@@ -418,12 +458,18 @@ describe("MessageAccumulator tool call status transitions", () => {
418
458
  expect(sub.messages[1].content).toBe("Here is a summary of AI.");
419
459
  });
420
460
 
421
- it("sub-agent extracts toolCall steps from conversationSteps", () => {
461
+ // Real Cursor task-result shape (verified against production agent_execution
462
+ // blobs): each step is a protobuf-oneof keyed DIRECTLY by kind, and a tool
463
+ // call is { toolCall: { toolCallId, <kind>ToolCall: { args, result } } } whose
464
+ // result is a oneof { success | error | permissionDenied | rejected }. This is
465
+ // the shape that exposed the dropped-tool-calls bug: the prior parser only
466
+ // matched a { type:"toolCall", message } envelope that never occurs.
467
+ it("sub-agent extracts real direct-keyed toolCall steps from conversationSteps", () => {
422
468
  const messages: AgentMessage[] = [];
423
469
  const acc = new MessageAccumulator(messages);
424
470
 
425
471
  const runningEvent = toolCallEvent("tc-sub7", "task", "running", "r1", {
426
- args: { description: "Use tools", prompt: "Run ls" },
472
+ args: { description: "Explore the repo", prompt: "Find the README" },
427
473
  });
428
474
  acc.processEvent(runningEvent);
429
475
  acc.trackSubAgentExecution(runningEvent);
@@ -433,16 +479,36 @@ describe("MessageAccumulator tool call status transitions", () => {
433
479
  status: "success",
434
480
  value: {
435
481
  conversationSteps: [
482
+ { thinkingMessage: { text: "Let me look for the README." } },
436
483
  {
437
- type: "toolCall",
438
- message: {
439
- type: "shell",
440
- args: { command: "ls -la" },
441
- result: { status: "success", value: { stdout: "file.txt", stderr: "", exitCode: 0 } },
484
+ toolCall: {
485
+ toolCallId: "glob-1",
486
+ globToolCall: {
487
+ args: { targetDirectory: "/repo", globPattern: "**/*.md" },
488
+ result: { success: { path: "/repo", files: ["README.md"] } },
489
+ },
442
490
  },
443
491
  },
444
- { type: "assistantMessage", message: { text: "I found file.txt." } },
492
+ {
493
+ toolCall: {
494
+ toolCallId: "read-1",
495
+ readToolCall: {
496
+ args: { path: "/repo/README.md" },
497
+ result: {
498
+ success: {
499
+ content: "# Hello",
500
+ path: "/repo/README.md",
501
+ totalLines: 1,
502
+ fileSize: 7,
503
+ },
504
+ },
505
+ },
506
+ },
507
+ },
508
+ { assistantMessage: { text: "I found and read the README." } },
445
509
  ],
510
+ agentId: "sub-agent-xyz",
511
+ durationMs: 4200,
446
512
  isBackground: false,
447
513
  backgroundReason: "unspecified",
448
514
  },
@@ -452,16 +518,31 @@ describe("MessageAccumulator tool call status transitions", () => {
452
518
  acc.trackSubAgentExecution(completedEvent);
453
519
 
454
520
  const sub = acc.subAgentExecutions[0];
455
- expect(sub.messages).toHaveLength(2);
521
+ // thinking + glob tool call + read tool call + assistant text
522
+ expect(sub.messages).toHaveLength(4);
456
523
 
457
- const toolMsg = sub.messages[0];
458
- expect(toolMsg.type).toBe(MessageType.MESSAGE_AI);
459
- expect(toolMsg.toolCalls).toHaveLength(1);
460
- expect(toolMsg.toolCalls[0].name).toBe("shell");
461
- expect(toolMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
462
-
463
- expect(sub.messages[1].type).toBe(MessageType.MESSAGE_AI);
464
- expect(sub.messages[1].content).toBe("I found file.txt.");
524
+ expect(sub.messages[0].type).toBe(MessageType.MESSAGE_THINKING);
525
+ expect(sub.messages[0].content).toBe("Let me look for the README.");
526
+
527
+ const globMsg = sub.messages[1];
528
+ expect(globMsg.type).toBe(MessageType.MESSAGE_AI);
529
+ expect(globMsg.toolCalls).toHaveLength(1);
530
+ expect(globMsg.toolCalls[0].id).toBe("glob-1");
531
+ expect(globMsg.toolCalls[0].name).toBe("glob");
532
+ expect(globMsg.toolCalls[0].toolKind).toBe(ToolKind.SEARCH);
533
+ expect(globMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
534
+
535
+ const readMsg = sub.messages[2];
536
+ expect(readMsg.type).toBe(MessageType.MESSAGE_AI);
537
+ expect(readMsg.toolCalls).toHaveLength(1);
538
+ expect(readMsg.toolCalls[0].id).toBe("read-1");
539
+ expect(readMsg.toolCalls[0].name).toBe("read");
540
+ expect(readMsg.toolCalls[0].toolKind).toBe(ToolKind.FILE_READ);
541
+ expect(readMsg.toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
542
+ expect(readMsg.toolCalls[0].result).toContain("# Hello");
543
+
544
+ expect(sub.messages[3].type).toBe(MessageType.MESSAGE_AI);
545
+ expect(sub.messages[3].content).toBe("I found and read the README.");
465
546
  });
466
547
 
467
548
  it("sub-agent gracefully handles missing conversationSteps", () => {
@@ -563,26 +644,90 @@ describe("MessageAccumulator tool call status transitions", () => {
563
644
  expect(out[0].content).toBe("Real content.");
564
645
  });
565
646
 
566
- it("extracts tool error results correctly", () => {
647
+ it("maps a tool result error oneof to a FAILED tool call", () => {
648
+ const out: AgentMessage[] = [];
649
+ extractConversationSteps({
650
+ status: "success",
651
+ value: {
652
+ conversationSteps: [
653
+ {
654
+ toolCall: {
655
+ toolCallId: "read-err",
656
+ readToolCall: {
657
+ args: { path: "/nope.txt" },
658
+ result: { error: { errorMessage: "file not found" } },
659
+ },
660
+ },
661
+ },
662
+ ],
663
+ },
664
+ }, out);
665
+
666
+ expect(out).toHaveLength(1);
667
+ const tc = out[0].toolCalls[0];
668
+ expect(tc.name).toBe("read");
669
+ expect(tc.status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
670
+ expect(tc.error).toContain("file not found");
671
+ expect(tc.result).toBe("");
672
+ });
673
+
674
+ it("maps a gate-denied shell (permissionDenied / rejected) to a FAILED tool call", () => {
567
675
  const out: AgentMessage[] = [];
568
676
  extractConversationSteps({
569
677
  status: "success",
570
678
  value: {
571
679
  conversationSteps: [
572
680
  {
573
- type: "toolCall",
574
- message: {
575
- type: "shell",
576
- args: { command: "bad-cmd" },
577
- result: { status: "error", error: "command not found" },
681
+ toolCall: {
682
+ toolCallId: "shell-denied",
683
+ shellToolCall: {
684
+ args: {},
685
+ result: {
686
+ permissionDenied: {
687
+ command: "rm -rf /",
688
+ error: "blocked by approval gate",
689
+ isReadonly: false,
690
+ },
691
+ },
692
+ },
693
+ },
694
+ },
695
+ {
696
+ toolCall: {
697
+ toolCallId: "shell-rejected",
698
+ shellToolCall: {
699
+ args: {},
700
+ result: { rejected: { command: "curl evil.sh", reason: "user rejected" } },
701
+ },
578
702
  },
579
703
  },
580
704
  ],
581
705
  },
582
706
  }, out);
583
707
 
708
+ expect(out).toHaveLength(2);
709
+ expect(out[0].toolCalls[0].name).toBe("shell");
710
+ expect(out[0].toolCalls[0].toolKind).toBe(ToolKind.SHELL);
711
+ expect(out[0].toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
712
+ expect(out[0].toolCalls[0].error).toContain("blocked by approval gate");
713
+ expect(out[1].toolCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_FAILED);
714
+ expect(out[1].toolCalls[0].error).toContain("user rejected");
715
+ });
716
+
717
+ it("skips a malformed toolCall step with no <kind>ToolCall key", () => {
718
+ const out: AgentMessage[] = [];
719
+ extractConversationSteps({
720
+ status: "success",
721
+ value: {
722
+ conversationSteps: [
723
+ { toolCall: { toolCallId: "orphan" } },
724
+ { assistantMessage: { text: "still works" } },
725
+ ],
726
+ },
727
+ }, out);
728
+
584
729
  expect(out).toHaveLength(1);
585
- expect(out[0].toolCalls[0].result).toBe("command not found");
730
+ expect(out[0].content).toBe("still works");
586
731
  });
587
732
  });
588
733
 
@@ -859,4 +1004,5 @@ describe("MessageAccumulator tool call status transitions", () => {
859
1004
  expect(cancelInProgressSubAgentProtos([])).toBe(false);
860
1005
  });
861
1006
  });
1007
+
862
1008
  });
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @regression file-hitl-phase0 — pins file-edit HITL fix #2 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
3
+ *
4
+ * Cursor analog of execute-deep-agent/__tests__/sequential-gate-resume.test.ts:
5
+ * two SEQUENTIAL approval gates across a resume, on the Cursor deny-and-reconcile
6
+ * harness instead of the native in-process gate.
7
+ *
8
+ * The shared failure class both files guard: a resume that rebuilds its transcript
9
+ * from empty drops gate A's already-committed tool-call id, the backend's
10
+ * append-only-at-identity guard rejects the update, pending_approvals projects to
11
+ * 0, and the WAITING_FOR_APPROVAL + pending=0 state makes the workflow watchdog
12
+ * auto-resume — silently skipping (and looping on) gate B.
13
+ *
14
+ * The Cursor specifics this file pins, which the native one cannot:
15
+ * - the resume re-issues gate A with a BRAND-NEW call id (Cursor has no pause
16
+ * primitive; the model re-runs the approved tool), so the fix must reconcile
17
+ * the re-run onto gate A's committed id by canonical identity, not append a
18
+ * duplicate row;
19
+ * - "each tool gates exactly once": gate A, approved on turn 1, must come back
20
+ * COMPLETED on the resume (never WAITING_APPROVAL again), while gate B gates
21
+ * for the first time — and the REAL bash hook must ALLOW A's re-issue while
22
+ * DENYING B, the out-of-process half the native gate has no equivalent for.
23
+ *
24
+ * Exercises the MessageAccumulator + denial reconciliation (the unit that owns
25
+ * the Cursor resume emitter) plus the generated hook, keeping the reproduction
26
+ * hermetic and pinned to the exact code under change.
27
+ */
28
+
29
+ import { describe, it, expect } from "vitest";
30
+ import { create, clone } from "@bufbuild/protobuf";
31
+ import {
32
+ AgentMessageSchema,
33
+ ToolCallSchema,
34
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
35
+ import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
36
+ import {
37
+ ApprovalAction,
38
+ MessageType,
39
+ ToolCallStatus,
40
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
41
+ import type { SDKMessage } from "@cursor/sdk";
42
+
43
+ import {
44
+ MessageAccumulator,
45
+ reconcileDeniedToolCalls,
46
+ toolCallIdentityToken,
47
+ } from "../message-translator.js";
48
+ import {
49
+ buildApprovalGrants,
50
+ buildApprovalState,
51
+ reconstructAdjudicatedApprovals,
52
+ } from "../approval-state.js";
53
+ import type { DeniedLedgerEntry } from "../approval-state.js";
54
+ import {
55
+ setupCursorHookHarness,
56
+ hasBash,
57
+ hookWrite,
58
+ hookShell,
59
+ } from "../__test-utils__/cursor-hook-harness.js";
60
+
61
+ const GATE_A_ID = "tool_edit_a";
62
+ const GATE_A_PATH = "/work/a.txt";
63
+ const GATE_B_ID = "tool_shell_b";
64
+ const GATE_B_CMD = "make build";
65
+
66
+ function builtInEvent(
67
+ callId: string,
68
+ name: string,
69
+ status: "running" | "completed",
70
+ args: Record<string, unknown>,
71
+ result?: unknown,
72
+ ): Extract<SDKMessage, { type: "tool_call" }> {
73
+ return { type: "tool_call", agent_id: "agent-1", run_id: "r2", call_id: callId, name, status, args, result };
74
+ }
75
+
76
+ function assistantEvent(text: string): Extract<SDKMessage, { type: "assistant" }> {
77
+ return {
78
+ type: "assistant",
79
+ agent_id: "agent-1",
80
+ run_id: "r2",
81
+ message: { role: "assistant", content: [{ type: "text", text }] },
82
+ };
83
+ }
84
+
85
+ /** Run-1 transcript: gate A (edit) committed and APPROVED by the user. */
86
+ function approvedGateA(): AgentMessage[] {
87
+ return [
88
+ create(AgentMessageSchema, {
89
+ type: MessageType.MESSAGE_AI,
90
+ content: "I'll write file A, then build.",
91
+ timestamp: "2026-06-20T00:00:01.000Z",
92
+ toolCalls: [
93
+ create(ToolCallSchema, {
94
+ id: GATE_A_ID,
95
+ name: "edit",
96
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
97
+ requiresApproval: true,
98
+ approvalAction: ApprovalAction.APPROVE,
99
+ argsPreview: JSON.stringify({ path: GATE_A_PATH, content: "A\n" }),
100
+ }),
101
+ ],
102
+ }),
103
+ ];
104
+ }
105
+
106
+ /**
107
+ * Resume events: gate A is re-issued with a FRESH id and completes (the approved
108
+ * tool finally runs), the agent advances and proposes gate B (shell), which is
109
+ * gated — emitted running, then cut off by the first-denial stop.
110
+ */
111
+ function resumeIntoGateB(): SDKMessage[] {
112
+ return [
113
+ assistantEvent("File A written. Now building."),
114
+ builtInEvent(`${GATE_A_ID}_RESUME`, "edit", "running", { path: GATE_A_PATH, content: "A\n" }),
115
+ builtInEvent(`${GATE_A_ID}_RESUME`, "edit", "completed", { path: GATE_A_PATH, content: "A\n" }, "wrote a.txt"),
116
+ builtInEvent(`${GATE_B_ID}_RESUME`, "shell", "running", { command: GATE_B_CMD }),
117
+ ];
118
+ }
119
+
120
+ function allToolCalls(messages: AgentMessage[]) {
121
+ return messages.flatMap((m) => m.toolCalls);
122
+ }
123
+
124
+ /** Local mirror of the backend append-only-at-identity guard (see hitl-resume-history). */
125
+ function guardRejectionReason(existing: AgentMessage[], incoming: AgentMessage[]): string | undefined {
126
+ if (incoming.length < existing.length) return "would shrink the message transcript";
127
+ const incomingIds = new Set<string>();
128
+ for (const m of incoming) for (const tc of m.toolCalls) if (tc.id) incomingIds.add(tc.id);
129
+ for (const m of existing) {
130
+ for (const tc of m.toolCalls) {
131
+ if (tc.id && !incomingIds.has(tc.id)) return "would drop a previously-committed tool call";
132
+ }
133
+ }
134
+ return undefined;
135
+ }
136
+
137
+ describe("Cursor sequential gates A->B across resume", () => {
138
+ it("preserves gate A (completed) and surfaces gate B exactly once — a superset the guard accepts", async () => {
139
+ const committed = approvedGateA();
140
+ const seeded = committed.map((m) => clone(AgentMessageSchema, m));
141
+
142
+ const acc = new MessageAccumulator(seeded, { seededSubAgents: [] });
143
+ for (const event of resumeIntoGateB()) acc.processEvent(event);
144
+ acc.finalize();
145
+
146
+ // Gate B was denied this turn; its identity comes off the reconciled call.
147
+ const bCall = allToolCalls(seeded).find((tc) => tc.name === "shell")!;
148
+ const ledger: DeniedLedgerEntry[] = [
149
+ { toolName: "shell", token: toolCallIdentityToken(bCall) },
150
+ ];
151
+ await reconcileDeniedToolCalls(seeded, ledger);
152
+
153
+ const tools = allToolCalls(seeded);
154
+ const byId = new Map(tools.map((tc) => [tc.id, tc]));
155
+
156
+ // Superset: gate A's committed id survives the resume (no drop, no re-key).
157
+ expect(byId.has(GATE_A_ID)).toBe(true);
158
+ expect(tools.some((tc) => tc.id === `${GATE_A_ID}_RESUME`)).toBe(false);
159
+
160
+ // Each gates exactly once: A is now COMPLETED (approved + run, never gated
161
+ // again); B is WAITING_APPROVAL, and only once.
162
+ expect(byId.get(GATE_A_ID)!.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
163
+ const waiting = tools.filter((tc) => tc.status === ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
164
+ expect(waiting).toHaveLength(1);
165
+ expect(waiting[0].name).toBe("shell");
166
+
167
+ // No duplicate rows for either gate.
168
+ expect(tools.filter((tc) => tc.name === "edit")).toHaveLength(1);
169
+ expect(tools.filter((tc) => tc.name === "shell")).toHaveLength(1);
170
+
171
+ // The decisive invariant: the resumed transcript is a strict superset, so the
172
+ // backend guard accepts it and gate B's pending is actually persisted.
173
+ expect(guardRejectionReason(committed, seeded)).toBeUndefined();
174
+ });
175
+
176
+ it("the hook ALLOWS gate A's re-issue and DENIES gate B (out-of-process closure)", () => {
177
+ if (!hasBash) return;
178
+
179
+ // Only gate A is approved going into the resume; its grant is what the hook
180
+ // reads. Gate B has no grant yet (it gates for the first time this turn).
181
+ const { pendingApprovals, decisions } = reconstructAdjudicatedApprovals(approvedGateA());
182
+ const grants = buildApprovalGrants(pendingApprovals, decisions);
183
+ const state = buildApprovalState(new Map(), false, new Set(), grants);
184
+
185
+ const harness = setupCursorHookHarness({ grants: state.approvedGrants });
186
+ expect(harness.decide(hookWrite(GATE_A_PATH)).permission).toBe("allow");
187
+ expect(harness.decide(hookShell(GATE_B_CMD)).permission).toBe("deny");
188
+ });
189
+ });
@@ -2,12 +2,25 @@
2
2
  * Unit tests for Cursor MCP image-result normalization.
3
3
  *
4
4
  * The Cursor SDK wraps an MCP tool result as
5
- * { status, value: { content: [ { text:{text} }, { image:{ data, mimeType } } ] } }
6
- * where image `data` is a Node Buffer-JSON ({ type:"Buffer", data:number[] }).
7
- * The translator must re-emit that as the canonical top-level content-block
8
- * array the shared persist-time offload consumes, so a screenshot lands as a
9
- * renderable image ToolCallOutputRef instead of text/plain. These tests pin
10
- * that normalization and confirm it flows end-to-end through the offload.
5
+ * { status, value: { content: [ { text:{text} }, { image:{ data, mimeType? } } ] } }
6
+ * where image `data` is a Node Buffer-JSON ({ type:"Buffer", data:number[] }) at
7
+ * RUNTIME confirmed verbatim from a real cursor-harness get_app_state result
8
+ * (a PNG screenshot, no mimeType field). The SDK's .d.ts types `data` as a
9
+ * string, but the runtime serialization is Buffer-JSON, so the canonical fixture
10
+ * below uses Buffer-JSON; base64 / data:-URL string `data` is also covered.
11
+ *
12
+ * The translator must re-emit the envelope as the canonical top-level
13
+ * content-block array the shared persist-time offload consumes, so a screenshot
14
+ * lands as a renderable image ToolCallOutputRef instead of text/plain. These
15
+ * tests pin that normalization and confirm it flows end-to-end through the
16
+ * offload.
17
+ *
18
+ * Note: this normalization only runs when the SDK delivers `result` as an
19
+ * OBJECT. When it arrives already-serialized as a STRING (observed in
20
+ * production: the whole envelope nested under value.content), the translator
21
+ * passes it through and the shared offload's detectImagePayload is what must
22
+ * recognize it — that string path (including the Buffer-JSON + no-mimeType
23
+ * shape) is guarded in shared/__tests__/status-offload.
11
24
  */
12
25
 
13
26
  import { describe, it, expect, vi } from "vitest";
@@ -15,7 +28,7 @@ import { create } from "@bufbuild/protobuf";
15
28
  import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
16
29
  import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
17
30
  import type { SDKMessage } from "@cursor/sdk";
18
- import type { ArtifactStorage } from "../../../shared/artifact-storage.js";
31
+ import { makeInMemoryArtifactStorage } from "../../../__test-utils__/fake-artifact-storage.js";
19
32
  import {
20
33
  offloadOversizedToolOutputs,
21
34
  detectImagePayload,
@@ -28,17 +41,22 @@ import {
28
41
  } from "../message-translator.js";
29
42
  import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
30
43
 
31
- // PNG signature + a little payload, the way the Cursor SDK serializes bytes.
44
+ // PNG signature + a little payload. PNG_BYTES is how the Cursor SDK delivers
45
+ // image data at runtime (Node Buffer-JSON); PNG_BASE64 is its base64 form, used
46
+ // by the string-`data` variant tests.
32
47
  const PNG_BYTES = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82];
33
48
  const PNG_BASE64 = Buffer.from(PNG_BYTES).toString("base64");
34
49
 
50
+ // The canonical envelope, matching the real runtime shape: image.data is Node
51
+ // Buffer-JSON and there is no mimeType. This is the shape the translator and
52
+ // offload must handle.
35
53
  function cursorImageEnvelope(text = "App=com.example") {
36
54
  return {
37
55
  status: "success",
38
56
  value: {
39
57
  content: [
40
58
  { text: { text } },
41
- { image: { data: { type: "Buffer", data: PNG_BYTES }, mimeType: "image/png" } },
59
+ { image: { data: { type: "Buffer", data: PNG_BYTES } } },
42
60
  ],
43
61
  isError: false,
44
62
  },
@@ -46,7 +64,7 @@ function cursorImageEnvelope(text = "App=com.example") {
46
64
  }
47
65
 
48
66
  describe("canonicalizeImageResult", () => {
49
- it("converts a Cursor image envelope (Buffer-JSON) to the canonical array", () => {
67
+ it("converts the canonical Cursor image envelope (Buffer-JSON) to the canonical array", () => {
50
68
  const out = canonicalizeImageResult(cursorImageEnvelope("App=Slack"));
51
69
  expect(out).toBeDefined();
52
70
  expect(JSON.parse(out!)).toEqual([
@@ -152,14 +170,12 @@ describe("buildToolCallProto image normalization", () => {
152
170
  describe("cursor image flows through the persist-time offload", () => {
153
171
  it("offloads the screenshot as an image ref with no inline bytes", async () => {
154
172
  const uploads: { key: string; contentType?: string }[] = [];
155
- const storage: ArtifactStorage = {
156
- upload: vi.fn(async (key: string, _content: Buffer, contentType?: string) => {
157
- uploads.push({ key, contentType });
158
- return key;
159
- }),
160
- getDownloadUrl: vi.fn(async (key: string) => `https://artifacts.local/${key}`),
161
- exists: vi.fn(async () => true),
162
- };
173
+ const { storage, blobs } = makeInMemoryArtifactStorage({ urlBase: "https://artifacts.local/" });
174
+ storage.upload.mockImplementation(async (key: string, content: Buffer, contentType?: string) => {
175
+ uploads.push({ key, contentType });
176
+ blobs.set(key, Buffer.from(content));
177
+ return key;
178
+ });
163
179
 
164
180
  const event = {
165
181
  type: "tool_call",
@@ -219,13 +235,18 @@ describe("sub-agent image normalization (extractConversationSteps)", () => {
219
235
  result: {
220
236
  status: "success",
221
237
  value: {
238
+ // Real Cursor task-result shape: a conversation step is keyed directly
239
+ // by kind, and a tool call is { toolCall: { toolCallId, <kind>ToolCall:
240
+ // { args, result } } } whose result is a oneof { success | error | ... }.
241
+ // The screenshot rides the `success` branch as the tool's result value.
222
242
  conversationSteps: [
223
243
  {
224
- type: "toolCall",
225
- message: {
226
- type: "get_app_state",
227
- args: {},
228
- result: cursorImageEnvelope("App=SubAgent"),
244
+ toolCall: {
245
+ toolCallId: "sub-img-1",
246
+ mcpToolCall: {
247
+ args: {},
248
+ result: { success: cursorImageEnvelope("App=SubAgent").value },
249
+ },
229
250
  },
230
251
  },
231
252
  ],