@stigmer/runner 3.14.0 → 3.15.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 (331) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/execution-record-fixture.d.ts +47 -0
  3. package/dist/__test-utils__/execution-record-fixture.js +68 -0
  4. package/dist/__test-utils__/execution-record-fixture.js.map +1 -0
  5. package/dist/__test-utils__/harness-contract/types.d.ts +166 -0
  6. package/dist/__test-utils__/harness-contract/types.js +49 -0
  7. package/dist/__test-utils__/harness-contract/types.js.map +1 -0
  8. package/dist/__test-utils__/hermetic-activity.d.ts +268 -0
  9. package/dist/__test-utils__/hermetic-activity.js +385 -0
  10. package/dist/__test-utils__/hermetic-activity.js.map +1 -0
  11. package/dist/__test-utils__/mock-client.d.ts +13 -0
  12. package/dist/__test-utils__/mock-client.js +45 -0
  13. package/dist/__test-utils__/mock-client.js.map +1 -0
  14. package/dist/__test-utils__/mock-workspace.d.ts +15 -0
  15. package/dist/__test-utils__/mock-workspace.js +21 -0
  16. package/dist/__test-utils__/mock-workspace.js.map +1 -0
  17. package/dist/__test-utils__/model-registry-fixture.d.ts +56 -0
  18. package/dist/__test-utils__/model-registry-fixture.js +66 -0
  19. package/dist/__test-utils__/model-registry-fixture.js.map +1 -0
  20. package/dist/__test-utils__/turn-input-fixture.d.ts +60 -0
  21. package/dist/__test-utils__/turn-input-fixture.js +118 -0
  22. package/dist/__test-utils__/turn-input-fixture.js.map +1 -0
  23. package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +107 -0
  24. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +344 -0
  25. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -0
  26. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +20 -0
  27. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +58 -0
  28. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
  29. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +2 -32
  30. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
  31. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +148 -0
  32. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +257 -0
  33. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -0
  34. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +183 -0
  35. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +262 -0
  36. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -0
  37. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +138 -0
  38. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +191 -0
  39. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -0
  40. package/dist/activities/execute-cursor/adapter.d.ts +55 -0
  41. package/dist/activities/execute-cursor/adapter.js +119 -0
  42. package/dist/activities/execute-cursor/adapter.js.map +1 -0
  43. package/dist/activities/execute-cursor/approval-state.d.ts +2 -2
  44. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  45. package/dist/activities/execute-cursor/capture-flow.d.ts +15 -30
  46. package/dist/activities/execute-cursor/capture-flow.js +17 -26
  47. package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
  48. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +23 -0
  49. package/dist/activities/execute-cursor/cursor-capabilities.js +33 -0
  50. package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -0
  51. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +2 -2
  52. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -1
  53. package/dist/activities/execute-cursor/fetch-interceptor.d.ts +15 -25
  54. package/dist/activities/execute-cursor/fetch-interceptor.js +39 -33
  55. package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
  56. package/dist/activities/execute-cursor/http2-interceptor.d.ts +21 -17
  57. package/dist/activities/execute-cursor/http2-interceptor.js +38 -31
  58. package/dist/activities/execute-cursor/http2-interceptor.js.map +1 -1
  59. package/dist/activities/execute-cursor/message-translator.d.ts +28 -19
  60. package/dist/activities/execute-cursor/message-translator.js +76 -27
  61. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  62. package/dist/activities/execute-cursor/prompt-builder.d.ts +180 -11
  63. package/dist/activities/execute-cursor/prompt-builder.js +171 -0
  64. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  65. package/dist/activities/execute-cursor/rejection-capture.d.ts +3 -6
  66. package/dist/activities/execute-cursor/rejection-capture.js +5 -7
  67. package/dist/activities/execute-cursor/rejection-capture.js.map +1 -1
  68. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -4
  69. package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
  70. package/dist/activities/execute-cursor/turn-boundary.d.ts +1 -1
  71. package/dist/activities/execute-cursor/turn-settle.d.ts +49 -0
  72. package/dist/activities/execute-cursor/turn-settle.js +507 -0
  73. package/dist/activities/execute-cursor/turn-settle.js.map +1 -0
  74. package/dist/activities/execute-cursor/turn-setup.d.ts +173 -0
  75. package/dist/activities/execute-cursor/turn-setup.js +496 -0
  76. package/dist/activities/execute-cursor/turn-setup.js.map +1 -0
  77. package/dist/activities/execute-cursor/turn-stream.d.ts +41 -71
  78. package/dist/activities/execute-cursor/turn-stream.js +70 -135
  79. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  80. package/dist/activities/execute-cursor/turn.d.ts +43 -0
  81. package/dist/activities/execute-cursor/turn.js +222 -0
  82. package/dist/activities/execute-cursor/turn.js.map +1 -0
  83. package/dist/activities/execute-cursor/usage-pricing.d.ts +38 -0
  84. package/dist/activities/execute-cursor/usage-pricing.js +50 -0
  85. package/dist/activities/execute-cursor/usage-pricing.js.map +1 -0
  86. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +1 -1
  87. package/dist/activities/execute-deep-agent/approval-file-change.js +1 -1
  88. package/dist/activities/execute-deep-agent/environment.js +1 -1
  89. package/dist/activities/execute-deep-agent/environment.js.map +1 -1
  90. package/dist/activities/execute-deep-agent/setup.js +3 -2
  91. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  92. package/dist/activities/hydrate-workflow-execution.js +1 -1
  93. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  94. package/dist/config.d.ts +15 -0
  95. package/dist/config.js.map +1 -1
  96. package/dist/harness/capabilities.d.ts +87 -0
  97. package/dist/harness/capabilities.js +37 -0
  98. package/dist/harness/capabilities.js.map +1 -0
  99. package/dist/harness/persist-chokepoint.d.ts +74 -0
  100. package/dist/harness/persist-chokepoint.js +88 -0
  101. package/dist/harness/persist-chokepoint.js.map +1 -0
  102. package/dist/harness/registry.d.ts +106 -0
  103. package/dist/harness/registry.js +168 -0
  104. package/dist/harness/registry.js.map +1 -0
  105. package/dist/harness/run-turn.d.ts +53 -0
  106. package/dist/harness/run-turn.js +528 -0
  107. package/dist/harness/run-turn.js.map +1 -0
  108. package/dist/harness/stop-controller.d.ts +50 -0
  109. package/dist/harness/stop-controller.js +62 -0
  110. package/dist/harness/stop-controller.js.map +1 -0
  111. package/dist/harness/terminal-table.d.ts +141 -0
  112. package/dist/harness/terminal-table.js +239 -0
  113. package/dist/harness/terminal-table.js.map +1 -0
  114. package/dist/harness/turn-context.d.ts +523 -0
  115. package/dist/harness/turn-context.js +739 -0
  116. package/dist/harness/turn-context.js.map +1 -0
  117. package/dist/harness/types.d.ts +448 -0
  118. package/dist/harness/types.js +64 -0
  119. package/dist/harness/types.js.map +1 -0
  120. package/dist/harness/usage-accumulator.d.ts +68 -0
  121. package/dist/harness/usage-accumulator.js +101 -0
  122. package/dist/harness/usage-accumulator.js.map +1 -0
  123. package/dist/harness-adapters.d.ts +27 -0
  124. package/dist/harness-adapters.js +28 -0
  125. package/dist/harness-adapters.js.map +1 -0
  126. package/dist/main.js.map +1 -1
  127. package/dist/runner-manager.js +64 -48
  128. package/dist/runner-manager.js.map +1 -1
  129. package/dist/runner.d.ts +5 -4
  130. package/dist/runner.js +65 -64
  131. package/dist/runner.js.map +1 -1
  132. package/dist/shared/approval-policy.d.ts +1 -1
  133. package/dist/shared/approval-policy.js.map +1 -1
  134. package/dist/{activities/execute-cursor → shared}/attachment-resolver.d.ts +2 -2
  135. package/dist/{activities/execute-cursor → shared}/attachment-resolver.js +5 -5
  136. package/dist/shared/attachment-resolver.js.map +1 -0
  137. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.d.ts +7 -5
  138. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.js +3 -63
  139. package/dist/shared/blueprint-resolver.js.map +1 -0
  140. package/dist/shared/channel-attachment.d.ts +1 -1
  141. package/dist/shared/channel-attachment.js.map +1 -1
  142. package/dist/{activities/execute-cursor → shared}/cost-guard.d.ts +5 -1
  143. package/dist/{activities/execute-cursor → shared}/cost-guard.js +5 -1
  144. package/dist/shared/cost-guard.js.map +1 -0
  145. package/dist/{activities/execute-cursor → shared}/env-resolver.d.ts +1 -1
  146. package/dist/shared/env-resolver.js.map +1 -0
  147. package/dist/{activities/execute-cursor → shared}/exact-apply.d.ts +1 -1
  148. package/dist/{activities/execute-cursor → shared}/exact-apply.js +6 -6
  149. package/dist/shared/exact-apply.js.map +1 -0
  150. package/dist/shared/execution-context.d.ts +35 -0
  151. package/dist/shared/execution-context.js +38 -0
  152. package/dist/shared/execution-context.js.map +1 -0
  153. package/dist/{activities/execute-cursor → shared}/extract-structured-output.d.ts +5 -2
  154. package/dist/{activities/execute-cursor → shared}/extract-structured-output.js +9 -6
  155. package/dist/shared/extract-structured-output.js.map +1 -0
  156. package/dist/shared/filereview/capture.d.ts +1 -1
  157. package/dist/shared/filereview/capture.js +1 -1
  158. package/dist/shared/heartbeat.d.ts +13 -11
  159. package/dist/shared/heartbeat.js +14 -29
  160. package/dist/shared/heartbeat.js.map +1 -1
  161. package/dist/{activities/execute-cursor → shared}/persist-decision.d.ts +5 -2
  162. package/dist/{activities/execute-cursor → shared}/persist-decision.js +4 -1
  163. package/dist/shared/persist-decision.js.map +1 -0
  164. package/dist/{activities/execute-cursor → shared}/skill-resolver.d.ts +16 -3
  165. package/dist/{activities/execute-cursor → shared}/skill-resolver.js +12 -4
  166. package/dist/shared/skill-resolver.js.map +1 -0
  167. package/dist/shared/subagent-rows.d.ts +21 -0
  168. package/dist/shared/subagent-rows.js +34 -0
  169. package/dist/shared/subagent-rows.js.map +1 -0
  170. package/dist/shared/worker-shutdown.d.ts +43 -16
  171. package/dist/shared/worker-shutdown.js +28 -13
  172. package/dist/shared/worker-shutdown.js.map +1 -1
  173. package/dist/shared/workspace/platform-dir.d.ts +1 -1
  174. package/dist/shared/workspace/platform-dir.js +1 -1
  175. package/dist/shared/workspace/session-provision.d.ts +54 -0
  176. package/dist/{activities/execute-cursor/workspace-provision.js → shared/workspace/session-provision.js} +15 -12
  177. package/dist/shared/workspace/session-provision.js.map +1 -0
  178. package/package.json +4 -4
  179. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +325 -0
  180. package/src/__test-utils__/config-fixture.ts +63 -0
  181. package/src/__test-utils__/execution-record-fixture.ts +104 -0
  182. package/src/__test-utils__/harness-boot-order-child.ts +58 -0
  183. package/src/__test-utils__/harness-contract/contract.ts +581 -0
  184. package/src/__test-utils__/harness-contract/recording-sink.ts +107 -0
  185. package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
  186. package/src/__test-utils__/harness-contract/scripted-adapter.ts +349 -0
  187. package/src/__test-utils__/harness-contract/types.ts +176 -0
  188. package/src/__test-utils__/hermetic-activity.ts +502 -0
  189. package/src/__test-utils__/model-registry-fixture.ts +72 -0
  190. package/src/__test-utils__/module-specifiers.ts +82 -0
  191. package/src/__test-utils__/proto-helpers.ts +25 -0
  192. package/src/__test-utils__/turn-input-fixture.ts +155 -0
  193. package/src/__tests__/harness-boot-order.test.ts +77 -0
  194. package/src/__tests__/harness-contract.test.ts +27 -0
  195. package/src/__tests__/runner-manager.test.ts +18 -0
  196. package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +102 -0
  197. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +396 -0
  198. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +66 -0
  199. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +2 -43
  200. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +342 -0
  201. package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +368 -0
  202. package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +254 -0
  203. package/src/activities/execute-cursor/__tests__/adapter-is-temporal-free.test.ts +45 -0
  204. package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +90 -0
  205. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -8
  206. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +28 -1
  207. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +1 -1
  208. package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +64 -6
  209. package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +228 -0
  210. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +180 -0
  211. package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
  212. package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn1.status.json +55 -0
  213. package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn2.status.json +77 -0
  214. package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json +126 -0
  215. package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +40 -0
  216. package/src/activities/execute-cursor/__tests__/hermetic/goldens/plain-turn.status.json +48 -0
  217. package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -0
  218. package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-fresh-agent.status.json +53 -0
  219. package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-transport-timeout.status.json +48 -0
  220. package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
  221. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-cancelled.status.json +36 -0
  222. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-error-non-retryable.status.json +37 -0
  223. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +38 -0
  224. package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +59 -0
  225. package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-call.status.json +68 -0
  226. package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +79 -0
  227. package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +42 -0
  228. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +170 -0
  229. package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +205 -0
  230. package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +171 -0
  231. package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +156 -0
  232. package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +286 -0
  233. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +332 -0
  234. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +193 -0
  235. package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +137 -0
  236. package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +185 -0
  237. package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +151 -0
  238. package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +65 -35
  239. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -40
  240. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +171 -0
  241. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -243
  242. package/src/activities/execute-cursor/__tests__/usage-pricing.test.ts +84 -0
  243. package/src/activities/execute-cursor/adapter.ts +138 -0
  244. package/src/activities/execute-cursor/approval-state.ts +2 -2
  245. package/src/activities/execute-cursor/capture-flow.ts +18 -41
  246. package/src/activities/execute-cursor/cursor-capabilities.ts +35 -0
  247. package/src/activities/execute-cursor/cursor-mcp-config.ts +2 -2
  248. package/src/activities/execute-cursor/fetch-interceptor.ts +48 -45
  249. package/src/activities/execute-cursor/http2-interceptor.ts +47 -34
  250. package/src/activities/execute-cursor/message-translator.ts +90 -45
  251. package/src/activities/execute-cursor/prompt-builder.ts +327 -12
  252. package/src/activities/execute-cursor/rejection-capture.ts +5 -12
  253. package/src/activities/execute-cursor/session-lifecycle.ts +1 -5
  254. package/src/activities/execute-cursor/turn-boundary.ts +1 -1
  255. package/src/activities/execute-cursor/turn-settle.ts +601 -0
  256. package/src/activities/execute-cursor/turn-setup.ts +645 -0
  257. package/src/activities/execute-cursor/turn-stream.ts +84 -210
  258. package/src/activities/execute-cursor/turn.ts +240 -0
  259. package/src/activities/execute-cursor/usage-pricing.ts +67 -0
  260. package/src/activities/execute-deep-agent/approval-file-change.ts +1 -1
  261. package/src/activities/execute-deep-agent/environment.ts +1 -1
  262. package/src/activities/execute-deep-agent/setup.ts +3 -2
  263. package/src/activities/hydrate-workflow-execution.ts +1 -1
  264. package/src/config.ts +13 -0
  265. package/src/harness/__tests__/import-direction.test.ts +168 -0
  266. package/src/harness/__tests__/persist-chokepoint.test.ts +146 -0
  267. package/src/harness/__tests__/registry.test.ts +168 -0
  268. package/src/harness/__tests__/run-turn.test.ts +165 -0
  269. package/src/harness/__tests__/stop-controller.test.ts +49 -0
  270. package/src/harness/__tests__/turn-context.test.ts +178 -0
  271. package/src/harness/__tests__/usage-accumulator.test.ts +83 -0
  272. package/src/harness/capabilities.ts +92 -0
  273. package/src/harness/persist-chokepoint.ts +112 -0
  274. package/src/harness/registry.ts +197 -0
  275. package/src/harness/run-turn.ts +618 -0
  276. package/src/harness/stop-controller.ts +83 -0
  277. package/src/harness/terminal-table.ts +276 -0
  278. package/src/harness/turn-context.ts +1028 -0
  279. package/src/harness/types.ts +473 -0
  280. package/src/harness/usage-accumulator.ts +122 -0
  281. package/src/harness-adapters.ts +30 -0
  282. package/src/main.ts +1 -1
  283. package/src/runner-manager.ts +68 -61
  284. package/src/runner.ts +67 -78
  285. package/src/shared/__tests__/attachment-naming.test.ts +1 -1
  286. package/src/{activities/execute-cursor → shared}/__tests__/attachment-resolver.test.ts +6 -4
  287. package/src/{activities/execute-cursor → shared}/__tests__/cost-guard.test.ts +2 -1
  288. package/src/{activities/execute-cursor → shared}/__tests__/exact-apply.test.ts +3 -3
  289. package/src/{activities/execute-cursor → shared}/__tests__/extract-structured-output.test.ts +8 -8
  290. package/src/{activities/execute-cursor → shared}/__tests__/persist-decision.test.ts +4 -2
  291. package/src/{activities/execute-cursor → shared}/__tests__/skill-resolver.test.ts +1 -1
  292. package/src/shared/__tests__/subagent-rows.test.ts +34 -0
  293. package/src/shared/__tests__/worker-shutdown.test.ts +44 -39
  294. package/src/shared/approval-policy.ts +1 -1
  295. package/src/{activities/execute-cursor → shared}/attachment-resolver.ts +6 -6
  296. package/src/{activities/execute-cursor → shared}/blueprint-resolver.ts +7 -78
  297. package/src/shared/channel-attachment.ts +1 -1
  298. package/src/{activities/execute-cursor → shared}/cost-guard.ts +5 -1
  299. package/src/{activities/execute-cursor → shared}/env-resolver.ts +1 -1
  300. package/src/{activities/execute-cursor → shared}/exact-apply.ts +7 -7
  301. package/src/shared/execution-context.ts +45 -0
  302. package/src/{activities/execute-cursor → shared}/extract-structured-output.ts +10 -7
  303. package/src/shared/filereview/capture.ts +1 -1
  304. package/src/shared/heartbeat.ts +13 -33
  305. package/src/{activities/execute-cursor → shared}/persist-decision.ts +5 -2
  306. package/src/{activities/execute-cursor → shared}/skill-resolver.ts +20 -6
  307. package/src/shared/subagent-rows.ts +39 -0
  308. package/src/shared/worker-shutdown.ts +55 -25
  309. package/src/{activities/execute-cursor/__tests__/workspace-provision.test.ts → shared/workspace/__tests__/session-provision.test.ts} +25 -28
  310. package/src/shared/workspace/platform-dir.ts +1 -1
  311. package/src/{activities/execute-cursor/workspace-provision.ts → shared/workspace/session-provision.ts} +29 -16
  312. package/dist/activities/execute-cursor/attachment-resolver.js.map +0 -1
  313. package/dist/activities/execute-cursor/blueprint-resolver.js.map +0 -1
  314. package/dist/activities/execute-cursor/cost-guard.js.map +0 -1
  315. package/dist/activities/execute-cursor/env-resolver.js.map +0 -1
  316. package/dist/activities/execute-cursor/exact-apply.js.map +0 -1
  317. package/dist/activities/execute-cursor/extract-structured-output.js.map +0 -1
  318. package/dist/activities/execute-cursor/index.d.ts +0 -209
  319. package/dist/activities/execute-cursor/index.js +0 -2345
  320. package/dist/activities/execute-cursor/index.js.map +0 -1
  321. package/dist/activities/execute-cursor/persist-decision.js.map +0 -1
  322. package/dist/activities/execute-cursor/skill-resolver.js.map +0 -1
  323. package/dist/activities/execute-cursor/usage-accumulator.d.ts +0 -89
  324. package/dist/activities/execute-cursor/usage-accumulator.js +0 -121
  325. package/dist/activities/execute-cursor/usage-accumulator.js.map +0 -1
  326. package/dist/activities/execute-cursor/workspace-provision.d.ts +0 -42
  327. package/dist/activities/execute-cursor/workspace-provision.js.map +0 -1
  328. package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +0 -164
  329. package/src/activities/execute-cursor/index.ts +0 -2876
  330. package/src/activities/execute-cursor/usage-accumulator.ts +0 -169
  331. /package/dist/{activities/execute-cursor → shared}/env-resolver.js +0 -0
