@stigmer/runner 3.15.3-dev.20260914121148 → 3.16.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 (592) hide show
  1. package/README.md +2 -1
  2. package/dist/.build-fingerprint +1 -1
  3. package/dist/__test-utils__/approval-contract/types.d.ts +1 -1
  4. package/dist/__test-utils__/hermetic-activity.d.ts +61 -7
  5. package/dist/__test-utils__/hermetic-activity.js +100 -9
  6. package/dist/__test-utils__/hermetic-activity.js.map +1 -1
  7. package/dist/__test-utils__/model-registry-fixture.d.ts +22 -3
  8. package/dist/__test-utils__/model-registry-fixture.js +22 -7
  9. package/dist/__test-utils__/model-registry-fixture.js.map +1 -1
  10. package/dist/activities/execute-cursor/__test-utils__/contract-subject.d.ts +2 -2
  11. package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +2 -2
  12. package/dist/activities/execute-cursor/__test-utils__/fold.d.ts +61 -0
  13. package/dist/activities/execute-cursor/__test-utils__/fold.js +94 -0
  14. package/dist/activities/execute-cursor/__test-utils__/fold.js.map +1 -0
  15. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js +1 -1
  16. package/dist/activities/execute-cursor/__test-utils__/gateway-substrate.js.map +1 -1
  17. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +47 -12
  18. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +41 -10
  19. package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
  20. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.d.ts +15 -6
  21. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js +17 -4
  22. package/dist/activities/execute-cursor/__test-utils__/scripted-agent.js.map +1 -1
  23. package/dist/activities/execute-cursor/adapter.d.ts +4 -4
  24. package/dist/activities/execute-cursor/adapter.js +4 -4
  25. package/dist/activities/execute-cursor/approval-policy.d.ts +10 -10
  26. package/dist/activities/execute-cursor/approval-policy.js +12 -23
  27. package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
  28. package/dist/activities/execute-cursor/approval-state.d.ts +35 -1
  29. package/dist/activities/execute-cursor/approval-state.js +65 -0
  30. package/dist/activities/execute-cursor/approval-state.js.map +1 -1
  31. package/dist/activities/execute-cursor/{message-translator.d.ts → boundary-rows.d.ts} +45 -341
  32. package/dist/activities/execute-cursor/boundary-rows.js +1018 -0
  33. package/dist/activities/execute-cursor/boundary-rows.js.map +1 -0
  34. package/dist/activities/execute-cursor/cursor-capabilities.d.ts +1 -1
  35. package/dist/activities/execute-cursor/cursor-capabilities.js +1 -1
  36. package/dist/activities/execute-cursor/cursor-event-recorder.d.ts +33 -14
  37. package/dist/activities/execute-cursor/cursor-event-recorder.js +43 -21
  38. package/dist/activities/execute-cursor/cursor-event-recorder.js.map +1 -1
  39. package/dist/activities/execute-cursor/prompt-builder.d.ts +3 -3
  40. package/dist/activities/execute-cursor/prompt-builder.js +4 -4
  41. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  42. package/dist/activities/execute-cursor/service-tier.js +1 -2
  43. package/dist/activities/execute-cursor/service-tier.js.map +1 -1
  44. package/dist/activities/execute-cursor/sub-agent-steps.d.ts +79 -0
  45. package/dist/activities/execute-cursor/sub-agent-steps.js +180 -0
  46. package/dist/activities/execute-cursor/sub-agent-steps.js.map +1 -0
  47. package/dist/activities/execute-cursor/tool-result.d.ts +49 -0
  48. package/dist/activities/execute-cursor/tool-result.js +130 -0
  49. package/dist/activities/execute-cursor/tool-result.js.map +1 -0
  50. package/dist/activities/execute-cursor/translator.d.ts +178 -0
  51. package/dist/activities/execute-cursor/translator.js +485 -0
  52. package/dist/activities/execute-cursor/translator.js.map +1 -0
  53. package/dist/activities/execute-cursor/turn-boundary.d.ts +15 -5
  54. package/dist/activities/execute-cursor/turn-boundary.js +10 -14
  55. package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
  56. package/dist/activities/execute-cursor/turn-settle.d.ts +11 -7
  57. package/dist/activities/execute-cursor/turn-settle.js +47 -34
  58. package/dist/activities/execute-cursor/turn-settle.js.map +1 -1
  59. package/dist/activities/execute-cursor/turn-setup.d.ts +2 -2
  60. package/dist/activities/execute-cursor/turn-setup.js +5 -5
  61. package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
  62. package/dist/activities/execute-cursor/turn-stream.d.ts +23 -14
  63. package/dist/activities/execute-cursor/turn-stream.js +41 -28
  64. package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
  65. package/dist/activities/execute-cursor/turn.d.ts +4 -4
  66. package/dist/activities/execute-cursor/turn.js +5 -5
  67. package/dist/activities/execute-cursor/usage-pricing.d.ts +2 -2
  68. package/dist/activities/execute-cursor/usage-pricing.js +2 -2
  69. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.d.ts +2 -2
  70. package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js +2 -2
  71. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +16 -11
  72. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +22 -10
  73. package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -1
  74. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js +1 -1
  75. package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js.map +1 -1
  76. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.d.ts +2 -3
  77. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js +2 -3
  78. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js.map +1 -1
  79. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +12 -3
  80. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +18 -4
  81. package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
  82. package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.d.ts +10 -1
  83. package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.js +24 -11
  84. package/dist/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.js.map +1 -1
  85. package/dist/activities/execute-deep-agent/adapter.d.ts +1 -1
  86. package/dist/activities/execute-deep-agent/adapter.js +1 -1
  87. package/dist/activities/execute-deep-agent/approval-file-change.d.ts +19 -19
  88. package/dist/activities/execute-deep-agent/approval-file-change.js +16 -15
  89. package/dist/activities/execute-deep-agent/approval-file-change.js.map +1 -1
  90. package/dist/activities/execute-deep-agent/deep-agent-capabilities.d.ts +2 -2
  91. package/dist/activities/execute-deep-agent/deep-agent-capabilities.js +2 -2
  92. package/dist/activities/execute-deep-agent/hitl.d.ts +2 -2
  93. package/dist/activities/execute-deep-agent/hitl.js +2 -2
  94. package/dist/activities/execute-deep-agent/inline-publisher.d.ts +13 -3
  95. package/dist/activities/execute-deep-agent/inline-publisher.js +3 -3
  96. package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
  97. package/dist/activities/execute-deep-agent/prompt-builder.d.ts +3 -3
  98. package/dist/activities/execute-deep-agent/prompt-builder.js +6 -7
  99. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  100. package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +16 -8
  101. package/dist/activities/execute-deep-agent/streaming-side-effects.js +20 -33
  102. package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
  103. package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +3 -3
  104. package/dist/activities/execute-deep-agent/subagent-transformer.js +1 -1
  105. package/dist/activities/execute-deep-agent/translator.d.ts +134 -0
  106. package/dist/activities/execute-deep-agent/translator.js +418 -0
  107. package/dist/activities/execute-deep-agent/translator.js.map +1 -0
  108. package/dist/activities/execute-deep-agent/turn-settle.d.ts +13 -7
  109. package/dist/activities/execute-deep-agent/turn-settle.js +34 -45
  110. package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -1
  111. package/dist/activities/execute-deep-agent/turn-setup.d.ts +8 -8
  112. package/dist/activities/execute-deep-agent/turn-setup.js +7 -7
  113. package/dist/activities/execute-deep-agent/turn-stream.d.ts +13 -10
  114. package/dist/activities/execute-deep-agent/turn-stream.js +44 -41
  115. package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -1
  116. package/dist/activities/execute-deep-agent/turn.d.ts +3 -3
  117. package/dist/activities/execute-deep-agent/turn.js +3 -3
  118. package/dist/activities/generate-session-subject.d.ts +25 -4
  119. package/dist/activities/generate-session-subject.js +17 -7
  120. package/dist/activities/generate-session-subject.js.map +1 -1
  121. package/dist/client/stigmer-client.d.ts +17 -6
  122. package/dist/client/stigmer-client.js +47 -14
  123. package/dist/client/stigmer-client.js.map +1 -1
  124. package/dist/config.d.ts +6 -3
  125. package/dist/config.js +4 -1
  126. package/dist/config.js.map +1 -1
  127. package/dist/harness/approval-decisions.d.ts +1 -1
  128. package/dist/harness/approval-decisions.js +1 -1
  129. package/dist/harness/capabilities.d.ts +21 -11
  130. package/dist/harness/capabilities.js +2 -2
  131. package/dist/harness/capture.d.ts +4 -4
  132. package/dist/harness/capture.js +4 -4
  133. package/dist/harness/persist-chokepoint.d.ts +3 -3
  134. package/dist/harness/persist-chokepoint.js +3 -3
  135. package/dist/harness/registry.d.ts +2 -2
  136. package/dist/harness/registry.js +2 -2
  137. package/dist/harness/run-turn.d.ts +6 -4
  138. package/dist/harness/run-turn.js +31 -9
  139. package/dist/harness/run-turn.js.map +1 -1
  140. package/dist/harness/stop-controller.d.ts +1 -1
  141. package/dist/harness/stop-controller.js +1 -1
  142. package/dist/harness/terminal-table.d.ts +7 -7
  143. package/dist/harness/terminal-table.js +7 -7
  144. package/dist/harness/transcript/builder.d.ts +182 -0
  145. package/dist/harness/transcript/builder.js +693 -0
  146. package/dist/harness/transcript/builder.js.map +1 -0
  147. package/dist/harness/transcript/events.d.ts +218 -0
  148. package/dist/harness/transcript/events.js +50 -0
  149. package/dist/harness/transcript/events.js.map +1 -0
  150. package/dist/harness/transcript/state.d.ts +110 -0
  151. package/dist/harness/transcript/state.js +148 -0
  152. package/dist/harness/transcript/state.js.map +1 -0
  153. package/dist/harness/turn-context.d.ts +54 -38
  154. package/dist/harness/turn-context.js +42 -43
  155. package/dist/harness/turn-context.js.map +1 -1
  156. package/dist/harness/types.d.ts +63 -22
  157. package/dist/harness/types.js +12 -11
  158. package/dist/harness/types.js.map +1 -1
  159. package/dist/harness/usage-accumulator.d.ts +2 -2
  160. package/dist/harness/usage-accumulator.js +2 -2
  161. package/dist/harness-adapters.d.ts +5 -4
  162. package/dist/harness-adapters.js +5 -4
  163. package/dist/harness-adapters.js.map +1 -1
  164. package/dist/interceptors/run-credential-activity.d.ts +28 -0
  165. package/dist/interceptors/run-credential-activity.js +56 -0
  166. package/dist/interceptors/run-credential-activity.js.map +1 -0
  167. package/dist/middleware/approval-gate.d.ts +6 -0
  168. package/dist/middleware/approval-gate.js +7 -57
  169. package/dist/middleware/approval-gate.js.map +1 -1
  170. package/dist/middleware/cost-advisory.d.ts +3 -3
  171. package/dist/middleware/cost-advisory.js +3 -3
  172. package/dist/middleware/index.d.ts +1 -1
  173. package/dist/middleware/index.js +1 -1
  174. package/dist/runner-manager.js +10 -4
  175. package/dist/runner-manager.js.map +1 -1
  176. package/dist/shared/activity-input.d.ts +9 -0
  177. package/dist/shared/activity-input.js +17 -0
  178. package/dist/shared/activity-input.js.map +1 -1
  179. package/dist/shared/approval-policy.d.ts +53 -0
  180. package/dist/shared/approval-policy.js +76 -0
  181. package/dist/shared/approval-policy.js.map +1 -1
  182. package/dist/shared/args-preview.d.ts +45 -22
  183. package/dist/shared/args-preview.js +51 -41
  184. package/dist/shared/args-preview.js.map +1 -1
  185. package/dist/shared/attachment-resolver.d.ts +4 -4
  186. package/dist/shared/attachment-resolver.js +4 -4
  187. package/dist/shared/attachment-zip.d.ts +2 -2
  188. package/dist/shared/attachment-zip.js +2 -2
  189. package/dist/shared/channel-attachment.d.ts +8 -5
  190. package/dist/shared/channel-attachment.js +10 -6
  191. package/dist/shared/channel-attachment.js.map +1 -1
  192. package/dist/shared/cost-guard.d.ts +2 -2
  193. package/dist/shared/cost-guard.js +2 -2
  194. package/dist/shared/exact-apply.d.ts +1 -1
  195. package/dist/shared/exact-apply.js +1 -1
  196. package/dist/shared/execution-context.d.ts +2 -2
  197. package/dist/shared/execution-context.js +2 -2
  198. package/dist/shared/extract-structured-output.d.ts +2 -2
  199. package/dist/shared/extract-structured-output.js +2 -2
  200. package/dist/shared/filereview/cas-touched.d.ts +2 -2
  201. package/dist/shared/filereview/cas-touched.js +2 -2
  202. package/dist/shared/filereview/progress.d.ts +13 -0
  203. package/dist/shared/filereview/progress.js +13 -0
  204. package/dist/shared/filereview/progress.js.map +1 -1
  205. package/dist/shared/heartbeat.d.ts +1 -1
  206. package/dist/shared/heartbeat.js +1 -1
  207. package/dist/shared/llm-backend.d.ts +0 -4
  208. package/dist/shared/llm-backend.js +0 -4
  209. package/dist/shared/llm-backend.js.map +1 -1
  210. package/dist/shared/memory-attachment.d.ts +16 -9
  211. package/dist/shared/memory-attachment.js +18 -10
  212. package/dist/shared/memory-attachment.js.map +1 -1
  213. package/dist/shared/model-error.d.ts +1 -2
  214. package/dist/shared/model-error.js +1 -2
  215. package/dist/shared/model-error.js.map +1 -1
  216. package/dist/shared/persist-decision.d.ts +18 -24
  217. package/dist/shared/persist-decision.js +19 -11
  218. package/dist/shared/persist-decision.js.map +1 -1
  219. package/dist/shared/prompt-sections.d.ts +4 -4
  220. package/dist/shared/prompt-sections.js +4 -4
  221. package/dist/shared/run-credential-store.d.ts +32 -0
  222. package/dist/shared/run-credential-store.js +39 -0
  223. package/dist/shared/run-credential-store.js.map +1 -0
  224. package/dist/shared/run-credential.d.ts +61 -0
  225. package/dist/shared/run-credential.js +68 -0
  226. package/dist/shared/run-credential.js.map +1 -0
  227. package/dist/shared/skill-resolver.d.ts +3 -3
  228. package/dist/shared/skill-resolver.js +3 -3
  229. package/dist/shared/status-offload.d.ts +1 -1
  230. package/dist/shared/status-offload.js +2 -2
  231. package/dist/shared/status-offload.js.map +1 -1
  232. package/dist/shared/streaming-scheduler.d.ts +2 -7
  233. package/dist/shared/streaming-scheduler.js +6 -14
  234. package/dist/shared/streaming-scheduler.js.map +1 -1
  235. package/dist/shared/subagent-rows.d.ts +2 -2
  236. package/dist/shared/subagent-rows.js +2 -2
  237. package/dist/shared/synthesized-attachment.d.ts +30 -3
  238. package/dist/shared/synthesized-attachment.js +26 -0
  239. package/dist/shared/synthesized-attachment.js.map +1 -1
  240. package/dist/shared/todos.d.ts +3 -3
  241. package/dist/shared/todos.js +3 -3
  242. package/dist/shared/tool-row.d.ts +19 -1
  243. package/dist/shared/tool-row.js +36 -2
  244. package/dist/shared/tool-row.js.map +1 -1
  245. package/dist/shared/worker-shutdown.d.ts +1 -1
  246. package/dist/shared/worker-shutdown.js +1 -1
  247. package/dist/shared/workspace/session-provision.d.ts +1 -1
  248. package/dist/shared/workspace/session-provision.js +1 -1
  249. package/dist/shared/workspace/sources/empty.d.ts +1 -1
  250. package/dist/shared/workspace/sources/empty.js +1 -2
  251. package/dist/shared/workspace/sources/empty.js.map +1 -1
  252. package/dist/shared/workspace/sources/git.js +4 -4
  253. package/dist/shared/workspace/sources/git.js.map +1 -1
  254. package/dist/shared/workspace/sources/local-path.js +6 -6
  255. package/dist/shared/workspace/sources/local-path.js.map +1 -1
  256. package/dist/shared/workspace/stigmer-link.d.ts +1 -1
  257. package/dist/shared/workspace/stigmer-link.js +1 -1
  258. package/dist/shared/workspace/types.d.ts +2 -5
  259. package/dist/shared/workspace/types.js +0 -6
  260. package/dist/shared/workspace/types.js.map +1 -1
  261. package/dist/shared/workspace/writeback-coordinator.d.ts +17 -4
  262. package/dist/shared/workspace/writeback-coordinator.js +7 -9
  263. package/dist/shared/workspace/writeback-coordinator.js.map +1 -1
  264. package/dist/worker.js +10 -3
  265. package/dist/worker.js.map +1 -1
  266. package/dist/workflow-source.d.ts +10 -0
  267. package/dist/workflow-source.js +12 -0
  268. package/dist/workflow-source.js.map +1 -1
  269. package/dist/workflows/execute-from-execution.d.ts +15 -0
  270. package/dist/workflows/execute-from-execution.js +11 -0
  271. package/dist/workflows/execute-from-execution.js.map +1 -1
  272. package/dist/workflows/index.d.ts +5 -3
  273. package/dist/workflows/index.js +5 -3
  274. package/dist/workflows/index.js.map +1 -1
  275. package/dist/workflows/interceptors/run-credential.d.ts +34 -0
  276. package/dist/workflows/interceptors/run-credential.js +71 -0
  277. package/dist/workflows/interceptors/run-credential.js.map +1 -0
  278. package/package.json +4 -4
  279. package/src/__test-utils__/__tests__/execution-record-barrier.test.ts +70 -0
  280. package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +35 -1
  281. package/src/__test-utils__/approval-contract/contract.ts +1 -1
  282. package/src/__test-utils__/approval-contract/types.ts +1 -1
  283. package/src/__test-utils__/harness-boot-order-child.ts +1 -1
  284. package/src/__test-utils__/harness-contract/contract.ts +8 -8
  285. package/src/__test-utils__/harness-contract/recording-sink.ts +11 -1
  286. package/src/__test-utils__/harness-contract/runtime-contract.ts +52 -16
  287. package/src/__test-utils__/harness-contract/scripted-adapter.ts +47 -43
  288. package/src/__test-utils__/hermetic-activity.ts +113 -9
  289. package/src/__test-utils__/model-registry-fixture.ts +31 -7
  290. package/src/__test-utils__/module-specifiers.ts +32 -3
  291. package/src/__tests__/harness-boot-order.test.ts +1 -1
  292. package/src/__tests__/harness-contract.test.ts +1 -1
  293. package/src/__tests__/run-credential-e2e.test.ts +233 -0
  294. package/src/__tests__/run-credential-registration.test.ts +80 -0
  295. package/src/__tests__/runner-manager.test.ts +1 -1
  296. package/src/activities/__tests__/generate-session-subject-wire.test.ts +119 -0
  297. package/src/activities/execute-cursor/__test-utils__/__tests__/scripted-double.test.ts +1 -1
  298. package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +2 -2
  299. package/src/activities/execute-cursor/__test-utils__/fold.ts +116 -0
  300. package/src/activities/execute-cursor/__test-utils__/gateway-substrate.ts +1 -1
  301. package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +67 -17
  302. package/src/activities/execute-cursor/__test-utils__/scripted-agent.ts +19 -5
  303. package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +2 -1
  304. package/src/activities/execute-cursor/__tests__/approval-state.test.ts +14 -21
  305. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +3 -3
  306. package/src/activities/execute-cursor/__tests__/coarse-fingerprint.test.ts +2 -1
  307. package/src/activities/execute-cursor/__tests__/cursor-event-recorder.test.ts +142 -0
  308. package/src/activities/execute-cursor/__tests__/cursor-fetch-interceptor-bypass.test.ts +19 -8
  309. package/src/activities/execute-cursor/__tests__/cursor-sdk-auth-smoke.test.ts +10 -8
  310. package/src/activities/execute-cursor/__tests__/cursor-transcript-live.test.ts +298 -0
  311. package/src/activities/execute-cursor/__tests__/deny-gate-exact-apply.test.ts +4 -2
  312. package/src/activities/execute-cursor/__tests__/fetch-interceptor.test.ts +1 -1
  313. package/src/activities/execute-cursor/__tests__/hermetic/deny-and-retry.test.ts +1 -1
  314. package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -1
  315. package/src/activities/execute-cursor/__tests__/hermetic/goldens/mcp-tool-call.status.json +71 -0
  316. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-task-note.status.json +73 -0
  317. package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.error-delta.status.json +78 -0
  318. package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.output-first.status.json +72 -0
  319. package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.status.json +72 -0
  320. package/src/activities/execute-cursor/__tests__/hermetic/goldens/sub-agent-delegation.status.json +109 -0
  321. package/src/activities/execute-cursor/__tests__/hermetic/goldens/thinking-turn.status.json +78 -0
  322. package/src/activities/execute-cursor/__tests__/hermetic/goldens/todo-write.status.json +119 -0
  323. package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-error.status.json +68 -0
  324. package/src/activities/execute-cursor/__tests__/hermetic/goldens/tool-only-step.status.json +67 -0
  325. package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +1 -1
  326. package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +7 -7
  327. package/src/activities/execute-cursor/__tests__/hermetic/mcp-tool-call.test.ts +146 -0
  328. package/src/activities/execute-cursor/__tests__/hermetic/pause-vs-shutdown.test.ts +5 -5
  329. package/src/activities/execute-cursor/__tests__/hermetic/plain-turn.test.ts +2 -1
  330. package/src/activities/execute-cursor/__tests__/hermetic/run-wait-arms.test.ts +2 -1
  331. package/src/activities/execute-cursor/__tests__/hermetic/sdk-task-note.test.ts +122 -0
  332. package/src/activities/execute-cursor/__tests__/hermetic/shell-output-delta.test.ts +276 -0
  333. package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +8 -8
  334. package/src/activities/execute-cursor/__tests__/hermetic/sub-agent-delegation.test.ts +196 -0
  335. package/src/activities/execute-cursor/__tests__/hermetic/thinking-turn.test.ts +145 -0
  336. package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +1 -1
  337. package/src/activities/execute-cursor/__tests__/hermetic/todo-write.test.ts +165 -0
  338. package/src/activities/execute-cursor/__tests__/hermetic/tool-call.test.ts +3 -3
  339. package/src/activities/execute-cursor/__tests__/hermetic/tool-error.test.ts +138 -0
  340. package/src/activities/execute-cursor/__tests__/hermetic/tool-only-step.test.ts +130 -0
  341. package/src/activities/execute-cursor/__tests__/hermetic/unattributed-hook-block.test.ts +13 -1
  342. package/src/activities/execute-cursor/__tests__/hermetic/workspace-lock-timeout.test.ts +2 -2
  343. package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +43 -60
  344. package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +23 -28
  345. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +2 -1
  346. package/src/activities/execute-cursor/__tests__/http2-interceptor.test.ts +1 -1
  347. package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +5 -5
  348. package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +11 -13
  349. package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +8 -11
  350. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +8 -22
  351. package/src/activities/execute-cursor/__tests__/translator.test.ts +594 -0
  352. package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +8 -5
  353. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +87 -63
  354. package/src/activities/execute-cursor/adapter.ts +4 -4
  355. package/src/activities/execute-cursor/approval-policy.ts +12 -24
  356. package/src/activities/execute-cursor/approval-state.ts +66 -1
  357. package/src/activities/execute-cursor/boundary-rows.ts +1132 -0
  358. package/src/activities/execute-cursor/cursor-capabilities.ts +1 -1
  359. package/src/activities/execute-cursor/cursor-event-recorder.ts +57 -28
  360. package/src/activities/execute-cursor/fetch-interceptor.ts +1 -1
  361. package/src/activities/execute-cursor/http2-interceptor.ts +1 -1
  362. package/src/activities/execute-cursor/prompt-builder.ts +6 -6
  363. package/src/activities/execute-cursor/service-tier.ts +1 -2
  364. package/src/activities/execute-cursor/sub-agent-steps.ts +188 -0
  365. package/src/activities/execute-cursor/tool-result.ts +128 -0
  366. package/src/activities/execute-cursor/translator.ts +541 -0
  367. package/src/activities/execute-cursor/turn-boundary.ts +21 -14
  368. package/src/activities/execute-cursor/turn-settle.ts +46 -34
  369. package/src/activities/execute-cursor/turn-setup.ts +5 -5
  370. package/src/activities/execute-cursor/turn-stream.ts +49 -46
  371. package/src/activities/execute-cursor/turn.ts +5 -5
  372. package/src/activities/execute-cursor/usage-pricing.ts +2 -2
  373. package/src/activities/execute-deep-agent/__test-utils__/contract-subject.ts +2 -2
  374. package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +23 -11
  375. package/src/activities/execute-deep-agent/__test-utils__/hitl-script.ts +1 -1
  376. package/src/activities/execute-deep-agent/__test-utils__/scripted-model-module.ts +2 -3
  377. package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +25 -4
  378. package/src/activities/execute-deep-agent/__test-utils__/v3-event-fixtures.ts +25 -11
  379. package/src/activities/execute-deep-agent/__tests__/adapter-graph-is-sdk-free.test.ts +1 -1
  380. package/src/activities/execute-deep-agent/__tests__/adapter-is-temporal-free.test.ts +1 -1
  381. package/src/activities/execute-deep-agent/__tests__/approval-file-change.test.ts +6 -17
  382. package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +25 -8
  383. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn1.status.json +2 -7
  384. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn2.status.json +4 -14
  385. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve.turn2.status.json +1 -5
  386. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +4 -7
  387. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/gate.turn1.status.json +1 -5
  388. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +2 -9
  389. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +2 -9
  390. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.after-tool.status.json +1 -4
  391. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.mid-tool.status.json +1 -4
  392. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/platform-stop.status.json +1 -4
  393. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +109 -76
  394. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/reject.turn2.status.json +1 -5
  395. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn2.status.json +2 -10
  396. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn3.status.json +2 -10
  397. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/skip.turn2.status.json +1 -5
  398. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sub-agent-delegation.status.json +3 -6
  399. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/tool-call.status.json +1 -4
  400. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/worker-shutdown.status.json +1 -4
  401. package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/write-todos.status.json +92 -0
  402. package/src/activities/execute-deep-agent/__tests__/hermetic/harness-contract.test.ts +3 -3
  403. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-approve-all-lease.test.ts +9 -5
  404. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-round-trips.test.ts +3 -3
  405. package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-sequential-gates.test.ts +3 -3
  406. package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +7 -6
  407. package/src/activities/execute-deep-agent/__tests__/hermetic/pause-vs-shutdown.test.ts +10 -11
  408. package/src/activities/execute-deep-agent/__tests__/hermetic/plain-turn.test.ts +4 -4
  409. package/src/activities/execute-deep-agent/__tests__/hermetic/platform-stop.test.ts +9 -8
  410. package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +21 -13
  411. package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +2 -2
  412. package/src/activities/execute-deep-agent/__tests__/hermetic/structured-output.test.ts +7 -7
  413. package/src/activities/execute-deep-agent/__tests__/hermetic/sub-agent-delegation.test.ts +28 -12
  414. package/src/activities/execute-deep-agent/__tests__/hermetic/tool-call.test.ts +21 -21
  415. package/src/activities/execute-deep-agent/__tests__/hermetic/workspace-lock-timeout.test.ts +5 -5
  416. package/src/activities/execute-deep-agent/__tests__/hermetic/write-todos.test.ts +160 -0
  417. package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +35 -30
  418. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +3 -4
  419. package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +12 -10
  420. package/src/activities/execute-deep-agent/__tests__/scripted-model.test.ts +13 -3
  421. package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +26 -18
  422. package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +1 -1
  423. package/src/activities/execute-deep-agent/__tests__/{subagent-tracker.test.ts → translator-subagents.test.ts} +76 -57
  424. package/src/activities/execute-deep-agent/__tests__/{v3-status-builder.test.ts → translator-through-builder.test.ts} +213 -299
  425. package/src/activities/execute-deep-agent/__tests__/translator.test.ts +505 -0
  426. package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +11 -29
  427. package/src/activities/execute-deep-agent/adapter.ts +1 -1
  428. package/src/activities/execute-deep-agent/approval-file-change.ts +20 -22
  429. package/src/activities/execute-deep-agent/deep-agent-capabilities.ts +2 -2
  430. package/src/activities/execute-deep-agent/hitl.ts +2 -2
  431. package/src/activities/execute-deep-agent/inline-publisher.ts +16 -5
  432. package/src/activities/execute-deep-agent/prompt-builder.ts +6 -7
  433. package/src/activities/execute-deep-agent/streaming-side-effects.ts +22 -31
  434. package/src/activities/execute-deep-agent/subagent-transformer.ts +3 -3
  435. package/src/activities/execute-deep-agent/translator.ts +463 -0
  436. package/src/activities/execute-deep-agent/turn-settle.ts +35 -43
  437. package/src/activities/execute-deep-agent/turn-setup.ts +8 -8
  438. package/src/activities/execute-deep-agent/turn-stream.ts +53 -51
  439. package/src/activities/execute-deep-agent/turn.ts +3 -3
  440. package/src/activities/generate-session-subject.ts +29 -4
  441. package/src/client/__tests__/stigmer-client.test.ts +92 -0
  442. package/src/client/stigmer-client.ts +48 -14
  443. package/src/config.ts +6 -3
  444. package/src/harness/README.md +335 -0
  445. package/src/harness/__tests__/approval-decisions.test.ts +2 -2
  446. package/src/harness/__tests__/capture.test.ts +3 -3
  447. package/src/harness/__tests__/import-direction.test.ts +3 -2
  448. package/src/harness/__tests__/mount-skills.test.ts +4 -1
  449. package/src/harness/__tests__/readme-index.test.ts +339 -0
  450. package/src/harness/__tests__/run-turn.test.ts +131 -6
  451. package/src/harness/__tests__/transcript-is-engine-free.test.ts +83 -0
  452. package/src/harness/__tests__/transcript-writers.test.ts +191 -0
  453. package/src/harness/__tests__/turn-attachments-vision.test.ts +5 -2
  454. package/src/harness/__tests__/turn-context.test.ts +5 -5
  455. package/src/harness/approval-decisions.ts +1 -1
  456. package/src/harness/capabilities.ts +21 -11
  457. package/src/harness/capture.ts +4 -4
  458. package/src/harness/persist-chokepoint.ts +3 -3
  459. package/src/harness/registry.ts +2 -2
  460. package/src/harness/run-turn.ts +33 -9
  461. package/src/harness/stop-controller.ts +1 -1
  462. package/src/harness/terminal-table.ts +7 -7
  463. package/src/harness/transcript/__tests__/builder.test.ts +975 -0
  464. package/src/harness/transcript/__tests__/state.test.ts +143 -0
  465. package/src/harness/transcript/builder.ts +755 -0
  466. package/src/harness/transcript/events.ts +262 -0
  467. package/src/harness/transcript/state.ts +167 -0
  468. package/src/harness/turn-context.ts +61 -46
  469. package/src/harness/types.ts +64 -22
  470. package/src/harness/usage-accumulator.ts +2 -2
  471. package/src/harness-adapters.ts +5 -4
  472. package/src/interceptors/__tests__/run-credential-activity.test.ts +133 -0
  473. package/src/interceptors/run-credential-activity.ts +67 -0
  474. package/src/middleware/__tests__/cost-advisory.test.ts +2 -2
  475. package/src/middleware/approval-gate.ts +7 -74
  476. package/src/middleware/cost-advisory.ts +3 -3
  477. package/src/middleware/index.ts +1 -1
  478. package/src/runner-manager.ts +14 -4
  479. package/src/shared/__tests__/approval-policy.test.ts +45 -0
  480. package/src/shared/__tests__/args-preview.test.ts +40 -20
  481. package/src/shared/__tests__/attachment-naming.test.ts +1 -1
  482. package/src/shared/__tests__/attachment-resolver.test.ts +5 -5
  483. package/src/shared/__tests__/attachment-zip.test.ts +1 -1
  484. package/src/shared/__tests__/blueprint-resolver.test.ts +1 -1
  485. package/src/shared/__tests__/channel-attachment.test.ts +12 -0
  486. package/src/shared/__tests__/cost-guard.test.ts +1 -1
  487. package/src/shared/__tests__/exact-apply.test.ts +2 -1
  488. package/src/shared/__tests__/memory-attachment.test.ts +17 -0
  489. package/src/shared/__tests__/persist-decision.test.ts +19 -33
  490. package/src/shared/__tests__/prompt-sections.test.ts +1 -1
  491. package/src/shared/__tests__/run-credential.test.ts +111 -0
  492. package/src/shared/__tests__/stamp-flowed-rows.test.ts +1 -1
  493. package/src/shared/__tests__/status-offload.test.ts +1 -1
  494. package/src/shared/__tests__/streaming-scheduler.test.ts +8 -9
  495. package/src/shared/__tests__/todos.test.ts +1 -1
  496. package/src/shared/__tests__/tool-row.test.ts +1 -1
  497. package/src/shared/activity-input.ts +21 -0
  498. package/src/shared/approval-policy.ts +95 -0
  499. package/src/shared/args-preview.ts +51 -42
  500. package/src/shared/attachment-resolver.ts +4 -4
  501. package/src/shared/attachment-zip.ts +2 -2
  502. package/src/shared/channel-attachment.ts +14 -6
  503. package/src/shared/cost-guard.ts +2 -2
  504. package/src/shared/exact-apply.ts +1 -1
  505. package/src/shared/execution-context.ts +2 -2
  506. package/src/shared/extract-structured-output.ts +2 -2
  507. package/src/shared/filereview/__tests__/capture.test.ts +9 -5
  508. package/src/shared/filereview/__tests__/git-substrate.test.ts +4 -1
  509. package/src/shared/filereview/__tests__/proxy-reconcile.test.ts +3 -3
  510. package/src/shared/filereview/cas-touched.ts +2 -2
  511. package/src/shared/filereview/progress.ts +13 -0
  512. package/src/shared/heartbeat.ts +1 -1
  513. package/src/shared/llm-backend.ts +0 -4
  514. package/src/shared/memory-attachment.ts +22 -10
  515. package/src/shared/model-error.ts +1 -2
  516. package/src/shared/persist-decision.ts +19 -28
  517. package/src/shared/prompt-sections.ts +4 -4
  518. package/src/shared/run-credential-store.ts +45 -0
  519. package/src/shared/run-credential.ts +70 -0
  520. package/src/shared/skill-resolver.ts +3 -3
  521. package/src/shared/status-offload.ts +2 -2
  522. package/src/shared/streaming-scheduler.ts +8 -13
  523. package/src/shared/subagent-rows.ts +2 -2
  524. package/src/shared/synthesized-attachment.ts +38 -3
  525. package/src/shared/todos.ts +3 -3
  526. package/src/shared/tool-row.ts +38 -2
  527. package/src/shared/worker-shutdown.ts +1 -1
  528. package/src/shared/workspace/__tests__/git-source.test.ts +3 -3
  529. package/src/shared/workspace/__tests__/provisioner.test.ts +5 -5
  530. package/src/shared/workspace/__tests__/session-provision.test.ts +2 -2
  531. package/src/shared/workspace/__tests__/writeback-coordinator.integration.test.ts +8 -9
  532. package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +25 -25
  533. package/src/shared/workspace/provisioner.ts +1 -1
  534. package/src/shared/workspace/session-provision.ts +1 -1
  535. package/src/shared/workspace/sources/empty.ts +2 -2
  536. package/src/shared/workspace/sources/git.ts +3 -4
  537. package/src/shared/workspace/sources/local-path.ts +5 -6
  538. package/src/shared/workspace/stigmer-link.ts +1 -1
  539. package/src/shared/workspace/types.ts +2 -5
  540. package/src/shared/workspace/writeback-coordinator.ts +23 -12
  541. package/src/worker.ts +15 -3
  542. package/src/workflow-source.ts +15 -0
  543. package/src/workflows/__tests__/run-credential-interceptor.test.ts +178 -0
  544. package/src/workflows/execute-from-execution.ts +17 -0
  545. package/src/workflows/index.ts +5 -3
  546. package/src/workflows/interceptors/run-credential.ts +101 -0
  547. package/dist/activities/execute-cursor/delta-enricher.d.ts +0 -87
  548. package/dist/activities/execute-cursor/delta-enricher.js +0 -265
  549. package/dist/activities/execute-cursor/delta-enricher.js.map +0 -1
  550. package/dist/activities/execute-cursor/message-translator.js +0 -2040
  551. package/dist/activities/execute-cursor/message-translator.js.map +0 -1
  552. package/dist/activities/execute-cursor/todo-tracker.d.ts +0 -40
  553. package/dist/activities/execute-cursor/todo-tracker.js +0 -80
  554. package/dist/activities/execute-cursor/todo-tracker.js.map +0 -1
  555. package/dist/activities/execute-deep-agent/execution-state.d.ts +0 -54
  556. package/dist/activities/execute-deep-agent/execution-state.js +0 -68
  557. package/dist/activities/execute-deep-agent/execution-state.js.map +0 -1
  558. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +0 -65
  559. package/dist/activities/execute-deep-agent/status-builder-shared.js +0 -90
  560. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +0 -1
  561. package/dist/activities/execute-deep-agent/subagent-tracker.d.ts +0 -113
  562. package/dist/activities/execute-deep-agent/subagent-tracker.js +0 -400
  563. package/dist/activities/execute-deep-agent/subagent-tracker.js.map +0 -1
  564. package/dist/activities/execute-deep-agent/v3-events.d.ts +0 -102
  565. package/dist/activities/execute-deep-agent/v3-events.js +0 -35
  566. package/dist/activities/execute-deep-agent/v3-events.js.map +0 -1
  567. package/dist/activities/execute-deep-agent/v3-protocol-normalizer.d.ts +0 -15
  568. package/dist/activities/execute-deep-agent/v3-protocol-normalizer.js +0 -235
  569. package/dist/activities/execute-deep-agent/v3-protocol-normalizer.js.map +0 -1
  570. package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +0 -111
  571. package/dist/activities/execute-deep-agent/v3-status-builder.js +0 -457
  572. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +0 -1
  573. package/dist/shared/execution-status-writer.d.ts +0 -31
  574. package/dist/shared/execution-status-writer.js +0 -43
  575. package/dist/shared/execution-status-writer.js.map +0 -1
  576. package/src/activities/execute-cursor/__tests__/delta-enricher.test.ts +0 -310
  577. package/src/activities/execute-cursor/__tests__/message-translator.test.ts +0 -1033
  578. package/src/activities/execute-cursor/__tests__/todo-tracker.test.ts +0 -498
  579. package/src/activities/execute-cursor/delta-enricher.ts +0 -307
  580. package/src/activities/execute-cursor/message-translator.ts +0 -2319
  581. package/src/activities/execute-cursor/todo-tracker.ts +0 -91
  582. package/src/activities/execute-deep-agent/__tests__/execution-state-extended.test.ts +0 -148
  583. package/src/activities/execute-deep-agent/__tests__/execution-state.test.ts +0 -154
  584. package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +0 -47
  585. package/src/activities/execute-deep-agent/__tests__/v3-protocol-normalizer.test.ts +0 -324
  586. package/src/activities/execute-deep-agent/execution-state.ts +0 -78
  587. package/src/activities/execute-deep-agent/status-builder-shared.ts +0 -142
  588. package/src/activities/execute-deep-agent/subagent-tracker.ts +0 -479
  589. package/src/activities/execute-deep-agent/v3-events.ts +0 -153
  590. package/src/activities/execute-deep-agent/v3-protocol-normalizer.ts +0 -264
  591. package/src/activities/execute-deep-agent/v3-status-builder.ts +0 -565
  592. package/src/shared/execution-status-writer.ts +0 -56
