@stigmer/runner 3.0.9-dev.20260616060535 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
  3. package/dist/__test-utils__/approval-contract/types.js +24 -0
  4. package/dist/__test-utils__/approval-contract/types.js.map +1 -0
  5. package/dist/activities/call-agent-status.d.ts +19 -1
  6. package/dist/activities/call-agent-status.js +64 -5
  7. package/dist/activities/call-agent-status.js.map +1 -1
  8. package/dist/activities/call-llm.js +19 -53
  9. package/dist/activities/call-llm.js.map +1 -1
  10. package/dist/activities/classify-tool-approvals.d.ts +24 -0
  11. package/dist/activities/classify-tool-approvals.js +69 -17
  12. package/dist/activities/classify-tool-approvals.js.map +1 -1
  13. package/dist/activities/discover-mcp-server.d.ts +7 -0
  14. package/dist/activities/discover-mcp-server.js +11 -1
  15. package/dist/activities/discover-mcp-server.js.map +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
  17. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
  18. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
  19. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
  20. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
  21. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
  22. package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
  23. package/dist/activities/execute-cursor/approval-policy.js +8 -120
  24. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  25. package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
  26. package/dist/activities/execute-cursor/approval-state.js +233 -17
  27. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  28. package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
  29. package/dist/activities/execute-cursor/capture-flow.js +234 -0
  30. package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
  31. package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
  32. package/dist/activities/execute-cursor/cas-observations.js +184 -0
  33. package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
  34. package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
  35. package/dist/activities/execute-cursor/command-provenance.js +128 -0
  36. package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
  37. package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
  38. package/dist/activities/execute-cursor/exact-apply.js +204 -0
  39. package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
  40. package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
  41. package/dist/activities/execute-cursor/hook-script.js +310 -47
  42. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  43. package/dist/activities/execute-cursor/index.d.ts +8 -1
  44. package/dist/activities/execute-cursor/index.js +500 -64
  45. package/dist/activities/execute-cursor/index.js.map +1 -1
  46. package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
  47. package/dist/activities/execute-cursor/message-translator.js +836 -90
  48. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  49. package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
  50. package/dist/activities/execute-cursor/prompt-builder.js +74 -15
  51. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  52. package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
  53. package/dist/activities/execute-cursor/workspace-setup.js +212 -33
  54. package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
  55. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
  56. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
  57. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
  58. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
  59. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
  60. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
  61. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
  62. package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
  63. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
  64. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
  65. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
  66. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
  68. package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
  69. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
  70. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
  71. package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
  72. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
  73. package/dist/activities/execute-deep-agent/hitl.d.ts +10 -0
  74. package/dist/activities/execute-deep-agent/hitl.js +5 -1
  75. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  76. package/dist/activities/execute-deep-agent/index.d.ts +2 -1
  77. package/dist/activities/execute-deep-agent/index.js +370 -56
  78. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  79. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
  80. package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
  81. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  82. package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
  83. package/dist/activities/execute-deep-agent/setup.js +149 -92
  84. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  85. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
  86. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
  87. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
  88. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
  89. package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
  90. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  91. package/dist/activities/execute-deep-agent/status-builder.d.ts +11 -5
  92. package/dist/activities/execute-deep-agent/status-builder.js +6 -2
  93. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  94. package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
  95. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/streaming.js +3 -15
  97. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  98. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
  99. package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
  100. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  101. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
  102. package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
  103. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  104. package/dist/activities/execute-deep-agent/v3-status-builder.js +6 -2
  105. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  106. package/dist/claimcheck/payload-codec.js +9 -5
  107. package/dist/claimcheck/payload-codec.js.map +1 -1
  108. package/dist/client/stigmer-client.d.ts +2 -0
  109. package/dist/client/stigmer-client.js +2 -0
  110. package/dist/client/stigmer-client.js.map +1 -1
  111. package/dist/middleware/approval-gate.d.ts +85 -4
  112. package/dist/middleware/approval-gate.js +165 -38
  113. package/dist/middleware/approval-gate.js.map +1 -1
  114. package/dist/middleware/types.d.ts +2 -5
  115. package/dist/shared/activity-input.d.ts +43 -0
  116. package/dist/shared/activity-input.js +17 -0
  117. package/dist/shared/activity-input.js.map +1 -0
  118. package/dist/shared/approval-canonicalize.d.ts +19 -0
  119. package/dist/shared/approval-canonicalize.js +119 -0
  120. package/dist/shared/approval-canonicalize.js.map +1 -0
  121. package/dist/shared/approval-fingerprint.d.ts +106 -0
  122. package/dist/shared/approval-fingerprint.js +113 -0
  123. package/dist/shared/approval-fingerprint.js.map +1 -0
  124. package/dist/shared/approval-policy.d.ts +182 -12
  125. package/dist/shared/approval-policy.js +213 -27
  126. package/dist/shared/approval-policy.js.map +1 -1
  127. package/dist/shared/args-preview.d.ts +52 -0
  128. package/dist/shared/args-preview.js +93 -0
  129. package/dist/shared/args-preview.js.map +1 -0
  130. package/dist/shared/artifact-storage.d.ts +19 -1
  131. package/dist/shared/artifact-storage.js +48 -11
  132. package/dist/shared/artifact-storage.js.map +1 -1
  133. package/dist/shared/file-change.d.ts +44 -0
  134. package/dist/shared/file-change.js +57 -0
  135. package/dist/shared/file-change.js.map +1 -0
  136. package/dist/shared/file-tools.d.ts +107 -0
  137. package/dist/shared/file-tools.js +168 -0
  138. package/dist/shared/file-tools.js.map +1 -0
  139. package/dist/shared/filereview/capture.d.ts +202 -0
  140. package/dist/shared/filereview/capture.js +498 -0
  141. package/dist/shared/filereview/capture.js.map +1 -0
  142. package/dist/shared/filereview/cas-substrate.d.ts +190 -0
  143. package/dist/shared/filereview/cas-substrate.js +284 -0
  144. package/dist/shared/filereview/cas-substrate.js.map +1 -0
  145. package/dist/shared/filereview/digest.d.ts +40 -0
  146. package/dist/shared/filereview/digest.js +66 -0
  147. package/dist/shared/filereview/digest.js.map +1 -0
  148. package/dist/shared/filereview/events.d.ts +170 -0
  149. package/dist/shared/filereview/events.js +298 -0
  150. package/dist/shared/filereview/events.js.map +1 -0
  151. package/dist/shared/filereview/git-substrate.d.ts +175 -0
  152. package/dist/shared/filereview/git-substrate.js +439 -0
  153. package/dist/shared/filereview/git-substrate.js.map +1 -0
  154. package/dist/shared/filereview/index.d.ts +11 -0
  155. package/dist/shared/filereview/index.js +12 -0
  156. package/dist/shared/filereview/index.js.map +1 -0
  157. package/dist/shared/filereview/secret-paths.d.ts +63 -0
  158. package/dist/shared/filereview/secret-paths.js +105 -0
  159. package/dist/shared/filereview/secret-paths.js.map +1 -0
  160. package/dist/shared/fingerprint-secret.d.ts +26 -0
  161. package/dist/shared/fingerprint-secret.js +47 -0
  162. package/dist/shared/fingerprint-secret.js.map +1 -0
  163. package/dist/shared/model-client.d.ts +51 -0
  164. package/dist/shared/model-client.js +77 -0
  165. package/dist/shared/model-client.js.map +1 -0
  166. package/dist/shared/plan-artifact.js +0 -2
  167. package/dist/shared/plan-artifact.js.map +1 -1
  168. package/dist/shared/status-offload.d.ts +83 -9
  169. package/dist/shared/status-offload.js +399 -79
  170. package/dist/shared/status-offload.js.map +1 -1
  171. package/dist/shared/status.js +14 -1
  172. package/dist/shared/status.js.map +1 -1
  173. package/dist/shared/tool-kind.d.ts +19 -0
  174. package/dist/shared/tool-kind.js +13 -0
  175. package/dist/shared/tool-kind.js.map +1 -1
  176. package/dist/shared/tool-row.d.ts +88 -0
  177. package/dist/shared/tool-row.js +127 -0
  178. package/dist/shared/tool-row.js.map +1 -0
  179. package/dist/shared/workspace/platform-dir.d.ts +25 -0
  180. package/dist/shared/workspace/platform-dir.js +38 -2
  181. package/dist/shared/workspace/platform-dir.js.map +1 -1
  182. package/dist/workflows/call-agent-orchestrator.js +56 -7
  183. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  184. package/dist/workflows/connect-mcp-server.d.ts +50 -0
  185. package/dist/workflows/connect-mcp-server.js +136 -15
  186. package/dist/workflows/connect-mcp-server.js.map +1 -1
  187. package/dist/workflows/types.d.ts +8 -0
  188. package/package.json +2 -2
  189. package/src/__test-utils__/approval-contract/contract.ts +224 -0
  190. package/src/__test-utils__/approval-contract/types.ts +179 -0
  191. package/src/__test-utils__/fake-artifact-storage.ts +72 -0
  192. package/src/__tests__/approval-gateway-contract.test.ts +29 -0
  193. package/src/__tests__/claimcheck-codec.test.ts +16 -53
  194. package/src/__tests__/golden-e2e.test.ts +2 -0
  195. package/src/__tests__/runner-token-coordinator.test.ts +3 -3
  196. package/src/activities/__tests__/call-agent-status.test.ts +135 -0
  197. package/src/activities/__tests__/call-llm.test.ts +1 -1
  198. package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
  199. package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
  200. package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
  201. package/src/activities/call-agent-status.ts +74 -4
  202. package/src/activities/call-llm.ts +18 -63
  203. package/src/activities/classify-tool-approvals.ts +101 -19
  204. package/src/activities/discover-mcp-server.ts +29 -1
  205. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
  206. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
  207. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
  208. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
  209. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
  210. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
  211. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
  212. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
  213. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
  214. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
  215. package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
  216. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
  217. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
  218. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
  219. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
  220. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
  221. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
  222. package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
  223. package/src/activities/execute-cursor/approval-policy.ts +28 -159
  224. package/src/activities/execute-cursor/approval-state.ts +366 -18
  225. package/src/activities/execute-cursor/capture-flow.ts +323 -0
  226. package/src/activities/execute-cursor/cas-observations.ts +204 -0
  227. package/src/activities/execute-cursor/command-provenance.ts +168 -0
  228. package/src/activities/execute-cursor/exact-apply.ts +253 -0
  229. package/src/activities/execute-cursor/hook-script.ts +317 -51
  230. package/src/activities/execute-cursor/index.ts +575 -67
  231. package/src/activities/execute-cursor/message-translator.ts +963 -89
  232. package/src/activities/execute-cursor/prompt-builder.ts +80 -14
  233. package/src/activities/execute-cursor/workspace-setup.ts +257 -42
  234. package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
  235. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
  236. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
  237. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
  238. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
  239. package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
  240. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
  241. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
  242. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +2 -5
  243. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
  244. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
  245. package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
  246. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +12 -11
  247. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
  248. package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
  249. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
  250. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
  251. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
  252. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
  253. package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
  254. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
  255. package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
  256. package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
  257. package/src/activities/execute-deep-agent/hitl.ts +15 -1
  258. package/src/activities/execute-deep-agent/index.ts +434 -64
  259. package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
  260. package/src/activities/execute-deep-agent/setup.ts +223 -125
  261. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
  262. package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
  263. package/src/activities/execute-deep-agent/status-builder.ts +19 -7
  264. package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
  265. package/src/activities/execute-deep-agent/streaming.ts +3 -13
  266. package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
  267. package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
  268. package/src/activities/execute-deep-agent/v3-status-builder.ts +8 -2
  269. package/src/claimcheck/payload-codec.ts +8 -8
  270. package/src/client/stigmer-client.ts +9 -1
  271. package/src/middleware/__tests__/approval-gate.test.ts +488 -4
  272. package/src/middleware/approval-gate.ts +247 -38
  273. package/src/middleware/types.ts +5 -5
  274. package/src/shared/__tests__/activity-input.test.ts +78 -0
  275. package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
  276. package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
  277. package/src/shared/__tests__/approval-policy.test.ts +274 -40
  278. package/src/shared/__tests__/args-preview.test.ts +78 -0
  279. package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
  280. package/src/shared/__tests__/artifact-storage.test.ts +123 -11
  281. package/src/shared/__tests__/file-change.test.ts +85 -0
  282. package/src/shared/__tests__/file-tools.test.ts +90 -0
  283. package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
  284. package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
  285. package/src/shared/__tests__/model-client.test.ts +162 -0
  286. package/src/shared/__tests__/plan-artifact.test.ts +11 -26
  287. package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
  288. package/src/shared/__tests__/status-offload.test.ts +573 -16
  289. package/src/shared/__tests__/status.test.ts +4 -5
  290. package/src/shared/__tests__/tool-kind.test.ts +24 -1
  291. package/src/shared/__tests__/tool-row.test.ts +221 -0
  292. package/src/shared/activity-input.ts +57 -0
  293. package/src/shared/approval-canonicalize.ts +159 -0
  294. package/src/shared/approval-fingerprint.ts +148 -0
  295. package/src/shared/approval-policy.ts +303 -27
  296. package/src/shared/args-preview.ts +98 -0
  297. package/src/shared/artifact-storage.ts +62 -11
  298. package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
  299. package/src/shared/file-change.ts +64 -0
  300. package/src/shared/file-tools.ts +169 -0
  301. package/src/shared/filereview/__tests__/capture.test.ts +856 -0
  302. package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
  303. package/src/shared/filereview/__tests__/digest.test.ts +100 -0
  304. package/src/shared/filereview/__tests__/events.test.ts +245 -0
  305. package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
  306. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
  307. package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
  308. package/src/shared/filereview/capture.ts +727 -0
  309. package/src/shared/filereview/cas-substrate.ts +401 -0
  310. package/src/shared/filereview/digest.ts +83 -0
  311. package/src/shared/filereview/events.ts +449 -0
  312. package/src/shared/filereview/git-substrate.ts +555 -0
  313. package/src/shared/filereview/index.ts +60 -0
  314. package/src/shared/filereview/secret-paths.ts +121 -0
  315. package/src/shared/fingerprint-secret.ts +53 -0
  316. package/src/shared/model-client.ts +122 -0
  317. package/src/shared/plan-artifact.ts +0 -2
  318. package/src/shared/status-offload.ts +433 -77
  319. package/src/shared/status.ts +13 -0
  320. package/src/shared/tool-kind.ts +33 -0
  321. package/src/shared/tool-row.ts +135 -0
  322. package/src/shared/workspace/platform-dir.ts +41 -2
  323. package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
  324. package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
  325. package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
  326. package/src/workflows/call-agent-orchestrator.ts +53 -6
  327. package/src/workflows/connect-mcp-server.ts +179 -24
  328. package/src/workflows/types.ts +8 -0
