@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
@@ -9,7 +9,15 @@
9
9
  import { create } from "@bufbuild/protobuf";
10
10
  import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
11
11
  import type { StreamingUsageSummary } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
12
+ import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
12
13
  import { utcTimestamp } from "../../shared/status.js";
14
+ import {
15
+ POLICY_ENGINE_VERSION,
16
+ resolveApprovalProvenance,
17
+ toProtoPolicySource,
18
+ type MergedToolPolicy,
19
+ } from "../../shared/approval-policy.js";
20
+ import type { ToolApprovalCategory } from "../../shared/tool-kind.js";
13
21
 
14
22
  // ── Usage Accumulator ──────────────────────────────────────────────
15
23
 
@@ -129,30 +137,61 @@ export function extractToolResultV3(output: unknown): string {
129
137
  }
130
138
  }
131
139
 
132
- // ── Approval Args Sanitization ─────────────────────────────────────
140
+ // ── Authorization Provenance ───────────────────────────────────────
133
141
 
134
- const SENSITIVE_ARG_KEYS = new Set([
135
- "password", "token", "secret", "api_key", "apikey",
136
- "credentials", "auth", "authorization",
137
- ]);
142
+ /**
143
+ * The policy inputs the StatusBuilders need to attribute a tool call's approval
144
+ * provenance. The full {@link ApprovalPolicyProvider} (status-builder.ts) is
145
+ * structurally this — defined here so the shared stamper has no import cycle back
146
+ * into the builders.
147
+ */
148
+ export interface ApprovalProvenanceInputs {
149
+ readonly policies: ReadonlyMap<string, MergedToolPolicy>;
150
+ readonly toolServerMap: ReadonlyMap<string, string>;
151
+ /**
152
+ * Built-in categories with a run-lifetime lease, so a leased built-in is
153
+ * attributed to its lease (approval_lease) rather than the plain category gate.
154
+ * Optional (mirroring {@link ApprovalGateConfig.leasedCategories}); absent =
155
+ * no lease active.
156
+ */
157
+ readonly leasedCategories?: ReadonlySet<ToolApprovalCategory>;
158
+ /** Pre-armed spec.auto_approve_all — the one whole-run global bypass. */
159
+ readonly globalBypass: boolean;
160
+ }
138
161
 
139
- const MAX_ARGS_PREVIEW_LENGTH = 500;
162
+ /** Shared empty set so a provider without leases allocates nothing per call. */
163
+ const NO_LEASED_CATEGORIES: ReadonlySet<ToolApprovalCategory> = new Set();
140
164
 
