@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,163 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { CasCaptureObserver } from "../cas-capture-observer.js";
6
+ import { CasCaptureFilesystemBackend } from "../cas-capture-backend.js";
7
+
8
+ /**
9
+ * The observer is the single, shared owner of one turn's CAS-capture state. It
10
+ * records the PRE-TURN bytes of first-touched gitignored paths (gate-independently,
11
+ * at the mutation point) and the gitignored paths the gate hard-blocked as
12
+ * secret-like. These tests assert the recording contract — only gitignored paths,
13
+ * only the first touch, never a post-write baseline — AND the cross-instance
14
+ * guarantee that makes sub-agent capture safe: the parent and every sub-agent
15
+ * backend share ONE observer, so first-touch-wins holds even when concurrent
16
+ * graphs touch the same path (Session 26, DD-19).
17
+ */
18
+ describe("CasCaptureObserver", () => {
19
+ let root: string;
20
+
21
+ /** Paths under `ignored/` are gitignored; everything else is git-tracked. */
22
+ const ignoredPredicate = (relPath: string): boolean => relPath.startsWith("ignored/");
23
+
24
+ function makeObserver(isIgnored = async (p: string) => ignoredPredicate(p)): CasCaptureObserver {
25
+ return new CasCaptureObserver({ rootDir: root, isIgnored });
26
+ }
27
+
28
+ beforeEach(async () => {
29
+ root = await mkdtemp(join(tmpdir(), "cas-observer-"));
30
+ await mkdir(join(root, "ignored"), { recursive: true });
31
+ await mkdir(join(root, "src"), { recursive: true });
32
+ });
33
+
34
+ afterEach(async () => {
35
+ await rm(root, { recursive: true, force: true });
36
+ });
37
+
38
+ describe("recordBefore", () => {
39
+ it("records before=null for a not-yet-existing gitignored path (an ADD)", async () => {
40
+ const observer = makeObserver();
41
+
42
+ await observer.recordBefore("ignored/new.txt");
43
+
44
+ expect(observer.before.has("ignored/new.txt")).toBe(true);
45
+ expect(observer.before.get("ignored/new.txt")).toBeNull();
46
+ });
47
+
48
+ it("records the pre-turn bytes for an existing gitignored file (a MODIFY)", async () => {
49
+ await writeFile(join(root, "ignored/cfg.txt"), "v0");
50
+ const observer = makeObserver();
51
+
52
+ await observer.recordBefore("ignored/cfg.txt");
53
+
54
+ const before = observer.before.get("ignored/cfg.txt");
55
+ expect(before).not.toBeNull();
56
+ expect(Buffer.from(before!).toString("utf8")).toBe("v0");
57
+ });
58
+
59
+ it("first-touch-wins: a later touch does not overwrite the recorded baseline", async () => {
60
+ await writeFile(join(root, "ignored/log.txt"), "a");
61
+ const observer = makeObserver();
62
+
63
+ await observer.recordBefore("ignored/log.txt");
64
+ // Simulate the file changing mid-turn, then a second mutation touches it.
65
+ await writeFile(join(root, "ignored/log.txt"), "b");
66
+ await observer.recordBefore("ignored/log.txt");
67
+
68
+ expect(Buffer.from(observer.before.get("ignored/log.txt")!).toString("utf8")).toBe("a");
69
+ });
70
+
71
+ it("does NOT record git-tracked paths (the boundary git diff owns those)", async () => {
72
+ const observer = makeObserver();
73
+
74
+ await observer.recordBefore("src/app.ts");
75
+
76
+ expect(observer.before.has("src/app.ts")).toBe(false);
77
+ expect(observer.before.size).toBe(0);
78
+ });
79
+
80
+ it("normalizes virtual-root ('/'-prefixed) paths to workspace-relative keys", async () => {
81
+ const observer = makeObserver();
82
+
83
+ await observer.recordBefore("/ignored/rooted.txt");
84
+
85
+ expect(observer.before.has("ignored/rooted.txt")).toBe(true);
86
+ });
87
+
88
+ it("memoizes the gitignore classification (one check per distinct path)", async () => {
89
+ const isIgnored = vi.fn(async (p: string) => ignoredPredicate(p));
90
+ const observer = makeObserver(isIgnored);
91
+
92
+ await observer.recordBefore("ignored/a.txt");
93
+ await observer.recordBefore("ignored/a.txt");
94
+ await observer.recordBefore("ignored/b.txt");
95
+
96
+ expect(isIgnored).toHaveBeenCalledTimes(2); // a.txt once, b.txt once
97
+ });
98
+ });
99
+
100
+ describe("recordBlockedSecret", () => {
101
+ it("resolves the raw path to a workspace-relative key and dedupes", () => {
102
+ const observer = makeObserver();
103
+
104
+ observer.recordBlockedSecret("/ignored/.env");
105
+ observer.recordBlockedSecret("ignored/.env");
106
+
107
+ expect([...observer.blockedSecretPaths]).toEqual(["ignored/.env"]);
108
+ });
109
+
110
+ it("blockedSecretPaths is empty until something is recorded", () => {
111
+ expect([...makeObserver().blockedSecretPaths]).toEqual([]);
112
+ });
113
+ });
114
+
115
+ describe("shared across backends (cross-instance first-touch)", () => {
116
+ /** Two backends on the SAME observer — the parent + sub-agent shape. */
117
+ function twoBackends(observer: CasCaptureObserver): [CasCaptureFilesystemBackend, CasCaptureFilesystemBackend] {
118
+ return [
119
+ new CasCaptureFilesystemBackend({ rootDir: root }, { observer }),
120
+ new CasCaptureFilesystemBackend({ rootDir: root }, { observer }),
121
+ ];
122
+ }
123
+
124
+ it("a write through either backend feeds the one shared before-map", async () => {
125
+ const observer = makeObserver();
126
+ const [parent, sub] = twoBackends(observer);
127
+
128
+ await parent.write("ignored/from-parent.txt", "p");
129
+ await sub.write("ignored/from-sub.txt", "s");
130
+
131
+ expect(observer.before.get("ignored/from-parent.txt")).toBeNull();
132
+ expect(observer.before.get("ignored/from-sub.txt")).toBeNull();
133
+ });
134
+
135
+ it("keeps the first backend's pre-turn baseline when a second backend touches the same path", async () => {
136
+ await writeFile(join(root, "ignored/shared.txt"), "orig");
137
+ const observer = makeObserver();
138
+ const [parent, sub] = twoBackends(observer);
139
+
140
+ await parent.edit("ignored/shared.txt", "orig", "parent");
141
+ await sub.edit("ignored/shared.txt", "parent", "sub");
142
+
143
+ expect(Buffer.from(observer.before.get("ignored/shared.txt")!).toString("utf8")).toBe("orig");
144
+ });
145
+
146
+ it("under CONCURRENT writes from two backends to one path, records the pre-turn baseline exactly once", async () => {
147
+ await writeFile(join(root, "ignored/race.txt"), "orig");
148
+ const observer = makeObserver();
149
+ const [a, b] = twoBackends(observer);
150
+
151
+ // The synchronous shared reservation must ensure only the pre-turn bytes
152
+ // are recorded — never a post-write value — even across backend instances
153
+ // with separate identities (the sub-agent concurrency case).
154
+ await Promise.allSettled([
155
+ a.edit("ignored/race.txt", "orig", "A"),
156
+ b.edit("ignored/race.txt", "orig", "B"),
157
+ ]);
158
+
159
+ expect(observer.before.size).toBe(1);
160
+ expect(Buffer.from(observer.before.get("ignored/race.txt")!).toString("utf8")).toBe("orig");
161
+ });
162
+ });
163
+ });
@@ -41,10 +41,11 @@ describe("HITL approval integration", () => {
41
41
  mcpServerSlug: "my-server",
42
42
  requiresApproval: true,
43
43
  approvalMessage: "Execute dangerous_tool",
44
+ source: "classifier_default",
44
45
  }],
