@stigmer/runner 3.14.0 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/execution-record-fixture.d.ts +47 -0
  3. package/dist/__test-utils__/execution-record-fixture.js +68 -0
  4. package/dist/__test-utils__/execution-record-fixture.js.map +1 -0
  5. package/dist/__test-utils__/harness-contract/types.d.ts +166 -0
  6. package/dist/__test-utils__/harness-contract/types.js +49 -0
  7. package/dist/__test-utils__/harness-contract/types.js.map +1 -0
  8. package/dist/__test-utils__/hermetic-activity.d.ts +268 -0
  9. package/dist/__test-utils__/hermetic-activity.js +385 -0
  10. package/dist/__test-utils__/hermetic-activity.js.map +1 -0
  11. package/dist/__test-utils__/mock-client.d.ts +13 -0
  12. package/dist/__test-utils__/mock-client.js +45 -0
  13. package/dist/__test-utils__/mock-client.js.map +1 -0
  14. package/dist/__test-utils__/mock-workspace.d.ts +15 -0
  15. package/dist/__test-utils__/mock-workspace.js +21 -0
  16. package/dist/__test-utils__/mock-workspace.js.map +1 -0
  17. package/dist/__test-utils__/model-registry-fixture.d.ts +56 -0
  18. package/dist/__test-utils__/model-registry-fixture.js +66 -0
  19. package/dist/__test-utils__/model-registry-fixture.js.map +1 -0
  20. package/dist/__test-utils__/turn-input-fixture.d.ts +60 -0
  21. package/dist/__test-utils__/turn-input-fixture.js +118 -0
  22. package/dist/__test-utils__/turn-input-fixture.js.map +1 -0
  23. package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +107 -0
  24. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +344 -0
  25. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -0
  26. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +20 -0
  27. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +58 -0
  28. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
  29. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +2 -32
  30. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
  31. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +148 -0
  32. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +257 -0
  33. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -0
  34. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +183 -0
  35. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +262 -0
  36. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -0
  37. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +138 -0
  38. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +191 -0
  39. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -0
  40. package/dist/activities/execute-cursor/adapter.d.ts +55 -0
  41. package/dist/activities/execute-cursor/adapter.js +119 -0
  42. package/dist/activities/execute-cursor/adapter.js.map +1 -0
  43. package/dist/activities/execute-cursor/approval-state.d.ts +2 -2
  44. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  45. package/dist/activities/execute-cursor/capture-flow.d.ts +15 -30
  46. package/dist/activities/execute-cursor/capture-flow.js +17 -26
  47. package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
  48. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +23 -0
  49. package/dist/activities/execute-cursor/cursor-capabilities.js +33 -0
  50. package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -0
  51. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +2 -2
  52. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -1
  53. package/dist/activities/execute-cursor/fetch-interceptor.d.ts +15 -25
  54. package/dist/activities/execute-cursor/fetch-interceptor.js +39 -33
  55. package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
  56. package/dist/activities/execute-cursor/http2-interceptor.d.ts +21 -17
  57. package/dist/activities/execute-cursor/http2-interceptor.js +38 -31
  58. package/dist/activities/execute-cursor/http2-interceptor.js.map +1 -1
  59. package/dist/activities/execute-cursor/message-translator.d.ts +28 -19
  60. package/dist/activities/execute-cursor/message-translator.js +76 -27
  61. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  62. package/dist/activities/execute-cursor/prompt-builder.d.ts +180 -11
  63. package/dist/activities/execute-cursor/prompt-builder.js +171 -0
  64. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  65. package/dist/activities/execute-cursor/rejection-capture.d.ts +3 -6
  66. package/dist/activities/execute-cursor/rejection-capture.js +5 -7
  67. package/dist/activities/execute-cursor/rejection-capture.js.map +1 -1
  68. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -4
  69. package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
  70. package/dist/activities/execute-cursor/turn-boundary.d.ts +1 -1
  71. package/dist/activities/execute-cursor/turn-settle.d.ts +49 -0
  72. package/dist/activities/execute-cursor/turn-settle.js +507 -0
  73. package/dist/activities/execute-cursor/turn-settle.js.map +1 -0
  74. package/dist/activities/execute-cursor/turn-setup.d.ts +173 -0
  75. package/dist/activities/execute-cursor/turn-setup.js +496 -0
  76. package/dist/activities/execute-cursor/turn-setup.js.map +1 -0
  77. package/dist/activities/execute-cursor/turn-stream.d.ts +41 -71
  78. package/dist/activities/execute-cursor/turn-stream.js +70 -135
  79. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  80. package/dist/activities/execute-cursor/turn.d.ts +43 -0
  81. package/dist/activities/execute-cursor/turn.js +222 -0
  82. package/dist/activities/execute-cursor/turn.js.map +1 -0
  83. package/dist/activities/execute-cursor/usage-pricing.d.ts +38 -0
  84. package/dist/activities/execute-cursor/usage-pricing.js +50 -0
  85. package/dist/activities/execute-cursor/usage-pricing.js.map +1 -0
  86. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +1 -1
  87. package/dist/activities/execute-deep-agent/approval-file-change.js +1 -1
  88. package/dist/activities/execute-deep-agent/environment.js +1 -1
  89. package/dist/activities/execute-deep-agent/environment.js.map +1 -1
  90. package/dist/activities/execute-deep-agent/setup.js +3 -2
  91. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  92. package/dist/activities/hydrate-workflow-execution.js +1 -1
  93. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  94. package/dist/config.d.ts +15 -0
  95. package/dist/config.js.map +1 -1
  96. package/dist/harness/capabilities.d.ts +87 -0
  97. package/dist/harness/capabilities.js +37 -0
  98. package/dist/harness/capabilities.js.map +1 -0
  99. package/dist/harness/persist-chokepoint.d.ts +74 -0
  100. package/dist/harness/persist-chokepoint.js +88 -0
  101. package/dist/harness/persist-chokepoint.js.map +1 -0
  102. package/dist/harness/registry.d.ts +106 -0
  103. package/dist/harness/registry.js +168 -0
  104. package/dist/harness/registry.js.map +1 -0
  105. package/dist/harness/run-turn.d.ts +53 -0
  106. package/dist/harness/run-turn.js +528 -0
  107. package/dist/harness/run-turn.js.map +1 -0
  108. package/dist/harness/stop-controller.d.ts +50 -0
  109. package/dist/harness/stop-controller.js +62 -0
  110. package/dist/harness/stop-controller.js.map +1 -0
  111. package/dist/harness/terminal-table.d.ts +141 -0
  112. package/dist/harness/terminal-table.js +239 -0
  113. package/dist/harness/terminal-table.js.map +1 -0
  114. package/dist/harness/turn-context.d.ts +523 -0
  115. package/dist/harness/turn-context.js +739 -0
  116. package/dist/harness/turn-context.js.map +1 -0
  117. package/dist/harness/types.d.ts +448 -0
  118. package/dist/harness/types.js +64 -0
  119. package/dist/harness/types.js.map +1 -0
  120. package/dist/harness/usage-accumulator.d.ts +68 -0
  121. package/dist/harness/usage-accumulator.js +101 -0
  122. package/dist/harness/usage-accumulator.js.map +1 -0
  123. package/dist/harness-adapters.d.ts +27 -0
  124. package/dist/harness-adapters.js +28 -0
  125. package/dist/harness-adapters.js.map +1 -0
  126. package/dist/main.js.map +1 -1
  127. package/dist/runner-manager.js +64 -48
  128. package/dist/runner-manager.js.map +1 -1
  129. package/dist/runner.d.ts +5 -4
  130. package/dist/runner.js +65 -64
  131. package/dist/runner.js.map +1 -1
  132. package/dist/shared/approval-policy.d.ts +1 -1
  133. package/dist/shared/approval-policy.js.map +1 -1
  134. package/dist/{activities/execute-cursor → shared}/attachment-resolver.d.ts +2 -2
  135. package/dist/{activities/execute-cursor → shared}/attachment-resolver.js +5 -5
  136. package/dist/shared/attachment-resolver.js.map +1 -0
  137. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.d.ts +7 -5
  138. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.js +3 -63
  139. package/dist/shared/blueprint-resolver.js.map +1 -0
  140. package/dist/shared/channel-attachment.d.ts +1 -1
  141. package/dist/shared/channel-attachment.js.map +1 -1
  142. package/dist/{activities/execute-cursor → shared}/cost-guard.d.ts +5 -1
  143. package/dist/{activities/execute-cursor → shared}/cost-guard.js +5 -1
  144. package/dist/shared/cost-guard.js.map +1 -0
  145. package/dist/{activities/execute-cursor → shared}/env-resolver.d.ts +1 -1
  146. package/dist/shared/env-resolver.js.map +1 -0
  147. package/dist/{activities/execute-cursor → shared}/exact-apply.d.ts +1 -1
  148. package/dist/{activities/execute-cursor → shared}/exact-apply.js +6 -6
  149. package/dist/shared/exact-apply.js.map +1 -0
  150. package/dist/shared/execution-context.d.ts +35 -0
  151. package/dist/shared/execution-context.js +38 -0
  152. package/dist/shared/execution-context.js.map +1 -0
  153. package/dist/{activities/execute-cursor → shared}/extract-structured-output.d.ts +5 -2
  154. package/dist/{activities/execute-cursor → shared}/extract-structured-output.js +9 -6
  155. package/dist/shared/extract-structured-output.js.map +1 -0
  156. package/dist/shared/filereview/capture.d.ts +1 -1
  157. package/dist/shared/filereview/capture.js +1 -1
  158. package/dist/shared/heartbeat.d.ts +13 -11
  159. package/dist/shared/heartbeat.js +14 -29
  160. package/dist/shared/heartbeat.js.map +1 -1
  161. package/dist/{activities/execute-cursor → shared}/persist-decision.d.ts +5 -2
  162. package/dist/{activities/execute-cursor → shared}/persist-decision.js +4 -1
  163. package/dist/shared/persist-decision.js.map +1 -0
  164. package/dist/{activities/execute-cursor → shared}/skill-resolver.d.ts +16 -3
  165. package/dist/{activities/execute-cursor → shared}/skill-resolver.js +12 -4
  166. package/dist/shared/skill-resolver.js.map +1 -0
  167. package/dist/shared/subagent-rows.d.ts +21 -0
  168. package/dist/shared/subagent-rows.js +34 -0
  169. package/dist/shared/subagent-rows.js.map +1 -0
  170. package/dist/shared/worker-shutdown.d.ts +43 -16
  171. package/dist/shared/worker-shutdown.js +28 -13
  172. package/dist/shared/worker-shutdown.js.map +1 -1
  173. package/dist/shared/workspace/platform-dir.d.ts +1 -1
  174. package/dist/shared/workspace/platform-dir.js +1 -1
  175. package/dist/shared/workspace/session-provision.d.ts +54 -0
  176. package/dist/{activities/execute-cursor/workspace-provision.js → shared/workspace/session-provision.js} +15 -12
  177. package/dist/shared/workspace/session-provision.js.map +1 -0
  178. package/package.json +4 -4
  179. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +325 -0
  180. package/src/__test-utils__/config-fixture.ts +63 -0
  181. package/src/__test-utils__/execution-record-fixture.ts +104 -0
  182. package/src/__test-utils__/harness-boot-order-child.ts +58 -0
  183. package/src/__test-utils__/harness-contract/contract.ts +581 -0
  184. package/src/__test-utils__/harness-contract/recording-sink.ts +107 -0
  185. package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
  186. package/src/__test-utils__/harness-contract/scripted-adapter.ts +349 -0
  187. package/src/__test-utils__/harness-contract/types.ts +176 -0
  188. package/src/__test-utils__/hermetic-activity.ts +502 -0
  189. package/src/__test-utils__/model-registry-fixture.ts +72 -0
  190. package/src/__test-utils__/module-specifiers.ts +82 -0
  191. package/src/__test-utils__/proto-helpers.ts +25 -0
  192. package/src/__test-utils__/turn-input-fixture.ts +155 -0
  193. package/src/__tests__/harness-boot-order.test.ts +77 -0
  194. package/src/__tests__/harness-contract.test.ts +27 -0
  195. package/src/__tests__/runner-manager.test.ts +18 -0
  196. package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +102 -0
  197. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +396 -0
  198. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +66 -0
  199. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +2 -43
  200. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +342 -0
  201. package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +368 -0
  202. package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +254 -0
  203. package/src/activities/execute-cursor/__tests__/adapter-is-temporal-free.test.ts +45 -0
  204. package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +90 -0
  205. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -8
  206. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +28 -1
  207. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +1 -1
  208. package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +64 -6
  209. package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +228 -0
  210. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +180 -0
  211. package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
  212. package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn1.status.json +55 -0
  213. package/src/activities/execute-cursor/__tests__/hermetic/goldens/deny-and-retry.turn2.status.json +77 -0
  214. package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json +126 -0
  215. package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +40 -0
  216. package/src/activities/execute-cursor/__tests__/hermetic/goldens/plain-turn.status.json +48 -0
  217. package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -0
  218. package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-fresh-agent.status.json +53 -0
  219. package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-transport-timeout.status.json +48 -0
  220. package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
  221. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-cancelled.status.json +36 -0
  222. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-error-non-retryable.status.json +37 -0
  223. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +38 -0
  224. package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +59 -0
  225. package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-call.status.json +68 -0
  226. package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +79 -0
  227. package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +42 -0
  228. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +170 -0
  229. package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +205 -0
  230. package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +171 -0
  231. package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +156 -0
  232. package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +286 -0
  233. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +332 -0
  234. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +193 -0
  235. package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +137 -0
  236. package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +185 -0
  237. package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +151 -0
  238. package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +65 -35
  239. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -40
  240. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +171 -0
  241. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -243
  242. package/src/activities/execute-cursor/__tests__/usage-pricing.test.ts +84 -0
  243. package/src/activities/execute-cursor/adapter.ts +138 -0
  244. package/src/activities/execute-cursor/approval-state.ts +2 -2
  245. package/src/activities/execute-cursor/capture-flow.ts +18 -41
  246. package/src/activities/execute-cursor/cursor-capabilities.ts +35 -0
  247. package/src/activities/execute-cursor/cursor-mcp-config.ts +2 -2
  248. package/src/activities/execute-cursor/fetch-interceptor.ts +48 -45
  249. package/src/activities/execute-cursor/http2-interceptor.ts +47 -34
  250. package/src/activities/execute-cursor/message-translator.ts +90 -45
  251. package/src/activities/execute-cursor/prompt-builder.ts +327 -12
  252. package/src/activities/execute-cursor/rejection-capture.ts +5 -12
  253. package/src/activities/execute-cursor/session-lifecycle.ts +1 -5
  254. package/src/activities/execute-cursor/turn-boundary.ts +1 -1
  255. package/src/activities/execute-cursor/turn-settle.ts +601 -0
  256. package/src/activities/execute-cursor/turn-setup.ts +645 -0
  257. package/src/activities/execute-cursor/turn-stream.ts +84 -210
  258. package/src/activities/execute-cursor/turn.ts +240 -0
  259. package/src/activities/execute-cursor/usage-pricing.ts +67 -0
  260. package/src/activities/execute-deep-agent/approval-file-change.ts +1 -1
  261. package/src/activities/execute-deep-agent/environment.ts +1 -1
  262. package/src/activities/execute-deep-agent/setup.ts +3 -2
  263. package/src/activities/hydrate-workflow-execution.ts +1 -1
  264. package/src/config.ts +13 -0
  265. package/src/harness/__tests__/import-direction.test.ts +168 -0
  266. package/src/harness/__tests__/persist-chokepoint.test.ts +146 -0
  267. package/src/harness/__tests__/registry.test.ts +168 -0
  268. package/src/harness/__tests__/run-turn.test.ts +165 -0
  269. package/src/harness/__tests__/stop-controller.test.ts +49 -0
  270. package/src/harness/__tests__/turn-context.test.ts +178 -0
  271. package/src/harness/__tests__/usage-accumulator.test.ts +83 -0
  272. package/src/harness/capabilities.ts +92 -0
  273. package/src/harness/persist-chokepoint.ts +112 -0
  274. package/src/harness/registry.ts +197 -0
  275. package/src/harness/run-turn.ts +618 -0
  276. package/src/harness/stop-controller.ts +83 -0
  277. package/src/harness/terminal-table.ts +276 -0
  278. package/src/harness/turn-context.ts +1028 -0
  279. package/src/harness/types.ts +473 -0
  280. package/src/harness/usage-accumulator.ts +122 -0
  281. package/src/harness-adapters.ts +30 -0
  282. package/src/main.ts +1 -1
  283. package/src/runner-manager.ts +68 -61
  284. package/src/runner.ts +67 -78
  285. package/src/shared/__tests__/attachment-naming.test.ts +1 -1
  286. package/src/{activities/execute-cursor → shared}/__tests__/attachment-resolver.test.ts +6 -4
  287. package/src/{activities/execute-cursor → shared}/__tests__/cost-guard.test.ts +2 -1
  288. package/src/{activities/execute-cursor → shared}/__tests__/exact-apply.test.ts +3 -3
  289. package/src/{activities/execute-cursor → shared}/__tests__/extract-structured-output.test.ts +8 -8
  290. package/src/{activities/execute-cursor → shared}/__tests__/persist-decision.test.ts +4 -2
  291. package/src/{activities/execute-cursor → shared}/__tests__/skill-resolver.test.ts +1 -1
  292. package/src/shared/__tests__/subagent-rows.test.ts +34 -0
  293. package/src/shared/__tests__/worker-shutdown.test.ts +44 -39
  294. package/src/shared/approval-policy.ts +1 -1
  295. package/src/{activities/execute-cursor → shared}/attachment-resolver.ts +6 -6
  296. package/src/{activities/execute-cursor → shared}/blueprint-resolver.ts +7 -78
  297. package/src/shared/channel-attachment.ts +1 -1
  298. package/src/{activities/execute-cursor → shared}/cost-guard.ts +5 -1
  299. package/src/{activities/execute-cursor → shared}/env-resolver.ts +1 -1
  300. package/src/{activities/execute-cursor → shared}/exact-apply.ts +7 -7
  301. package/src/shared/execution-context.ts +45 -0
  302. package/src/{activities/execute-cursor → shared}/extract-structured-output.ts +10 -7
  303. package/src/shared/filereview/capture.ts +1 -1
  304. package/src/shared/heartbeat.ts +13 -33
  305. package/src/{activities/execute-cursor → shared}/persist-decision.ts +5 -2
  306. package/src/{activities/execute-cursor → shared}/skill-resolver.ts +20 -6
  307. package/src/shared/subagent-rows.ts +39 -0
  308. package/src/shared/worker-shutdown.ts +55 -25
  309. package/src/{activities/execute-cursor/__tests__/workspace-provision.test.ts → shared/workspace/__tests__/session-provision.test.ts} +25 -28
  310. package/src/shared/workspace/platform-dir.ts +1 -1
  311. package/src/{activities/execute-cursor/workspace-provision.ts → shared/workspace/session-provision.ts} +29 -16
  312. package/dist/activities/execute-cursor/attachment-resolver.js.map +0 -1
  313. package/dist/activities/execute-cursor/blueprint-resolver.js.map +0 -1
  314. package/dist/activities/execute-cursor/cost-guard.js.map +0 -1
  315. package/dist/activities/execute-cursor/env-resolver.js.map +0 -1
  316. package/dist/activities/execute-cursor/exact-apply.js.map +0 -1
  317. package/dist/activities/execute-cursor/extract-structured-output.js.map +0 -1
  318. package/dist/activities/execute-cursor/index.d.ts +0 -209
  319. package/dist/activities/execute-cursor/index.js +0 -2345
  320. package/dist/activities/execute-cursor/index.js.map +0 -1
  321. package/dist/activities/execute-cursor/persist-decision.js.map +0 -1
  322. package/dist/activities/execute-cursor/skill-resolver.js.map +0 -1
  323. package/dist/activities/execute-cursor/usage-accumulator.d.ts +0 -89
  324. package/dist/activities/execute-cursor/usage-accumulator.js +0 -121
  325. package/dist/activities/execute-cursor/usage-accumulator.js.map +0 -1
  326. package/dist/activities/execute-cursor/workspace-provision.d.ts +0 -42
  327. package/dist/activities/execute-cursor/workspace-provision.js.map +0 -1
  328. package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +0 -164
  329. package/src/activities/execute-cursor/index.ts +0 -2876
  330. package/src/activities/execute-cursor/usage-accumulator.ts +0 -169
  331. /package/dist/{activities/execute-cursor → shared}/env-resolver.js +0 -0
