@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
@@ -0,0 +1,60 @@
1
+ /**
2
+ * A whole `TurnInput` for tests that stand in for the runtime.
3
+ *
4
+ * The contract's input is the runtime's entire resolved record (`harness/
5
+ * types.ts`): the execution and session protos, the blueprint, the
6
+ * environment, the workspace, the tool surface, the attachments, the model
7
+ * preferences, the standing context. A test that drives an adapter WITHOUT
8
+ * the runtime — the harness contract kit's `ExecutionDriver`, a unit test of
9
+ * one adapter step — needs a record that type-checks and is inert: empty
10
+ * protos, an empty tool surface, a workspace under the OS temp dir, a memory
11
+ * selection that answers nothing. Everything here is the neutral value;
12
+ * `overrides` deep-merges one level (a test states the fields it is about
13
+ * and nothing else), the way `testConfig()` does for `Config`.
14
+ *
15
+ * Nothing here is Cursor's or native's: the vision profile, the file-review
16
+ * identity and the state id source are the ADAPTER's capabilities, not
17
+ * input facts, so they do not appear.
18
+ */
19
+ import { type AgentExecution, type AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
20
+ import { type Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
21
+ import type { TurnInput } from "../harness/types.js";
22
+ import type { ResolvedBlueprint } from "../shared/blueprint-resolver.js";
23
+ /** The ids a fixture record carries when a test does not name its own. */
24
+ export declare const TURN_INPUT_FIXTURE_IDS: {
25
+ readonly executionId: "aex_fixture_0001";
26
+ readonly sessionId: "ses_fixture_0001";
27
+ readonly agentId: "agt_fixture_0001";
28
+ readonly org: "fixture-org";
29
+ };
30
+ export interface TurnInputFixtureOverrides extends Partial<Omit<TurnInput, "execution" | "session" | "blueprint">> {
31
+ readonly execution?: AgentExecution;
32
+ readonly session?: Session;
33
+ readonly blueprint?: Partial<ResolvedBlueprint>;
34
+ /**
35
+ * The user's message on the default execution's spec (ignored when a whole
36
+ * `execution` is given). Empty by default: the neutral record asks nothing.
37
+ */
38
+ readonly message?: string;
39
+ /**
40
+ * The status the default execution carries — what the control plane
41
+ * persisted at the end of the previous invocation, on a reinvocation. The
42
+ * runtime seeds the turn's in-progress status from a clone of it
43
+ * (`seedTranscriptFromExecution`) and an adapter may read the record's own
44
+ * copy for its facts, so a test standing in for the runtime hands both.
45
+ */
46
+ readonly persistedStatus?: AgentExecutionStatus;
47
+ /**
48
+ * Where the default workspace lives (ignored when a whole `workspace` is
49
+ * given). A test that only needs "my workspace is here" — because its
50
+ * engine writes into it — states the directory and nothing else of the
51
+ * provision record.
52
+ */
53
+ readonly workspaceDir?: string;
54
+ }
55
+ /**
56
+ * The neutral record, with `overrides` laid over it one level deep. The
57
+ * `session` and `blueprint.session` are the SAME object, as the runtime
58
+ * guarantees (`bindHarnessState` writes it).
59
+ */
60
+ export declare function turnInputFixture(overrides?: TurnInputFixtureOverrides): TurnInput;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * A whole `TurnInput` for tests that stand in for the runtime.
3
+ *
4
+ * The contract's input is the runtime's entire resolved record (`harness/
5
+ * types.ts`): the execution and session protos, the blueprint, the
6
+ * environment, the workspace, the tool surface, the attachments, the model
7
+ * preferences, the standing context. A test that drives an adapter WITHOUT
8
+ * the runtime — the harness contract kit's `ExecutionDriver`, a unit test of
9
+ * one adapter step — needs a record that type-checks and is inert: empty
10
+ * protos, an empty tool surface, a workspace under the OS temp dir, a memory
11
+ * selection that answers nothing. Everything here is the neutral value;
12
+ * `overrides` deep-merges one level (a test states the fields it is about
13
+ * and nothing else), the way `testConfig()` does for `Config`.
14
+ *
15
+ * Nothing here is Cursor's or native's: the vision profile, the file-review
16
+ * identity and the state id source are the ADAPTER's capabilities, not
17
+ * input facts, so they do not appear.
18
+ */
19
+ import { create } from "@bufbuild/protobuf";
20
+ import { tmpdir } from "node:os";
21
+ import { join } from "node:path";
22
+ import { AgentExecutionSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
23
+ import { AgentExecutionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
24
+ import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
25
+ import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
26
+ import { SessionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/spec_pb";
27
+ import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
28
+ import { AgentSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
29
+ import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
30
+ import { mockWorkspaceBackend } from "./mock-workspace.js";
31
+ /** The ids a fixture record carries when a test does not name its own. */
32
+ export const TURN_INPUT_FIXTURE_IDS = {
33
+ executionId: "aex_fixture_0001",
34
+ sessionId: "ses_fixture_0001",
35
+ agentId: "agt_fixture_0001",
36
+ org: "fixture-org",
37
+ };
38
+ /**
39
+ * The neutral record, with `overrides` laid over it one level deep. The
40
+ * `session` and `blueprint.session` are the SAME object, as the runtime
41
+ * guarantees (`bindHarnessState` writes it).
42
+ */
43
+ export function turnInputFixture(overrides = {}) {
44
+ const executionId = overrides.executionId ?? TURN_INPUT_FIXTURE_IDS.executionId;
45
+ const sessionId = overrides.sessionId ?? TURN_INPUT_FIXTURE_IDS.sessionId;
46
+ const session = overrides.session ??
47
+ create(SessionSchema, {
48
+ metadata: create(ApiResourceMetadataSchema, { id: sessionId, org: TURN_INPUT_FIXTURE_IDS.org, name: sessionId }),
49
+ spec: create(SessionSpecSchema, {}),
50
+ });
51
+ const execution = overrides.execution ??
52
+ create(AgentExecutionSchema, {
53
+ metadata: create(ApiResourceMetadataSchema, { id: executionId, org: TURN_INPUT_FIXTURE_IDS.org, name: executionId }),
54
+ spec: create(AgentExecutionSpecSchema, { sessionId, message: overrides.message ?? "" }),
55
+ status: overrides.persistedStatus,
56
+ });
57
+ const agent = create(AgentSchema, {
58
+ metadata: create(ApiResourceMetadataSchema, {
59
+ id: TURN_INPUT_FIXTURE_IDS.agentId,
60
+ org: TURN_INPUT_FIXTURE_IDS.org,
61
+ name: "fixture-agent",
62
+ }),
63
+ spec: create(AgentSpecSchema, { instructions: "You are the fixture agent." }),
64
+ });
65
+ const workspaceDir = overrides.workspaceDir ?? join(tmpdir(), "stigmer-runner-turn-input-fixture");
66
+ const blueprint = {
67
+ agent,
68
+ agentSpec: agent.spec,
69
+ session,
70
+ sessionSpec: session.spec,
71
+ instructions: agent.spec.instructions,
72
+ subAgents: [],
73
+ mergedMcpServerUsages: [],
74
+ mergedSkillRefs: [],
75
+ cloudRepos: [],
76
+ ...overrides.blueprint,
77
+ };
78
+ return {
79
+ executionId,
80
+ threadId: overrides.threadId ?? "",
81
+ turnSeq: overrides.turnSeq ?? 0,
82
+ sessionId,
83
+ approvalDecisions: overrides.approvalDecisions ?? new Map(),
84
+ execution,
85
+ session,
86
+ blueprint,
87
+ environment: overrides.environment ?? { envVars: {}, secretKeys: new Set() },
88
+ workspace: overrides.workspace ?? {
89
+ dirs: [workspaceDir],
90
+ primaryDir: workspaceDir,
91
+ gitWorkspace: false,
92
+ captureMode: false,
93
+ changeSetId: `${executionId}:${overrides.turnSeq ?? 0}`,
94
+ provision: { workspaceDirs: [workspaceDir], provisionResults: [], workspaceBackend: mockWorkspaceBackend() },
95
+ },
96
+ mcp: overrides.mcp ?? {
97
+ servers: [],
98
+ channelMessaging: [],
99
+ leases: { global: false, categories: new Set(), servers: new Set() },
100
+ policies: new Map(),
101
+ },
102
+ skills: overrides.skills ?? [],
103
+ attachments: overrides.attachments ?? { results: [], visionImages: [], visionNotViewable: [] },
104
+ appliedToolCallIds: overrides.appliedToolCallIds ?? new Set(),
105
+ model: overrides.model ?? { requested: "default", serviceTier: ServiceTier.STANDARD, thinkingMode: ThinkingMode.DISABLED },
106
+ structuredOutputSchema: overrides.structuredOutputSchema,
107
+ standing: overrides.standing ?? {
108
+ contextBridge: undefined,
109
+ senderIdentity: undefined,
110
+ sessionContext: undefined,
111
+ declaredPreferences: undefined,
112
+ conversationCatchup: undefined,
113
+ selectRecalledMemories: async () => undefined,
114
+ },
115
+ artifactStorage: overrides.artifactStorage,
116
+ };
117
+ }
118
+ //# sourceMappingURL=turn-input-fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-input-fixture.js","sourceRoot":"","sources":["../../src/__test-utils__/turn-input-fixture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The Cursor harness as a `HarnessContractSubject` — the REAL `createCursorAdapter()`
3
+ * driven through the harness contract kit, with the kit's engine-neutral
4
+ * scenario vocabulary translated onto S0's scripted `@cursor/sdk` double.
5
+ *
6
+ * The sibling of `gateway-substrate.ts` (the Cursor side of the approval
7
+ * contract kit): the kit owns the invariants and the runtime stand-in, this
8
+ * module owns the ENGINE — what the next turn of a session's agent will do —
9
+ * and reports what the double observed. The adapter under test is the
10
+ * production one, untouched; it imports no `@temporalio/*` (the fence test
11
+ * pins that), which is what lets the kit run it outside any activity
12
+ * context.
13
+ *
14
+ * ── The engine is per session ───────────────────────────────────────────────
15
+ * One scripted agent per session, minted when the kit first arranges a turn
16
+ * for it and handed out by the double's `Agent.create` for THAT session (keyed
17
+ * by the SDK's `platform.workspaceRef`, computed the way the adapter computes
18
+ * it), then parked by the real session cache across turns exactly as in a live
19
+ * worker. Arranging a turn REPLACES the agent's next script, so a turn the
20
+ * adapter interrupts before its `send()` leaves no stale script for the
21
+ * session's next turn. Every session also gets the workspace directory the
22
+ * runtime's provisioner would give it (`<workspaceRootDir>/sessions/<id>`), so
23
+ * both halves of the kit gate in the same directory shape; the hook's
24
+ * active-turn pointer is per workspace, so concurrent sessions cannot repoint
25
+ * each other's hook.
26
+ *
27
+ * ── The translation, one rule per word ──────────────────────────────────────
28
+ * - `say(text)` → an `assistant` event. A first send opens with the
29
+ * `system/init` event; a resumed send does not (the
30
+ * `deny-and-retry` turn-2 script is the precedent).
31
+ * - `propose(id, a)` → the STREAM tool call (`edit` / `shell` / `delete`, the
32
+ * kits' shared translation in `cursor-hook-harness.ts`),
33
+ * an `effect` that runs the REAL preToolUse hook the
34
+ * adapter installed against the session's workspace,
35
+ * and the call's completion event. `executionCount(id)`
36
+ * is the hook's `allow` answers for `id` — the SDK
37
+ * executes a tool iff the hook allows it, so this is the
38
+ * safety-critical observable. WHETHER the call is
39
+ * emitted is decided from the kit's view and this
40
+ * subject's own memory, as the model and the SDK would
41
+ * decide it (see {@link CursorContractSubject.proposeSteps}).
42
+ * - `read(id, path)` → the `read` tool call, the REAL hook (which allows it —
43
+ * a read is never gated) and the completion; counted as
44
+ * run. The discrete event the adapter's loop flushes a
45
+ * persist on.
46
+ * - `usage(delta)` → a `turn-ended` delta; the adapter prices it.
47
+ * - `hang` → an `effect` parked on the run's own `cancelled`
48
+ * status, which the adapter's stop-signal listener
49
+ * (`turn-stream.ts` `cancelOnStop`) produces. Never a
50
+ * timer. Resolves `whenHanging`.
51
+ * - `fail(msg, s)` → `engine`: the run ends `{ status: "error" }` and
52
+ * `run.wait()` carries the message; `internal`: an
53
+ * `effect` throws mid-stream (the adapter's catch
54
+ * classifies it). `actionable` is refused as a test bug:
55
+ * only the unattributed-hook-block path produces that
56
+ * surface, and the kit's invariants do not ask for it.
57
+ * - `cancelled` → an `effect` calling `run.cancel()` from the SDK side
58
+ * (the `run-cancelled` golden's shape).
59
+ * Every script that does not end in `fail`, `cancelled` or `hang` closes with
60
+ * `finished({ result: <last say> })` so `run.wait()` has an answer.
61
+ *
62
+ * ── Two modelling decisions (S2 M4, Q-M4-4) ─────────────────────────────────
63
+ * A `propose` for an id this subject has already seen the hook ALLOW emits
64
+ * nothing: the real SDK does not re-run a completed tool on resume — the parked
65
+ * agent's conversation already holds the tool's result. A `propose` whose id
66
+ * carries a SKIP or REJECT decision emits nothing either: the resume prompt
67
+ * tells the model the user declined the action and an obedient model moves
68
+ * on. Both are what "the engine re-reaches the same call" and "the turn
69
+ * continues" MEAN for a deny-and-retry harness; neither hides anything from
70
+ * the safety observable, because a fresh identical command from a
71
+ * disobedient model IS a new gated act and Cursor re-gates it — the subject
72
+ * file proves that with {@link CursorContractSubject.forceReissue}.
73
+ *
74
+ * The completion event a `propose` emits is scripted from the answer the hook
75
+ * is EXPECTED to give (deny for an undecided proposal, allow for an approved
76
+ * re-issue); the hook's actual answer is recorded at effect time and any
77
+ * disagreement is kept in {@link CursorContractSubject.hookDisagreements} for
78
+ * the subject file to assert empty — a hook that allows an undecided action
79
+ * would show up there AND in `executionCount`, never be papered over.
80
+ */
81
+ import type { HarnessAdapter } from "../../../harness/types.js";
82
+ import type { HarnessContractSubject } from "../../../__test-utils__/harness-contract/types.js";
83
+ import type { HermeticEnvironment } from "../../../__test-utils__/hermetic-activity.js";
84
+ import { ScriptedCursorAgent } from "./scripted-agent.js";
85
+ import { ScriptedCursorSdk } from "./scripted-sdk.js";
86
+ export interface CursorContractSubject extends HarnessContractSubject {
87
+ readonly adapter: HarnessAdapter;
88
+ /** The SDK double the adapter is talking to, for the subject file's own observations. */
89
+ readonly sdk: ScriptedCursorSdk;
90
+ /** The scripted agent minted for a session, once the kit has arranged a turn for it. */
91
+ agentFor(sessionId: string): ScriptedCursorAgent | undefined;
92
+ /**
93
+ * Make the NEXT `propose` of `toolCallId` emit its call whatever the
94
+ * decision or the memory says — the disobedient model, for the observation
95
+ * the kit's vocabulary cannot express.
96
+ */
97
+ forceReissue(toolCallId: string): void;
98
+ /** Every proposal at which the hook answered other than the script expected; the subject file asserts it stays empty. */
99
+ readonly hookDisagreements: readonly string[];
100
+ }
101
+ /**
102
+ * Build the subject over one hermetic environment (the temp `HOME` the gate's
103
+ * per-session HITL artifacts land under, the temp workspace root). The
104
+ * environment is the caller's to create before and dispose after; the SDK
105
+ * double is bound for the module here, so a file holds one subject.
106
+ */
107
+ export declare function createCursorContractSubject(env: HermeticEnvironment): CursorContractSubject;
@@ -0,0 +1,344 @@
1
+ /**
2
+ * The Cursor harness as a `HarnessContractSubject` — the REAL `createCursorAdapter()`
3
+ * driven through the harness contract kit, with the kit's engine-neutral
4
+ * scenario vocabulary translated onto S0's scripted `@cursor/sdk` double.
5
+ *
6
+ * The sibling of `gateway-substrate.ts` (the Cursor side of the approval
7
+ * contract kit): the kit owns the invariants and the runtime stand-in, this
8
+ * module owns the ENGINE — what the next turn of a session's agent will do —
9
+ * and reports what the double observed. The adapter under test is the
10
+ * production one, untouched; it imports no `@temporalio/*` (the fence test
11
+ * pins that), which is what lets the kit run it outside any activity
12
+ * context.
13
+ *
14
+ * ── The engine is per session ───────────────────────────────────────────────
15
+ * One scripted agent per session, minted when the kit first arranges a turn
16
+ * for it and handed out by the double's `Agent.create` for THAT session (keyed
17
+ * by the SDK's `platform.workspaceRef`, computed the way the adapter computes
18
+ * it), then parked by the real session cache across turns exactly as in a live
19
+ * worker. Arranging a turn REPLACES the agent's next script, so a turn the
20
+ * adapter interrupts before its `send()` leaves no stale script for the
21
+ * session's next turn. Every session also gets the workspace directory the
22
+ * runtime's provisioner would give it (`<workspaceRootDir>/sessions/<id>`), so
23
+ * both halves of the kit gate in the same directory shape; the hook's
24
+ * active-turn pointer is per workspace, so concurrent sessions cannot repoint
25
+ * each other's hook.
26
+ *
27
+ * ── The translation, one rule per word ──────────────────────────────────────
28
+ * - `say(text)` → an `assistant` event. A first send opens with the
29
+ * `system/init` event; a resumed send does not (the
30
+ * `deny-and-retry` turn-2 script is the precedent).
31
+ * - `propose(id, a)` → the STREAM tool call (`edit` / `shell` / `delete`, the
32
+ * kits' shared translation in `cursor-hook-harness.ts`),
33
+ * an `effect` that runs the REAL preToolUse hook the
34
+ * adapter installed against the session's workspace,
35
+ * and the call's completion event. `executionCount(id)`
36
+ * is the hook's `allow` answers for `id` — the SDK
37
+ * executes a tool iff the hook allows it, so this is the
38
+ * safety-critical observable. WHETHER the call is
39
+ * emitted is decided from the kit's view and this
40
+ * subject's own memory, as the model and the SDK would
41
+ * decide it (see {@link CursorContractSubject.proposeSteps}).
42
+ * - `read(id, path)` → the `read` tool call, the REAL hook (which allows it —
43
+ * a read is never gated) and the completion; counted as
44
+ * run. The discrete event the adapter's loop flushes a
45
+ * persist on.
46
+ * - `usage(delta)` → a `turn-ended` delta; the adapter prices it.
47
+ * - `hang` → an `effect` parked on the run's own `cancelled`
48
+ * status, which the adapter's stop-signal listener
49
+ * (`turn-stream.ts` `cancelOnStop`) produces. Never a
50
+ * timer. Resolves `whenHanging`.
51
+ * - `fail(msg, s)` → `engine`: the run ends `{ status: "error" }` and
52
+ * `run.wait()` carries the message; `internal`: an
53
+ * `effect` throws mid-stream (the adapter's catch
54
+ * classifies it). `actionable` is refused as a test bug:
55
+ * only the unattributed-hook-block path produces that
56
+ * surface, and the kit's invariants do not ask for it.
57
+ * - `cancelled` → an `effect` calling `run.cancel()` from the SDK side
58
+ * (the `run-cancelled` golden's shape).
59
+ * Every script that does not end in `fail`, `cancelled` or `hang` closes with
60
+ * `finished({ result: <last say> })` so `run.wait()` has an answer.
61
+ *
62
+ * ── Two modelling decisions (S2 M4, Q-M4-4) ─────────────────────────────────
63
+ * A `propose` for an id this subject has already seen the hook ALLOW emits
64
+ * nothing: the real SDK does not re-run a completed tool on resume — the parked
65
+ * agent's conversation already holds the tool's result. A `propose` whose id
66
+ * carries a SKIP or REJECT decision emits nothing either: the resume prompt
67
+ * tells the model the user declined the action and an obedient model moves
68
+ * on. Both are what "the engine re-reaches the same call" and "the turn
69
+ * continues" MEAN for a deny-and-retry harness; neither hides anything from
70
+ * the safety observable, because a fresh identical command from a
71
+ * disobedient model IS a new gated act and Cursor re-gates it — the subject
72
+ * file proves that with {@link CursorContractSubject.forceReissue}.
73
+ *
74
+ * The completion event a `propose` emits is scripted from the answer the hook
75
+ * is EXPECTED to give (deny for an undecided proposal, allow for an approved
76
+ * re-issue); the hook's actual answer is recorded at effect time and any
77
+ * disagreement is kept in {@link CursorContractSubject.hookDisagreements} for
78
+ * the subject file to assert empty — a hook that allows an undecided action
79
+ * would show up there AND in `executionCount`, never be papered over.
80
+ */
81
+ import { mkdirSync } from "node:fs";
82
+ import { join } from "node:path";
83
+ import { ApprovalAction, ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
84
+ import { createCursorAdapter } from "../adapter.js";
85
+ import { resolvePlatformOptions } from "../session-lifecycle.js";
86
+ import { hookInputFor, streamArgsFor, STREAM_NAME } from "./cursor-hook-harness.js";
87
+ import { FIXTURE, SDK_CATALOG, hermeticCursorConfig, runWorkspaceHook } from "./hermetic-cursor.js";
88
+ import { ScriptedCursorAgent, sdkEvents, step } from "./scripted-agent.js";
89
+ import { ScriptedCursorSdk, bindScriptedSdk } from "./scripted-sdk.js";
90
+ /** The user's message every kit turn asks; the prompt builder needs one and the kit has none to give. */
91
+ const KIT_USER_MESSAGE = "Do what the scenario says.";
92
+ /** What the double records the SDK "returning" for an allowed tool call. */
93
+ const ALLOWED_RESULT = "ok (kit)";
94
+ /** The error the SDK surfaces on a hook-denied call, as the goldens script it. */
95
+ const HOOK_BLOCKED_RESULT = "Blocked by hook";
96
+ /**
97
+ * Build the subject over one hermetic environment (the temp `HOME` the gate's
98
+ * per-session HITL artifacts land under, the temp workspace root). The
99
+ * environment is the caller's to create before and dispose after; the SDK
100
+ * double is bound for the module here, so a file holds one subject.
101
+ */
102
+ export function createCursorContractSubject(env) {
103
+ return new CursorSubject(env);
104
+ }
105
+ class CursorSubject {
106
+ env;
107
+ name = "cursor";
108
+ harness = "cursor";
109
+ adapter;
110
+ config;
111
+ sdk;
112
+ hookDisagreements = [];
113
+ agents = new Map();
114
+ agentsByWorkspaceRef = new Map();
115
+ proposals = new Map();
116
+ forcedReissues = new Set();
117
+ hangWaiters = [];
118
+ mintCounter = 0;
119
+ constructor(env) {
120
+ this.env = env;
121
+ this.config = hermeticCursorConfig(env);
122
+ this.adapter = createCursorAdapter();
123
+ this.sdk = new ScriptedCursorSdk({
124
+ agents: [],
125
+ catalog: SDK_CATALOG,
126
+ agentForWorkspaceRef: (ref) => this.agentsByWorkspaceRef.get(ref),
127
+ });
128
+ bindScriptedSdk(this.sdk);
129
+ }
130
+ // ── HarnessContractSubject ────────────────────────────────────────────────
131
+ arrange(turn, view) {
132
+ const agent = this.agents.get(view.sessionId) ?? this.mintAgent(view.sessionId);
133
+ mkdirSync(this.workspaceRootOf(view.sessionId), { recursive: true });
134
+ agent.arrangeNextTurn(this.translate(turn, view, agent));
135
+ }
136
+ inputOverrides(view) {
137
+ return {
138
+ // The adapter validates the name against its catalog and prices against
139
+ // the registry fixture; both know exactly this model.
140
+ model: { requested: FIXTURE.model, serviceTier: ServiceTier.STANDARD, thinkingMode: ThinkingMode.DISABLED },
141
+ workspaceDir: this.workspaceRootOf(view.sessionId),
142
+ message: KIT_USER_MESSAGE,
143
+ };
144
+ }
145
+ whenHanging() {
146
+ return new Promise((resolve) => this.hangWaiters.push(resolve));
147
+ }
148
+ executionCount(toolCallId) {
149
+ return this.proposals.get(toolCallId)?.allowed ?? 0;
150
+ }
151
+ // ── The subject file's observations ──────────────────────────────────────
152
+ agentFor(sessionId) {
153
+ return this.agents.get(sessionId);
154
+ }
155
+ forceReissue(toolCallId) {
156
+ this.forcedReissues.add(toolCallId);
157
+ }
158
+ // ── The engine ────────────────────────────────────────────────────────────
159
+ /** The directory the runtime's provisioner gives a session with no workspace entries (`session-root.ts`). */
160
+ workspaceRootOf(sessionId) {
161
+ return join(this.env.workspaceRootDir, "sessions", sessionId);
162
+ }
163
+ mintAgent(sessionId) {
164
+ const agent = new ScriptedCursorAgent({ agentId: `agent-kit-${++this.mintCounter}`, turns: [] });
165
+ this.agents.set(sessionId, agent);
166
+ // The adapter asks the SDK to create an agent for the session under this
167
+ // ref; the double answers with this agent.
168
+ const { workspaceRef } = resolvePlatformOptions(sessionId, this.config.workspaceRootDir);
169
+ this.agentsByWorkspaceRef.set(workspaceRef, agent);
170
+ return agent;
171
+ }
172
+ translate(turn, view, agent) {
173
+ const ev = sdkEvents(agent.agentId, agent.nextRunId);
174
+ const script = [];
175
+ if (agent.sends.length === 0)
176
+ script.push(step.event(ev.init()));
177
+ let lastSay = "";
178
+ let ended = false;
179
+ for (const s of turn) {
180
+ switch (s.kind) {
181
+ case "say":
182
+ script.push(step.event(ev.assistant(s.text)));
183
+ lastSay = s.text;
184
+ break;
185
+ case "propose":
186
+ script.push(...this.proposeSteps(s.toolCallId, s.action, view, ev));
187
+ break;
188
+ case "read":
189
+ script.push(...this.readSteps(s.toolCallId, s.path, view, ev));
190
+ break;
191
+ case "usage":
192
+ script.push(step.turnEnded({
193
+ inputTokens: s.delta.inputTokens,
194
+ outputTokens: s.delta.outputTokens,
195
+ cacheReadTokens: s.delta.cacheReadTokens,
196
+ cacheWriteTokens: s.delta.cacheWriteTokens,
197
+ }));
198
+ break;
199
+ case "hang":
200
+ script.push(step.effect("kit: hang until the adapter cancels the run", ({ run }) => this.park(run)));
201
+ ended = true;
202
+ break;
203
+ case "fail":
204
+ switch (s.surface) {
205
+ case "engine":
206
+ script.push(step.errored({ result: s.message }));
207
+ break;
208
+ case "internal":
209
+ script.push(step.effect("kit: the transport breaks mid-stream", () => {
210
+ throw new Error(s.message);
211
+ }));
212
+ break;
213
+ case "actionable":
214
+ throw new Error(`${this.name}: fail(..., "actionable") is not producible by this subject (only the unattributed hook block reaches that surface); test bug`);
215
+ default: {
216
+ const exhaustive = s.surface;
217
+ throw new Error(`${this.name}: unknown failure surface ${String(exhaustive)}`);
218
+ }
219
+ }
220
+ ended = true;
221
+ break;
222
+ case "cancelled":
223
+ script.push(step.effect("kit: the SDK cancels its own run", ({ run }) => run.cancel()));
224
+ ended = true;
225
+ break;
226
+ default: {
227
+ const exhaustive = s;
228
+ throw new Error(`${this.name}: unknown scenario step ${JSON.stringify(exhaustive)}`);
229
+ }
230
+ }
231
+ }
232
+ if (!ended)
233
+ script.push(step.finished({ result: lastSay, model: { id: FIXTURE.model, params: [] } }));
234
+ return script;
235
+ }
236
+ /**
237
+ * Whether the model emits the gated call this turn, and what the hook is
238
+ * expected to answer — one rule per case (see the header). `undefined`
239
+ * means "nothing is emitted".
240
+ */
241
+ emissionFor(toolCallId, view) {
242
+ if (this.forcedReissues.delete(toolCallId)) {
243
+ // The disobedient model: emitted regardless; the hook decides on its own
244
+ // (a grant exists only for an APPROVE), so what to expect is the grant.
245
+ return { expectAllow: view.approvalDecisions.get(toolCallId) === ApprovalAction.APPROVE };
246
+ }
247
+ if ((this.proposals.get(toolCallId)?.allowed ?? 0) > 0)
248
+ return undefined;
249
+ const decision = view.approvalDecisions.get(toolCallId) ?? ApprovalAction.UNSPECIFIED;
250
+ switch (decision) {
251
+ case ApprovalAction.UNSPECIFIED:
252
+ return { expectAllow: false };
253
+ case ApprovalAction.APPROVE:
254
+ case ApprovalAction.APPROVE_ALL:
255
+ return { expectAllow: true };
256
+ case ApprovalAction.SKIP:
257
+ case ApprovalAction.REJECT:
258
+ return undefined;
259
+ default: {
260
+ const exhaustive = decision;
261
+ throw new Error(`${this.name}: unknown approval action ${String(exhaustive)}`);
262
+ }
263
+ }
264
+ }
265
+ proposeSteps(toolCallId, action, view, ev) {
266
+ const emission = this.emissionFor(toolCallId, view);
267
+ if (!emission)
268
+ return [];
269
+ if (action.kind === "read" || action.kind === "mcp") {
270
+ throw new Error(`${this.name}: propose(${action.kind}) is not a gated built-in on this harness; test bug`);
271
+ }
272
+ const gated = { ...action, kind: action.kind };
273
+ const memory = this.proposals.get(toolCallId) ?? { emitted: 0, allowed: 0 };
274
+ this.proposals.set(toolCallId, memory);
275
+ // The first emission carries the kit's id (the WAITING row the kit looks
276
+ // up); a re-issue carries a fresh call id, as the resumed SDK agent does,
277
+ // and the adapter reconciles it onto the committed row by identity.
278
+ const callId = memory.emitted === 0 ? toolCallId : `${toolCallId}#${memory.emitted}`;
279
+ memory.emitted += 1;
280
+ const name = STREAM_NAME[gated.kind];
281
+ const args = streamArgsFor(gated);
282
+ const workspaceRoot = this.workspaceRootOf(view.sessionId);
283
+ return [
284
+ step.event(ev.toolCall(callId, name, "running", args)),
285
+ step.effect(`kit: the real hook judges ${name} ${gated.resource}`, () => {
286
+ const { permission } = runWorkspaceHook(workspaceRoot, hookInputFor(gated));
287
+ if (permission === "allow")
288
+ memory.allowed += 1;
289
+ if ((permission === "allow") !== emission.expectAllow) {
290
+ this.hookDisagreements.push(`${toolCallId}: expected ${emission.expectAllow ? "allow" : "deny"}, hook answered ${permission}`);
291
+ }
292
+ }),
293
+ emission.expectAllow
294
+ ? step.event(ev.toolCall(callId, name, "completed", args, ALLOWED_RESULT))
295
+ : step.event(ev.toolCall(callId, name, "error", args, HOOK_BLOCKED_RESULT)),
296
+ ];
297
+ }
298
+ /**
299
+ * An ungated read: the stream tool call, the REAL hook (which allows every
300
+ * read — the gate's baked-in category set has no arm for it; asserted
301
+ * through the disagreement record like every other hook answer), and the
302
+ * completion with contents. The SDK executes what the hook allows, so the
303
+ * read counts as run.
304
+ */
305
+ readSteps(toolCallId, path, view, ev) {
306
+ const memory = this.proposals.get(toolCallId) ?? { emitted: 0, allowed: 0 };
307
+ this.proposals.set(toolCallId, memory);
308
+ memory.emitted += 1;
309
+ const args = { path };
310
+ const workspaceRoot = this.workspaceRootOf(view.sessionId);
311
+ return [
312
+ step.event(ev.toolCall(toolCallId, "read", "running", args)),
313
+ step.effect(`kit: the real hook judges read ${path}`, () => {
314
+ const { permission } = runWorkspaceHook(workspaceRoot, hookInputFor({ kind: "read", resource: path }));
315
+ if (permission === "allow")
316
+ memory.allowed += 1;
317
+ else
318
+ this.hookDisagreements.push(`${toolCallId}: expected allow for a read, hook answered ${permission}`);
319
+ }),
320
+ step.event(ev.toolCall(toolCallId, "read", "completed", args, `contents of ${path}`)),
321
+ ];
322
+ }
323
+ /**
324
+ * Park until the adapter cancels the run — the real shape of "the engine
325
+ * makes no progress until told to stop": the adapter's stop-signal listener
326
+ * calls `run.cancel()`, the double's status flips, and its stream breaks at
327
+ * the next step.
328
+ */
329
+ park(run) {
330
+ for (const waiter of this.hangWaiters.splice(0))
331
+ waiter();
332
+ if (run.status === "cancelled")
333
+ return Promise.resolve();
334
+ return new Promise((resolve) => {
335
+ const unsubscribe = run.onDidChangeStatus((status) => {
336
+ if (status !== "cancelled")
337
+ return;
338
+ unsubscribe();
339
+ resolve();
340
+ });
341
+ });
342
+ }
343
+ }
344
+ //# sourceMappingURL=contract-subject.js.map