141
- export function sanitizeArgsPreview(args: Record<string, unknown>): string {
142
- const sanitized: Record<string, unknown> = {};
143
- for (const [key, value] of Object.entries(args)) {
144
- if (SENSITIVE_ARG_KEYS.has(key.toLowerCase())) {
145
- sanitized[key] = "[REDACTED]";
146
- } else {
147
- sanitized[key] = value;
148
- }
149
- }
150
- try {
151
- const json = JSON.stringify(sanitized);
152
- return json.length > MAX_ARGS_PREVIEW_LENGTH
153
- ? json.slice(0, MAX_ARGS_PREVIEW_LENGTH) + "…"
154
- : json;
155
- } catch {
156
- return "";
157
- }
165
+ /**
166
+ * Stamp a tool call's authorization provenance — which policy layer governs it —
167
+ * alongside `tool_kind`, in exactly the spot both builders classify the tool.
168
+ *
169
+ * This is the read-side companion to the gate: it records WHY a tool is gated or
170
+ * auto-approved for every observed tool call, so the persisted record is
171
+ * auditable and the UI can explain the gate. Built-ins that no layer governs (a
172
+ * read-only built-in) and the no-provider path both leave the field at
173
+ * UNSPECIFIED, like an unclassified tool_kind.
174
+ */
175
+ export function stampApprovalProvenance(
176
+ tc: ToolCall,
177
+ provider: ApprovalProvenanceInputs | null,
178
+ ): void {
179
+ if (!provider) return;
180
+ const serverSlug = tc.mcpServerSlug || provider.toolServerMap.get(tc.name) || "";
181
+ const source = resolveApprovalProvenance(
182
+ tc.name,
183
+ serverSlug,
184
+ provider.policies,
185
+ provider.leasedCategories ?? NO_LEASED_CATEGORIES,
186
+ provider.globalBypass,
187
+ );
188
+ tc.approvalPolicySource = toProtoPolicySource(source);
189
+ if (source) tc.policyEngineVersion = POLICY_ENGINE_VERSION;
158
190
  }
191
+
192
+ // ── Approval Args Sanitization ─────────────────────────────────────
193
+
194
+ // Relocated to src/shared/args-preview.ts so both harnesses share one home for
195
+ // arg sanitization. Re-exported here so the native importers (status-builder,
196
+ // v3-status-builder, approval-file-change) keep their existing import path.
197
+ export { sanitizeArgsPreview } from "../../shared/args-preview.js";
@@ -25,7 +25,7 @@ import {
25
25
  MessageType,
26
26
  ToolCallStatus,
27
27
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
28
- import { type MergedToolPolicy, resolveApprovalMessage as resolveApprovalMsg } from "../../shared/approval-policy.js";
28
+ import { resolveApprovalMessage as resolveApprovalMsg } from "../../shared/approval-policy.js";
29
29
  import { classifyTool } from "../../shared/tool-kind.js";
30
30
  import { ExecutionState } from "./execution-state.js";
31
31
  import { utcTimestamp } from "../../shared/status.js";
@@ -34,6 +34,8 @@ import {
34
34
  UsageAccumulator,
35
35
  extractToolResult,
36
36
  sanitizeArgsPreview,
37
+ stampApprovalProvenance,
38
+ type ApprovalProvenanceInputs,
37
39
  } from "./status-builder-shared.js";
38
40
 
39
41
  /** Minimal LangGraph streamEvents v2 event shape. */
@@ -48,11 +50,16 @@ export interface StreamEvent {
48
50
 
49
51
  type EventHandler = (event: StreamEvent, namespace: string) => void;
50
52
 
51
- export interface ApprovalPolicyProvider {
52
- readonly policies: ReadonlyMap<string, MergedToolPolicy>;
53
- readonly toolServerMap: ReadonlyMap<string, string>;
54
- readonly autoApproveAll: boolean;
55
- }
53
+ /**
54
+ * The approval inputs the StatusBuilder reads to set requires-approval and to
55
+ * attribute each tool call's authorization provenance.
56
+ *
57
+ * Extends {@link ApprovalProvenanceInputs} so the provenance stamper and the
58
+ * builder share one shape. `globalBypass` is the pre-armed spec.auto_approve_all
59
+ * (the one whole-run global bypass); `leasedCategories` lets a leased built-in be
60
+ * attributed to its run-lifetime lease rather than the plain category gate.
61
+ */
62
+ export interface ApprovalPolicyProvider extends ApprovalProvenanceInputs {}
56
63
 
57
64
  export class StatusBuilder {
58
65
  readonly executionId: string;
@@ -67,6 +74,14 @@ export class StatusBuilder {
67
74
  this.executionId = executionId;
68
75
  this.state = new ExecutionState(initialStatus);
69
76
 
77
+ // Resume path: when constructed from a persisted transcript (status seeded
78
+ // in index.ts on a durable-checkpoint resume), rebuild the tool-call index
79
+ // so resumed tool events reconcile to the existing calls instead of
80
+ // duplicating them. A first run carries no messages, so this is a no-op.
81
+ if (initialStatus.messages.length > 0) {
82
+ this.state.rebuildToolCallIndex();
83
+ }
84
+
70
85
  initialStatus.phase = ExecutionPhase.EXECUTION_IN_PROGRESS;
71
86
  if (!initialStatus.startedAt) {
72
87
  initialStatus.startedAt = utcTimestamp();
@@ -198,11 +213,29 @@ export class StatusBuilder {
198
213
  }
199
214
 
200
215
  private handleToolStart(event: StreamEvent, namespace: string): void {
216
+ const toolName = event.name ?? "unknown_tool";
217
+
218
+ // Resume reconciliation (v2): the seeded WAITING_APPROVAL tool call was
219
+ // indexed by its tool_call_id, but a resumed on_tool_start carries a fresh
220
+ // LangGraph run_id and exposes no tool_call_id. Match by tool name +
221
+ // pending-approval status so the existing call is resolved in place rather
222
+ // than duplicated, then re-key it under run_id so handleToolEnd resolves the
223
+ // same object. This name match is a v2-only fallback — v3 (the default) keys
224
+ // by the exact tool_call_id; it resolves the first pending call of that
225
+ // name, which is unambiguous for the single-gate resume the workflow drives.
226
+ const seeded = this.findResumableSeededToolCall(toolName);
227
+ if (seeded) {
228
+ seeded.status = ToolCallStatus.TOOL_CALL_RUNNING;
229
+ this.state.toolCalls.set(event.run_id, seeded);
230
+ this.state.toolStartTimes.set(event.run_id, performance.now());
231
+ this._forceNextUpdate = true;
232
+ return;
233
+ }
234
+
201
235
  const parentMsg = this.state.currentAiMessage.get(namespace)
202
236
  ?? this.ensureAiMessageForToolCall(event.run_id, namespace);
203
237
  if (!parentMsg) return;
204
238
 
205
- const toolName = event.name ?? "unknown_tool";
206
239
  const rawArgs = event.data?.input as Record<string, unknown> | undefined;
207
240
  const args = rawArgs ?? {};
208
241
 
@@ -228,6 +261,11 @@ export class StatusBuilder {
228
261
  // Classify after mcpServerSlug is set so MCP tools resolve correctly.
229
262
  tc.toolKind = classifyTool(tc.name, tc.mcpServerSlug);
230
263
 
264
+ // Stamp authorization provenance in the same spot as tool_kind, for every
265
+ // observed tool call (gated or auto-approved). A gated call is re-seeded on
266
+ // reinvocation (index.ts) with the same source carried through the interrupt.
267
+ stampApprovalProvenance(tc, this.approvalProvider);
268
+
231
269
  if (approvalReq.requiresApproval) {
232
270
  tc.requiresApproval = true;
233
271
  tc.approvalMessage = approvalReq.message;
@@ -248,6 +286,23 @@ export class StatusBuilder {
248
286
  this._forceNextUpdate = true;
249
287
  }
250
288
 
289
+ /**
290
+ * Find a seeded tool call awaiting approval for the given name, for v2 resume
291
+ * reconciliation. Returns the first WAITING_APPROVAL call of that name in the
292
+ * rebuilt index, or undefined when none is pending (the normal first-run path).
293
+ */
294
+ private findResumableSeededToolCall(toolName: string): ToolCall | undefined {
295
+ for (const tc of this.state.toolCalls.values()) {
296
+ if (
297
+ tc.name === toolName &&
298
+ tc.status === ToolCallStatus.TOOL_CALL_WAITING_APPROVAL
299
+ ) {
300
+ return tc;
301
+ }
302
+ }
303
+ return undefined;
304
+ }
305
+
251
306
  private checkApprovalRequirement(
252
307
  toolName: string,
253
308
  args: Record<string, unknown>,
@@ -258,7 +313,7 @@ export class StatusBuilder {
258
313
 
259
314
  const serverSlug = this.approvalProvider.toolServerMap.get(toolName) ?? "";
260
315
 
261
- if (this.approvalProvider.autoApproveAll) {
316
+ if (this.approvalProvider.globalBypass) {
262
317
  return { requiresApproval: false, message: "", serverSlug };
263
318
  }
264
319
 
@@ -9,12 +9,7 @@
9
9
  import type { V3ProtocolEvent } from "./v3-event-recorder.js";
10
10
  import type { InlinePublisher } from "./inline-publisher.js";
11
11
  import type { WriteBackCoordinator } from "./writeback-coordinator.js";
12
-
13
- const FILE_MODIFYING_TOOLS = new Set([
14
- "write_file", "edit_file", "create_file",
15
- "write", "edit", "create",
16
- "str_replace_editor",
17
- ]);
12
+ import { extractFilePath, isFileModifyingTool } from "../../shared/file-tools.js";
18
13
 
19
14
  interface CachedToolInput {
20
15
  readonly toolName: string;
@@ -64,7 +59,7 @@ export class StreamingSideEffects {
64
59
  const cached = this.inputCache.get(callId);
65
60
  this.inputCache.delete(callId);
66
61
  if (!cached) return;
67
- if (!FILE_MODIFYING_TOOLS.has(cached.toolName)) return;
62
+ if (!isFileModifyingTool(cached.toolName)) return;
68
63
 
69
64
  const filePath = extractFilePath(cached.input);
70
65
  if (!filePath) return;
@@ -78,12 +73,3 @@ export class StreamingSideEffects {
78
73
  }
79
74
  }
80
75
  }
81
-
82
- function extractFilePath(input: Record<string, unknown>): string | null {
83
- if (typeof input.path === "string") return input.path;
84
- if (typeof input.file_path === "string") return input.file_path;
85
- if (typeof input.filePath === "string") return input.filePath;
86
- if (typeof input.filename === "string") return input.filename;
87
- if (typeof input.file === "string") return input.file;
88
- return null;
89
- }
@@ -42,6 +42,7 @@ import type { InlinePublisher } from "./inline-publisher.js";
42
42
  import type { WriteBackCoordinator } from "./writeback-coordinator.js";
43
43
  import { createV2EventRecorder } from "./event-recorder.js";
44
44
  import { streamExecutionV3 } from "./streaming-v3.js";
45
+ import { extractFilePath, isFileModifyingTool } from "../../shared/file-tools.js";
45
46
 
46
47
  const DEFAULT_STALL_TIMEOUT_MS = 120_000;
47
48
 
@@ -283,25 +284,14 @@ function checkStallTimeout(
283
284
  }
284
285
  }
285
286
 
286
- const FILE_MODIFYING_TOOLS = new Set([
287
- "write_file", "edit_file", "create_file",
288
- "write", "edit", "create",
289
- "str_replace_editor",
290
- ]);
291
-
292
287
  function extractFilePathFromToolEnd(event: StreamEvent): string | null {
293
288
  const toolName = event.name ?? "";
294
- if (!FILE_MODIFYING_TOOLS.has(toolName)) return null;
289
+ if (!isFileModifyingTool(toolName)) return null;
295
290
 
296
291
  const input = event.data?.input as Record<string, unknown> | undefined;
297
292
  if (!input) return null;
298
293
 
299
- if (typeof input.path === "string") return input.path;
300
- if (typeof input.file_path === "string") return input.file_path;
301
- if (typeof input.filename === "string") return input.filename;
302
- if (typeof input.file === "string") return input.file;
303
-
304
- return null;
294
+ return extractFilePath(input);
305
295
  }
306
296
 
307
297
  export class StallTimeoutError extends Error {
@@ -25,7 +25,9 @@ import type { SubAgent, McpServerUsage } from "@stigmer/protos/ai/stigmer/agenti
25
25
 
26
26
  import type { WorkspaceBackend } from "../../shared/workspace/types.js";
27
27
  import type { StigmerClient } from "../../client/stigmer-client.js";
28
- import type { MergedToolPolicy } from "../../shared/approval-policy.js";
28
+ import type { ApprovalGateConfig } from "../../middleware/approval-gate.js";
29
+ import { CasCaptureFilesystemBackend } from "./cas-capture-backend.js";
30
+ import type { CasCaptureObserver } from "./cas-capture-observer.js";
29
31
  import type { CostCapMiddleware, StigmerMiddleware } from "../../middleware/index.js";
30
32
  import { createThinkTool } from "../../middleware/index.js";
31
33
  import { buildSubAgentMiddleware } from "./subagent-wiring.js";
@@ -131,18 +133,32 @@ export interface SubagentTransformOptions {
131
133
  readonly parentMcpUsages: readonly McpServerUsage[];
132
134
  readonly skillClient: StigmerClient;
133
135
  readonly workspaceBackend: WorkspaceBackend;
134
- readonly approvalPolicies: ReadonlyMap<string, MergedToolPolicy>;
135
- readonly autoApproveAll: boolean;
136
+ /**
137
+ * The parent's approval-gate config, inherited verbatim so a mutating tool
138
+ * inside a sub-agent is gated identically to one in the parent (same policies,
139
+ * toolServerMap, fingerprint key, execution id). Null under auto-approve-all,
140
+ * where the parent gate is inert too — sub-agents then install no gate either.
141
+ */
142
+ readonly approvalGate: ApprovalGateConfig | null;
143
+ /**
144
+ * The parent turn's shared CAS observer, present only in capture mode. When
145
+ * supplied, each sub-agent is built with a CAS-observing filesystem backend
146
+ * wired to THIS observer, so its gitignored writes are captured into the same
147
+ * change set as the parent's (Session 26, DD-19). Absent outside capture mode,
148
+ * where sub-agents keep the plain backend and the classic gitignored deny-gate.
149
+ */
150
+ readonly casObserver?: CasCaptureObserver;
136
151
  readonly parentModelName: string;
137
152
  readonly parentHasNativeThinking: boolean;
138
153
  readonly costCap?: CostCapMiddleware;
139
154
  /**
140
155
  * Builds a configured chat-model instance for a given model name. When
141
156
  * provided, sub-agents are compiled with the same proxy-aware model client
142
- * as the parent (base URL, auth headers, API key). When absent (unit tests),
143
- * the model name string is passed to deepagents directly.
157
+ * as the parent (base URL, auth headers, API key) including registry-id
158
+ * resolution, which is why this is async. When absent (unit tests), the
159
+ * model name string is passed to deepagents directly.
144
160
  */
145
- readonly modelFactory?: (modelName: string) => BaseChatModel;
161
+ readonly modelFactory?: (modelName: string) => Promise<BaseChatModel>;
146
162
  }
147
163
 
148
164
  // =========================================================================
@@ -404,9 +420,12 @@ export async function compileSubagents(
404
420
  transformed: readonly TransformedSubagent[],
405
421
  opts: {
406
422
  readonly costCap?: CostCapMiddleware;
423
+ readonly approvalGate?: ApprovalGateConfig | null;
407
424
  readonly parentModelName: string;
408
425
  readonly workspaceRootDir: string;
409
- readonly modelFactory?: (modelName: string) => BaseChatModel;
426
+ /** Shared CAS observer (capture mode only); see {@link SubagentTransformOptions.casObserver}. */
427
+ readonly casObserver?: CasCaptureObserver;
428
+ readonly modelFactory?: (modelName: string) => Promise<BaseChatModel>;
410
429
  },
411
430
  ): Promise<CompiledSubAgent[]> {
412
431
  if (transformed.length === 0) return [];
@@ -416,23 +435,40 @@ export async function compileSubagents(
416
435
 
417
436
  for (const spec of transformed) {
418
437
  try {
438
+ // Structural coupling (DD-19): a sub-agent gate flows gitignored writes into
439
+ // CAS iff a CAS observer backs that sub-agent's filesystem backend. Deriving
440
+ // both from the same `casObserver` makes "unobserved unreviewable bytes"
441
+ // impossible by construction.
419
442
  const middleware = buildSubAgentMiddleware({
420
443
  costCap: opts.costCap,
444
+ approvalGate: opts.approvalGate,
445
+ captureIgnored: !!opts.casObserver,
421
446
  });
422
447
 
423
448
  const modelName = spec.model ?? opts.parentModelName;
424
- // Use a configured model instance (proxy base URL + auth) when a
425
- // factory is supplied so sub-agent LLM calls route through the same
426
- // proxy as the parent. Falling back to the bare name lets deepagents
427
- // construct a default client (unit tests / no-proxy paths).
428
- const model = opts.modelFactory ? opts.modelFactory(modelName) : modelName;
449
+ // Use a configured model instance (proxy base URL + auth, plus registry
450
+ // id -> API id resolution) when a factory is supplied so sub-agent LLM
451
+ // calls route through the same proxy as the parent. Falling back to the
452
+ // bare name lets deepagents construct a default client (unit tests /
453
+ // no-proxy paths).
454
+ const model = opts.modelFactory ? await opts.modelFactory(modelName) : modelName;
455
+
456
+ // Capture mode: a CAS-observing backend wired to the shared observer, so
457
+ // the sub-agent's gitignored writes are captured (same class the parent
458
+ // uses). Otherwise the plain backend + classic gitignored deny-gate.
459
+ const backend = opts.casObserver
460
+ ? new CasCaptureFilesystemBackend(
461
+ { rootDir: opts.workspaceRootDir },
462
+ { observer: opts.casObserver },
463
+ )
464
+ : new FilesystemBackend({ rootDir: opts.workspaceRootDir });
429
465
 
430
466
  const agentGraph = await createDeepAgent({
431
467
  model,
432
468
  systemPrompt: spec.systemPrompt,
433
469
  tools: spec.tools.length > 0 ? spec.tools : undefined,
434
470
  middleware: middleware as unknown[],
435
- backend: new FilesystemBackend({ rootDir: opts.workspaceRootDir }),
471
+ backend,
436
472
  generalPurposeAgent: false,
437
473
  } as Parameters<typeof createDeepAgent>[0]);
438
474
 
@@ -489,6 +525,8 @@ export async function transformAndCompileSubagents(
489
525
  parentMcpUsages,
490
526
  skillClient,
491
527
  workspaceBackend,
528
+ approvalGate,
529
+ casObserver,
492
530
  parentModelName,
493
531
  parentHasNativeThinking,
494
532
  costCap,
@@ -602,8 +640,10 @@ export async function transformAndCompileSubagents(
602
640
  // Step 4: Compile all subagents
603
641
  const compiled = await compileSubagents(allSpecs, {
604
642
  costCap,
643
+ approvalGate,
605
644
  parentModelName,
606
645
  workspaceRootDir: workspaceBackend.rootDir,
646
+ casObserver,
607
647
  modelFactory,
608
648
  });
609
649
 
@@ -5,13 +5,21 @@
5
5
  * - Fresh loop detection (independent cycle tracking)
6
6
  * - Fresh tool truncation (same limits as parent)
7
7
  * - Periodic execution budget (interval=30, max=4 advisories)
8
+ * - Approval gate (so a mutating tool *inside* a sub-agent is gated, not
9
+ * bypassed) — installed only when the parent itself is gated
10
+ * (`approvalGate` present; absent under auto-approve-all)
8
11
  * - Shared cost cap view (parent's budget, no reset on sub-agent start)
9
12
  *
10
13
  * The sub-agent middleware stack does NOT include:
11
14
  * - Graceful stop (parent handles STOP signal propagation)
12
15
  * - OTel spans (parent's OTel context propagates automatically)
13
16
  * - Error hints (applied at tool level, not sub-agent level)
14
- * - Approval gate (sub-agent interrupts propagate to parent checkpoint)
17
+ *
18
+ * Approval-gate note: the gate is what *creates* the LangGraph `interrupt()`.
19
+ * A sub-agent's interrupt does surface at the parent checkpoint and resumes
20
+ * correctly (verified in subagent-approval-propagation.test.ts), but only if
21
+ * the gate runs inside the sub-agent in the first place — omitting it here was
22
+ * the live HITL bypass this wiring closes.
15
23
  */
16
24
 
17
25
  import type { StigmerMiddleware, ToolTruncationConfig } from "../../middleware/types.js";
@@ -19,6 +27,10 @@ import type { CostCapMiddleware } from "../../middleware/index.js";
19
27
  import { createLoopDetectionMiddleware } from "../../middleware/loop-detection.js";
20
28
  import { createToolTruncationMiddleware } from "../../middleware/tool-truncation.js";
21
29
  import { createExecutionBudgetMiddleware } from "../../middleware/execution-budget.js";
30
+ import {
31
+ createApprovalGateMiddleware,
32
+ type ApprovalGateConfig,
33
+ } from "../../middleware/approval-gate.js";
22
34
 
23
35
  const SUB_AGENT_ADVISORY_INTERVAL = 30;
24
36
  const SUB_AGENT_MAX_ADVISORIES = 4;
@@ -26,13 +38,31 @@ const SUB_AGENT_MAX_ADVISORIES = 4;
26
38
  export interface SubAgentMiddlewareOptions {
27
39
  readonly costCap?: CostCapMiddleware;
28
40
  readonly toolTruncation?: Partial<ToolTruncationConfig>;
41
+ /**
42
+ * Approval gate config inherited from the parent. When present, the sub-agent
43
+ * gates its own mutating tool calls; when null/undefined (e.g. auto-approve-
44
+ * all, where the parent gate is inert too) no gate is installed.
45
+ */
46
+ readonly approvalGate?: ApprovalGateConfig | null;
47
+ /**
48
+ * Route the sub-agent's gitignored writes into CAS capture (apply-then-review)
49
+ * instead of the interrupt gate. TRUE iff a CAS observer backs the sub-agent's
50
+ * filesystem backend (DD-19) — `compileSubagents` derives it from
51
+ * `!!casObserver`, keeping the gate and the backend coupled. Default false =
52
+ * the classic gitignored deny-gate, because flowing a gitignored edit on an
53
+ * unobserved backend would apply unreviewable bytes.
54
+ */
55
+ readonly captureIgnored?: boolean;
29
56
  }
30
57
 
31
58
  /**
32
59
  * Build the middleware stack for a single sub-agent.
33
60
  *
34
- * Returns an ordered array matching the Python `compile_subagent` composition:
35
- * loop detection → execution budget (periodic) → tool truncation → cost cap view.
61
+ * Returns an ordered array mirroring the parent composition:
62
+ * loop detection → execution budget (periodic) → tool truncation →
63
+ * [approval gate] → cost cap view. The gate sits before the cost-cap view so an
64
+ * approval pause happens before budget accounting, matching the parent order
65
+ * (…→ truncation → graceful-stop → approval gate → cost cap …).
36
66
  */
37
67
  export function buildSubAgentMiddleware(
38
68
  options: SubAgentMiddlewareOptions = {},
@@ -48,6 +78,23 @@ export function buildSubAgentMiddleware(
48
78
 
49
79
  stack.push(createToolTruncationMiddleware(options.toolTruncation));
50
80
 
81
+ if (options.approvalGate) {
82
+ // captureIgnored (DD-19): a sub-agent flows gitignored writes into CAS iff a
83
+ // CAS observer backs its filesystem backend (compileSubagents passes this as
84
+ // `!!casObserver`). When true, inherit the parent gate verbatim so its
85
+ // captureIgnored + recordBlockedSecret feed the SAME shared observer that
86
+ // backs the sub-agent's writes. When false (default; non-capture mode, or no
87
+ // observer), force CAS routing off and drop the blocked-secret sink so
88
+ // gitignored paths stay on the interrupt gate — a flowed gitignored edit on an
89
+ // unobserved backend would apply unobserved, unreviewable bytes. Sub-agent
90
+ // git-tracked edits are always captured by the backend-agnostic boundary diff.
91
+ stack.push(createApprovalGateMiddleware(
92
+ options.captureIgnored
93
+ ? options.approvalGate
94
+ : { ...options.approvalGate, captureIgnored: false, recordBlockedSecret: undefined },
95
+ ));
96
+ }
97
+
51
98
  if (options.costCap) {
52
99
  stack.push(options.costCap.forSubAgent());
53
100
  }
@@ -24,7 +24,7 @@ import {
24
24
  MessageType,
25
25
  ToolCallStatus,
26
26
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
27
- import { type MergedToolPolicy, resolveApprovalMessage as resolveApprovalMsg } from "../../shared/approval-policy.js";
27
+ import { resolveApprovalMessage as resolveApprovalMsg } from "../../shared/approval-policy.js";
28
28
  import { classifyTool } from "../../shared/tool-kind.js";
29
29
  import { ExecutionState } from "./execution-state.js";
30
30
  import { utcTimestamp } from "../../shared/status.js";
@@ -36,6 +36,7 @@ import {
36
36
  UsageAccumulator,
37
37
  extractToolResultV3,
38
38
  sanitizeArgsPreview,
39
+ stampApprovalProvenance,
39
40
  } from "./status-builder-shared.js";
40
41
  import { SubAgentTracker } from "./subagent-tracker.js";
41
42
 
@@ -54,6 +55,15 @@ export class V3StatusBuilder implements ExecutionStatusWriter {
54
55
  this.executionId = executionId;
55
56
  this.state = new ExecutionState(initialStatus);
56
57
 
58
+ // Resume path: when constructed from a persisted transcript (status seeded
59
+ // in index.ts on a durable-checkpoint resume), rebuild the tool-call index
60
+ // so resumed tool_started/tool_finished events reconcile to the existing
61
+ // calls instead of duplicating them. A first run carries no messages, so
62
+ // this is a no-op.
63
+ if (initialStatus.messages.length > 0) {
64
+ this.state.rebuildToolCallIndex();
65
+ }
66
+
57
67
  initialStatus.phase = ExecutionPhase.EXECUTION_IN_PROGRESS;
58
68
  if (!initialStatus.startedAt) {
59
69
  initialStatus.startedAt = utcTimestamp();
@@ -244,6 +254,25 @@ export class V3StatusBuilder implements ExecutionStatusWriter {
244
254
  input: Record<string, unknown>,
245
255
  namespace: string,
246
256
  ): void {
257
+ // Resume reconciliation: the gated tool call already exists, seeded from the
258
+ // persisted transcript of a prior invocation (seedStatusFromExecution in
259
+ // index.ts). The durable checkpoint re-emits tool_started now that approval
260
+ // is granted — flip the existing call to RUNNING in place rather than
261
+ // appending a duplicate or re-triggering the approval gate. v3 keys by
262
+ // tool_call_id, so this is an exact match (no name heuristics needed).
263
+ const existing = this.state.toolCalls.get(callId);
264
+ if (existing) {
265
+ if (existing.status === ToolCallStatus.TOOL_CALL_WAITING_APPROVAL) {
266
+ existing.status = ToolCallStatus.TOOL_CALL_RUNNING;
267
+ }
268
+ if (Object.keys(input).length > 0 && !existing.args) {
269
+ existing.args = input as JsonObject;
270
+ }
271
+ this.state.toolStartTimes.set(callId, performance.now());
272
+ this._forceNextUpdate = true;
273
+ return;
274
+ }
275
+
247
276
  const agentNs = this.resolveAgentNamespace(namespace);
248
277
  const parentMsg = this.state.currentAiMessage.get(agentNs)
249
278
  ?? this.ensureAiMessageForToolCall(agentNs);
@@ -271,6 +300,11 @@ export class V3StatusBuilder implements ExecutionStatusWriter {
271
300
  // Classify after mcpServerSlug is set so MCP tools resolve correctly.
272
301
  tc.toolKind = classifyTool(tc.name, tc.mcpServerSlug);
273
302
 
303
+ // Stamp authorization provenance in the same spot as tool_kind, for every
304
+ // observed tool call (gated or auto-approved). A gated call is re-seeded on
305
+ // reinvocation (index.ts) with the same source carried through the interrupt.
306
+ stampApprovalProvenance(tc, this.approvalProvider);
307
+
274
308
  if (approvalReq.requiresApproval) {
275
309
  tc.requiresApproval = true;
276
310
  tc.approvalMessage = approvalReq.message;
@@ -368,7 +402,7 @@ export class V3StatusBuilder implements ExecutionStatusWriter {
368
402
 
369
403
  const serverSlug = this.approvalProvider.toolServerMap.get(toolName) ?? "";
370
404
 
371
- if (this.approvalProvider.autoApproveAll) {
405
+ if (this.approvalProvider.globalBypass) {
372
406
  return { requiresApproval: false, message: "", serverSlug };
373
407
  }
374
408
 
@@ -82,15 +82,15 @@ export class ClaimcheckPayloadCodec implements PayloadCodec {
82
82
  Buffer.from(payload.data!).toString("utf-8"),
83
83
  );
84
84
 
85
- const downloadUrl = await this.storage.getDownloadUrl(marker.key);
86
- const response = await fetch(downloadUrl);
87
- if (!response.ok) {
88
- throw new Error(
89
- `Claimcheck retrieve failed for key ${marker.key}: HTTP ${response.status}`,
90
- );
85
+ let rawBuf: Buffer;
86
+ try {
87
+ rawBuf = await this.storage.download(marker.key);
88
+ } catch (err) {
89
+ // Preserve the claimcheck-scoped error contract; the shared download error
90
+ // already carries the HTTP status (proxy) or the miss (local) as the cause.
91
+ const cause = err instanceof Error ? err.message : String(err);
92
+ throw new Error(`Claimcheck retrieve failed for key ${marker.key}: ${cause}`);
91
93
  }
92
-
93
- const rawBuf = Buffer.from(await response.arrayBuffer());
94
94
  const dataBuf = marker.compressed ? decompress(rawBuf) : rawBuf;
95
95
 
96
96
  return {
@@ -273,12 +273,20 @@ export class StigmerClient {
273
273
  async updateWorkflowExecutionStatus(
274
274
  executionId: string,
275
275
  status: WorkflowExecutionStatus,
276
- options?: { updatePendingApprovals?: boolean },
276
+ options?: {
277
+ updatePendingApprovals?: boolean;
278
+ updatePendingFileReviews?: boolean;
279
+ // The child a per-child merge targets (required whenever either update flag
280
+ // is set, including the scoped-clear case where the list is empty).
281
+ pendingUpdateChildAgentExecutionId?: string;
282
+ },
277
283
  ): Promise<WorkflowExecution> {
278
284
  const input = create(WorkflowExecutionUpdateStatusInputSchema, {
279
285
  executionId,
280
286
  status,
281
287
  updatePendingApprovals: options?.updatePendingApprovals ?? false,
288
+ updatePendingFileReviews: options?.updatePendingFileReviews ?? false,
289
+ pendingUpdateChildAgentExecutionId: options?.pendingUpdateChildAgentExecutionId ?? "",
282
290
  });
283
291
  return this.workflowExecutionCommand.updateStatus(input);
284
292
  }