@stigmer/runner 3.0.9-dev.20260615153829 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/approval-contract/types.d.ts +174 -0
  3. package/dist/__test-utils__/approval-contract/types.js +24 -0
  4. package/dist/__test-utils__/approval-contract/types.js.map +1 -0
  5. package/dist/activities/call-agent-status.d.ts +19 -1
  6. package/dist/activities/call-agent-status.js +64 -5
  7. package/dist/activities/call-agent-status.js.map +1 -1
  8. package/dist/activities/call-llm.js +19 -53
  9. package/dist/activities/call-llm.js.map +1 -1
  10. package/dist/activities/classify-tool-approvals.d.ts +24 -0
  11. package/dist/activities/classify-tool-approvals.js +69 -17
  12. package/dist/activities/classify-tool-approvals.js.map +1 -1
  13. package/dist/activities/discover-mcp-server.d.ts +7 -0
  14. package/dist/activities/discover-mcp-server.js +11 -1
  15. package/dist/activities/discover-mcp-server.js.map +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +140 -0
  17. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +130 -0
  18. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -0
  19. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.d.ts +18 -0
  20. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +123 -0
  21. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -0
  22. package/dist/activities/execute-cursor/approval-policy.d.ts +17 -61
  23. package/dist/activities/execute-cursor/approval-policy.js +8 -120
  24. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  25. package/dist/activities/execute-cursor/approval-state.d.ts +219 -9
  26. package/dist/activities/execute-cursor/approval-state.js +233 -17
  27. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  28. package/dist/activities/execute-cursor/capture-flow.d.ts +127 -0
  29. package/dist/activities/execute-cursor/capture-flow.js +234 -0
  30. package/dist/activities/execute-cursor/capture-flow.js.map +1 -0
  31. package/dist/activities/execute-cursor/cas-observations.d.ts +96 -0
  32. package/dist/activities/execute-cursor/cas-observations.js +184 -0
  33. package/dist/activities/execute-cursor/cas-observations.js.map +1 -0
  34. package/dist/activities/execute-cursor/command-provenance.d.ts +62 -0
  35. package/dist/activities/execute-cursor/command-provenance.js +128 -0
  36. package/dist/activities/execute-cursor/command-provenance.js.map +1 -0
  37. package/dist/activities/execute-cursor/exact-apply.d.ts +110 -0
  38. package/dist/activities/execute-cursor/exact-apply.js +204 -0
  39. package/dist/activities/execute-cursor/exact-apply.js.map +1 -0
  40. package/dist/activities/execute-cursor/hook-script.d.ts +53 -24
  41. package/dist/activities/execute-cursor/hook-script.js +310 -47
  42. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  43. package/dist/activities/execute-cursor/index.d.ts +8 -1
  44. package/dist/activities/execute-cursor/index.js +500 -64
  45. package/dist/activities/execute-cursor/index.js.map +1 -1
  46. package/dist/activities/execute-cursor/message-translator.d.ts +258 -11
  47. package/dist/activities/execute-cursor/message-translator.js +836 -90
  48. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  49. package/dist/activities/execute-cursor/prompt-builder.d.ts +20 -23
  50. package/dist/activities/execute-cursor/prompt-builder.js +74 -15
  51. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  52. package/dist/activities/execute-cursor/workspace-setup.d.ts +17 -0
  53. package/dist/activities/execute-cursor/workspace-setup.js +212 -33
  54. package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
  55. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.d.ts +19 -0
  56. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js +143 -0
  57. package/dist/activities/execute-deep-agent/__test-utils__/gateway-substrate.js.map +1 -0
  58. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +88 -0
  59. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +81 -0
  60. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -0
  61. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +47 -0
  62. package/dist/activities/execute-deep-agent/approval-file-change.js +68 -0
  63. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -0
  64. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +8 -1
  65. package/dist/activities/execute-deep-agent/attachment-injector.js +7 -7
  66. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  67. package/dist/activities/execute-deep-agent/cas-capture-backend.d.ts +42 -0
  68. package/dist/activities/execute-deep-agent/cas-capture-backend.js +47 -0
  69. package/dist/activities/execute-deep-agent/cas-capture-backend.js.map +1 -0
  70. package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +79 -0
  71. package/dist/activities/execute-deep-agent/cas-capture-observer.js +112 -0
  72. package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -0
  73. package/dist/activities/execute-deep-agent/hitl.d.ts +16 -3
  74. package/dist/activities/execute-deep-agent/hitl.js +11 -3
  75. package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
  76. package/dist/activities/execute-deep-agent/index.d.ts +2 -1
  77. package/dist/activities/execute-deep-agent/index.js +393 -33
  78. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  79. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +7 -1
  80. package/dist/activities/execute-deep-agent/inline-publisher.js +23 -2
  81. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  82. package/dist/activities/execute-deep-agent/setup.d.ts +53 -2
  83. package/dist/activities/execute-deep-agent/setup.js +149 -92
  84. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  85. package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +36 -0
  86. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +56 -0
  87. package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +1 -0
  88. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +34 -1
  89. package/dist/activities/execute-deep-agent/status-builder-shared.js +26 -25
  90. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  91. package/dist/activities/execute-deep-agent/status-builder.d.ts +17 -5
  92. package/dist/activities/execute-deep-agent/status-builder.js +44 -3
  93. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  94. package/dist/activities/execute-deep-agent/streaming-side-effects.js +2 -19
  95. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  96. package/dist/activities/execute-deep-agent/streaming.js +3 -15
  97. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  98. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -7
  99. package/dist/activities/execute-deep-agent/subagent-transformer.js +23 -7
  100. package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
  101. package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +30 -3
  102. package/dist/activities/execute-deep-agent/subagent-wiring.js +29 -3
  103. package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
  104. package/dist/activities/execute-deep-agent/v3-status-builder.js +32 -2
  105. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  106. package/dist/claimcheck/payload-codec.js +9 -5
  107. package/dist/claimcheck/payload-codec.js.map +1 -1
  108. package/dist/client/stigmer-client.d.ts +2 -0
  109. package/dist/client/stigmer-client.js +2 -0
  110. package/dist/client/stigmer-client.js.map +1 -1
  111. package/dist/middleware/approval-gate.d.ts +85 -4
  112. package/dist/middleware/approval-gate.js +165 -38
  113. package/dist/middleware/approval-gate.js.map +1 -1
  114. package/dist/middleware/types.d.ts +2 -5
  115. package/dist/shared/activity-input.d.ts +43 -0
  116. package/dist/shared/activity-input.js +17 -0
  117. package/dist/shared/activity-input.js.map +1 -0
  118. package/dist/shared/approval-canonicalize.d.ts +19 -0
  119. package/dist/shared/approval-canonicalize.js +119 -0
  120. package/dist/shared/approval-canonicalize.js.map +1 -0
  121. package/dist/shared/approval-fingerprint.d.ts +106 -0
  122. package/dist/shared/approval-fingerprint.js +113 -0
  123. package/dist/shared/approval-fingerprint.js.map +1 -0
  124. package/dist/shared/approval-policy.d.ts +182 -12
  125. package/dist/shared/approval-policy.js +213 -27
  126. package/dist/shared/approval-policy.js.map +1 -1
  127. package/dist/shared/args-preview.d.ts +52 -0
  128. package/dist/shared/args-preview.js +93 -0
  129. package/dist/shared/args-preview.js.map +1 -0
  130. package/dist/shared/artifact-storage.d.ts +19 -1
  131. package/dist/shared/artifact-storage.js +48 -11
  132. package/dist/shared/artifact-storage.js.map +1 -1
  133. package/dist/shared/file-change.d.ts +44 -0
  134. package/dist/shared/file-change.js +57 -0
  135. package/dist/shared/file-change.js.map +1 -0
  136. package/dist/shared/file-tools.d.ts +107 -0
  137. package/dist/shared/file-tools.js +168 -0
  138. package/dist/shared/file-tools.js.map +1 -0
  139. package/dist/shared/filereview/capture.d.ts +202 -0
  140. package/dist/shared/filereview/capture.js +498 -0
  141. package/dist/shared/filereview/capture.js.map +1 -0
  142. package/dist/shared/filereview/cas-substrate.d.ts +190 -0
  143. package/dist/shared/filereview/cas-substrate.js +284 -0
  144. package/dist/shared/filereview/cas-substrate.js.map +1 -0
  145. package/dist/shared/filereview/digest.d.ts +40 -0
  146. package/dist/shared/filereview/digest.js +66 -0
  147. package/dist/shared/filereview/digest.js.map +1 -0
  148. package/dist/shared/filereview/events.d.ts +170 -0
  149. package/dist/shared/filereview/events.js +298 -0
  150. package/dist/shared/filereview/events.js.map +1 -0
  151. package/dist/shared/filereview/git-substrate.d.ts +175 -0
  152. package/dist/shared/filereview/git-substrate.js +439 -0
  153. package/dist/shared/filereview/git-substrate.js.map +1 -0
  154. package/dist/shared/filereview/index.d.ts +11 -0
  155. package/dist/shared/filereview/index.js +12 -0
  156. package/dist/shared/filereview/index.js.map +1 -0
  157. package/dist/shared/filereview/secret-paths.d.ts +63 -0
  158. package/dist/shared/filereview/secret-paths.js +105 -0
  159. package/dist/shared/filereview/secret-paths.js.map +1 -0
  160. package/dist/shared/fingerprint-secret.d.ts +26 -0
  161. package/dist/shared/fingerprint-secret.js +47 -0
  162. package/dist/shared/fingerprint-secret.js.map +1 -0
  163. package/dist/shared/model-client.d.ts +51 -0
  164. package/dist/shared/model-client.js +77 -0
  165. package/dist/shared/model-client.js.map +1 -0
  166. package/dist/shared/plan-artifact.js +0 -2
  167. package/dist/shared/plan-artifact.js.map +1 -1
  168. package/dist/shared/status-offload.d.ts +83 -9
  169. package/dist/shared/status-offload.js +399 -79
  170. package/dist/shared/status-offload.js.map +1 -1
  171. package/dist/shared/status.js +14 -1
  172. package/dist/shared/status.js.map +1 -1
  173. package/dist/shared/tool-kind.d.ts +19 -0
  174. package/dist/shared/tool-kind.js +13 -0
  175. package/dist/shared/tool-kind.js.map +1 -1
  176. package/dist/shared/tool-row.d.ts +88 -0
  177. package/dist/shared/tool-row.js +127 -0
  178. package/dist/shared/tool-row.js.map +1 -0
  179. package/dist/shared/workspace/platform-dir.d.ts +25 -0
  180. package/dist/shared/workspace/platform-dir.js +38 -2
  181. package/dist/shared/workspace/platform-dir.js.map +1 -1
  182. package/dist/workflows/call-agent-orchestrator.js +56 -7
  183. package/dist/workflows/call-agent-orchestrator.js.map +1 -1
  184. package/dist/workflows/connect-mcp-server.d.ts +50 -0
  185. package/dist/workflows/connect-mcp-server.js +136 -15
  186. package/dist/workflows/connect-mcp-server.js.map +1 -1
  187. package/dist/workflows/types.d.ts +8 -0
  188. package/package.json +2 -2
  189. package/src/__test-utils__/approval-contract/contract.ts +224 -0
  190. package/src/__test-utils__/approval-contract/types.ts +179 -0
  191. package/src/__test-utils__/fake-artifact-storage.ts +72 -0
  192. package/src/__tests__/approval-gateway-contract.test.ts +29 -0
  193. package/src/__tests__/claimcheck-codec.test.ts +16 -53
  194. package/src/__tests__/golden-e2e.test.ts +2 -0
  195. package/src/__tests__/runner-token-coordinator.test.ts +3 -3
  196. package/src/activities/__tests__/call-agent-status.test.ts +135 -0
  197. package/src/activities/__tests__/call-llm.test.ts +1 -1
  198. package/src/activities/__tests__/classify-tool-approvals.test.ts +208 -1
  199. package/src/activities/__tests__/discover-mcp-server.test.ts +30 -0
  200. package/src/activities/__tests__/workflow-event-activities.test.ts +2 -1
  201. package/src/activities/call-agent-status.ts +74 -4
  202. package/src/activities/call-llm.ts +18 -63
  203. package/src/activities/classify-tool-approvals.ts +101 -19
  204. package/src/activities/discover-mcp-server.ts +29 -1
  205. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +216 -0
  206. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +148 -0
  207. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +41 -9
  208. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +292 -0
  209. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +68 -1
  210. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +1005 -0
  211. package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +187 -0
  212. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +97 -0
  213. package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +240 -0
  214. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +203 -0
  215. package/src/activities/execute-cursor/__tests__/exact-apply.test.ts +375 -0
  216. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +1294 -24
  217. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +446 -0
  218. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +384 -110
  219. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +171 -25
  220. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +189 -0
  221. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +44 -23
  222. package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +190 -10
  223. package/src/activities/execute-cursor/approval-policy.ts +28 -159
  224. package/src/activities/execute-cursor/approval-state.ts +366 -18
  225. package/src/activities/execute-cursor/capture-flow.ts +323 -0
  226. package/src/activities/execute-cursor/cas-observations.ts +204 -0
  227. package/src/activities/execute-cursor/command-provenance.ts +168 -0
  228. package/src/activities/execute-cursor/exact-apply.ts +253 -0
  229. package/src/activities/execute-cursor/hook-script.ts +317 -51
  230. package/src/activities/execute-cursor/index.ts +575 -67
  231. package/src/activities/execute-cursor/message-translator.ts +963 -89
  232. package/src/activities/execute-cursor/prompt-builder.ts +80 -14
  233. package/src/activities/execute-cursor/workspace-setup.ts +257 -42
  234. package/src/activities/execute-deep-agent/__test-utils__/gateway-substrate.ts +180 -0
  235. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +134 -0
  236. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +84 -0
  237. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +11 -24
  238. package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +64 -0
  239. package/src/activities/execute-deep-agent/__tests__/cas-capture-observer.test.ts +163 -0
  240. package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +8 -5
  241. package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +342 -0
  242. package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +284 -0
  243. package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +14 -36
  244. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +31 -13
  245. package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +349 -0
  246. package/src/activities/execute-deep-agent/__tests__/stamp-flowed-rows.test.ts +119 -0
  247. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +91 -10
  248. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +9 -9
  249. package/src/activities/execute-deep-agent/__tests__/subagent-approval-propagation.test.ts +160 -0
  250. package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +213 -0
  251. package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +3 -6
  252. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +84 -1
  253. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +4 -1
  254. package/src/activities/execute-deep-agent/approval-file-change.ts +80 -0
  255. package/src/activities/execute-deep-agent/attachment-injector.ts +20 -11
  256. package/src/activities/execute-deep-agent/cas-capture-backend.ts +66 -0
  257. package/src/activities/execute-deep-agent/cas-capture-observer.ts +125 -0
  258. package/src/activities/execute-deep-agent/hitl.ts +23 -7
  259. package/src/activities/execute-deep-agent/index.ts +463 -40
  260. package/src/activities/execute-deep-agent/inline-publisher.ts +27 -4
  261. package/src/activities/execute-deep-agent/setup.ts +223 -125
  262. package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +64 -0
  263. package/src/activities/execute-deep-agent/status-builder-shared.ts +62 -23
  264. package/src/activities/execute-deep-agent/status-builder.ts +63 -8
  265. package/src/activities/execute-deep-agent/streaming-side-effects.ts +2 -16
  266. package/src/activities/execute-deep-agent/streaming.ts +3 -13
  267. package/src/activities/execute-deep-agent/subagent-transformer.ts +53 -13
  268. package/src/activities/execute-deep-agent/subagent-wiring.ts +50 -3
  269. package/src/activities/execute-deep-agent/v3-status-builder.ts +36 -2
  270. package/src/claimcheck/payload-codec.ts +8 -8
  271. package/src/client/stigmer-client.ts +9 -1
  272. package/src/middleware/__tests__/approval-gate.test.ts +488 -4
  273. package/src/middleware/approval-gate.ts +247 -38
  274. package/src/middleware/types.ts +5 -5
  275. package/src/shared/__tests__/activity-input.test.ts +78 -0
  276. package/src/shared/__tests__/approval-canonicalize.test.ts +106 -0
  277. package/src/shared/__tests__/approval-fingerprint.test.ts +115 -0
  278. package/src/shared/__tests__/approval-policy.test.ts +274 -40
  279. package/src/shared/__tests__/args-preview.test.ts +78 -0
  280. package/src/shared/__tests__/artifact-storage-extended.test.ts +62 -10
  281. package/src/shared/__tests__/artifact-storage.test.ts +123 -11
  282. package/src/shared/__tests__/file-change.test.ts +85 -0
  283. package/src/shared/__tests__/file-tools.test.ts +90 -0
  284. package/src/shared/__tests__/fingerprint-secret.test.ts +51 -0
  285. package/src/shared/__tests__/lease-scope-corpus.test.ts +56 -0
  286. package/src/shared/__tests__/model-client.test.ts +162 -0
  287. package/src/shared/__tests__/plan-artifact.test.ts +11 -26
  288. package/src/shared/__tests__/policy-source-corpus.test.ts +58 -0
  289. package/src/shared/__tests__/status-offload.test.ts +573 -16
  290. package/src/shared/__tests__/status.test.ts +4 -5
  291. package/src/shared/__tests__/tool-kind.test.ts +24 -1
  292. package/src/shared/__tests__/tool-row.test.ts +221 -0
  293. package/src/shared/activity-input.ts +57 -0
  294. package/src/shared/approval-canonicalize.ts +159 -0
  295. package/src/shared/approval-fingerprint.ts +148 -0
  296. package/src/shared/approval-policy.ts +303 -27
  297. package/src/shared/args-preview.ts +98 -0
  298. package/src/shared/artifact-storage.ts +62 -11
  299. package/src/shared/checkpointer/__tests__/http-saver.test.ts +1 -2
  300. package/src/shared/file-change.ts +64 -0
  301. package/src/shared/file-tools.ts +169 -0
  302. package/src/shared/filereview/__tests__/capture.test.ts +856 -0
  303. package/src/shared/filereview/__tests__/cas-substrate.test.ts +404 -0
  304. package/src/shared/filereview/__tests__/digest.test.ts +100 -0
  305. package/src/shared/filereview/__tests__/events.test.ts +245 -0
  306. package/src/shared/filereview/__tests__/git-substrate.test.ts +362 -0
  307. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +286 -0
  308. package/src/shared/filereview/__tests__/secret-paths.test.ts +121 -0
  309. package/src/shared/filereview/capture.ts +727 -0
  310. package/src/shared/filereview/cas-substrate.ts +401 -0
  311. package/src/shared/filereview/digest.ts +83 -0
  312. package/src/shared/filereview/events.ts +449 -0
  313. package/src/shared/filereview/git-substrate.ts +555 -0
  314. package/src/shared/filereview/index.ts +60 -0
  315. package/src/shared/filereview/secret-paths.ts +121 -0
  316. package/src/shared/fingerprint-secret.ts +53 -0
  317. package/src/shared/model-client.ts +122 -0
  318. package/src/shared/plan-artifact.ts +0 -2
  319. package/src/shared/status-offload.ts +433 -77
  320. package/src/shared/status.ts +13 -0
  321. package/src/shared/tool-kind.ts +33 -0
  322. package/src/shared/tool-row.ts +135 -0
  323. package/src/shared/workspace/platform-dir.ts +41 -2
  324. package/src/workflow-engine/__tests__/golden-execution.test.ts +35 -18
  325. package/src/workflow-engine/__tests__/tasks/try.test.ts +1 -1
  326. package/src/workflows/__tests__/connect-mcp-server.test.ts +304 -29
  327. package/src/workflows/call-agent-orchestrator.ts +53 -6
  328. package/src/workflows/connect-mcp-server.ts +179 -24
  329. package/src/workflows/types.ts +8 -0
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import {
5
+ APPROVAL_FINGERPRINT_VERSION,
6
+ coarseToolIdentity,
7
+ computeApprovalFingerprint,
8
+ computeCoarseApprovalFingerprint,
9
+ deriveExecutionFingerprintKey,
10
+ } from "../approval-fingerprint.js";
11
+ import type { ToolActionInput } from "../approval-canonicalize.js";
12
+
13
+ interface FingerprintVector {
14
+ name: string;
15
+ input: ToolActionInput;
16
+ expected: string;
17
+ }
18
+
19
+ // Single source of truth, shared byte-for-byte with the future Go/Java editions.
20
+ const vectorsPath = fileURLToPath(
21
+ new URL("../../../../../../apis/testdata/hitl/fingerprint/vectors.json", import.meta.url),
22
+ );
23
+ const corpus = JSON.parse(readFileSync(vectorsPath, "utf-8")) as {
24
+ key: string;
25
+ full: FingerprintVector[];
26
+ coarse: FingerprintVector[];
27
+ };
28
+
29
+ describe("approval fingerprint vector corpus", () => {
30
+ it("loads a non-trivial corpus", () => {
31
+ expect(corpus.full.length).toBeGreaterThanOrEqual(4);
32
+ expect(corpus.coarse.length).toBeGreaterThanOrEqual(4);
33
+ });
34
+
35
+ for (const v of corpus.full) {
36
+ it(`full vector: ${v.name}`, () => {
37
+ expect(computeApprovalFingerprint(corpus.key, v.input)).toBe(v.expected);
38
+ });
39
+ }
40
+
41
+ for (const v of corpus.coarse) {
42
+ it(`coarse vector: ${v.name}`, () => {
43
+ expect(computeCoarseApprovalFingerprint(corpus.key, v.input)).toBe(v.expected);
44
+ });
45
+ }
46
+ });
47
+
48
+ describe("fingerprint invariants", () => {
49
+ const key = "test-key";
50
+
51
+ it("tags every fingerprint with the version", () => {
52
+ const fp = computeApprovalFingerprint(key, { toolName: "read" });
53
+ expect(fp.startsWith(`${APPROVAL_FINGERPRINT_VERSION}:`)).toBe(true);
54
+ });
55
+
56
+ it("is stable for the same action (an approval re-driven is the same lease)", () => {
57
+ const input: ToolActionInput = { toolName: "Write", paths: ["src/a.ts"], args: { contents: "x" } };
58
+ expect(computeApprovalFingerprint(key, input)).toBe(computeApprovalFingerprint(key, input));
59
+ });
60
+
61
+ it("changes when the action changes (a drifted action is re-asked, never reused)", () => {
62
+ const a = computeApprovalFingerprint(key, { toolName: "Write", paths: ["src/a.ts"] });
63
+ const b = computeApprovalFingerprint(key, { toolName: "Write", paths: ["src/b.ts"] });
64
+ expect(a).not.toBe(b);
65
+ });
66
+
67
+ it("is key-bound (a fingerprint from one key cannot satisfy another)", () => {
68
+ const input: ToolActionInput = { toolName: "Shell", shellCommand: "rm -rf /" };
69
+ expect(computeApprovalFingerprint("key-a", input)).not.toBe(computeApprovalFingerprint("key-b", input));
70
+ });
71
+ });
72
+
73
+ describe("coarse projection (Cursor substrate)", () => {
74
+ it("collapses the write/edit taxonomies onto one identity", () => {
75
+ // Same logical edit named in the hook taxonomy (Write) and the stream
76
+ // taxonomy (edit) over the same resource -> one fingerprint. This is the
77
+ // March-2026 failure class, prevented by construction.
78
+ const hookSide: ToolActionInput = { toolName: "Write", paths: ["a.txt"] };
79
+ const streamSide: ToolActionInput = { toolName: "edit", paths: ["a.txt"] };
80
+ expect(coarseToolIdentity(hookSide).tool).toBe("write");
81
+ expect(coarseToolIdentity(streamSide).tool).toBe("write");
82
+ expect(computeCoarseApprovalFingerprint("k", hookSide)).toBe(
83
+ computeCoarseApprovalFingerprint("k", streamSide),
84
+ );
85
+ });
86
+
87
+ it("isolates categories (approving a write never satisfies a shell)", () => {
88
+ const write: ToolActionInput = { toolName: "Write", paths: ["a.txt"] };
89
+ const shell: ToolActionInput = { toolName: "Shell", shellCommand: "a.txt" };
90
+ expect(computeCoarseApprovalFingerprint("k", write)).not.toBe(
91
+ computeCoarseApprovalFingerprint("k", shell),
92
+ );
93
+ });
94
+
95
+ it("keys MCP tools on tool name with an empty salient", () => {
96
+ const id = coarseToolIdentity({ toolName: "create_issue", mcpServerSlug: "GitHub" });
97
+ expect(id).toEqual({ tool: "create_issue", mcpServerSlug: "github", salient: "" });
98
+ });
99
+ });
100
+
101
+ describe("per-execution key derivation", () => {
102
+ it("is stable for the same execution (gateway approves and enforces across re-invocations)", () => {
103
+ const master = "master-secret";
104
+ expect(deriveExecutionFingerprintKey(master, "exec-1")).toEqual(
105
+ deriveExecutionFingerprintKey(master, "exec-1"),
106
+ );
107
+ });
108
+
109
+ it("isolates executions (a key from one execution cannot serve another)", () => {
110
+ const master = "master-secret";
111
+ expect(deriveExecutionFingerprintKey(master, "exec-1")).not.toEqual(
112
+ deriveExecutionFingerprintKey(master, "exec-2"),
113
+ );
114
+ });
115
+ });
@@ -11,38 +11,69 @@ import {
11
11
  mergeApprovalPolicies,
12
12
  lookupMcpToolPolicy,
13
13
  resolveApprovalMessage,
14
- hasApproveAllDecision,
14
+ deriveActiveLeases,
15
+ resolveApprovalProvenance,
16
+ toProtoPolicySource,
17
+ type ActiveLeases,
18
+ type MergedToolPolicy,
15
19
  } from "../approval-policy.js";
