@stigmer/runner 3.0.9-dev.20260616060535 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
  3. package/dist/__test-utils__/approval-contract/types.js +24 -0
  4. package/dist/__test-utils__/approval-contract/types.js.map +1 -0
  5. package/dist/activities/call-agent-status.d.ts +19 -1
  6. package/dist/activities/call-agent-status.js +64 -5
  7. package/dist/activities/call-agent-status.js.map +1 -1
  8. package/dist/activities/call-llm.js +19 -53
  9. package/dist/activities/call-llm.js.map +1 -1
  10. package/dist/activities/classify-tool-approvals.d.ts +24 -0
  11. package/dist/activities/classify-tool-approvals.js +69 -17
  12. package/dist/activities/classify-tool-approvals.js.map +1 -1
  13. package/dist/activities/discover-mcp-server.d.ts +7 -0
  14. package/dist/activities/discover-mcp-server.js +11 -1
  15. package/dist/activities/discover-mcp-server.js.map +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
  17. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
  18. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
  19. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
  20. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
  21. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
  22. package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
  23. package/dist/activities/execute-cursor/approval-policy.js +8 -120
  24. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  25. package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
  26. package/dist/activities/execute-cursor/approval-state.js +233 -17
  27. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  28. package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
  29. package/dist/activities/execute-cursor/capture-flow.js +234 -0
  30. package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
  31. package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
  32. package/dist/activities/execute-cursor/cas-observations.js +184 -0
  33. package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
  34. package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
  35. package/dist/activities/execute-cursor/command-provenance.js +128 -0
  36. package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
  37. package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
  38. package/dist/activities/execute-cursor/exact-apply.js +204 -0
  39. package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
  40. package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
  41. package/dist/activities/execute-cursor/hook-script.js +310 -47
  42. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  43. package/dist/activities/execute-cursor/index.d.ts +8 -1
  44. package/dist/activities/execute-cursor/index.js +500 -64
  45. package/dist/activities/execute-cursor/index.js.map +1 -1
  46. package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
  47. package/dist/activities/execute-cursor/message-translator.js +836 -90
  48. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  49. package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
  50. package/dist/activities/execute-cursor/prompt-builder.js +74 -15
  51. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  52. package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
  53. package/dist/activities/execute-cursor/workspace-setup.js +212 -33
  54. package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
  55. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
  56. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
  57. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
  58. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
  59. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
  60. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
  61. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
  62. package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
  63. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
  64. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
  65. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
  66. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
  68. package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
  69. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
  70. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
  71. package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
  72. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
  73. package/dist/activities/execute-deep-agent/hitl.d.ts +10 -0
  74. package/dist/activities/execute-deep-agent/hitl.js +5 -1
  75. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  76. package/dist/activities/execute-deep-agent/index.d.ts +2 -1
  77. package/dist/activities/execute-deep-agent/index.js +370 -56
  78. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  79. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
  80. package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
  81. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  82. package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
  83. package/dist/activities/execute-deep-agent/setup.js +149 -92
  84. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  85. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
  86. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
  87. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
  88. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
  89. package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
  90. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  91. package/dist/activities/execute-deep-agent/status-builder.d.ts +11 -5
  92. package/dist/activities/execute-deep-agent/status-builder.js +6 -2
  93. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  94. package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
  95. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/streaming.js +3 -15
  97. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  98. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
  99. package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
  100. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  101. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
  102. package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
  103. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  104. package/dist/activities/execute-deep-agent/v3-status-builder.js +6 -2
  105. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  106. package/dist/claimcheck/payload-codec.js +9 -5
  107. package/dist/claimcheck/payload-codec.js.map +1 -1
  108. package/dist/client/stigmer-client.d.ts +2 -0
  109. package/dist/client/stigmer-client.js +2 -0
  110. package/dist/client/stigmer-client.js.map +1 -1
  111. package/dist/middleware/approval-gate.d.ts +85 -4
  112. package/dist/middleware/approval-gate.js +165 -38
  113. package/dist/middleware/approval-gate.js.map +1 -1
  114. package/dist/middleware/types.d.ts +2 -5
  115. package/dist/shared/activity-input.d.ts +43 -0
  116. package/dist/shared/activity-input.js +17 -0
  117. package/dist/shared/activity-input.js.map +1 -0
  118. package/dist/shared/approval-canonicalize.d.ts +19 -0
  119. package/dist/shared/approval-canonicalize.js +119 -0
  120. package/dist/shared/approval-canonicalize.js.map +1 -0
  121. package/dist/shared/approval-fingerprint.d.ts +106 -0
  122. package/dist/shared/approval-fingerprint.js +113 -0
  123. package/dist/shared/approval-fingerprint.js.map +1 -0
  124. package/dist/shared/approval-policy.d.ts +182 -12
  125. package/dist/shared/approval-policy.js +213 -27
  126. package/dist/shared/approval-policy.js.map +1 -1
  127. package/dist/shared/args-preview.d.ts +52 -0
  128. package/dist/shared/args-preview.js +93 -0
  129. package/dist/shared/args-preview.js.map +1 -0
  130. package/dist/shared/artifact-storage.d.ts +19 -1
  131. package/dist/shared/artifact-storage.js +48 -11
  132. package/dist/shared/artifact-storage.js.map +1 -1
  133. package/dist/shared/file-change.d.ts +44 -0
  134. package/dist/shared/file-change.js +57 -0
  135. package/dist/shared/file-change.js.map +1 -0
  136. package/dist/shared/file-tools.d.ts +107 -0
  137. package/dist/shared/file-tools.js +168 -0
  138. package/dist/shared/file-tools.js.map +1 -0
  139. package/dist/shared/filereview/capture.d.ts +202 -0
  140. package/dist/shared/filereview/capture.js +498 -0
  141. package/dist/shared/filereview/capture.js.map +1 -0
  142. package/dist/shared/filereview/cas-substrate.d.ts +190 -0
  143. package/dist/shared/filereview/cas-substrate.js +284 -0
  144. package/dist/shared/filereview/cas-substrate.js.map +1 -0
  145. package/dist/shared/filereview/digest.d.ts +40 -0
  146. package/dist/shared/filereview/digest.js +66 -0
  147. package/dist/shared/filereview/digest.js.map +1 -0
  148. package/dist/shared/filereview/events.d.ts +170 -0
  149. package/dist/shared/filereview/events.js +298 -0
  150. package/dist/shared/filereview/events.js.map +1 -0
  151. package/dist/shared/filereview/git-substrate.d.ts +175 -0
  152. package/dist/shared/filereview/git-substrate.js +439 -0
  153. package/dist/shared/filereview/git-substrate.js.map +1 -0
  154. package/dist/shared/filereview/index.d.ts +11 -0
  155. package/dist/shared/filereview/index.js +12 -0
  156. package/dist/shared/filereview/index.js.map +1 -0
  157. package/dist/shared/filereview/secret-paths.d.ts +63 -0
  158. package/dist/shared/filereview/secret-paths.js +105 -0
  159. package/dist/shared/filereview/secret-paths.js.map +1 -0
  160. package/dist/shared/fingerprint-secret.d.ts +26 -0
  161. package/dist/shared/fingerprint-secret.js +47 -0
  162. package/dist/shared/fingerprint-secret.js.map +1 -0
  163. package/dist/shared/model-client.d.ts +51 -0
  164. package/dist/shared/model-client.js +77 -0
  165. package/dist/shared/model-client.js.map +1 -0
  166. package/dist/shared/plan-artifact.js +0 -2
  167. package/dist/shared/plan-artifact.js.map +1 -1
  168. package/dist/shared/status-offload.d.ts +83 -9
  169. package/dist/shared/status-offload.js +399 -79
  170. package/dist/shared/status-offload.js.map +1 -1
  171. package/dist/shared/status.js +14 -1
  172. package/dist/shared/status.js.map +1 -1
  173. package/dist/shared/tool-kind.d.ts +19 -0
  174. package/dist/shared/tool-kind.js +13 -0
  175. package/dist/shared/tool-kind.js.map +1 -1
  176. package/dist/shared/tool-row.d.ts +88 -0
  177. package/dist/shared/tool-row.js +127 -0
  178. package/dist/shared/tool-row.js.map +1 -0
  179. package/dist/shared/workspace/platform-dir.d.ts +25 -0
  180. package/dist/shared/workspace/platform-dir.js +38 -2
  181. package/dist/shared/workspace/platform-dir.js.map +1 -1
  182. package/dist/workflows/call-agent-orchestrator.js +56 -7
  183. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  184. package/dist/workflows/connect-mcp-server.d.ts +50 -0
  185. package/dist/workflows/connect-mcp-server.js +136 -15
  186. package/dist/workflows/connect-mcp-server.js.map +1 -1
  187. package/dist/workflows/types.d.ts +8 -0
  188. package/package.json +2 -2
  189. package/src/__test-utils__/approval-contract/contract.ts +224 -0
  190. package/src/__test-utils__/approval-contract/types.ts +179 -0
  191. package/src/__test-utils__/fake-artifact-storage.ts +72 -0
  192. package/src/__tests__/approval-gateway-contract.test.ts +29 -0
  193. package/src/__tests__/claimcheck-codec.test.ts +16 -53
  194. package/src/__tests__/golden-e2e.test.ts +2 -0
  195. package/src/__tests__/runner-token-coordinator.test.ts +3 -3
  196. package/src/activities/__tests__/call-agent-status.test.ts +135 -0
  197. package/src/activities/__tests__/call-llm.test.ts +1 -1
  198. package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
  199. package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
  200. package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
  201. package/src/activities/call-agent-status.ts +74 -4
  202. package/src/activities/call-llm.ts +18 -63
  203. package/src/activities/classify-tool-approvals.ts +101 -19
  204. package/src/activities/discover-mcp-server.ts +29 -1
  205. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
  206. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
  207. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
  208. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
  209. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
  210. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
  211. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
  212. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
  213. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
  214. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
  215. package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
  216. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
  217. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
  218. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
  219. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
  220. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
  221. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
  222. package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
  223. package/src/activities/execute-cursor/approval-policy.ts +28 -159
  224. package/src/activities/execute-cursor/approval-state.ts +366 -18
  225. package/src/activities/execute-cursor/capture-flow.ts +323 -0
  226. package/src/activities/execute-cursor/cas-observations.ts +204 -0
  227. package/src/activities/execute-cursor/command-provenance.ts +168 -0
  228. package/src/activities/execute-cursor/exact-apply.ts +253 -0
  229. package/src/activities/execute-cursor/hook-script.ts +317 -51
  230. package/src/activities/execute-cursor/index.ts +575 -67
  231. package/src/activities/execute-cursor/message-translator.ts +963 -89
  232. package/src/activities/execute-cursor/prompt-builder.ts +80 -14
  233. package/src/activities/execute-cursor/workspace-setup.ts +257 -42
  234. package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
  235. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
  236. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
  237. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
  238. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
  239. package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
  240. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
  241. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
  242. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +2 -5
  243. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
  244. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
  245. package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
  246. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +12 -11
  247. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
  248. package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
  249. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
  250. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
  251. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
  252. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
  253. package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
  254. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
  255. package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
  256. package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
  257. package/src/activities/execute-deep-agent/hitl.ts +15 -1
  258. package/src/activities/execute-deep-agent/index.ts +434 -64
  259. package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
  260. package/src/activities/execute-deep-agent/setup.ts +223 -125
  261. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
  262. package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
  263. package/src/activities/execute-deep-agent/status-builder.ts +19 -7
  264. package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
  265. package/src/activities/execute-deep-agent/streaming.ts +3 -13
  266. package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
  267. package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
  268. package/src/activities/execute-deep-agent/v3-status-builder.ts +8 -2
  269. package/src/claimcheck/payload-codec.ts +8 -8
  270. package/src/client/stigmer-client.ts +9 -1
  271. package/src/middleware/__tests__/approval-gate.test.ts +488 -4
  272. package/src/middleware/approval-gate.ts +247 -38
  273. package/src/middleware/types.ts +5 -5
  274. package/src/shared/__tests__/activity-input.test.ts +78 -0
  275. package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
  276. package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
  277. package/src/shared/__tests__/approval-policy.test.ts +274 -40
  278. package/src/shared/__tests__/args-preview.test.ts +78 -0
  279. package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
  280. package/src/shared/__tests__/artifact-storage.test.ts +123 -11
  281. package/src/shared/__tests__/file-change.test.ts +85 -0
  282. package/src/shared/__tests__/file-tools.test.ts +90 -0
  283. package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
  284. package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
  285. package/src/shared/__tests__/model-client.test.ts +162 -0
  286. package/src/shared/__tests__/plan-artifact.test.ts +11 -26
  287. package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
  288. package/src/shared/__tests__/status-offload.test.ts +573 -16
  289. package/src/shared/__tests__/status.test.ts +4 -5
  290. package/src/shared/__tests__/tool-kind.test.ts +24 -1
  291. package/src/shared/__tests__/tool-row.test.ts +221 -0
  292. package/src/shared/activity-input.ts +57 -0
  293. package/src/shared/approval-canonicalize.ts +159 -0
  294. package/src/shared/approval-fingerprint.ts +148 -0
  295. package/src/shared/approval-policy.ts +303 -27
  296. package/src/shared/args-preview.ts +98 -0
  297. package/src/shared/artifact-storage.ts +62 -11
  298. package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
  299. package/src/shared/file-change.ts +64 -0
  300. package/src/shared/file-tools.ts +169 -0
  301. package/src/shared/filereview/__tests__/capture.test.ts +856 -0
  302. package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
  303. package/src/shared/filereview/__tests__/digest.test.ts +100 -0
  304. package/src/shared/filereview/__tests__/events.test.ts +245 -0
  305. package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
  306. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
  307. package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
  308. package/src/shared/filereview/capture.ts +727 -0
  309. package/src/shared/filereview/cas-substrate.ts +401 -0
  310. package/src/shared/filereview/digest.ts +83 -0
  311. package/src/shared/filereview/events.ts +449 -0
  312. package/src/shared/filereview/git-substrate.ts +555 -0
  313. package/src/shared/filereview/index.ts +60 -0
  314. package/src/shared/filereview/secret-paths.ts +121 -0
  315. package/src/shared/fingerprint-secret.ts +53 -0
  316. package/src/shared/model-client.ts +122 -0
  317. package/src/shared/plan-artifact.ts +0 -2
  318. package/src/shared/status-offload.ts +433 -77
  319. package/src/shared/status.ts +13 -0
  320. package/src/shared/tool-kind.ts +33 -0
  321. package/src/shared/tool-row.ts +135 -0
  322. package/src/shared/workspace/platform-dir.ts +41 -2
  323. package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
  324. package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
  325. package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
  326. package/src/workflows/call-agent-orchestrator.ts +53 -6
  327. package/src/workflows/connect-mcp-server.ts +179 -24
  328. package/src/workflows/types.ts +8 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Approved-command turn provenance (DD-28): the runner-owned qualification for