@@ -12,7 +12,7 @@
12
12
  * path is written into the workspace itself.
13
13
  *
14
14
  * This module replaces the duplicated `getPlatformDir` helpers in
15
- * execute-cursor/skill-resolver.ts and execute-cursor/attachment-resolver.ts.
15
+ * execute-cursor/skill-resolver.ts and shared/attachment-resolver.ts.
16
16
  */
17
17
  /**
18
18
  * Compute the WORKSPACE-scoped HITL gate directory — the home for the STABLE
@@ -12,7 +12,7 @@
12
12
  * path is written into the workspace itself.
13
13
  *
14
14
  * This module replaces the duplicated `getPlatformDir` helpers in
15
- * execute-cursor/skill-resolver.ts and execute-cursor/attachment-resolver.ts.
15
+ * execute-cursor/skill-resolver.ts and shared/attachment-resolver.ts.
16
16
  */
17
17
  import { join } from "node:path";
18
18
  import { mkdir } from "node:fs/promises";
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Local workspace provisioning for a harness turn.
3
+ *
4
+ * Harness-agnostic: the Cursor activity is today's only caller, the turn
5
+ * runtime takes it over in S2 M3, and S3 retires the native twin
6
+ * (execute-deep-agent/setup.ts `provisionWorkspace`). Agents run LOCAL
7
+ * (cloud is disabled — see execute-cursor/cursor-mode.ts), so the runner
8
+ * must clone git-repo workspace entries itself and mount local-path entries
9
+ * before the agent runs. Cloud agents previously cloned git repos
10
+ * server-side; that path is no longer used.
11
+ */
12
+ import type { Config } from "../../config.js";
13
+ import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
14
+ import type { ProvisionResult, WorkspaceBackend } from "./types.js";
15
+ /**
16
+ * The two runner settings provisioning reads. A `Pick` over `Config` rather
17
+ * than a fresh options type so a caller holding the whole `Config` (the turn
18
+ * orchestrator, and the runtime that replaces it) passes it unchanged, while
19
+ * the signature states exactly what is consumed: the root every workspace
20
+ * resolves under, and the mode that decides whether git entries are cloned
21
+ * or expected in place. A test constructs the two fields and nothing else.
22
+ */
23
+ export type SessionProvisionConfig = Pick<Config, "workspaceRootDir" | "mode">;
24
+ /** What {@link provisionSessionWorkspace} hands back to the harness. */
25
+ export interface SessionWorkspaceProvision {
26
+ /** The directories the agent should operate in (never empty). */
27
+ readonly workspaceDirs: string[];
28
+ /**
29
+ * Per-entry provision outcomes — carries the git metadata (repo URL, base
30
+ * branch, credential state) the write-back coordinator needs. Empty for a
31
+ * session with no workspace entries.
32
+ */
33
+ readonly provisionResults: ProvisionResult[];
34
+ /**
35
+ * The backend the entries were provisioned through — the write-back
36
+ * coordinator executes its git commands through it.
37
+ */
38
+ readonly workspaceBackend: WorkspaceBackend;
39
+ }
40
+ /**
41
+ * Provision the session's workspace entries for a LOCAL agent.
42
+ *
43
+ * Clones git-repo entries (using the user's GITHUB_TOKEN from the resolved
44
+ * execution environment) and mounts local-path entries, then returns the
45
+ * directories the agent should operate in — along with the provision results
46
+ * and backend the git write-back coordinator needs. A session with no
47
+ * workspace entries gets its own empty per-session directory (see
48
+ * session-root.ts) — never the shared root, which would leak other sessions'
49
+ * files into it.
50
+ *
51
+ * provisionGit is idempotent (it reuses an existing clone), so this is safe
52
+ * to call on every execution, including multi-turn and HITL reinvocations.
53
+ */
54
+ export declare function provisionSessionWorkspace(config: SessionProvisionConfig, session: Session, envVars: Record<string, string>, sessionId: string): Promise<SessionWorkspaceProvision>;
@@ -1,17 +1,20 @@
1
1
  /**
2
- * Local workspace provisioning for the Cursor harness.
2
+ * Local workspace provisioning for a harness turn.
3
3
  *
4
- * Cursor agents run LOCAL (cloud is disabled see cursor-mode.ts), so the
5
- * runner must clone git-repo workspace entries itself and mount local-path
6
- * entries before the agent runs, mirroring the native harness. Cloud agents
7
- * previously cloned git repos server-side; that path is no longer used.
4
+ * Harness-agnostic: the Cursor activity is today's only caller, the turn
5
+ * runtime takes it over in S2 M3, and S3 retires the native twin
6
+ * (execute-deep-agent/setup.ts `provisionWorkspace`). Agents run LOCAL
7
+ * (cloud is disabled see execute-cursor/cursor-mode.ts), so the runner
8
+ * must clone git-repo workspace entries itself and mount local-path entries
9
+ * before the agent runs. Cloud agents previously cloned git repos
10
+ * server-side; that path is no longer used.
8
11
  */