20
+ import type { ToolApprovalCategory } from "../tool-kind.js";
16
21
  import type { ResolvedMcpServer } from "../mcp-resolver.js";
17
22
  import type { ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
18
23
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
19
- import { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
24
+ import { ApprovalAction, ApprovalPolicySource } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
25
+
26
+ /** No active leases — the default gate-everything state used by most merge tests. */
27
+ const NO_LEASES: ActiveLeases = { global: false, categories: new Set(), servers: new Set() };
28
+
29
+ /** ActiveLeases with the given scopes; `global` defaults to false. */
30
+ function leases(opts: {
31
+ global?: boolean;
32
+ categories?: Array<"write" | "delete" | "shell">;
33
+ servers?: string[];
34
+ }): ActiveLeases {
35
+ return {
36
+ global: opts.global ?? false,
37
+ categories: new Set(opts.categories ?? []),
38
+ servers: new Set(opts.servers ?? []),
39
+ };
40
+ }
41
+
42
+ /** A single tool call shaped just enough for deriveActiveLeases. */
43
+ interface TestToolCall {
44
+ approvalAction: ApprovalAction;
45
+ name?: string;
46
+ mcpServerSlug?: string;
47
+ }
20
48
 
21
49
  /**
22
- * Builds a minimal AgentExecution shaped just enough for
23
- * hasApproveAllDecision, which only reads tool-call approval actions on
24
- * root and sub-agent messages.
50
+ * Builds a minimal AgentExecution shaped just enough for deriveActiveLeases,
51
+ * which reads each tool call's approval action plus its name / mcp_server_slug
52
+ * (the scope inputs) on root and sub-agent messages, and spec.auto_approve_all.
25
53
  */
26
54
  function makeExecution(opts: {
27
- rootActions?: ApprovalAction[];
28
- subAgentActions?: ApprovalAction[];
55
+ rootCalls?: TestToolCall[];
56
+ subAgentCalls?: TestToolCall[];
57
+ autoApproveAll?: boolean;
29
58
  hasStatus?: boolean;
30
59
  }): AgentExecution {
31
60
  const hasStatus = opts.hasStatus ?? true;
61
+ const spec = { autoApproveAll: opts.autoApproveAll ?? false };
32
62
  if (!hasStatus) {
33
- return { status: undefined } as unknown as AgentExecution;
63
+ return { spec, status: undefined } as unknown as AgentExecution;
34
64
  }
65
+ const toCalls = (calls: TestToolCall[] = []) =>
66
+ calls.map(c => ({
67
+ approvalAction: c.approvalAction,
68
+ name: c.name ?? "",
69
+ mcpServerSlug: c.mcpServerSlug ?? "",
70
+ }));
35
71
  return {
72
+ spec,
36
73
  status: {
37
- messages: [
38
- { toolCalls: (opts.rootActions ?? []).map(a => ({ approvalAction: a })) },
39
- ],
74
+ messages: [{ toolCalls: toCalls(opts.rootCalls) }],
40
75
  subAgentExecutions: [
41
- {
42
- messages: [
43
- { toolCalls: (opts.subAgentActions ?? []).map(a => ({ approvalAction: a })) },
44
- ],
45
- },
76
+ { messages: [{ toolCalls: toCalls(opts.subAgentCalls) }] },
46
77
  ],
47
78
  },
48
79
  } as unknown as AgentExecution;
@@ -69,19 +100,29 @@ function makeServer(
69
100
  }
70
101
 
71
102
  describe("mergeApprovalPolicies", () => {
72
- it("returns empty map when autoApproveAll is true", () => {
103
+ it("returns empty map under a global lease (spec.auto_approve_all)", () => {
73
104
  const servers = [makeServer("github", [{ toolName: "push" }])];
74
- const result = mergeApprovalPolicies(servers, [], true);
105
+ const result = mergeApprovalPolicies(servers, [], leases({ global: true }));
75
106
  expect(result.size).toBe(0);
76
107
  });
77
108
 
109
+ it("drops a leased server's tools while keeping other servers gated", () => {
110
+ const servers = [
111
+ makeServer("github", [{ toolName: "push" }]),
112
+ makeServer("database", [{ toolName: "drop_table" }]),
113
+ ];
114
+ const result = mergeApprovalPolicies(servers, [], leases({ servers: ["github"] }));
115
+ expect(result.has("github/push")).toBe(false);
116
+ expect(result.has("database/drop_table")).toBe(true);
117
+ });
118
+
78
119
  it("creates policies from toolApprovals", () => {
79
120
  const servers = [
80
121
  makeServer("github", [
81
122
  { toolName: "create_issue", message: "Create issue?" },
82
123
  ]),
83
124
  ];
84
- const result = mergeApprovalPolicies(servers, [], false);
125
+ const result = mergeApprovalPolicies(servers, [], NO_LEASES);
85
126
 
86
127
  expect(result.size).toBe(1);
87
128
  const policy = result.get("github/create_issue")!;
@@ -98,7 +139,7 @@ describe("mergeApprovalPolicies", () => {
98
139
  [{ toolName: "push", message: "pinned message" }],
99
140
  ),
100
141
  ];
101
- const result = mergeApprovalPolicies(servers, [], false);
142
+ const result = mergeApprovalPolicies(servers, [], NO_LEASES);
102
143
 
103
144
  const policy = result.get("github/push")!;
104
145
  expect(policy.approvalMessage).toBe("pinned message");
@@ -114,7 +155,7 @@ describe("mergeApprovalPolicies", () => {
114
155
  message: "",
115
156
  }] as any[];
116
157
 
117
- const result = mergeApprovalPolicies(servers, overrides, false);
158
+ const result = mergeApprovalPolicies(servers, overrides, NO_LEASES);
118
159
  expect(result.size).toBe(0);
119
160
  });
120
161
 
@@ -126,15 +167,57 @@ describe("mergeApprovalPolicies", () => {
126
167
  message: "Really delete?",
127
168
  }] as any[];
128
169
 
129
- const result = mergeApprovalPolicies(servers, overrides, false);
170
+ const result = mergeApprovalPolicies(servers, overrides, NO_LEASES);
130
171
  expect(result.has("github/delete_repo")).toBe(true);
131
172
  });
132
173
 
174
+ describe("provenance (source stamping)", () => {
175
+ it("stamps classifier_default for a layer-1 tool approval", () => {
176
+ const servers = [makeServer("github", [{ toolName: "create_issue" }])];
177
+ const result = mergeApprovalPolicies(servers, [], NO_LEASES);
178
+ expect(result.get("github/create_issue")!.source).toBe("classifier_default");
179
+ });
180
+
181
+ it("stamps pinned_override when a pinned approval wins", () => {
182
+ const servers = [
183
+ makeServer("github", [{ toolName: "push" }], [{ toolName: "push" }]),
184
+ ];
185
+ const result = mergeApprovalPolicies(servers, [], NO_LEASES);
186
+ expect(result.get("github/push")!.source).toBe("pinned_override");
187
+ });
188
+
189
+ it("stamps agent_override when a per-agent override adds a gate", () => {
190
+ const servers = [makeServer("github", [])];
191
+ const overrides: ToolApprovalOverride[] = [{
192
+ toolName: "delete_repo",
193
+ requiresApproval: true,
194
+ message: "Really delete?",
195
+ }] as any[];
196
+ const result = mergeApprovalPolicies(servers, overrides, NO_LEASES);
197
+ expect(result.get("github/delete_repo")!.source).toBe("agent_override");
198
+ });
199
+
200
+ it("stamps agent_override when it overrides a classifier/pinned entry", () => {
201
+ const servers = [
202
+ makeServer("github", [{ toolName: "push", message: "classifier" }]),
203
+ ];
204
+ const overrides: ToolApprovalOverride[] = [{
205
+ toolName: "push",
206
+ requiresApproval: true,
207
+ message: "agent says gate",
208
+ }] as any[];
209
+ const result = mergeApprovalPolicies(servers, overrides, NO_LEASES);
210
+ const policy = result.get("github/push")!;
211
+ expect(policy.source).toBe("agent_override");
212
+ expect(policy.approvalMessage).toBe("agent says gate");
213
+ });
214
+ });
215
+
133
216
  it("skips tools without names", () => {
134
217
  const servers = [
135
218
  makeServer("github", [{ toolName: "" }]),
136
219
  ];
137
- const result = mergeApprovalPolicies(servers, [], false);
220
+ const result = mergeApprovalPolicies(servers, [], NO_LEASES);
138
221
  expect(result.size).toBe(0);
139
222
  });
140
223
 
@@ -142,7 +225,7 @@ describe("mergeApprovalPolicies", () => {
142
225
  const servers = [
143
226
  makeServer("github", [{ toolName: "push" }]),
144
227
  ];
145
- const result = mergeApprovalPolicies(servers, [], false);
228
+ const result = mergeApprovalPolicies(servers, [], NO_LEASES);
146
229
  expect(result.get("github/push")!.approvalMessage).toContain("Execute tool: push");
147
230
  });
148
231
 
@@ -151,50 +234,100 @@ describe("mergeApprovalPolicies", () => {
151
234
  makeServer("github", [{ toolName: "push" }]),
152
235
  makeServer("database", [{ toolName: "drop_table" }]),
153
236
  ];
154
- const result = mergeApprovalPolicies(servers, [], false);
237
+ const result = mergeApprovalPolicies(servers, [], NO_LEASES);
155
238
  expect(result.size).toBe(2);
156
239
  expect(result.has("github/push")).toBe(true);
157
240
  expect(result.has("database/drop_table")).toBe(true);
158
241
  });
159
242
  });
160
243
 
161
- describe("hasApproveAllDecision", () => {
162
- it("returns false when there is no status", () => {
163
- expect(hasApproveAllDecision(makeExecution({ hasStatus: false }))).toBe(false);
244
+ describe("deriveActiveLeases", () => {
245
+ it("returns no leases when there is no status", () => {
246
+ const result = deriveActiveLeases(makeExecution({ hasStatus: false }));
247
+ expect(result.global).toBe(false);
248
+ expect(result.categories.size).toBe(0);
249
+ expect(result.servers.size).toBe(0);
250
+ });
251
+
252
+ it("returns no scoped leases when no tool call carries APPROVE_ALL", () => {
253
+ const exec = makeExecution({
254
+ rootCalls: [
255
+ { approvalAction: ApprovalAction.APPROVE, name: "shell" },
256
+ { approvalAction: ApprovalAction.SKIP, name: "write" },
257
+ ],
258
+ subAgentCalls: [{ approvalAction: ApprovalAction.REJECT, name: "delete" }],
259
+ });
260
+ const result = deriveActiveLeases(exec);
261
+ expect(result.categories.size).toBe(0);
262
+ expect(result.servers.size).toBe(0);
263
+ });
264
+
265
+ it("reflects the global pre-arm from spec.auto_approve_all", () => {
266
+ const result = deriveActiveLeases(makeExecution({ autoApproveAll: true }));
267
+ expect(result.global).toBe(true);
268
+ });
269
+
270
+ it("derives a built-in CATEGORY lease from an APPROVE_ALL on a built-in", () => {
271
+ const exec = makeExecution({
272
+ rootCalls: [{ approvalAction: ApprovalAction.APPROVE_ALL, name: "shell" }],
273
+ });
274
+ const result = deriveActiveLeases(exec);
275
+ expect([...result.categories]).toEqual(["shell"]);
276
+ expect(result.servers.size).toBe(0);
277
+ });
278
+
279
+ it("collapses FILE_WRITE and FILE_EDIT to a single 'write' category lease", () => {
280
+ const exec = makeExecution({
281
+ rootCalls: [
282
+ { approvalAction: ApprovalAction.APPROVE_ALL, name: "write_file" },
283
+ { approvalAction: ApprovalAction.APPROVE_ALL, name: "edit_file" },
284
+ ],
285
+ });
286
+ expect([...deriveActiveLeases(exec).categories]).toEqual(["write"]);
164
287
  });
165
288
 
166
- it("returns false when no tool call carries APPROVE_ALL", () => {
289
+ it("derives a SERVER lease from an APPROVE_ALL on an MCP tool", () => {
167
290
  const exec = makeExecution({
168
- rootActions: [ApprovalAction.APPROVE, ApprovalAction.SKIP],
169
- subAgentActions: [ApprovalAction.REJECT],
291
+ rootCalls: [
292
+ { approvalAction: ApprovalAction.APPROVE_ALL, name: "create_issue", mcpServerSlug: "github" },
293
+ ],
170
294
  });
171
- expect(hasApproveAllDecision(exec)).toBe(false);
295
+ const result = deriveActiveLeases(exec);
296
+ expect([...result.servers]).toEqual(["github"]);
297
+ expect(result.categories.size).toBe(0);
172
298
  });
173
299
 
174
- it("detects APPROVE_ALL on a root message tool call", () => {
300
+ it("derives leases from sub-agent tool calls too", () => {
175
301
  const exec = makeExecution({
176
- rootActions: [ApprovalAction.APPROVE, ApprovalAction.APPROVE_ALL],
302
+ subAgentCalls: [
303
+ { approvalAction: ApprovalAction.APPROVE_ALL, name: "delete" },
304
+ { approvalAction: ApprovalAction.APPROVE_ALL, name: "drop", mcpServerSlug: "database" },
305
+ ],
177
306
  });
178
- expect(hasApproveAllDecision(exec)).toBe(true);
307
+ const result = deriveActiveLeases(exec);
308
+ expect([...result.categories]).toEqual(["delete"]);
309
+ expect([...result.servers]).toEqual(["database"]);
179
310
  });
180
311
 
181
- it("detects APPROVE_ALL on a sub-agent message tool call", () => {
312
+ it("does not lease a read-only built-in (no category) on APPROVE_ALL", () => {
182
313
  const exec = makeExecution({
183
- rootActions: [ApprovalAction.APPROVE],
184
- subAgentActions: [ApprovalAction.APPROVE_ALL],
314
+ rootCalls: [{ approvalAction: ApprovalAction.APPROVE_ALL, name: "read" }],
185
315
  });
186
- expect(hasApproveAllDecision(exec)).toBe(true);
316
+ const result = deriveActiveLeases(exec);
317
+ expect(result.categories.size).toBe(0);
318
+ expect(result.servers.size).toBe(0);
187
319
  });
188
320
  });
189
321
 
190
322
  describe("lookupMcpToolPolicy", () => {
191
323
  it("finds policy by server/tool key", () => {
192
- const policies = new Map([
324
+ const policies = new Map<string, MergedToolPolicy>([
193
325
  ["github/push", {
194
326
  toolName: "push",
195
327
  mcpServerSlug: "github",
196
328
  requiresApproval: true,
197
329
  approvalMessage: "Push?",
330
+ source: "classifier_default",
198
331
  }],
199
332
  ]);
200
333
 
@@ -254,3 +387,104 @@ describe("resolveApprovalMessage", () => {
254
387
  .toBe("Simple message");
255
388
  });
256
389
  });
390
+
391
+ // ---------------------------------------------------------------------------
392
+ // resolveApprovalProvenance — the read-side seam that stamps
393
+ // ToolCall.approval_policy_source. Each branch must map to the right
394
+ // PolicySource so the persisted provenance is correct per layer.
395
+ // ---------------------------------------------------------------------------
396
+
397
+ describe("resolveApprovalProvenance", () => {
398
+ const NO_CATEGORIES: ReadonlySet<ToolApprovalCategory> = new Set();
399
+
400
+ function mcpPolicy(source: MergedToolPolicy["source"]): ReadonlyMap<string, MergedToolPolicy> {
401
+ return new Map([
402
+ [
403
+ "github/create_issue",
404
+ {
405
+ toolName: "create_issue",
406
+ mcpServerSlug: "github",
407
+ requiresApproval: true,
408
+ approvalMessage: "Create issue?",
409
+ source,
410
+ },
411
+ ],
412
+ ]);
413
+ }
414
+
415
+ it("returns auto_approve_all when the whole-run global bypass is armed", () => {
416
+ expect(
417
+ resolveApprovalProvenance("delete", "", new Map(), NO_CATEGORIES, true),
418
+ ).toBe("auto_approve_all");
419
+ });
420
+
421
+ it("surfaces the merged MCP policy's layer for a gated MCP tool", () => {
422
+ expect(
423
+ resolveApprovalProvenance("create_issue", "github", mcpPolicy("agent_override"), NO_CATEGORIES, false),
424
+ ).toBe("agent_override");
425
+ });
426
+
427
+ it("surfaces annotation_destructive_tighten for a destructive-hint-gated MCP tool", () => {
428
+ expect(
429
+ resolveApprovalProvenance(
430
+ "create_issue",
431
+ "github",
432
+ mcpPolicy("annotation_destructive_tighten"),
433
+ NO_CATEGORIES,
434
+ false,
435
+ ),
436
+ ).toBe("annotation_destructive_tighten");
437
+ });
438
+
439
+ it("reads classifier_default for an MCP tool the chain cleared (no entry)", () => {
440
+ expect(
441
+ resolveApprovalProvenance("list_issues", "github", new Map(), NO_CATEGORIES, false),
442
+ ).toBe("classifier_default");
443
+ });
444
+
445
+ it("returns builtin_category for a mutating built-in with no lease", () => {
446
+ expect(
447
+ resolveApprovalProvenance("write", "", new Map(), NO_CATEGORIES, false),
448
+ ).toBe("builtin_category");
449
+ });
450
+
451
+ it("returns approval_lease for a mutating built-in whose category is leased", () => {
452
+ expect(
453
+ resolveApprovalProvenance("write", "", new Map(), new Set<ToolApprovalCategory>(["write"]), false),
454
+ ).toBe("approval_lease");
455
+ });
456
+
457
+ it("returns undefined for a read-only built-in no layer governs", () => {
458
+ expect(
459
+ resolveApprovalProvenance("read", "", new Map(), NO_CATEGORIES, false),
460
+ ).toBeUndefined();
461
+ });
462
+ });
463
+
464
+ // ---------------------------------------------------------------------------
465
+ // toProtoPolicySource — the runner-union -> proto-enum mapping. The
466
+ // cross-edition corpus (policy-source-corpus.test.ts) pins the numbers; these
467
+ // cases assert the mapping is total and that undefined collapses to UNSPECIFIED.
468
+ // ---------------------------------------------------------------------------
469
+
470
+ describe("toProtoPolicySource", () => {
471
+ it("maps undefined (no governing layer) to UNSPECIFIED", () => {
472
+ expect(toProtoPolicySource(undefined)).toBe(ApprovalPolicySource.UNSPECIFIED);
473
+ });
474
+
475
+ it("maps every union member to a distinct non-UNSPECIFIED enum value", () => {
476
+ const sources = [
477
+ "classifier_default",
478
+ "pinned_override",
479
+ "agent_override",
480
+ "auto_approve_all",
481
+ "approval_lease",
482
+ "builtin_category",
483
+ "annotation_destructive_tighten",
484
+ ] as const;
485
+ const mapped = sources.map((s) => toProtoPolicySource(s));
486
+ // All non-UNSPECIFIED and all distinct.
487
+ expect(mapped.every((v) => v !== ApprovalPolicySource.UNSPECIFIED)).toBe(true);
488
+ expect(new Set(mapped).size).toBe(sources.length);
489
+ });
490
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Unit tests for the shared args-preview sanitizers.
3
+ *
4
+ * The Cursor gate path depends on {@link buildElidedArgsPreview} producing a
5
+ * SMALL, ALWAYS-VALID, salient-preserving preview: the resumed turn re-parses it
6
+ * to rebuild an approval grant's identity, and the persist-time size backstop
7
+ * would replace an oversized preview with an unparseable marker. These pin those
8
+ * invariants so a large write can never silently break the resume round-trip.
9
+ */
10
+
11
+ import { describe, it, expect } from "vitest";
12
+ import {
13
+ buildElidedArgsPreview,
14
+ sanitizeArgsPreview,
15
+ MAX_ARGS_PREVIEW_LENGTH,
16
+ } from "../args-preview.js";
17
+
18
+ describe("sanitizeArgsPreview (native)", () => {
19
+ it("redacts sensitive keys", () => {
20
+ const out = JSON.parse(sanitizeArgsPreview({ api_key: "sk-123", path: "a.txt" }));
21
+ expect(out.api_key).toBe("[REDACTED]");
22
+ expect(out.path).toBe("a.txt");
23
+ });
24
+
25
+ it("truncates the whole string past the cap", () => {
26
+ const preview = sanitizeArgsPreview({ contents: "x".repeat(MAX_ARGS_PREVIEW_LENGTH * 2) });
27
+ expect(preview.length).toBeLessThanOrEqual(MAX_ARGS_PREVIEW_LENGTH + 1);
28
+ expect(preview.endsWith("…")).toBe(true);
29
+ });
30
+ });
31
+
32
+ describe("buildElidedArgsPreview (cursor gate)", () => {
33
+ const SALIENT = ["file_path", "path", "target_notebook", "command"] as const;
34
+
35
+ it("stays valid JSON and small for a large write, eliding the content value", () => {
36
+ const content = "line\n".repeat(100_000); // ~500 KB
37
+ const preview = buildElidedArgsPreview(
38
+ { path: "src/big.ts", contents: content },
39
+ SALIENT,
40
+ );
41
+
42
+ // Parseable — the resume round-trip depends on this.
43
+ const parsed = JSON.parse(preview) as Record<string, unknown>;
44
+ // Bounded — nowhere near the raw content size.
45
+ expect(preview.length).toBeLessThan(1_000);
46
+ // The salient path survives verbatim (grant identity), content is elided.
47
+ expect(parsed.path).toBe("src/big.ts");
48
+ expect(parsed.contents).toBe(`[${content.length} chars]`);
49
+ });
50
+
51
+ it("never elides a salient field, even a very long shell command", () => {
52
+ const command = "echo " + "a".repeat(5_000);
53
+ const preview = buildElidedArgsPreview({ command, cwd: "/x" }, SALIENT);
54
+
55
+ const parsed = JSON.parse(preview) as Record<string, unknown>;
56
+ // The grant token is base64(category\ncommand) — the command MUST be whole.
57
+ expect(parsed.command).toBe(command);
58
+ });
59
+
60
+ it("redacts secret keys", () => {
61
+ const preview = buildElidedArgsPreview(
62
+ { path: "a.txt", authorization: "Bearer abc" },
63
+ SALIENT,
64
+ );
65
+ const parsed = JSON.parse(preview) as Record<string, unknown>;
66
+ expect(parsed.authorization).toBe("[REDACTED]");
67
+ });
68
+
69
+ it("preserves small non-salient values unchanged", () => {
70
+ const preview = buildElidedArgsPreview(
71
+ { path: "a.txt", old_string: "alpha", new_string: "beta" },
72
+ SALIENT,
73
+ );
74
+ const parsed = JSON.parse(preview) as Record<string, unknown>;
75
+ expect(parsed.old_string).toBe("alpha");
76
+ expect(parsed.new_string).toBe("beta");
77
+ });
78
+ });