@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
@@ -27,6 +27,7 @@ import {
27
27
  buildMergedConfig,
28
28
  } from "../workspace-setup.js";
29
29
  import { buildApprovalState } from "../approval-state.js";
30
+ import { getHitlGateDir } from "../../../shared/workspace/platform-dir.js";
30
31
 
31
32
  const tempDirs: string[] = [];
32
33
  afterEach(() => {
@@ -159,42 +160,117 @@ describe("buildMergedConfig", () => {
159
160
  // ...and never "fix" the user's file: restore their exact original bytes.
160
161
  expect(restoreTo).toBe(garbage);
161
162
  });
163
+
164
+ // ── H-G regression: the pre-#173 in-workspace hook ──────────────────────────
165
+ // The root cause of "approved tool still blocked": an older runner build wrote
166
+ // the gate INTO the repo as `.cursor/hooks/stigmer-approval.sh` (a relative
167
+ // command). The recognizer used to match only the current `/.stigmer/sessions/`
168
+ // path, so the merge treated this stale entry as a USER hook and preserved it —
169
+ // it then ran alongside the current gate and vetoed every gated tool, even
170
+ // approved ones. These pin that it is now recognized, stripped, and self-healed.
171
+
172
+ it("recognizes and strips the legacy in-workspace hook; deletes the entirely-leftover file", () => {
173
+ // The exact shape observed in a polluted repo.
174
+ const original = JSON.stringify({
175
+ version: 1,
176
+ hooks: {
177
+ preToolUse: [
178
+ { command: ".cursor/hooks/stigmer-approval.sh", timeout: 10, failClosed: true },
179
+ ],
180
+ },
181
+ });
182
+ const fresh = "/home/u/.stigmer/sessions/ses-9/hitl/stigmer-approval.sh";
183
+ const { merged, restoreTo } = buildMergedConfig(original, pre(fresh));
184
+
185
+ // The active config carries ONLY the current runner hook — the stale legacy
186
+ // entry is gone, so it can never run alongside (and veto) the live gate.
187
+ const m = JSON.parse(merged);
188
+ expect(m.hooks.preToolUse.map((e: any) => e.command)).toEqual([fresh]);
189
+
190
+ // The whole hooks.json was our own leftover (only version+hooks, no user
191
+ // hooks remain after stripping) → teardown deletes it, leaving the repo
192
+ // pristine rather than a `{hooks:{preToolUse:[]}}` husk.
193
+ expect(restoreTo).toBeNull();
194
+ });
195
+
196
+ it("strips the legacy hook but preserves a genuine user hook in the same array", () => {
197
+ const original = JSON.stringify({
198
+ version: 1,
199
+ hooks: {
200
+ preToolUse: [
201
+ { command: "./user.sh" },
202
+ { command: ".cursor/hooks/stigmer-approval.sh", timeout: 10, failClosed: true },
203
+ ],
204
+ },
205
+ });
206
+ const fresh = "/home/u/.stigmer/sessions/ses-9/hitl/stigmer-approval.sh";
207
+ const { merged, restoreTo } = buildMergedConfig(original, pre(fresh));
208
+
209
+ // No duplicate, stale legacy entry dropped, user hook kept ahead of ours.
210
+ expect(JSON.parse(merged).hooks.preToolUse.map((e: any) => e.command)).toEqual([
211
+ "./user.sh",
212
+ fresh,
213
+ ]);
214
+ // A real user hook remains → restore the cleaned form, not delete.
215
+ expect(JSON.parse(restoreTo!).hooks.preToolUse).toEqual([{ command: "./user.sh" }]);
216
+ });
162
217
  });
163
218
 