@@ -19,7 +19,9 @@
19
19
  * the facts the runtime owes on it: the terminal phase, the copy
20
20
  * (`terminal-table.ts` `TERMINAL_COPY`, byte-pinned), `status.error`,
21
21
  * `completedAt`, "the step after never runs", the whole-activity heartbeat,
22
- * one bind for an engine-minted harness, the slim return. What is NOT
22
+ * one bind for an engine-minted harness, the slim return, and — on every
23
+ * arm, whatever the outcome — a transcript that streams nothing once the
24
+ * turn has settled ({@link assertTranscriptStreamsNothing}). What is NOT
23
25
  * asserted here is engine cadence — how many IN_PROGRESS persists precede
24
26
  * the WAITING write, the exact setup labels beyond the runtime's own, the
25
27
  * exact price a vendor's table yields — because those are a subject's facts;
@@ -216,13 +218,47 @@ export class RuntimeExecutionDriver {
216
218
  }
217
219
  }
218
220
 
219
- /** The final full status the record holds; an arm that persisted nothing is a failure, named. */
221
+ /**
222
+ * The final full status the record holds, checked for the one fact every
223
+ * settled turn owes its transcript before the arm reads anything else. An
224
+ * arm that persisted nothing is a failure, named.
225
+ */
220
226
  function finalStatusOf(harness: RuntimeContractHarness, driver: RuntimeExecutionDriver): AgentExecutionStatus {
221
227
  const final = driver.record.lastFullStatus;
222
228
  if (!final) throw new Error(`${harness.subject.name}: the runtime persisted no full status`);
229
+ assertTranscriptStreamsNothing(harness.subject.name, final);
223
230
  return final;
224
231
  }
