@stigmer/runner 3.14.1 → 3.15.1

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 (320) 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 +28 -5
  9. package/dist/__test-utils__/hermetic-activity.js +23 -7
  10. package/dist/__test-utils__/hermetic-activity.js.map +1 -1
  11. package/dist/__test-utils__/mock-workspace.d.ts +15 -0
  12. package/dist/__test-utils__/mock-workspace.js +21 -0
  13. package/dist/__test-utils__/mock-workspace.js.map +1 -0
  14. package/dist/__test-utils__/model-registry-fixture.d.ts +56 -0
  15. package/dist/__test-utils__/model-registry-fixture.js +66 -0
  16. package/dist/__test-utils__/model-registry-fixture.js.map +1 -0
  17. package/dist/__test-utils__/turn-input-fixture.d.ts +60 -0
  18. package/dist/__test-utils__/turn-input-fixture.js +118 -0
  19. package/dist/__test-utils__/turn-input-fixture.js.map +1 -0
  20. package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +107 -0
  21. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +344 -0
  22. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -0
  23. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +20 -0
  24. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +58 -0
  25. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
  26. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +2 -32
  27. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
  28. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +38 -62
  29. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +47 -121
  30. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
  31. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +17 -1
  32. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +27 -4
  33. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -1
  34. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +52 -11
  35. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +66 -7
  36. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -1
  37. package/dist/activities/execute-cursor/adapter.d.ts +55 -0
  38. package/dist/activities/execute-cursor/adapter.js +119 -0
  39. package/dist/activities/execute-cursor/adapter.js.map +1 -0
  40. package/dist/activities/execute-cursor/approval-state.d.ts +2 -2
  41. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  42. package/dist/activities/execute-cursor/capture-flow.d.ts +15 -30
  43. package/dist/activities/execute-cursor/capture-flow.js +17 -26
  44. package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
  45. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +23 -0
  46. package/dist/activities/execute-cursor/cursor-capabilities.js +33 -0
  47. package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -0
  48. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +2 -2
  49. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -1
  50. package/dist/activities/execute-cursor/fetch-interceptor.d.ts +15 -25
  51. package/dist/activities/execute-cursor/fetch-interceptor.js +39 -33
  52. package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
  53. package/dist/activities/execute-cursor/http2-interceptor.d.ts +21 -17
  54. package/dist/activities/execute-cursor/http2-interceptor.js +38 -31
  55. package/dist/activities/execute-cursor/http2-interceptor.js.map +1 -1
  56. package/dist/activities/execute-cursor/message-translator.d.ts +28 -19
  57. package/dist/activities/execute-cursor/message-translator.js +76 -27
  58. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  59. package/dist/activities/execute-cursor/prompt-builder.d.ts +180 -11
  60. package/dist/activities/execute-cursor/prompt-builder.js +171 -0
  61. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  62. package/dist/activities/execute-cursor/rejection-capture.d.ts +3 -6
  63. package/dist/activities/execute-cursor/rejection-capture.js +5 -7
  64. package/dist/activities/execute-cursor/rejection-capture.js.map +1 -1
  65. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -4
  66. package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
  67. package/dist/activities/execute-cursor/turn-boundary.d.ts +1 -1
  68. package/dist/activities/execute-cursor/turn-settle.d.ts +49 -0
  69. package/dist/activities/execute-cursor/turn-settle.js +507 -0
  70. package/dist/activities/execute-cursor/turn-settle.js.map +1 -0
  71. package/dist/activities/execute-cursor/turn-setup.d.ts +173 -0
  72. package/dist/activities/execute-cursor/turn-setup.js +496 -0
  73. package/dist/activities/execute-cursor/turn-setup.js.map +1 -0
  74. package/dist/activities/execute-cursor/turn-stream.d.ts +41 -71
  75. package/dist/activities/execute-cursor/turn-stream.js +70 -135
  76. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  77. package/dist/activities/execute-cursor/turn.d.ts +43 -0
  78. package/dist/activities/execute-cursor/turn.js +222 -0
  79. package/dist/activities/execute-cursor/turn.js.map +1 -0
  80. package/dist/activities/execute-cursor/usage-pricing.d.ts +38 -0
  81. package/dist/activities/execute-cursor/usage-pricing.js +50 -0
  82. package/dist/activities/execute-cursor/usage-pricing.js.map +1 -0
  83. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +1 -1
  84. package/dist/activities/execute-deep-agent/approval-file-change.js +1 -1
  85. package/dist/activities/execute-deep-agent/environment.js +1 -1
  86. package/dist/activities/execute-deep-agent/environment.js.map +1 -1
  87. package/dist/activities/execute-deep-agent/setup.js +3 -2
  88. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  89. package/dist/activities/hydrate-workflow-execution.js +1 -1
  90. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  91. package/dist/config.d.ts +15 -0
  92. package/dist/config.js.map +1 -1
  93. package/dist/harness/capabilities.d.ts +16 -0
  94. package/dist/harness/capabilities.js +1 -0
  95. package/dist/harness/capabilities.js.map +1 -1
  96. package/dist/harness/persist-chokepoint.d.ts +74 -0
  97. package/dist/harness/persist-chokepoint.js +88 -0
  98. package/dist/harness/persist-chokepoint.js.map +1 -0
  99. package/dist/harness/registry.d.ts +44 -5
  100. package/dist/harness/registry.js +61 -5
  101. package/dist/harness/registry.js.map +1 -1
  102. package/dist/harness/run-turn.d.ts +53 -0
  103. package/dist/harness/run-turn.js +528 -0
  104. package/dist/harness/run-turn.js.map +1 -0
  105. package/dist/harness/stop-controller.d.ts +50 -0
  106. package/dist/harness/stop-controller.js +62 -0
  107. package/dist/harness/stop-controller.js.map +1 -0
  108. package/dist/harness/terminal-table.d.ts +141 -0
  109. package/dist/harness/terminal-table.js +239 -0
  110. package/dist/harness/terminal-table.js.map +1 -0
  111. package/dist/harness/turn-context.d.ts +523 -0
  112. package/dist/harness/turn-context.js +739 -0
  113. package/dist/harness/turn-context.js.map +1 -0
  114. package/dist/harness/types.d.ts +232 -52
  115. package/dist/harness/types.js +17 -8
  116. package/dist/harness/types.js.map +1 -1
  117. package/dist/harness/usage-accumulator.d.ts +68 -0
  118. package/dist/harness/usage-accumulator.js +101 -0
  119. package/dist/harness/usage-accumulator.js.map +1 -0
  120. package/dist/harness-adapters.d.ts +27 -0
  121. package/dist/harness-adapters.js +28 -0
  122. package/dist/harness-adapters.js.map +1 -0
  123. package/dist/main.js.map +1 -1
  124. package/dist/runner-manager.js +64 -48
  125. package/dist/runner-manager.js.map +1 -1
  126. package/dist/runner.d.ts +5 -4
  127. package/dist/runner.js +65 -64
  128. package/dist/runner.js.map +1 -1
  129. package/dist/shared/approval-policy.d.ts +1 -1
  130. package/dist/shared/approval-policy.js.map +1 -1
  131. package/dist/{activities/execute-cursor → shared}/attachment-resolver.d.ts +2 -2
  132. package/dist/{activities/execute-cursor → shared}/attachment-resolver.js +5 -5
  133. package/dist/shared/attachment-resolver.js.map +1 -0
  134. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.d.ts +7 -5
  135. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.js +3 -63
  136. package/dist/shared/blueprint-resolver.js.map +1 -0
  137. package/dist/shared/channel-attachment.d.ts +1 -1
  138. package/dist/shared/channel-attachment.js.map +1 -1
  139. package/dist/{activities/execute-cursor → shared}/cost-guard.d.ts +5 -1
  140. package/dist/{activities/execute-cursor → shared}/cost-guard.js +5 -1
  141. package/dist/shared/cost-guard.js.map +1 -0
  142. package/dist/{activities/execute-cursor → shared}/env-resolver.d.ts +1 -1
  143. package/dist/shared/env-resolver.js.map +1 -0
  144. package/dist/{activities/execute-cursor → shared}/exact-apply.d.ts +1 -1
  145. package/dist/{activities/execute-cursor → shared}/exact-apply.js +6 -6
  146. package/dist/shared/exact-apply.js.map +1 -0
  147. package/dist/shared/execution-context.d.ts +35 -0
  148. package/dist/shared/execution-context.js +38 -0
  149. package/dist/shared/execution-context.js.map +1 -0
  150. package/dist/{activities/execute-cursor → shared}/extract-structured-output.d.ts +5 -2
  151. package/dist/{activities/execute-cursor → shared}/extract-structured-output.js +9 -6
  152. package/dist/shared/extract-structured-output.js.map +1 -0
  153. package/dist/shared/filereview/capture.d.ts +1 -1
  154. package/dist/shared/filereview/capture.js +1 -1
  155. package/dist/shared/heartbeat.d.ts +13 -11
  156. package/dist/shared/heartbeat.js +14 -29
  157. package/dist/shared/heartbeat.js.map +1 -1
  158. package/dist/{activities/execute-cursor → shared}/persist-decision.d.ts +5 -2
  159. package/dist/{activities/execute-cursor → shared}/persist-decision.js +4 -1
  160. package/dist/shared/persist-decision.js.map +1 -0
  161. package/dist/{activities/execute-cursor → shared}/skill-resolver.d.ts +16 -3
  162. package/dist/{activities/execute-cursor → shared}/skill-resolver.js +12 -4
  163. package/dist/shared/skill-resolver.js.map +1 -0
  164. package/dist/shared/subagent-rows.d.ts +21 -0
  165. package/dist/shared/subagent-rows.js +34 -0
  166. package/dist/shared/subagent-rows.js.map +1 -0
  167. package/dist/shared/worker-shutdown.d.ts +43 -16
  168. package/dist/shared/worker-shutdown.js +28 -13
  169. package/dist/shared/worker-shutdown.js.map +1 -1
  170. package/dist/shared/workspace/platform-dir.d.ts +1 -1
  171. package/dist/shared/workspace/platform-dir.js +1 -1
  172. package/dist/shared/workspace/session-provision.d.ts +54 -0
  173. package/dist/{activities/execute-cursor/workspace-provision.js → shared/workspace/session-provision.js} +15 -12
  174. package/dist/shared/workspace/session-provision.js.map +1 -0
  175. package/package.json +4 -4
  176. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +110 -14
  177. package/src/__test-utils__/execution-record-fixture.ts +104 -0
  178. package/src/__test-utils__/harness-boot-order-child.ts +58 -0
  179. package/src/__test-utils__/harness-contract/contract.ts +128 -83
  180. package/src/__test-utils__/harness-contract/recording-sink.ts +15 -4
  181. package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
  182. package/src/__test-utils__/harness-contract/scripted-adapter.ts +92 -32
  183. package/src/__test-utils__/harness-contract/types.ts +85 -9
  184. package/src/__test-utils__/hermetic-activity.ts +33 -8
  185. package/src/__test-utils__/model-registry-fixture.ts +72 -0
  186. package/src/__test-utils__/module-specifiers.ts +82 -0
  187. package/src/__test-utils__/turn-input-fixture.ts +155 -0
  188. package/src/__tests__/harness-boot-order.test.ts +77 -0
  189. package/src/__tests__/harness-contract.test.ts +7 -5
  190. package/src/__tests__/runner-manager.test.ts +18 -0
  191. package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +102 -0
  192. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +396 -0
  193. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +66 -0
  194. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +2 -43
  195. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +65 -145
  196. package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +30 -4
  197. package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +96 -8
  198. package/src/activities/execute-cursor/__tests__/adapter-is-temporal-free.test.ts +45 -0
  199. package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +90 -0
  200. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -8
  201. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +28 -1
  202. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +1 -1
  203. package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +64 -6
  204. package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +1 -1
  205. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -1
  206. package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
  207. package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +1 -6
  208. package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -0
  209. package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-transport-timeout.status.json +48 -0
  210. package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
  211. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-cancelled.status.json +36 -0
  212. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-error-non-retryable.status.json +37 -0
  213. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +38 -0
  214. package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +59 -0
  215. package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +79 -0
  216. package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +2 -7
  217. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +170 -0
  218. package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +22 -18
  219. package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +1 -1
  220. package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +1 -1
  221. package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +286 -0
  222. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +332 -0
  223. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +193 -0
  224. package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +1 -1
  225. package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +185 -0
  226. package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +151 -0
  227. package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +65 -35
  228. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -40
  229. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +171 -0
  230. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -243
  231. package/src/activities/execute-cursor/__tests__/usage-pricing.test.ts +84 -0
  232. package/src/activities/execute-cursor/adapter.ts +138 -0
  233. package/src/activities/execute-cursor/approval-state.ts +2 -2
  234. package/src/activities/execute-cursor/capture-flow.ts +18 -41
  235. package/src/activities/execute-cursor/cursor-capabilities.ts +35 -0
  236. package/src/activities/execute-cursor/cursor-mcp-config.ts +2 -2
  237. package/src/activities/execute-cursor/fetch-interceptor.ts +48 -45
  238. package/src/activities/execute-cursor/http2-interceptor.ts +47 -34
  239. package/src/activities/execute-cursor/message-translator.ts +90 -45
  240. package/src/activities/execute-cursor/prompt-builder.ts +327 -12
  241. package/src/activities/execute-cursor/rejection-capture.ts +5 -12
  242. package/src/activities/execute-cursor/session-lifecycle.ts +1 -5
  243. package/src/activities/execute-cursor/turn-boundary.ts +1 -1
  244. package/src/activities/execute-cursor/turn-settle.ts +601 -0
  245. package/src/activities/execute-cursor/turn-setup.ts +645 -0
  246. package/src/activities/execute-cursor/turn-stream.ts +84 -210
  247. package/src/activities/execute-cursor/turn.ts +240 -0
  248. package/src/activities/execute-cursor/usage-pricing.ts +67 -0
  249. package/src/activities/execute-deep-agent/approval-file-change.ts +1 -1
  250. package/src/activities/execute-deep-agent/environment.ts +1 -1
  251. package/src/activities/execute-deep-agent/setup.ts +3 -2
  252. package/src/activities/hydrate-workflow-execution.ts +1 -1
  253. package/src/config.ts +13 -0
  254. package/src/harness/__tests__/import-direction.test.ts +168 -0
  255. package/src/harness/__tests__/persist-chokepoint.test.ts +146 -0
  256. package/src/harness/__tests__/registry.test.ts +1 -0
  257. package/src/harness/__tests__/run-turn.test.ts +165 -0
  258. package/src/harness/__tests__/stop-controller.test.ts +49 -0
  259. package/src/harness/__tests__/turn-context.test.ts +178 -0
  260. package/src/harness/__tests__/usage-accumulator.test.ts +83 -0
  261. package/src/harness/capabilities.ts +17 -0
  262. package/src/harness/persist-chokepoint.ts +112 -0
  263. package/src/harness/registry.ts +79 -5
  264. package/src/harness/run-turn.ts +618 -0
  265. package/src/harness/stop-controller.ts +83 -0
  266. package/src/harness/terminal-table.ts +276 -0
  267. package/src/harness/turn-context.ts +1028 -0
  268. package/src/harness/types.ts +248 -53
  269. package/src/harness/usage-accumulator.ts +122 -0
  270. package/src/harness-adapters.ts +30 -0
  271. package/src/main.ts +1 -1
  272. package/src/runner-manager.ts +68 -61
  273. package/src/runner.ts +67 -78
  274. package/src/shared/__tests__/attachment-naming.test.ts +1 -1
  275. package/src/{activities/execute-cursor → shared}/__tests__/attachment-resolver.test.ts +6 -4
  276. package/src/{activities/execute-cursor → shared}/__tests__/cost-guard.test.ts +2 -1
  277. package/src/{activities/execute-cursor → shared}/__tests__/exact-apply.test.ts +3 -3
  278. package/src/{activities/execute-cursor → shared}/__tests__/extract-structured-output.test.ts +8 -8
  279. package/src/{activities/execute-cursor → shared}/__tests__/persist-decision.test.ts +4 -2
  280. package/src/{activities/execute-cursor → shared}/__tests__/skill-resolver.test.ts +1 -1
  281. package/src/shared/__tests__/subagent-rows.test.ts +34 -0
  282. package/src/shared/__tests__/worker-shutdown.test.ts +44 -39
  283. package/src/shared/approval-policy.ts +1 -1
  284. package/src/{activities/execute-cursor → shared}/attachment-resolver.ts +6 -6
  285. package/src/{activities/execute-cursor → shared}/blueprint-resolver.ts +7 -78
  286. package/src/shared/channel-attachment.ts +1 -1
  287. package/src/{activities/execute-cursor → shared}/cost-guard.ts +5 -1
  288. package/src/{activities/execute-cursor → shared}/env-resolver.ts +1 -1
  289. package/src/{activities/execute-cursor → shared}/exact-apply.ts +7 -7
  290. package/src/shared/execution-context.ts +45 -0
  291. package/src/{activities/execute-cursor → shared}/extract-structured-output.ts +10 -7
  292. package/src/shared/filereview/capture.ts +1 -1
  293. package/src/shared/heartbeat.ts +13 -33
  294. package/src/{activities/execute-cursor → shared}/persist-decision.ts +5 -2
  295. package/src/{activities/execute-cursor → shared}/skill-resolver.ts +20 -6
  296. package/src/shared/subagent-rows.ts +39 -0
  297. package/src/shared/worker-shutdown.ts +55 -25
  298. package/src/{activities/execute-cursor/__tests__/workspace-provision.test.ts → shared/workspace/__tests__/session-provision.test.ts} +25 -28
  299. package/src/shared/workspace/platform-dir.ts +1 -1
  300. package/src/{activities/execute-cursor/workspace-provision.ts → shared/workspace/session-provision.ts} +29 -16
  301. package/dist/activities/execute-cursor/attachment-resolver.js.map +0 -1
  302. package/dist/activities/execute-cursor/blueprint-resolver.js.map +0 -1
  303. package/dist/activities/execute-cursor/cost-guard.js.map +0 -1
  304. package/dist/activities/execute-cursor/env-resolver.js.map +0 -1
  305. package/dist/activities/execute-cursor/exact-apply.js.map +0 -1
  306. package/dist/activities/execute-cursor/extract-structured-output.js.map +0 -1
  307. package/dist/activities/execute-cursor/index.d.ts +0 -209
  308. package/dist/activities/execute-cursor/index.js +0 -2345
  309. package/dist/activities/execute-cursor/index.js.map +0 -1
  310. package/dist/activities/execute-cursor/persist-decision.js.map +0 -1
  311. package/dist/activities/execute-cursor/skill-resolver.js.map +0 -1
  312. package/dist/activities/execute-cursor/usage-accumulator.d.ts +0 -89
  313. package/dist/activities/execute-cursor/usage-accumulator.js +0 -121
  314. package/dist/activities/execute-cursor/usage-accumulator.js.map +0 -1
  315. package/dist/activities/execute-cursor/workspace-provision.d.ts +0 -42
  316. package/dist/activities/execute-cursor/workspace-provision.js.map +0 -1
  317. package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +0 -164
  318. package/src/activities/execute-cursor/index.ts +0 -2876
  319. package/src/activities/execute-cursor/usage-accumulator.ts +0 -169
  320. /package/dist/{activities/execute-cursor → shared}/env-resolver.js +0 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The Cursor harness as a `HarnessAdapter` — the worker- and session-lifetime
