@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
@@ -54,22 +54,40 @@
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
  import { SALIENT_ARG_FIELDS, getBuiltInGatedCategories } from "./approval-policy.js";
89
+ import { EDIT_OLD_FIELDS, EDIT_NEW_FIELDS, WRITE_CONTENT_FIELDS, } from "../../shared/file-tools.js";
90
+ import { buildObservationStagingScript, CAS_OBSERVATIONS_DIRNAME } from "./cas-observations.js";
73
91
  // Shown to the model when the gate denies a tool call. It must NOT teach the
74
92
  // model to ask for permission in prose or to "stop and wait" — that framing
75
93
  // makes the model narrate approval requests instead of invoking tools (the
@@ -80,9 +98,22 @@ import { SALIENT_ARG_FIELDS, getBuiltInGatedCategories } from "./approval-policy
80
98
  // single-quoted bash echo of a JSON object, so the text must contain no double
81
99
  // quotes, apostrophes, or backslashes.
82
100
  const APPROVAL_REQUIRED_AGENT_MESSAGE = "This action has been submitted to the user for approval automatically; you " +
83
- "do not need to ask for permission. Do not retry it or attempt a workaround " +
84
- "for this action. The platform will resume you automatically after the user " +
85
- "responds continue with the rest of the task.";
101
+ "do not need to ask for permission. This is the platform approval gate working " +
102
+ "as intended it is not an error and not a Cursor misconfiguration, so never " +
103
+ "tell the user to change Cursor settings or enable hooks. Do not retry it or " +
104
+ "attempt a workaround for this action. The platform will resume you " +
105
+ "automatically after the user responds — continue with the rest of the task.";
106
+ // Shown to the model when a secret-like gitignored write is hard-blocked (DD-E /
107
+ // DD-18). Unlike APPROVAL_REQUIRED_AGENT_MESSAGE, this must NOT promise a resume:
108
+ // the write is discarded and never captured for review, so the model must move on
109
+ // rather than wait or retry. Same embedding constraint (single-quoted bash echo
110
+ // of a JSON object): no double quotes, apostrophes, or backslashes.
111
+ const SECRET_BLOCKED_AGENT_MESSAGE = "This file was blocked for security because its path matches a secret-like " +
112
+ "pattern Stigmer will not capture for review. Nothing was written. This is the " +
113
+ "platform safety gate working as intended — it is not an error and not a Cursor " +
114
+ "misconfiguration, so never tell the user to change Cursor settings or enable " +
115
+ "hooks. Do not retry this write or attempt a workaround; the write will not be " +
116
+ "applied. Continue with the rest of the task.";
86
117
  /**
87
118
  * Build the bash `case` arms that map an incoming hook `tool_name` to its
88
119
  * canonical approval category. Generated from approval-policy.ts so the hook and
@@ -102,20 +133,52 @@ function buildCategoryCaseArms() {
102
133
  }
103
134
  return arms.join("\n");
104
135
  }
136
+ /**
137
+ * Build the inline content-digest extractor — a BYTE-IDENTICAL MIRROR of
138
+ * {@link file://../../shared/file-tools.ts} `contentDigest()`.
139
+ *
140
+ * Computes the same `sha256(JSON.stringify(["w", content]))` /
141
+ * `sha256(JSON.stringify(["e", old, new]))` from the parsed tool_input `a`,
142
+ * using the SAME union field lists injected from file-tools.ts (so the
143
+ * file_path/path & content/contents cross-layer name divergence is normalized
144
+ * identically) and the SAME Node binary as the runner — so the hook-side and
145
+ * runner-side digests agree. Any change to the format here or in
146
+ * file-tools.ts MUST be mirrored in the other. Empty (`dig===""`) for a tool
147
+ * with no edit content (shell/delete/read/MCP).
148
+ *
149
+ * Authored as part of a single-quoted bash string, so the JS must not contain
150
+ * single quotes (JSON.stringify emits double quotes).
151
+ */
152
+ function buildContentDigestScript() {
153
+ const wc = JSON.stringify(WRITE_CONTENT_FIELDS);
154
+ const eo = JSON.stringify(EDIT_OLD_FIELDS);
155
+ const en = JSON.stringify(EDIT_NEW_FIELDS);
156
+ return [
157
+ `const pick=(fl)=>{for(const f of fl){const v=a[f];if(typeof v==="string")return v;}return null;};`,
158
+ `const sha=(x)=>require("crypto").createHash("sha256").update(x,"utf8").digest("hex");`,
159
+ `let dig="";`,
160
+ `const _wc=pick(${wc});`,
161
+ `if(_wc!==null){dig=sha(JSON.stringify(["w",_wc]));}`,
162
+ `else{const _o=pick(${eo}),_n=pick(${en});if(_o!==null||_n!==null){dig=sha(JSON.stringify(["e",_o===null?"":_o,_n===null?"":_n]));}}`,
163
+ ].join("");
164
+ }
105
165
  /**
106
166
  * Build the inline Node.js identity extractor embedded in the hook script.
107
167
  *
108
168
  * Parses the hook's stdin JSON properly (the bash fallback's grep truncates
109
- * string values at the first escaped quote) and emits five lines: tool_name,
110
- * canonical category, identity token, MCP name-token, and hook_event_name (the
111
- * event discriminator: `preToolUse` for built-ins, `beforeMCPExecution` for MCP).
112
- * The token encodings must stay byte-identical to grantToken() in
113
- * approval-state.ts.
169
+ * string values at the first escaped quote) and emits SEVEN lines: tool_name,
170
+ * canonical category, coarse identity token, MCP name-token, hook_event_name
171
+ * (the event discriminator: `preToolUse` for built-ins, `beforeMCPExecution`
172
+ * for MCP), base64(JSON(tool_input)) the authoritative pre-execution args the
173
+ * runner overlays onto the gated tool call for the approval preview — and the
174
+ * CONTENT token (base64(category \n salient \n contentDigest), empty when the
175
+ * tool has no edit content). The token encodings must stay byte-identical to
176
+ * grantToken()/contentToken() in approval-state.ts.
114
177
  *
115
178
  * Authored as a single-quoted bash string, so the JS must not contain single
116
- * quotes. The category map and salient field list are baked from
117
- * approval-policy.ts — the same source the runner uses — so the two sides can
118
- * never disagree.
179
+ * quotes. The category map, salient field list, and edit/content field lists are
180
+ * baked from approval-policy.ts / file-tools.ts — the same source the runner
181
+ * uses — so the two sides can never disagree.
119
182
  */
