@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,2955 @@
1
+ import { writeChildEnv } from "@humwick/sdk/env";
2
+ import { normalizeDeliverResult } from "./delivery-result";
3
+ import { TokenUsageReportCoverage } from "./agent-runner/token-usage-coverage";
4
+ import { spawnWithServiceAccess } from "./service-access";
5
+ import { LifecycleMilestones } from "./agent-runner/lifecycle-milestones";
6
+ import { CommandDispatch } from "./agent-runner/commands/dispatch";
7
+ import { LifecycleCommands } from "./agent-runner/commands/lifecycle";
8
+ import { PromptCommands, promptOrigin } from "./agent-runner/commands/prompt";
9
+ import type { NativeSelfResumeHandoff } from "./native-self-resume";
10
+ import { LifecyclePhase } from "./agent-runner/lifecycle-phase";
11
+ import { startRunnerMcpProxy, runnerWebToolLog, runnerWebSearch, runnerMcpHiddenTools } from "./runner-mcp-proxy";
12
+ import { HttpLiveness } from "./agent-runner/http-liveness";
13
+ import { TaskClaims } from "./agent-runner/task-claims";
14
+ import { TurnReport } from "./agent-runner/turn-report";
15
+ import { SessionMirror } from "./agent-runner/session-mirror";
16
+ import { Registration, runnerIdentityMeta } from "./agent-runner/registration";
17
+ import { RunnerContext, type RunnerOptions } from "./agent-runner/runner-context";
18
+ export type { RunnerOptions } from "./agent-runner/runner-context";
19
+ import { hostname } from "node:os";
20
+ import { resolveEnvLayers, selectProviderConfigEnv } from "@humwick/sdk/child-env";
21
+ import { createHash } from "node:crypto";
22
+ import { join } from "node:path";
23
+ import { type ContextState, type Message, type ProviderState, HELD_FOR_PAYLOAD_KEY, PROVIDER_SUBMIT_UNCONFIRMED, PROXY_AUTH_HOLD_KIND, errMessage, frameSuppressedFromTurn, stringValue } from "@humwick/sdk";
24
+ import { exitRunner } from "./runner-exit";
25
+ import { isResumeWake, ProviderHoldEpisodes } from "./provider-hold";
26
+ import { contextStateFromProbeMetrics, quotaStateFromProbeMetrics } from "@humwick/sdk/context-probe";
27
+ import { isPidAlive } from "@humwick/sdk/process-utils";
28
+ import { HELD_PROMPT_IMAGE_PATHS, providerMessageText, type PromptOfferOptions, type ProviderInputObserver } from "./adapter";
29
+ import { ProviderSubmitFailure, PROVIDER_SUBMIT_MAX_DELIVERIES, providerSubmitRetryDelayMs } from "./provider-submit-failure";
30
+ import { computeLivenessSignal, type LivenessInputs } from "./liveness";
31
+ import type { DeliverResult, ManagedProcess, PaneObservation, ProviderDeliverContext, ProviderStatusUpdate, RunnerSpawnConfig, SemanticStatus, ShutdownOutcome, TerminalAttachSpec } from "./adapter";
32
+ import { messagesWithCachedAttachments } from "./attachment-cache";
33
+ import { AttachmentCacheSweep } from "./attachment-cache-sweep";
34
+ import { resolveTestRunRootPid, TEST_RUN_DEDUP_SWEEP_MS, TestRunDedupSweep } from "./test-run-dedup";
35
+ import { cachedGateRunCommandsForCwd } from "./gate-run-config";
36
+ import { AreaMemoryTracker } from "./area-memory";
37
+ import { GateRunLeaseProbe } from "./gate-run-lease-probe";
38
+ import { ClaimTracker } from "./claim-tracker";
39
+ import { ProviderQuotaHarvest } from "./adapters/provider-quota-harvest";
40
+ import { agentShellLaunchEnv } from "./adapters/project-shell";
41
+ import { isNotificationOnlyInput } from "./inbox-carrier";
42
+ import { readRunnerContextProbeState } from "./context-probe-state";
43
+ import { startControlServer, type ControlServer, type ResolvedPermissionPrompt } from "./control-server";
44
+ import { fileQuestionDecision } from "./question-decision";
45
+ import { ReplyObligationCache, obligationNagEligible, responseCaptureReplyRoute } from "./reply-obligation-cache";
46
+ import { Outbox } from "./outbox";
47
+ import { RefusedPromptStore } from "./refused-prompt-store";
48
+ import { retryParkPost } from "./park-post";
49
+ import { getManifest } from "@humwick/providers";
50
+ import { hostSourcesFor } from "./host-asset-plan";
51
+ import { RELAY_MCP_TOKEN_ENV, relayMcpEndpoint, resolveSharedMcpUrl } from "./relay-mcp";
52
+ import { RelayMcpProxy, isolatedWorkspaceDescriptor } from "./relay-mcp-proxy";
53
+ import { runtimeMetadata } from "./version";
54
+ import { logger } from "./logger";
55
+ import { ensureSessionScratch, type SessionScratchLayout } from "./session-scratch";
56
+ import { sweepRunnerStaleScratch } from "./runner-stale-scratch-sweep";
57
+ import { RunnerInsights } from "./runner-insights";
58
+ import { BusyReconciler } from "./busy-reconciler";
59
+ import { fireAndForget } from "./async-guard";
60
+ import { providerTurnWatchdogTimelineEvent, reconciledTurnTimelineEvent, type RunnerTimelineEvent } from "./turn-reconcile-event";
61
+ import { EMPTY_TURN_NUDGE, EmptyTurnWatch, type EmptyTurnArm } from "./empty-turn";
62
+ import { buildBackgroundTaskLedger, isStaleBackgroundScriptOnlyBusy } from "./background-script-reconcile";
63
+ import { buildRunnerSpawnConfig } from "./runner-spawn-config";
64
+ import { PendingPermissionStore, type PendingPermissionRequestRecord } from "./pending-permission-store";
65
+ import { ProviderInputQueue, type ProviderInputTicket } from "./provider-input-queue";
66
+ import { runnerLaunchInjectionSessionEvents, type RunnerRelayInjectionEvent } from "./relay-injection-events";
67
+ import { providerTerminalSession, providerTerminalSocket } from "./process-meta";
68
+ import { capsFromEnv, mcpProxyEnabledFromEnv, orchestratorBaseDirFromEnv, registrationTimeoutMsFromEnv, runnerLogFileFromEnv, runnerOutboxDirWithInfoFallback, tagsFromEnv, workspaceModeFromEnv } from "./config";
69
+ import { PRE_DESTROY_TIMEOUT_MS, type SessionDestroyReason } from "./session-destroy";
70
+ import { assertClearInjectCommitted, ClearInjectInboxFence, executeCompactCommand, lateClearHookContent, LateClearRecovery } from "./context-command";
71
+ import { ContextResetReporter, ContextSubmitAborted, DeliverySettlements } from "./context-boundary";
72
+ import { EffortReconciler, isEffortNotice, runnerEffortSwitch } from "./effort-command";
73
+ import { isNativeSelfResumeCompact, NativeSelfResumeTracker, recoverNativeSelfResumeExit } from "./native-self-resume";
74
+ import { initializeRunnerTemplates } from "./template-resolver";
75
+ import { publishRuntimeTokenForCli } from "./runtime-token-file";
76
+ import { Credentials } from "./agent-runner/credentials";
77
+ import {
78
+ csvTags,
79
+ isContextState,
80
+ isHttpStatusError,
81
+ isTurnInProgressDeliveryError,
82
+ providerExitLastError,
83
+ providerExitDiagnostics,
84
+ providerExitResumeId,
85
+ providerManualRecoveryMessage,
86
+ providerManualRecoveryReason,
87
+ providerMonitorUnavailableMessage,
88
+ providerStateFromActiveWork,
89
+ registerWithinDeadline,
90
+ relayBusUrl,
91
+ runnerAgentStatus,
92
+ runnerBusErrorAction,
93
+ runnerMessageMatches,
94
+ runnerMessageShouldDirtyObligations,
95
+ runnerShouldResolveProviderExit,
96
+ runnerShouldRestartUnexpectedProviderExit,
97
+ runtimeProviderCapabilities,
98
+ shouldLogDeliveryFailure,
99
+ stripRunnerClaimedGuidance,
100
+ taskIdFromMessage,
101
+ type ProbeModelInfo, type RestartProviderOptions,
102
+ } from "./runner-helpers";
103
+
104
+ const STALE_PROVIDER_TURN_BUSY_MS = 10 * 60_000;
105
+
106
+ type HeldSubmit = { messages: Map<number, Message>; resend?: () => Promise<unknown> };
107
+ const emptyHeldSubmit = (): HeldSubmit => ({ messages: new Map() });
108
+ type RunnerStatusFrame = { agentStatus: string; ready?: boolean; meta?: Record<string, unknown> };
109
+
110
+ // #650: how often to reconcile pid-bearing busy work against actual process liveness.
111
+ const BUSY_LIVENESS_RECONCILE_MS = 5_000;
112
+ const HTTP_LIVENESS_LOG_INTERVAL_MS = 5 * 60 * 1000;
113
+ const UNEXPECTED_EXIT_WINDOW_MS = 2 * 60 * 1000;
114
+ // Bound retries after a terminal provider failure.
115
+ const TERMINAL_FAILURE_RETRY_BASE_MS = 2_000;
116
+ const TERMINAL_FAILURE_RETRY_MAX_MS = 60_000;
117
+ // #2539 live-fire — recency bound for ids this runner has positively SETTLED (acked + marked
118
+ // read). A server list snapshot taken before the settle's markRead (the startup unread poll,
119
+ // any future server-listed lane) can land its stale copy in enqueueMessage AFTER the delivery —
120
+ // re-delivering an already-delivered message (immediately as a steer under the soft-steer
121
+ // default, or at turn-end before it). Insertion-ordered, oldest evicted; the race window is
122
+ // intra-process and short, so a recency cap is generous.
123
+ const SETTLED_MESSAGE_IDS_CAP = 1024;
124
+ // #2539 Phase B — bound on soft-steered ids held between persist and consumption. The adapter's
125
+ // ledger cap backpressures first (it stops accepting new positive persists), so this is the
126
+ // runner-side defense-in-depth ceiling, not the operating bound.
127
+ const AWAITING_MID_TURN_DELIVERY_CAP = 256;
128
+ const RAPID_EXIT_MS = 30 * 1000;
129
+ const MAX_RAPID_UNEXPECTED_EXITS = 3;
130
+ const UNDETERMINED_PROVIDER_EXIT_CONFIRM_MS = 1_500;
131
+ const PROVIDER_EXIT_CONFIRM_POLL_MS = 250;
132
+ // A UserPromptSubmit echo matching a runner-injected prompt within this window is
133
+ // the same prompt arriving back from the provider — drop it to avoid a duplicate.
134
+ const PROMPT_ECHO_DEDUP_MS = 30_000;
135
+ // Relay-injected content (delivered messages, memory context) is wrapped with
136
+ // these markers; a UserPromptSubmit echo starting with one is a runner injection,
137
+ // not a human typing into the terminal, so it must not be mirrored as a prompt.
138
+ // `<task-notification>` is the harness wrapper for async monitor/task wake-ups
139
+ // (which embed a `[relay message #…]` deeper inside, defeating a plain prefix
140
+ // match) — it is emitted only by the harness, never typed by a human, so any
141
+ // echo starting with it is a system injection, not a user prompt (#289).
142
+ const RELAY_INJECTION_MARKERS = ["[relay message #", "[agent-relay", "<task-notification>"];
143
+ // #1116: bounded settle-poll for provider pre-flush anchors. The adapter owns the
144
+ // source-specific read; the runner owns the shared timeout knobs.
145
+ const SETTLE_POLL_INTERVAL_MS = 250;
146
+ const SETTLE_POLL_TIMEOUT_MS = 4_000;
147
+
148
+ export class AgentRunner {
149
+ private readonly context: RunnerContext;
150
+ private get options() { return this.context.options; }
151
+ private get agentId() { return this.context.agentId; }
152
+ private get http() { return this.context.http; }
153
+ private get bus() { return this.context.bus; }
154
+ private readonly effortRuntime: Pick<RunnerOptions, "instanceId" | "adapter" | "effort">;
155
+ private readonly claims = new ClaimTracker();
156
+ // Phase 2 (#196): the Stop hook reads reply obligations from this local snapshot, never
157
+ // from the server — so a slow server can no longer wedge a turn (the crux fix).
158
+ private readonly obligationCache: ReplyObligationCache;
159
+ // Phase 2 (#196): Runner→server append-log events (session turns, reasoning, prompts,
160
+ // insights, hook-fatal) go through this durable, disk-backed, timestamped queue instead of
161
+ // direct fire-and-forget HTTP — so nothing is lost across a server/Runner restart.
162
+ private readonly outbox: Outbox;
163
+ private readonly sessionMirror: SessionMirror;
164
+ private readonly turnReport: TurnReport;
165
+ private readonly commandDispatch: CommandDispatch;
166
+ private readonly lifecycleCommands: LifecycleCommands;
167
+ private readonly lifecycleMilestones: LifecycleMilestones;
168
+ private readonly promptCommands: PromptCommands;
169
+ private readonly pendingPermissionStore: PendingPermissionStore;
170
+ // #1565 F1 — the one gate in front of the provider's single input channel. Every payload the
171
+ // runner delivers (message batch, injected prompt, self-resume continuation, memory preamble)
172
+ // passes through it, so two payloads for one agent can never interleave at the wire. FIFO, with
173
+ // the place in line taken synchronously at dispatch, so the payload the relay created first is
174
+ // the one delivered first.
175
+ private readonly providerInput = new ProviderInputQueue({
176
+ onStuck: (heldMs) => logger.warn("delivery", `provider input stream held for ${heldMs}ms; force-releasing so delivery cannot wedge`),
177
+ });
178
+ // #2984 — converges the provider on Relay's desired effort between turns; a bus notice only speeds it up.
179
+ private readonly effort = new EffortReconciler({ http: () => this.http, agentId: () => this.agentId, runner: () => this.effortRuntime, process: () => this.process, publish: () => this.bus.flushHeartbeat(), hasProviderTurn: () => this.claims.activeWork().some((w) => w.kind === "provider-turn"), reserveInput: () => this.providerInput.reserve(), log: (message) => this.sessionLog(message) });
180
+ // #2389 — "did that turn actually do anything", and what to do when the answer is no. The ledger,
181
+ // its timers and the nudge/escalate ladder all live in empty-turn.ts; what stays here is the
182
+ // wiring only AgentRunner can own — the adapter, the process handle and the input queue.
183
+ private readonly emptyTurns = new EmptyTurnWatch({
184
+ provider: () => this.options.provider,
185
+ canNudge: () => Boolean(this.process) && Boolean(this.options.adapter.deliverInitialPrompt),
186
+ nudge: () => void this.deliverEmptyTurnNudge().catch((error) => this.reportBackgroundRejection("empty-turn nudge", "turn.nudge_failed", "Empty-turn nudge not delivered", error)),
187
+ capturesInFlight: () => this.sessionMirror.capturesInFlight(),
188
+ log: (message) => this.sessionLog(message),
189
+ publishTimeline: (event) => this.publishRunnerTimelineEvent(event),
190
+ onMarkerCleared: () => this.publishStatus(),
191
+ });
192
+ private deliveringEmptyTurnNudge = false;
193
+ private readonly insights: RunnerInsights;
194
+ private readonly busyReconciler: BusyReconciler;
195
+ private readonly providerQuotaHarvest: ProviderQuotaHarvest;
196
+ private readonly credentials: Credentials;
197
+ private control?: ControlServer;
198
+ private registeredAt?: number;
199
+ // Stage 2 (#215): the local MCP endpoint the agent connects to, fronting the relay so the
200
+ // Runner owns reconnect/backoff + a durable buffer. Disabled via AGENT_RELAY_MCP_PROXY=0
201
+ // (then the agent connects to the relay directly, Stage-1 behaviour). The proxy secret is the
202
+ // bearer the agent presents to the localhost proxy — it decouples the agent from the rotating
203
+ // relay token (the proxy injects the live token relay-side).
204
+ private proxy?: RelayMcpProxy;
205
+ private mcpProxyEndpoint?: string;
206
+ private readonly mcpProxyEnabled: boolean;
207
+ private readonly mcpProxySecret: string;
208
+ private process?: ManagedProcess;
209
+ private readonly lifecycle = new LifecyclePhase({
210
+ suspendHeartbeat: () => this.bus.suspendHeartbeat(),
211
+ stopStatusWatchers: () => this.options.adapter.stopStatusWatchers?.(),
212
+ });
213
+ // #633 — set when the provider terminally exits but the runner stays alive (native
214
+ // auto-compaction / manual-resume hold). Rides on the offline status frame so the server fires a
215
+ // terminal `agent.exited` event with a #636 diagnosis instead of the death going silent. Cleared
216
+ // on any genuine restart back to busy/idle.
217
+ private terminalProviderExit?: Record<string, unknown>;
218
+ private nativeSelfResume = new NativeSelfResumeTracker();
219
+ private readonly registration: Registration;
220
+ private readonly httpLiveness: HttpLiveness;
221
+ private readonly taskClaims: TaskClaims;
222
+ private delivering = false;
223
+ private drainTimer?: Timer;
224
+ private livenessReconcileTimer?: Timer;
225
+ private readonly attachmentCacheSweep: AttachmentCacheSweep;
226
+ private readonly testRunDedupSweep: TestRunDedupSweep;
227
+ private readonly gateRunLeaseProbe: GateRunLeaseProbe;
228
+ private processStartedAt = 0;
229
+ private providerSessionId = crypto.randomUUID();
230
+ // Last transcript path seen this session — used by end-of-session Insights (#184)
231
+ // when the SessionEnd hook payload omits it.
232
+ private lastTranscriptPath?: string;
233
+ private readonly unexpectedExitTimes: number[] = [];
234
+ private readonly pendingMessages = new Map<number, Message>();
235
+ // Hold inbox input through clear+inject and the bounded late-commit grace (#2477/#2849).
236
+ private readonly continuationFence = new ClearInjectInboxFence(() => { if (this.pendingMessages.size > 0) this.scheduleDrain(); });
237
+ private readonly lateClearRecovery = new LateClearRecovery((commandId) => this.finishLateClearGrace(commandId));
238
+ private readonly deliverySettlements = new DeliverySettlements();
239
+ private readonly contextResets = new ContextResetReporter(this.deliverySettlements, (fact) => this.outbox.enqueue({
240
+ kind: "context-reset", payload: { agentId: this.agentId, instanceId: this.options.instanceId, ...fact }, idempotencyKey: `context-reset:${fact.resetId}` }), (message) => this.sessionLog(message), () => this.resetContextGeneration());
241
+ // A drain removes its batch from pendingMessages before its claim and attachment awaits. Keep that
242
+ // pre-boundary lane visible so a clear that begins during either await fences the same messages.
243
+ private drainingMessageIds?: Set<number>;
244
+ private pendingTimelineEvent?: RunnerTimelineEvent;
245
+ private lastProviderEventAt?: number;
246
+ // Session-mirror: a synthesized id grouping a turn's reasoning/tool steps and
247
+ // its final response. Set when a provider-turn starts, cleared when it ends.
248
+ private currentTurnId?: string;
249
+ private currentTurnStartedAt?: number;
250
+ private completedProviderTurns = 0;
251
+ // True while a turn that was already in flight is being compacted. Some providers
252
+ // post a transient idle after compaction even though the turn resumes afterward, so
253
+ // treating that idle as a turn end (flip idle, kill the reasoning tail) makes chat go dark
254
+ // until the next prompt. Set when `compacting` arrives with a turn running; cleared on the
255
+ // genuine end (a plain idle with no compaction timeline).
256
+ private compactionMidTurn = false;
257
+ // Prompt-echo dedup: a short, time-bounded queue of prompts the runner itself
258
+ // injected (chat box or initial prompt) that are still awaiting their matching
259
+ // UserPromptSubmit echo. A single slot dropped earlier entries when several prompts
260
+ // were injected before their echoes returned (rapid sends while the provider is busy
261
+ // and queues them) — the evicted ones then double-posted. Match consumes one entry.
262
+ private injectedPrompts: Array<{ text: string; at: number; imagePaths?: readonly string[] }> = [];
263
+ // Tracks whether the provider is in a legitimate blocked/approval state, so the
264
+ // busy reconciler doesn't mistake a permission prompt for a stuck-busy turn.
265
+ private providerBlocked = false;
266
+ // Display-only startup modal evidence. It never touches work claims or turn state.
267
+ private startupPaneDialog?: ProviderState;
268
+ private deliverySubmitHold?: ProviderState;
269
+ /** #2664 — what the hold withheld: the unconfirmed item for the operator's resend, and later prompts parked in order behind it. */
270
+ private heldSubmit: HeldSubmit = emptyHeldSubmit();
271
+ private readonly submitDeliveryAttempts = new Map<number, number>();
272
+ private submitRecoveryInFlight = false;
273
+ private submitRecoveryTurnStarted = false;
274
+ private submitRecoveryRequested = false;
275
+ // #2664 — a held submit that did land often leaves no later event to re-run the recovery probe.
276
+ private submitRecoveryTimer?: ReturnType<typeof setInterval>;
277
+ private submitRecoveryPollMs = 10_000;
278
+ private submitRecoveryPollWindowMs = 5 * 60_000;
279
+ // #2664 — the operator's resend of the held prompt runs ahead of every parked inbox row.
280
+ private submitResendInFlight = false;
281
+ // #2664 — set INSIDE the FIFO slot of an unconfirmed submit, so the next queued prompt sees the fence
282
+ // before the failing caller has published the hold; cleared with the hold.
283
+ private submitFenceRaised = false;
284
+ // #2664 — park posts (and their retries) run in the input order, their place taken at the parking decision.
285
+ private readonly parkPosts = new ProviderInputQueue({ onStuck: (heldMs) => logger.warn("delivery", `parked prompt post held ${heldMs}ms; releasing the next`) });
286
+ private terminalFailure?: { reason: string; message: string; providerState?: ProviderState };
287
+ // #286: a usage/rate-limit hold. The turn truly ended (idle), but the agent is
288
+ // held until the limit resets, so this can't ride an active-work claim like the
289
+ // permission-blocked state does. publishStatus surfaces it as the agent's
290
+ // providerState; it clears when a new turn starts (the relay's resume message
291
+ // wakes one) so the blocked badge lifts on its own.
292
+ private rateLimitHold?: ProviderState;
293
+ private pendingRateLimitHold?: ProviderState;
294
+ // #1173: like rateLimitHold — a deny-then-hold "questions" (AskUserQuestion) prompt's own
295
+ // turn genuinely ends (the provider reacts to the denial and stops normally) before the human
296
+ // answers, so the pendingApproval can't ride the provider-turn active-work claim (it would
297
+ // vanish the instant that claim clears on the real idle). Persists across idles and new turns
298
+ // (#2913); cleared once the answer is injected (or the prompt is dismissed/times out).
299
+ private pendingQuestionHold?: ProviderState;
300
+ // #2919 — the turn-end capture saw accepted input queued behind this turn: its idle opens the next turn.
301
+ private queuedTurnFollows = false;
302
+ // Settle-poll cadence for the pre-flush anchor (#1116) — instance fields (not just the
303
+ // module constants) so tests can shrink them without waiting out the real bound.
304
+ private settlePollIntervalMs = SETTLE_POLL_INTERVAL_MS;
305
+ private settlePollTimeoutMs = SETTLE_POLL_TIMEOUT_MS;
306
+ private scratch?: SessionScratchLayout;
307
+ // #417: tracks how many times each spawner obligation has been re-injected so a
308
+ // non-responsive provider can't spin in a re-injection loop.
309
+ private readonly pendingObligationInjections = new Map<number, number>();
310
+ // #2539 live-fire — ids positively settled (acked + marked read) by THIS process; enqueueMessage
311
+ // drops stale re-enqueues of them. Bounded recency (see SETTLED_MESSAGE_IDS_CAP).
312
+ private readonly settledMessageIds = new Set<number>();
313
+ // #2550 — defense-in-depth backstop under the server-side decideDelivery rule: content hashes of
314
+ // the system-owned context payloads (memory/identity/charter/startup-context/self-resume
315
+ // continuation) already delivered to the CURRENT provider context generation. A byte-identical
316
+ // re-inject within one generation is skipped (a future double-emitting path must not paste the
317
+ // same envelope twice); the table is cleared at every generation boundary (provider respawn,
318
+ // compact, clear) because the new generation legitimately re-receives its charter/memory. Only
319
+ // successful deliveries are recorded, so a failed attempt's retry still delivers. User/agent
320
+ // prompts and normal messages never pass through this table — identical prompts legitimately
321
+ // repeat. This is a safety net, not the primary fix: the server's history classification of the
322
+ // recovery artifact is what removes the cause.
323
+ private readonly injectedContextHashes = new Set<string>();
324
+ // #3411 — which path areas this context generation entered; reset with injectedContextHashes.
325
+ private readonly areaMemory = new AreaMemoryTracker({ agentId: () => this.agentId, cwd: () => this.options.cwd, providerSessionId: () => this.providerSessionId, http: () => this.http, log: (message) => this.sessionLog(message) });
326
+ // #2539 Phase B — soft-steered batches accepted by the provider but NOT yet consumed: the
327
+ // persisted-but-unconsumed window. The runner keeps the offered Message so the adapter's ledger
328
+ // can settle by id alone ({@link settleSoftSteerConsumption} marks read;
329
+ // {@link reofferSoftSteerIds} requeues the ORIGINAL message into the normal lane). Bounded —
330
+ // past the cap settle falls back to the un-acked requeue path (the adapter's own ledger cap
331
+ // backpressures before this, so the fallback is defense-in-depth).
332
+ private readonly awaitingMidTurnDelivery = new Map<number, Message>();
333
+ // #2619 — the hold/resume state machine: hold episodes + the ONE parked queue of refused input
334
+ // (unread, claim released, never re-drained into the wall). See provider-hold.ts.
335
+ private readonly hold = new ProviderHoldEpisodes();
336
+ private readonly refusedPrompts: RefusedPromptStore;
337
+ // #2619 — refused context injections parked as prompts (by heldPromptKey): what their ACCEPTANCE
338
+ // must still do that the refused injectContext did not (the #2550 hash, the clear fence release).
339
+ private readonly parkedContextInjections = new Map<string, { contentHash: string; afterClearCommandId?: string }>();
340
+ // #2397 round 3 review (MED-3r): same ladder, for a message an adapter reports via
341
+ // DeliverResult.terminalFailureIds — without this, a re-queued message whose provider just
342
+ // exhausted a terminal-failure retry budget (e.g. OpenCode against a rate-limited upstream) was
343
+ // re-attempted on the very next tick, forever, with no backoff. Cleared once it is acknowledged.
344
+ private readonly providerTerminalFailureAttempts = new Map<number, number>();
345
+
346
+ constructor(options: RunnerOptions) {
347
+ this.context = new RunnerContext(options, { providerSessionId: () => this.providerSessionId, setTimelineEvent: (event) => { this.pendingTimelineEvent = event; }, publishStatus: () => this.publishStatus() });
348
+ this.effortRuntime = { instanceId: options.instanceId, adapter: options.adapter, effort: options.effort };
349
+ this.registration = new Registration({ options: this.options, agentId: this.agentId, process: () => this.process, control: () => this.control, registeredAt: () => this.registeredAt, latestProbeMetrics: () => this.latestProbeMetrics(), sessionLog: (message) => this.sessionLog(message), flushHeartbeat: () => this.bus.flushHeartbeat() });
350
+ this.promptCommands = new PromptCommands({
351
+ options: this.options, agentId: this.agentId, process: () => this.process, isStopped: () => this.lifecycle.stopped,
352
+ armInput: (now, mode, initial) => this.emptyTurns.armInput(now, mode, initial), disarmInput: (arm) => this.emptyTurns.disarmInput(arm), inputObserver: (arm) => this.emptyTurns.inputObserver(arm),
353
+ recordInjectedPrompt: (prompt, images) => this.recordInjectedPrompt(prompt, images),
354
+ offerPromptOrPark: (prompt, opts, ticket, onHeld, heldFor) => this.offerPromptOrPark(prompt, opts, ticket, onHeld, heldFor),
355
+ offerProviderInput: (run, offeredNothing) => this.offerProviderInput(run, offeredNothing), terminalFailure: () => this.terminalFailure,
356
+ sessionLog: (message) => this.sessionLog(message), publishRunnerTimelineEvent: (event) => this.publishRunnerTimelineEvent(event),
357
+ holdFailedProviderSubmit: (detail, held) => this.holdFailedProviderSubmit(detail, held), resolvePermissionDecision: (input) => this.control?.resolvePermissionDecision(input),
358
+ reservePark: () => this.parkPosts.reserve(), notePendingPrompt: (messageId) => this.turnReport.notePendingPrompt(messageId),
359
+ cacheAttachments: (messages) => messagesWithCachedAttachments(messages, this.http, { agentId: this.agentId, onError: (message) => logger.warn("attachments", message) }),
360
+ parkPromptBehindSubmitHold: (params, key, turn) => this.parkPromptBehindSubmitHold(params, key, turn), waitForProviderTurnClear: () => this.busyReconciler.waitForProviderTurnClear(),
361
+ pendingQuestionApprovalId: () => typeof this.pendingQuestionHold?.pendingApproval?.id === "string" ? this.pendingQuestionHold.pendingApproval.id : undefined,
362
+ clearPendingQuestionHold: () => { this.pendingQuestionHold = undefined; }, publishStatus: () => this.publishStatus(),
363
+ });
364
+ this.lifecycleMilestones = new LifecycleMilestones({
365
+ options: this.options, agentId: this.agentId, process: () => this.process,
366
+ stopAttachmentCacheSweep: () => this.attachmentCacheSweep.stop(), stopTestRunDedupSweep: () => this.testRunDedupSweep.stop(), stopGateRunLeaseProbe: () => this.gateRunLeaseProbe.stop(),
367
+ disposeEmptyTurns: () => this.emptyTurns.dispose(), resetEmptyTurns: () => this.emptyTurns.reset(), resetLateClearRecovery: () => { this.lateClearRecovery.reset(); this.continuationFence.reset(); },
368
+ clearLifecycleAction: () => this.lifecycle.setLifecycleAction(undefined), setOffline: () => this.claims.setTerminalStatus("offline"), publishStatus: () => this.publishStatus(),
369
+ stopDrainTimer: () => { if (this.drainTimer) clearTimeout(this.drainTimer); this.drainTimer = undefined; }, stopSubmitRecoveryPoll: () => this.stopSubmitRecoveryPoll(),
370
+ stopTaskClaims: () => this.taskClaims.stop(), stopHttpLiveness: () => this.httpLiveness.stop(), stopCredentials: () => this.credentials.stop(),
371
+ disarmBusyLivenessReconcile: () => this.disarmBusyLivenessReconcile(), disarmBusyReconciler: () => this.busyReconciler.disarm(),
372
+ disposeProviderInput: () => this.providerInput.dispose(), disposeParkPosts: () => this.parkPosts.dispose(),
373
+ stopReasoningTail: () => this.sessionMirror.stopReasoningTail(), clearNativeSessionCapture: () => this.sessionMirror.clearNativeSessionCapture(), stopObligationCache: () => this.obligationCache.stop(),
374
+ closeStores: () => { this.outbox.close(); this.sessionMirror.close(); this.pendingPermissionStore.close(); this.refusedPrompts.close(); }, stopProxy: () => this.proxy?.stop(), stopControl: () => this.control?.stop(), closeBus: () => this.bus.close(),
375
+ clearTerminalStatus: () => this.claims.clearTerminalStatus(), clearProviderTurnWork: () => this.claims.clearWorkKind("provider-turn"), clearSubagentWork: () => this.claims.clearWorkKind("subagent"), clearBackgroundScriptWork: () => this.claims.clearWorkKind("background-script"),
376
+ endLateClearGrace: () => this.lateClearRecovery.endGrace(), clearDeliverySubmitHold: () => { this.deliverySubmitHold = undefined; }, clearSubmitFence: () => { this.submitFenceRaised = false; },
377
+ clearHeldSubmit: () => { this.heldSubmit = emptyHeldSubmit(); }, clearSubmitDeliveryAttempts: () => this.submitDeliveryAttempts.clear(),
378
+ settleTerminalTokenUsage: () => this.sessionMirror.settleTerminalTokenUsage(), terminalStatusLatched: () => this.lifecycle.terminalStatusLatched,
379
+ restoreProviderSessionId: (value) => { this.providerSessionId = value; }, restoreLastTranscriptPath: (value) => { this.lastTranscriptPath = value; },
380
+ clearPreDestroyRun: () => this.turnReport.clearPreDestroyRun(), reopenProviderStatus: () => this.lifecycle.reopenProviderStatus(), resumeHeartbeat: () => this.bus.resumeHeartbeat(),
381
+ });
382
+ this.lifecycleCommands = new LifecycleCommands({
383
+ options: this.options, agentId: this.agentId, process: () => this.process,
384
+ restartProvider: (opts) => this.restartProvider(opts), shutdownProvider: (hard, timeoutMs) => this.shutdownProvider(hard, timeoutMs), releaseSubmitHold: (resend) => this.releaseSubmitHold(resend),
385
+ hasProviderTurn: () => this.claims.activeWork().some((work) => work.kind === "provider-turn"), sessionLog: (message) => this.sessionLog(message), resetEmptyTurns: () => this.emptyTurns.reset(),
386
+ scheduleInterruptReconcile: () => this.busyReconciler.scheduleInterruptReconcile(), waitForProviderTurnClear: () => this.busyReconciler.waitForProviderTurnClear(), injectPrompt: (params) => this.promptCommands.injectPrompt(params),
387
+ markStopped: () => this.lifecycle.markStopped(), armTerminal: () => this.lifecycle.armTerminal(), setLifecycleAction: (action) => this.lifecycle.setLifecycleAction(action), publishStatus: () => this.publishStatus(),
388
+ });
389
+ this.commandDispatch = new CommandDispatch({
390
+ options: this.options, agentId: this.agentId, reserveInput: () => this.providerInput.reserve(), disarmForcedContext: (type) => this.registration.disarmForcedContext(type),
391
+ beginExitCommand: () => this.lifecycle.beginExitCommand(), setLifecycleAction: (action) => this.lifecycle.setLifecycleAction(action), isStopped: () => this.lifecycle.stopped,
392
+ beginNativeSelfResume: (type, params, id) => this.beginNativeSelfResume(type, params, id), runNativeSelfResume: (handoff, operation) => this.nativeSelfResume.run(handoff, operation),
393
+ rejectNativeSelfResume: (handoff, error) => this.nativeSelfResume.reject(handoff, error), finishNativeSelfResume: (handoff) => this.nativeSelfResume.finish(handoff),
394
+ beginSelfResumeClearFence: (type, params, id) => this.beginSelfResumeClearFence(type, params, id), startCommandClaim: (id) => this.claims.startClaim("command", id), finishCommandClaim: (id) => this.claims.finishClaim("command", id),
395
+ runPreSessionDestroy: (reason) => this.turnReport.runPreSessionDestroy(reason), publishStatus: () => this.publishStatus(), restart: (params) => this.lifecycleCommands.restart(params), reconnect: () => this.lifecycleCommands.reconnect(),
396
+ compactCommand: (params, handoff, id) => this.compactCommand(params, handoff, id), clearContextCommand: (params, ticket, id) => this.clearContextCommand(params, ticket, id),
397
+ releaseSubmitHold: (params) => this.lifecycleCommands.releaseSubmitHold(params), interrupt: (params) => this.lifecycleCommands.interrupt(params),
398
+ injectContext: (params, ticket) => { if (!this.process) throw new Error("provider process is unavailable"); return this.injectContext(params, ticket); },
399
+ respondToPermissionDecision: (params) => this.promptCommands.respondToPermissionDecision(params), injectPrompt: (params, ticket, id) => this.promptCommands.injectPrompt(params, ticket, id),
400
+ shutdown: (type, params) => this.lifecycleCommands.shutdown(type, params), finishClearCommand: (id) => this.finishClearCommand(id),
401
+ setOffline: () => this.http.setStatus(this.agentId, "offline", this.options.instanceId), deleteAgent: () => this.http.deleteAgent(this.agentId), stop: () => this.stop(),
402
+ updateCommand: (id, input) => this.bus.updateCommand(id, input), fetchCommand: (id) => this.http.request("GET", `/api/commands/${encodeURIComponent(id)}`), destructiveInputUnsettled: (id) => this.contextResets.unsettled(id), checkAcknowledgement: (ack, status) => this.nativeSelfResume.checkAcknowledgement(ack, status),
403
+ });
404
+ this.attachmentCacheSweep = new AttachmentCacheSweep(this.agentId, (message) => this.logRunnerDiagnostic(message));
405
+ // #2142 — "latest wins" test-run dedup, scoped to THIS session's command-owning provider
406
+ // process tree. Interactive Codex exposes a visible TUI pid separately from its App Server;
407
+ // commands descend from the latter, so resolveTestRunRootPid prefers the runner-owned child.
408
+ // No pid ⇒ no scope ⇒ no signalling: a sweep that cannot prove ownership must never act.
409
+ const sessionProviderPid = () => resolveTestRunRootPid(this.process, (managedProcess) => this.options.adapter.resolveBusyPid?.(managedProcess));
410
+ // #2461 — ancestry alone made this sweep a no-op for `relay_run` gate runs (a disjoint tree
411
+ // under the orchestrator). It now also owns anything env-tagged with this agent's id, classifies
412
+ // against the repo's CONFIGURED gate commands, and defers to the launch-time lease holder. The
413
+ // config read is cached for the session — it is a repo file, so re-reading it every 15s for the
414
+ // agent's whole life buys nothing. All three helpers live in their own leaves.
415
+ this.gateRunLeaseProbe = new GateRunLeaseProbe(this.agentId, TEST_RUN_DEDUP_SWEEP_MS);
416
+ this.testRunDedupSweep = new TestRunDedupSweep({
417
+ rootPid: sessionProviderPid, agentId: () => this.agentId,
418
+ gateCommands: () => cachedGateRunCommandsForCwd(this.options.cwd),
419
+ leaseHolderPid: () => this.gateRunLeaseProbe.pid(),
420
+ protectedPids: () => [process.pid], log: (message) => this.logRunnerDiagnostic(message),
421
+ });
422
+ this.context.configureLogger();
423
+ this.credentials = new Credentials(options, {
424
+ isStopped: () => this.lifecycle.stopped,
425
+ renewRuntimeToken: () => this.http.renewRuntimeToken(),
426
+ remintRuntimeToken: (reason) => this.http.remintRuntimeToken(reason),
427
+ onTokenChanged: (token, record) => {
428
+ publishRuntimeTokenForCli(token, record.expiresAt, (message) => this.logRunnerDiagnostic(message));
429
+ this.http.setToken(token);
430
+ this.bus.setToken(token);
431
+ // The proxy reads the token live via getToken(), so forwarding already uses the new one.
432
+ // A re-mint can change scope (e.g. a profile change), so refresh the relay tool list and
433
+ // emit tools/list_changed if the visible set changed (#215 — token-scope transition).
434
+ // #2973 — a re-register follows a relay restart (a deploy): re-list so changed tools reach the agent.
435
+ void this.proxy?.refreshTools().catch(() => {});
436
+ },
437
+ resumeLivenessAndPublishRotation: (status, jti) => {
438
+ this.httpLiveness.resetAuthFailure();
439
+ // An earlier auth failure may have stopped the liveness loop; restart it so the
440
+ // agent reports live again on the fresh token. startHttpLiveness clears any
441
+ // existing timer first, so this is safe on the normal (proactive) renew path too.
442
+ this.startHttpLiveness();
443
+ this.pendingTimelineEvent = { status, id: jti, timestamp: Date.now() };
444
+ this.bus.reconnectTransport(status === "runtime-token-reminted" ? "runtime token re-minted" : "runtime token renewed");
445
+ this.publishStatus();
446
+ },
447
+ publishRenewalFailure: () => {
448
+ this.pendingTimelineEvent = { status: "runtime-token-renewal-failed", timestamp: Date.now() };
449
+ this.publishStatus();
450
+ },
451
+ logRunnerDiagnostic: (message) => this.logRunnerDiagnostic(message),
452
+ reportBackgroundRejection: (scope, status, title, error) => this.reportBackgroundRejection(scope, status, title, error),
453
+ });
454
+ const token = this.credentials.current();
455
+ if (token) publishRuntimeTokenForCli(token, this.credentials.expiresAt, (message) => this.logRunnerDiagnostic(message));
456
+ this.mcpProxyEnabled = mcpProxyEnabledFromEnv();
457
+ this.mcpProxySecret = crypto.randomUUID();
458
+ const runtime = runtimeMetadata(options.provider);
459
+ this.context.initializeHttp(this.credentials.current());
460
+ this.options.adapter.configureTokenUsageReporter?.({
461
+ report: (record) => this.tokenUsageCoverage.report(record, (r) => this.http.reportTokenUsage(this.agentId, r)),
462
+ capture: (key, read, pending) => this.tokenUsageCoverage.capture(key, read, pending),
463
+ debug: (message) => this.sessionDebug(message),
464
+ // Capture/POST failures must be visible at INFO alongside empty passes (#2491).
465
+ reportFailure: (error) => this.sessionLog(`token-usage capture failed: ${errMessage(error)}`),
466
+ });
467
+ this.providerQuotaHarvest = new ProviderQuotaHarvest({
468
+ agentId: this.agentId,
469
+ provider: options.provider,
470
+ // #2514 — the per-provider config env carries a dedicated-home provider's (claude-b's) own
471
+ // credential, which the quota harvest needs to key its reports under the B subscription.
472
+ providerConfigEnv: () => this.options.providerConfig.env,
473
+ http: () => this.http,
474
+ });
475
+ this.httpLiveness = new HttpLiveness({
476
+ http: this.http, agentId: this.agentId, instanceId: options.instanceId, logIntervalMs: HTTP_LIVENESS_LOG_INTERVAL_MS,
477
+ isStopped: () => this.lifecycle.stopped, isTerminalStatusLatched: () => this.lifecycle.terminalStatusLatched,
478
+ currentStatus: () => this.claims.currentStatus(),
479
+ publishQuota: () => this.providerQuotaHarvest.publish(),
480
+ bootstrapUnreadMessages: () => this.bootstrapUnreadMessages(), triggerEffort: () => this.effort.trigger(),
481
+ recoverRuntimeTokenAfterAuthFailure: (source) => this.credentials.recoverRuntimeTokenAfterAuthFailure(source),
482
+ logRunnerDiagnostic: (message) => this.logRunnerDiagnostic(message),
483
+ });
484
+ this.taskClaims = new TaskClaims({
485
+ http: this.http, agentId: this.agentId,
486
+ startClaim: (kind, id, expiresAt) => this.claims.startClaim(kind, id, expiresAt),
487
+ finishClaim: (kind, id) => this.claims.finishClaim(kind, id),
488
+ isDrainingMessage: (messageId) => this.drainingMessageIds?.has(messageId) === true,
489
+ updateTaskStatus: async (taskId, input) => { await this.http.updateTaskStatus(taskId, input); },
490
+ enqueueCompletion: (record) => this.outbox.enqueue(record),
491
+ publishStatus: () => this.publishStatus(),
492
+ });
493
+ this.obligationCache = new ReplyObligationCache({ fetch: () => this.http.listReplyObligations(this.agentId) });
494
+ // Co-locate the durable outbox with the runner's runtime state (survives reboot) when the
495
+ // orchestrator told us where that is; otherwise the Outbox falls back to a temp dir.
496
+ const outboxDir = runnerOutboxDirWithInfoFallback();
497
+ this.refusedPrompts = new RefusedPromptStore({ agentId: this.agentId, dir: outboxDir });
498
+ this.outbox = new Outbox({ agentId: this.agentId, dir: outboxDir, send: (record) => record.kind === "task-completion" ? this.taskClaims.deliverCompletion(record) : this.sessionMirror.deliverOutboxEvent(record) });
499
+ this.sessionMirror = new SessionMirror({
500
+ agentId: this.agentId, provider: options.provider, instanceId: options.instanceId, startedAt: options.startedAt,
501
+ relayUrl: options.relayUrl, outboxDir, http: this.http, adapter: options.adapter,
502
+ getToken: () => this.credentials.current(),
503
+ updateOutboxPayload: (seq, payload) => this.outbox.updatePayload(seq, payload),
504
+ recoverRuntimeTokenAfterAuthFailure: (source) => this.credentials.recoverRuntimeTokenAfterAuthFailure(source),
505
+ sessionLog: (message) => this.sessionLog(message),
506
+ sessionTurnContext: () => this.turnReport.sessionTurnContext(),
507
+ lastTranscriptPath: () => this.lastTranscriptPath,
508
+ currentTurnId: () => this.currentTurnId,
509
+ reasoningCapture: () => this.options.providerConfig.reasoningCapture,
510
+ isRunnerInjectedPrompt: (text) => this.isRunnerInjectedPrompt(text),
511
+ publishProviderResponse: (event, body, turnId) => this.turnReport.publishProviderResponse(event, body, turnId),
512
+ });
513
+ this.turnReport = new TurnReport({
514
+ agentId: this.agentId, provider: options.provider, adapter: options.adapter, providerConfig: options.providerConfig, http: this.http,
515
+ currentTurnId: () => this.currentTurnId, currentTurnStartedAt: () => this.currentTurnStartedAt,
516
+ setTurnId: (id) => { this.currentTurnId = id; }, setTurnStartedAt: (at) => { this.currentTurnStartedAt = at; },
517
+ lastTranscriptPath: () => this.lastTranscriptPath, noteTranscriptPath: (path) => { this.lastTranscriptPath = path; },
518
+ providerSessionId: () => this.providerSessionId,
519
+ queuedTurnFollows: () => this.queuedTurnFollows, setQueuedTurnFollows: (queued) => { this.queuedTurnFollows = queued; },
520
+ settlePollIntervalMs: () => this.settlePollIntervalMs, settlePollTimeoutMs: () => this.settlePollTimeoutMs,
521
+ beginTurnCapture: () => this.sessionMirror.beginTurnCapture(), endTurnCapture: () => this.sessionMirror.endTurnCapture(),
522
+ flushSessionEvents: (timeoutMs) => this.sessionMirror.flushSessionEvents(timeoutMs),
523
+ pendingSessionEventCount: () => this.sessionMirror.pendingSessionEventCount(),
524
+ publishSessionEvent: (event) => this.sessionMirror.publishSessionEvent(event),
525
+ reportOutbox: { enqueue: (record) => this.sessionMirror.enqueueResponseReport(record) },
526
+ flushOutbox: (timeoutMs) => this.outbox.flush(timeoutMs),
527
+ settleTerminalTokenUsage: (path) => this.sessionMirror.settleTerminalTokenUsage(path),
528
+ noteOutput: () => this.emptyTurns.noteOutput(), notePrompt: (text, at) => this.nativeSelfResume.notePrompt(text, at),
529
+ isRunnerInjectedPrompt: (text) => this.isRunnerInjectedPrompt(text),
530
+ deliverySubmitHold: () => Boolean(this.deliverySubmitHold), tryRecoverProviderSubmitHold: (started) => this.tryRecoverProviderSubmitHold(started),
531
+ lifecycleAction: () => this.lifecycle.lifecycleAction, isStopped: () => this.lifecycle.stopped, terminalStatusLatched: () => this.lifecycle.terminalStatusLatched,
532
+ setLifecycleAction: (action) => this.lifecycle.setLifecycleAction(action),
533
+ noteFinalizing: (action, at) => this.lifecycle.notePublishedFinalizing(action, at),
534
+ publishRunnerStatus: (update) => this.publishRunnerStatus(update), publishStatus: () => this.publishStatus(),
535
+ captureContextRatio: (reason, opts) => this.captureContextRatio(reason, opts),
536
+ captureContinuationArchive: (reason, opts) => this.captureContinuationArchive(reason, opts),
537
+ responseCaptureReplyRoute: (startedAt, suppressUser) => responseCaptureReplyRoute(this.obligationCache.get(), startedAt, { suppressUser }),
538
+ dirtyObligations: () => this.obligationCache.markDirty(),
539
+ sessionLog: (message) => this.sessionLog(message), sessionDebug: (message) => this.sessionDebug(message),
540
+ });
541
+ this.pendingPermissionStore = new PendingPermissionStore({ agentId: this.agentId, dir: outboxDir });
542
+ this.insights = new RunnerInsights({
543
+ agentId: this.agentId,
544
+ cwd: options.cwd,
545
+ workspace: options.workspace,
546
+ getOutbox: () => this.outbox,
547
+ getProviderSessionId: () => this.providerSessionId,
548
+ getProcess: () => this.process,
549
+ getAdapter: () => this.options.adapter,
550
+ getLastTranscriptPath: () => this.lastTranscriptPath,
551
+ sessionLog: (message) => this.sessionLog(message),
552
+ });
553
+ this.registerProviderSessionRoutes();
554
+ this.busyReconciler = new BusyReconciler({
555
+ isStopped: () => this.lifecycle.stopped,
556
+ hasProcess: () => Boolean(this.process),
557
+ currentStatus: () => this.claims.currentStatus(),
558
+ isProviderBlocked: () => this.providerBlocked,
559
+ hasProviderTurn: () => this.claims.activeWork().some((w) => w.kind === "provider-turn"),
560
+ canProbeActivity: () => Boolean(this.options.adapter.probeActivity),
561
+ probeActivity: () => this.process && this.options.adapter.probeActivity
562
+ ? this.options.adapter.probeActivity(this.process)
563
+ : undefined,
564
+ probePaneBusy: () => this.process && this.options.adapter.probePaneBusy
565
+ ? this.options.adapter.probePaneBusy(this.process)
566
+ : undefined,
567
+ clearProviderTurn: (reason) => this.forceClearProviderTurn(reason),
568
+ escalateWatchdog: (reason) => this.escalateProviderTurnWatchdog(reason),
569
+ turnCleared: () => { this.effort.turnCleared(); if (this.pendingMessages.size > 0) this.scheduleDrain(); },
570
+ sessionDebug: (message) => this.sessionDebug(message),
571
+ });
572
+ this.context.initializeBus({
573
+ url: relayBusUrl(options.relayUrl),
574
+ role: "provider",
575
+ componentId: options.runnerId,
576
+ agentId: this.agentId,
577
+ instanceId: options.instanceId,
578
+ token: this.credentials.current(),
579
+ machine: hostname(),
580
+ capabilities: [
581
+ ...new Set([
582
+ ...options.providerConfig.defaultCapabilities,
583
+ ...options.capabilities,
584
+ ...csvTags(capsFromEnv()),
585
+ "lifecycle.shutdown.hard",
586
+ "lifecycle.restart.hard",
587
+ "lifecycle.status.semantic",
588
+ "lifecycle.heartbeat",
589
+ "lifecycle.reconnect.transport",
590
+ "commands.lifecycle",
591
+ "capabilities.report",
592
+ ]),
593
+ ],
594
+ tags: [...new Set([options.provider, ...csvTags(tagsFromEnv()), ...options.tags, ...options.providerConfig.defaultTags, ...(options.headless ? ["headless"] : [])])],
595
+ meta: runnerIdentityMeta(this.options, { runtime, agentId: this.agentId, tmuxSession: providerTerminalSession(this.process), tmuxSocket: providerTerminalSocket(this.process), auth: { profileId: this.credentials.profileId, jti: this.credentials.jti, expiresAt: this.credentials.expiresAt } }, "registration"),
596
+ heartbeatMeta: () => this.heartbeatRuntimeMeta(),
597
+ heartbeatIntervalMs: 30_000,
598
+ initialStatus: "idle",
599
+ });
600
+ }
601
+
602
+ get id(): string {
603
+ return this.agentId;
604
+ }
605
+
606
+ private registerProviderSessionRoutes(): void {
607
+ // (#1617) — neither publisher has a try/catch of its own, and both bottom out in
608
+ // sessionOutbox.enqueue: a synchronous bun:sqlite INSERT that throws outright once the outbox
609
+ // is closed (teardown ordering in stop(): the adapter keeps emitting after close) or the
610
+ // database errors. This is the highest-frequency fire-and-forget site in the runner — every
611
+ // provider session event passes through it — so leaving it unguarded is the likeliest way a
612
+ // live agent silently stops existing (Bun exits(1) on an unhandled rejection, #1614).
613
+ // #2389 — turn-output evidence is counted HERE, at the registration seam, not inside the
614
+ // publish functions: `publishProviderSessionEvents` delegates to the single-event path only for
615
+ // `prompt`/`response` and handles every other type itself, and Claude's reasoning tail emits
616
+ // ALL of its tool/reasoning steps through that batch. Counting downstream missed them, so a
617
+ // Claude turn that ran a dozen tools and produced no extractable closing text counted zero.
618
+ this.options.adapter.onSessionEvent?.((event) => { this.emptyTurns.noteOutput(event); this.areaMemory.noteToolPaths([event]); if (event.type === "prompt") this.nativeSelfResume.notePrompt(event.body, event.occurredAt); void this.sessionMirror.publishProviderSessionEvent(event).catch((error) => this.reportBackgroundRejection("provider session event publish", "session.publish_failed", "Provider session event not mirrored", error)); });
619
+ this.options.adapter.onSessionEvents?.((events) => { this.areaMemory.noteToolPaths(events); for (const event of events) { this.emptyTurns.noteOutput(event); if (event.type === "prompt") this.nativeSelfResume.notePrompt(event.body, event.occurredAt); } void this.sessionMirror.publishProviderSessionEvents(events).catch((error) => this.reportBackgroundRejection("provider session event batch publish", "session.publish_failed", "Provider session events not mirrored", error)); });
620
+ // #2389 — output a provider can observe but cannot mirror (an OpenCode tool part, whose wire
621
+ // shape we deliberately do not guess at). Evidence only; nothing is published.
622
+ this.options.adapter.onTurnActivity?.(() => this.emptyTurns.noteOutput());
623
+ }
624
+
625
+ // The adapter's status seam, split out of run() (#1617) so the guarded restart call below is
626
+ // reachable without booting a whole runner — the crash it guards against is exactly the one a
627
+ // test has to be able to drive.
628
+ /** Pane evidence can change only the displayed startup dialog and timeline, never work claims. */
629
+ private recordPaneObservation(observation: PaneObservation): void {
630
+ const detail = observation.message ? `: ${observation.message}` : "";
631
+ this.sessionLog(`pane observation ${observation.eventType}/${observation.detectorId}${detail}`);
632
+ const displayGate = observation.displayGate;
633
+ if (this.options.approvalMode !== "open" || !displayGate) return;
634
+ if (displayGate.phase === "clear") {
635
+ if (this.startupPaneDialog) { this.startupPaneDialog = undefined; this.publishStatus(); }
636
+ return;
637
+ }
638
+ if (this.startupPaneDialog) return;
639
+ this.startupPaneDialog = displayGate.providerState;
640
+ this.publishRunnerTimelineEvent({ status: "provider_blocked", timestamp: observation.observedAt,
641
+ title: displayGate.timelineTitle, body: this.startupPaneDialog.message });
642
+ }
643
+
644
+ private handleAdapterStatusChange(status: ProviderStatusUpdate): void {
645
+ // #2505 — stop() publishes the runner's terminal edge directly. Once it has started, an
646
+ // adapter callback can only be stale: an input already inside ProviderInputQueue may finish
647
+ // after stopStatusWatchers() and arm a fresh provider watcher, while an older watcher may
648
+ // still be returning from an async probe. Neither is allowed to reopen terminal status.
649
+ if (this.lifecycle.stopped || this.lifecycle.restartInProgress || this.lifecycle.restartPending) return;
650
+ this.contextResets.observeStatus(status);
651
+ const semanticStatus = typeof status === "string" ? status : status.status;
652
+ if ((semanticStatus === "busy" || semanticStatus === "offline" || semanticStatus === "error") && this.startupPaneDialog) this.startupPaneDialog = undefined;
653
+ if (this.shouldRestartUnexpectedProviderExit(semanticStatus)) {
654
+ // (#1617) — restartUnexpectedProviderExit is an outer try/finally with NO catch wrapped
655
+ // around awaited work that can still throw (recoverNativeSelfResumeExit, the undetermined-
656
+ // exit confirmation read, the timeline/status publishes). A reject here used to kill the
657
+ // runner outright: the crash path for the exact situation this method exists to recover
658
+ // from, on a runner nothing respawns (#1618).
659
+ void this.restartUnexpectedProviderExit(semanticStatus).catch((error) => this.reportBackgroundRejection("unexpected provider exit restart", "provider.restart_failed", "Provider restart after unexpected exit failed", error));
660
+ return;
661
+ }
662
+ this.setProviderStatus(status);
663
+ if (runnerShouldResolveProviderExit(semanticStatus, this.lifecycle.exitCommandInProgress)) {
664
+ const exitCode = semanticStatus === "offline" ? 0 : 1;
665
+ // #2443 — a provider-reported terminal failure (model retired/404, auth error, exhausted
666
+ // retries, ...) used to resolve straight to `onProviderExit` with nothing surfaced beyond
667
+ // the mirror log: the orchestrator's own exit diagnostics tail the process's stdout/stderr
668
+ // capture, which held only the provider's OWN startup output, not this. Log it here —
669
+ // generically, off whatever the adapter's failTurn-equivalent reported via `terminalFailure`,
670
+ // never provider-specific — and hand the reason to the caller so it can be written to
671
+ // stdout/stderr before the process actually exits (see index.ts's onProviderExit).
672
+ const reason = exitCode !== 0 && this.terminalFailure ? `${this.terminalFailure.reason}: ${this.terminalFailure.message}` : undefined;
673
+ if (reason) logger.error("runner", `exiting after a terminal provider failure: ${reason}`);
674
+ this.options.onProviderExit?.(exitCode, reason);
675
+ }
676
+ }
677
+
678
+ async run(): Promise<void> {
679
+ this.control = startControlServer({
680
+ onStatus: (status) => { if (!this.options.adapter.reportsContextResets) this.contextResets.observeStatus(status); this.setProviderStatus(status); },
681
+ onTerminalAttachSpec: () => this.terminalAttachSpec(),
682
+ // #2459 — the current non-secret attachment generation, resolved from the live process
683
+ // the same way terminalAttachSpec reads it. Only wired when the adapter declares one;
684
+ // absent ⇒ the endpoint reports no generation and the orchestrator skips the check.
685
+ onTerminalGeneration: () => Promise.resolve(this.process ? this.options.adapter.terminalAttachGeneration?.(this.process) : undefined),
686
+ // Hot-path-safe: answered instantly from the local snapshot, never a server
687
+ // round-trip. The snapshot is kept warm by the background refresh below (#196).
688
+ onReplyObligations: () => this.obligationCache.freshForNag(),
689
+ onSubagentUsage: this.options.adapter.captureSubagentTokenUsage?.bind(this.options.adapter),
690
+ onSessionTurn: (input) => this.turnReport.publishSessionTurn(input), onPermissionResolved: (input) => this.sessionMirror.publishSessionEvent({ from: this.agentId, to: "user", body: input.body, occurredAt: input.occurredAt, session: { type: "prompt-resolution", origin: "provider", label: input.decisionLabel, ...(this.currentTurnId ? { turnId: this.currentTurnId } : {}), stepId: `prompt-resolution:${input.approvalId}`, promptResolution: input } }),
691
+ onPermissionAbandoned: (input) => this.publishPermissionAbandonedNotice(input),
692
+ onAnswerInject: (input) => this.promptCommands.injectAnswerPrompt(input), onQuestionDecision: (input) => fileQuestionDecision({ http: this.http, profile: this.options.profile, log: (message) => this.sessionLog(message) }, input),
693
+ onUserPrompt: (input) => this.turnReport.handleUserPrompt(input), ...this.areaMemory.controlHooks(this.options.adapter, (input) => this.options.adapter.handleHookCheck?.(input, this.turnReport.sessionTurnContext())),
694
+ onSessionBoundary: (input) => this.turnReport.handleSessionBoundary(input),
695
+ onSessionStart: (input) => this.recoverLateClear(input),
696
+ onHookFatal: (report) => this.reportHookFatal(report),
697
+ instanceId: this.options.instanceId,
698
+ pendingPermissionStore: this.pendingPermissionStore,
699
+ permissionPromptHandler: this.options.adapter.permissionPromptHandler, autoApprovePermissions: this.options.approvalMode === "open",
700
+ providerSourceId: this.options.adapter.statusSourceId ?? this.options.provider,
701
+ stopObligationPath: this.options.adapter.stopObligationPath,
702
+ monitorUnavailableMessage: providerMonitorUnavailableMessage(this.options.provider),
703
+ });
704
+ this.startMcpProxy();
705
+ this.registration.writeRunnerInfoFile();
706
+ this.options.adapter.onStatusChange((status) => this.handleAdapterStatusChange(status));
707
+ this.options.adapter.onPaneObservation?.((observation) => this.recordPaneObservation(observation));
708
+ this.registerProviderSessionRoutes();
709
+ this.bus.on("message.new", (message) => {
710
+ // A delivered message may create a new reply obligation — warm the snapshot so the
711
+ // next turn-end sees it without a hot-path server read.
712
+ const msg = message as Message;
713
+ if (runnerMessageShouldDirtyObligations(msg, this.agentId)) this.obligationCache.markDirty();
714
+ this.enqueueMessage(msg);
715
+ });
716
+ this.bus.on("command", (type, params, commandId, command) => {
717
+ this.commandDispatch.dispatchCommand(type, params, commandId, command);
718
+ });
719
+ this.bus.on("registered", () => this.handleBusRegistered());
720
+ this.bus.on("event", (eventType, data) => {
721
+ if (eventType.startsWith("command.") && data && typeof data === "object" && "command" in data) this.nativeSelfResume.settleCommand(data.command as Record<string, unknown>);
722
+ if (isEffortNotice(eventType, data)) this.effort.trigger();
723
+ });
724
+ this.bus.on("error", (code, message) => this.handleBusError(String(code), String(message)));
725
+ await registerWithinDeadline(this.bus, registrationTimeoutMsFromEnv(), this.options.relayUrl, (reason) => logger.fatal("register", reason));
726
+ await this.bus.subscribe(["command.*"]);
727
+ this.registeredAt = Date.now();
728
+ this.registration.writeRunnerInfoFile();
729
+ await initializeRunnerTemplates(this.http);
730
+ this.obligationCache.start();
731
+ this.outbox.start();
732
+ this.sessionMirror.start();
733
+ this.ensureScratch();
734
+ void sweepRunnerStaleScratch({ listAgents: () => this.http.listAgents(), agentId: this.agentId, cwd: this.options.cwd, fallbackBaseDir: orchestratorBaseDirFromEnv(), log: (message) => this.logRunnerDiagnostic(message) });
735
+ this.scheduleAttachmentCacheSweep();
736
+ this.adoptProcess(await this.spawnProvider());
737
+ this.registration.writeRunnerInfoFile();
738
+ await this.promptCommands.startLaunchSeededTurn();
739
+ this.publishStatus();
740
+ await this.promptCommands.deliverInitialPrompt();
741
+ await this.bootstrapUnreadMessages();
742
+ this.taskClaims.start();
743
+ this.startHttpLiveness();
744
+ this.testRunDedupSweep.start();
745
+ this.gateRunLeaseProbe.start();
746
+ this.credentials.scheduleRuntimeTokenRenewal();
747
+ this.scheduleDrain();
748
+ }
749
+
750
+ markShuttingDown(): void { this.lifecycle.markShuttingDown(); }
751
+
752
+ async stop(): Promise<void> {
753
+ const alreadyStopped = this.lifecycle.beginStop();
754
+ this.lifecycleMilestones.onStop("teardown-started", alreadyStopped);
755
+ this.lifecycleMilestones.onStop("background-stopped");
756
+ if (this.process && !alreadyStopped) await this.lifecycleMilestones.onStop("provider-shut-down");
757
+ this.lifecycleMilestones.onStop("offline-published", alreadyStopped);
758
+ this.lifecycleMilestones.onStop("timers-stopped");
759
+ await this.lifecycleMilestones.onStop("closed");
760
+ }
761
+
762
+ // Start the local MCP proxy the agent connects to (Stage 2, #215). Forwards tool calls to the
763
+ // relay with the runner's LIVE token, buffers bufferable writes durably during a relay outage,
764
+ // and narrows the tool list to this agent's workspace context. Best-effort: if it can't bind,
765
+ // we fall back to a direct relay MCP connection (the agent env still works, no resilience).
766
+ // #1450 — the on-demand relay set to hide from a lean worker's DIRECT relay client (so only the
767
+ // eager hot-path set boots), or undefined for any agent that keeps its full relay surface. The
768
+ // gate MIRRORS the callmux relay-downstream gate (launch-assembly) EXACTLY — worker profile,
769
+ // runner-managed deferral, sharedMcp on (the metaOnly callmux that fronts these exists), relay mcp
770
+ // on, no web tool off (#3024) — so the direct client is narrowed only when callmux fronts the set.
771
+ // ("proxy active" is implicit: only a constructed proxy consults this, and the relay downstream is
772
+ // added only when the proxy is active — the two are on/off together, never a half state.)
773
+ private startMcpProxy(): void {
774
+ if (!this.mcpProxyEnabled) return;
775
+ const onDemandTools = runnerMcpHiddenTools(this.options.provider, this.options.agentProfile);
776
+ const started = startRunnerMcpProxy({
777
+ webToolLog: runnerWebToolLog(Boolean(this.options.agentProfile?.sharedMcp), resolveSharedMcpUrl(),
778
+ () => ({ agentId: this.agentId, provider: this.options.provider, model: this.options.model ?? this.latestProbeMetrics()?.model ?? null, effort: this.effortRuntime.effort ?? this.latestProbeMetrics()?.effort ?? null }),
779
+ call => this.http.request("POST", "/api/web-tool-calls", call), process.env, runnerWebSearch(this.http)),
780
+ redactToolResponse: this.options.adapter.mcpToolResponseRedactor?.(this.options.cwd),
781
+ relayMcpEndpoint: relayMcpEndpoint(this.options.relayUrl, this.context.mcpToolGeneration),
782
+ getToken: () => this.credentials.current(),
783
+ authSecret: this.mcpProxySecret,
784
+ enqueueBuffered: call => this.outbox.enqueue({ kind: "mcp-tool-call", payload: { tool: call.tool, arguments: call.arguments }, idempotencyKey: call.idempotencyKey }),
785
+ initialContext: { isolatedWorktree: this.ownsIsolatedWorktree(), workspaceDescriptor: isolatedWorkspaceDescriptor(this.options.workspace, this.ownsIsolatedWorktree()) },
786
+ ...(onDemandTools ? { onDemandTools } : {}),
787
+ });
788
+ this.proxy = started.proxy;
789
+ this.mcpProxyEndpoint = started.endpoint;
790
+ }
791
+
792
+ private handleBusRegistered(): void {
793
+ void this.nativeSelfResume.reconcile((id) => this.http.request("GET", `/api/commands/${encodeURIComponent(id)}`)).catch((error) => this.sessionLog(`compact settlement reconcile: ${errMessage(error)}`));
794
+ // #2973 — a re-register follows a relay restart (a deploy): re-list so changed tools reach the agent.
795
+ void this.proxy?.refreshTools().catch(() => {}); this.effort.trigger();
796
+ }
797
+
798
+ private ownsIsolatedWorktree(): boolean {
799
+ const mode = this.options.workspace?.requestedMode ?? this.options.workspace?.mode ?? workspaceModeFromEnv();
800
+ return mode === "isolated";
801
+ }
802
+
803
+ // #2966 — publish the new incarnation's capabilities (effortSwitch) before its first turn: the
804
+ // heartbeat spawnProvider resumes still describes the previous process, and the next is ~30s away.
805
+ private adoptProcess(process: ManagedProcess): void {
806
+ this.process = process;
807
+ this.processStartedAt = Date.now();
808
+ this.bus.flushHeartbeat(); this.effort.trigger();
809
+ }
810
+
811
+ private async spawnProvider(opts: RestartProviderOptions = {}): Promise<ManagedProcess> {
812
+ this.providerSessionId = crypto.randomUUID();
813
+ // A provider spawn is the incarnation boundary. Old teardown captures and source fences must
814
+ // not leak into the replacement, while the bus transport itself remains reusable.
815
+ this.turnReport.clearPreDestroyRun();
816
+ this.lifecycle.reopenProviderStatus();
817
+ this.lastTranscriptPath = undefined;
818
+ // #2550 — a new provider process is a new context generation: its (empty) context may
819
+ // legitimately receive the same charter/memory bytes again, so the dedupe table resets.
820
+ this.resetContextGeneration();
821
+ const providerEnv = selectProviderConfigEnv(this.options.providerConfig.env, hostSourcesFor(this.options).settings);
822
+ const env = writeChildEnv({}, {
823
+ ...resolveEnvLayers([process.env, providerEnv]),
824
+ ...(this.context.mcpToolGeneration === "humwick" ? { AGENT_RELAY_MCP_TOOLS: this.context.mcpToolGeneration } : {}),
825
+ AGENT_RELAY_RUNNER_PORT: String(this.control!.port),
826
+ AGENT_RELAY_RUNNER_ID: this.options.runnerId,
827
+ AGENT_RELAY_PROVIDER_SESSION_ID: this.providerSessionId,
828
+ AGENT_RELAY_ID: this.agentId,
829
+ // The repo-name project id the runner uses for insight observations. Exposed so
830
+ // agent-side signals (e.g. /introspect) tag the SAME project and aggregate together,
831
+ // instead of the agent's worktree cwd splitting one repo into many "projects".
832
+ AGENT_RELAY_PROJECT: this.insightProject(),
833
+ ...(this.scratch ? { AGENT_RELAY_SCRATCH_DIR: this.scratch.tmpDir } : {}),
834
+ ...await agentShellLaunchEnv(this.agentId, providerEnv),
835
+ AGENT_RELAY_URL: this.options.relayUrl,
836
+ AGENT_RELAY_APPROVAL: this.options.approvalMode,
837
+ ...(this.credentials.current() ? { AGENT_RELAY_TOKEN: this.credentials.current()! } : {}),
838
+ // Dedicated, un-clobberable credential for the injected MCP endpoint. A rig's
839
+ // settings.json `env.AGENT_RELAY_TOKEN` would override the scoped token above at
840
+ // MCP-parse time → server-actor auth, no identity (#233). The MCP config references
841
+ // ${AGENT_RELAY_SESSION_TOKEN}, which rigs never set. See runner/src/relay-mcp.ts.
842
+ //
843
+ // Stage 2 (#215): when the proxy is active the agent connects to the LOCAL proxy, so this
844
+ // holds the per-session PROXY SECRET (not the relay token). The proxy injects the live
845
+ // relay token itself — the agent never holds it, and token rotation is invisible. With the
846
+ // proxy disabled this stays the scoped relay token (Stage-1 direct connection).
847
+ ...(this.proxy
848
+ ? { [RELAY_MCP_TOKEN_ENV]: this.mcpProxySecret }
849
+ : (this.credentials.current() ? { [RELAY_MCP_TOKEN_ENV]: this.credentials.current()! } : {})),
850
+ ...(this.credentials.jti ? { AGENT_RELAY_TOKEN_JTI: this.credentials.jti } : {}),
851
+ ...(this.credentials.profileId ? { AGENT_RELAY_TOKEN_PROFILE: this.credentials.profileId } : {}),
852
+ ...(this.credentials.expiresAt ? { AGENT_RELAY_TOKEN_EXPIRES_AT: String(this.credentials.expiresAt) } : {}),
853
+ });
854
+ const config = buildRunnerSpawnConfig({ ...this.options, effort: this.effortRuntime.effort }, {
855
+ getRelayToken: () => this.credentials.current(),
856
+ agentId: this.agentId,
857
+ env,
858
+ controlPort: this.control!.port,
859
+ ...(opts.resumeId ? { resumeId: opts.resumeId } : {}),
860
+ ...(opts.suppressPrompt ? { suppressPrompt: true } : {}),
861
+ ...(opts.systemPromptAppend ? { systemPromptAppend: opts.systemPromptAppend } : {}),
862
+ // Stage 2 (#215): the MCP endpoint the agent's client should target — the runner-local
863
+ // proxy when active, undefined when disabled (adapters fall back to the direct relay URL).
864
+ ...(this.mcpProxyEndpoint ? { relayMcpEndpoint: this.mcpProxyEndpoint } : {}),
865
+ // #1450: the proxy secret, exposed to the assembler ONLY when the proxy is active, so the
866
+ // Codex per-agent metaOnly callmux can reach the relay endpoint on-demand THROUGH the proxy
867
+ // (live-token-injected — never the relay token on disk, #215).
868
+ ...(this.mcpProxyEndpoint ? { mcpProxySecret: this.mcpProxySecret } : {}),
869
+ // #672: the shared host-listener URL the `callmux bridge` connects to (env → default).
870
+ // Always resolved; the assembler only emits the bridge when `agentProfile.sharedMcp` is on.
871
+ sharedMcpUrl: this.proxy?.webToolsUrl ?? resolveSharedMcpUrl(),
872
+ monitor: {
873
+ deliver: (messages) => this.control!.deliverToMonitor(messages),
874
+ },
875
+ });
876
+ const launchSeededPrompt = this.promptCommands.noteLaunchSeededPrompt(opts);
877
+ // Provider-owned hydration refreshes every spawn/restart and keeps provider-specific I/O out
878
+ // of the runner core. Assembly sees only the resulting snapshot.
879
+ const { hydratedConfig, managedProcess } = await (async () => {
880
+ try {
881
+ return await spawnWithServiceAccess(this.http, config, this.options.adapter, this.providerSessionId, Boolean(this.options.profile && this.options.spawnRequestId));
882
+ } finally {
883
+ // A failed replacement remains observable through the runner's status projection. Do not
884
+ // also strand the reusable bus with heartbeats, quota/context metadata, and its ack
885
+ // watchdog suspended for the rest of this runner process.
886
+ if (!this.lifecycle.stopped && !this.lifecycle.shuttingDown) this.bus.resumeHeartbeat();
887
+ }
888
+ })();
889
+ if (launchSeededPrompt) managedProcess.meta = { ...managedProcess.meta, launchSeededTurn: true };
890
+ const injectionEvents = runnerLaunchInjectionSessionEvents({ carried: opts.relayInjectionEvents ?? this.options.relayInjectionEvents, provider: this.options.provider, config: hydratedConfig, providerConfig: this.options.providerConfig, agentId: this.agentId, providerSessionId: this.providerSessionId });
891
+ for (const event of injectionEvents) {
892
+ this.sessionMirror.publishSessionEvent(event);
893
+ }
894
+ return managedProcess;
895
+ }
896
+
897
+ private async terminalAttachSpec(): Promise<TerminalAttachSpec> {
898
+ if (!this.process) throw new Error("provider process is unavailable");
899
+ if (!this.options.adapter.terminalAttachSpec) throw new Error("provider does not support terminal attach");
900
+ return this.options.adapter.terminalAttachSpec(this.process);
901
+ }
902
+
903
+ private enqueueMessage(message: Message): void {
904
+ if (!this.matchesMessage(message)) return;
905
+ // #2539 live-fire — a message this runner is ALREADY delivering (in this snapshot's drain,
906
+ // so a stale server-list copy racing the offer) or has already SETTLED (acked + marked read)
907
+ // is DONE: neither may re-enter the queue and be re-delivered (as a soft-steer into the very
908
+ // turn that consumed it, or at turn-end). The in-flight check clears in the drain's finally,
909
+ // so every legitimate requeue path (unacked, claim failure) re-enqueues fine.
910
+ if (this.drainingMessageIds?.has(message.id) || this.heldSubmit.messages.has(message.id)) return;
911
+ if (this.settledMessageIds.has(message.id)) return;
912
+ // #2539 Phase B — an id handed to the soft-steer lane (persisted, awaiting consumption) is in
913
+ // the adapter's ledger custody: a stale snapshot re-enqueue would double-POST the same body
914
+ // into the live turn. Same class as the settled guard above; the reoffer seam is the only way
915
+ // an awaiting id returns to this queue.
916
+ if (this.awaitingMidTurnDelivery.has(message.id)) return;
917
+ // #2619 — a provider-refused id is parked until the provider takes a turn again; a poll
918
+ // snapshot re-enqueueing it would re-POST it into the wall.
919
+ if (this.hold.isParked(message.id)) return;
920
+ // #2619 — the relay's push of a refused prompt's own inbox row: it binds the pending park (the
921
+ // push can beat the self-post's response), or — its caller already failed — is settled.
922
+ const heldPromptKey = message.payload?.heldPromptKey;
923
+ if (typeof heldPromptKey === "string" && this.hold.isPending(heldPromptKey)) {
924
+ this.refusedPrompts.confirm(heldPromptKey);
925
+ this.releaseParked(this.hold.bind(heldPromptKey, message));
926
+ return;
927
+ }
928
+ if (typeof heldPromptKey === "string" && this.refusedPrompts.isUnconfirmed(heldPromptKey)) {
929
+ void this.settleSilently(message).catch(() => {}); // markRead failures are already swallowed inside
930
+ return;
931
+ }
932
+ // #2029 — the wake:false / kind:session suppression is now decided ONCE server-side (decideDelivery)
933
+ // and the server no longer emits a turn frame for it, so no client-side `payload.wake` / `kind`
934
+ // policy branch survives here. What remains is a belt-and-braces structural check, shared with the
935
+ // SDK provider tail (frameSuppressedFromTurn) so the two clients cannot drift: obey a non-`turn`
936
+ // delivery stamp if one ever rides in on a frame (e.g. a re-injected obligation, or a stale server).
937
+ if (frameSuppressedFromTurn(message)) return;
938
+ const pending = this.pendingMessages.get(message.id);
939
+ this.pendingMessages.set(message.id, message);
940
+ if (this.deliverySubmitHold) this.tryRecoverProviderSubmitHold(false);
941
+ this.scheduleDrain();
942
+ }
943
+
944
+ private async bootstrapUnreadMessages(): Promise<void> {
945
+ try {
946
+ const messages = await this.http.pollMessages({ for: this.agentId, unread: true, limit: 100, turnDelivery: true });
947
+ for (const message of messages) this.enqueueMessage(message);
948
+ } catch (error) {
949
+ logger.error("runner", `inbox bootstrap failed: ${error}`);
950
+ }
951
+ }
952
+
953
+ private scheduleDrain(delayMs = 0): void {
954
+ if (this.lifecycle.stopped || this.drainTimer || this.deliverySubmitHold || this.submitResendInFlight) return;
955
+ this.drainTimer = setTimeout(() => {
956
+ this.drainTimer = undefined;
957
+ void this.drainMessages();
958
+ }, delayMs);
959
+ }
960
+
961
+ private holdFailedProviderSubmit(detail: string, held: { messages?: Message[]; resend?: () => Promise<unknown> } = {}): void {
962
+ for (const message of held.messages ?? []) this.heldSubmit.messages.set(message.id, message);
963
+ // The FIRST unconfirmed prompt is the one the operator resends; a later refusal must not replace it.
964
+ this.heldSubmit.resend ??= held.resend;
965
+ this.deliverySubmitHold = {
966
+ state: "blocked", reason: PROVIDER_SUBMIT_UNCONFIRMED, label: "Provider prompt needs inspection",
967
+ message: detail, source: "provider-input-submit", terminal: true,
968
+ recommendedAction: "Inspect the provider input. If the prompt was not accepted, release the hold and resend it; if it was, release the hold without resending.", enteredAt: Date.now(),
969
+ };
970
+ logger.error("delivery", detail);
971
+ this.startSubmitRecoveryPoll();
972
+ this.publishRunnerTimelineEvent({ status: "provider_blocked", timestamp: Date.now(), title: "Provider prompt submission unconfirmed", body: detail });
973
+ this.publishStatus();
974
+ }
975
+
976
+ /** #2664 — the operator's escape hatch: clear the hold and the adapter fence, optionally resend what was held. */
977
+ private async releaseSubmitHold(resend: boolean): Promise<Record<string, unknown>> {
978
+ const held = this.heldSubmit;
979
+ const released = this.deliverySubmitHold !== undefined;
980
+ this.heldSubmit = emptyHeldSubmit();
981
+ this.deliverySubmitHold = undefined;
982
+ this.stopSubmitRecoveryPoll();
983
+ this.submitFenceRaised = false;
984
+ if (this.process) this.options.adapter.releaseSubmitHold?.(this.process);
985
+ this.sessionLog(`provider submit hold released by the operator (${resend ? "resending" : "not resending"} ${held.messages.size} message(s)${held.resend ? " and a prompt" : ""})`);
986
+ this.publishStatus();
987
+ if (resend) for (const message of held.messages.values()) { this.submitDeliveryAttempts.delete(message.id); this.pendingMessages.set(message.id, message); }
988
+ this.submitResendInFlight = resend && held.resend !== undefined;
989
+ try {
990
+ if (resend) await held.resend?.();
991
+ } finally {
992
+ // Prompts parked behind the hold are inbox rows; they drain after the resend, in id order. A resend
993
+ // that fails again re-enters the hold, so they stay parked behind it.
994
+ this.submitResendInFlight = false;
995
+ if (this.pendingMessages.size > 0) this.scheduleDrain();
996
+ }
997
+ return { released, resentMessages: resend ? held.messages.size : 0, resentPrompt: resend && held.resend !== undefined };
998
+ }
999
+
1000
+ /**
1001
+ * #2664 — a dashboard prompt that meets the submit hold (at entry, or in its FIFO slot behind the
1002
+ * submit that raised it) parks as a durable self-addressed inbox row: it drains in order once the
1003
+ * hold clears and survives a runner restart like any unread message. Not durable: the command fails.
1004
+ */
1005
+ private async parkPromptBehindSubmitHold(params: Record<string, unknown>, key: string, turn: ProviderInputTicket): Promise<Record<string, unknown>> {
1006
+ return this.parkPosts.run(() => this.postParkedPrompt(params, key), turn);
1007
+ }
1008
+
1009
+ private async postParkedPrompt(params: Record<string, unknown>, key: string): Promise<Record<string, unknown>> {
1010
+ const messageId = typeof params.messageId === "number" ? params.messageId : undefined;
1011
+ const payload = { ...(Array.isArray(params.attachments) ? { attachments: params.attachments } : {}), ...(messageId ? { submitHoldPromptOf: messageId } : {}), [HELD_FOR_PAYLOAD_KEY]: promptOrigin(params) };
1012
+ const posted = await retryParkPost("submit-hold prompt inbox post", () => this.http.sendMessage({ from: this.agentId, to: this.agentId, kind: "chat", subject: "Dashboard prompt held behind an unconfirmed provider submit", body: params.body as string, payload, idempotencyKey: key }));
1013
+ if (!posted) throw new Error("dashboard prompt met the provider submit hold and could not be parked durably; not delivered");
1014
+ this.sessionLog(`dashboard prompt parked behind the provider submit hold as inbox message ${posted.id}`);
1015
+ return { injected: false, parked: true, messageId, parkedMessageId: posted.id };
1016
+ }
1017
+
1018
+ /** Bounded: stops when the hold clears, the runner stops, or the window ends; release/recovery/respawn/teardown stop it too. */
1019
+ private startSubmitRecoveryPoll(): void {
1020
+ if (this.submitRecoveryTimer) return;
1021
+ const until = Date.now() + this.submitRecoveryPollWindowMs;
1022
+ this.submitRecoveryTimer = setInterval(() => {
1023
+ if (!this.deliverySubmitHold || this.lifecycle.stopped || Date.now() >= until) return this.stopSubmitRecoveryPoll();
1024
+ this.tryRecoverProviderSubmitHold(false);
1025
+ }, this.submitRecoveryPollMs);
1026
+ }
1027
+
1028
+ private stopSubmitRecoveryPoll(): void {
1029
+ if (this.submitRecoveryTimer) clearInterval(this.submitRecoveryTimer);
1030
+ this.submitRecoveryTimer = undefined;
1031
+ }
1032
+
1033
+ private tryRecoverProviderSubmitHold(turnStarted: boolean): void {
1034
+ const hold = this.deliverySubmitHold;
1035
+ const process = this.process;
1036
+ const probe = this.options.adapter.confirmSubmitRecovery;
1037
+ if (!hold || !process || !probe) return;
1038
+ this.submitRecoveryTurnStarted ||= turnStarted;
1039
+ if (this.submitRecoveryInFlight) { this.submitRecoveryRequested = true; return; }
1040
+ this.submitRecoveryInFlight = true;
1041
+ const started = this.submitRecoveryTurnStarted;
1042
+ this.submitRecoveryTurnStarted = false;
1043
+ void probe.call(this.options.adapter, process, started).then((recovered) => {
1044
+ if (!recovered || this.deliverySubmitHold !== hold || this.process !== process) return;
1045
+ this.deliverySubmitHold = undefined;
1046
+ this.stopSubmitRecoveryPoll();
1047
+ this.submitFenceRaised = false;
1048
+ this.heldSubmit = emptyHeldSubmit();
1049
+ this.submitRecoveryTurnStarted = false;
1050
+ this.publishStatus();
1051
+ if (this.pendingMessages.size > 0) this.scheduleDrain();
1052
+ }).catch((error) => logger.warn("delivery", `provider submit recovery probe failed: ${error}`)).finally(() => {
1053
+ this.submitRecoveryInFlight = false;
1054
+ const requested = this.submitRecoveryRequested || this.submitRecoveryTurnStarted;
1055
+ this.submitRecoveryRequested = false;
1056
+ if (requested && this.deliverySubmitHold === hold) this.tryRecoverProviderSubmitHold(false);
1057
+ });
1058
+ }
1059
+
1060
+ // ===================================================================== // #2539 Phase A — the ONE provider-input offer/settle seam.
1061
+ //
1062
+ // Everything that puts content in front of the provider's single input channel OFFERS through
1063
+ // {@link offerProviderInput} (arm the empty-turn watch BEFORE the delivery is awaited — see
1064
+ // armInput — serialize through the FIFO `providerInput` queue, roll the arm back when the
1065
+ // offer was rejected), and an inbox batch SETTLES through {@link settleMessageDelivery} — the
1066
+ // single place that records delivery, marks read, releases claims and requeues the un-acked.
1067
+ // drainMessages (persisted messages: claim → attach → offer → settle) and injectPrompt
1068
+ // (dashboard continue/chat: render → offer) are the two callers; the tails exist once, so the
1069
+ // soft-steer partial-ack settlement is written in exactly one place.
1070
+ // =====================================================================
1071
+ /**
1072
+ * Offer one payload to the provider: arm the empty-turn watch for work-bearing input, run
1073
+ * delivery through the FIFO input queue, and disarm the arm if the offer throws. On success
1074
+ * the arm stays because the delivery obliges the agent to produce work (#2389). A notice-only
1075
+ * batch is delivered without an arm because it owes no answer.
1076
+ */
1077
+ private async offerProviderInput<T>(run: (inputObserver?: ProviderInputObserver) => Promise<T>, offeredNothing?: (result: T) => boolean, armEmptyTurn = true): Promise<T> {
1078
+ const arm = armEmptyTurn ? this.emptyTurns.armInput(Date.now(), "ladder", true) : undefined;
1079
+ try {
1080
+ const result = await run(arm ? this.emptyTurns.inputObserver(arm) : undefined);
1081
+ if (arm && arm.evidence?.offeredAt === undefined) this.emptyTurns.inputObserver(arm).offered(Date.now(), false);
1082
+ if (arm && offeredNothing?.(result)) this.emptyTurns.disarmInput(arm);
1083
+ return result;
1084
+ } catch (error) {
1085
+ if (arm) this.emptyTurns.disarmInput(arm);
1086
+ throw error;
1087
+ }
1088
+ }
1089
+
1090
+ // #2619 — the ONLY two places runner code hands input to the provider (runner-input-seam.guard.test.ts
1091
+ // enforces it). A provider may REFUSE input under a quota wall or throttle, so `onHeld` is a required
1092
+ // parameter: no caller can drop a refusal and report the input delivered. Both run in the caller's
1093
+ // FIFO slot (`ticket`), so the refusal outcome is judged in the serialized input order.
1094
+
1095
+ /** Offer a prompt; `onHeld` runs INSIDE the slot, synchronously, when the provider refused it —
1096
+ * a refusal is parked before any accepted offer can end the episode. */
1097
+ private async offerPromptToProvider(prompt: string, options: PromptOfferOptions, onHeld: () => void, ticket?: ProviderInputTicket, onSubmitHeld?: () => void): Promise<"accepted" | "held" | "submit-held"> {
1098
+ return this.providerInput.run(async () => {
1099
+ await this.lateClearRecovery.ready();
1100
+ if (this.lifecycle.stopped) throw new Error("runner stopped before provider prompt offer");
1101
+ // #2664 — decided in the slot: a prompt queued before the preceding submit raised the hold never reaches the fence.
1102
+ if (onSubmitHeld && (this.deliverySubmitHold || this.submitFenceRaised)) {
1103
+ onSubmitHeld();
1104
+ return "submit-held" as const;
1105
+ }
1106
+ if (!this.currentTurnId) this.turnReport.armTurnOpener([]); // #2840 — a prompt-opened turn answers no relay message
1107
+ if (await this.noteSubmitFence(() => this.options.adapter.deliverInitialPrompt!(this.process!, prompt, options)) !== "held") return "accepted" as const;
1108
+ onHeld();
1109
+ return "held" as const;
1110
+ }, ticket);
1111
+ }
1112
+
1113
+ private async noteSubmitFence<T>(offer: () => Promise<T>): Promise<T> {
1114
+ try {
1115
+ const result = await offer();
1116
+ this.submitFenceRaised = false; // the provider took input, so no submit fence stands
1117
+ return result;
1118
+ } catch (error) {
1119
+ if (error instanceof ProviderSubmitFailure && !error.retrySafe) this.submitFenceRaised = true;
1120
+ throw error;
1121
+ }
1122
+ }
1123
+
1124
+ /** Offer a prompt; a refusal parks it and then makes it durable ("not-durable": the caller fails). */
1125
+ private async offerPromptOrPark(prompt: string, options: PromptOfferOptions, ticket?: ProviderInputTicket, onSubmitHeld?: () => void, heldFor?: string): Promise<"accepted" | "parked" | "not-durable" | "submit-held"> {
1126
+ let key = "";
1127
+ const payload = { ...(options.imagePaths?.length ? { [HELD_PROMPT_IMAGE_PATHS]: options.imagePaths } : {}), ...(heldFor ? { [HELD_FOR_PAYLOAD_KEY]: heldFor } : {}) };
1128
+ const offered = await this.offerPromptToProvider(prompt, options, () => { key = this.parkRefusedPrompt(prompt, payload); }, ticket, onSubmitHeld);
1129
+ if (offered !== "held") return offered;
1130
+ return await this.persistParkedPrompt(key) ? "parked" : "not-durable";
1131
+ }
1132
+
1133
+ /**
1134
+ * Offer messages. `select` runs INSIDE the FIFO slot, immediately before the adapter call, so a
1135
+ * decision taken earlier (a resume wake's continue) is re-validated at the offer instant; an
1136
+ * empty selection offers nothing. `onHeld` gets the ids the provider refused, INSIDE the slot and
1137
+ * synchronously (see offerPromptToProvider).
1138
+ */
1139
+ private async offerMessagesToProvider(select: () => Message[], onHeld: (heldIds: number[], offered: Message[]) => void, options: { ctx?: ProviderDeliverContext; ticket?: ProviderInputTicket; settlement?: Promise<void> } = {}): Promise<{ result: void | number[] | DeliverResult; offered: Message[] }> {
1140
+ let offered: Message[] = [];
1141
+ const result = await this.providerInput.run(async () => {
1142
+ offered = select();
1143
+ this.hold.noteOffered(offered);
1144
+ if (options.settlement && offered.length > 0) this.deliverySettlements.track(options.settlement);
1145
+ // #2840 — armed BEFORE the offer: a synchronous provider starts, or runs, the turn inside deliver.
1146
+ const opening = this.turnReport.openTurnOpener(offered, !this.currentTurnId, options.ctx);
1147
+ const delivered = offered.length > 0 ? await this.noteSubmitFence(() => this.options.adapter.deliver(this.process!, offered, { ...opening.ctx, recordSubmittedPrompt: (text, images) => this.recordInjectedPrompt(text, images) })).catch((error) => { opening.settle(false); throw error; }) : [];
1148
+ const normalized = normalizeDeliverResult(delivered, offered);
1149
+ const acknowledged = new Set(normalized.acknowledgedIds);
1150
+ opening.settle(acknowledged.size > 0);
1151
+ const heldIds = normalized.providerHeldIds.filter((id) => !acknowledged.has(id));
1152
+ if (heldIds.length > 0) onHeld(heldIds, offered);
1153
+ return delivered;
1154
+ }, options.ticket);
1155
+ return { result, offered };
1156
+ }
1157
+
1158
+ /**
1159
+ * Settle an offered message batch (#2539 Phase A): the ONE place that records delivery
1160
+ * attempts, marks acked messages read, releases their claims, and requeues everything the
1161
+ * adapter did not accept (partial acks are the soft-steer contract — the un-acked subset
1162
+ * re-offers toward turn-end). Returns the backoff (ms) to pace the next drain by: 0 for an
1163
+ * immediate reschedule, or the bounded terminal-failure backoff after a failed provider offer.
1164
+ */
1165
+ private async settleMessageDelivery(deliverable: Message[], acknowledged: void | number[] | DeliverResult): Promise<number> {
1166
+ const normalized = normalizeDeliverResult(acknowledged, deliverable);
1167
+ // A refusal read before this settle already re-parked these inputs: they are held, not delivered.
1168
+ const reparked = this.hold.takeReparked(deliverable.map((message) => message.id));
1169
+ if (reparked.size > 0) {
1170
+ normalized.acknowledgedIds = normalized.acknowledgedIds.filter((id) => !reparked.has(id));
1171
+ normalized.providerHeldIds = [...normalized.providerHeldIds, ...reparked];
1172
+ }
1173
+ const acknowledgedIds = new Set(normalized.acknowledgedIds);
1174
+ const terminalFailureIds = new Set(normalized.terminalFailureIds);
1175
+ // #2539 Phase B — soft-steer accepted-but-unconsumed ids: persistence is NOT delivery, so they
1176
+ // are neither acked (no mark read / claim release / settled-id poison) nor un-acked (no requeue —
1177
+ // the provider row exists and must never be re-POSTed). Their settlement completes through
1178
+ // {@link settleSoftSteerConsumption} / {@link reofferSoftSteerIds} when the adapter's ledger
1179
+ // resolves consumption or a session lifecycle loss.
1180
+ const awaitingConsumptionIds = new Set(normalized.awaitingConsumptionIds);
1181
+ // Synchronously, before any await: inputs the provider did not accept leave the live-turn record, accepted ones leave the park.
1182
+ this.hold.forget(deliverable.filter((message) => !acknowledgedIds.has(message.id) || awaitingConsumptionIds.has(message.id)).map((message) => message.id));
1183
+ for (const message of deliverable) if (acknowledgedIds.has(message.id) && !awaitingConsumptionIds.has(message.id)) this.hold.unpark(message.id);
1184
+ let terminalFailureBackoffMs = 0;
1185
+ for (const message of deliverable.filter((item) => awaitingConsumptionIds.has(item.id))) {
1186
+ await this.http.recordMessageDeliveryAttempt(message.id, { agentId: this.agentId, status: "delivered" }).catch(() => undefined);
1187
+ // r3 LOW-1 — settle-before-stash orphan race: the SSE consumption door can fire inside the
1188
+ // awaited recordMessageDeliveryAttempt above, and settleSoftSteerConsumption has then ALREADY
1189
+ // marked read, released the claim, and poisoned the intake guard. Stashing now would insert an
1190
+ // ORPHAN awaiting-map entry for a settled id — a later session swap would reoffer it (duplicate
1191
+ // delivery into the new session) and the orphan would occupy the awaiting map permanently.
1192
+ // The id is settled: nothing left to do.
1193
+ if (this.settledMessageIds.has(message.id)) continue;
1194
+ // Backpressure past the awaiting-map cap: the lane cannot track the id to settlement, so fall
1195
+ // back to the un-acked path (requeue → normal lane). The OpenCode row may ALREADY be persisted —
1196
+ // the adapter POSTs before noting the batch (r1 adjudication) — so this is the designed
1197
+ // at-least-once duplicate-not-loss degradation, never a silent drop.
1198
+ if (!this.trackAwaitingMidTurnDelivery(message)) {
1199
+ awaitingConsumptionIds.delete(message.id);
1200
+ this.taskClaims.clearActiveClaim(message);
1201
+ this.pendingMessages.set(message.id, message);
1202
+ }
1203
+ }
1204
+ for (const message of deliverable.filter((item) => acknowledgedIds.has(item.id) && !awaitingConsumptionIds.has(item.id))) {
1205
+ await this.http.recordMessageDeliveryAttempt(message.id, { agentId: this.agentId, status: "delivered" }).catch(() => undefined);
1206
+ this.providerTerminalFailureAttempts.delete(message.id);
1207
+ this.submitDeliveryAttempts.delete(message.id);
1208
+ await this.http.markRead(message.id, this.agentId).catch(() => {});
1209
+ if (!taskIdFromMessage(message)) this.claims.finishClaim("message", String(message.id));
1210
+ // Delivered + marked read: remember it so a stale server-list copy (a poll snapshot that
1211
+ // raced this markRead) can never re-enqueue it (see SETTLED_MESSAGE_IDS_CAP).
1212
+ this.rememberSettled(message.id);
1213
+ this.parkedContextAccepted(message);
1214
+ }
1215
+ // #2397 round 3 review (MED-3r) — an un-acknowledged id the adapter attributes to a
1216
+ // TERMINAL provider failure (as opposed to an ordinary mid-turn/busy defer) paces its own
1217
+ // re-drain with bounded exponential backoff.
1218
+ // Without this, a message whose provider just exhausted its own retry budget against an
1219
+ // upstream that is ALREADY rate-limiting got re-queued and re-attempted on the very next
1220
+ // tick (`scheduleDrain(0)` below), forever.
1221
+ if (this.taskClaims.hasDeferredCompletion()) {
1222
+ // Queue persistence failures must be visible; PATCH retries belong to the durable outbox.
1223
+ this.taskClaims.completeObservedTaskClaims(acknowledgedIds).catch((error) => logger.error("task", `completion enqueue failed: ${error}`));
1224
+ for (const message of deliverable) this.taskClaims.forgetDeferredCompletion(message.id);
1225
+ }
1226
+ const providerHeldIds = new Set(normalized.providerHeldIds.filter((id) => !acknowledgedIds.has(id)));
1227
+ for (const message of deliverable) {
1228
+ if (!providerHeldIds.has(message.id) && !acknowledgedIds.has(message.id)) this.hold.unpark(message.id);
1229
+ // A refused resume wake is spent (the next wake retries); never parked as input. Refused
1230
+ // input was already parked inside the offer's FIFO slot (drainMessages).
1231
+ else if (isResumeWake(message)) await this.settleSilently(message);
1232
+ }
1233
+ for (const message of deliverable.filter((item) => !acknowledgedIds.has(item.id) && !awaitingConsumptionIds.has(item.id) && !providerHeldIds.has(item.id))) {
1234
+ this.taskClaims.clearActiveClaim(message);
1235
+ this.pendingMessages.set(message.id, message);
1236
+ if (terminalFailureIds.has(message.id)) {
1237
+ const attempts = (this.providerTerminalFailureAttempts.get(message.id) ?? 0) + 1;
1238
+ this.providerTerminalFailureAttempts.set(message.id, attempts);
1239
+ terminalFailureBackoffMs = Math.max(terminalFailureBackoffMs, Math.min(TERMINAL_FAILURE_RETRY_BASE_MS * 2 ** (attempts - 1), TERMINAL_FAILURE_RETRY_MAX_MS));
1240
+ }
1241
+ }
1242
+ return terminalFailureBackoffMs;
1243
+ }
1244
+
1245
+ /** Delivered + marked read: a stale server-list copy may never re-enqueue it (see SETTLED_MESSAGE_IDS_CAP). */
1246
+ private rememberSettled(id: number): void {
1247
+ this.settledMessageIds.delete(id);
1248
+ this.settledMessageIds.add(id);
1249
+ while (this.settledMessageIds.size > SETTLED_MESSAGE_IDS_CAP) {
1250
+ const oldest = this.settledMessageIds.values().next().value;
1251
+ if (oldest === undefined) break;
1252
+ this.settledMessageIds.delete(oldest);
1253
+ }
1254
+ }
1255
+
1256
+ /** Mark read without delivering: a stale server-list copy may never re-enqueue it. */
1257
+ private async settleSilently(message: Message): Promise<void> {
1258
+ await this.http.markRead(message.id, this.agentId).catch(() => {});
1259
+ this.rememberSettled(message.id);
1260
+ }
1261
+
1262
+ /** #2619 — resume wakes, decided by the hold state machine (provider-hold.ts planWakes). */
1263
+ private async resolveResumeWakes(messages: Message[]): Promise<Message[]> {
1264
+ const plan = this.hold.planWakes(messages);
1265
+ for (const wake of plan.settle) await this.settleSilently(wake);
1266
+ for (const message of plan.replay) this.drainingMessageIds?.add(message.id);
1267
+ return plan.batch;
1268
+ }
1269
+
1270
+ /** #2619 — park a refused prompt PENDING, synchronously (call inside the FIFO slot). Returns its
1271
+ * heldPromptKey; {@link persistParkedPrompt} then makes it durable. */
1272
+ private parkRefusedPrompt(prompt: string, payload: Record<string, unknown> = {}): string {
1273
+ const heldPromptKey = crypto.randomUUID();
1274
+ this.refusedPrompts.begin(heldPromptKey);
1275
+ this.hold.parkPending(heldPromptKey, { from: this.agentId, to: this.agentId, kind: "chat", subject: "Prompt refused by the provider, held for redelivery", body: prompt, payload: { ...payload, heldPromptKey }, readBy: [], createdAt: Date.now() });
1276
+ return heldPromptKey;
1277
+ }
1278
+
1279
+ /**
1280
+ * #2619 — post a pending parked prompt to this agent's own inbox so a restart re-reads it like any
1281
+ * unread message. The relay idempotency key IS the heldPromptKey, so retries and an ambiguous
1282
+ * (committed, response lost) post converge on ONE row. False: it could not be made durable — the
1283
+ * park is dropped and the caller must report failure (never success).
1284
+ */
1285
+ private async persistParkedPrompt(key: string): Promise<boolean> {
1286
+ await retryParkPost("refused prompt inbox post", async () => {
1287
+ const pending = this.hold.pendingMessage(key);
1288
+ if (!pending) return true; // the relay's push of the row bound it meanwhile
1289
+ const posted = await this.http.sendMessage({ from: pending.from, to: pending.to, kind: "chat", subject: pending.subject, body: pending.body, payload: pending.payload, idempotencyKey: key });
1290
+ this.refusedPrompts.confirm(key);
1291
+ this.releaseParked(this.hold.bind(key, posted));
1292
+ return true;
1293
+ });
1294
+ if (!this.hold.isPending(key)) return true;
1295
+ this.hold.drop(key);
1296
+ this.parkedContextInjections.delete(key);
1297
+ logger.error("runner", "refused prompt could not be persisted to the inbox; not parked — its caller fails");
1298
+ return false;
1299
+ }
1300
+
1301
+ /** Parked input released by a bind after the episode ended: deliver it now. */
1302
+ private releaseParked(messages: Message[]): void {
1303
+ for (const message of messages) this.pendingMessages.set(message.id, message);
1304
+ if (messages.length > 0) this.scheduleDrain();
1305
+ }
1306
+
1307
+ /** #2619 — a parked context injection was accepted: finish what its refused injectContext skipped. */
1308
+ private parkedContextAccepted(message: Message): void {
1309
+ const key = message.payload?.heldPromptKey;
1310
+ const parked = typeof key === "string" ? this.parkedContextInjections.get(key) : undefined;
1311
+ if (!parked) return;
1312
+ this.parkedContextInjections.delete(key as string);
1313
+ this.injectedContextHashes.add(parked.contentHash);
1314
+ if (parked.afterClearCommandId) this.releaseSelfResumeClearFence(parked.afterClearCommandId);
1315
+ }
1316
+
1317
+ // #2539 Phase B — stash a soft-steered Message for id-keyed settlement later. False ⇒ over the
1318
+ // cap (caller falls back to the un-acked requeue path).
1319
+ private trackAwaitingMidTurnDelivery(message: Message): boolean {
1320
+ if (this.awaitingMidTurnDelivery.size >= AWAITING_MID_TURN_DELIVERY_CAP) return false;
1321
+ this.awaitingMidTurnDelivery.set(message.id, message);
1322
+ return true;
1323
+ }
1324
+
1325
+ /**
1326
+ * #2539 Phase B — the runner-owned CONSUMED settlement for soft-steered ids, handed to the
1327
+ * adapter through ProviderDeliverContext.settleMidTurnConsumed. Mirrors the acked tail of
1328
+ * {@link settleMessageDelivery} exactly (mark read → release the claim → poison the intake
1329
+ * guard) plus one belt: a pendingMessages entry is dropped, so a requeue racing the consumption
1330
+ * detection can never resurrect an id the model already read. The awaiting-map entry is removed
1331
+ * first so a duplicate callback is a no-op (consume-once, like the ledger's own idempotence).
1332
+ */
1333
+ private async settleSoftSteerConsumption(relayIds: number[]): Promise<void> {
1334
+ for (const id of relayIds) {
1335
+ this.awaitingMidTurnDelivery.delete(id);
1336
+ await this.http.markRead(id, this.agentId).catch(() => {});
1337
+ this.claims.finishClaim("message", String(id));
1338
+ this.pendingMessages.delete(id);
1339
+ this.rememberSettled(id);
1340
+ }
1341
+ }
1342
+
1343
+ /**
1344
+ * #2539 Phase B — the runner-owned REOFFER for soft-steered ids whose persisted row can no
1345
+ * longer be consumed (session clear/compact/reroll swap, shutdown, reconcile-absent). Same
1346
+ * destination as settle's un-acked path (normal queue, turn-end delivery), driven by the
1347
+ * adapter's ledger. Ids no longer tracked (never stashed, or already settled/reoffered) are
1348
+ * ignored — the ledger's at-least-once retries stay idempotent.
1349
+ */
1350
+ private async reofferSoftSteerIds(relayIds: number[]): Promise<void> {
1351
+ let reoffered = 0;
1352
+ for (const id of relayIds) {
1353
+ const message = this.awaitingMidTurnDelivery.get(id);
1354
+ if (!message) continue;
1355
+ this.awaitingMidTurnDelivery.delete(id);
1356
+ this.taskClaims.clearActiveClaim(message);
1357
+ this.pendingMessages.set(message.id, message);
1358
+ reoffered += 1;
1359
+ }
1360
+ if (reoffered > 0) this.scheduleDrain(0);
1361
+ }
1362
+
1363
+ private async drainMessages(): Promise<void> {
1364
+ if (this.lifecycle.stopped || this.delivering || this.deliverySubmitHold || this.submitResendInFlight || this.pendingMessages.size === 0) return;
1365
+ if (!this.process) {
1366
+ this.scheduleDrain(500);
1367
+ return;
1368
+ }
1369
+ this.delivering = true;
1370
+ let messages = [...this.pendingMessages.values()].sort((a, b) => a.id - b.id);
1371
+ this.pendingMessages.clear();
1372
+ this.drainingMessageIds = new Set(messages.map((message) => message.id));
1373
+ messages = this.holdPreClearInboxMessages(messages);
1374
+ // Awaited only when a resume wake is in the batch: every other drain keeps its exact tick timing.
1375
+ if (messages.some(isResumeWake)) messages = await this.resolveResumeWakes(messages);
1376
+ if (messages.length === 0) {
1377
+ this.drainingMessageIds = undefined;
1378
+ this.delivering = false;
1379
+ this.publishStatus();
1380
+ return;
1381
+ }
1382
+ let deliverable: Message[] = [];
1383
+ const providerAlreadyBusy = this.claims.reasons().includes("provider-turn");
1384
+ for (const message of messages) {
1385
+ let toDeliver = message;
1386
+ if (message.claimable) {
1387
+ const claimed = await this.http.claimMessageResult(message.id, this.agentId).catch(() => ({ ok: false, claimExpiresAt: undefined }));
1388
+ if (!claimed.ok) continue;
1389
+ this.claims.startClaim("message", String(message.id), claimed.claimExpiresAt);
1390
+ const taskId = taskIdFromMessage(message);
1391
+ if (taskId) {
1392
+ this.taskClaims.noteClaim(message.id, taskId, providerAlreadyBusy, claimed.claimExpiresAt);
1393
+ await this.http.updateTaskStatus(taskId, {
1394
+ status: "in_progress",
1395
+ agentId: this.agentId,
1396
+ metadata: { messageId: message.id, completedBy: "runner" },
1397
+ }).catch((error) => logger.error("task", `task ${taskId} in_progress update failed: ${error}`));
1398
+ // Runner owns claim + status here; drop the server's self-claim instruction
1399
+ // so the agent doesn't improvise a stray claim send (see stripRunnerClaimedGuidance).
1400
+ toDeliver = { ...message, body: stripRunnerClaimedGuidance(message.body) };
1401
+ }
1402
+ }
1403
+ deliverable.push(toDeliver);
1404
+ }
1405
+ // A clear can have begun while a claim was in flight. Re-check the entire batch before
1406
+ // attachment preparation; the fence owns the pre-boundary input lane, not just the inbox map.
1407
+ deliverable = this.holdPreClearInboxMessages(deliverable, { releaseClaims: true });
1408
+ if (deliverable.length === 0) {
1409
+ this.drainingMessageIds = undefined;
1410
+ this.delivering = false;
1411
+ this.publishStatus();
1412
+ return;
1413
+ }
1414
+ this.publishStatus();
1415
+ let failed = false;
1416
+ let drainBackoffMs = 0;
1417
+ const settlement = Promise.withResolvers<void>();
1418
+ try {
1419
+ let prepared = await messagesWithCachedAttachments(deliverable, this.http, {
1420
+ agentId: this.agentId,
1421
+ onError: (message) => logger.error("runner", message),
1422
+ });
1423
+ // Attachment materialization itself awaits. A clear that arrives in that window must still
1424
+ // keep this old batch out of the fresh provider session, immediately before adapter.deliver.
1425
+ deliverable = this.holdPreClearInboxMessages(deliverable, { releaseClaims: true });
1426
+ if (deliverable.length === 0) return;
1427
+ const deliverableIds = new Set(deliverable.map((message) => message.id));
1428
+ prepared = prepared.filter((message) => deliverableIds.has(message.id));
1429
+ // #1565 F1 — behind the same gate as injected payloads: a message batch and a boundary
1430
+ // inject arriving together must not paste into each other.
1431
+ // Preserve arrival-time evidence across the input queue; adapters decide whether delivery actually deferred.
1432
+ const deliverCtx: ProviderDeliverContext = {
1433
+ isRecipientMidTurn: () => this.claims.reasons().includes("provider-turn"),
1434
+ // #2539 Phase B — the deferred-settlement handoff: the adapter's soft-steer ledger settles
1435
+ // its persisted-but-unconsumed batches through these runner-owned seams (never its own
1436
+ // settlement implementation).
1437
+ settleMidTurnConsumed: (relayIds) => this.deliverySettlements.track(this.settleSoftSteerConsumption(relayIds)),
1438
+ reofferMidTurnIds: (relayIds) => this.reofferSoftSteerIds(relayIds),
1439
+ };
1440
+ // OFFER — arm the empty-turn watch only for work-bearing input, serialize through the FIFO
1441
+ // input queue, disarm on rejection. (#2389 — armed at hand-off, not on resolution.)
1442
+ // #2619 — a resume wake's continue was decided at planning time, before the claim/attachment
1443
+ // awaits above; the episode may have ended (or a new one parked input) since. Re-validated in
1444
+ // the FIFO slot at the offer instant: a continue that no longer holds is dropped and settled
1445
+ // silently. Refused input parks inside the slot (below).
1446
+ const staleWakes: Message[] = [];
1447
+ const { result: acknowledged, offered } = await this.offerProviderInput((inputObserver) => this.offerMessagesToProvider(() => this.holdPreClearInboxMessages(prepared.filter((message) => {
1448
+ if (!isResumeWake(message) || this.hold.continueStillValid(message)) return true;
1449
+ staleWakes.push(message);
1450
+ return false;
1451
+ }), { releaseClaims: true }), (heldIds) => {
1452
+ // #2619 — park refused input NOW, inside the slot: no accepted offer may end the episode first.
1453
+ const held = new Set(heldIds);
1454
+ for (const message of deliverable) {
1455
+ if (!held.has(message.id) || isResumeWake(message)) continue;
1456
+ this.taskClaims.clearActiveClaim(message);
1457
+ this.hold.park([message]);
1458
+ }
1459
+ }, { ctx: { ...deliverCtx, inputObserver }, settlement: settlement.promise }), (outcome) => outcome.offered.length === 0, prepared.some((message) => !isNotificationOnlyInput(message)));
1460
+ for (const wake of staleWakes) await this.settleSilently(wake);
1461
+ const offeredIds = new Set(offered.map((message) => message.id));
1462
+ deliverable = deliverable.filter((message) => offeredIds.has(message.id));
1463
+ // SETTLE — record/read/release-claims/requeue, in ONE place (soft-steer partial acks included).
1464
+ if (deliverable.length > 0) drainBackoffMs = await this.settleMessageDelivery(deliverable, acknowledged);
1465
+ } catch (error) {
1466
+ failed = true;
1467
+ if (error instanceof ProviderSubmitFailure) {
1468
+ const counted = deliverable.map((message) => {
1469
+ const count = (this.submitDeliveryAttempts.get(message.id) ?? 0) + 1;
1470
+ this.submitDeliveryAttempts.set(message.id, count);
1471
+ return { message, count };
1472
+ });
1473
+ const terminal = counted.filter(({ count }) => !error.retrySafe || count >= PROVIDER_SUBMIT_MAX_DELIVERIES);
1474
+ const retry = counted.filter(({ count }) => error.retrySafe && count < PROVIDER_SUBMIT_MAX_DELIVERIES);
1475
+ if (terminal.length > 0) {
1476
+ this.holdFailedProviderSubmit(`Provider prompt submission could not be confirmed: ${error.message}`, { messages: terminal.map(({ message }) => message) });
1477
+ for (const { message, count } of terminal) {
1478
+ const detail = `Provider prompt submission unconfirmed after ${count} delivery attempt(s): ${error.message}`;
1479
+ this.taskClaims.clearActiveClaim(message);
1480
+ this.submitDeliveryAttempts.delete(message.id);
1481
+ await this.http.recordMessageDeliveryAttempt(message.id, { agentId: this.agentId, status: "failed", error: detail }).catch((reportError) => {
1482
+ logger.error("delivery", `could not record failed provider submission for message ${message.id}: ${reportError}`);
1483
+ });
1484
+ }
1485
+ }
1486
+ if (retry.length > 0) {
1487
+ const highestAttempt = Math.max(...retry.map(({ count }) => count));
1488
+ drainBackoffMs = providerSubmitRetryDelayMs(highestAttempt);
1489
+ logger.warn("delivery", `Provider prompt submit failed before paste; ${retry.length} message(s) retry in ${drainBackoffMs}ms: ${error.message}`);
1490
+ for (const { message } of retry) { this.taskClaims.clearActiveClaim(message); this.pendingMessages.set(message.id, message); }
1491
+ }
1492
+ } else {
1493
+ // Other adapter failures retain the existing requeue policy.
1494
+ if (shouldLogDeliveryFailure(error)) logger.warn("delivery", `message delivery failed: ${error}`);
1495
+ for (const message of deliverable) {
1496
+ this.taskClaims.clearActiveClaim(message);
1497
+ this.pendingMessages.set(message.id, message);
1498
+ }
1499
+ }
1500
+ } finally {
1501
+ settlement.resolve();
1502
+ this.drainingMessageIds = undefined;
1503
+ this.delivering = false;
1504
+ this.publishStatus();
1505
+ // Unaccepted input during a live turn waits for turnCleared, rather than reoffering on every tick (#3315).
1506
+ if (this.pendingMessages.size > 0 && (failed || drainBackoffMs > 0 || !this.currentTurnId)) this.scheduleDrain(failed ? Math.max(1000, drainBackoffMs) : drainBackoffMs);
1507
+ }
1508
+ }
1509
+
1510
+ private async compactCommand(params: Record<string, unknown>, nativeSelfResume: NativeSelfResumeHandoff | undefined, commandId: string): Promise<Record<string, unknown>> {
1511
+ let providerResult: Record<string, unknown> | void = undefined;
1512
+ providerResult = await executeCompactCommand(this.options.adapter, this.process, params, this.nativeSelfResume, nativeSelfResume,
1513
+ () => this.awaitContextCommandTurnClear("agent.compact"), () => { this.contextResets.submitting(commandId, "compact"); this.resetContextGeneration(); });
1514
+ providerResult = { ...(providerResult || {}), tokenRemint: await this.remintRuntimeTokenAtBoundary("compact") };
1515
+ return providerResult;
1516
+ }
1517
+
1518
+ private async clearContextCommand(params: Record<string, unknown>, ticket: ProviderInputTicket | undefined, commandId: string): Promise<Record<string, unknown>> {
1519
+ let providerResult: Record<string, unknown> | void = undefined;
1520
+ if (!this.options.adapter.clearContext || !this.process) throw new Error("provider does not support clearContext");
1521
+ if (ticket) await ticket.turn;
1522
+ await this.awaitContextCommandTurnClear("agent.clearContext");
1523
+ // #2550 — same new-generation reset as the compact arm above: cleared before the
1524
+ // destructive step so any failure path still fails open toward re-delivery.
1525
+ this.resetContextGeneration();
1526
+ this.lateClearRecovery.begin(commandId, params, this.options.adapter.couplesClearInjectContinuation);
1527
+ this.contextResets.submitting(commandId, "clear");
1528
+ try { providerResult = await this.options.adapter.clearContext(this.process); }
1529
+ finally { this.lateClearRecovery.finish(commandId, Boolean(providerResult && providerResult.clearConfirmed === true)); }
1530
+ if (providerResult && providerResult.clearConfirmed === true) this.contextResets.confirmed("clear", commandId);
1531
+ // #2043 F2 — commit barrier for the self-resume clear-inject path: a `/clear` whose commit
1532
+ // could not be POSITIVELY confirmed is a possible no-op, so surface it as a FAILED command
1533
+ // (mirroring native's timeout->FAILED) instead of the pre-#2043 silent success. Throw
1534
+ // BEFORE the coupled in-process deliver so we never inject a continuation into a context
1535
+ // that may not have been cleared; the relay's continuation re-injection backstop (now also
1536
+ // armed for a FAILED clear-inject) is then the single recovery path — a false-negative
1537
+ // clear detection recovers rather than wiping working state.
1538
+ this.assertClearInjectCommitted(params, providerResult || undefined);
1539
+ // The cleared context goes to the card now; the ~30s beat would leave the old usage visible meanwhile (#2771).
1540
+ this.bus.flushHeartbeat();
1541
+ // Before the continuation, so the next generation's first turn already sees the re-derived tools.
1542
+ providerResult = { ...(providerResult || {}), tokenRemint: await this.remintRuntimeTokenAtBoundary("clear") };
1543
+ const coupled = await this.deliverCoupledContinuationAfterClear(params, providerResult || undefined, ticket);
1544
+ if (coupled) providerResult = { ...(providerResult ?? {}), ...coupled };
1545
+ if (coupled?.continuationDelivered === true) this.releaseSelfResumeClearFence(commandId);
1546
+ return providerResult;
1547
+ }
1548
+
1549
+ private beginNativeSelfResume(type: string, params: Record<string, unknown>, commandId: string): NativeSelfResumeHandoff | undefined {
1550
+ return isNativeSelfResumeCompact(type, params) || (type === "agent.compact" && this.options.adapter.compactReportsSubmission === true)
1551
+ ? this.nativeSelfResume.begin(commandId, params)
1552
+ : undefined;
1553
+ }
1554
+
1555
+ private finishClearCommand(commandId: string): void {
1556
+ this.continuationFence.finish(commandId, this.lateClearRecovery.holds(commandId));
1557
+ }
1558
+
1559
+ // Fence the predecessor provider turn before a destructive compact/clearContext runs, so the
1560
+ // successor generation never overlaps it. Two provider shapes need two different fences:
1561
+ //
1562
+ // #2043 F1 — a blind context-command submit (Claude's tmux `/compact` // `/clear`) must land on
1563
+ // an IDLE prompt to parse as a slash command. Submitted while a turn is live it queues behind the
1564
+ // turn and dequeues as literal chat, so the compaction silently evaporates — the #2043 runaway.
1565
+ // For those adapters we WAIT for the live turn to clear on its own; a wait that times out THROWS,
1566
+ // so the command fails loudly (F2/F4) rather than blind-submitting into a live turn and no-oping.
1567
+ //
1568
+ // #2569 — an app-server adapter (Codex) whose clearContext starts a FRESH thread / whose compact
1569
+ // rewrites the current one via an awaited handshake. The await confirms the NEW generation is
1570
+ // ready but does NOT settle the OLD turn: relay_compact_and_resume runs from within the model's
1571
+ // own live turn, so that turn keeps writing the worktree on the previous thread while the
1572
+ // successor is armed on the new one — two generations revert each other (the #2569 silent
1573
+ // overlap). A plain wait would time out on the still-live in-turn compaction, so we INTERRUPT the
1574
+ // predecessor turn, then wait for it to reach a terminal state (interrupt-then-settle, mirroring
1575
+ // the redirect path at the agent.interrupt arm) before the destructive step. A settle timeout
1576
+ // THROWS — the command fails closed and the relay re-injects — never opening the overlap window.
1577
+ private async awaitContextCommandTurnClear(type: string): Promise<void> {
1578
+ const adapter = this.options.adapter;
1579
+ if (adapter.awaitsIdleBeforeContextCommand) {
1580
+ try {
1581
+ await this.busyReconciler.waitForProviderTurnClear();
1582
+ } catch (error) {
1583
+ throw new ContextSubmitAborted(`provider turn did not clear before ${type} submit; aborting the submit to avoid a silent no-op (${errMessage(error)})`);
1584
+ }
1585
+ return;
1586
+ }
1587
+ if (adapter.interruptsTurnBeforeContextCommand) {
1588
+ const hasProviderTurn = this.claims.activeWork().some((work) => work.kind === "provider-turn");
1589
+ if (!hasProviderTurn || !adapter.interrupt || !this.process) return;
1590
+ // #2389 — an interrupted turn ends with no output; that state has its own notice
1591
+ // (agent.turn_interrupted) and must not also draw an empty-turn strike.
1592
+ this.emptyTurns.reset();
1593
+ try {
1594
+ await adapter.interrupt(this.process);
1595
+ } catch (error) {
1596
+ // The turn may have raced to terminal between the hasProviderTurn read and here (no active
1597
+ // turn to interrupt). The scheduled reconcile + settle wait below is the authoritative gate.
1598
+ this.sessionLog(`context-command fence: interrupt before ${type} failed: ${errMessage(error)}`);
1599
+ }
1600
+ this.busyReconciler.scheduleInterruptReconcile();
1601
+ try {
1602
+ await this.busyReconciler.waitForProviderTurnClear();
1603
+ } catch (error) {
1604
+ throw new ContextSubmitAborted(`predecessor provider turn did not settle before ${type}; aborting to avoid two generations writing the one worktree (${errMessage(error)})`);
1605
+ }
1606
+ }
1607
+ }
1608
+
1609
+ // #2043 F2 — the clear-inject commit barrier. A self-resume `/clear` whose commit the adapter's
1610
+ // clear-aware barrier could not POSITIVELY confirm (clearConfirmed !== true) is treated as a
1611
+ // possible no-op and surfaced as a FAILED command, never the pre-#2043 silent success. Scoped to
1612
+ // the coupled clear-inject path (couplesClearInjectContinuation) carrying a real continuation —
1613
+ // a manual `/clear` with no self-resume payload keeps its existing best-effort semantics.
1614
+ private assertClearInjectCommitted(params: Record<string, unknown>, clearResult: Record<string, unknown> | undefined): void {
1615
+ assertClearInjectCommitted(this.options.adapter.couplesClearInjectContinuation, params, clearResult, (message) => this.sessionLog(message));
1616
+ }
1617
+
1618
+ // #1588 — couple the self-resume continuation delivery into the SAME clearContext command that
1619
+ // wipes the context, so the two can never race across separate commands/transports again. The
1620
+ // relay embeds the fully-built (identity-augmented) continuation in the clear command's
1621
+ // `selfResume.continuation`; we deliver it in-process, strictly AFTER adapter.clearContext has
1622
+ // returned. `continuationDelivered` tells the relay whether it can skip the redundant reactive
1623
+ // inject or must still fire its #1023 backstop — and it MUST be truthful, because the relay skips
1624
+ // BOTH backstops on continuationDelivered===true, so a false-positive true is a silent-wipe risk
1625
+ // with no recovery. So `true` requires TWO things: (1) the adapter's clear-aware barrier
1626
+ // POSITIVELY confirmed the TUI clear committed (clearResult.clearConfirmed === true — the strong
1627
+ // clear-committed evidence), and (2) the in-process deliver resolved. A settle timeout / uncertain
1628
+ // clear → clearConfirmed !== true → we still deliver in-process (best effort) but report false so
1629
+ // the backstop re-injects. Uncertainty ALWAYS resolves to false (backstop covers), never true.
1630
+ private async deliverCoupledContinuationAfterClear(params: Record<string, unknown>, clearResult: Record<string, unknown> | undefined, ticket?: ProviderInputTicket): Promise<Record<string, unknown> | undefined> {
1631
+ // Opt-in per provider: only adapters whose clearContext is a fire-and-forget keystroke with no
1632
+ // completion barrier (Claude) couple the continuation in-process. Codex's clearContext is an
1633
+ // awaited atomic threadStart (the await IS the barrier), so it keeps the separate confirmed-inject
1634
+ // flow untouched — never sets continuationDelivered, so the relay never skips its reactive inject.
1635
+ if (!this.options.adapter.couplesClearInjectContinuation) return undefined;
1636
+ const selfResume = params.selfResume !== null && typeof params.selfResume === "object" && !Array.isArray(params.selfResume)
1637
+ ? params.selfResume as Record<string, unknown>
1638
+ : undefined;
1639
+ if (!selfResume || selfResume.mode !== "clear-inject") return undefined;
1640
+ const continuation = typeof selfResume.continuation === "string" ? selfResume.continuation.trim() : "";
1641
+ if (!continuation) return undefined;
1642
+ // Only a genuine clear-commit confirmation from the adapter's clear-aware barrier permits a
1643
+ // truthful continuationDelivered:true. Absent/false (older runner, or a settle timeout) → false.
1644
+ const clearConfirmed = clearResult?.clearConfirmed === true;
1645
+ try {
1646
+ // #1607 N3 — reuse the outer clearContext ticket instead of letting injectContext reserve a
1647
+ // fresh one, which would queue behind (and thus deadlock on) the still-held outer ticket.
1648
+ await this.injectContext({ content: continuation, reason: "self-resume-continuation", selfResume }, ticket);
1649
+ if (!clearConfirmed) {
1650
+ // Deliver ran, but the clear was NOT positively confirmed committed — the continuation may
1651
+ // have raced a still-processing clear. Report false so the relay backstop re-injects out of
1652
+ // band. A redundant re-inject is always preferable to a silent wipe.
1653
+ this.sessionLog("coupled self-resume continuation delivered but clear commit was NOT confirmed; relay backstop will re-inject");
1654
+ }
1655
+ return { continuationDelivered: clearConfirmed };
1656
+ } catch (error) {
1657
+ // The clear already ran — reporting the command as failed would strand the agent with the
1658
+ // relay unaware its context is gone. Instead report the clear succeeded WITHOUT
1659
+ // continuationDelivered so the relay's reactive/sweep backstop re-injects out-of-band.
1660
+ this.sessionLog(`coupled self-resume continuation delivery failed, relay backstop will retry: ${errMessage(error)}`);
1661
+ return { continuationDelivered: false };
1662
+ }
1663
+ }
1664
+
1665
+ private async injectContext(params: Record<string, unknown>, ticket?: ProviderInputTicket): Promise<Record<string, unknown>> {
1666
+ const content = typeof params.content === "string" ? params.content.trim() : "";
1667
+ if (!content) throw new Error("content required");
1668
+ const memoryIds = Array.isArray(params.memoryIds) ? params.memoryIds.filter((id): id is string => typeof id === "string") : [];
1669
+ const reason = typeof params.reason === "string" ? params.reason : undefined;
1670
+ const selfResume = params.selfResume !== null && typeof params.selfResume === "object" && !Array.isArray(params.selfResume)
1671
+ ? params.selfResume as Record<string, unknown>
1672
+ : undefined;
1673
+ const isSelfResumeContinuation = reason === "self-resume-continuation" || Boolean(selfResume);
1674
+ const fullContextMessageId = typeof selfResume?.fullContextMessageId === "number"
1675
+ ? selfResume.fullContextMessageId
1676
+ : undefined;
1677
+ // #2550 — the one content-hash backstop for system-owned context injections. injectContext is
1678
+ // the SINGLE chokepoint every such payload flows through (continuation, identity, memory
1679
+ // preamble, startup context — all arrive as agent.injectContext commands), so one check here
1680
+ // covers every path. Byte-identical match only, per current generation. Prompts and messages
1681
+ // use injectPrompt/drainMessages and are never deduped here.
1682
+ const contentHash = createHash("sha256").update(content, "utf8").digest("hex");
1683
+ if (this.injectedContextHashes.has(contentHash)) {
1684
+ this.sessionLog(`context injection (${reason ?? "no reason"}) skipped: byte-identical content already delivered this generation (#2550 backstop)`);
1685
+ this.areaMemory.noteInjected(params, memoryIds);
1686
+ return {
1687
+ memoryIds,
1688
+ injectedMemoryCount: memoryIds.length,
1689
+ duplicateInjectSkipped: true,
1690
+ // Truthful settle evidence: the hash table IS the proof the payload reached this
1691
+ // provider context, so a skipped continuation still reports ingested (the relay's
1692
+ // #2545 fence and transport settle then close the boundary instead of re-arming).
1693
+ ...(isSelfResumeContinuation ? { continuationIngested: true, ...(fullContextMessageId !== undefined ? { fullContextMessageId } : {}) } : {}),
1694
+ } as Record<string, unknown>;
1695
+ }
1696
+ const payload: Message = {
1697
+ id: 0,
1698
+ from: "system",
1699
+ to: this.agentId,
1700
+ kind: "system",
1701
+ subject: isSelfResumeContinuation ? "Agent Relay self-resume continuation" : "Agent Relay memory context",
1702
+ body: content,
1703
+ payload: {
1704
+ memoryInjection: true,
1705
+ reason,
1706
+ memoryIds,
1707
+ ...(isSelfResumeContinuation ? { resumeInjection: true } : {}),
1708
+ ...(selfResume ? { selfResume } : {}),
1709
+ },
1710
+ readBy: [],
1711
+ createdAt: Date.now(),
1712
+ };
1713
+ // #1565 F1 — one payload at a time, in dispatch order (see dispatchCommand). The ticket, when
1714
+ // present, is the place in line this command took the moment it arrived.
1715
+ // #2389 — armed WATCH-ONLY. The turn after a memory block or a self-resume continuation
1716
+ // legitimately produces nothing (a compaction, a silent acknowledgment), so it must take no
1717
+ // strike and provoke no nudge — but a continuation that genuinely strands still has to wake
1718
+ // someone, and the strike ladder can never see that. The timed backstop is what covers it.
1719
+ const contextArm = this.emptyTurns.armInput(Date.now(), "watch-only", true);
1720
+ // #2619 — a context payload the provider REFUSED (a quota wall, a throttle) did not land. It
1721
+ // parks in the hold machine like every other refused input (inbox-persisted, replayed by the
1722
+ // resume wake — never a generic continue, which would run it from the provider's history AND
1723
+ // again on replay). No hash, no `continuationIngested`, no fence release until the parked copy
1724
+ // is ACCEPTED (parkedContextAccepted). Reporting it ingested is how a held continuation vanished.
1725
+ // Not durable (the inbox post failed) → the command FAILS, not ingested: the relay's backstop
1726
+ // replays it (src/self-resume.ts). A parked continuation names its recovery artifact so the relay
1727
+ // promotes it to ingested when the parked copy is delivered.
1728
+ let parkedKey: string | undefined;
1729
+ // #3411 — decided inside the provider-input slot on every attempt: a reset queued ahead of this payload runs first.
1730
+ let staleArea = false;
1731
+ const deliver = (slot?: ProviderInputTicket) => this.offerMessagesToProvider(() => (staleArea = this.areaMemory.isStaleInjection(params)) ? [] : [payload], () => {
1732
+ parkedKey = this.parkRefusedPrompt(content, isSelfResumeContinuation && fullContextMessageId !== undefined ? { selfResumeFullContextMessageId: fullContextMessageId } : {});
1733
+ this.parkedContextInjections.set(parkedKey, { contentHash, ...(typeof selfResume?.afterClearCommandId === "string" ? { afterClearCommandId: selfResume.afterClearCommandId } : {}) });
1734
+ }, { ticket: slot, ctx: { inputObserver: this.emptyTurns.inputObserver(contextArm) } });
1735
+ try {
1736
+ await deliver(ticket);
1737
+ } catch (error) {
1738
+ // Codex has no input buffer: a payload offered during a live turn is rejected rather than
1739
+ // queued (#1339). Serializing the boundary pair means the SECOND payload now meets the turn
1740
+ // the first one started, so re-offer it once the turn ends instead of failing the command —
1741
+ // the same queue-behind-the-turn semantics Claude's transports get for free from the CLI's
1742
+ // own buffer. A timeout waiting for the turn re-throws: the command fails, the memory
1743
+ // delta-gate does not advance, and the relay backstop re-sends. Never a silent drop.
1744
+ if (!isTurnInProgressDeliveryError(error)) { this.emptyTurns.disarmInput(contextArm); throw error; }
1745
+ this.sessionLog(`context injection (${reason ?? "no reason"}) deferred behind the active provider turn; re-delivering when it ends`);
1746
+ await this.busyReconciler.waitForProviderTurnClear();
1747
+ try {
1748
+ await deliver();
1749
+ } catch (retryError) {
1750
+ this.emptyTurns.disarmInput(contextArm);
1751
+ throw retryError;
1752
+ }
1753
+ }
1754
+ if (staleArea) { this.emptyTurns.disarmInput(contextArm); return { memoryIds, injectedMemoryCount: 0, staleAreaGeneration: true }; }
1755
+ if (parkedKey !== undefined) {
1756
+ this.emptyTurns.disarmInput(contextArm);
1757
+ if (!await this.persistParkedPrompt(parkedKey)) throw new Error(`context injection (${reason ?? "no reason"}) refused under a provider hold and could not be parked durably; not delivered`);
1758
+ this.sessionLog(`context injection (${reason ?? "no reason"}) refused under a provider hold; parked for redelivery`);
1759
+ this.areaMemory.noteInjected(params, memoryIds);
1760
+ return { memoryIds, injectedMemoryCount: 0, heldForRedelivery: true };
1761
+ }
1762
+ // Record ONLY after a successful delivery (direct or deferred-retry): a failed attempt must
1763
+ // still be retryable, so it never poisons the table.
1764
+ this.injectedContextHashes.add(contentHash);
1765
+ this.areaMemory.noteInjected(params, memoryIds);
1766
+ if (isSelfResumeContinuation && typeof selfResume?.afterClearCommandId === "string") {
1767
+ this.releaseSelfResumeClearFence(selfResume.afterClearCommandId);
1768
+ }
1769
+ return {
1770
+ memoryIds,
1771
+ injectedMemoryCount: memoryIds.length,
1772
+ ...(isSelfResumeContinuation ? {
1773
+ continuationIngested: true,
1774
+ ...(fullContextMessageId !== undefined ? { fullContextMessageId } : {}),
1775
+ } : {}),
1776
+ };
1777
+ }
1778
+
1779
+ private beginSelfResumeClearFence(type: string, params: Record<string, unknown>, commandId: string): void {
1780
+ if (type !== "agent.clearContext") return;
1781
+ this.continuationFence.begin(type, params, commandId, [...this.pendingMessages.keys(), ...(this.drainingMessageIds ?? [])]);
1782
+ }
1783
+
1784
+ private releaseSelfResumeClearFence(clearCommandId: string): void {
1785
+ this.continuationFence.accept(clearCommandId, this.lateClearRecovery.holds(clearCommandId));
1786
+ }
1787
+
1788
+ private holdPreClearInboxMessages(messages: Message[], options: { releaseClaims?: boolean } = {}): Message[] {
1789
+ if (!this.continuationFence.active) return messages;
1790
+ const allowed: Message[] = [];
1791
+ for (const message of messages) {
1792
+ if (this.continuationFence.holds(message.id)) {
1793
+ if (options.releaseClaims) this.taskClaims.clearActiveClaim(message);
1794
+ this.pendingMessages.set(message.id, message);
1795
+ }
1796
+ else allowed.push(message);
1797
+ }
1798
+ return allowed;
1799
+ }
1800
+
1801
+ /** #2550/#3411 — a new provider context generation: charter/memory may land again, every path area is unentered. */
1802
+ private resetContextGeneration(): void { this.injectedContextHashes.clear(); this.areaMemory.resetGeneration(); }
1803
+
1804
+ private recoverLateClear(input: { source?: string; providerSessionId?: string }): string | undefined {
1805
+ if (input.source !== "clear" || input.providerSessionId !== this.providerSessionId) return;
1806
+ const recovery = this.lateClearRecovery.observe();
1807
+ if (!recovery) return;
1808
+ this.resetContextGeneration();
1809
+ const content = lateClearHookContent(recovery);
1810
+ if (content === recovery.content) this.injectedContextHashes.add(createHash("sha256").update(content, "utf8").digest("hex"));
1811
+ this.releaseSelfResumeClearFence(recovery.clearCommandId);
1812
+ this.sessionLog(`late clear committed after ${recovery.clearCommandId} failed; restoring continuation through SessionStart`);
1813
+ return content;
1814
+ }
1815
+
1816
+ private finishLateClearGrace(commandId: string): void {
1817
+ this.continuationFence.endLateClearGrace(commandId);
1818
+ this.sessionLog(`late clear grace ended for ${commandId}; resuming inbox delivery without a confirmed clear`);
1819
+ }
1820
+
1821
+ private async restartProvider(opts: RestartProviderOptions = {}): Promise<void> {
1822
+ this.lifecycle.beginRestart();
1823
+ this.startupPaneDialog = undefined;
1824
+ const priorProviderSessionId = this.providerSessionId;
1825
+ const priorLastTranscriptPath = this.lastTranscriptPath;
1826
+ let providerShutdownFailed = false;
1827
+ let replacementSpawned = false;
1828
+ try {
1829
+ // Fence the old incarnation before adapter shutdown. spawnProvider() reopens provider
1830
+ // sources and heartbeats only after the old shutdown has completed.
1831
+ this.lifecycle.armTerminal();
1832
+ if (this.process) {
1833
+ try {
1834
+ await this.options.adapter.shutdown(this.process, {
1835
+ graceful: true,
1836
+ timeoutMs: this.options.providerConfig.headless.shutdownTimeoutMs,
1837
+ });
1838
+ } catch (error) {
1839
+ providerShutdownFailed = true;
1840
+ throw error;
1841
+ }
1842
+ }
1843
+ this.lifecycleMilestones.onRestart("work-cleared");
1844
+ // #2414 — also honor a shutdown that began mid-restart (signal-driven, before stop() itself
1845
+ // has run); this.lifecycle.stopped alone lags stop()'s own entry.
1846
+ if (this.lifecycle.stopped || this.lifecycle.shuttingDown) return;
1847
+ this.adoptProcess(await this.spawnProvider(opts));
1848
+ this.lifecycleMilestones.onRestart("replacement-adopted");
1849
+ replacementSpawned = true;
1850
+ await this.promptCommands.startLaunchSeededTurn();
1851
+ this.lifecycleMilestones.onRestart("launch-seeded");
1852
+ this.publishStatus();
1853
+ if (this.pendingMessages.size > 0) this.scheduleDrain();
1854
+ } finally {
1855
+ this.lifecycle.finishRestart();
1856
+ // Only a failed teardown can leave the old provider incarnation alive. Reopen every source
1857
+ // armTerminal() fenced in that case; a confirmed shutdown followed by spawn failure has no
1858
+ // surviving provider to watch, while a successful replacement owns freshly reset sources.
1859
+ if (providerShutdownFailed && !replacementSpawned && !this.lifecycle.stopped && !this.lifecycle.shuttingDown) {
1860
+ await this.lifecycleMilestones.onRestart("teardown-failed", priorProviderSessionId, priorLastTranscriptPath);
1861
+ }
1862
+ }
1863
+ }
1864
+
1865
+ private shouldRestartUnexpectedProviderExit(status: SemanticStatus): boolean {
1866
+ return runnerShouldRestartUnexpectedProviderExit(status, {
1867
+ exitCommandInProgress: this.lifecycle.exitCommandInProgress,
1868
+ // #2414 — this is the actual restart-suppression race: `shuttingDown` (armed synchronously
1869
+ // from the signal handler) must win it even before stop() itself has run and set `stopped`.
1870
+ stopped: this.lifecycle.stopped || this.lifecycle.shuttingDown,
1871
+ restartInProgress: this.lifecycle.restartInProgress,
1872
+ provider: this.options.provider,
1873
+ headless: this.options.headless, lifecycle: this.options.lifecycle,
1874
+ hasTerminalSession: typeof this.process?.meta?.tmuxSession === "string"
1875
+ || (this.options.adapter.supportsUnexpectedExitRestart?.() ?? false),
1876
+ });
1877
+ }
1878
+
1879
+ private async restartUnexpectedProviderExit(status: SemanticStatus): Promise<void> {
1880
+ if (this.lifecycle.restartPending) return;
1881
+ this.lifecycle.beginUnexpectedExitRestart();
1882
+ try {
1883
+ // Best-effort Insights capture for the segment that just ended in a crash (#183). This
1884
+ // path has no controlled teardown, so without it crashed sessions silently drop their
1885
+ // context-ratio datapoint. The process handle is still set (cleared later), so the
1886
+ // provider capture source is readable; the runner stays alive here (restart or offline), so the
1887
+ // durable outbox drains normally — no flush needed.
1888
+ await Promise.race([
1889
+ Promise.all([
1890
+ this.captureContextRatio("crash"),
1891
+ this.sessionMirror.settleTerminalTokenUsage(),
1892
+ ]).then(() => undefined),
1893
+ new Promise<void>((resolve) => setTimeout(resolve, PRE_DESTROY_TIMEOUT_MS)),
1894
+ ]).catch((error) => this.sessionLog(`insights: crash capture failed: ${errMessage(error)}`));
1895
+
1896
+ const now = Date.now();
1897
+ const runtimeMs = this.processStartedAt ? now - this.processStartedAt : Number.POSITIVE_INFINITY;
1898
+ const recent = this.unexpectedExitTimes.filter((time) => now - time <= UNEXPECTED_EXIT_WINDOW_MS);
1899
+ recent.push(now);
1900
+ this.unexpectedExitTimes.splice(0, this.unexpectedExitTimes.length, ...recent);
1901
+ const diagnostics = providerExitDiagnostics({ status, runtimeMs, processMeta: this.process?.meta, hasProcess: Boolean(this.process?.process), exitCommandInProgress: this.lifecycle.exitCommandInProgress, stopped: this.lifecycle.stopped || this.lifecycle.shuttingDown, restartInProgress: this.lifecycle.restartInProgress, restartPending: this.lifecycle.restartPending, headless: this.options.headless, provider: this.options.provider, logFile: runnerLogFileFromEnv() });
1902
+
1903
+ this.publishRunnerTimelineEvent({
1904
+ status: "provider.exit_detected",
1905
+ id: `provider-exit-${this.providerSessionId}-${now}`,
1906
+ timestamp: now,
1907
+ title: "Provider exited",
1908
+ body: `${this.options.provider} reported ${status} after ${Math.round(runtimeMs / 1000)}s`,
1909
+ icon: "ti-plug-off",
1910
+ metadata: {
1911
+ eventType: "provider.exit_detected",
1912
+ ...diagnostics,
1913
+ },
1914
+ });
1915
+
1916
+ const nativeSelfResume = this.nativeSelfResume.active;
1917
+ if (nativeSelfResume && getManifest(this.options.provider)?.resume?.supported) {
1918
+ // suppressPrompt only applies (and only matters) on the no-resumeId branch below — see the
1919
+ // gate-hardening note at the tmux-exit-auto-resume call site further down for why it's a
1920
+ // no-op whenever resumeId is set, and what actually keeps a resume off the bypass gate.
1921
+ await recoverNativeSelfResumeExit({ handoff: nativeSelfResume, tracker: this.nativeSelfResume, providerSessionId: this.providerSessionId, status, diagnostics, now, publishTimeline: (event) => this.publishRunnerTimelineEvent(event), setTerminalProviderExit: (marker) => { this.terminalProviderExit = { ...marker, providerSessionId: this.providerSessionId }; }, setProviderStatus: (update) => this.setProviderStatus(update), restartProvider: (resumeId) => this.restartProvider(resumeId ? { resumeId } : { suppressPrompt: true }), publishStatus: () => this.publishStatus(), scheduleDrain: () => this.scheduleDrain() });
1922
+ return;
1923
+ }
1924
+
1925
+ let restartResumeId: string | undefined;
1926
+ if (this.shouldStopUnexpectedProviderExit(diagnostics)) {
1927
+ const resumeId = providerExitResumeId(diagnostics);
1928
+ const hasResumeId = resumeId !== undefined;
1929
+ if (!hasResumeId) {
1930
+ const recoveredStatus = await this.recoveredStatusAfterUndeterminedProviderExit();
1931
+ if (recoveredStatus) {
1932
+ logger.warn("lifecycle", `${this.options.provider} exit signal recovered as ${recoveredStatus}; suppressing terminal exit`);
1933
+ this.publishRunnerTimelineEvent({
1934
+ status: "provider.restart_decision",
1935
+ id: `provider-restart-decision-${this.providerSessionId}-${now}`,
1936
+ timestamp: Date.now(),
1937
+ title: "Provider exit suppressed",
1938
+ body: `${this.options.provider} reported ${recoveredStatus} during exit confirmation; runner kept the provider live.`,
1939
+ icon: "ti-player-play",
1940
+ metadata: {
1941
+ eventType: "provider.restart_decision",
1942
+ decision: "suppress-recovered",
1943
+ reason: "undetermined-provider-exit-recovered",
1944
+ recoveredStatus,
1945
+ confirmWindowMs: UNDETERMINED_PROVIDER_EXIT_CONFIRM_MS,
1946
+ ...diagnostics,
1947
+ },
1948
+ });
1949
+ this.setProviderStatus(recoveredStatus);
1950
+ return;
1951
+ }
1952
+ }
1953
+ if (!hasResumeId && this.shouldRestartRecoverableProviderExit(diagnostics)) {
1954
+ logger.warn("lifecycle", `${this.options.provider} exit signal is early/recoverable; allowing fresh restart`);
1955
+ } else if (resumeId && this.shouldResumeWithContextProviderExit(diagnostics, recent.length)) {
1956
+ // #1258 (L2): a long-lived agent killed via tmux-session-ended (external SIGTERM, reboot,
1957
+ // OOM, admin cleanup) used to be stranded permanently offline even though the provider CLI printed
1958
+ // a resumable session id. Now, when Layer 3 captured a valid resume id, auto-resume WITH
1959
+ // context instead of surfacing manual recovery. Crash-loop protection reuses the existing
1960
+ // unexpected-exit window: `recent` is capped at MAX_RAPID_UNEXPECTED_EXITS within
1961
+ // UNEXPECTED_EXIT_WINDOW_MS, and the fall-through path below applies backoff + a hard
1962
+ // give-up — so a genuinely broken agent stops resurrecting once the window fills, while a
1963
+ // resume that survives past the window resets the counter (no permanent lockout).
1964
+ restartResumeId = resumeId;
1965
+ logger.warn("lifecycle", `${this.options.provider} exited via tmux; auto-resuming with captured context (resume id ${resumeId})`);
1966
+ } else {
1967
+ logger.warn("lifecycle", `${this.options.provider} exited; leaving agent offline for manual recovery`);
1968
+ this.publishRunnerTimelineEvent({
1969
+ status: "provider.restart_decision",
1970
+ id: `provider-restart-decision-${this.providerSessionId}-${now}`,
1971
+ timestamp: Date.now(),
1972
+ title: "Provider restart skipped",
1973
+ body: providerManualRecoveryMessage(this.options.provider, diagnostics),
1974
+ icon: "ti-player-stop",
1975
+ metadata: {
1976
+ eventType: "provider.restart_decision",
1977
+ decision: "stop-surface",
1978
+ reason: providerManualRecoveryReason(diagnostics),
1979
+ ...diagnostics,
1980
+ },
1981
+ });
1982
+ this.process = undefined;
1983
+ // #633: the runner stays alive (manual-resume hold), so neither the orchestrator tmux-gone
1984
+ // path nor the heartbeat reaper will ever fire. Stamp the terminal marker so the SAME offline
1985
+ // status frame (published by setProviderStatus below) carries it; the server's bus status
1986
+ // handler turns that edge into a terminal `agent.exited` event with a #636 diagnosis.
1987
+ this.terminalProviderExit = {
1988
+ providerSessionId: this.providerSessionId,
1989
+ reason: providerManualRecoveryReason(diagnostics),
1990
+ ...diagnostics,
1991
+ };
1992
+ this.setProviderStatus({
1993
+ status,
1994
+ reason: "provider-turn",
1995
+ id: `provider-exit-${this.providerSessionId}`,
1996
+ clear: ["provider-turn", "subagent", "background-script"],
1997
+ });
1998
+ return;
1999
+ }
2000
+ }
2001
+
2002
+ if (runtimeMs < RAPID_EXIT_MS && recent.length > MAX_RAPID_UNEXPECTED_EXITS) {
2003
+ logger.error("lifecycle", `provider session exited ${recent.length} times within ${Math.round(UNEXPECTED_EXIT_WINDOW_MS / 1000)}s; giving up`);
2004
+ this.publishRunnerTimelineEvent({
2005
+ status: "provider.restart_decision",
2006
+ id: `provider-restart-decision-${this.providerSessionId}-${now}`,
2007
+ timestamp: Date.now(),
2008
+ title: "Provider restart skipped",
2009
+ body: `rapid unexpected exits exceeded ${MAX_RAPID_UNEXPECTED_EXITS}`,
2010
+ icon: "ti-alert-triangle",
2011
+ metadata: {
2012
+ eventType: "provider.restart_decision",
2013
+ decision: "give-up",
2014
+ reason: "rapid-unexpected-provider-exits",
2015
+ rapidExitCount: recent.length,
2016
+ rapidExitWindowMs: UNEXPECTED_EXIT_WINDOW_MS,
2017
+ maxRapidUnexpectedExits: MAX_RAPID_UNEXPECTED_EXITS,
2018
+ ...diagnostics,
2019
+ },
2020
+ });
2021
+ this.setProviderStatus(status);
2022
+ this.options.onProviderExit?.(0);
2023
+ return;
2024
+ }
2025
+
2026
+ const delayMs = Math.min(10_000, Math.max(500, 500 * recent.length));
2027
+ const resuming = restartResumeId !== undefined;
2028
+ logger.warn("lifecycle", `provider session exited unexpectedly after ${Math.round(runtimeMs / 1000)}s; ${resuming ? "resuming with context" : "restarting"} in ${delayMs}ms`);
2029
+ this.publishRunnerTimelineEvent({
2030
+ status: "provider.restart_decision",
2031
+ id: `provider-restart-decision-${this.providerSessionId}-${now}`,
2032
+ timestamp: Date.now(),
2033
+ title: "Provider restart scheduled",
2034
+ body: resuming
2035
+ ? `runner will resume ${this.options.provider} with captured context in ${delayMs}ms`
2036
+ : `runner will start a fresh ${this.options.provider} provider in ${delayMs}ms`,
2037
+ icon: "ti-refresh",
2038
+ metadata: {
2039
+ eventType: "provider.restart_decision",
2040
+ decision: resuming ? "resume-with-context" : "restart-fresh",
2041
+ reason: resuming ? "tmux-exit-auto-resume" : "unexpected-headless-terminal-exit",
2042
+ delayMs,
2043
+ rapidExitCount: recent.length,
2044
+ rapidExitWindowMs: UNEXPECTED_EXIT_WINDOW_MS,
2045
+ ...diagnostics,
2046
+ },
2047
+ });
2048
+ await Bun.sleep(delayMs);
2049
+ if (this.lifecycle.stopped || this.lifecycle.exitCommandInProgress) return;
2050
+ try {
2051
+ // #1368/#harden — a resume relaunch always carries a resumeId here, so passing
2052
+ // suppressPrompt would be a no-op (it's only consumed under !resumeId, see spawnProvider).
2053
+ // What actually keeps a skip-permissions resume off the interactive "Bypass Permissions
2054
+ // mode" gate: the config-seed re-heal in profile-home.ts, which idempotently re-applies
2055
+ // every prompt-gate key on the persisted, instance-keyed provider config home before each
2056
+ // launch, plus the adapter's always-on pane-gate send-keys watcher as a fallback if a gate
2057
+ // still renders.
2058
+ await this.restartProvider(restartResumeId ? { resumeId: restartResumeId } : undefined);
2059
+ this.publishStatus();
2060
+ this.scheduleDrain();
2061
+ } catch (error) {
2062
+ logger.error("lifecycle", `provider restart after unexpected exit failed: ${error}`);
2063
+ this.setProviderStatus("error");
2064
+ this.options.onProviderExit?.(1);
2065
+ }
2066
+ } finally {
2067
+ this.lifecycle.finishUnexpectedExitRestart();
2068
+ }
2069
+ }
2070
+
2071
+ private shouldStopUnexpectedProviderExit(diagnostics: Record<string, unknown>): boolean {
2072
+ return getManifest(this.options.provider)?.launch?.managedPolicyPrompt?.alwaysOn === "append-system-prompt" && diagnostics.exitCommandInProgress !== true;
2073
+ }
2074
+
2075
+ private shouldRestartRecoverableProviderExit(diagnostics: Record<string, unknown>): boolean {
2076
+ return diagnostics.exitSource === "tmux-session-ended"
2077
+ && typeof diagnostics.runtimeMs === "number"
2078
+ && diagnostics.runtimeMs >= 0
2079
+ && diagnostics.runtimeMs < RAPID_EXIT_MS;
2080
+ }
2081
+
2082
+ // #1258 (L2): auto-resume-with-context gate for a tmux-session-ended death at any point in an
2083
+ // agent's life (not just the <30s early-flake window). Requires a captured resume id (the caller
2084
+ // only reaches this when one exists) and bounds crash loops via the shared unexpected-exit window:
2085
+ // once `recent` exceeds MAX_RAPID_UNEXPECTED_EXITS the agent is left offline for manual recovery.
2086
+ private shouldResumeWithContextProviderExit(diagnostics: Record<string, unknown>, recentUnexpectedExits: number): boolean {
2087
+ return diagnostics.exitSource === "tmux-session-ended"
2088
+ && recentUnexpectedExits <= MAX_RAPID_UNEXPECTED_EXITS;
2089
+ }
2090
+
2091
+ private async recoveredStatusAfterUndeterminedProviderExit(): Promise<"busy" | "idle" | undefined> {
2092
+ if (!this.process || !this.options.adapter.probeActivity) return undefined;
2093
+ const deadline = Date.now() + UNDETERMINED_PROVIDER_EXIT_CONFIRM_MS;
2094
+ do {
2095
+ const activity = await this.options.adapter.probeActivity(this.process).catch((error) => {
2096
+ this.sessionLog(`provider-exit confirmation probe failed: ${errMessage(error)}`);
2097
+ return "unknown" as const;
2098
+ });
2099
+ if (activity === "busy" || activity === "idle") return activity;
2100
+ if (Date.now() >= deadline) return undefined;
2101
+ await Bun.sleep(PROVIDER_EXIT_CONFIRM_POLL_MS);
2102
+ } while (!this.lifecycle.stopped && !this.lifecycle.exitCommandInProgress);
2103
+ return undefined;
2104
+ }
2105
+
2106
+ private async shutdownProvider(hard: boolean, timeoutMs = this.options.providerConfig.headless.shutdownTimeoutMs): Promise<ShutdownOutcome> {
2107
+ // Close provider-owned status sources before the graceful await, while keeping the runner's
2108
+ // own transitional projection live until the adapter confirms the outcome (#2505).
2109
+ this.lifecycle.armTerminal();
2110
+ this.lifecycle.setLifecycleAction(hard ? "killing" : "shutting-down");
2111
+ this.publishStatus();
2112
+ // #1746 — no process to kill is vacuously terminal. Otherwise the adapter now returns a
2113
+ // VERIFIED terminal outcome (it re-observes liveness, it does not trust the kill call);
2114
+ // a legacy adapter that returns void is treated as `killed` for back-compat.
2115
+ let outcome: ShutdownOutcome = "killed";
2116
+ if (this.process) {
2117
+ outcome = (await this.options.adapter.shutdown(this.process, { graceful: !hard, timeoutMs })) ?? "killed";
2118
+ }
2119
+ this.lifecycle.markStopped();
2120
+ if (outcome === "failed-to-kill") {
2121
+ // Do NOT clear to a normal terminal state: surface the failure so the operator sees a
2122
+ // "kill-failed" pill instead of an indefinite "shutting-down", and so the confirm-dead
2123
+ // gate in handleCommand keeps the agent on the roster rather than deregistering a zombie.
2124
+ this.lifecycle.setLifecycleAction("kill-failed");
2125
+ this.publishStatus();
2126
+ logger.error("lifecycle", `provider force-kill failed for ${this.agentId}: process survived SIGKILL; keeping agent registered`);
2127
+ } else {
2128
+ this.lifecycle.setLifecycleAction(undefined);
2129
+ this.claims.setTerminalStatus("offline");
2130
+ this.publishStatus();
2131
+ }
2132
+ return outcome;
2133
+ }
2134
+
2135
+ private publishRunnerTimelineEvent(event: RunnerTimelineEvent): void {
2136
+ this.context.publishRunnerTimelineEvent(event);
2137
+ }
2138
+
2139
+ private reportBackgroundRejection(scope: string, status: string, title: string, error: unknown): void {
2140
+ this.context.reportBackgroundRejection(scope, status, title, error);
2141
+ }
2142
+
2143
+ private handleBusError(code: string, message: string): void {
2144
+ const action = runnerBusErrorAction(code, this.lifecycle.stopped);
2145
+ if (action === "ignore") return;
2146
+ logger.error("bus", `bus error ${code}: ${message}`);
2147
+ if (action === "stop") {
2148
+ void this.stop().catch((error) => {
2149
+ logger.error("bus", `stop after bus error failed: ${error}`);
2150
+ }).finally(() => exitRunner(0, `bus error ${code}: ${message}`));
2151
+ }
2152
+ }
2153
+
2154
+ private setProviderStatus(update: ProviderStatusUpdate): void {
2155
+ // Once runner-owned teardown begins, every provider-originated frame is stale — including an
2156
+ // early offline hook. The runner keeps its transitional pill until adapter shutdown confirms
2157
+ // completion, then owns the single terminal projection itself.
2158
+ if (this.lifecycle.stopped || this.lifecycle.terminalStatusLatched) return;
2159
+ const status = typeof update === "string" ? update : update.status;
2160
+ this.lastProviderEventAt = Date.now();
2161
+ const providerSessionId = typeof update === "string" ? undefined : update.providerSessionId;
2162
+ if (providerSessionId && providerSessionId !== this.providerSessionId) return;
2163
+ const reason = typeof update === "string" ? "provider-turn" : update.reason ?? "provider-turn";
2164
+ if (status === "busy" && reason === "provider-turn" && this.deliverySubmitHold) this.tryRecoverProviderSubmitHold(true);
2165
+ let id = typeof update === "string" ? reason : update.id ?? reason;
2166
+ if (typeof update !== "string" && update.timeline) {
2167
+ this.pendingTimelineEvent = {
2168
+ status: update.timeline.status,
2169
+ ...(update.timeline.id ? { id: update.timeline.id } : {}),
2170
+ timestamp: update.timeline.timestamp ?? Date.now(),
2171
+ ...(update.timeline.title ? { title: update.timeline.title } : {}),
2172
+ ...(update.timeline.body ? { body: update.timeline.body } : {}),
2173
+ ...(update.timeline.icon ? { icon: update.timeline.icon } : {}),
2174
+ ...(update.timeline.metadata ? { metadata: update.timeline.metadata } : {}),
2175
+ };
2176
+ }
2177
+ let deferredRateLimitHold = false, openQueuedTurn = false;
2178
+ // #2619 — a turn the provider REFUSED (it ends in a rate-limit hold) did not run: it is neither
2179
+ // an empty turn to nudge (the nudge would hit the same wall) nor a completion of claimed work.
2180
+ // The relay's resume turn is the one that gets judged/credited.
2181
+ const providerRefusedTurn = status === "idle" && typeof update !== "string" && update.providerState?.state === "blocked" && update.providerState.reason === "rate_limit";
2182
+ if (typeof update !== "string" && update.providerState) {
2183
+ const ps = update.providerState;
2184
+ // A rate-limit hold persists across the idle the turn ends on; any other
2185
+ // providerState supersedes it (clears a stale hold).
2186
+ if (ps.state === "blocked" && ps.reason === "rate_limit") {
2187
+ const paneRateLimitHold = typeof ps.source === "string" && ps.source.startsWith(`${this.options.adapter.statusSourceId ?? this.options.provider}-pane`);
2188
+ if (status === "idle" && reason === "provider-turn" && paneRateLimitHold && this.claims.activeWork().some((work) => work.kind === "provider-turn")) {
2189
+ this.pendingRateLimitHold = update.providerState;
2190
+ this.providerBlocked = false;
2191
+ deferredRateLimitHold = true;
2192
+ this.sessionLog(`rate-limit hold deferred until active turn finishes (turn ${this.currentTurnId ?? "?"})`);
2193
+ } else {
2194
+ this.providerBlocked = true;
2195
+ this.pendingRateLimitHold = undefined;
2196
+ const fresh = !this.rateLimitHold;
2197
+ this.rateLimitHold = { ...update.providerState, holdEpisodeId: this.hold.refused() };
2198
+ for (const message of this.hold.reparkAcked(update.refusedInputIds ?? [])) {
2199
+ this.taskClaims.clearActiveClaim(message);
2200
+ this.settledMessageIds.delete(message.id);
2201
+ }
2202
+ if (fresh) this.publishRateLimitNotice(update.providerState);
2203
+ }
2204
+ } else if (ps.state === "blocked" && ps.reason === "pendingQuestion") {
2205
+ // #1173: set directly (never deferred) — unlike the rate-limit hold, this always
2206
+ // arrives while the turn that asked is still busy, well before the real idle it must
2207
+ // survive. Any other providerState (a fresh question, a plain approval, etc.) still
2208
+ // supersedes it via the `else` branch below.
2209
+ this.providerBlocked = true;
2210
+ this.pendingQuestionHold = update.providerState;
2211
+ } else {
2212
+ this.providerBlocked = ps.state === "blocked";
2213
+ this.pendingRateLimitHold = undefined;
2214
+ this.rateLimitHold = undefined;
2215
+ this.pendingQuestionHold = undefined;
2216
+ }
2217
+ } else if (status === "idle") {
2218
+ this.providerBlocked = false;
2219
+ }
2220
+ // After the refusal re-park above: a finished turn's input can no longer be refused.
2221
+ if ((status === "idle" && reason === "provider-turn" && !deferredRateLimitHold) || status === "error") this.hold.endTurn();
2222
+ // Forward progress (a real turn) lifts the hold so the blocked badge clears.
2223
+ if (status === "busy") this.rateLimitHold = undefined;
2224
+ if (deferredRateLimitHold) {
2225
+ if (typeof update !== "string") {
2226
+ for (const kind of update.clear ?? []) this.claims.clearWorkKind(kind);
2227
+ }
2228
+ this.publishStatus();
2229
+ return;
2230
+ }
2231
+ if (status === "error") this.emptyTurns.reset();
2232
+ if (typeof update !== "string" && status === "error") {
2233
+ const terminalReason = stringValue(update.metadata?.terminalFailureReason) ?? stringValue(update.providerState?.reason) ?? "provider-error";
2234
+ const terminalMessage = stringValue(update.metadata?.terminalFailureMessage) ?? stringValue(update.providerState?.message) ?? "Provider reported an unrecoverable error.";
2235
+ this.terminalFailure = {
2236
+ reason: terminalReason,
2237
+ message: terminalMessage,
2238
+ ...(update.providerState ? { providerState: update.providerState } : {}),
2239
+ };
2240
+ } else if (status === "idle" || status === "busy") {
2241
+ this.terminalFailure = undefined;
2242
+ }
2243
+ // Compaction lifecycle (`compacting` busy / `compacted` idle timeline) is separate
2244
+ // idle). A `compacted` idle for a turn that predated compaction is a hook artifact — the
2245
+ // turn resumes — so it must NOT end the turn. Discriminate on whether a turn was running,
2246
+ // captured BEFORE the busy logic below mints a currentTurnId (it does for /compact at idle).
2247
+ const timelineStatus = typeof update !== "string" ? update.timeline?.status : undefined;
2248
+ if (timelineStatus === "compacting") {
2249
+ this.compactionMidTurn = this.currentTurnId !== undefined;
2250
+ } else if (timelineStatus === "compacted") {
2251
+ // #3411 — a native compaction drops what area memory delivered, not what the dedupe table proves.
2252
+ this.areaMemory.resetGeneration();
2253
+ this.nativeSelfResume.noteCompacted(typeof update !== "string" && typeof update.timeline?.timestamp === "number" ? update.timeline.timestamp : Date.now());
2254
+ this.publishCompactionNotice();
2255
+ if (this.compactionMidTurn) {
2256
+ // Keep the turn (and its live reasoning tail) alive; the genuine Stop hook ends it.
2257
+ // pendingTimelineEvent (the marker) was set above and is consumed by publishStatus.
2258
+ this.sessionLog(`compaction completed mid-turn (turn ${this.currentTurnId ?? "?"}) — staying busy`);
2259
+ this.publishStatus();
2260
+ return;
2261
+ }
2262
+ }
2263
+ if (status === "busy" && reason === "provider-turn") {
2264
+ // #2482 — a launch-seeded, monitorless Claude turn can discover its transcript only after
2265
+ // the initial synthetic busy edge: Claude writes sessions/<pid>.json asynchronously after
2266
+ // its launcher gate opens. Accept a path on every busy edge, not just the edge that mints
2267
+ // the turn, so the terminal drain still knows where to settle the one/last turn's usage.
2268
+ const nativeTranscriptPath = typeof update !== "string" ? update.transcriptPath : undefined;
2269
+ const startNativeSessionCapture = Boolean(nativeTranscriptPath) && !this.sessionMirror.nativeCaptureActive();
2270
+ if (nativeTranscriptPath) this.lastTranscriptPath = nativeTranscriptPath;
2271
+ if (!this.currentTurnId) {
2272
+ // `update.id` carries a provider-native turn id when available. Falls back
2273
+ // to a random mint only when no native id was supplied.
2274
+ this.currentTurnId = typeof update !== "string" && update.id ? update.id : crypto.randomUUID();
2275
+ this.currentTurnStartedAt = Date.now();
2276
+ this.compactionMidTurn = false;
2277
+ this.emptyTurns.noteTurnStarted(this.currentTurnId, this.currentTurnStartedAt);
2278
+ this.turnReport.noteTurnStarted(this.currentTurnId);
2279
+ this.sessionLog(`turn started (turn ${this.currentTurnId})`);
2280
+ // #2913: never clear pendingQuestionHold here — the busy edge that sets it can mint a turn.
2281
+ // #1235 — a hookless (monitorless) adapter turn carries its live transcript path on the
2282
+ // busy edge. Drive the reasoning tail natively so lean/isolated Claude workers, which
2283
+ // never install the Stop/UserPromptSubmit hooks, still stream chat. Hook-driven agents
2284
+ // never set transcriptPath here, so their hook-triggered capture is untouched.
2285
+ }
2286
+ // startNativeSessionCapture snapshots sessionTurnContext synchronously. It must run after
2287
+ // the branch above mints currentTurnId so native reasoning/tool events retain their turn
2288
+ // correlation on the normal first-probe-success path.
2289
+ if (nativeTranscriptPath && startNativeSessionCapture) {
2290
+ void this.sessionMirror.startNativeSessionCapture(nativeTranscriptPath);
2291
+ }
2292
+ id = this.currentTurnId;
2293
+ this.busyReconciler.arm();
2294
+ } else if (status === "idle" && reason === "provider-turn") {
2295
+ id = typeof update !== "string" && update.id ? update.id : this.currentTurnId ?? reason;
2296
+ openQueuedTurn = this.queuedTurnFollows;
2297
+ this.queuedTurnFollows = false;
2298
+ // A watcher may resolve the transcript on its final idle edge, after its initial busy edge
2299
+ // raced Claude's session-probe creation. Preserve it before deciding whether native capture
2300
+ // is active, so terminal token-usage draining has the same late-bound path.
2301
+ const lateNativeTranscriptPath = typeof update !== "string" ? update.transcriptPath : undefined;
2302
+ if (lateNativeTranscriptPath) this.lastTranscriptPath = lateNativeTranscriptPath;
2303
+ if (this.currentTurnId) {
2304
+ this.completedProviderTurns += 1;
2305
+ this.sessionLog(`turn ended via provider idle (turn ${this.currentTurnId})`);
2306
+ // #2389 — "the provider says idle" is NOT the same as "the turn did something". Take the
2307
+ // verdict after a settle delay, because several capture paths publish the closing response
2308
+ // asynchronously, after this edge.
2309
+ //
2310
+ // A compaction or a context clear is exempt: both mint a provider-turn busy and end it with
2311
+ // an idle (codex.ts emits exactly that pair for `compacted` and `context-cleared`), so the
2312
+ // turn is genuinely output-free and means nothing is wrong. Discarding rather than judging
2313
+ // leaves the undischarged marker intact, so the real turn that follows is still judged.
2314
+ if (timelineStatus === "compacted" || timelineStatus === "context-cleared" || this.compactionMidTurn || providerRefusedTurn) this.emptyTurns.noteTurnDiscarded(this.currentTurnId);
2315
+ else this.emptyTurns.noteTurnEnded(this.currentTurnId, Date.now());
2316
+ }
2317
+ // #1235 — for a native (hookless) turn, capture the final assistant response from the
2318
+ // transcript BEFORE tearing down the tail. Snapshot the ctx while currentTurnId is still
2319
+ // set so the closing response is tagged with the correct turn.
2320
+ const nativeFinish = this.sessionMirror.nativeCaptureActive() || Boolean(lateNativeTranscriptPath);
2321
+ const nativeTranscriptPath = nativeFinish ? (lateNativeTranscriptPath ?? this.lastTranscriptPath) : undefined;
2322
+ const nativeCtx = nativeFinish ? { ...this.turnReport.sessionTurnContext(), opener: this.turnReport.takeTurnOpener(this.currentTurnId) ?? null } : undefined;
2323
+ this.currentTurnId = undefined;
2324
+ this.currentTurnStartedAt = undefined;
2325
+ this.compactionMidTurn = false;
2326
+ this.busyReconciler.disarm();
2327
+ if (nativeFinish) {
2328
+ this.sessionMirror.clearNativeSessionCapture();
2329
+ void this.sessionMirror.finishNativeSessionCapture(nativeTranscriptPath, nativeCtx!);
2330
+ } else {
2331
+ this.sessionMirror.stopReasoningTail();
2332
+ }
2333
+ // Providers with append-system-prompt policy use lifecycle hooks for obligation handling;
2334
+ // others need the runner to re-inject pending obligations on idle.
2335
+ // (#1614) — this delivery can suspend on the FIFO (#1607 N1) and, once resumed, reject if
2336
+ // the process died out from under it or the adapter is mid-turn; reported the same way
2337
+ // attemptInitialPromptDelivery reports its own adapter.deliver failures, so it can never
2338
+ // reach the top as an unhandled rejection (Bun exits(1) on those) and never vanish silently.
2339
+ // (#1617) folded onto the shared reportBackgroundRejection shape — same channel, same
2340
+ // status/title/body, one less hand-rolled copy of the reporting block.
2341
+ if (getManifest(this.options.provider)?.launch?.managedPolicyPrompt?.alwaysOn !== "append-system-prompt") {
2342
+ void this.reInjectPendingObligation().catch((error) => this.reportBackgroundRejection("re-inject pending obligation", "obligation.reinject_failed", "Pending reply obligation not re-injected", error));
2343
+ }
2344
+ }
2345
+ // #2389 — work of any other kind starting inside a live turn (a background script, tracked
2346
+ // subprocess work) is independent proof the model acted, even if nothing was ever mirrored.
2347
+ if (status === "busy" && reason !== "provider-turn" && this.currentTurnId) this.emptyTurns.noteOutput();
2348
+ // A genuine restart back to forward progress clears the terminal-exit marker so a later offline
2349
+ // doesn't re-announce a stale provider exit.
2350
+ if (status === "busy" || status === "idle") this.terminalProviderExit = undefined;
2351
+ if (status === "busy") {
2352
+ this.claims.clearTerminalStatus();
2353
+ // #650: liveness-track the pids a reporter explicitly supplied; otherwise, for a
2354
+ // provider-turn, fall back to the managed provider process itself. If that process
2355
+ // dies out-of-band with no exit event reaching the runner (crash/orphan/external
2356
+ // kill), the reconcile clears the stuck turn → idle. A dead pid means the turn is
2357
+ // definitively over, so this can never falsely clear a live turn (#653's hazard).
2358
+ const reportedPids = typeof update === "string" ? undefined : update.pids;
2359
+ // #1138: process.pid is undefined for tmux-launched providers (Claude) — only
2360
+ // the adapter can resolve a real pid from the tmux session/socket. Without this,
2361
+ // an ordinary hook-driven turn tracks no pid at all, so the stale-turn backstop
2362
+ // below has nothing but elapsed time to go on.
2363
+ const fallbackPid = reason === "provider-turn"
2364
+ ? (typeof this.process?.pid === "number" && this.process.pid > 0
2365
+ ? this.process.pid
2366
+ : this.process ? this.options.adapter.resolveBusyPid?.(this.process) : undefined)
2367
+ : undefined;
2368
+ const livenessPids = reportedPids?.length ? reportedPids : (fallbackPid ? [fallbackPid] : undefined);
2369
+ this.claims.startWork(reason, id, typeof update === "string" ? (livenessPids ? { pids: livenessPids } : {}) : {
2370
+ label: update.label,
2371
+ role: update.role,
2372
+ parentId: update.parentId,
2373
+ ...(livenessPids ? { pids: livenessPids } : {}),
2374
+ metadata: {
2375
+ ...(update.metadata ?? {}),
2376
+ ...(update.providerState ? { providerState: update.providerState } : {}),
2377
+ // #2034 (#2031 L3): stamp/preserve each background task's startedAt — the hook
2378
+ // can only ever report the current snapshot, so age has to be tracked here,
2379
+ // against the prior background-script work record. Pure merge lives in
2380
+ // background-script-reconcile.ts beside this record's other reconcile logic.
2381
+ ...(reason === "background-script" && Array.isArray(update.metadata?.backgroundTasks)
2382
+ ? { backgroundTasks: buildBackgroundTaskLedger(update.metadata.backgroundTasks, this.claims.activeWork().find((work) => work.kind === "background-script" && work.id === id)?.metadata?.backgroundTasks, Date.now()) }
2383
+ : {}),
2384
+ },
2385
+ });
2386
+ // A busy report with liveness-trackable pids arms the periodic reconcile (self-disarms
2387
+ // once no pid-bearing work remains, so idle/untracked agents pay nothing). #1856 — a
2388
+ // `background-script` busy also arms it, even with no pids: it is the ONLY signal that ever
2389
+ // runs the no-pid stale backstop that unwedges a leftover background-script (Claude's hook
2390
+ // can never supply pids for CC background tasks).
2391
+ if (livenessPids?.length || reason === "background-script") this.armBusyLivenessReconcile();
2392
+ if (reason === "provider-turn") {
2393
+ this.taskClaims.observeProviderBusy();
2394
+ }
2395
+ } else if (status === "idle") {
2396
+ this.claims.clearTerminalStatus();
2397
+ if (reason === "provider-turn") this.claims.clearWorkKind("provider-turn");
2398
+ else this.claims.finishWork(reason, id);
2399
+ if (reason === "provider-turn" && !providerRefusedTurn) {
2400
+ this.taskClaims.completeObservedTaskClaims().catch((error) => logger.error("task", `completion enqueue failed: ${error}`));
2401
+ // #2619 — the provider took a turn: the hold episode ends, parked input goes out. Not while
2402
+ // a hold is still standing or pending (a pane-detected wall can land before its turn's idle):
2403
+ // the published hold names this episode and its wake must still act.
2404
+ if (!this.rateLimitHold && !this.pendingRateLimitHold) {
2405
+ const released = this.hold.turnAccepted();
2406
+ for (const message of released) this.pendingMessages.set(message.id, message);
2407
+ if (released.length > 0) this.scheduleDrain();
2408
+ }
2409
+ }
2410
+ }
2411
+ else if (status === "offline" || status === "error") this.claims.setTerminalStatus(status);
2412
+ if (typeof update !== "string") {
2413
+ for (const kind of update.clear ?? []) this.claims.clearWorkKind(kind);
2414
+ }
2415
+ if (!this.claims.activeWork().some((work) => work.kind === "provider-turn")) this.busyReconciler.providerTurnCleared();
2416
+ // #329: the provider just went idle (ready). If a first initial-prompt delivery timed out on
2417
+ // a cold-start TUI, re-attempt now — the ready wait should resolve almost immediately.
2418
+ if (status === "idle") this.promptCommands.retryInitialPrompt();
2419
+ if (status === "idle" && this.pendingRateLimitHold && !this.claims.activeWork().some((work) => work.kind === "provider-turn")) {
2420
+ const hold = this.pendingRateLimitHold;
2421
+ this.pendingRateLimitHold = undefined;
2422
+ this.providerBlocked = true;
2423
+ const fresh = !this.rateLimitHold;
2424
+ this.rateLimitHold = { ...hold, holdEpisodeId: this.hold.refused() };
2425
+ if (fresh) this.publishRateLimitNotice(hold);
2426
+ }
2427
+ // #1146: native self-resume is invoked by the agent itself mid-turn (a tool call from
2428
+ // within its own active response), so the very next idle is almost always the CALLING
2429
+ // turn's own natural end — long before Claude Code even starts processing the queued
2430
+ // `/compact <continuation>`. Resolving on any idle here raced that unrelated end against
2431
+ // the real compaction and (de facto always) won, marking the command "succeeded" and
2432
+ // firing the continuation injection before compaction ever ran — a silent no-op. Gate on
2433
+ // `compactedAt` (stamped only by the genuine PostCompact "compacted" timeline event, via
2434
+ // noteCompacted) so this only resolves once compaction has actually happened.
2435
+ if (status === "idle" && this.nativeSelfResume.active?.compactedAt !== undefined) {
2436
+ this.nativeSelfResume.resolve(this.nativeSelfResume.active, { resumed: true, via: "provider-idle" });
2437
+ }
2438
+ if (openQueuedTurn) return this.openQueuedTurn();
2439
+ this.publishStatus();
2440
+ }
2441
+
2442
+ // #2919 — Claude runs a prompt it queued during the turn's end without a UserPromptSubmit of its own.
2443
+ // The ended turn is settled above; mint the queued turn now. Its tail starts with no turn id so its
2444
+ // steps carry Claude's fresh prompt id, the id its final gets from the Stop (#1086).
2445
+ private openQueuedTurn(): void {
2446
+ this.sessionLog("prompt queued behind the turn end — opening its turn");
2447
+ this.setProviderStatus({ status: "busy", reason: "provider-turn" });
2448
+ const { turnId: _, ...ctx } = this.turnReport.sessionTurnContext(), transcriptPath = this.lastTranscriptPath;
2449
+ if (transcriptPath) void fireAndForget("queued turn reasoning tail", () => this.options.adapter.handleUserPrompt?.({ prompt: "", transcriptPath }, ctx));
2450
+ }
2451
+
2452
+ private publishRunnerStatus(update: RunnerStatusFrame): boolean {
2453
+ // This is the only runner→bus status seam. Once completion pins the claims projection terminal,
2454
+ // no delayed runner callback may publish a non-terminal frame after that edge. During teardown
2455
+ // intent (before completion), runner-owned lifecycle pills are still allowed.
2456
+ const terminal = update.agentStatus === "offline" || update.agentStatus === "error";
2457
+ const projected = this.claims.currentStatus();
2458
+ if ((projected === "offline" || projected === "error") && !terminal) {
2459
+ return false;
2460
+ }
2461
+ // HumwickBusClient.status() only derives semanticStatus from idle/busy frames. Normalize every
2462
+ // accepted terminal frame here as well, otherwise a provider-reported offline edge leaves a
2463
+ // prior busy value alive and the next unsuspended heartbeat resurrects the row as busy.
2464
+ this.bus.setSemanticStatus(terminal ? "idle" : (update.agentStatus as "idle" | "busy"));
2465
+ this.bus.status(update);
2466
+ return true;
2467
+ }
2468
+
2469
+ // #417: when a provider finishes a turn with a pending spawner obligation still
2470
+ // unresolved, re-inject the obligation as a synthetic message so providerMessageText
2471
+ // surfaces the specific `humwick /reply <id>` command.
2472
+ private async reInjectPendingObligation(): Promise<void> {
2473
+ // Filter to spawner obligations only — user obligations go through the existing
2474
+ // prompt-capture path (publishProviderSessionEvent) and the session mirror (#418), never
2475
+ // an explicit /reply. Shared predicate so this rule stays in lockstep with
2476
+ // the stop-hook nag exemption (control-server.replyObligationStopDecision).
2477
+ const obligations = (await this.obligationCache.freshForNag()).filter(obligationNagEligible);
2478
+ if (!this.process) return;
2479
+ // #2619 — no re-injection into a provider hold: it would only be refused.
2480
+ if (this.rateLimitHold || this.hold.activeEpisode) return;
2481
+ if (!obligations.length) return;
2482
+ const MAX_REINJECTIONS = 3;
2483
+ for (const obligation of obligations) {
2484
+ // #2029 — the obligation carries the server's delivery decision. A non-`turn` obligation (wake:false
2485
+ // / session) must never be re-surfaced as a turn nudge: this re-injection path builds a synthetic
2486
+ // frame and calls `adapter.deliver` directly, BYPASSING the fanout seam, so the decision that would
2487
+ // otherwise be lost to the `payload: {}` reconstruction is honored explicitly here instead.
2488
+ if (obligation.deliverySurface && obligation.deliverySurface !== "turn") continue;
2489
+ const count = this.pendingObligationInjections.get(obligation.messageId) ?? 0;
2490
+ if (count >= MAX_REINJECTIONS) continue;
2491
+ this.pendingObligationInjections.set(obligation.messageId, count + 1);
2492
+ this.sessionLog(`re-injecting pending reply obligation #${obligation.messageId} from ${obligation.from} (attempt ${count + 1}/${MAX_REINJECTIONS})`);
2493
+ const replayMessage = {
2494
+ id: obligation.messageId,
2495
+ from: obligation.from,
2496
+ to: this.agentId,
2497
+ kind: obligation.kind,
2498
+ ...(obligation.subject ? { subject: obligation.subject } : {}),
2499
+ body: obligation.bodyPreview,
2500
+ payload: {},
2501
+ // No deliverySurface stamp needed on the replay frame: a non-`turn` obligation is `continue`d
2502
+ // above (and the server only ever stamps a non-`turn` surface), so every frame reaching here is a
2503
+ // plain `turn` re-injection. The decision was honored by the skip, not carried onto the frame.
2504
+ readBy: [] as string[],
2505
+ createdAt: obligation.createdAt,
2506
+ };
2507
+ // Suppress the userMessage echo that providers can reflect back when
2508
+ // a turn is started with relay-formatted content — the echo body is exactly what
2509
+ // providerMessageText produces, which starts with "[relay message #" and is caught
2510
+ // by the RELAY_INJECTION_MARKERS prefix check, but record it explicitly as
2511
+ // defense-in-depth for adapters that might echo only the unformatted body text.
2512
+ this.recordInjectedPrompt(providerMessageText([replayMessage]));
2513
+ // #1607 N1 — route through the FIFO like every other deliver/deliverInitialPrompt call site.
2514
+ // #2389 — a re-injected obligation is the runner asking for work the agent still owes, so it
2515
+ // arms — but as a RETRY, not a fresh ladder arm. This fires up to MAX_REINJECTIONS times per
2516
+ // message; opening a new strike budget each time would let one message earn four nudges and
2517
+ // keep consecutiveEmpty below the escalation threshold indefinitely, on exactly the agents
2518
+ // this feature exists to catch (an outstanding obligation plus empty turns).
2519
+ const arm = this.emptyTurns.armInput(Date.now(), "retry", true);
2520
+ let held = false;
2521
+ try {
2522
+ // #2619 — a refused re-injection spends no attempt and leaves the obligation standing.
2523
+ await this.offerMessagesToProvider(() => [replayMessage], () => {
2524
+ held = true;
2525
+ this.pendingObligationInjections.set(obligation.messageId, count);
2526
+ }, { ctx: { inputObserver: this.emptyTurns.inputObserver(arm) } });
2527
+ } catch (error) {
2528
+ this.emptyTurns.disarmInput(arm);
2529
+ throw error;
2530
+ }
2531
+ if (held) {
2532
+ this.emptyTurns.disarmInput(arm);
2533
+ this.sessionLog(`pending reply obligation #${obligation.messageId} re-injection refused under a provider hold; kept for the next idle`);
2534
+ break;
2535
+ }
2536
+ // Kick off a cache refresh so a cleared obligation (agent replied) is detected
2537
+ // before the next idle fires — without this, the stale snapshot re-injects up to
2538
+ // MAX_REINJECTIONS times even after the agent has already /reply-ed.
2539
+ this.obligationCache.markDirty();
2540
+ // One obligation at a time — let the provider reply before nudging again.
2541
+ break;
2542
+ }
2543
+ }
2544
+
2545
+ // A hook reported an unhandled failure (#198 seam). Already logged FATAL by the control
2546
+ // server; here we additionally surface it durably to the server as a generic insight so
2547
+ // it shows up in observability rather than only in the per-agent log (#196).
2548
+ private reportHookFatal(report: { hook: string; error: string }): void {
2549
+ try {
2550
+ this.insights.queueHookFatal(report);
2551
+ } catch (error) {
2552
+ logger.error("outbox", `failed to queue hook-fatal report: ${errMessage(error)}`);
2553
+ }
2554
+ }
2555
+
2556
+ // Repo-name project id for insight observations, resolved once by the insights collaborator.
2557
+ private insightProject(): string {
2558
+ return this.insights.project();
2559
+ }
2560
+
2561
+ private async captureContinuationArchive(reason: SessionDestroyReason, opts?: { transcriptPath?: string }): Promise<void> {
2562
+ await this.insights.captureContinuationArchive(reason, opts);
2563
+ }
2564
+
2565
+ private async captureContextRatio(reason: SessionDestroyReason, opts?: { transcriptPath?: string }): Promise<void> {
2566
+ await this.insights.captureContextRatio(reason, opts);
2567
+ }
2568
+
2569
+ // Remember an injected prompt so its UserPromptSubmit echo can be suppressed. Prunes
2570
+ // expired entries first; a defensive length cap guards against echoes that never
2571
+ // arrive (e.g. the provider drops a queued prompt) so the queue can't grow unbounded.
2572
+ private recordInjectedPrompt(text: string, imagePaths?: readonly string[]): void {
2573
+ const now = Date.now();
2574
+ this.injectedPrompts = this.injectedPrompts.filter((p) => now - p.at < PROMPT_ECHO_DEDUP_MS);
2575
+ this.injectedPrompts.push({ text, at: now, imagePaths });
2576
+ if (this.injectedPrompts.length > 50) this.injectedPrompts.shift();
2577
+ }
2578
+
2579
+ private isRunnerInjectedPrompt(text: string): boolean {
2580
+ if (RELAY_INJECTION_MARKERS.some((marker) => text.startsWith(marker))) return true;
2581
+ const now = Date.now();
2582
+ this.injectedPrompts = this.injectedPrompts.filter((p) => now - p.at < PROMPT_ECHO_DEDUP_MS);
2583
+ const matches = this.options.adapter.matchesSubmittedPrompt ?? ((echo: string, sent: string) => echo === sent);
2584
+ const idx = this.injectedPrompts.findIndex((p) => matches(text, p.text, p.imagePaths));
2585
+ if (idx !== -1) {
2586
+ this.injectedPrompts.splice(idx, 1); // consume one — identical repeats each match once
2587
+ return true;
2588
+ }
2589
+ return false;
2590
+ }
2591
+
2592
+ // Force-clear a stuck provider-turn claim directly. Unlike the idle status path
2593
+ // it does NOT depend on a matching claim id (the Stop hook keys busy as
2594
+ // provider-turn:provider-turn, but reconciliation has no specific id), and it
2595
+ // deliberately leaves the reasoning tail alone so a late clear can't truncate
2596
+ // a turn's activity stream.
2597
+ private forceClearProviderTurn(reason: string): void {
2598
+ const work = this.claims.activeWork().find((w) => w.kind === "provider-turn");
2599
+ if (!work) return;
2600
+ this.sessionLog(`force-clearing stuck provider-turn (${reason})`);
2601
+ // #2389 — the OTHER producer of `agent.turn_interrupted`, and the same reason the interrupt
2602
+ // command resets: a force-cleared turn ended with no output through no fault of the model, and
2603
+ // that state already has its own notice. Resetting also drops the turn record this path would
2604
+ // otherwise leave open forever, which would wedge the timed backstop.
2605
+ this.emptyTurns.reset();
2606
+ this.queuedTurnFollows = false;
2607
+ const event = reconciledTurnTimelineEvent({ turnId: this.currentTurnId ?? work.id, reason, startedAt: work.startedAt });
2608
+ this.claims.clearWorkKind("provider-turn");
2609
+ this.busyReconciler.providerTurnCleared();
2610
+ this.currentTurnId = undefined;
2611
+ this.compactionMidTurn = false;
2612
+ // #1621: the clear is EMITTED, not silently applied — publishRunnerTimelineEvent publishes
2613
+ // the status too, so the idle edge and its explanation travel in the same frame.
2614
+ this.publishRunnerTimelineEvent(event);
2615
+ }
2616
+
2617
+ // #2389 — the recovery. NOT a re-send: the instruction is already in the provider's session
2618
+ // history (which is why the stranded agent ran its original spawn prompt the moment a human typed
2619
+ // "hello" at it hours later), so this carries no copy of it. Delivered with origin "runner" so it
2620
+ // does NOT arm the ledger — a nudge that armed a fresh nudge budget would loop without bound
2621
+ // against a provider returning consecutive empty turns, which is the exact scenario this exists
2622
+ // for. The ledger's own one-per-input bound is the primary guard; this is the second.
2623
+ private async deliverEmptyTurnNudge(): Promise<void> {
2624
+ if (this.lifecycle.stopped || this.deliveringEmptyTurnNudge) return;
2625
+ if (!this.process || !this.options.adapter.deliverInitialPrompt) return;
2626
+ this.deliveringEmptyTurnNudge = true;
2627
+ try {
2628
+ this.recordInjectedPrompt(EMPTY_TURN_NUDGE);
2629
+ // Deliberately does NOT call armInput. That is the whole bound: the original input stays
2630
+ // undischarged (so the nudge's own turn is still judged, and a second empty turn escalates),
2631
+ // but the per-input `nudged` flag is not reset — so a provider returning empty turn after
2632
+ // empty turn gets exactly one nudge, never a nudge → empty → nudge runaway.
2633
+ // A refused nudge is simply not sent: it carries no content, and the hold's resume wake is
2634
+ // what continues the agent once the provider takes input again.
2635
+ const outcome = await this.offerPromptToProvider(EMPTY_TURN_NUDGE, { isInitial: false }, () => this.sessionLog("empty-turn nudge refused under a provider hold; the resume wake continues the agent"));
2636
+ if (outcome === "accepted") this.sessionLog("empty-turn nudge delivered (no prompt content re-sent)");
2637
+ } finally {
2638
+ this.deliveringEmptyTurnNudge = false;
2639
+ }
2640
+ }
2641
+
2642
+ // #1980 U2 — the busy backstop has HELD a live provider turn past its prolonged-hold threshold
2643
+ // instead of cancelling it (a frozen-active `shell` gate, or a pane-scrape-only long tool the
2644
+ // runner cannot positively prove idle). Emit a NOTIFY-ONLY watchdog: the turn is untouched (the
2645
+ // card stays busy, no work-kind is cleared, currentTurnId is preserved) — this only tells a
2646
+ // coordinator waiting on the turn that it is being held, not that it ended. The reconciler fires
2647
+ // this at most once per turn, so no de-dup is needed here.
2648
+ private escalateProviderTurnWatchdog(reason: string): void {
2649
+ const work = this.claims.activeWork().find((w) => w.kind === "provider-turn");
2650
+ if (!work) return;
2651
+ this.sessionLog(`busy backstop holding live provider-turn past threshold (${reason}); escalating watchdog notice`);
2652
+ this.publishRunnerTimelineEvent(providerTurnWatchdogTimelineEvent({
2653
+ turnId: this.currentTurnId ?? work.id,
2654
+ reason,
2655
+ startedAt: work.startedAt,
2656
+ }));
2657
+ }
2658
+
2659
+ // #650: reconcile pid-bearing busy work against actual process liveness. A tracked
2660
+ // command/shell that dies out-of-band (killed by the operator, crashes, orphaned)
2661
+ // sends no exit event, so its busy marker would otherwise never clear and the agent
2662
+ // shows `busy` forever — blocking transient auto-reap and holding provider quota.
2663
+ // #1856 — this timer ALSO runs the no-pid `background-script` backstop: Claude's Stop
2664
+ // hook can only post a pid-less `background-script` busy (its `background_tasks[]` payload
2665
+ // exposes no OS pid), which #650's pid reconcile can never touch. So the timer is armed for
2666
+ // ANY background-script busy too, and stays armed while one exists. It self-disarms once
2667
+ // neither pid-bearing work nor a background-script busy remains, so idle agents pay nothing.
2668
+ private armBusyLivenessReconcile(): void {
2669
+ if (this.livenessReconcileTimer || this.lifecycle.stopped) return;
2670
+ this.livenessReconcileTimer = setInterval(() => this.runBusyLivenessReconcile(), BUSY_LIVENESS_RECONCILE_MS);
2671
+ }
2672
+
2673
+ private disarmBusyLivenessReconcile(): void {
2674
+ if (this.livenessReconcileTimer) clearInterval(this.livenessReconcileTimer);
2675
+ this.livenessReconcileTimer = undefined;
2676
+ }
2677
+
2678
+ private runBusyLivenessReconcile(): void {
2679
+ if (this.lifecycle.stopped) {
2680
+ this.disarmBusyLivenessReconcile();
2681
+ return;
2682
+ }
2683
+ let changed = false;
2684
+ const removed = this.claims.reconcileLiveness(isPidAlive);
2685
+ if (removed.length > 0) {
2686
+ changed = true;
2687
+ this.sessionLog(`cleared ${removed.length} stuck busy work item(s) whose tracked process died out-of-band: ${removed.map((w) => `${w.kind}:${w.id}`).join(", ")}`);
2688
+ // A cleared provider-turn must also reset the turn bookkeeping so a later idle
2689
+ // doesn't double-end it and the reasoning tail stops.
2690
+ if (removed.some((w) => w.kind === "provider-turn")) {
2691
+ this.currentTurnId = undefined;
2692
+ this.currentTurnStartedAt = undefined;
2693
+ this.compactionMidTurn = false;
2694
+ this.busyReconciler.disarm();
2695
+ this.sessionMirror.stopReasoningTail();
2696
+ this.busyReconciler.providerTurnCleared();
2697
+ }
2698
+ }
2699
+ // #1856 — the no-pid backstop: a leftover `background-script` busy that outlived its work
2700
+ // (background task finished or was killed manually) leaves the agent wedged busy forever,
2701
+ // because it carries no pid to reconcile and only the next turn's Stop hook would clear it.
2702
+ // Once the busy state is background-script ALONE (the provider turn already ended) and has
2703
+ // gone quiet past the stale window, force-clear it so the agent reconciles to idle on its
2704
+ // own — no human nudge, and the held turn-final report can flush on the resulting reap.
2705
+ if (isStaleBackgroundScriptOnlyBusy({ activeWork: this.claims.activeWork(), lastProgressAt: this.lastProviderEventAt, now: Date.now(), isPidAlive })) {
2706
+ changed = true;
2707
+ this.sessionLog("clearing stale background-script busy: provider idle past the stale window with no live background work — reconciling to idle");
2708
+ this.claims.clearWorkKind("background-script");
2709
+ }
2710
+ if (changed) this.publishStatus();
2711
+ // Stay armed while a background-script busy remains (its no-pid backstop above needs the timer);
2712
+ // otherwise self-disarm once no pid-bearing work is left, so idle agents pay nothing.
2713
+ if (!this.claims.hasLivenessTrackedWork() && !this.claims.activeWork().some((work) => work.kind === "background-script")) this.disarmBusyLivenessReconcile();
2714
+ }
2715
+
2716
+ // Mirror a discreet, durable "context compacted" marker into chat via the existing
2717
+ // session-mirror lane (not a parallel channel). `notice` renders as an inline timeline
2718
+ // marker (never a bubble) and survives reload, unlike the ephemeral timeline-status one.
2719
+ private publishCompactionNotice(): void {
2720
+ this.sessionMirror.publishSessionEvent({
2721
+ from: this.agentId,
2722
+ to: "user",
2723
+ body: "🗜 Context compacted",
2724
+ session: { type: "notice", origin: "provider", label: "compacted", ...(this.currentTurnId ? { turnId: this.currentTurnId } : {}) },
2725
+ });
2726
+ }
2727
+
2728
+ private publishPermissionAbandonedNotice(input: PendingPermissionRequestRecord & { reason: string }): void {
2729
+ const title = input.view.title || "permission request";
2730
+ this.sessionMirror.publishSessionEvent({
2731
+ from: this.agentId,
2732
+ to: "user",
2733
+ body: `Permission request denied after runner restart: ${title}. ${input.reason}`,
2734
+ occurredAt: Date.now(),
2735
+ session: {
2736
+ type: "notice",
2737
+ origin: "provider",
2738
+ label: "permission-denied",
2739
+ stepId: `permission-abandoned:${input.approvalId}`,
2740
+ ...(this.currentTurnId ? { turnId: this.currentTurnId } : {}),
2741
+ },
2742
+ });
2743
+ }
2744
+
2745
+ // #286: a discreet, durable chat marker when a usage/rate-limit hold begins, via
2746
+ // the same session-mirror lane as the compaction notice. Outbound session event
2747
+ // (NOT an inbound message) so it shows in the dashboard chat WITHOUT waking a turn
2748
+ // — waking a still-limited agent would just re-fail. The relay sends the waking
2749
+ // resume message later, once the window has reset.
2750
+ private publishRateLimitNotice(providerState: ProviderState): void {
2751
+ const label = providerState.label || "usage limit reached";
2752
+ this.sessionMirror.publishSessionEvent({
2753
+ from: this.agentId,
2754
+ to: "user",
2755
+ body: `⏳ ${label} — ${providerState.kind === PROXY_AUTH_HOLD_KIND ? "holding until its Vault access is fixed and the agent restarts." : "holding; Humwick will auto-resume at reset."}`,
2756
+ session: { type: "notice", origin: "provider", label: "rate-limit", ...(this.currentTurnId ? { turnId: this.currentTurnId } : {}) },
2757
+ });
2758
+ }
2759
+
2760
+ private isStaleProviderTurnOnlyBusy(
2761
+ status: SemanticStatus,
2762
+ activeWork: Array<{ kind: string; pids?: number[] }>,
2763
+ busyReasons: string[],
2764
+ liveness: { hasLiveWork: boolean; lastProgressAt: number },
2765
+ ): boolean {
2766
+ if (status !== "busy") return false;
2767
+ if (activeWork.length === 0 || activeWork.some((work) => work.kind !== "provider-turn")) return false;
2768
+ if (busyReasons.some((reason) => reason !== "provider-turn")) return false;
2769
+ if (liveness.hasLiveWork || !liveness.lastProgressAt) return false;
2770
+ // #1138: a turn whose tracked pid is still alive is definitively still running — a
2771
+ // silent single Claude turn (no subagents, no compaction event) must not be
2772
+ // force-healed to idle purely because it's been quiet past the threshold. Only fall
2773
+ // back to the elapsed-time heuristic when there's no pid evidence to check; the #650
2774
+ // reconcile independently clears a confirmed-dead pid's work the instant it dies, so
2775
+ // this can never mask a genuine leak.
2776
+ if (activeWork.some((work) => work.pids?.some((pid) => isPidAlive(pid)))) return false;
2777
+ return Date.now() - liveness.lastProgressAt >= STALE_PROVIDER_TURN_BUSY_MS;
2778
+ }
2779
+
2780
+ private readonly tokenUsageCoverage = new TokenUsageReportCoverage(() => this.publishStatus());
2781
+ private publishStatus(): void {
2782
+ this.claims.expire();
2783
+ const status = this.claims.currentStatus();
2784
+ const activeWork = this.claims.activeWork();
2785
+ const activeSubagents = activeWork.filter((item) => item.kind === "subagent");
2786
+ const terminalFailure = this.terminalFailure;
2787
+ const timelineEvent = this.pendingTimelineEvent;
2788
+ // §4.1 Runner↔provider liveness contract (#725 / #746): fold the raw transport/timeline/
2789
+ // token/work-kind signals into ONE normalized verdict the server reads instead of six raw
2790
+ // `meta` fields. The adapter owns what counts as progress for its provider (#713/#724);
2791
+ // mock adapters fall back to the shared fold.
2792
+ const livenessInputs: LivenessInputs = {
2793
+ transport: this.bus.transportState,
2794
+ ...(this.lastProviderEventAt !== undefined ? { lastProviderEventAt: this.lastProviderEventAt } : {}),
2795
+ ...(timelineEvent ? { timelineEvent } : {}),
2796
+ activeWork,
2797
+ // #1980 U3 — feed the runner's coarse status + provider-hold flag so the fold can derive
2798
+ // busyPhase (active/passive/blocked) beside hasLiveWork, provider-agnostically.
2799
+ providerStatus: status,
2800
+ providerBlocked: this.providerBlocked || this.startupPaneDialog !== undefined || this.deliverySubmitHold !== undefined,
2801
+ };
2802
+ const liveness = this.options.adapter.liveness
2803
+ ? this.options.adapter.liveness(livenessInputs)
2804
+ : computeLivenessSignal(livenessInputs);
2805
+ const busyReasons = this.claims.reasons();
2806
+ const staleProviderTurnBusy = this.isStaleProviderTurnOnlyBusy(status, activeWork, busyReasons, liveness);
2807
+ const effectiveStatus = staleProviderTurnBusy ? "idle" : status;
2808
+ // Teardown keeps the process registered but deliberately non-addressable until its outcome is
2809
+ // confirmed. Project every teardown pill through this one seam as idle+ready:false; if provider
2810
+ // state was already terminal, keep that terminal frame so the shared publish guard cannot drop it.
2811
+ const teardownLifecycle = this.lifecycle.lifecycleAction === "shutting-down"
2812
+ || this.lifecycle.lifecycleAction === "killing"
2813
+ || this.lifecycle.lifecycleAction === "kill-failed";
2814
+ const agentStatus = teardownLifecycle && effectiveStatus !== "offline" && effectiveStatus !== "error"
2815
+ ? "idle"
2816
+ : runnerAgentStatus(effectiveStatus);
2817
+ // #2389 — `emptyTurnState` is the LAST fallback: a live hold, a pending question or real active
2818
+ // work all describe the agent better than "the previous turn was empty", and any of them means
2819
+ // it is no longer sitting idle having produced nothing.
2820
+ const providerState = this.deliverySubmitHold ?? terminalFailure?.providerState ?? this.startupPaneDialog ?? this.rateLimitHold ?? this.pendingQuestionHold
2821
+ ?? (staleProviderTurnBusy ? null : providerStateFromActiveWork(activeWork))
2822
+ ?? (activeWork.length === 0 ? this.emptyTurns.providerState() ?? null : null);
2823
+ const published = this.publishRunnerStatus({
2824
+ agentStatus,
2825
+ ready: agentStatus !== "offline" && !this.lifecycle.stopped && !teardownLifecycle && !this.startupPaneDialog && !this.deliverySubmitHold,
2826
+ meta: {
2827
+ ...runnerIdentityMeta(this.options, { tmuxSession: providerTerminalSession(this.process), tmuxSocket: providerTerminalSocket(this.process), auth: { profileId: this.credentials.profileId, jti: this.credentials.jti, expiresAt: this.credentials.expiresAt } }, "status"),
2828
+ lifecycleAction: this.lifecycle.lifecycleAction ?? null,
2829
+ lifecycleActionAt: this.lifecycle.lifecycleActionAt ?? null,
2830
+ ...(status === "error" ? { terminalStatus: "error" } : {}),
2831
+ ...(terminalFailure ? {
2832
+ lastError: terminalFailure.message,
2833
+ terminalFailureReason: terminalFailure.reason,
2834
+ terminalFailureMessage: terminalFailure.message,
2835
+ } : {}),
2836
+ // #633 — terminal provider-exit marker (runner alive, provider gone). Drives the server's
2837
+ // silent-death terminal event + #636 post-mortem.
2838
+ ...(this.terminalProviderExit ? {
2839
+ terminalProviderExit: this.terminalProviderExit,
2840
+ lastError: providerExitLastError(this.options.provider, this.terminalProviderExit),
2841
+ } : {}),
2842
+ busyReasons: staleProviderTurnBusy ? busyReasons.filter((reason) => reason !== "provider-turn") : busyReasons,
2843
+ completedProviderTurns: this.completedProviderTurns,
2844
+ // #2389 — machine-readable beside the displayed providerState, so a coordinator polling the
2845
+ // roster can tell "idle having produced nothing" from "idle having finished work" without
2846
+ // reading the timeline. 0 once any turn produces output again.
2847
+ consecutiveEmptyTurns: this.emptyTurns.consecutiveEmptyTurns(),
2848
+ unreportedTokenUsage: this.tokenUsageCoverage.unreported, // #3351
2849
+ activeWork,
2850
+ activeSubagents,
2851
+ activeSubagentCount: activeSubagents.length,
2852
+ ...(this.lastProviderEventAt !== undefined ? { lastProviderEventAt: this.lastProviderEventAt } : {}),
2853
+ providerState,
2854
+ // #2365 — this is a one-shot marker. An explicit null lets Relay's merge-patch status
2855
+ // ingress delete the previously consumed event on the next frame instead of auditing the
2856
+ // same latched watchdog transition for every subsequent provider update.
2857
+ timelineEvent: timelineEvent ?? null,
2858
+ transport: this.bus.transportState,
2859
+ // #746 — normalized liveness verdict; the server reads ONLY this for stuck-busy.
2860
+ // The raw fields above stay for other consumers (timeline rendering, diagnostics).
2861
+ liveness,
2862
+ },
2863
+ });
2864
+ // A dropped post-terminal frame must defer its timeline evidence, not consume it. Clear only
2865
+ // the exact event carried by a successfully published frame; a newer event wins the race.
2866
+ if (published && this.pendingTimelineEvent === timelineEvent) this.pendingTimelineEvent = undefined;
2867
+ }
2868
+
2869
+ private startHttpLiveness(): void { this.httpLiveness.start(); }
2870
+
2871
+ private logRunnerDiagnostic(message: string): void { this.context.logRunnerDiagnostic(message); }
2872
+ private sessionLog(message: string): void { this.context.sessionLog(message); }
2873
+ private sessionDebug(message: string): void { this.context.sessionDebug(message); }
2874
+
2875
+ private ensureScratch(): void {
2876
+ try {
2877
+ this.scratch = ensureSessionScratch({
2878
+ agentId: this.agentId,
2879
+ cwd: this.options.cwd,
2880
+ fallbackBaseDir: orchestratorBaseDirFromEnv(),
2881
+ });
2882
+ } catch (error) {
2883
+ this.logRunnerDiagnostic(`session scratch setup failed: ${errMessage(error)}`);
2884
+ }
2885
+ }
2886
+
2887
+ private scheduleAttachmentCacheSweep(): void { this.attachmentCacheSweep.start(); }
2888
+
2889
+ // #1245 — a newly allowed tool group or scope reaches the next generation without a restart.
2890
+ private remintRuntimeTokenAtBoundary(reason: "clear" | "compact"): Promise<Record<string, unknown>> {
2891
+ return this.credentials.remintRuntimeTokenAtBoundary(reason);
2892
+ }
2893
+
2894
+ private heartbeatRuntimeMeta(): Record<string, unknown> | undefined {
2895
+ const processContext = this.latestProcessContext();
2896
+ const probeMetrics = this.latestProbeMetrics();
2897
+ const probeContext = probeMetrics ? contextStateFromProbeMetrics(probeMetrics) : undefined;
2898
+ let context = processContext ?? probeContext;
2899
+ // #1512: when a provider has no event-stream context of its own, fall back to
2900
+ // the transcript so context.lastUpdatedAt keeps advancing after an in-process
2901
+ // compact/clear/native-resume freezes the status-line probe. Freshest wins, so
2902
+ // a live probe still takes precedence (source stays "statusline").
2903
+ if (!processContext) {
2904
+ const derived = this.sessionMirror.transcriptContext(probeMetrics?.tokensMax);
2905
+ if (derived && (!probeContext || derived.lastUpdatedAt > probeContext.lastUpdatedAt)) {
2906
+ context = derived;
2907
+ }
2908
+ }
2909
+ // #2095 test rig — override the reported context with the forced synthetic while the one-shot
2910
+ // latch is armed. Applied AFTER the real context is resolved (so tokensMax/window carry over) and
2911
+ // BEFORE providerCapabilities + meta.context are built from it, so the server ingests the forced
2912
+ // utilization and its REAL lifecycle tick drives the REAL advisory + forced clear-inject.
2913
+ context = this.registration.applyForcedContext(context);
2914
+ const quota = probeMetrics ? quotaStateFromProbeMetrics(probeMetrics) : undefined;
2915
+ const terminalSession = providerTerminalSession(this.process);
2916
+ const terminalSocket = providerTerminalSocket(this.process);
2917
+ const probeModel: ProbeModelInfo | undefined = probeMetrics?.model || probeMetrics?.effort
2918
+ ? { model: probeMetrics.model, effort: probeMetrics.effort }
2919
+ : undefined;
2920
+ const meta: Record<string, unknown> = {
2921
+ providerCapabilities: runtimeProviderCapabilities({ ...this.options, effort: this.effortRuntime.effort, effortSwitch: runnerEffortSwitch(this.options.adapter, this.process) }, context, probeModel, quota),
2922
+ ...runnerIdentityMeta(this.options, { tmuxSession: terminalSession, tmuxSocket: terminalSocket }, "heartbeat"),
2923
+ };
2924
+ if (context) meta.context = context;
2925
+ return meta;
2926
+ }
2927
+
2928
+ private latestProcessContext(): ContextState | undefined {
2929
+ const getContext = this.process?.meta?.getContext;
2930
+ if (typeof getContext === "function") {
2931
+ const context = getContext();
2932
+ if (isContextState(context)) return context;
2933
+ }
2934
+ const context = this.process?.meta?.context;
2935
+ return isContextState(context) ? context : undefined;
2936
+ }
2937
+
2938
+ private latestProbeMetrics() {
2939
+ return readRunnerContextProbeState(this.agentId);
2940
+ }
2941
+
2942
+ private matchesMessage(message: Message): boolean {
2943
+ return runnerMessageMatches(message, {
2944
+ agentId: this.agentId,
2945
+ label: this.options.label,
2946
+ provider: this.options.provider,
2947
+ tags: this.options.tags,
2948
+ capabilities: this.options.capabilities,
2949
+ defaultTags: this.options.providerConfig.defaultTags,
2950
+ defaultCapabilities: this.options.providerConfig.defaultCapabilities,
2951
+ allowBroadTargets: this.options.agentProfile?.relay?.context !== false,
2952
+ });
2953
+ }
2954
+
2955
+ }