9
- import { WorkspaceProvisioner } from "../../shared/workspace/provisioner.js";
10
- import { LocalWorkspaceBackend } from "../../shared/workspace/local-backend.js";
11
- import { ensurePlatformDir } from "../../shared/workspace/platform-dir.js";
12
- import { resolveSessionWorkspaceRoot } from "../../shared/workspace/session-root.js";
12
+ import { WorkspaceProvisioner } from "./provisioner.js";
13
+ import { LocalWorkspaceBackend } from "./local-backend.js";
14
+ import { ensurePlatformDir } from "./platform-dir.js";
15
+ import { resolveSessionWorkspaceRoot } from "./session-root.js";
13
16
  /**
14
- * Provision the session's workspace entries for a LOCAL Cursor agent.
17
+ * Provision the session's workspace entries for a LOCAL agent.
15
18
  *
16
19
  * Clones git-repo entries (using the user's GITHUB_TOKEN from the resolved
17
20
  * execution environment) and mounts local-path entries, then returns the
@@ -24,7 +27,7 @@ import { resolveSessionWorkspaceRoot } from "../../shared/workspace/session-root
24
27
  * provisionGit is idempotent (it reuses an existing clone), so this is safe
25
28
  * to call on every execution, including multi-turn and HITL reinvocations.
26
29
  */
