@stigmer/runner 3.14.1 → 3.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/__test-utils__/execution-record-fixture.d.ts +47 -0
  3. package/dist/__test-utils__/execution-record-fixture.js +68 -0
  4. package/dist/__test-utils__/execution-record-fixture.js.map +1 -0
  5. package/dist/__test-utils__/harness-contract/types.d.ts +166 -0
  6. package/dist/__test-utils__/harness-contract/types.js +49 -0
  7. package/dist/__test-utils__/harness-contract/types.js.map +1 -0
  8. package/dist/__test-utils__/hermetic-activity.d.ts +28 -5
  9. package/dist/__test-utils__/hermetic-activity.js +23 -7
  10. package/dist/__test-utils__/hermetic-activity.js.map +1 -1
  11. package/dist/__test-utils__/mock-workspace.d.ts +15 -0
  12. package/dist/__test-utils__/mock-workspace.js +21 -0
  13. package/dist/__test-utils__/mock-workspace.js.map +1 -0
  14. package/dist/__test-utils__/model-registry-fixture.d.ts +56 -0
  15. package/dist/__test-utils__/model-registry-fixture.js +66 -0
  16. package/dist/__test-utils__/model-registry-fixture.js.map +1 -0
  17. package/dist/__test-utils__/turn-input-fixture.d.ts +60 -0
  18. package/dist/__test-utils__/turn-input-fixture.js +118 -0
  19. package/dist/__test-utils__/turn-input-fixture.js.map +1 -0
  20. package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +107 -0
  21. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +344 -0
  22. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -0
  23. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.d.ts +20 -0
  24. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js +58 -0
  25. package/dist/activities/execute-cursor/__test-utils__/cursor-hook-harness.js.map +1 -1
  26. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +2 -32
  27. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
  28. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +38 -62
  29. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +47 -121
  30. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
  31. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +17 -1
  32. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +27 -4
  33. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -1
  34. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.d.ts +52 -11
  35. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js +66 -7
  36. package/dist/activities/execute-cursor/__test-utils__/scripted-sdk.js.map +1 -1
  37. package/dist/activities/execute-cursor/adapter.d.ts +55 -0
  38. package/dist/activities/execute-cursor/adapter.js +119 -0
  39. package/dist/activities/execute-cursor/adapter.js.map +1 -0
  40. package/dist/activities/execute-cursor/approval-state.d.ts +2 -2
  41. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  42. package/dist/activities/execute-cursor/capture-flow.d.ts +15 -30
  43. package/dist/activities/execute-cursor/capture-flow.js +17 -26
  44. package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
  45. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +23 -0
  46. package/dist/activities/execute-cursor/cursor-capabilities.js +33 -0
  47. package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -0
  48. package/dist/activities/execute-cursor/cursor-mcp-config.d.ts +2 -2
  49. package/dist/activities/execute-cursor/cursor-mcp-config.js.map +1 -1
  50. package/dist/activities/execute-cursor/fetch-interceptor.d.ts +15 -25
  51. package/dist/activities/execute-cursor/fetch-interceptor.js +39 -33
  52. package/dist/activities/execute-cursor/fetch-interceptor.js.map +1 -1
  53. package/dist/activities/execute-cursor/http2-interceptor.d.ts +21 -17
  54. package/dist/activities/execute-cursor/http2-interceptor.js +38 -31
  55. package/dist/activities/execute-cursor/http2-interceptor.js.map +1 -1
  56. package/dist/activities/execute-cursor/message-translator.d.ts +28 -19
  57. package/dist/activities/execute-cursor/message-translator.js +76 -27
  58. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  59. package/dist/activities/execute-cursor/prompt-builder.d.ts +180 -11
  60. package/dist/activities/execute-cursor/prompt-builder.js +171 -0
  61. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  62. package/dist/activities/execute-cursor/rejection-capture.d.ts +3 -6
  63. package/dist/activities/execute-cursor/rejection-capture.js +5 -7
  64. package/dist/activities/execute-cursor/rejection-capture.js.map +1 -1
  65. package/dist/activities/execute-cursor/session-lifecycle.d.ts +1 -4
  66. package/dist/activities/execute-cursor/session-lifecycle.js.map +1 -1
  67. package/dist/activities/execute-cursor/turn-boundary.d.ts +1 -1
  68. package/dist/activities/execute-cursor/turn-settle.d.ts +49 -0
  69. package/dist/activities/execute-cursor/turn-settle.js +507 -0
  70. package/dist/activities/execute-cursor/turn-settle.js.map +1 -0
  71. package/dist/activities/execute-cursor/turn-setup.d.ts +173 -0
  72. package/dist/activities/execute-cursor/turn-setup.js +496 -0
  73. package/dist/activities/execute-cursor/turn-setup.js.map +1 -0
  74. package/dist/activities/execute-cursor/turn-stream.d.ts +41 -71
  75. package/dist/activities/execute-cursor/turn-stream.js +70 -135
  76. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  77. package/dist/activities/execute-cursor/turn.d.ts +43 -0
  78. package/dist/activities/execute-cursor/turn.js +222 -0
  79. package/dist/activities/execute-cursor/turn.js.map +1 -0
  80. package/dist/activities/execute-cursor/usage-pricing.d.ts +38 -0
  81. package/dist/activities/execute-cursor/usage-pricing.js +50 -0
  82. package/dist/activities/execute-cursor/usage-pricing.js.map +1 -0
  83. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +1 -1
  84. package/dist/activities/execute-deep-agent/approval-file-change.js +1 -1
  85. package/dist/activities/execute-deep-agent/environment.js +1 -1
  86. package/dist/activities/execute-deep-agent/environment.js.map +1 -1
  87. package/dist/activities/execute-deep-agent/setup.js +3 -2
  88. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  89. package/dist/activities/hydrate-workflow-execution.js +1 -1
  90. package/dist/activities/hydrate-workflow-execution.js.map +1 -1
  91. package/dist/config.d.ts +15 -0
  92. package/dist/config.js.map +1 -1
  93. package/dist/harness/capabilities.d.ts +16 -0
  94. package/dist/harness/capabilities.js +1 -0
  95. package/dist/harness/capabilities.js.map +1 -1
  96. package/dist/harness/persist-chokepoint.d.ts +74 -0
  97. package/dist/harness/persist-chokepoint.js +88 -0
  98. package/dist/harness/persist-chokepoint.js.map +1 -0
  99. package/dist/harness/registry.d.ts +44 -5
  100. package/dist/harness/registry.js +61 -5
  101. package/dist/harness/registry.js.map +1 -1
  102. package/dist/harness/run-turn.d.ts +53 -0
  103. package/dist/harness/run-turn.js +528 -0
  104. package/dist/harness/run-turn.js.map +1 -0
  105. package/dist/harness/stop-controller.d.ts +50 -0
  106. package/dist/harness/stop-controller.js +62 -0
  107. package/dist/harness/stop-controller.js.map +1 -0
  108. package/dist/harness/terminal-table.d.ts +141 -0
  109. package/dist/harness/terminal-table.js +239 -0
  110. package/dist/harness/terminal-table.js.map +1 -0
  111. package/dist/harness/turn-context.d.ts +523 -0
  112. package/dist/harness/turn-context.js +739 -0
  113. package/dist/harness/turn-context.js.map +1 -0
  114. package/dist/harness/types.d.ts +232 -52
  115. package/dist/harness/types.js +17 -8
  116. package/dist/harness/types.js.map +1 -1
  117. package/dist/harness/usage-accumulator.d.ts +68 -0
  118. package/dist/harness/usage-accumulator.js +101 -0
  119. package/dist/harness/usage-accumulator.js.map +1 -0
  120. package/dist/harness-adapters.d.ts +27 -0
  121. package/dist/harness-adapters.js +28 -0
  122. package/dist/harness-adapters.js.map +1 -0
  123. package/dist/main.js.map +1 -1
  124. package/dist/runner-manager.js +64 -48
  125. package/dist/runner-manager.js.map +1 -1
  126. package/dist/runner.d.ts +5 -4
  127. package/dist/runner.js +65 -64
  128. package/dist/runner.js.map +1 -1
  129. package/dist/shared/approval-policy.d.ts +1 -1
  130. package/dist/shared/approval-policy.js.map +1 -1
  131. package/dist/{activities/execute-cursor → shared}/attachment-resolver.d.ts +2 -2
  132. package/dist/{activities/execute-cursor → shared}/attachment-resolver.js +5 -5
  133. package/dist/shared/attachment-resolver.js.map +1 -0
  134. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.d.ts +7 -5
  135. package/dist/{activities/execute-cursor → shared}/blueprint-resolver.js +3 -63
  136. package/dist/shared/blueprint-resolver.js.map +1 -0
  137. package/dist/shared/channel-attachment.d.ts +1 -1
  138. package/dist/shared/channel-attachment.js.map +1 -1
  139. package/dist/{activities/execute-cursor → shared}/cost-guard.d.ts +5 -1
  140. package/dist/{activities/execute-cursor → shared}/cost-guard.js +5 -1
  141. package/dist/shared/cost-guard.js.map +1 -0
  142. package/dist/{activities/execute-cursor → shared}/env-resolver.d.ts +1 -1
  143. package/dist/shared/env-resolver.js.map +1 -0
  144. package/dist/{activities/execute-cursor → shared}/exact-apply.d.ts +1 -1
  145. package/dist/{activities/execute-cursor → shared}/exact-apply.js +6 -6
  146. package/dist/shared/exact-apply.js.map +1 -0
  147. package/dist/shared/execution-context.d.ts +35 -0
  148. package/dist/shared/execution-context.js +38 -0
  149. package/dist/shared/execution-context.js.map +1 -0
  150. package/dist/{activities/execute-cursor → shared}/extract-structured-output.d.ts +5 -2
  151. package/dist/{activities/execute-cursor → shared}/extract-structured-output.js +9 -6
  152. package/dist/shared/extract-structured-output.js.map +1 -0
  153. package/dist/shared/filereview/capture.d.ts +1 -1
  154. package/dist/shared/filereview/capture.js +1 -1
  155. package/dist/shared/heartbeat.d.ts +13 -11
  156. package/dist/shared/heartbeat.js +14 -29
  157. package/dist/shared/heartbeat.js.map +1 -1
  158. package/dist/{activities/execute-cursor → shared}/persist-decision.d.ts +5 -2
  159. package/dist/{activities/execute-cursor → shared}/persist-decision.js +4 -1
  160. package/dist/shared/persist-decision.js.map +1 -0
  161. package/dist/{activities/execute-cursor → shared}/skill-resolver.d.ts +16 -3
  162. package/dist/{activities/execute-cursor → shared}/skill-resolver.js +12 -4
  163. package/dist/shared/skill-resolver.js.map +1 -0
  164. package/dist/shared/subagent-rows.d.ts +21 -0
  165. package/dist/shared/subagent-rows.js +34 -0
  166. package/dist/shared/subagent-rows.js.map +1 -0
  167. package/dist/shared/worker-shutdown.d.ts +43 -16
  168. package/dist/shared/worker-shutdown.js +28 -13
  169. package/dist/shared/worker-shutdown.js.map +1 -1
  170. package/dist/shared/workspace/platform-dir.d.ts +1 -1
  171. package/dist/shared/workspace/platform-dir.js +1 -1
  172. package/dist/shared/workspace/session-provision.d.ts +54 -0
  173. package/dist/{activities/execute-cursor/workspace-provision.js → shared/workspace/session-provision.js} +15 -12
  174. package/dist/shared/workspace/session-provision.js.map +1 -0
  175. package/package.json +4 -4
  176. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +110 -14
  177. package/src/__test-utils__/execution-record-fixture.ts +104 -0
  178. package/src/__test-utils__/harness-boot-order-child.ts +58 -0
  179. package/src/__test-utils__/harness-contract/contract.ts +128 -83
  180. package/src/__test-utils__/harness-contract/recording-sink.ts +15 -4
  181. package/src/__test-utils__/harness-contract/runtime-contract.ts +666 -0
  182. package/src/__test-utils__/harness-contract/scripted-adapter.ts +92 -32
  183. package/src/__test-utils__/harness-contract/types.ts +85 -9
  184. package/src/__test-utils__/hermetic-activity.ts +33 -8
  185. package/src/__test-utils__/model-registry-fixture.ts +72 -0
  186. package/src/__test-utils__/module-specifiers.ts +82 -0
  187. package/src/__test-utils__/turn-input-fixture.ts +155 -0
  188. package/src/__tests__/harness-boot-order.test.ts +77 -0
  189. package/src/__tests__/harness-contract.test.ts +7 -5
  190. package/src/__tests__/runner-manager.test.ts +18 -0
  191. package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +102 -0
  192. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +396 -0
  193. package/src/activities/execute-cursor/__test-utils__/cursor-hook-harness.ts +66 -0
  194. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +2 -43
  195. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +65 -145
  196. package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +30 -4
  197. package/src/activities/execute-cursor/__test-utils__/scripted-sdk.ts +96 -8
  198. package/src/activities/execute-cursor/__tests__/adapter-is-temporal-free.test.ts +45 -0
  199. package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +90 -0
  200. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -8
  201. package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +28 -1
  202. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +1 -1
  203. package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +64 -6
  204. package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +1 -1
  205. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -1
  206. package/src/activities/execute-cursor/__tests__/hermetic/goldens/cost-cap.status.json +53 -0
  207. package/src/activities/execute-cursor/__tests__/hermetic/goldens/pause.status.json +1 -6
  208. package/src/activities/execute-cursor/__tests__/hermetic/goldens/platform-stop.status.json +56 -0
  209. package/src/activities/execute-cursor/__tests__/hermetic/goldens/recovery-transport-timeout.status.json +48 -0
  210. package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
  211. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-cancelled.status.json +36 -0
  212. package/src/activities/execute-cursor/__tests__/hermetic/goldens/run-error-non-retryable.status.json +37 -0
  213. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +38 -0
  214. package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +59 -0
  215. package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +79 -0
  216. package/src/activities/execute-cursor/__tests__/hermetic/goldens/worker-shutdown.status.json +2 -7
  217. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +170 -0
  218. package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +22 -18
  219. package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +1 -1
  220. package/src/activities/execute-cursor/__tests__/hermetic/recovery-fresh-agent.test.ts +1 -1
  221. package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +286 -0
  222. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +332 -0
  223. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +193 -0
  224. package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +1 -1
  225. package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +185 -0
  226. package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +151 -0
  227. package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +65 -35
  228. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -40
  229. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +171 -0
  230. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +118 -243
  231. package/src/activities/execute-cursor/__tests__/usage-pricing.test.ts +84 -0
  232. package/src/activities/execute-cursor/adapter.ts +138 -0
  233. package/src/activities/execute-cursor/approval-state.ts +2 -2
  234. package/src/activities/execute-cursor/capture-flow.ts +18 -41
  235. package/src/activities/execute-cursor/cursor-capabilities.ts +35 -0
  236. package/src/activities/execute-cursor/cursor-mcp-config.ts +2 -2
  237. package/src/activities/execute-cursor/fetch-interceptor.ts +48 -45
  238. package/src/activities/execute-cursor/http2-interceptor.ts +47 -34
  239. package/src/activities/execute-cursor/message-translator.ts +90 -45
  240. package/src/activities/execute-cursor/prompt-builder.ts +327 -12
  241. package/src/activities/execute-cursor/rejection-capture.ts +5 -12
  242. package/src/activities/execute-cursor/session-lifecycle.ts +1 -5
  243. package/src/activities/execute-cursor/turn-boundary.ts +1 -1
  244. package/src/activities/execute-cursor/turn-settle.ts +601 -0
  245. package/src/activities/execute-cursor/turn-setup.ts +645 -0
  246. package/src/activities/execute-cursor/turn-stream.ts +84 -210
  247. package/src/activities/execute-cursor/turn.ts +240 -0
  248. package/src/activities/execute-cursor/usage-pricing.ts +67 -0
  249. package/src/activities/execute-deep-agent/approval-file-change.ts +1 -1
  250. package/src/activities/execute-deep-agent/environment.ts +1 -1
  251. package/src/activities/execute-deep-agent/setup.ts +3 -2
  252. package/src/activities/hydrate-workflow-execution.ts +1 -1
  253. package/src/config.ts +13 -0
  254. package/src/harness/__tests__/import-direction.test.ts +168 -0
  255. package/src/harness/__tests__/persist-chokepoint.test.ts +146 -0
  256. package/src/harness/__tests__/registry.test.ts +1 -0
  257. package/src/harness/__tests__/run-turn.test.ts +165 -0
  258. package/src/harness/__tests__/stop-controller.test.ts +49 -0
  259. package/src/harness/__tests__/turn-context.test.ts +178 -0
  260. package/src/harness/__tests__/usage-accumulator.test.ts +83 -0
  261. package/src/harness/capabilities.ts +17 -0
  262. package/src/harness/persist-chokepoint.ts +112 -0
  263. package/src/harness/registry.ts +79 -5
  264. package/src/harness/run-turn.ts +618 -0
  265. package/src/harness/stop-controller.ts +83 -0
  266. package/src/harness/terminal-table.ts +276 -0
  267. package/src/harness/turn-context.ts +1028 -0
  268. package/src/harness/types.ts +248 -53
  269. package/src/harness/usage-accumulator.ts +122 -0
  270. package/src/harness-adapters.ts +30 -0
  271. package/src/main.ts +1 -1
  272. package/src/runner-manager.ts +68 -61
  273. package/src/runner.ts +67 -78
  274. package/src/shared/__tests__/attachment-naming.test.ts +1 -1
  275. package/src/{activities/execute-cursor → shared}/__tests__/attachment-resolver.test.ts +6 -4
  276. package/src/{activities/execute-cursor → shared}/__tests__/cost-guard.test.ts +2 -1
  277. package/src/{activities/execute-cursor → shared}/__tests__/exact-apply.test.ts +3 -3
  278. package/src/{activities/execute-cursor → shared}/__tests__/extract-structured-output.test.ts +8 -8
  279. package/src/{activities/execute-cursor → shared}/__tests__/persist-decision.test.ts +4 -2
  280. package/src/{activities/execute-cursor → shared}/__tests__/skill-resolver.test.ts +1 -1
  281. package/src/shared/__tests__/subagent-rows.test.ts +34 -0
  282. package/src/shared/__tests__/worker-shutdown.test.ts +44 -39
  283. package/src/shared/approval-policy.ts +1 -1
  284. package/src/{activities/execute-cursor → shared}/attachment-resolver.ts +6 -6
  285. package/src/{activities/execute-cursor → shared}/blueprint-resolver.ts +7 -78
  286. package/src/shared/channel-attachment.ts +1 -1
  287. package/src/{activities/execute-cursor → shared}/cost-guard.ts +5 -1
  288. package/src/{activities/execute-cursor → shared}/env-resolver.ts +1 -1
  289. package/src/{activities/execute-cursor → shared}/exact-apply.ts +7 -7
  290. package/src/shared/execution-context.ts +45 -0
  291. package/src/{activities/execute-cursor → shared}/extract-structured-output.ts +10 -7
  292. package/src/shared/filereview/capture.ts +1 -1
  293. package/src/shared/heartbeat.ts +13 -33
  294. package/src/{activities/execute-cursor → shared}/persist-decision.ts +5 -2
  295. package/src/{activities/execute-cursor → shared}/skill-resolver.ts +20 -6
  296. package/src/shared/subagent-rows.ts +39 -0
  297. package/src/shared/worker-shutdown.ts +55 -25
  298. package/src/{activities/execute-cursor/__tests__/workspace-provision.test.ts → shared/workspace/__tests__/session-provision.test.ts} +25 -28
  299. package/src/shared/workspace/platform-dir.ts +1 -1
  300. package/src/{activities/execute-cursor/workspace-provision.ts → shared/workspace/session-provision.ts} +29 -16
  301. package/dist/activities/execute-cursor/attachment-resolver.js.map +0 -1
  302. package/dist/activities/execute-cursor/blueprint-resolver.js.map +0 -1
  303. package/dist/activities/execute-cursor/cost-guard.js.map +0 -1
  304. package/dist/activities/execute-cursor/env-resolver.js.map +0 -1
  305. package/dist/activities/execute-cursor/exact-apply.js.map +0 -1
  306. package/dist/activities/execute-cursor/extract-structured-output.js.map +0 -1
  307. package/dist/activities/execute-cursor/index.d.ts +0 -209
  308. package/dist/activities/execute-cursor/index.js +0 -2345
  309. package/dist/activities/execute-cursor/index.js.map +0 -1
  310. package/dist/activities/execute-cursor/persist-decision.js.map +0 -1
  311. package/dist/activities/execute-cursor/skill-resolver.js.map +0 -1
  312. package/dist/activities/execute-cursor/usage-accumulator.d.ts +0 -89
  313. package/dist/activities/execute-cursor/usage-accumulator.js +0 -121
  314. package/dist/activities/execute-cursor/usage-accumulator.js.map +0 -1
  315. package/dist/activities/execute-cursor/workspace-provision.d.ts +0 -42
  316. package/dist/activities/execute-cursor/workspace-provision.js.map +0 -1
  317. package/src/activities/execute-cursor/__tests__/usage-accumulator.test.ts +0 -164
  318. package/src/activities/execute-cursor/index.ts +0 -2876
  319. package/src/activities/execute-cursor/usage-accumulator.ts +0 -169
  320. /package/dist/{activities/execute-cursor → shared}/env-resolver.js +0 -0
