@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
@@ -7,8 +7,8 @@
7
7
  * half: what an adapter owes the runtime through `runTurn`, `boot`,
8
8
  * `shutdown` and `releaseSession`. The runtime-side half (the throw-vs-return
9
9
  * table end to end, the whole-activity heartbeat, the single persist
10
- * chokepoint, the byte-pinned copy) is proven against the real runtime once
11
- * it exists, through the hermetic activity driver.
10
+ * chokepoint, the byte-pinned copy) is `runtime-contract.ts`, proven against
11
+ * the real runtime under `MockActivityEnvironment` with the same subjects.
12
12
  *
13
13
  * The kit IS the runtime stand-in. {@link ExecutionDriver} does what the
14
14
  * runtime and the server do around a turn: builds the `TurnInput`, threads
@@ -16,10 +16,17 @@
16
16
  * the id the adapter bound; one fixed id for a deterministic harness),
17
17
  * advances `turnSeq`, records a user's decision the way `SubmitApproval` does
18
18
  * (on the persisted row's `approvalAction`, the one copy) and derives the
19
- * decisions map from those rows on the next invocation, hands each
20
- * reinvocation a CLONE of the previous status (the runtime persists and reads
21
- * back; nothing survives by object identity), and owns the sink. The subject
22
- * owns the engine.
19
+ * decisions map from those rows on the next invocation with the runtime's
20
+ * own reader, hands each reinvocation a CLONE of the previous status on BOTH
21
+ * the sink and the record (`execution.status` the runtime seeds the one
22
+ * from the other, and an adapter may read the record's copy for its facts;
23
+ * nothing survives by object identity), and owns the sink. The subject owns
24
+ * the engine, and tells the kit when that engine is parked on a hang, which
25
+ * is the only moment the kit stops a turn from the outside: a real engine
26
+ * reaches its hang after real setup, and a stop delivered earlier lands in
27
+ * the setup and proves nothing. The kit also owns the adapter's lifetime —
28
+ * booted before the first turn, shut down after the last — because the
29
+ * registry does, and a real adapter refuses a turn before boot.
23
30
  *
24
31
  * ── Invariant catalog ───────────────────────────────────────────────────────
25
32
  * 1. Every exit is a `TurnOutcome`. `runTurn` resolves, never rejects, under
@@ -27,9 +34,12 @@
27
34
  * never escapes (the runtime, not the adapter, throws it).
28
35
  * 2. A proposal is a WAITING_APPROVAL row on `sink.status` when
29
36
  * `awaiting_approval` resolves, and never executes in that turn.
30
- * 3. Reinvoked with APPROVE for that id → executes exactly once; with REJECT
31
- * or SKIP never; reinvoked again after the approval still once.
32
- * 4. Aborting `stopSignal` mid-turn settles `runTurn` as `interrupted` within
37
+ * 3. Reinvoked with APPROVE for that id → executes exactly once and the row
38
+ * is carried to COMPLETED; with REJECT or SKIPnever executes and the
39
+ * turn continues (the row's terminal status is the runtime's to write,
40
+ * not the adapter's — Q-M4-1); reinvoked again after the approval → still
41
+ * once, never re-gated.
42
+ * 4. Aborting `stopSignal` mid-hang settles `runTurn` as `interrupted` within
33
43
  * {@link INTERRUPT_SETTLE_BOUND_MS} and nothing after the stop executes; a
34
44
  * signal aborted BEFORE `runTurn` yields `interrupted` with nothing done.
35
45
  * 5. Usage reaches the sink as non-negative deltas summing to what the engine
@@ -38,9 +48,9 @@
38
48
  * adapter binds before its first persist and resumes by the bound id, and
39
49
  * surfaces a rejected bind as `failed` with nothing done after it; a
40
50
  * `deterministic` adapter never binds.
41
- * 7. Lifetimes: `boot` then `shutdown` resolve; `releaseSession` for a served
42
- * session and for an unknown one both resolve; `shutdown` after a release
43
- * resolves.
51
+ * 7. Lifetimes: `releaseSession` for a served session and for an unknown one
52
+ * both resolve; `shutdown` after a release resolves; `boot` after
53
+ * `shutdown` resolves (a worker restart re-boots the same module state).
44
54
  * 8. One adapter object serves concurrent turns independently: a hanging turn
45
55
  * and a completing turn on the same adapter never cross outcomes or
46
56
  * execution counts.
@@ -48,7 +58,8 @@
48
58
  * Every invariant is an exported plain async function first and an `it`
49
59
  * block second, so `__tests__/harness-contract-self-check.test.ts` can run
50
60
  * each one against a deliberately broken adapter and prove it fires — a kit
51
- * that cannot fail proves nothing. Every message names the subject.
61
+ * that cannot fail proves nothing. Every message names the subject. Each
62
+ * function requires a booted adapter; {@link describeHarnessContract} boots.
52
63
  *
53
64
  * Above the contract line the pause primitives are indistinguishable: both
54
65
  * end a turn `awaiting_approval`, both take the decisions on reinvocation.
@@ -56,19 +67,21 @@
56
67
  * runner test runs the fake under both to prove it.
57
68
  */
58
69
 
59
- import { describe, it, expect } from "vitest";
70
+ import { afterAll, beforeAll, describe, it, expect } from "vitest";
60
71
  import { CancelledFailure } from "@temporalio/activity";
61
72
  import { clone } from "@bufbuild/protobuf";
62
73
  import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
63
74
  import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
64
75
  import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
65
76
 
77
+ import { approvalDecisionsOf } from "../../harness/turn-context.js";
66
78
  import type { TurnInput, TurnOutcome, UsageDelta } from "../../harness/types.js";
67
79
  import type { ProposedAction } from "../approval-contract/types.js";
68
80
  import { emptyStatus, findToolCallRow } from "../proto-helpers.js";
81
+ import { turnInputFixture } from "../turn-input-fixture.js";
69
82
  import { RecordingTurnSink } from "./recording-sink.js";
70
83
  import { scenario } from "./types.js";
71
- import type { HarnessContractSubject, TurnScenario } from "./types.js";
84
+ import type { EngineView, HarnessContractSubject, TurnScenario } from "./types.js";
72
85
 
73
86
  /**
74
87
  * How long a turn may take to settle `interrupted` after its signal aborts.
@@ -79,7 +92,7 @@ import type { HarnessContractSubject, TurnScenario } from "./types.js";
79
92
  */
80
93
  export const INTERRUPT_SETTLE_BOUND_MS = 5_000;
81
94
 
82
- const OUTCOME_KINDS = ["completed", "awaiting_approval", "failed", "interrupted"] as const satisfies readonly TurnOutcome["kind"][];
95
+ const OUTCOME_KINDS = ["completed", "cancelled", "awaiting_approval", "failed", "interrupted"] as const satisfies readonly TurnOutcome["kind"][];
83
96
 
84
97
  /** Representative gated action shared by the invariants. */
85
98
  const WRITE_ALPHA: ProposedAction = { kind: "write", resource: "/work/alpha.txt" };
@@ -95,8 +108,12 @@ export interface TurnRun {
95
108
  export interface TurnOptions {
96
109
  /** A prepared sink (e.g. one whose bind rejects); defaults to a fresh one over the threaded status. */
97
110
  readonly sink?: RecordingTurnSink;
98
- /** Called synchronously once `runTurn` has been entered, with the live sink — the hook that stops a turn mid-flight. */
99
- readonly onStarted?: (sink: RecordingTurnSink) => void;
111
+ /**
112
+ * Abort the sink's signal with this reason the moment the engine reports
113
+ * it is parked on a `hang` — the one way the kit stops a turn from the
114
+ * outside (see the header on why not sooner).
115
+ */
116
+ readonly stopWhenHanging?: string;
100
117
  }
101
118
 
102
119
  export interface TurnInFlight {
@@ -105,25 +122,6 @@ export interface TurnInFlight {
105
122
  readonly settled: Promise<TurnOutcome>;
106
123
  }
107
124
 
108
- /**
109
- * The runtime's one reader of approval decisions, as the two harnesses agree
110
- * on it today: every tool-call row still WAITING_APPROVAL whose
111
- * `approvalAction` the server has set. The runtime extraction exports this
112
- * from `src/harness/` and this kit imports it from there; until then the kit
113
- * carries the definition so the data flow it drives is the real one.
114
- */
115
- function approvalDecisionsOf(status: AgentExecutionStatus): ReadonlyMap<string, ApprovalAction> {
116
- const decisions = new Map<string, ApprovalAction>();
117
- for (const message of status.messages) {
118
- for (const row of message.toolCalls) {
119
- if (row.status === ToolCallStatus.TOOL_CALL_WAITING_APPROVAL && row.approvalAction !== ApprovalAction.UNSPECIFIED) {
120
- decisions.set(row.id, row.approvalAction);
121
- }
122
- }
123
- }
124
- return decisions;
125
- }
126
-
127
125
  /**
128
126
  * One execution's worth of turns against one subject, doing the runtime's
129
127
  * and the server's bookkeeping between them. A new driver per test: it
@@ -148,17 +146,34 @@ export class ExecutionDriver {
148
146
 
149
147
  /** Start a turn without awaiting it, for invariants about turns in flight. */
150
148
  begin(turn: TurnScenario, options: TurnOptions = {}): TurnInFlight {
151
- this.subject.arrange(turn);
152
149
  const sink = options.sink ?? new RecordingTurnSink({ status: this.seedStatus() });
153
- const input: TurnInput = {
150
+ // The engine's view and the record's copy both derive from the status
151
+ // this turn folds into, so a prepared sink's status is honoured too.
152
+ const view: EngineView = {
154
153
  executionId: this.executionId,
155
- threadId: this.threadId,
156
- turnSeq: this.turnSeq,
157
154
  sessionId: this.sessionId,
158
155
  approvalDecisions: approvalDecisionsOf(sink.status),
159
156
  };
157
+ this.subject.arrange(turn, view);
158
+ // Registered BEFORE the turn is entered so a hang reached synchronously
159
+ // is still observed; the abort itself lands when the engine reports.
160
+ const parked = options.stopWhenHanging !== undefined ? this.subject.whenHanging() : undefined;
161
+ // The whole resolved record: the subject's needs under the driver's five
162
+ // facts and the persisted status (`turn-input-fixture.ts`).
163
+ const input: TurnInput = turnInputFixture({
164
+ ...this.subject.inputOverrides?.(view),
165
+ executionId: this.executionId,
166
+ threadId: this.threadId,
167
+ turnSeq: this.turnSeq,
168
+ sessionId: this.sessionId,
169
+ approvalDecisions: view.approvalDecisions,
170
+ persistedStatus: clone(AgentExecutionStatusSchema, sink.status),
171
+ });
160
172
  const settled = this.subject.adapter.runTurn(input, sink);
161
- options.onStarted?.(sink);
173
+ if (parked && options.stopWhenHanging !== undefined) {
174
+ const reason = options.stopWhenHanging;
175
+ void parked.then(() => sink.abort(reason));
176
+ }
162
177
  return { input, sink, settled };
163
178
  }
164
179
 
@@ -186,7 +201,11 @@ export class ExecutionDriver {
186
201
  decide(toolCallId: string, action: ApprovalAction): void {
187
202
  const row = this.persisted ? findToolCallRow(this.persisted, toolCallId) : undefined;
188
203
  if (!row || row.status !== ToolCallStatus.TOOL_CALL_WAITING_APPROVAL) {
189
- throw new Error(`${this.subject.name}: kit bug — decide('${toolCallId}') but no WAITING row was persisted`);
204
+ const rows = (this.persisted?.messages ?? [])
205
+ .flatMap((m) => m.toolCalls)
206
+ .map((tc) => `${tc.id}=${ToolCallStatus[tc.status]}`)
207
+ .join(", ");
208
+ throw new Error(`${this.subject.name}: kit bug — decide('${toolCallId}') but no WAITING row was persisted (rows: ${rows || "none"})`);
190
209
  }
191
210
  row.approvalAction = action;
192
211
  }
@@ -233,13 +252,6 @@ async function settleWithinBound<T>(subject: HarnessContractSubject, settled: Pr
233
252
  }
234
253
  }
235
254
 
236
- /** Stop the turn on the next macrotask, after the adapter has reached whatever it was going to park on. */
237
- function stopSoon(reason: string): (sink: RecordingTurnSink) => void {
238
- return (sink) => {
239
- setImmediate(() => sink.abort(reason));
240
- };
241
- }
242
-
243
255
  async function expectResolves(subject: HarnessContractSubject, call: Promise<void>, what: string): Promise<void> {
244
256
  try {
245
257
  await call;
@@ -263,7 +275,7 @@ export async function assertEveryExitIsAnOutcome(subject: HarnessContractSubject
263
275
  { when: "on a usage-only turn", turn: [scenario.usage({ inputTokens: 1, outputTokens: 1 })] },
264
276
  { when: "on an undecided proposal", turn: [scenario.propose("inv1-call", WRITE_ALPHA)] },
265
277
  { when: "on an engine failure", turn: [scenario.fail("engine exploded")] },
266
- { when: "on a hang stopped from the outside", turn: [scenario.hang()], options: { onStarted: stopSoon("kit: stop") } },
278
+ { when: "on a hang stopped from the outside", turn: [scenario.hang()], options: { stopWhenHanging: "kit: stop" } },
267
279
  ];
268
280
  for (const play of plays) {
269
281
  const { sink, settled } = driver.begin(play.turn, play.options);
@@ -304,10 +316,14 @@ export async function assertProposalIsWaitingAndUnexecuted(subject: HarnessContr
304
316
 
305
317
  /**
306
318
  * Invariant 3: the decision is honoured exactly. APPROVE executes once and
307
- * only once, however many times the same decided row is seen again; REJECT
308
- * and SKIP never execute and carry the row to SKIPPED (the enum's own
309
- * transition). The reinvocation sees a CLONE of the persisted status, as it
310
- * would from the server.
319
+ * only once, however many times the same decided row is seen again, and the
320
+ * engine's completion reaches the row; REJECT and SKIP never execute and the
321
+ * turn continues the row's terminal status is NOT asserted here because
322
+ * it is not the adapter's to write (the decision is the server's field and
323
+ * the WAITING → SKIPPED transition follows from it with no engine knowledge,
324
+ * so it is the runtime's; S2 M4 Q-M4-1, the runtime's arm lands in S3). The
325
+ * reinvocation sees a CLONE of the persisted status, as it would from the
326
+ * server.
311
327
  */
312
328
  export async function assertDecisionsExecuteExactlyOnce(subject: HarnessContractSubject): Promise<void> {
313
329
  const driver = new ExecutionDriver(subject, "inv3");
@@ -334,7 +350,7 @@ export async function assertDecisionsExecuteExactlyOnce(subject: HarnessContract
334
350
  const decided = await driver.turn([scenario.propose(id, WRITE_ALPHA), scenario.say("moving on")]);
335
351
  expect(decided.outcome.kind, `${subject.name}: after ${label} the turn must continue to completion`).toBe("completed");
336
352
  expect(subject.executionCount(id), `${subject.name}: a ${label}-ed action must never execute`).toBe(0);
337
- expect(findToolCallRow(decided.sink.status, id)?.status, `${subject.name}: a ${label}-ed row must be carried to SKIPPED`).toBe(ToolCallStatus.TOOL_CALL_SKIPPED);
353
+ expect(findToolCallRow(decided.sink.status, id)?.status, `${subject.name}: a ${label}-ed action must never be reported as executed`).not.toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
338
354
  }
339
355
  }
340
356
 
@@ -342,9 +358,11 @@ export async function assertDecisionsExecuteExactlyOnce(subject: HarnessContract
342
358
 
343
359
  /**
344
360
  * Invariant 4: the stop signal is honoured promptly and completely. A turn
345
- * stopped while hanging settles `interrupted` within the bound and executes
346
- * nothing that came after the hang, even an already-approved action; a turn
347
- * entered under an aborted signal does nothing at all.
361
+ * stopped WHILE HANGING (the engine has reported it is parked, so the stop
362
+ * lands in the hang and not in the adapter's setup) settles `interrupted`
363
+ * within the bound and executes nothing that came after the hang, even an
364
+ * already-approved action; a turn entered under an aborted signal does
365
+ * nothing at all.
348
366
  */
349
367
  export async function assertStopSignalInterrupts(subject: HarnessContractSubject, boundMs = INTERRUPT_SETTLE_BOUND_MS): Promise<void> {
350
368
  const driver = new ExecutionDriver(subject, "inv4");
@@ -354,7 +372,7 @@ export async function assertStopSignalInterrupts(subject: HarnessContractSubject
354
372
  expect(proposed.outcome.kind, `${subject.name}: proposal must end awaiting_approval`).toBe("awaiting_approval");
355
373
  driver.decide(id, ApprovalAction.APPROVE);
356
374
 
357
- const hanging = driver.begin([scenario.say("working"), scenario.hang(), scenario.propose(id, WRITE_ALPHA)], { onStarted: stopSoon("kit: user pause") });
375
+ const hanging = driver.begin([scenario.say("working"), scenario.hang(), scenario.propose(id, WRITE_ALPHA)], { stopWhenHanging: "kit: user pause" });
358
376
  const outcome = await settleWithinBound(subject, hanging.settled, "after stopSignal aborted mid-hang", boundMs);
359
377
  expect(outcome.kind, `${subject.name}: a turn stopped mid-hang must settle interrupted`).toBe("interrupted");
360
378
  expect(subject.executionCount(id), `${subject.name}: nothing after the stop may execute, even an approved action`).toBe(0);
@@ -371,22 +389,31 @@ export async function assertStopSignalInterrupts(subject: HarnessContractSubject
371
389
 
372
390
  // ── Invariant 5 ─────────────────────────────────────────────────────────────
373
391
 
374
- function sumUsage(deltas: readonly UsageDelta[]): Required<UsageDelta> {
375
- const total = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
392
+ /** The four counts summed; the price and its basis are the adapter's per delta and are not summed here. */
393
+ interface UsageTotals {
394
+ inputTokens: number;
395
+ outputTokens: number;
396
+ cacheReadTokens: number;
397
+ cacheWriteTokens: number;
398
+ }
399
+
400
+ const TOKEN_COUNT_FIELDS = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"] as const satisfies readonly (keyof UsageTotals)[];
401
+
402
+ function sumUsage(deltas: readonly UsageDelta[]): UsageTotals {
403
+ const total: UsageTotals = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
376
404
  for (const d of deltas) {
377
- total.inputTokens += d.inputTokens ?? 0;
378
- total.outputTokens += d.outputTokens ?? 0;
379
- total.cacheReadTokens += d.cacheReadTokens ?? 0;
380
- total.cacheWriteTokens += d.cacheWriteTokens ?? 0;
405
+ for (const field of TOKEN_COUNT_FIELDS) total[field] += d[field] ?? 0;
381
406
  }
382
407
  return total;
383
408
  }
384
409
 
385
410
  /**
386
- * Invariant 5: usage reaches the sink as deltas — non-negative, and summing
387
- * to what the engine emitted. An adapter that reports cumulative totals
388
- * instead of deltas would double-count in the runtime's accumulator and trip
389
- * the cost cap early; one that reports a negative number would credit it.
411
+ * Invariant 5: usage reaches the sink as deltas — the token counts and the
412
+ * price non-negative, and the counts summing to what the engine emitted. An
413
+ * adapter that reports cumulative totals instead of deltas would
414
+ * double-count in the runtime's accumulator and trip the cost cap early; one
415
+ * that reports a negative number would credit it. The basis a delta names
416
+ * (`model`, `requestedModelParams`) is the adapter's own and not judged here.
390
417
  */
391
418
  export async function assertUsageReachesSinkAsDeltas(subject: HarnessContractSubject): Promise<void> {
392
419
  const driver = new ExecutionDriver(subject, "inv5");
@@ -399,7 +426,9 @@ export async function assertUsageReachesSinkAsDeltas(subject: HarnessContractSub
399
426
  expect(outcome.kind, `${subject.name}: a usage-reporting turn must complete`).toBe("completed");
400
427
 
401
428
  for (const delta of sink.usageDeltas) {
402
- for (const [field, value] of Object.entries(delta)) {
429
+ for (const field of [...TOKEN_COUNT_FIELDS, "estimatedCostUsd"] as const) {
430
+ const value = delta[field];
431
+ if (value === undefined) continue;
403
432
  expect(value, `${subject.name}: usage delta field ${field} must be non-negative`).toBeGreaterThanOrEqual(0);
404
433
  }
405
434
  }
@@ -455,15 +484,15 @@ export async function assertStateIdCapabilityAgrees(subject: HarnessContractSubj
455
484
  // ── Invariant 7 ─────────────────────────────────────────────────────────────
456
485
 
457
486
  /**
458
- * Invariant 7: the three lifetimes resolve. Boot with the subject's config,
459
- * serve a turn, release the served session and an unknown one, shut down.
460
- * Each must resolve a rejection in any would fail a worker's boot, leak a
461
- * session's resources, or hang a drain.
487
+ * Invariant 7: the lifetimes resolve. On the booted adapter: serve a turn,
488
+ * release the served session and an unknown one, shut down, and boot again
489
+ * (a worker restart re-boots the same module state). Each must resolve a
490
+ * rejection in any would leak a session's resources, hang a drain, or fail
491
+ * the next worker's boot. Boot itself is proven by every turn before this
492
+ * one having run.
462
493
  */
463
494
  export async function assertLifetimesResolve(subject: HarnessContractSubject): Promise<void> {
464
495
  const { adapter } = subject;
465
- await expectResolves(subject, adapter.boot(subject.config), "boot(config)");
466
-
467
496
  const driver = new ExecutionDriver(subject, "inv7");
468
497
  const served = await driver.turn([scenario.say("served")]);
469
498
  expect(served.outcome.kind, `${subject.name}: a plain turn must complete`).toBe("completed");
@@ -471,23 +500,28 @@ export async function assertLifetimesResolve(subject: HarnessContractSubject): P
471
500
  await expectResolves(subject, adapter.releaseSession(served.input.sessionId), `releaseSession('${served.input.sessionId}') for a served session`);
472
501
  await expectResolves(subject, adapter.releaseSession("ses-never-served"), "releaseSession for an unknown session");
473
502
  await expectResolves(subject, adapter.shutdown(), "shutdown() after release");
503
+ await expectResolves(subject, adapter.boot(subject.config), "boot(config) after shutdown (a worker restart)");
474
504
  }
475
505
 
476
506
  // ── Invariant 8 ─────────────────────────────────────────────────────────────
477
507
 
478
508
  /**
479
509
  * Invariant 8: one adapter object, many turns. While one execution's turn
480
- * hangs, another execution's turns propose, get decided and execute on the
481
- * SAME adapter; stopping the hanging turn settles only it; nothing crosses.
482
- * This is "the adapter holds no per-turn state" made observable the
483
- * property `maxConcurrentActivities` relies on.
510
+ * hangs (reported parked before the other execution starts, so the two are
511
+ * truly concurrent and not merely queued), another execution's turns
512
+ * propose, get decided and execute on the SAME adapter; stopping the hanging
513
+ * turn settles only it; nothing crosses. This is "the adapter holds no
514
+ * per-turn state" made observable — the property `maxConcurrentActivities`
515
+ * relies on.
484
516
  */
485
517
  export async function assertConcurrentTurnsAreIndependent(subject: HarnessContractSubject, boundMs = INTERRUPT_SETTLE_BOUND_MS): Promise<void> {
486
518
  const hangingExecution = new ExecutionDriver(subject, "inv8-hanging");
487
519
  const busyExecution = new ExecutionDriver(subject, "inv8-busy");
488
520
  const id = "inv8-busy-write";
489
521
 
522
+ const parked = subject.whenHanging();
490
523
  const hanging = hangingExecution.begin([scenario.say("parked"), scenario.hang()]);
524
+ await settleWithinBound(subject, parked, "waiting for the first execution to park on its hang", boundMs);
491
525
 
492
526
  const proposed = await settleWithinBound(subject, busyExecution.turn([scenario.propose(id, WRITE_ALPHA)]), "on a second execution while another turn hangs", boundMs);
493
527
  expect(proposed.outcome.kind, `${subject.name}: a second execution's proposal must settle while another turn hangs`).toBe("awaiting_approval");
@@ -505,9 +539,20 @@ export async function assertConcurrentTurnsAreIndependent(subject: HarnessContra
505
539
 
506
540
  // ── The runnable suite ──────────────────────────────────────────────────────
507
541
 
508
- /** Register the adapter-side contract against one subject. */
542
+ /**
543
+ * Register the adapter-side contract against one subject. Boots the adapter
544
+ * once before the first invariant and shuts it down after the last, as the
545
+ * registry does around a worker's life (`bootHarnesses` / `shutdownHarnesses`).
546
+ */
509
547
  export function describeHarnessContract(subject: HarnessContractSubject): void {
510
548
  describe(`harness contract — ${subject.name}`, () => {
549
+ beforeAll(async () => {
550
+ await subject.adapter.boot(subject.config);
551
+ });
552
+ afterAll(async () => {
553
+ await subject.adapter.shutdown();
554
+ });
555
+
511
556
  it("settles every turn with a TurnOutcome and never rejects (invariant 1)", async () => {
512
557
  await assertEveryExitIsAnOutcome(subject);
513
558
  });
@@ -526,7 +571,7 @@ export function describeHarnessContract(subject: HarnessContractSubject): void {
526
571
  it(`binds its state id as its ${subject.adapter.capabilities.stateIdSource} capability declares (invariant 6)`, async () => {
527
572
  await assertStateIdCapabilityAgrees(subject);
528
573
  });
529
- it("boots, serves, releases and shuts down without a rejection (invariant 7)", async () => {
574
+ it("serves, releases, shuts down and re-boots without a rejection (invariant 7)", async () => {
530
575
  await assertLifetimesResolve(subject);
531
576
  });
532
577
  it("serves concurrent turns on one adapter object independently (invariant 8)", async () => {
@@ -21,13 +21,15 @@
21
21
  import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
22
22
 
23
23
  import type { TurnSink, UsageDelta } from "../../harness/types.js";
24
+ import { TimingRecorder } from "../../shared/cold-start-timing.js";
24
25
  import { emptyStatus } from "../proto-helpers.js";
25
26
 
26
27
  /** One call the adapter made on the sink, in the order it made it. */
27
28
  export type SinkEvent =
28
29
  | { readonly kind: "persist" }
29
- | { readonly kind: "activity" }
30
+ | { readonly kind: "activity"; readonly detail: string | undefined }
30
31
  | { readonly kind: "usage"; readonly delta: UsageDelta }
32
+ | { readonly kind: "progress"; readonly label: string }
31
33
  | { readonly kind: "bind"; readonly harnessStateId: string };
32
34
 
33
35
  export interface RecordingTurnSinkOptions {
@@ -40,6 +42,7 @@ export interface RecordingTurnSinkOptions {
40
42
  export class RecordingTurnSink implements TurnSink {
41
43
  readonly status: AgentExecutionStatus;
42
44
  readonly stopSignal: AbortSignal;
45
+ readonly setupTiming = new TimingRecorder();
43
46
 
44
47
  private readonly controller = new AbortController();
45
48
  private readonly log: SinkEvent[] = [];
@@ -56,18 +59,22 @@ export class RecordingTurnSink implements TurnSink {
56
59
  this.controller.abort(reason);
57
60
  }
58
61
 
59
- requestPersist(): void {
62
+ async requestPersist(): Promise<void> {
60
63
  this.log.push({ kind: "persist" });
61
64
  }
62
65
 
63
- recordActivity(): void {
64
- this.log.push({ kind: "activity" });
66
+ recordActivity(detail?: string): void {
67
+ this.log.push({ kind: "activity", detail });
65
68
  }
66
69
 
67
70
  reportUsage(delta: UsageDelta): void {
68
71
  this.log.push({ kind: "usage", delta });
69
72
  }
70
73
 
74
+ async reportProgress(label: string): Promise<void> {
75
+ this.log.push({ kind: "progress", label });
76
+ }
77
+
71
78
  async bindHarnessState(harnessStateId: string): Promise<void> {
72
79
  if (this.bindRejectsWith) throw this.bindRejectsWith;
73
80
  this.log.push({ kind: "bind", harnessStateId });
@@ -93,4 +100,8 @@ export class RecordingTurnSink implements TurnSink {
93
100
  get boundStateIds(): readonly string[] {
94
101
  return this.log.flatMap((e) => (e.kind === "bind" ? [e.harnessStateId] : []));
95
102
  }
103
+
104
+ get progressLabels(): readonly string[] {
105
+ return this.log.flatMap((e) => (e.kind === "progress" ? [e.label] : []));
106
+ }
96
107
  }