45
46
  ]),
46
47
  toolServerMap: new Map([["dangerous_tool", "my-server"]]),
47
- autoApproveAll: false,
48
+ globalBypass: false,
48
49
  });
49
50
 
50
51
  sb.processEvent({
@@ -83,7 +84,7 @@ describe("HITL approval integration", () => {
83
84
  sb.setApprovalProvider({
84
85
  policies: new Map(),
85
86
  toolServerMap: new Map([["safe_tool", "my-server"]]),
86
- autoApproveAll: false,
87
+ globalBypass: false,
87
88
  });
88
89
 
89
90
  sb.processEvent({
@@ -110,7 +111,7 @@ describe("HITL approval integration", () => {
110
111
  expect(tc?.requiresApproval).toBe(false);
111
112
  });
112
113
 
113
- it("does NOT set WAITING_FOR_APPROVAL when autoApproveAll", () => {
114
+ it("does NOT set WAITING_FOR_APPROVAL under the global bypass", () => {
114
115
  const status = create(AgentExecutionStatusSchema, {});
115
116
  const sb = new StatusBuilder("exec-3", status);
116
117
 
@@ -121,10 +122,11 @@ describe("HITL approval integration", () => {
121
122
  mcpServerSlug: "srv",
122
123
  requiresApproval: true,
123
124
  approvalMessage: "msg",
125
+ source: "classifier_default",
124
126
  }],
125
127
  ]),
126
128
  toolServerMap: new Map([["tool", "srv"]]),
127
- autoApproveAll: true,
129
+ globalBypass: true,
128
130
  });
129
131
 
130
132
  sb.processEvent({
@@ -159,10 +161,11 @@ describe("HITL approval integration", () => {
159
161
  mcpServerSlug: "srv",
160
162
  requiresApproval: true,
161
163
  approvalMessage: "Run tool_x",
164
+ source: "classifier_default",
162
165
  }],
163
166
  ]),
164
167
  toolServerMap: new Map([["tool_x", "srv"]]),
165
- autoApproveAll: false,
168
+ globalBypass: false,
166
169
  });
167
170
 
168
171
  sb.processEvent({
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Diagnostic regression test (triangulation — RUNNER boundary).
3
+ *
4
+ * Reproduces the user-reported scenario exactly: a gated `getAppState` call
5
+ * (open-computer-use MCP) is approved with APPROVE_ALL, which leases the whole
6
+ * server so the resume runs `getAppState` AND every following computer-use tool
7
+ * to completion in ONE durable-checkpoint resume — no further interrupts.
8
+ *
9
+ * The reported symptom is that the LEADING thinking block and the FIRST tool
10
+ * call (`getAppState`) vanish from the transcript after approve-all, while the
11
+ * later auto-executed tools remain. This test pins what the RUNNER persists at
12
+ * its single gRPC chokepoint (`updateStatus`). If the runner is faithful, the
13
+ * final status it sends must be a SUPERSET of run-1's transcript: the leading
14
+ * thinking block survives, the gated tool call is reconciled in place (exactly
15
+ * one copy, now COMPLETED), and the leased follow-up tool is appended.
16
+ *
17
+ * Models the cloud path deterministically, mirroring hitl-resume-history.test.ts:
18
+ * - the persisted execution already holds the run-1 transcript
19
+ * (THINKING + AI(getAppState WAITING_APPROVAL, approvalAction=APPROVE_ALL));
20
+ * - the durable checkpoint exists (getState reports prior values + a pending
21
+ * interrupt), so streamEvents re-emits ONLY post-interrupt events;
22
+ * - the lease drops open-computer-use from the policy map, so the follow-up
23
+ * `click` is NOT re-gated (its key is absent from approvalPolicies) and the
24
+ * resume reaches a terminal COMPLETED phase.
25
+ */
26
+
27
+ import { describe, it, expect, vi, beforeEach } from "vitest";
28
+ import { makeInMemoryArtifactStorage } from "../../../__test-utils__/fake-artifact-storage.js";
29
+ import { create, clone } from "@bufbuild/protobuf";
30
+ import {
31
+ AgentExecutionStatusSchema,
32
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
33
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
34
+ import {
35
+ AgentMessageSchema,
36
+ ToolCallSchema,
37
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
38
+ import {
39
+ ApprovalAction,
40
+ ExecutionPhase,
41
+ MessageType,
42
+ ToolCallStatus,
43
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
44
+ import type { V3ProtocolEvent } from "../v3-event-recorder.js";
45
+ import type { Config } from "../../../config.js";
46
+ import type { SetupResult } from "../setup.js";
47
+
48
+ // The Temporal activity context is unavailable outside a worker.
49
+ vi.mock("@temporalio/activity", () => ({
50
+ Context: {
51
+ current: () => ({
52
+ cancellationSignal: { aborted: false },
53
+ heartbeat: vi.fn(),
54
+ }),
55
+ },
56
+ CancelledFailure: class CancelledFailure extends Error {},
57
+ }));
58
+
59
+ vi.mock("../../../idle-watchdog.js", () => ({
60
+ activityStarted: vi.fn(),
61
+ activityFinished: vi.fn(),
62
+ }));
63
+
64
+ // Capture every persisted status (the gRPC chokepoint) for assertions.
65
+ const { persistedStatuses } = vi.hoisted(() => ({
66
+ persistedStatuses: [] as AgentExecutionStatus[],
67
+ }));
68
+
69
+ vi.mock("../../../client/stigmer-client.js", () => ({
70
+ StigmerClient: vi.fn().mockImplementation(() => ({
71
+ updateStatus: vi.fn(async (_id: string, status: AgentExecutionStatus) => {
72
+ // Snapshot point-in-time: persistStatus mutates the same object in place.
73
+ persistedStatuses.push(clone(AgentExecutionStatusSchema, status));
74
+ return { signal: 0 };
75
+ }),
76
+ getExecution: vi.fn(),
77
+ })),
78
+ }));
79
+
80
+ // performSetup is mocked per-test to return a fake durable-resume SetupResult.
81
+ vi.mock("../setup.js", () => ({ performSetup: vi.fn() }));
82
+
83
+ import { createDeepAgentActivities } from "../index.js";
84
+ import { performSetup } from "../setup.js";
85
+
86
+ const MCP_SLUG = "open-computer-use";
87
+ const GATED_TOOL_CALL_ID = "toolu_getappstate_01";
88
+ const LEASED_TOOL_CALL_ID = "toolu_click_01";
89
+ const THINKING_TEXT = "Let me capture the current app state before clicking anything.";
90
+ const PRE_RESUME_AI_TEXT = "I'll capture the app state first.";
91
+ const POST_RESUME_AI_TEXT = "Captured the app state and clicked the element.";
92
+
93
+ function v3Event(
94
+ seq: number,
95
+ method: string,
96
+ data: unknown,
97
+ namespace: string[] = [],
98
+ ): V3ProtocolEvent {
99
+ return {
100
+ type: "event",
101
+ seq,
102
+ method,
103
+ params: { namespace, timestamp: Date.now(), data },
104
+ };
105
+ }
106
+
107
+ function toolMessageOutput(content: string, toolCallId: string): unknown {
108
+ return {
109
+ lc: 1,
110
+ type: "constructor",
111
+ id: ["langchain_core", "messages", "ToolMessage"],
112
+ kwargs: { content, tool_call_id: toolCallId },
113
+ };
114
+ }
115
+
116
+ /**
117
+ * The events a durable resume re-emits after APPROVE_ALL: the gated tool
118
+ * executes (reconciled in place), then the leased follow-up tool auto-runs,
119
+ * then the assistant produces a final turn. No interrupts — the lease dropped
120
+ * open-computer-use from the policy map.
121
+ */
122
+ function resumeEvents(): V3ProtocolEvent[] {
123
+ return [
124
+ // 1. Gated getAppState now runs (reconciled to the seeded WAITING call).
125
+ v3Event(0, "tools", {
126
+ event: "tool-started",
127
+ tool_call_id: GATED_TOOL_CALL_ID,
128
+ tool_name: "getAppState",
129
+ input: {},
130
+ }, [`tools:${GATED_TOOL_CALL_ID}`]),
131
+ v3Event(1, "tools", {
132
+ event: "tool-finished",
133
+ tool_call_id: GATED_TOOL_CALL_ID,
134
+ output: toolMessageOutput("app-state-captured: 3 windows", GATED_TOOL_CALL_ID),
135
+ }, [`tools:${GATED_TOOL_CALL_ID}`]),
136
+ // 2. The model decides to click; the leased tool auto-runs (no gate).
137
+ v3Event(2, "messages", { event: "message-start", run_id: "r2" }),
138
+ v3Event(3, "tools", {
139
+ event: "tool-started",
140
+ tool_call_id: LEASED_TOOL_CALL_ID,
141
+ tool_name: "click",
142
+ input: { x: 10, y: 20 },
143
+ }, [`tools:${LEASED_TOOL_CALL_ID}`]),
144
+ v3Event(4, "tools", {
145
+ event: "tool-finished",
146
+ tool_call_id: LEASED_TOOL_CALL_ID,
147
+ output: toolMessageOutput("clicked at (10,20)", LEASED_TOOL_CALL_ID),
148
+ }, [`tools:${LEASED_TOOL_CALL_ID}`]),
149
+ // 3. Final assistant turn.
150
+ v3Event(5, "messages", { event: "message-start", run_id: "r3" }),
151
+ v3Event(6, "messages", {
152
+ event: "content-block-delta",
153
+ index: 0,
154
+ delta: { type: "text-delta", text: POST_RESUME_AI_TEXT },
155
+ run_id: "r3",
156
+ }),
157
+ v3Event(7, "messages", { event: "message-finish", run_id: "r3" }),
158
+ ];
159
+ }
160
+
161
+ function mockV3Run(events: V3ProtocolEvent[]) {
162
+ return {
163
+ [Symbol.asyncIterator]: async function* () {
164
+ for (const e of events) yield e;
165
+ },
166
+ output: Promise.resolve({ messages: [] }),
167
+ abort: vi.fn(),
168
+ signal: new AbortController().signal,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * The run-1 transcript already persisted on the execution: a leading thinking
174
+ * block, then the gated getAppState call the user approved with APPROVE_ALL.
175
+ */
176
+ function persistedRunOneStatus(): AgentExecutionStatus {
177
+ return create(AgentExecutionStatusSchema, {
178
+ phase: ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
179
+ startedAt: "2026-06-15T00:00:00.000Z",
180
+ messages: [
181
+ create(AgentMessageSchema, {
182
+ type: MessageType.MESSAGE_THINKING,
183
+ content: THINKING_TEXT,
184
+ timestamp: "2026-06-15T00:00:01.000Z",
185
+ }),
186
+ create(AgentMessageSchema, {
187
+ type: MessageType.MESSAGE_AI,
188
+ content: PRE_RESUME_AI_TEXT,
189
+ timestamp: "2026-06-15T00:00:02.000Z",
190
+ toolCalls: [
191
+ create(ToolCallSchema, {
192
+ id: GATED_TOOL_CALL_ID,
193
+ name: "getAppState",
194
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
195
+ requiresApproval: true,
196
+ approvalMessage: "Execute getAppState tool",
197
+ approvalAction: ApprovalAction.APPROVE_ALL,
198
+ mcpServerSlug: MCP_SLUG,
199
+ }),
200
+ ],
201
+ }),
202
+ ],
203
+ });
204
+ }
205
+
206
+ function fakeDurableResumeSetup(): SetupResult {
207
+ let streamed = false;
208
+
209
+ const pendingInterruptState = {
210
+ values: { messages: [{ role: "assistant", content: PRE_RESUME_AI_TEXT }] },
211
+ tasks: [{
212
+ id: "task-1",
213
+ interrupts: [{
214
+ value: {
215
+ tool_call_id: GATED_TOOL_CALL_ID,
216
+ tool_name: "getAppState",
217
+ mcp_server_slug: MCP_SLUG,
218
+ message: "Execute getAppState tool",
219
+ },
220
+ resumeValue: undefined,
221
+ }],
222
+ }],
223
+ };
224
+ const resolvedState = {
225
+ values: { messages: [{ role: "assistant", content: PRE_RESUME_AI_TEXT }] },
226
+ tasks: [],
227
+ };
228
+
229
+ const agentGraph = {
230
+ // Before the resumed stream: a pending interrupt awaits the decision.
231
+ // After the stream: the interrupt is resolved (no pending approvals).
232
+ getState: vi.fn(async () => (streamed ? resolvedState : pendingInterruptState)),
233
+ streamEvents: vi.fn(async () => {
234
+ streamed = true;
235
+ return mockV3Run(resumeEvents());
236
+ }),
237
+ };
238
+
239
+ // Approve-all leased open-computer-use: the resume's policy map no longer
240
+ // contains the leased server's tools, so the follow-up `click` is NOT gated.
241
+ // Only the originally-gated getAppState policy remains for resume resolution.
242
+ const approvalPolicies = new Map([
243
+ [`${MCP_SLUG}/getAppState`, {
244
+ toolName: "getAppState",
245
+ mcpServerSlug: MCP_SLUG,
246
+ requiresApproval: true,
247
+ approvalMessage: "Execute getAppState tool",
248
+ }],
249
+ ]);
250
+
251
+ return {
252
+ agentGraph,
253
+ langgraphConfig: { configurable: { thread_id: "thread-1" } },
254
+ langgraphInput: { messages: [{ role: "user", content: "Self-DM me" }] },
255
+ execution: {
256
+ spec: { message: "Self-DM me to confirm you can." },
257
+ status: persistedRunOneStatus(),
258
+ },
259
+ agent: {},
260
+ session: { spec: { workspaceEntries: [] } },
261
+ workspaceBackend: { rootDir: "/tmp/ws" },
262
+ mcpConnection: null,
263
+ mergedEnvVars: {},
264
+ secretKeys: new Set<string>(),
265
+ modelName: "claude-sonnet",
266
+ gracefulStop: undefined,
267
+ artifactStorage: makeInMemoryArtifactStorage({ urlBase: "https://artifacts.local/" }).storage,
268
+ provisionResults: [],
269
+ approvalPolicies,
270
+ // Both computer-use tools belong to the leased server.
271
+ toolServerMap: new Map([["getAppState", MCP_SLUG], ["click", MCP_SLUG]]),
272
+ autoApproveAll: false,
273
+ hasStructuredOutput: false,
274
+ streamVersion: "v3",
275
+ } as unknown as SetupResult;
276
+ }
277
+
278
+ const baseConfig: Config = {
279
+ taskQueue: "test-queue",
280
+ temporalAddress: "localhost:7233",
281
+ temporalNamespace: "default",
282
+ stigmerBackendEndpoint: "http://localhost:7234",
283
+ stigmerToken: null,
284
+ cursorApiKey: "",
285
+ workspaceRootDir: "/tmp/test",
286
+ mode: "cloud",
287
+ proxyEndpoint: "http://localhost:7234",
288
+ maxConcurrentActivities: 5,
289
+ idleTimeoutSeconds: null,
290
+ cloudModeEnabled: true,
291
+ checkpointerType: "http",
292
+ checkpointerProxyEndpoint: "http://localhost:7234",
293
+ primaryModel: "claude-sonnet",
294
+ cursorStreamStallTimeoutMs: 180000,
295
+ };
296
+
297
+ describe("ExecuteDeepAgent — APPROVE_ALL durable resume preserves leading thinking + first tool call", () => {
298
+ beforeEach(() => {
299
+ persistedStatuses.length = 0;
300
+ vi.mocked(performSetup).mockResolvedValue(fakeDurableResumeSetup());
301
+ });
302
+
303
+ it("keeps the leading thinking block and the gated getAppState call, and appends the leased tool", async () => {
304
+ const activities = createDeepAgentActivities(baseConfig);
305
+ await activities.ExecuteDeepAgent("exec-1", "thread-1");
306
+
307
+ expect(persistedStatuses.length).toBeGreaterThan(0);
308
+ const finalStatus = persistedStatuses.at(-1)!;
309
+
310
+ // 1. The LEADING thinking block survives the resume (the reported symptom).
311
+ const thinkingMessages = finalStatus.messages.filter(
312
+ m => m.type === MessageType.MESSAGE_THINKING,
313
+ );
314
+ expect(thinkingMessages).toHaveLength(1);
315
+ expect(thinkingMessages[0].content).toBe(THINKING_TEXT);
316
+
317
+ // 2. The thinking block stays at the FRONT — it is still messages[0], proving
318
+ // the transcript was not front-truncated and rebuilt from a later turn.
319
+ expect(finalStatus.messages[0].type).toBe(MessageType.MESSAGE_THINKING);
320
+
321
+ // 3. The FIRST tool call (getAppState) is reconciled in place: exactly one
322
+ // copy, now COMPLETED with its result — not dropped, not duplicated.
323
+ const getAppStateCalls = finalStatus.messages
324
+ .flatMap(m => m.toolCalls)
325
+ .filter(tc => tc.id === GATED_TOOL_CALL_ID);
326
+ expect(getAppStateCalls).toHaveLength(1);
327
+ expect(getAppStateCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
328
+ expect(getAppStateCalls[0].result).toContain("app-state-captured");
329
+
330
+ // 4. The leased follow-up tool is appended.
331
+ const clickCalls = finalStatus.messages
332
+ .flatMap(m => m.toolCalls)
333
+ .filter(tc => tc.id === LEASED_TOOL_CALL_ID);
334
+ expect(clickCalls).toHaveLength(1);
335
+ expect(clickCalls[0].status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
336
+
337
+ // 5. The post-resume assistant turn is appended and the run completes.
338
+ const allText = finalStatus.messages.map(m => m.content).join("\n");
339
+ expect(allText).toContain(POST_RESUME_AI_TEXT);
340
+ expect(finalStatus.phase).toBe(ExecutionPhase.EXECUTION_COMPLETED);
341
+ });
342
+ });