@humwick/server 0.0.0-stage → 0.130.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2508) hide show
  1. package/CLA.md +66 -0
  2. package/CONTRIBUTING.md +144 -0
  3. package/LICENSE +167 -0
  4. package/README.md +511 -2
  5. package/SECURITY.md +67 -0
  6. package/THIRD_PARTY_NOTICES.md +144 -0
  7. package/bundled/prompts/README.md +10 -0
  8. package/bundled/prompts/context.advisory.compact.md +1 -0
  9. package/bundled/prompts/context.advisory.warn.md +1 -0
  10. package/bundled/prompts/continuation.envelope.md +18 -0
  11. package/bundled/prompts/manifest.json +838 -0
  12. package/bundled/prompts/memory.capture.boundary.post.md +1 -0
  13. package/bundled/prompts/memory.capture.boundary.pre.md +1 -0
  14. package/bundled/prompts/memory.charter.md +3 -0
  15. package/bundled/prompts/memory.native-off.md +0 -0
  16. package/bundled/prompts/notify.agent.exited.md +1 -0
  17. package/bundled/prompts/notify.agent.never-ready.md +1 -0
  18. package/bundled/prompts/notify.agent.ready.md +1 -0
  19. package/bundled/prompts/notify.automation.budget-warning.md +1 -0
  20. package/bundled/prompts/notify.land.failure.md +1 -0
  21. package/bundled/prompts/notify.land.steward-taken.coordinator.md +1 -0
  22. package/bundled/prompts/notify.land.steward-taken.md +1 -0
  23. package/bundled/prompts/notify.land.success.md +1 -0
  24. package/bundled/prompts/notify.routing.policy-changed.md +2 -0
  25. package/bundled/prompts/notify.spawn.failed.md +1 -0
  26. package/bundled/prompts/notify.spawn.queued.md +1 -0
  27. package/bundled/prompts/notify.spawn.recovered.md +1 -0
  28. package/bundled/prompts/notify.spawn.unconfirmed.md +1 -0
  29. package/bundled/prompts/profile.coordinator.charter.md +17 -0
  30. package/bundled/prompts/profile.debugger.system.md +12 -0
  31. package/bundled/prompts/profile.device-qa.system.md +10 -0
  32. package/bundled/prompts/profile.image-gen.append.0.md +2 -0
  33. package/bundled/prompts/profile.image-gen.system.md +4 -0
  34. package/bundled/prompts/profile.ops-assistant.brief.md +4 -0
  35. package/bundled/prompts/profile.probe.system.md +5 -0
  36. package/bundled/prompts/profile.qa.system.md +10 -0
  37. package/bundled/prompts/profile.sec-ops.system.md +14 -0
  38. package/bundled/prompts/profile.social-media.system.md +7 -0
  39. package/bundled/prompts/profile.software-design.system.md +26 -0
  40. package/bundled/prompts/profile.sub-coordinator.charter.md +2 -0
  41. package/bundled/prompts/profile.worker-gui.system.md +7 -0
  42. package/bundled/prompts/profile.worker.system.md +2 -0
  43. package/bundled/prompts/relay.agent-core.delivery.md +13 -0
  44. package/bundled/prompts/relay.agent-core.engineering.md +13 -0
  45. package/bundled/prompts/relay.agent-core.md +22 -0
  46. package/bundled/prompts/relay.context.claude.md +1 -0
  47. package/bundled/prompts/relay.context.claude.readonly.md +3 -0
  48. package/bundled/prompts/relay.context.codex.md +1 -0
  49. package/bundled/prompts/relay.context.opencode.md +1 -0
  50. package/bundled/prompts/relay.manual.claude.md +8 -0
  51. package/bundled/prompts/relay.search.fff.md +1 -0
  52. package/bundled/prompts/relay.temp-dir.md +1 -0
  53. package/bundled/prompts/relay.web.brave.md +1 -0
  54. package/bundled/prompts/relay.web.fetch.md +1 -0
  55. package/bundled/prompts/skill.device-lane.md +20 -0
  56. package/bundled/prompts/skill.security-code-review.md +10 -0
  57. package/bundled/prompts/skill.security-hardening.md +10 -0
  58. package/bundled/prompts/skill.security-host-audit.md +11 -0
  59. package/bundled/prompts/spawn.primer.md +6 -0
  60. package/bundled/prompts/spawn.reportup.child.md +3 -0
  61. package/bundled/prompts/spawn.reportup.coordinator.md +2 -0
  62. package/bundled/prompts/spawn.reportup.workflow-step.md +4 -0
  63. package/bundled/prompts/spawn.validation.delivery.md +1 -0
  64. package/bundled/prompts/spawn.validation.iterate.generic.md +1 -0
  65. package/bundled/prompts/spawn.validation.iterate.imperative.md +1 -0
  66. package/bundled/prompts/spawn.validation.landgates.header.md +1 -0
  67. package/bundled/prompts/steward.merge-stall.md +1 -0
  68. package/bundled/prompts/steward.prompt.md +20 -0
  69. package/bundled/prompts/steward.wake.md +1 -0
  70. package/bundled/prompts/task.claim.instruction.md +1 -0
  71. package/bundled/prompts/task.hydration.md +23 -0
  72. package/bundled/prompts/user.response.brief.md +1 -0
  73. package/bundled/prompts/user.response.normal.md +1 -0
  74. package/bundled/prompts/user.style.neutral.md +1 -0
  75. package/bundled/prompts/user.style.warm.md +1 -0
  76. package/bundled/prompts/workspace.deps.command.md +1 -0
  77. package/bundled/prompts/workspace.deps.none.md +1 -0
  78. package/bundled/prompts/workspace.deps.symlink.md +1 -0
  79. package/bundled/prompts/workspace.lifecycle.md +4 -0
  80. package/bundled/prompts/workspace.mcp.instructions.md +7 -0
  81. package/bundled/prompts/workspace.phase.abandoned.headline.md +1 -0
  82. package/bundled/prompts/workspace.phase.abandoned.hint.md +1 -0
  83. package/bundled/prompts/workspace.phase.active.headline.md +1 -0
  84. package/bundled/prompts/workspace.phase.active.hint.md +1 -0
  85. package/bundled/prompts/workspace.phase.conflict.headline.md +1 -0
  86. package/bundled/prompts/workspace.phase.conflict.hint.md +1 -0
  87. package/bundled/prompts/workspace.phase.merge-planned.headline.md +1 -0
  88. package/bundled/prompts/workspace.phase.merge-planned.hint.md +1 -0
  89. package/bundled/prompts/workspace.phase.merged.headline.md +1 -0
  90. package/bundled/prompts/workspace.phase.merged.hint.md +1 -0
  91. package/bundled/prompts/workspace.phase.ready.headline.md +1 -0
  92. package/bundled/prompts/workspace.phase.ready.hint.md +1 -0
  93. package/bundled/prompts/workspace.ready.contract.md +6 -0
  94. package/bundled/prompts/workspace.symlinks.md +1 -0
  95. package/bundled/roles/builder.md +18 -0
  96. package/bundled/roles/design-reviewer.md +13 -0
  97. package/bundled/roles/designer.md +11 -0
  98. package/bundled/roles/plan-auditor.md +29 -0
  99. package/bundled/roles/reviewer.md +36 -0
  100. package/bundled/roles/slice-qa.md +13 -0
  101. package/bundled/roles/verifier.md +13 -0
  102. package/bundled/skills/agent-browser/SKILL.md +72 -0
  103. package/bundled/skills/test-audit/SKILL.md +75 -0
  104. package/bundled/workflows/build-review.yaml +8 -0
  105. package/bundled/workflows/design-review.yaml +60 -0
  106. package/bundled/workflows/plan-conformance.yaml +5 -0
  107. package/bundled/workflows/slice-proof.yaml +85 -0
  108. package/bundled/workflows/verify-deployed.yaml +140 -0
  109. package/docs/guides/sub-coordinators.md +114 -0
  110. package/docs/guides/tools-administration.md +84 -0
  111. package/docs/guides/tools-agents.md +137 -0
  112. package/docs/guides/tools-coordination.md +228 -0
  113. package/docs/guides/tools-execution.md +48 -0
  114. package/docs/guides/tools-issues.md +69 -0
  115. package/docs/guides/tools-memory.md +150 -0
  116. package/docs/guides/tools-messages.md +192 -0
  117. package/docs/guides/tools-operations.md +133 -0
  118. package/docs/guides/tools-tasks.md +235 -0
  119. package/docs/guides/workflows.md +324 -0
  120. package/docs/license-faq.md +37 -0
  121. package/docs/openapi.json +18531 -0
  122. package/examples/integrations/github-issue.ts +48 -0
  123. package/examples/integrations/ops-alert.sh +27 -0
  124. package/examples/integrations/prometheus-alertmanager.ts +55 -0
  125. package/examples/integrations/support-ticket.sh +28 -0
  126. package/node_modules/@humwick/kernel/LICENSE +167 -0
  127. package/node_modules/@humwick/kernel/async-entity-persistence.ts +344 -0
  128. package/node_modules/@humwick/kernel/async-event-log.ts +107 -0
  129. package/node_modules/@humwick/kernel/automations.pilot.ts +147 -0
  130. package/node_modules/@humwick/kernel/config.ts +111 -0
  131. package/node_modules/@humwick/kernel/contract.ts +572 -0
  132. package/node_modules/@humwick/kernel/control-authz.ts +281 -0
  133. package/node_modules/@humwick/kernel/control.ts +645 -0
  134. package/node_modules/@humwick/kernel/effects.ts +493 -0
  135. package/node_modules/@humwick/kernel/event-log.ts +319 -0
  136. package/node_modules/@humwick/kernel/http-transport.ts +384 -0
  137. package/node_modules/@humwick/kernel/identity-grammar.ts +20 -0
  138. package/node_modules/@humwick/kernel/identity.ts +248 -0
  139. package/node_modules/@humwick/kernel/index.ts +28 -0
  140. package/node_modules/@humwick/kernel/kernel-persistence.ts +181 -0
  141. package/node_modules/@humwick/kernel/landing-incident.ts +306 -0
  142. package/node_modules/@humwick/kernel/landing.ts +953 -0
  143. package/node_modules/@humwick/kernel/manifest-validation.ts +460 -0
  144. package/node_modules/@humwick/kernel/mcp-transport.ts +109 -0
  145. package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +200 -0
  146. package/node_modules/@humwick/kernel/message-authority.ts +281 -0
  147. package/node_modules/@humwick/kernel/module-host.ts +722 -0
  148. package/node_modules/@humwick/kernel/openapi-init.ts +14 -0
  149. package/node_modules/@humwick/kernel/package.json +39 -0
  150. package/node_modules/@humwick/kernel/persistence-schema.ts +248 -0
  151. package/node_modules/@humwick/kernel/persistence.ts +510 -0
  152. package/node_modules/@humwick/kernel/registry.ts +60 -0
  153. package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +499 -0
  154. package/node_modules/@humwick/kernel/sqlite-module-host.ts +87 -0
  155. package/node_modules/@humwick/kernel/timers-chaos-worker.ts +186 -0
  156. package/node_modules/@humwick/kernel/timers.pilot.ts +1411 -0
  157. package/node_modules/@humwick/kernel/workflow-activation.ts +25 -0
  158. package/node_modules/@humwick/kernel/workflow-backstop.ts +40 -0
  159. package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +20 -0
  160. package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +213 -0
  161. package/node_modules/@humwick/kernel/workflow-composition.ts +231 -0
  162. package/node_modules/@humwick/kernel/workflow-container.ts +72 -0
  163. package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +42 -0
  164. package/node_modules/@humwick/kernel/workflow-control.ts +178 -0
  165. package/node_modules/@humwick/kernel/workflow-definition.ts +507 -0
  166. package/node_modules/@humwick/kernel/workflow-dispatch.ts +60 -0
  167. package/node_modules/@humwick/kernel/workflow-foreach.ts +167 -0
  168. package/node_modules/@humwick/kernel/workflow-journal-codec.ts +339 -0
  169. package/node_modules/@humwick/kernel/workflow-journal.ts +922 -0
  170. package/node_modules/@humwick/kernel/workflow-lane.ts +48 -0
  171. package/node_modules/@humwick/kernel/workflow-lease-owner.ts +60 -0
  172. package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +19 -0
  173. package/node_modules/@humwick/kernel/workflow-runtime.ts +938 -0
  174. package/node_modules/@humwick/kernel/workflow-script.ts +11 -0
  175. package/node_modules/@humwick/kernel/workflow-state.ts +299 -0
  176. package/node_modules/@humwick/kernel/workflow-step-commit.ts +195 -0
  177. package/node_modules/@humwick/kernel/workflow-transitions.ts +317 -0
  178. package/node_modules/@humwick/kernel/workflow-values.ts +153 -0
  179. package/node_modules/@humwick/kernel/workflow.pilot.ts +376 -0
  180. package/node_modules/@humwick/offload/LICENSE +167 -0
  181. package/node_modules/@humwick/offload/README.md +57 -0
  182. package/node_modules/@humwick/offload/dist/async-runner.d.ts +18 -0
  183. package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +1 -0
  184. package/node_modules/@humwick/offload/dist/async-runner.js +158 -0
  185. package/node_modules/@humwick/offload/dist/async-runner.js.map +1 -0
  186. package/node_modules/@humwick/offload/dist/envelope.d.ts +25 -0
  187. package/node_modules/@humwick/offload/dist/envelope.d.ts.map +1 -0
  188. package/node_modules/@humwick/offload/dist/envelope.js +80 -0
  189. package/node_modules/@humwick/offload/dist/envelope.js.map +1 -0
  190. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +10 -0
  191. package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +1 -0
  192. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +59 -0
  193. package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +1 -0
  194. package/node_modules/@humwick/offload/dist/index.d.ts +5 -0
  195. package/node_modules/@humwick/offload/dist/index.d.ts.map +1 -0
  196. package/node_modules/@humwick/offload/dist/index.js +5 -0
  197. package/node_modules/@humwick/offload/dist/index.js.map +1 -0
  198. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +62 -0
  199. package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +1 -0
  200. package/node_modules/@humwick/offload/dist/subprocess-runner.js +295 -0
  201. package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +1 -0
  202. package/node_modules/@humwick/offload/dist/types.d.ts +111 -0
  203. package/node_modules/@humwick/offload/dist/types.d.ts.map +1 -0
  204. package/node_modules/@humwick/offload/dist/types.js +176 -0
  205. package/node_modules/@humwick/offload/dist/types.js.map +1 -0
  206. package/node_modules/@humwick/offload/dist/worker-pool.d.ts +23 -0
  207. package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +1 -0
  208. package/node_modules/@humwick/offload/dist/worker-pool.js +435 -0
  209. package/node_modules/@humwick/offload/dist/worker-pool.js.map +1 -0
  210. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +2 -0
  211. package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +1 -0
  212. package/node_modules/@humwick/offload/dist/worker-runtime.js +27 -0
  213. package/node_modules/@humwick/offload/dist/worker-runtime.js.map +1 -0
  214. package/node_modules/@humwick/offload/dist/worker.d.ts +5 -0
  215. package/node_modules/@humwick/offload/dist/worker.d.ts.map +1 -0
  216. package/node_modules/@humwick/offload/dist/worker.js +1 -0
  217. package/node_modules/@humwick/offload/dist/worker.js.map +1 -0
  218. package/node_modules/@humwick/offload/package.json +42 -0
  219. package/node_modules/@humwick/offload/src/async-runner.ts +170 -0
  220. package/node_modules/@humwick/offload/src/envelope.ts +92 -0
  221. package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +49 -0
  222. package/node_modules/@humwick/offload/src/index.ts +4 -0
  223. package/node_modules/@humwick/offload/src/subprocess-runner.ts +334 -0
  224. package/node_modules/@humwick/offload/src/types.ts +235 -0
  225. package/node_modules/@humwick/offload/src/worker-pool.ts +463 -0
  226. package/node_modules/@humwick/offload/src/worker-runtime.ts +32 -0
  227. package/node_modules/@humwick/offload/src/worker.ts +5 -0
  228. package/package.json +229 -4
  229. package/public/assets/MessageBubble-BWpoBjl4.js +4 -0
  230. package/public/assets/PlanGraphPanel-C0auck3n.js +2 -0
  231. package/public/assets/TraceView-DBnOx7AQ.js +5 -0
  232. package/public/assets/VaultDecisionCard-0kp2oNBC.js +2 -0
  233. package/public/assets/activity-TMzFOdh2.js +2 -0
  234. package/public/assets/activity-yLztXEBt.js +2 -0
  235. package/public/assets/agent-lineage-BbJDR1dW.js +2 -0
  236. package/public/assets/agent-liveness-DyqxfB-t.js +2 -0
  237. package/public/assets/agent-name-CTZBrSD9.js +2 -0
  238. package/public/assets/agent-profiles-BpI_jTxq.js +2 -0
  239. package/public/assets/agent-project-badge-9TqzUUYE.js +2 -0
  240. package/public/assets/agent-tools-D4n40pn8.js +2 -0
  241. package/public/assets/agent-type-icon-D6FUarOf.js +2 -0
  242. package/public/assets/agents-BYDslkJc.js +2 -0
  243. package/public/assets/analytics-B-XSbEMC.js +910 -0
  244. package/public/assets/andromeeda-vGVdxbeo.js +2 -0
  245. package/public/assets/angular-html-BVBpGdXr.js +2 -0
  246. package/public/assets/angular-ts-BfdufMKP.js +2 -0
  247. package/public/assets/api-CIIVBtAV.js +10 -0
  248. package/public/assets/archive-CIuPSvmx.js +2 -0
  249. package/public/assets/arrow-right-vFsRqG8V.js +2 -0
  250. package/public/assets/arrow-up-a-z-CKC2RCjD.js +2 -0
  251. package/public/assets/artifact-viewer-DdWIfV6M.js +2 -0
  252. package/public/assets/artifacts-DPN1q4pA.js +2 -0
  253. package/public/assets/artifacts-oBOqks-2.js +2 -0
  254. package/public/assets/astro-JKRPP03v.js +2 -0
  255. package/public/assets/aurora-x-CDeNXAV0.js +2 -0
  256. package/public/assets/automation-4KcRFXEu.js +2 -0
  257. package/public/assets/ayu-dark-DluEY0Gj.js +2 -0
  258. package/public/assets/ayu-light-C3h-C4tm.js +2 -0
  259. package/public/assets/ayu-mirage-Bqwy1Gya.js +2 -0
  260. package/public/assets/backlog-Cw9xPTCF.js +2 -0
  261. package/public/assets/badge-DqJRtj_p.js +2 -0
  262. package/public/assets/bell-_Yzbxxrt.js +2 -0
  263. package/public/assets/blade-DA8yMPsw.js +2 -0
  264. package/public/assets/bot-B0NpDNhr.js +2 -0
  265. package/public/assets/boxes-Cv7Wq3h4.js +2 -0
  266. package/public/assets/braces-BOks9SsY.js +2 -0
  267. package/public/assets/brain-CagXMEKE.js +2 -0
  268. package/public/assets/brain-circuit-CpQeSmp3.js +2 -0
  269. package/public/assets/bundle-web-ifbJd0t-.js +14 -0
  270. package/public/assets/busy-health-CTu32MZt.js +2 -0
  271. package/public/assets/button-Do6Z3vvn.js +2 -0
  272. package/public/assets/c-Wt1voDr2.js +2 -0
  273. package/public/assets/calendar-QJjoZ1JU.js +2 -0
  274. package/public/assets/calendar-clock-CDG_KBhx.js +2 -0
  275. package/public/assets/card-C4QJ4hCW.js +2 -0
  276. package/public/assets/catppuccin-frappe-3VR1Za6u.js +2 -0
  277. package/public/assets/catppuccin-latte-DwIHMF0Q.js +2 -0
  278. package/public/assets/catppuccin-macchiato-DYnBP6_5.js +2 -0
  279. package/public/assets/catppuccin-mocha-DYhrFGRu.js +2 -0
  280. package/public/assets/channels-BiXzg1G9.js +2 -0
  281. package/public/assets/chart-column-S02ZGfTR.js +2 -0
  282. package/public/assets/chat-Ctk2qwvA.js +6 -0
  283. package/public/assets/check-B3M9Ug1P.js +2 -0
  284. package/public/assets/chevron-down-BoG3JCZn.js +2 -0
  285. package/public/assets/chevron-right-iljytE0x.js +2 -0
  286. package/public/assets/chevron-up-Bv3yC5ma.js +2 -0
  287. package/public/assets/circle-UESLMi2-.js +2 -0
  288. package/public/assets/circle-alert-CryagaZd.js +2 -0
  289. package/public/assets/circle-arrow-up-B432r0Kk.js +2 -0
  290. package/public/assets/circle-check-CkUkN96J.js +2 -0
  291. package/public/assets/circle-x-CZ0erS7Q.js +2 -0
  292. package/public/assets/clipboard-list-W2YeUsxT.js +2 -0
  293. package/public/assets/clock-3-BH47318N.js +2 -0
  294. package/public/assets/clock-BWPPBh1_.js +2 -0
  295. package/public/assets/code-preview-Bm1wWqAp.js +3 -0
  296. package/public/assets/coffee-CThvmt4R.js +2 -0
  297. package/public/assets/connectors-C36ax4Vw.js +2 -0
  298. package/public/assets/copy-DFxhHRtV.js +2 -0
  299. package/public/assets/copy-button-CjOj4HEf.js +2 -0
  300. package/public/assets/cosmos-Dn8XrlnS.css +1 -0
  301. package/public/assets/cosmos-Jj9LK2Wf.js +3993 -0
  302. package/public/assets/cpp-C5WNSHb9.js +2 -0
  303. package/public/assets/cpu-Dndy84IU.js +2 -0
  304. package/public/assets/crate-agent-H6S7-B9T.js +2 -0
  305. package/public/assets/css-DJp_X0uY.js +2 -0
  306. package/public/assets/csv-Dx-8-gkx.js +2 -0
  307. package/public/assets/dark-plus-Cs2F2srj.js +2 -0
  308. package/public/assets/dependency-graph-Cd1E23wa.js +2 -0
  309. package/public/assets/deploys-Bwfq146q.js +2 -0
  310. package/public/assets/dialog-uuJ9_geR.js +6 -0
  311. package/public/assets/display-CfO7rTuF.js +3 -0
  312. package/public/assets/dist-B2h2GeWt.js +2 -0
  313. package/public/assets/dist-BeVYU67E.js +2 -0
  314. package/public/assets/dist-Bmm-RKub.js +2 -0
  315. package/public/assets/dist-C1xmD7qJ.js +2 -0
  316. package/public/assets/dist-C2J943E6.js +2 -0
  317. package/public/assets/dist-C6lpJDoA.js +2 -0
  318. package/public/assets/dist-CoWL6B0c.js +2 -0
  319. package/public/assets/dist-D93SmTNC.js +2 -0
  320. package/public/assets/dist-Da7HrHNA.js +2 -0
  321. package/public/assets/dist-DeW2Y1R5.js +2 -0
  322. package/public/assets/dist-DwBdqQ6H.js +2 -0
  323. package/public/assets/dist-Dz182zaW.js +2 -0
  324. package/public/assets/dist-H3DLHJ0C.js +2 -0
  325. package/public/assets/dist-RrY9C4SI.js +2 -0
  326. package/public/assets/download-KgtpDbt5.js +2 -0
  327. package/public/assets/dracula-BHWKrbxM.js +2 -0
  328. package/public/assets/dracula-soft-5eyTD99u.js +2 -0
  329. package/public/assets/dropdown-menu-BXiNephE.js +2 -0
  330. package/public/assets/es2015-DMmZ8vbD.js +42 -0
  331. package/public/assets/esm-ZSuD7QuY.js +2 -0
  332. package/public/assets/everforest-dark-sB-x3p7T.js +2 -0
  333. package/public/assets/everforest-light-Df2xbC6M.js +2 -0
  334. package/public/assets/external-link-BRfu-fHG.js +2 -0
  335. package/public/assets/eye-BAP6fHrX.js +2 -0
  336. package/public/assets/file-code-corner-CXhIR1vB.js +2 -0
  337. package/public/assets/file-pen-Crunre5f.js +2 -0
  338. package/public/assets/file-text-CKuoMR2U.js +2 -0
  339. package/public/assets/files-DhhZI2Ih.js +3 -0
  340. package/public/assets/flag-CnEG9PH-.js +2 -0
  341. package/public/assets/flame-CxBuz46L.js +2 -0
  342. package/public/assets/fleet-B_rFEbnL.js +2 -0
  343. package/public/assets/fleet-metrics-BkHP-P3R.js +2 -0
  344. package/public/assets/folder-git-2-CJOmTT06.js +2 -0
  345. package/public/assets/folder-open-CBUCMSlu.js +2 -0
  346. package/public/assets/folder-tree-BG3Y9nqH.js +2 -0
  347. package/public/assets/formatted-body-Cm1RrzmN.js +3 -0
  348. package/public/assets/formatted-body-impl-DQiOuOD1.js +3 -0
  349. package/public/assets/gauge-BIODY-BQ.js +2 -0
  350. package/public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  351. package/public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  352. package/public/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  353. package/public/assets/git-branch-yxhV0m8E.js +2 -0
  354. package/public/assets/git-commit-horizontal-DS1pJ_SS.js +2 -0
  355. package/public/assets/git-merge-C7eIG425.js +2 -0
  356. package/public/assets/github-dark-C-LZuMrd.js +2 -0
  357. package/public/assets/github-dark-default-DXG-b-1a.js +2 -0
  358. package/public/assets/github-dark-dimmed-Bx1FflLF.js +2 -0
  359. package/public/assets/github-dark-high-contrast-B_tTalzw.js +2 -0
  360. package/public/assets/github-light-EUqPIrTm.js +2 -0
  361. package/public/assets/github-light-default-BXViO-2h.js +2 -0
  362. package/public/assets/github-light-high-contrast-B68TUdTA.js +2 -0
  363. package/public/assets/globe-Cl5fXtqs.js +2 -0
  364. package/public/assets/glsl-Dv5r7kPw.js +2 -0
  365. package/public/assets/graphql-CcFwQwm0.js +2 -0
  366. package/public/assets/gruvbox-dark-hard-C820rvS2.js +2 -0
  367. package/public/assets/gruvbox-dark-medium-BPjhmG05.js +2 -0
  368. package/public/assets/gruvbox-dark-soft-MrdJrrXF.js +2 -0
  369. package/public/assets/gruvbox-light-hard-BC_s9l72.js +2 -0
  370. package/public/assets/gruvbox-light-medium-BAWPOn9u.js +2 -0
  371. package/public/assets/gruvbox-light-soft-BSMLrYjP.js +2 -0
  372. package/public/assets/haml-Bg4I2hiD.js +2 -0
  373. package/public/assets/handlebars-DnwbnL5R.js +2 -0
  374. package/public/assets/hash-CbtKih3g.js +2 -0
  375. package/public/assets/horizon-CE9ld1lL.js +2 -0
  376. package/public/assets/horizon-bright-DSNQnXHK.js +2 -0
  377. package/public/assets/host-mTgRw7xh.js +2 -0
  378. package/public/assets/host-providers-dialog-CHqwXTov.js +2 -0
  379. package/public/assets/hourglass-txu4ax6n.js +2 -0
  380. package/public/assets/houston-CsvMBhTu.js +2 -0
  381. package/public/assets/html-C8UlPnhE.js +2 -0
  382. package/public/assets/html-derivative-CY6NRz-J.js +2 -0
  383. package/public/assets/http-CoW620Fi.js +2 -0
  384. package/public/assets/humwick-lockup-on-dark-Ds44Y7BK.svg +19 -0
  385. package/public/assets/humwick-lockup-on-light-ClALZAHt.svg +19 -0
  386. package/public/assets/hurl-CSSETujw.js +2 -0
  387. package/public/assets/imba-DsUTQ-LC.js +2 -0
  388. package/public/assets/inbox-Dfwk3Gt8.js +2 -0
  389. package/public/assets/index-B7ZerHLY.css +2 -0
  390. package/public/assets/index-BU3uWo2t.js +15 -0
  391. package/public/assets/input-CsTB6jIx.js +2 -0
  392. package/public/assets/insights-fH1EL-qZ.js +2 -0
  393. package/public/assets/integrations-C5Oix6ng.js +2 -0
  394. package/public/assets/issue-ref-chip-ODyondIa.js +3 -0
  395. package/public/assets/java-CGc3VwQr.js +2 -0
  396. package/public/assets/javascript-CUt1pgmJ.js +2 -0
  397. package/public/assets/jinja-CD-Z-FLd.js +2 -0
  398. package/public/assets/jison-imPNup1l.js +2 -0
  399. package/public/assets/json-qhed-kSA.js +2 -0
  400. package/public/assets/json5-BR5RXkoi.js +2 -0
  401. package/public/assets/jsonc-CYpm1nAK.js +2 -0
  402. package/public/assets/jsonl-CmCQp5Yx.js +2 -0
  403. package/public/assets/jsx-CY6oMTks.js +2 -0
  404. package/public/assets/julia-CjffhBEM.js +2 -0
  405. package/public/assets/kanagawa-dragon-CXtmUGW6.js +2 -0
  406. package/public/assets/kanagawa-lotus-BN08jTvb.js +2 -0
  407. package/public/assets/kanagawa-wave-CTweb8Dz.js +2 -0
  408. package/public/assets/kernel-Bqr0CZGv.js +2 -0
  409. package/public/assets/key-round-CcSxl5qr.js +2 -0
  410. package/public/assets/keyboard-DcmEulph.js +2 -0
  411. package/public/assets/label-BPBzm9FJ.js +2 -0
  412. package/public/assets/laserwave-C_8bwKvT.js +2 -0
  413. package/public/assets/layers-DEfClY-v.js +2 -0
  414. package/public/assets/legacy-storage-carry-C-tBSxRA.js +2 -0
  415. package/public/assets/less-DVTAwKKz.js +2 -0
  416. package/public/assets/lib-D7DTGM1F.js +2 -0
  417. package/public/assets/lib-G-25QXXf.js +30 -0
  418. package/public/assets/light-plus-DVQuIRkW.js +2 -0
  419. package/public/assets/lightbulb-RDhZ4p-D.js +2 -0
  420. package/public/assets/link-2-off-DfRF9Hwb.js +2 -0
  421. package/public/assets/link-6RR7Hy3-.js +2 -0
  422. package/public/assets/list-checks-CwnS3hly.js +2 -0
  423. package/public/assets/list-ordered-D8nCM7rT.js +2 -0
  424. package/public/assets/list-todo-9qT0fgxZ.js +2 -0
  425. package/public/assets/loader-circle-BfPd4kOD.js +2 -0
  426. package/public/assets/lock-BW8MPKw_.js +2 -0
  427. package/public/assets/log-viewer-Cvfy6PW7.js +4 -0
  428. package/public/assets/mail-DyaDmwfY.js +2 -0
  429. package/public/assets/maintenance-D8CDH5wZ.js +2 -0
  430. package/public/assets/managed-agents-egllbpij.js +4 -0
  431. package/public/assets/markdown-BYOwaDjH.js +2 -0
  432. package/public/assets/markdown-preview-CYSFgVgK.js +3 -0
  433. package/public/assets/markdown-preview-impl-BteVIjX4.js +2 -0
  434. package/public/assets/marko-DvhNOisQ.js +2 -0
  435. package/public/assets/material-theme-Bm3Qr25_.js +2 -0
  436. package/public/assets/material-theme-darker-2IIEA8gg.js +2 -0
  437. package/public/assets/material-theme-lighter-uhdI0v04.js +2 -0
  438. package/public/assets/material-theme-ocean-CHQ94UKr.js +2 -0
  439. package/public/assets/material-theme-palenight-B5W6OYN7.js +2 -0
  440. package/public/assets/mdc-CazklSAj.js +2 -0
  441. package/public/assets/mdx-DQZ5AkYe.js +2 -0
  442. package/public/assets/memory-C8WmXsyr.js +2 -0
  443. package/public/assets/message-circle-wjztgHBe.js +2 -0
  444. package/public/assets/message-square-C-1G8dMM.js +2 -0
  445. package/public/assets/message-square-reply-B9Z4cbQx.js +2 -0
  446. package/public/assets/messages-BzQdqlO6.js +2 -0
  447. package/public/assets/messages-square-DVM1ZtcI.js +2 -0
  448. package/public/assets/min-dark-BSWPekZh.js +2 -0
  449. package/public/assets/min-light-DDpmG2fV.js +2 -0
  450. package/public/assets/minimize-2-BkK6XoMP.js +2 -0
  451. package/public/assets/monokai-CdkpiU2Y.js +2 -0
  452. package/public/assets/night-owl-DhmEMT88.js +2 -0
  453. package/public/assets/night-owl-light-eJ-hLW7d.js +2 -0
  454. package/public/assets/nord-Cb4Vim4T.js +2 -0
  455. package/public/assets/octagon-x-B5lnUY-8.js +2 -0
  456. package/public/assets/one-dark-pro-CLwyXe_n.js +2 -0
  457. package/public/assets/one-light-D7Lr4KcI.js +2 -0
  458. package/public/assets/orchestrators-B1HxzkNw.js +2 -0
  459. package/public/assets/overview-DCUnI4Z7.js +2 -0
  460. package/public/assets/package-open-ckPgcjpv.js +2 -0
  461. package/public/assets/package-plus-C1AKMjsl.js +2 -0
  462. package/public/assets/pairs-ftCd2XcD.js +2 -0
  463. package/public/assets/paperclip-CNpKZKum.js +2 -0
  464. package/public/assets/pause-DttnHD4c.js +2 -0
  465. package/public/assets/pencil-B1Hf2V_J.js +2 -0
  466. package/public/assets/php-CBqsN0ai.js +2 -0
  467. package/public/assets/pin-DcsKeXxr.js +2 -0
  468. package/public/assets/plastic-DQwYfKfQ.js +2 -0
  469. package/public/assets/play-BbT8m7A0.js +2 -0
  470. package/public/assets/plug-vIyO9_P8.js +2 -0
  471. package/public/assets/plug-zap-V0wMDJ-G.js +2 -0
  472. package/public/assets/plus-D5Ek4e2S.js +2 -0
  473. package/public/assets/poimandres-DRFjx7u4.js +2 -0
  474. package/public/assets/popover-8w1TWKMG.js +2 -0
  475. package/public/assets/postcss-BXeXVLqQ.js +2 -0
  476. package/public/assets/power-6tZOhYPg.js +2 -0
  477. package/public/assets/preload-helper-HclGiUj8.js +1 -0
  478. package/public/assets/profile-icon-B2zH8DMH.js +2 -0
  479. package/public/assets/projects-B_lmcqfO.js +2 -0
  480. package/public/assets/prompt-settings-Bftq9czX.js +2 -0
  481. package/public/assets/provider-brand-icon-_67JR7Zh.js +2 -0
  482. package/public/assets/provider-display-DbLWpP3j.js +2 -0
  483. package/public/assets/pug-C5hz5LQ7.js +2 -0
  484. package/public/assets/python-gzcpVVnB.js +2 -0
  485. package/public/assets/r-Dzep-lPg.js +2 -0
  486. package/public/assets/radio-Bn0_qc0G.js +2 -0
  487. package/public/assets/react-dom-DSFxiC7T.js +2 -0
  488. package/public/assets/red-CJ3rzSJv.js +2 -0
  489. package/public/assets/refresh-cw-CfpvQcUn.js +2 -0
  490. package/public/assets/regexp-Omp9DhTb.js +2 -0
  491. package/public/assets/registry-DWCU5c8q.js +4 -0
  492. package/public/assets/repeat-hl4p_Klo.js +2 -0
  493. package/public/assets/rocket-BmO1qSu-.js +2 -0
  494. package/public/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  495. package/public/assets/rose-pine-BthvhNj6.js +2 -0
  496. package/public/assets/rose-pine-dawn-Dg85fqjY.js +2 -0
  497. package/public/assets/rose-pine-moon-hon4tzzS.js +2 -0
  498. package/public/assets/rotate-ccw-CtD77qkX.js +2 -0
  499. package/public/assets/rotate-cw-FPtkDewA.js +2 -0
  500. package/public/assets/run-card-CO_bguQf.js +2 -0
  501. package/public/assets/sass-DXrisJhu.js +2 -0
  502. package/public/assets/save-B_PGqSm4.js +2 -0
  503. package/public/assets/scroll-area-CIN4KQse.js +2 -0
  504. package/public/assets/scss-DdSxiZKl.js +2 -0
  505. package/public/assets/search-DjzbnTbf.js +2 -0
  506. package/public/assets/secrets-C7GIUATz.js +2 -0
  507. package/public/assets/security-BUFau5-N.js +3 -0
  508. package/public/assets/select-DTQMP4Dk.js +2 -0
  509. package/public/assets/separator-t1cLZErD.js +2 -0
  510. package/public/assets/server-CZIRjFyY.js +2 -0
  511. package/public/assets/service-access-settings-C3aujqTG.js +2 -0
  512. package/public/assets/settings-C6sZ9eBa.js +6 -0
  513. package/public/assets/shellscript-CLZ0U2zV.js +2 -0
  514. package/public/assets/shield-CtXNUA0i.js +2 -0
  515. package/public/assets/shield-alert-B9jCWcKW.js +2 -0
  516. package/public/assets/shipping-Bu8p1gEw.js +2 -0
  517. package/public/assets/shipping-stats-BiMTsA4A.js +2 -0
  518. package/public/assets/slack-dark-DnToyrRv.js +2 -0
  519. package/public/assets/slack-ochin-B2OO5cIa.js +2 -0
  520. package/public/assets/smile-plus-q6EQBXHZ.js +2 -0
  521. package/public/assets/snazzy-light-4G7pJPwS.js +2 -0
  522. package/public/assets/solarized-dark-DV17i1UV.js +2 -0
  523. package/public/assets/solarized-light-DSh2HLQt.js +2 -0
  524. package/public/assets/sparkles-CzwR10ZU.js +2 -0
  525. package/public/assets/sql-DNssxck8.js +2 -0
  526. package/public/assets/square-Bqly37YM.js +2 -0
  527. package/public/assets/stats-V9Z-GxUd.js +2 -0
  528. package/public/assets/status-dot-CVv0iGHL.js +2 -0
  529. package/public/assets/stethoscope-SCBrnQQ8.js +2 -0
  530. package/public/assets/store-BvtFdlGX.js +9 -0
  531. package/public/assets/stylus-B6D30XZt.js +2 -0
  532. package/public/assets/svelte-DOdLCIlh.js +2 -0
  533. package/public/assets/switch-DuCA7mrB.js +2 -0
  534. package/public/assets/synthwave-84-nFMaYfgc.js +2 -0
  535. package/public/assets/tabs-jZgNvNHr.js +2 -0
  536. package/public/assets/task-chip-CYj0mpoL.js +2 -0
  537. package/public/assets/tasks-BIyeAaPC.js +2 -0
  538. package/public/assets/tasks-CXU75HRF.js +11 -0
  539. package/public/assets/teams-BPf976zb.js +2 -0
  540. package/public/assets/terminal-Bl2u7KxT.js +2 -0
  541. package/public/assets/terminal-viewer-DE9BOe-8.js +3 -0
  542. package/public/assets/terminal-viewer-impl-BrP-ENHg.css +1 -0
  543. package/public/assets/terminal-viewer-impl-D04pjmGh.js +143 -0
  544. package/public/assets/textarea-hs6Mpyfl.js +2 -0
  545. package/public/assets/themes-G5j9m1Ji.js +2 -0
  546. package/public/assets/tokyo-night-oM2G3aXe.js +2 -0
  547. package/public/assets/trash-2-FbcSq927.js +2 -0
  548. package/public/assets/triangle-alert-BBNhfgMi.js +2 -0
  549. package/public/assets/ts-tags-Ca73dJJY.js +2 -0
  550. package/public/assets/tsx-5Eka4NBX.js +2 -0
  551. package/public/assets/typescript-DOu2WMV5.js +2 -0
  552. package/public/assets/use-agent-terminal-B1yR456R.js +2 -0
  553. package/public/assets/use-decisions-D-S4tpDf.js +2 -0
  554. package/public/assets/use-project-list-B-T2Bnnn.js +2 -0
  555. package/public/assets/use-selectors-CnrAk2YP.js +6 -0
  556. package/public/assets/user-avatar-nBFIcR4M.js +2 -0
  557. package/public/assets/user-cog-CKcouW1J.js +2 -0
  558. package/public/assets/user-plus-D9749q73.js +2 -0
  559. package/public/assets/users-C7B3u9jv.js +2 -0
  560. package/public/assets/versioned-prompt-editor-Diu8BIaM.js +8 -0
  561. package/public/assets/vesper-DdrHHSXu.js +2 -0
  562. package/public/assets/vitesse-black-fwtXNY1n.js +2 -0
  563. package/public/assets/vitesse-dark-BZCL-v6S.js +2 -0
  564. package/public/assets/vitesse-light-VbXTXTou.js +2 -0
  565. package/public/assets/volume-2-XrcRKD9Z.js +2 -0
  566. package/public/assets/vue-Bfx0ffIq.js +2 -0
  567. package/public/assets/vue-html-BeluIYX0.js +2 -0
  568. package/public/assets/vue-vine-DGUAbOCX.js +2 -0
  569. package/public/assets/warehouse-BanzOanY.js +2 -0
  570. package/public/assets/wasm-BnjxR4X6.js +2 -0
  571. package/public/assets/wasm-ByWQv1Qj.js +2 -0
  572. package/public/assets/waypoints-CIzINtLT.js +2 -0
  573. package/public/assets/wgsl-BsKzXJz4.js +2 -0
  574. package/public/assets/wit-DdvCle-K.js +2 -0
  575. package/public/assets/work-queue-TCjJxeT-.js +2 -0
  576. package/public/assets/workflow-marker-Cf3Hf2fN.js +2 -0
  577. package/public/assets/workflows-CXQX20Qj.js +6 -0
  578. package/public/assets/workspaces-qSYzKYMA.js +3 -0
  579. package/public/assets/wrench-C7-4CgnY.js +2 -0
  580. package/public/assets/x-Bge17hWJ.js +2 -0
  581. package/public/assets/xml-DIqSwXR3.js +2 -0
  582. package/public/assets/yaml-rwi0_p6S.js +2 -0
  583. package/public/assets/zap-DGuDTw4w.js +2 -0
  584. package/public/assets/zoom-in-Bs8Qv8VL.js +2 -0
  585. package/public/assets/zoomable-image-B3Og53nj.js +2 -0
  586. package/public/cosmos/audio/README.md +50 -0
  587. package/public/cosmos/audio/alert.mp3 +0 -0
  588. package/public/cosmos/audio/ambient-bed.mp3 +0 -0
  589. package/public/cosmos/audio/comet.mp3 +0 -0
  590. package/public/cosmos/audio/cross-galaxy.mp3 +0 -0
  591. package/public/cosmos/audio/deploy.mp3 +0 -0
  592. package/public/cosmos/audio/galaxy-dissipate.mp3 +0 -0
  593. package/public/cosmos/audio/galaxy-form.mp3 +0 -0
  594. package/public/cosmos/audio/land.mp3 +0 -0
  595. package/public/cosmos/audio/reap.mp3 +0 -0
  596. package/public/cosmos/audio/spawn.mp3 +0 -0
  597. package/public/cosmos.html +34 -0
  598. package/public/favicon.ico +0 -0
  599. package/public/favicon.svg +10 -0
  600. package/public/icons/apple-touch-icon.png +0 -0
  601. package/public/icons/humwick-192.png +0 -0
  602. package/public/icons/humwick-512.png +0 -0
  603. package/public/icons/humwick.svg +19 -0
  604. package/public/index.html +188 -0
  605. package/public/manifest.webmanifest +45 -0
  606. package/public/sw.js +160 -0
  607. package/recipes/code-review.yaml +26 -0
  608. package/recipes/debug.yaml +20 -0
  609. package/recipes/feature.yaml +26 -0
  610. package/recipes/refactor.yaml +20 -0
  611. package/recipes/test.yaml +20 -0
  612. package/runner/LICENSE +167 -0
  613. package/runner/agent-bin/agent-relay-browser +10 -0
  614. package/runner/agent-bin/humwick +10 -0
  615. package/runner/hooks/fff-search-redirect/fff-search-redirect.cjs +161 -0
  616. package/runner/hooks/fff-search-redirect/hooks.json +13 -0
  617. package/runner/hooks/rg-r-guard/hooks.json +18 -0
  618. package/runner/hooks/rg-r-guard/rg-r-guard.cjs +144 -0
  619. package/runner/hooks/web-tools-redirect/hooks.json +13 -0
  620. package/runner/hooks/web-tools-redirect/web-tools-redirect.cjs +84 -0
  621. package/runner/plugins/browser/skills/playwright-cli/SKILL.md +53 -0
  622. package/runner/plugins/claude/.claude-plugin/plugin.json +8 -0
  623. package/runner/plugins/claude/claude-statusline-probe.cjs +144 -0
  624. package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +914 -0
  625. package/runner/plugins/claude/hooks/gate-run-guard.sh +9 -0
  626. package/runner/plugins/claude/hooks/gate-run-guard.ts +237 -0
  627. package/runner/plugins/claude/hooks/hooks.json +158 -0
  628. package/runner/plugins/claude/hooks/kill-guard.provisioned.mjs +7027 -0
  629. package/runner/plugins/claude/hooks/kill-guard.sh +341 -0
  630. package/runner/plugins/claude/hooks/kill-guard.ts +337 -0
  631. package/runner/plugins/claude/hooks/permission-request.sh +22 -0
  632. package/runner/plugins/claude/hooks/post-compact.sh +10 -0
  633. package/runner/plugins/claude/hooks/post-tool-use.sh +8 -0
  634. package/runner/plugins/claude/hooks/pre-compact.sh +12 -0
  635. package/runner/plugins/claude/hooks/read-only-bash-guard.sh +587 -0
  636. package/runner/plugins/claude/hooks/relay-status.sh +373 -0
  637. package/runner/plugins/claude/hooks/session-end.sh +26 -0
  638. package/runner/plugins/claude/hooks/session-start.sh +31 -0
  639. package/runner/plugins/claude/hooks/shell-trust.ts +28 -0
  640. package/runner/plugins/claude/hooks/stop-failure.sh +47 -0
  641. package/runner/plugins/claude/hooks/stop.sh +48 -0
  642. package/runner/plugins/claude/hooks/subagent-guard.cjs +26 -0
  643. package/runner/plugins/claude/hooks/subagent-skill-policy.cjs +160 -0
  644. package/runner/plugins/claude/hooks/subagent-start.sh +13 -0
  645. package/runner/plugins/claude/hooks/subagent-stop.sh +19 -0
  646. package/runner/plugins/claude/hooks/user-prompt-submit.sh +18 -0
  647. package/runner/plugins/claude/hooks/wait-loop-guard.ts +120 -0
  648. package/runner/plugins/claude/monitors/monitors.json +7 -0
  649. package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +786 -0
  650. package/runner/plugins/claude/monitors/relay-monitor.ts +58 -0
  651. package/runner/plugins/claude/skills/disconnect/SKILL.md +16 -0
  652. package/runner/plugins/claude/skills/label/SKILL.md +23 -0
  653. package/runner/plugins/claude/skills/message/SKILL.md +40 -0
  654. package/runner/plugins/claude/skills/pair/SKILL.md +30 -0
  655. package/runner/plugins/claude/skills/react/SKILL.md +18 -0
  656. package/runner/plugins/claude/skills/read-message/SKILL.md +24 -0
  657. package/runner/plugins/claude/skills/reply/SKILL.md +44 -0
  658. package/runner/plugins/claude/skills/send-claimable/SKILL.md +39 -0
  659. package/runner/plugins/claude/skills/slice-first/SKILL.md +32 -0
  660. package/runner/plugins/claude/skills/slice-first/references/plan-recipe.md +46 -0
  661. package/runner/plugins/claude/skills/status/SKILL.md +16 -0
  662. package/runner/plugins/claude/skills/tags/SKILL.md +25 -0
  663. package/runner/plugins/claude/skills/task/SKILL.md +28 -0
  664. package/runner/plugins/claude-output-style/.claude-plugin/plugin.json +5 -0
  665. package/runner/plugins/claude-output-style/output-styles/agent-relay.md +5 -0
  666. package/runner/plugins/codex/skills/disconnect/SKILL.md +15 -0
  667. package/runner/plugins/codex/skills/guide/SKILL.md +15 -0
  668. package/runner/plugins/codex/skills/label/SKILL.md +22 -0
  669. package/runner/plugins/codex/skills/message/SKILL.md +40 -0
  670. package/runner/plugins/codex/skills/pair/SKILL.md +29 -0
  671. package/runner/plugins/codex/skills/react/SKILL.md +17 -0
  672. package/runner/plugins/codex/skills/read-message/SKILL.md +23 -0
  673. package/runner/plugins/codex/skills/reply/SKILL.md +44 -0
  674. package/runner/plugins/codex/skills/send-claimable/SKILL.md +39 -0
  675. package/runner/plugins/codex/skills/slice-first/SKILL.md +32 -0
  676. package/runner/plugins/codex/skills/slice-first/references/plan-recipe.md +46 -0
  677. package/runner/plugins/codex/skills/status/SKILL.md +15 -0
  678. package/runner/plugins/codex/skills/tags/SKILL.md +24 -0
  679. package/runner/plugins/codex/skills/task/SKILL.md +28 -0
  680. package/runner/src/adapter.ts +1061 -0
  681. package/runner/src/adapters/__fixtures__/codex-app-server.ts +47 -0
  682. package/runner/src/adapters/claude-api.ts +243 -0
  683. package/runner/src/adapters/claude-context-command.ts +16 -0
  684. package/runner/src/adapters/claude-credentials.ts +22 -0
  685. package/runner/src/adapters/claude-delivery-lanes.ts +28 -0
  686. package/runner/src/adapters/claude-delivery.ts +166 -0
  687. package/runner/src/adapters/claude-effort.ts +94 -0
  688. package/runner/src/adapters/claude-exit.ts +290 -0
  689. package/runner/src/adapters/claude-headless-mirror.ts +88 -0
  690. package/runner/src/adapters/claude-headless-refusal.ts +51 -0
  691. package/runner/src/adapters/claude-headless.ts +617 -0
  692. package/runner/src/adapters/claude-monitor-delivery-plan.ts +317 -0
  693. package/runner/src/adapters/claude-permission.ts +243 -0
  694. package/runner/src/adapters/claude-prompt-gates.ts +269 -0
  695. package/runner/src/adapters/claude-prompt-hook.ts +145 -0
  696. package/runner/src/adapters/claude-session-capture.ts +293 -0
  697. package/runner/src/adapters/claude-session-probe.ts +414 -0
  698. package/runner/src/adapters/claude-shutdown.ts +190 -0
  699. package/runner/src/adapters/claude-status-detectors.ts +426 -0
  700. package/runner/src/adapters/claude-subagents.ts +93 -0
  701. package/runner/src/adapters/claude-submit-confirmation.ts +287 -0
  702. package/runner/src/adapters/claude-tmux.ts +548 -0
  703. package/runner/src/adapters/claude-token-usage-capture.ts +253 -0
  704. package/runner/src/adapters/claude-tool-policy.ts +165 -0
  705. package/runner/src/adapters/claude-transcript-tail.ts +193 -0
  706. package/runner/src/adapters/claude-transcript.ts +812 -0
  707. package/runner/src/adapters/claude.ts +1097 -0
  708. package/runner/src/adapters/codex-agent-message-capture.ts +189 -0
  709. package/runner/src/adapters/codex-app-server.ts +70 -0
  710. package/runner/src/adapters/codex-client.ts +362 -0
  711. package/runner/src/adapters/codex-effective-config.ts +87 -0
  712. package/runner/src/adapters/codex-launch-config.ts +153 -0
  713. package/runner/src/adapters/codex-rollout-context.ts +272 -0
  714. package/runner/src/adapters/codex-skill-discovery.ts +18 -0
  715. package/runner/src/adapters/codex-token-usage-capture.ts +163 -0
  716. package/runner/src/adapters/codex-tool-output.ts +53 -0
  717. package/runner/src/adapters/codex-turn-outcome.ts +27 -0
  718. package/runner/src/adapters/codex-version.ts +64 -0
  719. package/runner/src/adapters/codex.ts +1442 -0
  720. package/runner/src/adapters/incremental-file.ts +110 -0
  721. package/runner/src/adapters/opencode-api.ts +71 -0
  722. package/runner/src/adapters/opencode-context.ts +277 -0
  723. package/runner/src/adapters/opencode-model-registry.ts +126 -0
  724. package/runner/src/adapters/opencode-provider.ts +169 -0
  725. package/runner/src/adapters/opencode-server-retry.ts +50 -0
  726. package/runner/src/adapters/opencode-soft-steer.ts +302 -0
  727. package/runner/src/adapters/opencode-spawn.ts +145 -0
  728. package/runner/src/adapters/opencode-subscribe.ts +62 -0
  729. package/runner/src/adapters/opencode-token-usage-capture.ts +187 -0
  730. package/runner/src/adapters/opencode-tool-events.ts +302 -0
  731. package/runner/src/adapters/opencode-tools-config.ts +17 -0
  732. package/runner/src/adapters/opencode.ts +2264 -0
  733. package/runner/src/adapters/project-shell.ts +80 -0
  734. package/runner/src/adapters/provider-launch-config.ts +21 -0
  735. package/runner/src/adapters/provider-quota-harvest.ts +127 -0
  736. package/runner/src/adapters/service-access.ts +153 -0
  737. package/runner/src/adapters/tmux-offload.ts +207 -0
  738. package/runner/src/adapters/tool-summary.ts +214 -0
  739. package/runner/src/agent-redact.ts +75 -0
  740. package/runner/src/agent-runner/commands/dispatch.ts +185 -0
  741. package/runner/src/agent-runner/commands/lifecycle.ts +84 -0
  742. package/runner/src/agent-runner/commands/prompt.ts +284 -0
  743. package/runner/src/agent-runner/credentials.ts +208 -0
  744. package/runner/src/agent-runner/http-liveness.ts +85 -0
  745. package/runner/src/agent-runner/lifecycle-milestones.ts +207 -0
  746. package/runner/src/agent-runner/lifecycle-phase.ts +80 -0
  747. package/runner/src/agent-runner/registration.ts +163 -0
  748. package/runner/src/agent-runner/runner-context.ts +158 -0
  749. package/runner/src/agent-runner/session-mirror.ts +278 -0
  750. package/runner/src/agent-runner/task-claims.ts +116 -0
  751. package/runner/src/agent-runner/token-usage-coverage.ts +54 -0
  752. package/runner/src/agent-runner/turn-report.ts +269 -0
  753. package/runner/src/api-budget-accounting.ts +91 -0
  754. package/runner/src/area-memory.ts +255 -0
  755. package/runner/src/artifact-path-materialize.ts +52 -0
  756. package/runner/src/async-guard.ts +35 -0
  757. package/runner/src/attachment-cache-sweep.ts +31 -0
  758. package/runner/src/attachment-cache.ts +195 -0
  759. package/runner/src/background-script-reconcile.ts +106 -0
  760. package/runner/src/bounded-log.ts +59 -0
  761. package/runner/src/bounded-spawn-sync.ts +18 -0
  762. package/runner/src/busy-reconciler.ts +282 -0
  763. package/runner/src/claim-tracker.ts +140 -0
  764. package/runner/src/claude-settings.ts +167 -0
  765. package/runner/src/claude-workspace-trust.ts +289 -0
  766. package/runner/src/config.ts +547 -0
  767. package/runner/src/context-boundary.ts +101 -0
  768. package/runner/src/context-command.ts +151 -0
  769. package/runner/src/context-probe-state.ts +6 -0
  770. package/runner/src/continuation-archive.ts +179 -0
  771. package/runner/src/control-server.ts +730 -0
  772. package/runner/src/delivery-policy.ts +9 -0
  773. package/runner/src/delivery-result.ts +24 -0
  774. package/runner/src/effort-command.ts +125 -0
  775. package/runner/src/empty-turn.ts +816 -0
  776. package/runner/src/free-port.ts +25 -0
  777. package/runner/src/fs-offload.ts +26 -0
  778. package/runner/src/gate-run-config.ts +35 -0
  779. package/runner/src/gate-run-lease-probe.ts +38 -0
  780. package/runner/src/host-asset-plan.ts +450 -0
  781. package/runner/src/host-claude-config-worker.ts +34 -0
  782. package/runner/src/host-sources.ts +40 -0
  783. package/runner/src/inbox-carrier.ts +7 -0
  784. package/runner/src/index.ts +386 -0
  785. package/runner/src/launch-assembly.ts +955 -0
  786. package/runner/src/liveness.ts +105 -0
  787. package/runner/src/logger.ts +108 -0
  788. package/runner/src/mcp-outbox.ts +204 -0
  789. package/runner/src/mcp-preflight.ts +86 -0
  790. package/runner/src/mcp-source-selection.ts +54 -0
  791. package/runner/src/mcp-tool-deferral.ts +35 -0
  792. package/runner/src/mcp-vault-env.ts +25 -0
  793. package/runner/src/message-body-config/config.ts +16 -0
  794. package/runner/src/message-body-config.ts +1 -0
  795. package/runner/src/mid-turn-delivery.ts +43 -0
  796. package/runner/src/native-memory-lever.ts +329 -0
  797. package/runner/src/native-self-resume.ts +244 -0
  798. package/runner/src/outbox.ts +453 -0
  799. package/runner/src/park-post.ts +17 -0
  800. package/runner/src/pending-permission-store.ts +148 -0
  801. package/runner/src/process-meta.ts +9 -0
  802. package/runner/src/profile-home.ts +419 -0
  803. package/runner/src/profile-projection.ts +411 -0
  804. package/runner/src/project-hooks.ts +65 -0
  805. package/runner/src/project-instructions.ts +119 -0
  806. package/runner/src/provider-double.ts +59 -0
  807. package/runner/src/provider-hold.ts +228 -0
  808. package/runner/src/provider-home-marker.ts +3 -0
  809. package/runner/src/provider-home-tombstone.ts +4 -0
  810. package/runner/src/provider-input-queue.ts +134 -0
  811. package/runner/src/provider-integration.ts +93 -0
  812. package/runner/src/provider-processes.ts +17 -0
  813. package/runner/src/provider-submit-failure.ts +15 -0
  814. package/runner/src/providers.ts +35 -0
  815. package/runner/src/provisioning.ts +703 -0
  816. package/runner/src/question-decision.ts +18 -0
  817. package/runner/src/quota.ts +22 -0
  818. package/runner/src/rate-limit.ts +351 -0
  819. package/runner/src/refused-prompt-store.ts +36 -0
  820. package/runner/src/relay-injection-events.ts +161 -0
  821. package/runner/src/relay-instructions.ts +107 -0
  822. package/runner/src/relay-mcp-proxy.long-turn-e2e.ts +183 -0
  823. package/runner/src/relay-mcp-proxy.ts +704 -0
  824. package/runner/src/relay-mcp.ts +583 -0
  825. package/runner/src/reply-obligation-cache.ts +211 -0
  826. package/runner/src/response-capture-report.ts +111 -0
  827. package/runner/src/response-routing.ts +6 -0
  828. package/runner/src/runner-core.ts +2955 -0
  829. package/runner/src/runner-exit.ts +12 -0
  830. package/runner/src/runner-helpers.ts +582 -0
  831. package/runner/src/runner-insights.ts +105 -0
  832. package/runner/src/runner-mcp-proxy.ts +46 -0
  833. package/runner/src/runner-spawn-config.ts +68 -0
  834. package/runner/src/runner-stale-scratch-sweep.ts +21 -0
  835. package/runner/src/runner.ts +9 -0
  836. package/runner/src/runtime-token-file.ts +45 -0
  837. package/runner/src/runtime-token-remint.ts +56 -0
  838. package/runner/src/service-access.ts +88 -0
  839. package/runner/src/session-destroy.ts +27 -0
  840. package/runner/src/session-insights.ts +118 -0
  841. package/runner/src/session-scratch.ts +296 -0
  842. package/runner/src/spawn-input.ts +147 -0
  843. package/runner/src/startup-config-fetch.ts +48 -0
  844. package/runner/src/subreaper.ts +113 -0
  845. package/runner/src/template-resolver.ts +29 -0
  846. package/runner/src/test-run-dedup.ts +435 -0
  847. package/runner/src/transcript-context.ts +99 -0
  848. package/runner/src/transport-errors.ts +48 -0
  849. package/runner/src/turn-opener.ts +55 -0
  850. package/runner/src/turn-reconcile-event.ts +88 -0
  851. package/runner/src/vault-proxy-hop.ts +99 -0
  852. package/runner/src/version.ts +44 -0
  853. package/runner/src/web-tool-call-proxy.ts +128 -0
  854. package/runner/src/web-tools.ts +59 -0
  855. package/runner/test-support/codex-web-config-proof.ts +149 -0
  856. package/runner/test-support/effective-launch.ts +189 -0
  857. package/runner/test-support/native-context-sources.ts +77 -0
  858. package/runner/test-support/provisioning-context.ts +55 -0
  859. package/runner/test-support/recorder-double.ts +130 -0
  860. package/runner/test-support/recording-provider.ts +242 -0
  861. package/runner/test-support/runner-harness.ts +249 -0
  862. package/runner/test-support/service-access-fixture.ts +12 -0
  863. package/scripts/agent-vault/bootstrap.py +87 -0
  864. package/scripts/agent-vault/install.py +200 -0
  865. package/scripts/agent-vault/install_refresh.py +98 -0
  866. package/scripts/agent-vault/refresh.py +116 -0
  867. package/scripts/agent-vault/refresh_provision.py +109 -0
  868. package/scripts/agent-vault/refresh_store.py +54 -0
  869. package/scripts/agent-vault/runtime.py +253 -0
  870. package/scripts/agent-vault/sec-ops-activation.json +23 -0
  871. package/scripts/artifact-checkout-gc.ts +51 -0
  872. package/scripts/cd-config.ts +237 -0
  873. package/scripts/cd-deploy-debounce.ts +94 -0
  874. package/scripts/cd-deploy-worktree-gc.ts +235 -0
  875. package/scripts/cd-fleet-membership.ts +48 -0
  876. package/scripts/cd-host-epochs.ts +101 -0
  877. package/scripts/cd-hub-wake.ts +40 -0
  878. package/scripts/cd-land-boundary.ts +49 -0
  879. package/scripts/cd-watcher.ts +985 -0
  880. package/scripts/dashboard-build-marker.ts +71 -0
  881. package/scripts/fleet-sha-coverage.ts +66 -0
  882. package/scripts/gate-run-lease.ts +329 -0
  883. package/scripts/gate-slot +9 -0
  884. package/scripts/gate-slot-fleet-health.ts +60 -0
  885. package/scripts/gate.sh +6 -0
  886. package/scripts/install-bin-shim.cjs +149 -0
  887. package/scripts/orchestrator-spawn-smoke.ts +398 -0
  888. package/scripts/provisioning-matrix.ts +72 -0
  889. package/scripts/release-cd-stamp.ts +146 -0
  890. package/scripts/release-health.ts +133 -0
  891. package/scripts/release-report.ts +102 -0
  892. package/scripts/release-upgrade.ts +1296 -0
  893. package/scripts/steward-policy-cleanup.ts +73 -0
  894. package/scripts/wait-for-deploy.ts +43 -0
  895. package/src/agent-authz.ts +219 -0
  896. package/src/agent-branch-state.ts +45 -0
  897. package/src/agent-busy-health.ts +158 -0
  898. package/src/agent-card-projection.ts +86 -0
  899. package/src/agent-channel-binding.ts +49 -0
  900. package/src/agent-command-activity.ts +39 -0
  901. package/src/agent-core.ts +29 -0
  902. package/src/agent-death-diagnosis.ts +314 -0
  903. package/src/agent-drive-arbiter.ts +112 -0
  904. package/src/agent-exit-alarm.ts +10 -0
  905. package/src/agent-exit-cleanup-async.ts +69 -0
  906. package/src/agent-exit-cleanup.ts +99 -0
  907. package/src/agent-exit-report.ts +28 -0
  908. package/src/agent-issue-repo.ts +53 -0
  909. package/src/agent-launch-shape-redaction.ts +72 -0
  910. package/src/agent-lifecycle-events.ts +428 -0
  911. package/src/agent-principal.ts +14 -0
  912. package/src/agent-profile-catalog.ts +188 -0
  913. package/src/agent-ref.ts +340 -0
  914. package/src/agent-spawn.ts +45 -0
  915. package/src/agent-status-events.ts +41 -0
  916. package/src/agent-system-tags.ts +38 -0
  917. package/src/artifact-media.ts +34 -0
  918. package/src/artifact-storage.ts +245 -0
  919. package/src/artifact-text.ts +31 -0
  920. package/src/async-deadline.ts +59 -0
  921. package/src/auth/admin-routes.ts +36 -0
  922. package/src/auth/index.ts +23 -0
  923. package/src/auth/login-rate-limiter.ts +153 -0
  924. package/src/auth/password.ts +142 -0
  925. package/src/auth/provider.ts +60 -0
  926. package/src/auth/role-scopes.ts +15 -0
  927. package/src/auth/session-cookie.ts +35 -0
  928. package/src/auth/session-refresh.ts +61 -0
  929. package/src/auth/session.test-fixture.ts +10 -0
  930. package/src/auth/session.ts +107 -0
  931. package/src/authority-audit.ts +56 -0
  932. package/src/authz-meta-keys.ts +64 -0
  933. package/src/auto-spawn-dispatch.ts +154 -0
  934. package/src/automations/constants.ts +14 -0
  935. package/src/automations/cron.ts +129 -0
  936. package/src/automations/crud.ts +129 -0
  937. package/src/automations/dispatch.ts +299 -0
  938. package/src/automations/index.ts +5 -0
  939. package/src/automations/precheck.ts +124 -0
  940. package/src/automations/reconcile.ts +314 -0
  941. package/src/automations/storage.ts +200 -0
  942. package/src/automations/types.ts +16 -0
  943. package/src/automations/validation.ts +216 -0
  944. package/src/automations.ts +1 -0
  945. package/src/branch-landed.ts +761 -0
  946. package/src/browser-authz.ts +18 -0
  947. package/src/browser-config.ts +31 -0
  948. package/src/bundled-workflows.ts +48 -0
  949. package/src/bus-outbox-async.ts +16 -0
  950. package/src/bus-outbox.ts +265 -0
  951. package/src/bus-resume-commands.ts +18 -0
  952. package/src/bus.ts +792 -0
  953. package/src/canonical-repo-root.ts +131 -0
  954. package/src/cd-deploy-notify.ts +59 -0
  955. package/src/cd-host-deferrals.ts +23 -0
  956. package/src/cd-host-lag-notify.ts +36 -0
  957. package/src/cd-repository.ts +49 -0
  958. package/src/channel-output-control.ts +43 -0
  959. package/src/channel-output-validation.ts +11 -0
  960. package/src/channel-policy-health.ts +20 -0
  961. package/src/channel-target.ts +40 -0
  962. package/src/channels-host-service.ts +219 -0
  963. package/src/ci/bun-test-log.ts +41 -0
  964. package/src/ci/failed-test-files.ts +52 -0
  965. package/src/ci/gate-history.ts +66 -0
  966. package/src/ci/github-actions.ts +145 -0
  967. package/src/ci/incidents.ts +175 -0
  968. package/src/ci/index.ts +6 -0
  969. package/src/ci/job-verdict.ts +27 -0
  970. package/src/ci/land-boundary-ci.ts +104 -0
  971. package/src/ci/monitor.ts +528 -0
  972. package/src/ci/not-triggered.ts +116 -0
  973. package/src/ci/notifications.ts +234 -0
  974. package/src/ci/provider.ts +87 -0
  975. package/src/ci/root-missing.ts +35 -0
  976. package/src/ci/transition-notify.ts +279 -0
  977. package/src/ci-monitor.ts +7 -0
  978. package/src/claude-api-budget-async.ts +11 -0
  979. package/src/claude-api-budget-config.ts +23 -0
  980. package/src/claude-api-expiry.ts +70 -0
  981. package/src/claude-api-overflow.ts +146 -0
  982. package/src/claude-app/config.ts +13 -0
  983. package/src/claude-app/oauth-http.ts +127 -0
  984. package/src/claude-app/oauth-provider.ts +136 -0
  985. package/src/claude-app/passphrase.ts +14 -0
  986. package/src/claude-app-auth.ts +65 -0
  987. package/src/claude-app-gateway.ts +68 -0
  988. package/src/cli/_shared.ts +136 -0
  989. package/src/cli/agent-detect.ts +175 -0
  990. package/src/cli/agent-guide.ts +152 -0
  991. package/src/cli/agent-keep.ts +37 -0
  992. package/src/cli/agent-meta.ts +111 -0
  993. package/src/cli/artifact.ts +154 -0
  994. package/src/cli/browser.ts +56 -0
  995. package/src/cli/channel-output.ts +17 -0
  996. package/src/cli/channels-host.ts +13 -0
  997. package/src/cli/channels.ts +16 -0
  998. package/src/cli/claude-api-budget.ts +41 -0
  999. package/src/cli/context-probe.ts +90 -0
  1000. package/src/cli/credentials.ts +1 -0
  1001. package/src/cli/daemon.ts +125 -0
  1002. package/src/cli/dev.ts +185 -0
  1003. package/src/cli/ephemeral.ts +159 -0
  1004. package/src/cli/hosts.ts +41 -0
  1005. package/src/cli/index.ts +442 -0
  1006. package/src/cli/introspect.ts +74 -0
  1007. package/src/cli/maintenance.ts +24 -0
  1008. package/src/cli/merges.ts +78 -0
  1009. package/src/cli/message.ts +229 -0
  1010. package/src/cli/orchestrator.ts +384 -0
  1011. package/src/cli/pair.ts +135 -0
  1012. package/src/cli/profile.ts +35 -0
  1013. package/src/cli/project.ts +52 -0
  1014. package/src/cli/prompts.ts +23 -0
  1015. package/src/cli/provider-enablement.ts +42 -0
  1016. package/src/cli/provider.ts +230 -0
  1017. package/src/cli/recipe.ts +110 -0
  1018. package/src/cli/reply.ts +130 -0
  1019. package/src/cli/roles.ts +60 -0
  1020. package/src/cli/routing-policy.ts +20 -0
  1021. package/src/cli/run.ts +96 -0
  1022. package/src/cli/secrets-backup.ts +85 -0
  1023. package/src/cli/secrets.ts +55 -0
  1024. package/src/cli/service-access.ts +23 -0
  1025. package/src/cli/setup.ts +57 -0
  1026. package/src/cli/shutdown.ts +16 -0
  1027. package/src/cli/spawn.ts +31 -0
  1028. package/src/cli/state.ts +73 -0
  1029. package/src/cli/steward.ts +58 -0
  1030. package/src/cli/task.ts +151 -0
  1031. package/src/cli/team.ts +18 -0
  1032. package/src/cli/timers.ts +53 -0
  1033. package/src/cli/token.ts +72 -0
  1034. package/src/cli/upgrade.ts +200 -0
  1035. package/src/cli/web-tool-calls.ts +17 -0
  1036. package/src/cli/workflow.ts +46 -0
  1037. package/src/cli/workspace.ts +544 -0
  1038. package/src/cli.ts +4 -0
  1039. package/src/codex-reset-consume.ts +274 -0
  1040. package/src/codex-wham-quota.ts +177 -0
  1041. package/src/command-events.ts +69 -0
  1042. package/src/command-secrets.ts +198 -0
  1043. package/src/command-ttl.ts +63 -0
  1044. package/src/commands-db.ts +1200 -0
  1045. package/src/commands-landing-async.ts +166 -0
  1046. package/src/commands-write.ts +168 -0
  1047. package/src/comment-blocks.ts +84 -0
  1048. package/src/compaction-watch.ts +259 -0
  1049. package/src/config-model-selection.ts +14 -0
  1050. package/src/config-store-write.ts +39 -0
  1051. package/src/config-store.ts +1253 -0
  1052. package/src/config.ts +939 -0
  1053. package/src/connector-boot-health.ts +27 -0
  1054. package/src/connector-paths.ts +47 -0
  1055. package/src/connectors.ts +547 -0
  1056. package/src/content-security.ts +12 -0
  1057. package/src/context-advisory.ts +279 -0
  1058. package/src/context-block-rendered-at.ts +33 -0
  1059. package/src/context-injection-channel.ts +49 -0
  1060. package/src/context-router.ts +121 -0
  1061. package/src/context-threshold-config.ts +52 -0
  1062. package/src/continuation-replay-edge.ts +30 -0
  1063. package/src/continuity-working-state.ts +107 -0
  1064. package/src/contracts.ts +109 -0
  1065. package/src/coordinator-authority.ts +100 -0
  1066. package/src/coordinator-digest-config.ts +20 -0
  1067. package/src/cosmos-events.ts +164 -0
  1068. package/src/daemon.ts +570 -0
  1069. package/src/dashboard-notifications.ts +169 -0
  1070. package/src/dashboard-presence.ts +86 -0
  1071. package/src/db/activity-async.ts +58 -0
  1072. package/src/db/activity-read-ops.ts +42 -0
  1073. package/src/db/activity-write-ops.ts +44 -0
  1074. package/src/db/activity.ts +454 -0
  1075. package/src/db/agent-card-read.ts +98 -0
  1076. package/src/db/agent-effort-state.ts +146 -0
  1077. package/src/db/agent-exit-cleanup.ts +94 -0
  1078. package/src/db/agent-exit-report.ts +33 -0
  1079. package/src/db/agent-gate-runs-write-ops.ts +7 -0
  1080. package/src/db/agent-gate-runs.ts +96 -0
  1081. package/src/db/agent-identity-archive.test-fixture.ts +12 -0
  1082. package/src/db/agent-identity-archive.ts +75 -0
  1083. package/src/db/agent-keep.ts +21 -0
  1084. package/src/db/agent-lifecycle-write-ops.ts +93 -0
  1085. package/src/db/agent-meta.ts +74 -0
  1086. package/src/db/agent-outstanding-work.ts +154 -0
  1087. package/src/db/agent-ownership-async.ts +45 -0
  1088. package/src/db/agent-ownership-read-ops.ts +12 -0
  1089. package/src/db/agent-ownership-write-ops.ts +54 -0
  1090. package/src/db/agent-ownership.ts +200 -0
  1091. package/src/db/agent-predicates.ts +64 -0
  1092. package/src/db/agent-read.ts +39 -0
  1093. package/src/db/agent-search-async.ts +21 -0
  1094. package/src/db/agent-search-read-ops.ts +8 -0
  1095. package/src/db/agent-search.ts +162 -0
  1096. package/src/db/agent-shutdown-in-flight.ts +21 -0
  1097. package/src/db/agents-async.ts +337 -0
  1098. package/src/db/agents-prune-write-op.ts +37 -0
  1099. package/src/db/agents-read-ops.ts +33 -0
  1100. package/src/db/agents-write-ops.ts +188 -0
  1101. package/src/db/agents.ts +1284 -0
  1102. package/src/db/api-budget-schema.ts +144 -0
  1103. package/src/db/api-budget-usage-migration.ts +31 -0
  1104. package/src/db/api-budget-usage.ts +40 -0
  1105. package/src/db/artifact-browse.ts +145 -0
  1106. package/src/db/artifact-retention.ts +115 -0
  1107. package/src/db/artifacts-async.ts +81 -0
  1108. package/src/db/artifacts-read-ops.ts +24 -0
  1109. package/src/db/artifacts-write-ops.ts +51 -0
  1110. package/src/db/artifacts.ts +307 -0
  1111. package/src/db/auth-identities.ts +128 -0
  1112. package/src/db/backlog.ts +172 -0
  1113. package/src/db/blackboard.ts +171 -0
  1114. package/src/db/broadcast-scope.ts +63 -0
  1115. package/src/db/broker-memory-migration.ts +71 -0
  1116. package/src/db/browser-sessions.ts +89 -0
  1117. package/src/db/budget-lineage.ts +26 -0
  1118. package/src/db/bus-outbox-read-ops.ts +29 -0
  1119. package/src/db/bus-outbox-write-ops.ts +58 -0
  1120. package/src/db/bus-prune-async.ts +16 -0
  1121. package/src/db/busy-regression.worker.ts +48 -0
  1122. package/src/db/caller-lifecycle.ts +24 -0
  1123. package/src/db/carry-pending-marker.ts +129 -0
  1124. package/src/db/carry-pending.ts +145 -0
  1125. package/src/db/channel-binding-delete.ts +15 -0
  1126. package/src/db/channel-egress-allowlist.ts +200 -0
  1127. package/src/db/channel-identity.ts +38 -0
  1128. package/src/db/channel-runtime.ts +19 -0
  1129. package/src/db/channels-async.ts +55 -0
  1130. package/src/db/channels-read-ops.ts +13 -0
  1131. package/src/db/channels-session-fence.ts +18 -0
  1132. package/src/db/channels-write-ops.ts +46 -0
  1133. package/src/db/channels.ts +654 -0
  1134. package/src/db/checkout-refresh.ts +36 -0
  1135. package/src/db/ci-gate-runs.ts +92 -0
  1136. package/src/db/ci-incidents-async.ts +76 -0
  1137. package/src/db/ci-incidents-read-ops.ts +15 -0
  1138. package/src/db/ci-incidents-write-ops.ts +57 -0
  1139. package/src/db/ci-incidents.ts +667 -0
  1140. package/src/db/ci-sha-observations-schema.ts +33 -0
  1141. package/src/db/claim-sql.ts +44 -0
  1142. package/src/db/claude-api-budget-ops.ts +15 -0
  1143. package/src/db/claude-api-budget-reconcile.ts +39 -0
  1144. package/src/db/claude-api-budget.ts +198 -0
  1145. package/src/db/claude-app-oauth.ts +74 -0
  1146. package/src/db/claude-catalog-reconcile.ts +27 -0
  1147. package/src/db/codex-reset-consumes-async.ts +9 -0
  1148. package/src/db/codex-reset-consumes-read-ops.ts +5 -0
  1149. package/src/db/codex-reset-consumes-write-ops.ts +22 -0
  1150. package/src/db/codex-reset-consumes.ts +122 -0
  1151. package/src/db/command-agent-profiles.ts +58 -0
  1152. package/src/db/command-row.ts +88 -0
  1153. package/src/db/command-terminal-status.ts +33 -0
  1154. package/src/db/command-vault-pruning.ts +30 -0
  1155. package/src/db/commands-cancel-write.ts +45 -0
  1156. package/src/db/commands-claim-admission.ts +144 -0
  1157. package/src/db/commands-effort-read.ts +58 -0
  1158. package/src/db/commands-host-land-admission.ts +106 -0
  1159. package/src/db/commands-kernel-landing.ts +268 -0
  1160. package/src/db/commands-land-occupancy.ts +40 -0
  1161. package/src/db/commands-land-wait.ts +38 -0
  1162. package/src/db/commands-landing-completeness.ts +66 -0
  1163. package/src/db/commands-landing-tuples.ts +22 -0
  1164. package/src/db/commands-landing.ts +1046 -0
  1165. package/src/db/commands-leaseless-sweep.ts +72 -0
  1166. package/src/db/commands-list-query.ts +47 -0
  1167. package/src/db/commands-planner-stats.test-fixture.ts +97 -0
  1168. package/src/db/commands-read-ops.ts +49 -0
  1169. package/src/db/commands-scheduler-recovery.ts +71 -0
  1170. package/src/db/commands-spawn-read.ts +33 -0
  1171. package/src/db/commands-write-ops.ts +307 -0
  1172. package/src/db/config-store-write-ops.ts +60 -0
  1173. package/src/db/connection-instrumentation.ts +76 -0
  1174. package/src/db/connection.ts +532 -0
  1175. package/src/db/context-reset-carry.ts +144 -0
  1176. package/src/db/continuation-archive-purge.ts +48 -0
  1177. package/src/db/continuation-archive-search.ts +249 -0
  1178. package/src/db/continuation-archives-async.ts +59 -0
  1179. package/src/db/continuation-archives-read-ops.ts +10 -0
  1180. package/src/db/continuation-archives-write-ops.ts +17 -0
  1181. package/src/db/continuation-archives.ts +158 -0
  1182. package/src/db/continuations.ts +201 -0
  1183. package/src/db/coordinator-delivery.ts +10 -0
  1184. package/src/db/coordinator-digest-async.ts +13 -0
  1185. package/src/db/coordinator-digest-flush.ts +6 -0
  1186. package/src/db/coordinator-digest.ts +145 -0
  1187. package/src/db/coordinator-seat.ts +130 -0
  1188. package/src/db/crash-reports.ts +115 -0
  1189. package/src/db/db-error-registry.ts +290 -0
  1190. package/src/db/db-reader-worker.ts +91 -0
  1191. package/src/db/db-writer-worker.ts +66 -0
  1192. package/src/db/deferred-lineage-report-queries.ts +28 -0
  1193. package/src/db/deferred-lineage-reports.ts +44 -0
  1194. package/src/db/delivery-async.ts +102 -0
  1195. package/src/db/delivery-expiration-health.ts +97 -0
  1196. package/src/db/delivery-expiration.ts +52 -0
  1197. package/src/db/delivery-quality-async.ts +23 -0
  1198. package/src/db/delivery-quality.ts +116 -0
  1199. package/src/db/delivery-read-ops.ts +26 -0
  1200. package/src/db/delivery-report-echo.ts +53 -0
  1201. package/src/db/delivery-write-ops.ts +77 -0
  1202. package/src/db/delivery.ts +545 -0
  1203. package/src/db/deploy-events-async.ts +66 -0
  1204. package/src/db/deploy-events-read-ops.ts +25 -0
  1205. package/src/db/deploy-events-write-ops.ts +81 -0
  1206. package/src/db/deploy-events.ts +554 -0
  1207. package/src/db/deploy-task-completion.ts +48 -0
  1208. package/src/db/drive-leases.ts +165 -0
  1209. package/src/db/durable-payloads.ts +92 -0
  1210. package/src/db/fixtures/writer-shutdown.ts +28 -0
  1211. package/src/db/fts-query.ts +8 -0
  1212. package/src/db/gate-policy.ts +27 -0
  1213. package/src/db/gate-runs.ts +181 -0
  1214. package/src/db/gateway.ts +513 -0
  1215. package/src/db/generation-launch.ts +82 -0
  1216. package/src/db/guides-search.ts +18 -0
  1217. package/src/db/history-delivery-settle.ts +57 -0
  1218. package/src/db/hosts-async.ts +150 -0
  1219. package/src/db/hosts-backfill.ts +91 -0
  1220. package/src/db/hosts-read-ops.ts +21 -0
  1221. package/src/db/hosts-registration.ts +452 -0
  1222. package/src/db/hosts-views.ts +259 -0
  1223. package/src/db/hosts-write-ops.ts +108 -0
  1224. package/src/db/hosts.ts +251 -0
  1225. package/src/db/human-address.ts +6 -0
  1226. package/src/db/idle-digest.ts +54 -0
  1227. package/src/db/idle-self-care.ts +100 -0
  1228. package/src/db/inbox-async.ts +29 -0
  1229. package/src/db/inbox-read-ops.ts +8 -0
  1230. package/src/db/inbox-write-ops.ts +29 -0
  1231. package/src/db/inbox.ts +197 -0
  1232. package/src/db/index.ts +147 -0
  1233. package/src/db/injection-attempts.ts +41 -0
  1234. package/src/db/insights-read-ops.ts +12 -0
  1235. package/src/db/insights-write-ops.ts +16 -0
  1236. package/src/db/integrations.ts +297 -0
  1237. package/src/db/issue-associations.ts +136 -0
  1238. package/src/db/issue-lifecycle.ts +199 -0
  1239. package/src/db/issue-refs.ts +312 -0
  1240. package/src/db/kernel-event-log-schema.ts +14 -0
  1241. package/src/db/kernel-landing-admission.ts +15 -0
  1242. package/src/db/kernel-landing-command-authority.ts +222 -0
  1243. package/src/db/kernel-persistence-async.ts +48 -0
  1244. package/src/db/kernel-persistence-read-ops.ts +24 -0
  1245. package/src/db/kernel-persistence-write-ops.ts +43 -0
  1246. package/src/db/land-boundaries.ts +138 -0
  1247. package/src/db/land-executing.ts +147 -0
  1248. package/src/db/land-gate-attestations.ts +161 -0
  1249. package/src/db/land-incident-eligibility.ts +44 -0
  1250. package/src/db/land-queue-trace.ts +28 -0
  1251. package/src/db/land-stack.ts +80 -0
  1252. package/src/db/land-telemetry-schema.ts +50 -0
  1253. package/src/db/land-telemetry-write-ops.ts +16 -0
  1254. package/src/db/land-telemetry.ts +114 -0
  1255. package/src/db/land-withdrawal-state.ts +26 -0
  1256. package/src/db/landed-merge-evidence.ts +72 -0
  1257. package/src/db/landed-task-attribution.ts +23 -0
  1258. package/src/db/landing-audit.ts +18 -0
  1259. package/src/db/landing-controllers.ts +67 -0
  1260. package/src/db/landing-ingress-async.ts +102 -0
  1261. package/src/db/landing-ingress-read-ops.ts +8 -0
  1262. package/src/db/landing-ingress-write-ops.ts +42 -0
  1263. package/src/db/landing-ingress.ts +380 -0
  1264. package/src/db/landing-read-ops.ts +18 -0
  1265. package/src/db/landing-receipts.ts +206 -0
  1266. package/src/db/landing-schema.ts +479 -0
  1267. package/src/db/launch-facts.ts +60 -0
  1268. package/src/db/legacy-landing-publication.ts +103 -0
  1269. package/src/db/legacy-message-backfill.ts +90 -0
  1270. package/src/db/lifecycle-seq.ts +127 -0
  1271. package/src/db/lifecycle-write-window.ts +216 -0
  1272. package/src/db/lineage-pair-dedupe-sql.ts +80 -0
  1273. package/src/db/lineage-startup-captures.ts +43 -0
  1274. package/src/db/long-running-work-async.ts +12 -0
  1275. package/src/db/long-running-work-write-ops.ts +8 -0
  1276. package/src/db/long-running-work.ts +78 -0
  1277. package/src/db/macos-privacy.ts +72 -0
  1278. package/src/db/main-connection-writes.ts +44 -0
  1279. package/src/db/main-thread-state.ts +38 -0
  1280. package/src/db/maintenance-health.ts +560 -0
  1281. package/src/db/maintenance-indexes.ts +37 -0
  1282. package/src/db/maintenance-job-runs-async.ts +15 -0
  1283. package/src/db/maintenance-job-runs.ts +62 -0
  1284. package/src/db/maintenance-job-write-ops.ts +10 -0
  1285. package/src/db/maintenance-pauses.ts +224 -0
  1286. package/src/db/maintenance-worker.ts +111 -0
  1287. package/src/db/mappers.ts +422 -0
  1288. package/src/db/memory-preamble-read.ts +13 -0
  1289. package/src/db/memory-resolve.ts +209 -0
  1290. package/src/db/memory-retirement-plan.ts +31 -0
  1291. package/src/db/memory-retirement.ts +15 -0
  1292. package/src/db/merge-drain-async.ts +8 -0
  1293. package/src/db/merge-lease.ts +225 -0
  1294. package/src/db/message-claims.ts +16 -0
  1295. package/src/db/message-queries.ts +16 -0
  1296. package/src/db/message-read-mark.ts +32 -0
  1297. package/src/db/message-reads-async.ts +46 -0
  1298. package/src/db/message-reads-read-ops.ts +35 -0
  1299. package/src/db/message-reads-write-ops.ts +27 -0
  1300. package/src/db/message-reads.ts +553 -0
  1301. package/src/db/message-send-suppression.ts +58 -0
  1302. package/src/db/message-send.ts +76 -0
  1303. package/src/db/message-thread.ts +15 -0
  1304. package/src/db/message-write-ops.ts +100 -0
  1305. package/src/db/messages-async.ts +31 -0
  1306. package/src/db/messages-read-ops.ts +14 -0
  1307. package/src/db/messages.ts +1201 -0
  1308. package/src/db/migration-marker.ts +13 -0
  1309. package/src/db/migrations.ts +1761 -0
  1310. package/src/db/notification-subscriptions.ts +282 -0
  1311. package/src/db/on-loop-write-transaction.ts +34 -0
  1312. package/src/db/operator-actions-schema.ts +10 -0
  1313. package/src/db/operator-decisions.ts +180 -0
  1314. package/src/db/orchestrators-async.ts +61 -0
  1315. package/src/db/orchestrators-read-ops.ts +7 -0
  1316. package/src/db/orchestrators-write-ops.ts +58 -0
  1317. package/src/db/orchestrators.ts +497 -0
  1318. package/src/db/outbox-codec.ts +16 -0
  1319. package/src/db/pair-maintenance.ts +9 -0
  1320. package/src/db/pairs-async.ts +10 -0
  1321. package/src/db/pairs-write-ops.ts +8 -0
  1322. package/src/db/pairs.ts +264 -0
  1323. package/src/db/pending-relay-runs.ts +73 -0
  1324. package/src/db/plan-blurb-migration.ts +158 -0
  1325. package/src/db/plan-ci-blocks.ts +28 -0
  1326. package/src/db/plan-completion.ts +55 -0
  1327. package/src/db/plan-conformance.ts +104 -0
  1328. package/src/db/plan-dispatch-schema.ts +17 -0
  1329. package/src/db/plan-dispatch-store.ts +112 -0
  1330. package/src/db/plan-external.ts +79 -0
  1331. package/src/db/plan-graph-async.ts +23 -0
  1332. package/src/db/plan-graph-read-ops.ts +16 -0
  1333. package/src/db/plan-graph-write-ops.ts +37 -0
  1334. package/src/db/plan-graph.ts +403 -0
  1335. package/src/db/plan-import-schema.ts +19 -0
  1336. package/src/db/plan-import.ts +211 -0
  1337. package/src/db/plan-task-create.ts +44 -0
  1338. package/src/db/planner-stats.ts +123 -0
  1339. package/src/db/plans-async.ts +22 -0
  1340. package/src/db/plans-read-ops.ts +11 -0
  1341. package/src/db/plans-write-ops.ts +9 -0
  1342. package/src/db/plans.ts +385 -0
  1343. package/src/db/policy-runtime.ts +14 -0
  1344. package/src/db/presence-buffer.ts +111 -0
  1345. package/src/db/presence-loop.ts +58 -0
  1346. package/src/db/presence-write-ops.ts +28 -0
  1347. package/src/db/presence-write.ts +38 -0
  1348. package/src/db/profile-artifact-backfill-migration.ts +32 -0
  1349. package/src/db/profile-host-sources-migration.ts +150 -0
  1350. package/src/db/profile-legacy-project-instructions-migration.ts +41 -0
  1351. package/src/db/profile-name-case-migration.ts +46 -0
  1352. package/src/db/profile-override-migration.ts +200 -0
  1353. package/src/db/profile-project-ack-migration.ts +55 -0
  1354. package/src/db/profile-slim-migration.ts +125 -0
  1355. package/src/db/profile-slim-plan.ts +190 -0
  1356. package/src/db/project-entries-async.ts +18 -0
  1357. package/src/db/project-entries-read-ops.ts +28 -0
  1358. package/src/db/project-entries.ts +514 -0
  1359. package/src/db/project-entry-search.ts +63 -0
  1360. package/src/db/project-identity.ts +16 -0
  1361. package/src/db/project-journal.ts +256 -0
  1362. package/src/db/project-lifecycle.ts +80 -0
  1363. package/src/db/project-memory-group-migration.ts +76 -0
  1364. package/src/db/project-phantom-merge.ts +228 -0
  1365. package/src/db/project-root-schema.ts +30 -0
  1366. package/src/db/project-trust-approvals.ts +98 -0
  1367. package/src/db/projects-async.ts +50 -0
  1368. package/src/db/projects-read-ops.ts +59 -0
  1369. package/src/db/projects-write-ops.ts +74 -0
  1370. package/src/db/projects.ts +629 -0
  1371. package/src/db/prompt-templates-schema.ts +23 -0
  1372. package/src/db/prompt-templates.ts +138 -0
  1373. package/src/db/prompt-version-migration.ts +115 -0
  1374. package/src/db/prompt-versions-ops.ts +17 -0
  1375. package/src/db/prompt-versions.ts +167 -0
  1376. package/src/db/provider-catalog-async.ts +31 -0
  1377. package/src/db/provider-catalog-read-ops.ts +8 -0
  1378. package/src/db/provider-catalog-write-ops.ts +16 -0
  1379. package/src/db/provider-config-history.ts +49 -0
  1380. package/src/db/provider-config-secrets.ts +117 -0
  1381. package/src/db/provider-flap-messages-async.ts +6 -0
  1382. package/src/db/provider-flap-messages-write-ops.ts +13 -0
  1383. package/src/db/provider-flap-messages.ts +19 -0
  1384. package/src/db/provider-quota-advisory-types.ts +39 -0
  1385. package/src/db/provider-quota-burn.ts +322 -0
  1386. package/src/db/provider-quota-effect-occurrences.ts +13 -0
  1387. package/src/db/provider-quota-lease-write-ops.ts +24 -0
  1388. package/src/db/provider-quota-leases-async.ts +35 -0
  1389. package/src/db/provider-quota-leases.ts +174 -0
  1390. package/src/db/provider-quota-matrix-cache.ts +85 -0
  1391. package/src/db/provider-quota-matrix-fetch.ts +85 -0
  1392. package/src/db/provider-quota-merge.ts +90 -0
  1393. package/src/db/provider-quota-notification-write.ts +98 -0
  1394. package/src/db/provider-quota-prune.ts +35 -0
  1395. package/src/db/provider-quota-read-async.ts +77 -0
  1396. package/src/db/provider-quota-read-ops.ts +56 -0
  1397. package/src/db/provider-quota-recipients.ts +38 -0
  1398. package/src/db/provider-quota-state.ts +139 -0
  1399. package/src/db/provider-quota-visibility.ts +22 -0
  1400. package/src/db/provider-quota-write-ops.ts +71 -0
  1401. package/src/db/provider-quotas.ts +726 -0
  1402. package/src/db/provider-secret-scrub.ts +64 -0
  1403. package/src/db/provisioning-assets-migration.ts +85 -0
  1404. package/src/db/provisioning-async.ts +116 -0
  1405. package/src/db/provisioning-callmux.ts +184 -0
  1406. package/src/db/provisioning-github-api.ts +16 -0
  1407. package/src/db/provisioning-github-import-target.ts +29 -0
  1408. package/src/db/provisioning-mcp-validation.ts +103 -0
  1409. package/src/db/provisioning-read-ops.ts +23 -0
  1410. package/src/db/provisioning-registry.ts +1122 -0
  1411. package/src/db/provisioning-resolve.ts +321 -0
  1412. package/src/db/provisioning-write-ops.ts +44 -0
  1413. package/src/db/qa-skill-seed.ts +27 -0
  1414. package/src/db/quota-lease-busy.worker.ts +62 -0
  1415. package/src/db/read-op-marshaling-fixtures.ts +724 -0
  1416. package/src/db/read-ops.ts +266 -0
  1417. package/src/db/reader-pool.ts +252 -0
  1418. package/src/db/recipe-read-ops.ts +15 -0
  1419. package/src/db/recipe-write-ops.ts +68 -0
  1420. package/src/db/reply-obligations.ts +265 -0
  1421. package/src/db/report-suppression.ts +126 -0
  1422. package/src/db/restart-epoch-admin-async.ts +14 -0
  1423. package/src/db/restart-epoch-admin.ts +67 -0
  1424. package/src/db/restart-epoch-admission.ts +114 -0
  1425. package/src/db/restart-epoch-async.ts +80 -0
  1426. package/src/db/restart-epoch-begin.ts +177 -0
  1427. package/src/db/restart-epoch-read-ops.ts +40 -0
  1428. package/src/db/restart-epoch-release.ts +248 -0
  1429. package/src/db/restart-epoch-write-ops.ts +117 -0
  1430. package/src/db/restart-epoch.test-fixture.ts +37 -0
  1431. package/src/db/restart-epoch.ts +623 -0
  1432. package/src/db/restart-sensitive.ts +81 -0
  1433. package/src/db/retention.ts +423 -0
  1434. package/src/db/review-ref-cleanups.ts +306 -0
  1435. package/src/db/routing-policy.ts +64 -0
  1436. package/src/db/scheduled-timer-projection.ts +123 -0
  1437. package/src/db/scheduled-timer-quota-chaos-worker.ts +84 -0
  1438. package/src/db/scheduled-timers.ts +521 -0
  1439. package/src/db/schema.ts +1472 -0
  1440. package/src/db/scoped-message-reads.ts +31 -0
  1441. package/src/db/secrets-broker-async.ts +31 -0
  1442. package/src/db/secrets-broker-read-ops.ts +8 -0
  1443. package/src/db/secrets-broker-write-ops.ts +23 -0
  1444. package/src/db/secrets-broker.ts +221 -0
  1445. package/src/db/secrets-vault-import.ts +37 -0
  1446. package/src/db/secrets-vault-key-lifecycle.ts +64 -0
  1447. package/src/db/secrets-vault-metadata.ts +37 -0
  1448. package/src/db/secrets-vault-migration.ts +53 -0
  1449. package/src/db/secrets-vault-write.ts +97 -0
  1450. package/src/db/secrets-vault.ts +69 -0
  1451. package/src/db/self-effort-permissions.ts +50 -0
  1452. package/src/db/service-access-read-ops.ts +10 -0
  1453. package/src/db/services-async.ts +29 -0
  1454. package/src/db/services-read-ops.ts +134 -0
  1455. package/src/db/services-write-ops.ts +74 -0
  1456. package/src/db/shared.ts +7 -0
  1457. package/src/db/shipping-board-async.ts +6 -0
  1458. package/src/db/shipping-board.ts +319 -0
  1459. package/src/db/shipping-stats-async.ts +24 -0
  1460. package/src/db/shipping-stats-histogram.ts +45 -0
  1461. package/src/db/shipping-stats-schema.ts +8 -0
  1462. package/src/db/shipping-stats.ts +265 -0
  1463. package/src/db/snapshot-ledger-prune.ts +53 -0
  1464. package/src/db/spawn-budget.ts +145 -0
  1465. package/src/db/spawn-command-busy.worker.ts +71 -0
  1466. package/src/db/spawn-delegation-state.ts +124 -0
  1467. package/src/db/spawn-delegation.ts +114 -0
  1468. package/src/db/spawn-host-cap.ts +29 -0
  1469. package/src/db/spawn-in-flight-count.ts +37 -0
  1470. package/src/db/spawn-in-flight.ts +15 -0
  1471. package/src/db/spawn-process-evidence.ts +68 -0
  1472. package/src/db/spawn-project-target.ts +57 -0
  1473. package/src/db/spawn-registration-confirmed-backfill.ts +71 -0
  1474. package/src/db/spawn-request-id-migration.ts +36 -0
  1475. package/src/db/spawn-request-id.ts +35 -0
  1476. package/src/db/spawn-request-reuse.ts +28 -0
  1477. package/src/db/spawn-tree-cleanup.ts +92 -0
  1478. package/src/db/spawn-tree.ts +29 -0
  1479. package/src/db/sql-audit-indexes.ts +77 -0
  1480. package/src/db/stalled-message-delivery.ts +52 -0
  1481. package/src/db/startup-write-lock.ts +36 -0
  1482. package/src/db/stats-async.ts +235 -0
  1483. package/src/db/stats-health-read-model.ts +225 -0
  1484. package/src/db/stats-read-ops.ts +25 -0
  1485. package/src/db/stats.ts +633 -0
  1486. package/src/db/target-host.ts +25 -0
  1487. package/src/db/task-async.ts +165 -0
  1488. package/src/db/task-comment-through-async.ts +14 -0
  1489. package/src/db/task-comment-through-read-ops.ts +4 -0
  1490. package/src/db/task-comment-through-write-ops.ts +7 -0
  1491. package/src/db/task-comment-through.ts +123 -0
  1492. package/src/db/task-core.ts +44 -0
  1493. package/src/db/task-deliverables-async.ts +60 -0
  1494. package/src/db/task-deliverables-read-ops.ts +4 -0
  1495. package/src/db/task-deliverables-write-ops.ts +37 -0
  1496. package/src/db/task-deliverables.ts +387 -0
  1497. package/src/db/task-dependency-cycle.worker.ts +43 -0
  1498. package/src/db/task-detail-projection.ts +91 -0
  1499. package/src/db/task-entity-async.ts +252 -0
  1500. package/src/db/task-entity-read-ops.ts +51 -0
  1501. package/src/db/task-entity-write-ops.ts +197 -0
  1502. package/src/db/task-entity.ts +1143 -0
  1503. package/src/db/task-execution.ts +20 -0
  1504. package/src/db/task-issue-relation.ts +23 -0
  1505. package/src/db/task-issue-source.ts +46 -0
  1506. package/src/db/task-land-withdraw-async.ts +10 -0
  1507. package/src/db/task-land-withdraw-read-ops.ts +13 -0
  1508. package/src/db/task-land-withdraw-read.ts +47 -0
  1509. package/src/db/task-land-withdraw-write-ops.ts +31 -0
  1510. package/src/db/task-land-withdraw.ts +423 -0
  1511. package/src/db/task-list.ts +113 -0
  1512. package/src/db/task-maintenance.ts +25 -0
  1513. package/src/db/task-memory-async.ts +19 -0
  1514. package/src/db/task-memory-read-ops.ts +9 -0
  1515. package/src/db/task-memory-write-ops.ts +23 -0
  1516. package/src/db/task-memory.ts +170 -0
  1517. package/src/db/task-owner.ts +126 -0
  1518. package/src/db/task-read-ops.ts +44 -0
  1519. package/src/db/task-review-hold-async.ts +20 -0
  1520. package/src/db/task-review-hold-delivery.ts +58 -0
  1521. package/src/db/task-review-hold-write-ops.ts +26 -0
  1522. package/src/db/task-review-hold.ts +615 -0
  1523. package/src/db/task-state-reconcile-async.ts +12 -0
  1524. package/src/db/task-state-reconcile.ts +163 -0
  1525. package/src/db/task-validation.ts +71 -0
  1526. package/src/db/task-write-ops.ts +145 -0
  1527. package/src/db/tasks.ts +528 -0
  1528. package/src/db/team-epics.ts +37 -0
  1529. package/src/db/team-idle.ts +35 -0
  1530. package/src/db/team-project.ts +69 -0
  1531. package/src/db/team-spawn-delegation-read.ts +36 -0
  1532. package/src/db/team-spawn-delegation.ts +49 -0
  1533. package/src/db/teams-async.ts +58 -0
  1534. package/src/db/teams-read-ops.ts +27 -0
  1535. package/src/db/teams-write-ops.ts +17 -0
  1536. package/src/db/teams.ts +504 -0
  1537. package/src/db/terminal-events.ts +113 -0
  1538. package/src/db/timer-async.ts +523 -0
  1539. package/src/db/timer-cutover-core.ts +545 -0
  1540. package/src/db/timer-cutover-report-read-ops.ts +211 -0
  1541. package/src/db/timer-cutover-write-ops.ts +282 -0
  1542. package/src/db/timer-ingress-payload.ts +160 -0
  1543. package/src/db/timer-ingress-read-ops.ts +39 -0
  1544. package/src/db/timer-ingress-request-keys.ts +282 -0
  1545. package/src/db/timer-ingress-write-ops.ts +154 -0
  1546. package/src/db/timer-ingress.ts +515 -0
  1547. package/src/db/timer-message-cutover.ts +322 -0
  1548. package/src/db/timer-message-fire-deadletter.ts +136 -0
  1549. package/src/db/timer-message-fire.ts +209 -0
  1550. package/src/db/timer-message-flip-chaos-worker.ts +89 -0
  1551. package/src/db/timer-message-flip-consistency.ts +147 -0
  1552. package/src/db/timer-message-flip.ts +676 -0
  1553. package/src/db/timer-message-participant.ts +138 -0
  1554. package/src/db/timer-migrate-input.ts +92 -0
  1555. package/src/db/timer-operator-async.ts +177 -0
  1556. package/src/db/timer-operator-read-ops.ts +21 -0
  1557. package/src/db/timer-operator-stage10.ts +119 -0
  1558. package/src/db/timer-operator-write-ops.ts +116 -0
  1559. package/src/db/timer-operator.ts +393 -0
  1560. package/src/db/timer-read-ops.ts +147 -0
  1561. package/src/db/timer-transfer-errors.ts +10 -0
  1562. package/src/db/timer-transfer-write-ops.ts +43 -0
  1563. package/src/db/timer-verifier-schema.ts +29 -0
  1564. package/src/db/timer-verifier.ts +379 -0
  1565. package/src/db/timer-write-ops.ts +291 -0
  1566. package/src/db/token-db-write-ops.ts +96 -0
  1567. package/src/db/token-standing.ts +84 -0
  1568. package/src/db/token-usage-read-ops.ts +8 -0
  1569. package/src/db/token-usage-write-ops.ts +35 -0
  1570. package/src/db/token-usage.ts +338 -0
  1571. package/src/db/tool-groups-empty-migration.ts +59 -0
  1572. package/src/db/urgent-intake.ts +30 -0
  1573. package/src/db/user-invites.ts +107 -0
  1574. package/src/db/user-settings-read-ops.ts +19 -0
  1575. package/src/db/user-settings-write-ops.ts +43 -0
  1576. package/src/db/users.ts +173 -0
  1577. package/src/db/vault-backup.ts +59 -0
  1578. package/src/db/vault-expiry.ts +71 -0
  1579. package/src/db/vault-health.ts +99 -0
  1580. package/src/db/vault-token-launch.ts +77 -0
  1581. package/src/db/vault-token-ops.ts +14 -0
  1582. package/src/db/vault-tokens.ts +101 -0
  1583. package/src/db/viewer-scope.ts +117 -0
  1584. package/src/db/wal-checkpointer-worker.ts +48 -0
  1585. package/src/db/wal-checkpointer.ts +107 -0
  1586. package/src/db/wal-index.ts +53 -0
  1587. package/src/db/wal-live-log.ts +68 -0
  1588. package/src/db/wal-reclaim-async.ts +45 -0
  1589. package/src/db/wal-reclaim.ts +140 -0
  1590. package/src/db/web-tool-call-ops.ts +15 -0
  1591. package/src/db/web-tool-calls.ts +87 -0
  1592. package/src/db/withdraw-land-error.ts +18 -0
  1593. package/src/db/work-evidence.ts +43 -0
  1594. package/src/db/workflow-budget.ts +44 -0
  1595. package/src/db/workflow-contact.ts +173 -0
  1596. package/src/db/workflow-execution.ts +10 -0
  1597. package/src/db/workflow-journal-async.ts +64 -0
  1598. package/src/db/workflow-journal-prune.ts +71 -0
  1599. package/src/db/workflow-journal-read-ops.ts +21 -0
  1600. package/src/db/workflow-journal-schema.ts +9 -0
  1601. package/src/db/workflow-journal-write-ops.ts +33 -0
  1602. package/src/db/workflow-journal.ts +324 -0
  1603. package/src/db/workflow-spawn-quota.ts +57 -0
  1604. package/src/db/workflow-stats-read-ops.ts +177 -0
  1605. package/src/db/workflow-step-tasks.ts +74 -0
  1606. package/src/db/workflow-task-read-ops.ts +187 -0
  1607. package/src/db/workspace-land-state.ts +65 -0
  1608. package/src/db/workspace-merge-hold.ts +28 -0
  1609. package/src/db/workspace-persistence.ts +77 -0
  1610. package/src/db/workspace-rekey-write-ops.ts +11 -0
  1611. package/src/db/workspace-rekey.ts +148 -0
  1612. package/src/db/workspace-steward-cancel.ts +85 -0
  1613. package/src/db/workspace-steward-fence.ts +30 -0
  1614. package/src/db/workspace-write-ops.ts +26 -0
  1615. package/src/db/workspace-writes-async.ts +46 -0
  1616. package/src/db/workspace-writes.ts +196 -0
  1617. package/src/db/workspaces.ts +617 -0
  1618. package/src/db/write-ops.ts +602 -0
  1619. package/src/db/writer-drain.ts +27 -0
  1620. package/src/db/writer-fault-seam.ts +104 -0
  1621. package/src/db/writer-gateway.ts +843 -0
  1622. package/src/db/writer-ledger.ts +56 -0
  1623. package/src/db.ts +4 -0
  1624. package/src/deploy-executor.ts +139 -0
  1625. package/src/dev.ts +541 -0
  1626. package/src/dispatch-availability.ts +55 -0
  1627. package/src/dispatch-match.ts +21 -0
  1628. package/src/ephemeral/host-landing-slots-proof-worker.ts +45 -0
  1629. package/src/ephemeral/index.ts +58 -0
  1630. package/src/ephemeral/instance.ts +832 -0
  1631. package/src/ephemeral/landing-ingress-proof-worker.ts +30 -0
  1632. package/src/ephemeral/ports.ts +40 -0
  1633. package/src/ephemeral/profile.ts +323 -0
  1634. package/src/ephemeral/rehearsal-stamps.ts +149 -0
  1635. package/src/ephemeral/rehearsal.ts +213 -0
  1636. package/src/ephemeral/snapshot-kernel.ts +41 -0
  1637. package/src/ephemeral/snapshot.ts +753 -0
  1638. package/src/ephemeral/sweep.ts +40 -0
  1639. package/src/event-loop-lag.ts +584 -0
  1640. package/src/event-loop-yield.ts +27 -0
  1641. package/src/events.ts +140 -0
  1642. package/src/execution-mode.ts +63 -0
  1643. package/src/fleet-convergence.ts +123 -0
  1644. package/src/gate-resolver.ts +151 -0
  1645. package/src/gate-run-capture.ts +71 -0
  1646. package/src/git-sha.ts +58 -0
  1647. package/src/guides.ts +73 -0
  1648. package/src/host/capacity.ts +79 -0
  1649. package/src/host/gate-capacity-alert.ts +90 -0
  1650. package/src/host/identity.ts +128 -0
  1651. package/src/host/presentation.ts +234 -0
  1652. package/src/host/pressure-details.ts +89 -0
  1653. package/src/host/pressure.ts +216 -0
  1654. package/src/host/state.ts +132 -0
  1655. package/src/host/utilization-buffer.ts +71 -0
  1656. package/src/host-awareness.ts +57 -0
  1657. package/src/host-identity.ts +39 -0
  1658. package/src/host-provider-enablement.ts +75 -0
  1659. package/src/host-provider-store.ts +102 -0
  1660. package/src/host-tools.ts +39 -0
  1661. package/src/http-body.ts +49 -0
  1662. package/src/http-compression.ts +16 -0
  1663. package/src/hub-metrics-report.ts +67 -0
  1664. package/src/hub-metrics.ts +166 -0
  1665. package/src/idle-digest-config.ts +40 -0
  1666. package/src/idle-self-care.ts +58 -0
  1667. package/src/index.ts +35 -0
  1668. package/src/injection-sweep-pacing.ts +24 -0
  1669. package/src/insights-async.ts +16 -0
  1670. package/src/insights-db.ts +188 -0
  1671. package/src/internal-spawn-model.ts +18 -0
  1672. package/src/invite-link.ts +11 -0
  1673. package/src/issue-tracker/adapter.ts +58 -0
  1674. package/src/issue-tracker/github.ts +342 -0
  1675. package/src/issue-tracker/index.ts +12 -0
  1676. package/src/issue-tracker/registry.ts +26 -0
  1677. package/src/journal-preamble.ts +350 -0
  1678. package/src/kernel-correlation.ts +28 -0
  1679. package/src/land-authorization.ts +73 -0
  1680. package/src/land-block.ts +59 -0
  1681. package/src/land-failure-event-key.ts +8 -0
  1682. package/src/land-gate-failure-routing.ts +69 -0
  1683. package/src/land-gate-notifications.ts +173 -0
  1684. package/src/land-gate-queue-progress.ts +54 -0
  1685. package/src/land-gates-source.ts +126 -0
  1686. package/src/land-gates.ts +29 -0
  1687. package/src/land-lifecycle-drives.ts +15 -0
  1688. package/src/land-settle-dispatch.ts +134 -0
  1689. package/src/land-stack-depth.ts +11 -0
  1690. package/src/land-telemetry.ts +40 -0
  1691. package/src/landing-base-ref.ts +3 -0
  1692. package/src/landing-host-safety.ts +38 -0
  1693. package/src/landing-lane-key.ts +74 -0
  1694. package/src/launch-mode-support.ts +11 -0
  1695. package/src/launch-mode.ts +58 -0
  1696. package/src/legacy-landing.test-fixture.ts +56 -0
  1697. package/src/lifecycle-context-pressure.ts +134 -0
  1698. package/src/lifecycle-manager.ts +831 -0
  1699. package/src/lifecycle-signal-registry.ts +87 -0
  1700. package/src/live-runs-section.ts +38 -0
  1701. package/src/loop-block-attribution.ts +442 -0
  1702. package/src/loop-block-regression-alarm.ts +156 -0
  1703. package/src/loop-task-stamps.ts +62 -0
  1704. package/src/main-thread-clock.ts +95 -0
  1705. package/src/maintenance/agent-identity-archive-job.ts +15 -0
  1706. package/src/maintenance/artifact-checkout-gc-job.ts +14 -0
  1707. package/src/maintenance/artifact-checkout-gc.ts +33 -0
  1708. package/src/maintenance/artifact-sweep.ts +23 -0
  1709. package/src/maintenance/breaker.ts +200 -0
  1710. package/src/maintenance/cd-watcher.ts +176 -0
  1711. package/src/maintenance/checkout-refresh.ts +83 -0
  1712. package/src/maintenance/ci-monitor.ts +14 -0
  1713. package/src/maintenance/claude-api-budget-job.ts +16 -0
  1714. package/src/maintenance/constants.ts +134 -0
  1715. package/src/maintenance/continuation-archive-search-job.ts +27 -0
  1716. package/src/maintenance/critical-job-watchdog.ts +252 -0
  1717. package/src/maintenance/db-planner-stats-job.ts +19 -0
  1718. package/src/maintenance/db-wal-reclaim-job.ts +82 -0
  1719. package/src/maintenance/events.ts +28 -0
  1720. package/src/maintenance/gate-capacity-sample-job.ts +16 -0
  1721. package/src/maintenance/index.ts +10 -0
  1722. package/src/maintenance/jobs.ts +857 -0
  1723. package/src/maintenance/long-running-work.ts +41 -0
  1724. package/src/maintenance/orphaned-sessions.ts +126 -0
  1725. package/src/maintenance/pauses.ts +174 -0
  1726. package/src/maintenance/provider-secret-scrub-job.ts +16 -0
  1727. package/src/maintenance/queued-message-expiration.ts +23 -0
  1728. package/src/maintenance/red-main-controller-job.ts +22 -0
  1729. package/src/maintenance/reply-obligation-parity-job.ts +27 -0
  1730. package/src/maintenance/restart-epoch-hold-health.ts +62 -0
  1731. package/src/maintenance/run-registry.ts +194 -0
  1732. package/src/maintenance/scheduler.ts +778 -0
  1733. package/src/maintenance/service-access-job.ts +31 -0
  1734. package/src/maintenance/shipping-stats-rollup-job.ts +23 -0
  1735. package/src/maintenance/snapshot-ledger-prune-job.ts +17 -0
  1736. package/src/maintenance/stalled-delivery-pager.ts +125 -0
  1737. package/src/maintenance/steward-handback.ts +11 -0
  1738. package/src/maintenance/task-state-reconcile-job.ts +22 -0
  1739. package/src/maintenance/teams.ts +426 -0
  1740. package/src/maintenance/types.ts +124 -0
  1741. package/src/maintenance/vault-backup-job.ts +34 -0
  1742. package/src/maintenance/vault-expiry-job.ts +29 -0
  1743. package/src/maintenance/web-tool-call-prune-job.ts +13 -0
  1744. package/src/maintenance/workflow-backstop-job.ts +12 -0
  1745. package/src/maintenance/workflow-journal-prune-job.ts +29 -0
  1746. package/src/maintenance/workspaces.ts +784 -0
  1747. package/src/maintenance.ts +1 -0
  1748. package/src/managed-policy-escalation.ts +123 -0
  1749. package/src/managed-policy.ts +236 -0
  1750. package/src/managed-provider-gate.ts +27 -0
  1751. package/src/managed-spawn-reservation.ts +23 -0
  1752. package/src/markdown-frontmatter.ts +14 -0
  1753. package/src/mcp/activity-output.ts +57 -0
  1754. package/src/mcp/artifact-upload.ts +53 -0
  1755. package/src/mcp/context-output.ts +73 -0
  1756. package/src/mcp/dispatch-shape-schema.ts +13 -0
  1757. package/src/mcp/guides.ts +33 -0
  1758. package/src/mcp/http.ts +27 -0
  1759. package/src/mcp/identity-projection.ts +121 -0
  1760. package/src/mcp/identity.ts +235 -0
  1761. package/src/mcp/index.ts +355 -0
  1762. package/src/mcp/issue-selector-schema.ts +12 -0
  1763. package/src/mcp/message-read.ts +87 -0
  1764. package/src/mcp/output-descriptions.ts +72 -0
  1765. package/src/mcp/output-levels.ts +71 -0
  1766. package/src/mcp/output-projection.ts +88 -0
  1767. package/src/mcp/plan-presentation.ts +166 -0
  1768. package/src/mcp/presentation-values.ts +11 -0
  1769. package/src/mcp/presentation.ts +653 -0
  1770. package/src/mcp/project-entry-search-schema.ts +7 -0
  1771. package/src/mcp/prompts.ts +36 -0
  1772. package/src/mcp/runtime-output.ts +84 -0
  1773. package/src/mcp/schema-budget.ts +28 -0
  1774. package/src/mcp/self-care-visibility.ts +23 -0
  1775. package/src/mcp/service-access.ts +18 -0
  1776. package/src/mcp/spawn-targets-matrix.ts +68 -0
  1777. package/src/mcp/spawn-targets.test-fixture.ts +21 -0
  1778. package/src/mcp/task-validation-presentation.ts +19 -0
  1779. package/src/mcp/tool-generation.ts +8 -0
  1780. package/src/mcp/tool-groups.ts +136 -0
  1781. package/src/mcp/tools-artifacts.ts +82 -0
  1782. package/src/mcp/tools-messaging.ts +541 -0
  1783. package/src/mcp/tools-permission.ts +88 -0
  1784. package/src/mcp/tools-run.ts +73 -0
  1785. package/src/mcp/tools-scheduler.ts +156 -0
  1786. package/src/mcp/tools-spawn.ts +418 -0
  1787. package/src/mcp/tools-web-tool-calls.ts +28 -0
  1788. package/src/mcp/tools-workspace.ts +260 -0
  1789. package/src/mcp/types.ts +68 -0
  1790. package/src/mcp/validation.ts +66 -0
  1791. package/src/mcp-agent-action.ts +96 -0
  1792. package/src/mcp-agent-keep.ts +37 -0
  1793. package/src/mcp-backlog-tools.ts +137 -0
  1794. package/src/mcp-blackboard-tools.ts +131 -0
  1795. package/src/mcp-channel-binding-tools.ts +27 -0
  1796. package/src/mcp-claude-api-budget.ts +35 -0
  1797. package/src/mcp-codex-reset.ts +43 -0
  1798. package/src/mcp-decision-tools.ts +96 -0
  1799. package/src/mcp-dispatch-tools.ts +142 -0
  1800. package/src/mcp-egress-tools.ts +168 -0
  1801. package/src/mcp-errors.ts +7 -0
  1802. package/src/mcp-host-tools.ts +61 -0
  1803. package/src/mcp-interrupt-agent.ts +47 -0
  1804. package/src/mcp-issue-activity-tools.ts +114 -0
  1805. package/src/mcp-issue-association-tools.ts +103 -0
  1806. package/src/mcp-issue-lifecycle-tools.ts +49 -0
  1807. package/src/mcp-issue-ref-tools.ts +214 -0
  1808. package/src/mcp-maintenance-tools.ts +113 -0
  1809. package/src/mcp-merge-tools.ts +66 -0
  1810. package/src/mcp-notification-subscription-tools.ts +297 -0
  1811. package/src/mcp-output-levels.test-fixture.ts +95 -0
  1812. package/src/mcp-plan-tools.ts +347 -0
  1813. package/src/mcp-profile-trust-tools.ts +47 -0
  1814. package/src/mcp-project-journal-tools.ts +160 -0
  1815. package/src/mcp-project-tools.ts +420 -0
  1816. package/src/mcp-provider-catalog.ts +9 -0
  1817. package/src/mcp-release-tools.ts +33 -0
  1818. package/src/mcp-routing-policy-tools.ts +35 -0
  1819. package/src/mcp-secrets-tools.ts +87 -0
  1820. package/src/mcp-task-deliver-tools.ts +166 -0
  1821. package/src/mcp-task-land-withdraw-tools.ts +49 -0
  1822. package/src/mcp-task-memory-tools.ts +119 -0
  1823. package/src/mcp-task-review-hold-tools.ts +57 -0
  1824. package/src/mcp-task-tools.ts +342 -0
  1825. package/src/mcp-task-validation-tools.ts +30 -0
  1826. package/src/mcp-task-weave-tools.ts +63 -0
  1827. package/src/mcp-team-template-tools.ts +141 -0
  1828. package/src/mcp-team-tools.ts +197 -0
  1829. package/src/mcp-tool-allowlist.ts +88 -0
  1830. package/src/mcp-workflow-tools.ts +158 -0
  1831. package/src/mcp.ts +1 -0
  1832. package/src/memory-admission-rank.ts +55 -0
  1833. package/src/memory-area-injection.ts +216 -0
  1834. package/src/memory-boundary-capture.ts +206 -0
  1835. package/src/memory-entry-safety.ts +28 -0
  1836. package/src/memory-inject-filter.ts +190 -0
  1837. package/src/memory-injection.ts +954 -0
  1838. package/src/memory-preamble-admission.ts +158 -0
  1839. package/src/memory-preamble-async.ts +13 -0
  1840. package/src/memory-preamble.ts +1142 -0
  1841. package/src/memory-retirement.ts +11 -0
  1842. package/src/memory-seam.ts +273 -0
  1843. package/src/memory-tokens.ts +91 -0
  1844. package/src/merge-drain.ts +101 -0
  1845. package/src/merge-pause-maintenance.ts +19 -0
  1846. package/src/message-visibility.ts +50 -0
  1847. package/src/messaging-policy.ts +94 -0
  1848. package/src/native-memory.ts +59 -0
  1849. package/src/notification-prefs.ts +25 -0
  1850. package/src/notification-role-policy.ts +14 -0
  1851. package/src/notification-router.ts +329 -0
  1852. package/src/notification-rules.ts +390 -0
  1853. package/src/notification-settings-store.ts +47 -0
  1854. package/src/notification-settings.ts +166 -0
  1855. package/src/notification-subscription-rules.ts +11 -0
  1856. package/src/notification-types.ts +312 -0
  1857. package/src/notify.ts +55 -0
  1858. package/src/observed-reset-boundary.ts +23 -0
  1859. package/src/observed-reset-refresh.ts +21 -0
  1860. package/src/operator-alarm.ts +41 -0
  1861. package/src/operator-decisions-section.ts +29 -0
  1862. package/src/orchestrator-lookup.ts +29 -0
  1863. package/src/orchestrator-self-report-authz.ts +34 -0
  1864. package/src/package-pack.ts +249 -0
  1865. package/src/post-bind-startup.ts +58 -0
  1866. package/src/process-guard.ts +31 -0
  1867. package/src/profile-cells.ts +305 -0
  1868. package/src/profile-effective-survey.ts +121 -0
  1869. package/src/profile-host-sources.ts +73 -0
  1870. package/src/profile-instruction-blocks.ts +11 -0
  1871. package/src/profile-project-validation.ts +23 -0
  1872. package/src/profile-provider-options.ts +59 -0
  1873. package/src/profile-role-templates.ts +26 -0
  1874. package/src/profile-workflow-policy.ts +14 -0
  1875. package/src/project-claudemd-sweep.ts +176 -0
  1876. package/src/project-file-source.ts +54 -0
  1877. package/src/project-ingest.ts +195 -0
  1878. package/src/project-instructions.ts +700 -0
  1879. package/src/project-mcp.ts +18 -0
  1880. package/src/project-role-assets.ts +109 -0
  1881. package/src/project-roles-validate.ts +53 -0
  1882. package/src/project-roles.ts +364 -0
  1883. package/src/project-settings.ts +5 -0
  1884. package/src/project-skills.ts +5 -0
  1885. package/src/project-toolchain.ts +58 -0
  1886. package/src/project-trust-fingerprint.ts +174 -0
  1887. package/src/project-trust.ts +262 -0
  1888. package/src/project-workflows.ts +185 -0
  1889. package/src/prompt-resolver.ts +697 -0
  1890. package/src/prompt-template-fallback.ts +15 -0
  1891. package/src/prompt-version-errors.ts +13 -0
  1892. package/src/provider-catalog-store.ts +393 -0
  1893. package/src/provider-config-store.ts +308 -0
  1894. package/src/provider-enablement.ts +36 -0
  1895. package/src/provider-home-gc.ts +58 -0
  1896. package/src/provider-model-seed.ts +18 -0
  1897. package/src/provider-quota-async.ts +152 -0
  1898. package/src/provider-quota-config-store.ts +60 -0
  1899. package/src/provider-secrets.test-fixture.ts +34 -0
  1900. package/src/provider-state.ts +93 -0
  1901. package/src/provider-timeline-event.ts +76 -0
  1902. package/src/quota-advisory.ts +359 -0
  1903. package/src/quota-band-delivery.ts +13 -0
  1904. package/src/quota-band-projection.ts +149 -0
  1905. package/src/quota-band-transition.ts +316 -0
  1906. package/src/quota-pacing.ts +46 -0
  1907. package/src/quota-threshold-config.ts +46 -0
  1908. package/src/quota.ts +61 -0
  1909. package/src/ratchet-files.ts +202 -0
  1910. package/src/recipe-async.ts +50 -0
  1911. package/src/recipe-db.ts +260 -0
  1912. package/src/recipe-loader.ts +132 -0
  1913. package/src/recipe-runner.ts +259 -0
  1914. package/src/recipe-validator.ts +107 -0
  1915. package/src/relay-event-drain.ts +39 -0
  1916. package/src/relay-host-locality.ts +20 -0
  1917. package/src/relay-run-lease.ts +17 -0
  1918. package/src/repo-config-source.ts +209 -0
  1919. package/src/report-verbosity.ts +5 -0
  1920. package/src/restart-epoch-deploy-hold.ts +221 -0
  1921. package/src/retention-config-store.ts +32 -0
  1922. package/src/retention-config.ts +70 -0
  1923. package/src/reviewer-pipeline.ts +238 -0
  1924. package/src/routes/_route.ts +70 -0
  1925. package/src/routes/_shared.ts +513 -0
  1926. package/src/routes/activity.ts +70 -0
  1927. package/src/routes/agent-effort.ts +69 -0
  1928. package/src/routes/agent-keep.ts +27 -0
  1929. package/src/routes/agent-profiles.ts +169 -0
  1930. package/src/routes/agent-reporting.ts +19 -0
  1931. package/src/routes/agent-self-effort.ts +51 -0
  1932. package/src/routes/agent-sessions.ts +650 -0
  1933. package/src/routes/agent-sharing.ts +23 -0
  1934. package/src/routes/agents-spawn.ts +258 -0
  1935. package/src/routes/agents.ts +452 -0
  1936. package/src/routes/artifacts.ts +340 -0
  1937. package/src/routes/auth.ts +123 -0
  1938. package/src/routes/authority-audit.ts +14 -0
  1939. package/src/routes/automations.ts +83 -0
  1940. package/src/routes/backlog.ts +112 -0
  1941. package/src/routes/blackboard.ts +54 -0
  1942. package/src/routes/browser.ts +73 -0
  1943. package/src/routes/channel-binding-delete.ts +21 -0
  1944. package/src/routes/channel-output.ts +17 -0
  1945. package/src/routes/claude-api-budget.ts +65 -0
  1946. package/src/routes/commands-listing.ts +50 -0
  1947. package/src/routes/commands.ts +586 -0
  1948. package/src/routes/config.ts +75 -0
  1949. package/src/routes/connectors.ts +108 -0
  1950. package/src/routes/context-resets.ts +35 -0
  1951. package/src/routes/continuation-archives.ts +37 -0
  1952. package/src/routes/coordinator-teams.ts +14 -0
  1953. package/src/routes/decisions.ts +82 -0
  1954. package/src/routes/deploys.ts +15 -0
  1955. package/src/routes/fleet-convergence.ts +5 -0
  1956. package/src/routes/guides.ts +13 -0
  1957. package/src/routes/host-providers.ts +73 -0
  1958. package/src/routes/host-resource-control.ts +48 -0
  1959. package/src/routes/host-secret-files.ts +38 -0
  1960. package/src/routes/hosts.ts +240 -0
  1961. package/src/routes/inbox.ts +151 -0
  1962. package/src/routes/index.ts +614 -0
  1963. package/src/routes/insights.ts +86 -0
  1964. package/src/routes/integrations.ts +636 -0
  1965. package/src/routes/issue-activity.ts +78 -0
  1966. package/src/routes/issue-associations.ts +76 -0
  1967. package/src/routes/issue-lifecycle.ts +34 -0
  1968. package/src/routes/issue-refs.ts +163 -0
  1969. package/src/routes/kernel-timers-operator.ts +57 -0
  1970. package/src/routes/memory.ts +25 -0
  1971. package/src/routes/merges.ts +109 -0
  1972. package/src/routes/message-viewer.ts +4 -0
  1973. package/src/routes/messages.ts +621 -0
  1974. package/src/routes/notifications.ts +36 -0
  1975. package/src/routes/orchestrator-bootstrap.ts +101 -0
  1976. package/src/routes/orchestrator-host-registration.ts +125 -0
  1977. package/src/routes/orchestrator-host-utilization.ts +255 -0
  1978. package/src/routes/orchestrator-proxy.ts +174 -0
  1979. package/src/routes/orchestrator.ts +699 -0
  1980. package/src/routes/pairs.ts +214 -0
  1981. package/src/routes/plans.ts +98 -0
  1982. package/src/routes/project-onboarding.ts +56 -0
  1983. package/src/routes/project-trust.ts +73 -0
  1984. package/src/routes/projects.ts +296 -0
  1985. package/src/routes/prompt-templates.ts +106 -0
  1986. package/src/routes/provider-config.ts +233 -0
  1987. package/src/routes/provider-quota-config.ts +48 -0
  1988. package/src/routes/provider-quotas.ts +312 -0
  1989. package/src/routes/provisioning-registry.ts +192 -0
  1990. package/src/routes/recipes.ts +129 -0
  1991. package/src/routes/restart-epochs.ts +98 -0
  1992. package/src/routes/routing-policy.ts +30 -0
  1993. package/src/routes/run-command.ts +25 -0
  1994. package/src/routes/secrets-backup.ts +53 -0
  1995. package/src/routes/secrets-import.ts +32 -0
  1996. package/src/routes/secrets-page.ts +98 -0
  1997. package/src/routes/secrets-vault.ts +121 -0
  1998. package/src/routes/secrets.ts +165 -0
  1999. package/src/routes/service-access.ts +57 -0
  2000. package/src/routes/settings.ts +191 -0
  2001. package/src/routes/shipping.ts +56 -0
  2002. package/src/routes/spawn-policy.ts +299 -0
  2003. package/src/routes/spec.ts +58 -0
  2004. package/src/routes/sse.ts +18 -0
  2005. package/src/routes/stats.ts +159 -0
  2006. package/src/routes/steward.ts +49 -0
  2007. package/src/routes/task-graph.ts +30 -0
  2008. package/src/routes/task-http.ts +27 -0
  2009. package/src/routes/task-issues.ts +81 -0
  2010. package/src/routes/task-reads.ts +139 -0
  2011. package/src/routes/task-validation.ts +22 -0
  2012. package/src/routes/tasks.ts +553 -0
  2013. package/src/routes/team-templates.ts +65 -0
  2014. package/src/routes/teams-config.ts +15 -0
  2015. package/src/routes/teams.ts +141 -0
  2016. package/src/routes/token-usage.ts +167 -0
  2017. package/src/routes/tokens.ts +393 -0
  2018. package/src/routes/users.ts +248 -0
  2019. package/src/routes/viewer-read.ts +27 -0
  2020. package/src/routes/web-tool-calls.ts +38 -0
  2021. package/src/routes/workflows.ts +142 -0
  2022. package/src/routes/workspaces.ts +642 -0
  2023. package/src/routes.ts +3 -0
  2024. package/src/routing-eligibility.ts +42 -0
  2025. package/src/routing-model-aliases.ts +21 -0
  2026. package/src/routing-policy-normalize.ts +412 -0
  2027. package/src/routing-policy-push.ts +154 -0
  2028. package/src/routing-policy-service.ts +19 -0
  2029. package/src/routing-policy-store.ts +16 -0
  2030. package/src/routing-preferences.ts +82 -0
  2031. package/src/routing-step-defaults.ts +31 -0
  2032. package/src/runtime-grant.ts +164 -0
  2033. package/src/runtime-prefix.ts +8 -0
  2034. package/src/runtime-tokens.ts +404 -0
  2035. package/src/search-query.ts +10 -0
  2036. package/src/secret-keys.ts +24 -0
  2037. package/src/secrets-broker/config.ts +53 -0
  2038. package/src/secrets-broker/error.ts +8 -0
  2039. package/src/secrets-broker/infisical.ts +259 -0
  2040. package/src/secrets-broker/reader-infisical.ts +41 -0
  2041. package/src/secrets-broker/reader.ts +101 -0
  2042. package/src/secrets-broker/service.ts +242 -0
  2043. package/src/secrets-broker/tool-credentials.ts +9 -0
  2044. package/src/secrets-vault/backend-conformance.ts +26 -0
  2045. package/src/secrets-vault/backend.ts +102 -0
  2046. package/src/secrets-vault/backup.ts +103 -0
  2047. package/src/secrets-vault/config.ts +7 -0
  2048. package/src/secrets-vault/expiry.ts +10 -0
  2049. package/src/secrets-vault/identity.ts +27 -0
  2050. package/src/secrets-vault/import.ts +90 -0
  2051. package/src/secrets-vault/key-files.ts +91 -0
  2052. package/src/secrets-vault/keys.ts +79 -0
  2053. package/src/secrets-vault/metadata.ts +51 -0
  2054. package/src/secrets-vault/provider-env.ts +25 -0
  2055. package/src/secrets-vault/resolve.ts +101 -0
  2056. package/src/secrets-vault/restore.ts +57 -0
  2057. package/src/secrets-vault/scope.ts +25 -0
  2058. package/src/secrets-vault/service.ts +192 -0
  2059. package/src/secrets-vault/shared.ts +10 -0
  2060. package/src/security.ts +772 -0
  2061. package/src/self-effort-rule.ts +36 -0
  2062. package/src/self-resume-carry.ts +131 -0
  2063. package/src/self-resume-command.ts +24 -0
  2064. package/src/self-resume-config.ts +54 -0
  2065. package/src/self-resume-identity.ts +40 -0
  2066. package/src/self-resume-supersede.ts +19 -0
  2067. package/src/self-resume.ts +884 -0
  2068. package/src/server-background.ts +24 -0
  2069. package/src/server.ts +726 -0
  2070. package/src/service-access/config.ts +3 -0
  2071. package/src/service-access/health.ts +151 -0
  2072. package/src/service-access/host-health.ts +62 -0
  2073. package/src/service-access/host.ts +63 -0
  2074. package/src/service-access/operations.ts +21 -0
  2075. package/src/service-access/policy.ts +30 -0
  2076. package/src/service-access/probe.ts +62 -0
  2077. package/src/service-access/request-logs.ts +93 -0
  2078. package/src/service-access/scope.ts +7 -0
  2079. package/src/service-access/service.ts +185 -0
  2080. package/src/service-access/tokens.ts +174 -0
  2081. package/src/service-access/validation.ts +75 -0
  2082. package/src/service-access/vault.ts +59 -0
  2083. package/src/services/agent-action.ts +238 -0
  2084. package/src/services/agent-control-host.ts +74 -0
  2085. package/src/services/agent-effort.ts +60 -0
  2086. package/src/services/agent-hang-watchdog-job.ts +513 -0
  2087. package/src/services/agent-hang-watchdog-wiring.ts +487 -0
  2088. package/src/services/agent-hang-watchdog.ts +395 -0
  2089. package/src/services/agent-keep.ts +72 -0
  2090. package/src/services/agent-message-fanout.ts +91 -0
  2091. package/src/services/agent-outstanding-work.ts +66 -0
  2092. package/src/services/agent-reporting.ts +27 -0
  2093. package/src/services/artifact-access.ts +39 -0
  2094. package/src/services/artifact-multipart.ts +70 -0
  2095. package/src/services/artifact-path-host.ts +55 -0
  2096. package/src/services/artifact-read.ts +88 -0
  2097. package/src/services/artifact-write.ts +25 -0
  2098. package/src/services/auth-context.ts +117 -0
  2099. package/src/services/automatic-disposal.ts +33 -0
  2100. package/src/services/backlog.ts +149 -0
  2101. package/src/services/browser-inventory.ts +52 -0
  2102. package/src/services/browser-session-state.ts +41 -0
  2103. package/src/services/browser-session.ts +167 -0
  2104. package/src/services/bus-command-update.ts +84 -0
  2105. package/src/services/bus-expiration-death.ts +57 -0
  2106. package/src/services/canonical-root.ts +284 -0
  2107. package/src/services/captured-delivery-gate.ts +12 -0
  2108. package/src/services/carry-pending.ts +5 -0
  2109. package/src/services/channel-binding-delete.ts +20 -0
  2110. package/src/services/channel-suppression-receipt.ts +28 -0
  2111. package/src/services/checkout-refresh-state.ts +18 -0
  2112. package/src/services/checkout-refresh.test-fixture.ts +12 -0
  2113. package/src/services/child-work-evidence.ts +77 -0
  2114. package/src/services/claude-app.ts +91 -0
  2115. package/src/services/command-claim-rejection.ts +23 -0
  2116. package/src/services/command-host.ts +42 -0
  2117. package/src/services/coordinator-digest.ts +30 -0
  2118. package/src/services/decision-audience.ts +26 -0
  2119. package/src/services/delivery-gate-policy.ts +98 -0
  2120. package/src/services/delivery-tip.test-fixture.ts +25 -0
  2121. package/src/services/diff-gate.ts +110 -0
  2122. package/src/services/dispatch-command.ts +388 -0
  2123. package/src/services/effective-launch-driver.ts +240 -0
  2124. package/src/services/effective-launch-harness.ts +257 -0
  2125. package/src/services/errors.ts +31 -0
  2126. package/src/services/git-process.ts +176 -0
  2127. package/src/services/git-remote.ts +100 -0
  2128. package/src/services/harness-subagents.ts +12 -0
  2129. package/src/services/host-changed-paths.test-fixture.ts +134 -0
  2130. package/src/services/host-landing-placement.ts +65 -0
  2131. package/src/services/host-project-readiness.ts +49 -0
  2132. package/src/services/host-providers.ts +114 -0
  2133. package/src/services/host-utilization.ts +70 -0
  2134. package/src/services/idle-agent-digest.ts +318 -0
  2135. package/src/services/idle-epics.ts +105 -0
  2136. package/src/services/idle-self-care.ts +47 -0
  2137. package/src/services/interrupt-agent.ts +146 -0
  2138. package/src/services/isolated-workspace-placement.ts +258 -0
  2139. package/src/services/issue-activity.ts +23 -0
  2140. package/src/services/issue-associations.ts +72 -0
  2141. package/src/services/issue-completion-drain.ts +24 -0
  2142. package/src/services/issue-completion.ts +24 -0
  2143. package/src/services/issue-lifecycle-comments.ts +12 -0
  2144. package/src/services/issue-lifecycle.ts +620 -0
  2145. package/src/services/issue-refs.ts +65 -0
  2146. package/src/services/kernel-command-bridge.ts +219 -0
  2147. package/src/services/kernel-control-host.ts +78 -0
  2148. package/src/services/kernel-control-http.ts +119 -0
  2149. package/src/services/kernel-control-mcp.ts +193 -0
  2150. package/src/services/kernel-landing-command.ts +28 -0
  2151. package/src/services/kernel-landing-ingress.ts +65 -0
  2152. package/src/services/kernel-timers-authoritative-ingress.ts +48 -0
  2153. package/src/services/kernel-timers-authz.ts +229 -0
  2154. package/src/services/kernel-timers-command.ts +182 -0
  2155. package/src/services/kernel-timers-cutover-chaos-worker.ts +211 -0
  2156. package/src/services/kernel-timers-cutover.ts +39 -0
  2157. package/src/services/kernel-timers-ingress-apply.ts +39 -0
  2158. package/src/services/kernel-timers-ingress-chaos-worker.ts +312 -0
  2159. package/src/services/kernel-timers-ingress.ts +702 -0
  2160. package/src/services/kernel-timers-message-authority-chaos-worker.ts +97 -0
  2161. package/src/services/kernel-timers-migrate.ts +158 -0
  2162. package/src/services/kernel-timers-operator-chaos-worker.ts +259 -0
  2163. package/src/services/kernel-timers-operator-preflight.ts +18 -0
  2164. package/src/services/kernel-timers-operator.ts +424 -0
  2165. package/src/services/kernel-timers-outbox.ts +138 -0
  2166. package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +80 -0
  2167. package/src/services/kernel-timers-stage10-operator.ts +210 -0
  2168. package/src/services/kernel-timers-stage10.ts +85 -0
  2169. package/src/services/kernel-timers-stage9.ts +57 -0
  2170. package/src/services/kernel-timers-transfer-chaos-worker.ts +118 -0
  2171. package/src/services/kernel-timers-transfer.ts +525 -0
  2172. package/src/services/kernel-timers-verifier.ts +39 -0
  2173. package/src/services/kernel-timers.ts +247 -0
  2174. package/src/services/land-boundary-settle.ts +32 -0
  2175. package/src/services/land-gate-placement.ts +60 -0
  2176. package/src/services/land-issue-relations.ts +46 -0
  2177. package/src/services/land-restart-retry.ts +77 -0
  2178. package/src/services/landed-ancestry.ts +417 -0
  2179. package/src/services/landed-task-attribution.ts +14 -0
  2180. package/src/services/landing-terminal-report.ts +119 -0
  2181. package/src/services/legacy-landing-backfill.ts +159 -0
  2182. package/src/services/legacy-landing-host.ts +291 -0
  2183. package/src/services/legacy-landing-publication.ts +35 -0
  2184. package/src/services/legacy-landing-recovery.ts +111 -0
  2185. package/src/services/macos-privacy.ts +34 -0
  2186. package/src/services/managed-running.ts +189 -0
  2187. package/src/services/memory-areas.ts +172 -0
  2188. package/src/services/memory-identity.ts +162 -0
  2189. package/src/services/merge-pause.ts +37 -0
  2190. package/src/services/message-delivery-actions.ts +11 -0
  2191. package/src/services/operator-decisions.ts +260 -0
  2192. package/src/services/orchestrator-landing-recovery.ts +55 -0
  2193. package/src/services/orchestrator-registration-recovery.ts +12 -0
  2194. package/src/services/orchestrator-upgrade.ts +83 -0
  2195. package/src/services/parity-harness.ts +141 -0
  2196. package/src/services/permission-decision.ts +129 -0
  2197. package/src/services/plan-ci-recovery.ts +76 -0
  2198. package/src/services/plan-conformance.ts +105 -0
  2199. package/src/services/plan-dispatch-policy.ts +79 -0
  2200. package/src/services/plan-dispatch.ts +372 -0
  2201. package/src/services/plan-external-view.ts +7 -0
  2202. package/src/services/plan-frontier.ts +34 -0
  2203. package/src/services/plan-graph.ts +703 -0
  2204. package/src/services/plan-import.ts +66 -0
  2205. package/src/services/plan-live-bindings.ts +139 -0
  2206. package/src/services/plan-task-create.ts +80 -0
  2207. package/src/services/plan-terminal-refs.ts +83 -0
  2208. package/src/services/plan-wake-digest.ts +88 -0
  2209. package/src/services/plan-wake-policy.ts +12 -0
  2210. package/src/services/profile-trust.ts +54 -0
  2211. package/src/services/project-checkout-binding.ts +65 -0
  2212. package/src/services/project-checkout-enrollment.ts +27 -0
  2213. package/src/services/project-cwd.ts +41 -0
  2214. package/src/services/project-onboarding.ts +165 -0
  2215. package/src/services/project-repo-create.ts +27 -0
  2216. package/src/services/project-role.test-fixture.ts +74 -0
  2217. package/src/services/project.ts +191 -0
  2218. package/src/services/prompt-templates.ts +46 -0
  2219. package/src/services/prompt-version-writer.ts +42 -0
  2220. package/src/services/provider-catalog.ts +7 -0
  2221. package/src/services/provider-hold-flap.ts +130 -0
  2222. package/src/services/provider-hold-nudge.ts +191 -0
  2223. package/src/services/provider-hold-record.ts +137 -0
  2224. package/src/services/provider-state-audit.ts +214 -0
  2225. package/src/services/provisioning-harness/analyze.ts +285 -0
  2226. package/src/services/provisioning-harness/budget-baseline.json +391 -0
  2227. package/src/services/provisioning-harness/capture.ts +197 -0
  2228. package/src/services/provisioning-harness/config.ts +55 -0
  2229. package/src/services/provisioning-harness/context-sources.ts +85 -0
  2230. package/src/services/provisioning-harness/driver.ts +156 -0
  2231. package/src/services/provisioning-harness/expect.ts +81 -0
  2232. package/src/services/provisioning-harness/fixture-matrix.ts +38 -0
  2233. package/src/services/provisioning-harness/fixtures/project/.agent-relay/land-gates.json +1 -0
  2234. package/src/services/provisioning-harness/fixtures/project/.claude/skills/harness-skill/SKILL.md +6 -0
  2235. package/src/services/provisioning-harness/fixtures/project/.mcp.json +1 -0
  2236. package/src/services/provisioning-harness/fixtures/project/AGENTS.md +1 -0
  2237. package/src/services/provisioning-harness/fixtures/project/CLAUDE.md +1 -0
  2238. package/src/services/provisioning-harness/fixtures/project/mcp-server.ts +33 -0
  2239. package/src/services/provisioning-harness/fixtures/provider-home/AGENTS.md +1 -0
  2240. package/src/services/provisioning-harness/fixtures/provider-home/CLAUDE.md +1 -0
  2241. package/src/services/provisioning-harness/host-fixtures.ts +25 -0
  2242. package/src/services/provisioning-harness/index.ts +4 -0
  2243. package/src/services/provisioning-harness/inventory-types.ts +8 -0
  2244. package/src/services/provisioning-harness/inventory.ts +33 -0
  2245. package/src/services/provisioning-harness/lifecycle.ts +193 -0
  2246. package/src/services/provisioning-harness/live.ts +61 -0
  2247. package/src/services/provisioning-harness/manifests.ts +118 -0
  2248. package/src/services/provisioning-harness/prepare.ts +113 -0
  2249. package/src/services/provisioning-harness/redundancy-report.ts +32 -0
  2250. package/src/services/provisioning-harness/redundancy.ts +137 -0
  2251. package/src/services/provisioning-harness/report.ts +129 -0
  2252. package/src/services/provisioning-harness/types.ts +174 -0
  2253. package/src/services/provisioning-registry.ts +15 -0
  2254. package/src/services/rate-limit-resume.ts +220 -0
  2255. package/src/services/reap-verdict.ts +52 -0
  2256. package/src/services/red-main-auto-revert.ts +103 -0
  2257. package/src/services/red-main-controller-deps.ts +80 -0
  2258. package/src/services/red-main-controller.ts +262 -0
  2259. package/src/services/red-main-incident.ts +44 -0
  2260. package/src/services/red-main-quarantine.ts +92 -0
  2261. package/src/services/register-agent.ts +447 -0
  2262. package/src/services/release-readiness.ts +219 -0
  2263. package/src/services/reported-exit-death.ts +29 -0
  2264. package/src/services/response-report-admission.ts +17 -0
  2265. package/src/services/restart-epoch-driver.ts +290 -0
  2266. package/src/services/restart-epoch-reconciler.ts +326 -0
  2267. package/src/services/restart-epoch-stale-command-recovery.ts +133 -0
  2268. package/src/services/restart-instructions.ts +86 -0
  2269. package/src/services/restart-project-role.ts +74 -0
  2270. package/src/services/restart-secret-refs.ts +14 -0
  2271. package/src/services/restart-spawn.ts +510 -0
  2272. package/src/services/review-publish.ts +76 -0
  2273. package/src/services/review-ref-reconcile.ts +337 -0
  2274. package/src/services/run-command-input.ts +22 -0
  2275. package/src/services/run-command.ts +510 -0
  2276. package/src/services/run-completion-message.ts +70 -0
  2277. package/src/services/run-log.ts +212 -0
  2278. package/src/services/run-queue.ts +63 -0
  2279. package/src/services/scheduler-command-cancel.ts +49 -0
  2280. package/src/services/scheduler-command.ts +590 -0
  2281. package/src/services/scheduler-create.ts +25 -0
  2282. package/src/services/scheduler-plan.ts +202 -0
  2283. package/src/services/scheduler-recovery.ts +31 -0
  2284. package/src/services/scheduler.ts +406 -0
  2285. package/src/services/self-care-state.ts +10 -0
  2286. package/src/services/self-effort-grant.ts +93 -0
  2287. package/src/services/self-effort.ts +152 -0
  2288. package/src/services/send-message.ts +548 -0
  2289. package/src/services/server-credentials.ts +53 -0
  2290. package/src/services/shipping-board.ts +111 -0
  2291. package/src/services/shipping-project-config.ts +86 -0
  2292. package/src/services/shipping-stats.ts +86 -0
  2293. package/src/services/shutdown-agent.ts +463 -0
  2294. package/src/services/shutdown-watchdog.ts +119 -0
  2295. package/src/services/spawn-agent.ts +967 -0
  2296. package/src/services/spawn-approval.ts +26 -0
  2297. package/src/services/spawn-brief.ts +39 -0
  2298. package/src/services/spawn-chain-inspector.ts +286 -0
  2299. package/src/services/spawn-cwd-placement.ts +250 -0
  2300. package/src/services/spawn-identity.ts +127 -0
  2301. package/src/services/spawn-instruction-inputs.ts +114 -0
  2302. package/src/services/spawn-issue-activity.ts +102 -0
  2303. package/src/services/spawn-launch-decision.ts +32 -0
  2304. package/src/services/spawn-launch.ts +116 -0
  2305. package/src/services/spawn-loss-escalation.ts +253 -0
  2306. package/src/services/spawn-placement.test-fixture.ts +30 -0
  2307. package/src/services/spawn-project-role.ts +133 -0
  2308. package/src/services/spawn-registration-watchdog.ts +352 -0
  2309. package/src/services/spawn-replay.ts +61 -0
  2310. package/src/services/spawn-repo-probe.ts +118 -0
  2311. package/src/services/spawn-tree-cleanup.ts +37 -0
  2312. package/src/services/spawn-warnings.ts +13 -0
  2313. package/src/services/steward-reaper.ts +244 -0
  2314. package/src/services/task-context-hydration.ts +413 -0
  2315. package/src/services/task-delivery-close.ts +26 -0
  2316. package/src/services/task-delivery-event.ts +11 -0
  2317. package/src/services/task-delivery-land-policy.ts +35 -0
  2318. package/src/services/task-delivery-summary.ts +38 -0
  2319. package/src/services/task-delivery.ts +762 -0
  2320. package/src/services/task-dependency-graph.ts +116 -0
  2321. package/src/services/task-dispatch.ts +178 -0
  2322. package/src/services/task-done-when.ts +45 -0
  2323. package/src/services/task-entity.ts +758 -0
  2324. package/src/services/task-from-issue.ts +80 -0
  2325. package/src/services/task-issue-digest.ts +45 -0
  2326. package/src/services/task-issue-link.ts +158 -0
  2327. package/src/services/task-issue-weave.ts +570 -0
  2328. package/src/services/task-land-withdraw.ts +462 -0
  2329. package/src/services/task-lifecycle.ts +668 -0
  2330. package/src/services/task-nothing-to-land.ts +37 -0
  2331. package/src/services/task-plan-view.ts +172 -0
  2332. package/src/services/task-review-hold.ts +244 -0
  2333. package/src/services/task-semantic-events.ts +396 -0
  2334. package/src/services/task-stage-wake.ts +15 -0
  2335. package/src/services/task-state-reconcile.ts +169 -0
  2336. package/src/services/task-validation.ts +42 -0
  2337. package/src/services/team-claim-race.worker.ts +72 -0
  2338. package/src/services/team-spawn-delegation.ts +30 -0
  2339. package/src/services/team-templates.ts +172 -0
  2340. package/src/services/team.ts +320 -0
  2341. package/src/services/terminal-provider-exit.ts +57 -0
  2342. package/src/services/timer-message-fire-chaos-worker.ts +29 -0
  2343. package/src/services/timer-message-fire-outbox.ts +184 -0
  2344. package/src/services/transient-agent-reaper.ts +412 -0
  2345. package/src/services/turn-reconcile-audit.ts +236 -0
  2346. package/src/services/user-onboarding.ts +98 -0
  2347. package/src/services/user-profile.ts +57 -0
  2348. package/src/services/web-search.ts +83 -0
  2349. package/src/services/web-tool-calls.ts +65 -0
  2350. package/src/services/withdraw-steward.ts +57 -0
  2351. package/src/services/work-finality.ts +34 -0
  2352. package/src/services/workflow-agent-obligation.ts +35 -0
  2353. package/src/services/workflow-auto-start.ts +126 -0
  2354. package/src/services/workflow-backstop.ts +38 -0
  2355. package/src/services/workflow-budget.test-fixture.ts +10 -0
  2356. package/src/services/workflow-caller.ts +32 -0
  2357. package/src/services/workflow-catalog.ts +43 -0
  2358. package/src/services/workflow-child-proof-worker.ts +76 -0
  2359. package/src/services/workflow-child.test-fixture.ts +51 -0
  2360. package/src/services/workflow-ci-main.ts +93 -0
  2361. package/src/services/workflow-control-proof-worker.ts +149 -0
  2362. package/src/services/workflow-coordinator-decision-proof-worker.ts +123 -0
  2363. package/src/services/workflow-coordinator.ts +8 -0
  2364. package/src/services/workflow-decision-answer.ts +18 -0
  2365. package/src/services/workflow-decision-proof-worker.ts +92 -0
  2366. package/src/services/workflow-decision.test-fixture.ts +58 -0
  2367. package/src/services/workflow-decisions.ts +107 -0
  2368. package/src/services/workflow-deferred-cleanup.ts +40 -0
  2369. package/src/services/workflow-dispatch-result.ts +20 -0
  2370. package/src/services/workflow-effect-driver.ts +165 -0
  2371. package/src/services/workflow-effects.ts +544 -0
  2372. package/src/services/workflow-foreach-proof-worker.ts +209 -0
  2373. package/src/services/workflow-land-notice.ts +18 -0
  2374. package/src/services/workflow-land.ts +105 -0
  2375. package/src/services/workflow-notice.ts +32 -0
  2376. package/src/services/workflow-overdue-notice.ts +19 -0
  2377. package/src/services/workflow-pins.ts +162 -0
  2378. package/src/services/workflow-reconcile.ts +210 -0
  2379. package/src/services/workflow-resume-pins.ts +30 -0
  2380. package/src/services/workflow-run-chaos-worker.ts +67 -0
  2381. package/src/services/workflow-run-view.ts +71 -0
  2382. package/src/services/workflow-run.test-fixture.ts +165 -0
  2383. package/src/services/workflow-runs.ts +370 -0
  2384. package/src/services/workflow-scripts.ts +100 -0
  2385. package/src/services/workflow-slice-proof.ts +14 -0
  2386. package/src/services/workflow-stats.ts +208 -0
  2387. package/src/services/workflow-supervisor-state.ts +117 -0
  2388. package/src/services/workflow-supervisor.ts +122 -0
  2389. package/src/services/workflow-verification.ts +68 -0
  2390. package/src/services/workspace-recovery.ts +168 -0
  2391. package/src/services/workspace-task-binding.ts +22 -0
  2392. package/src/settings/modules.ts +149 -0
  2393. package/src/settings/registry.ts +629 -0
  2394. package/src/settings/user-settings.ts +81 -0
  2395. package/src/setup.ts +199 -0
  2396. package/src/shared-mcp.ts +87 -0
  2397. package/src/shutdown.ts +33 -0
  2398. package/src/spawn-admission.ts +48 -0
  2399. package/src/spawn-budget.ts +18 -0
  2400. package/src/spawn-command.ts +373 -0
  2401. package/src/spawn-env.ts +27 -0
  2402. package/src/spawn-intent.ts +17 -0
  2403. package/src/spawn-lease.ts +41 -0
  2404. package/src/spawn-models.ts +22 -0
  2405. package/src/spawn-placement.ts +348 -0
  2406. package/src/spawn-profile-errors.ts +9 -0
  2407. package/src/spawn-quota.ts +15 -0
  2408. package/src/spawn-targets.ts +679 -0
  2409. package/src/sse-agent-status.ts +35 -0
  2410. package/src/sse-outbox-cursor.ts +28 -0
  2411. package/src/sse-visibility.ts +36 -0
  2412. package/src/sse.ts +684 -0
  2413. package/src/startup-context-injection.ts +518 -0
  2414. package/src/startup-timing.ts +30 -0
  2415. package/src/state-adoption/artifacts.ts +27 -0
  2416. package/src/state-adoption/cd-cutover.ts +125 -0
  2417. package/src/state-adoption/files.ts +31 -0
  2418. package/src/state-adoption/index.ts +166 -0
  2419. package/src/state-adoption/linux-plan.ts +67 -0
  2420. package/src/state-adoption/linux.ts +104 -0
  2421. package/src/state-adoption/macos-plan.ts +58 -0
  2422. package/src/state-adoption/macos.ts +112 -0
  2423. package/src/steward-claim-release.ts +38 -0
  2424. package/src/steward-handoff.ts +28 -0
  2425. package/src/steward-identity.ts +54 -0
  2426. package/src/steward-policy-cleanup.ts +222 -0
  2427. package/src/steward-policy-model-reconcile.ts +33 -0
  2428. package/src/steward-policy-name.ts +13 -0
  2429. package/src/steward-provenance.ts +59 -0
  2430. package/src/steward-queue.ts +85 -0
  2431. package/src/steward-slot-recovery.ts +34 -0
  2432. package/src/steward-spawn-guard.ts +344 -0
  2433. package/src/steward.test-fixture.ts +11 -0
  2434. package/src/steward.ts +199 -0
  2435. package/src/stream-proxy.ts +84 -0
  2436. package/src/task-authz.ts +40 -0
  2437. package/src/task-completion.ts +39 -0
  2438. package/src/task-dispatch-live.ts +70 -0
  2439. package/src/task-dispatcher-ranking.ts +136 -0
  2440. package/src/task-dispatcher.ts +841 -0
  2441. package/src/task-owner-notices.ts +35 -0
  2442. package/src/task-owner.ts +63 -0
  2443. package/src/task-plan-status.ts +21 -0
  2444. package/src/task-verification.ts +35 -0
  2445. package/src/team-idle-tracker.ts +39 -0
  2446. package/src/team-template-config.ts +100 -0
  2447. package/src/teams-config-store.ts +22 -0
  2448. package/src/terminal-authz.ts +49 -0
  2449. package/src/token-db-write.ts +44 -0
  2450. package/src/token-db.ts +487 -0
  2451. package/src/token-usage-async.ts +40 -0
  2452. package/src/transport-subscriptions.ts +57 -0
  2453. package/src/types.ts +1 -0
  2454. package/src/upgrade-install-verify.ts +179 -0
  2455. package/src/upgrade-local-id.ts +3 -0
  2456. package/src/upgrade-services.ts +125 -0
  2457. package/src/upgrade.ts +1006 -0
  2458. package/src/user-awaiting-reply.ts +42 -0
  2459. package/src/user-path.ts +246 -0
  2460. package/src/user-settings-async.ts +33 -0
  2461. package/src/user-settings-store.ts +106 -0
  2462. package/src/user-style.ts +83 -0
  2463. package/src/utils.ts +42 -0
  2464. package/src/validation.ts +324 -0
  2465. package/src/vault-backup-config.ts +28 -0
  2466. package/src/vault-expiry-config.ts +12 -0
  2467. package/src/versioned-prompts.ts +57 -0
  2468. package/src/workflow-capabilities.ts +10 -0
  2469. package/src/workflow-prompt-templates.ts +20 -0
  2470. package/src/workspace-action-audit.ts +42 -0
  2471. package/src/workspace-actions.ts +689 -0
  2472. package/src/workspace-auto-merge.ts +1055 -0
  2473. package/src/workspace-changed-paths-client.ts +97 -0
  2474. package/src/workspace-claim.ts +45 -0
  2475. package/src/workspace-cleanup-settle.ts +51 -0
  2476. package/src/workspace-cleanup-stale.ts +231 -0
  2477. package/src/workspace-command-settle.ts +589 -0
  2478. package/src/workspace-config.ts +7 -0
  2479. package/src/workspace-conflict-resolver.ts +182 -0
  2480. package/src/workspace-dead-owner-recovery.ts +19 -0
  2481. package/src/workspace-delivery-tip.ts +51 -0
  2482. package/src/workspace-escalation.ts +107 -0
  2483. package/src/workspace-git-state-client.ts +42 -0
  2484. package/src/workspace-host-client.ts +86 -0
  2485. package/src/workspace-host-target.ts +5 -0
  2486. package/src/workspace-host.ts +61 -0
  2487. package/src/workspace-human-escalation.ts +70 -0
  2488. package/src/workspace-land-gate-watchdog.ts +76 -0
  2489. package/src/workspace-land-gate.ts +99 -0
  2490. package/src/workspace-land-result.ts +12 -0
  2491. package/src/workspace-land-retry.ts +82 -0
  2492. package/src/workspace-land-stopped.ts +57 -0
  2493. package/src/workspace-merge-dispatch.ts +27 -0
  2494. package/src/workspace-merge-progress.ts +369 -0
  2495. package/src/workspace-merge-retry-policy.ts +59 -0
  2496. package/src/workspace-merge.ts +614 -0
  2497. package/src/workspace-nothing-to-land-notice.ts +27 -0
  2498. package/src/workspace-orphans.ts +927 -0
  2499. package/src/workspace-phase.ts +507 -0
  2500. package/src/workspace-pr-completion.ts +285 -0
  2501. package/src/workspace-probe.ts +212 -0
  2502. package/src/workspace-recorded-land.ts +47 -0
  2503. package/src/workspace-recovery-authz.ts +88 -0
  2504. package/src/workspace-rekey.ts +106 -0
  2505. package/src/workspace-steward-authority.ts +16 -0
  2506. package/src/workspace-steward-failure.ts +42 -0
  2507. package/src/workspace-steward-wake.ts +150 -0
  2508. package/src/workspace-steward-watchdog.ts +240 -0
@@ -0,0 +1,2264 @@
1
+ import { relayEnv } from "@humwick/sdk/env";
2
+ import { chmodSync, writeFileSync } from "node:fs";
3
+ import { assertOpenCodeApprovalMode, buildOpenCodeSpawnArgs } from "./opencode-spawn";
4
+ export { assertOpenCodeApprovalMode, assertOpenCodeManagedServerArgs, assertOpenCodeSafeModel, resolveGhConfigDir } from "./opencode-spawn";
5
+ import { subscribeOpenCodeEvents } from "./opencode-subscribe";
6
+ import { terminateProviderProcesses } from "../provider-processes";
7
+ import { createHash } from "node:crypto";
8
+ import { join } from "node:path";
9
+ import { errMessage, isRecord, stringValue, type ContextState, type Message, type ProviderState } from "@humwick/sdk";
10
+ import { killPid } from "@humwick/sdk/process-utils";
11
+ import { shellEscape } from "@humwick/sdk/shell-utils";
12
+ import { logger } from "../logger";
13
+ import { fireAndForget } from "../async-guard";
14
+ import { buildProviderRefusalHold, buildRateLimitProviderState, isProviderOverload, isProviderThrottle, isProviderUsageLimit } from "../rate-limit";
15
+ import { PromptAcceptedError, providerMessageText, type DeliverResult, type ManagedProcess, type PromptOfferOptions, type ProviderAdapter, type ProviderConfig, type ProviderDeliverContext, type ProviderInputObserver, type ProviderSessionEvent, type ProviderSessionTurnContext, type ProviderStatusUpdate, type ProviderTokenUsageReporter, type RunnerSpawnConfig, type ShutdownOutcome, type SpawnArgs, type TerminalAttachSpec } from "../adapter";
16
+ import { freshContextState } from "../transcript-context";
17
+ import { partitionMidTurnDelivery, resolveActiveTurnMessageDelivery } from "../mid-turn-delivery";
18
+ import { isOpenCodeServerRetryWall, openCodeServerRetryState, parseOpenCodeServerRetry, type OpenCodeServerRetry } from "./opencode-server-retry";
19
+ import { consumeOpenCodeContextAcceptance, openCodeContextState, prepareOpenCodeRelayContext, sendOpenCodeRelayTurn } from "./opencode-context";
20
+ import { captureOpenCodeApiUsage, spawnOpenCodeServer, openCodeApiKey, OpenCodeApiBudget } from "./opencode-api";
21
+ import { OpenCodeTokenUsagePoster, openCodeMessageFinished, openCodeMessageModel, openCodeMessageObservedAt, scheduleOpenCodeTokenUsage } from "./opencode-token-usage-capture";
22
+ import { decodeOpenCodeReasoningPart, decodeOpenCodeToolPart, openCodeAssistantReasoningSteps, openCodeAssistantText, openCodeReasoningSessionEvent, openCodeToolSessionEvent, openCodeToolStatusRank, openCodeTurnFinalEvents, type OpenCodeReasoningStep } from "./opencode-tool-events";
23
+ import { openCodeBackend, openCodeContextThreshold, openCodeContextWindowTokens, openCodeLaunchModel, openCodeOnLaunchModel, openCodeModelRecord, openCodeQualifiedModel, openCodeTurnDescriptor } from "./opencode-provider";
24
+ import { adoptOpenCodeTerminalModel, resolveOpenCodeLaunchModel } from "./opencode-model-registry";
25
+ import { OPENCODE_SOFT_STEER_POST_TIMEOUT_MS, OPENCODE_SOFT_STEER_RECONCILE_TIMEOUT_MS, OPENCODE_SOFT_STEER_WAKE_TEXT, OpenCodeSoftSteerLedger, composeSoftSteerText, newSoftSteerMessageID } from "./opencode-soft-steer";
26
+
27
+ export { openCodeContextThreshold, openCodeContextWindowTokens, openCodeModelDescriptor, openCodeModelRecord, openCodeQualifiedModel } from "./opencode-provider";
28
+
29
+ /**
30
+ * #2459 — the non-secret attachment generation for a web-terminal guest: a short one-way
31
+ * digest of the ACTIVE OpenCode session id. It changes exactly when the session rotates
32
+ * (clearContext / the #2397 retry re-roll) and lets the orchestrator detect a stale guest
33
+ * TUI — attached to a dead conversation — WITHOUT ever seeing the session id itself or any
34
+ * credential (a digest of a 122-bit-random uuid is not reversible in practice). Rides the
35
+ * attach spec, the runner's GET /terminal/generation, and the orchestrator's guest registry;
36
+ * never persisted by Relay, never logged, never sent to the dashboard.
37
+ */
38
+ export function openCodeTerminalGeneration(sessionId: string): string {
39
+ return createHash("sha256").update("opencode-attach-generation\0").update(sessionId).digest("hex").slice(0, 16);
40
+ }
41
+
42
+ export interface OpenCodeServerEvent {
43
+ type: string;
44
+ properties?: Record<string, unknown>;
45
+ }
46
+
47
+ type OpenCodeFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
48
+
49
+ /**
50
+ * #2428 — Bun extends its fetch init with a per-request `timeout` field that the standard
51
+ * RequestInit type does not declare (`number | false`; `false` disables Bun's native HTTP idle
52
+ * watchdog for that one request). Neither @types/node's nor bun-types 1.3.14's ambient
53
+ * RequestInit/BunFetchRequestInit declares the field, so it is declared here rather than reached
54
+ * through a cast. Verified empirically against this repo's Bun 1.3.14: with
55
+ * BUN_CONFIG_HTTP_IDLE_TIMEOUT compressed, a plain fetch to a server that sends no bytes dies with
56
+ * TimeoutError "The operation timed out" while the identical request plus `timeout: false`
57
+ * completes — and an explicit AbortSignal still fires on its own schedule either way, so the two
58
+ * mechanisms compose.
59
+ */
60
+ export type OpenCodeRequestInit = RequestInit & { timeout?: number | false };
61
+
62
+ /**
63
+ * #2425 — whole-turn budget for `OpenCodeClient.sendMessage`'s synchronous POST (see its doc).
64
+ * Env-tunable so an operator can trade a tighter bound for faster abandonment-detection on a
65
+ * flaky network, or a longer one for a model/workload that legitimately runs longer than an hour.
66
+ * The default is intentionally large rather than unbounded — `AbortSignal.timeout` requires a
67
+ * finite duration — and is far above the ~294s limit the incident was cut off at.
68
+ */
69
+ export const OPENCODE_TURN_TIMEOUT_DEFAULT_MS = 60 * 60 * 1000;
70
+
71
+ export function openCodeTurnTimeoutMsFromEnv(): number {
72
+ const parsed = Number(process.env.OPENCODE_TURN_TIMEOUT_MS);
73
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : OPENCODE_TURN_TIMEOUT_DEFAULT_MS;
74
+ }
75
+
76
+ /** Small, pinned-protocol client for the v1.18.21 loopback server. */
77
+ export class OpenCodeClient {
78
+ constructor(
79
+ readonly baseUrl: string,
80
+ private readonly password: string,
81
+ private readonly fetchImpl: OpenCodeFetch = fetch,
82
+ private readonly provider = "opencode",
83
+ private readonly retryDelay?: (ms: number, signal: AbortSignal) => Promise<unknown>,
84
+ ) {}
85
+
86
+ private headers(extra: HeadersInit = {}): Headers {
87
+ const headers = new Headers(extra);
88
+ headers.set("Authorization", `Basic ${Buffer.from(`opencode:${this.password}`).toString("base64")}`);
89
+ return headers;
90
+ }
91
+
92
+ private async request(path: string, init: OpenCodeRequestInit = {}): Promise<Response> {
93
+ const response = await this.fetchImpl(new URL(path, this.baseUrl), {
94
+ ...init,
95
+ headers: this.headers(init.headers),
96
+ });
97
+ if (!response.ok) throw new Error(`OpenCode server ${path} failed: HTTP ${response.status}`);
98
+ return response;
99
+ }
100
+
101
+ /**
102
+ * The loopback server logs "listening" as soon as its socket is bound, well before the
103
+ * Basic-auth-gated HTTP handler is actually wired up. A request that lands in that window is
104
+ * neither refused nor answered — the connection is accepted and then left open, so an
105
+ * unbounded fetch can hang far longer than any overall deadline (reproduced: a single such
106
+ * request outlived a 40s wait). Each attempt therefore gets its own short-lived AbortSignal so
107
+ * a stuck connection can never swallow the whole retry budget — only a real, authenticated
108
+ * `/global/health` 200 resolves the wait.
109
+ */
110
+ async waitForReady(timeoutMs = 20_000, attemptTimeoutMs = 2_000): Promise<void> {
111
+ const deadline = Date.now() + timeoutMs;
112
+ let lastError: unknown;
113
+ while (Date.now() < deadline) {
114
+ const remaining = deadline - Date.now();
115
+ try {
116
+ await this.request("/global/health", { signal: AbortSignal.timeout(Math.min(attemptTimeoutMs, remaining)) });
117
+ return;
118
+ } catch (error) {
119
+ lastError = error;
120
+ await Bun.sleep(Math.min(100, Math.max(0, deadline - Date.now())));
121
+ }
122
+ }
123
+ throw new Error(`OpenCode server did not become ready within ${timeoutMs}ms${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
124
+ }
125
+
126
+ async createSession(title: string): Promise<string> {
127
+ const response = await this.request("/session", {
128
+ method: "POST",
129
+ headers: { "Content-Type": "application/json" },
130
+ body: JSON.stringify({ title }),
131
+ });
132
+ const body = await response.json().catch(() => undefined);
133
+ const id = isRecord(body) ? stringValue(body.id) : undefined;
134
+ if (!id) throw new Error("OpenCode server created a session without an id");
135
+ return id;
136
+ }
137
+
138
+ /**
139
+ * #2389 — this POST is the SYNCHRONOUS endpoint: it returns only once the assistant message is
140
+ * complete, so its status and duration describe the whole turn, not just the hand-off.
141
+ *
142
+ * It used to be entirely silent on success — no status, no timing, no access log — so when a
143
+ * spawned agent idled forever there was no way to establish whether its first prompt had even
144
+ * been accepted; that took reading the provider's own retained SQLite. Log it.
145
+ *
146
+ * #2425 — this call used to carry NO abort signal at all, yet a live, still-running turn was
147
+ * observed being cut off client-side at roughly 294s regardless. Combined with the old PERMANENT
148
+ * idle suppression, that stranded the agent's status at "idle" forever even though the server
149
+ * kept working — see the `suppressProviderIdle` field doc on `OpenCodeAdapter`. Passing an
150
+ * EXPLICIT, much longer bound (`openCodeTurnTimeoutMsFromEnv`) means a legitimate long turn is
151
+ * never the thing that trips it, and gives `send()` a deterministic abandonment point to recover
152
+ * from if it ever does.
153
+ *
154
+ * #2428 — ROOT CAUSED (Bun repro): that ~294s cutoff is Bun's native HTTP idle watchdog — a
155
+ * fetch whose response sends no bytes for BUN_CONFIG_HTTP_IDLE_TIMEOUT (default 300s) dies
156
+ * client-side with TimeoutError "The operation timed out" INDEPENDENTLY of the AbortSignal
157
+ * above, which bounds only its own much longer duration. This endpoint is synchronous, so any
158
+ * turn longer than that window was aborted client-side while the server kept working (and
159
+ * RunnerCore's retry re-POSTed into the still-running turn). The fix passes Bun-only
160
+ * `timeout: false` on exactly this request (see OpenCodeRequestInit): verified empirically that
161
+ * it disables only that watchdog while the explicit deadline above keeps bounding the whole
162
+ * call. Deliberately NOT set globally (no BUN_CONFIG_HTTP_IDLE_TIMEOUT env change) and not on
163
+ * the short bounded calls — waitForReady/createSession/abort keep their default watchdog plus
164
+ * their own signals.
165
+ */
166
+ async sendMessage(sessionId: string, text: string, model: string, timeoutMs: number = openCodeTurnTimeoutMsFromEnv()): Promise<OpenCodeCompletion> {
167
+ const startedAt = Date.now();
168
+ try {
169
+ const response = await this.request(`/session/${encodeURIComponent(sessionId)}/message`, {
170
+ method: "POST",
171
+ headers: { "Content-Type": "application/json" },
172
+ body: JSON.stringify({ model: openCodeTurnDescriptor(model, this.provider), parts: [{ type: "text", text }] }),
173
+ signal: AbortSignal.timeout(timeoutMs),
174
+ // #2428 — disable Bun's native HTTP idle watchdog for this whole-turn request only; the
175
+ // signal above remains the sole deadline. See OpenCodeRequestInit for the verified semantics.
176
+ timeout: false,
177
+ });
178
+ const completion = openCodeCompletion(await response.json().catch(() => undefined));
179
+ logger.info("opencode", `session ${sessionId} message accepted: HTTP ${response.status} (${text.length} chars, ${Date.now() - startedAt}ms)${describeCompletion(completion)}`);
180
+ return completion;
181
+ } catch (error) {
182
+ logger.error("opencode", `session ${sessionId} message failed after ${Date.now() - startedAt}ms (${text.length} chars): ${errMessage(error)}`);
183
+ throw error;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * #2534 — bounded read of a session's persisted messages, used ONLY by the acceptance
189
+ * reconciliation when a context-carrying turn's completion await failed: a user message whose
190
+ * persisted text equals the outbound prompt is positive proof the server ACCEPTED it, even
191
+ * though the await never resolved. Kept on a short, explicit deadline — this runs on a failure
192
+ * path and must never stretch the caller's own recovery by more than a few seconds.
193
+ */
194
+ async sessionMessages(sessionId: string, timeoutMs = 5_000): Promise<unknown> {
195
+ const response = await this.request(`/session/${encodeURIComponent(sessionId)}/message`, { signal: AbortSignal.timeout(timeoutMs) });
196
+ return response.json().catch(() => undefined);
197
+ }
198
+
199
+ /**
200
+ * #2539 Phase B — the soft-steer persist primitive: POST /session/:id/message with a CALLER
201
+ * messageID + noReply:true. LIVE-PROVEN on pinned 1.18.21: persists the user row in single-digit
202
+ * milliseconds WITHOUT joining/cancelling an in-flight run (does not touch the runner effect that
203
+ * prompt_async would join), and the response body IS the persisted user message — so
204
+ * `info.id === messageID` is positive persistence identity straight from the POST, with no
205
+ * reconcile read on the clean path. Tri-state outcome: "persisted" (row exists, identity
206
+ * confirmed), "absent" (the server DEFINITIVELY refused — 4xx; the row does not exist), or
207
+ * "unknown" (timeout/transport/5xx — the POST may or may not have landed; the caller must
208
+ * reconcile by exact id via {@link hasMessageWithId} and never blind-retry). Bounded deadline
209
+ * (5s default) — a soft steer must never hang on the whole-turn budget.
210
+ */
211
+ async sendMessageNoReply(sessionId: string, text: string, messageID: string, timeoutMs = 5_000): Promise<"persisted" | "absent" | "unknown"> {
212
+ try {
213
+ const response = await this.fetchImpl(new URL(`/session/${encodeURIComponent(sessionId)}/message`, this.baseUrl), {
214
+ method: "POST",
215
+ headers: this.headers({ "Content-Type": "application/json" }),
216
+ body: JSON.stringify({ parts: [{ type: "text", text }], messageID, noReply: true }),
217
+ signal: AbortSignal.timeout(timeoutMs),
218
+ });
219
+ if (response.status >= 500) return "unknown";
220
+ if (!response.ok) return "absent";
221
+ const body = await response.json().catch(() => undefined);
222
+ const id = isRecord(body) && isRecord(body.info) ? stringValue(body.info.id) : undefined;
223
+ return id === messageID ? "persisted" : "unknown";
224
+ } catch {
225
+ return "unknown";
226
+ }
227
+ }
228
+
229
+ /**
230
+ * #2539 Phase B — the ambiguous-outcome reconcile: does the session's persisted message list
231
+ * contain EXACTLY this caller messageID? true/false are definitive (the GET succeeded and the
232
+ * full session listing either has the row or does not); undefined means the listing itself was
233
+ * unavailable — still ambiguous. Matching is by exact id, never by text (a resend must never
234
+ * pass reconciliation by colliding with its own previous attempt's text).
235
+ */
236
+ async hasMessageWithId(sessionId: string, messageID: string, timeoutMs = 5_000): Promise<boolean | undefined> {
237
+ try {
238
+ const messages = await this.sessionMessages(sessionId, timeoutMs);
239
+ if (!Array.isArray(messages)) return undefined;
240
+ return messages.some((entry) => isRecord(entry) && isRecord(entry.info) && stringValue(entry.info.id) === messageID);
241
+ } catch {
242
+ return undefined;
243
+ }
244
+ }
245
+
246
+ async abort(sessionId: string): Promise<void> {
247
+ await this.request(`/session/${encodeURIComponent(sessionId)}/abort`, { method: "POST" });
248
+ }
249
+
250
+ /**
251
+ * #2440 — OpenCode's own native session-compaction endpoint (a prior effort, #2431, wrongly
252
+ * concluded it did not exist and shipped clear-inject only). Like `sendMessage`, this may run
253
+ * long server-side, so it gets the same Bun idle-watchdog bypass (`timeout: false`) plus an
254
+ * explicit bounded `AbortSignal` — see `sendMessage`'s doc for the verified #2428 semantics.
255
+ * A session-not-found / compact-already-in-progress / server error all surface as the same
256
+ * `request()`-thrown `HTTP <status>` error; the adapter's caller resets its busy hold on catch.
257
+ */
258
+ async compact(sessionId: string, timeoutMs: number = openCodeTurnTimeoutMsFromEnv()): Promise<void> {
259
+ const startedAt = Date.now();
260
+ try {
261
+ const response = await this.request(`/session/${encodeURIComponent(sessionId)}/compact`, {
262
+ method: "POST",
263
+ signal: AbortSignal.timeout(timeoutMs),
264
+ timeout: false,
265
+ });
266
+ logger.info("opencode", `session ${sessionId} compact accepted: HTTP ${response.status} (${Date.now() - startedAt}ms)`);
267
+ } catch (error) {
268
+ logger.error("opencode", `session ${sessionId} compact failed after ${Date.now() - startedAt}ms: ${errMessage(error)}`);
269
+ throw error;
270
+ }
271
+ }
272
+
273
+ /** Consume SSE without assuming every event shape is stable outside the pinned version. */
274
+ async subscribe(signal: AbortSignal, onEvent: (event: OpenCodeServerEvent) => void): Promise<void> {
275
+ return subscribeOpenCodeEvents(() => this.request("/event", { headers: { Accept: "text/event-stream" }, signal }), signal, onEvent, this.retryDelay);
276
+ }
277
+
278
+ }
279
+
280
+ /**
281
+ * Retry/re-roll policy for a retryable turn-ending provider error (#2397). Direct measurement
282
+ * (issue #2397 comment) showed these failures are BIMODAL per OpenCode session — a session either
283
+ * succeeds on every attempt or fails on every attempt for its whole life — so retrying harder
284
+ * into the SAME session eventually accomplishes nothing. Bound the in-session retry tightly, then
285
+ * re-roll to a fresh session (new session id, same server process) exactly once before giving up.
286
+ */
287
+ const OPENCODE_ERROR_RETRY_MAX_ATTEMPTS = 3;
288
+ const OPENCODE_ERROR_RETRY_BASE_DELAY_MS = 1_000;
289
+
290
+ /** Exponential backoff for retry attempt N (1-indexed): 1s, 2s, 4s at the defaults above. Driven
291
+ * entirely by our own policy — OpenRouter sends neither `retry-after` nor `x-ratelimit-*` for
292
+ * this error class (confirmed live, #2397), so there is nothing external to read. */
293
+ export function openCodeErrorRetryDelayMs(attempt: number): number {
294
+ return OPENCODE_ERROR_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1);
295
+ }
296
+
297
+ /**
298
+ * #2397 — a parsed assistant-message error off the pinned v1.18.21 wire.
299
+ *
300
+ * `AssistantMessage.error` is a union (ProviderAuthError | UnknownError |
301
+ * MessageOutputLengthError | MessageAbortedError | ApiError — see @opencode-ai/sdk's generated
302
+ * types). Only `ApiError` carries a structured `statusCode`/`isRetryable`; OpenCode falls back to
303
+ * `UnknownError` for anything it doesn't recognize. Verified live (#2397): a real OpenRouter 429
304
+ * arrived as `UnknownError` with the raw provider response embedded as a JSON STRING in
305
+ * `data.message` (`{"code":429,"message":"...","metadata":{"error_type":"rate_limit_exceeded"}}`),
306
+ * not as a structured ApiError. So this parses defensively — peeling that nested JSON when
307
+ * present — rather than trusting a shape that is usually absent for the errors that matter most.
308
+ */
309
+ /** #2619 — how one `send()` turn ended; see its doc. */
310
+ type OpenCodeSendOutcome = "ok" | "failed" | "held";
311
+
312
+ export interface OpenCodeAssistantError {
313
+ name: string;
314
+ statusCode?: number;
315
+ errorType?: string;
316
+ message: string;
317
+ isRetryable: boolean;
318
+ }
319
+
320
+ export function parseOpenCodeAssistantError(raw: unknown): OpenCodeAssistantError | undefined {
321
+ if (!isRecord(raw)) return undefined;
322
+ const name = stringValue(raw.name) ?? "UnknownError";
323
+ const data = isRecord(raw.data) ? raw.data : {};
324
+ let statusCode = typeof data.statusCode === "number" ? data.statusCode : undefined;
325
+ let message = stringValue(data.message) ?? "";
326
+ let errorType: string | undefined;
327
+ const trimmed = message.trim();
328
+ if (trimmed.startsWith("{")) {
329
+ try {
330
+ const nested = JSON.parse(trimmed) as unknown;
331
+ if (isRecord(nested)) {
332
+ if (statusCode === undefined && typeof nested.code === "number") statusCode = nested.code;
333
+ if (typeof nested.message === "string" && nested.message) message = nested.message;
334
+ if (isRecord(nested.metadata) && typeof nested.metadata.error_type === "string") errorType = nested.metadata.error_type;
335
+ }
336
+ } catch { /* not JSON — keep the raw (already-human) message */ }
337
+ }
338
+ const isRetryable = data.isRetryable === true
339
+ || statusCode === 429
340
+ || (typeof statusCode === "number" && statusCode >= 500)
341
+ || errorType === "rate_limit_exceeded";
342
+ return {
343
+ name,
344
+ ...(statusCode !== undefined ? { statusCode } : {}),
345
+ ...(errorType ? { errorType } : {}),
346
+ message: message || `OpenCode reported ${name}`,
347
+ isRetryable,
348
+ };
349
+ }
350
+
351
+ /**
352
+ * #2443 — a 404 from OpenCode's own session-message endpoint means OpenRouter has no route for
353
+ * the requested provider/model at all (retired, mistyped, or never released) — generic across
354
+ * ANY OpenRouter-routed model, never a specific one. Classifying it as the SDK's canonical
355
+ * `model-unavailable` reason (already used by the Claude adapter for its own "model doesn't
356
+ * exist" stop-failure) instead of the catch-all `opencode-provider-error` is what lets a terminal
357
+ * failure surface as "model unavailable" rather than an undifferentiated provider error.
358
+ */
359
+ export function openCodeTerminalFailureReason(error: Pick<OpenCodeAssistantError, "statusCode">): string {
360
+ return error.statusCode === 404 ? "model-unavailable" : "opencode-provider-error";
361
+ }
362
+
363
+ /**
364
+ * #2397 — the operator-visible providerState for "still trying" (retrying in-session, or
365
+ * re-rolled to a fresh session and retrying), reusing `buildRateLimitProviderState`'s shape
366
+ * (label/kind/source/errorType/timestamps) rather than a bespoke one. `reason` is overridden away
367
+ * from `RATE_LIMIT_BLOCK_REASON` ("rate_limit") deliberately: that reason is special-cased in
368
+ * runner-core.ts for Claude's idle+blocked usage-limit hold (and is unconditionally cleared by any
369
+ * `status: "busy"` update — "forward progress lifts the hold"), which would erase this state on
370
+ * the very same busy event that carries it. A distinct reason avoids that collision while the
371
+ * `state: "blocked"` + `providerState` metadata still surfaces normally through the adapter's
372
+ * ordinary busy-claim path (the same path `permission.asked` already uses below).
373
+ */
374
+ export function openCodeUpstreamRetryState(input: { attempt: number; rerolled: boolean; message: string; errorType?: string }): ProviderState {
375
+ // #2397 round 2 (F7) — only call it a rate limit when the parsed error actually said so.
376
+ // `errorType` used to default to "rate_limit_exceeded" here, and the label was hardcoded to
377
+ // "Rate-limited upstream" regardless of cause, so a 500/503 (isRetryable via the >=500 branch
378
+ // in parseOpenCodeAssistantError, with no errorType at all) was misreported as a rate limit.
379
+ const rateLimited = input.errorType === "rate_limit_exceeded";
380
+ const base = buildRateLimitProviderState({
381
+ kind: "transient_overload",
382
+ errorType: input.errorType ?? "provider_error",
383
+ message: input.message,
384
+ source: "opencode",
385
+ });
386
+ const upstream = rateLimited ? "Rate-limited upstream" : "OpenCode upstream provider error";
387
+ return {
388
+ ...base,
389
+ reason: "provider-error",
390
+ label: input.rerolled
391
+ ? input.attempt > 0
392
+ ? `${upstream} — re-rolled to a new OpenCode session, retrying (attempt ${input.attempt}/${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS})…`
393
+ : `${upstream} — re-rolled to a new OpenCode session, retrying…`
394
+ : `${upstream}, retrying (attempt ${input.attempt}/${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS})…`,
395
+ resumeAttempt: input.attempt,
396
+ };
397
+ }
398
+
399
+ /**
400
+ * #2389 — what the synchronous `/message` POST hands back: the completed assistant message.
401
+ *
402
+ * This is ground truth for the incident and needs no inference at all. The stranded agent's turn
403
+ * came back `finish=stop` with no text part, no tool part and zero tokens — an empty completion the
404
+ * runner then reported as a clean idle. The response body carrying exactly that was being discarded.
405
+ *
406
+ * Read defensively: OpenCode's wire is only pinned at v1.18.21, so an unrecognised shape yields
407
+ * `undefined` counts and simply contributes no signal, leaving the cross-provider ledger to decide.
408
+ *
409
+ * #2397 — also carries the assistant message's `error`, when positively parseable. This is a
410
+ * STRONGER signal than `empty`: a turn can end with a non-empty `parts` count (the live incident's
411
+ * completion carried one bare step-start part) and still be a turn-ending PROVIDER failure, so
412
+ * `error` is checked ahead of (and independently of) the `empty` heuristic by every reader.
413
+ */
414
+ export interface OpenCodeCompletion {
415
+ parts?: number;
416
+ /**
417
+ * #2397 round 3 (HIGH-1) — `parts` count MINUS bookkeeping-only parts (`step-start`,
418
+ * `step-finish`), which the server emits on every turn — including one that produced nothing
419
+ * but a provider error. The live incident this issue was filed on captured exactly that shape:
420
+ * one bare `step-start` part, zero tokens, a 429. `parts` alone is 1, which reads as "the model
421
+ * produced output"; `outputParts` is 0, which correctly does not. Present whenever `parts` is.
422
+ */
423
+ outputParts?: number;
424
+ tokens?: number;
425
+ finish?: string;
426
+ error?: OpenCodeAssistantError;
427
+ /**
428
+ * #2413 — the terminal assistant message's TEXT, concatenated from its text parts. This
429
+ * synchronous POST returns the completed assistant message (ground truth, #2389), so the
430
+ * turn-final response is read from it directly rather than reconstructed from a possibly-late
431
+ * or lost SSE frame. Present only when the completed message carried assistant text; absent for
432
+ * a tools-only or empty turn.
433
+ */
434
+ text?: string;
435
+ /** True only when the shape WAS understood and it says the model produced nothing. */
436
+ empty: boolean;
437
+ /**
438
+ * #2605 — the terminal assistant message's REASONING as per-part steps (`{stepId, body}[]`,
439
+ * wire order). Present only when the completed message carried non-empty reasoning; absent
440
+ * for text/tools-only or empty turns. Per-part identity (not a joined string) is what lets
441
+ * the turn-final emit upsert the live SSE rows instead of duplicating them — the server keys
442
+ * step rows on stepId ONLY. Read from the same POST body as `text`, so a turn whose SSE
443
+ * reasoning frames were never observed still surfaces its thinking in the trace.
444
+ */
445
+ reasoningSteps?: OpenCodeReasoningStep[];
446
+ /**
447
+ * #2605 — the same reasoning as one joined string (the steps' bodies, `\n\n` join mirroring
448
+ * `text`). Kept for the completion log line only — the EMIT path reads `reasoningSteps`.
449
+ */
450
+ reasoning?: string;
451
+ }
452
+
453
+ /** #2397 round 3 (HIGH-1) — a bookkeeping marker the server emits around every turn regardless of
454
+ * outcome, never evidence the model produced anything. An unrecognised part shape is counted as
455
+ * output (the conservative default): under-counting risks re-treating real work as empty and
456
+ * re-executing it, which is the worse failure. */
457
+ function isOutputPart(part: unknown): boolean {
458
+ if (!isRecord(part)) return true;
459
+ const type = stringValue(part.type);
460
+ return type !== "step-start" && type !== "step-finish";
461
+ }
462
+
463
+ /** Whether a parsed completion is proof the model produced something — the ONE predicate both the
464
+ * error branch (HIGH-1) and the ordinary success branch must agree on, so a step-start-only body
465
+ * can never read as "produced output" from either path. */
466
+ function completionProducedOutput(completion: OpenCodeCompletion): boolean {
467
+ return (completion.outputParts ?? completion.parts ?? 0) > 0 || (completion.tokens ?? 0) > 0;
468
+ }
469
+
470
+ function countTokens(value: unknown): number | undefined {
471
+ if (typeof value === "number" && Number.isFinite(value)) return value;
472
+ if (!isRecord(value)) return undefined;
473
+ // `{ input, output, reasoning, cache: {...} }` on the pinned version, plus — confirmed against a
474
+ // real captured v1.18.21 session (#2355) — a top-level `total` that is ALREADY the sum of every
475
+ // other leaf here (`input + output + reasoning + cache.read + cache.write`). When present it is
476
+ // authoritative and must be returned directly: falling through to the leaf sum below as well
477
+ // would double-count it (`total` + its own constituent parts).
478
+ if (typeof value.total === "number" && Number.isFinite(value.total)) return value.total;
479
+ // Sum the numeric leaves so a renamed or added field neither breaks the read nor silently zeroes
480
+ // it.
481
+ let total: number | undefined;
482
+ for (const entry of Object.values(value)) {
483
+ const nested = countTokens(entry);
484
+ if (nested !== undefined) total = (total ?? 0) + nested;
485
+ }
486
+ return total;
487
+ }
488
+
489
+ export function openCodeCompletion(body: unknown): OpenCodeCompletion {
490
+ if (!isRecord(body)) return { empty: false };
491
+ // Either `{ info, parts }` or the message object itself, depending on the endpoint's mood.
492
+ const info = isRecord(body.info) ? body.info : body;
493
+ const parts = Array.isArray(body.parts) ? body.parts : Array.isArray(info.parts) ? info.parts : undefined;
494
+ const tokens = countTokens(info.tokens);
495
+ // `info.time` is ALWAYS present on an AssistantMessage, so guarding the read on its absence made
496
+ // this always yield undefined. The result was that the one log line written to capture
497
+ // `finish=stop` never printed it.
498
+ const finish = stringValue(info.finish);
499
+ const error = parseOpenCodeAssistantError(info.error);
500
+ const outputParts = parts?.filter(isOutputPart).length;
501
+ const text = openCodeAssistantText(parts);
502
+ const reasoningSteps = openCodeAssistantReasoningSteps(parts);
503
+ const reasoning = reasoningSteps.length ? reasoningSteps.map((step) => step.body).join("\n\n") : undefined;
504
+ return {
505
+ ...(parts ? { parts: parts.length, outputParts: outputParts! } : {}),
506
+ ...(tokens !== undefined ? { tokens } : {}),
507
+ ...(finish ? { finish } : {}),
508
+ ...(error ? { error } : {}),
509
+ ...(text ? { text } : {}),
510
+ ...(reasoningSteps.length ? { reasoningSteps } : {}),
511
+ ...(reasoning ? { reasoning } : {}),
512
+ // Only claim emptiness when the shape was actually understood: a body we could not parse must
513
+ // never be reported as an empty completion.
514
+ // #2397 round 3 review (LOW-4r) — must agree with `completionProducedOutput`'s `outputParts`,
515
+ // not raw `parts`, or a bookkeeping-only body (step-start, tokens:0, no error) slips past BOTH
516
+ // predicates in send(): not `producedOutput` (correctly), but also not `empty` — landing on
517
+ // "if (!completion.empty) return true" with no turnActivityCb, no retry-hold clear, and no
518
+ // deterministic idle close.
519
+ empty: outputParts !== undefined && outputParts === 0 && tokens !== undefined && tokens === 0,
520
+ };
521
+ }
522
+
523
+ function describeCompletion(completion: OpenCodeCompletion): string {
524
+ const bits: string[] = [];
525
+ if (completion.parts !== undefined) bits.push(`${completion.parts} part(s)${completion.outputParts !== undefined && completion.outputParts !== completion.parts ? ` (${completion.outputParts} output)` : ""}${completion.reasoningSteps?.length ? ` (${completion.reasoningSteps.length} reasoning)` : ""}`);
526
+ if (completion.tokens !== undefined) bits.push(`${completion.tokens} token(s)`);
527
+ if (completion.finish) bits.push(`finish=${completion.finish}`);
528
+ if (completion.error) bits.push(`error=${completion.error.name}${completion.error.statusCode !== undefined ? ` ${completion.error.statusCode}` : ""}`);
529
+ if (!bits.length) return "";
530
+ return ` → ${bits.join(", ")}${completion.empty ? " — EMPTY COMPLETION" : ""}`;
531
+ }
532
+
533
+ /** Bound on the live per-session bookkeeping in `OpenCodeAdapter` (#2347): a headless agent can
534
+ * run for days across many turns, so unbounded maps keyed by message/part id would leak. Both
535
+ * `messageRoles` and `emittedTextParts` are pruned back to this size, oldest first, whenever a
536
+ * write would exceed it. */
537
+ const OPENCODE_EVENT_STATE_LIMIT = 500;
538
+
539
+ function pruneToLimit(map: Map<string, unknown> | Set<string>, limit: number): void {
540
+ const overflow = map.size - limit;
541
+ if (overflow <= 0) return;
542
+ const it = map.keys();
543
+ for (let i = 0; i < overflow; i++) {
544
+ const next = it.next();
545
+ if (next.done) break;
546
+ map.delete(next.value);
547
+ }
548
+ }
549
+
550
+ export class OpenCodeAdapter implements ProviderAdapter {
551
+ readonly reportsContextResets = true;
552
+ readonly provider: string;
553
+ readonly statusSourceId: string;
554
+ // #2440 — unlocks `context.resume = "native"` capability advertisement (see
555
+ // runtimeProviderContextCapabilities). `opts.instructions` is deliberately NOT threaded into the
556
+ // `/compact` HTTP call itself (see `performCompact`'s doc) — the relay's own reactive
557
+ // continuation inject already delivers it as a follow-up message either way, exactly like
558
+ // Codex's own compact() (also instructions-blind) relies on for its forced-resume path.
559
+ // IMPORTANT semantic gap this flag papers over: unlike Claude's real `/compact <instructions>`,
560
+ // OpenCode's `/compact` endpoint is NOT instruction-steerable — `performCompact` never sends the
561
+ // continuation on the wire at all, so the summarizer itself never sees it. Setting this flag true
562
+ // is what makes `resume:"native"` selectable; the actual delivery guarantee comes entirely from
563
+ // the reactive inject, not from this flag. Follow-up #2442 tracks a cleaner capability bit that
564
+ // distinguishes "native resume is offered" from "the compact call itself accepts steering text",
565
+ // so a future reader doesn't read this as "the summary is instructable" when it is not.
566
+ readonly compactSupportsInstructions = true;
567
+ private statusCb: (status: ProviderStatusUpdate) => void = () => {};
568
+ private sessionEventCb: (event: ProviderSessionEvent) => void = () => {};
569
+ private turnActivityCb: () => void = () => {};
570
+ // `message.updated` (role) always precedes that message's `message.part.updated` events on the
571
+ // real v1.18.21 wire (verified live against OpenRouter meta/muse-spark-1.3-contributor, #2347) — spawn()
572
+ // subscribes before any prompt is sent, so there is no race for the normal launch flow.
573
+ private readonly messageRoles = new Map<string, string>();
574
+ private readonly emittedTextParts = new Set<string>();
575
+ // #2354 — stepId → highest lifecycle rank already emitted for that tool part, so a duplicate or
576
+ // reordered `message.part.updated` (a late `running` after `completed`, a repeated frame) never
577
+ // regresses or double-emits the step. Pruned to OPENCODE_EVENT_STATE_LIMIT like the maps above.
578
+ private readonly emittedToolSteps = new Map<string, number>();
579
+ // #2437 — the ToolPart wire shape only carries `input` while `running`; a `completed` decode has
580
+ // none. Cache the redacted params captured at running, keyed by stepId, and carry them forward
581
+ // onto the terminal event so the row's params don't vanish when the server upserts it in place
582
+ // (upsertSessionStepInPlace replaces the whole payload with the incoming one, not a merge).
583
+ private readonly toolStepParams = new Map<string, unknown>();
584
+ // #2605 — reasoning part id → full body already emitted for that part, so a repeated or
585
+ // finalized `message.part.updated` frame for the same reasoning part emits the CURRENT full
586
+ // text (the server upserts the trace row in place by stepId) instead of appending a duplicate
587
+ // row. Pruned to OPENCODE_EVENT_STATE_LIMIT like the maps above.
588
+ private readonly emittedReasoningParts = new Map<string, string>();
589
+ // #2413 review (HIGH-1) — a genuinely PER-SEND turn id, minted at the top of each `send()` and
590
+ // used for the busy edge, narration, tool steps, and the turn-final response alike. Was
591
+ // `process.meta.sessionId`, which PERSISTS across sends: two turns in one session then shared a
592
+ // turnId, so lineage dedup (promoteLineageCapturedResponse/flushWorkFinalReports) forwarded the
593
+ // FIRST turn's body and dropped the SECOND — the real final report never reached the coordinator.
594
+ private sendTurnId: string | undefined;
595
+ // #2397 round 2 (F3/F4) — live-confirmed: the real server emits its OWN busy/idle
596
+ // session.status pair around EVERY attempt, including failed ones (two `idle` events for one
597
+ // errored turn). Left alone, each failed attempt's idle looks like a real provider-turn close
598
+ // to runner-core's #2389 seam: it clears `currentTurnId` and judges that blip as an empty turn,
599
+ // then the next attempt's busy mints a brand-new turn id — up to
600
+ // `OPENCODE_ERROR_RETRY_MAX_ATTEMPTS * 2` extra empty-turn closes the operator sees, and a
601
+ // premature nudge/escalation mid-retry. Raised for the WHOLE `send()` call (see the loop entry),
602
+ // this swallows every transient idle for the call's own turn so the whole retry sequence stays
603
+ // ONE turn from the runner's view.
604
+ // #2397 round 5 review (MED-1) — this is now a STICKY suppression, not cleared at close time:
605
+ // `closeSuppressedTurn` used to lower it as it fired, so a HEALTHY turn's own trailing real SSE
606
+ // idle — if processed after the POST already resolved — sailed through unsuppressed and closed
607
+ // the same turn a SECOND time (duplicate reInjectPendingObligation, duplicate
608
+ // completeObservedTaskClaims/Task-done). The invariant that actually holds: an idle belonging to
609
+ // a turn `send()` already closed must stay swallowed; only an idle for a turn that STARTS AFTER
610
+ // that close may pass.
611
+ // #2397 round 6 review (MED-1r) — round 5 had `handleEvent`'s BUSY door lower this flag whenever
612
+ // `turnInFlight` was false, on the assumption that such a busy edge could only be a genuinely new
613
+ // turn. That is a timing assumption, not an invariant: `subscribe()` dispatches a whole SSE chunk
614
+ // synchronously in one loop, and the SSE/POST sockets have no cross-ordering guarantee, so a late
615
+ // own-turn busy+idle pair batched together AFTER the POST resolves cleared suppression via the
616
+ // busy half and then let the idle half sail straight through — double-closing the turn.
617
+ // #2397 round 7 review (MED-1/MED-2/LOW-1) — round 6 only made the reopen one-shot (the FIRST
618
+ // post-close idle lowered this flag again), which still let a SECOND trailing idle in the same
619
+ // window (two late idles; a 429-retry where both attempts' own idles land after send() returns;
620
+ // a foreign child session's busy+idle, since `handleEvent` never filters `properties.sessionID`)
621
+ // double-close. There is no legitimate case this flag ever needs to go back to `false` FOR A
622
+ // DETERMINISTIC turn outcome: OpenCode is headless with no server-initiated turns, and every
623
+ // real turn-boundary transition for a turn `send()` actually SAW END is already emitted
624
+ // EXPLICITLY by `send()` (its own `busy` at each attempt's start) and by
625
+ // `closeSuppressedTurn`/`failTurn` (their own `idle`/`error`) — never by this SSE echo.
626
+ // #2425 — that reasoning has exactly one hole: it assumes `send()` always learns how its own
627
+ // turn ended. `sendMessage`'s POST can instead THROW with the server-side turn's outcome
628
+ // unknown (a client-side timeout on a turn that keeps running upstream) — see `turnAbandoned`
629
+ // below. In that one case ONLY, staying permanent forever would swallow every subsequent real
630
+ // busy/idle for the rest of this adapter instance's life, freezing status at whatever
631
+ // `send()`'s own `finally` guessed. Both SSE doors below therefore also check `turnAbandoned`
632
+ // — the per-session filter (also added for #2425) is what makes that safe: a foreign session's
633
+ // busy/idle can never reach here regardless of `turnAbandoned`, so lifting suppression for OUR
634
+ // OWN session's real events reopens no hazard round 7 was guarding against.
635
+ private suppressProviderIdle = false;
636
+ // #2425 — true once THIS call's own `finally` closed the turn WITHOUT any deterministic
637
+ // success/failure branch having run first (i.e. `sendMessage` or something else in the retry
638
+ // loop threw — a client-side timeout being the motivating case, #2425's incident). The server-
639
+ // side outcome is genuinely unknown in that case, unlike a normal completion or `failTurn`
640
+ // (both of which set `turnClosed` themselves before the `finally` runs, so `turnAbandoned` stays
641
+ // false for them — see the `finally` block). While true, the two SSE doors above stop enforcing
642
+ // the sticky `suppressProviderIdle` for this session's real events, letting the server's own
643
+ // busy/idle re-open and then close the turn — self-healing instead of freezing idle forever.
644
+ // Reset to `false` at the top of every `send()` call: once a NEW call takes over the turn
645
+ // boundary, its own explicit busy/idle is authoritative again, and any stray leftover event from
646
+ // a truly-abandoned previous turn must stay suppressed rather than corrupt the new turn's state.
647
+ private turnAbandoned = false;
648
+ // #2619 — a session re-roll already spent in the current provider-hold episode; cleared by the
649
+ // first turn that goes through (send() → "ok"). Bounds a long wall to ONE history-dropping re-roll.
650
+ private rerolledThisHoldEpisode = false;
651
+ // #2775 — a wall OpenCode's own retry hit during this send(); the session is aborted and send() holds.
652
+ private serverRetryWall?: OpenCodeServerRetry;
653
+ // #2397 round 5 review (MED-1/LOW-2) — true from the moment `send()` is actually about to run an
654
+ // attempt (past the readiness guard, so a "session not ready" throw never sets it) through the
655
+ // call's `finally`.
656
+ // #2397 round 7 review — no longer read by either SSE door (both now key only on the permanent
657
+ // `suppressProviderIdle` above). Still captured by `send()`'s `finally` (round 6, MED-2r) to
658
+ // decide whether THIS call ever actually started an attempt, so a "session not ready" throw
659
+ // never triggers a close for a turn that never began.
660
+ private turnInFlight = false;
661
+ // #2397 round 5 review (MED-1) — true once THIS call's own close has already fired (via
662
+ // `closeSuppressedTurn` or `failTurn`), reset at the top of every `send()` call. Distinct from
663
+ // `suppressProviderIdle` (which deliberately stays raised after firing, per above): this is the
664
+ // idempotency guard that stops the SAME call's multiple close sites — an explicit branch call
665
+ // plus the `finally` fallback — from emitting a second idle for one call.
666
+ // #2425 — also doubles as the double-close guard for the `turnAbandoned` escape hatch: once
667
+ // abandonment force-closes a turn, this stays true until a genuine same-session busy re-arms it
668
+ // (see the busy door), so a stray trailing idle for the SAME abandoned turn finds it already
669
+ // closed and is a no-op rather than a second `idle` dispatch.
670
+ private turnClosed = false;
671
+ // #2431 round 1 (HIGH) — FIFO serializer making `clearContext()` safe against an IN-FLIGHT turn.
672
+ // `agent.clearContext` is dispatched fire-and-forget (dispatchCommand holds no provider-input
673
+ // ticket for it — PROVIDER_INPUT_COMMAND_TYPES is inject-only) and OpenCode's `/message` POST is
674
+ // SYNCHRONOUS (#2389), so the model calling relay_compact_and_resume from within its own turn
675
+ // means the command genuinely runs WHILE send() is still awaiting that POST. send()'s retry loop
676
+ // re-reads process.meta.sessionId every iteration and cannot tell its own #2397 reroll from an
677
+ // external swap, so an unserialized clear redirected the live turn's retries/rerolls — and their
678
+ // reportContextUsage writes — straight into the freshly cleared session. Every writer of that
679
+ // per-call state therefore goes through this queue: both send() call sites (deliverInitialPrompt/
680
+ // deliver — already mutually serialized upstream via providerInput.run, so uncontended between
681
+ // themselves) and clearContext() itself, so a clear lands strictly BETWEEN turns: the live turn
682
+ // completes or fails wholly on the session it started with, then the swap happens, then the next
683
+ // delivery — the reactive continuation inject included — starts on the new session. The wait is
684
+ // bounded by the in-flight turn's own AbortSignal deadline (openCodeTurnTimeoutMsFromEnv), the
685
+ // same bound every turn already carries; there is deliberately no separate timer to fail the
686
+ // command early, because "clear anyway on timeout" would reintroduce exactly this bug.
687
+ private turnQueue: Promise<unknown> = Promise.resolve();
688
+
689
+ // ─── #2539 Phase B — non-destructive soft-steer lane (default-OFF kill switch) ───
690
+ // Durable-until-consumed ledger for positively-persisted soft batches (persistence != delivery).
691
+ private readonly softSteer = new OpenCodeSoftSteerLedger();
692
+ // The soft lane's OWN one-in-flight FIFO (fast loopback POSTs) — deliberately NOT turnQueue:
693
+ // serializing soft steers behind whole turns is the 84-minute #2539 delay this lane exists to
694
+ // remove. Only the POST+reconcile run here; the wake turn (rare, fallback) goes through
695
+ // turnQueue like any normal turn so it owns a real busy/idle pair.
696
+ private softSteerQueue: Promise<unknown> = Promise.resolve();
697
+ // Most recent live process, retained so the turn-close wake hook can reach the session without
698
+ // threading a process through closeSuppressedTurn/closeEventTurn. Best-effort only: a stale
699
+ // process fails safely in send()'s own readiness guard.
700
+ private softSteerProcess: ManagedProcess | undefined;
701
+
702
+ /**
703
+ * #2544 — the operator-facing capability bit. DEFAULT-ON since the review-gated flip: the
704
+ * deployed-fleet canary soak (one opencode/zai agent, 2026-09-11) proved the end-to-end path
705
+ * through A's seam — fold-in, durable-until-consumed settlement, no dup/loss/hot-loop, no run
706
+ * disruption. The per-offer policy reads `ProviderConfig.activeTurnMessageDelivery` via
707
+ * resolveActiveTurnMessageDelivery (the shared config seam; this adapter's default is "steer",
708
+ * an explicit "defer" stays the per-agent kill switch). Both opencode and zai inherit — one
709
+ * switch, no per-provider settings (A's design A: advertise only enabled+proven).
710
+ */
711
+ readonly supportsMidTurnSteer = true;
712
+
713
+ // #2459 — event-driven turn identity for TERMINAL-origin turns. OpenCode is headless with no
714
+ // server-initiated turns (the #2397 round-7 invariant), so a session.status:busy arriving for
715
+ // the active session OUTSIDE send() can only be a prompt typed into an attached web-terminal
716
+ // TUI. send() is not running then, so its sticky `suppressProviderIdle` would otherwise swallow
717
+ // the whole turn, and the events would fall back to the STALE `sendTurnId` of the last headless
718
+ // turn — mislabeled grouping. Instead: mint a provider turn id on that busy, associate the
719
+ // turn's user/assistant/tool message ids with it, and close it on the matching idle.
720
+ private eventTurnId: string | undefined;
721
+ // Assistant text accumulated (finalized text parts, active session) while the event turn is
722
+ // open, so its close can emit the turn-final response exactly like send()'s POST path does.
723
+ private eventTurnText: string[] = [];
724
+ // #2605 — reasoning observed this event turn, keyed by part id to the LATEST body seen. The
725
+ // terminal-origin path has no completed-message POST to read back from, so the close must
726
+ // reconcile from what the live door saw — but keyed per part (not a body pool): a growth
727
+ // frame for an already-emitted part only needs the newest snapshot, and an identical body in
728
+ // a LATER turn must never read as already-seen. Cleared with eventTurnText on mint/close.
729
+ // Bounded: part ids are server-unique per turn and the map is reset every turn, so it holds
730
+ // at most one entry per reasoning part of the CURRENT turn — no prefix scan, no growth.
731
+ private eventTurnReasoning = new Map<string, string>();
732
+ // #2459 round 1 (review D) — the EVENT-SOURCE discriminator replacing the round-0 time
733
+ // window. A terminal-origin turn is always PRECEDED by its prompt: the attached TUI's
734
+ // POST creates the user message on the active session before/while the turn's busy edge
735
+ // flows, so the message.updated(role=user) door arms this flag. The busy door admits a
736
+ // terminal turn only when armed (or when no headless turn ever suppressed, or #2425
737
+ // abandonment). A HEADLESS echo — the buffered busy+idle pair of a turn send() just
738
+ // closed — has NO such fresh user message by construction, so it stays suppressed with NO
739
+ // time bound: a delayed echo of any age is indistinguishable from a stale one and both are
740
+ // dropped, restoring #2397 round-7 permanence positively instead of heuristically.
741
+ private pendingTerminalPrompt = false;
742
+ // #2459 round 2 (review D) — the terminal user prompt BUFFER. The [user message.updated →
743
+ // user part → busy] wire ordering means the prompt can arrive BEFORE the busy that mints
744
+ // the terminal turn id; buffering keeps the prompt from publishing a raw session id as its
745
+ // turnId and from splitting off from its trace/final. SINGLE SLOT (the bound): a newer
746
+ // prompt flushes the older one under the session pseudo-turn first. Flush points: busy
747
+ // admission (minted id — the primary path), first trace activity, any active-session idle,
748
+ // send() takeover, and the clear/re-roll swap — the fallback id is ALWAYS the session
749
+ // pseudo-turn or the minted id, never a stale headless sendTurnId.
750
+ private bufferedTerminalPrompt: { body: string; occurredAt: number } | undefined;
751
+ // #2459 round 1 (review D) — permanent marker that at least one headless turn ran on this
752
+ // process. Once true, the busy door admits a terminal-origin turn ONLY through the armed
753
+ // `pendingTerminalPrompt` (fresh first-seen user message), with NO time bound: a headless
754
+ // busy echo of any age stays suppressed. Fresh adapters (no headless turn yet) admit freely,
755
+ // preserving pure-terminal sessions whose busy arrives before any user-message echo.
756
+ private headlessTurnSeen = false;
757
+ // #2459 round 1 (review D) — user-message ids already observed on this process (bounded like
758
+ // the other event-state maps). A REPEAT user message.updated is a buffered duplicate of a
759
+ // turn that already ran (send()'s own POST echo delivered late) and must never re-arm the
760
+ // terminal gate; only a FIRST-SEEN user message is evidence of a freshly typed TUI prompt.
761
+ private seenUserMessageIds = new Set<string>();
762
+
763
+ private readonly sleepImpl: (ms: number) => Promise<void>;
764
+
765
+ // #2484 — the token-usage spine transport, wired by runner-core (Claude's #2482/#2491 seam).
766
+ private readonly apiBudgets = new WeakMap<ManagedProcess, OpenCodeApiBudget>();
767
+
768
+ private tokenUsageReporter?: ProviderTokenUsageReporter;
769
+ // #2484 round 1 review (F2/F3) — collapses the message.updated door's streaming echoes to one
770
+ // spine post per message; flushed early at real turn boundaries (see flushAll() call sites).
771
+ private readonly tokenUsagePoster = new OpenCodeTokenUsagePoster(() => this.tokenUsageReporter);
772
+
773
+ constructor(
774
+ providerOrSleep: string | ((ms: number) => Promise<void>) = "opencode",
775
+ sleepImpl: (ms: number) => Promise<void> = (ms) => Bun.sleep(ms),
776
+ ) {
777
+ this.provider = typeof providerOrSleep === "string" ? providerOrSleep : "opencode";
778
+ this.statusSourceId = this.provider;
779
+ this.sleepImpl = typeof providerOrSleep === "function" ? providerOrSleep : sleepImpl;
780
+ openCodeBackend(this.provider);
781
+ }
782
+
783
+ onStatusChange(cb: (status: ProviderStatusUpdate) => void): void {
784
+ this.statusCb = cb;
785
+ }
786
+
787
+ onTurnActivity(cb: () => void): void {
788
+ this.turnActivityCb = cb;
789
+ }
790
+
791
+ onSessionEvent(cb: (event: ProviderSessionEvent) => void): void {
792
+ this.sessionEventCb = cb;
793
+ }
794
+
795
+ configureTokenUsageReporter(reporter: ProviderTokenUsageReporter): void {
796
+ this.tokenUsageReporter = reporter;
797
+ }
798
+
799
+ // #2484 round 2 review (MED-1) — the runner's ONE awaited, time-boxed teardown seam
800
+ // (RunnerCore.settleTerminalTokenUsage, wired at stop() / restartUnexpectedProviderExit /
801
+ // runPreSessionDestroy) previously no-op'd for OpenCode because it bails on a falsy
802
+ // transcriptPath — a Claude-shaped precondition OpenCode never has (it has no transcript file
803
+ // to tail; #2484's capture is event-sourced from the message.updated SSE door). Owning that
804
+ // bail here — ignore transcriptPath, just settle the poster — means the crash-exit and
805
+ // pre-session-destroy paths (which never call adapter.shutdown()) now reach the same debounced
806
+ // report shutdown() already flushes, instead of losing the session's final token counts.
807
+ async captureFinalTokenUsage(_transcriptPath: string | undefined, _ctx: ProviderSessionTurnContext): Promise<void> {
808
+ await this.tokenUsagePoster.flushAll();
809
+ }
810
+
811
+ supportsUnexpectedExitRestart(): boolean {
812
+ return true;
813
+ }
814
+
815
+ async spawn(config: RunnerSpawnConfig): Promise<ManagedProcess> {
816
+ assertOpenCodeApprovalMode(config.approvalMode);
817
+ const launch = await resolveOpenCodeLaunchModel(config, config.providerConfig as ProviderConfig, this.provider);
818
+ openCodeApiKey(config, launch.qualified);
819
+ const args = this.buildSpawnArgs(config, config.providerConfig as ProviderConfig, launch);
820
+ const apiBudget = launch.backendID === "openai" ? new OpenCodeApiBudget(config) : undefined;
821
+ await apiBudget?.start(relayEnv(config.env, "AGENT_RELAY_PROVIDER_SESSION_ID") ?? crypto.randomUUID());
822
+ const server = await spawnOpenCodeServer(args, apiBudget);
823
+ const password = args.env.OPENCODE_SERVER_PASSWORD!;
824
+ const client = new OpenCodeClient(args.env.OPENCODE_SERVER_URL!, password, fetch, this.provider);
825
+ try {
826
+ await client.waitForReady();
827
+ const sessionTitle = config.label ?? config.agentId;
828
+ const sessionId = await client.createSession(sessionTitle);
829
+ const events = new AbortController();
830
+ // sessionTitle is also read by a #2397 session re-roll (a fresh session created mid-flight
831
+ // after repeated retryable errors); sessionId is MUTATED IN PLACE there, so every other
832
+ // reader of process.meta.sessionId (send/interrupt/shutdown) picks the new session up too.
833
+ // #2459 — the launch metadata terminalAttachSpec() needs is retained here as well:
834
+ // resolved executable, loopback server URL, agent cwd and the server password. These live
835
+ // ONLY in this runner-local memory (and the password ONLY re-emerges as the guest process
836
+ // env inside the attach spec); they are never persisted, logged, or returned anywhere else.
837
+ // #2599 — the payload record (when present) pins meta.model to the server-computed
838
+ // qualified name, so later reportContextUsage calls keep payload precedence instead of
839
+ // re-deriving from a possibly-ambiguous alias. Manifest path unchanged when absent.
840
+ // #2659 — meta.model is the normalized launch model; turns split it verbatim, never re-resolve.
841
+ const process: ManagedProcess = { pid: server.pid, process: server, meta: { client, sessionId, events, model: launch.qualified, launchModel: launch.qualified, sessionTitle, config, relayContextSent: false, launchContextSent: false, attachCommand: args.command, cwd: args.cwd, serverUrl: args.env.OPENCODE_SERVER_URL!, serverPassword: password, stateDir: args.env.XDG_STATE_HOME } };
842
+ if (apiBudget) this.apiBudgets.set(process, apiBudget);
843
+ // #2443 — both used to be a bare `void promise.catch(...)`: a synchronous throw from
844
+ // `this.statusCb`/`handleEvent` inside the fulfillment or catch handler produced a SECOND,
845
+ // truly unhandled rejection nothing consumed (Bun exits(1) on that; nothing respawns the
846
+ // runner — #1618). `fireAndForget` guarantees the rejection — from either the initial call
847
+ // or the handler itself — is always logged, never escapes.
848
+ void fireAndForget(`opencode SSE subscribe (session ${sessionId})`, () => client.subscribe(events.signal, (event) => this.handleEvent(event, process)), (message) => logger.error("opencode", message));
849
+ void fireAndForget(`opencode server exit status callback (session ${sessionId})`, async () => {
850
+ const code = await server.exited;
851
+ await this.tokenUsagePoster.flushAll();
852
+ await apiBudget?.settle().catch(() => {});
853
+ this.statusCb(code === 0 ? "offline" : "error");
854
+ }, (message) => logger.error("opencode", message));
855
+ return process;
856
+ } catch (error) {
857
+ killPid(server.pid, "SIGKILL");
858
+ await server.exited;
859
+ await apiBudget?.settle().catch(() => {});
860
+ throw error;
861
+ }
862
+ }
863
+
864
+ // #2659 — spawn() passes the registry-normalized launch model; direct callers resolve statically.
865
+ buildSpawnArgs(config: RunnerSpawnConfig, providerConfig: ProviderConfig, launch = openCodeLaunchModel(config.model, this.provider, openCodeModelRecord(config.modelRecord, this.provider))): SpawnArgs {
866
+ return buildOpenCodeSpawnArgs(config, providerConfig, this.provider, launch);
867
+ }
868
+
869
+ /**
870
+ * #2459 — the web-terminal attach spec for an interactive `opencode attach` guest TUI,
871
+ * reusing the codex guest-terminal path downstream (runner /terminal/attach-spec →
872
+ * orchestrator guest tmux → Relay's authenticated terminal proxy → dashboard xterm).
873
+ *
874
+ * Fail-closed BEFORE readiness/session: every input is read from the launch metadata
875
+ * retained at spawn() and a missing piece throws rather than degrading. The command is
876
+ * exactly the pinned-CLI seam the #2459 feasibility verified against v1.18.21:
877
+ * `opencode attach <loopback server URL> --session <active session id> --dir <agent cwd>
878
+ * --pure`. `--dir` MUST equal the server/session cwd — OpenCode sessions are
879
+ * project-directory scoped and attach fails (or silently diverges) when the client and
880
+ * server directories differ.
881
+ *
882
+ * SECRET HANDLING (round-1 review A): the generated server password NEVER travels in the
883
+ * spec — not as `--password`, not in the URL, and (since round 1) not in a response-body
884
+ * env record either. terminalAttachSpec() pre-stages it in a 0600 env file under the
885
+ * runner's own XDG state dir and the spec carries only that PATH (`envFile`); the
886
+ * orchestrator's guest wrapper sources the file into the guest process environment, so the
887
+ * secret materializes solely in the guest process env. The server URL is loopback-only.
888
+ */
889
+ async terminalAttachSpec(process: ManagedProcess): Promise<TerminalAttachSpec> {
890
+ const sessionId = stringValue(process.meta?.sessionId);
891
+ if (!sessionId) throw new Error("OpenCode session is not ready");
892
+ const serverUrl = typeof process.meta?.serverUrl === "string" && process.meta.serverUrl ? process.meta.serverUrl : "";
893
+ if (!serverUrl) throw new Error("OpenCode server URL is unavailable");
894
+ const serverPassword = typeof process.meta?.serverPassword === "string" && process.meta.serverPassword ? process.meta.serverPassword : "";
895
+ if (!serverPassword) throw new Error("OpenCode server password is unavailable");
896
+ const stateDir = typeof process.meta?.stateDir === "string" && process.meta.stateDir ? process.meta.stateDir : "";
897
+ if (!stateDir) throw new Error("OpenCode state dir is unavailable");
898
+ const attachCommand = typeof process.meta?.attachCommand === "string" && process.meta.attachCommand ? process.meta.attachCommand : "opencode";
899
+ const cwd = typeof process.meta?.cwd === "string" && process.meta.cwd ? process.meta.cwd : globalThis.process.cwd();
900
+ // Pre-stage the credential where the guest can read it: 0600 file in the runner-owned
901
+ // 0700 XDG state dir. Path-only propagation — the contents never leave this user's
902
+ // file system boundary, and nothing here lands in argv, HTTP bodies, logs, or persistence.
903
+ const envFile = join(stateDir, "terminal-env");
904
+ writeFileSync(envFile, `OPENCODE_SERVER_PASSWORD=${shellEscape(serverPassword)}\n`, { mode: 0o600 });
905
+ chmodSync(envFile, 0o600);
906
+ return {
907
+ mode: "guest",
908
+ provider: this.provider,
909
+ cwd,
910
+ command: [attachCommand, "attach", serverUrl, "--session", sessionId, "--dir", cwd, "--pure"],
911
+ envFile,
912
+ title: "OpenCode terminal",
913
+ ttlMs: 60 * 60 * 1000,
914
+ generation: openCodeTerminalGeneration(sessionId),
915
+ };
916
+ }
917
+
918
+ /**
919
+ * #2459 — the CURRENT attachment generation (derived from the active session id), read by
920
+ * the runner control server's GET /terminal/generation so the orchestrator can detect a
921
+ * session rotation without learning the session id or any credential. undefined when the
922
+ * session is not ready (nothing to check).
923
+ */
924
+ terminalAttachGeneration(process: ManagedProcess): string | undefined {
925
+ const sessionId = stringValue(process.meta?.sessionId);
926
+ return sessionId ? openCodeTerminalGeneration(sessionId) : undefined;
927
+ }
928
+
929
+ /**
930
+ * #2431 round 1 (HIGH) — run `operation` as the exclusive holder of the turn queue (see the
931
+ * `turnQueue` field doc for why this exists). FIFO: an operation enqueued while a turn is
932
+ * mid-flight starts only after that turn's promise settles, success or failure.
933
+ */
934
+ private enqueueTurn<T>(operation: () => Promise<T>): Promise<T> {
935
+ const settled = this.turnQueue.then(operation, operation);
936
+ this.turnQueue = settled.then(() => undefined, () => undefined);
937
+ return settled;
938
+ }
939
+
940
+ async deliverInitialPrompt(process: ManagedProcess, prompt: string, options?: PromptOfferOptions): Promise<void | "held"> {
941
+ // #2397 round 3 review (LOW-5r) — `send()`'s boolean is intentionally NOT surfaced as a throw
942
+ // here: this method's `Promise<void>` contract is shared by every adapter, and a throw would
943
+ // route into attemptInitialPromptDelivery's (runner-core.ts) generic retry-and-eventually-
944
+ // give-up path, re-sending the SAME prompt on top of the retry+reroll budget `send()` already
945
+ // exhausted. Instead, runner-core reads `this.terminalFailure` (set by failTurn's own `error`
946
+ // status, cleared by the `busy` edge this very call started with) right after this resolves, so
947
+ // it can log the terminal outcome correctly without inventing a second retry loop.
948
+ // #2619 — a refusal under a provider hold is surfaced ("held"): the runner parks the prompt.
949
+ if (await this.enqueueTurn(() => this.send(process, prompt, options?.inputObserver)) === "held") return "held";
950
+ }
951
+
952
+ async deliver(process: ManagedProcess, messages: Message[], ctx?: ProviderDeliverContext): Promise<DeliverResult> {
953
+ // #2539 Phase B — the paste-instant soft-steer routing (Phase A's single-call-path pattern,
954
+ // codex.ts:435): a mid-turn offer with steerable content goes through the adapter's
955
+ // deliverMidTurn (noReply persist, settlement deferred through ctx); boundary-only classes
956
+ // and the lane-off/idle cases keep the turn-end path below. Policy resolves per offer from
957
+ // the spawn config; this adapter's default is "steer" (#2544 flip), "defer" the kill switch.
958
+ if (messages.length > 0 && this.softSteerPolicyFor(process) === "steer" && ctx?.isRecipientMidTurn?.() === true) {
959
+ // r3 HIGH — steer ONLY the steerable partition, never the whole batch: the omitted ids
960
+ // (claimables and synthetic memory/resume injections) stay in this
961
+ // call's un-acked tail, so the runner's settle requeues them untouched onto the turn-end
962
+ // path. Folding the full batch into the one noReply row would carry a claimable's claim
963
+ // lease into the hours-long awaiting-consumption window with no renewal (lease expiry →
964
+ // duplicate task delivery), reorder injections, —
965
+ // violating partitionMidTurnDelivery's contract. (codex.ts does the same partition INSIDE
966
+ // its deliverMidTurn.)
967
+ const { steerable } = partitionMidTurnDelivery(messages);
968
+ if (steerable.length > 0) return this.deliverMidTurn(process, steerable, ctx);
969
+ }
970
+ // #2397 round 3 (MED-3) — only a message `send()` actually got through (no terminal failure)
971
+ // is acknowledged. runner-core marks every acknowledged id read and re-queues everything else
972
+ // (runner-core.ts ~969-995); returning ALL ids unconditionally — the old behavior — marked a
973
+ // message that provoked 8 failed POSTs and a terminal failure as delivered+read though the
974
+ // model never processed it: recorded delivered, silently lost. #2397 round 3 (MED-6) — once a
975
+ // message fails terminally the session is now `error`/offline, so stop rather than burn the
976
+ // same ~16-minute worst-case retry budget on every remaining queued message in this batch.
977
+ // #2397 round 3 review (MED-3r) — `terminalFailureIds` tells runner-core WHY this id was not
978
+ // acknowledged, so its re-drain paces with bounded exponential backoff instead of hammering an
979
+ // upstream that is already rate-limiting on the very next tick.
980
+ const acknowledgedIds: number[] = [];
981
+ const terminalFailureIds: number[] = [];
982
+ const providerHeldIds: number[] = [];
983
+ for (const message of messages) {
984
+ let disarmOpener: (() => void) | undefined; // #2840 — each send is its own turn, opened by this message
985
+ try {
986
+ const outcome = await this.enqueueTurn(() => { disarmOpener = ctx?.openTurnFor?.([message]); return this.send(process, providerMessageText([message]), ctx?.inputObserver); });
987
+ if (outcome !== "ok") disarmOpener?.();
988
+ if (outcome === "failed") {
989
+ terminalFailureIds.push(message.id);
990
+ break;
991
+ }
992
+ // #2619 — refused under a provider hold: neither delivered nor failed. Stop the batch and
993
+ // hand the refused id AND the untried tail to the runner to park — they would only hit
994
+ // the same wall; the runner redelivers them once a provider turn goes through again.
995
+ if (outcome === "held") {
996
+ for (const rest of messages.slice(messages.indexOf(message))) providerHeldIds.push(rest.id);
997
+ break;
998
+ }
999
+ } catch (error) {
1000
+ // #2534 review (LOW-3) — symmetric with runner-core's attemptInitialPromptDelivery: a
1001
+ // PromptAcceptedError means the provider ACCEPTED this queued message's POST and the
1002
+ // completion await failed/aborted on a still-running turn. Propagating it reached
1003
+ // runner-core's drain, which requeued the whole batch and double-POSTed into that live
1004
+ // turn — acknowledge the accepted message instead (its output streams via the normal
1005
+ // session events). Any other failure still propagates: nothing delivered, whole batch
1006
+ // requeued.
1007
+ if (!(error instanceof PromptAcceptedError)) { disarmOpener?.(); throw error; }
1008
+ }
1009
+ acknowledgedIds.push(message.id);
1010
+ }
1011
+ return { acknowledgedIds, terminalFailureIds, ...(providerHeldIds.length ? { providerHeldIds } : {}) };
1012
+ }
1013
+
1014
+ /**
1015
+ * #2539 Phase B — the soft-steer op (see the adapter-interface doc): persist the batch into
1016
+ * the in-flight turn WITHOUT touching any send() turn state. Non-negotiable invariants:
1017
+ * - send()/its instance fields stay non-reentrant: this path must not read or write
1018
+ * sendTurnId/turnInFlight/suppressProviderIdle/turnClosed/retry/reroll and must never emit a
1019
+ * second busy/idle (the ongoing turn's pair is the only truthful one).
1020
+ * - ONE composed OpenCode user row per batch (sorted by Relay id), one caller messageID, one
1021
+ * loopback POST at a time (adapter-local FIFO, not turnQueue).
1022
+ * - Session fencing from intent time: the sessionId is captured at entry and re-checked
1023
+ * immediately before AND after the POST — a pre-POST swap defers the batch (never acked onto
1024
+ * a session that is gone); a swap INSIDE the POST window (batch already persisted/tracked)
1025
+ * targeted-reoffers it to the normal queue — never stranded on the dead session, never
1026
+ * reposted into the new one.
1027
+ * - Paste-instant authority: ctx.isRecipientMidTurn() is consulted at the actual POST instant;
1028
+ * a turn that already ended defers (or, when it ends inside the post-POST window, persists +
1029
+ * arms the one bounded idle wake) — never a claimed in-turn success from a stale precheck.
1030
+ * - At-least-once with explicit ambiguity: one POST, then (only on ambiguity) ONE bounded
1031
+ * exact-id reconcile; a positive persistence is NEVER re-POSTed.
1032
+ *
1033
+ * #2539 Phase A reconciliation — the result speaks the shared DeliverResult vocabulary:
1034
+ * persisted/ambiguous ids return as `awaitingConsumptionIds` (their settlement completes later
1035
+ * through ctx.settleMidTurnConsumed / ctx.reofferMidTurnIds — persistence is not delivery);
1036
+ * everything NOT accepted (lane off, boundary-only, cap overflow, pre-POST fencing loss) is
1037
+ * simply omitted, so the runner's settle requeues it for turn-end delivery. A
1038
+ * reconciled-ABSENT POST (definitive non-persistence) throws so the runner paces the retry.
1039
+ */
1040
+ async deliverMidTurn(process: ManagedProcess, messages: Message[], ctx?: ProviderDeliverContext): Promise<DeliverResult> {
1041
+ const nothingAccepted: DeliverResult = { acknowledgedIds: [], awaitingConsumptionIds: [], terminalFailureIds: [] };
1042
+ const allIds = messages.map((message) => message.id);
1043
+ if (this.softSteerPolicyFor(process) !== "steer" || messages.length === 0) {
1044
+ return nothingAccepted;
1045
+ }
1046
+ const client = process.meta?.client as OpenCodeClient | undefined;
1047
+ const sessionId = stringValue(process.meta?.sessionId);
1048
+ if (!client || !sessionId) {
1049
+ return nothingAccepted;
1050
+ }
1051
+ this.softSteerProcess = process;
1052
+ this.softSteer.setCallbacks({ settleMidTurnConsumed: ctx?.settleMidTurnConsumed, reofferMidTurnIds: ctx?.reofferMidTurnIds });
1053
+ const composed = composeSoftSteerText(messages);
1054
+ if (composed.included.length === 0) {
1055
+ return nothingAccepted;
1056
+ }
1057
+ const settled = this.softSteerQueue.then(
1058
+ () => this.softSteerDeliverBatch(process, client, sessionId, ctx, composed),
1059
+ () => this.softSteerDeliverBatch(process, client, sessionId, ctx, composed),
1060
+ );
1061
+ this.softSteerQueue = settled.then(() => undefined, () => undefined);
1062
+ return settled;
1063
+ }
1064
+
1065
+ private async softSteerDeliverBatch(process: ManagedProcess, client: OpenCodeClient, sessionIdAtIntent: string, ctx: ProviderDeliverContext | undefined, composed: ReturnType<typeof composeSoftSteerText>): Promise<DeliverResult> {
1066
+ const nothingAccepted: DeliverResult = { acknowledgedIds: [], awaitingConsumptionIds: [], terminalFailureIds: [] };
1067
+ const deferredOverflow = composed.deferred.map((message) => message.id);
1068
+ const included = composed.included;
1069
+ const includedIds = included.map((message) => message.id);
1070
+ // Fencing from intent time: a clear/reroll swap between the seam's decision and this POST
1071
+ // means the turn this batch was composed for is gone.
1072
+ const sessionId = stringValue(process.meta?.sessionId);
1073
+ if (sessionId !== sessionIdAtIntent || ctx?.isRecipientMidTurn?.() !== true) {
1074
+ return nothingAccepted;
1075
+ }
1076
+ const messageID = newSoftSteerMessageID();
1077
+ ctx?.inputObserver?.offered(Date.now(), true);
1078
+ let outcome = await client.sendMessageNoReply(sessionId, composed.text, messageID, OPENCODE_SOFT_STEER_POST_TIMEOUT_MS);
1079
+ if (outcome === "unknown") {
1080
+ // One bounded exact-id reconcile before ANY reoffer decision; never double-POST after
1081
+ // positive persistence.
1082
+ const found = await client.hasMessageWithId(sessionId, messageID, OPENCODE_SOFT_STEER_RECONCILE_TIMEOUT_MS);
1083
+ if (found === true) outcome = "persisted";
1084
+ else if (found === false) outcome = "absent";
1085
+ }
1086
+ if (outcome === "persisted") {
1087
+ const tracked = this.softSteer.noteBatch(messageID, includedIds, sessionId, { inputObserver: ctx?.inputObserver });
1088
+ logger.info("opencode", `soft-steer batch ${messageID} persisted (${includedIds.length} relay id(s), ${composed.text.length} chars) on session ${sessionId}`);
1089
+ if (tracked) {
1090
+ // Post-POST race windows (the row is persisted either way):
1091
+ // - Session swapped INSIDE the POST→noteBatch window: the swap's own sessionReplaced
1092
+ // scan ran before this noteBatch, so the batch is stranded on the dead session —
1093
+ // targeted reoffer now (see reofferStaleSessionBatches).
1094
+ // - Turn ended on the SAME session: the row strands until the next turn (live-proven
1095
+ // at-stop behavior) — arm the bounded wake.
1096
+ const sessionIdNow = stringValue(process.meta?.sessionId);
1097
+ if (sessionIdNow !== sessionId) await this.reofferStaleSessionBatches(sessionIdNow, sessionId);
1098
+ else if (ctx?.isRecipientMidTurn?.() !== true) this.maybeArmSoftSteerWake(process);
1099
+ return { acknowledgedIds: [], awaitingConsumptionIds: [...includedIds], terminalFailureIds: [] };
1100
+ }
1101
+ // Over the ledger's backpressure cap: the row IS persisted, so it must not be re-posted,
1102
+ // but the lane cannot track it to settlement — omit it from the result so the normal lane
1103
+ // carries it (duplicate text at the next boundary is the designed degradation, not silent
1104
+ // loss).
1105
+ return nothingAccepted;
1106
+ }
1107
+ if (outcome === "absent") {
1108
+ // Definitive non-persistence after the bounded reconcile: nothing was accepted, so throw —
1109
+ // the runner's drain catch requeues the whole batch and paces the retry at its 1s failure
1110
+ // cadence (the same defer-by-throw shape codex uses), instead of an unpaced drain-tick loop.
1111
+ throw new Error(`opencode soft-steer POST ${messageID} did not persist (reconciled absent) on session ${sessionId}`);
1112
+ }
1113
+ // Unknown after POST + reconcile: hold the ids in the ledger (the row may exist) — resolved by
1114
+ // the consumption door, session replacement, or shutdown reoffer. Never an immediate repost.
1115
+ // Force-tracked even at the ledger cap: the cap gates new POSITIVE persists (backpressure to
1116
+ // the normal queue), never the RESOLVABILITY of an already-attempted POST — an untracked
1117
+ // unknown batch could never be resolved by any ledger exit while this contract reports it as
1118
+ // held (silent orphan under exactly the long-turn + one-timeout scenario #2539 exists for).
1119
+ this.softSteer.noteBatch(messageID, includedIds, sessionId, { force: true, inputObserver: ctx?.inputObserver });
1120
+ // Same swap-inside-the-POST-window fence as the persisted path: a batch noted onto a dead
1121
+ // session can never be resolved there (the same-session consumption door and the
1122
+ // new-session wake both miss it), so reoffer immediately instead of holding it until the
1123
+ // NEXT swap/shutdown — the delayed silent-loss shape for non-claimable messages. The row
1124
+ // (if it exists) stays on the dead session, unconsumed — at-least-once holds.
1125
+ const sessionIdAfterReconcile = stringValue(process.meta?.sessionId);
1126
+ if (sessionIdAfterReconcile !== sessionId) await this.reofferStaleSessionBatches(sessionIdAfterReconcile, sessionId);
1127
+ else logger.warn("opencode", `soft-steer batch ${messageID} outcome UNKNOWN after POST + reconcile; holding ${includedIds.length} relay id(s) until consumption or session end`);
1128
+ return { acknowledgedIds: [], awaitingConsumptionIds: [...includedIds], terminalFailureIds: [] };
1129
+ }
1130
+
1131
+ // The spawn config the adapter was launched with (meta.config is the RunnerSpawnConfig handed
1132
+ // to spawn()); the steer policy reads its providerConfig — never a provider-name branch.
1133
+ // Same shape as codex's spawnProviderConfig (kept per-adapter, matching the landed precedent).
1134
+ // #2544: the absent-config default is "steer" (default-on); an explicit "defer" is the
1135
+ // per-agent kill switch, resolved through the shared seam like codex's "steer" default.
1136
+ private softSteerPolicyFor(process: ManagedProcess): "steer" | "defer" {
1137
+ const config = (process.meta?.config as { providerConfig?: ProviderConfig } | undefined)?.providerConfig;
1138
+ return resolveActiveTurnMessageDelivery(config, "steer");
1139
+ }
1140
+
1141
+ /**
1142
+ * Targeted post-POST reoffer for the swap-inside-the-POST-window race: the clear/reroll
1143
+ * swap's own sessionReplaced(newId) scan ran BEFORE the just-attempted batch was noted, so
1144
+ * that batch (and any other stale-session entry) would sit stranded on the dead session —
1145
+ * invisible to the same-session consumption door, unarmable by the new-session wake. Reoffer
1146
+ * everything not bound to the live session now (an empty live id reoffers all: nothing is
1147
+ * consumable without a live session). NEVER a repost into the new session. The deliverMidTurn
1148
+ * result still reports the POST's own truthful outcome; the onReoffer settlement is the
1149
+ * authoritative resolution — the same interplay as the clear/reroll call sites, whose
1150
+ * reoffers arrive after earlier results already reported persistedIds.
1151
+ */
1152
+ private async reofferStaleSessionBatches(liveSessionId: string | undefined, deadSessionId: string): Promise<number[]> {
1153
+ // An undefined/empty live id reoffers ALL entries: nothing is consumable without a live session.
1154
+ const reoffered = await this.softSteer.sessionReplaced(liveSessionId ?? "");
1155
+ if (reoffered.length > 0) {
1156
+ logger.info("opencode", `soft-steer: session swapped inside the POST window (${deadSessionId} → ${liveSessionId || "<none>"}); reoffered ${reoffered.length} relay id(s) to the normal queue`);
1157
+ }
1158
+ return reoffered;
1159
+ }
1160
+
1161
+ /**
1162
+ * #2539 Phase B — the bounded idle-wake fallback: the provider just went idle with
1163
+ * positively-persisted-but-unconsumed soft batches (mid-run reload missed them — the live
1164
+ * at-stop strand, or an unknown-outcome batch). Arm EXACTLY ONE content-free wake turn through
1165
+ * turnQueue (a NORMAL send() turn: real busy/idle pair, full retry semantics — no second turn
1166
+ * machinery, no resend of the steer payload). Event-driven only: armed from turn-close edges
1167
+ * and the post-POST race check, never from a wall-clock timer. Its completion settles the
1168
+ * batches it was armed for (their rows are provably in the session context by then); the
1169
+ * consumption door remains the primary settle path and stays idempotent with this one.
1170
+ */
1171
+ private maybeArmSoftSteerWake(process?: ManagedProcess): void {
1172
+ if (this.softSteer.isWakePending()) return;
1173
+ const target = process ?? this.softSteerProcess;
1174
+ const sessionId = stringValue(target?.meta?.sessionId);
1175
+ if (!target || !sessionId || !this.softSteer.hasAwaiting(sessionId)) return;
1176
+ const armed = this.softSteer.armWake(sessionId);
1177
+ if (!armed) return;
1178
+ logger.info("opencode", `soft-steer wake armed on session ${sessionId} for ${armed.targets.length} unconsumed batch(es)`);
1179
+ void this.enqueueTurn(() => this.send(target, OPENCODE_SOFT_STEER_WAKE_TEXT))
1180
+ .then(async (ok) => {
1181
+ // Failed wake turn: leave the entries pending — session replacement / shutdown reoffer
1182
+ // them to the normal queue (a failed session must not mark anything delivered, and the
1183
+ // old delete-then-maybe-notify order here silently dropped them).
1184
+ if (ok !== "ok") return;
1185
+ const settled = await this.softSteer.settleWakeTargets(armed);
1186
+ if (settled.length > 0) logger.info("opencode", `soft-steer wake settled ${settled.length} relay id(s) on session ${sessionId}`);
1187
+ })
1188
+ .catch((error) => logger.warn("opencode", `soft-steer wake turn failed: ${errMessage(error)}`))
1189
+ .finally(() => this.softSteer.endWake());
1190
+ }
1191
+
1192
+ async interrupt(process: ManagedProcess): Promise<Record<string, unknown>> {
1193
+ const client = process.meta?.client as OpenCodeClient | undefined;
1194
+ const sessionId = stringValue(process.meta?.sessionId);
1195
+ if (!client || !sessionId) throw new Error("OpenCode session is not ready");
1196
+ await client.abort(sessionId);
1197
+ return { sessionId, method: "session.abort" };
1198
+ }
1199
+
1200
+ /**
1201
+ * #2431 — self-resume clear-inject support: mint a fresh, history-less session and make it the
1202
+ * active one. This REUSES the proven #2397 re-roll seam (the same `POST /session` via
1203
+ * `createSession`, which explicitly drops conversation history, and the same in-place
1204
+ * `process.meta.sessionId` swap so every reader — send/interrupt/shutdown and the SSE doors'
1205
+ * foreign-session filter — picks the new id up with no separate bookkeeping); it does not invent
1206
+ * a second session-minting mechanism.
1207
+ *
1208
+ * Ordering/composition with the turn state machine (#2397/#2425/#2427/#2428) is ENFORCED, not
1209
+ * assumed (round 1 HIGH): the whole swap runs inside `enqueueTurn`, so a clear that arrives while
1210
+ * a turn is mid-flight — which DOES happen, since the model calls relay_compact_and_resume from
1211
+ * within its own synchronous `/message` turn and dispatchCommand gives agent.clearContext no
1212
+ * provider-input ticket — waits for that turn to settle and completes it wholly on the session
1213
+ * it started with before minting anything. The reactive continuation inject is separately
1214
+ * serialized behind deliveries through providerInput.run (`PROVIDER_INPUT_COMMAND_TYPES`), so the
1215
+ * first thing that touches the new session is a delivery, never a half-swapped retry. The last
1216
+ * closed turn left `turnClosed` set and `suppressProviderIdle` sticky per round-5 MED-1; those
1217
+ * fields are deliberately NOT touched — resetting them could double-close an already-closed turn,
1218
+ * and any trailing SSE event belonging to the PRE-clear turn carries the OLD sessionID, which the
1219
+ * #2425 foreign-session filter now drops against the NEW active id. Same reasoning leaves
1220
+ * `messageRoles`/`emittedTextParts`/`emittedToolSteps` alone: ids are server-global on this one
1221
+ * process and pruned to OPENCODE_EVENT_STATE_LIMIT regardless (the #2397 re-roll leaves them
1222
+ * too). The SSE subscription is per-server, not per-session, so the new session stays fully
1223
+ * observable through the existing wiring.
1224
+ *
1225
+ * Only relayContextSent re-arms after the fresh session exists, so the first delivery carries
1226
+ * the generic Relay context again. The systemPromptAppend launch block is intentionally NOT
1227
+ * re-sent here: server startup-context and memory-injection seams own post-boundary charter
1228
+ * and memory re-delivery.
1229
+ * The fresh ContextState mirrors Codex's clearContext shape: utilization 0 IS ground truth
1230
+ * immediately after a wipe.
1231
+ */
1232
+ async clearContext(process: ManagedProcess): Promise<Record<string, unknown>> {
1233
+ return this.enqueueTurn(() => this.swapSessionForClear(process));
1234
+ }
1235
+
1236
+ private async swapSessionForClear(process: ManagedProcess): Promise<Record<string, unknown>> {
1237
+ const client = process.meta?.client as OpenCodeClient | undefined;
1238
+ const previousSessionId = stringValue(process.meta?.sessionId);
1239
+ if (!client || !previousSessionId) throw new Error("OpenCode session is not ready");
1240
+ // #2459 — the session swap is also an attachment-generation rotation: any open event-driven
1241
+ // terminal turn belongs to the OLD session (its matching idle carries the old sessionID and
1242
+ // would be dropped by the foreign-session filter, stranding runner-core busy). Close it on
1243
+ // the old session's truth before the swap; the orchestrator's generation check kills the
1244
+ // stale guest TUI on its next input/stream contact.
1245
+ // #2459 round 2 (review D) — flush a buffered terminal prompt under the OLD session
1246
+ // pseudo-turn before the swap (the operator typed there; the buffer must not leak into
1247
+ // the new session), then close any open terminal turn as in send() takeover.
1248
+ this.flushBufferedPrompt(previousSessionId || undefined);
1249
+ if (this.eventTurnId) this.closeEventTurn();
1250
+ this.statusCb({ status: "busy", reason: "provider-turn", timeline: { status: "clearing-context", timestamp: Date.now() } });
1251
+ let newSessionId: string;
1252
+ try {
1253
+ newSessionId = await client.createSession(stringValue(process.meta?.sessionTitle) ?? "agent-relay");
1254
+ } catch (error) {
1255
+ // The old session is still intact and active — restore idle so the failed command doesn't
1256
+ // strand a provider-turn busy hold (same recovery shape as Codex's clearContext).
1257
+ this.statusCb({ status: "idle", reason: "provider-turn" });
1258
+ throw error;
1259
+ }
1260
+ const clearedAt = Date.now();
1261
+ if (process.meta) {
1262
+ process.meta.sessionId = newSessionId;
1263
+ // #2539 Phase B — soft-steer fencing: batches accepted-but-unconsumed on the replaced
1264
+ // session can never be consumed on the fresh one; their relay ids go BACK to the runner's
1265
+ // normal queue (the settlement callback fires inside sessionReplaced — never reposted into
1266
+ // the cleared session).
1267
+ const softSteerOrphans = await this.softSteer.sessionReplaced(newSessionId);
1268
+ if (softSteerOrphans.length > 0) {
1269
+ logger.info("opencode", `soft-steer: reoffering ${softSteerOrphans.length} relay id(s) after clear swapped session`);
1270
+ }
1271
+ process.meta.relayContextSent = false;
1272
+ // Boundary charter/memory delivery belongs to the server inject seams.
1273
+ process.meta.launchContextSent = true;
1274
+ process.meta.context = freshContextState(clearedAt);
1275
+ }
1276
+ this.statusCb({ status: "idle", reason: "provider-turn", timeline: { status: "context-cleared", timestamp: clearedAt } });
1277
+ return { previousSessionId, sessionId: newSessionId };
1278
+ }
1279
+
1280
+ /**
1281
+ * #2440 — native self-resume compaction via OpenCode's own `POST /session/{id}/compact`
1282
+ * (mistakenly believed unavailable when #2431 shipped clear-inject only). Reuses the SAME
1283
+ * turnQueue FIFO serializer clearContext() established: `agent.compact` is dispatched
1284
+ * fire-and-forget with no provider-input ticket (like `agent.clearContext`), and OpenCode's
1285
+ * `/message` POST is synchronous, so a compact triggered from inside the model's own turn
1286
+ * (relay_compact_and_resume) genuinely races an in-flight send() without it. See the
1287
+ * `turnQueue` field doc for the full race shape this closes off.
1288
+ */
1289
+ async compact(process: ManagedProcess, opts?: { instructions?: string }): Promise<Record<string, unknown>> {
1290
+ await this.apiBudgets.get(process)?.prepare();
1291
+ return this.enqueueTurn(() => this.performCompact(process, opts));
1292
+ }
1293
+
1294
+ /**
1295
+ * `opts.instructions` (the relay-built continuation note) is deliberately NOT threaded into the
1296
+ * `/compact` request body: this pinned server version's wire shape for a steering instruction on
1297
+ * that endpoint is unconfirmed, and guessing wrong risks a hard 4xx in place of a clean
1298
+ * compaction. This is safe to skip — OpenCode does not couple continuation delivery in-process
1299
+ * (no `couplesClearInjectContinuation`, same as its clear-inject path today), so the relay's own
1300
+ * reactive continuation inject (`enqueueContinuationInjectionAfterSelfResume`, src/self-resume.ts)
1301
+ * delivers the SAME continuation as a follow-up `agent.injectContext` once this command succeeds
1302
+ * — the identical path clear-inject already relies on for OpenCode (see the #2431 test "the first
1303
+ * post-clear delivery lands in the NEW session ... through the same deliver()/send() path").
1304
+ */
1305
+ private async performCompact(process: ManagedProcess, _opts?: { instructions?: string }): Promise<Record<string, unknown>> {
1306
+ const client = process.meta?.client as OpenCodeClient | undefined;
1307
+ const sessionId = stringValue(process.meta?.sessionId);
1308
+ if (!client || !sessionId) throw new Error("OpenCode session is not ready");
1309
+ this.statusCb({ status: "busy", reason: "provider-turn", timeline: { status: "compacting", timestamp: Date.now() } });
1310
+ try {
1311
+ await client.compact(sessionId);
1312
+ } catch (error) {
1313
+ // The session is still intact and active — restore idle so a failed command doesn't strand
1314
+ // a provider-turn busy hold (same recovery shape as clearContext's own catch above).
1315
+ this.statusCb({ status: "idle", reason: "provider-turn" });
1316
+ throw error;
1317
+ }
1318
+ const compactedAt = Date.now();
1319
+ const currentContext = process.meta?.context as ContextState | undefined;
1320
+ if (process.meta) {
1321
+ // #2440 round 2 — stamp unconditionally, mirroring swapSessionForClear's shape: a compact
1322
+ // that lands before any context state was ever reported (e.g. immediately after spawn) must
1323
+ // still record the compaction rather than silently no-oping, exactly like clearContext's
1324
+ // always-fresh stamp below. Falls back to clearContext's own baseline defaults (utilization
1325
+ // 0, no warm topics/active memories, source "api"/confidence "reported") when there is no
1326
+ // prior state to carry forward; lifecycleState is "cooling" (not "fresh") because — unlike a
1327
+ // clear — compact does not wipe conversation history.
1328
+ process.meta.context = {
1329
+ ...(currentContext ?? { utilization: 0, warmTopics: [], source: "api" as const, confidence: "reported" as const }),
1330
+ lifecycleState: "cooling",
1331
+ tasksSinceCompact: 0,
1332
+ lastCompactedAt: compactedAt,
1333
+ lastUpdatedAt: compactedAt,
1334
+ } satisfies ContextState;
1335
+ }
1336
+ this.statusCb({ status: "idle", reason: "provider-turn", timeline: { status: "compacted", timestamp: compactedAt } });
1337
+ return { sessionId };
1338
+ }
1339
+
1340
+ async shutdown(process: ManagedProcess, opts: { graceful: boolean; timeoutMs: number }): Promise<ShutdownOutcome> {
1341
+ // #2539 Phase B — anything accepted-but-unconsumed goes back to the runner's normal queue via
1342
+ // the settlement callback (inside reofferAll) before teardown; the ledger itself never drops
1343
+ // an id silently — a failed callback re-inserts the entries for the next exit.
1344
+ const softSteerLeftovers = await this.softSteer.reofferAll();
1345
+ if (softSteerLeftovers.length > 0) logger.info("opencode", `soft-steer: shutdown reoffering ${softSteerLeftovers.length} relay id(s)`);
1346
+ this.softSteerProcess = undefined;
1347
+ // #2484 round 1 review (F2/F3) — session end: force any still-pending debounced token-usage
1348
+ // report out now, rather than losing it to a timer that never gets to fire. Round 2 (MED-1)
1349
+ // — awaited, not fire-and-forget: this covers the controlled-teardown paths that call
1350
+ // adapter.shutdown() directly (restartProvider, shutdownProvider) without going through
1351
+ // runner-core's settleTerminalTokenUsage seam first.
1352
+ await this.tokenUsagePoster.flushAll();
1353
+ (process.meta?.events as AbortController | undefined)?.abort();
1354
+ if (opts.graceful) await this.interrupt(process).catch(() => {});
1355
+ const outcome = await terminateProviderProcesses(process.process ? [process.process] : [], opts);
1356
+ if ((process.process as Bun.Subprocess | undefined)?.exitCode !== null) await this.apiBudgets.get(process)?.settle().catch(() => {});
1357
+ return outcome;
1358
+ }
1359
+
1360
+ /**
1361
+ * Exported behavior through the adapter so fixture contracts can pin the v1.18.21 event shapes.
1362
+ *
1363
+ * Reproduced live against a real server (#2347): the wire shapes here were captured directly
1364
+ * off `/event`, not inferred from docs. Two things the previous implementation got wrong:
1365
+ *
1366
+ * 1. `session.status` carries a nested `{ status: { type: "busy" | "idle" } }`, not a bare
1367
+ * `properties.status` string — so idle was never detected and a finished turn never told
1368
+ * Relay it was done. `session.updated` never carries a status field at all; it no longer
1369
+ * needs to be treated as a status source.
1370
+ * 2. `message.updated` only ever carries message metadata (`properties.info`, with `role`) —
1371
+ * never the reply text. The assistant's text streams separately as `message.part.updated`
1372
+ * events (`properties.part`), one per part, finalized once `part.time.end` is set. A
1373
+ * message's `message.updated` (role) event always arrives before that message's part
1374
+ * events, so `messageRoles` is populated before any part needs to look a role up.
1375
+ */
1376
+ handleEvent(event: OpenCodeServerEvent, process?: ManagedProcess): void {
1377
+ const properties = event.properties ?? {};
1378
+ if (event.type === "session.status") {
1379
+ // #2425 — a foreign-session event must never affect this adapter's turn/idle state, full
1380
+ // stop, regardless of suppression state. This is what makes it safe for the two doors below
1381
+ // to stop enforcing PERMANENT suppression once `turnAbandoned`: an unfiltered foreign child
1382
+ // session's busy/idle was exactly the hazard round 7 made suppression permanent to guard
1383
+ // against, and this filter closes it directly instead.
1384
+ // #2459 round 1 (review C) — FAIL CLOSED: whenever the active session id is known (a live
1385
+ // spawn always carries one), an event WITHOUT a sessionID is rejected too — a session-
1386
+ // less status cannot be positively attributed to the active conversation, and acting on
1387
+ // it could open/close a turn for a dead session. Test-only call sites that pass no
1388
+ // process (hence no active id) keep the lenient behavior.
1389
+ const sessionID = stringValue(properties.sessionID);
1390
+ const activeSessionId = stringValue(process?.meta?.sessionId);
1391
+ if (activeSessionId && sessionID !== activeSessionId) return;
1392
+ const statusValue = properties.status;
1393
+ const status = stringValue(isRecord(statusValue) ? statusValue.type : statusValue);
1394
+ if (status === "retry") {
1395
+ this.onServerRetry(parseOpenCodeServerRetry(statusValue), process);
1396
+ return;
1397
+ }
1398
+ if (status === "busy") {
1399
+ // send() owns the turn boundary while a headless delivery is in flight — its own
1400
+ // explicit busy (with its minted id) is authoritative, exactly as before #2459. Only
1401
+ // busy events OUTSIDE send() can open a terminal-origin turn below.
1402
+ if (this.turnInFlight) return;
1403
+ // A terminal turn is already open — its matching idle owns the close; a duplicate
1404
+ // busy must not re-mint.
1405
+ if (this.eventTurnId) return;
1406
+ // #2425 — re-arm the close guard: this real busy is evidence the turn (from
1407
+ // `send()`'s perspective, already force-closed to idle by the abandonment path) is
1408
+ // actually still running, so the eventual matching idle must be allowed to close it
1409
+ // again.
1410
+ if (this.turnAbandoned) this.turnClosed = false;
1411
+ // #2459 round 1 (review D) — #2397 round-7 echo guard, restored to PERMANENT and made
1412
+ // POSITIVE (no time heuristic): once any headless turn has run on this process, a busy
1413
+ // is admitted ONLY when a fresh terminal user prompt armed `pendingTerminalPrompt` (the
1414
+ // TUI's POST creates the user message before the turn's busy flows). The buffered
1415
+ // own-turn echo has no fresh user message by construction — at ANY delay — so it stays
1416
+ // swallowed. (#2425 abandonment keeps this door open — those server events are the only
1417
+ // remaining proof of the turn's real state.)
1418
+ if (!this.turnAbandoned && this.headlessTurnSeen && !this.pendingTerminalPrompt) return;
1419
+ // #2459 round 1 (review D) — a genuinely new, terminal-origin turn: mint its provider turn id and hand it
1420
+ // to runner-core (which keys currentTurnId off this busy), then associate every user/
1421
+ // assistant/tool message id of the turn with it (see activeTurnId()) until the matching
1422
+ // idle closes it. Un-stick the idle suppression so this turn's own idle can pass.
1423
+ this.pendingTerminalPrompt = false; // consumed
1424
+ this.eventTurnId = `opencode-turn-${crypto.randomUUID()}`;
1425
+ this.eventTurnText = [];
1426
+ this.eventTurnReasoning = new Map();
1427
+ this.suppressProviderIdle = false;
1428
+ // #2459 round 2 (review D) — a prompt buffered by the [user message → user part →
1429
+ // busy] ordering is emitted NOW under the freshly minted id, so prompt → trace →
1430
+ // final-response all share one opencode-turn-* id.
1431
+ this.flushBufferedPrompt(this.eventTurnId);
1432
+ this.statusCb({ status: "busy", reason: "provider-turn", id: this.eventTurnId });
1433
+ }
1434
+ if (status === "idle") {
1435
+ // send() closes its own turn deterministically (closeSuppressedTurn / failTurn / the
1436
+ // finally) — an SSE idle while a headless delivery is in flight stays swallowed, as
1437
+ // since #2397 round 2.
1438
+ if (this.turnInFlight) return;
1439
+ // #2459 round 2 (review D) — bounded fallback: an active-session idle with a prompt
1440
+ // still buffered means the turn ended without us seeing its busy (or it was itself an
1441
+ // echo). Flush under the session pseudo-turn so the operator's input is never stranded
1442
+ // in the buffer; RunnerCore's injection dedup guards the chat-echo case.
1443
+ if (this.bufferedTerminalPrompt && !this.eventTurnId) {
1444
+ this.flushBufferedPrompt(stringValue(process?.meta?.sessionId) || undefined);
1445
+ }
1446
+ // #2459 — the matching close of an event-driven (terminal) turn: emit the turn-final
1447
+ // response accumulated from the turn's live assistant text parts (mirroring send()'s
1448
+ // emitTurnFinalResponse → close ordering) and then the idle edge that closes
1449
+ // runner-core's turn.
1450
+ if (this.eventTurnId) {
1451
+ this.closeEventTurn();
1452
+ return;
1453
+ }
1454
+ // #2397 round 2 (F4) — a transient idle from a failed attempt, still mid-retry-loop.
1455
+ // #2397 round 7 review (MED-2/LOW-1) — round 6's one-shot reopen (lower the flag on the
1456
+ // FIRST post-close idle, keyed on `turnInFlight`) let a SECOND idle in the same trailing-
1457
+ // echo window pass through and double-close. There is no legitimate turn boundary this SSE
1458
+ // door needs to report that `send()`/`closeSuppressedTurn`/`failTurn` don't already emit
1459
+ // explicitly for a DETERMINISTIC outcome (see the field comment) — so there is no reopen on
1460
+ // that path: suppression stays permanent for any turn `send()` itself closed knowingly.
1461
+ // #2459 — EXCEPT the terminal-origin path above: an idle outside send() with no event
1462
+ // turn open is still an echo/stray (the busy that minted a turn would have set one), so
1463
+ // the permanent suppression holds exactly as before for chat turns.
1464
+ // #2425 — the one exception is the same `turnAbandoned` escape hatch as the busy door
1465
+ // above; `turnClosed` (not `suppressProviderIdle`) is what still prevents a double-close
1466
+ // here — either the abandonment path's own forced idle already closed it (this real idle
1467
+ // is then a no-op confirmation), or a real busy re-armed it above and this is the genuine
1468
+ // close.
1469
+ if (this.suppressProviderIdle && !this.turnAbandoned) return;
1470
+ if (this.turnClosed) return;
1471
+ this.turnClosed = true;
1472
+ this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"] });
1473
+ }
1474
+ return;
1475
+ }
1476
+ if (event.type === "permission.asked") {
1477
+ if (!this.turnInFlight && !this.eventTurnId) return;
1478
+ // `opencode.json` grants every permission for this open-only adapter. Keep the event
1479
+ // observable in case a future server version ignores the config; never surface a misleading
1480
+ // guarded approval form or silently reinterpret the session's requested policy.
1481
+ this.statusCb({ status: "busy", reason: "provider-turn", providerState: { state: "active", reason: "openPermission", label: "OpenCode permission auto-allowed by approvalMode=open", source: "opencode", updatedAt: Date.now() } });
1482
+ return;
1483
+ }
1484
+ if (event.type === "message.updated") {
1485
+ const info = isRecord(properties.info) ? properties.info : undefined;
1486
+ const id = info ? stringValue(info.id) : undefined;
1487
+ const role = info ? stringValue(info.role) : undefined;
1488
+ if (id && role) {
1489
+ this.messageRoles.set(id, role);
1490
+ pruneToLimit(this.messageRoles, OPENCODE_EVENT_STATE_LIMIT);
1491
+ }
1492
+ // #2427 — the SSE echo of a finalized assistant message carries the SAME per-message
1493
+ // `tokens` totals the synchronous POST resolves with (send()'s #2355 report), but on a long
1494
+ // turn that POST can die client-side or stay in-flight forever, so usage reported ONLY from
1495
+ // it never lands (#2427's blank context-window). Report from this door instead: it fires
1496
+ // DURING the turn regardless of POST survival, mirroring how Codex consumes its
1497
+ // `thread/tokenUsage/updated` push. Idempotent with the POST path — last write wins and both
1498
+ // doors feed the same openCodeContextState no-signal rules (0/unparsable → no-op), so neither
1499
+ // can regress a good reading — and same-session-filtered like #2425's session.status door.
1500
+ // #2459 round 1 (review C) — FAIL CLOSED: with the active session id known, a usage echo
1501
+ // WITHOUT a sessionID is rejected too (an unattributable reading must never overwrite the
1502
+ // active session's context state). Test-only process-less call sites keep the lenient path.
1503
+ const sessionID = stringValue(properties.sessionID);
1504
+ const activeSessionId = stringValue(process?.meta?.sessionId);
1505
+ const tokens = countTokens(info?.tokens);
1506
+ const sessionUnattributable = Boolean(activeSessionId && sessionID !== activeSessionId);
1507
+ if (process && role === "assistant" && tokens !== undefined && !sessionUnattributable) {
1508
+ const model = stringValue(process.meta?.model) ?? openCodeQualifiedModel(undefined, this.provider);
1509
+ this.reportContextUsage(process, model, tokens);
1510
+ }
1511
+ // #2484 — same door, reused for the spine report: model/createdAt prefer the message's
1512
+ // own info (round 1 review F5/F6) over the session's configured model / receipt time.
1513
+ // #3351 — outside the context guard so a finished message without counts reports unknown.
1514
+ if (process && role === "assistant" && id) captureOpenCodeApiUsage(this.apiBudgets.get(process), info, sessionID ?? activeSessionId, stringValue(process.meta?.model), id);
1515
+ if (process && role === "assistant" && id && !sessionUnattributable) {
1516
+ scheduleOpenCodeTokenUsage(this.tokenUsagePoster, {
1517
+ provider: this.provider,
1518
+ sessionId: activeSessionId ?? sessionID,
1519
+ model: openCodeMessageModel(info) ?? stringValue(process.meta?.model) ?? openCodeQualifiedModel(undefined, this.provider),
1520
+ messageId: id,
1521
+ tokens: info?.tokens,
1522
+ createdAt: openCodeMessageObservedAt(info),
1523
+ finished: openCodeMessageFinished(info),
1524
+ });
1525
+ }
1526
+ // #2459 round 1 (review D) — ARM the terminal-turn gate: a FIRST-SEEN user message on the
1527
+ // ACTIVE session observed outside send() is the event-source proof that a human typed into
1528
+ // the attached TUI. The next active-session busy is then admitted as terminal-origin even
1529
+ // after a headless close (whose own buffered echo carries no fresh user message — its user
1530
+ // echo was already seen live during send(), so the dedup set prevents the late duplicate
1531
+ // from arming). Fail-closed session-filtered like the usage read above, never armed while
1532
+ // send() is in flight (the chat delivery's own user echo must not open a terminal turn
1533
+ // behind send()'s back) nor while a terminal turn is already open.
1534
+ // #2539 Phase B — soft-steer consumption door: an assistant message whose info.parentID
1535
+ // equals a soft batch's user row id is positive proof the active loop CONSUMED that batch
1536
+ // (the loop reloads full messages each step and re-parents subsequent assistant steps to
1537
+ // the newest user row — live-verified on pinned 1.18.21). FIFO settle: the matched batch
1538
+ // plus every OLDER pending batch, ids marked once. Same-session fail-closed like the usage
1539
+ // read above — a stale session's echo must never settle a live batch.
1540
+ const parentID = isRecord(info) ? stringValue(info.parentID) : undefined;
1541
+ if (process && role === "assistant" && activeSessionId && parentID) {
1542
+ // Settlement callbacks fire inside consumeByParent (consumed → reoffer fallback on
1543
+ // callback failure); this door only logs the outcome. Terminal .catch satisfies the
1544
+ // unguarded-async ratchet (the settle path already never rejects).
1545
+ void this.softSteer.consumeByParent(parentID, activeSessionId).then((consumed) => {
1546
+ if (consumed.length > 0) logger.info("opencode", `soft-steer batch consumed on session ${activeSessionId}: settling ${consumed.length} relay id(s)`);
1547
+ }).catch((error) => logger.warn("opencode", `soft-steer consumption door failed: ${errMessage(error)}`));
1548
+ }
1549
+ if (role === "user" && id) {
1550
+ const firstSeen = !this.seenUserMessageIds.has(id);
1551
+ this.seenUserMessageIds.add(id);
1552
+ pruneToLimit(this.seenUserMessageIds, OPENCODE_EVENT_STATE_LIMIT);
1553
+ // #2539 Phase B — relay's OWN soft-steer user rows never arm the terminal-origin gate:
1554
+ // they are first-seen user messages OUTSIDE send() (the whole point of the lane), and
1555
+ // arming here would misclassify the wake turn (or any user's next turn) as terminal.
1556
+ const terminalOrigin = firstSeen && !this.turnInFlight && !this.eventTurnId && !sessionUnattributable && !this.softSteer.isTrackedUserId(id);
1557
+ if (terminalOrigin) this.pendingTerminalPrompt = true;
1558
+ if (terminalOrigin) adoptOpenCodeTerminalModel(process?.meta, info); // #2659 — keep a model picked in the TUI
1559
+ }
1560
+ return;
1561
+ }
1562
+ if (event.type === "message.part.updated") {
1563
+ const part = isRecord(properties.part) ? properties.part : undefined;
1564
+ if (!part) return;
1565
+ // #2534 — acceptance door: a persisted user text part whose text equals the armed
1566
+ // context-carrying outbound prompt is positive proof OpenCode ACCEPTED it. Consuming here
1567
+ // marks the first-turn markers MID-TURN — seconds after dispatch — so a first turn that
1568
+ // later runs past the whole-turn deadline can never leave them false (the await-then-mark
1569
+ // flaw that re-prepended the ~48KB launch briefing to every queued message). Session and
1570
+ // text identity are fail-closed inside consumeOpenCodeContextAcceptance: a stale-session
1571
+ // echo or an operator-typed prompt never marks.
1572
+ consumeOpenCodeContextAcceptance(process, stringValue(properties.sessionID), stringValue(part.text));
1573
+ // #2389 — ANY part on an assistant message is proof the model produced something, including
1574
+ // the tool parts this adapter does not mirror (their internal shape is not pinned, so they are
1575
+ // reported as bare activity rather than published as a half-understood trace step). Without
1576
+ // this a tools-only OpenCode turn emits no session events at all and reads as empty.
1577
+ // #2397 round 3 review (HIGH-1r) — EXCEPT a bare step-start/step-finish bookkeeping part,
1578
+ // which the server emits on every turn regardless of outcome. `isOutputPart` is the same
1579
+ // predicate `completionProducedOutput` uses on the POST-body door (HIGH-1); this is the SSE
1580
+ // door, and both must agree or the issue's own incident (a step-start-only turn) still
1581
+ // discharges #2389's escalation through whichever door fires first. `noteOutput()` is called
1582
+ // with no event here (onTurnActivity takes no args), so MED-5's `diagnostic` filter cannot
1583
+ // see this path at all — it must be excluded at the source instead.
1584
+ if (isOutputPart(part) && this.messageRoles.get(stringValue(part.messageID) ?? "") === "assistant") this.turnActivityCb();
1585
+ // #2459 round 2 (review D) — no-busy-seen fallback, bounded: when trace activity
1586
+ // (tool/narration/reasoning) arrives while a terminal prompt is still buffered and no
1587
+ // terminal turn ever opened, flush the prompt FIRST under the same session pseudo-turn
1588
+ // the trace will use, keeping prompt→trace grouped instead of stranding the buffer.
1589
+ if (this.bufferedTerminalPrompt && !this.eventTurnId && !this.turnInFlight) {
1590
+ this.flushBufferedPrompt(stringValue(process?.meta?.sessionId) || undefined);
1591
+ }
1592
+ // #2354 — surface tool calls in chat/timeline the way Codex does: decode the ToolPart and
1593
+ // emit a running/completed/failed/cancelled session step (upserted in place by stepId). This
1594
+ // must run before the text-only early-return below, which used to drop every non-text part.
1595
+ if (stringValue(part.type) === "tool") {
1596
+ this.emitToolStep(part, process);
1597
+ return;
1598
+ }
1599
+ // #2605 — surface the model's thinking as a `reasoning` trace row the way Codex/Claude do:
1600
+ // decode the ReasoningPart and emit it (upserted in place by stepId = stable part id). Runs
1601
+ // before the text-only early-return below, which is also what used to drop these parts.
1602
+ if (stringValue(part.type) === "reasoning") {
1603
+ this.emitReasoningStep(part, process);
1604
+ return;
1605
+ }
1606
+ if (stringValue(part.type) !== "text") return;
1607
+ const partId = stringValue(part.id);
1608
+ const messageId = stringValue(part.messageID);
1609
+ const text = stringValue(part.text);
1610
+ const finalized = isRecord(part.time) && part.time.end !== undefined;
1611
+ if (!partId || !messageId || !text || !finalized) return;
1612
+ // #2459 — a finalized role:user text part on the ACTIVE server session is a prompt typed
1613
+ // into an attached web-terminal TUI: mirror it into chat as a terminal-origin prompt.
1614
+ // RunnerCore's recordInjectedPrompt already consumes the echo of every Relay-injected
1615
+ // prompt (chat deliveries), so terminal prompts appear once while dashboard-injected
1616
+ // prompts do not duplicate. #2459 round 1 (review C) — FAIL CLOSED: with the active
1617
+ // session id known, a part WITHOUT a sessionID is rejected too — after a clearContext()
1618
+ // or retry re-roll an unattributable/old-session prompt belongs (at best) to a dead
1619
+ // conversation, and the generation boundary kills that stale TUI anyway.
1620
+ if (this.messageRoles.get(messageId) === "user") {
1621
+ const eventSessionId = stringValue(properties.sessionID);
1622
+ const activeSessionId = stringValue(process?.meta?.sessionId);
1623
+ if (activeSessionId && eventSessionId !== activeSessionId) return;
1624
+ if (this.emittedTextParts.has(partId)) return;
1625
+ this.emittedTextParts.add(partId);
1626
+ pruneToLimit(this.emittedTextParts, OPENCODE_EVENT_STATE_LIMIT);
1627
+ // #2459 round 2 (review D) — turn-id ordering. While send() is in flight, emit
1628
+ // immediately under send()'s id (RunnerCore dedups the chat-injection echo). While a
1629
+ // terminal turn is open, emit under its minted id. Otherwise — the [user message →
1630
+ // user part → busy] wire ordering, where the TUI's POST creates the user message
1631
+ // BEFORE the busy edge mints the terminal turn id — BUFFER the prompt: emitting now
1632
+ // would publish the raw session id as a turnId and split the prompt from its
1633
+ // trace/final. The buffer is flushed under the minted id at busy admission, and the
1634
+ // bounded no-busy fallbacks (trace activity, any active-session idle, chat delivery
1635
+ // takeover, session swap) flush it under the session pseudo-turn — NEVER under a
1636
+ // stale headless sendTurnId.
1637
+ if (this.turnInFlight) {
1638
+ const turnId = this.sendTurnId ?? activeSessionId;
1639
+ this.sessionEventCb({ type: "prompt", origin: "terminal", body: text, ...(turnId ? { turnId } : {}) });
1640
+ return;
1641
+ }
1642
+ if (this.eventTurnId) {
1643
+ this.sessionEventCb({ type: "prompt", origin: "terminal", body: text, turnId: this.eventTurnId });
1644
+ return;
1645
+ }
1646
+ if (this.bufferedTerminalPrompt) this.flushBufferedPrompt(activeSessionId || undefined);
1647
+ this.bufferedTerminalPrompt = { body: text, occurredAt: Date.now() };
1648
+ return;
1649
+ }
1650
+ if (this.messageRoles.get(messageId) !== "assistant") return;
1651
+ if (this.emittedTextParts.has(partId)) return;
1652
+ this.emittedTextParts.add(partId);
1653
+ pruneToLimit(this.emittedTextParts, OPENCODE_EVENT_STATE_LIMIT);
1654
+ // #2459 — while an event-driven terminal turn is open, its finalized assistant text also
1655
+ // accumulates toward the turn-final response emitted by closeEventTurn().
1656
+ if (this.eventTurnId) this.eventTurnText.push(text.trim());
1657
+ const turnId = this.activeTurnId() ?? stringValue(process?.meta?.sessionId);
1658
+ this.sessionEventCb({ type: "narration", origin: "provider", body: text, ...(turnId ? { turnId } : {}) });
1659
+ }
1660
+ }
1661
+
1662
+ /**
1663
+ * #2605 — decode one ReasoningPart and emit its trace row, with in-place upsert semantics: a
1664
+ * reasoning part GROWS as the model thinks (SSE emits the part repeatedly, each frame carrying
1665
+ * the FULL text so far), so a frame for an already-emitted part id re-emits the current full
1666
+ * body — the server upserts it into the same row by stepId. Only assistant-message reasoning
1667
+ * parts are surfaced (same fail-closed role gate as emitToolStep). `streaming` stays true
1668
+ * while the part is unfinished (`time.end` unset), false on finalize — mirroring the tool
1669
+ * step's running→terminal upsert. Reasoning arriving while an event-driven terminal turn is
1670
+ * open also accumulates toward the turn-final reasoning emitted by closeEventTurn().
1671
+ */
1672
+ private emitReasoningStep(part: Record<string, unknown>, process?: ManagedProcess): void {
1673
+ const messageId = stringValue(part.messageID);
1674
+ if (!messageId || this.messageRoles.get(messageId) !== "assistant") return;
1675
+ const step = decodeOpenCodeReasoningPart(part);
1676
+ // Empty reasoning text is backend muzak (the provider's own SQLite shows sessions whose
1677
+ // first persisted reasoning part is `""`) — not operator signal. Dropping it here (rather
1678
+ // than emitting an empty row) also keeps noteOutput's `!body.trim()` filter from ever
1679
+ // seeing it. A part that STARTS empty still gets its row the moment text arrives.
1680
+ if (!step) return;
1681
+ const prev = this.emittedReasoningParts.get(step.stepId);
1682
+ if (prev === step.body) return;
1683
+ this.emittedReasoningParts.set(step.stepId, step.body);
1684
+ pruneToLimit(this.emittedReasoningParts, OPENCODE_EVENT_STATE_LIMIT);
1685
+ // #2605 — record the latest snapshot per part for the event-turn close below. Unconditional
1686
+ // (not just on emit): the close's reconcile must see the newest body even for frames that
1687
+ // were live-emitted, so it can compare per-part and emit nothing already traced.
1688
+ if (this.eventTurnId) this.eventTurnReasoning.set(step.stepId, step.body);
1689
+ const finalized = isRecord(part.time) && part.time.end !== undefined;
1690
+ this.sessionEventCb(openCodeReasoningSessionEvent(step, this.activeTurnId() ?? stringValue(process?.meta?.sessionId), !finalized));
1691
+ }
1692
+
1693
+ /**
1694
+ * #2354 — decode one ToolPart and emit its session step, with dedup + terminal-lock: a
1695
+ * duplicate frame or a reordered update (a late `running` after `completed`) is dropped rather
1696
+ * than regressing or double-appending the row. Only assistant-message tool parts are surfaced.
1697
+ * `stepId` = the stable part id, so the server upserts running→completed IN PLACE (one row),
1698
+ * exactly like Codex's app-server item-id upsert.
1699
+ */
1700
+ private emitToolStep(part: Record<string, unknown>, process?: ManagedProcess): void {
1701
+ // #2354 review (LOW) — fail CLOSED: a tool part lacking a messageID (so its role can't be
1702
+ // confirmed assistant) is dropped, not surfaced.
1703
+ const messageId = stringValue(part.messageID);
1704
+ if (!messageId || this.messageRoles.get(messageId) !== "assistant") return;
1705
+ const step = decodeOpenCodeToolPart(part);
1706
+ if (!step) return;
1707
+ const rank = openCodeToolStatusRank(step.status);
1708
+ const prevRank = this.emittedToolSteps.get(step.stepId);
1709
+ if (prevRank !== undefined && rank <= prevRank) return;
1710
+ this.emittedToolSteps.set(step.stepId, rank);
1711
+ pruneToLimit(this.emittedToolSteps, OPENCODE_EVENT_STATE_LIMIT);
1712
+ if (step.params !== undefined) this.toolStepParams.set(step.stepId, step.params);
1713
+ const params = step.params !== undefined ? step.params : this.toolStepParams.get(step.stepId);
1714
+ pruneToLimit(this.toolStepParams, OPENCODE_EVENT_STATE_LIMIT);
1715
+ // #2459 — tool steps attach to the live turn id (send()'s while a headless delivery runs,
1716
+ // the event-driven terminal turn otherwise), never to a stale prior headless turn id.
1717
+ this.sessionEventCb(openCodeToolSessionEvent({ ...step, params }, this.activeTurnId() ?? stringValue(process?.meta?.sessionId)));
1718
+ }
1719
+
1720
+ /**
1721
+ * #2413 — mark the turn-final assistant text as a `response` (not `narration`), so the runner's
1722
+ * Final Response capture identifies it, auto-forwards an OpenCode child's report to its
1723
+ * coordinator, and the chat styles it apart from reasoning — parity with Claude/Codex, which
1724
+ * OpenCode lacked entirely (everything read as thinking, nothing auto-forwarded).
1725
+ *
1726
+ * #2605 — also emits the turn-final `reasoning` rows from the SAME completed assistant message
1727
+ * (`completion.reasoningSteps`, via `openCodeTurnFinalEvents` in opencode-tool-events.ts), so
1728
+ * the model's thinking survives in the trace even when the SSE reasoning frames were never
1729
+ * observed (a POST-only reader, a dropped subscription, a turn whose frames raced the close):
1730
+ * the completed message persists every part with its full text. Each row carries its OWN part
1731
+ * id as stepId — the exact key the live SSE door already emitted — so the server upserts
1732
+ * live+final into ONE row (the upsert keys on stepId ONLY); a merged body under a synthetic
1733
+ * stepId would store the turn's thinking twice. The ledger below is the per-part
1734
+ * emitted-body record the SSE door shares, scoped to part ids — never a global body pool.
1735
+ *
1736
+ * The text is read from the synchronous POST's completed assistant message (`completion.text`,
1737
+ * ground truth per #2389) rather than reconstructed from SSE. `confidence:"definitive"` because
1738
+ * the POST returning IS the provider-native turn-end edge; `final:true` is what makes it a
1739
+ * turn-final capture downstream. The same text already streamed live as `narration` (per text
1740
+ * part); the dashboard's per-turn narration/response dedup (keyed on the shared `turnId`)
1741
+ * suppresses the duplicate, exactly as it does for Codex.
1742
+ */
1743
+ private emitTurnFinalResponse(process: ManagedProcess, completion: OpenCodeCompletion): void {
1744
+ const turnId = this.sendTurnId ?? stringValue(process.meta?.sessionId);
1745
+ const emittedBodies: Record<string, string> = {};
1746
+ for (const [stepId, body] of this.emittedReasoningParts) emittedBodies[stepId] = body;
1747
+ for (const event of openCodeTurnFinalEvents(completion, turnId, emittedBodies)) this.emitReasoningFinal(event);
1748
+ }
1749
+
1750
+ /** #2605 — ledger write for a turn-final reasoning row, shared by both turn-close seams. */
1751
+ private emitReasoningFinal(event: ProviderSessionEvent): void {
1752
+ if (event.stepId) {
1753
+ this.emittedReasoningParts.set(event.stepId, event.body);
1754
+ pruneToLimit(this.emittedReasoningParts, OPENCODE_EVENT_STATE_LIMIT);
1755
+ }
1756
+ this.sessionEventCb(event);
1757
+ }
1758
+
1759
+ /**
1760
+ * #2397 — turn-ending PROVIDER errors (e.g. an OpenRouter 429 on a throttled preview model)
1761
+ * are read off THIS synchronous POST's response body (`completion.error`, via
1762
+ * `openCodeCompletion`), not off a separate SSE listener. #2389 already established that this
1763
+ * response is ground truth and deterministic (no dependency on an SSE frame's timing); reusing
1764
+ * that one seam here — instead of also inspecting `message.updated`'s `info.error` — keeps
1765
+ * OpenCode's turn-outcome detection in exactly one place, as the standing one-seam rule in this
1766
+ * repo's CLAUDE.md requires.
1767
+ *
1768
+ * Retry/re-roll runs entirely inside this loop (bounded: `OPENCODE_ERROR_RETRY_MAX_ATTEMPTS`
1769
+ * retries per session, one session re-roll) so `deliverInitialPrompt`/`deliver` see one turn
1770
+ * outcome regardless of how many attempts it took — matching this endpoint's existing contract
1771
+ * of "does not return until the turn is over".
1772
+ */
1773
+ /**
1774
+ * @returns `"ok"` when the turn ended in output (or a positively-parsed empty completion that
1775
+ * is not itself a failure — #2389's normal empty-turn path), `"failed"` only when `failTurn` was
1776
+ * called — i.e. the message provoked a terminal, unrecoverable failure — and `"held"` (#2619)
1777
+ * when the provider refused the turn and it ended in the rate-limit hold: not delivered, not
1778
+ * failed. #2397 round 3 (MED-3): the caller uses this to decide whether the message may be
1779
+ * marked delivered/read.
1780
+ *
1781
+ * #2397 round 5 review (INFO) — this method is non-reentrant BY CONSTRUCTION, and the
1782
+ * `suppressProviderIdle`/`turnInFlight`/`turnClosed`/`turnAbandoned` instance fields it owns
1783
+ * assume that: every `adapter.deliver`/`deliverInitialPrompt` call site in runner-core runs inside
1784
+ * `providerInput.run`, which serializes them, so a second `send()` call never starts before the
1785
+ * previous one's promise has settled. If that ever changed, these fields would need to become
1786
+ * per-call state instead of per-adapter-instance state.
1787
+ */
1788
+ private async send(process: ManagedProcess, text: string, inputObserver?: ProviderInputObserver): Promise<OpenCodeSendOutcome> {
1789
+ await this.apiBudgets.get(process)?.prepare();
1790
+ // #2539 Phase B — retain the live process for the soft-steer wake hook (see its doc).
1791
+ this.softSteerProcess = process;
1792
+ let attempt = 0;
1793
+ let rerolled = false;
1794
+ let lastFailureDetail: string | undefined;
1795
+ let lastFailureErrorType: string | undefined;
1796
+ // #2397 round 4 review (F1/F2) — round 3 only raised this flag on a failed, retryable attempt,
1797
+ // so it stayed false while ATTEMPT 1's own POST was in flight. On a fast (e.g. loopback) server
1798
+ // the FAILED-attempt idle SSE frame routinely beats that attempt's own POST response — the
1799
+ // issue's own captured incident shape, a 429 on the very first attempt — so an unsuppressed
1800
+ // attempt-1 idle still closed the turn early and, via runner-core's #2389 seam, marked the
1801
+ // linked Task done while the retry loop was still running. Raised for the WHOLE call (below,
1802
+ // past the readiness guard — round 5 LOW-2) so there is no "attempt 1 is unguarded" window.
1803
+ // Every return path must therefore close the turn itself — `closeSuppressedTurn` does that,
1804
+ // idempotently per call (`turnClosed`) — see the `finally`.
1805
+ // #2459 — close a still-open event-driven terminal turn BEFORE the reset below: a chat
1806
+ // delivery taking over the boundary must end the terminal turn first (its own SSE idle, if
1807
+ // it even arrived, would be swallowed under this call's suppression), and closeEventTurn
1808
+ // sets `turnClosed` — resetting it after the close keeps THIS call's deterministic close
1809
+ // live. Order: terminal idle clears the terminal turn id, then this call's busy mints its own.
1810
+ if (this.eventTurnId) this.closeEventTurn();
1811
+ // #2459 round 1 (review D) — a chat delivery also consumes any armed terminal prompt: a
1812
+ // stale arm surviving this call would wrongly admit this call's own buffered busy echo
1813
+ // after the close. (This call's POST user echo is recorded in seenUserMessageIds live, so
1814
+ // its late duplicate can never re-arm — see the message.updated door.)
1815
+ this.pendingTerminalPrompt = false;
1816
+ // #2459 round 2 (review D) — a buffered terminal prompt flushed under the session
1817
+ // pseudo-turn BEFORE the delivery takes over: the operator typed first, and the buffer
1818
+ // must never leak across the headless turn boundary. Fallback id is the session
1819
+ // pseudo-turn — never this call's (or any prior headless) sendTurnId.
1820
+ this.flushBufferedPrompt(stringValue(process.meta?.sessionId) || undefined);
1821
+ this.turnClosed = false;
1822
+ // #2425 — reset at the top of every call: a stray leftover event from a truly-abandoned
1823
+ // PREVIOUS turn must not be allowed to affect THIS new call's turn boundary (see the field
1824
+ // comment).
1825
+ this.turnAbandoned = false;
1826
+ this.serverRetryWall = undefined;
1827
+ // #2413 review (HIGH-1) — one fresh turn id per send() (stable across this call's retries/
1828
+ // rerolls, which are ONE logical turn), so every send is distinct and the busy edge below hands
1829
+ // runner-core this exact id as its currentTurnId.
1830
+ this.sendTurnId = `opencode-turn-${crypto.randomUUID()}`;
1831
+ try {
1832
+ for (const relayContextInput = prepareOpenCodeRelayContext(process, text);;) {
1833
+ const client = process.meta?.client as OpenCodeClient | undefined;
1834
+ const sessionId = stringValue(process.meta?.sessionId);
1835
+ const model = stringValue(process.meta?.model) ?? openCodeQualifiedModel(undefined, this.provider);
1836
+ if (!client || !sessionId) throw new Error("OpenCode session is not ready");
1837
+ // #2397 round 5 review (LOW-2) — raised only once we're actually about to run an attempt
1838
+ // (past the guard above), so a "session not ready" throw never emits a close for a turn
1839
+ // that never started (never emitted its own busy edge below).
1840
+ this.turnInFlight = true;
1841
+ this.suppressProviderIdle = true;
1842
+ // #2459 round 1 (review D) — a headless turn now exists on this process: from here on,
1843
+ // terminal-origin busies are admitted only through the armed fresh-prompt gate.
1844
+ this.headlessTurnSeen = true;
1845
+ const providerState = attempt > 0 || rerolled
1846
+ ? openCodeUpstreamRetryState({ attempt, rerolled, message: lastFailureDetail ?? "retrying", errorType: lastFailureErrorType })
1847
+ : undefined;
1848
+ this.statusCb({ status: "busy", reason: "provider-turn", ...(this.sendTurnId ? { id: this.sendTurnId } : {}), ...(providerState ? { providerState } : {}) });
1849
+ // #2389 — this endpoint is synchronous: it returns the COMPLETED assistant message. That
1850
+ // body is ground truth about whether the model produced anything, so it feeds the same
1851
+ // evidence funnel the cross-provider ledger reads, rather than being discarded as it was
1852
+ // during the incident. Only a positively-parsed non-empty completion counts; an
1853
+ // unrecognised body says nothing.
1854
+ // #2418/#2532 — compose once outside this retry loop, but the durable first-turn markers
1855
+ // are consumed by PROOF OF ACCEPTANCE, not by this await's return: `sendOpenCodeRelayTurn`
1856
+ // arms the SSE acceptance door before dispatch and reconciles against persisted session
1857
+ // messages if the await itself fails, so an accepted-but-interrupted turn (e.g. the #2425
1858
+ // whole-turn abort) still retains the consumed flags instead of re-prepending the briefing
1859
+ // on every queued message. A readiness guard or rejected transport request has not reached
1860
+ // the provider, so RunnerCore's retry must still include the briefing. Retryable resolved
1861
+ // completions and the same-turn session re-roll reuse this exact immutable local input, so
1862
+ // prefixes neither stack nor disappear mid-turn.
1863
+ inputObserver?.offered(Date.now(), false);
1864
+ let completion: OpenCodeCompletion;
1865
+ try {
1866
+ completion = await sendOpenCodeRelayTurn(client, process, sessionId, model, relayContextInput, (body) => this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body }));
1867
+ } catch (error) {
1868
+ if (this.holdOnServerRetryWall()) return "held";
1869
+ throw error;
1870
+ }
1871
+ if (this.holdOnServerRetryWall()) return "held";
1872
+ // #2355 — every completion (success, a produced-output error, or a still-retrying error)
1873
+ // carries this turn's fresh token usage; report it to Relay's normal context telemetry path
1874
+ // the same way Codex's `codexTokenUsageContext` does, rather than only on the happy path.
1875
+ this.reportContextUsage(process, model, completion.tokens);
1876
+ // MessageAbortedError is the intended outcome of an interrupt() (session.abort) WE issued
1877
+ // (or the operator issued) — not a provider failure. Fall through to the ordinary
1878
+ // empty/non-empty handling below rather than retrying or failing an intentional cancel.
1879
+ if (completion.error && completion.error.name !== "MessageAbortedError") {
1880
+ const error = completion.error;
1881
+ const detail = `${error.name}${error.statusCode !== undefined ? ` ${error.statusCode}` : ""}: ${error.message}`;
1882
+ // #2397 round 2 (F1/F2) — `completion.error` used to be checked independently of any
1883
+ // output the turn already produced. A turn that ran tool steps (a commit, say) and THEN
1884
+ // hit a retryable error was resent into a brand-new session with no memory of that work
1885
+ // — re-executing it. And a non-retryable error on a turn that DID produce a reply (e.g.
1886
+ // MessageOutputLengthError after a long, otherwise-successful answer) took a healthy
1887
+ // agent offline. Retrying or failing must only ever happen when the turn produced
1888
+ // NOTHING; with output present, report the error and let the turn end normally.
1889
+ // #2397 round 3 (HIGH-1) — `completionProducedOutput` excludes bare step-start/finish
1890
+ // bookkeeping parts. The issue's own captured incident (one bare step-start part, zero
1891
+ // tokens, a 429) used to satisfy the old parts>0 check and take THIS branch — no retry,
1892
+ // no error status, reported as a clean success. It must fall through to the retry path.
1893
+ const producedOutput = completionProducedOutput(completion);
1894
+ if (producedOutput) {
1895
+ this.sessionEventCb({ type: "narration", origin: "provider", body: `OpenCode reported ${detail} after the turn had already produced output; not retrying — reporting the output instead.` });
1896
+ this.clearUpstreamRetryState(attempt, rerolled);
1897
+ this.emitTurnFinalResponse(process, completion);
1898
+ this.closeSuppressedTurn();
1899
+ this.turnActivityCb();
1900
+ this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
1901
+ return "ok";
1902
+ }
1903
+ // #2619 — an explicit account usage/quota wall: in-session retries and a re-roll cannot
1904
+ // clear it (the re-roll would only drop the conversation). Hold now on the same session;
1905
+ // the relay resumes us. Checked BEFORE the non-retryable exit: a provider may send a
1906
+ // quota wall as a non-retryable 403 ("Quota exceeded; limit will reset tomorrow").
1907
+ if (isProviderUsageLimit(error)) {
1908
+ this.holdTurnOnProviderLimit(error, detail);
1909
+ return "held";
1910
+ }
1911
+ // #2619 — throttle wording is retryable whatever the provider flagged (zai sends a
1912
+ // non-retryable 403 "Rate limit exceeded"): retry, then hold like any throttle. A real
1913
+ // auth/model refusal still fails here.
1914
+ if (!error.isRetryable && !isProviderThrottle(error)) {
1915
+ this.failTurn(detail, openCodeTerminalFailureReason(error));
1916
+ return "failed";
1917
+ }
1918
+ if (attempt < OPENCODE_ERROR_RETRY_MAX_ATTEMPTS) {
1919
+ attempt += 1;
1920
+ const delayMs = openCodeErrorRetryDelayMs(attempt);
1921
+ lastFailureDetail = detail;
1922
+ lastFailureErrorType = error.errorType;
1923
+ // #2397 round 3 (MED-5) — `diagnostic: true`: this narrates the ADAPTER's own retry
1924
+ // mechanics, not anything the model produced. Left uncredited it would let
1925
+ // emptyTurns.noteOutput() (runner-core's onSessionEvent wiring) read a retry storm
1926
+ // that ultimately fails as forward progress, cancelling #2389's input-armed escalation
1927
+ // that is the only thing left to wake the spawn parent once failTurn takes the agent
1928
+ // offline below.
1929
+ this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed (${detail}); retrying in ${Math.round(delayMs / 1000)}s (attempt ${attempt}/${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS})…` });
1930
+ // suppressProviderIdle is already raised for the whole call (see entry) — nothing to
1931
+ // set here; this attempt's own leaked idle is suppressed same as attempt 1's.
1932
+ await this.sleepImpl(delayMs);
1933
+ continue;
1934
+ }
1935
+ // #2619 — a provider overload (5xx) is provider-wide, not a broken session: a re-roll
1936
+ // would only drop the history before the same wall. Hold on the SAME session.
1937
+ if (isProviderOverload(error)) {
1938
+ this.holdTurnOnProviderLimit(error, `${detail} (after ${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS} in-session retries)`);
1939
+ return "held";
1940
+ }
1941
+ // Retry exhausted for the CURRENT session. Bimodal-per-session evidence (#2397 issue
1942
+ // comment) says an ambiguous 429 can be a per-session failure — re-roll to a fresh one
1943
+ // once. #2619 — at most ONCE PER HOLD EPISODE (until a turn goes through again), so a
1944
+ // long wall re-rolls once and then holds: history loss stays bounded to one attempt.
1945
+ if (!rerolled && !this.rerolledThisHoldEpisode) {
1946
+ this.rerolledThisHoldEpisode = true;
1947
+ // #2397 round 2 (F5) — say plainly that this drops the conversation: re-rolling
1948
+ // mutates process.meta.sessionId to a brand-new, history-less session, which for a
1949
+ // long-lived agent throws away everything said so far.
1950
+ this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed ${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS} times in a row in this session (${detail}); re-rolling to a new OpenCode session (this drops the current conversation history) and retrying…` });
1951
+ let newSessionId: string;
1952
+ try {
1953
+ newSessionId = await client.createSession(stringValue(process.meta?.sessionTitle) ?? "agent-relay");
1954
+ } catch (createError) {
1955
+ // #2619 — the original session is still intact; a failed re-roll during a
1956
+ // provider outage is no reason to end the agent. Hold like the exhausted path.
1957
+ this.holdTurnOnProviderLimit(error, `${detail} (session re-roll failed: ${errMessage(createError)})`);
1958
+ return "held";
1959
+ }
1960
+ // Every other reader of the session id (send/interrupt/shutdown) goes through
1961
+ // process.meta.sessionId, so mutating it in place re-routes the whole adapter to the
1962
+ // new session — no separate "which session is active" bookkeeping needed.
1963
+ if (process.meta) process.meta.sessionId = newSessionId;
1964
+ // #2539 Phase B — soft-steer fencing on re-roll: same contract as the clear swap —
1965
+ // accepted-but-unconsumed batches on the dead session reoffer to the normal queue
1966
+ // (the settlement callback fires inside sessionReplaced).
1967
+ const softSteerOrphans = await this.softSteer.sessionReplaced(newSessionId);
1968
+ if (softSteerOrphans.length > 0) {
1969
+ logger.info("opencode", `soft-steer: reoffering ${softSteerOrphans.length} relay id(s) after the retry budget re-rolled the session`);
1970
+ }
1971
+ rerolled = true;
1972
+ attempt = 0;
1973
+ lastFailureDetail = detail;
1974
+ lastFailureErrorType = error.errorType;
1975
+ // suppressProviderIdle is already raised for the whole call — see entry.
1976
+ continue;
1977
+ }
1978
+ // #2619 — retries and the re-roll are exhausted on a RETRYABLE error: the provider is
1979
+ // down or throttling, not the agent. This used to failTurn → terminal `error` → runner
1980
+ // exit, killing every zai session ~10 min into a quota wall. Hold and let the relay's
1981
+ // resume sweep back off and wake the agent, as it does for Claude.
1982
+ this.holdTurnOnProviderLimit(error, `${detail} (after ${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS} retries and a session re-roll)`);
1983
+ return "held";
1984
+ }
1985
+ if (!completion.empty && completionProducedOutput(completion)) {
1986
+ this.clearUpstreamRetryState(attempt, rerolled);
1987
+ this.emitTurnFinalResponse(process, completion);
1988
+ this.closeSuppressedTurn();
1989
+ this.turnActivityCb();
1990
+ this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
1991
+ return "ok";
1992
+ }
1993
+ // Either a non-empty completion with no positively-parsable output (an unrecognised body —
1994
+ // says nothing either way), or a POSITIVELY parsed empty completion (#2389's normal
1995
+ // empty-turn path, closed deterministically here rather than depending on a possibly late
1996
+ // or lost SSE idle frame). Either way this POST returning is itself proof the turn is over
1997
+ // — the endpoint is synchronous — so let the shared `finally` below perform the close
1998
+ // instead of duplicating it at two near-identical return sites. #2397 round 4 review (F3):
1999
+ // that exact duplication was unadjudicated — deleting either explicit call left every
2000
+ // opencode test green, since `finally` already produces the identical close with no
2001
+ // observable ordering difference from here (nothing else runs between this `return` and
2002
+ // the `finally`).
2003
+ this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
2004
+ return "ok";
2005
+ }
2006
+ } finally {
2007
+ // #2397 round 4/5 review (F2) — the single deterministic close for this whole call. Some
2008
+ // return paths above call `closeSuppressedTurn()` explicitly first (when something else in
2009
+ // that branch — `turnActivityCb`, most notably — must run AFTER the close, so ordering
2010
+ // matters); the rest fall straight through to here, since nothing runs between their
2011
+ // `return` and this `finally` and an explicit call there would be a pure no-op duplicate
2012
+ // (see F3 above). `failTurn`'s own unconditional close also marks `turnClosed`, so by the
2013
+ // time ANY return path reaches here this is a no-op — EXCEPT when NONE of those ran: a throw
2014
+ // from `client.sendMessage` (transport reset, socket drop) or the "session not ready" guard.
2015
+ // #2397 round 6 review (MED-2r) — that guard throw must NOT fall through to
2016
+ // `closeSuppressedTurn()`. The old comment here claimed `suppressProviderIdle` "was also
2017
+ // never raised this call, so closeSuppressedTurn is a no-op there too" — true only on the
2018
+ // FIRST call. `suppressProviderIdle` is STICKY (round 5, MED-1): on any later call, if the
2019
+ // prior call's own trailing idle has not yet arrived to lower it back to false, it is still
2020
+ // true when this guard throws — so the old code emitted a phantom `idle + clear` for a turn
2021
+ // that never started, never emitted a busy, never reached the provider (verified: cold = 0
2022
+ // closes, warm = 1). Gate on whether THIS call ever actually started an attempt
2023
+ // (`turnInFlight` was raised past the guard), captured before resetting it below, instead of
2024
+ // on the sticky flag. Before F2 (unrelated to this gate) that left `suppressProviderIdle`
2025
+ // raised forever with no further idle to lower it — `currentTurnId` never cleared, the
2026
+ // provider-turn claim stuck busy, queued mail never draining.
2027
+ const turnStartedThisCall = this.turnInFlight;
2028
+ this.turnInFlight = false;
2029
+ // Closing here (when `turnStartedThisCall` is true — a genuine mid-attempt throw) trades a
2030
+ // turn the server might still be running for a turn that reliably ends; the adapter has no
2031
+ // other signal on a throw, and `failTurn` already makes that same trade unconditionally today.
2032
+ // #2425 — `turnClosed` still being false at this point means NEITHER a success branch nor
2033
+ // `failTurn` ran before we got here — the only way in is an uncaught throw (e.g. `sendMessage`
2034
+ // aborting on the #2425 turn-timeout, or any other transport failure) whose actual server-side
2035
+ // outcome is unknown. Mark the turn abandoned before force-closing it, so a genuine same-
2036
+ // session busy/idle that arrives later can still self-heal the status instead of being
2037
+ // swallowed forever by the otherwise-permanent `suppressProviderIdle` (see its field doc).
2038
+ if (turnStartedThisCall) {
2039
+ if (!this.turnClosed) this.turnAbandoned = true;
2040
+ this.closeSuppressedTurn();
2041
+ }
2042
+ }
2043
+ }
2044
+
2045
+ /**
2046
+ * #2355 — the runner's `latestProcessContext()` (runner-core.ts) reads `process.meta.context`
2047
+ * directly (the same fallback path Codex's `meta.context` sets from `thread/tokenUsage/updated`),
2048
+ * feeding Relay's normal telemetry (dashboard/API/timeline) and the context-pressure warning /
2049
+ * self-compaction advisory. A no-op whenever the turn carried no parsable token count or the
2050
+ * model's window is unknown — silence here, not a fabricated 0/0 reading.
2051
+ */
2052
+ private reportContextUsage(process: ManagedProcess, model: string | undefined, tokensUsed: number | undefined): void {
2053
+ // #2599 — precedence payload-record > legacy #2595 window > manifest. The record's qualified
2054
+ // name is ALSO threaded through: meta.model is re-pinned to the server-computed qualified name
2055
+ // when the record is present, so later reportContextUsage calls (which read meta.model) keep
2056
+ // resolving against the payload instead of re-deriving from a possibly-ambiguous alias.
2057
+ const config = process.meta?.config as RunnerSpawnConfig | undefined;
2058
+ const record = openCodeModelRecord(config?.modelRecord, this.provider);
2059
+ // #2659 — the configured window belongs to the LAUNCH model, not a terminal-switched one.
2060
+ const configWindow = openCodeOnLaunchModel(process.meta, model) ? record?.contextWindowTokens ?? config?.contextWindowTokens : undefined;
2061
+ const windowTokens = typeof configWindow === "number" && Number.isFinite(configWindow) && configWindow > 0
2062
+ ? configWindow
2063
+ : openCodeContextWindowTokens(model, this.provider);
2064
+ const context = openCodeContextState({ tokensUsed, windowTokens });
2065
+ if (context && process.meta) process.meta.context = context;
2066
+ }
2067
+
2068
+ /**
2069
+ * #2397 round 2 (F6) — once a retry (or the post-reroll retry) actually produces output, the
2070
+ * operator-visible "retrying…"/blocked hold must not linger on screen until whatever SSE idle
2071
+ * happens to arrive next — which `suppressProviderIdle` above can delay well past this turn.
2072
+ * Clear it explicitly and immediately; a no-op when no retry hold was ever shown.
2073
+ */
2074
+ private clearUpstreamRetryState(attempt: number, rerolled: boolean): void {
2075
+ if (attempt === 0 && !rerolled) return;
2076
+ this.statusCb({
2077
+ status: "busy",
2078
+ reason: "provider-turn",
2079
+ providerState: { state: "active", reason: "provider-error-recovered", label: "OpenCode recovered after a provider error", source: "opencode", updatedAt: Date.now() },
2080
+ });
2081
+ }
2082
+
2083
+ /**
2084
+ * #2397 round 4/5 review (F1/F2/MED-1) — `suppressProviderIdle` is raised for the WHOLE `send()`
2085
+ * call, from just past the readiness guard (see the loop entry), so every path that ends the
2086
+ * turn — success, either failure, or the `finally`'s throw fallback — must close it
2087
+ * deterministically itself; the synchronous POST returning (or the throw itself) is ground truth
2088
+ * either way. `turnClosed` (NOT `suppressProviderIdle`) is the idempotency guard: it stops the
2089
+ * SAME call's multiple close sites — an explicit branch call plus the `finally` fallback — from
2090
+ * emitting a second idle for one call. `suppressProviderIdle` itself deliberately stays raised
2091
+ * once this fires (round 5, MED-1): a healthy turn's own trailing real SSE idle can still arrive
2092
+ * AFTER this close, and stays swallowed unless a genuine terminal turn or an abandoned turn
2093
+ * admits its own events — see the field comments.
2094
+ */
2095
+ private closeSuppressedTurn(hold?: ProviderState): void {
2096
+ if (this.turnClosed) return;
2097
+ this.turnClosed = true;
2098
+ // #2484 round 1 review (F2/F3) — turn close: flush the debounced token-usage report now
2099
+ // rather than wait out its timer. Not awaited — a live turn boundary, not teardown; flush()
2100
+ // already routes failures to reporter.reportFailure internally, so the `.catch(() => {})`
2101
+ // here only satisfies the unguarded-async ratchet (#1665) — it never actually fires.
2102
+ void this.tokenUsagePoster.flushAll().catch(() => {});
2103
+ this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"], ...(hold ? { providerState: hold } : {}) });
2104
+ // #2539 Phase B — idle edge with unconsumed soft batches: arm the one bounded wake. Never
2105
+ // into a provider-limit hold: the wake would only hit the same wall; the relay's resume turn
2106
+ // ends in a normal idle that arms it then.
2107
+ if (!hold) this.maybeArmSoftSteerWake();
2108
+ }
2109
+
2110
+ /**
2111
+ * #2619 — end the turn in the shared rate-limit hold instead of a terminal failure: the turn
2112
+ * closes (idle + provider-turn) carrying the `blocked/rate_limit` providerState, which
2113
+ * runner-core keeps as its rateLimitHold until the relay's resume turn goes busy. The session
2114
+ * (and its history, including the refused user message) stays alive.
2115
+ */
2116
+ private holdTurnOnProviderLimit(error: OpenCodeAssistantError, detail: string, resetAt?: number): void {
2117
+ const hold = buildProviderRefusalHold({ ...error, detail, source: "opencode", ...(resetAt ? { resetAt } : {}) });
2118
+ this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode provider refused the turn (${detail}); holding the session — Humwick resumes it ${hold.kind === "usage_limit" ? "when the usage window resets" : "with bounded backoff"}.` });
2119
+ this.closeSuppressedTurn(hold);
2120
+ }
2121
+
2122
+ /**
2123
+ * #2775 — OpenCode's own retry of an in-flight send(): a transient one is shown as "retrying"; a wall
2124
+ * (usage/free-tier limit, far-off `next`, or an endless throttle) aborts the session so the POST
2125
+ * returns and send() ends the turn in the shared rate-limit hold instead of waiting silently.
2126
+ */
2127
+ private onServerRetry(retry: OpenCodeServerRetry | undefined, process?: ManagedProcess): void {
2128
+ if (!retry || !this.turnInFlight || this.serverRetryWall) return;
2129
+ if (!isOpenCodeServerRetryWall(retry)) {
2130
+ this.statusCb({ status: "busy", reason: "provider-turn", ...(this.sendTurnId ? { id: this.sendTurnId } : {}), providerState: openCodeServerRetryState(retry) });
2131
+ return;
2132
+ }
2133
+ this.serverRetryWall = retry;
2134
+ const client = process?.meta?.client as OpenCodeClient | undefined;
2135
+ const sessionId = stringValue(process?.meta?.sessionId);
2136
+ if (client && sessionId) void fireAndForget(`opencode abort on provider wall (session ${sessionId})`, () => client.abort(sessionId), (message) => logger.error("opencode", message));
2137
+ }
2138
+
2139
+ private holdOnServerRetryWall(): boolean {
2140
+ const wall = this.serverRetryWall;
2141
+ if (!wall) return false;
2142
+ this.serverRetryWall = undefined;
2143
+ const error: OpenCodeAssistantError = { name: "OpenCodeRetry", message: wall.message, ...(wall.reason ? { errorType: wall.reason } : {}), isRetryable: true };
2144
+ this.holdTurnOnProviderLimit(error, `${wall.message} (OpenCode retry attempt ${wall.attempt}${wall.reason ? `, ${wall.reason}` : ""})`, wall.next);
2145
+ return true;
2146
+ }
2147
+
2148
+ /**
2149
+ * #2459 — close the event-driven (terminal-origin) turn on its matching SSE idle: emit the
2150
+ * turn-final response accumulated from the turn's finalized assistant text parts (the same
2151
+ * narration→final-response shape send()'s POST path produces, and the same turnId so the
2152
+ * dashboard's per-turn narration/response dedup applies), then the idle edge that closes
2153
+ * runner-core's turn. Completes the #2425 abandonment self-heal (the server's own truth for
2154
+ * the abandoned turn has now arrived). Deliberately does NOT re-raise the sticky idle
2155
+ * suppression or stamp the echo window: an event turn's busy+idle arrive LIVE — they DRIVE
2156
+ * this state machine; there is no send()-POST await whose buffered chunk could replay them —
2157
+ * so there is no trailing-echo shape to guard against, and a genuine follow-up terminal turn
2158
+ * must be able to open immediately.
2159
+ */
2160
+ private closeEventTurn(): void {
2161
+ const turnId = this.eventTurnId;
2162
+ const text = this.eventTurnText.join("\n\n").trim();
2163
+ // #2605 — reconcile the turn's reasoning parts against what the live door already emitted,
2164
+ // per part id: a part whose latest snapshot EQUALS its last-emitted body needs no row (it is
2165
+ // already traced); a part that GREW after its last live emit — or was never live-emitted
2166
+ // (frames racing the close, a part observed only at close time) — gets one row under its OWN
2167
+ // part id, so the server upserts rather than duplicates. Comparison is scoped to THIS turn's
2168
+ // part ids (eventTurnReasoning) against their own ledger entries — never to a global body
2169
+ // pool — so an identical body in a later turn is never misread as already-seen. Each entry
2170
+ // is already the newest full snapshot for its part, so there is nothing to prefix-merge.
2171
+ const unseen: OpenCodeReasoningStep[] = [];
2172
+ for (const [stepId, body] of this.eventTurnReasoning) {
2173
+ const trimmed = body.trim();
2174
+ if (!trimmed) continue;
2175
+ if (this.emittedReasoningParts.get(stepId) === trimmed) continue;
2176
+ unseen.push({ stepId, body: trimmed });
2177
+ }
2178
+ this.eventTurnId = undefined;
2179
+ this.eventTurnText = [];
2180
+ this.eventTurnReasoning = new Map();
2181
+ // Stray idles with no open event turn stay swallowed via the existing `turnClosed` guard.
2182
+ this.turnClosed = true;
2183
+ // #2425 self-heal completion: this idle close was the server's own truth for the abandoned
2184
+ // turn. Round 1 (review D) — re-latch the sticky suppression it had bypassed (the #2425
2185
+ // escape hatch exists only while the abandonment is unresolved), so post-heal strays stay
2186
+ // swallowed under the same permanent suppression as every other send()-closed turn.
2187
+ if (this.turnAbandoned) {
2188
+ this.turnAbandoned = false;
2189
+ this.suppressProviderIdle = true;
2190
+ }
2191
+ if (turnId) {
2192
+ for (const step of unseen) this.emitReasoningFinal(openCodeReasoningSessionEvent(step, turnId, false));
2193
+ for (const event of openCodeTurnFinalEvents({ text }, turnId)) this.sessionEventCb(event);
2194
+ }
2195
+ // #2484 round 1 review (F2/F3) — same flush as closeSuppressedTurn's turn close; the
2196
+ // `.catch(() => {})` is the same ratchet-only no-op (flush() already self-catches).
2197
+ void this.tokenUsagePoster.flushAll().catch(() => {});
2198
+ this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"] });
2199
+ // #2539 Phase B — a terminal-origin turn's close is an idle edge too: unconsumed soft
2200
+ // batches get the same single bounded wake.
2201
+ this.maybeArmSoftSteerWake();
2202
+ }
2203
+
2204
+ /**
2205
+ * #2459 round 2 (review D) — emit a buffered terminal prompt under `turnId` and clear the
2206
+ * slot. `turnId` is the freshly minted terminal turn id (primary path — busy admission) or
2207
+ * the session pseudo-turn (bounded no-busy fallbacks); it is NEVER a stale headless
2208
+ * sendTurnId, which only exists while send() is in flight and is never passed here.
2209
+ */
2210
+ private flushBufferedPrompt(turnId: string | undefined): void {
2211
+ const buffered = this.bufferedTerminalPrompt;
2212
+ if (!buffered) return;
2213
+ this.bufferedTerminalPrompt = undefined;
2214
+ this.sessionEventCb({ type: "prompt", origin: "terminal", body: buffered.body, occurredAt: buffered.occurredAt, ...(turnId ? { turnId } : {}) });
2215
+ }
2216
+
2217
+ /**
2218
+ * #2459 — the turn id live session events should attach to: send()'s per-call id while a
2219
+ * headless delivery is in flight, otherwise the event-driven terminal turn minted on the
2220
+ * server's own busy edge. Deliberately NO fallback to a stale `sendTurnId` outside send() —
2221
+ * that inheritance is exactly the contamination this replaces; callers fall back to the
2222
+ * session pseudo-turn instead.
2223
+ */
2224
+ private activeTurnId(): string | undefined {
2225
+ return this.turnInFlight ? this.sendTurnId : this.eventTurnId;
2226
+ }
2227
+
2228
+ private failTurn(detail: string, terminalFailureReason: string): void {
2229
+ // #2397 round 3 (MED-5) — diagnostic:true; see the retry-loop narration above for why.
2230
+ this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed: ${detail}` });
2231
+ // #2397 round 2 (F3) — an "error" status alone is never treated as a turn boundary by
2232
+ // #2389's seam in runner-core (only `idle && provider-turn` clears `currentTurnId` and notes
2233
+ // the turn ended); without this, the next `busy` reuses the stale turn id and the empty-turn
2234
+ // detector is blinded for the rest of the agent's life. Close the turn through that same seam
2235
+ // first — exactly as the parsed-empty-completion path above already does — before marking the
2236
+ // terminal failure. This close is unconditional (fires whether or not suppressProviderIdle was
2237
+ // raised), so mark `turnClosed` here too — otherwise `send()`'s `finally` fallback would see it
2238
+ // still unset and emit a second, out-of-order idle after the error status below.
2239
+ this.turnClosed = true;
2240
+ // #2484 round 1 review (F2/F3) — same flush as the other turn-close sites: a failed turn can
2241
+ // still carry real (partial) usage worth persisting; the `.catch(() => {})` is the same
2242
+ // ratchet-only no-op as the other two sites.
2243
+ void this.tokenUsagePoster.flushAll().catch(() => {});
2244
+ this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"] });
2245
+ this.statusCb({
2246
+ status: "error",
2247
+ reason: "provider-turn",
2248
+ clear: ["provider-turn"],
2249
+ // #2443 — mirrors the Claude adapter's own "model-unavailable" stop-failure shape
2250
+ // (providerState + an embedded timeline event) instead of leaving this error silent beyond
2251
+ // the mirror log: a terminal failure used to reach runner-core with no timeline event and no
2252
+ // classification, so an exit driven by this status surfaced nothing to the operator beyond
2253
+ // "exited after Ns". `terminal: true` marks it as needing operator attention, same as Claude's.
2254
+ providerState: { state: "failed", reason: terminalFailureReason, message: detail, source: "opencode-turn-failure", terminal: true },
2255
+ metadata: { terminalFailureReason, terminalFailureMessage: detail },
2256
+ timeline: {
2257
+ status: "provider.restart_decision",
2258
+ title: terminalFailureReason === "model-unavailable" ? "OpenCode model unavailable" : "OpenCode turn failed",
2259
+ body: detail,
2260
+ metadata: { eventType: "provider.restart_decision", decision: "stop-surface", reason: terminalFailureReason },
2261
+ },
2262
+ });
2263
+ }
2264
+ }