225
232
 
233
+ /**
234
+ * No streaming flag survives a settled turn — on any message, any tool-call
235
+ * row, in the root transcript or a sub-agent's — however the turn ended. A
236
+ * flag left on is a spinner the console never stops. The runtime closes
237
+ * them once, after `runTurn` returns, on every path (`harness/run-turn.ts`),
238
+ * so the fact holds for every subject and every outcome, and every arm
239
+ * checks it through {@link finalStatusOf}. Before the runtime owned that
240
+ * close, this fired on both real adapters' internal-failure arm: a turn that
241
+ * failed before its settle left its last message streaming. Exported so the
242
+ * kit's self-check can hand it a status that still streams and prove it
243
+ * fires.
244
+ */
245
+ export function assertTranscriptStreamsNothing(subjectName: string, status: AgentExecutionStatus): void {
246
+ const streaming: string[] = [];
247
+ const sweep = (scope: string, messages: AgentExecutionStatus["messages"]): void => {
248
+ messages.forEach((m, i) => {
249
+ if (m.isStreaming) streaming.push(`${scope}messages[${i}]`);
250
+ for (const tc of m.toolCalls) {
251
+ if (tc.isStreaming) streaming.push(`${scope}messages[${i}].toolCalls[${tc.id}]`);
252
+ }
253
+ });
254
+ };
255
+ sweep("", status.messages);
256
+ for (const sub of status.subAgentExecutions) sweep(`subAgentExecutions[${sub.id}].`, sub.messages);
257
+ if (streaming.length > 0) {
258
+ throw new Error(`${subjectName}: a settled turn's transcript is still streaming at ${streaming.join(", ")}`);
259
+ }
260
+ }
261
+
226
262
  /** What every arm hands back so a subject's file can add its own facts. */