@@ -1,8 +1,8 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
2
  import { createRequire } from "node:module";
3
3
  import type http2Type from "node:http2";
4
- import { installHttp2Interceptor, uninstallHttp2Interceptor, updateHttp2InterceptorToken, assertHttp2ConnectPatched } from "../http2-interceptor.js";
5
- import { getExecutionContext } from "../fetch-interceptor.js";
4
+ import { installHttp2Interceptor, uninstallHttp2Interceptor, assertHttp2ConnectPatched } from "../http2-interceptor.js";
5
+ import { getExecutionContext } from "../../../shared/execution-context.js";
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
8
  const http2: typeof http2Type = require("node:http2");
@@ -53,27 +53,56 @@ describe("http2-interceptor", () => {
53
53
  describe("installHttp2Interceptor", () => {
54
54
  it("is a no-op when proxyEndpoint is undefined", () => {
55
55
  const before = http2.connect;
56
- installHttp2Interceptor({ proxyEndpoint: undefined, stigmerToken: STIGMER_TOKEN });
56
+ installHttp2Interceptor({ proxyEndpoint: undefined, proxyTokenRef: { current: STIGMER_TOKEN } });
57
57
  expect(http2.connect).toBe(before);
58
58
  });
59
59
 
60
60
  it("is a no-op when proxyEndpoint is empty string", () => {
61
61
  const before = http2.connect;
62
- installHttp2Interceptor({ proxyEndpoint: "", stigmerToken: STIGMER_TOKEN });
62
+ installHttp2Interceptor({ proxyEndpoint: "", proxyTokenRef: { current: STIGMER_TOKEN } });
63
63
  expect(http2.connect).toBe(before);
64
64
  });
65
65
 
66
- it("is a no-op when stigmerToken is undefined", () => {
66
+ it("is a no-op when no token ref is bound", () => {
67
67
  const before = http2.connect;
68
- installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, stigmerToken: undefined });
68
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: undefined });
69
69
  expect(http2.connect).toBe(before);
70
70
  });
71
71
 
72
- it("replaces http2.connect when proxyEndpoint and stigmerToken are valid", () => {
72
+ it("is a no-op when the bound ref is empty at install", () => {
73
73
  const before = http2.connect;
74
- installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, stigmerToken: STIGMER_TOKEN });
74
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: null } });
75
+ expect(http2.connect).toBe(before);
76
+ });
77
+
78
+ it("replaces http2.connect when proxyEndpoint and a bound ref are valid", () => {
79
+ const before = http2.connect;
80
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
75
81
  expect(http2.connect).not.toBe(before);
76
82
  });