27
- export async function provisionCursorWorkspace(config, session, envVars, sessionId) {
30
+ export async function provisionSessionWorkspace(config, session, envVars, sessionId) {
28
31
  const entries = session.spec?.workspaceEntries ?? [];
29
32
  const platformDir = await ensurePlatformDir(sessionId);
30
33
  if (entries.length === 0) {
@@ -47,4 +50,4 @@ export async function provisionCursorWorkspace(config, session, envVars, session
47
50
  workspaceBackend: backend,
48
51
  };
49
52
  }
50
- //# sourceMappingURL=workspace-provision.js.map
53
+ //# sourceMappingURL=session-provision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-provision.js","sourceRoot":"","sources":["../../../src/shared/workspace/session-provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AA6BhE;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAA8B,EAC9B,OAAgB,EAChB,OAA+B,EAC/B,SAAiB;IAEjB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,gBAAgB,IAAI,EAAE,CAAC;IACrD,MAAM,WAAW,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAEvD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,WAAW,GAAG,MAAM,2BAA2B,CACnD,MAAM,CAAC,gBAAgB,EAAE,OAAO,EAAE,SAAS,CAC5C,CAAC;QACF,OAAO;YACL,aAAa,EAAE,CAAC,WAAW,CAAC;YAC5B,gBAAgB,EAAE,EAAE;YACpB,gBAAgB,EAAE,IAAI,qBAAqB,CAAC,WAAW,EAAE,WAAW,CAAC;SACtE,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,qBAAqB,CAAC,MAAM,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAEhF,MAAM,WAAW,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,YAAY,CAC5C,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,EACpE,OAAO,EACP,OAAO,EACP,MAAM,CAAC,IAAI,KAAK,OAAO,EACvB,MAAM,CAAC,IAAI,KAAK,OAAO,CACxB,CAAC;IAEF,MAAM,IAAI,GAAG,OAAO;SACjB,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC;SAC/B,MAAM,CAAC,CAAC,GAAG,EAAiB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAEhD,OAAO;QACL,aAAa,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC;QACjE,gBAAgB,EAAE,OAAO;QACzB,gBAAgB,EAAE,OAAO;KAC1B,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/runner",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "description": "Embeddable Temporal worker for the Stigmer AI agent platform — handles agent execution, workflow orchestration, and MCP server management",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -104,9 +104,9 @@
104
104
  "@opentelemetry/sdk-metrics": "^2.0.0",
105
105
  "@opentelemetry/sdk-trace-base": "^2.0.0",
106
106
  "@opentelemetry/sdk-trace-node": "^2.0.0",
107
- "@stigmer/protos": "3.14.0",
108
- "@stigmer/temporal-codecs": "3.14.0",
109
- "@stigmer/zip-structure": "3.14.0",
107
+ "@stigmer/protos": "3.15.0",
108
+ "@stigmer/temporal-codecs": "3.15.0",
109
+ "@stigmer/zip-structure": "3.15.0",
110
110
  "@temporalio/activity": "1.16.2",
111
111
  "@temporalio/common": "1.16.2",
112
112
  "@temporalio/interceptors-opentelemetry": "1.16.2",
@@ -0,0 +1,325 @@
1
+ /**
2
+ * The harness contract kit's self-check: proof that every invariant in
3
+ * `harness-contract/contract.ts` can FAIL.
4
+ *
5
+ * A contract kit that only ever passes proves nothing about the adapters it
6
+ * runs against — it might be asserting the wrong thing, or nothing. So each
7
+ * invariant is run here against an adapter deliberately broken in exactly the
8
+ * way that invariant exists to catch, and the kit must reject with a message
9
+ * that names the subject and the violation. The tester role's rule: a test
10
+ * that cannot fail is not a test.
11
+ *
12
+ * Every broken adapter wraps the honest scripted fake and breaks ONE thing,
13
+ * so a rejection here is attributable to the kit's assertion and not to some
14
+ * other defect of the double. Where an invariant races a hang against the
15
+ * interrupt bound, the bound is shortened through the assertion's parameter;
16
+ * the production bound stays what `contract.ts` declares. Every subject is
17
+ * booted first, as the kit's `describeHarnessContract` boots the adapters it
18
+ * runs (the assertion functions require it).
19
+ *
20
+ * The second half checks the kit's own driver — the runtime stand-in — for
21
+ * the facts a real adapter depends on and the fake never notices: the record
22
+ * carries the persisted status (an adapter may read its grants from
23
+ * `execution.status`, as the runtime guarantees), the engine's view carries
24
+ * the driver's ids and decisions, and a stop lands only once the engine
25
+ * reports it is parked.
26
+ */
27
+
28
+ import { describe, it, expect } from "vitest";
29
+ import { CancelledFailure } from "@temporalio/activity";
30
+ import { ApprovalAction, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
31
+
32
+ import type { HarnessAdapter, TurnInput, TurnOutcome, TurnSink } from "../../harness/types.js";
33
+ import {
34
+ ExecutionDriver,
35
+ assertConcurrentTurnsAreIndependent,
36
+ assertDecisionsExecuteExactlyOnce,
37
+ assertEveryExitIsAnOutcome,
38
+ assertLifetimesResolve,
39
+ assertProposalIsWaitingAndUnexecuted,
40
+ assertStateIdCapabilityAgrees,
41
+ assertStopSignalInterrupts,
42
+ assertUsageReachesSinkAsDeltas,
43
+ } from "../harness-contract/contract.js";
44
+ import { scriptedSubject } from "../harness-contract/scripted-adapter.js";
45
+ import type { ScriptedSubject } from "../harness-contract/scripted-adapter.js";
46
+ import { scenario } from "../harness-contract/types.js";
47
+ import type { EngineView, HarnessContractSubject, TurnScenario } from "../harness-contract/types.js";
48
+ import { findToolCallRow } from "../proto-helpers.js";
49
+
50
+ /** Short enough to keep this file fast; long enough that an honest microtask settle never trips it. */
51
+ const SHORT_BOUND_MS = 100;
52
+
53
+ // ── Wrapping helpers ────────────────────────────────────────────────────────
54
+
55
+ /** The kit boots before it asserts; so does every arm here. */
56
+ async function booted<S extends HarnessContractSubject>(subject: S): Promise<S> {
57
+ await subject.adapter.boot(subject.config);
58
+ return subject;
59
+ }
60
+
61
+ /** The honest fake's adapter with the named members replaced. */
62
+ function adapterOver(inner: HarnessAdapter, name: string, patch: Partial<HarnessAdapter>): HarnessAdapter {
63
+ return {
64
+ name,
65
+ capabilities: inner.capabilities,
66
+ boot: (config) => inner.boot(config),
67
+ shutdown: () => inner.shutdown(),
68
+ releaseSession: (sessionId) => inner.releaseSession(sessionId),
69
+ runTurn: (input, sink) => inner.runTurn(input, sink),
70
+ ...patch,
71
+ };
72
+ }
73
+
74
+ /** A sink that delegates to the real one except for the named members. */
75
+ function sinkOver(inner: TurnSink, patch: Partial<TurnSink>): TurnSink {
76
+ return {
77
+ status: inner.status,
78
+ stopSignal: inner.stopSignal,
79
+ setupTiming: inner.setupTiming,
80
+ requestPersist: () => inner.requestPersist(),
81
+ recordActivity: (detail) => inner.recordActivity(detail),
82
+ reportUsage: (delta) => inner.reportUsage(delta),
83
+ reportProgress: (label) => inner.reportProgress(label),
84
+ bindHarnessState: (id) => inner.bindHarnessState(id),
85
+ ...patch,
86
+ };
87
+ }
88
+
89
+ /** A subject over a broken adapter that still arranges and observes through the honest fake. */
90
+ function subjectOver(inner: ScriptedSubject, adapter: HarnessAdapter): HarnessContractSubject {
91
+ return {
92
+ name: adapter.name,
93
+ harness: inner.harness,
94
+ adapter,
95
+ config: inner.config,
96
+ arrange: (turn, view) => inner.arrange(turn, view),
97
+ whenHanging: () => inner.whenHanging(),
98
+ executionCount: (toolCallId) => inner.executionCount(toolCallId),
99
+ };
100
+ }
101
+
102
+ function honest(): ScriptedSubject {
103
+ return scriptedSubject({ pausePrimitive: "interrupt", stateIdSource: "engine-minted" });
104
+ }
105
+
106
+ // ── The broken adapters, one violation each ─────────────────────────────────
107
+
108
+ /** Throws the one thing an adapter must never throw. */
109
+ function throwsCancelledFailure(): HarnessContractSubject {
110
+ const inner = honest();
111
+ return subjectOver(inner, adapterOver(inner.adapter, "broken:throws-CancelledFailure", {
112
+ runTurn: async () => {
113
+ throw new CancelledFailure("adapter decided this was a pause");
114
+ },
115
+ }));
116
+ }
117
+
118
+ /** Treats every proposal as approved: executes what was never decided. */
119
+ function executesUndecidedProposals(): HarnessContractSubject {
120
+ const inner = honest();
121
+ let proposedIds: string[] = [];
122
+ const adapter = adapterOver(inner.adapter, "broken:executes-undecided", {
123
+ runTurn: (input, sink) => {
124
+ const approvalDecisions = new Map(input.approvalDecisions);
125
+ for (const id of proposedIds) if (!approvalDecisions.has(id)) approvalDecisions.set(id, ApprovalAction.APPROVE);
126
+ return inner.adapter.runTurn({ ...input, approvalDecisions }, sink);
127
+ },
128
+ });
129
+ return {
130
+ ...subjectOver(inner, adapter),
131
+ arrange: (turn: TurnScenario, view: EngineView) => {
132
+ proposedIds = turn.flatMap((step) => (step.kind === "propose" ? [step.toolCallId] : []));
133
+ inner.arrange(turn, view);
134
+ },
135
+ };
136
+ }
137
+
138
+ /** Re-gates a row an earlier invocation already settled — the second-ledger drift. */
139
+ function reGatesSettledRows(): HarnessContractSubject {
140
+ const inner = honest();
141
+ return subjectOver(inner, adapterOver(inner.adapter, "broken:re-gates-settled", {
142
+ runTurn: (input, sink) => {
143
+ for (const message of sink.status.messages) {
144
+ for (const row of message.toolCalls) {
145
+ if (row.status === ToolCallStatus.TOOL_CALL_COMPLETED) row.status = ToolCallStatus.TOOL_CALL_WAITING_APPROVAL;
146
+ }
147
+ }
148
+ return inner.adapter.runTurn(input, sink);
149
+ },
150
+ }));
151
+ }
152
+
153
+ /** Hands the engine a signal nobody will ever abort. */
154
+ function ignoresStopSignal(): HarnessContractSubject {
155
+ const inner = honest();
156
+ return subjectOver(inner, adapterOver(inner.adapter, "broken:ignores-stopSignal", {
157
+ runTurn: (input, sink) => inner.adapter.runTurn(input, sinkOver(sink, { stopSignal: new AbortController().signal })),
158
+ }));
159
+ }
160
+
161
+ /** Reports running totals where the contract asks for deltas. */
162
+ function reportsCumulativeUsage(): HarnessContractSubject {
163
+ const inner = honest();
164
+ return subjectOver(inner, adapterOver(inner.adapter, "broken:cumulative-usage", {
165
+ runTurn: (input, sink) => {
166
+ const total = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
167
+ return inner.adapter.runTurn(input, sinkOver(sink, {
168
+ reportUsage: (delta) => {
169
+ total.inputTokens += delta.inputTokens ?? 0;
170
+ total.outputTokens += delta.outputTokens ?? 0;
171
+ total.cacheReadTokens += delta.cacheReadTokens ?? 0;
172
+ total.cacheWriteTokens += delta.cacheWriteTokens ?? 0;
173
+ sink.reportUsage({ ...total });
174
+ },
175
+ }));
176
+ },
177
+ }));
178
+ }
179
+
180
+ /** Declares an engine-minted id and never binds one. */
181
+ function claimsEngineMintedNeverBinds(): HarnessContractSubject {
182
+ const inner = scriptedSubject({ pausePrimitive: "interrupt", stateIdSource: "deterministic" });
183
+ return subjectOver(inner, adapterOver(inner.adapter, "broken:claims-engine-minted", {
184
+ capabilities: { ...inner.adapter.capabilities, stateIdSource: "engine-minted" },
185
+ }));
186
+ }
187
+
188
+ /** Refuses to release a session it does not know. */
189
+ function refusesUnknownRelease(): HarnessContractSubject {
190
+ const inner = honest();
191
+ const served = new Set<string>();
192
+ return subjectOver(inner, adapterOver(inner.adapter, "broken:refuses-unknown-release", {
193
+ runTurn: (input, sink) => {
194
+ served.add(input.sessionId);
195
+ return inner.adapter.runTurn(input, sink);
196
+ },
197
+ releaseSession: async (sessionId) => {
198
+ if (!served.has(sessionId)) throw new Error(`unknown session ${sessionId}`);
199
+ await inner.adapter.releaseSession(sessionId);
200
+ },
201
+ }));
202
+ }
203
+
204
+ /** Runs turns one at a time through a single slot: per-turn state on the adapter object. */
205
+ function serializesTurns(): HarnessContractSubject {
206
+ const inner = honest();
207
+ let slot: Promise<unknown> = Promise.resolve();
208
+ return subjectOver(inner, adapterOver(inner.adapter, "broken:serializes-turns", {
209
+ runTurn: (input: TurnInput, sink: TurnSink): Promise<TurnOutcome> => {
210
+ const next = slot.then(() => inner.adapter.runTurn(input, sink));
211
+ slot = next.catch(() => undefined);
212
+ return next;
213
+ },
214
+ }));
215
+ }
216
+
217
+ // ── The proof ───────────────────────────────────────────────────────────────
218
+
219
+ describe("harness contract kit self-check — every invariant can fail", () => {
220
+ it("invariant 1 fires when runTurn throws a CancelledFailure", async () => {
221
+ await expect(assertEveryExitIsAnOutcome(await booted(throwsCancelledFailure()), SHORT_BOUND_MS)).rejects.toThrow(/broken:throws-CancelledFailure: runTurn rejected with a CancelledFailure/);
222
+ });
223
+
224
+ it("invariant 2 fires when an undecided proposal executes", async () => {
225
+ await expect(assertProposalIsWaitingAndUnexecuted(await booted(executesUndecidedProposals()))).rejects.toThrow(/broken:executes-undecided: .*awaiting_approval/);
226
+ });
227
+
228
+ it("invariant 3 fires when a settled row is re-gated on a later invocation", async () => {
229
+ await expect(assertDecisionsExecuteExactlyOnce(await booted(reGatesSettledRows()))).rejects.toThrow(/broken:re-gates-settled: .*must not re-gate/);
230
+ });
231
+
232
+ it("invariant 4 fires when the adapter ignores stopSignal", async () => {
233
+ await expect(assertStopSignalInterrupts(await booted(ignoresStopSignal()), SHORT_BOUND_MS)).rejects.toThrow(/broken:ignores-stopSignal: runTurn did not settle within 100ms/);
234
+ });
235
+
236
+ it("invariant 5 fires when usage is reported as running totals", async () => {
237
+ await expect(assertUsageReachesSinkAsDeltas(await booted(reportsCumulativeUsage()))).rejects.toThrow(/broken:cumulative-usage: .*must sum to what the engine emitted/);
238
+ });
239
+
240
+ it("invariant 6 fires when an adapter claims engine-minted and never binds", async () => {
241
+ await expect(assertStateIdCapabilityAgrees(await booted(claimsEngineMintedNeverBinds()))).rejects.toThrow(/broken:claims-engine-minted: .*must bind its state id/);
242
+ });
243
+
244
+ it("invariant 7 fires when releaseSession rejects an unknown session", async () => {
245
+ await expect(assertLifetimesResolve(await booted(refusesUnknownRelease()))).rejects.toThrow(/broken:refuses-unknown-release: releaseSession for an unknown session rejected/);
246
+ });
247
+
248
+ it("invariant 8 fires when the adapter serializes turns through shared state", async () => {
249
+ await expect(assertConcurrentTurnsAreIndependent(await booted(serializesTurns()), SHORT_BOUND_MS)).rejects.toThrow(/broken:serializes-turns: runTurn did not settle within 100ms/);
250
+ });
251
+ });
252
+
253
+ // ── The driver: the runtime stand-in's own guarantees ───────────────────────
254
+
255
+ const WRITE_BETA = { kind: "write", resource: "/work/beta.txt" } as const;
256
+
257
+ describe("harness contract kit self-check — the driver stands in for the runtime faithfully", () => {
258
+ it("hands the adapter the persisted status on the record AND the sink, as two clones", async () => {
259
+ const subject = await booted(honest());
260
+ const driver = new ExecutionDriver(subject, "driver-record");
261
+ const id = "driver-record-write";
262
+
263
+ const first = await driver.turn([scenario.propose(id, WRITE_BETA)]);
264
+ expect(first.outcome.kind).toBe("awaiting_approval");
265
+ expect(first.input.execution.status?.messages ?? [], "a first turn's record carries no persisted status").toHaveLength(0);
266
+ driver.decide(id, ApprovalAction.APPROVE);
267
+
268
+ const second = driver.begin([scenario.propose(id, WRITE_BETA)]);
269
+ const onRecord = second.input.execution.status;
270
+ expect(onRecord, "the reinvocation's record must carry what the previous turn persisted").toBeDefined();
271
+ expect(findToolCallRow(onRecord!, id)?.approvalAction, "the decision is on the record's row").toBe(ApprovalAction.APPROVE);
272
+ expect(findToolCallRow(second.sink.status, id)?.approvalAction, "and on the sink's row").toBe(ApprovalAction.APPROVE);
273
+ expect(onRecord, "the record's copy and the sink's copy are distinct objects, as after a real persist and read-back").not.toBe(second.sink.status);
274
+ expect(second.input.approvalDecisions.get(id), "the decisions map is the runtime's reader over the same rows").toBe(ApprovalAction.APPROVE);
275
+ await second.settled;
276
+ });
277
+
278
+ it("arranges the engine with the driver's ids and the decisions the prompt will carry", async () => {
279
+ const inner = await booted(honest());
280
+ const views: EngineView[] = [];
281
+ const subject: HarnessContractSubject = {
282
+ ...inner,
283
+ arrange: (turn, view) => {
284
+ views.push(view);
285
+ inner.arrange(turn, view);
286
+ },
287
+ };
288
+ const driver = new ExecutionDriver(subject, "driver-view");
289
+ const id = "driver-view-write";
290
+
291
+ await driver.turn([scenario.propose(id, WRITE_BETA)]);
292
+ driver.decide(id, ApprovalAction.SKIP);
293
+ await driver.turn([scenario.propose(id, WRITE_BETA), scenario.say("on")]);
294
+
295
+ expect(views.map((v) => v.executionId)).toEqual([driver.executionId, driver.executionId]);
296
+ expect(views.map((v) => v.sessionId)).toEqual([driver.sessionId, driver.sessionId]);
297
+ expect(views[0]!.approvalDecisions.size, "nothing decided before the first turn").toBe(0);
298
+ expect(views[1]!.approvalDecisions.get(id), "the reinvocation's view carries the decision").toBe(ApprovalAction.SKIP);
299
+ });
300
+
301
+ it("stops a turn only once the engine reports it is parked, never during setup", async () => {
302
+ const inner = await booted(honest());
303
+ let sinkInFlight: TurnSink | undefined;
304
+ let abortedWhenParked: boolean | undefined;
305
+ // Observes the signal at the instant the engine reports the hang; the
306
+ // driver's own abort is chained after this observer.
307
+ const subject: HarnessContractSubject = {
308
+ ...inner,
309
+ whenHanging: () =>
310
+ inner.whenHanging().then(() => {
311
+ abortedWhenParked = sinkInFlight?.stopSignal.aborted;
312
+ }),
313
+ };
314
+ const driver = new ExecutionDriver(subject, "driver-stop");
315
+
316
+ const turn = driver.begin([scenario.say("setting up"), scenario.hang(), scenario.say("never")], { stopWhenHanging: "kit: stop" });
317
+ sinkInFlight = turn.sink;
318
+ const outcome = await turn.settled;
319
+
320
+ expect(outcome.kind).toBe("interrupted");
321
+ expect(abortedWhenParked, "at the moment the engine reported the hang the signal was still live").toBe(false);
322
+ expect(turn.sink.stopSignal.aborted, "and it was aborted after").toBe(true);
323
+ expect(turn.sink.status.messages.map((m) => m.content), "the stop landed in the hang, after the setup step and before the step after it").toEqual(["setting up"]);
324
+ });
325
+ });
@@ -0,0 +1,63 @@
1
+ /**
2
+ * A complete, inert runner `Config` for tests.
3
+ *
4
+ * `Config` has twenty required fields (plus two optional token refs), so
5
+ * every test that needs one has had to spell all of them out; eight test
6
+ * files carry their own literal today. This is the one place a test-only `Config` is
7
+ * built from now on: {@link testConfig} returns the whole record with values
8
+ * that dial nothing (loopback endpoints on a port nothing listens on, no
9
+ * proxy, no token, the in-memory checkpointer, a temp workspace root) and
10
+ * takes a `Partial<Config>` for the fields a test cares about.
11
+ *
12
+ * Inert means: a component handed this config may READ every field, but a
13
+ * component that tries to ACT on one (connect, authenticate, spawn) fails
14
+ * loudly and immediately rather than reaching a real service. That is the
15
+ * posture the harness contract kit needs for `HarnessAdapter.boot(config)`,
16
+ * and the one the existing inline literals were reaching for one at a time.
17
+ *
18
+ * Not a `vi.mock`: `Config` is plain data, and a fixture that is plain data
19
+ * stays type-checked against the real interface, so a field added to `Config`
20
+ * fails here at `tsc` time instead of at the first test that needed it.
21
+ */
22
+
23
+ import { tmpdir } from "node:os";
24
+ import { join } from "node:path";
25
+
26
+ import type { Config } from "../config.js";
27
+ import {
28
+ DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS,
29
+ DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS,
30
+ DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS,
31
+ } from "../config.js";
32
+
33
+ /**
34
+ * Loopback on a port nothing listens on, so a component that dials it fails
35
+ * fast with ECONNREFUSED instead of hanging on a route to nowhere.
36
+ */
37
+ const INERT_ENDPOINT = "http://127.0.0.1:1";
38
+
39
+ export function testConfig(overrides: Partial<Config> = {}): Config {
40
+ return {
41
+ taskQueue: "test-task-queue",
42
+ temporalAddress: "127.0.0.1:1",
43
+ temporalNamespace: "default",
44
+ stigmerBackendEndpoint: INERT_ENDPOINT,
45
+ stigmerToken: null,
46
+ mcpBridgeEndpoint: null,
47
+ cursorApiKey: "",
48
+ workspaceRootDir: join(tmpdir(), "stigmer-runner-test-workspaces"),
49
+ mode: "local",
50
+ proxyEndpoint: null,
51
+ maxConcurrentActivities: 1,
52
+ idleTimeoutSeconds: null,
53
+ cloudModeEnabled: false,
54
+ checkpointerType: "memory",
55
+ checkpointerProxyEndpoint: null,
56
+ artifactProxyEndpoint: null,
57
+ primaryModel: "test-model",
58
+ cursorStreamStallTimeoutMs: DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS,
59
+ agentResolveTimeoutMs: DEFAULT_CURSOR_AGENT_RESOLVE_TIMEOUT_MS,
60
+ workspaceLockTimeoutMs: DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS,
61
+ ...overrides,
62
+ };
63
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The four resources of one execution, wired by id into the chain the turn
3
+ * runtime resolves (`execution.spec.sessionId` → `session.spec.
4
+ * agentInstanceId` → `agentInstance.spec.agentId` → the agent), as an
5
+ * `ExecutionRecord` the hermetic client answers from.
6
+ *
7
+ * Harness-agnostic: the record is the control plane's, and the runtime's
8
+ * phases read it the same way whatever engine runs the turn. A harness's
9
+ * driver supplies its own fixture ids and model (`execute-cursor/__test-
10
+ * utils__/hermetic-cursor.ts` `cursorExecutionRecord`); the runtime's own
11
+ * tests use the defaults. Ids are fixed so goldens are readable and
12
+ * byte-stable.
13
+ */
14
+
15
+ import { create } from "@bufbuild/protobuf";
16
+ import { AgentExecutionSchema, type AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
17
+ import { AgentExecutionSpecSchema, ExecutionConfigSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
18
+ import { SessionSchema, type Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
19
+ import { SessionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/spec_pb";
20
+ import type { WorkspaceEntry } from "@stigmer/protos/ai/stigmer/agentic/session/v1/workspace_pb";
21
+ import { AgentSchema, type Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
22
+ import { AgentSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
23
+ import { AgentInstanceSchema, type AgentInstance } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/api_pb";
24
+ import { AgentInstanceSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/agentinstance/v1/spec_pb";
25
+ import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
26
+
27
+ import { ExecutionRecord, type ExecutionRecordInput } from "./hermetic-activity.js";
28
+ import { FIXTURE_MODEL } from "./model-registry-fixture.js";
29
+
30
+ /** The ids a record's four resources carry. */
31
+ export interface ExecutionRecordIds {
32
+ readonly org: string;
33
+ readonly executionId: string;
34
+ readonly sessionId: string;
35
+ readonly agentInstanceId: string;
36
+ readonly agentId: string;
37
+ readonly agentName: string;
38
+ }
39
+
40
+ export const DEFAULT_RECORD_IDS: ExecutionRecordIds = {
41
+ org: "hermetic-org",
42
+ executionId: "aex_hermetic_0001",
43
+ sessionId: "ses_hermetic_0001",
44
+ agentInstanceId: "ain_hermetic_0001",
45
+ agentId: "agt_hermetic_0001",
46
+ agentName: "hermetic-agent",
47
+ };
48
+
49
+ export interface ExecutionRecordOptions {
50
+ /** The user's message for this execution. */
51
+ readonly message: string;
52
+ /** The agent's instructions (system prompt body). */
53
+ readonly instructions?: string;
54
+ /** Session workspace entries (a `local_path` entry turns on capture mode). */
55
+ readonly workspaceEntries?: WorkspaceEntry[];
56
+ /** The requested model; the pinned fixture model when omitted. */
57
+ readonly modelName?: string;
58
+ readonly autoApproveAll?: boolean;
59
+ /**
60
+ * `ExecutionConfig.max_cost_usd`: the per-message cost budget the runtime
61
+ * enforces as a hard stop (cost-guard.ts). Omitted or 0 = no cap, the
62
+ * proto's default. Priced against the registry fixture's round numbers, so
63
+ * a scenario can state its overrun exactly (600 000 input tokens = $0.60).
64
+ */
65
+ readonly maxCostUsd?: number;
66
+ /** The control plane's STOP lever; see `ExecutionRecordInput.controlSignal`. */
67
+ readonly controlSignal?: ExecutionRecordInput["controlSignal"];
68
+ readonly ids?: ExecutionRecordIds;
69
+ }
70
+
71
+ export function executionRecordFixture(options: ExecutionRecordOptions): ExecutionRecord {
72
+ const ids = options.ids ?? DEFAULT_RECORD_IDS;
73
+ const execution: AgentExecution = create(AgentExecutionSchema, {
74
+ metadata: create(ApiResourceMetadataSchema, { id: ids.executionId, org: ids.org, name: ids.executionId }),
75
+ spec: create(AgentExecutionSpecSchema, {
76
+ sessionId: ids.sessionId,
77
+ message: options.message,
78
+ autoApproveAll: options.autoApproveAll ?? false,
79
+ executionConfig: create(ExecutionConfigSchema, {
80
+ modelName: options.modelName ?? FIXTURE_MODEL,
81
+ maxCostUsd: options.maxCostUsd ?? 0,
82
+ }),
83
+ }),
84
+ });
85
+ const session: Session = create(SessionSchema, {
86
+ metadata: create(ApiResourceMetadataSchema, { id: ids.sessionId, org: ids.org, name: ids.sessionId }),
87
+ spec: create(SessionSpecSchema, {
88
+ agentInstanceId: ids.agentInstanceId,
89
+ workspaceEntries: options.workspaceEntries ?? [],
90
+ }),
91
+ });
92
+ const agentInstance: AgentInstance = create(AgentInstanceSchema, {
93
+ metadata: create(ApiResourceMetadataSchema, { id: ids.agentInstanceId, org: ids.org, name: ids.agentInstanceId }),
94
+ spec: create(AgentInstanceSpecSchema, { agentId: ids.agentId }),
95
+ });
96
+ const agent: Agent = create(AgentSchema, {
97
+ metadata: create(ApiResourceMetadataSchema, { id: ids.agentId, org: ids.org, name: ids.agentName }),
98
+ spec: create(AgentSpecSchema, {
99
+ description: "Hermetic fixture agent",
100
+ instructions: options.instructions ?? "You are the hermetic fixture agent. Answer briefly.",
101
+ }),
102
+ });
103
+ return new ExecutionRecord({ execution, session, agentInstance, agent, controlSignal: options.controlSignal });
104
+ }