@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,368 @@
1
+ /**
2
+ * Scripted `@cursor/sdk` agent — the double the hermetic `ExecuteCursor` runs
3
+ * drive instead of a live Cursor agent.
4
+ *
5
+ * The Cursor harness delegates the agent loop to the SDK: `agent.send()`
6
+ * returns a `Run` whose `stream()` yields `SDKMessage`s, whose `onDelta`
7
+ * callback carries usage, and whose `wait()` resolves the `RunResult`. Between
8
+ * those events the REAL SDK also performs side effects the harness observes
9
+ * only indirectly — it executes the tool (writing a file into the workspace) and
10
+ * it spawns the workspace's `.cursor/hooks.json` hook before a gated tool (the
11
+ * runner's own bash script, which appends to the denial ledger). A double that
12
+ * replays events alone would leave those effects out and the harness's
13
+ * deny-and-retry and file-review paths untested.
14
+ *
15
+ * So a turn is a SCRIPT of ordered steps, one of four kinds:
16
+ *
17
+ * - `event(SDKMessage)` — yielded from `stream()`
18
+ * - `delta(update)` — fired through `onDelta` (usage, `turn-ended`)
19
+ * - `effect(fn)` — the side effect the SDK would have performed
20
+ * (run the real hook, write a workspace file,
21
+ * cancel the activity from the outside)
22
+ * - `result(RunResult)` — what `wait()` resolves; ends the run
23
+ *
24
+ * The same shape as the native harness's `__test-utils__/scripted-model.ts`
25
+ * (`ScriptStep`, driven in order), applied to the SDK's event surface.
26
+ *
27
+ * Typing: `SDKMessage`, `Run`, `RunResult` and `SDKAgent` are the SDK's own
28
+ * types (`@cursor/sdk` 1.0.13, concrete in `messages.d.ts` / `run.d.ts` /
29
+ * `agent.d.ts`), so a script that drifts from the real event shape fails
30
+ * `tsc`. The delta channel is the exception: `InteractionUpdate` re-exports
31
+ * from `@anysphere/cursor-sdk-shared`, a workspace package the published SDK
32
+ * does not ship, so under `skipLibCheck` it resolves to `any` — in production
33
+ * code too (`turn-stream.ts`, `delta-enricher.ts` read it untyped). The one
34
+ * delta this double emits, the `turn-ended` usage, is therefore typed HERE
35
+ * ({@link TurnEndedUsageDelta}) against what `turn-stream.ts` reads from it,
36
+ * and that gap is recorded in the entry's findings rather than papered over.
37
+ *
38
+ * Cancellation: `run.cancel()` is how the harness stops a turn (first denial,
39
+ * cost cap, stall). The double honours it the way the SDK does — the stream
40
+ * ends at the next step, `wait()` resolves `{ status: "cancelled" }` — because
41
+ * the deny-and-retry path depends on exactly that ordering.
42
+ *
43
+ * Every `send()` consumes the NEXT script in the agent's queue: turn 1, then
44
+ * the resumed turn 2 on the same parked agent. A `send()` with no script left
45
+ * is a test bug and throws. A golden scenario declares every script up front
46
+ * (`turns`); a driver that learns what the next turn plays only just before
47
+ * it runs — the harness contract kit's Cursor subject — arranges it with
48
+ * {@link ScriptedCursorAgent.arrangeNextTurn}, which REPLACES the slot the
49
+ * next `send()` will consume.
50
+ */
51
+
52
+ import type {
53
+ ModelSelection,
54
+ Run,
55
+ RunOperation,
56
+ RunResult,
57
+ RunStatus,
58
+ SDKAgent,
59
+ SDKMessage,
60
+ SDKUserMessage,
61
+ SendOptions,
62
+ } from "@cursor/sdk";
63
+ import type { ConversationTurn } from "@cursor/sdk";
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Script steps
67
+ // ---------------------------------------------------------------------------
68
+
69
+ /**
70
+ * The `turn-ended` delta as `turn-stream.ts` reads it (`update.type ===
71
+ * "turn-ended" && update.usage` -> `usageAccumulator.addTurn(update.usage)`).
72
+ * See the module header for why this is typed locally.
73
+ */
74
+ export interface TurnEndedUsageDelta {
75
+ readonly type: "turn-ended";
76
+ readonly usage: {
77
+ readonly inputTokens?: number;
78
+ readonly outputTokens?: number;
79
+ readonly cacheWriteTokens?: number;
80
+ readonly cacheReadTokens?: number;
81
+ };
82
+ }
83
+
84
+ /** What an `effect` step can reach: the run it is part of. */
85
+ export interface EffectContext {
86
+ readonly run: ScriptedRun;
87
+ readonly agent: ScriptedCursorAgent;
88
+ }
89
+
90
+ export type ScriptStep =
91
+ | { readonly kind: "event"; readonly event: SDKMessage }
92
+ | { readonly kind: "delta"; readonly update: TurnEndedUsageDelta }
93
+ | { readonly kind: "effect"; readonly label: string; readonly run: (ctx: EffectContext) => void | Promise<void> }
94
+ | { readonly kind: "result"; readonly result: Omit<RunResult, "id"> };
95
+
96
+ /** One `send()`'s worth of behaviour. */
97
+ export type TurnScript = readonly ScriptStep[];
98
+
99
+ /** Step builders — the vocabulary a scenario file reads as. */
100
+ export const step = {
101
+ event(event: SDKMessage): ScriptStep {
102
+ return { kind: "event", event };
103
+ },
104
+ turnEnded(usage: TurnEndedUsageDelta["usage"]): ScriptStep {
105
+ return { kind: "delta", update: { type: "turn-ended", usage } };
106
+ },
107
+ effect(label: string, run: (ctx: EffectContext) => void | Promise<void>): ScriptStep {
108
+ return { kind: "effect", label, run };
109
+ },
110
+ finished(result?: Partial<Omit<RunResult, "id" | "status">>): ScriptStep {
111
+ return { kind: "result", result: { status: "finished", ...result } };
112
+ },
113
+ errored(result?: Partial<Omit<RunResult, "id" | "status">>): ScriptStep {
114
+ return { kind: "result", result: { status: "error", ...result } };
115
+ },
116
+ } as const;
117
+
118
+ /**
119
+ * `SDKMessage` builders bound to one (agent_id, run_id) pair, so a scenario
120
+ * reads as the conversation it scripts. Shapes are the SDK's `messages.d.ts`;
121
+ * tool names are the STREAM taxonomy (`edit`, `shell`, `read` — lowercase; the
122
+ * hook sees `Write`, `Shell`, `Read`; see `approval-gate.test.ts`).
123
+ */
124
+ export function sdkEvents(agentId: string, runId: string) {
125
+ const base = { agent_id: agentId, run_id: runId } as const;
126
+ return {
127
+ init(): SDKMessage {
128
+ return { type: "system", subtype: "init", ...base };
129
+ },
130
+ assistant(text: string): SDKMessage {
131
+ return { type: "assistant", ...base, message: { role: "assistant", content: [{ type: "text", text }] } };
132
+ },
133
+ thinking(text: string): SDKMessage {
134
+ return { type: "thinking", ...base, text };
135
+ },
136
+ toolCall(
137
+ callId: string,
138
+ name: string,
139
+ status: "running" | "completed" | "error",
140
+ args?: unknown,
141
+ result?: unknown,
142
+ ): SDKMessage {
143
+ return { type: "tool_call", ...base, call_id: callId, name, status, args, result };
144
+ },
145
+ status(status: "CREATING" | "RUNNING" | "FINISHED" | "ERROR" | "CANCELLED" | "EXPIRED", message?: string): SDKMessage {
146
+ return { type: "status", ...base, status, message };
147
+ },
148
+ };
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // The run
153
+ // ---------------------------------------------------------------------------
154
+
155
+ /** Called once per step so the driver's clock can tick. */
156
+ export type StepObserver = (step: ScriptStep, index: number) => void;
157
+
158
+ export class ScriptedRun implements Run {
159
+ readonly id: string;
160
+ readonly agentId: string;
161
+ private _status: RunStatus = "running";
162
+ private cancelled = false;
163
+ private scriptedResult: Omit<RunResult, "id"> | undefined;
164
+ private streamed = false;
165
+ private readonly statusListeners = new Set<(status: RunStatus) => void>();
166
+ /** `run.cancel()` calls, for assertions. */
167
+ readonly cancelCalls: number[] = [];
168
+
169
+ constructor(
170
+ readonly agent: ScriptedCursorAgent,
171
+ id: string,
172
+ private readonly script: TurnScript,
173
+ private readonly onDelta: SendOptions["onDelta"],
174
+ private readonly observeStep: StepObserver | undefined,
175
+ /** What `conversation()` answers; the error classifier reads it on a failed run. */
176
+ private readonly conversationTurns: ConversationTurn[] = [],
177
+ ) {
178
+ this.id = id;
179
+ this.agentId = agent.agentId;
180
+ }
181
+
182
+ get status(): RunStatus {
183
+ return this._status;
184
+ }
185
+
186
+ get result(): string | undefined {
187
+ return this.scriptedResult?.result;
188
+ }
189
+
190
+ get model(): ModelSelection | undefined {
191
+ return this.scriptedResult?.model ?? this.agent.model;
192
+ }
193
+
194
+ supports(_operation: RunOperation): boolean {
195
+ return true;
196
+ }
197
+
198
+ unsupportedReason(_operation: RunOperation): string | undefined {
199
+ return undefined;
200
+ }
201
+
202
+ onDidChangeStatus(listener: (status: RunStatus) => void): () => void {
203
+ this.statusListeners.add(listener);
204
+ return () => this.statusListeners.delete(listener);
205
+ }
206
+
207
+ async conversation(): Promise<ConversationTurn[]> {
208
+ return this.conversationTurns;
209
+ }
210
+
211
+ async *stream(): AsyncGenerator<SDKMessage, void> {
212
+ if (this.streamed) {
213
+ throw new Error(`ScriptedRun ${this.id}: stream() consumed twice`);
214
+ }
215
+ this.streamed = true;
216
+ for (let i = 0; i < this.script.length; i++) {
217
+ // A cancel lands between steps, exactly where the SDK's own stream would
218
+ // stop delivering.
219
+ if (this.cancelled) break;
220
+ const s = this.script[i];
221
+ this.observeStep?.(s, i);
222
+ switch (s.kind) {
223
+ case "event":
224
+ yield s.event;
225
+ break;
226
+ case "delta":
227
+ await this.onDelta?.({ update: s.update as never });
228
+ break;
229
+ case "effect":
230
+ await s.run({ run: this, agent: this.agent });
231
+ break;
232
+ case "result":
233
+ this.scriptedResult = s.result;
234
+ break;
235
+ default: {
236
+ const exhaustive: never = s;
237
+ throw new Error(`ScriptedRun: unknown step ${String(exhaustive)}`);
238
+ }
239
+ }
240
+ }
241
+ if (!this.cancelled) this.setStatus(this.scriptedResult?.status ?? "finished");
242
+ }
243
+
244
+ async wait(): Promise<RunResult> {
245
+ if (this.cancelled) {
246
+ return { id: this.id, status: "cancelled" };
247
+ }
248
+ if (!this.scriptedResult) {
249
+ // A script that never declared its result is a test bug — surface it
250
+ // instead of inventing a "finished" the golden would then pin.
251
+ throw new Error(
252
+ `ScriptedRun ${this.id}: wait() called but the script has no result step ` +
253
+ `(add step.finished() or step.errored())`,
254
+ );
255
+ }
256
+ return { id: this.id, ...this.scriptedResult };
257
+ }
258
+
259
+ async cancel(): Promise<void> {
260
+ this.cancelCalls.push(Date.now());
261
+ this.cancelled = true;
262
+ this.setStatus("cancelled");
263
+ }
264
+
265
+ private setStatus(status: RunStatus): void {
266
+ this._status = status;
267
+ for (const l of this.statusListeners) l(status);
268
+ }
269
+ }
270
+
271
+ // ---------------------------------------------------------------------------
272
+ // The agent
273
+ // ---------------------------------------------------------------------------
274
+
275
+ export interface ScriptedAgentOptions {
276
+ readonly agentId: string;
277
+ /** One script per `send()`, consumed in order. */
278
+ readonly turns: readonly TurnScript[];
279
+ /** Run ids, one per turn, so goldens carry stable `run_id`s. */
280
+ readonly runIds?: readonly string[];
281
+ readonly observeStep?: StepObserver;
282
+ readonly conversationTurns?: ConversationTurn[];
283
+ }
284
+
285
+ /** What the harness passed to `send()`, kept for assertions on the prompt. */
286
+ export interface RecordedSend {
287
+ readonly message: string | SDKUserMessage;
288
+ readonly hasOnDelta: boolean;
289
+ }
290
+
291
+ /** The run id a send is given when the scenario named none: `run-<agentId>-<n>`, `n` counting sends from 1. */
292
+ export function defaultRunId(agentId: string, sendIndex: number): string {
293
+ return `run-${agentId}-${sendIndex + 1}`;
294
+ }
295
+
296
+ export class ScriptedCursorAgent implements SDKAgent {
297
+ readonly agentId: string;
298
+ model: ModelSelection | undefined = undefined;
299
+ readonly sends: RecordedSend[] = [];
300
+ readonly runs: ScriptedRun[] = [];
301
+ closeCalls = 0;
302
+ private nextTurn = 0;
303
+ /** The scenario's scripts, one slot per send; `arrangeNextTurn` writes the slot the next send consumes. */
304
+ private readonly turns: TurnScript[];
305
+
306
+ constructor(private readonly options: ScriptedAgentOptions) {
307
+ this.agentId = options.agentId;
308
+ this.turns = [...options.turns];
309
+ }
310
+
311
+ /** The run id the NEXT `send()` will be given — so a script can be built with matching event ids before the send happens. */
312
+ get nextRunId(): string {
313
+ return this.options.runIds?.[this.nextTurn] ?? defaultRunId(this.agentId, this.nextTurn);
314
+ }
315
+
316
+ /**
317
+ * Set (or replace) what the NEXT `send()` plays. Scripts already consumed
318
+ * are untouched; a slot declared up front for a later send stays where it
319
+ * is.
320
+ */
321
+ arrangeNextTurn(script: TurnScript): void {
322
+ this.turns[this.nextTurn] = script;
323
+ }
324
+
325
+ async send(message: string | SDKUserMessage, options?: SendOptions): Promise<Run> {
326
+ const script = this.turns[this.nextTurn];
327
+ if (!script) {
328
+ throw new Error(
329
+ `ScriptedCursorAgent ${this.agentId}: send() #${this.nextTurn + 1} has no script ` +
330
+ `(the scenario declared ${this.turns.length})`,
331
+ );
332
+ }
333
+ const runId = this.nextRunId;
334
+ this.nextTurn++;
335
+ this.sends.push({ message, hasOnDelta: !!options?.onDelta });
336
+ if (options?.model) this.model = options.model;
337
+ const run = new ScriptedRun(
338
+ this,
339
+ runId,
340
+ script,
341
+ options?.onDelta,
342
+ this.options.observeStep,
343
+ this.options.conversationTurns,
344
+ );
345
+ this.runs.push(run);
346
+ return run;
347
+ }
348
+
349
+ close(): void {
350
+ this.closeCalls++;
351
+ }
352
+
353
+ async reload(): Promise<void> {
354
+ // No persisted state to reload in the double.
355
+ }
356
+
357
+ async [Symbol.asyncDispose](): Promise<void> {
358
+ this.close();
359
+ }
360
+
361
+ async listArtifacts(): Promise<never[]> {
362
+ return [];
363
+ }
364
+
365
+ async downloadArtifact(_path: string): Promise<Buffer> {
366
+ throw new Error("ScriptedCursorAgent: artifacts are not part of the scripted surface");
367
+ }
368
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * The `@cursor/sdk` module a hermetic `ExecuteCursor` run imports — every
3
+ * runtime surface the activity path touches, backed by scripted agents.
4
+ *
5
+ * The activity reaches the SDK at three runtime sites and nowhere else:
6
+ *
7
+ * - `session-lifecycle.ts`: `Agent.create(options)`, `Agent.resume(id, options)`,
8
+ * `Agent.archive(id, ...)` — the agent handle.
9
+ * - `service-tier.ts`: `Cursor.models.list({ apiKey })` — the catalog the
10
+ * variant params (`fast`, `thinking`) are pinned from.
11
+ * - `index.ts` outer catch: `import("@cursor/sdk")` for `CursorSdkError`, the
12
+ * `instanceof` the error classifier keys on.
13
+ *
14
+ * Neither the SDK nor the client is injectable into the activity (the real
15
+ * seam arrives with the harness contract; parent Q1/Q2), so — exactly as the
16
+ * native activity tests already do for their boundary modules — the module is
17
+ * substituted with `vi.mock`. Three existing tests each mock ONE of these
18
+ * surfaces (`session-lifecycle.test.ts`, `service-tier.test.ts`,
19
+ * `sdk-warmup.test.ts`); a whole-activity run needs all three at once, which is
20
+ * what this module is.
21
+ *
22
+ * `vi.mock` is hoisted and must appear in the test file; the factory can
23
+ * `await import()` this module and return {@link scriptedCursorSdkModule}. The
24
+ * module's statics read the {@link ScriptedCursorSdk} bound for the current
25
+ * scenario ({@link bindScriptedSdk}), so one test file can run several
26
+ * scenarios with different agents against the same mocked module.
27
+ *
28
+ * Resolution rules a scenario declares, mirroring what the real SDK does:
29
+ * - `Agent.create` hands out the agent declared for the session it is asked
30
+ * for (`agentForSession`, when the scenario resolves agents per session —
31
+ * the harness contract kit runs several sessions concurrently on one
32
+ * adapter, and the order their creates arrive in is real I/O timing), else
33
+ * the NEXT unclaimed agent in `agents` (turn 1 of a fresh session; the fresh
34
+ * agent of a poisoned-handle recovery) — unless the scenario declared a
35
+ * `createFailure`, which the FIRST create throws instead (the SDK refusing
36
+ * to mint an agent: a 401 on the key, a validation error).
37
+ * - `Agent.resume(id)` hands back the agent with that id if the scenario
38
+ * declared it (in `agents` or `resumableAgents`, or handed out for a
39
+ * session), else throws — `resolveAgent` then falls back to `create`, which
40
+ * is the production shape of a lost handle.
41
+ * - `Cursor.models.list` answers the scenario's catalog.
42
+ */
43
+
44
+ import type { AgentOptions, ModelListItem, SDKAgent } from "@cursor/sdk";
45
+ import type { ScriptedCursorAgent } from "./scripted-agent.js";
46
+
47
+ export interface ScriptedSdkOptions {
48
+ /** Agents handed out by `Agent.create`, in order. Also resumable by id. */
49
+ readonly agents: readonly ScriptedCursorAgent[];
50
+ /**
51
+ * The agent `Agent.create` hands out for the session the create names,
52
+ * consulted before the ordered list. The SDK's create options carry the
53
+ * session as `platform.workspaceRef` (`session-lifecycle.ts`
54
+ * `resolvePlatformOptions`: `stigmer-session:<sessionId>`), so the resolver
55
+ * is keyed by that ref — a scenario computes the ref the same way the
56
+ * adapter does. `undefined` falls through to the list. Consulted live on
57
+ * every create, so a scenario may mint the session's agent only when it
58
+ * learns of the session.
59
+ */
60
+ readonly agentForWorkspaceRef?: (workspaceRef: string) => ScriptedCursorAgent | undefined;
61
+ /**
62
+ * Agents `Agent.resume` finds by id but `Agent.create` never hands out — a
63
+ * previous turn's agent the session knows only by `harness_state_id`.
64
+ */
65
+ readonly resumableAgents?: readonly ScriptedCursorAgent[];
66
+ /**
67
+ * Thrown by the FIRST `Agent.create` instead of handing out an agent; later
68
+ * creates hand out `agents` as usual. A {@link ScriptedCursorSdkError} here is
69
+ * the SDK's own refusal (the activity's outer catch keys on `instanceof`);
70
+ * a plain `Error` is anything else that can escape the SDK boundary.
71
+ */
72
+ readonly createFailure?: Error;
73
+ /** What `Cursor.models.list` answers. */
74
+ readonly catalog: readonly ModelListItem[];
75
+ }
76
+
77
+ /** One `Agent.create` / `Agent.resume` call as the activity made it. */
78
+ export interface RecordedResolution {
79
+ readonly kind: "create" | "resume";
80
+ readonly agentId: string | undefined;
81
+ readonly options: Partial<AgentOptions> | undefined;
82
+ }
83
+
84
+ /**
85
+ * The `platform.workspaceRef` a create names, read defensively: the platform
86
+ * option's type re-exports from the unshipped `@anysphere/cursor-sdk-local-runtime`
87
+ * and resolves to `any` under `skipLibCheck` (the same gap `scripted-agent.ts`
88
+ * records for the delta channel), so the shape is checked here, not assumed.
89
+ */
90
+ function workspaceRefOf(options: AgentOptions): string | undefined {
91
+ const platform: unknown = (options as { platform?: unknown }).platform;
92
+ if (typeof platform !== "object" || platform === null) return undefined;
93
+ const ref = (platform as { workspaceRef?: unknown }).workspaceRef;
94
+ return typeof ref === "string" ? ref : undefined;
95
+ }
96
+
97
+ export class ScriptedCursorSdk {
98
+ readonly resolutions: RecordedResolution[] = [];
99
+ readonly archived: string[] = [];
100
+ private nextCreate = 0;
101
+ private createFailurePending: boolean;
102
+ private readonly byId = new Map<string, ScriptedCursorAgent>();
103
+
104
+ constructor(private readonly options: ScriptedSdkOptions) {
105
+ for (const a of [...options.agents, ...(options.resumableAgents ?? [])]) {
106
+ this.byId.set(a.agentId, a);
107
+ }
108
+ this.createFailurePending = options.createFailure !== undefined;
109
+ }
110
+
111
+ create(options: AgentOptions): SDKAgent {
112
+ if (this.createFailurePending && this.options.createFailure) {
113
+ this.createFailurePending = false;
114
+ this.resolutions.push({ kind: "create", agentId: undefined, options });
115
+ throw this.options.createFailure;
116
+ }
117
+ const workspaceRef = workspaceRefOf(options);
118
+ const forSession = workspaceRef !== undefined ? this.options.agentForWorkspaceRef?.(workspaceRef) : undefined;
119
+ if (forSession) {
120
+ this.byId.set(forSession.agentId, forSession);
121
+ forSession.model = options.model;
122
+ this.resolutions.push({ kind: "create", agentId: forSession.agentId, options });
123
+ return forSession;
124
+ }
125
+ const agent = this.options.agents[this.nextCreate];
126
+ if (!agent) {
127
+ throw new Error(
128
+ `ScriptedCursorSdk: Agent.create() #${this.nextCreate + 1} has no agent ` +
129
+ `(the scenario declared ${this.options.agents.length})`,
130
+ );
131
+ }
132
+ this.nextCreate++;
133
+ agent.model = options.model;
134
+ this.resolutions.push({ kind: "create", agentId: agent.agentId, options });
135
+ return agent;
136
+ }
137
+
138
+ resume(agentId: string, options: Partial<AgentOptions> | undefined): SDKAgent {
139
+ this.resolutions.push({ kind: "resume", agentId, options });
140
+ const agent = this.byId.get(agentId);
141
+ if (!agent) {
142
+ throw new ScriptedCursorSdkError(`agent ${agentId} not found`, { code: "agent_not_found" });
143
+ }
144
+ if (options?.model) agent.model = options.model;
145
+ return agent;
146
+ }
147
+
148
+ archive(agentId: string): void {
149
+ this.archived.push(agentId);
150
+ }
151
+
152
+ listModels(): readonly ModelListItem[] {
153
+ return this.options.catalog;
154
+ }
155
+ }
156
+
157
+ /** The construction options of the SDK's `CursorSdkError` (`errors.d.ts`), minus `cause`. */
158
+ export interface ScriptedCursorSdkErrorOptions {
159
+ readonly isRetryable?: boolean;
160
+ readonly code?: string;
161
+ readonly status?: number;
162
+ readonly endpoint?: string;
163
+ readonly requestId?: string;
164
+ readonly operation?: string;
165
+ }
166
+
167
+ /**
168
+ * The `CursorSdkError` the mocked module exports. A real `Error` subclass with
169
+ * the SDK class's public surface (`@cursor/sdk` `errors.d.ts`: `isRetryable`,
170
+ * `code`, `status`, `endpoint`, `requestId`, `operation`, `toJSON()`), under
171
+ * the SDK's class name so `err.constructor.name` and `instanceof` (against
172
+ * THIS module's export, which is what the activity's dynamic import resolves
173
+ * to) both hold. The activity's outer catch reads `code`, `status` and
174
+ * `message` for the classifier and logs `toJSON()`; the double must carry the
175
+ * whole surface or the catch arm itself throws instead of mapping the error.
176
+ */
177
+ export class ScriptedCursorSdkError extends Error {
178
+ readonly isRetryable: boolean;
179
+ readonly code: string | undefined;
180
+ readonly status: number | undefined;
181
+ readonly endpoint: string | undefined;
182
+ readonly requestId: string | undefined;
183
+ readonly operation: string | undefined;
184
+ constructor(message: string, opts: ScriptedCursorSdkErrorOptions = {}) {
185
+ super(message);
186
+ this.name = "CursorSdkError";
187
+ this.isRetryable = opts.isRetryable ?? false;
188
+ this.code = opts.code;
189
+ this.status = opts.status;
190
+ this.endpoint = opts.endpoint;
191
+ this.requestId = opts.requestId;
192
+ this.operation = opts.operation;
193
+ }
194
+
195
+ toJSON(): Record<string, unknown> {
196
+ return {
197
+ name: this.name,
198
+ message: this.message,
199
+ isRetryable: this.isRetryable,
200
+ code: this.code,
201
+ status: this.status,
202
+ endpoint: this.endpoint,
203
+ requestId: this.requestId,
204
+ operation: this.operation,
205
+ };
206
+ }
207
+ }
208
+
209
+ let bound: ScriptedCursorSdk | undefined;
210
+
211
+ /** Bind the SDK the mocked module serves for the current scenario. */
212
+ export function bindScriptedSdk(sdk: ScriptedCursorSdk): void {
213
+ bound = sdk;
214
+ }
215
+
216
+ function current(): ScriptedCursorSdk {
217
+ if (!bound) {
218
+ throw new Error(
219
+ "scripted-sdk: no ScriptedCursorSdk bound — call bindScriptedSdk(sdk) before running the activity",
220
+ );
221
+ }
222
+ return bound;
223
+ }
224
+
225
+ /**
226
+ * The factory a test passes to `vi.mock("@cursor/sdk", ...)`:
227
+ *
228
+ * ```ts
229
+ * vi.mock("@cursor/sdk", async () =>
230
+ * (await import("../../__test-utils__/scripted-sdk.js")).scriptedCursorSdkModule(),
231
+ * );
232
+ * ```
233
+ *
234
+ * Only the runtime surface the activity path uses is provided. Any other
235
+ * import from the mocked module is `undefined` and fails loudly at the call
236
+ * site — deliberately, so a new SDK dependency in production code is noticed
237
+ * here rather than silently stubbed.
238
+ */
239
+ export function scriptedCursorSdkModule(): Record<string, unknown> {
240
+ return {
241
+ Agent: {
242
+ create: async (options: AgentOptions): Promise<SDKAgent> => current().create(options),
243
+ resume: async (agentId: string, options?: Partial<AgentOptions>): Promise<SDKAgent> =>
244
+ current().resume(agentId, options),
245
+ archive: async (agentId: string): Promise<void> => current().archive(agentId),
246
+ },
247
+ Cursor: {
248
+ models: {
249
+ list: async (): Promise<readonly ModelListItem[]> => current().listModels(),
250
+ },
251
+ },
252
+ CursorSdkError: ScriptedCursorSdkError,
253
+ };
254
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Pins that the Cursor adapter is an SDK slice and nothing more: no
3
+ * production module under `activities/execute-cursor/` imports
4
+ * `@temporalio/*`.
5
+ *
6
+ * Why it matters: every Temporal fact a turn needs — the heartbeat, the
7
+ * cancellation, the throw the workflow sees — is the turn runtime's
8
+ * (`harness/run-turn.ts`) and reaches the adapter through the sink. The
9
+ * contract kit runs an adapter OUTSIDE any activity context
10
+ * (`__test-utils__/harness-contract/`), so a `Context.current()` anywhere
11
+ * in the adapter would throw there; and an adapter that threw a
12
+ * `CancelledFailure` would be deciding pause-vs-shutdown, which is the
13
+ * runtime's table. A static fence names the file in seconds where the kit
14
+ * would name it after a stack trace.
15
+ *
16
+ * Read off the syntax tree with the same primitive the import-direction
17
+ * fence uses (`__test-utils__/module-specifiers.ts`), so a package name in a
18
+ * comment cannot trip it and no import form can slip past it.
19
+ */
20
+
21
+ import { describe, it, expect } from "vitest";
22
+ import { join, relative } from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+
25
+ import { packageImports, readSource, typeScriptFilesUnder } from "../../../__test-utils__/module-specifiers.js";
26
+
27
+ const SRC_ROOT = fileURLToPath(new URL("../../../", import.meta.url));
28
+ const ADAPTER_ROOT = join(SRC_ROOT, "activities", "execute-cursor");
29
+
30
+ describe("the Cursor adapter never imports @temporalio", () => {
31
+ const files = typeScriptFilesUnder(ADAPTER_ROOT, new Set(["__tests__", "__test-utils__"]));
32
+ const offences = files.flatMap((file) =>
33
+ packageImports(file, readSource(file), "@temporalio").map((specifier) => ` ${relative(SRC_ROOT, file)} imports "${specifier}"`),
34
+ );
35
+
36
+ it("inspected the adapter's own files (the walk is rooted where it claims)", () => {
37
+ expect(files.map((f) => relative(ADAPTER_ROOT, f))).toEqual(
38
+ expect.arrayContaining(["adapter.ts", "turn.ts", "turn-setup.ts", "turn-stream.ts", "turn-settle.ts"]),
39
+ );
40
+ });
41
+
42
+ it("finds no @temporalio import in any production module", () => {
43
+ expect(offences, `the Cursor adapter must not import @temporalio/*:\n${offences.join("\n")}`).toEqual([]);
44
+ });
45
+ });