@@ -0,0 +1,1028 @@
1
+ /**
2
+ * The turn context — everything the runtime resolves for one agent turn
3
+ * before any harness runs, as small named phases producing one typed record.
4
+ *
5
+ * A turn begins with a dozen reads and provisions that have nothing to do
6
+ * with the engine that will run it: the execution and its session, the
7
+ * agent blueprint, the environment, the workspace and its lock, what the
8
+ * previous turn left waiting for approval, the MCP servers and their
9
+ * approval policies, the attachments, the standing context. Until S2 M2
10
+ * that sequence lived inline in the Cursor orchestrator
11
+ * (`activities/execute-cursor/index.ts` `executeCursorInner`, phases 1 to
12
+ * 9c); the native orchestrator carried a second copy in its `performSetup`.
13
+ * This module is the one copy. Each phase is a function that reads its
14
+ * inputs, reports its progress label, heartbeats once on entry, and returns
15
+ * a typed slice of the contract's `TurnInput`; {@link resolveTurnContext}
16
+ * sequences them, and the adapter runs its own harness-specific steps after
17
+ * the whole record exists.
18
+ *
19
+ * Shape, chosen against `execute-deep-agent/setup.ts`: a typed record built
20
+ * from small functions (the `SetupResult` mold), never one 775-line
21
+ * `performSetup`. The record is the adapter contract's `TurnInput`
22
+ * (`types.ts`): every phase returns a named slice of it, and
23
+ * {@link resolveTurnContext} composes the whole. There is no wider
24
+ * "runtime-private" record beside it — the lock release and the write-back
25
+ * coordinator a phase produces are the caller's `TurnFrame` resources, set
26
+ * the instant they exist so a `finally` sees them on every path (M2 had a
27
+ * `TurnContext extends TurnInput` for them; once the groups lifted, nothing
28
+ * read the extension after composition, so M3 deleted it).
29
+ *
30
+ * Three rules every phase obeys:
31
+ *
32
+ * - It never persists and never returns a slim status. A phase that can end
33
+ * the turn (the lock, the reinvocation reconcile) returns a
34
+ * {@link TurnSettlement} naming WHAT was decided; the caller writes the
35
+ * status, persists once and returns. The lock golden pins that persist as
36
+ * the turn's one and only, which is why the rule is structural.
37
+ * - It imports nothing from `src/activities/` (pinned by
38
+ * `__tests__/import-direction.test.ts`). A fact only the harness knows
39
+ * (its file-review identity, its vision profile) arrives through
40
+ * `HarnessCapabilities`.
41
+ * - Its heartbeat pulse and its cold-start timing marks are its own: the
42
+ * step name it enters under is the mark name it emits, one vocabulary for
43
+ * "where in setup are we" in Temporal heartbeat details and the
44
+ * `execution_setup` log (Q-S2-10).
45
+ *
46
+ * What is deliberately NOT here, and where it is instead:
47
+ *
48
+ * - Cursor's catalog validation of the requested model (`resolveModelId`),
49
+ * the pricing preload and the MCP env pre-flight warning stay in the
50
+ * Cursor adapter for S2: each reads a module in the adapter column of the
51
+ * entry's disposition table, and the runtime carries the REQUESTED model
52
+ * name so every harness validates against its own catalog (S2 M2 gate,
53
+ * Q-M2-1). The skill mount was in that list until M3b found it reads the
54
+ * control plane, which only the runtime holds; it is {@link mountSkills}.
55
+ * - The approval grants, the HITL gate, the capture baseline pin and the
56
+ * Cursor agent itself (phases 5c, 7, 8a, 9) are the adapter's.
57
+ * - The memory selection is a memoized thunk, not a value: whether a prompt
58
+ * carries standing context depends on how the ENGINE resolved (a resumed
59
+ * agent already holds it), which the runtime learns only after the
60
+ * adapter runs. The runtime prepares the selection once; the adapter
61
+ * pulls it when its prompt needs it.
62
+ *
63
+ * Every `index.ts` line reference below was read from `2c06bc7ea` on
64
+ * 2026-09-11, before the orchestrator moved onto this module.
65
+ */
66
+
67
+ import { CancelledFailure } from "@temporalio/activity";
68
+ import { clone } from "@bufbuild/protobuf";
69
+ import type { AgentExecution, AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
70
+ import type { AgentExecutionSpec } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
71
+ import { AgentMessageSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
72
+ import { ApprovalAction, FileChangeSetStatus, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
73
+ import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
74
+
75
+ import type { Config } from "../config.js";
76
+ import type { StigmerClient } from "../client/stigmer-client.js";
77
+ import type { NormalizedActivityInput } from "../shared/activity-input.js";
78
+ import type { ArtifactStorage } from "../shared/artifact-storage.js";
79
+ import type { TimingRecorder } from "../shared/cold-start-timing.js";
80
+ import { resolveBlueprint, type ResolvedBlueprint } from "../shared/blueprint-resolver.js";
81
+ import { resolveExecutionEnv } from "../shared/env-resolver.js";
82
+ import { provisionSessionWorkspace } from "../shared/workspace/session-provision.js";
83
+ import { WriteBackCoordinator } from "../shared/workspace/writeback-coordinator.js";
84
+ import { statusProtoWriter } from "../shared/execution-status-writer.js";
85
+ import { isGitWorkTree } from "../shared/filereview/git-substrate.js";
86
+ import { applyCaptureDecisions, deriveCaptureMode } from "../shared/filereview/capture.js";
87
+ import { casBlobReader } from "../shared/filereview/cas-substrate.js";
88
+ import {
89
+ acquireWorkspaceLock,
90
+ WorkspaceLockCancelledError,
91
+ WorkspaceLockTimeoutError,
92
+ type ReleaseWorkspaceLock,
93
+ } from "../shared/workspace/workspace-lock.js";
94
+ import { LocalWorkspaceBackend } from "../shared/workspace/local-backend.js";
95
+ import { resolveMcpServers } from "../shared/mcp-resolver.js";
96
+ import { resolveMcpTransportPosture } from "../shared/mcp-transport-guard.js";
97
+ import { injectCallerIdentityEnv, resolveCallerIdentity } from "../shared/caller-identity.js";
98
+ import { backfillMcpServersIfNeeded } from "../shared/connect-backfill.js";
99
+ import { discoverChannelMessaging, synthesizeChannelAttachment } from "../shared/channel-attachment.js";
100
+ import { readChannelConversationId, synthesizeConversationAttachment } from "../shared/conversation-attachment.js";
101
+ import { synthesizeMemoryAttachment } from "../shared/memory-attachment.js";
102
+ import { injectSynthesizedAttachment } from "../shared/synthesized-attachment.js";
103
+ import { deriveActiveLeases, mergeApprovalPolicies } from "../shared/approval-policy.js";
104
+ import { resolveAttachments } from "../shared/attachment-resolver.js";
105
+ import { resolveSkills, type SkillMetadata } from "../shared/skill-resolver.js";
106
+ import { VisionBudget, type NotViewableEntry, type VisionProfile } from "../shared/attachment-vision.js";
107
+ import { getModelVisionCapability } from "../shared/model-registry.js";
108
+ import { applyApprovedWholeFileWrites } from "../shared/exact-apply.js";
109
+ import { resolveEffectiveServiceTier } from "../shared/service-tier.js";
110
+ import { resolveEffectiveThinkingMode } from "../shared/thinking-mode.js";
111
+ import { readContextBridge } from "../shared/context-bridge.js";
112
+ import { readSenderIdentity } from "../shared/sender-identity.js";
113
+ import { readSessionContext } from "../shared/session-context.js";
114
+ import { readDeclaredPreferences } from "../shared/declared-preferences.js";
115
+ import { readConversationCatchup } from "../shared/conversation-catchup.js";
116
+ import { selectRecalledFacts } from "../shared/memory-retrieval.js";
117
+ import type { RecalledMemoriesContent } from "../shared/recalled-memories.js";
118
+ import type { FileReviewIdentity, HarnessCapabilities, StateIdSource } from "./capabilities.js";
119
+ import type {
120
+ TurnAttachments,
121
+ TurnEnvironment,
122
+ TurnInput,
123
+ TurnMcp,
124
+ TurnModelPreferences,
125
+ TurnStandingContext,
126
+ TurnWorkspace,
127
+ } from "./types.js";
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // What the phases take
131
+ // ---------------------------------------------------------------------------
132
+
133
+ /**
134
+ * What every phase reads from its caller. The activity's identity and the
135
+ * runtime's own seams; nothing harness-specific. The four function members
136
+ * are the caller's, injected so the phase bodies are the same code under
137
+ * the Cursor orchestrator today and under `run-turn.ts` in M3 (only the
138
+ * binding changes), and so a test can drive a phase without a Temporal
139
+ * activity context.
140
+ */
141
+ export interface ResolutionDeps {
142
+ /** The activity's own identity: execution id, engine state id, HITL cycle index. */
143
+ readonly input: NormalizedActivityInput;
144
+ readonly client: StigmerClient;
145
+ /**
146
+ * The whole runner config. The runtime is its rightful reader; a shared
147
+ * helper that needs two fields is handed a narrowed view of it
148
+ * (`SessionProvisionConfig`), never the other way round.
149
+ */
150
+ readonly config: Config;
151
+ /**
152
+ * The one execution status this turn folds into. Phases that carry state
153
+ * across invocations write here (the seeded transcript, the file-review
154
+ * reconcile's events, the memory-selection report); nothing else does.
155
+ */
156
+ readonly status: AgentExecutionStatus;
157
+ /** Resolved once by the caller before any phase; absent when no substrate works. */
158
+ readonly artifactStorage: ArtifactStorage | undefined;
159
+ /** The cold-start timeline; each phase marks its own segment(s) exactly as before. */
160
+ readonly timing: TimingRecorder;
161
+ /** Cancellation for the one wait a phase can block on (the workspace lock). */
162
+ readonly signal: AbortSignal;
163
+ /** The Temporal heartbeat, handed to helpers that pulse mid-call (the lock wait, the MCP backfill). */
164
+ readonly heartbeat: () => void;
165
+ /**
166
+ * "Resolution is now at this step": labels the caller's periodic heartbeat
167
+ * and pulses once. Every phase calls it first with its own step name.
168
+ */
169
+ readonly enterPhase: (step: string) => void;
170
+ /** The user-visible setup label (`setupProgress`); the six strings are unchanged from the orchestrator. */
171
+ readonly reportProgress: (label: string) => Promise<void>;
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // What the reinvocation phase answers
176
+ // ---------------------------------------------------------------------------
177
+
178
+ /**
179
+ * What the previous invocation left for this one (phase 3). Not part of
180
+ * `TurnInput`: `isReinvocation` is the adapter's own derivation from
181
+ * `threadId` (the contract carries no flag by design, `types.ts` header) and
182
+ * the runtime reads it only inside {@link resolveTurnContext}; the decisions
183
+ * ARE on the input. The seeded sub-agent rows are the adapter's to clone from
184
+ * `execution.status` (its accumulator owns `status.subAgentExecutions` and
185
+ * overwrites it on every flush), so they ride nothing here.
186
+ */
187
+ export interface TurnReinvocation {
188
+ /** The engine already holds state for this execution (derived per {@link isReinvocation}). */
189
+ readonly isReinvocation: boolean;
190
+ /** The adjudicated WAITING rows, keyed by tool-call id; empty when nothing was decided. */
191
+ readonly approvalDecisions: ReadonlyMap<string, ApprovalAction>;
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Settlements
196
+ // ---------------------------------------------------------------------------
197
+
198
+ /**
199
+ * A turn that ended during resolution, before any harness ran. Each arm
200
+ * names what was decided and carries only the facts the caller's status
201
+ * writes need; the copy, the phase flip, the persist and the slim return are
202
+ * the caller's (the orchestrator today, the runtime's terminal table in M3).
203
+ *
204
+ * - `workspace-lock-timeout`: another turn held the primary tree past
205
+ * `Config.workspaceLockTimeoutMs`. FAILED, returned (a Temporal retry
206
+ * would queue behind the same holder).
207
+ * - `rejected-by-user`: an adjudicated irreversible action (shell / MCP)
208
+ * was REJECTed. FAILED, returned.
209
+ * - `file-review-resolved`: a pure file-review resume; the agent already
210
+ * finished its turn during capture and the reconcile is the whole act.
211
+ * COMPLETED, returned, with the write-back finalized first when the
212
+ * reconcile held.
213
+ */
214
+ export type TurnSettlement =
215
+ | { readonly kind: "workspace-lock-timeout"; readonly error: WorkspaceLockTimeoutError }
216
+ | { readonly kind: "rejected-by-user" }
217
+ | {
218
+ readonly kind: "file-review-resolved";
219
+ readonly failed: boolean;
220
+ readonly failureDetail: string;
221
+ readonly discardedPaths: readonly string[];
222
+ };
223
+
224
+ /** The lock phase's answer: the release handle (absent when there is no primary tree), or the one settlement a lock can produce. */
225
+ export type WorkspaceLockOutcome =
226
+ | { readonly kind: "acquired"; readonly release: ReleaseWorkspaceLock | undefined }
227
+ | { readonly kind: "settled"; readonly settlement: Extract<TurnSettlement, { kind: "workspace-lock-timeout" }> };
228
+
229
+ /** The reinvocation phase's answer: what the previous invocation left, or one of the two settlements a reconcile can produce. */
230
+ export type ReinvocationOutcome =
231
+ | { readonly kind: "ready"; readonly reinvocation: TurnReinvocation }
232
+ | { readonly kind: "settled"; readonly settlement: Exclude<TurnSettlement, { kind: "workspace-lock-timeout" }> };
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Pure helpers (exported for their tests and for the adapter kit)
236
+ // ---------------------------------------------------------------------------
237
+
238
+ /**
239
+ * Whether the engine already holds state for this execution, so this
240
+ * invocation resumes rather than begins. One function keyed on how the
241
+ * harness mints its state id (Q-S2-8):
242
+ *
243
+ * - `engine-minted` (Cursor: the agent id): the runtime knows nothing until
244
+ * the adapter binds an id, so a non-empty `threadId` IS the evidence.
245
+ * Correct today because a pure-reconcile turn does not advance `turnSeq`
246
+ * and the legacy positional wire shape yields 0, so `turnSeq` cannot be
247
+ * the signal (`index.ts` L402-407).
248
+ * - `deterministic` (native: the runtime-minted thread id): the id exists
249
+ * on the FIRST turn too, so the evidence is the persisted transcript
250
+ * (pending approvals, DECIDED sets). That arm lands in S3 with the native
251
+ * adapter; until then this function refuses rather than guess.
252
+ */
253
+ export function isReinvocation(stateIdSource: StateIdSource, input: NormalizedActivityInput): boolean {
254
+ switch (stateIdSource) {
255
+ case "engine-minted":
256
+ return input.threadId !== "";
257
+ case "deterministic":
258
+ throw new Error(
259
+ "isReinvocation: the deterministic arm lands with the native adapter (S3); no S2 harness mints its state id deterministically",
260
+ );
261
+ default: {
262
+ const exhaustive: never = stateIdSource;
263
+ throw new Error(`isReinvocation: unknown state id source ${String(exhaustive)}`);
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * The runtime's one reader of approval decisions: every top-level tool-call
270
+ * row still WAITING_APPROVAL whose `approvalAction` the server has set,
271
+ * keyed by tool-call id. Derived from the status on every invocation, never
272
+ * stored, so it cannot drift from the rows. The same rows, in the same
273
+ * order, that the Cursor adapter's `reconstructAdjudicatedApprovals` reads
274
+ * for its own facts (the pending-approval protos and content digests); a
275
+ * test pins the two readers' agreement.
276
+ */
277
+ export function approvalDecisionsOf(status: AgentExecutionStatus): ReadonlyMap<string, ApprovalAction> {
278
+ const decisions = new Map<string, ApprovalAction>();
279
+ for (const message of status.messages) {
280
+ for (const row of message.toolCalls) {
281
+ if (row.status !== ToolCallStatus.TOOL_CALL_WAITING_APPROVAL) continue;
282
+ if (row.approvalAction === ApprovalAction.UNSPECIFIED) continue;
283
+ decisions.set(row.id, row.approvalAction);
284
+ }
285
+ }
286
+ return decisions;
287
+ }
288
+
289
+ /** The facts the reinvocation decision reads; produced by {@link reconcileReinvocation}, exposed for the decision's own tests. */
290
+ export interface ReinvocationFacts {
291
+ readonly approvalDecisions: ReadonlyMap<string, ApprovalAction>;
292
+ /** At least one DECIDED change set was reconciled this invocation. */
293
+ readonly reconciledFileReview: boolean;
294
+ /** A reconcile could not honor what-you-approve-is-what-applies (HASH_MISMATCH). */
295
+ readonly fileReviewFailed: boolean;
296
+ readonly fileReviewFailureDetail: string;
297
+ readonly discardedPaths: readonly string[];
298
+ }
299
+
300
+ /**
301
+ * How a reinvocation proceeds, decided once from the facts (`index.ts`
302
+ * L547-618): a REJECT of an irreversible action fails the turn; any other
303
+ * adjudicated decision runs the agent (the approved shell/MCP proceeds and
304
+ * may produce further edits); a resume that reconciled file review and
305
+ * decided nothing else is complete, because keeping or discarding a file
306
+ * never re-prompts the agent (Cursor-like). `undefined` means "run the
307
+ * agent".
308
+ */
309
+ export function decideReinvocation(
310
+ facts: ReinvocationFacts,
311
+ ): Exclude<TurnSettlement, { kind: "workspace-lock-timeout" }> | undefined {
312
+ if (facts.approvalDecisions.size > 0) {
313
+ const hasReject = [...facts.approvalDecisions.values()].some((a) => a === ApprovalAction.REJECT);
314
+ return hasReject ? { kind: "rejected-by-user" } : undefined;
315
+ }
316
+ if (facts.reconciledFileReview) {
317
+ return {
318
+ kind: "file-review-resolved",
319
+ failed: facts.fileReviewFailed,
320
+ failureDetail: facts.fileReviewFailureDetail,
321
+ discardedPaths: facts.discardedPaths,
322
+ };
323
+ }
324
+ return undefined;
325
+ }
326
+
327
+ /**
328
+ * Seed an in-progress status from the persisted execution on a durable
329
+ * resume (HITL approval, pause/resume, transient recovery) so the upcoming
330
+ * turn APPENDS onto prior history instead of replacing it.
331
+ *
332
+ * Why it is required: a resumed engine re-issues the previously gated tool
333
+ * calls with brand-new call ids. Without seeding, the harness's accumulator
334
+ * would rebuild the transcript from empty and emit a status that drops the
335
+ * already-committed tool-call ids. The backend's append-only-at-identity
336
+ * guard rejects any non-terminal update that drops a committed tool-call id,
337
+ * so the resumed progress would never persist: the run stalls in
338
+ * WAITING_FOR_APPROVAL with no pending approvals and the workflow watchdog
339
+ * fails it. Seeding makes the resume status a strict superset; the re-runs
340
+ * are then reconciled in place onto these seeded calls by canonical identity
341
+ * inside the accumulator.
342
+ *
343
+ * The persisted protos are cloned so the input execution stays immutable.
344
+ * Messages are pushed into `status.messages` (which the accumulator wraps by
345
+ * reference) BEFORE the accumulator is constructed. Sub-agent executions are
346
+ * NOT seeded here: the Cursor accumulator owns `status.subAgentExecutions`
347
+ * and overwrites it on every flush, so the adapter clones them itself from
348
+ * `input.execution.status` under the same "left a transcript" test.
349
+ *
350
+ * Moved from `execute-cursor/index.ts` `seedCursorTranscriptFromExecution`
351
+ * (L2468-2478); the native twin is `execute-deep-agent/index.ts`
352
+ * `seedStatusFromExecution` (S3 retires it).
353
+ */
354
+ export function seedTranscriptFromExecution(
355
+ status: AgentExecutionStatus,
356
+ execution: AgentExecution,
357
+ ): void {
358
+ const persisted = execution.status;
359
+ if (!persisted || persisted.messages.length === 0) return;
360
+ for (const message of persisted.messages) {
361
+ status.messages.push(clone(AgentMessageSchema, message));
362
+ }
363
+ }
364
+
365
+ // ---------------------------------------------------------------------------
366
+ // The phases, in the order the turn runs them
367
+ // ---------------------------------------------------------------------------
368
+
369
+ /** Phase 1 (`index.ts` L325-330): hydrate the execution from the control plane. */
370
+ export async function fetchExecution(deps: ResolutionDeps): Promise<{
371
+ readonly execution: AgentExecution;
372
+ /** `execution.spec`, asserted once here: the control plane never dispatches an execution without one. */
373
+ readonly spec: AgentExecutionSpec;
374
+ readonly sessionId: string;
375
+ }> {
376
+ deps.enterPhase("fetch_execution");
377
+ await deps.reportProgress("Fetching execution");
378
+ const execution = await deps.client.getExecution(deps.input.executionId);
379
+ const spec = execution.spec!;
380
+ deps.timing.mark("fetch_execution");
381
+ return { execution, spec, sessionId: spec.sessionId };
382
+ }
383
+
384
+ /** Phase 2 (`index.ts` L332-336): load the session and resolve the full agent blueprint through it. */
385
+ export async function resolveAgentBlueprint(
386
+ deps: ResolutionDeps,
387
+ sessionId: string,
388
+ ): Promise<{ readonly session: Session; readonly blueprint: ResolvedBlueprint }> {
389
+ deps.enterPhase("resolve_blueprint");
390
+ await deps.reportProgress("Resolving agent blueprint");
391
+ const session = await deps.client.getSession(sessionId);
392
+ const blueprint = await resolveBlueprint(deps.client, session);
393
+ deps.timing.mark("resolve_blueprint");
394
+ return { session, blueprint };
395
+ }
396
+
397
+ /** Phase 2b (`index.ts` L338-343): the execution environment (MCP server credentials). */
398
+ export async function resolveEnvironment(deps: ResolutionDeps): Promise<TurnEnvironment> {
399
+ deps.enterPhase("resolve_environment");
400
+ await deps.reportProgress("Resolving environment");
401
+ const { envVars, secretKeys } = await resolveExecutionEnv(deps.client, deps.input.executionId);
402
+ deps.timing.mark("resolve_environment");
403
+ return { envVars, secretKeys };
404
+ }
405
+
406
+ /**
407
+ * Phase 2c, first half (`index.ts` L351-408): provision the workspace (clone
408
+ * git repos / mount local paths) so the LOCAL agent operates on the actual
409
+ * repo, wire the git write-back coordinator, and decide the capture posture.
410
+ *
411
+ * Git provisioning is idempotent across multi-turn and HITL reinvocations.
412
+ * The write-back coordinator pushes the session's APPROVED tree to the
413
+ * session branch (stigmer/<session-id>) and keeps one PR open; finalize runs
414
+ * at exactly two seams (the pure file-review resume and terminal
415
+ * completion), never mid-turn, because the working tree is speculative until
416
+ * reviewed. Non-eligible workspaces (local paths, no credentials) make it a
417
+ * no-op coordinator, here `null`.
418
+ *
419
+ * Apply-then-review is the universal file-review model (Slice 2c): a git work
420
+ * tree captures per-file from the git diff at the turn boundary; a NON-git
421
+ * workspace captures every write via the path-scoped CAS substrate, which
422
+ * needs artifact storage to persist blobs, and falls back to the classic
423
+ * deny-gate without it. `gitWorkspace` selects the substrate; `captureMode`
424
+ * says whether file edits flow at all.
425
+ *
426
+ * The change-set id is minted from the workflow-threaded turn index so it is
427
+ * stable across a Temporal retry (idempotent ledger authoring) and unique per
428
+ * turn; a "wasted" id on a pure-reconcile resume (which never authors a
429
+ * baseline) is harmless.
430
+ */
431
+ export async function provisionWorkspace(
432
+ deps: ResolutionDeps,
433
+ args: { readonly session: Session; readonly sessionId: string; readonly envVars: Record<string, string> },
434
+ ): Promise<{ readonly workspace: TurnWorkspace; readonly writeback: WriteBackCoordinator | null }> {
435
+ deps.enterPhase("provision_workspace");
436
+ await deps.reportProgress("Provisioning workspace");
437
+ const provision = await provisionSessionWorkspace(deps.config, args.session, args.envVars, args.sessionId);
438
+ deps.timing.mark("provision_workspace");
439
+
440
+ const writeback = provision.provisionResults.length > 0
441
+ ? new WriteBackCoordinator({
442
+ statusWriter: statusProtoWriter(deps.status),
443
+ executionId: deps.input.executionId,
444
+ sessionId: args.sessionId,
445
+ githubToken: args.envVars.GITHUB_TOKEN ?? "",
446
+ provisionResults: provision.provisionResults,
447
+ workspaceEntries: args.session.spec?.workspaceEntries ?? [],
448
+ workspaceBackend: provision.workspaceBackend,
449
+ })
450
+ : null;
451
+
452
+ const primaryDir = provision.workspaceDirs[0];
453
+ const gitWorkspace = primaryDir ? await isGitWorkTree(primaryDir) : false;
454
+ const captureMode = deriveCaptureMode(primaryDir, gitWorkspace, !!deps.artifactStorage);
455
+ const changeSetId = `${deps.input.executionId}:${deps.input.turnSeq}`;
456
+
457
+ return {
458
+ workspace: { dirs: provision.workspaceDirs, primaryDir, gitWorkspace, captureMode, changeSetId, provision },
459
+ writeback,
460
+ };
461
+ }
462
+
463
+ /**
464
+ * Phase 2c, second half (`index.ts` L410-449): serialize this turn against
465
+ * every other execution sharing the primary working tree. Sessions declaring
466
+ * the same localPath (or the shared runner root) resolve to ONE directory,
467
+ * and an unserialized concurrent write lands inside this turn's
468
+ * baseline→candidate window, misattributing another session's file to this
469
+ * turn's review. Acquired before ANY tree mutation (decision reconcile, gate
470
+ * install, agent writes, capture); while another turn holds the lock this
471
+ * surfaces a visible waiting state and heartbeats; a cancel aborts the wait
472
+ * at once.
473
+ *
474
+ * Its own phase so the release handle reaches the caller the instant the
475
+ * lock exists: nothing runs between acquisition and hand-over, so nothing
476
+ * can throw and leave a held lock the caller's `finally` never sees.
477
+ */
478
+ export async function acquireWorkspaceTurnLock(
479
+ deps: ResolutionDeps,
480
+ primaryDir: string,
481
+ ): Promise<WorkspaceLockOutcome> {
482
+ deps.enterPhase("acquire_workspace_lock");
483
+ let release: ReleaseWorkspaceLock | undefined;
484
+ if (primaryDir) {
485
+ try {
486
+ release = await acquireWorkspaceLock(primaryDir, {
487
+ onWaiting: () => deps.reportProgress("Waiting for workspace — in use by another session"),
488
+ heartbeat: deps.heartbeat,
489
+ signal: deps.signal,
490
+ timeoutMs: deps.config.workspaceLockTimeoutMs,
491
+ });
492
+ } catch (lockErr) {
493
+ if (lockErr instanceof WorkspaceLockCancelledError) {
494
+ throw new CancelledFailure("Activity cancelled while waiting for the workspace lock");
495
+ }
496
+ if (lockErr instanceof WorkspaceLockTimeoutError) {
497
+ return { kind: "settled", settlement: { kind: "workspace-lock-timeout", error: lockErr } };
498
+ }
499
+ throw lockErr;
500
+ }
501
+ }
502
+ deps.timing.mark("acquire_workspace_lock");
503
+ return { kind: "acquired", release };
504
+ }
505
+
506
+ /**
507
+ * `index.ts` L451-461: set OTel baggage so downstream calls carry execution
508
+ * context. Best-effort; tracing not initialized is a silent skip. Runs after
509
+ * the lock, as it always has.
510
+ */
511
+ export async function bindTelemetryBaggage(
512
+ deps: ResolutionDeps,
513
+ args: { readonly session: Session; readonly sessionId: string },
514
+ ): Promise<void> {
515
+ try {
516
+ const { setBaggage, BAGGAGE_EXECUTION_ID, BAGGAGE_SESSION_ID, BAGGAGE_ORG_ID } = await import("../otel.js");
517
+ await setBaggage({
518
+ [BAGGAGE_EXECUTION_ID]: deps.input.executionId,
519
+ [BAGGAGE_SESSION_ID]: args.sessionId,
520
+ [BAGGAGE_ORG_ID]: args.session.metadata?.org ?? "",
521
+ });
522
+ } catch {
523
+ // Tracing not initialized — silently skip.
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Phase 3 (`index.ts` L475-619): what the previous invocation left, and
529
+ * whether this one runs the agent at all.
530
+ *
531
+ * On a reinvocation, first seed the in-progress status from the persisted
532
+ * execution ({@link seedTranscriptFromExecution}), BEFORE the harness's
533
+ * accumulator wraps `status.messages`. Then the file-review reconcile (the
534
+ * dual-source half): every change set the server projected as DECIDED is
535
+ * reconciled from the ledger decisions and the pinned git refs (approved
536
+ * kept at their "after" bytes, rejected snapped back to baseline, all
537
+ * uncommitted, hash-verified) through the shared reconcile under the
538
+ * harness's {@link FileReviewIdentity}. Independent of tool approvals: a
539
+ * single turn can carry BOTH a DECIDED file change set AND an approved
540
+ * shell/MCP action. Tool approvals (shell / MCP / gitignored writes) still
541
+ * resolve from the message transcript, the deny-gate path, read here as
542
+ * {@link approvalDecisionsOf} after the reconcile, at the same point the
543
+ * orchestrator always read them.
544
+ *
545
+ * The outcome is {@link decideReinvocation}'s: a settlement the caller
546
+ * writes and persists, or the facts the turn proceeds with.
547
+ */
548
+ export async function reconcileReinvocation(
549
+ deps: ResolutionDeps,
550
+ args: {
551
+ readonly stateIdSource: StateIdSource;
552
+ readonly execution: AgentExecution;
553
+ readonly workspace: TurnWorkspace;
554
+ readonly fileReview: FileReviewIdentity;
555
+ },
556
+ ): Promise<ReinvocationOutcome> {
557
+ deps.enterPhase("reconcile_reinvocation");
558
+ const reinvoked = isReinvocation(args.stateIdSource, deps.input);
559
+ let reconciledFileReview = false;
560
+ let fileReviewFailed = false;
561
+ let fileReviewFailureDetail = "";
562
+ const discardedPaths: string[] = [];
563
+
564
+ if (reinvoked) {
565
+ const existingStatus = args.execution.status;
566
+ seedTranscriptFromExecution(deps.status, args.execution);
567
+
568
+ if (args.workspace.captureMode && args.workspace.primaryDir) {
569
+ const decidedSets = (existingStatus?.fileChangeSets ?? []).filter(
570
+ (cs) => cs.status === FileChangeSetStatus.DECIDED,
571
+ );
572
+ for (const changeSet of decidedSets) {
573
+ const capResult = await applyCaptureDecisions({
574
+ status: deps.status,
575
+ gitRoot: args.workspace.primaryDir,
576
+ executionId: deps.input.executionId,
577
+ changeSet,
578
+ harnessId: args.fileReview.harnessId,
579
+ excludePaths: args.fileReview.excludePaths,
580
+ // Thread the CAS store so CAS-captured files in the change set
581
+ // reconcile from the durable manifest (approved after-blobs written,
582
+ // rejected snapped back). In a non-git workspace this is the ONLY
583
+ // reconcile; in a git tree it composes with the git-ref reconcile.
584
+ storage: deps.artifactStorage,
585
+ readBlob: deps.artifactStorage ? casBlobReader(deps.artifactStorage) : undefined,
586
+ gitWorkspace: args.workspace.gitWorkspace,
587
+ });
588
+ if (!capResult.isCaptureTurn) continue;
589
+ reconciledFileReview = true;
590
+ if (capResult.failed) {
591
+ fileReviewFailed = true;
592
+ fileReviewFailureDetail = capResult.failureDetail ?? "file review reconcile failed";
593
+ }
594
+ if (capResult.hadReject) discardedPaths.push(...capResult.rejectedPaths);
595
+ }
596
+ }
597
+ }
598
+
599
+ const approvalDecisions = approvalDecisionsOf(deps.status);
600
+ const settlement = decideReinvocation({
601
+ approvalDecisions,
602
+ reconciledFileReview,
603
+ fileReviewFailed,
604
+ fileReviewFailureDetail,
605
+ discardedPaths,
606
+ });
607
+ if (settlement) return { kind: "settled", settlement };
608
+ return { kind: "ready", reinvocation: { isReinvocation: reinvoked, approvalDecisions } };
609
+ }
610
+
611
+ /**
612
+ * Phases 4 to 4b (`index.ts` L620-770): the tool surface.
613
+ *
614
+ * The MCP-bound env map (and ONLY it, never the agent process env) carries
615
+ * the reserved caller-identity keys, so a server that declares them in
616
+ * spec.env can template the platform-verified caller into its headers;
617
+ * filterEnvToDeclaredKeys keeps every other server blind. The resolved list
618
+ * then mutates through the Connect backfill and three synthesized
619
+ * attachments, deliberately AFTER resolve + backfill so the backfill's
620
+ * destructiveHint tightener can never force-gate an attachment's tools:
621
+ *
622
+ * - channel messaging (DD-006 D7/D8): the discovery read IS the attachment
623
+ * decision, and every failure mode degrades to honest absence;
624
+ * - conversation participation (channel-conversations DD-008 D-c): the
625
+ * channel-id session label IS the decision, a free local read;
626
+ * - memory capture (DD-005 D1): the recall snapshot's enabled bit IS the
627
+ * decision, server-stamped at execution create.
628
+ *
629
+ * Their credential story (DD-006 D4): the exchanged scoped runner token
630
+ * authenticates the discovery reads per call; the exchange is opportunistic
631
+ * (every consumer degrades to an empty answer by contract, and the server
632
+ * refuses the ambient fallback safely), so a failed exchange must not kill
633
+ * the run, unlike the env read, where secrets are load-bearing.
634
+ *
635
+ * Approval policies merge last, from all layers, with two bypasses shared
636
+ * with the native harness (`shared/approval-policy.ts` `ActiveLeases`): the
637
+ * pre-armed spec.auto_approve_all is the one whole-run global bypass; an
638
+ * interactive APPROVE_ALL grants a run-lifetime lease scoped to that
639
+ * action's class. Layer-3 overrides ride each resolved server from its
640
+ * merged usage (issue #349). The harness projects its SDK config from
641
+ * `servers` after this returns, exactly once, so every mutation is visible
642
+ * by construction.
643
+ */
644
+ export async function resolveMcpServersAndPolicies(
645
+ deps: ResolutionDeps,
646
+ args: {
647
+ readonly execution: AgentExecution;
648
+ readonly session: Session;
649
+ readonly sessionId: string;
650
+ readonly blueprint: ResolvedBlueprint;
651
+ readonly environment: TurnEnvironment;
652
+ },
653
+ ): Promise<TurnMcp> {
654
+ const { client, config } = deps;
655
+ const { executionId } = deps.input;
656
+ const { execution, session, sessionId, blueprint, environment } = args;
657
+
658
+ deps.enterPhase("resolve_mcp_servers");
659
+ await deps.reportProgress("Resolving MCP servers");
660
+ const transportPosture = resolveMcpTransportPosture(config.mode);
661
+ const mcpEnvVars = injectCallerIdentityEnv(
662
+ environment.envVars,
663
+ resolveCallerIdentity(
664
+ blueprint.sessionSpec.metadata,
665
+ session.status?.audit?.specAudit?.createdBy,
666
+ ),
667
+ sessionId,
668
+ );
669
+ let servers = (await resolveMcpServers(
670
+ client, blueprint.mergedMcpServerUsages, mcpEnvVars, transportPosture,
671
+ )).resolvedServers;
672
+ deps.timing.mark("resolve_mcp_servers");
673
+
674
+ // Phase 4a: Connect backfill for undiscovered MCP servers.
675
+ const sessionOrg = session.metadata?.org ?? "";
676
+ servers = await backfillMcpServersIfNeeded(
677
+ client, servers, blueprint.mergedMcpServerUsages, mcpEnvVars, sessionOrg,
678
+ transportPosture, deps.heartbeat, environment.secretKeys,
679
+ );
680
+ deps.timing.mark("backfill_mcp");
681
+
682
+ let exchangedRunnerToken: string | undefined;
683
+ try {
684
+ exchangedRunnerToken =
685
+ await client.acquireScopedRunnerToken({ agentExecutionId: executionId });
686
+ } catch (err) {
687
+ console.warn(
688
+ "[turn-context] Scoped-token exchange failed for attachment/discovery " +
689
+ `reads; degrading to the ambient credential: ${err instanceof Error ? err.message : err}`,
690
+ );
691
+ }
692
+ const attachmentCredential = exchangedRunnerToken
693
+ ?? config.stigmerTokenRef?.current
694
+ ?? config.stigmerToken;
695
+ const attachmentEndpoints = {
696
+ bridgeEndpoint: config.mcpBridgeEndpoint,
697
+ credential: attachmentCredential,
698
+ backendEndpoint: config.stigmerBackendEndpoint,
699
+ };
700
+
701
+ // Phase 4a2: the channel messaging attachment.
702
+ const channelMessaging = await discoverChannelMessaging(client, exchangedRunnerToken);
703
+ if (channelMessaging.length > 0) {
704
+ const attachment = synthesizeChannelAttachment(channelMessaging, attachmentEndpoints);
705
+ if (attachment) {
706
+ servers = injectSynthesizedAttachment(servers, attachment, "channel messaging");
707
+ }
708
+ }
709
+
710
+ // Phase 4a4: the conversation participation attachment. HTTP-only:
711
+ // synthesize answers undefined with no bridge endpoint by design.
712
+ const conversationAttachment = synthesizeConversationAttachment(
713
+ readChannelConversationId(session.metadata?.labels),
714
+ attachmentEndpoints,
715
+ );
716
+ if (conversationAttachment) {
717
+ servers = injectSynthesizedAttachment(servers, conversationAttachment, "conversation participation");
718
+ }
719
+
720
+ // Phase 4a5: the memory capture attachment. The capture context is
721
+ // attribution the server verifies or trusts per edition; the subject is
722
+ // never threaded, it derives from the credential.
723
+ const memoryAttachment = synthesizeMemoryAttachment(
724
+ execution.spec?.recalledMemories,
725
+ {
726
+ org: session.metadata?.org ?? "",
727
+ agentId: blueprint.agent.metadata?.id ?? "",
728
+ sessionId,
729
+ agentExecutionId: executionId,
730
+ },
731
+ attachmentEndpoints,
732
+ );
733
+ if (memoryAttachment) {
734
+ servers = injectSynthesizedAttachment(servers, memoryAttachment, "memory capture");
735
+ }
736
+
737
+ // Phase 4b: merge approval policies from all layers.
738
+ const leases = deriveActiveLeases(execution);
739
+ const policies = mergeApprovalPolicies(servers, leases);
740
+
741
+ return { servers, channelMessaging, leases, policies };
742
+ }
743
+
744
+ /**
745
+ * Phase 5 (`index.ts` L493-501, the orchestrator's position between the
746
+ * tool surface and the attachments): mount the merged skills under the
747
+ * session's platform dir, reachable from the workspace through its
748
+ * `.stigmer` link, and return what a prompt renders (`shared/skill-
749
+ * resolver.ts`). Reads the control plane, which is why it is the runtime's
750
+ * and not a harness's; each harness places the returned metadata in its own
751
+ * prompt shape. The `.stigmer` link this and the attachment phase create is
752
+ * removed in the runtime's finally, after the harness's own teardown.
753
+ */
754
+ export async function mountSkills(
755
+ deps: ResolutionDeps,
756
+ args: { readonly blueprint: ResolvedBlueprint; readonly sessionId: string; readonly primaryDir: string },
757
+ ): Promise<readonly SkillMetadata[]> {
758
+ deps.enterPhase("resolve_skills");
759
+ await deps.reportProgress("Resolving skills");
760
+ const skills = await resolveSkills(deps.client, args.blueprint.mergedSkillRefs, {
761
+ sessionId: args.sessionId,
762
+ primaryWorkspaceDir: args.primaryDir,
763
+ });
764
+ deps.timing.mark("resolve_skills");
765
+ return skills;
766
+ }
767
+
768
+ /**
769
+ * Phase 5b (`index.ts` L803-838): resolve the turn's attachments, fail-hard
770
+ * (they are explicit user inputs; see `shared/attachment-resolver.ts`).
771
+ * Downloads by storage key through the same artifact storage resolved for
772
+ * status offload. The vision budget rides along so image attachments are
773
+ * selected for inline delivery while their bytes are already in hand
774
+ * (`shared/attachment-vision.ts` owns all policy); it carries the requested
775
+ * model's registry vision capability, looked up from the raw
776
+ * executionConfig name, because full model validation isn't needed for
777
+ * this and ""/"default" (the Auto pool) resolves to unknown, which the
778
+ * policy treats as sighted. The vision profile is the harness's (each
779
+ * engine accepts different image shapes), so it arrives as an argument.
780
+ */
781
+ export async function resolveTurnAttachments(
782
+ deps: ResolutionDeps,
783
+ args: {
784
+ readonly spec: AgentExecutionSpec;
785
+ readonly sessionId: string;
786
+ readonly primaryDir: string;
787
+ readonly visionProfile: VisionProfile;
788
+ },
789
+ ): Promise<TurnAttachments> {
790
+ deps.enterPhase("resolve_attachments");
791
+ const visionBudget = new VisionBudget(args.visionProfile, {
792
+ modelVision: await getModelVisionCapability(args.spec.executionConfig?.modelName ?? ""),
793
+ });
794
+ const results = await resolveAttachments(args.spec.attachments, {
795
+ sessionId: args.sessionId,
796
+ primaryWorkspaceDir: args.primaryDir,
797
+ mode: deps.config.mode,
798
+ storage: deps.artifactStorage,
799
+ visionBudget,
800
+ });
801
+ // Vision facts, derived once from the single resolution result: the
802
+ // images the model will see inline (in attachment order) and the ones
803
+ // that degraded to path-only, disclosed in the prompt.
804
+ const visionImages = results.flatMap((a) => (a.vision ? [a.vision] : []));
805
+ const visionNotViewable: NotViewableEntry[] = results.flatMap((a) =>
806
+ a.visionDegraded ? [{ path: a.relativePath, reason: a.visionDegraded }] : [],
807
+ );
808
+ if (visionImages.length > 0 || visionNotViewable.length > 0) {
809
+ console.log(
810
+ `[attachment-vision] execution=${deps.input.executionId} inline=${visionImages.length} ` +
811
+ `(${visionImages.reduce((n, v) => n + v.byteSize, 0)} bytes) ` +
812
+ `degraded=${JSON.stringify(visionNotViewable.map((d) => `${d.path}:${d.reason}`))}`,
813
+ );
814
+ }
815
+ deps.timing.mark("resolve_attachments");
816
+ return { results, visionImages, visionNotViewable };
817
+ }
818
+
819
+ /**
820
+ * Phase 5b3 (`index.ts` L849-867): exact-apply approved whole-file writes
821
+ * (HITL "what you approve is what gets applied"). A deny-only harness
822
+ * reinvokes the model, which regenerates content, so a resource grant alone
823
+ * cannot guarantee the bytes that land match the bytes the user approved.
824
+ * The runner therefore writes the EXACT approved whole-file content itself
825
+ * and marks those tool calls COMPLETED; the harness then issues NO grant for
826
+ * them, so any FURTHER change the model makes to those files is re-gated.
827
+ * Hunk edits / shell / MCP stay on the grant + reinvocation path, and every
828
+ * uncertain case degrades to that path, so this can never corrupt a file
829
+ * (see `shared/exact-apply.ts`).
830
+ *
831
+ * Scoped OUT of capture mode, which does not reinvoke the model for file
832
+ * edits: it applies the exact captured bytes itself in the reconcile.
833
+ *
834
+ * The caller persists when the returned set is non-empty (the applied state
835
+ * must be durable before the continuation runs, and the UI reflects it at
836
+ * once); this phase, like every phase, never persists.
837
+ */
838
+ export async function applyApprovedWrites(
839
+ deps: ResolutionDeps,
840
+ args: {
841
+ readonly workspace: TurnWorkspace;
842
+ readonly reinvocation: TurnReinvocation;
843
+ },
844
+ ): Promise<ReadonlySet<string>> {
845
+ const { workspace, reinvocation } = args;
846
+ if (workspace.captureMode || !reinvocation.isReinvocation || reinvocation.approvalDecisions.size === 0) {
847
+ return new Set();
848
+ }
849
+ deps.enterPhase("apply_approved_writes");
850
+ return applyApprovedWholeFileWrites({
851
+ messages: deps.status.messages,
852
+ workspaceBackend: new LocalWorkspaceBackend(workspace.primaryDir),
853
+ workspaceDirs: workspace.dirs,
854
+ executionId: deps.input.executionId,
855
+ });
856
+ }
857
+
858
+ /**
859
+ * Phase 6, the harness-agnostic half (`index.ts` L998, L1005-1006): the
860
+ * requested model name as the execution states it, and the effective service
861
+ * tier and thinking mode. UNSPECIFIED → STANDARD (#357) and UNSPECIFIED →
862
+ * DISABLED (#772) resolve here and nowhere else; every upstream layer
863
+ * preserves the caller's raw enum values. Validating the NAME against a
864
+ * catalog is the harness's (Cursor's `resolveModelId`), so it is not here.
865
+ */
866
+ export function resolveModelPreferences(spec: AgentExecutionSpec): TurnModelPreferences {
867
+ return {
868
+ requested: spec.executionConfig?.modelName || "default",
869
+ serviceTier: resolveEffectiveServiceTier(spec.executionConfig?.serviceTier),
870
+ thinkingMode: resolveEffectiveThinkingMode(spec.executionConfig?.thinkingMode),
871
+ };
872
+ }
873
+
874
+ /** Phase 9b (`index.ts` L1183-1184): the structured-output schema the execution asks for, if any. */
875
+ export function structuredOutputSchemaOf(spec: AgentExecutionSpec): Record<string, unknown> | undefined {
876
+ return spec.executionConfig?.structuredOutputSchema as Record<string, unknown> | undefined;
877
+ }
878
+
879
+ /**
880
+ * Phase 9c (`index.ts` L1186-1216, and the metadata reads at L1240-1245):
881
+ * the standing context, read once, and the memory selection prepared once.
882
+ *
883
+ * Selection runs against the frozen first message's semantics: above the
884
+ * activation threshold it picks top-k for spec.message, otherwise (and on
885
+ * any failure) it injects the full candidate set. The outcome report is
886
+ * stamped on the turn's status ONCE, picked up by the next persist; a
887
+ * re-invocation replays the report already persisted on the execution
888
+ * rather than re-selecting (the written-once rule). Memoized because a
889
+ * resumed-agent primary send carries no memories, but a mid-send
890
+ * poisoned-handle failure rebuilds on a FRESH agent whose recovery prompt
891
+ * does, so every memory-carrying build site awaits this lazily.
892
+ */
893
+ export function resolveStandingContext(
894
+ deps: ResolutionDeps,
895
+ args: { readonly execution: AgentExecution; readonly spec: AgentExecutionSpec; readonly blueprint: ResolvedBlueprint },
896
+ ): TurnStandingContext {
897
+ const { execution, spec, blueprint } = args;
898
+ let memorySelection: Promise<RecalledMemoriesContent | undefined> | undefined;
899
+ const selectRecalledMemories = (): Promise<RecalledMemoriesContent | undefined> => {
900
+ memorySelection ??= selectRecalledFacts(spec.recalledMemories, spec.message, {
901
+ proxyEndpoint: deps.config.proxyEndpoint,
902
+ stigmerToken: deps.config.stigmerToken,
903
+ executionId: deps.input.executionId,
904
+ priorReport: execution.status?.recalledMemoriesReport,
905
+ }).then((selection) => {
906
+ if (selection.report !== undefined) {
907
+ deps.status.recalledMemoriesReport = selection.report;
908
+ }
909
+ return selection.content;
910
+ });
911
+ return memorySelection;
912
+ };
913
+ return {
914
+ contextBridge: readContextBridge(blueprint.sessionSpec.metadata),
915
+ senderIdentity: readSenderIdentity(blueprint.sessionSpec.metadata),
916
+ sessionContext: readSessionContext(blueprint.sessionSpec.metadata),
917
+ declaredPreferences: readDeclaredPreferences(spec.declaredPreferences),
918
+ conversationCatchup: readConversationCatchup(spec.conversationCatchup),
919
+ selectRecalledMemories,
920
+ };
921
+ }
922
+
923
+ // ---------------------------------------------------------------------------
924
+ // The composer
925
+ // ---------------------------------------------------------------------------
926
+
927
+ /**
928
+ * The resources a turn holds that outlive the phase that produced them and
929
+ * must be released or finalized by the caller's `finally` and terminal
930
+ * writes — set on this frame the instant they exist, so no throw between
931
+ * acquisition and hand-over can leave a held lock the caller never sees.
932
+ * Not part of `TurnInput`: an adapter never releases the lock or finalizes
933
+ * the write-back.
934
+ */
935
+ export interface TurnFrame {
936
+ /** The primary tree once provisioned: the `.stigmer` link the skill and attachment phases create under it is removed at turn end. */
937
+ primaryDir: string | undefined;
938
+ /** Exclusive turn lock on the primary tree; released LAST, after the adapter's own teardown. */
939
+ releaseWorkspaceLock: ReleaseWorkspaceLock | undefined;
940
+ /** Finalizes at exactly two seams: the pure file-review resume and terminal completion. `null` when nothing is eligible. */
941
+ writeback: WriteBackCoordinator | null;
942
+ }
943
+
944
+ /** What the composer answers: the whole record, or a settlement the caller writes and returns. */
945
+ export type TurnResolution =
946
+ | { readonly kind: "ready"; readonly input: TurnInput }
947
+ | { readonly kind: "settled"; readonly settlement: TurnSettlement };
948
+
949
+ /**
950
+ * Run the twelve phases in order and compose the turn's `TurnInput`, or
951
+ * stop at the first settlement.
952
+ *
953
+ * The order is the orchestrator's, with its harness-specific steps gone
954
+ * (they run inside `adapter.runTurn`, after this returns): the tool surface,
955
+ * the skills, the attachments, the exact-apply, then the reads that need no
956
+ * network.
957
+ *
958
+ * The one persist a phase requires — the applied writes must be durable
959
+ * before the continuation runs — goes through the caller's chokepoint
960
+ * (`persist`), so the rule that phases never persist holds here too.
961
+ */
962
+ export async function resolveTurnContext(
963
+ deps: ResolutionDeps,
964
+ capabilities: HarnessCapabilities,
965
+ frame: TurnFrame,
966
+ persist: () => Promise<void>,
967
+ ): Promise<TurnResolution> {
968
+ const { execution, spec, sessionId } = await fetchExecution(deps);
969
+ const { session, blueprint } = await resolveAgentBlueprint(deps, sessionId);
970
+ const environment = await resolveEnvironment(deps);
971
+ const { workspace, writeback } = await provisionWorkspace(deps, { session, sessionId, envVars: environment.envVars });
972
+ frame.primaryDir = workspace.primaryDir;
973
+ frame.writeback = writeback;
974
+
975
+ const lock = await acquireWorkspaceTurnLock(deps, workspace.primaryDir);
976
+ if (lock.kind === "settled") return { kind: "settled", settlement: lock.settlement };
977
+ frame.releaseWorkspaceLock = lock.release;
978
+
979
+ await bindTelemetryBaggage(deps, { session, sessionId });
980
+
981
+ const reinvoked = await reconcileReinvocation(deps, {
982
+ stateIdSource: capabilities.stateIdSource,
983
+ execution,
984
+ workspace,
985
+ fileReview: capabilities.fileReview,
986
+ });
987
+ if (reinvoked.kind === "settled") return { kind: "settled", settlement: reinvoked.settlement };
988
+
989
+ const mcp = await resolveMcpServersAndPolicies(deps, { execution, session, sessionId, blueprint, environment });
990
+ const skills = await mountSkills(deps, { blueprint, sessionId, primaryDir: workspace.primaryDir });
991
+ const attachments = await resolveTurnAttachments(deps, {
992
+ spec,
993
+ sessionId,
994
+ primaryDir: workspace.primaryDir,
995
+ visionProfile: capabilities.visionProfile,
996
+ });
997
+ const appliedToolCallIds = await applyApprovedWrites(deps, { workspace, reinvocation: reinvoked.reinvocation });
998
+ if (appliedToolCallIds.size > 0) {
999
+ // Persist the applied writes (tool calls now COMPLETED with the approved
1000
+ // diff) before reinvocation, so the applied state is durable even if the
1001
+ // continuation fails, and the UI reflects it immediately.
1002
+ await persist();
1003
+ }
1004
+
1005
+ return {
1006
+ kind: "ready",
1007
+ input: {
1008
+ executionId: deps.input.executionId,
1009
+ threadId: deps.input.threadId,
1010
+ turnSeq: deps.input.turnSeq,
1011
+ sessionId,
1012
+ approvalDecisions: reinvoked.reinvocation.approvalDecisions,
1013
+ execution,
1014
+ session,
1015
+ blueprint,
1016
+ environment,
1017
+ workspace,
1018
+ mcp,
1019
+ skills,
1020
+ attachments,
1021
+ appliedToolCallIds,
1022
+ model: resolveModelPreferences(spec),
1023
+ structuredOutputSchema: structuredOutputSchemaOf(spec),
1024
+ standing: resolveStandingContext(deps, { execution, spec, blueprint }),
1025
+ artifactStorage: deps.artifactStorage,
1026
+ },
1027
+ };
1028
+ }