3
+ * the auto-keep policy.
4
+ *
5
+ * A turn whose ONLY mutation source was shell commands the human had already
6
+ * authorized should not re-gate its file effects at the turn-boundary review —
7
+ * the user consented to the command, and the command's file effects are the
8
+ * consented outcome. The server cannot derive this itself (tool calls carry no
9
+ * turn marker), so the runner — the only component that owns turn scoping —
10
+ * derives the facts here and attaches them to the CANDIDATE_CAPTURED event as
11
+ * {@link TurnCommandProvenance}.
12
+ *
13
+ * TRUST BOUNDARY. This module asserts turn FACTS (which tools ran this turn),
14
+ * the same trust level as the captured bytes themselves. It never asserts
15
+ * CONSENT: `consentToolCallIds` merely POINT at transcript rows whose
16
+ * `approval_action` was authored by the server's SubmitApproval (and is
17
+ * preserved against runner writes) — the backend re-verifies every claimed row
18
+ * against that server-owned record before authoring the policy decision, so a
19
+ * runner cannot mint authorization it was never given.
20
+ *
21
+ * FAIL-CLOSED. Every uncertainty disqualifies (returns undefined → the set
22
+ * reviews manually, exactly as before DD-28): a file-tool call, an MCP tool, a
23
+ * sub-agent delegation, an unrecognized tool name, or an executed shell command
24
+ * with no provable consent source. Being conservative here costs only an extra
25
+ * review; being permissive would silently waive one.
26
+ */
27
+ import { create } from "@bufbuild/protobuf";
28
+ import { TurnCommandProvenanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
29
+ import { ApprovalAction, ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
30
+ import { classifyTool, toolApprovalCategory } from "../../shared/tool-kind.js";
31
+ import { isToolCallRowHidden } from "../../shared/tool-row.js";
32
+ import { toolCallIdentityToken } from "./message-translator.js";
33
+ /**
34
+ * Tool kinds that can never mutate the workspace: their presence in a turn is
35
+ * irrelevant to change-set provenance. Everything OUTSIDE this set is either a
36
+ * consent-mapped shell command or a disqualifier — never silently ignored.
37
+ */
38
+ const NON_MUTATING_KINDS = new Set([
39
+ ToolKind.FILE_READ,
40
+ ToolKind.SEARCH,
41
+ ToolKind.LIST,
42
+ ToolKind.FETCH,
43
+ ToolKind.WEB_SEARCH,
44
+ ToolKind.THINK,
45
+ ToolKind.TODO,
46
+ ]);
47
+ /**
48
+ * Derive the {@link TurnCommandProvenance} for the turn, or undefined when the
49
+ * turn does not qualify (any non-shell mutation-capable call, any unknown tool,
50
+ * any executed command without a provable consent source, or no executed
51
+ * consented command at all).
52
+ */
53
+ export function deriveTurnCommandProvenance(inputs) {
54
+ const { messages, turnStartIndex, deniedTokens, grantTokenToConsentId, globalBypass } = inputs;
55
+ const consentIds = new Set();
56
+ let authorizedByAutoApproveAll = false;
57
+ let executedCommandCount = 0;
58
+ for (let i = turnStartIndex; i < messages.length; i++) {
59
+ for (const tc of messages[i].toolCalls) {
60
+ // A hidden row is a collapsed twin/reaction — it never executed.
61
+ if (isToolCallRowHidden(tc))
62
+ continue;
63
+ const kind = classifyTool(tc.name, tc.mcpServerSlug);
64
+ if (NON_MUTATING_KINDS.has(kind))
65
+ continue;
66
+ if (kind !== ToolKind.SHELL) {
67
+ // A file tool (mixed turn), a sub-agent (unattributable mutations), an
68
+ // MCP tool (opaque side effects), or an unrecognized name — the turn's
69
+ // mutations cannot be attributed to consented commands. Fail closed.
70
+ return undefined;
71
+ }
72
+ const token = toolCallIdentityToken(tc);
73
+ if (deniedTokens.has(token)) {
74
+ // This turn's pending gate — denied by the hook, never executed.
75
+ continue;
76
+ }
77
+ executedCommandCount++;
78
+ const consentId = grantTokenToConsentId.get(token);
79
+ if (consentId) {
80
+ consentIds.add(consentId);
81
+ continue;
82
+ }
83
+ const leaseConsentId = findLeaseConsentId(messages, tc.name);
84
+ if (leaseConsentId) {
85
+ consentIds.add(leaseConsentId);
86
+ continue;
87
+ }
88
+ if (globalBypass) {
89
+ authorizedByAutoApproveAll = true;
90
+ continue;
91
+ }
92
+ // An executed shell command with no grant, no lease, and no bypass should
93
+ // be impossible (the hook gates every un-consented shell); if it ever
94
+ // happens, the honest answer is a manual review, not a waived one.
95
+ return undefined;
96
+ }
97
+ }
98
+ // A turn that executed no consented command has nothing to attribute the
99
+ // change set to — whatever changed came from somewhere else. Manual review.
100
+ if (executedCommandCount === 0)
101
+ return undefined;
102
+ return create(TurnCommandProvenanceSchema, {
103
+ consentToolCallIds: [...consentIds],
104
+ authorizedByAutoApproveAll,
105
+ });
106
+ }
107
+ /**
108
+ * Find the transcript row whose APPROVE_ALL authored the run-lifetime lease
109
+ * covering `toolName`'s category — the consent row a lease-executed command
110
+ * cites. Searched across the whole transcript (a lease usually originates in a
111
+ * prior turn); the first (earliest) APPROVE_ALL of the category is the lease's
112
+ * origin. Returns undefined when no lease row exists for the category.
113
+ */
114
+ function findLeaseConsentId(messages, toolName) {
115
+ const category = toolApprovalCategory(toolName);
116
+ if (!category)
117
+ return undefined;
118
+ for (const msg of messages) {
119
+ for (const tc of msg.toolCalls) {
120
+ if (tc.approvalAction !== ApprovalAction.APPROVE_ALL)
121
+ continue;
122
+ if (toolApprovalCategory(tc.name) === category)
123
+ return tc.id;
124
+ }
125
+ }
126
+ return undefined;
127
+ }
128
+ //# sourceMappingURL=command-provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-provenance.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/command-provenance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Resume-time exact-apply for approved whole-file writes (Cursor deny-only HITL).
3
+ *
4
+ * THE GUARANTEE THIS RESTORES
5
+ * ---------------------------
6
+ * A user must get "what you approve is what gets applied" — the contract the
7
+ * native LangGraph harness and Cursor's own IDE honor by pausing at the exact
8
+ * tool call and applying its exact args on approval. The Cursor deny-only
9
+ * harness cannot pause: it denies the tool, grants the RESOURCE (`write\n<path>`),
10
+ * and reinvokes the model, which REGENERATES content from scratch. So the bytes
11
+ * that land can differ from the bytes the gate showed (observed in production: a
12
+ * gate previewing one edit, an applied file carrying more).
13
+ *
14
+ * The fix turns an approved WHOLE-FILE write into a runner-executed action: the
15
+ * runner writes the EXACT approved bytes itself, marks the tool COMPLETED, and —
16
+ * crucially — issues NO resource grant for it. Any FURTHER write the model makes
17
+ * to that file on reinvocation is therefore re-gated, so the user sees and
18
+ * approves every change. This module owns the apply AND the grant-exclusion
19
+ * derivation ({@link excludeAppliedFromGrants}, the "issues NO grant" half); the
20
+ * caller wires the resulting grants and the prompt split (so the model is told
21
+ * the write is already applied and does not redo it).
22
+ *
23
+ * SCOPE — whole-file writes only. A hunk edit (`old_string`/`new_string`) is left
24
+ * on the grant + reinvocation path: applying a hunk would require locating the
25
+ * fragment in the file, which would make the runner a second source of truth for
26
+ * the edit result. Shell/MCP grants are already command-/name-specific, so their
27
+ * approved==applied property is already tight.
28
+ *
29
+ * SOURCE OF TRUTH — the approved whole-file bytes and target path are read from
30
+ * the gated tool call's `args` (the authoritative proposed content the deny-gate
31
+ * stamped from the hook input; see execute-cursor/message-translator.ts
32
+ * `applyGateInput`). This is the single copy — there is no separate captured
33
+ * `file_changes` mirror — so "what was shown == what is applied" holds by
34
+ * construction. (Phase 5 Slice 4 removed the redundant `ToolCall.file_changes`
35
+ * copy; `args` was always its source.)
36
+ *
37
+ * SAFETY — exact-apply writes ONLY a fully-resolved body. It never writes a
38
+ * truncated preview or the elision marker (which would silently corrupt the
39
+ * file); an unresolvable body or any write failure degrades to the existing
40
+ * grant + reinvocation path. Corruption is never an option — degradation to the
41
+ * prior behavior is.
42
+ *
43
+ * TESTING — the apply + grant-exclusion + re-gating composition is proven
44
+ * end-to-end (real workspace backend + the real deny-oracle hook) in
45
+ * `__tests__/deny-gate-exact-apply.test.ts`. That deterministic runner test is
46
+ * the achievable substitute for a pure-Go offline e2e, which is structurally
47
+ * infeasible (there is no offline Cursor agent driver — see DD-23).
48
+ */
49
+ import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
50
+ import type { PendingApproval } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb";
51
+ import type { WorkspaceBackend } from "../../shared/workspace/types.js";
52
+ /** Options for {@link applyApprovedWholeFileWrites}. */
53
+ export interface ExactApplyOptions {
54
+ /** The (seeded) transcript whose approved tool calls are applied in place. */
55
+ readonly messages: AgentMessage[];
56
+ /** Workspace the files live in (local FS for OSS, sandbox in cloud). */
57
+ readonly workspaceBackend: WorkspaceBackend;
58
+ /** Configured workspace roots; a write is refused outside all of them. */
59
+ readonly workspaceDirs: readonly string[];
60
+ /** For structured logs. */
61
+ readonly executionId: string;
62
+ }
63
+ /**
64
+ * Apply every APPROVED whole-file write in `messages` to disk with its EXACT
65
+ * approved bytes, marking each applied tool call COMPLETED in place.
66
+ *
67
+ * Returns the set of tool-call ids that were exact-applied, so the caller can
68
+ * (a) exclude their resources from the approval grants — the key to re-gating a
69
+ * later write — and (b) describe them as "already applied" in the reinvocation
70
+ * prompt rather than asking the model to redo them.
71
+ *
72
+ * Idempotent: a call already COMPLETED (a Temporal activity retry re-runs this
73
+ * phase) is skipped, never written twice. Every uncertain case (no resolvable
74
+ * body, out-of-workspace target, write failure) is SKIPPED, leaving the call
75
+ * WAITING_APPROVAL so the existing grant + reinvocation path handles it — the
76
+ * conservative fallback that can never corrupt a file.
77
+ */
78
+ export declare function applyApprovedWholeFileWrites(opts: ExactApplyOptions): Promise<Set<string>>;
79
+ /**
80
+ * The adjudicated approvals MINUS the ones the runner already exact-applied — the
81
+ * set the caller turns into resource grants for the resumed agent.
82
+ *
83
+ * This is the "issues NO grant for an applied write" half of the guarantee: an
84
+ * exact-applied whole-file write is intentionally NOT granted, so if the
85
+ * reinvoked model writes to that same file again it is re-gated and the user
86
+ * reviews the new change (the deny-gate's "what you approve is what gets applied"
87
+ * property). Non-applied approvals (hunk edits, shell, MCP, and every reject /
88
+ * skip) pass through unchanged — they still need their grant to flow on resume.
89
+ *
90
+ * Pure and order-independent. Load-bearing because the caller computes the
91
+ * adjudicated set ONCE (before {@link applyApprovedWholeFileWrites} flips the
92
+ * applied calls to COMPLETED) and never re-derives it, so this exclusion is the
93
+ * only thing that keeps an applied write out of the grants.
94
+ */
95
+ export declare function excludeAppliedFromGrants(adjudicatedApprovals: readonly PendingApproval[], appliedToolCallIds: ReadonlySet<string>): PendingApproval[];
96
+ /**
97
+ * The exact approved bytes for a whole-file write, or `null` when they cannot be
98
+ * recovered (in which case the caller must fall back, never write).
99
+ *
100
+ * The bytes come from the gated call's `args` whole-file content — the single
101
+ * authoritative copy of the proposed body (the deny-gate stamped it from the
102
+ * hook input; see {@link isApprovedWholeFileWrite}). `args` is never offloaded to
103
+ * a ref (only dropped wholesale by the aggregate size backstop), so when present
104
+ * it is intact.
105
+ *
106
+ * It NEVER returns the elision marker: that is a lossy stand-in and writing it
107
+ * would corrupt the file. An `args` body equal to the marker is treated as
108
+ * unresolved, and the caller degrades to grant + reinvocation.
109
+ */
110
+ export declare function resolveApprovedWholeFileContent(tc: ToolCall): string | null;
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Resume-time exact-apply for approved whole-file writes (Cursor deny-only HITL).
3
+ *
4
+ * THE GUARANTEE THIS RESTORES
5
+ * ---------------------------
6
+ * A user must get "what you approve is what gets applied" — the contract the
7
+ * native LangGraph harness and Cursor's own IDE honor by pausing at the exact
8
+ * tool call and applying its exact args on approval. The Cursor deny-only
9
+ * harness cannot pause: it denies the tool, grants the RESOURCE (`write\n<path>`),
10
+ * and reinvokes the model, which REGENERATES content from scratch. So the bytes
11
+ * that land can differ from the bytes the gate showed (observed in production: a
12
+ * gate previewing one edit, an applied file carrying more).
13
+ *
14
+ * The fix turns an approved WHOLE-FILE write into a runner-executed action: the
15
+ * runner writes the EXACT approved bytes itself, marks the tool COMPLETED, and —
16
+ * crucially — issues NO resource grant for it. Any FURTHER write the model makes
17
+ * to that file on reinvocation is therefore re-gated, so the user sees and
18
+ * approves every change. This module owns the apply AND the grant-exclusion
19
+ * derivation ({@link excludeAppliedFromGrants}, the "issues NO grant" half); the
20
+ * caller wires the resulting grants and the prompt split (so the model is told
21
+ * the write is already applied and does not redo it).
22
+ *
23
+ * SCOPE — whole-file writes only. A hunk edit (`old_string`/`new_string`) is left
24
+ * on the grant + reinvocation path: applying a hunk would require locating the
25
+ * fragment in the file, which would make the runner a second source of truth for
26
+ * the edit result. Shell/MCP grants are already command-/name-specific, so their
27
+ * approved==applied property is already tight.
28
+ *
29
+ * SOURCE OF TRUTH — the approved whole-file bytes and target path are read from
30
+ * the gated tool call's `args` (the authoritative proposed content the deny-gate
31
+ * stamped from the hook input; see execute-cursor/message-translator.ts
32
+ * `applyGateInput`). This is the single copy — there is no separate captured
33
+ * `file_changes` mirror — so "what was shown == what is applied" holds by
34
+ * construction. (Phase 5 Slice 4 removed the redundant `ToolCall.file_changes`
35
+ * copy; `args` was always its source.)
36
+ *
37
+ * SAFETY — exact-apply writes ONLY a fully-resolved body. It never writes a
38
+ * truncated preview or the elision marker (which would silently corrupt the
39
+ * file); an unresolvable body or any write failure degrades to the existing
40
+ * grant + reinvocation path. Corruption is never an option — degradation to the
41
+ * prior behavior is.
42
+ *
43
+ * TESTING — the apply + grant-exclusion + re-gating composition is proven
44
+ * end-to-end (real workspace backend + the real deny-oracle hook) in
45
+ * `__tests__/deny-gate-exact-apply.test.ts`. That deterministic runner test is
46
+ * the achievable substitute for a pure-Go offline e2e, which is structurally
47
+ * infeasible (there is no offline Cursor agent driver — see DD-23).
48
+ */
49
+ import { isAbsolute, relative, resolve } from "node:path";
50
+ import { ApprovalAction, ToolCallStatus, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
51
+ import { ELISION_MARKER } from "../../shared/status-offload.js";
52
+ import { extractFilePath, extractWriteContent } from "../../shared/file-tools.js";
53
+ import { resolveWorkspacePath } from "../../shared/file-change.js";
54
+ import { utcTimestamp } from "../../shared/status.js";
55
+ /**
56
+ * Apply every APPROVED whole-file write in `messages` to disk with its EXACT
57
+ * approved bytes, marking each applied tool call COMPLETED in place.
58
+ *
59
+ * Returns the set of tool-call ids that were exact-applied, so the caller can
60
+ * (a) exclude their resources from the approval grants — the key to re-gating a
61
+ * later write — and (b) describe them as "already applied" in the reinvocation
62
+ * prompt rather than asking the model to redo them.
63
+ *
64
+ * Idempotent: a call already COMPLETED (a Temporal activity retry re-runs this
65
+ * phase) is skipped, never written twice. Every uncertain case (no resolvable
66
+ * body, out-of-workspace target, write failure) is SKIPPED, leaving the call
67
+ * WAITING_APPROVAL so the existing grant + reinvocation path handles it — the
68
+ * conservative fallback that can never corrupt a file.
69
+ */
70
+ export async function applyApprovedWholeFileWrites(opts) {
71
+ const applied = new Set();
72
+ for (const msg of opts.messages) {
73
+ for (const tc of msg.toolCalls) {
74
+ if (!isApprovedWholeFileWrite(tc))
75
+ continue;
76
+ const args = argsRecord(tc);
77
+ const rawPath = extractFilePath(args);
78
+ if (!rawPath) {
79
+ // A whole-file write always carries a path arg; a missing one is
80
+ // unexpected — do not guess a path, fall back.
81
+ logSkip(opts.executionId, tc, "no file path in tool args");
82
+ continue;
83
+ }
84
+ // Resolve against the workspace root with the same convention the gate used
85
+ // (Cursor: real paths, not virtual), so the applied path matches the one the
86
+ // user approved.
87
+ const { absolutePath: target } = resolveWorkspacePath(rawPath, opts.workspaceBackend.rootDir,
88
+ /* virtualRoot */ false);
89
+ if (!isWithinWorkspace(target, opts.workspaceDirs)) {
90
+ logSkip(opts.executionId, tc, `target outside workspace: ${target}`);
91
+ continue;
92
+ }
93
+ const content = resolveApprovedWholeFileContent(tc);
94
+ if (content === null) {
95
+ logSkip(opts.executionId, tc, "exact approved bytes unresolvable");
96
+ continue;
97
+ }
98
+ try {
99
+ await opts.workspaceBackend.writeFile(target, content);
100
+ }
101
+ catch (err) {
102
+ logSkip(opts.executionId, tc, `write failed: ${err instanceof Error ? err.message : String(err)}`);
103
+ continue;
104
+ }
105
+ // The tool is now genuinely done: the runner applied exactly what the user
106
+ // approved (the same `args` bytes the gate showed). Mark COMPLETED in place
107
+ // (same id — the backend's append-only-at-identity guard accepts a status
108
+ // change). The approval_action is preserved as the audit trail; the status
109
+ // flip removes it from the server's pending_approvals projection (which
110
+ // keys on WAITING_APPROVAL).
111
+ tc.status = ToolCallStatus.TOOL_CALL_COMPLETED;
112
+ if (!tc.completedAt)
113
+ tc.completedAt = utcTimestamp();
114
+ tc.error = "";
115
+ applied.add(tc.id);
116
+ console.log(`ExecuteCursor exact-apply: wrote approved bytes to ${target} ` +
117
+ `(tool=${tc.id}); no resource grant issued, so a further change ` +
118
+ `re-gates. execution=${opts.executionId}`);
119
+ }
120
+ }
121
+ return applied;
122
+ }
123
+ /**
124
+ * The adjudicated approvals MINUS the ones the runner already exact-applied — the
125
+ * set the caller turns into resource grants for the resumed agent.
126
+ *
127
+ * This is the "issues NO grant for an applied write" half of the guarantee: an
128
+ * exact-applied whole-file write is intentionally NOT granted, so if the
129
+ * reinvoked model writes to that same file again it is re-gated and the user
130
+ * reviews the new change (the deny-gate's "what you approve is what gets applied"
131
+ * property). Non-applied approvals (hunk edits, shell, MCP, and every reject /
132
+ * skip) pass through unchanged — they still need their grant to flow on resume.
133
+ *
134
+ * Pure and order-independent. Load-bearing because the caller computes the
135
+ * adjudicated set ONCE (before {@link applyApprovedWholeFileWrites} flips the
136
+ * applied calls to COMPLETED) and never re-derives it, so this exclusion is the
137
+ * only thing that keeps an applied write out of the grants.
138
+ */
139
+ export function excludeAppliedFromGrants(adjudicatedApprovals, appliedToolCallIds) {
140
+ return adjudicatedApprovals.filter((pa) => !appliedToolCallIds.has(pa.toolCallId));
141
+ }
142
+ /**
143
+ * The exact approved bytes for a whole-file write, or `null` when they cannot be
144
+ * recovered (in which case the caller must fall back, never write).
145
+ *
146
+ * The bytes come from the gated call's `args` whole-file content — the single
147
+ * authoritative copy of the proposed body (the deny-gate stamped it from the
148
+ * hook input; see {@link isApprovedWholeFileWrite}). `args` is never offloaded to
149
+ * a ref (only dropped wholesale by the aggregate size backstop), so when present
150
+ * it is intact.
151
+ *
152
+ * It NEVER returns the elision marker: that is a lossy stand-in and writing it
153
+ * would corrupt the file. An `args` body equal to the marker is treated as
154
+ * unresolved, and the caller degrades to grant + reinvocation.
155
+ */
156
+ export function resolveApprovedWholeFileContent(tc) {
157
+ const content = extractWriteContent(argsRecord(tc));
158
+ if (content !== null && content !== ELISION_MARKER)
159
+ return content;
160
+ return null;
161
+ }
162
+ /**
163
+ * Whether `tc` is an approved, still-pending WHOLE-FILE write eligible for
164
+ * exact-apply. APPROVE and APPROVE_ALL both approve the clicked tool (the latter
165
+ * also leases the class, handled separately by the caller). A SKIP/REJECT, an
166
+ * edit-family call (`old_string`/`new_string`, which carries no whole-file body),
167
+ * and an already-applied (COMPLETED) call are all excluded.
168
+ *
169
+ * The whole-file predicate is `extractWriteContent(args) !== null` — the exact
170
+ * condition the removed field-22 capture used to classify a change WHOLE_FILE
171
+ * (a whole-file body present) vs HUNK_ONLY (an edit fragment). Reading it from
172
+ * `args` keeps eligibility on the single source of truth.
173
+ */
174
+ function isApprovedWholeFileWrite(tc) {
175
+ if (tc.status !== ToolCallStatus.TOOL_CALL_WAITING_APPROVAL)
176
+ return false;
177
+ if (tc.approvalAction !== ApprovalAction.APPROVE &&
178
+ tc.approvalAction !== ApprovalAction.APPROVE_ALL) {
179
+ return false;
180
+ }
181
+ return extractWriteContent(argsRecord(tc)) !== null;
182
+ }
183
+ /** The gated tool call's structured args as a plain record (empty when absent). */
184
+ function argsRecord(tc) {
185
+ return tc.args && typeof tc.args === "object"
186
+ ? tc.args
187
+ : {};
188
+ }
189
+ /** Whether `absTarget` resolves inside one of the configured workspace roots. */
190
+ function isWithinWorkspace(absTarget, workspaceDirs) {
191
+ const target = resolve(absTarget);
192
+ return workspaceDirs.some((dir) => {
193
+ const root = resolve(dir);
194
+ if (target === root)
195
+ return true;
196
+ const rel = relative(root, target);
197
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
198
+ });
199
+ }
200
+ function logSkip(executionId, tc, reason) {
201
+ console.log(`ExecuteCursor exact-apply skipped (falling back to grant+reinvocation): ` +
202
+ `tool=${tc.id} reason="${reason}" execution=${executionId}`);
203
+ }
204
+ //# sourceMappingURL=exact-apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact-apply.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/exact-apply.ts"],"names":[],"mappings":"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"}
@@ -54,47 +54,76 @@
54
54
  * recorded here correlates to the streamed tool call, and an approval grant
55
55
  * matches the agent's re-attempt on reinvocation.
56
56
  *
57
+ * Content-exact identity (the sibling-hole fix): for a file edit the coarse
58
+ * (category, salient) is not enough — approving one edit to a file must not let
59
+ * a DIFFERENT edit to the SAME file ride through. So the hook ALSO computes a
60
+ * CONTENT token `base64(category \n salient \n contentDigest)`, where
61
+ * contentDigest is a sha256 over the edit content (mirror of file-tools.ts
62
+ * contentDigest; see buildContentDigestScript). It allows a built-in when EITHER
63
+ * the content token (a file edit approved with this exact content) OR the coarse
64
+ * token (shell/delete, or a content-less degrade) is granted, and records the
65
+ * content token as the denial identity. The runner grants the content token when
66
+ * it has the approved content (the persisted approval_content_digest), so a
67
+ * sibling edit re-gates; it degrades to the coarse grant only when the content is
68
+ * unrecoverable.
69
+ *
57
70
  * Policy evaluation order (first match wins). The model is "gate the dangerous
58
71
  * set, allow the rest" — matching the native harness and avoiding denial of
59
72
  * auto-approved MCP tools (which are absent from mcpToolPolicies):
60
73
  * 0. Scope guard: not the runner's own agent → allow (never touch the ledger)
61
- * 1. Missing state file → deny (fail-closed); autoApproveAll allow
74
+ * 1. Missing state file → deny (fail-closed); autoApproveAll (the pre-armed
75
+ * spec.auto_approve_all global bypass) → allow
62
76
  * 2. beforeMCPExecution event → MCP tool present in mcpToolPolicies
63
77
  * (require-approval):
64
78
  * a. name token in approvedGrantTokens → allow (reinvocation grant)
65
79
  * b. otherwise → record denial, deny
66
- * (auto-approved / unlisted MCP tools fall through → allow)
80
+ * (auto-approved / unlisted MCP tools fall through → allow; a server-scoped
81
+ * lease drops the server's tools from mcpToolPolicies, so they fall through)
67
82
  * 3. preToolUse event → gated built-in (category non-empty):
68
83
  * a. identity token in approvedGrantTokens → allow (reinvocation grant)
69
- * b. otherwiserecord denial, deny
84
+ * b. category in leasedCategories allow (run-lifetime scoped lease)
85
+ * c. otherwise → record denial, deny
70
86
  * (read-only / ungated built-ins fall through → allow)
71
87
  */
72
88
  /**
73
- * Generates the bash hook script content.
89
+ * Generates the STABLE bash hook script content.
90
+ *
91
+ * The script is STABLE across executions in a runner process — its only inputs
92
+ * are the absolute path of the active-turn pointer (and the runner's Node
93
+ * binary), both constant for a given workspace. This is deliberate and
94
+ * load-bearing: the Cursor SDK loads `<workspace>/.cursor/hooks.json` (the hook
95
+ * script PATH) ONCE per runner process and caches it, ignoring later
96
+ * per-execution rewrites. A per-session script with per-session baked paths
97
+ * therefore gets cached at the FIRST execution and reused for every later one,
98
+ * recording their denials into the FIRST session's ledger while each later runner
99
+ * reads its own (empty) ledger and silently completes — the no-approval-button /
100
+ * "execution completed" regression. Keeping the script stable and resolving the
101
+ * CURRENT turn's artifacts from the pointer (which bash re-reads every
102
+ * invocation; see {@link ActiveTurnPointer}/writeActiveTurnPointer) makes a
103
+ * long-lived multi-session runner correct.
74
104
  *
75
- * The script reads a JSON state file written by the cursor-runner before
76
- * each agent.send() call. The state file is the single source of truth
77
- * for the dynamic approval inputs (autoApproveAll, mcpToolPolicies,
78
- * approvedGrantTokens). The static policy (which built-ins are gated and their
79
- * categories, and which arg fields are salient) is baked into the script at
80
- * generation time from approval-policy.ts.
105
+ * From the pointer the script reads the current turn's approval-state file (the
106
+ * single source of truth for the dynamic inputs: autoApproveAll, leasedCategories,
107
+ * mcpToolPolicies, approvedGrantTokens), denial ledger, and runner PID. The
108
+ * static policy (which built-ins are gated, their categories, the salient arg
109
+ * fields) is baked at generation time from approval-policy.ts.
81
110
  *
82
111
  * The identity token encoding (`base64(key \n salient)`) must stay byte-identical
83
112
  * to grantToken() in approval-state.ts.
84
113
  *
85
114
  * Scope guard (the crux of issue #173): the Cursor SDK loads project hooks from
86
- * `<workspace>/.cursor/hooks.json`, which is the SAME per-repo surface every
87
- * Cursor client reads. When a session runs against the user's real repo, the
88
- * user's own interactive Cursor IDE would otherwise load and run this hook too —
89
- * gating the IDE, polluting the denial ledger with the IDE's tool calls, and (in
90
- * multi-root windows) failing closed. We make the gate apply ONLY to the
91
- * runner's own agent by baking in the runner process PID and checking, on every
92
- * invocation, whether the runner is an ancestor of the hook process. The SDK
93
- * runs hooks in-process via child_process, so the runner's own agent (and its
94
- * delegated sub-agents) spawn the hook as a descendant of the runner; any other
95
- * Cursor client spawns it under a different process tree. A non-descendant
96
- * invocation is allowed immediately and never touches the ledger. This also
97
- * makes a leftover hooks.json self-neutralizing: once the runner exits, no
98
- * invocation can match its (now-dead) PID, so the gate is inert.
115
+ * `<workspace>/.cursor/hooks.json`, the SAME per-repo surface every Cursor client
116
+ * reads. When a session runs against the user's real repo, the user's own
117
+ * interactive Cursor IDE would otherwise load and run this hook too — gating the
118
+ * IDE, polluting the denial ledger, and (in multi-root windows) failing closed.
119
+ * We make the gate apply ONLY to the runner's own agent by checking, on every
120
+ * invocation, whether the runner PID (FROM THE POINTER) is an ancestor of the
121
+ * hook process. The SDK runs hooks in-process via child_process, so the runner's
122
+ * own agent (and its delegated sub-agents) spawn the hook as a descendant of the
123
+ * runner; any other Cursor client spawns it under a different process tree. A
124
+ * non-descendant invocation is allowed immediately and never touches the ledger.
125
+ * Combined with pointer teardown, a leftover hooks.json is self-neutralizing:
126
+ * once the turn ends (pointer removed) or the runner exits (PID dead), no
127
+ * invocation gates, so the gate is inert.
99
128
  */
100
- export declare function generateHookScript(stateFilePath: string, ledgerFilePath: string, runnerPid: number): string;
129
+ export declare function generateHookScript(activePointerPath: string, workspaceRoot?: string): string;