3
+ * half of its SDK slice, behind the turn runtime's contract
4
+ * (`harness/types.ts`). One engine turn is `turn.ts`'s `runCursorTurn`; this
5
+ * module boots, releases, and delegates.
6
+ *
7
+ * What this adapter owns: the proxy interceptors and the parked-agent cache
8
+ * (worker lifetime), the parked agent per session (session lifetime), and one
9
+ * engine turn (`runTurn`, through `turn.ts`). Everything else about a turn is
10
+ * the runtime's (`harness/run-turn.ts`).
11
+ *
12
+ * THIS MODULE'S STATIC GRAPH IS SDK-FREE, ON PURPOSE. The composition roots
13
+ * import it (through `src/harness-adapters.ts`) BEFORE they boot it, and
14
+ * `boot` is where the fetch and HTTP/2 interceptors are installed. The HTTP/2
15
+ * one patches `node:http2`, whose ESM facade is snapshotted the first time
16
+ * `@connectrpc/connect-node` is imported — and `@cursor/sdk` imports
17
+ * connect-node. So the SDK-touching half of this harness (`turn.ts` and,
18
+ * through it, `turn-setup.ts` and its siblings) is loaded with a dynamic
19
+ * import INSIDE `boot`, after the installs and after
20
+ * `assertHttp2ConnectPatched` has proven the facade sees the patch. This is
21
+ * the contract's own rule ("vendor SDKs are imported lazily inside" boot,
22
+ * `types.ts`), and the runner's dynamic-import-for-order convention
23
+ * (`bootstrap.ts`). `src/__tests__/harness-boot-order.test.ts` boots a fresh
24
+ * process through this path and fails if the graph regresses; the modules
25
+ * this file imports statically must each stay connect-free.
26
+ *
27
+ * The interceptors read the proxy credential from `Config.proxyTokenRef` per
28
+ * request; which credential that is (the static root's control-plane token,
29
+ * the manager's minted runner token) is the root's decision, not this
30
+ * module's (Q-S2-7).
31
+ *
32
+ * What this adapter never does: throw out of `runTurn` (an SDK failure is
33
+ * `failed` with the classifier's sentence; an unexpected exception is
34
+ * `failed` on the `internal` surface; a `CancelledFailure` never exists
35
+ * here), branch on why `stopSignal` aborted, write a phase or a terminal
36
+ * copy, or import `@temporalio/*` (`__tests__/adapter-is-temporal-free.test.ts`
37
+ * pins it, which is what lets the contract kit run this adapter outside any
38
+ * activity context).
39
+ *
40
+ * The file-review boundary (`turn-boundary.ts`) runs whole inside `turn.ts`
41
+ * for S2 (Q-S2-1): five of its six steps read the denial ledger, this
42
+ * harness's deny-and-retry primitive. S3 lifts the capture into the runtime
43
+ * with both harnesses' captures in view; `types.ts`'s "the file-review
44
+ * boundary is the runtime's" becomes true then.
45
+ *
46
+ * Extracted from `index.ts` `createCursorActivities` at S2 M3b; split from
47
+ * the turn body at S2 M5. The seventeen hermetic goldens under
48
+ * `__tests__/hermetic/` pin the result byte for byte.
49
+ */
50
+
51
+ import type { Config } from "../../config.js";
52
+ import type { HarnessAdapter, TurnInput, TurnOutcome, TurnSink } from "../../harness/types.js";
53
+ import { markBoot } from "../../shared/cold-start-timing.js";
54
+ import { closeAllCachedAgents, evictSessionAgent } from "./agent-session-cache.js";
55
+ import { CURSOR_CAPABILITIES } from "./cursor-capabilities.js";
56
+ import { installFetchInterceptor } from "./fetch-interceptor.js";
57
+ import { assertHttp2ConnectPatched, installHttp2Interceptor } from "./http2-interceptor.js";
58
+ import type { CursorAdapterConfig } from "./turn-setup.js";
59
+
60
+ /** The config slice this adapter reads per turn, taken at `boot`; a whole `Config` satisfies it. */
61
+ export function resolveCursorConfig(config: Config): CursorAdapterConfig {
62
+ return {
63
+ proxyEndpoint: config.proxyEndpoint,
64
+ cursorApiKey: config.cursorApiKey,
65
+ stigmerToken: config.stigmerToken,
66
+ stigmerTokenRef: config.stigmerTokenRef,
67
+ workspaceRootDir: config.workspaceRootDir,
68
+ cloudModeEnabled: config.cloudModeEnabled,
69
+ agentResolveTimeoutMs: config.agentResolveTimeoutMs,
70
+ };
71
+ }
72
+
73
+ /** What `boot` leaves behind for the turns: the config slice and the engine turn, loaded after the interceptors. */
74
+ interface BootedCursorHarness {
75
+ readonly config: CursorAdapterConfig;
76
+ readonly runCursorTurn: typeof import("./turn.js").runCursorTurn;
77
+ }
78
+
79
+ export function createCursorAdapter(): HarnessAdapter {
80
+ let booted: BootedCursorHarness | undefined;
81
+
82
+ return {
83
+ name: "cursor",
84
+ capabilities: CURSOR_CAPABILITIES,
85
+
86
+ /**
87
+ * Install the proxy transport, prove the HTTP/2 patch reached the facade,
88
+ * then load the SDK slice — in that order, for the reason in the header.
89
+ * Runs before bootstrap resolution, so `bootConfig` carries no Temporal
90
+ * coordinates yet; every field the slice reads is known at that point.
91
+ * Idempotent: a re-boot rebinds the interceptors and re-reads the slice.
92
+ */
93
+ async boot(bootConfig: Config): Promise<void> {
94
+ if (bootConfig.proxyEndpoint && !bootConfig.proxyTokenRef) {
95
+ throw new Error(
96
+ "cursor adapter: a proxy endpoint needs Config.proxyTokenRef (the composition root binds its proxy credential there; see config.ts)",
97
+ );
98
+ }
99
+ // The Cursor SDK's REST client resolves `fetch` at call time and its
100
+ // Connect transport opens native HTTP/2 sessions; the two interceptors
101
+ // route the first through the proxy and stamp the second with the proxy
102
+ // credential and the execution id. Both are no-ops without a proxy.
103
+ installFetchInterceptor({
104
+ proxyEndpoint: bootConfig.proxyEndpoint ?? undefined,
105
+ proxyTokenRef: bootConfig.proxyTokenRef,
106
+ });
107
+ installHttp2Interceptor({
108
+ proxyEndpoint: bootConfig.proxyEndpoint ?? undefined,
109
+ proxyTokenRef: bootConfig.proxyTokenRef,
110
+ });
111
+ markBoot("interceptors_installed");
112
+ // Fail loudly at boot if a load-order regression left the node:http2 ESM
113
+ // facade unpatched (otherwise BiDi streams would silently 401). No-op
114
+ // when the interceptor is unconfigured (no proxy).
115
+ await assertHttp2ConnectPatched();
116
+
117
+ const { runCursorTurn } = await import("./turn.js");
118
+ booted = { config: resolveCursorConfig(bootConfig), runCursorTurn };
119
+ },
120
+
121
+ /** Close every parked agent (their executor leases and MCP subprocesses). */
122
+ async shutdown(): Promise<void> {
123
+ closeAllCachedAgents();
124
+ },
125
+
126
+ /** The session is done on this host: close the agent parked for it, if any (#215). */
127
+ async releaseSession(sessionId: string): Promise<void> {
128
+ evictSessionAgent(sessionId);
129
+ },
130
+
131
+ async runTurn(input: TurnInput, sink: TurnSink): Promise<TurnOutcome> {
132
+ if (booted === undefined) {
133
+ throw new Error("cursor adapter: runTurn before boot (the registry boots every adapter before the worker polls)");
134
+ }
135
+ return booted.runCursorTurn(input, sink, booted.config);
136
+ },
137
+ };
138
+ }
@@ -346,7 +346,7 @@ export function emitCursorGrantReceipts(
346
346
  */
347
347
  export function buildApprovalGrants(
348
348
  pendingApprovals: PendingApproval[],
349
- decisions: Map<string, ApprovalAction>,
349
+ decisions: ReadonlyMap<string, ApprovalAction>,
350
350
  contentDigests?: Map<string, string>,
351
351
  ): ApprovalGrant[] {
352
352
  const grants: ApprovalGrant[] = [];
@@ -406,7 +406,7 @@ function parseArgs(argsPreview: string): Record<string, unknown> | undefined {
406
406
  * generated hook script (from approval-policy.ts), not carried here.
407
407
  */
408
408
  export function buildApprovalState(
409
- mergedPolicies: Map<string, MergedToolPolicy>,
409
+ mergedPolicies: ReadonlyMap<string, MergedToolPolicy>,
410
410
  globalBypass: boolean,
411
411
  leasedCategories: ReadonlySet<ApprovalCategory>,
412
412
  grants?: ApprovalGrant[],
@@ -14,8 +14,12 @@
14
14
  * observational/audit records at their transcript positions, while
15
15
  * `file_change_sets` remains the single DECISION surface.
16
16
  *
17
- * The three turn-boundary functions keep their original signatures so the
18
- * activity wiring (index.ts) and the cutover tests are unchanged.
17
+ * The resume-time reconcile is NOT here since S2 M2: the turn runtime's
18
+ * `reconcileReinvocation` (`harness/turn-context.ts`) calls the shared
19
+ * reconcile directly under {@link CURSOR_FILE_REVIEW_IDENTITY}, the one
20
+ * export that carries the two Cursor facts it needs. The two capture
21
+ * functions keep their original signatures so the activity wiring (index.ts)
22
+ * and the cutover tests are unchanged.
19
23
  */
20
24
 
21
25
  import { readFile } from "node:fs/promises";
@@ -30,10 +34,8 @@ import { readCasObservations } from "./cas-observations.js";
30
34
  import { contentDigest } from "../../shared/file-tools.js";
31
35
  import { isToolCallRowHidden, stampFileEditRow } from "../../shared/tool-row.js";
32
36
  import {
33
- applyCaptureDecisions as sharedApplyCaptureDecisions,
34
37
  captureBaselineToLedger as sharedCaptureBaselineToLedger,
35
38
  captureCandidateToLedger as sharedCaptureCandidateToLedger,
36
- type CaptureResumeResult,
37
39
  } from "../../shared/filereview/capture.js";
38
40
  import {
39
41
  createGitProgressSubstrate,
@@ -50,8 +52,7 @@ import { partitionIgnoredPathsBySecret } from "../../shared/filereview/secret-pa
50
52
  import { casBlobReader, type CasPathCapture } from "../../shared/filereview/cas-substrate.js";
51
53
  import type { GitSubstrateChange as GitCapturedChange } from "../../shared/filereview/git-substrate.js";
52
54
  import type { ArtifactStorage } from "../../shared/artifact-storage.js";
53
-
54
- export type { CaptureResumeResult };
55
+ import type { FileReviewIdentity } from "../../harness/capabilities.js";
55
56
 
56
57
  /** The harness id the projection reads from the BASELINE payload (load-bearing). */
57
58
  const HARNESS_ID = "cursor";
@@ -67,6 +68,17 @@ const CURSOR_RUNNER_OWNED_PATHS: readonly string[] = [
67
68
  ".cursor/rules/stigmer-tool-approval.mdc",
68
69
  ];
69
70
 
71
+ /**
72
+ * The two facts the runtime's resume-time reconcile needs from this harness
73
+ * (`harness/turn-context.ts` `reconcileReinvocation`): the same id and
74
+ * exclude list the capture functions below stamp and apply, so the baseline a
75
+ * turn authors and the reconcile a resume runs agree by construction.
76
+ */
77
+ export const CURSOR_FILE_REVIEW_IDENTITY: FileReviewIdentity = {
78
+ harnessId: HARNESS_ID,
79
+ excludePaths: CURSOR_RUNNER_OWNED_PATHS,
80
+ };
81
+
70
82
  // `deriveCaptureMode` is the single capture-vs-deny-gate decision, now shared by
71
83
  // BOTH harnesses from `shared/filereview/capture.ts`. Re-exported here so existing
72
84
  // Cursor imports (index.ts, the deny-gate-exact-apply seam test) keep resolving
@@ -326,41 +338,6 @@ async function readFileOrNull(absolutePath: string): Promise<Uint8Array | null>
326
338
  }
327
339
  }
328
340
 
329
- /**
330
- * Resume: reconcile the working tree to the change set's DECIDED decisions
331
- * (keep approved "after" bytes, snap rejected/undecided back to baseline,
332
- * hash-verified), author RECONCILED / FAILED, and release the refs. Delegates to
333
- * the shared orchestration with the Cursor harness id + exclude paths.
334
- *
335
- * `storage` threads the CAS blob store + reader so gitignored (CAS) files in the
336
- * change set reconcile from the durable manifest — approved after-blobs written
337
- * (hash-verified), rejected files snapped back to their before-blobs. Omit for a
338
- * git-only harness/turn; the shared CAS branch is then skipped. The read path is
339
- * the same one `exact-apply` already uses (`ArtifactStorage.download`).
340
- */
341
- export function applyCaptureDecisions(opts: {
342
- readonly status: AgentExecutionStatus;
343
- readonly gitRoot: string;
344
- readonly executionId: string;
345
- readonly changeSet: FileChangeSet;
346
- readonly storage?: ArtifactStorage;
347
- /**
348
- * True (default) for a git work tree; false for a CAS-only non-git workspace
349
- * (Slice 2c). When false the reconcile never consults git refs — it is driven
350
- * entirely by the durable CAS manifest.
351
- */
352
- readonly gitWorkspace?: boolean;
353
- }): Promise<CaptureResumeResult> {
354
- const { storage, ...rest } = opts;
355
- return sharedApplyCaptureDecisions({
356
- ...rest,
357
- harnessId: HARNESS_ID,
358
- excludePaths: CURSOR_RUNNER_OWNED_PATHS,
359
- storage,
360
- readBlob: storage ? casBlobReader(storage) : undefined,
361
- });
362
- }
363
-
364
341
  // ---------------------------------------------------------------------------
365
342
  // Cursor-specific presentation
366
343
  // ---------------------------------------------------------------------------
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The Cursor harness's capability flags — the facts about `@cursor/sdk` the
3
+ * turn runtime branches on, declared once (`harness/capabilities.ts` has the
4
+ * matrix and the meaning of each flag).
5
+ *
6
+ * - `deny-and-retry`: an out-of-process hook denies a gated tool, the
7
+ * denial lands in the session ledger, the run is cancelled, and the next
8
+ * invocation re-runs with grants (`hook-script.ts`, `approval-state.ts`).
9
+ * - `engine-minted`: the agent id exists only once `Agent.create` returns;
10
+ * the adapter binds it through `TurnSink.bindHarnessState` before its
11
+ * first persist.
12
+ * - No system prompt: instructions ride the first user message
13
+ * (`prompt-builder.ts`).
14
+ * - Sub-agents through the SDK's `agents` option (`subagent-config.ts`).
15
+ * - No tool restriction in the SDK config: the hook's "disabled" arm
16
+ * enforces `enabledTools` at call time (issue #350).
17
+ * - PNG and JPEG inline; the transport re-sniffs (`shared/attachment-vision.ts`).
18
+ * - File review under the `cursor` harness id, with the transient
19
+ * `.cursor/hooks.json` the gate writes excluded from every diff
20
+ * (`capture-flow.ts`).
21
+ */
22
+
23
+ import type { HarnessCapabilities } from "../../harness/capabilities.js";
24
+ import { CURSOR_VISION_PROFILE } from "../../shared/attachment-vision.js";
25
+ import { CURSOR_FILE_REVIEW_IDENTITY } from "./capture-flow.js";
26
+
27
+ export const CURSOR_CAPABILITIES: HarnessCapabilities = {
28
+ pausePrimitive: "deny-and-retry",
29
+ stateIdSource: "engine-minted",
30
+ systemPrompt: false,
31
+ subAgents: true,
32
+ toolRestriction: false,
33
+ visionProfile: CURSOR_VISION_PROFILE,
34
+ fileReview: CURSOR_FILE_REVIEW_IDENTITY,
35
+ };
@@ -42,7 +42,7 @@ export type CursorMcpServerConfig =
42
42
  * expressed here — the HITL hook enforces it (see ResolvedMcpServer.enabledTools).
43
43
  */
44
44
  export function toCursorMcpConfig(
45
- servers: ResolvedMcpServer[],
45
+ servers: readonly ResolvedMcpServer[],
46
46
  ): Record<string, CursorMcpServerConfig> {
47
47
  const result: Record<string, CursorMcpServerConfig> = {};
48
48
 
@@ -75,7 +75,7 @@ export function toCursorMcpConfig(
75
75
  * Used as a pre-flight check before agent.send() to surface config issues early.
76
76
  */
77
77
  export function validateMcpServerEnv(
78
- servers: ResolvedMcpServer[],
78
+ servers: readonly ResolvedMcpServer[],
79
79
  usages: McpServerUsage[],
80
80
  ): string[] {
81
81
  const warnings: string[] = [];
@@ -9,15 +9,17 @@
9
9
  * This is the JavaScript-level equivalent of LangChain's base_url parameter
10
10
  * that the Python agent-runner uses for the LLM proxy pattern.
11
11
  *
12
- * IMPORTANT: This module must be imported BEFORE @cursor/sdk to ensure the
13
- * interceptor is in place when the SDK initializes its HTTP client.
12
+ * Installed by the Cursor adapter's `boot` (adapter.ts), which the registry
13
+ * runs in both composition roots before Temporal coordinates are resolved and
14
+ * before the adapter loads `@cursor/sdk`, so the interceptor is in place
15
+ * before the SDK issues its first request. The proxy credential is read per
16
+ * request from the ref the root bound as `Config.proxyTokenRef`.
14
17
  *
15
18
  * When STIGMER_PROXY_ENDPOINT is not set, this module is a no-op.
16
19
  */
17
20
 
18
- import { AsyncLocalStorage } from "node:async_hooks";
19
-
20
21
  import { TimingRecorder, emitTimingLog } from "../../shared/cold-start-timing.js";
22
+ import { getExecutionContext } from "../../shared/execution-context.js";
21
23
 
22
24
  /** One REST path's timing identity: the emitted timeline event and its
23
25
  * single segment name. */
@@ -56,22 +58,37 @@ const CURSOR_DOMAINS = [
56
58
 
57
59
  interface ProxyConfig {
58
60
  proxyEndpoint: string;
59
- stigmerToken: string;
61
+ /**
62
+ * The proxy credential, read on EVERY request (never copied): the
63
+ * composition root that installed this interceptor owns the ref and
64
+ * rotates it in place (the static root's sandbox-token renewal, the
65
+ * manager's token coordinator), so a rotation reaches the next request
66
+ * without a call into this module. Which credential it is differs per
67
+ * root — the static root's control-plane token, the manager's minted
68
+ * runner token (Q-S2-7) — and this module never knows which.
69
+ */
70
+ proxyTokenRef: { readonly current: string | null };
60
71
  executionId?: string;
61
72
  }
62
73
 
63
- let interceptorConfig: ProxyConfig | null = null;
64
- const originalFetch = globalThis.fetch;
65
-
66
- interface ExecutionContextStore {
67
- executionId: string;
74
+ /**
75
+ * The `Authorization`-style header value for the current proxy credential,
76
+ * or undefined when the ref is empty. Empty is unreachable by construction
77
+ * today (neither root ever writes null into the ref it bound), so the request
78
+ * goes out without the header and the proxy's 401 is the diagnostic; a
79
+ * runner-side throw here would surface inside the SDK's transport, a path
80
+ * nothing exercises.
81
+ */
82
+ function bearer(config: ProxyConfig): string | undefined {
83
+ const token = config.proxyTokenRef.current;
84
+ return token === null ? undefined : `Bearer ${token}`;
68
85
  }
69
86
 
70
- const executionContext = new AsyncLocalStorage<ExecutionContextStore>();
87
+ let interceptorConfig: ProxyConfig | null = null;
88
+ const originalFetch = globalThis.fetch;
71
89
 
72
- export function getExecutionContext(): AsyncLocalStorage<ExecutionContextStore> {
73
- return executionContext;
74
- }
90
+ /** The execution-scoped store the headers below are stamped from (`shared/execution-context.ts`). */
91
+ const executionContext = getExecutionContext();
75
92
 
76
93
  /**
77
94
  * Connect RPC path prefixes used by the Cursor SDK. Most requests with
@@ -193,7 +210,11 @@ function rewriteUrl(originalUrl: string, proxyEndpoint: string): string {
193
210
  */
194
211
  function replaceAuth(init: RequestInit | undefined, config: ProxyConfig): RequestInit {
195
212
  const headers = new Headers(init?.headers);
196
- headers.set("authorization", `Bearer ${config.stigmerToken}`);
213
+ const auth = bearer(config);
214
+ // Replace, never leak: with no proxy credential the SDK's own authorization
215
+ // (a Cursor credential) must still not reach the proxy.
216
+ if (auth !== undefined) headers.set("authorization", auth);
217
+ else headers.delete("authorization");
197
218
  const ctx = executionContext.getStore();
198
219
  const effectiveExecutionId = ctx?.executionId ?? config.executionId;
199
220
  if (effectiveExecutionId) {
@@ -210,7 +231,8 @@ function replaceAuth(init: RequestInit | undefined, config: ProxyConfig): Reques
210
231
  */
211
232
  function injectProxyAuth(init: RequestInit | undefined, config: ProxyConfig): RequestInit {
212
233
  const headers = new Headers(init?.headers);
213
- headers.set("x-stigmer-auth", `Bearer ${config.stigmerToken}`);
234
+ const auth = bearer(config);
235
+ if (auth !== undefined) headers.set("x-stigmer-auth", auth);
214
236
  const ctx = executionContext.getStore();
215
237
  const effectiveExecutionId = ctx?.executionId ?? config.executionId;
216
238
  if (effectiveExecutionId) {
@@ -362,21 +384,26 @@ async function fetchWithProxyAuth(
362
384
  }
363
385
 
364
386
  /**
365
- * Install the global fetch interceptor. Call once at startup, BEFORE
366
- * importing @cursor/sdk.
387
+ * Install the global fetch interceptor, at boot, BEFORE `@cursor/sdk` is
388
+ * imported. Idempotent: a second install rebinds the config (a worker may
389
+ * re-boot its harnesses) and never captures the interceptor itself as the
390
+ * original fetch.
367
391
  *
368
392
  * When proxyEndpoint is empty or not provided, the interceptor is not
369
393
  * installed and all fetch calls pass through to the original implementation.
394
+ * A proxy endpoint with no credential bound at install is a configuration
395
+ * defect and fails the boot here, the same failure as before the ref (the
396
+ * ref is read per request afterwards; only its presence is judged now).
370
397
  */
371
398
  export function installFetchInterceptor(config: {
372
399
  proxyEndpoint: string | undefined;
373
- stigmerToken: string | undefined;
400
+ proxyTokenRef: { readonly current: string | null } | undefined;
374
401
  }): void {
375
402
  if (!config.proxyEndpoint) {
376
403
  return;
377
404
  }
378
405
 
379
- if (!config.stigmerToken) {
406
+ if (!config.proxyTokenRef || config.proxyTokenRef.current === null) {
380
407
  throw new Error(
381
408
  "STIGMER_TOKEN is required when STIGMER_PROXY_ENDPOINT is set. " +
382
409
  "In proxy mode, the runner authenticates with Stigmer's proxy using STIGMER_TOKEN.",
@@ -385,7 +412,7 @@ export function installFetchInterceptor(config: {
385
412
 
386
413
  interceptorConfig = {
387
414
  proxyEndpoint: config.proxyEndpoint,
388
- stigmerToken: config.stigmerToken,
415
+ proxyTokenRef: config.proxyTokenRef,
389
416
  };
390
417
 
391
418
  globalThis.fetch = interceptedFetch;
@@ -395,18 +422,6 @@ export function installFetchInterceptor(config: {
395
422
  );
396
423
  }
397
424
 
398
- /**
399
- * Update the auth token on the live interceptor config. Must be called
400
- * whenever the Stigmer JWT is refreshed (e.g. via IPC updateToken) so
401
- * that fetch-intercepted REST calls (token exchange, /v1/models) use the
402
- * current token instead of the one frozen at install time.
403
- */
404
- export function updateInterceptorToken(token: string): void {
405
- if (interceptorConfig) {
406
- interceptorConfig = { ...interceptorConfig, stigmerToken: token };
407
- }
408
- }
409
-
410
425
  /**
411
426
  * Update the execution ID on the active interceptor (legacy global path).
412
427
  * Prefer {@link runWithExecutionContext} for concurrent-safe isolation.
@@ -419,18 +434,6 @@ export function setInterceptorExecutionId(executionId: string | undefined): void
419
434
  }
420
435
  }
421
436
 
422
- /**
423
- * Run an async function with execution-scoped context. The executionId is
424
- * propagated through the async call chain via AsyncLocalStorage, ensuring
425
- * concurrent activities on the same runner process don't overwrite each
426
- * other's proxy headers.
427
- */
428
- export function runWithExecutionContext<T>(
429
- executionId: string,
430
- fn: () => Promise<T>,
431
- ): Promise<T> {
432
- return executionContext.run({ executionId }, fn);
433
- }
434
437
 
435
438
  /**
436
439
  * Remove the interceptor and restore the original fetch. Primarily for
@@ -16,11 +16,14 @@
16
16
  *
17
17
  * LOAD ORDER IS THEREFORE LOAD-BEARING: this interceptor MUST be installed
18
18
  * before the first connect-node import in the process. The runner enforces this
19
- * by (a) keeping connect-node out of the pre-install static module graph
20
- * (bootstrap.ts loads StigmerClient via dynamic import) and (b) installing this
21
- * interceptor before resolving Temporal coordinates / importing the SDK in the
22
- * runner factories. assertHttp2ConnectPatched() verifies the ESM-facade view at
23
- * boot so any future regression fails loudly instead of silently 401-ing.
19
+ * by (a) keeping connect-node out of the pre-boot static module graph
20
+ * (bootstrap.ts and harness/registry.ts load StigmerClient via dynamic import;
21
+ * adapter.ts loads the SDK inside boot) and (b) installing this interceptor in
22
+ * the Cursor adapter's `boot`, which the registry runs in both composition
23
+ * roots before Temporal coordinates are resolved. assertHttp2ConnectPatched()
24
+ * verifies the ESM-facade view right after the install so any future
25
+ * regression fails loudly instead of silently 401-ing;
26
+ * src/__tests__/harness-boot-order.test.ts runs that boot in a fresh process.
24
27
  *
25
28
  * This module patches `http2.connect()` to wrap returned sessions. The
26
29
  * wrapped session's `request()` method reads the execution ID from the
@@ -42,7 +45,7 @@
42
45
 
43
46
  import { createRequire } from "node:module";
44
47
  import type http2Type from "node:http2";
45
- import { getExecutionContext } from "./fetch-interceptor.js";
48
+ import { getExecutionContext } from "../../shared/execution-context.js";
46
49
 
47
50
  // Use require() to get the ACTUAL CJS module singleton. Mutations here
48
51
  // are visible to all importers including ESM namespace imports, because
@@ -56,7 +59,14 @@ const STIGMER_AUTH_HEADER = "x-stigmer-auth";
56
59
  interface Http2InterceptorConfig {
57
60
  proxyHostname: string;
58
61
  proxyPort: string;
59
- stigmerToken: string;
62
+ /**
63
+ * The proxy credential, read on EVERY stream (never copied): the root that
64
+ * installed this interceptor owns the ref and rotates it in place, so a
65
+ * rotation reaches the next `request()` on an already-open session without
66
+ * a call into this module (see fetch-interceptor.ts for the two roots'
67
+ * bindings, Q-S2-7).
68
+ */
69
+ proxyTokenRef: { readonly current: string | null };
60
70
  }
61
71
 
62
72
  let config: Http2InterceptorConfig | null = null;
@@ -110,10 +120,15 @@ function wrapSession(session: http2Type.ClientHttp2Session): http2Type.ClientHtt
110
120
  }
111
121
 
112
122
  const ctx = getExecutionContext().getStore();
113
- const augmented: http2Type.OutgoingHttpHeaders = {
114
- ...headers,
115
- [STIGMER_AUTH_HEADER]: `Bearer ${config.stigmerToken}`,
116
- };
123
+ const augmented: http2Type.OutgoingHttpHeaders = { ...headers };
124
+ // An empty ref is unreachable by construction today (no root writes null
125
+ // into the ref it bound); the stream then goes out without the header and
126
+ // the proxy's 401 is the diagnostic, rather than a throw inside the SDK's
127
+ // transport, a path nothing exercises.
128
+ const token = config.proxyTokenRef.current;
129
+ if (token !== null) {
130
+ augmented[STIGMER_AUTH_HEADER] = `Bearer ${token}`;
131
+ }
117
132
  if (ctx?.executionId) {
118
133
  augmented[EXECUTION_ID_HEADER] = ctx.executionId;
119
134
  }
@@ -124,21 +139,27 @@ function wrapSession(session: http2Type.ClientHttp2Session): http2Type.ClientHtt
124
139
  }
125
140
 
126
141
  /**
127
- * Install the HTTP/2 interceptor. Call once at startup, BEFORE importing
128
- * @cursor/sdk.
142
+ * Install the HTTP/2 interceptor, at boot, BEFORE `@cursor/sdk` (and so
143
+ * connect-node) is imported. Idempotent: a second install rebinds the config
144
+ * (a worker may re-boot its harnesses) and leaves the one patch in place —
145
+ * re-capturing `http2.connect` here would take the patched function as the
146
+ * original and wrap every proxy session twice.
129
147
  *
130
- * When proxyEndpoint is not provided, the interceptor is not installed
131
- * and all http2.connect() calls pass through to the original.
148
+ * When proxyEndpoint is not provided, or no credential ref is bound, the
149
+ * interceptor is not installed and all http2.connect() calls pass through to
150
+ * the original. (The fetch interceptor, installed first, throws on a proxy
151
+ * without a credential; this one stays a silent no-op as it always has, so a
152
+ * caller that installs only this half keeps its historical posture.)
132
153
  */
133
154
  export function installHttp2Interceptor(opts: {
134
155
  proxyEndpoint: string | undefined;
135
- stigmerToken: string | undefined;
156
+ proxyTokenRef: { readonly current: string | null } | undefined;
136
157
  }): void {
137
158
  if (!opts.proxyEndpoint) {
138
159
  return;
139
160
  }
140
161
 
141
- if (!opts.stigmerToken) {
162
+ if (!opts.proxyTokenRef || opts.proxyTokenRef.current === null) {
142
163
  return;
143
164
  }
144
165
 
@@ -150,7 +171,11 @@ export function installHttp2Interceptor(opts: {
150
171
  return;
151
172
  }
152
173
 
153
- config = { proxyHostname: parsed.hostname, proxyPort: parsed.port, stigmerToken: opts.stigmerToken };
174
+ const alreadyPatched = config !== null;
175
+ config = { proxyHostname: parsed.hostname, proxyPort: parsed.port, proxyTokenRef: opts.proxyTokenRef };
176
+ if (alreadyPatched) {
177
+ return;
178
+ }
154
179
  originalConnect = http2.connect;
155
180
 
156
181
  http2.connect = function patchedConnect(
@@ -205,26 +230,14 @@ export async function assertHttp2ConnectPatched(): Promise<void> {
205
230
  "[http2-interceptor] node:http2 ESM facade is unpatched: connect-node imported " +
206
231
  "node:http2 before installHttp2Interceptor() ran, so its frozen namespace still " +
207
232
  "holds the original http2.connect. BiDi streams would omit x-stigmer-auth and 401. " +
208
- "Fix the load order: keep @connectrpc/connect-node out of the pre-install static " +
209
- "module graph (load StigmerClient via dynamic import) and install this interceptor " +
210
- "before resolving Temporal coordinates / importing @cursor/sdk. See bootstrap.ts " +
211
- "and the runner factories.",
233
+ "Fix the load order: keep @connectrpc/connect-node out of the pre-boot static module " +
234
+ "graph (load StigmerClient via dynamic import, as bootstrap.ts and harness/registry.ts " +
235
+ "do) and keep @cursor/sdk behind the Cursor adapter's boot (adapter.ts loads turn.ts " +
236
+ "after this install). See src/__tests__/harness-boot-order.test.ts.",
212
237
  );
213
238
  }
214
239
  }
215
240
 
216
- /**
217
- * Update the auth token on the live interceptor config. Must be called
218
- * whenever the Stigmer JWT is refreshed (e.g. via IPC updateToken) so
219
- * that HTTP/2-intercepted streams use the current token instead of the
220
- * one frozen at install time.
221
- */
222
- export function updateHttp2InterceptorToken(token: string): void {
223
- if (config) {
224
- config = { ...config, stigmerToken: token };
225
- }
226
- }
227
-
228
241
  /**
229
242
  * Remove the interceptor and restore the original http2.connect.
230
243
  * Primarily for testing.