120
183
  function buildNodeIdentityScript() {
121
184
  const categoryMap = {};
@@ -128,46 +191,78 @@ function buildNodeIdentityScript() {
128
191
  `const t=JSON.parse(require("fs").readFileSync(0,"utf8"));`,
129
192
  `const name=typeof t.tool_name==="string"?t.tool_name:"";`,
130
193
  `const cat=(${categories})[name]||"";`,
131
- `const a=(t.tool_input&&typeof t.tool_input==="object")?t.tool_input:{};`,
194
+ // tool_input is an object for built-ins (preToolUse) but a JSON STRING for
195
+ // MCP tools (beforeMCPExecution). Parse the string form so the captured
196
+ // input is the same object shape on both paths.
197
+ `let a={};`,
198
+ `if(t.tool_input&&typeof t.tool_input==="object"){a=t.tool_input;}`,
199
+ `else if(typeof t.tool_input==="string"){try{const p=JSON.parse(t.tool_input);if(p&&typeof p==="object")a=p;}catch(e){}}`,
132
200
  `let s="";`,
133
201
  `for(const f of ${fields}){const v=a[f];if(typeof v==="string"&&v){s=v;break;}}`,
134
202
  `const b=(x)=>Buffer.from(x,"utf8").toString("base64");`,
203
+ // Content digest of the edit (mirror of file-tools.ts contentDigest); `dig`
204
+ // is "" for a non-edit tool, in which case the content token (line 7) is "".
205
+ buildContentDigestScript(),
135
206
  `const ev=typeof t.hook_event_name==="string"?t.hook_event_name:"";`,
136
- `process.stdout.write(name+"\\n"+cat+"\\n"+b(cat+"\\n"+s)+"\\n"+b(name+"\\n")+"\\n"+ev);`,
207
+ // Line 6 is base64(JSON(tool_input)): the AUTHORITATIVE pre-execution args
208
+ // the runner overlays onto the gated tool call so the approval card can show
209
+ // the proposed change before the user approves. Base64 keeps the bash side
210
+ // free of quoting/escaping concerns even for large multi-line file content.
211
+ // Line 7 is the CONTENT token (empty when no digest) — the exact-identity
212
+ // grant the runner authorizes for a file edit. Line 8 is base64(salient) —
213
+ // the raw resource value (file path / command) capture mode needs to run
214
+ // `git check-ignore` on a file path; base64 keeps newlines/quotes out of the
215
+ // line-oriented bash parse.
216
+ `process.stdout.write(name+"\\n"+cat+"\\n"+b(cat+"\\n"+s)+"\\n"+b(name+"\\n")+"\\n"+ev+"\\n"+b(JSON.stringify(a))+"\\n"+(dig?b(cat+"\\n"+s+"\\n"+dig):"")+"\\n"+b(s));`,
137
217
  ].join("");
138
218
  }
