@stigmer/runner 3.14.1 → 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 (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
@@ -29,9 +29,20 @@
29
29
  * approval exactly once, refusing to resume a state it never minted — because
30
30
  * those are what the kit measures.
31
31
  *
32
- * A `runTurn` with no scenario arranged is a test bug and throws (the same
33
- * rule as the scripted `@cursor/sdk` agent's `send()` with no script left);
34
- * every other exit is a `TurnOutcome`.
32
+ * What this fake deliberately does NOT do, because it is not an adapter's
33
+ * job: carry a REJECTed or SKIPped row to its terminal status. The decision
34
+ * is on the row (the server's field) and the transition follows from it with
35
+ * no engine knowledge, so it belongs to the runtime — one writer per field,
36
+ * as `approvalDecisionsOf` is the runtime's one reader (S2 M4, Q-M4-1; the
37
+ * runtime's arm lands in S3). An adapter's whole duty for a non-executing
38
+ * decision is to not execute.
39
+ *
40
+ * Scenarios are arranged PER SESSION (the engine is per session in every real
41
+ * harness; a subject arranges that session's engine), and arranging replaces
42
+ * what that session had not yet played. A `runTurn` with no scenario arranged
43
+ * for its session is a test bug and throws (the same rule as the scripted
44
+ * `@cursor/sdk` agent's `send()` with no script left); every other exit is a
45
+ * `TurnOutcome`.
35
46
  */
36
47
 
37
48
  import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
@@ -43,14 +54,21 @@ import type { HarnessAdapter, TurnInput, TurnOutcome, TurnSink } from "../../har
43
54
  import { DEEP_AGENT_VISION_PROFILE } from "../../shared/attachment-vision.js";
44
55
  import type { ProposedAction } from "../approval-contract/types.js";
45
56
  import { testConfig } from "../config-fixture.js";
46
- import { aiMessage, findToolCallRow, waitingToolCall } from "../proto-helpers.js";
47
- import type { HarnessContractSubject, ScenarioStep, TurnScenario } from "./types.js";
57
+ import { aiMessage, findToolCallRow, toolCall, waitingToolCall } from "../proto-helpers.js";
58
+ import type { EngineView, HarnessContractSubject, ScenarioStep, TurnScenario } from "./types.js";
48
59
 
49
60
  export interface ScriptedHarnessOptions {
50
61
  /** Diagnostic name; defaults to a name that says which primitives this instance runs under. */
51
62
  readonly name?: string;
52
63
  readonly pausePrimitive: PausePrimitive;
53
64
  readonly stateIdSource: StateIdSource;
65
+ /**
66
+ * The subject's `Config` over `testConfig()`'s inert defaults. The fake reads
67
+ * none of it; the RUNTIME does when the subject runs through the real
68
+ * activity (the workspace root it provisions under, the task queue the
69
+ * drain signal is keyed by).
70
+ */
71
+ readonly config?: Partial<Config>;
54
72
  }
55
73
 
56
74
  /**
@@ -74,9 +92,11 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
74
92
  readonly name: string;
75
93
  readonly capabilities: HarnessCapabilities;
76
94
 
77
- private readonly queue: TurnScenario[] = [];
95
+ /** The next turn each session plays; arranging a session replaces its entry. */
96
+ private readonly arranged = new Map<string, TurnScenario>();
78
97
  private readonly mintedStateIds = new Set<string>();
79
98
  private readonly executions = new Map<string, number>();
99
+ private readonly hangWaiters: Array<() => void> = [];
80
100
  private mintCounter = 0;
81
101
 
82
102
  constructor(options: ScriptedHarnessOptions) {
@@ -88,14 +108,29 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
88
108
  subAgents: false,
89
109
  toolRestriction: true,
90
110
  visionProfile: DEEP_AGENT_VISION_PROFILE,
111
+ fileReview: { harnessId: "scripted", excludePaths: [] },
91
112
  };
92
113
  }
93
114
 
115
+ /**
116
+ * Resolves the next time a turn parks on a `hang` step — for a test that
117
+ * stands where the runtime's watchdog stands and needs to know the engine
118
+ * is silent before it advances the clock. Never on a timer.
119
+ */
120
+ whenHanging(): Promise<void> {
121
+ return new Promise((resolve) => this.hangWaiters.push(resolve));
122
+ }
123
+
94
124
  // ── Engine controls (the subject's side of the kit seam) ──────────────────
95
125
 
96
- /** Queue what the next `runTurn` plays. */
97
- arrange(turn: TurnScenario): void {
98
- this.queue.push(turn);
126
+ /**
127
+ * What the session's next `runTurn` plays. The rest of the view is not
128
+ * needed here: this fake reads its decisions from `input.approvalDecisions`
129
+ * and settles a repeated proposal from the row itself, so it has nothing to
130
+ * decide before the turn.
131
+ */
132
+ arrange(turn: TurnScenario, view: Pick<EngineView, "sessionId">): void {
133
+ this.arranged.set(view.sessionId, turn);
99
134
  }
100
135
 
101
136
  executionCount(toolCallId: string): number {
@@ -114,8 +149,9 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
114
149
  async releaseSession(_sessionId: string): Promise<void> {}
115
150
 
116
151
  async runTurn(input: TurnInput, sink: TurnSink): Promise<TurnOutcome> {
117
- const turn = this.queue.shift();
118
- if (!turn) throw new Error(`${this.name}: runTurn called with no scenario arranged (test bug)`);
152
+ const turn = this.arranged.get(input.sessionId);
153
+ if (!turn) throw new Error(`${this.name}: runTurn called with no scenario arranged for session '${input.sessionId}' (test bug)`);
154
+ this.arranged.delete(input.sessionId);
119
155
 
120
156
  // The signal may be aborted before the turn is entered; do no work then.
121
157
  if (sink.stopSignal.aborted) return { kind: "interrupted" };
@@ -153,7 +189,7 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
153
189
  } catch (err) {
154
190
  return {
155
191
  kind: "ended",
156
- outcome: { kind: "failed", message: `${this.name}: could not bind engine state`, cause: err },
192
+ outcome: { kind: "failed", surface: "internal", message: `${this.name}: could not bind engine state`, cause: err },
157
193
  };
158
194
  }
159
195
  return { kind: "ok" };
@@ -162,7 +198,7 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
162
198
  if (!this.mintedStateIds.has(input.threadId)) {
163
199
  return {
164
200
  kind: "ended",
165
- outcome: { kind: "failed", message: `${this.name}: no engine state '${input.threadId}' to resume` },
201
+ outcome: { kind: "failed", surface: "internal", message: `${this.name}: no engine state '${input.threadId}' to resume` },
166
202
  };
167
203
  }
168
204
  return { kind: "ok" };
@@ -174,22 +210,42 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
174
210
  case "say": {
175
211
  sink.status.messages.push(aiMessage(step.text));
176
212
  sink.recordActivity();
177
- sink.requestPersist();
213
+ // Awaited, as the Cursor loop awaits its own: a platform STOP the
214
+ // runtime reads from this write aborts the signal before the next
215
+ // step boundary sees it (the contract's "MAY await for ordering").
216
+ await sink.requestPersist();
178
217
  return undefined;
179
218
  }
180
219
  case "propose":
181
220
  return this.propose(step.toolCallId, step.action, input, sink);
221
+ case "read": {
222
+ // Ungated: the row lands COMPLETED at once, the effect counts as run,
223
+ // and the persist is awaited like `say`'s — a real engine's tool call
224
+ // is the discrete event its loop flushes on.
225
+ const message = aiMessage("");
226
+ const row = toolCall(step.toolCallId, "read", ToolCallStatus.TOOL_CALL_COMPLETED);
227
+ row.result = `contents of ${step.path}`;
228
+ message.toolCalls.push(row);
229
+ sink.status.messages.push(message);
230
+ this.executions.set(step.toolCallId, this.executionCount(step.toolCallId) + 1);
231
+ sink.recordActivity("read");
232
+ await sink.requestPersist();
233
+ return undefined;
234
+ }
182
235
  case "usage": {
183
236
  sink.reportUsage(step.delta);
184
237
  sink.recordActivity();
185
238
  return undefined;
186
239
  }
187
240
  case "hang": {
241
+ for (const waiter of this.hangWaiters.splice(0)) waiter();
188
242
  await whenAborted(sink.stopSignal);
189
243
  return { kind: "interrupted" };
190
244
  }
191
245
  case "fail":
192
- return { kind: "failed", message: step.message };
246
+ return { kind: "failed", surface: step.surface, message: step.message };
247
+ case "cancelled":
248
+ return { kind: "cancelled" };
193
249
  default: {
194
250
  const exhaustive: never = step;
195
251
  throw new Error(`${this.name}: unknown scenario step ${JSON.stringify(exhaustive)}`);
@@ -230,12 +286,14 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
230
286
  case ApprovalAction.APPROVE:
231
287
  case ApprovalAction.APPROVE_ALL: {
232
288
  this.executions.set(toolCallId, this.executionCount(toolCallId) + 1);
233
- this.settleRow(existing, toolCallId, action, ToolCallStatus.TOOL_CALL_COMPLETED, sink);
289
+ this.completeRow(existing, toolCallId, action, sink);
234
290
  return undefined;
235
291
  }
236
292
  case ApprovalAction.SKIP:
237
293
  case ApprovalAction.REJECT: {
238
- this.settleRow(existing, toolCallId, action, ToolCallStatus.TOOL_CALL_SKIPPED, sink);
294
+ // Not executed, and the row is left as the runtime handed it (see
295
+ // the header): the engine simply moves on.
296
+ sink.recordActivity();
239
297
  return undefined;
240
298
  }
241
299
  default: {
@@ -246,24 +304,19 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
246
304
  }
247
305
 
248
306
  /**
249
- * Carry the proposal's row to its terminal status. The row normally exists
250
- * (the runtime seeded the status with last turn's WAITING row); a decision
251
- * with no row is a runtime that decided out of band, and the adapter still
252
- * records what happened rather than losing the fact.
307
+ * Report the executed action on its row, as a real engine's completion
308
+ * event does. The row normally exists (the runtime seeded the status with
309
+ * last turn's WAITING row); a decision with no row is a runtime that decided
310
+ * out of band, and the adapter still records what happened rather than
311
+ * losing the fact.
253
312
  */
254
- private settleRow(
255
- existing: ToolCall | undefined,
256
- toolCallId: string,
257
- action: ProposedAction,
258
- status: ToolCallStatus,
259
- sink: TurnSink,
260
- ): void {
313
+ private completeRow(existing: ToolCall | undefined, toolCallId: string, action: ProposedAction, sink: TurnSink): void {
261
314
  if (existing) {
262
- existing.status = status;
315
+ existing.status = ToolCallStatus.TOOL_CALL_COMPLETED;
263
316
  } else {
264
317
  const message = aiMessage("");
265
318
  const row = waitingToolCall(toolCallId, action.kind, "");
266
- row.status = status;
319
+ row.status = ToolCallStatus.TOOL_CALL_COMPLETED;
267
320
  message.toolCalls.push(row);
268
321
  sink.status.messages.push(message);
269
322
  }
@@ -277,13 +330,20 @@ export interface ScriptedSubject extends HarnessContractSubject {
277
330
  readonly adapter: ScriptedHarnessAdapter;
278
331
  }
279
332
 
333
+ /**
334
+ * The fake fills the `deep-agent` row in S2 — the one row the runtime does
335
+ * not yet serve for real (the native adapter lands in S3, and takes the row
336
+ * over from the fake in the runtime half then).
337
+ */
280
338
  export function scriptedSubject(options: ScriptedHarnessOptions): ScriptedSubject {
281
339
  const adapter = new ScriptedHarnessAdapter(options);
282
340
  return {
283
341
  name: adapter.name,
342
+ harness: "deep-agent",
284
343
  adapter,
285
- config: testConfig(),
286
- arrange: (turn) => adapter.arrange(turn),
344
+ config: testConfig(options.config),
345
+ arrange: (turn, view) => adapter.arrange(turn, view),
346
+ whenHanging: () => adapter.whenHanging(),
287
347
  executionCount: (toolCallId) => adapter.executionCount(toolCallId),
288
348
  };
289
349
  }
@@ -23,9 +23,13 @@
23
23
  * HITL taxonomy.
24
24
  */
25
25
 
26
+ import type { ApprovalAction } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
27
+
26
28
  import type { Config } from "../../config.js";
27
- import type { HarnessAdapter, UsageDelta } from "../../harness/types.js";
29
+ import type { HarnessName } from "../../harness/registry.js";
30
+ import type { FailureSurface, HarnessAdapter, UsageDelta } from "../../harness/types.js";
28
31
  import type { ProposedAction } from "../approval-contract/types.js";
32
+ import type { TurnInputFixtureOverrides } from "../turn-input-fixture.js";
29
33
 
30
34
  /**
31
35
  * One thing the engine does during a turn. A turn plays its steps in order
@@ -35,20 +39,33 @@ import type { ProposedAction } from "../approval-contract/types.js";
35
39
  * - `propose`: the engine reaches a gated side effect identified by
36
40
  * `toolCallId`. Undecided → the turn ends `awaiting_approval` and later
37
41
  * steps do not run; APPROVE → the effect runs once and the turn continues;
38
- * REJECT / SKIP → the effect never runs and the turn continues.
42
+ * REJECT / SKIP → the effect never runs and the turn continues. WHICH
43
+ * kinds a harness gates is the harness's policy and the workspace's
44
+ * posture: a `shell` is gated by every harness in every posture; a `write`
45
+ * is gated only outside apply-then-review capture (a git tree, or artifact
46
+ * storage), so an arm that must gate on every subject proposes a `shell`.
47
+ * - `read`: the engine performs an UNGATED read of `path` — a tool-call row
48
+ * that runs at once, never pauses, and is the discrete event every harness
49
+ * flushes a persist on (the lever an arm pulls when it needs the control
50
+ * plane to answer a mid-turn persist).
39
51
  * - `usage`: the engine reports one turn's token counts.
40
52
  * - `hang`: the engine makes no further progress until told to stop. The
41
53
  * step that the stop-signal invariants and the runtime's stall watchdog
42
54
  * are built around; it must be parked on the signal, never on a timer.
43
55
  * - `fail`: the engine fails with a message the adapter can name; the turn
44
- * ends `failed`.
56
+ * ends `failed` on the given surface (`engine` unless the scenario says
57
+ * otherwise — the runtime's three failure copies are its own invariant).
58
+ * - `cancelled`: the engine ends its own run cancelled with nothing to
59
+ * wait for (an SDK-side cancel); the turn ends `cancelled`.
45
60
  */
46
61
  export type ScenarioStep =
47
62
  | { readonly kind: "say"; readonly text: string }
48
63
  | { readonly kind: "propose"; readonly toolCallId: string; readonly action: ProposedAction }
64
+ | { readonly kind: "read"; readonly toolCallId: string; readonly path: string }
49
65
  | { readonly kind: "usage"; readonly delta: UsageDelta }
50
66
  | { readonly kind: "hang" }
51
- | { readonly kind: "fail"; readonly message: string };
67
+ | { readonly kind: "fail"; readonly message: string; readonly surface: FailureSurface }
68
+ | { readonly kind: "cancelled" };
52
69
 
53
70
  /** One turn's worth of engine behaviour. */
54
71
  export type TurnScenario = readonly ScenarioStep[];
@@ -61,36 +78,95 @@ export const scenario = {
61
78
  propose(toolCallId: string, action: ProposedAction): ScenarioStep {
62
79
  return { kind: "propose", toolCallId, action };
63
80
  },
81
+ read(toolCallId: string, path: string): ScenarioStep {
82
+ return { kind: "read", toolCallId, path };
83
+ },
64
84
  usage(delta: UsageDelta): ScenarioStep {
65
85
  return { kind: "usage", delta };
66
86
  },
67
87
  hang(): ScenarioStep {
68
88
  return { kind: "hang" };
69
89
  },
70
- fail(message: string): ScenarioStep {
71
- return { kind: "fail", message };
90
+ fail(message: string, surface: FailureSurface = "engine"): ScenarioStep {
91
+ return { kind: "fail", message, surface };
92
+ },
93
+ cancelled(): ScenarioStep {
94
+ return { kind: "cancelled" };
72
95
  },
73
96
  } as const;
74
97
 
98
+ /**
99
+ * What the engine can know about the turn it is about to play, handed to the
100
+ * subject with every {@link HarnessContractSubject.arrange}: which execution
101
+ * and session it serves (a real engine is per session — its handle is parked
102
+ * under the session, its workspace and hook state live under the session —
103
+ * so a subject arranges THAT session's engine, never "the next turn"), and
104
+ * the approval decisions the prompt will carry (the runtime's
105
+ * `approvalDecisionsOf` over the persisted status — the one reader both
106
+ * drivers call). A subject whose engine cannot re-run a settled action, or
107
+ * whose model is told not to perform a skipped one, reads its decision here
108
+ * instead of parsing the prompt for it.
109
+ */
110
+ export interface EngineView {
111
+ readonly executionId: string;
112
+ readonly sessionId: string;
113
+ readonly approvalDecisions: ReadonlyMap<string, ApprovalAction>;
114
+ }
115
+
116
+ /**
117
+ * What a subject may say about the record its engine is handed, laid UNDER
118
+ * the driver's own facts: the five it threads (`executionId`, `threadId`,
119
+ * `turnSeq`, `sessionId`, `approvalDecisions`) and the persisted status it
120
+ * seeds are the kit's bookkeeping and never a subject's to state.
121
+ */
122
+ export type SubjectInputOverrides = Omit<
123
+ TurnInputFixtureOverrides,
124
+ "executionId" | "threadId" | "turnSeq" | "sessionId" | "approvalDecisions" | "persistedStatus"
125
+ >;
126
+
75
127
  /**
76
128
  * One adapter under test, with the engine controls the kit needs. Adapters
77
129
  * translate scenarios into their own double's drives and report what the
78
130
  * double observed; they never reimplement contract behaviour.
131
+ *
132
+ * The kit owns the adapter's lifetime: it boots the adapter before the first
133
+ * turn and shuts it down after the last (the registry boots every adapter
134
+ * before the worker polls, and a real adapter refuses a turn before boot).
135
+ * The exported assertion functions therefore REQUIRE a booted adapter.
79
136
  */
80
137
  export interface HarnessContractSubject {
81
138
  /** Stable name, used in suite titles and every assertion message. */
82
139
  readonly name: string;
140
+ /** The registry row this adapter fills; the runtime half selects the activity by it. */
141
+ readonly harness: HarnessName;
83
142
  /** The adapter under test — the real implementation of the contract. */
84
143
  readonly adapter: HarnessAdapter;
85
144
  /** What `adapter.boot` needs. The subject knows its adapter's fields; the kit does not. */
86
145
  readonly config: Config;
87
146
  /**
88
- * Arrange the engine so that the NEXT `runTurn` on this adapter plays
89
- * `turn`. Called by the kit before every `runTurn`, including a
147
+ * Arrange the engine so that the NEXT `runTurn` for `view.sessionId` plays
148
+ * `turn` REPLACING whatever was arranged for that session and not yet
149
+ * played (a turn interrupted before its engine ran leaves no stale script
150
+ * behind). Called by the kit before every `runTurn`, including a
90
151
  * reinvocation (the engine re-reaches the same gated call on resume, so the
91
152
  * same proposal is arranged again).
92
153
  */
93
- arrange(turn: TurnScenario): void;
154
+ arrange(turn: TurnScenario, view: EngineView): void;
155
+ /**
156
+ * What this engine needs the record to say beyond the driver's facts (the
157
+ * model the adapter validates, the workspace its gate installs into). A
158
+ * subject whose engine reads nothing of the record omits it.
159
+ */
160
+ inputOverrides?(view: EngineView): SubjectInputOverrides;
161
+ /**
162
+ * Resolves the next time a turn parks on a `hang` step — the moment the
163
+ * kit stops a turn from the outside, and the moment the runtime half
164
+ * delivers a cancellation, a drain or the clock tick that trips the stall
165
+ * watchdog. A real engine reaches its hang only after real setup (file
166
+ * writes, catalog fetches); a stop delivered any earlier would land in that
167
+ * setup and prove nothing about the hang. Never a timer.
168
+ */
169
+ whenHanging(): Promise<void>;
94
170
  /**
95
171
  * How many times the side effect behind `toolCallId` actually ran, as the
96
172
  * subject's double observed it. The safety-critical observable; zero for an
@@ -77,12 +77,13 @@ import {
77
77
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
78
78
  import {
79
79
  ApprovalAction,
80
+ ExecutionControlSignal,
80
81
  ExecutionPhase,
81
82
  ToolCallStatus,
82
83
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
83
84
  import { UpdateStatusResponseSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
84
85
  import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
85
- import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
86
+ import { SessionSchema, type Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
86
87
  import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
87
88
  import type { AgentInstance } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
88
89
  import type { StigmerClient } from "../client/stigmer-client.js";
@@ -107,11 +108,20 @@ export interface ExecutionRecordInput {
107
108
  readonly session: Session;
108
109
  readonly agentInstance: AgentInstance;
109
110
  readonly agent: Agent;
111
+ /**
112
+ * What `UpdateStatus` answers for each FULL status write — the platform's
113
+ * STOP lever (`ExecutionControlSignal`), decided by the control plane per
114
+ * write and read by the harness stream loop on its mid-stream persist.
115
+ * Evaluated AFTER the write lands, on the snapshot just persisted, so a
116
+ * policy can key on the transcript ("stop once the first assistant message
117
+ * is in"). Defaults to UNSPECIFIED: keep going.
118
+ */
119
+ readonly controlSignal?: (status: AgentExecutionStatus) => ExecutionControlSignal;
110
120
  }
111
121
 
112
122
  /**
113
123
  * An in-memory stand-in for the server's execution row, with the TWO merge
114
- * rules the activity depends on and no others:
124
+ * rules and the ONE control lever the activity depends on, and no others:
115
125
  *
116
126
  * - A status whose phase is UNSPECIFIED is a setup-progress report
117
127
  * (`reportSetupProgress`): the server keeps `setup_progress` and leaves the
@@ -120,6 +130,10 @@ export interface ExecutionRecordInput {
120
130
  * writer of the transcript; the server's own field-ownership merge — approval
121
131
  * fields it owns — is exercised by the test SETTING `approvalAction` on the
122
132
  * record between invocations, exactly as `SubmitApproval` would).
133
+ * - Every `UpdateStatus` answers a control signal ({@link ExecutionRecordInput.controlSignal});
134
+ * the server's STOP is the one instruction that travels back to the runner
135
+ * on this channel, and it is a server decision, so it is modelled here and
136
+ * not by the test hand-writing the client.
123
137
  *
124
138
  * Every persisted status is also kept in order (`persisted`) so a test can
125
139
  * assert the phase sequence the activity wrote, independent of the final state.
@@ -129,11 +143,16 @@ export class ExecutionRecord {
129
143
  readonly session: Session;
130
144
  readonly agentInstance: AgentInstance;
131
145
  readonly agent: Agent;
146
+ private readonly controlSignal: (status: AgentExecutionStatus) => ExecutionControlSignal;
132
147
  /** Every `updateStatus` payload, in order, snapshotted at write time. */
133
148
  readonly persisted: AgentExecutionStatus[] = [];
134
149
  /** Setup-progress labels reported before the stream started, in order. */
135
150
  readonly setupProgress: string[] = [];
136
- /** Every `updateSession` payload, in order (the `harness_state_id` write-back). */
151
+ /**
152
+ * Every `updateSession` payload, in order (the `harness_state_id` write-back),
153
+ * snapshotted at write time: the activity re-binds the SAME session object on
154
+ * a fresh-agent recovery, so a live reference would show the second id twice.
155
+ */
137
156
  readonly sessionUpdates: Session[] = [];
138
157
 
139
158
  constructor(input: ExecutionRecordInput) {
@@ -141,6 +160,7 @@ export class ExecutionRecord {
141
160
  this.session = input.session;
142
161
  this.agentInstance = input.agentInstance;
143
162
  this.agent = input.agent;
163
+ this.controlSignal = input.controlSignal ?? (() => ExecutionControlSignal.UNSPECIFIED);
144
164
  }
145
165
 
146
166
  get executionId(): string {
@@ -201,16 +221,22 @@ export class ExecutionRecord {
201
221
  return waiting.length;
202
222
  }
203
223
 
204
- applyStatusUpdate(status: AgentExecutionStatus): void {
224
+ /**
225
+ * Apply one `UpdateStatus` write and answer the control signal the server
226
+ * would. A setup-progress report never carries a signal (the runner ignores
227
+ * the response there; the server has nothing to say about a label).
228
+ */
229
+ applyStatusUpdate(status: AgentExecutionStatus): ExecutionControlSignal {
205
230
  const snapshot = clone(AgentExecutionStatusSchema, status);
206
231
  this.persisted.push(snapshot);
207
232
  if (snapshot.phase === ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED) {
208
233
  if (snapshot.setupProgress?.currentPhase) {
209
234
  this.setupProgress.push(snapshot.setupProgress.currentPhase);
210
235
  }
211
- return;
236
+ return ExecutionControlSignal.UNSPECIFIED;
212
237
  }
213
238
  this.execution.status = clone(AgentExecutionStatusSchema, snapshot);
239
+ return this.controlSignal(snapshot);
214
240
  }
215
241
 
216
242
  /**
@@ -228,12 +254,11 @@ export class ExecutionRecord {
228
254
  getAgentInstance: vi.fn(async () => this.agentInstance),
229
255
  getAgent: vi.fn(async () => this.agent),
230
256
  updateStatus: vi.fn(async (_id: string, status: AgentExecutionStatus) => {
231
- this.applyStatusUpdate(status);
232
257
  // The activity reads only `.signal`; UNSPECIFIED means "keep going".
233
- return create(UpdateStatusResponseSchema, {});
258
+ return create(UpdateStatusResponseSchema, { signal: this.applyStatusUpdate(status) });
234
259
  }),
235
260
  updateSession: vi.fn(async (session: Session) => {
236
- this.sessionUpdates.push(session);
261
+ this.sessionUpdates.push(clone(SessionSchema, session));
237
262
  return session;
238
263
  }),
239
264
  getExecutionContextByExecutionId: vi.fn(async () => {
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The model registry document a hermetic activity run reads, and the `fetch`
3
+ * stub that serves it and refuses everything else.
4
+ *
5
+ * The activity's only HTTP fetch is the model registry (`shared/model-
6
+ * registry.ts` for vision capability; the Cursor harness's `model-pricing-
7
+ * data.ts` for pricing). Both fail SOFT to defaults with a 60 s failure
8
+ * cache, which would make a golden depend on DEFAULT_PRICING and log a
9
+ * warning on every run, so a hermetic run stubs `fetch` to answer one
10
+ * registry document — and to THROW for any other URL, so a new network
11
+ * dependency on the activity path fails the run instead of leaking.
12
+ *
13
+ * One entry, `composer-2.5`, priced at round numbers so a golden's
14
+ * `estimatedCostUsd` is legible (600 000 input tokens = $0.60). The entry is
15
+ * a Cursor-harness model because that is what the registry lists; the
16
+ * document itself is the control plane's, read by every harness's runtime.
17
+ * `parsePricingTable` wants `harness: "cursor"` + `pricing`; `parseRegistry`
18
+ * wants `id` + `provider` and reads `capabilities.vision`.
19
+ */
20
+
21
+ import { vi } from "vitest";
22
+
23
+ /** The pinned model of every hermetic run; in the registry document AND in the Cursor double's catalog. */
24
+ export const FIXTURE_MODEL = "composer-2.5";
25
+
26
+ export const REGISTRY_DOCUMENT = {
27
+ models: [
28
+ {
29
+ id: FIXTURE_MODEL,
30
+ displayName: "Composer 2.5 (hermetic fixture)",
31
+ provider: "cursor",
32
+ harness: "cursor",
33
+ costTier: "standard",
34
+ featured: true,
35
+ capabilities: { vision: true },
36
+ pricing: {
37
+ inputPricePerMillion: 1.0,
38
+ outputPricePerMillion: 4.0,
39
+ cacheWritePricePerMillion: 1.0,
40
+ cacheReadPricePerMillion: 0.1,
41
+ },
42
+ pricingVariants: {
43
+ fast: {
44
+ inputPricePerMillion: 3.0,
45
+ outputPricePerMillion: 12.0,
46
+ cacheWritePricePerMillion: 3.0,
47
+ cacheReadPricePerMillion: 0.3,
48
+ },
49
+ },
50
+ },
51
+ ],
52
+ } as const;
53
+
54
+ /**
55
+ * Stub `fetch` to answer the registry document and refuse everything else.
56
+ * Returns the URLs fetched, for the "no other network" assertion.
57
+ */
58
+ export function stubRegistryFetch(): { readonly urls: string[]; restore(): void } {
59
+ const urls: string[] = [];
60
+ vi.stubGlobal(
61
+ "fetch",
62
+ vi.fn(async (input: string | URL | Request) => {
63
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
64
+ urls.push(url);
65
+ if (!url.includes("/model-registry")) {
66
+ throw new Error(`hermetic run attempted a non-registry network call: ${url}`);
67
+ }
68
+ return { ok: true, status: 200, json: async () => REGISTRY_DOCUMENT } as unknown as Response;
69
+ }),
70
+ );
71
+ return { urls, restore: () => vi.unstubAllGlobals() };
72
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The module specifiers a TypeScript file names, read off its syntax tree —
3
+ * the primitive behind the runner's import fences.
4
+ *
5
+ * Two fences stand on it: `src/harness/` and production `src/shared/` never
6
+ * import `src/activities/` (`harness/__tests__/import-direction.test.ts`,
7
+ * the rule the turn runtime is written under), and the Cursor adapter never
8
+ * imports `@temporalio/*` (`execute-cursor/__tests__/adapter-is-temporal-
9
+ * free.test.ts`, what lets the contract kit run it outside an activity).
10
+ *
11
+ * A syntax tree, not a regex, so a path quoted in a header comment cannot
12
+ * trip a fence, and no import form can slip past one: static, type-only,
13
+ * side-effect, re-export and dynamic `import()` are all module specifiers to
14
+ * the parser. A dynamic import whose argument is not a string literal names
15
+ * nothing and is ignored (no fence has a literal to match).
16
+ */
17
+
18
+ import { readdirSync, readFileSync } from "node:fs";
19
+ import { dirname, join, resolve, sep } from "node:path";
20
+ import ts from "typescript";
21
+
22
+ /** Every module specifier a file names, in source order, whatever the form. */
23
+ export function moduleSpecifiers(fileName: string, source: string): string[] {
24
+ const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, /* setParentNodes */ false);
25
+ const specifiers: string[] = [];
26
+ const visit = (node: ts.Node): void => {
27
+ if (
28
+ (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) &&
29
+ node.moduleSpecifier !== undefined &&
30
+ ts.isStringLiteral(node.moduleSpecifier)
31
+ ) {
32
+ specifiers.push(node.moduleSpecifier.text);
33
+ } else if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
34
+ const [argument] = node.arguments;
35
+ if (argument !== undefined && ts.isStringLiteralLike(argument)) {
36
+ specifiers.push(argument.text);
37
+ }
38
+ }
39
+ ts.forEachChild(node, visit);
40
+ };
41
+ visit(sourceFile);
42
+ return specifiers;
43
+ }
44
+
45
+ /**
46
+ * The relative specifiers in `source` that resolve to a path inside
47
+ * `forbiddenRoot`. Package specifiers are ignored: no alias maps a package
48
+ * name onto a source directory.
49
+ */
50
+ export function forbiddenImports(filePath: string, source: string, forbiddenRoot: string): string[] {
51
+ return moduleSpecifiers(filePath, source).filter((specifier) => {
52
+ if (!specifier.startsWith(".")) return false;
53
+ const target = resolve(dirname(filePath), specifier);
54
+ return target === forbiddenRoot || target.startsWith(forbiddenRoot + sep);
55
+ });
56
+ }
57
+
58
+ /** The package specifiers in `source` whose name is `pkg` or starts with `${pkg}/`. */
59
+ export function packageImports(filePath: string, source: string, pkg: string): string[] {
60
+ return moduleSpecifiers(filePath, source).filter((specifier) => specifier === pkg || specifier.startsWith(`${pkg}/`));
61
+ }
62
+
63
+ /** Every `.ts` file under `dir`, sorted, skipping the named directories at any depth. */
64
+ export function typeScriptFilesUnder(dir: string, skipDirectories: ReadonlySet<string>): string[] {
65
+ const files: string[] = [];
66
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
67
+ const path = join(dir, entry.name);
68
+ if (entry.isDirectory()) {
69
+ if (!skipDirectories.has(entry.name)) {
70
+ files.push(...typeScriptFilesUnder(path, skipDirectories));
71
+ }
72
+ } else if (entry.name.endsWith(".ts")) {
73
+ files.push(path);
74
+ }
75
+ }
76
+ return files.sort();
77
+ }
78
+
79
+ /** Read a file for a sweep; one place so every fence reads the same bytes the compiler does. */
80
+ export function readSource(filePath: string): string {
81
+ return readFileSync(filePath, "utf-8");
82
+ }