@stigmer/runner 3.0.9-dev.20260615153829 → 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 (329) 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 +16 -3
  74. package/dist/activities/execute-deep-agent/hitl.js +11 -3
  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 +393 -33
  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 +17 -5
  92. package/dist/activities/execute-deep-agent/status-builder.js +44 -3
  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 +32 -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 +284 -0
  243. package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +14 -36
  244. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
  245. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
  246. package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
  247. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +91 -10
  248. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
  249. package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
  250. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
  251. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
  252. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
  253. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
  254. package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
  255. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
  256. package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
  257. package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
  258. package/src/activities/execute-deep-agent/hitl.ts +23 -7
  259. package/src/activities/execute-deep-agent/index.ts +463 -40
  260. package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
  261. package/src/activities/execute-deep-agent/setup.ts +223 -125
  262. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
  263. package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
  264. package/src/activities/execute-deep-agent/status-builder.ts +63 -8
  265. package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
  266. package/src/activities/execute-deep-agent/streaming.ts +3 -13
  267. package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
  268. package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
  269. package/src/activities/execute-deep-agent/v3-status-builder.ts +36 -2
  270. package/src/claimcheck/payload-codec.ts +8 -8
  271. package/src/client/stigmer-client.ts +9 -1
  272. package/src/middleware/__tests__/approval-gate.test.ts +488 -4
  273. package/src/middleware/approval-gate.ts +247 -38
  274. package/src/middleware/types.ts +5 -5
  275. package/src/shared/__tests__/activity-input.test.ts +78 -0
  276. package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
  277. package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
  278. package/src/shared/__tests__/approval-policy.test.ts +274 -40
  279. package/src/shared/__tests__/args-preview.test.ts +78 -0
  280. package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
  281. package/src/shared/__tests__/artifact-storage.test.ts +123 -11
  282. package/src/shared/__tests__/file-change.test.ts +85 -0
  283. package/src/shared/__tests__/file-tools.test.ts +90 -0
  284. package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
  285. package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
  286. package/src/shared/__tests__/model-client.test.ts +162 -0
  287. package/src/shared/__tests__/plan-artifact.test.ts +11 -26
  288. package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
  289. package/src/shared/__tests__/status-offload.test.ts +573 -16
  290. package/src/shared/__tests__/status.test.ts +4 -5
  291. package/src/shared/__tests__/tool-kind.test.ts +24 -1
  292. package/src/shared/__tests__/tool-row.test.ts +221 -0
  293. package/src/shared/activity-input.ts +57 -0
  294. package/src/shared/approval-canonicalize.ts +159 -0
  295. package/src/shared/approval-fingerprint.ts +148 -0
  296. package/src/shared/approval-policy.ts +303 -27
  297. package/src/shared/args-preview.ts +98 -0
  298. package/src/shared/artifact-storage.ts +62 -11
  299. package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
  300. package/src/shared/file-change.ts +64 -0
  301. package/src/shared/file-tools.ts +169 -0
  302. package/src/shared/filereview/__tests__/capture.test.ts +856 -0
  303. package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
  304. package/src/shared/filereview/__tests__/digest.test.ts +100 -0
  305. package/src/shared/filereview/__tests__/events.test.ts +245 -0
  306. package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
  307. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
  308. package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
  309. package/src/shared/filereview/capture.ts +727 -0
  310. package/src/shared/filereview/cas-substrate.ts +401 -0
  311. package/src/shared/filereview/digest.ts +83 -0
  312. package/src/shared/filereview/events.ts +449 -0
  313. package/src/shared/filereview/git-substrate.ts +555 -0
  314. package/src/shared/filereview/index.ts +60 -0
  315. package/src/shared/filereview/secret-paths.ts +121 -0
  316. package/src/shared/fingerprint-secret.ts +53 -0
  317. package/src/shared/model-client.ts +122 -0
  318. package/src/shared/plan-artifact.ts +0 -2
  319. package/src/shared/status-offload.ts +433 -77
  320. package/src/shared/status.ts +13 -0
  321. package/src/shared/tool-kind.ts +33 -0
  322. package/src/shared/tool-row.ts +135 -0
  323. package/src/shared/workspace/platform-dir.ts +41 -2
  324. package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
  325. package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
  326. package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
  327. package/src/workflows/call-agent-orchestrator.ts +53 -6
  328. package/src/workflows/connect-mcp-server.ts +179 -24
  329. package/src/workflows/types.ts +8 -0
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Regression: two SEQUENTIAL approval gates under the ephemeral memory
3
+ * checkpointer (the OSS local / desktop default — config.ts resolves
4
+ * `checkpointerType: "memory"`).
5
+ *
6
+ * Unlike the durable (http) checkpointer — which resumes via Command(resume) and
7
+ * re-emits only post-checkpoint events — the memory checkpointer is recreated
8
+ * empty every invocation (factory.ts), so the Temporal workflow's post-approval
9
+ * re-invocation REPLAYS the graph from scratch. The langgraph input is just the
10
+ * original user message (setup.ts), and the blind-FIFO mock LLM advances exactly
11
+ * one gate per invocation, so the replay reaches the NEXT gate (gate B) directly.
12
+ *
13
+ * The defect this pins: on that gate-B re-invocation the live checkpoint is
14
+ * empty, so a status rebuilt from an empty proto emits a transcript that holds
15
+ * ONLY gate B and silently drops gate A's already-committed tool-call id. The
16
+ * server's append-only-at-identity guard (update_status.go
17
+ * nonTerminalTranscriptRegression) then rejects the update and keeps the old
18
+ * messages, but projects pending_approvals from those kept messages — gate A is
19
+ * already decided, so pending=0, and WAITING_FOR_APPROVAL + pending=0 makes the
20
+ * workflow auto-resume, skipping gate B. The fix seeds the status from the
21
+ * persisted transcript whenever the execution already has history, so gate B's
22
+ * update is a superset of gate A rather than a replacement.
23
+ *
24
+ * This test exercises BOTH invocations through the real activity, asserting the
25
+ * gate-A tool call survives into the gate-B update — the single behavior that,
26
+ * if regressed, reintroduces the silent skip.
27
+ */
28
+
29
+ import { describe, it, expect, vi, beforeEach } from "vitest";
30
+ import { makeInMemoryArtifactStorage } from "../../../__test-utils__/fake-artifact-storage.js";
31
+ import { create, clone } from "@bufbuild/protobuf";
32
+ import {
33
+ AgentExecutionStatusSchema,
34
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
35
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
36
+ import {
37
+ AgentMessageSchema,
38
+ ToolCallSchema,
39
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
40
+ import {
41
+ ApprovalAction,
42
+ ExecutionPhase,
43
+ MessageType,
44
+ ToolCallStatus,
45
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
46
+ import type { V3ProtocolEvent } from "../v3-event-recorder.js";
47
+ import type { Config } from "../../../config.js";
48
+ import type { SetupResult } from "../setup.js";
49
+
50
+ // The Temporal activity context is unavailable outside a worker.
51
+ vi.mock("@temporalio/activity", () => ({
52
+ Context: {
53
+ current: () => ({
54
+ cancellationSignal: { aborted: false },
55
+ heartbeat: vi.fn(),
56
+ }),
57
+ },
58
+ CancelledFailure: class CancelledFailure extends Error {},
59
+ }));
60
+
61
+ vi.mock("../../../idle-watchdog.js", () => ({
62
+ activityStarted: vi.fn(),
63
+ activityFinished: vi.fn(),
64
+ }));
65
+
66
+ // Capture every persisted status (the gRPC chokepoint) for assertions.
67
+ const { persistedStatuses } = vi.hoisted(() => ({
68
+ persistedStatuses: [] as AgentExecutionStatus[],
69
+ }));
70
+
71
+ vi.mock("../../../client/stigmer-client.js", () => ({
72
+ StigmerClient: vi.fn().mockImplementation(() => ({
73
+ updateStatus: vi.fn(async (_id: string, status: AgentExecutionStatus) => {
74
+ // Snapshot point-in-time: persistStatus mutates the same object in place.
75
+ persistedStatuses.push(clone(AgentExecutionStatusSchema, status));
76
+ return { signal: 0 };
77
+ }),
78
+ getExecution: vi.fn(),
79
+ })),
80
+ }));
81
+
82
+ // performSetup is mocked per-invocation to return a fake memory-replay SetupResult.
83
+ vi.mock("../setup.js", () => ({ performSetup: vi.fn() }));
84
+
85
+ import { createDeepAgentActivities } from "../index.js";
86
+ import { performSetup } from "../setup.js";
87
+
88
+ const GATE_A_ID = "toolu_gate_a";
89
+ const GATE_B_ID = "toolu_gate_b";
90
+
91
+ function v3Event(
92
+ seq: number,
93
+ method: string,
94
+ data: unknown,
95
+ namespace: string[] = [],
96
+ ): V3ProtocolEvent {
97
+ return {
98
+ type: "event",
99
+ seq,
100
+ method,
101
+ params: { namespace, timestamp: Date.now(), data },
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Events for a replayed turn that proposes a gated tool but pauses BEFORE
107
+ * execution. The model turn commits no transcript message (no text/tool deltas);
108
+ * the gated tool call is authored downstream by the activity from the pending
109
+ * interrupt, matching the live e2e's single-tool gate update. Two events keep the
110
+ * v3 loop from erroring on an empty stream.
111
+ */
112
+ function gateTurnEvents(): V3ProtocolEvent[] {
113
+ return [
114
+ v3Event(0, "messages", { event: "message-start", run_id: "g1" }),
115
+ v3Event(1, "messages", { event: "message-finish", run_id: "g1" }),
116
+ ];
117
+ }
118
+
119
+ function mockV3Run(events: V3ProtocolEvent[]) {
120
+ return {
121
+ [Symbol.asyncIterator]: async function* () {
122
+ for (const e of events) yield e;
123
+ },
124
+ output: Promise.resolve({ messages: [] }),
125
+ abort: vi.fn(),
126
+ signal: new AbortController().signal,
127
+ };
128
+ }
129
+
130
+ /** The empty live checkpoint a freshly-recreated MemorySaver reports pre-stream. */
131
+ const EMPTY_REPLAY_STATE = { values: { messages: [] as unknown[] }, tasks: [] };
132
+
133
+ /** The paused graph state after the replayed stream reaches the next gate. */
134
+ function gatedGraphState(toolCallId: string, toolName: string, filePath: string) {
135
+ return {
136
+ values: {
137
+ messages: [{
138
+ role: "assistant",
139
+ content: "",
140
+ tool_calls: [{ id: toolCallId, name: toolName, args: { file_path: filePath, content: "x\n" } }],
141
+ }],
142
+ },
143
+ tasks: [{
144
+ id: "task-gate",
145
+ interrupts: [{
146
+ value: {
147
+ tool_call_id: toolCallId,
148
+ tool_name: toolName,
149
+ message: `Execute ${toolName}`,
150
+ },
151
+ resumeValue: undefined,
152
+ }],
153
+ }],
154
+ };
155
+ }
156
+
157
+ /**
158
+ * A memory-checkpointer invocation that replays from scratch and re-pauses at a
159
+ * single gate: getState reports an empty live checkpoint before the stream and a
160
+ * pending interrupt after it.
161
+ */
162
+ function fakeMemoryGateSetup(opts: {
163
+ gateId: string;
164
+ gateName: string;
165
+ filePath: string;
166
+ executionStatus: AgentExecutionStatus;
167
+ }): SetupResult {
168
+ let streamed = false;
169
+ const gated = gatedGraphState(opts.gateId, opts.gateName, opts.filePath);
170
+
171
+ const agentGraph = {
172
+ getState: vi.fn(async () => (streamed ? gated : EMPTY_REPLAY_STATE)),
173
+ streamEvents: vi.fn(async () => {
174
+ streamed = true;
175
+ return mockV3Run(gateTurnEvents());
176
+ }),
177
+ };
178
+
179
+ return {
180
+ agentGraph,
181
+ langgraphConfig: { configurable: { thread_id: "thread-seq" } },
182
+ langgraphInput: { messages: [{ role: "user", content: "Write A then B" }] },
183
+ execution: {
184
+ spec: { message: "Write file A, then write file B." },
185
+ status: opts.executionStatus,
186
+ },
187
+ agent: {},
188
+ session: { spec: { workspaceEntries: [] } },
189
+ workspaceBackend: {
190
+ rootDir: "/tmp/ws",
191
+ exists: vi.fn(async () => false),
192
+ readFile: vi.fn(async () => ""),
193
+ },
194
+ mcpConnection: null,
195
+ mergedEnvVars: {},
196
+ secretKeys: new Set<string>(),
197
+ modelName: "claude-sonnet",
198
+ gracefulStop: undefined,
199
+ artifactStorage: makeInMemoryArtifactStorage({ urlBase: "https://artifacts.local/" }).storage,
200
+ provisionResults: [],
201
+ approvalPolicies: new Map(),
202
+ toolServerMap: new Map(),
203
+ leasedCategories: new Set(),
204
+ globalBypass: false,
205
+ hasStructuredOutput: false,
206
+ streamVersion: "v3",
207
+ } as unknown as SetupResult;
208
+ }
209
+
210
+ /** Run-1 transcript persisted on the execution, with the user's APPROVE recorded. */
211
+ function approvedGateAStatus(): AgentExecutionStatus {
212
+ return create(AgentExecutionStatusSchema, {
213
+ phase: ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
214
+ startedAt: "2026-06-20T00:00:00.000Z",
215
+ messages: [
216
+ create(AgentMessageSchema, {
217
+ type: MessageType.MESSAGE_AI,
218
+ content: "",
219
+ timestamp: "2026-06-20T00:00:01.000Z",
220
+ toolCalls: [
221
+ create(ToolCallSchema, {
222
+ id: GATE_A_ID,
223
+ name: "write_file",
224
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
225
+ requiresApproval: true,
226
+ approvalMessage: "Execute write_file",
227
+ // The user approved gate A; SubmitApproval records the decision on
228
+ // the persisted transcript (status stays WAITING_APPROVAL until the
229
+ // resumed tool runs), which is what re-invokes the activity.
230
+ approvalAction: ApprovalAction.APPROVE,
231
+ }),
232
+ ],
233
+ }),
234
+ ],
235
+ });
236
+ }
237
+
238
+ const memoryConfig: Config = {
239
+ taskQueue: "test-queue",
240
+ temporalAddress: "localhost:7233",
241
+ temporalNamespace: "default",
242
+ stigmerBackendEndpoint: "http://localhost:7234",
243
+ stigmerToken: null,
244
+ cursorApiKey: "",
245
+ workspaceRootDir: "/tmp/test",
246
+ mode: "local",
247
+ proxyEndpoint: null,
248
+ maxConcurrentActivities: 5,
249
+ idleTimeoutSeconds: null,
250
+ cloudModeEnabled: false,
251
+ checkpointerType: "memory",
252
+ checkpointerProxyEndpoint: null,
253
+ primaryModel: "claude-sonnet",
254
+ cursorStreamStallTimeoutMs: 180000,
255
+ };
256
+
257
+ function toolCallIds(status: AgentExecutionStatus): string[] {
258
+ return status.messages.flatMap(m => m.toolCalls).map(tc => tc.id);
259
+ }
260
+
261
+ describe("ExecuteDeepAgent — sequential gates under the memory checkpointer", () => {
262
+ beforeEach(() => {
263
+ persistedStatuses.length = 0;
264
+ vi.mocked(performSetup).mockReset();
265
+ });
266
+
267
+ it("preserves gate A's committed tool call when resuming into gate B", async () => {
268
+ const activities = createDeepAgentActivities(memoryConfig);
269
+
270
+ // ── Invocation 1: first run reaches gate A (no prior history to seed). ──
271
+ vi.mocked(performSetup).mockResolvedValueOnce(
272
+ fakeMemoryGateSetup({
273
+ gateId: GATE_A_ID,
274
+ gateName: "write_file",
275
+ filePath: "a.txt",
276
+ executionStatus: create(AgentExecutionStatusSchema, {}),
277
+ }),
278
+ );
279
+ await activities.ExecuteDeepAgent("exec-seq", "thread-seq");
280
+
281
+ const afterGateA = persistedStatuses.at(-1)!;
282
+ expect(afterGateA.phase).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
283
+ expect(toolCallIds(afterGateA)).toContain(GATE_A_ID);
284
+
285
+ // ── User approves gate A; the server records the decision on the persisted
286
+ // transcript. The next invocation re-invocation reads this status. ──
287
+ const approved = clone(AgentExecutionStatusSchema, afterGateA);
288
+ for (const m of approved.messages) {
289
+ for (const tc of m.toolCalls) {
290
+ if (tc.id === GATE_A_ID) tc.approvalAction = ApprovalAction.APPROVE;
291
+ }
292
+ }
293
+
294
+ // ── Invocation 2: re-invoked after approval. The memory checkpointer replays
295
+ // from scratch (empty live checkpoint) and the mock serves gate B. ──
296
+ persistedStatuses.length = 0;
297
+ vi.mocked(performSetup).mockResolvedValueOnce(
298
+ fakeMemoryGateSetup({
299
+ gateId: GATE_B_ID,
300
+ gateName: "write_file",
301
+ filePath: "b.txt",
302
+ executionStatus: approved,
303
+ }),
304
+ );
305
+ await activities.ExecuteDeepAgent("exec-seq", "thread-seq");
306
+
307
+ const afterGateB = persistedStatuses.at(-1)!;
308
+ const ids = toolCallIds(afterGateB);
309
+
310
+ // Gate B is the new pending approval.
311
+ expect(afterGateB.phase).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
312
+ expect(ids.filter(id => id === GATE_B_ID)).toHaveLength(1);
313
+
314
+ // The regression guard: gate A's committed tool call survives the resume.
315
+ // Dropping it lets the server guard reject the update and project pending=0,
316
+ // which auto-resumes the workflow and silently skips gate B.
317
+ expect(ids).toContain(GATE_A_ID);
318
+ });
319
+
320
+ /** A status that is a strict superset of the persisted transcript is what the
321
+ * server's append-only-at-identity guard accepts (no committed id dropped). */
322
+ it("emits a gate-B transcript that is a superset of the persisted gate-A transcript", async () => {
323
+ const activities = createDeepAgentActivities(memoryConfig);
324
+
325
+ const approved = approvedGateAStatus();
326
+ const priorIds = toolCallIds(approved);
327
+
328
+ vi.mocked(performSetup).mockResolvedValueOnce(
329
+ fakeMemoryGateSetup({
330
+ gateId: GATE_B_ID,
331
+ gateName: "write_file",
332
+ filePath: "b.txt",
333
+ executionStatus: approved,
334
+ }),
335
+ );
336
+ await activities.ExecuteDeepAgent("exec-seq", "thread-seq");
337
+
338
+ const afterGateB = persistedStatuses.at(-1)!;
339
+ const ids = new Set(toolCallIds(afterGateB));
340
+
341
+ // Every previously-committed tool-call id is still present (no drop), and the
342
+ // transcript did not shrink — exactly the two regressions the server guard
343
+ // rejects (update_status.go nonTerminalTranscriptRegression).
344
+ for (const id of priorIds) {
345
+ expect(ids.has(id)).toBe(true);
346
+ }
347
+ expect(afterGateB.messages.length).toBeGreaterThanOrEqual(approved.messages.length);
348
+ });
349
+ });
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Unit tests for the deep-agent turn-boundary flowed-row stamping
3
+ * (`stamp-flowed-rows.ts`), with emphasis on the sub-agent path added by the
4
+ * DD-24 follow-up: sub-agent edit rows fold into the parent turn's change set,
5
+ * so they carry the parent change set id, scoped to this turn by tool-call-id
6
+ * novelty (they lack the already-stamped/hidden shields the top-level pass has).
7
+ */
8
+
9
+ import { describe, it, expect } from "vitest";
10
+ import { create } from "@bufbuild/protobuf";
11
+ import {
12
+ AgentMessageSchema,
13
+ ToolCallSchema,
14
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
15
+ import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
16
+ import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
17
+ import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
18
+ import { collectSubAgentToolCallIds } from "../../../shared/tool-row.js";
19
+ import {
20
+ stampFlowedFileEditRows,
21
+ stampFlowedSubAgentFileEditRows,
22
+ } from "../stamp-flowed-rows.js";
23
+
24
+ const CHANGE_SET_ID = "exec-1:0";
25
+
26
+ /** A streamed (COMPLETED) file-edit tool call, as the deep-agent would record. */
27
+ function editMessage(id: string, path: string, name = "write_file"): AgentMessage {
28
+ return create(AgentMessageSchema, {
29
+ type: 1, // MESSAGE_AI
30
+ toolCalls: [
31
+ create(ToolCallSchema, {
32
+ id,
33
+ name,
34
+ status: ToolCallStatus.TOOL_CALL_COMPLETED,
35
+ args: { file_path: path, content: "x" },
36
+ }),
37
+ ],
38
+ });
39
+ }
40
+
41
+ function subAgent(subId: string, ...messages: AgentMessage[]) {
42
+ return create(SubAgentExecutionSchema, { id: subId, name: "code_editor", messages });
43
+ }
44
+
45
+ describe("stampFlowedFileEditRows (deep-agent)", () => {
46
+ it("stamps a flowed write/delete row and leaves a non-file row untouched", () => {
47
+ const write = editMessage("tc-1", "src/a.ts", "write_file");
48
+ const shell = create(AgentMessageSchema, {
49
+ type: 1,
50
+ toolCalls: [create(ToolCallSchema, { id: "tc-2", name: "shell", status: ToolCallStatus.TOOL_CALL_COMPLETED })],
51
+ });
52
+
53
+ stampFlowedFileEditRows([write, shell], CHANGE_SET_ID);
54
+
55
+ expect(write.toolCalls[0].fileChangeSetId).toBe(CHANGE_SET_ID);
56
+ expect(shell.toolCalls[0].fileChangeSetId).toBe("");
57
+ });
58
+
59
+ it("skips a tool-call id in skipToolCallIds (current-turn scoping)", () => {
60
+ const prior = editMessage("tc-prior", "src/old.ts");
61
+ const current = editMessage("tc-current", "src/new.ts");
62
+
63
+ stampFlowedFileEditRows([prior, current], CHANGE_SET_ID, new Set(["tc-prior"]));
64
+
65
+ expect(prior.toolCalls[0].fileChangeSetId).toBe(""); // skipped
66
+ expect(current.toolCalls[0].fileChangeSetId).toBe(CHANGE_SET_ID);
67
+ });
68
+
69
+ it("never overwrites an already-stamped row", () => {
70
+ const row = editMessage("tc-1", "src/a.ts");
71
+ row.toolCalls[0].fileChangeSetId = "exec-1:prior";
72
+
73
+ stampFlowedFileEditRows([row], CHANGE_SET_ID);
74
+
75
+ expect(row.toolCalls[0].fileChangeSetId).toBe("exec-1:prior");
76
+ });
77
+ });
78
+
79
+ describe("stampFlowedSubAgentFileEditRows (deep-agent)", () => {
80
+ it("stamps a current-turn sub-agent's edit row with the parent change set id", () => {
81
+ const sub = subAgent("sa-1", editMessage("sa-tc-1", "src/sub.ts"));
82
+
83
+ stampFlowedSubAgentFileEditRows([sub], CHANGE_SET_ID, new Set());
84
+
85
+ expect(sub.messages[0].toolCalls[0].fileChangeSetId).toBe(CHANGE_SET_ID);
86
+ });
87
+
88
+ it("stamps a fresh continuation row of a spanning sub-agent, not its prior row", () => {
89
+ // A sub-agent that paused on an internal tool gate spans invocations: its
90
+ // sub-agent id and its earlier row pre-exist on resume, but the continuation
91
+ // edit row carries a fresh tool-call id and belongs to the completing turn.
92
+ const priorRow = editMessage("sa-tc-old", "src/before.ts");
93
+ const spanning = subAgent("sa-span", priorRow);
94
+ const priorToolCallIds = collectSubAgentToolCallIds([spanning]); // {sa-tc-old}
95
+
96
+ // On resume the same sub-agent appends a new edit row.
97
+ spanning.messages.push(editMessage("sa-tc-new", "src/after.ts"));
98
+
99
+ stampFlowedSubAgentFileEditRows([spanning], "exec-1:1", priorToolCallIds);
100
+
101
+ expect(spanning.messages[0].toolCalls[0].fileChangeSetId).toBe(""); // prior row untouched
102
+ expect(spanning.messages[1].toolCalls[0].fileChangeSetId).toBe("exec-1:1"); // continuation stamped
103
+ });
104
+
105
+ it("withholds content for a sub-agent write to a tracked secret-like path (DD-12 D4 inherited)", () => {
106
+ const sub = subAgent("sa-1", editMessage("sa-tc-secret", "config/credentials.json"));
107
+
108
+ stampFlowedSubAgentFileEditRows([sub], CHANGE_SET_ID, new Set());
109
+
110
+ const row = sub.messages[0].toolCalls[0];
111
+ expect(row.fileChangeSetId).toBe(CHANGE_SET_ID); // stamped (path visible)
112
+ // stampFileEditRow normalizes the withheld row to `{ path }` (see tool-row.ts).
113
+ expect(row.args).toEqual({ path: "config/credentials.json" }); // body withheld
114
+ });
115
+
116
+ it("does nothing for no sub-agents", () => {
117
+ expect(() => stampFlowedSubAgentFileEditRows([], CHANGE_SET_ID, new Set())).not.toThrow();
118
+ });
119
+ });
@@ -2,6 +2,11 @@ import { describe, it, expect, vi } from "vitest";
2
2
  import { create } from "@bufbuild/protobuf";
3
3
  import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
4
4
  import {
5
+ AgentMessageSchema,
6
+ ToolCallSchema,
7
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
8
+ import {
9
+ ApprovalAction,
5
10
  ExecutionPhase,
6
11
  MessageType,
7
12
  ToolCallStatus,
@@ -379,7 +384,6 @@ describe("StatusBuilder", () => {
379
384
  kind: 0,
380
385
  sizeBytes: 100n,
381
386
  storageKey: "artifacts/exec-1/report.txt",
382
- downloadUrl: "http://localhost/dl",
383
387
  createdAt: "2026-05-19T12:00:00Z",
384
388
  expiresAt: "",
385
389
  entries: [],
@@ -400,7 +404,6 @@ describe("StatusBuilder", () => {
400
404
  kind: 0,
401
405
  sizeBytes: 10n,
402
406
  storageKey: "k",
403
- downloadUrl: "u",
404
407
  createdAt: "",
405
408
  expiresAt: "",
406
409
  entries: [],
@@ -424,7 +427,6 @@ describe("StatusBuilder", () => {
424
427
  kind: 0,
425
428
  sizeBytes: 10n,
426
429
  storageKey: "k",
427
- downloadUrl: "u",
428
430
  createdAt: "",
429
431
  expiresAt: "",
430
432
  entries: [],
@@ -542,7 +544,7 @@ describe("StatusBuilder", () => {
542
544
  return {
543
545
  policies: new Map(),
544
546
  toolServerMap: new Map(),
545
- autoApproveAll: false,
547
+ globalBypass: false,
546
548
  ...overrides,
547
549
  };
548
550
  }
@@ -559,6 +561,7 @@ describe("StatusBuilder", () => {
559
561
  mcpServerSlug: serverSlug,
560
562
  requiresApproval: true,
561
563
  approvalMessage: message,
564
+ source: "classifier_default",
562
565
  },
563
566
  ];
564
567
  }
@@ -595,13 +598,13 @@ describe("StatusBuilder", () => {
595
598
  expect(sb.currentStatus.phase).toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
596
599
  });
597
600
 
598
- it("does not require approval when autoApproveAll is true", () => {
601
+ it("does not require approval under the global bypass (spec.auto_approve_all)", () => {
599
602
  const sb = makeBuilder();
600
603
  const [key, policy] = policyFor("github", "delete_repo");
601
604
  sb.setApprovalProvider(makeApprovalProvider({
602
605
  policies: new Map([[key, policy]]),
603
606
  toolServerMap: new Map([["delete_repo", "github"]]),
604
- autoApproveAll: true,
607
+ globalBypass: true,
605
608
  }));
606
609
 
607
610
  sb.processEvent(chatStreamEvent("run-1", "deleting"));
@@ -612,11 +615,11 @@ describe("StatusBuilder", () => {
612
615
  expect(tc.requiresApproval).toBe(false);
613
616
  });
614
617
 
615
- it("sets mcpServerSlug even when autoApproveAll is true", () => {
618
+ it("sets mcpServerSlug even under the global bypass (spec.auto_approve_all)", () => {
616
619
  const sb = makeBuilder();
617
620
  sb.setApprovalProvider(makeApprovalProvider({
618
621
  toolServerMap: new Map([["echo", "test-mcp-server"]]),
619
- autoApproveAll: true,
622
+ globalBypass: true,
620
623
  }));
621
624
 
622
625
  sb.processEvent(chatStreamEvent("run-1", "echoing"));
@@ -713,10 +716,11 @@ describe("StatusBuilder", () => {
713
716
  mcpServerSlug: serverSlug,
714
717
  requiresApproval: true,
715
718
  approvalMessage: "Approve?",
719
+ source: "classifier_default",
716
720
  }],
717
721
  ]),
718
722
  toolServerMap: new Map([[toolName, serverSlug]]),
719
- autoApproveAll: false,
723
+ globalBypass: false,
720
724
  });
721
725
  return sb;
722
726
  }
@@ -1566,10 +1570,11 @@ describe("StatusBuilder", () => {
1566
1570
  mcpServerSlug: "github",
1567
1571
  requiresApproval: true,
1568
1572
  approvalMessage: "Create PR '{{args.title}}' in {{args.repo}}?",
1573
+ source: "classifier_default",
1569
1574
  }],
1570
1575
  ]),
1571
1576
  toolServerMap: new Map([["create_pull_request", "github"]]),
1572
- autoApproveAll: false,
1577
+ globalBypass: false,
1573
1578
  });
1574
1579
 
1575
1580
  // Model streams text then calls the approval-gated tool
@@ -1771,4 +1776,80 @@ describe("StatusBuilder", () => {
1771
1776
  });
1772
1777
  });
1773
1778
  });
1779
+
1780
+ // Durable-checkpoint resume on the legacy v2 path. The activity seeds the
1781
+ // builder from the persisted transcript (see seedStatusFromExecution in
1782
+ // index.ts); the resumed stream re-emits the gated tool's lifecycle with a
1783
+ // FRESH LangGraph run_id that does not match the seeded tool_call_id. The
1784
+ // builder must reconcile by name so the existing call is resolved in place,
1785
+ // preserving history with no duplicate (the documented v2 fallback; v3, the
1786
+ // default, matches by the exact tool_call_id).
1787
+ describe("resume reconciliation (durable checkpoint)", () => {
1788
+ const GATED_ID = "toolu_seeded_01";
1789
+
1790
+ function seededBuilder(): StatusBuilder {
1791
+ const status = create(AgentExecutionStatusSchema, {
1792
+ phase: ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
1793
+ messages: [
1794
+ create(AgentMessageSchema, {
1795
+ type: MessageType.MESSAGE_AI,
1796
+ content: "I'll call the gated tool.",
1797
+ toolCalls: [
1798
+ create(ToolCallSchema, {
1799
+ id: GATED_ID,
1800
+ name: "dangerous_tool",
1801
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
1802
+ requiresApproval: true,
1803
+ approvalAction: ApprovalAction.APPROVE,
1804
+ mcpServerSlug: "my-server",
1805
+ }),
1806
+ ],
1807
+ }),
1808
+ ],
1809
+ });
1810
+ const sb = new StatusBuilder("exec-resume", status);
1811
+ sb.setApprovalProvider({
1812
+ policies: new Map([
1813
+ ["my-server/dangerous_tool", {
1814
+ toolName: "dangerous_tool",
1815
+ mcpServerSlug: "my-server",
1816
+ requiresApproval: true,
1817
+ approvalMessage: "Execute dangerous_tool",
1818
+ source: "classifier_default",
1819
+ }],
1820
+ ]),
1821
+ toolServerMap: new Map([["dangerous_tool", "my-server"]]),
1822
+ globalBypass: false,
1823
+ });
1824
+ return sb;
1825
+ }
1826
+
1827
+ it("resolves the seeded gated tool call in place — no duplicate, history kept", () => {
1828
+ const sb = seededBuilder();
1829
+
1830
+ // Resumed lifecycle: fresh run_id, no tool_call_id available in v2.
1831
+ sb.processEvent(toolStartEvent("run-fresh-99", "dangerous_tool", { target: "prod" }));
1832
+ sb.processEvent(toolEndEvent("run-fresh-99", "tool executed ok"));
1833
+
1834
+ // Following assistant turn appends after the seeded message.
1835
+ sb.processEvent(chatStreamEvent("llm-after", "All done."));
1836
+ sb.processEvent(chatEndEvent("llm-after"));
1837
+
1838
+ const status = sb.currentStatus;
1839
+
1840
+ const gated = status.messages
1841
+ .flatMap(m => m.toolCalls)
1842
+ .filter(tc => tc.id === GATED_ID);
1843
+ expect(gated).toHaveLength(1);
1844
+ expect(gated[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
1845
+ expect(gated[0].result).toContain("tool executed ok");
1846
+
1847
+ const allText = status.messages.map(m => m.content).join("\n");
1848
+ expect(allText).toContain("I'll call the gated tool.");
1849
+ expect(allText).toContain("All done.");
1850
+
1851
+ // No re-gate: the resumed tool must not flip the run back to waiting.
1852
+ expect(status.phase).not.toBe(ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL);
1853
+ });
1854
+ });
1774
1855
  });
@@ -1,4 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { makeInMemoryArtifactStorage } from "../../../__test-utils__/fake-artifact-storage.js";
2
3
  import { create } from "@bufbuild/protobuf";
3
4
  import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
4
5
  import { streamExecutionV3 } from "../streaming-v3.js";
@@ -531,14 +532,13 @@ describe("streamExecutionV3", () => {
531
532
  // faithfully (no truncation), and the persist chokepoint offloads the image
532
533
  // to artifact storage so the persisted ToolCall carries outputRef.isImage.
533
534
  const uploads: { key: string; size: number; contentType?: string }[] = [];
534
- const artifactStorage = {
535
- upload: vi.fn(async (key: string, content: Buffer, contentType?: string) => {
536
- uploads.push({ key, size: content.length, contentType });
537
- return key;
538
- }),
539
- getDownloadUrl: vi.fn(async (key: string) => `https://artifacts.local/${key}`),
540
- exists: vi.fn(async () => true),
541
- } as any;
535
+ const { storage: artifactStorage, blobs } = makeInMemoryArtifactStorage({ urlBase: "https://artifacts.local/" });
536
+ artifactStorage.exists.mockResolvedValue(true);
537
+ artifactStorage.upload.mockImplementation(async (key: string, content: Buffer, contentType?: string) => {
538
+ uploads.push({ key, size: content.length, contentType });
539
+ blobs.set(key, Buffer.from(content));
540
+ return key;
541
+ });
542
542
 
543
543
  const base64 = Buffer.from("PNGBYTES".repeat(64)).toString("base64");
544
544
  const imageEnvelope = {
@@ -587,7 +587,7 @@ describe("streamExecutionV3", () => {
587
587
  expect(refTc).toBeDefined();
588
588
  expect(refTc.outputRef.isImage).toBe(true);
589
589
  expect(refTc.outputRef.mimeType).toBe("image/png");
590
- expect(refTc.outputRef.downloadUrl).toContain("artifacts/exec-v3-test/toolcalls/toolu_1.png");
590
+ expect(refTc.outputRef.storageKey).toBe("artifacts/exec-v3-test/toolcalls/toolu_1.png");
591
591
  // The base64 must not survive inline on the persisted result.
592
592
  expect(refTc.result).not.toContain(base64);
593
593
  expect(uploads).toHaveLength(1);