139
219
  /**
140
- * Generates the bash hook script content.
220
+ * Generates the STABLE bash hook script content.
141
221
  *
142
- * The script reads a JSON state file written by the cursor-runner before
143
- * each agent.send() call. The state file is the single source of truth
144
- * for the dynamic approval inputs (autoApproveAll, mcpToolPolicies,
145
- * approvedGrantTokens). The static policy (which built-ins are gated and their
146
- * categories, and which arg fields are salient) is baked into the script at
147
- * generation time from approval-policy.ts.
222
+ * The script is STABLE across executions in a runner process its only inputs
223
+ * are the absolute path of the active-turn pointer (and the runner's Node
224
+ * binary), both constant for a given workspace. This is deliberate and
225
+ * load-bearing: the Cursor SDK loads `<workspace>/.cursor/hooks.json` (the hook
226
+ * script PATH) ONCE per runner process and caches it, ignoring later
227
+ * per-execution rewrites. A per-session script with per-session baked paths
228
+ * therefore gets cached at the FIRST execution and reused for every later one,
229
+ * recording their denials into the FIRST session's ledger while each later runner
230
+ * reads its own (empty) ledger and silently completes — the no-approval-button /
231
+ * "execution completed" regression. Keeping the script stable and resolving the
232
+ * CURRENT turn's artifacts from the pointer (which bash re-reads every
233
+ * invocation; see {@link ActiveTurnPointer}/writeActiveTurnPointer) makes a
234
+ * long-lived multi-session runner correct.
235
+ *
236
+ * From the pointer the script reads the current turn's approval-state file (the
237
+ * single source of truth for the dynamic inputs: autoApproveAll, leasedCategories,
238
+ * mcpToolPolicies, approvedGrantTokens), denial ledger, and runner PID. The
239
+ * static policy (which built-ins are gated, their categories, the salient arg
240
+ * fields) is baked at generation time from approval-policy.ts.
148
241
  *
149
242
  * The identity token encoding (`base64(key \n salient)`) must stay byte-identical
150
243
  * to grantToken() in approval-state.ts.
151
244
  *
152
245
  * Scope guard (the crux of issue #173): the Cursor SDK loads project hooks from
153
- * `<workspace>/.cursor/hooks.json`, which is the SAME per-repo surface every
154
- * Cursor client reads. When a session runs against the user's real repo, the
155
- * user's own interactive Cursor IDE would otherwise load and run this hook too —
156
- * gating the IDE, polluting the denial ledger with the IDE's tool calls, and (in
157
- * multi-root windows) failing closed. We make the gate apply ONLY to the
158
- * runner's own agent by baking in the runner process PID and checking, on every
159
- * invocation, whether the runner is an ancestor of the hook process. The SDK
160
- * runs hooks in-process via child_process, so the runner's own agent (and its
161
- * delegated sub-agents) spawn the hook as a descendant of the runner; any other
162
- * Cursor client spawns it under a different process tree. A non-descendant
163
- * invocation is allowed immediately and never touches the ledger. This also
164
- * makes a leftover hooks.json self-neutralizing: once the runner exits, no
165
- * invocation can match its (now-dead) PID, so the gate is inert.
246
+ * `<workspace>/.cursor/hooks.json`, the SAME per-repo surface every Cursor client
247
+ * reads. When a session runs against the user's real repo, the user's own
248
+ * interactive Cursor IDE would otherwise load and run this hook too — gating the
249
+ * IDE, polluting the denial ledger, and (in multi-root windows) failing closed.
250
+ * We make the gate apply ONLY to the runner's own agent by checking, on every
251
+ * invocation, whether the runner PID (FROM THE POINTER) is an ancestor of the
252
+ * hook process. The SDK runs hooks in-process via child_process, so the runner's
253
+ * own agent (and its delegated sub-agents) spawn the hook as a descendant of the
254
+ * runner; any other Cursor client spawns it under a different process tree. A
255
+ * non-descendant invocation is allowed immediately and never touches the ledger.
256
+ * Combined with pointer teardown, a leftover hooks.json is self-neutralizing:
257
+ * once the turn ends (pointer removed) or the runner exits (PID dead), no
258
+ * invocation gates, so the gate is inert.
166
259
  */
