@stigmer/runner 3.14.1 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/execution-record-fixture.d.ts +47 -0
  3. package/dist/__test-utils__/execution-record-fixture.js +68 -0
  4. package/dist/__test-utils__/execution-record-fixture.js.map +1 -0
  5. package/dist/__test-utils__/harness-contract/types.d.ts +166 -0
  6. package/dist/__test-utils__/harness-contract/types.js +49 -0
  7. package/dist/__test-utils__/harness-contract/types.js.map +1 -0
  8. package/dist/__test-utils__/hermetic-activity.d.ts +28 -5
  9. package/dist/__test-utils__/hermetic-activity.js +23 -7
  10. package/dist/__test-utils__/hermetic-activity.js.map +1 -1
  11. package/dist/__test-utils__/mock-workspace.d.ts +15 -0
  12. package/dist/__test-utils__/mock-workspace.js +21 -0
  13. package/dist/__test-utils__/mock-workspace.js.map +1 -0
  14. package/dist/__test-utils__/model-registry-fixture.d.ts +56 -0
  15. package/dist/__test-utils__/model-registry-fixture.js +66 -0
  16. package/dist/__test-utils__/model-registry-fixture.js.map +1 -0
  17. package/dist/__test-utils__/turn-input-fixture.d.ts +60 -0
  18. package/dist/__test-utils__/turn-input-fixture.js +118 -0
  19. package/dist/__test-utils__/turn-input-fixture.js.map +1 -0
  20. package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +107 -0
  21. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +344 -0
  22. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -0
  23. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +20 -0
  24. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +58 -0
  25. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
  26. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +2 -32
  27. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
  28. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +38 -62
  29. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +47 -121
  30. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
  31. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +17 -1
  32. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +27 -4
  33. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -1
  34. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +52 -11
  35. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +66 -7
  36. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -1
  37. package/dist/activities/execute-cursor/adapter.d.ts +55 -0
  38. package/dist/activities/execute-cursor/adapter.js +119 -0
  39. package/dist/activities/execute-cursor/adapter.js.map +1 -0
  40. package/dist/activities/execute-cursor/approval-state.d.ts +2 -2
  41. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  42. package/dist/activities/execute-cursor/capture-flow.d.ts +15 -30
  43. package/dist/activities/execute-cursor/capture-flow.js +17 -26
  44. package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
  45. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +23 -0
  46. package/dist/activities/execute-cursor/cursor-capabilities.js +33 -0
  47. package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -0
  48. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +2 -2
  49. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -1
  50. package/dist/activities/execute-cursor/fetch-interceptor.d.ts +15 -25
  51. package/dist/activities/execute-cursor/fetch-interceptor.js +39 -33
  52. package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
  53. package/dist/activities/execute-cursor/http2-interceptor.d.ts +21 -17
  54. package/dist/activities/execute-cursor/http2-interceptor.js +38 -31
  55. package/dist/activities/execute-cursor/http2-interceptor.js.map +1 -1
  56. package/dist/activities/execute-cursor/message-translator.d.ts +28 -19
  57. package/dist/activities/execute-cursor/message-translator.js +76 -27
  58. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  59. package/dist/activities/execute-cursor/prompt-builder.d.ts +180 -11
  60. package/dist/activities/execute-cursor/prompt-builder.js +171 -0
  61. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  62. package/dist/activities/execute-cursor/rejection-capture.d.ts +3 -6
  63. package/dist/activities/execute-cursor/rejection-capture.js +5 -7
  64. package/dist/activities/execute-cursor/rejection-capture.js.map +1 -1
  65. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -4
  66. package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
  67. package/dist/activities/execute-cursor/turn-boundary.d.ts +1 -1
  68. package/dist/activities/execute-cursor/turn-settle.d.ts +49 -0
  69. package/dist/activities/execute-cursor/turn-settle.js +507 -0
  70. package/dist/activities/execute-cursor/turn-settle.js.map +1 -0
  71. package/dist/activities/execute-cursor/turn-setup.d.ts +173 -0
  72. package/dist/activities/execute-cursor/turn-setup.js +496 -0
  73. package/dist/activities/execute-cursor/turn-setup.js.map +1 -0
  74. package/dist/activities/execute-cursor/turn-stream.d.ts +41 -71
  75. package/dist/activities/execute-cursor/turn-stream.js +70 -135
  76. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  77. package/dist/activities/execute-cursor/turn.d.ts +43 -0
  78. package/dist/activities/execute-cursor/turn.js +222 -0
  79. package/dist/activities/execute-cursor/turn.js.map +1 -0
  80. package/dist/activities/execute-cursor/usage-pricing.d.ts +38 -0
  81. package/dist/activities/execute-cursor/usage-pricing.js +50 -0
  82. package/dist/activities/execute-cursor/usage-pricing.js.map +1 -0
  83. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +1 -1
  84. package/dist/activities/execute-deep-agent/approval-file-change.js +1 -1
  85. package/dist/activities/execute-deep-agent/environment.js +1 -1
  86. package/dist/activities/execute-deep-agent/environment.js.map +1 -1
  87. package/dist/activities/execute-deep-agent/setup.js +3 -2
  88. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  89. package/dist/activities/hydrate-workflow-execution.js +1 -1
  90. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  91. package/dist/config.d.ts +15 -0
  92. package/dist/config.js.map +1 -1
  93. package/dist/harness/capabilities.d.ts +16 -0
  94. package/dist/harness/capabilities.js +1 -0
  95. package/dist/harness/capabilities.js.map +1 -1
  96. package/dist/harness/persist-chokepoint.d.ts +74 -0
  97. package/dist/harness/persist-chokepoint.js +88 -0
  98. package/dist/harness/persist-chokepoint.js.map +1 -0
  99. package/dist/harness/registry.d.ts +44 -5
  100. package/dist/harness/registry.js +61 -5
  101. package/dist/harness/registry.js.map +1 -1
  102. package/dist/harness/run-turn.d.ts +53 -0
  103. package/dist/harness/run-turn.js +528 -0
  104. package/dist/harness/run-turn.js.map +1 -0
  105. package/dist/harness/stop-controller.d.ts +50 -0
  106. package/dist/harness/stop-controller.js +62 -0
  107. package/dist/harness/stop-controller.js.map +1 -0
  108. package/dist/harness/terminal-table.d.ts +141 -0
  109. package/dist/harness/terminal-table.js +239 -0
  110. package/dist/harness/terminal-table.js.map +1 -0
  111. package/dist/harness/turn-context.d.ts +523 -0
  112. package/dist/harness/turn-context.js +739 -0
  113. package/dist/harness/turn-context.js.map +1 -0
  114. package/dist/harness/types.d.ts +232 -52
  115. package/dist/harness/types.js +17 -8
  116. package/dist/harness/types.js.map +1 -1
  117. package/dist/harness/usage-accumulator.d.ts +68 -0
  118. package/dist/harness/usage-accumulator.js +101 -0
  119. package/dist/harness/usage-accumulator.js.map +1 -0
  120. package/dist/harness-adapters.d.ts +27 -0
  121. package/dist/harness-adapters.js +28 -0
  122. package/dist/harness-adapters.js.map +1 -0
  123. package/dist/main.js.map +1 -1
  124. package/dist/runner-manager.js +64 -48
  125. package/dist/runner-manager.js.map +1 -1
  126. package/dist/runner.d.ts +5 -4
  127. package/dist/runner.js +65 -64
  128. package/dist/runner.js.map +1 -1
  129. package/dist/shared/approval-policy.d.ts +1 -1
  130. package/dist/shared/approval-policy.js.map +1 -1
  131. package/dist/{activities/execute-cursor → shared}/attachment-resolver.d.ts +2 -2
  132. package/dist/{activities/execute-cursor → shared}/attachment-resolver.js +5 -5
  133. package/dist/shared/attachment-resolver.js.map +1 -0
  134. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.d.ts +7 -5
  135. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.js +3 -63
  136. package/dist/shared/blueprint-resolver.js.map +1 -0
  137. package/dist/shared/channel-attachment.d.ts +1 -1
  138. package/dist/shared/channel-attachment.js.map +1 -1
  139. package/dist/{activities/execute-cursor → shared}/cost-guard.d.ts +5 -1
  140. package/dist/{activities/execute-cursor → shared}/cost-guard.js +5 -1
  141. package/dist/shared/cost-guard.js.map +1 -0
  142. package/dist/{activities/execute-cursor → shared}/env-resolver.d.ts +1 -1
  143. package/dist/shared/env-resolver.js.map +1 -0
  144. package/dist/{activities/execute-cursor → shared}/exact-apply.d.ts +1 -1
  145. package/dist/{activities/execute-cursor → shared}/exact-apply.js +6 -6
  146. package/dist/shared/exact-apply.js.map +1 -0
  147. package/dist/shared/execution-context.d.ts +35 -0
  148. package/dist/shared/execution-context.js +38 -0
  149. package/dist/shared/execution-context.js.map +1 -0
  150. package/dist/{activities/execute-cursor → shared}/extract-structured-output.d.ts +5 -2
  151. package/dist/{activities/execute-cursor → shared}/extract-structured-output.js +9 -6
  152. package/dist/shared/extract-structured-output.js.map +1 -0
  153. package/dist/shared/filereview/capture.d.ts +1 -1
  154. package/dist/shared/filereview/capture.js +1 -1
  155. package/dist/shared/heartbeat.d.ts +13 -11
  156. package/dist/shared/heartbeat.js +14 -29
  157. package/dist/shared/heartbeat.js.map +1 -1
  158. package/dist/{activities/execute-cursor → shared}/persist-decision.d.ts +5 -2
  159. package/dist/{activities/execute-cursor → shared}/persist-decision.js +4 -1
  160. package/dist/shared/persist-decision.js.map +1 -0
  161. package/dist/{activities/execute-cursor → shared}/skill-resolver.d.ts +16 -3
  162. package/dist/{activities/execute-cursor → shared}/skill-resolver.js +12 -4
  163. package/dist/shared/skill-resolver.js.map +1 -0
  164. package/dist/shared/subagent-rows.d.ts +21 -0
  165. package/dist/shared/subagent-rows.js +34 -0
  166. package/dist/shared/subagent-rows.js.map +1 -0
  167. package/dist/shared/worker-shutdown.d.ts +43 -16
  168. package/dist/shared/worker-shutdown.js +28 -13
  169. package/dist/shared/worker-shutdown.js.map +1 -1
  170. package/dist/shared/workspace/platform-dir.d.ts +1 -1
  171. package/dist/shared/workspace/platform-dir.js +1 -1
  172. package/dist/shared/workspace/session-provision.d.ts +54 -0
  173. package/dist/{activities/execute-cursor/workspace-provision.js → shared/workspace/session-provision.js} +15 -12
  174. package/dist/shared/workspace/session-provision.js.map +1 -0
  175. package/package.json +4 -4
  176. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +110 -14
  177. package/src/__test-utils__/execution-record-fixture.ts +104 -0
  178. package/src/__test-utils__/harness-boot-order-child.ts +58 -0
  179. package/src/__test-utils__/harness-contract/contract.ts +128 -83
  180. package/src/__test-utils__/harness-contract/recording-sink.ts +15 -4
  181. package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
  182. package/src/__test-utils__/harness-contract/scripted-adapter.ts +92 -32
  183. package/src/__test-utils__/harness-contract/types.ts +85 -9
  184. package/src/__test-utils__/hermetic-activity.ts +33 -8
  185. package/src/__test-utils__/model-registry-fixture.ts +72 -0
  186. package/src/__test-utils__/module-specifiers.ts +82 -0
  187. package/src/__test-utils__/turn-input-fixture.ts +155 -0
  188. package/src/__tests__/harness-boot-order.test.ts +77 -0
  189. package/src/__tests__/harness-contract.test.ts +7 -5
  190. package/src/__tests__/runner-manager.test.ts +18 -0
  191. package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +102 -0
  192. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +396 -0
  193. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +66 -0
  194. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +2 -43
  195. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +65 -145
  196. package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +30 -4
  197. package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +96 -8
  198. package/src/activities/execute-cursor/__tests__/adapter-is-temporal-free.test.ts +45 -0
  199. package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +90 -0
  200. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -8
  201. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +28 -1
  202. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +1 -1
  203. package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +64 -6
  204. package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +1 -1
  205. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -1
  206. package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
  207. package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +1 -6
  208. package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -0
  209. package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-transport-timeout.status.json +48 -0
  210. package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
  211. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-cancelled.status.json +36 -0
  212. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-error-non-retryable.status.json +37 -0
  213. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +38 -0
  214. package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +59 -0
  215. package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +79 -0
  216. package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +2 -7
  217. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +170 -0
  218. package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +22 -18
  219. package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +1 -1
  220. package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +1 -1
  221. package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +286 -0
  222. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +332 -0
  223. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +193 -0
  224. package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +1 -1
  225. package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +185 -0
  226. package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +151 -0
  227. package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +65 -35
  228. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -40
  229. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +171 -0
  230. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -243
  231. package/src/activities/execute-cursor/__tests__/usage-pricing.test.ts +84 -0
  232. package/src/activities/execute-cursor/adapter.ts +138 -0
  233. package/src/activities/execute-cursor/approval-state.ts +2 -2
  234. package/src/activities/execute-cursor/capture-flow.ts +18 -41
  235. package/src/activities/execute-cursor/cursor-capabilities.ts +35 -0
  236. package/src/activities/execute-cursor/cursor-mcp-config.ts +2 -2
  237. package/src/activities/execute-cursor/fetch-interceptor.ts +48 -45
  238. package/src/activities/execute-cursor/http2-interceptor.ts +47 -34
  239. package/src/activities/execute-cursor/message-translator.ts +90 -45
  240. package/src/activities/execute-cursor/prompt-builder.ts +327 -12
  241. package/src/activities/execute-cursor/rejection-capture.ts +5 -12
  242. package/src/activities/execute-cursor/session-lifecycle.ts +1 -5
  243. package/src/activities/execute-cursor/turn-boundary.ts +1 -1
  244. package/src/activities/execute-cursor/turn-settle.ts +601 -0
  245. package/src/activities/execute-cursor/turn-setup.ts +645 -0
  246. package/src/activities/execute-cursor/turn-stream.ts +84 -210
  247. package/src/activities/execute-cursor/turn.ts +240 -0
  248. package/src/activities/execute-cursor/usage-pricing.ts +67 -0
  249. package/src/activities/execute-deep-agent/approval-file-change.ts +1 -1
  250. package/src/activities/execute-deep-agent/environment.ts +1 -1
  251. package/src/activities/execute-deep-agent/setup.ts +3 -2
  252. package/src/activities/hydrate-workflow-execution.ts +1 -1
  253. package/src/config.ts +13 -0
  254. package/src/harness/__tests__/import-direction.test.ts +168 -0
  255. package/src/harness/__tests__/persist-chokepoint.test.ts +146 -0
  256. package/src/harness/__tests__/registry.test.ts +1 -0
  257. package/src/harness/__tests__/run-turn.test.ts +165 -0
  258. package/src/harness/__tests__/stop-controller.test.ts +49 -0
  259. package/src/harness/__tests__/turn-context.test.ts +178 -0
  260. package/src/harness/__tests__/usage-accumulator.test.ts +83 -0
  261. package/src/harness/capabilities.ts +17 -0
  262. package/src/harness/persist-chokepoint.ts +112 -0
  263. package/src/harness/registry.ts +79 -5
  264. package/src/harness/run-turn.ts +618 -0
  265. package/src/harness/stop-controller.ts +83 -0
  266. package/src/harness/terminal-table.ts +276 -0
  267. package/src/harness/turn-context.ts +1028 -0
  268. package/src/harness/types.ts +248 -53
  269. package/src/harness/usage-accumulator.ts +122 -0
  270. package/src/harness-adapters.ts +30 -0
  271. package/src/main.ts +1 -1
  272. package/src/runner-manager.ts +68 -61
  273. package/src/runner.ts +67 -78
  274. package/src/shared/__tests__/attachment-naming.test.ts +1 -1
  275. package/src/{activities/execute-cursor → shared}/__tests__/attachment-resolver.test.ts +6 -4
  276. package/src/{activities/execute-cursor → shared}/__tests__/cost-guard.test.ts +2 -1
  277. package/src/{activities/execute-cursor → shared}/__tests__/exact-apply.test.ts +3 -3
  278. package/src/{activities/execute-cursor → shared}/__tests__/extract-structured-output.test.ts +8 -8
  279. package/src/{activities/execute-cursor → shared}/__tests__/persist-decision.test.ts +4 -2
  280. package/src/{activities/execute-cursor → shared}/__tests__/skill-resolver.test.ts +1 -1
  281. package/src/shared/__tests__/subagent-rows.test.ts +34 -0
  282. package/src/shared/__tests__/worker-shutdown.test.ts +44 -39
  283. package/src/shared/approval-policy.ts +1 -1
  284. package/src/{activities/execute-cursor → shared}/attachment-resolver.ts +6 -6
  285. package/src/{activities/execute-cursor → shared}/blueprint-resolver.ts +7 -78
  286. package/src/shared/channel-attachment.ts +1 -1
  287. package/src/{activities/execute-cursor → shared}/cost-guard.ts +5 -1
  288. package/src/{activities/execute-cursor → shared}/env-resolver.ts +1 -1
  289. package/src/{activities/execute-cursor → shared}/exact-apply.ts +7 -7
  290. package/src/shared/execution-context.ts +45 -0
  291. package/src/{activities/execute-cursor → shared}/extract-structured-output.ts +10 -7
  292. package/src/shared/filereview/capture.ts +1 -1
  293. package/src/shared/heartbeat.ts +13 -33
  294. package/src/{activities/execute-cursor → shared}/persist-decision.ts +5 -2
  295. package/src/{activities/execute-cursor → shared}/skill-resolver.ts +20 -6
  296. package/src/shared/subagent-rows.ts +39 -0
  297. package/src/shared/worker-shutdown.ts +55 -25
  298. package/src/{activities/execute-cursor/__tests__/workspace-provision.test.ts → shared/workspace/__tests__/session-provision.test.ts} +25 -28
  299. package/src/shared/workspace/platform-dir.ts +1 -1
  300. package/src/{activities/execute-cursor/workspace-provision.ts → shared/workspace/session-provision.ts} +29 -16
  301. package/dist/activities/execute-cursor/attachment-resolver.js.map +0 -1
  302. package/dist/activities/execute-cursor/blueprint-resolver.js.map +0 -1
  303. package/dist/activities/execute-cursor/cost-guard.js.map +0 -1
  304. package/dist/activities/execute-cursor/env-resolver.js.map +0 -1
  305. package/dist/activities/execute-cursor/exact-apply.js.map +0 -1
  306. package/dist/activities/execute-cursor/extract-structured-output.js.map +0 -1
  307. package/dist/activities/execute-cursor/index.d.ts +0 -209
  308. package/dist/activities/execute-cursor/index.js +0 -2345
  309. package/dist/activities/execute-cursor/index.js.map +0 -1
  310. package/dist/activities/execute-cursor/persist-decision.js.map +0 -1
  311. package/dist/activities/execute-cursor/skill-resolver.js.map +0 -1
  312. package/dist/activities/execute-cursor/usage-accumulator.d.ts +0 -89
  313. package/dist/activities/execute-cursor/usage-accumulator.js +0 -121
  314. package/dist/activities/execute-cursor/usage-accumulator.js.map +0 -1
  315. package/dist/activities/execute-cursor/workspace-provision.d.ts +0 -42
  316. package/dist/activities/execute-cursor/workspace-provision.js.map +0 -1
  317. package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +0 -164
  318. package/src/activities/execute-cursor/index.ts +0 -2876
  319. package/src/activities/execute-cursor/usage-accumulator.ts +0 -169
  320. /package/dist/{activities/execute-cursor → shared}/env-resolver.js +0 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * The persist chokepoint's two contracts: what one write does (the secret