83
+
84
+ it("is idempotent: a second install rebinds the ref and does not wrap a proxy session twice", async () => {
85
+ const mock = createMockConnect();
86
+ http2.connect = mock.mockConnect;
87
+ const first = { current: "first-token" };
88
+ const second = { current: "second-token" };
89
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: first });
90
+ const patched = http2.connect;
91
+
92
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: second });
93
+ // The one patch stays; a re-capture here would have made the patched
94
+ // connect the "original" and wrapped every proxy session twice.
95
+ expect(http2.connect).toBe(patched);
96
+
97
+ const session = http2.connect(PROXY_ENDPOINT);
98
+ session.request({ ":path": "/agent.v1.AgentService/Run" });
99
+ expect(mock.calls).toHaveLength(1);
100
+ expect(mock.calls[0].headers).toHaveProperty("x-stigmer-auth", "Bearer second-token");
101
+
102
+ // Uninstall restores the connect that was live at the FIRST install.
103
+ uninstallHttp2Interceptor();
104
+ expect(http2.connect).toBe(mock.mockConnect);
105
+ });
77
106
  });
78
107
 
79
108
  describe("uninstallHttp2Interceptor", () => {
@@ -81,7 +110,7 @@ describe("http2-interceptor", () => {
81
110
  const { mockConnect } = createMockConnect();
82
111
  http2.connect = mockConnect;
83
112
 
84
- installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, stigmerToken: STIGMER_TOKEN });
113
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
85
114
  expect(http2.connect).not.toBe(mockConnect);
86
115
 
87
116
  uninstallHttp2Interceptor();
@@ -95,7 +124,7 @@ describe("http2-interceptor", () => {
95
124
  beforeEach(() => {
96
125
  mock = createMockConnect();
97
126
  http2.connect = mock.mockConnect;
98
- installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, stigmerToken: STIGMER_TOKEN });
127
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
99
128
  });
100
129
 
101
130
  it("injects x-stigmer-execution-id and x-stigmer-auth when context is active and target is proxy", async () => {
@@ -188,7 +217,7 @@ describe("http2-interceptor", () => {
188
217
  beforeEach(() => {
189
218
  mock = createMockConnect();
190
219
  http2.connect = mock.mockConnect;
191
- installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, stigmerToken: STIGMER_TOKEN });
220
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
192
221
  });
193
222
 
194
223
  it("different requests on same session get execution IDs from their respective ALS contexts", async () => {
@@ -229,7 +258,7 @@ describe("http2-interceptor", () => {
229
258
  it("matches https with explicit port", async () => {
230
259
  const mock = createMockConnect();
231
260
  http2.connect = mock.mockConnect;
232
- installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com:9443", stigmerToken: STIGMER_TOKEN });
261
+ installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com:9443", proxyTokenRef: { current: STIGMER_TOKEN } });
233
262
 
234
263
  const executionContext = getExecutionContext();
235
264
  await executionContext.run({ executionId: "exec-port" }, async () => {
@@ -243,7 +272,7 @@ describe("http2-interceptor", () => {
243
272
  it("matches https with default port (443)", async () => {
244
273
  const mock = createMockConnect();
245
274
  http2.connect = mock.mockConnect;
246
- installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com", stigmerToken: STIGMER_TOKEN });
275
+ installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com", proxyTokenRef: { current: STIGMER_TOKEN } });
247
276
 
248
277
  const executionContext = getExecutionContext();
249
278
  await executionContext.run({ executionId: "exec-default-port" }, async () => {
@@ -257,7 +286,7 @@ describe("http2-interceptor", () => {
257
286
  it("matches http with explicit port", async () => {
258
287
  const mock = createMockConnect();
259
288
  http2.connect = mock.mockConnect;
260
- installHttp2Interceptor({ proxyEndpoint: "http://localhost:9090", stigmerToken: STIGMER_TOKEN });
289
+ installHttp2Interceptor({ proxyEndpoint: "http://localhost:9090", proxyTokenRef: { current: STIGMER_TOKEN } });
261
290
 
262
291
  const executionContext = getExecutionContext();
263
292
  await executionContext.run({ executionId: "exec-http" }, async () => {
@@ -271,7 +300,7 @@ describe("http2-interceptor", () => {
271
300
  it("does not match when port differs", async () => {
272
301
  const mock = createMockConnect();
273
302
  http2.connect = mock.mockConnect;
274
- installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com:8443", stigmerToken: STIGMER_TOKEN });
303
+ installHttp2Interceptor({ proxyEndpoint: "https://proxy.example.com:8443", proxyTokenRef: { current: STIGMER_TOKEN } });
275
304
 
276
305
  const executionContext = getExecutionContext();
277
306
  await executionContext.run({ executionId: "exec-wrong-port" }, async () => {
@@ -283,31 +312,30 @@ describe("http2-interceptor", () => {
283
312
  });
284
313
  });
285
314
 
286
- describe("updateHttp2InterceptorToken", () => {
315
+ describe("the token ref is read per request (the root rotates it in place; Q-S2-7)", () => {
287
316
  let mock: ReturnType<typeof createMockConnect>;
317
+ let proxyTokenRef: { current: string | null };
288
318
 
289
319
  beforeEach(() => {
290
320
  mock = createMockConnect();
291
321
  http2.connect = mock.mockConnect;
292
- installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, stigmerToken: STIGMER_TOKEN });
322
+ proxyTokenRef = { current: STIGMER_TOKEN };
323
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef });
293
324
  });
294
325
 
295
- it("subsequent requests use the updated token", () => {
326
+ it("a session opened BEFORE a rotation carries the new token on its next request", () => {
296
327
  const session = http2.connect(PROXY_ENDPOINT);
297
328
  session.request({ ":path": "/agent.v1.AgentService/Run" });
298
-
299
329
  expect(mock.calls[0].headers).toHaveProperty("x-stigmer-auth", `Bearer ${STIGMER_TOKEN}`);
300
330
 
301
- const refreshedToken = "refreshed-stigmer-jwt-token";
302
- updateHttp2InterceptorToken(refreshedToken);
303
-
331
+ proxyTokenRef.current = "refreshed-stigmer-jwt-token";
304
332
  session.request({ ":path": "/agent.v1.AgentService/Run" });
305
333
 
306
- expect(mock.calls[1].headers).toHaveProperty("x-stigmer-auth", `Bearer ${refreshedToken}`);
334
+ expect(mock.calls[1].headers).toHaveProperty("x-stigmer-auth", "Bearer refreshed-stigmer-jwt-token");
307
335
  });
308
336
 
309
- it("new sessions after token update use the updated token", () => {
310
- updateHttp2InterceptorToken("new-token-value");
337
+ it("a session opened AFTER a rotation carries the new token", () => {
338
+ proxyTokenRef.current = "new-token-value";
311
339
 
312
340
  const session = http2.connect(PROXY_ENDPOINT);
313
341
  session.request({ ":path": "/aiserver.v1.AnalyticsService/BootstrapStatsig" });
@@ -315,15 +343,16 @@ describe("http2-interceptor", () => {
315
343
  expect(mock.calls[0].headers).toHaveProperty("x-stigmer-auth", "Bearer new-token-value");
316
344
  });
317
345
 
318
- it("is a no-op when interceptor is not installed", () => {
319
- uninstallHttp2Interceptor();
320
- // Should not throw
321
- updateHttp2InterceptorToken("orphan-token");
322
-
323
- const session = http2.connect(PROXY_ENDPOINT);
324
- session.request({ ":path": "/test" });
346
+ it("an emptied ref omits x-stigmer-auth and keeps the execution id (the proxy's 401 is the diagnostic)", async () => {
347
+ proxyTokenRef.current = null;
348
+ const executionContext = getExecutionContext();
349
+ await executionContext.run({ executionId: "exec-empty-ref" }, async () => {
350
+ const session = http2.connect(PROXY_ENDPOINT);
351
+ session.request({ ":path": "/agent.v1.AgentService/Run" });
352
+ });
325
353
 
326
354
  expect(mock.calls[0].headers).not.toHaveProperty("x-stigmer-auth");
355
+ expect(mock.calls[0].headers).toHaveProperty("x-stigmer-execution-id", "exec-empty-ref");
327
356
  });
328
357
  });
329
358
 
@@ -333,8 +362,9 @@ describe("http2-interceptor", () => {
333
362
  // and CASE A/B in the probe). That makes the positive "configured + in-sync"
334
363
  // path impossible to assert deterministically inside a shared test process
335
364
  // — it is guaranteed by load order (interceptor installed before connect-node
336
- // imports node:http2) and exercised by the boot guard in the runner
337
- // factories. Here we cover the two deterministic contracts: it is a no-op
365
+ // imports node:http2), exercised by the boot guard inside the Cursor
366
+ // adapter's boot, and proven in a FRESH process by
367
+ // src/__tests__/harness-boot-order.test.ts. Here we cover the two deterministic contracts: it is a no-op
338
368
  // when unconfigured (no import, so it never freezes the facade), and it
339
369
  // throws when the frozen facade is out of sync with the patched connect.
340
370
  //
@@ -359,7 +389,7 @@ describe("http2-interceptor", () => {
359
389
 
360
390
  const mock = createMockConnect();
361
391
  http2.connect = mock.mockConnect;
362
- installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, stigmerToken: STIGMER_TOKEN });
392
+ installHttp2Interceptor({ proxyEndpoint: PROXY_ENDPOINT, proxyTokenRef: { current: STIGMER_TOKEN } });
363
393
 
364
394
  await expect(assertHttp2ConnectPatched()).rejects.toThrow(
365
395
  /node:http2 ESM facade is unpatched/,
@@ -26,9 +26,7 @@ import type { SDKMessage } from "@cursor/sdk";
26
26
  import {
27
27
  MessageAccumulator,
28
28
  extractConversationSteps,
29
- cancelInProgressSubAgentProtos,
30
29
  } from "../message-translator.js";
31
- import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
32
30
 
33
31
  function toolCallEvent(
34
32
  callId: string,
@@ -1032,42 +1030,4 @@ describe("MessageAccumulator tool call status transitions", () => {
1032
1030
  });
1033
1031
  });
1034
1032
 
1035
- describe("cancelInProgressSubAgentProtos standalone", () => {
1036
- it("cancels IN_PROGRESS/PENDING protos in place and reports whether anything changed", () => {
1037
- const running = create(SubAgentExecutionSchema, {
1038
- id: "a",
1039
- status: SubAgentStatus.SUB_AGENT_IN_PROGRESS,
1040
- });
1041
- const pending = create(SubAgentExecutionSchema, {
1042
- id: "b",
1043
- status: SubAgentStatus.SUB_AGENT_PENDING,
1044
- });
1045
- const completed = create(SubAgentExecutionSchema, {
1046
- id: "c",
1047
- status: SubAgentStatus.SUB_AGENT_COMPLETED,
1048
- completedAt: "2026-01-01T00:00:00Z",
1049
- });
1050
-
1051
- const list = [running, pending, completed];
1052
- const changed = cancelInProgressSubAgentProtos(list);
1053
-
1054
- expect(changed).toBe(true);
1055
- expect(running.status).toBe(SubAgentStatus.SUB_AGENT_CANCELLED);
1056
- expect(running.completedAt).not.toBe("");
1057
- expect(pending.status).toBe(SubAgentStatus.SUB_AGENT_CANCELLED);
1058
- // Terminal sub-agents are untouched.
1059
- expect(completed.status).toBe(SubAgentStatus.SUB_AGENT_COMPLETED);
1060
- expect(completed.completedAt).toBe("2026-01-01T00:00:00Z");
1061
- });
1062
-
1063
- it("returns false when there is nothing to cancel", () => {
1064
- const completed = create(SubAgentExecutionSchema, {
1065
- id: "c",
1066
- status: SubAgentStatus.SUB_AGENT_COMPLETED,
1067
- });
1068
- expect(cancelInProgressSubAgentProtos([completed])).toBe(false);
1069
- expect(cancelInProgressSubAgentProtos([])).toBe(false);
1070
- });
1071
- });
1072
-
1073
1033
  });
@@ -0,0 +1,171 @@
1
+ /**
2
+ * A later, same-identity proposal is a NEW act: it never lands on a row the
3
+ * user already decided (S2 M4 finding F9).
4
+ *
5
+ * The defect the harness contract kit found against this adapter: within one
6
+ * execution, after the user APPROVED `edit a.txt` and the resumed agent ran
7
+ * it (its row COMPLETED, still carrying `approval_action = APPROVE`), the
8
+ * model proposed the SAME edit again in a later turn. The hook denied it
9
+ * (correct: no grant). The denial overlay then matched the denial to the
10
+ * FIRST same-identity row in the whole transcript — the completed, approved
11
+ * one — flipped it back to WAITING_APPROVAL with its APPROVE still on it and
12
+ * wiped its result; the actual streamed call was hidden as a twin. On the next
13
+ * invocation `approvalDecisionsOf` read that row as a decision and the new,
14
+ * never-approved proposal ran under the old approval. The accumulator had the
15
+ * sibling flaw: a re-issue after a SKIP or REJECT was reconciled onto the
16
+ * declined row by identity, inheriting its decision.
17
+ *
18
+ * The rule, in `message-translator.ts`: a row the server has adjudicated
19
+ * (`isAdjudicatedRow`) is never the home of THIS turn's denial and never a
20
+ * twin to collapse; a row the user DECLINED (`isDeclinedRow`) is never the
21
+ * home of a later same-identity call. The approved re-issue (the
22
+ * `sequential-gate-resume` shape) still reconciles onto its APPROVE row — the
23
+ * one case where the later call IS the decided act.
24
+ *
25
+ * Each arm ends by deriving the decisions the runtime would read from the
26
+ * reconciled transcript (`approvalDecisionsOf`): the assertion that matters
27
+ * is that nothing decided bleeds onto the new proposal.
28
+ */
29
+
30
+ import { describe, it, expect } from "vitest";
31
+ import { create, clone } from "@bufbuild/protobuf";
32
+ import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
33
+ import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
34
+ import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
35
+ import { ApprovalAction, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
36
+ import type { SDKMessage } from "@cursor/sdk";
37
+
38
+ import { approvalDecisionsOf } from "../../../harness/turn-context.js";
39
+ import { MessageAccumulator, reconcileDeniedToolCalls, toolCallIdentityToken } from "../message-translator.js";
40
+ import type { DeniedLedgerEntry } from "../approval-state.js";
41
+
42
+ const PATH = "/work/a.txt";
43
+ const CONTENT = "A\n";
44
+ const FIRST_ID = "tool_edit_first";
45
+ const LATER_ID = "tool_edit_later";
46
+ const RESULT_OF_FIRST = "wrote a.txt";
47
+
48
+ function toolCallEvent(
49
+ callId: string,
50
+ status: "running" | "completed" | "error",
51
+ result?: unknown,
52
+ ): Extract<SDKMessage, { type: "tool_call" }> {
53
+ return { type: "tool_call", agent_id: "agent-1", run_id: "r3", call_id: callId, name: "edit", status, args: { path: PATH, content: CONTENT }, result };
54
+ }
55
+
56
+ /** The transcript after the first gate was decided `action` and, for an APPROVE, run. */
57
+ function decidedFirstGate(action: ApprovalAction): AgentMessage[] {
58
+ const executed = action === ApprovalAction.APPROVE;
59
+ return [
60
+ create(AgentMessageSchema, {
61
+ type: MessageType.MESSAGE_AI,
62
+ content: "I'll write file A.",
63
+ toolCalls: [
64
+ create(ToolCallSchema, {
65
+ id: FIRST_ID,
66
+ name: "edit",
67
+ status: executed ? ToolCallStatus.TOOL_CALL_COMPLETED : ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
68
+ requiresApproval: true,
69
+ approvalAction: action,
70
+ args: { path: PATH, content: CONTENT },
71
+ argsPreview: JSON.stringify({ path: PATH, content: CONTENT }),
72
+ result: executed ? RESULT_OF_FIRST : "",
73
+ completedAt: executed ? "2026-06-20T00:00:05.000Z" : "",
74
+ }),
75
+ ],
76
+ }),
77
+ ];
78
+ }
79
+
80
+ function allToolCalls(messages: AgentMessage[]): ToolCall[] {
81
+ return messages.flatMap((m) => m.toolCalls);
82
+ }
83
+
84
+ /** The later turn: the model proposes the same edit under a fresh id, the hook denies it, the turn boundary reconciles. */
85
+ async function laterTurnProposesSameEdit(seeded: AgentMessage[]): Promise<ToolCall[]> {
86
+ const acc = new MessageAccumulator(seeded, { seededSubAgents: [] });
87
+ acc.processEvent(toolCallEvent(LATER_ID, "running"));
88
+ acc.processEvent(toolCallEvent(LATER_ID, "error", "Blocked by hook"));
89
+ acc.finalize();
90
+ const ledger: DeniedLedgerEntry[] = [{ toolName: "Write", token: toolCallIdentityToken(allToolCalls(seeded).find((tc) => tc.id === LATER_ID)!) }];
91
+ return reconcileDeniedToolCalls(seeded, ledger);
92
+ }
93
+
94
+ function decisionsTheRuntimeWouldRead(messages: AgentMessage[]): ReadonlyMap<string, ApprovalAction> {
95
+ return approvalDecisionsOf(create(AgentExecutionStatusSchema, { messages }));
96
+ }
97
+
98
+ describe("a later same-identity proposal never lands on a decided row (F9)", () => {
99
+ it("after APPROVE and execution: the completed row keeps its result and decision; the new call gets its own gate; nothing bleeds", async () => {
100
+ const seeded = decidedFirstGate(ApprovalAction.APPROVE).map((m) => clone(AgentMessageSchema, m));
101
+
102
+ const gated = await laterTurnProposesSameEdit(seeded);
103
+
104
+ const byId = new Map(allToolCalls(seeded).map((tc) => [tc.id, tc]));
105
+ const first = byId.get(FIRST_ID)!;
106
+ expect(first.status, "the executed row is the transcript's record and is never rewritten").toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
107
+ expect(first.result).toBe(RESULT_OF_FIRST);
108
+ expect(first.approvalAction).toBe(ApprovalAction.APPROVE);
109
+
110
+ const later = byId.get(LATER_ID);
111
+ expect(later, "the new act has its own row").toBeDefined();
112
+ expect(later!.status, "and it is the turn's one gate").toBe(ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
113
+ expect(later!.approvalAction, "undecided — the user has not been asked yet").toBe(ApprovalAction.UNSPECIFIED);
114
+ expect(gated.map((tc) => tc.id)).toEqual([LATER_ID]);
115
+
116
+ expect(decisionsTheRuntimeWouldRead(seeded).size, "no decision bleeds onto the new proposal").toBe(0);
117
+ });
118
+
119
+ it.each([
120
+ ["SKIP", ApprovalAction.SKIP],
121
+ ["REJECT", ApprovalAction.REJECT],
122
+ ] as const)("after %s: the re-issue is not reconciled onto the declined row; it gets its own gate and inherits nothing", async (_label, action) => {
123
+ const seeded = decidedFirstGate(action).map((m) => clone(AgentMessageSchema, m));
124
+
125
+ const gated = await laterTurnProposesSameEdit(seeded);
126
+
127
+ const byId = new Map(allToolCalls(seeded).map((tc) => [tc.id, tc]));
128
+ const first = byId.get(FIRST_ID)!;
129
+ expect(first.status, "the declined gate is untouched").toBe(ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
130
+ expect(first.approvalAction).toBe(action);
131
+ expect(first.error, "the later denial was not merged onto it").toBe("");
132
+
133
+ const later = byId.get(LATER_ID);
134
+ expect(later, "the re-issue is a new act with its own row").toBeDefined();
135
+ expect(later!.status).toBe(ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
136
+ expect(later!.approvalAction).toBe(ApprovalAction.UNSPECIFIED);
137
+ expect(gated.map((tc) => tc.id)).toEqual([LATER_ID]);
138
+
139
+ const decisions = decisionsTheRuntimeWouldRead(seeded);
140
+ expect(decisions.get(FIRST_ID), "the declined row's decision stays its own").toBe(action);
141
+ expect(decisions.has(LATER_ID), "and is not the new proposal's").toBe(false);
142
+ });
143
+
144
+ it("the approved re-issue itself still reconciles onto its APPROVE row (the sequential-gate-resume shape is unchanged)", () => {
145
+ const seeded = [
146
+ create(AgentMessageSchema, {
147
+ type: MessageType.MESSAGE_AI,
148
+ toolCalls: [
149
+ create(ToolCallSchema, {
150
+ id: FIRST_ID,
151
+ name: "edit",
152
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
153
+ requiresApproval: true,
154
+ approvalAction: ApprovalAction.APPROVE,
155
+ argsPreview: JSON.stringify({ path: PATH, content: CONTENT }),
156
+ }),
157
+ ],
158
+ }),
159
+ ];
160
+
161
+ const acc = new MessageAccumulator(seeded, { seededSubAgents: [] });
162
+ acc.processEvent(toolCallEvent(`${FIRST_ID}_RESUME`, "running"));
163
+ acc.processEvent(toolCallEvent(`${FIRST_ID}_RESUME`, "completed", RESULT_OF_FIRST));
164
+ acc.finalize();
165
+
166
+ const tools = allToolCalls(seeded);
167
+ expect(tools.map((tc) => tc.id), "one row, the committed id").toEqual([FIRST_ID]);
168
+ expect(tools[0]!.status).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
169
+ expect(tools[0]!.result).toBe(RESULT_OF_FIRST);
170
+ });
171
+ });