@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
@@ -0,0 +1,755 @@
1
+ /**
2
+ * TranscriptBuilder — folds `TranscriptEvent`s into the transcript half of
3
+ * the AgentExecutionStatus proto it is handed: messages, tool-call rows and
4
+ * their approval status, sub-agent rows, todos, artifacts, write-backs.
5
+ *
6
+ * The one transcript builder, for every harness (#1097). It
7
+ * was the native harness's `V3StatusBuilder` (the one builder there since
8
+ * #1096 retired the v2 `StatusBuilder`), promoted here on 2026-09-14
9
+ * unchanged and then cut engine-neutral one rule per commit:
10
+ * it reads no engine envelope, decides nothing about approval,
11
+ * and knows no engine's tool names or namespace grammar — every engine
12
+ * fact reaches it as a translator's output, on one union. The Cursor
13
+ * harness feeds it through a translator; the runtime constructs it once
14
+ * per turn and hands it to every adapter as `TurnSink.transcript`, so
15
+ * a transcript row has exactly one way into the status — a source-walking
16
+ * fence, `harness/__tests__/transcript-writers.test.ts`, refuses a second.
17
+ *
18
+ * ONE set of handlers over ONE scope shape: every handler takes
19
+ * the scope's {@link Transcript} — the root's over `status.messages`, or a
20
+ * sub-agent's over its row's — from `TranscriptState`, so a sub-agent's
21
+ * transcript is folded by exactly the rules the root's is. Until #1097 the
22
+ * sub-agents' half was a second module (`SubAgentTracker`) with its own copy
23
+ * of every handler, differing only in which array it pushed into — the
24
+ * memo's third copy of the folding rule, and the one whose namespace filter
25
+ * differed from the root's by one segment (found in #1096: the root
26
+ * filed text under `model_request:<uuid>` and looked the tool's parent up
27
+ * under `""`, so every native tool row sat on an empty message after its
28
+ * text). With scope a single value per transcript, that class of miss
29
+ * cannot exist.
30
+ *
31
+ * The rules this builder owns, each with an arm in
32
+ * `harness/transcript/__tests__/builder.test.ts` and the harness whose rule
33
+ * it generalises in parentheses:
34
+ *
35
+ * - A row per `callId`, indexed over the status as handed and over what
36
+ * {@link seed} appends (a reinvocation's prior rows, indexed by the same
37
+ * routine, so a builder born before the seed is no different from one
38
+ * born after it), reconciled in place on a re-emitted start — an UNSETTLED row becomes
39
+ * RUNNING (a WAITING row on native's durable-checkpoint resume; an
40
+ * INTERRUPTED row on a recovery replay, the enum's supersede rule) and
41
+ * takes the args and preview it lacked — never duplicated. Every row
42
+ * with args carries `argsPreview`, elided and redacted, salient fields
43
+ * verbatim. A finish or an error is an upsert: status
44
+ * monotonic, `completedAt` once, a non-empty result or message
45
+ * overwrites, an empty one never clears; a re-emit that
46
+ * changes nothing forces no persist; a fact the harness
47
+ * observed earlier and delivers later stamps its own instant
48
+ * (`observedAt`); an output chunk for a settled row is stale
49
+ * and dropped — the completion's result is the whole output.
50
+ * - The AI-message boundary (native's own rule, made correct by
51
+ * scoping): a tool row attaches to the scope's current AI message — the
52
+ * latest with text, the seed's last AI message over a seeded transcript
53
+ * — and to a new empty one only when the scope has none.
54
+ * - A THINKING row per `runId`, a text message per `runId`; a
55
+ * new run closes the previous message's streaming flag, and a run's
56
+ * finish closes both its text and its thinking.
57
+ * - A sub-agent row per `subAgentId`, opened by `sub_agent_started`,
58
+ * closed COMPLETED/FAILED by `sub_agent_finished/failed` with its
59
+ * transcript's streaming flags cleared; a known id is never re-opened.
60
+ * CANCELLED is not this builder's: `shared/subagent-rows.ts`
61
+ * `cancelInProgressSubAgentProtos` is the one home of that transition.
62
+ * - `approval_proposed` is the one place a row is ever WAITING: a known
63
+ * row REOPENS — WAITING, the outcome fields cleared,
64
+ * `approvalRequestedAt` stamped once, the proposal's args and preview
65
+ * taken when carried (Cursor's `markWaitingApproval` + `applyGateInput`);
66
+ * an unknown call gets a WAITING row on the scope's current AI message,
67
+ * the text that proposed it (native's post-stream seed, before #1097 a new
68
+ * empty message of its own). Either way {@link awaitingApproval} is set.
69
+ * - `system_note` is a SYSTEM message in the scope, in the harness's
70
+ * voice; it hosts no rows and moves no boundary.
71
+ * - A completed `ToolKind.TODO` call in the ROOT scope is projected into
72
+ * `status.todos` through the shared `applyTodoUpdate`, a full replace
73
+ * unless the call's args say `merge: true` (the row stays — the
74
+ * clients filter it); a sub-agent's is not.
75
+ * - `finalize()` clears every streaming flag in every scope.
76
+ * - Artifacts upsert by `sandboxPath`/`contentHash`, write-backs by
77
+ * `workspaceEntryName`: the inline publisher and the write-back
78
+ * coordinator register theirs here, each through the one-method view it
79
+ * declares for itself (`ArtifactSink`, `WriteBackSink`), which this class
80
+ * satisfies by shape.
81
+ * - {@link dirty} is set by every discrete change above and by nothing a
82
+ * token delta does; the adapter's loop reads it and clears it with
83
+ * {@link markPersisted} as it requests the persist.
84
+ *
85
+ * What this builder deliberately does NOT write, and who does (the adapter
86
+ * contract's field ownership, `harness/types.ts` `TurnSink`): the phase,
87
+ * `startedAt`, `streamingUsage` and the epilogue's plan artifact are the
88
+ * turn runtime's (the plan artifact supersedes by its own name rule, which
89
+ * is the plan's knowledge, not the transcript's). A row left
90
+ * WAITING_APPROVAL is reported as the {@link awaitingApproval} fact and the
91
+ * caller decides what that means for the turn — a fact only
92
+ * `approval_proposed` produces, because a call that has
93
+ * STARTED is never parked at creation. Usage never passes
94
+ * through here: the native loop reads it off the wire (`usageOf`) and
95
+ * prices it into the sink, the Cursor loop reads its own from the SDK.
96
+ */
97
+
98
+ import { create, type JsonObject } from "@bufbuild/protobuf";
99
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
100
+ import {
101
+ AgentMessageSchema,
102
+ ToolCallSchema,
103
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
104
+ import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
105
+ import { SubAgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
106
+ import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
107
+ import type { WorkspaceWriteBack } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/writeback_pb";
108
+ import {
109
+ MessageType,
110
+ SubAgentStatus,
111
+ ToolCallStatus,
112
+ ToolCallStreamingSource,
113
+ ToolKind,
114
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
115
+ import { POLICY_ENGINE_VERSION, toProtoPolicySource } from "../../shared/approval-policy.js";
116
+ import { SALIENT_ARG_FIELDS, buildElidedArgsPreview } from "../../shared/args-preview.js";
117
+ import { classifyTool } from "../../shared/tool-kind.js";
118
+ import { applyTodoUpdate } from "../../shared/todos.js";
119
+ import { utcTimestamp } from "../../shared/status.js";
120
+ import { TranscriptState, type Transcript, type TranscriptSeed } from "./state.js";
121
+ import type {
122
+ ApprovalProposedEvent,
123
+ SubAgentFailedEvent,
124
+ SubAgentFinishedEvent,
125
+ SubAgentStartedEvent,
126
+ ToolStartedEvent,
127
+ TranscriptEvent,
128
+ } from "./events.js";
129
+
130
+ export class TranscriptBuilder {
131
+ readonly executionId: string;
132
+ private readonly state: TranscriptState;
133
+ private _dirty = false;
134
+ private _awaitingApproval = false;
135
+
136
+ /**
137
+ * Builds INTO `status`, by reference: its `messages`, `subAgentExecutions`
138
+ * and every sub-agent row's `messages` are the arrays indexed and pushed
139
+ * into, never replaced, so a caller that wraps the same status (the
140
+ * runtime's chokepoint, a settle amending rows by identity) keeps seeing
141
+ * every row. Rows already on the status are indexed here; rows a
142
+ * reinvocation carries over arrive later through {@link seed}, indexed by
143
+ * the same routine, so a re-driven event reconciles onto them either way.
144
+ *
145
+ * The builder hands nothing back. A reader holds the status it built the
146
+ * builder over — `TurnSink.status` in production, the test's own in a
147
+ * test — so the transcript has one read handle and one write handle, and
148
+ * no second spelling of either.
149
+ */
150
+ constructor(executionId: string, status: AgentExecutionStatus) {
151
+ this.executionId = executionId;
152
+ this.state = new TranscriptState(status);
153
+ }
154
+
155
+ /**
156
+ * Append a reinvocation's persisted transcript — messages, sub-agent
157
+ * rows, artifacts, write-backs, todos — and index it, so the turn that
158
+ * follows APPENDS onto prior history and a re-issued call reconciles onto
159
+ * its seeded row instead of duplicating it. The runtime calls this once,
160
+ * in its reinvocation phase, before any harness runs; the reasons every
161
+ * collection is seeded are that phase's (`harness/turn-context.ts`
162
+ * `seedFromPersistedStatus`). Seeded rows are last turn's facts: this sets
163
+ * neither {@link dirty} nor {@link awaitingApproval}.
164
+ */
165
+ seed(persisted: TranscriptSeed): void {
166
+ this.state.seed(persisted);
167
+ }
168
+
169
+ /**
170
+ * True once a row this turn was left WAITING_APPROVAL: the engine has
171
+ * interrupted (or the harness's boundary parked the call) and the turn
172
+ * should end awaiting a decision. A fact about the transcript, not a phase
173
+ * — the caller owns the phase. No `tool_started` sets it (a started call
174
+ * is running); `approval_proposed` does.
175
+ */
176
+ get awaitingApproval(): boolean {
177
+ return this._awaitingApproval;
178
+ }
179
+
180
+ /**
181
+ * True once a discrete, user-visible change has landed since the last
182
+ * persist — a row started or settled, a sub-agent opened or closed, an
183
+ * artifact or write-back registered, a note appended; never a token
184
+ * delta, which rides the streaming cadence. The one fact the adapter's
185
+ * persist decision reads (`shared/persist-decision.ts`); the adapter
186
+ * clears it with {@link markPersisted} as it requests the write, so a
187
+ * change that lands after the write started dirties the next one.
188
+ */
189
+ get dirty(): boolean {
190
+ return this._dirty;
191
+ }
192
+
193
+ markPersisted(): void {
194
+ this._dirty = false;
195
+ }
196
+
197
+ /** Fold one canonical event into the transcript. Never throws: a handler's error is logged and the stream goes on. */
198
+ apply(event: TranscriptEvent): void {
199
+ try {
200
+ switch (event.kind) {
201
+ case "sub_agent_started":
202
+ this.openSubAgent(event);
203
+ return;
204
+ case "sub_agent_finished":
205
+ this.closeSubAgent(event);
206
+ return;
207
+ case "sub_agent_failed":
208
+ this.failSubAgent(event);
209
+ return;
210
+ default:
211
+ break;
212
+ }
213
+
214
+ const scope = this.state.scope(event.subAgentId);
215
+ if (!scope) {
216
+ throw new Error(`event for unknown sub-agent ${event.subAgentId}`);
217
+ }
218
+
219
+ switch (event.kind) {
220
+ case "message_start":
221
+ this.handleMessageStart(scope, event.runId);
222
+ break;
223
+ case "text_delta":
224
+ this.appendTextContent(scope, event.runId, event.text);
225
+ break;
226
+ case "reasoning_delta":
227
+ this.appendThinkingContent(scope, event.runId, event.text);
228
+ break;
229
+ case "message_finish":
230
+ this.handleMessageFinish(scope, event.runId);
231
+ break;
232
+ case "tool_started":
233
+ this.handleToolStarted(scope, event);
234
+ break;
235
+ case "tool_arg_delta":
236
+ this.handleToolArgDelta(scope, event.callId, event.argsChunk);
237
+ break;
238
+ case "tool_output_delta":
239
+ this.handleToolOutputDelta(scope, event.callId, event.delta);
240
+ break;
241
+ case "tool_finished":
242
+ this.handleToolFinished(scope, event.callId, event.result, event.observedAt);
243
+ break;
244
+ case "tool_error":
245
+ this.handleToolError(scope, event.callId, event.message, event.observedAt);
246
+ break;
247
+ case "approval_proposed":
248
+ this.handleApprovalProposed(scope, event);
249
+ break;
250
+ case "system_note":
251
+ this.appendSystemNote(scope, event.text);
252
+ break;
253
+ default: {
254
+ const exhaustive: never = event;
255
+ throw new Error(`unknown transcript event ${String((exhaustive as { kind: string }).kind)}`);
256
+ }
257
+ }
258
+ } catch (err) {
259
+ console.error(
260
+ `[TranscriptBuilder] Event handler error: execution=${this.executionId} kind=${event.kind}: ${err}`,
261
+ );
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Every streaming flag off, in every scope — messages and rows. What a
267
+ * turn owes the transcript when its stream ends, however it ended: a
268
+ * stopped turn's in-flight message must not persist as still streaming.
269
+ * The sub-agent rows' own CANCELLED transition is the caller's, through
270
+ * the shared `cancelInProgressSubAgentProtos`.
271
+ */
272
+ finalize(): void {
273
+ for (const transcript of this.state.transcripts()) finalizeStreaming(transcript);
274
+ }
275
+
276
+ // ── Artifact & WriteBack ───────────────────────────────────────────
277
+
278
+ addArtifact(artifact: ExecutionArtifact): void {
279
+ const artifacts = this.state.proto.artifacts;
280
+ const idx = artifacts.findIndex(a => a.sandboxPath === artifact.sandboxPath);
281
+
282
+ if (idx >= 0) {
283
+ if (artifacts[idx].contentHash !== artifact.contentHash) {
284
+ artifacts[idx] = artifact;
285
+ this._dirty = true;
286
+ }
287
+ return;
288
+ }
289
+
290
+ artifacts.push(artifact);
291
+ this._dirty = true;
292
+ }
293
+
294
+ addWriteBack(wb: WorkspaceWriteBack): void {
295
+ const backs = this.state.proto.workspaceWriteBacks;
296
+ const idx = backs.findIndex(b => b.workspaceEntryName === wb.workspaceEntryName);
297
+
298
+ if (idx >= 0) {
299
+ backs[idx] = wb;
300
+ } else {
301
+ backs.push(wb);
302
+ }
303
+ this._dirty = true;
304
+ }
305
+
306
+ // ── Sub-Agent Rows ─────────────────────────────────────────────────
307
+
308
+ private openSubAgent(event: SubAgentStartedEvent): void {
309
+ // A seeded row re-announced by a replayed engine (the memory-checkpointer
310
+ // re-drives the whole graph): the row and its transcript already exist.
311
+ if (this.state.subAgent(event.subAgentId)) return;
312
+
313
+ const row = create(SubAgentExecutionSchema, {
314
+ id: event.subAgentId,
315
+ name: event.name,
316
+ subject: event.subject,
317
+ input: event.input,
318
+ status: SubAgentStatus.SUB_AGENT_IN_PROGRESS,
319
+ startedAt: utcTimestamp(),
320
+ });
321
+ this.state.openSubAgent(row);
322
+ this._dirty = true;
323
+ }
324
+
325
+ private closeSubAgent(event: SubAgentFinishedEvent): void {
326
+ const scope = this.state.subAgent(event.subAgentId);
327
+ if (!scope) return;
328
+ scope.row.status = SubAgentStatus.SUB_AGENT_COMPLETED;
329
+ scope.row.completedAt = utcTimestamp();
330
+ if (event.output !== undefined) scope.row.output = event.output;
331
+ finalizeStreaming(scope.transcript);
332
+ this._dirty = true;
333
+ }
334
+
335
+ private failSubAgent(event: SubAgentFailedEvent): void {
336
+ const scope = this.state.subAgent(event.subAgentId);
337
+ if (!scope) return;
338
+ scope.row.status = SubAgentStatus.SUB_AGENT_FAILED;
339
+ scope.row.completedAt = utcTimestamp();
340
+ scope.row.error = event.error;
341
+ finalizeStreaming(scope.transcript);
342
+ this._dirty = true;
343
+ }
344
+
345
+ // ── Message Handlers ───────────────────────────────────────────────
346
+
347
+ private handleMessageStart(scope: Transcript, runId: string): void {
348
+ // Record the run for the message boundary but do NOT create the AI
349
+ // message yet: it is created lazily on the first text_delta, so a
350
+ // THINKING row that streams first sits before the text it precedes.
351
+ if (scope.lastRunId && scope.lastRunId !== runId && scope.currentAiMessage) {
352
+ scope.currentAiMessage.isStreaming = false;
353
+ }
354
+ scope.lastRunId = runId;
355
+ }
356
+
357
+ private handleMessageFinish(scope: Transcript, runId: string): void {
358
+ // A finished run's text AND its thinking are finished (Cursor closes
359
+ // both when a tool call ends the segment). Before #1097 only the
360
+ // text closed here and the THINKING row spun until `finalize()` — a live
361
+ // spinner on a block the model had left, on both harnesses.
362
+ const msg = scope.messagesByRun.get(runId);
363
+ if (msg) {
364
+ msg.isStreaming = false;
365
+ }
366
+ const thinking = scope.messagesByRun.get(thinkingKeyOf(runId));
367
+ if (thinking) {
368
+ thinking.isStreaming = false;
369
+ }
370
+ }
371
+
372
+ private appendTextContent(scope: Transcript, runId: string, text: string): void {
373
+ const msg = this.ensureAiMessage(scope, runId);
374
+ msg.content += text;
375
+ msg.isStreaming = true;
376
+ }
377
+
378
+ private appendThinkingContent(scope: Transcript, runId: string, text: string): void {
379
+ // One THINKING row per run, keyed apart from the run's text so
380
+ // the two never share a message.
381
+ const thinkingKey = thinkingKeyOf(runId);
382
+ const existingMsg = scope.messagesByRun.get(thinkingKey);
383
+ if (existingMsg && existingMsg.type === MessageType.MESSAGE_THINKING) {
384
+ existingMsg.content += text;
385
+ existingMsg.isStreaming = true;
386
+ return;
387
+ }
388
+
389
+ const msg = create(AgentMessageSchema, {
390
+ type: MessageType.MESSAGE_THINKING,
391
+ content: text,
392
+ timestamp: utcTimestamp(),
393
+ isStreaming: true,
394
+ });
395
+ scope.messages.push(msg);
396
+ scope.messagesByRun.set(thinkingKey, msg);
397
+ }
398
+
399
+ // ── Tool Handlers ──────────────────────────────────────────────────
400
+
401
+ private handleToolStarted(scope: Transcript, event: ToolStartedEvent): void {
402
+ const { callId, name, input } = event;
403
+ // A re-emitted start reconciles onto the existing row — never a duplicate,
404
+ // never a second gate. Keyed by the provider's tool_call_id, so this is an
405
+ // exact match (no name heuristics). Two shapes reach here: the durable
406
+ // checkpoint re-emitting a seeded WAITING call now that approval is granted
407
+ // (native's resume; the runtime's `seedFromPersistedStatus`), and a
408
+ // recovery replaying a call the server had marked INTERRUPTED when the
409
+ // execution terminalized with it in flight (the enum's supersede rule; the
410
+ // Cursor accumulator's merge). Either way an UNSETTLED row becomes RUNNING
411
+ // (before #1097 only WAITING flipped); a settled one keeps
412
+ // its outcome. Args it lacked are filled and previewed, once.
413
+ const existing = scope.toolCalls.get(callId);
414
+ if (existing) {
415
+ if (!isSettled(existing.status)) {
416
+ existing.status = ToolCallStatus.TOOL_CALL_RUNNING;
417
+ }
418
+ if (Object.keys(input).length > 0) {
419
+ if (!existing.args) existing.args = input as JsonObject;
420
+ if (!existing.argsPreview) stampArgsPreview(existing, input);
421
+ }
422
+ this._dirty = true;
423
+ return;
424
+ }
425
+
426
+ // The message boundary: the row joins the message whose text
427
+ // proposed it — the scope's current AI message — and gets an empty one
428
+ // only when the scope has no message yet.
429
+ const parentMsg = scope.currentAiMessage ?? this.ensureAiMessageForToolCall(scope);
430
+
431
+ // A started call is RUNNING, gated or not: a gated call that has started
432
+ // runs until the harness's boundary parks it through `approval_proposed`
433
+ // (Cursor), and on native a held call never starts at all — the engine
434
+ // interrupts before the tool runs and the post-stream seed proposes it.
435
+ // The builder writes the gate's word it is told and decides nothing
436
+ // about approval.
437
+ const tc = create(ToolCallSchema, {
438
+ id: callId,
439
+ name,
440
+ status: ToolCallStatus.TOOL_CALL_RUNNING,
441
+ startedAt: utcTimestamp(),
442
+ });
443
+
444
+ if (Object.keys(input).length > 0) {
445
+ tc.args = input as JsonObject;
446
+ stampArgsPreview(tc, input);
447
+ }
448
+
449
+ if (event.mcpServerSlug) {
450
+ tc.mcpServerSlug = event.mcpServerSlug;
451
+ }
452
+
453
+ // Classify after mcpServerSlug is set so MCP tools resolve correctly.
454
+ tc.toolKind = classifyTool(tc.name, tc.mcpServerSlug);
455
+
456
+ // Authorization provenance — which policy layer governs the call — stamped
457
+ // in the same spot as tool_kind, for every observed call the translator
458
+ // could attribute (gated or auto-approved), so the persisted record is
459
+ // auditable and the UI can explain the gate. Absent provenance leaves the
460
+ // field at UNSPECIFIED, like an unclassified tool_kind. A gated call is
461
+ // re-seeded on reinvocation (the runtime's wide seed) with the same source
462
+ // carried through the interrupt.
463
+ if (event.provenance !== undefined) {
464
+ tc.approvalPolicySource = toProtoPolicySource(event.provenance);
465
+ tc.policyEngineVersion = POLICY_ENGINE_VERSION;
466
+ }
467
+
468
+ // The gate's word: the row says it is gated and what the card will ask;
469
+ // `approvalRequestedAt` is stamped when the boundary actually parks it
470
+ // (`approval_proposed`, or a gated row's `tool_error`), never here.
471
+ if (event.gate) {
472
+ tc.requiresApproval = true;
473
+ tc.approvalMessage = event.gate.message;
474
+ }
475
+
476
+ parentMsg.toolCalls.push(tc);
477
+ scope.toolCalls.set(callId, tc);
478
+
479
+ this._dirty = true;
480
+ }
481
+
482
+ private handleToolFinished(scope: Transcript, callId: string, result: string, observedAt?: string): void {
483
+ const tc = scope.toolCalls.get(callId);
484
+ if (!tc) return;
485
+
486
+ // An UPSERT, not an overwrite (Cursor's `mergeToolCallEvent`):
487
+ // the status advances monotonically — a settled row never regresses;
488
+ // `completedAt` is stamped once; only a non-empty result overwrites, so a
489
+ // completion that carries none never wipes the output an earlier one
490
+ // did. Two completions per call is the Cursor shape after stigmer#1053
491
+ // (the SDK's timing delta precedes the stream's completion); on native
492
+ // there is one, and the rules read as the plain write they replace.
493
+ //
494
+ // Store the faithful result; bounding the gRPC payload is owned solely by
495
+ // the persist chokepoint (offload + enforce in status.ts/status-offload.ts).
496
+ // Truncating here would corrupt binary content (e.g. a screenshot's base64)
497
+ // before offload can lift it into a renderable ToolCallOutputRef.
498
+ //
499
+ // A completion that changes nothing — a settled row re-completed with no
500
+ // new result — forces no persist (Cursor's "a redundant
501
+ // terminal re-emit is noise, not a state change"). On Cursor every call
502
+ // completes twice (the delta, then the stream), and the second carries the
503
+ // result, so the flush the UI needs still happens exactly once.
504
+ const wasSettled = isSettled(tc.status);
505
+ const resultChanged = !!result && result !== tc.result;
506
+ if (!wasSettled) tc.status = ToolCallStatus.TOOL_CALL_COMPLETED;
507
+ // The instant the harness OBSERVED the completion when it delivers it
508
+ // later (Cursor's queued delta); otherwise now.
509
+ if (!tc.completedAt) tc.completedAt = observedAt ?? utcTimestamp();
510
+ if (resultChanged) tc.result = result;
511
+ stopStreaming(tc);
512
+ scope.argBuffers.delete(callId);
513
+ if (wasSettled && !resultChanged) return;
514
+
515
+ // Project a completed to-do write into status.todos. deepagents' write_todos
516
+ // runs its state-mutating Command when the tool node COMPLETES, so we mirror
517
+ // it here (not at tool-start) — a call cancelled before finishing correctly
518
+ // projects nothing. Keyed on the harness-agnostic ToolKind.TODO (stamped at
519
+ // tool-start) and fed by the same shared mapper every harness uses; the
520
+ // call's own `merge` flag decides replace-or-merge (Cursor's `updateTodos`
521
+ // sets it; deepagents never does, so native stays a full replace);
522
+ // a sub-agent's list is its own, never the execution's. The tool
523
+ // call itself stays in messages (the client filters ToolKind.TODO from the
524
+ // thread).
525
+ if (scope === this.state.root && tc.toolKind === ToolKind.TODO) {
526
+ applyTodoUpdate(this.state.proto.todos, tc.args?.todos, { merge: tc.args?.merge === true });
527
+ }
528
+
529
+ this._dirty = true;
530
+ }
531
+
532
+ private handleToolError(scope: Transcript, callId: string, message: string, observedAt?: string): void {
533
+ const tc = scope.toolCalls.get(callId);
534
+ if (!tc) return;
535
+
536
+ // The same upsert as a finish. A gated row that fails is the
537
+ // Cursor boundary's shape — the hook denied the call and the stream
538
+ // reports `error`; the boundary parks it after the stream — so the
539
+ // moment approval became due is stamped here if nothing stamped it yet.
540
+ const wasSettled = isSettled(tc.status);
541
+ const messageChanged = !!message && message !== tc.error;
542
+ const at = observedAt ?? utcTimestamp();
543
+ if (!wasSettled) tc.status = ToolCallStatus.TOOL_CALL_FAILED;
544
+ if (!tc.completedAt) tc.completedAt = at;
545
+ if (messageChanged) tc.error = message;
546
+ if (tc.requiresApproval && !tc.approvalRequestedAt) tc.approvalRequestedAt = at;
547
+ stopStreaming(tc);
548
+ scope.argBuffers.delete(callId);
549
+
550
+ // The same no-change rule as a finish.
551
+ if (wasSettled && !messageChanged) return;
552
+ this._dirty = true;
553
+ }
554
+
555
+ private handleToolArgDelta(scope: Transcript, callId: string, argsChunk: string): void {
556
+ const tc = scope.toolCalls.get(callId);
557
+ if (!tc) return;
558
+
559
+ const buffer = (scope.argBuffers.get(callId) ?? "") + argsChunk;
560
+ scope.argBuffers.set(callId, buffer);
561
+
562
+ try {
563
+ tc.args = JSON.parse(buffer) as JsonObject;
564
+ } catch {
565
+ // Partial JSON — will parse on next chunk or tool-finished
566
+ }
567
+ }
568
+
569
+ private handleToolOutputDelta(scope: Transcript, callId: string, delta: string): void {
570
+ const tc = scope.toolCalls.get(callId);
571
+ if (!tc) return;
572
+ // A chunk for a row that has already settled is stale: the completion's
573
+ // result IS the whole output, and appending would double it (Cursor's
574
+ // delta channel and stream can deliver the output's
575
+ // chunks and the completion in one loop window, completion first).
576
+ if (isSettled(tc.status)) return;
577
+ // Output arriving live: the row streams its OUTPUT (Cursor's
578
+ // enricher), so the client can show the partial result as it grows;
579
+ // the finish, or `finalize()`, closes it.
580
+ tc.result = (tc.result ?? "") + delta;
581
+ tc.isStreaming = true;
582
+ tc.streamingSource = ToolCallStreamingSource.OUTPUT;
583
+ }
584
+
585
+ // ── Post-Stream Facts ──────────────────────────────────────────────
586
+
587
+ private handleApprovalProposed(scope: Transcript, event: ApprovalProposedEvent): void {
588
+ const now = utcTimestamp();
589
+ const existing = scope.toolCalls.get(event.callId);
590
+ if (existing) {
591
+ // Reopen (Cursor's `markWaitingApproval`): the boundary is re-proposing
592
+ // a call the stream already showed — an errored built-in the hook
593
+ // denied — so the outcome the stream wrote is not the outcome.
594
+ existing.status = ToolCallStatus.TOOL_CALL_WAITING_APPROVAL;
595
+ existing.requiresApproval = true;
596
+ existing.approvalMessage = event.message;
597
+ if (!existing.approvalRequestedAt) existing.approvalRequestedAt = now;
598
+ existing.completedAt = "";
599
+ existing.error = "";
600
+ existing.result = "";
601
+ // The proposal's args are the authoritative proposed change when the
602
+ // harness carries them (Cursor's `applyGateInput`: the hook's captured
603
+ // input outranks what the stream carried before the first-denial cancel),
604
+ // and they re-derive the preview. Absent, the row
605
+ // keeps what it had.
606
+ if (event.args && Object.keys(event.args).length > 0) {
607
+ existing.args = event.args as JsonObject;
608
+ stampArgsPreview(existing, event.args);
609
+ }
610
+ if (event.contentDigest) existing.approvalContentDigest = event.contentDigest;
611
+ stopStreaming(existing);
612
+ this._awaitingApproval = true;
613
+ this._dirty = true;
614
+ return;
615
+ }
616
+
617
+ // A call the stream never showed (native: the engine interrupted before
618
+ // the tool started). Its row lands on the text that proposed it, like
619
+ // any other row (before #1097 the seed pushed a new empty message).
620
+ const parentMsg = scope.currentAiMessage ?? this.ensureAiMessageForToolCall(scope);
621
+ const tc = create(ToolCallSchema, {
622
+ id: event.callId,
623
+ name: event.name,
624
+ status: ToolCallStatus.TOOL_CALL_WAITING_APPROVAL,
625
+ requiresApproval: true,
626
+ approvalMessage: event.message,
627
+ approvalRequestedAt: now,
628
+ startedAt: now,
629
+ });
630
+ if (event.mcpServerSlug) tc.mcpServerSlug = event.mcpServerSlug;
631
+ tc.toolKind = classifyTool(tc.name, tc.mcpServerSlug);
632
+ if (event.provenance !== undefined) {
633
+ tc.approvalPolicySource = toProtoPolicySource(event.provenance);
634
+ tc.policyEngineVersion = POLICY_ENGINE_VERSION;
635
+ }
636
+ if (event.args && Object.keys(event.args).length > 0) {
637
+ tc.args = event.args as JsonObject;
638
+ stampArgsPreview(tc, event.args);
639
+ }
640
+ if (event.contentDigest) tc.approvalContentDigest = event.contentDigest;
641
+
642
+ parentMsg.toolCalls.push(tc);
643
+ scope.toolCalls.set(event.callId, tc);
644
+ this._awaitingApproval = true;
645
+ this._dirty = true;
646
+ }
647
+
648
+ private appendSystemNote(scope: Transcript, text: string): void {
649
+ scope.messages.push(create(AgentMessageSchema, {
650
+ type: MessageType.MESSAGE_SYSTEM,
651
+ content: text,
652
+ timestamp: utcTimestamp(),
653
+ }));
654
+ this._dirty = true;
655
+ }
656
+
657
+ // ── Content Helpers ───────────────────────────────────────────────
658
+
659
+ /** The AI message a run's text streams into, created on its first token; a new run closes the previous message's flag. */
660
+ private ensureAiMessage(scope: Transcript, runId: string): AgentMessage {
661
+ const existingByRun = scope.messagesByRun.get(runId);
662
+ if (existingByRun) return existingByRun;
663
+
664
+ if (scope.lastRunId && scope.lastRunId !== runId && scope.currentAiMessage) {
665
+ scope.currentAiMessage.isStreaming = false;
666
+ }
667
+
668
+ const msg = create(AgentMessageSchema, {
669
+ type: MessageType.MESSAGE_AI,
670
+ content: "",
671
+ timestamp: utcTimestamp(),
672
+ isStreaming: true,
673
+ });
674
+
675
+ scope.messages.push(msg);
676
+ scope.messagesByRun.set(runId, msg);
677
+ scope.currentAiMessage = msg;
678
+ scope.lastRunId = runId;
679
+
680
+ return msg;
681
+ }
682
+
683
+ /** The message a tool row lands on when the scope has no AI message yet: the latest run's, else a new empty one. */
684
+ private ensureAiMessageForToolCall(scope: Transcript): AgentMessage {
685
+ if (scope.lastRunId) {
686
+ const existing = scope.messagesByRun.get(scope.lastRunId);
687
+ if (existing) {
688
+ scope.currentAiMessage = existing;
689
+ return existing;
690
+ }
691
+ }
692
+
693
+ const msg = create(AgentMessageSchema, {
694
+ type: MessageType.MESSAGE_AI,
695
+ content: "",
696
+ timestamp: utcTimestamp(),
697
+ isStreaming: false,
698
+ });
699
+
700
+ scope.messages.push(msg);
701
+ scope.currentAiMessage = msg;
702
+
703
+ return msg;
704
+ }
705
+ }
706
+
707
+ /** The `messagesByRun` key of a run's THINKING row — apart from the run's text, so the two never share a message. */
708
+ function thinkingKeyOf(runId: string): string {
709
+ return `thinking:${runId}`;
710
+ }
711
+
712
+ /** Every streaming flag off in one transcript — messages and rows. */
713
+ function finalizeStreaming(transcript: Transcript): void {
714
+ for (const message of transcript.messages) {
715
+ message.isStreaming = false;
716
+ for (const tc of message.toolCalls) stopStreaming(tc);
717
+ }
718
+ }
719
+
720
+ /**
721
+ * The row's `args_preview`, on EVERY row with args (since #1097):
722
+ * `message.proto` promises it sanitized, redacted, at creation, for inline
723
+ * visibility, and the one builder that keeps it small and always valid JSON
724
+ * is `buildElidedArgsPreview` over the platform's salient fields. Until then
725
+ * native stamped a whole-string-truncating preview on gated rows only and
726
+ * Cursor stamped `JSON.stringify(args)` unredacted. An empty preview (a
727
+ * cycle in the args) leaves the field unset rather than failing the row.
728
+ */
729
+ function stampArgsPreview(tc: ToolCall, args: Record<string, unknown>): void {
730
+ const preview = buildElidedArgsPreview(args, SALIENT_ARG_FIELDS);
731
+ if (preview) tc.argsPreview = preview;
732
+ }
733
+
734
+ /** A row no longer streaming: the flag off and the source cleared, so a stale OUTPUT marker never outlives the stream. */
735
+ function stopStreaming(tc: ToolCall): void {
736
+ if (!tc.isStreaming) return;
737
+ tc.isStreaming = false;
738
+ tc.streamingSource = ToolCallStreamingSource.UNSPECIFIED;
739
+ }
740
+
741
+ /**
742
+ * A row that has finished, however it finished — the statuses a later event
743
+ * must not move backwards (Cursor's `isTerminalToolStatus`). INTERRUPTED is
744
+ * deliberately NOT here: it is server-authored when an execution
745
+ * terminalized with the call in flight, and a recovery-replayed event must
746
+ * be able to advance the row to its true outcome (the enum's recovery
747
+ * supersede rule; `shared/tool-row.ts` explains the two sets).
748
+ */
749
+ function isSettled(status: ToolCallStatus): boolean {
750
+ return (
751
+ status === ToolCallStatus.TOOL_CALL_COMPLETED ||
752
+ status === ToolCallStatus.TOOL_CALL_FAILED ||
753
+ status === ToolCallStatus.TOOL_CALL_SKIPPED
754
+ );
755
+ }