227
263
  export interface RuntimeArmResult {
228
264
  readonly driver: RuntimeExecutionDriver;
@@ -357,11 +393,11 @@ export async function assertFileReviewRoundTrip(harness: RuntimeContractHarness,
357
393
  expect(rows.get(keepId)?.fileChangeSetId, `${subject.name}: a flowed row is badged with the change set`).toBe(changeSetId);
358
394
  expect(rows.get(dropId)?.fileChangeSetId).toBe(changeSetId);
359
395
  expect(rows.get(keepId)?.status, `${subject.name}: a flowed row is COMPLETED, never gated`).toBe(ToolCallStatus.TOOL_CALL_COMPLETED);
360
- // The strip is the runtime's, attached on its writes (Q-M4-7). Its COUNT is
396
+ // The strip is the runtime's, attached on its writes. Its COUNT is
361
397
  // the capture floor's: a real engine's first persist precedes its first
362
398
  // write and both writes land within one floor interval, so the pause can
363
- // honestly read "0 files" beside a two-file candidate (F-M4-3, recorded
364
- // for S5: whether the terminal write should bypass the floor). The arm
399
+ // honestly read "0 files" beside a two-file candidate (#1114: whether the
400
+ // terminal write should bypass the floor). The arm
365
401
  // asserts the ownership, not the floor.
366
402
  expect(afterCapture.fileChangeProgress?.changeSetId, `${subject.name}: the runtime carries this turn's strip`).toBe(changeSetId);
367
403
  expect(afterCapture.completedAt, `${subject.name}: a review pause is not complete`).toBe("");
@@ -374,7 +410,7 @@ export async function assertFileReviewRoundTrip(harness: RuntimeContractHarness,
374
410
  const second = await driver.turn([]);
375
411
  expect(slimOf(subject, second).phase, `${subject.name}: a pure file-review resume completes without the engine`).toBe("EXECUTION_COMPLETED");
376
412
  // The kept file is the engine-not-re-run witness: a subject that counts
377
- // executions by reading its ledger off disk (the native one, Q-M3-4) reads
413
+ // executions by reading its ledger off disk (the native one) reads
378
414
  // the DISCARDED file's count as 0 now, because the reconcile just restored
379
415
  // that file to its baseline — which is the reconcile working, not the
380
416
  // engine forgetting.
@@ -398,7 +434,7 @@ export async function assertFileReviewRoundTrip(harness: RuntimeContractHarness,
398
434
  * settles the row SKIPPED (a REJECT row carries `error: "Rejected by user"`),
399
435
  * so no WAITING row survives on a finished execution. This is the proto
400
436
  * contract as of stigmer#197 and the runtime's own write
401
- * (`harness/approval-decisions.ts`); before S3 M1 the runtime FAILED a
437
+ * (`harness/approval-decisions.ts`); before #1096 the runtime FAILED a
402
438
  * REJECTed reinvocation before any engine ran.
403
439
  */
404
440
  export async function assertNonExecutingDecisionSettlesSkipped(
@@ -440,14 +476,14 @@ export async function assertNonExecutingDecisionSettlesSkipped(
440
476
  * the next message), `error` starting with the cross-repo prefix Stigmer
441
477
  * Cloud's channel delivery matches on, the user copy as the one system row,
442
478
  * `completedAt` stamped, RETURNED (a retry would spend the same budget), and
443
- * the step after the limit never processed. Before S3 M1 the native harness
444
- * RETURNED this terminal without persisting it (M0 finding F-M0-4); the
479
+ * the step after the limit never processed. Before #1096 the native harness
480
+ * RETURNED this terminal without persisting it; the
445
481
  * runtime's arm is the one persisted terminal.
446
482
  *
447
483
  * The execution SETS the budget it exhausts: the record carries the smallest
448
484
  * valid `max_tool_rounds` (an unbounded execution has no limit to reach — on
449
485
  * native `resolveRecursionLimit(0)` is unlimited, and the first run of this
450
- * arm against it spun until the test timed out; S3 M3 F-M3-3). The fake
486
+ * arm against it spun until the test timed out). The fake
451
487
  * exhausts whatever budget it is given; a real engine spins its cheapest
452
488
  * ungated tool until the graph stops it.
453
489
  */
@@ -625,12 +661,12 @@ const STALL_CLOCK_INTERVAL_MS = 25;
625
661
  * park is reported while the chunks the engine produced before parking are
626
662
  * still in its event pipeline ahead of the adapter's loop, so activity marks
627
663
  * land AFTER a single tick and a frozen clock reads idle 0 from then on
628
- * (S3 M3 F-M3-5, measured on the native adapter: 35 marks at the park, 37
664
+ * (measured on the native adapter: 35 marks at the park, 37
629
665
  * within 10 ms). A pull-based double's park implies everything before it
630
666
  * was seen; a real stall is wall time passing while the engine is silent,
631
667
  * whichever shape the engine has. The interval advances a SCRIPTED clock —
632
668
  * the kit's rule against timers is about stopping a turn on a guess, which
633
- * this never does (Q-M3-7).
669
+ * this never does.
634
670
  */
635
671
  export async function assertStallFailsTheTurn(harness: RuntimeContractHarness, label = "rt-stall"): Promise<RuntimeArmResult> {
636
672
  const { subject, clock } = harness;
@@ -692,7 +728,7 @@ export async function assertCostCapTerminates(harness: RuntimeContractHarness, l
692
728
  /**
693
729
  * A platform STOP answered to a mid-turn persist: the turn completes early
694
730
  * with the platform-stop row and no error, and the step after the STOP never
695
- * runs — the awaited `requestPersist` at work (Q-M3-2). The persist the
731
+ * runs — the awaited `requestPersist` at work. The persist the
696
732
  * control plane answers is the one carrying a completed `read`: the discrete
697
733
  * event every harness flushes on (the Cursor golden's lever), where a bare
698
734
  * assistant message may ride an engine's cadence to the settle persist, too
@@ -786,7 +822,7 @@ export async function assertFailedSurfaceWritesItsCopy(harness: RuntimeContractH
786
822
 
787
823
  /**
788
824
  * A rejected `bindHarnessState` (the session write refused) ends the turn
789
- * FAILED on the internal surface with nothing executed (Q-S2-11, R3).
825
+ * FAILED on the internal surface with nothing executed.
790
826
  * Engine-minted harnesses only: a deterministic one never binds.
791
827
  */
792
828
  export async function assertRejectedBindFails(harness: RuntimeContractHarness, label = "rt-bind-rejects"): Promise<RuntimeArmResult> {
@@ -884,7 +920,7 @@ export interface RuntimeContractOptions {
884
920
  * to wait for (`scenario.cancelled()`, an SDK-side cancel); the arm is
885
921
  * registered SKIPPED otherwise. On by default; the native subject turns it
886
922
  * off (no LangGraph path ends a turn `cancelled` — a stop is the runtime's
887
- * and settles `interrupted`; S3 M3 F-M3-2).
923
+ * and settles `interrupted`).
888
924
  */
889
925
  readonly engineCancel?: boolean;
890
926
  }
@@ -968,7 +1004,7 @@ export function describeHarnessRuntimeContract(harness: RuntimeContractHarness,
968
1004
  await assertFailedSurfaceWritesItsCopy(harness, surface, FAILURE_MESSAGES[surface]);
969
1005
  });
970
1006
  }
971
- it.skipIf(subject.adapter.capabilities.stateIdSource !== "engine-minted")("a rejected bindHarnessState ends the turn failed with nothing executed (Q-S2-11)", async () => {
1007
+ it.skipIf(subject.adapter.capabilities.stateIdSource !== "engine-minted")("a rejected bindHarnessState ends the turn failed with nothing executed", async () => {
972
1008
  clock.reset();
973
1009
  await assertRejectedBindFails(harness);
974
1010
  });
@@ -22,8 +22,12 @@
22
22
  *
23
23
  * What is deliberately simple here and would be real work in an adapter:
24
24
  * "executing" a side effect is incrementing a counter; the engine state id is
25
- * a counter too; the transcript rows are built with the shared proto
26
- * factories. What is NOT simplified is the contract behaviour itself —
25
+ * a counter too; there is no engine event to translate, so each step emits
26
+ * the canonical `TranscriptEvent`s a translator would (a run for a `say`, a
27
+ * start and a finish for a tool, a proposal for a gate) straight into
28
+ * `sink.transcript` — the one way any harness creates a transcript row, and
29
+ * the whole of what a real adapter's translator adds on top of this file.
30
+ * What is NOT simplified is the contract behaviour itself —
27
31
  * settling with an outcome and never rejecting, stopping at every step
28
32
  * boundary and inside a hang, binding before the first persist, executing an
29
33
  * approval exactly once, refusing to resume a state it never minted — because
@@ -34,8 +38,8 @@
34
38
  * is on the row (the server's field) and the transition follows from it with
35
39
  * no engine knowledge, so it belongs to the runtime — one writer per field,
36
40
  * as `approvalDecisionsOf` is the runtime's one reader and
37
- * `terminalizeNonExecutingDecisions` its one writer (S2 M4, Q-M4-1; landed at
38
- * S3 M1, `harness/approval-decisions.ts`). An adapter's whole duty for a
41
+ * `terminalizeNonExecutingDecisions` its one writer (landed in #1096,
42
+ * `harness/approval-decisions.ts`). An adapter's whole duty for a
39
43
  * non-executing decision is to not execute.
40
44
  *
41
45
  * Scenarios are arranged PER SESSION (the engine is per session in every real
@@ -57,7 +61,7 @@ import type { HarnessAdapter, TurnInput, TurnOutcome, TurnSink } from "../../har
57
61
  import { DEEP_AGENT_VISION_PROFILE } from "../../shared/attachment-vision.js";
58
62
  import type { ProposedAction } from "../approval-contract/types.js";
59
63
  import { testConfig } from "../config-fixture.js";
60
- import { aiMessage, findToolCallRow, toolCall, waitingToolCall } from "../proto-helpers.js";
64
+ import { findToolCallRow } from "../proto-helpers.js";
61
65
  import type { EngineView, HarnessContractSubject, ScenarioStep, TurnScenario } from "./types.js";
62
66
 
63
67
  export interface ScriptedHarnessOptions {
@@ -101,6 +105,8 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
101
105
  private readonly executions = new Map<string, number>();
102
106
  private readonly hangWaiters: Array<() => void> = [];
103
107
  private mintCounter = 0;
108
+ /** One `runId` per `say`, as an engine mints one per model run; the builder keys a message on it. */
109
+ private runCounter = 0;
104
110
 
105
111
  constructor(options: ScriptedHarnessOptions) {
106
112
  this.name = options.name ?? `scripted(${options.pausePrimitive}, ${options.stateIdSource})`;
@@ -211,7 +217,12 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
211
217
  private async play(step: ScenarioStep, input: TurnInput, sink: TurnSink): Promise<TurnOutcome | undefined> {
212
218
  switch (step.kind) {
213
219
  case "say": {
214
- sink.status.messages.push(aiMessage(step.text));
220
+ // One model run: start, its text, finish — the shape every
221
+ // translator emits for an assistant message.
222
+ const runId = `run-${++this.runCounter}`;
223
+ sink.transcript.apply({ kind: "message_start", runId });
224
+ sink.transcript.apply({ kind: "text_delta", runId, text: step.text });
225
+ sink.transcript.apply({ kind: "message_finish", runId });
215
226
  sink.recordActivity();
216
227
  // Awaited, as the Cursor loop awaits its own: a platform STOP the
217
228
  // runtime reads from this write aborts the signal before the next
@@ -223,14 +234,12 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
223
234
  if (step.flows) return this.flowingWrite(step.toolCallId, step.action, input, sink);
224
235
  return this.propose(step.toolCallId, step.action, input, sink);
225
236
  case "read": {
226
- // Ungated: the row lands COMPLETED at once, the effect counts as run,
227
- // and the persist is awaited like `say`'s — a real engine's tool call
228
- // is the discrete event its loop flushes on.
229
- const message = aiMessage("");
230
- const row = toolCall(step.toolCallId, "read", ToolCallStatus.TOOL_CALL_COMPLETED);
231
- row.result = `contents of ${step.path}`;
232
- message.toolCalls.push(row);
233
- sink.status.messages.push(message);
237
+ // Ungated: the row starts and finishes at once, the effect counts as
238
+ // run, and the persist is awaited like `say`'s — a real engine's tool
239
+ // call is the discrete event its loop flushes on. The builder puts
240
+ // the row on the message whose text preceded it.
241
+ sink.transcript.apply({ kind: "tool_started", callId: step.toolCallId, name: "read", input: { path: step.path }, mcpServerSlug: "" });
242
+ sink.transcript.apply({ kind: "tool_finished", callId: step.toolCallId, result: `contents of ${step.path}` });
234
243
  this.executions.set(step.toolCallId, this.executionCount(step.toolCallId) + 1);
235
244
  sink.recordActivity("read");
236
245
  await sink.requestPersist();
@@ -279,12 +288,18 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
279
288
  switch (decision) {
280
289
  case ApprovalAction.UNSPECIFIED: {
281
290
  // Propose: surface the gated call as a WAITING row and stop the turn.
282
- // Never write a second row for the same id on a resumed turn.
283
- if (!existing) {
284
- const message = aiMessage("");
285
- message.toolCalls.push(waitingToolCall(toolCallId, action.kind, `Approve ${action.kind} ${action.resource}?`));
286
- sink.status.messages.push(message);
287
- }
291
+ // `approval_proposed` is an upsert by id — a resumed turn that
292
+ // re-proposes reopens the seeded row, never writes a second one. No
293
+ // `args` on purpose: a `write` carrying whole-file content would make
294
+ // the runtime's exact-apply eligible on APPROVE, and this fake's
295
+ // execution count would then measure a path the engine never ran.
296
+ sink.transcript.apply({
297
+ kind: "approval_proposed",
298
+ callId: toolCallId,
299
+ name: action.kind,
300
+ mcpServerSlug: "",
301
+ message: `Approve ${action.kind} ${action.resource}?`,
302
+ });
288
303
  sink.recordActivity();
289
304
  sink.requestPersist();
290
305
  return { kind: "awaiting_approval" };
@@ -292,7 +307,7 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
292
307
  case ApprovalAction.APPROVE:
293
308
  case ApprovalAction.APPROVE_ALL: {
294
309
  this.executions.set(toolCallId, this.executionCount(toolCallId) + 1);
295
- this.completeRow(existing, toolCallId, action, sink);
310
+ this.completeRow(toolCallId, action, sink);
296
311
  return undefined;
297
312
  }
298
313
  case ApprovalAction.SKIP:
@@ -321,11 +336,8 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
321
336
  const file = join(input.workspace.primaryDir, action.resource);
322
337
  await mkdir(dirname(file), { recursive: true });
323
338
  await writeFile(file, `${toolCallId}\n`);
324
- const message = aiMessage("");
325
- const row = toolCall(toolCallId, "write", ToolCallStatus.TOOL_CALL_COMPLETED);
326
- row.args = { path: action.resource };
327
- message.toolCalls.push(row);
328
- sink.status.messages.push(message);
339
+ sink.transcript.apply({ kind: "tool_started", callId: toolCallId, name: "write", input: { path: action.resource }, mcpServerSlug: "" });
340
+ sink.transcript.apply({ kind: "tool_finished", callId: toolCallId, result: "" });
329
341
  this.executions.set(toolCallId, this.executionCount(toolCallId) + 1);
330
342
  sink.recordActivity("write");
331
343
  await sink.requestPersist();
@@ -333,22 +345,14 @@ export class ScriptedHarnessAdapter implements HarnessAdapter {
333
345
  }
334
346
 
335
347
  /**
336
- * Report the executed action on its row, as a real engine's completion
337
- * event does. The row normally exists (the runtime seeded the status with
338
- * last turn's WAITING row); a decision with no row is a runtime that decided
339
- * out of band, and the adapter still records what happened rather than
340
- * losing the fact.
348
+ * Report the executed action on its row, as a real engine's re-issue does:
349
+ * a start (which flips the seeded WAITING row to RUNNING — or creates the
350
+ * row when a runtime decided out of band and no row exists, so the fact is
351
+ * recorded rather than lost) and a finish.
341
352
  */
342
- private completeRow(existing: ToolCall | undefined, toolCallId: string, action: ProposedAction, sink: TurnSink): void {
343
- if (existing) {
344
- existing.status = ToolCallStatus.TOOL_CALL_COMPLETED;
345
- } else {
346
- const message = aiMessage("");
347
- const row = waitingToolCall(toolCallId, action.kind, "");
348
- row.status = ToolCallStatus.TOOL_CALL_COMPLETED;
349
- message.toolCalls.push(row);
350
- sink.status.messages.push(message);
351
- }
353
+ private completeRow(toolCallId: string, action: ProposedAction, sink: TurnSink): void {
354
+ sink.transcript.apply({ kind: "tool_started", callId: toolCallId, name: action.kind, input: {}, mcpServerSlug: "" });
355
+ sink.transcript.apply({ kind: "tool_finished", callId: toolCallId, result: "" });
352
356
  sink.recordActivity();
353
357
  sink.requestPersist();
354
358
  }
@@ -360,8 +364,8 @@ export interface ScriptedSubject extends HarnessContractSubject {
360
364
  }
361
365
 
362
366
  /**
363
- * The fake declares the `deep-agent` row: in S2 the runtime served that row
364
- * through this fake alone; since S3 M3 the real native adapter takes it over
367
+ * The fake declares the `deep-agent` row: in #1070 the runtime served that row
368
+ * through this fake alone; since #1096 the real native adapter takes it over
365
369
  * in the runtime half
366
370
  * (`execute-deep-agent/__test-utils__/contract-subject.ts`), and the fake
367
371
  * keeps declaring it so the kit's self-check runs a full subject under both
@@ -14,7 +14,7 @@
14
14
  * and driven by the framework's own activity environment. Both harnesses now
15
15
  * run on it (`execute-cursor/__test-utils__/hermetic-cursor.ts`,
16
16
  * `execute-deep-agent/__test-utils__/hermetic-deep-agent.ts`), and the earlier
17
- * native tests were re-homed onto the native driver (S3 M0), their assertions
17
+ * native tests were re-homed onto the native driver (#1096), their assertions
18
18
  * carried into `execute-deep-agent/__tests__/hermetic/`.
19
19
  *
20
20
  * What is generic here and what is not: everything an activity touches that is
@@ -55,11 +55,22 @@
55
55
  * Determinism: the clock is faked for `Date` ONLY (`vi.useFakeTimers({ toFake:
56
56
  * ["Date"] })`) and TICKS — the harness double advances it a fixed quantum per
57
57
  * script step through {@link ScriptedClock}. Frozen time would make
58
- * `delta-enricher.ts`'s persist debounce (`Date.now() - lastPersistTime`) never
58
+ * the streaming scheduler's cadence (`Date.now()` against its last send) never
59
59
  * elapse and silently skip the mid-stream persist path production always runs;
60
60
  * a ticking clock runs the same path and lands on the same instants every run.
61
61
  * Timers stay real so the periodic heartbeat and the stall watchdog behave.
62
62
  *
63
+ * The live posture (#1097's live-run gate): a `CURSOR_API_KEY`-gated live
64
+ * instrument keeps all three substitutions above — the real `Context`, the
65
+ * record behind the client, the temp environment — and drops only the
66
+ * harness's SDK double, so the REAL activity runs against the REAL vendor SDK
67
+ * with everything the activity persists still readable from the record.
68
+ * Two things differ from a hermetic run and both are the caller's explicit
69
+ * choice: no `ScriptedClock` is installed (the real SDK is not ticked, and a
70
+ * frozen clock would skip the persist cadence production runs), and the
71
+ * environment may name an `eventRecordingDir` so the run leaves a recording.
72
+ * The harness's driver names the entry (`beginLiveCursorScenario`).
73
+ *
63
74
  * Not in scope: this module never edits a production module and never
64
75
  * normalizes output. If a golden is not byte-stable, the volatile source is
65
76
  * controlled at its origin or the surprise is escalated — never redacted here.
@@ -151,6 +162,8 @@ export class ExecutionRecord {
151
162
  private readonly controlSignal: (status: AgentExecutionStatus) => ExecutionControlSignal;
152
163
  /** Every `updateStatus` payload, in order, snapshotted at write time. */
153
164
  readonly persisted: AgentExecutionStatus[] = [];
165
+ /** Fired after every full-status `applyStatusUpdate`; {@link whenToolCallsSettled} subscribes here. */
166
+ private readonly persistListeners = new Set<() => void>();
154
167
  /** Setup-progress labels reported before the stream started, in order. */
155
168
  readonly setupProgress: string[] = [];
156
169
  /**
@@ -205,6 +218,78 @@ export class ExecutionRecord {
205
218
  return this.execution.status?.messages.flatMap((m) => m.toolCalls) ?? [];
206
219
  }
207
220
 
221
+ /**
222
+ * Resolves once every one of `ids` is a SETTLED row (not RUNNING) in a status
223
+ * the activity has persisted — root messages and every sub-agent's alike.
224
+ * Resolves at once for an empty list or when they already are.
225
+ *
226
+ * This is the barrier a real model gives the runtime for free and a
227
+ * scripted one does not: a model that takes 300 ms to answer never starts
228
+ * its next turn before the loop has folded and persisted the tool results
229
+ * it is answering, while `ScriptedModel` answers instantly and LangGraph's
230
+ * producer can run a whole turn ahead of the consumer while the consumer
231
+ * awaits one mid-stream persist (on a git workspace that persist shells out
232
+ * for `file_change_progress`). The stamps the consumer writes at apply time
233
+ * then land on either side of the scripted clock's next tick depending on
234
+ * I/O timing — a golden that flips between `:01` and `:02` on the day. The
235
+ * driver awaits this before it ticks (`hermetic-deep-agent.ts`), so every
236
+ * stamp of turn N precedes turn N+1's instant, run after run.
237
+ *
238
+ * Why "settled" and not "seen": the row's `completedAt` is stamped when the
239
+ * tool's end event is applied, and the producer calls the next model turn
240
+ * right after emitting that event, so a barrier on the row merely existing
241
+ * would still race the completion stamp. The persist that carries the
242
+ * settled row always exists: a tool's finish forces one (the builder's
243
+ * force flag), and a turn followed by another turn always has a tool call.
244
+ *
245
+ * Bounded by real time (timers are real under the scripted clock): a
246
+ * scenario that breaks the fact above — a persist that never carries the
247
+ * row — fails naming the ids still open, never hangs.
248
+ */
249
+ whenToolCallsSettled(ids: readonly string[], timeoutMs = 10_000): Promise<void> {
250
+ const unsettled = (): string[] => {
251
+ const settled = this.settledToolCallIds();
252
+ return ids.filter((id) => !settled.has(id));
253
+ };
254
+ if (unsettled().length === 0) return Promise.resolve();
255
+
256
+ return new Promise<void>((resolve, reject) => {
257
+ const timer = setTimeout(() => {
258
+ this.persistListeners.delete(check);
259
+ reject(
260
+ new Error(
261
+ `ExecutionRecord: tool call(s) ${unsettled().join(", ")} never settled in a persisted status ` +
262
+ `within ${timeoutMs} ms (${this.persisted.length} persists seen). A model turn was asked to ` +
263
+ `wait for the results it answers; the loop never persisted them — a scenario or runtime bug.`,
264
+ ),
265
+ );
266
+ }, timeoutMs);
267
+ const check = (): void => {
268
+ if (unsettled().length > 0) return;
269
+ clearTimeout(timer);
270
+ this.persistListeners.delete(check);
271
+ resolve();
272
+ };
273
+ this.persistListeners.add(check);
274
+ });
275
+ }
276
+
277
+ /** The ids of every row not RUNNING in the held status, across the root and every sub-agent transcript. */
278
+ private settledToolCallIds(): Set<string> {
279
+ const out = new Set<string>();
280
+ const status = this.execution.status;
281
+ if (!status) return out;
282
+ const transcripts = [status.messages, ...status.subAgentExecutions.map((s) => s.messages)];
283
+ for (const messages of transcripts) {
284
+ for (const message of messages) {
285
+ for (const tc of message.toolCalls) {
286
+ if (tc.status !== ToolCallStatus.TOOL_CALL_RUNNING) out.add(tc.id);
287
+ }
288
+ }
289
+ }
290
+ return out;
291
+ }
292
+
208
293
  /** The tool calls currently paused for a decision. */
209
294
  waitingToolCalls(): ToolCall[] {
210
295
  return this.toolCalls().filter((tc) => tc.status === ToolCallStatus.TOOL_CALL_WAITING_APPROVAL);
@@ -254,6 +339,7 @@ export class ExecutionRecord {
254
339
  return ExecutionControlSignal.UNSPECIFIED;
255
340
  }
256
341
  this.execution.status = clone(AgentExecutionStatusSchema, snapshot);
342
+ for (const listener of [...this.persistListeners]) listener();
257
343
  return this.controlSignal(snapshot);
258
344
  }
259
345
 
@@ -343,14 +429,30 @@ export function hermeticStigmerClientModule(): { StigmerClient: new () => Stigme
343
429
  * (an absent store flips capture off — a different code path).
344
430
  * - `STIGMER_RUNNER_HITL_SECRET`: the one randomness source on the activity
345
431
  * path, pinned so grant tokens and fingerprints are byte-stable.
346
- * - `CURSOR_EVENT_RECORD_DIR` cleared: never write recordings from a test.
432
+ * - `CURSOR_EVENT_RECORD_DIR` cleared: never write recordings from a test —
433
+ * unless the caller names an `eventRecordingDir`, the live instruments'
434
+ * opt-in (below), in which case this environment SETS it. Either way the
435
+ * variable has one owner here, and dispose restores it.
347
436
  */
348
437
  const PINNED_ENV = {
349
438
  ARTIFACT_STORAGE_TYPE: "local",
350
439
  STIGMER_RUNNER_HITL_SECRET: "hermetic-fixture-secret-do-not-use-in-production",
351
440
  } as const;
352
441
 
353
- const CLEARED_ENV = ["CURSOR_EVENT_RECORD_DIR", "STIGMER_PROXY_ENDPOINT", "STIGMER_CLOUD_API_URL"] as const;
442
+ const CLEARED_ENV = ["STIGMER_PROXY_ENDPOINT", "STIGMER_CLOUD_API_URL"] as const;
443
+
444
+ /** The one name the Cursor recorder is switched on by (`turn-settle.ts` reads it). */
445
+ const CURSOR_EVENT_RECORD_DIR = "CURSOR_EVENT_RECORD_DIR";
446
+
447
+ export interface HermeticEnvironmentOptions {
448
+ /**
449
+ * Where the Cursor event recorder writes this run's raw SDK recording
450
+ * (`<dir>/<executionId>.cursor-events.jsonl`). Named ONLY by a live
451
+ * instrument, whose point is a recording of the real SDK; a hermetic
452
+ * scenario leaves it unset and the variable is cleared for the run.
453
+ */
454
+ readonly eventRecordingDir?: string;
455
+ }
354
456
 
355
457
  export interface HermeticEnvironment {
356
458
  /** The temp `HOME` (the runner's `~/.stigmer` lands under it). */
@@ -368,7 +470,7 @@ export interface HermeticEnvironment {
368
470
  * `beforeAll`) — the fingerprint secret is memoized per process on first use,
369
471
  * and vitest isolates files in forks, so per-file is the natural unit.
370
472
  */
371
- export function createHermeticEnvironment(): HermeticEnvironment {
473
+ export function createHermeticEnvironment(options: HermeticEnvironmentOptions = {}): HermeticEnvironment {
372
474
  const root = mkdtempSync(join(tmpdir(), "stigmer-hermetic-"));
373
475
  const home = join(root, "home");
374
476
  const workspaceRootDir = join(root, "workspaces");
@@ -384,6 +486,7 @@ export function createHermeticEnvironment(): HermeticEnvironment {
384
486
  set("LOCAL_ARTIFACT_PATH", artifactPath);
385
487
  for (const [k, v] of Object.entries(PINNED_ENV)) set(k, v);
386
488
  for (const k of CLEARED_ENV) set(k, undefined);
489
+ set(CURSOR_EVENT_RECORD_DIR, options.eventRecordingDir);
387
490
 
388
491
  let disposed = false;
389
492
  return {
@@ -411,15 +514,16 @@ export function createHermeticEnvironment(): HermeticEnvironment {
411
514
  * advance together; timers stay real (the periodic heartbeat, the stall
412
515
  * watchdog, `withTimeout` all need them). The harness double calls
413
516
  * {@link ScriptedClock.tick} once per script step, so every clock-based
414
- * decision on the activity path — status timestamps, the enricher's persist
415
- * debounce, cache TTLs (`Date`); the streaming scheduler's persist cadence
517
+ * decision on the activity path — status timestamps, the builder's row
518
+ * instants, cache TTLs (`Date`); the streaming scheduler's persist cadence
416
519
  * (`performance.now()`) — sees the same instants run after run and runs the
417
520
  * same branches production runs.
418
521
  *
419
- * Why both (S3 M5, Q-M5-7; F-M2a-20): until M5 only `Date` was faked, so the
522
+ * Why both (since #1096): until then only `Date` was faked, so the
420
523
  * scheduler paced persists on REAL elapsed time and `fileChangeProgress`,
421
524
  * which rides persists, could gain or lose a capture under load — a golden
422
- * flake seen twice across S3. One clock, two faces: `tick` advances through
525
+ * flake seen twice while the native adapter moved onto the runtime. One clock,
526
+ * two faces: `tick` advances through
423
527
  * the fake clock's own `tick` (`vi.advanceTimersByTime`), which moves both
424
528
  * `Date` and `performance.now()`; `vi.setSystemTime` would move `Date` alone
425
529
  * (fake-timers keeps `performance` monotonic across a system-time jump), so
@@ -23,6 +23,15 @@
23
23
  * never consulted and adding an entry is byte-invisible to the other
24
24
  * harness's goldens (verified when the native entry landed: Cursor's
25
25
  * seventeen unchanged).
26
+ *
27
+ * The live posture (`stubRegistryFetch({ live: true })`): a live instrument
28
+ * runs the real activity against the REAL `@cursor/sdk`, whose cloud-api
29
+ * client (`Cursor.models.list`, the user-key token exchange) goes through
30
+ * global `fetch` — only its Connect RPC stream rides connect-node. Such a run
31
+ * keeps this document for the registry (pricing and tier stay pinned; nothing
32
+ * a live turn proves lives there) and lets every other URL reach the network
33
+ * through the `fetch` that was global before the stub. The refusal stays the
34
+ * default: a hermetic run that grows a network dependency must still fail.
26
35
  */
27
36
 
28
37
  import { vi } from "vitest";
@@ -76,21 +85,36 @@ export const REGISTRY_DOCUMENT = {
76
85
  ],
77
86
  } as const;
78
87
 
88
+ export interface StubRegistryFetchOptions {
89
+ /**
90
+ * The live posture (header): every URL that is not the registry reaches the
91
+ * network through the pre-stub `fetch`. Default false — refuse, and fail the
92
+ * run.
93
+ */
94
+ readonly live?: boolean;
95
+ }
96
+
79
97
  /**
80
- * Stub `fetch` to answer the registry document and refuse everything else.
81
- * Returns the URLs fetched, for the "no other network" assertion.
98
+ * Stub `fetch` to answer the registry document and — by default — refuse
99
+ * everything else. Returns the URLs fetched, for the "no other network"
100
+ * assertion (and, in the live posture, for the record of what a real turn
101
+ * dialled).
82
102
  */
83
- export function stubRegistryFetch(): { readonly urls: string[]; restore(): void } {
103
+ export function stubRegistryFetch(options: StubRegistryFetchOptions = {}): { readonly urls: string[]; restore(): void } {
84
104
  const urls: string[] = [];
105
+ const networkFetch = globalThis.fetch;
85
106
  vi.stubGlobal(
86
107
  "fetch",
87
- vi.fn(async (input: string | URL | Request) => {
108
+ vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
88
109
  const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
89
110
  urls.push(url);
90
- if (!url.includes("/model-registry")) {
91
- throw new Error(`hermetic run attempted a non-registry network call: ${url}`);
111
+ if (url.includes("/model-registry")) {
112
+ return { ok: true, status: 200, json: async () => REGISTRY_DOCUMENT } as unknown as Response;
113
+ }
114
+ if (options.live) {
115
+ return networkFetch(input, init);
92
116
  }
93
- return { ok: true, status: 200, json: async () => REGISTRY_DOCUMENT } as unknown as Response;
117
+ throw new Error(`hermetic run attempted a non-registry network call: ${url}`);
94
118
  }),
95
119
  );
96
120
  return { urls, restore: () => vi.unstubAllGlobals() };
@@ -2,11 +2,14 @@
2
2
  * The module specifiers a TypeScript file names, read off its syntax tree —
3
3
  * the primitive behind the runner's import fences.
4
4
  *
5
- * Two fences stand on it: `src/harness/` and production `src/shared/` never
5
+ * Three fences stand on it: `src/harness/` and production `src/shared/` never
6
6
  * import `src/activities/` (`harness/__tests__/import-direction.test.ts`,
7
- * the rule the turn runtime is written under), and the Cursor adapter never
7
+ * the rule the turn runtime is written under); the Cursor adapter never
8
8
  * imports `@temporalio/*` (`execute-cursor/__tests__/adapter-is-temporal-
9
- * free.test.ts`, what lets the contract kit run it outside an activity).
9
+ * free.test.ts`, what lets the contract kit run it outside an activity); and
10
+ * `src/harness/transcript/` names no package outside a short allow-list
11
+ * (`harness/__tests__/transcript-is-engine-free.test.ts`, what keeps the one
12
+ * transcript builder engine-neutral).
10
13
  *
11
14
  * A syntax tree, not a regex, so a path quoted in a header comment cannot
12
15
  * trip a fence, and no import form can slip past one: static, type-only,
@@ -84,6 +87,32 @@ export function packageImports(filePath: string, source: string, pkg: string): s
84
87
  return moduleSpecifiers(filePath, source).filter((specifier) => specifier === pkg || specifier.startsWith(`${pkg}/`));
85
88
  }
86
89
 
90
+ /**
91
+ * The packages `source` names — every specifier that is neither a relative
92
+ * path nor a `node:` builtin, reduced to its package (`@scope/name` or
93
+ * `name`, the subpath dropped), deduplicated, in first-appearance order.
94
+ *
95
+ * What an ALLOW-list fence compares against, where `packageImports` serves a
96
+ * deny: a deny-list must be extended for every SDK that does not exist yet,
97
+ * an allow-list refuses it on the day it is written.
98
+ */
99
+ export function packageNames(filePath: string, source: string): string[] {
100
+ const names = new Set<string>();
101
+ for (const specifier of moduleSpecifiers(filePath, source)) {
102
+ if (specifier.startsWith(".") || specifier.startsWith("node:")) continue;
103
+ names.add(packageName(specifier));
104
+ }
105
+ return [...names];
106
+ }
107
+
108
+ /** `@scope/name/sub/path` → `@scope/name`; `name/sub/path` → `name`. */
109
+ function packageName(specifier: string): string {
110
+ const parts = specifier.split("/");
111
+ const [first, second] = parts;
112
+ if (first === undefined) return specifier;
113
+ return first.startsWith("@") && second !== undefined ? `${first}/${second}` : first;
114
+ }
115
+
87
116
  /** Every `.ts` file under `dir`, sorted, skipping the named directories at any depth. */
88
117
  export function typeScriptFilesUnder(dir: string, skipDirectories: ReadonlySet<string>): string[] {
89
118
  const files: string[] = [];
@@ -20,7 +20,7 @@
20
20
  * `adapter.ts`'s graph, or a static client import on the registry's, fails
21
21
  * the first arm; a `boot` that stopped asserting fails the second.
22
22
  *
23
- * The table has two rows since S3 M2b (2026-09-13): the child boots the
23
+ * The table has two rows since #1096 (2026-09-13): the child boots the
24
24
  * Cursor adapter and then the native deep-agent adapter, whose `boot`
25
25
  * registers the deepagents profiles and loads LangChain — after the patch,
26
26
  * because it is the second row. A `boot` that reordered the rows, or a