3
+ * backstop, the usage summary, the heartbeat, the STOP), and how requests
4
+ * coalesce (one write in flight, one follow-up at most, every request's
5
+ * promise resolving only once its state is on the wire).
6
+ */
7
+
8
+ import { describe, expect, it, vi } from "vitest";
9
+ import { create } from "@bufbuild/protobuf";
10
+ import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
11
+ import { UpdateStatusResponseSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
12
+ import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
13
+ import { ExecutionControlSignal, MessageType, ServiceTier, ThinkingMode, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
14
+
15
+ import type { StigmerClient } from "../../client/stigmer-client.js";
16
+ import { PersistChokepoint } from "../persist-chokepoint.js";
17
+ import { UsageAccumulator } from "../usage-accumulator.js";
18
+
19
+ interface Gate {
20
+ readonly open: () => void;
21
+ readonly opened: Promise<void>;
22
+ }
23
+
24
+ function gate(): Gate {
25
+ let open!: () => void;
26
+ const opened = new Promise<void>((resolve) => (open = resolve));
27
+ return { open, opened };
28
+ }
29
+
30
+ /** A client whose `updateStatus` answers through a gate the test opens, so writes can be held in flight. */
31
+ function heldClient(answers: ExecutionControlSignal[] = []) {
32
+ const gates: Gate[] = [];
33
+ const writes: number[] = [];
34
+ const client = {
35
+ updateStatus: vi.fn(async (_id: string, status: { messages: unknown[] }) => {
36
+ const g = gate();
37
+ gates.push(g);
38
+ writes.push(status.messages.length);
39
+ await g.opened;
40
+ return create(UpdateStatusResponseSchema, { signal: answers.shift() ?? ExecutionControlSignal.UNSPECIFIED });
41
+ }),
42
+ } as unknown as StigmerClient;
43
+ return { client, gates, writes };
44
+ }
45
+
46
+ function chokepointOver(client: StigmerClient, options: { usage?: UsageAccumulator; onPlatformStop?: () => void } = {}) {
47
+ const status = create(AgentExecutionStatusSchema, {});
48
+ const heartbeat = vi.fn();
49
+ const onPlatformStop = options.onPlatformStop ?? vi.fn();
50
+ const chokepoint = new PersistChokepoint({
51
+ client,
52
+ executionId: "aex_test",
53
+ status,
54
+ offload: undefined,
55
+ usage: () => options.usage,
56
+ heartbeat,
57
+ onPlatformStop,
58
+ });
59
+ return { chokepoint, status, heartbeat, onPlatformStop };
60
+ }
61
+
62
+ describe("PersistChokepoint: one write", () => {
63
+ it("withholds secret content, refreshes streaming_usage, writes, heartbeats, and relays STOP", async () => {
64
+ const { client, gates } = heldClient([ExecutionControlSignal.STOP]);
65
+ const usage = new UsageAccumulator(ServiceTier.STANDARD, ThinkingMode.DISABLED);
66
+ usage.addTurn({ inputTokens: 10, estimatedCostUsd: 0.01, model: "m" });
67
+ const onPlatformStop = vi.fn();
68
+ const { chokepoint, status, heartbeat } = chokepointOver(client, { usage, onPlatformStop });
69
+ status.messages.push(
70
+ create(AgentMessageSchema, {
71
+ type: MessageType.MESSAGE_AI,
72
+ toolCalls: [
73
+ create(ToolCallSchema, {
74
+ id: "w1",
75
+ name: "write",
76
+ status: ToolCallStatus.TOOL_CALL_COMPLETED,
77
+ args: { path: ".env", content: "SECRET=1" },
78
+ }),
79
+ ],
80
+ }),
81
+ );
82
+
83
+ const written = chokepoint.write();
84
+ gates[0]!.open();
85
+ await written;
86
+
87
+ expect(status.messages[0]!.toolCalls[0]!.args, "the secret backstop ran before the write").not.toMatchObject({ content: "SECRET=1" });
88
+ expect(status.streamingUsage?.estimatedCostUsd).toBe(0.01);
89
+ expect(heartbeat).toHaveBeenCalledTimes(1);
90
+ expect(onPlatformStop).toHaveBeenCalledTimes(1);
91
+ });
92
+
93
+ it("writes no streaming_usage when no turn was reported", async () => {
94
+ const { client, gates } = heldClient();
95
+ const { chokepoint, status } = chokepointOver(client, { usage: new UsageAccumulator() });
96
+ const written = chokepoint.write();
97
+ gates[0]!.open();
98
+ await written;
99
+ expect(status.streamingUsage).toBeUndefined();
100
+ });
101
+ });
102
+
103
+ describe("PersistChokepoint: coalescing", () => {
104
+ it("a request while a write is in flight schedules ONE follow-up and resolves only when it lands", async () => {
105
+ const { client, gates, writes } = heldClient();
106
+ const { chokepoint, status } = chokepointOver(client);
107
+
108
+ status.messages.push(create(AgentMessageSchema, { content: "one" }));
109
+ const first = chokepoint.request();
110
+ status.messages.push(create(AgentMessageSchema, { content: "two" }));
111
+ const second = chokepoint.request();
112
+ status.messages.push(create(AgentMessageSchema, { content: "three" }));
113
+ const third = chokepoint.request();
114
+
115
+ let secondSettled = false;
116
+ let thirdSettled = false;
117
+ void second.then(() => (secondSettled = true));
118
+ void third.then(() => (thirdSettled = true));
119
+
120
+ expect(gates, "one write in flight, the follow-up waits").toHaveLength(1);
121
+ gates[0]!.open();
122
+ await first;
123
+ await Promise.resolve();
124
+ expect(secondSettled, "the follow-up has not landed yet").toBe(false);
125
+
126
+ await vi.waitFor(() => expect(gates).toHaveLength(2));
127
+ expect(writes, "the follow-up carries the state as of the LAST request").toEqual([1, 3]);
128
+ gates[1]!.open();
129
+ await Promise.all([second, third]);
130
+ expect(secondSettled && thirdSettled).toBe(true);
131
+ expect(client.updateStatus).toHaveBeenCalledTimes(2);
132
+ });
133
+
134
+ it("sequential awaited requests write once each, in order", async () => {
135
+ const { client, gates } = heldClient();
136
+ const { chokepoint } = chokepointOver(client);
137
+ const first = chokepoint.request();
138
+ gates[0]!.open();
139
+ await first;
140
+ const second = chokepoint.request();
141
+ await vi.waitFor(() => expect(gates).toHaveLength(2));
142
+ gates[1]!.open();
143
+ await second;
144
+ expect(client.updateStatus).toHaveBeenCalledTimes(2);
145
+ });
146
+ });
@@ -36,6 +36,7 @@ function stubAdapter(name: string, log: string[], behaviour: StubBehaviour = {})
36
36
  subAgents: false,
37
37
  toolRestriction: true,
38
38
  visionProfile: DEEP_AGENT_VISION_PROFILE,
39
+ fileReview: { harnessId: "stub", excludePaths: [] },
39
40
  },
40
41
  async boot() {
41
42
  log.push(`boot:${name}`);
@@ -0,0 +1,165 @@
1
+ /**
2
+ * The turn runtime, proven without any engine: the scripted fake adapter
3
+ * (`__test-utils__/harness-contract/scripted-adapter.ts`) through the kit's
4
+ * runtime-side half (`__test-utils__/harness-contract/runtime-contract.ts`)
5
+ * under `MockActivityEnvironment`, one arm per row of the terminal table.
6
+ *
7
+ * The runtime-side contract itself — the throw-vs-return rule end to end, the
8
+ * single persist chokepoint, the whole-activity heartbeat, the stop
9
+ * controller's four producers, the reinvocation seam, the byte-pinned copy —
10
+ * is the kit's and is registered here through `describeHarnessRuntimeContract`
11
+ * exactly as `activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts`
12
+ * registers it against the real Cursor adapter. An arm that differs between
13
+ * the fake and the real engine is an adapter defect, not a runtime one.
14
+ *
15
+ * What is asserted HERE and not in the kit are the FAKE's facts: the engine
16
+ * cadence a real engine has no reason to share. The fake's `say` awaits its
17
+ * persist and its `propose` persists nothing before returning
18
+ * `awaiting_approval`, so under the runtime its first full write IS the
19
+ * WAITING write (`persistedPhases === [WAITING_FOR_APPROVAL]`) where a
20
+ * streaming engine's IN_PROGRESS writes precede it; the fake has no setup
21
+ * labels of its own, so the runtime's six are the whole list; the fake states
22
+ * its own price, so the cost copy reads exactly `~$0.6000`; and the fake's
23
+ * failure messages reach the status verbatim, where a real engine's
24
+ * classifier rephrases them.
25
+ *
26
+ * The hermetic environment is created at module level because the subject's
27
+ * `config` is read at collection time; the forks pool keeps it private to
28
+ * this file.
29
+ */
30
+
31
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
32
+ import { ApprovalAction, ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
33
+
34
+ vi.mock("../../client/stigmer-client.js", async () =>
35
+ (await import("../../__test-utils__/hermetic-activity.js")).hermeticStigmerClientModule(),
36
+ );
37
+
38
+ import { ScriptedClock, createHermeticEnvironment } from "../../__test-utils__/hermetic-activity.js";
39
+ import { stubRegistryFetch } from "../../__test-utils__/model-registry-fixture.js";
40
+ import { scriptedSubject } from "../../__test-utils__/harness-contract/scripted-adapter.js";
41
+ import {
42
+ COST_CAP_FAKE_PRICE_USD,
43
+ FAILURE_MESSAGES,
44
+ NEVER_SEEN,
45
+ RUNTIME_SETUP_LABELS,
46
+ RuntimeExecutionDriver,
47
+ aiMessages,
48
+ assertApprovalRoundTrip,
49
+ assertCompletedTurn,
50
+ assertCostCapTerminates,
51
+ assertFailedSurfaceWritesItsCopy,
52
+ assertRejectedBindFails,
53
+ describeHarnessRuntimeContract,
54
+ slimOf,
55
+ systemMessages,
56
+ type RuntimeContractHarness,
57
+ } from "../../__test-utils__/harness-contract/runtime-contract.js";
58
+ import { scenario } from "../../__test-utils__/harness-contract/types.js";
59
+ import { TERMINAL_COPY } from "../terminal-table.js";
60
+
61
+ const TASK_QUEUE = "runtime-test-queue";
62
+
63
+ const env = createHermeticEnvironment();
64
+ const clock = new ScriptedClock();
65
+ const subject = scriptedSubject({
66
+ pausePrimitive: "interrupt",
67
+ stateIdSource: "engine-minted",
68
+ config: { workspaceRootDir: env.workspaceRootDir, taskQueue: TASK_QUEUE },
69
+ });
70
+ const harness: RuntimeContractHarness = { subject, env, clock };
71
+
72
+ describe("run-turn: the fake adapter through the real runtime", () => {
73
+ let registry: ReturnType<typeof stubRegistryFetch>;
74
+
75
+ beforeAll(() => {
76
+ registry = stubRegistryFetch();
77
+ clock.install();
78
+ });
79
+
80
+ afterAll(() => {
81
+ clock.uninstall();
82
+ registry.restore();
83
+ env.dispose();
84
+ });
85
+
86
+ describeHarnessRuntimeContract(harness);
87
+
88
+ describe("the fake's own facts", () => {
89
+ beforeAll(async () => {
90
+ await subject.adapter.boot(subject.config);
91
+ });
92
+
93
+ it("a completed turn: the runtime's six labels are the whole list, the phases, the stated price and its basis", async () => {
94
+ clock.reset();
95
+ const { driver, final } = await assertCompletedTurn(harness, "fake-completed");
96
+ expect(driver.record.persistedPhases).toEqual([ExecutionPhase.EXECUTION_IN_PROGRESS, ExecutionPhase.EXECUTION_COMPLETED]);
97
+ expect(driver.record.setupProgress).toEqual([...RUNTIME_SETUP_LABELS]);
98
+ expect(final.streamingUsage?.estimatedCostUsd, "the summary is the adapter's stated price, summed").toBeCloseTo(0.00136, 9);
99
+ expect(final.streamingUsage?.model, "the basis the adapter named").toBe("fixture-model");
100
+ expect(registry.urls.every((u) => u.includes("/model-registry"))).toBe(true);
101
+ });
102
+
103
+ it("the approval round trip: the WAITING write is the fake's first full persist (M3a finding 3)", async () => {
104
+ clock.reset();
105
+ const { driver } = await assertApprovalRoundTrip(harness, "fake-approval");
106
+ expect(driver.record.persistedPhases).toEqual([
107
+ ExecutionPhase.EXECUTION_WAITING_FOR_APPROVAL,
108
+ ExecutionPhase.EXECUTION_IN_PROGRESS,
109
+ ExecutionPhase.EXECUTION_COMPLETED,
110
+ ]);
111
+ expect(driver.record.sessionUpdates, "an engine-minted adapter binds once across the round trip").toHaveLength(1);
112
+ });
113
+
114
+ it("the cost cap: the fake's stated price reads exactly in the copy", async () => {
115
+ clock.reset();
116
+ const { final } = await assertCostCapTerminates(harness, "fake-cost-cap");
117
+ expect(final.error).toBe(`Agent reached the cost limit for this message (~$${COST_CAP_FAKE_PRICE_USD.toFixed(4)} of the $0.50 budget). Send another message to continue.`);
118
+ expect(final.streamingUsage?.estimatedCostUsd).toBe(COST_CAP_FAKE_PRICE_USD);
119
+ });
120
+
121
+ it.each([
122
+ ["engine", []],
123
+ ["actionable", [`Execution failed: ${FAILURE_MESSAGES.actionable}`]],
124
+ ["internal", [TERMINAL_COPY.internalFailure.row, `Error details: ${FAILURE_MESSAGES.internal}`]],
125
+ ] as const)("a %s failure: the fake's message reaches the status verbatim, with that surface's rows", async (surface, rows) => {
126
+ clock.reset();
127
+ const { driver, final } = await assertFailedSurfaceWritesItsCopy(harness, surface, FAILURE_MESSAGES[surface], `fake-failed-${surface}`);
128
+ expect(driver.record.persistedPhases).toEqual([ExecutionPhase.EXECUTION_IN_PROGRESS, ExecutionPhase.EXECUTION_FAILED]);
129
+ expect(final.error).toBe(FAILURE_MESSAGES[surface]);
130
+ expect(systemMessages(final)).toEqual(rows);
131
+ });
132
+
133
+ it("a rejected bind: the fake names the failure and nothing of the scenario was folded in", async () => {
134
+ clock.reset();
135
+ const { final } = await assertRejectedBindFails(harness, "fake-bind-rejects");
136
+ expect(final.error).toContain("could not bind engine state");
137
+ expect(aiMessages(final)).toEqual([]);
138
+ });
139
+
140
+ it("REJECT of an irreversible action settles the reinvocation FAILED before any engine runs (today's runtime; see F1)", async () => {
141
+ // The runtime fails a reinvocation that carries any REJECT
142
+ // (`decideReinvocation`, moved from Cursor's orchestrator). The proto
143
+ // contract as of #197 — the enum doc, the conformance suite, the native
144
+ // harness's `reconcileNonExecutingDecisions` — says REJECT denies the
145
+ // tool and the run continues with the row SKIPPED. This arm pins the
146
+ // runtime AS IT IS so the S3 change is a visible diff, and is
147
+ // deliberately NOT in the kit (S2 M4 finding F1, Q-M4-1).
148
+ clock.reset();
149
+ const driver = new RuntimeExecutionDriver(harness, "fake-reject");
150
+ const propose = scenario.propose("call-runtime-reject", { kind: "shell", resource: "rm -rf build" });
151
+
152
+ const first = await driver.turn([propose]);
153
+ expect(slimOf(subject, first).phase).toBe("EXECUTION_WAITING_FOR_APPROVAL");
154
+ driver.decide(ApprovalAction.REJECT);
155
+
156
+ const second = await driver.turn([scenario.say(NEVER_SEEN)]);
157
+ expect(slimOf(subject, second).phase).toBe("EXECUTION_FAILED");
158
+ const final = driver.record.lastFullStatus!;
159
+ expect(final.error).toBe(TERMINAL_COPY.rejectedByUser.error);
160
+ expect(systemMessages(final).at(-1)).toBe(TERMINAL_COPY.rejectedByUser.row);
161
+ expect(final.completedAt).not.toBe("");
162
+ expect(aiMessages(final)).not.toContain(NEVER_SEEN);
163
+ });
164
+ });
165
+ });
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The stop controller: one signal for every cause, every cause recorded,
3
+ * the first cause the abort's reason. The terminal table's precedence is
4
+ * decided at settlement from the recorded evidence, not from which cause
5
+ * aborted first.
6
+ */
7
+
8
+ import { describe, expect, it } from "vitest";
9
+
10
+ import { StallTimeoutError } from "../../shared/stall-watchdog.js";
11
+ import { StopController } from "../stop-controller.js";
12
+
13
+ describe("StopController", () => {
14
+ it("starts live with no evidence", () => {
15
+ const stop = new StopController();
16
+ expect(stop.signal.aborted).toBe(false);
17
+ expect(stop.evidence).toEqual({ stall: undefined, costCapExceeded: false, platformStop: false, cancellation: false });
18
+ });
19
+
20
+ it("the first cause aborts the signal and names the abort; later causes are recorded but do not re-abort", () => {
21
+ const stop = new StopController();
22
+ stop.stop({ kind: "cost-cap" });
23
+ expect(stop.signal.aborted).toBe(true);
24
+ expect(stop.signal.reason).toBe("cost-cap");
25
+
26
+ stop.stop({ kind: "cancellation" });
27
+ stop.stop({ kind: "platform-stop" });
28
+ expect(stop.signal.reason, "the abort keeps its first reason").toBe("cost-cap");
29
+ expect(stop.evidence).toMatchObject({ costCapExceeded: true, cancellation: true, platformStop: true });
30
+ });
31
+
32
+ it("keeps the FIRST stall error when the watchdog reports twice", () => {
33
+ const stop = new StopController();
34
+ const first = new StallTimeoutError(3_000, "last tool: shell");
35
+ stop.stop({ kind: "stall", error: first });
36
+ stop.stop({ kind: "stall", error: new StallTimeoutError(9_000) });
37
+ expect(stop.evidence.stall).toBe(first);
38
+ });
39
+
40
+ it("fires abort listeners exactly once", () => {
41
+ const stop = new StopController();
42
+ let fired = 0;
43
+ stop.signal.addEventListener("abort", () => fired++);
44
+ stop.stop({ kind: "cancellation" });
45
+ stop.stop({ kind: "cancellation" });
46
+ stop.stop({ kind: "cost-cap" });
47
+ expect(fired).toBe(1);
48
+ });
49
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Pins the turn context's pure decisions — the three places `turn-context.ts`
3
+ * decides something rather than fetching it:
4
+ *
5
+ * - `isReinvocation`: the one reading of "does the engine already hold state
6
+ * for this execution", keyed on how the harness mints its state id. The
7
+ * engine-minted arm is today's `!!threadId`; the deterministic arm refuses
8
+ * until the native adapter lands (S3), and the refusal is pinned so a
9
+ * future caller cannot get a silent `false`.
10
+ * - `approvalDecisionsOf`: the runtime's reader of adjudicated approvals,
11
+ * over a transcript that carries every row shape it must include or
12
+ * exclude. Its agreement with the Cursor adapter's own reader
13
+ * (`reconstructAdjudicatedApprovals`) is pinned from the adapter's side
14
+ * (`activities/execute-cursor/__tests__/approval-state.test.ts`), because
15
+ * `src/harness/` never imports `src/activities/`, tests included.
16
+ * - `decideReinvocation`: the four ways a resume proceeds (reject fails,
17
+ * approvals run, a clean file-review resume completes, a failed or
18
+ * partially discarded one completes with its facts carried).
19
+ *
20
+ * The phases that fetch and provision are exercised end to end by the
21
+ * hermetic goldens (`activities/execute-cursor/__tests__/hermetic/`), which
22
+ * pin their observable behavior byte for byte; nothing here re-drives a
23
+ * client.
24
+ */
25
+
26
+ import { describe, expect, it } from "vitest";
27
+ import { create } from "@bufbuild/protobuf";
28
+ import { AgentExecutionStatusSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
29
+ import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
30
+ import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
31
+ import { ApprovalAction, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
32
+
33
+ import { approvalDecisionsOf, decideReinvocation, isReinvocation, type ReinvocationFacts } from "../turn-context.js";
34
+
35
+ const INPUT = { executionId: "aex_1", threadId: "", turnSeq: 0 } as const;
36
+
37
+ describe("isReinvocation", () => {
38
+ it("engine-minted: an empty state id is a first turn, any id is a resume", () => {
39
+ expect(isReinvocation("engine-minted", INPUT)).toBe(false);
40
+ expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42" })).toBe(true);
41
+ });
42
+
43
+ it("engine-minted: turnSeq is NOT the signal (a pure-reconcile turn does not advance it; the legacy wire shape yields 0)", () => {
44
+ expect(isReinvocation("engine-minted", { ...INPUT, turnSeq: 3 })).toBe(false);
45
+ expect(isReinvocation("engine-minted", { ...INPUT, threadId: "agent-42", turnSeq: 0 })).toBe(true);
46
+ });
47
+
48
+ it("deterministic: refuses rather than guess until the native adapter lands (S3)", () => {
49
+ expect(() => isReinvocation("deterministic", { ...INPUT, threadId: "thread-1" })).toThrow(
50
+ /deterministic arm lands with the native adapter \(S3\)/,
51
+ );
52
+ });
53
+ });
54
+
55
+ /** A transcript carrying every row shape the reader must include or exclude. */
56
+ function transcriptWithEveryRowShape() {
57
+ const waitingApproved = create(ToolCallSchema, {
58
+ id: "tc-approved",
59
+ name: "Shell",
60
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
61
+ approvalAction: ApprovalAction.APPROVE,
62
+ approvalContentDigest: "sha256:approved",
63
+ });
64
+ const waitingRejected = create(ToolCallSchema, {
65
+ id: "tc-rejected",
66
+ name: "Write",
67
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
68
+ approvalAction: ApprovalAction.REJECT,
69
+ });
70
+ const waitingUndecided = create(ToolCallSchema, {
71
+ id: "tc-undecided",
72
+ name: "Shell",
73
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
74
+ approvalAction: ApprovalAction.UNSPECIFIED,
75
+ });
76
+ const completedWithStaleAction = create(ToolCallSchema, {
77
+ id: "tc-completed",
78
+ name: "Read",
79
+ status: ToolCallStatus.TOOL_CALL_COMPLETED,
80
+ approvalAction: ApprovalAction.APPROVE,
81
+ });
82
+ const subAgentWaiting = create(ToolCallSchema, {
83
+ id: "tc-sub",
84
+ name: "Shell",
85
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
86
+ approvalAction: ApprovalAction.APPROVE,
87
+ });
88
+ return create(AgentExecutionStatusSchema, {
89
+ messages: [
90
+ create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, toolCalls: [waitingApproved, waitingUndecided] }),
91
+ create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, toolCalls: [completedWithStaleAction, waitingRejected] }),
92
+ ],
93
+ subAgentExecutions: [
94
+ create(SubAgentExecutionSchema, {
95
+ messages: [create(AgentMessageSchema, { type: MessageType.MESSAGE_AI, toolCalls: [subAgentWaiting] })],
96
+ }),
97
+ ],
98
+ });
99
+ }
100
+
101
+ describe("approvalDecisionsOf", () => {
102
+ it("reads every WAITING row with a verdict, in transcript order, and nothing else", () => {
103
+ const decisions = approvalDecisionsOf(transcriptWithEveryRowShape());
104
+ expect([...decisions.entries()]).toEqual([
105
+ ["tc-approved", ApprovalAction.APPROVE],
106
+ ["tc-rejected", ApprovalAction.REJECT],
107
+ ]);
108
+ });
109
+
110
+ it("an undecided WAITING row, a COMPLETED row with a stale verdict, and a sub-agent row are all excluded", () => {
111
+ const decisions = approvalDecisionsOf(transcriptWithEveryRowShape());
112
+ expect(decisions.has("tc-undecided"), "no verdict yet").toBe(false);
113
+ expect(decisions.has("tc-completed"), "already executed; the verdict on it is history").toBe(false);
114
+ expect(decisions.has("tc-sub"), "sub-agent transcripts are not the top-level rows").toBe(false);
115
+ });
116
+
117
+ it("is empty on a fresh status", () => {
118
+ expect(approvalDecisionsOf(create(AgentExecutionStatusSchema, {})).size).toBe(0);
119
+ });
120
+ });
121
+
122
+ function facts(overrides: Partial<ReinvocationFacts> = {}): ReinvocationFacts {
123
+ return {
124
+ approvalDecisions: new Map(),
125
+ reconciledFileReview: false,
126
+ fileReviewFailed: false,
127
+ fileReviewFailureDetail: "",
128
+ discardedPaths: [],
129
+ ...overrides,
130
+ };
131
+ }
132
+
133
+ describe("decideReinvocation", () => {
134
+ it("a first turn, or a resume that decided nothing and reconciled nothing, runs the agent", () => {
135
+ expect(decideReinvocation(facts())).toBeUndefined();
136
+ });
137
+
138
+ it("any REJECT among the adjudicated decisions fails the turn, whatever else was decided", () => {
139
+ const decisions = new Map([
140
+ ["tc-1", ApprovalAction.APPROVE],
141
+ ["tc-2", ApprovalAction.REJECT],
142
+ ]);
143
+ expect(decideReinvocation(facts({ approvalDecisions: decisions }))).toEqual({ kind: "rejected-by-user" });
144
+ });
145
+
146
+ it("approvals without a reject run the agent, even when file review was also reconciled", () => {
147
+ const decisions = new Map([["tc-1", ApprovalAction.APPROVE]]);
148
+ expect(decideReinvocation(facts({ approvalDecisions: decisions, reconciledFileReview: true }))).toBeUndefined();
149
+ expect(
150
+ decideReinvocation(facts({ approvalDecisions: new Map([["tc-1", ApprovalAction.SKIP]]) })),
151
+ "a SKIP is not a reject",
152
+ ).toBeUndefined();
153
+ });
154
+
155
+ it("a pure file-review resume completes, carrying the reconcile's facts", () => {
156
+ expect(decideReinvocation(facts({ reconciledFileReview: true }))).toEqual({
157
+ kind: "file-review-resolved",
158
+ failed: false,
159
+ failureDetail: "",
160
+ discardedPaths: [],
161
+ });
162
+ expect(
163
+ decideReinvocation(
164
+ facts({
165
+ reconciledFileReview: true,
166
+ fileReviewFailed: true,
167
+ fileReviewFailureDetail: "src/a.ts changed after review",
168
+ discardedPaths: ["src/b.ts"],
169
+ }),
170
+ ),
171
+ ).toEqual({
172
+ kind: "file-review-resolved",
173
+ failed: true,
174
+ failureDetail: "src/a.ts changed after review",
175
+ discardedPaths: ["src/b.ts"],
176
+ });
177
+ });
178
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The runtime's usage accounting over priced deltas: the token-accounting
3
+ * convention the Usage widget and the billing parity test depend on, the
4
+ * audit-trail fields, and the rule that the accumulator adds and never
5
+ * prices. The pricing half is `execute-cursor/__tests__/usage-pricing.test.ts`.
6
+ */
7
+
8
+ import { describe, it, expect } from "vitest";
9
+ import { ServiceTier, ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
10
+
11
+ import { UsageAccumulator } from "../usage-accumulator.js";
12
+
13
+ describe("UsageAccumulator", () => {
14
+ it("reports total_tokens as input + output (cache is a subset of input, not additive)", () => {
15
+ const acc = new UsageAccumulator();
16
+
17
+ // Two turns. cacheRead/cacheWrite are subsets of inputTokens.
18
+ acc.addTurn({ inputTokens: 100, outputTokens: 50, cacheReadTokens: 30, cacheWriteTokens: 10 });
19
+ acc.addTurn({ inputTokens: 300, outputTokens: 20, cacheReadTokens: 200, cacheWriteTokens: 0 });
20
+
21
+ const snap = acc.snapshot();
22
+
23
+ expect(snap.inputTokens).toBe(400n);
24
+ expect(snap.outputTokens).toBe(70n);
25
+ expect(snap.cacheReadTokens).toBe(230n);
26
+ expect(snap.cacheWriteTokens).toBe(10n);
27
+ // True total = input + output = 470. NOT 470 + 230 + 10 (which would
28
+ // double-count the cached tokens already inside inputTokens).
29
+ expect(snap.totalTokens).toBe(470n);
30
+ expect(snap.turnCount).toBe(2);
31
+ });
32
+
33
+ it("returns an empty snapshot when no turns were observed", () => {
34
+ const acc = new UsageAccumulator();
35
+ const snap = acc.snapshot();
36
+ expect(acc.hasTurns).toBe(false);
37
+ expect(snap.totalTokens).toBe(0n);
38
+ expect(snap.turnCount).toBe(0);
39
+ expect(snap.estimatedCostUsd).toBe(0);
40
+ });
41
+
42
+ it("treats missing token fields as zero", () => {
43
+ const acc = new UsageAccumulator();
44
+ acc.addTurn({ inputTokens: 42 });
45
+ const snap = acc.snapshot();
46
+ expect(snap.inputTokens).toBe(42n);
47
+ expect(snap.outputTokens).toBe(0n);
48
+ expect(snap.totalTokens).toBe(42n);
49
+ });
50
+
51
+ it("sums the adapter's stated cost and never prices on its own", () => {
52
+ const acc = new UsageAccumulator();
53
+ acc.addTurn({ inputTokens: 1_000_000, estimatedCostUsd: 0.25 });
54
+ acc.addTurn({ inputTokens: 1_000_000, estimatedCostUsd: 0.5 });
55
+ acc.addTurn({ inputTokens: 1_000_000 });
56
+ expect(acc.snapshot().estimatedCostUsd).toBeCloseTo(0.75, 9);
57
+ });
58
+
59
+ it("records the latest stated price basis; a delta without one inherits it (#357 audit trail)", () => {
60
+ const acc = new UsageAccumulator(ServiceTier.FAST, ThinkingMode.DISABLED);
61
+ acc.addTurn({ inputTokens: 10, model: "composer-2.5", requestedModelParams: '[{"id":"fast","value":"true"}]' });
62
+ acc.addTurn({ inputTokens: 5 });
63
+ const snap = acc.snapshot();
64
+ expect(snap.model).toBe("composer-2.5");
65
+ expect(snap.requestedModelParams).toBe('[{"id":"fast","value":"true"}]');
66
+ expect(snap.requestedServiceTier).toBe(ServiceTier.FAST);
67
+ });
68
+
69
+ it("records an empty basis when no delta named one", () => {
70
+ const acc = new UsageAccumulator(ServiceTier.STANDARD, ThinkingMode.DISABLED);
71
+ acc.addTurn({ inputTokens: 1 });
72
+ const snap = acc.snapshot();
73
+ expect(snap.model).toBe("");
74
+ expect(snap.requestedModelParams).toBe("");
75
+ expect(snap.requestedServiceTier).toBe(ServiceTier.STANDARD);
76
+ });
77
+
78
+ it("records the requested thinking mode into the snapshot (#772 audit trail)", () => {
79
+ const acc = new UsageAccumulator(ServiceTier.STANDARD, ThinkingMode.ENABLED);
80
+ acc.addTurn({ inputTokens: 10, outputTokens: 5 });
81
+ expect(acc.snapshot().requestedThinkingMode).toBe(ThinkingMode.ENABLED);
82
+ });
83
+ });
@@ -20,6 +20,7 @@
20
20
  * | `subAgents` | yes (compiled sub-graphs) | yes (`agents` option) | yes (`AgentDefinition`) | no |
21
21
  * | `toolRestriction` | yes (tool list) | no (the hook enforces) | yes (`disallowedTools`, `tools`) | per-server config |
22
22
  * | `visionProfile` | PNG, JPEG, WebP, GIF | PNG, JPEG (transport re-sniffs) | (surveyed later) | (surveyed later) |
23
+ * | `fileReview` | `deep-agent`, no excludes | `cursor`, `.cursor/hooks.json` excluded | (surveyed later) | (surveyed later) |
23
24
  *
24
25
  * Adapter-internal, deliberately NOT flags: how MCP servers are bound, how
25
26
  * metering is routed, how the cost cap is applied inside the engine, how a
@@ -61,6 +62,20 @@ export type PausePrimitive = "interrupt" | "deny-and-retry" | "callback" | "none
61
62
  */
62
63
  export type StateIdSource = "deterministic" | "engine-minted";
63
64
 
65
+ /**
66
+ * The two facts the runtime's file-review reconcile needs from the harness
67
+ * and cannot know: the id the projection reads from the BASELINE payload
68
+ * (`shared/filereview/capture.ts` `applyCaptureDecisions`), and the
69
+ * workspace-relative paths the harness writes into the repo that a turn's
70
+ * diff must never show (Cursor's transient `.cursor/hooks.json`). Declared
71
+ * once per adapter, read by `turn-context.ts` (ruled Q-M2-6 as an argument,
72
+ * homed here at M3).
73
+ */
74
+ export interface FileReviewIdentity {
75
+ readonly harnessId: string;
76
+ readonly excludePaths: readonly string[];
77
+ }
78
+
64
79
  export interface HarnessCapabilities {
65
80
  readonly pausePrimitive: PausePrimitive;
66
81
  readonly stateIdSource: StateIdSource;
@@ -72,4 +87,6 @@ export interface HarnessCapabilities {
72
87
  readonly toolRestriction: boolean;
73
88
  /** Which image types the engine can display inline; the runtime degrades the rest before the turn. */
74
89
  readonly visionProfile: VisionProfile;
90
+ /** How the harness's captured file changes are identified and what its own repo writes are excluded from a diff. */
91
+ readonly fileReview: FileReviewIdentity;
75
92
  }