@@ -9,17 +9,25 @@
9
9
  * incremental git writeback, and post-stream safety net.
10
10
  */
11
11
 
12
+ import { readFile } from "node:fs/promises";
13
+ import { join } from "node:path";
12
14
  import { Context, CancelledFailure } from "@temporalio/activity";
13
15
  import { create, clone, type JsonObject } from "@bufbuild/protobuf";
14
16
  import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
15
17
  import type { AgentExecution, AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
16
18
  import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
17
- import { ExecutionPhase, InteractionMode, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
19
+ import { ExecutionPhase, FileCaptureClass, FileChangeSetStatus, InteractionMode, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
18
20
  import { activityStarted, activityFinished } from "../../idle-watchdog.js";
21
+ import { normalizeActivityInput, type ExecuteActivityInput } from "../../shared/activity-input.js";
19
22
  import { persistStatus, slimStatus, utcTimestamp } from "../../shared/status.js";
20
23
  import type { ToolOutputOffloadContext } from "../../shared/status-offload.js";
21
24
  import { publishPlanArtifact } from "../../shared/plan-artifact.js";
22
25
  import { classifyTool } from "../../shared/tool-kind.js";
26
+ import {
27
+ POLICY_ENGINE_VERSION,
28
+ toProtoPolicySource,
29
+ type PolicySource,
30
+ } from "../../shared/approval-policy.js";
23
31
  import type { Config } from "../../config.js";
24
32
  import { StigmerClient } from "../../client/stigmer-client.js";
25
33
  import { performSetup, type SetupResult } from "./setup.js";
@@ -30,6 +38,21 @@ import { InlinePublisher } from "./inline-publisher.js";
30
38
  import { WriteBackCoordinator } from "./writeback-coordinator.js";
31
39
  import { processPostStream } from "./post-stream.js";
32
40
  import { resolveResumeInput, type GraphStateSnapshot } from "./hitl.js";
41
+ import { captureApprovalArtifacts } from "./approval-file-change.js";
42
+ import {
43
+ applyCaptureDecisions,
44
+ captureBaselineToLedger,
45
+ captureCandidateToLedger,
46
+ } from "../../shared/filereview/capture.js";
47
+ import { hasCandidateCaptured } from "../../shared/filereview/events.js";
48
+ import { casBlobReader, type CasPathCapture } from "../../shared/filereview/cas-substrate.js";
49
+ import { partitionIgnoredPathsBySecret } from "../../shared/filereview/secret-paths.js";
50
+ import type { CasCaptureObserver } from "./cas-capture-observer.js";
51
+ import { collectSubAgentToolCallIds } from "../../shared/tool-row.js";
52
+ import { stampFlowedFileEditRows, stampFlowedSubAgentFileEditRows } from "./stamp-flowed-rows.js";
53
+
54
+ /** The harness id stamped on the deep-agent's file-review ledger events. */
55
+ const DEEP_AGENT_HARNESS_ID = "deep-agent";
33
56
 
34
57
  export function createDeepAgentActivities(config: Config) {
35
58
  const client = new StigmerClient({
@@ -41,7 +64,15 @@ export function createDeepAgentActivities(config: Config) {
41
64
  const streamingConfig = loadStreamingConfig();
42
65
 
43
66
  return {
44
- ExecuteDeepAgent: async (executionId: string, threadId: string): Promise<unknown> => {
67
+ // Accepts the new typed object OR the legacy positional args (transitional
68
+ // dual-shape so the runner can deploy before the control planes — see
69
+ // shared/activity-input.ts). Drop the positional arm once both control
70
+ // planes send the object.
71
+ ExecuteDeepAgent: async (
72
+ arg0: ExecuteActivityInput | string,
73
+ arg1?: string,
74
+ ): Promise<unknown> => {
75
+ const { executionId, threadId, turnSeq } = normalizeActivityInput(arg0, arg1);
45
76
  activityStarted();
46
77
  let setup: SetupResult | null = null;
47
78
 
@@ -54,25 +85,24 @@ export function createDeepAgentActivities(config: Config) {
54
85
  // oversized tool outputs (e.g. computer-use screenshots) to artifact
55
86
  // storage so the UI can render them, and keep the status under the
56
87
  // gRPC cap. Threaded into the streaming loop and reused for the
57
- // terminal persists below so the guard is never skipped.
58
- const statusOffload: ToolOutputOffloadContext = {
59
- artifactStorage: setup.artifactStorage,
60
- executionId,
61
- };
62
-
63
- // One read of the graph checkpoint drives two decisions below: whether
64
- // this invocation resumes an existing durable checkpoint (so status must
65
- // be seeded from the persisted transcript instead of starting empty),
66
- // and how any pending approval interrupt resolves. Reading once avoids a
67
- // redundant round-trip on the durable (http) saver.
88
+ // terminal persists below so the guard is never skipped. Undefined when
89
+ // there is no artifact store (proxy misconfig): offload is then disabled,
90
+ // but persistStatus still enforces the aggregate size cap, so persistence
91
+ // can never silently blow past the gRPC limit.
92
+ const statusOffload: ToolOutputOffloadContext | undefined = setup.artifactStorage
93
+ ? { artifactStorage: setup.artifactStorage, executionId }
94
+ : undefined;
95
+
96
+ // Read the graph checkpoint once, to resolve how a pending approval
97
+ // interrupt continues (Command(resume) vs a fresh replay). Reading once
98
+ // avoids a redundant round-trip on the durable (http) saver.
68
99
  const graphState: GraphStateSnapshot = await setup.agentGraph.getState(setup.langgraphConfig);
69
100
 
70
- // Seed from the persisted transcript when resuming an existing
71
- // checkpoint; otherwise start empty. On a first run and under the
72
- // memory checkpointer (recreated empty per invocation, so the graph
73
- // replays from scratch)there are no prior checkpoint turns, so this
74
- // is a no-op and the original start-from-empty behavior is preserved.
75
- const initialStatus = shouldSeedFromCheckpoint(graphState, setup.execution)
101
+ // Seed from the persisted transcript whenever the execution already has
102
+ // committed history, so streamed deltas append onto it instead of
103
+ // replacing it. This holds for every re-invocation durable resume and
104
+ // memory-checkpointer replay alikeand is a no-op on a first run.
105
+ const initialStatus = shouldSeedFromPersistedTranscript(setup.execution)
76
106
  ? seedStatusFromExecution(setup.execution)
77
107
  : create(AgentExecutionStatusSchema, {});
78
108
  const statusBuilder = new StatusBuilder(executionId, initialStatus);
@@ -80,7 +110,8 @@ export function createDeepAgentActivities(config: Config) {
80
110
  statusBuilder.setApprovalProvider({
81
111
  policies: setup.approvalPolicies,
82
112
  toolServerMap: setup.toolServerMap,
83
- autoApproveAll: setup.autoApproveAll,
113
+ leasedCategories: setup.leasedCategories,
114
+ globalBypass: setup.globalBypass,
84
115
  });
85
116
 
86
117
  const resume = resolveResumeInput(
@@ -128,6 +159,116 @@ export function createDeepAgentActivities(config: Config) {
128
159
  })
129
160
  : null;
130
161
 
162
+ // Apply-then-review capture identity + substrate root for this turn.
163
+ const gitRoot = setup.workspaceBackend.rootDir;
164
+ const changeSetId = `${executionId}:${turnSeq}`;
165
+
166
+ // (1) Capture-mode resume — reconcile any DECIDED change set FIRST (this
167
+ // drops the per-execution refs), before the next baseline re-pins them
168
+ // (refs are executionId-keyed). Then (2) short-circuit a pure file-review
169
+ // resume — the agent already finished in a prior segment, so finalize with
170
+ // NO model re-invocation. A mixed turn (a tool gate is also pending) falls
171
+ // through to resume the graph for the gated tool.
172
+ if (setup.captureMode) {
173
+ const decidedSets = (setup.execution.status?.fileChangeSets ?? []).filter(
174
+ (cs) => cs.status === FileChangeSetStatus.DECIDED,
175
+ );
176
+ let reconciledAny = false;
177
+ let reconcileFailed = false;
178
+ let reconcileFailureDetail = "";
179
+ // The CAS blob reader reconciles ignored-file decisions from the durable
180
+ // manifest (git-only turns have no manifest, so the CAS branch no-ops).
181
+ // Undefined with no store: this block still runs for a git workspace
182
+ // whose captureMode is true without storage, but such a turn captures no
183
+ // CAS files (captureIgnored is off), so its change set carries no CAS ref
184
+ // and applyCaptureDecisions never needs the reader.
185
+ const casReadBlob = setup.artifactStorage
186
+ ? casBlobReader(setup.artifactStorage)
187
+ : undefined;
188
+ for (const changeSet of decidedSets) {
189
+ const capResult = await applyCaptureDecisions({
190
+ status: initialStatus,
191
+ gitRoot,
192
+ executionId,
193
+ changeSet,
194
+ harnessId: DEEP_AGENT_HARNESS_ID,
195
+ storage: setup.artifactStorage,
196
+ readBlob: casReadBlob,
197
+ gitWorkspace: setup.gitWorkspace,
198
+ });
199
+ if (!capResult.isCaptureTurn) continue;
200
+ reconciledAny = true;
201
+ if (capResult.failed) {
202
+ reconcileFailed = true;
203
+ reconcileFailureDetail =
204
+ capResult.failureDetail ?? "file review reconcile failed";
205
+ }
206
+ }
207
+
208
+ if (reconciledAny) {
209
+ if (reconcileFailed) {
210
+ // What-you-approve-is-what-applies failed: nothing was applied.
211
+ initialStatus.phase = ExecutionPhase.EXECUTION_FAILED;
212
+ initialStatus.error = `File review reconcile failed: ${reconcileFailureDetail}`;
213
+ initialStatus.completedAt = utcTimestamp();
214
+ await persistStatus(client, executionId, initialStatus, { offload: statusOffload });
215
+ return slimStatus(initialStatus);
216
+ }
217
+ if (!hasPendingToolApprovals(setup.execution)) {
218
+ // Pure file review: the agent's final answer + outputs already rode
219
+ // the prior segment's persisted status (seeded into initialStatus).
220
+ // Complete now and push only the approved tree — NO model re-run.
221
+ //
222
+ // The discriminator is the PERSISTED transcript, not the live graph
223
+ // checkpoint: the memory checkpointer (OSS local / desktop) recreates
224
+ // the checkpoint empty each invocation, so a graphState-based check
225
+ // would wrongly fire for a mixed turn. A mixed turn leaves
226
+ // WAITING_APPROVAL tool rows in the transcript; a pure file-review
227
+ // turn leaves none.
228
+ initialStatus.phase = ExecutionPhase.EXECUTION_COMPLETED;
229
+ initialStatus.completedAt = utcTimestamp();
230
+ if (writebackCoordinator) {
231
+ await processCaptureWriteback(writebackCoordinator, executionId);
232
+ }
233
+ await persistStatus(client, executionId, initialStatus, { offload: statusOffload });
234
+ // Surface the agent's final outputs (computed in the prior segment,
235
+ // carried on the seeded status) the same way a normal completion does.
236
+ const slim = slimStatus(initialStatus) as Record<string, unknown>;
237
+ const lastAi = [...initialStatus.messages]
238
+ .reverse()
239
+ .find((m) => m.type === MessageType.MESSAGE_AI);
240
+ if (lastAi?.content) slim.final_text = lastAi.content;
241
+ if (initialStatus.structuredOutput !== undefined) {
242
+ slim.structured = initialStatus.structuredOutput;
243
+ }
244
+ return slim;
245
+ }
246
+ // else: mixed turn — fall through; the graph resumes for the gated
247
+ // tool and a new baseline is taken below for the continuation segment.
248
+ }
249
+ }
250
+
251
+ // Turn start (capture mode): pin the pre-turn tree + author BASELINE. Taken
252
+ // AFTER any reconcile above and AFTER performSetup's workspace writes, so
253
+ // the baseline absorbs the reconciled state and the runner-owned files.
254
+ let captureBaselineTree = "";
255
+ // Snapshot the sub-agent tool-call ids that exist BEFORE this turn's
256
+ // stream, so the turn-boundary stamp scopes itself to sub-agent rows
257
+ // created this turn (empty here by construction — this harness never
258
+ // seeds prior sub-agents — but computed, not assumed, so it stays correct
259
+ // if that ever changes; see collectSubAgentToolCallIds).
260
+ const priorSubAgentToolCallIds = collectSubAgentToolCallIds(initialStatus.subAgentExecutions);
261
+ if (setup.captureMode) {
262
+ captureBaselineTree = await captureBaselineToLedger({
263
+ status: initialStatus,
264
+ gitRoot,
265
+ executionId,
266
+ changeSetId,
267
+ harnessId: DEEP_AGENT_HARNESS_ID,
268
+ gitWorkspace: setup.gitWorkspace,
269
+ });
270
+ }
271
+
131
272
  const cancellationSignal = Context.current().cancellationSignal;
132
273
 
133
274
  const result: StreamResult = await streamExecution({
@@ -141,13 +282,16 @@ export function createDeepAgentActivities(config: Config) {
141
282
  offload: statusOffload,
142
283
  gracefulStop: setup.gracefulStop,
143
284
  inlinePublisher,
144
- writebackCoordinator: writebackCoordinator ?? undefined,
285
+ // Capture mode: never push speculative edits during the turn. Writeback
286
+ // is deferred until the approved tree is reconciled (post-decision).
287
+ writebackCoordinator: setup.captureMode ? undefined : (writebackCoordinator ?? undefined),
145
288
  heartbeatFn: (details) => Context.current().heartbeat(details),
146
289
  isCancelledFn: () => cancellationSignal.aborted,
147
290
  approvalProvider: {
148
291
  policies: setup.approvalPolicies,
149
292
  toolServerMap: setup.toolServerMap,
150
- autoApproveAll: setup.autoApproveAll,
293
+ leasedCategories: setup.leasedCategories,
294
+ globalBypass: setup.globalBypass,
151
295
  },
152
296
  streamVersion: setup.streamVersion,
153
297
  });
@@ -155,37 +299,94 @@ export function createDeepAgentActivities(config: Config) {
155
299
  await processPostStream({
156
300
  status: initialStatus,
157
301
  inlinePublisher,
158
- writebackCoordinator,
302
+ // Capture mode: suppress the post-stream writeback finalize — the edits
303
+ // on disk are still speculative until reviewed. Writeback runs only on
304
+ // the approved tree (processCaptureWriteback, after reconcile).
305
+ writebackCoordinator: setup.captureMode ? null : writebackCoordinator,
159
306
  pendingPublishPromises: result.pendingPublishPromises,
160
307
  pendingWritebackPromises: result.pendingWritebackPromises,
161
308
  executionId,
162
309
  });
163
310
 
311
+ // Turn boundary (capture mode): capture the candidate change set from the
312
+ // git diff and author CANDIDATE_CAPTURED, then stamp the flowed file-edit
313
+ // rows with the change set id — they stay visible in place as
314
+ // observational records while file_change_sets remains the single
315
+ // decision surface. Skipped on an abnormal terminal (pause / stop /
316
+ // recursion limit) — there is no review to open. `fileReviewPending`
317
+ // then composes with any tool gate below.
318
+ let fileReviewPending = false;
319
+ const abnormalTerminal =
320
+ !!result.terminalStatus &&
321
+ initialStatus.phase !== ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
322
+ if (setup.captureMode && !abnormalTerminal) {
323
+ // Compose the CAS-captured paths (before-bytes from the observer,
324
+ // after-bytes re-read from disk) and the secret-blocked paths into the
325
+ // change set. The substrate class labels their provenance:
326
+ // GIT_IGNORED_CAPTURED for a git tree's ignored paths, NON_GIT_CAS for a
327
+ // non-git workspace (where these ARE the whole change set). In a git turn
328
+ // they compose with the git-tracked diff into one hybrid set; in a non-git
329
+ // turn they are the entire (CAS-only) set.
330
+ const casCaptureClass = setup.gitWorkspace
331
+ ? FileCaptureClass.GIT_IGNORED_CAPTURED
332
+ : FileCaptureClass.NON_GIT_CAS;
333
+ const { casCaptures, unreviewablePaths } = await buildCasTurnCaptures(
334
+ setup.casObserver,
335
+ gitRoot,
336
+ casCaptureClass,
337
+ );
338
+ await captureCandidateToLedger({
339
+ status: initialStatus,
340
+ gitRoot,
341
+ executionId,
342
+ changeSetId,
343
+ baselineTree: captureBaselineTree,
344
+ harnessId: DEEP_AGENT_HARNESS_ID,
345
+ casCaptures,
346
+ storage: setup.artifactStorage,
347
+ unreviewablePaths,
348
+ unreviewableCaptureClass: casCaptureClass,
349
+ gitWorkspace: setup.gitWorkspace,
350
+ });
351
+ // Review is pending iff a CANDIDATE was actually authored (the seam
352
+ // drops no-op captures), so a turn that only touched-then-unchanged an
353
+ // ignored file does not open an empty review. Stamping is gated on the
354
+ // same signal: a row must never reference a change set that does not
355
+ // exist.
356
+ fileReviewPending = hasCandidateCaptured(initialStatus, changeSetId);
357
+ if (fileReviewPending) {
358
+ stampFlowedFileEditRows(initialStatus.messages, changeSetId);
359
+ stampFlowedSubAgentFileEditRows(
360
+ initialStatus.subAgentExecutions,
361
+ changeSetId,
362
+ priorSubAgentToolCallIds,
363
+ );
364
+ }
365
+ }
366
+
164
367
  if (result.terminalStatus) {
165
368
  if (initialStatus.phase === ExecutionPhase.EXECUTION_PAUSED) {
166
369
  await persistStatus(client, executionId, initialStatus, { offload: statusOffload });
167
370
  console.log(`[ExecuteDeepAgent] Paused for execution ${executionId}: events=${result.eventsProcessed}`);
168
371
  throw new CancelledFailure("Activity paused by orchestrator");
169
372
  }
373
+ // Capture mode: a tool gate set WAITING during the stream while file
374
+ // edits also flowed this turn. The CANDIDATE event + stamped file-edit
375
+ // rows post-date the stream's slim, so persist + return the current
376
+ // status (both review surfaces — the file change set and the tool gate —
377
+ // are now pending). `!abnormalTerminal` means the capture block ran.
378
+ if (setup.captureMode && !abnormalTerminal) {
379
+ await persistStatus(client, executionId, initialStatus, { offload: statusOffload });
380
+ return slimStatus(initialStatus);
381
+ }
170
382
  return result.terminalStatus;
171
383
  }
172
384
 
173
- if (!setup.autoApproveAll) {
174
- const graphState = await setup.agentGraph.getState(setup.langgraphConfig);
175
- const pendingInterrupts = graphState.tasks?.flatMap(
176
- (task: { id: string; interrupts?: readonly { value: Record<string, unknown>; resumeValue?: unknown }[] }) =>
177
- (task.interrupts ?? [])
178
- .filter((intr) => intr.resumeValue === undefined)
179
- .map((intr) => {
180
- const val = intr.value as Record<string, unknown>;
181
- return {
182
- toolCallId: (val?.tool_call_id as string) ?? "",
183
- toolName: (val?.tool_name as string) ?? "",
184
- mcpServerSlug: (val?.mcp_server_slug as string) ?? "",
185
- message: (val?.message as string) ?? "",
186
- };
187
- }),
188
- ) ?? [];
385
+ if (!setup.globalBypass) {
386
+ const postStreamGraphState = await setup.agentGraph.getState(setup.langgraphConfig);
387
+ const graphMessages = (postStreamGraphState.values as { messages?: unknown }).messages;
388
+ const aiMessages = Array.isArray(graphMessages) ? graphMessages : [];
389
+ const pendingInterrupts = detectPendingInterrupts(postStreamGraphState);
189
390
 
190
391
  if (pendingInterrupts.length > 0) {
191
392
  console.log(
@@ -201,7 +402,7 @@ export function createDeepAgentActivities(config: Config) {
201
402
  isStreaming: false,
202
403
  });
203
404
  for (const intr of pendingInterrupts) {
204
- aiMsg.toolCalls.push(create(ToolCallSchema, {
405
+ const toolCall = create(ToolCallSchema, {
205
406
  id: intr.toolCallId,
206
407
  name: intr.toolName,
207
408
  status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
@@ -211,7 +412,21 @@ export function createDeepAgentActivities(config: Config) {
211
412
  mcpServerSlug: intr.mcpServerSlug,
212
413
  startedAt: utcTimestamp(),
213
414
  toolKind: classifyTool(intr.toolName, intr.mcpServerSlug),
214
- }));
415
+ approvalPolicySource: toProtoPolicySource(intr.policySource),
416
+ policyEngineVersion: intr.policySource ? POLICY_ENGINE_VERSION : "",
417
+ });
418
+
419
+ // Capture a sanitized args preview while the graph is paused, so the
420
+ // approval UI renders the proposed change before the tool runs. Args
421
+ // are correlated from the AI-message tool call in graph state (the
422
+ // single source of truth).
423
+ const { argsPreview } = captureApprovalArtifacts({
424
+ toolCallId: intr.toolCallId,
425
+ messages: aiMessages,
426
+ });
427
+ if (argsPreview) toolCall.argsPreview = argsPreview;
428
+
429
+ aiMsg.toolCalls.push(toolCall);
215
430
  }
216
431
  initialStatus.messages.push(aiMsg);
217
432
 
@@ -220,8 +435,17 @@ export function createDeepAgentActivities(config: Config) {
220
435
  }
221
436
  }
222
437
 
223
- initialStatus.phase = ExecutionPhase.EXECUTION_COMPLETED;
224
- initialStatus.completedAt = utcTimestamp();
438
+ // Capture mode: file edits flowed but no tool gate fired — open file
439
+ // review instead of completing. The agent's final answer + outputs are
440
+ // still computed and persisted below, so the pure-file-review resume can
441
+ // complete from the seeded status with no model re-invocation.
442
+ const completeNow = !fileReviewPending;
443
+ initialStatus.phase = completeNow
444
+ ? ExecutionPhase.EXECUTION_COMPLETED
445
+ : ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL;
446
+ if (completeNow) {
447
+ initialStatus.completedAt = utcTimestamp();
448
+ }
225
449
 
226
450
  let structuredOutput: JsonObject | undefined;
227
451
  let finalText: string | undefined;
@@ -271,9 +495,13 @@ export function createDeepAgentActivities(config: Config) {
271
495
  // first-class plan.md artifact so the UI can render a reviewable Plan
272
496
  // card and a follow-up Implement run can reference it. Read-only mode
273
497
  // produces no file to auto-publish, so this is the only artifact path.
498
+ // Requires artifact storage; with none (proxy misconfig) the plan text
499
+ // still lives in the final message — only the reviewable artifact is
500
+ // skipped (mirrors Cursor's storage-guarded plan publish).
274
501
  if (
275
502
  setup.execution.spec?.executionConfig?.interactionMode === InteractionMode.PLAN &&
276
- finalText
503
+ finalText &&
504
+ setup.artifactStorage
277
505
  ) {
278
506
  await publishPlanArtifact({
279
507
  status: initialStatus,
@@ -283,10 +511,17 @@ export function createDeepAgentActivities(config: Config) {
283
511
  });
284
512
  }
285
513
 
514
+ // Capture mode: push the approved tree exactly once, on terminal
515
+ // completion — never the speculative mid-turn edits.
516
+ if (setup.captureMode && completeNow && writebackCoordinator) {
517
+ await processCaptureWriteback(writebackCoordinator, executionId);
518
+ }
519
+
286
520
  await persistStatus(client, executionId, initialStatus, { offload: statusOffload });
287
521
 
288
522
  console.log(
289
- `[ExecuteDeepAgent] Completed for execution ${executionId}: ` +
523
+ `[ExecuteDeepAgent] ${completeNow ? "Completed" : "Awaiting file review for"} ` +
524
+ `execution ${executionId}: ` +
290
525
  `events=${result.eventsProcessed}, ` +
291
526
  `messages=${initialStatus.messages.length}, ` +
292
527
  `artifacts=${initialStatus.artifacts.length}, ` +
@@ -294,6 +529,13 @@ export function createDeepAgentActivities(config: Config) {
294
529
  `hasStructuredOutput=${structuredOutput !== undefined}`,
295
530
  );
296
531
 
532
+ // A WAITING (file-review) return carries no final outputs yet — those are
533
+ // surfaced when the pure-file-review resume completes from this persisted
534
+ // status. The COMPLETED return enriches the slim with final text/outputs.
535
+ if (!completeNow) {
536
+ return slimStatus(initialStatus);
537
+ }
538
+
297
539
  const slim = slimStatus(initialStatus) as Record<string, unknown>;
298
540
  if (finalText !== undefined) {
299
541
  slim.final_text = finalText;
@@ -354,30 +596,158 @@ export function createDeepAgentActivities(config: Config) {
354
596
  };
355
597
  }
356
598
 
599
+ /** A pending LangGraph approval interrupt, normalized from the graph checkpoint. */
600
+ interface PendingInterrupt {
601
+ readonly toolCallId: string;
602
+ readonly toolName: string;
603
+ readonly mcpServerSlug: string;
604
+ readonly message: string;
605
+ /** Gate provenance carried through the interrupt; undefined → UNSPECIFIED. */
606
+ readonly policySource: PolicySource | undefined;
607
+ }
608
+
609
+ /**
610
+ * Normalize the graph checkpoint's un-resumed interrupts into pending approvals.
611
+ * Used both before streaming (to detect whether a resume is a pure file review)
612
+ * and after (to seed the WAITING_FOR_APPROVAL tool rows).
613
+ */
614
+ function detectPendingInterrupts(graphState: GraphStateSnapshot): PendingInterrupt[] {
615
+ const tasks = (graphState as {
616
+ tasks?: readonly {
617
+ interrupts?: readonly { value: Record<string, unknown>; resumeValue?: unknown }[];
618
+ }[];
619
+ }).tasks;
620
+ return (
621
+ tasks?.flatMap((task) =>
622
+ (task.interrupts ?? [])
623
+ .filter((intr) => intr.resumeValue === undefined)
624
+ .map((intr) => {
625
+ const val = intr.value as Record<string, unknown>;
626
+ return {
627
+ toolCallId: (val?.tool_call_id as string) ?? "",
628
+ toolName: (val?.tool_name as string) ?? "",
629
+ mcpServerSlug: (val?.mcp_server_slug as string) ?? "",
630
+ message: (val?.message as string) ?? "",
631
+ policySource: (val?.policy_source as PolicySource) || undefined,
632
+ };
633
+ }),
634
+ ) ?? []
635
+ );
636
+ }
637
+
638
+ /**
639
+ * Whether the persisted transcript holds any tool call awaiting approval — the
640
+ * checkpointer-independent signal that a resume must drive a tool gate (resume
641
+ * the graph), not just a file review. Scans root + sub-agent messages. A tool
642
+ * keeps `WAITING_APPROVAL` after the user decides (the decision rides
643
+ * `approval_action`) until the runner reconciles it, so this is true for a mixed
644
+ * turn's resume and false for a pure file-review turn.
645
+ */
646
+ function hasPendingToolApprovals(execution: AgentExecution): boolean {
647
+ const status = execution.status;
648
+ if (!status) return false;
649
+ const anyWaiting = (msgs: { toolCalls: { status: ToolCallStatus }[] }[]): boolean =>
650
+ msgs.some((m) =>
651
+ m.toolCalls.some((tc) => tc.status === ToolCallStatus.TOOL_CALL_WAITING_APPROVAL),
652
+ );
653
+ if (anyWaiting(status.messages)) return true;
654
+ return status.subAgentExecutions.some((sa) => anyWaiting(sa.messages));
655
+ }
656
+
657
+ /**
658
+ * Assemble the turn's CAS captures and secret-blocked paths from the shared
659
+ * observer (the pre-turn bytes recorded by the parent AND every sub-agent CAS
660
+ * backend, plus the gate's secret-blocked set).
661
+ *
662
+ * For each observed CAS-owned path the after-bytes are re-read from disk (the
663
+ * authoritative net result of the turn; `null` when the file is gone). A path
664
+ * that is secret-like is diverted to `unreviewablePaths` and its before-bytes are
665
+ * dropped — the fail-closed backstop that holds even under the global bypass,
666
+ * where the gate did not run to block it up front. The result composes into the
667
+ * change set in {@link captureCandidateToLedger}.
668
+ *
669
+ * `captureClass` is the turn's CAS substrate class — GIT_IGNORED_CAPTURED for a
670
+ * git work tree's ignored paths, NON_GIT_CAS for a non-git workspace (where these
671
+ * ARE the whole change set) — so each captured path carries its true provenance.
672
+ */
673
+ async function buildCasTurnCaptures(
674
+ observer: CasCaptureObserver,
675
+ workspaceRoot: string,
676
+ captureClass: FileCaptureClass,
677
+ ): Promise<{ casCaptures: CasPathCapture[]; unreviewablePaths: string[] }> {
678
+ // The secret partition (pure, corpus-lockable) decides what may be captured;
679
+ // this shell only performs the IO for the capturable set. The backstop that
680
+ // withholds a secret observed under the global bypass lives in the partition.
681
+ const { capturablePaths, unreviewablePaths } = partitionIgnoredPathsBySecret(
682
+ observer.before.keys(),
683
+ observer.blockedSecretPaths,
684
+ );
685
+ const casCaptures: CasPathCapture[] = [];
686
+ for (const relPath of capturablePaths) {
687
+ const after = await readFileOrNull(join(workspaceRoot, relPath));
688
+ casCaptures.push({
689
+ path: relPath,
690
+ before: observer.before.get(relPath) ?? null,
691
+ after,
692
+ captureClass,
693
+ });
694
+ }
695
+ return { casCaptures, unreviewablePaths: [...unreviewablePaths] };
696
+ }
697
+
698
+ /** Raw bytes of a file, or `null` when it does not exist (a DELETE). */
699
+ async function readFileOrNull(absolutePath: string): Promise<Uint8Array | null> {
700
+ try {
701
+ return await readFile(absolutePath);
702
+ } catch {
703
+ return null;
704
+ }
705
+ }
706
+
707
+ /**
708
+ * Push the reconciled (approved) tree exactly once, on terminal completion in
709
+ * capture mode. The coordinator's {@link WriteBackCoordinator.finalize} commits +
710
+ * pushes whatever uncommitted changes remain — which, post-reconcile, is exactly
711
+ * the approved content (rejected files were already snapped back to baseline).
712
+ * Speculative mid-turn edits never reach here (writeback is suppressed during the
713
+ * turn). Fire-and-forget by the coordinator's own contract (errors logged).
714
+ */
715
+ async function processCaptureWriteback(
716
+ writebackCoordinator: WriteBackCoordinator,
717
+ executionId: string,
718
+ ): Promise<void> {
719
+ await writebackCoordinator.finalize();
720
+ console.log(`[ExecuteDeepAgent] capture writeback finalized for execution ${executionId}`);
721
+ }
722
+
357
723
  /**
358
- * Whether this invocation resumes an existing durable checkpoint.
724
+ * Whether to seed the initial status from the persisted execution transcript.
359
725
  *
360
726
  * The Temporal workflow re-invokes ExecuteDeepAgent with the same thread_id on
361
- * three triggers — HITL approval, pause/resume, and transient recovery. With a
362
- * durable (http) checkpointer the graph resumes from its checkpoint and
363
- * streamEvents re-emits ONLY post-checkpoint events, so a status rebuilt from
364
- * empty would silently drop the prior transcript.
727
+ * every continuation — HITL approval, pause/resume, and transient recovery. The
728
+ * runner owns the transcript and the server enforces it as append-only at
729
+ * identity, so any continuation must build ON the committed history rather than
730
+ * rebuilding from an empty proto and replacing it.
365
731
  *
366
- * The resume is detected from the checkpoint itself prior message turns are
367
- * present in graph statewhich holds for every resume trigger (not just HITL)
368
- * and is false both on a first run and under the memory checkpointer (recreated
369
- * empty per invocation, so the graph replays). Requiring persisted history too
370
- * guarantees seeding never runs ahead of a transcript to seed from.
732
+ * The decision is therefore driven solely by whether the server already holds
733
+ * committed history for this execution NOT by the live graph checkpoint, which
734
+ * the two checkpointers leave in different states for the same continuation:
735
+ * - durable (http): the checkpoint survives, the graph resumes via
736
+ * Command(resume), and streamEvents re-emits only post-checkpoint events;
737
+ * - memory (the OSS local / desktop default): the checkpoint is recreated
738
+ * empty every invocation, so the graph REPLAYS from scratch and the blind
739
+ * FIFO turns advance one gate at a time.
740
+ * Keying on the live checkpoint skipped seeding on that memory-replay path, so a
741
+ * second sequential gate emitted a transcript holding only the new gate, dropped
742
+ * the first gate's committed tool-call id, and tripped the server's append-only
743
+ * guard — the silent sequential-gate skip. Seeding whenever persisted history
744
+ * exists covers both paths; a re-emitted prior turn reconciles in place by
745
+ * tool-call id (StatusBuilder.rebuildToolCallIndex), so seeding never
746
+ * duplicates. False on a first run (no persisted messages), preserving the
747
+ * original start-from-empty behavior.
371
748
  */
372
- function shouldSeedFromCheckpoint(
373
- graphState: GraphStateSnapshot,
374
- execution: AgentExecution,
375
- ): boolean {
376
- const checkpointMessages = (graphState.values as { messages?: unknown }).messages;
377
- const checkpointHasPriorTurns =
378
- Array.isArray(checkpointMessages) && checkpointMessages.length > 0;
379
- const persistedHasHistory = (execution.status?.messages.length ?? 0) > 0;
380
- return checkpointHasPriorTurns && persistedHasHistory;
749
+ function shouldSeedFromPersistedTranscript(execution: AgentExecution): boolean {
750
+ return (execution.status?.messages.length ?? 0) > 0;
381
751
  }
382
752
 
383
753
  /**