164
219
  describe("installHitlGate / removeHitlGate", () => {
165
- const approvalState = buildApprovalState(new Map(), false);
220
+ const approvalState = buildApprovalState(new Map(), false, new Set());
221
+
222
+ // Sandbox HOME so the workspace-scoped gate dir (`~/.stigmer/hitl-gate/<hash>`)
223
+ // lands under the per-test temp root, asserted and cleaned with everything else.
224
+ const realHome = process.env.HOME;
225
+ afterEach(() => {
226
+ if (realHome === undefined) delete process.env.HOME;
227
+ else process.env.HOME = realHome;
228
+ });
166
229
 
167
230
  function dirs() {
168
231
  const root = freshRoot();
232
+ process.env.HOME = root;
169
233
  const workspaceRoot = join(root, "repo");
170
234
  const hitlDir = join(root, ".stigmer", "sessions", "ses-1", "hitl");
171
235
  mkdirSync(workspaceRoot, { recursive: true });
172
- return { workspaceRoot, hitlDir };
236
+ // The stable hook script + active-turn pointer live in the workspace-scoped
237
+ // gate dir (outside the repo), not the per-session HITL dir.
238
+ const gateDir = getHitlGateDir(workspaceRoot);
239
+ return { workspaceRoot, hitlDir, gateDir };
173
240
  }
174
241
 
175
242
  it("writes artifacts OUTSIDE the workspace and a hooks.json with an absolute command", async () => {
176
- const { workspaceRoot, hitlDir } = dirs();
243
+ const { workspaceRoot, hitlDir, gateDir } = dirs();
177
244
  await installHitlGate({ workspaceRoot, hitlDir, approvalState, runnerPid: process.pid });
178
245
 
179
- // Gate artifacts live in the HITL dir, never in the repo.
180
- expect(existsSync(join(hitlDir, "stigmer-approval.sh"))).toBe(true);
246
+ // Per-session hook input/output live in the HITL dir; the STABLE hook script
247
+ // and the active-turn pointer live in the workspace gate dir. Neither is in
248
+ // the repo.
181
249
  expect(existsSync(join(hitlDir, "approval-state.json"))).toBe(true);
182
250
  expect(existsSync(join(hitlDir, "denials.jsonl"))).toBe(true);
251
+ expect(existsSync(join(gateDir, "stigmer-approval.sh"))).toBe(true);
252
+ expect(existsSync(join(gateDir, "active.json"))).toBe(true);
183
253
  // The script is executable.
184
- expect(statSync(join(hitlDir, "stigmer-approval.sh")).mode & 0o111).toBeTruthy();
254
+ expect(statSync(join(gateDir, "stigmer-approval.sh")).mode & 0o111).toBeTruthy();
255
+
256
+ // The pointer names THIS turn's per-session artifacts + runner PID.
257
+ const pointer = JSON.parse(readFileSync(join(gateDir, "active.json"), "utf-8"));
258
+ expect(pointer.stateFile).toBe(join(hitlDir, "approval-state.json"));
259
+ expect(pointer.ledgerFile).toBe(join(hitlDir, "denials.jsonl"));
260
+ expect(pointer.runnerPid).toBe(process.pid);
185
261
 
186
- // The only in-repo file is hooks.json, pointing at the script by ABSOLUTE
187
- // path (the relative path was the multi-root exit-127 bug).
262
+ // The only in-repo file is hooks.json, pointing at the STABLE script by
263
+ // ABSOLUTE path (the relative path was the multi-root exit-127 bug).
188
264
  const hooksJson = JSON.parse(
189
265
  readFileSync(join(workspaceRoot, ".cursor", "hooks.json"), "utf-8"),
190
266
  );
191
267
  const command = hooksJson.hooks.preToolUse[0].command;
192
- expect(command).toBe(join(hitlDir, "stigmer-approval.sh"));
268
+ expect(command).toBe(join(gateDir, "stigmer-approval.sh"));
193
269
  expect(command.startsWith("/")).toBe(true);
194
270
  // The SAME script gates MCP via beforeMCPExecution (preToolUse does not
195
271
  // enforce MCP); the script branches internally on hook_event_name.
196
272
  expect(hooksJson.hooks.beforeMCPExecution[0].command).toBe(
197
- join(hitlDir, "stigmer-approval.sh"),
273
+ join(gateDir, "stigmer-approval.sh"),
198
274
  );
199
275
  // The workspace holds no relocated artifacts.
200
276
  expect(existsSync(join(workspaceRoot, ".cursor", "hooks"))).toBe(false);
@@ -250,4 +326,108 @@ describe("installHitlGate / removeHitlGate", () => {
250
326
  expect(existsSync(join(workspaceRoot, ".cursor", "hooks.json"))).toBe(false);
251
327
  }
252
328
  });
329
+
330
+ it("installs the always-applied tool-approval rule and removes it on teardown", async () => {
331
+ const { workspaceRoot, hitlDir } = dirs();
332
+ const rulePath = join(workspaceRoot, ".cursor", "rules", "stigmer-tool-approval.mdc");
333
+
334
+ const handle = await installHitlGate({
335
+ workspaceRoot, hitlDir, approvalState, runnerPid: process.pid,
336
+ });
337
+ // During the turn the rule is present and carries the protocol.
338
+ expect(existsSync(rulePath)).toBe(true);
339
+ const ruleBody = readFileSync(rulePath, "utf-8");
340
+ expect(ruleBody).toContain("alwaysApply: true");
341
+ expect(ruleBody.toLowerCase()).toContain("blocked by a hook");
342
+
343
+ await removeHitlGate(handle);
344
+ // Teardown removes our rule and the now-empty rules dir (the repo had none).
345
+ expect(existsSync(rulePath)).toBe(false);
346
+ expect(existsSync(join(workspaceRoot, ".cursor", "rules"))).toBe(false);
347
+ });
348
+
349
+ it("heals a workspace polluted by a pre-#173 in-workspace gate (H-G end-to-end)", async () => {
350
+ const { workspaceRoot, hitlDir, gateDir } = dirs();
351
+ // Reconstruct exactly what a polluted repo looks like: a hooks.json pointing
352
+ // at a relative in-workspace script, plus the orphaned script/state/ledger
353
+ // files an older runner left in `.cursor/hooks/`.
354
+ const cursorDir = join(workspaceRoot, ".cursor");
355
+ const legacyHooksDir = join(cursorDir, "hooks");
356
+ mkdirSync(legacyHooksDir, { recursive: true });
357
+ writeFileSync(
358
+ join(cursorDir, "hooks.json"),
359
+ JSON.stringify({
360
+ version: 1,
361
+ hooks: {
362
+ preToolUse: [
363
+ { command: ".cursor/hooks/stigmer-approval.sh", timeout: 10, failClosed: true },
364
+ ],
365
+ },
366
+ }),
367
+ "utf-8",
368
+ );
369
+ writeFileSync(join(legacyHooksDir, "stigmer-approval.sh"), "#!/bin/bash\nexit 0\n", "utf-8");
370
+ writeFileSync(join(legacyHooksDir, "stigmer-approval-state.json"), "{}", "utf-8");
371
+ writeFileSync(join(legacyHooksDir, "stigmer-denials.jsonl"), "", "utf-8");
372
+
373
+ const handle = await installHitlGate({
374
+ workspaceRoot, hitlDir, approvalState, runnerPid: process.pid,
375
+ });
376
+
377
+ // The active gate is ONLY the current runner-owned hook (absolute, in the
378
+ // session dir). The stale relative entry that vetoed approved tools is gone.
379
+ const during = JSON.parse(readFileSync(join(cursorDir, "hooks.json"), "utf-8"));
380
+ expect(during.hooks.preToolUse.map((e: any) => e.command)).toEqual([
381
+ join(gateDir, "stigmer-approval.sh"),
382
+ ]);
383
+ // The orphaned legacy files are removed; the now-empty hooks dir is gone too.
384
+ expect(existsSync(join(legacyHooksDir, "stigmer-approval.sh"))).toBe(false);
385
+ expect(existsSync(join(legacyHooksDir, "stigmer-approval-state.json"))).toBe(false);
386
+ expect(existsSync(join(legacyHooksDir, "stigmer-denials.jsonl"))).toBe(false);
387
+ expect(existsSync(legacyHooksDir)).toBe(false);
388
+
389
+ await removeHitlGate(handle);
390
+ // The hooks.json was entirely our own leftover → teardown deletes it, leaving
391
+ // the repo pristine (no husk, no stale entry waiting to re-pollute).
392
+ expect(existsSync(join(cursorDir, "hooks.json"))).toBe(false);
393
+ });
394
+
395
+ it("removes only stigmer-* files from .cursor/hooks, preserving a user's own hook script", async () => {
396
+ const { workspaceRoot, hitlDir } = dirs();
397
+ const legacyHooksDir = join(workspaceRoot, ".cursor", "hooks");
398
+ mkdirSync(legacyHooksDir, { recursive: true });
399
+ writeFileSync(join(legacyHooksDir, "stigmer-approval.sh"), "#!/bin/bash\nexit 0\n", "utf-8");
400
+ writeFileSync(join(legacyHooksDir, "user-custom.sh"), "#!/bin/bash\necho hi\n", "utf-8");
401
+
402
+ const handle = await installHitlGate({
403
+ workspaceRoot, hitlDir, approvalState, runnerPid: process.pid,
404
+ });
405
+
406
+ // Our leftover is gone; the user's script and the dir they own remain.
407
+ expect(existsSync(join(legacyHooksDir, "stigmer-approval.sh"))).toBe(false);
408
+ expect(readFileSync(join(legacyHooksDir, "user-custom.sh"), "utf-8")).toBe(
409
+ "#!/bin/bash\necho hi\n",
410
+ );
411
+ expect(existsSync(legacyHooksDir)).toBe(true);
412
+
413
+ await removeHitlGate(handle);
414
+ });
415
+
416
+ it("preserves a user's own .cursor/rules and rules dir after teardown", async () => {
417
+ const { workspaceRoot, hitlDir } = dirs();
418
+ const rulesDir = join(workspaceRoot, ".cursor", "rules");
419
+ mkdirSync(rulesDir, { recursive: true });
420
+ const userRule = join(rulesDir, "user-rule.mdc");
421
+ writeFileSync(userRule, "---\nalwaysApply: false\n---\nmine\n", "utf-8");
422
+
423
+ const handle = await installHitlGate({
424
+ workspaceRoot, hitlDir, approvalState, runnerPid: process.pid,
425
+ });
426
+ await removeHitlGate(handle);
427
+
428
+ // Our rule is gone; the user's rule and the dir they owned remain untouched.
429
+ expect(existsSync(join(rulesDir, "stigmer-tool-approval.mdc"))).toBe(false);
430
+ expect(readFileSync(userRule, "utf-8")).toBe("---\nalwaysApply: false\n---\nmine\n");
431
+ expect(existsSync(rulesDir)).toBe(true);
432
+ });
253
433
  });
@@ -16,21 +16,20 @@
16
16
  * a separate local policy applies.
17
17
  */
18
18
 
19
- import type { ToolApprovalPolicy } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
20
- import type { ToolApprovalOverride } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
21
- import { ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
22
- import type { ResolvedMcpServer } from "./mcp-resolver.js";
23
- import { classifyTool } from "../../shared/tool-kind.js";
24
-
25
- /**
26
- * A single tool's merged approval decision after evaluating all policy layers.
27
- */
28
- export interface MergedToolPolicy {
29
- toolName: string;
30
- mcpServerSlug: string;
31
- requiresApproval: boolean;
32
- approvalMessage: string;
33
- }
19
+ import { toolApprovalCategory, type ToolApprovalCategory } from "../../shared/tool-kind.js";
20
+
21
+ // The four-level MCP policy merge is harness-agnostic and lives in exactly one
22
+ // place (shared/approval-policy.ts) so the Cursor and native harnesses can never
23
+ // diverge. Re-exported here so existing Cursor-harness imports
24
+ // (`from "./approval-policy.js"`) keep working unchanged. This module now owns
25
+ // only the Cursor-specific built-in-tool gating helpers below.
26
+ export {
27
+ mergeApprovalPolicies,
28
+ lookupMcpToolPolicy,
29
+ resolveApprovalMessage,
30
+ POLICY_ENGINE_VERSION,
31
+ } from "../../shared/approval-policy.js";
32
+ export type { MergedToolPolicy, PolicySource } from "../../shared/approval-policy.js";
34
33
 
35
34
  /**
36
35
  * Built-in Cursor tools the preToolUse hook gates, named as the hook receives
@@ -53,36 +52,24 @@ const BUILT_IN_GATED: ReadonlySet<string> = new Set([
53
52
  ]);
54
53
 
55
54
  /**
56
- * Canonical approval category for a gated tool, derived from EITHER taxonomy's
57
- * name via the shared {@link classifyTool}.
55
+ * Canonical approval category for a gated tool.
58
56
  *
59
- * The hook (`Write`/`Shell`/`Delete`) and the stream (`edit`/`shell`/`delete`)
60
- * name the same operation differently, so neither raw name is a stable
61
- * cross-layer identity. The category collapses both onto one value so the denial
62
- * ledger (recorded by the hook) correlates to the streamed tool call (read by
63
- * the runner) and so an approval grant matches the agent's re-attempt on
64
- * reinvocation regardless of which taxonomy named it. `FILE_WRITE` and
65
- * `FILE_EDIT` both map to `write` because the Cursor hook reports every file
66
- * mutation — create or edit — as `Write`.
57
+ * This is the cross-substrate identity, and it lives in ONE place:
58
+ * {@link toolApprovalCategory} in shared/tool-kind.ts (shared with the deep-agent
59
+ * gateway). The names below are kept as the Cursor harness's public surface
60
+ * (`approvalCategory` / `ApprovalCategory`) so existing imports do not churn, but
61
+ * they are exact aliases there is no second implementation to drift.
67
62
  *
68
- * Returns undefined for non-gated tools (read-only built-ins, MCP tools, and
69
- * anything `classifyTool` does not place in a mutating kind).
63
+ * Why this matters here: the hook (`Write`/`Shell`/`Delete`) and the stream
64
+ * (`edit`/`shell`/`delete`) name the same operation differently, so neither raw
65
+ * name is a stable cross-layer identity. The category collapses both onto one
66
+ * value so the denial ledger (recorded by the hook) correlates to the streamed
67
+ * tool call (read by the runner) and an approval grant matches the agent's
68
+ * re-attempt on reinvocation regardless of which taxonomy named it.
70
69
  */
71
- export type ApprovalCategory = "write" | "delete" | "shell";
70
+ export type ApprovalCategory = ToolApprovalCategory;
72
71
 
73
- export function approvalCategory(toolName: string): ApprovalCategory | undefined {
74
- switch (classifyTool(toolName)) {
75
- case ToolKind.FILE_WRITE:
76
- case ToolKind.FILE_EDIT:
77
- return "write";
78
- case ToolKind.FILE_DELETE:
79
- return "delete";
80
- case ToolKind.SHELL:
81
- return "shell";
82
- default:
83
- return undefined;
84
- }
85
- }
72
+ export const approvalCategory = toolApprovalCategory;
86
73
 
87
74
  /**
88
75
  * Human-readable approval-message template per canonical category. Keyed by
@@ -186,121 +173,3 @@ export function extractArgKey(args: Record<string, unknown> | undefined): string
186
173
  return "";
187
174
  }
188
175
 
189
- /**
190
- * Merge approval policies from all four levels into a single lookup map.
191
- *
192
- * Keys are the raw tool name (e.g., "apply_cloud_resource"). Each MCP server
193
- * contributes its own set of policies, so the map is keyed by
194
- * "serverSlug/toolName" to avoid collisions between servers.
195
- *
196
- * Policy chain (each level overrides the previous):
197
- * 1. status.toolApprovals — presence means "requires approval"
198
- * 2. spec.pinnedToolApprovals — presence means "requires approval" (overrides)
199
- * 3. agent tool_approval_overrides — explicit boolean per tool
200
- * 4. auto_approve_all — bypasses everything
201
- *
202
- * When auto_approve_all is true, the returned map is empty (no tool
203
- * requires approval).
204
- */
205
- export function mergeApprovalPolicies(
206
- resolvedServers: ResolvedMcpServer[],
207
- agentOverrides: ToolApprovalOverride[],
208
- autoApproveAll: boolean,
209
- ): Map<string, MergedToolPolicy> {
210
- const merged = new Map<string, MergedToolPolicy>();
211
-
212
- if (autoApproveAll) return merged;
213
-
214
- for (const server of resolvedServers) {
215
- const serverPolicies = new Map<string, { requiresApproval: boolean; message: string }>();
216
-
217
- // Layer 1: system-generated defaults (presence = requires approval)
218
- for (const policy of server.toolApprovals) {
219
- if (!policy.toolName) continue;
220
- serverPolicies.set(policy.toolName, {
221
- requiresApproval: true,
222
- message: policy.message || `Execute tool: ${policy.toolName}`,
223
- });
224
- }
225
-
226
- // Layer 2: manual overrides (presence = requires approval, overrides layer 1)
227
- for (const pinned of server.pinnedToolApprovals) {
228
- if (!pinned.toolName) continue;
229
- serverPolicies.set(pinned.toolName, {
230
- requiresApproval: true,
231
- message: pinned.message || serverPolicies.get(pinned.toolName)?.message || `Execute tool: ${pinned.toolName}`,
232
- });
233
- }
234
-
235
- // Layer 3: per-agent overrides (explicit boolean, can enable or disable)
236
- for (const override of agentOverrides) {
237
- if (!override.toolName) continue;
238
- const existing = serverPolicies.get(override.toolName);
239
- if (existing) {
240
- existing.requiresApproval = override.requiresApproval;
241
- if (override.message) {
242
- existing.message = override.message;
243
- }
244
- } else if (override.requiresApproval) {
245
- serverPolicies.set(override.toolName, {
246
- requiresApproval: true,
247
- message: override.message || `Execute tool: ${override.toolName}`,
248
- });
249
- }
250
- }
251
-
252
- // Write merged policies into the result map
253
- for (const [toolName, policy] of serverPolicies) {
254
- if (!policy.requiresApproval) continue;
255
- const key = `${server.slug}/${toolName}`;
256
- merged.set(key, {
257
- toolName,
258
- mcpServerSlug: server.slug,
259
- requiresApproval: true,
260
- approvalMessage: policy.message,
261
- });
262
- }
263
- }
264
-
265
- return merged;
266
- }
267
-
268
- /**
269
- * Look up whether an MCP tool requires approval.
270
- *
271
- * @param toolName - The actual MCP tool name (e.g., "apply_cloud_resource")
272
- * @param mcpServerSlug - The MCP server slug (e.g., "planton")
273
- * @param policies - The merged policy map from mergeApprovalPolicies()
274
- * @returns The policy if approval is required, undefined if auto-approved
275
- */
276
- export function lookupMcpToolPolicy(
277
- toolName: string,
278
- mcpServerSlug: string,
279
- policies: Map<string, MergedToolPolicy>,
280
- ): MergedToolPolicy | undefined {
281
- return policies.get(`${mcpServerSlug}/${toolName}`);
282
- }
283
-
284
- /**
285
- * Resolve {{args.field}} placeholders in an approval message using the
286
- * tool's actual arguments.
287
- *
288
- * Placeholder syntax matches the proto-documented format:
289
- * - {{args.field_name}} — replaced with the argument value
290
- * - {{tool_name}} — replaced with the tool name
291
- * - Missing fields are replaced with "<unknown>"
292
- */
293
- export function resolveApprovalMessage(
294
- template: string,
295
- toolName: string,
296
- args: Record<string, unknown>,
297
- ): string {
298
- return template
299
- .replace(/\{\{tool_name\}\}/g, toolName)
300
- .replace(/\{\{args\.(\w+)\}\}/g, (_match, field: string) => {
301
- const value = args[field];
302
- if (value === undefined || value === null) return "<unknown>";
303
- if (typeof value === "string") return value;
304
- return JSON.stringify(value);
305
- });
306
- }