167
- export function generateHookScript(stateFilePath, ledgerFilePath, runnerPid) {
260
+ export function generateHookScript(activePointerPath, workspaceRoot = "") {
168
261
  const salientFields = SALIENT_ARG_FIELDS.join(" ");
169
262
  const categoryCaseArms = buildCategoryCaseArms();
170
263
  const nodeIdentityScript = buildNodeIdentityScript();
264
+ const observationStagingScript = buildObservationStagingScript();
265
+ const nodeBin = process.execPath;
171
266
  return `#!/bin/bash
172
267
  # Stigmer HITL approval hook for Cursor (preToolUse + beforeMCPExecution).
173
268
  # Generated by cursor-runner — do not edit manually.
@@ -183,9 +278,47 @@ set -euo pipefail
183
278
 
184
279
  INPUT=$(cat)
185
280
 
186
- STATE_FILE="${stateFilePath}"
187
- LEDGER_FILE="${ledgerFilePath}"
188
- RUNNER_PID="${runnerPid}"
281
+ NODE_BIN="${nodeBin}"
282
+ ACTIVE_FILE="${activePointerPath}"
283
+ # Baked workspace root for capture-mode's gitignore check (empty in unit tests
284
+ # that don't exercise capture mode; the check then falls back to the path's dir).
285
+ GIT_ROOT="${workspaceRoot}"
286
+
287
+ # --- Resolve the CURRENT turn from the runner-written pointer ----------------
288
+ # The Cursor SDK caches .cursor/hooks.json (the hook script PATH) for the runner
289
+ # process, so THIS script is stable across executions and the per-turn pointer
290
+ # (active.json) — which bash re-reads on every invocation — is the only thing
291
+ # that changes. It names the CURRENT turn's approval-state, denial ledger, and
292
+ # runner PID. This indirection is what makes a long-lived runner correct: a
293
+ # per-session script baked with per-session paths would be cached at the FIRST
294
+ # execution and reused for every later one, recording their denials to the FIRST
295
+ # session's ledger while each later runner reads its own empty ledger and
296
+ # silently completes (the no-approval-button / "completed" regression).
297
+ #
298
+ # A missing/garbled pointer means no active Stigmer turn (between turns, after
299
+ # teardown, or a dead runner) -> allow (inert), matching the
300
+ # leftover-hooks.json-is-inert invariant (issue #173).
301
+ if [ ! -f "$ACTIVE_FILE" ]; then
302
+ echo '{"permission":"allow"}'
303
+ exit 0
304
+ fi
305
+ PTR=$(ELECTRON_RUN_AS_NODE=1 "$NODE_BIN" -e 'const p=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));process.stdout.write((p.stateFile||"")+"\\n"+(p.ledgerFile||"")+"\\n"+((p.runnerPid==null)?"":String(p.runnerPid)))' "$ACTIVE_FILE" 2>/dev/null || true)
306
+ if [ -n "$PTR" ]; then
307
+ STATE_FILE=$(printf '%s\\n' "$PTR" | sed -n 1p)
308
+ LEDGER_FILE=$(printf '%s\\n' "$PTR" | sed -n 2p)
309
+ RUNNER_PID=$(printf '%s\\n' "$PTR" | sed -n 3p)
310
+ else
311
+ # Node unavailable: the pointer holds plain ~/.stigmer paths and an integer
312
+ # (no JSON-escaped quotes), so grep/cut is reliable here.
313
+ STATE_FILE=$(grep -o '"stateFile":"[^"]*"' "$ACTIVE_FILE" | head -1 | cut -d'"' -f4 || true)
314
+ LEDGER_FILE=$(grep -o '"ledgerFile":"[^"]*"' "$ACTIVE_FILE" | head -1 | cut -d'"' -f4 || true)
315
+ RUNNER_PID=$(grep -o '"runnerPid":[0-9]*' "$ACTIVE_FILE" | head -1 | cut -d: -f2 || true)
316
+ fi
317
+ if [ -z "$RUNNER_PID" ]; then
318
+ # Pointer unreadable -> no scope owner to gate for; stay inert.
319
+ echo '{"permission":"allow"}'
320
+ exit 0
321
+ fi
189
322
 
190
323
  # --- Scope guard: gate ONLY the runner's own agent (issue #173) -------------
191
324
  # The Cursor SDK runs hooks in-process, so the runner's own agent invocations
@@ -220,14 +353,31 @@ if ! __stigmer_is_own_agent; then
220
353
  exit 0
221
354
  fi
222
355
 
356
+ # --- Capture-mode helper: is a path gitignored? -----------------------------
357
+ # In capture mode the runner snapshots the working tree with git and reconciles
358
+ # it to the user's per-file decisions on resume, but a gitignored path (e.g.
359
+ # .env, build output) is invisible to that snapshot — so it can be neither
360
+ # captured for review nor reverted on reject. Such writes/deletes therefore stay
361
+ # on the deny-gate. Returns 0 (true) when the path is ignored. A non-git context
362
+ # or a missing path returns non-zero (treated as not-ignored -> allow).
363
+ __stigmer_is_gitignored() {
364
+ _p="$1"
365
+ [ -z "$_p" ] && return 1
366
+ if [ -n "$GIT_ROOT" ]; then
367
+ git -C "$GIT_ROOT" check-ignore -q -- "$_p" 2>/dev/null
368
+ else
369
+ git -C "$(dirname "$_p")" check-ignore -q -- "$_p" 2>/dev/null
370
+ fi
371
+ }
372
+
223
373
  # --- Canonical identity: tool_name / category / identity token / MCP token ---
224
374
  # Computed by the same Node.js binary that runs the cursor-runner (absolute path
225
375
  # baked at generation time) so JSON string values — file paths and especially
226
376
  # shell commands containing quotes, newlines, or unicode escapes — decode to the
227
377
  # exact bytes the runner sees in the stream event. ELECTRON_RUN_AS_NODE makes
228
378
  # the invocation safe when the runner is embedded in an Electron app (where
229
- # process.execPath is the Electron binary).
230
- NODE_BIN="${process.execPath}"
379
+ # process.execPath is the Electron binary). NODE_BIN is defined once near the top
380
+ # (it also parses the active-turn pointer).
231
381
  IDENTITY=$(printf '%s' "$INPUT" | ELECTRON_RUN_AS_NODE=1 "$NODE_BIN" -e '${nodeIdentityScript}' 2>/dev/null || true)
232
382
  if [ -n "$IDENTITY" ]; then
233
383
  TOOL_NAME=$(printf '%s\\n' "$IDENTITY" | sed -n 1p)
@@ -235,6 +385,16 @@ if [ -n "$IDENTITY" ]; then
235
385
  TOKEN=$(printf '%s\\n' "$IDENTITY" | sed -n 3p)
236
386
  MCP_TOKEN=$(printf '%s\\n' "$IDENTITY" | sed -n 4p)
237
387
  HOOK_EVENT=$(printf '%s\\n' "$IDENTITY" | sed -n 5p)
388
+ # base64(JSON(tool_input)) — the authoritative args the runner overlays onto
389
+ # the gated tool call. A single unwrapped base64 line (Node does not wrap), so
390
+ # sed reads it whole even for large file content.
391
+ INPUT_B64=$(printf '%s\\n' "$IDENTITY" | sed -n 6p)
392
+ # Content token (base64 of category\\nsalient\\ndigest), empty for a non-edit
393
+ # tool. The exact-identity grant the runner authorizes for a file edit.
394
+ CONTENT_TOKEN=$(printf '%s\\n' "$IDENTITY" | sed -n 7p)
395
+ # Raw salient (base64) — the file path / command. Capture mode decodes it to
396
+ # run git check-ignore on a file path.
397
+ SALIENT=$(printf '%s\\n' "$IDENTITY" | sed -n 8p | base64 -d 2>/dev/null || true)
238
398
  else
239
399
  # Fallback when the Node binary cannot run: grep/cut extraction. Best-effort
240
400
  # only — '"field":"[^"]*"' truncates at the first JSON-escaped quote, so the
@@ -256,6 +416,11 @@ ${categoryCaseArms}
256
416
  esac
257
417
  TOKEN=$(printf '%s\\n%s' "$CATEGORY" "$SALIENT" | base64 | tr -d '\\n')
258
418
  MCP_TOKEN=$(printf '%s\\n' "$TOOL_NAME" | base64 | tr -d '\\n')
419
+ # The grep fallback cannot reliably capture full multi-line tool_input, so the
420
+ # gated call degrades to today's stream-recovered args (no authoritative input)
421
+ # and cannot compute a content digest — the coarse token is the only identity.
422
+ INPUT_B64=""
423
+ CONTENT_TOKEN=""
259
424
  fi
260
425
 
261
426
  # --- Failsafe: missing state file → deny (fail-closed) ---
@@ -266,6 +431,66 @@ fi
266
431
 
267
432
  STATE=$(cat "$STATE_FILE")
268
433
 
434
+ # Capture mode (git workspaces): file mutations flow during the turn and are
435
+ # captured/gated per-file by the runner at the turn boundary (see
436
+ # shared/filereview/git-substrate.ts). Read once; consulted only in the
437
+ # gated-built-in arm below.
438
+ CAPTURE_MODE=false
439
+ if echo "$STATE" | grep -q '"captureMode":true'; then
440
+ CAPTURE_MODE=true
441
+ fi
442
+ # CAS capture of gitignored writes (the deep-agent parity switch). Set only when
443
+ # capture mode is on AND an artifact storage is configured (to persist blobs). It
444
+ # governs whether a non-secret gitignored write is staged+flowed for review vs.
445
+ # kept on the deny-gate. Read here; consulted only in the gitignored-capture arm
446
+ # below, which runs BEFORE auto-approve-all (capture is a turn property).
447
+ CAPTURE_IGNORED=false
448
+ if echo "$STATE" | grep -q '"captureIgnored":true'; then
449
+ CAPTURE_IGNORED=true
450
+ fi
451
+ # gitWorkspace selects the capture substrate (Slice 2c). Default true when the
452
+ # key is absent (older state files). When false the workspace is NOT a git tree:
453
+ # there is no git snapshot, so EVERY file write is CAS-staged below (not only
454
+ # gitignored ones), the git-tracked flow arm is skipped, and a delete stays gated
455
+ # (no CAS delete-capture path, parity with the deep-agent).
456
+ GIT_WORKSPACE=true
457
+ if echo "$STATE" | grep -q '"gitWorkspace":false'; then
458
+ GIT_WORKSPACE=false
459
+ fi
460
+
461
+ # --- Capture mode: observe CAS-owned writes for review ----------------------
462
+ # Runs BEFORE the auto-approve-all shortcut and the grant/lease checks because
463
+ # capture is a property of the TURN, not authorization: a non-secret CAS-owned
464
+ # write must be staged and reviewed even under the global bypass, and a
465
+ # secret-like one must be hard-blocked in every mode. WHICH writes are CAS-owned
466
+ # depends on the substrate: in a git tree it is only the GITIGNORED writes (git
467
+ # captures the tracked ones); in a NON-GIT workspace it is EVERY write (there is
468
+ # no git snapshot). Only a built-in write/edit (category "write") takes this arm;
469
+ # deletes and shell/MCP stay on the deny-gate below (parity with the deep-agent
470
+ # approval gate). The staging runs on the runner's own Node binary (the
471
+ # disk-backed mirror of CasCaptureFilesystemBackend.recordBefore): the salient
472
+ # path rides stdin (no argv escaping), the workspace root and the per-turn
473
+ # cas-observations dir ride argv. "captured" -> allow (apply-then-review);
474
+ # "secret" -> hard-block; "error"/Node-unavailable -> fail closed (deny, since a
475
+ # write we cannot capture cannot be reviewed).
476
+ if [ "$CAPTURE_IGNORED" = "true" ] && [ "$CATEGORY" = "write" ] && [ -n "$SALIENT" ] && { [ "$GIT_WORKSPACE" = "false" ] || __stigmer_is_gitignored "$SALIENT"; }; then
477
+ OBS_DIR="$(dirname "$STATE_FILE")/${CAS_OBSERVATIONS_DIRNAME}"
478
+ OBS_RESULT=$(printf '%s' "$SALIENT" | ELECTRON_RUN_AS_NODE=1 "$NODE_BIN" -e '${observationStagingScript}' "$GIT_ROOT" "$OBS_DIR" 2>/dev/null || echo error)
479
+ if [ "$OBS_RESULT" = "captured" ]; then
480
+ echo '{"permission":"allow"}'
481
+ exit 0
482
+ elif [ "$OBS_RESULT" = "secret" ]; then
483
+ echo '{"permission":"deny","agent_message":"${SECRET_BLOCKED_AGENT_MESSAGE}","user_message":"Blocked for security: this file matches a secret-like path and was not written or captured for review."}'
484
+ exit 0
485
+ else
486
+ # Node unavailable or a staging error: fail closed. A gitignored write we
487
+ # cannot stage cannot be captured for review, so keep gating it (today's
488
+ # behavior) rather than letting unreviewable bytes flow.
489
+ echo '{"permission":"deny","agent_message":"${APPROVAL_REQUIRED_AGENT_MESSAGE}","user_message":"Tool requires approval: '"$TOOL_NAME"'"}'
490
+ exit 0
491
+ fi
492
+ fi
493
+
269
494
  # --- 1. Auto-approve all ---
270
495
  if echo "$STATE" | grep -q '"autoApproveAll":true'; then
271
496
  echo '{"permission":"allow"}'
@@ -274,9 +499,13 @@ fi
274
499
 
275
500
  # Append a denial record to the ledger. Best-effort: a ledger write failure must
276
501
  # never abort the decision (the deny still goes out on stdout). toolName is raw
277
- # for human-readable debugging; token drives correlation in the runner.
502
+ # for human-readable debugging; token drives correlation in the runner; input is
503
+ # base64(JSON(tool_input)) — the authoritative pre-execution args the runner
504
+ # overlays for the approval preview (empty on the grep fallback path). Written
505
+ # with printf (a builtin, so no ARG_MAX limit) because the input can be a large
506
+ # multi-MB file body.
278
507
  record_denial() {
279
- echo '{"toolName":"'"$TOOL_NAME"'","token":"'"$1"'"}' >> "$LEDGER_FILE" 2>/dev/null || true
508
+ printf '{"toolName":"%s","token":"%s","input":"%s"}\\n' "$TOOL_NAME" "$1" "$INPUT_B64" >> "$LEDGER_FILE" 2>/dev/null || true
280
509
  }
281
510
 
282
511
  # --- 2. MCP tools (beforeMCPExecution event) ---
@@ -311,12 +540,46 @@ fi
311
540
 
312
541
  # --- 3. Gated built-in tools (preToolUse event, category non-empty) ---
313
542
  if [ -n "$CATEGORY" ]; then
314
- # Reinvocation grant: this exact resource was approved earlier allow.
315
- if echo "$STATE" | grep -qF "\\"$TOKEN\\""; then
543
+ # Capture mode (GIT tree only): a git-tracked file mutation (write/edit/delete)
544
+ # flows freely the runner captures the whole change set with git at the turn
545
+ # boundary and gates it per-file for review. A gitignored path is invisible to
546
+ # that git snapshot: a non-secret gitignored WRITE was already handled above
547
+ # (staged + allowed, or hard-blocked) when captureIgnored is on; here it only
548
+ # reaches the deny-gate when captureIgnored is off (no artifact storage) or it is
549
+ # a gitignored DELETE (no CAS capture path, parity with deep-agent). shell
550
+ # (category "shell") never takes this branch and stays gated as always. In a
551
+ # NON-GIT workspace this arm is skipped entirely (there is no git diff): writes
552
+ # were CAS-staged above and a delete falls through to the deny-gate.
553
+ if [ "$CAPTURE_MODE" = "true" ] && [ "$GIT_WORKSPACE" = "true" ] && { [ "$CATEGORY" = "write" ] || [ "$CATEGORY" = "delete" ]; }; then
554
+ if ! __stigmer_is_gitignored "$SALIENT"; then
555
+ echo '{"permission":"allow"}'
556
+ exit 0
557
+ fi
558
+ fi
559
+ # Reinvocation grant: allow when the CONTENT-exact token (a file edit approved
560
+ # earlier with this exact content) OR the COARSE token (a shell/delete, or the
561
+ # content-less degrade) is in approvedGrantTokens. A sibling edit to the same
562
+ # file has a different content token and no coarse grant, so it re-gates.
563
+ if { [ -n "$CONTENT_TOKEN" ] && echo "$STATE" | grep -qF "\\"$CONTENT_TOKEN\\""; } || echo "$STATE" | grep -qF "\\"$TOKEN\\""; then
316
564
  echo '{"permission":"allow"}'
317
565
  exit 0
318
566
  fi
319
- record_denial "$TOKEN"
567
+ # Run-lifetime category lease: the user chose "approve all <category>" earlier
568
+ # in this run (the scoped successor to autoApproveAll), so every built-in of
569
+ # this category is allowed for the rest of the run. Matched within the extracted
570
+ # leasedCategories array so a category word elsewhere in the state can't grant.
571
+ LEASED_CATEGORIES=$(echo "$STATE" | grep -o '"leasedCategories":\\[[^]]*\\]' | head -1 || true)
572
+ if [ -n "$LEASED_CATEGORIES" ] && echo "$LEASED_CATEGORIES" | grep -q "\\"$CATEGORY\\""; then
573
+ echo '{"permission":"allow"}'
574
+ exit 0
575
+ fi
576
+ # Record the PRIMARY token (content-exact when available, else coarse) so the
577
+ # runner's denial correlation keys on the SAME identity it grants on approval.
578
+ if [ -n "$CONTENT_TOKEN" ]; then
579
+ record_denial "$CONTENT_TOKEN"
580
+ else
581
+ record_denial "$TOKEN"
582
+ fi
320
583
  echo '{"permission":"deny","agent_message":"${APPROVAL_REQUIRED_AGENT_MESSAGE}","user_message":"Tool requires approval: '"$TOOL_NAME"'"}'
321
584
  exit 0
322
585
  fi
@@ -1 +1 @@
1
- {"version":3,"file":"hook-script.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/hook-script.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hook-script.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/hook-script.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AAEH,OAAO,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAO,EACL,eAAe,EACf,eAAe,EACf,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEhG,6EAA6E;AAC7E,4EAA4E;AAC5E,2EAA2E;AAC3E,gEAAgE;AAChE,+EAA+E;AAC/E,6EAA6E;AAC7E,oEAAoE;AACpE,+EAA+E;AAC/E,uCAAuC;AACvC,MAAM,+BAA+B,GACnC,6EAA6E;IAC7E,gFAAgF;IAChF,+EAA+E;IAC/E,8EAA8E;IAC9E,qEAAqE;IACrE,6EAA6E,CAAC;AAEhF,iFAAiF;AACjF,kFAAkF;AAClF,kFAAkF;AAClF,gFAAgF;AAChF,oEAAoE;AACpE,MAAM,4BAA4B,GAChC,4EAA4E;IAC5E,gFAAgF;IAChF,iFAAiF;IACjF,+EAA+E;IAC/E,gFAAgF;IAChF,8CAA8C,CAAC;AAEjD;;;;GAIG;AACH,SAAS,qBAAqB;IAC5B,MAAM,UAAU,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,yBAAyB,EAAE,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC7C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IACD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrD,IAAI,CAAC,IAAI,CAAC,SAAS,OAAO,eAAe,QAAQ,MAAM,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,wBAAwB;IAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;IAChD,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAC3C,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IAC3C,OAAO;QACL,mGAAmG;QACnG,uFAAuF;QACvF,aAAa;QACb,kBAAkB,EAAE,IAAI;QACxB,qDAAqD;QACrD,sBAAsB,EAAE,aAAa,EAAE,8FAA8F;KACtI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAS,uBAAuB;IAC9B,MAAM,WAAW,GAA2B,EAAE,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,yBAAyB,EAAE,EAAE,CAAC;QAC3D,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC;IAC/B,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IAClD,OAAO;QACL,2DAA2D;QAC3D,0DAA0D;QAC1D,cAAc,UAAU,cAAc;QACtC,2EAA2E;QAC3E,wEAAwE;QACxE,gDAAgD;QAChD,WAAW;QACX,mEAAmE;QACnE,yHAAyH;QACzH,WAAW;QACX,kBAAkB,MAAM,wDAAwD;QAChF,wDAAwD;QACxD,4EAA4E;QAC5E,6EAA6E;QAC7E,wBAAwB,EAAE;QAC1B,oEAAoE;QACpE,2EAA2E;QAC3E,6EAA6E;QAC7E,2EAA2E;QAC3E,4EAA4E;QAC5E,0EAA0E;QAC1E,2EAA2E;QAC3E,yEAAyE;QACzE,6EAA6E;QAC7E,4BAA4B;QAC5B,uKAAuK;KACxK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,UAAU,kBAAkB,CAAC,iBAAyB,EAAE,aAAa,GAAG,EAAE;IAC9E,MAAM,aAAa,GAAG,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,gBAAgB,GAAG,qBAAqB,EAAE,CAAC;IACjD,MAAM,kBAAkB,GAAG,uBAAuB,EAAE,CAAC;IACrD,MAAM,wBAAwB,GAAG,6BAA6B,EAAE,CAAC;IACjE,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IACjC,OAAO;;;;;;;;;;;;;;;YAeG,OAAO;eACJ,iBAAiB;;;YAGpB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2EAgGkD,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2B5E,aAAa;;;;;;EAM5B,gBAAgB;;;;;;;;;;;;;;gDAc8B,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sCAiDzC,wBAAwB;iFACmB,wBAAwB;;;;;kDAKvD,4BAA4B;;;;;;kDAM5B,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oDA2C7B,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gDAmDnC,+BAA+B;;;;;;;;;CAS9E,CAAC;AACF,CAAC"}
@@ -28,12 +28,13 @@
28
28
  import { InteractionMode, ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
29
29
  import type { Config } from "../../config.js";
30
30
  import type { AgentResolution } from "./session-lifecycle.js";
31
+ import { type ExecuteActivityInput } from "../../shared/activity-input.js";
31
32
  /**
32
33
  * Creates the activity functions bound to the runner config.
33
34
  * Returned object is passed to Temporal Worker.create({ activities }).
34
35
  */
35
36
  export declare function createCursorActivities(config: Config): {
36
- ExecuteCursor: (executionId: string, threadId: string) => Promise<unknown>;
37
+ ExecuteCursor: (arg0: ExecuteActivityInput | string, arg1?: string) => Promise<unknown>;
37
38
  };
38
39
  export interface BuildPromptInput {
39
40
  resolution: AgentResolution;
@@ -46,6 +47,12 @@ export interface BuildPromptInput {
46
47
  workspaceFileRefs: string[];
47
48
  attachmentPaths: string[];
48
49
  pendingApprovals: import("@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/approval_pb").PendingApproval[];
50
+ /**
51
+ * Approved whole-file writes the runner already applied itself (exact-apply).
52
+ * The reinvocation prompt marks these as done so the model does not redo them;
53
+ * the remaining approved actions are the ones it must still carry out.
54
+ */
55
+ appliedToolCallIds?: ReadonlySet<string>;
49
56
  interactionMode?: InteractionMode;
50
57
  }
51
58
  /**