@overdeck/desktop 0.45.15 → 0.45.18

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 (372) hide show
  1. package/dist-electron/main.js +149 -158
  2. package/dist-electron/main.js.map +1 -1
  3. package/dist-electron/preload.js +17 -2
  4. package/dist-electron/preload.js.map +1 -1
  5. package/package.json +5 -4
  6. package/server/acceptance-criteria-aQFKQf-J.js +62 -0
  7. package/server/{advancing-selfheal-CSuZ31bB.js → advancing-selfheal-DgwQgm4D.js} +6 -5
  8. package/server/{agent-gc-D_DxfBHR.js → agent-gc-kpzxqT7P.js} +3 -3
  9. package/server/agent-state-C_6TG-Ad.js +3 -0
  10. package/server/{agent-state-service-C_2ZAQRP.js → agent-state-service-BUfovFfB.js} +1 -1
  11. package/server/{agent-state-service-BZkvgQf4.js → agent-state-service-nUD3qfob.js} +9 -52
  12. package/server/agents-BA15AQqo.js +3 -0
  13. package/server/{briefing-freshness-Kh5V8RTh.js → agents-BHBxr8c9.js} +5 -131
  14. package/server/{archive-planning-DkivCk1A.js → archive-planning-CzCW9Ifl.js} +5 -10
  15. package/server/{archive-planning-CTILQRnV.js → archive-planning-D-QsBufn.js} +1 -1
  16. package/server/{artifact-index-jobs-CmsvQ1ey.js → artifact-index-jobs-qqa51B2Z.js} +2 -2
  17. package/server/{backlog-auto-trigger-ueRaT8Dy.js → backlog-auto-trigger-B7Bi0bvY.js} +3 -3
  18. package/server/behavior-CWqNMqE5.js +10 -0
  19. package/server/briefing-freshness-DuxIE1yr.js +131 -0
  20. package/server/{checkpoint-manager-fu9oVsR-.js → checkpoint-manager-DF6hb9Jw.js} +3 -3
  21. package/server/{checkpoint-manager-CAFos8e1.js → checkpoint-manager-DixkLus7.js} +1 -1
  22. package/server/checkpoint-worker.js +1 -1
  23. package/server/{checkpoints-DvhGi8P_.js → checkpoints-B1Z42tJE.js} +1 -1
  24. package/server/{checkpoints-1BqOlS4q.js → checkpoints-BiviJqei.js} +2 -2
  25. package/server/claude-hooks-provision-BeMGi4ty.js +3 -0
  26. package/server/{claude-hooks-provision-s_wdpeVj.js → claude-hooks-provision-Du_PkMsp.js} +132 -114
  27. package/server/{claude-permissions-CaWqysff.js → claude-permissions-BB6agcOZ.js} +2 -2
  28. package/server/{claude-settings-overlay-CcTf0xzG.js → claude-settings-overlay-CAq2oFSK.js} +2 -2
  29. package/server/{claude-settings-overlay-BbA4FbVI.js → claude-settings-overlay-DUWWLd6M.js} +1 -1
  30. package/server/cli-BFgrSWKV.js +3 -0
  31. package/server/{cli-BwCvCUBK.js → cli-C33xPZNm.js} +9 -9
  32. package/server/{cliproxy-C2xC-3vH.js → cliproxy-BQX1TL6N.js} +1 -1
  33. package/server/{cliproxy-BsoNNDAj.js → cliproxy-CGBA1GzA.js} +2 -2
  34. package/server/{close-issue-BleIwP3f.js → close-issue-DNv8npc1.js} +5 -5
  35. package/server/{closed-issue-reaper-BwUUvjEW.js → closed-issue-reaper-DrqgBJlr.js} +1 -1
  36. package/server/{closed-issue-reaper-mC5pujeb.js → closed-issue-reaper-LoMnnjtJ.js} +9 -9
  37. package/server/codex-DxLivxri.js +3 -0
  38. package/server/{behavior-Cpj03N4v.js → codex-parser-DboQnEXN.js} +3 -9
  39. package/server/{codex-DW60_5j2.js → codex-sgWVCGUd.js} +43 -17
  40. package/server/{config-BRNQJXhA.js → config-6MCHOZ2N.js} +1 -1
  41. package/server/{config-ZNnMpIlh.js → config-DfaUXr6r.js} +2 -2
  42. package/server/{config-yaml-Dcz-8uit.js → config-yaml-CqaAyn8y.js} +149 -80
  43. package/server/{conflict-gate-DTje2Ll7.js → conflict-gate-BF_zhcmb.js} +3 -3
  44. package/server/{conflict-gate-DGT8jCLp.js → conflict-gate-BubJprtD.js} +1 -1
  45. package/server/{context-layers-BdUCQ-cl.js → context-layers-BFrxfl0l.js} +17 -3
  46. package/server/{context-layers-ByOWUjUg.js → context-layers-DWL_8HhY.js} +2 -2
  47. package/server/{control-settings-DwkAeooY.js → control-settings-BguNQ1F_.js} +4 -4
  48. package/server/{conversation-compaction-WYQZUVDm.js → conversation-compaction-CRKAf2qP.js} +1 -1
  49. package/server/{conversation-compaction-OTYgd6eg.js → conversation-compaction-DxSXl0pT.js} +8 -8
  50. package/server/{conversation-service-C_kNZPLx.js → conversation-service-DzuY9qoH.js} +1 -1
  51. package/server/{conversation-service-BR1OgvxO.js → conversation-service-mFVFdDQK.js} +6 -5
  52. package/server/{conversations-DG2M8WnT.js → conversations-BX5aD-6F.js} +4 -4
  53. package/server/{conversations-izHvSoAR.js → conversations-BajHQ60c.js} +1 -1
  54. package/server/{cost-DQ2ajr_m.js → cost-BoqD1Pxz.js} +2 -2
  55. package/server/{cost-B2mj17iG.js → cost-DuZ5vlv-.js} +3 -3
  56. package/server/{cost-sync-gTayEuWP.js → cost-sync-BYnmnMkY.js} +3 -3
  57. package/server/{dag-CqTehWpY.js → dag-C6kYyF17.js} +7 -243
  58. package/server/{dangerous-git-ops-DDWQlPC3.js → dangerous-git-ops-q5OPtXMd.js} +1 -1
  59. package/server/dashboard-db-worker.js +6 -6
  60. package/server/{deacon-BqW9HMew.js → deacon-CwCiNuEZ.js} +3 -3
  61. package/server/{deacon-DrKDBeJL.js → deacon-Dru5onpk.js} +258 -167
  62. package/server/{deacon-nudge-log-D1wEK6zF.js → deacon-nudge-log-ilDfxrhK.js} +1 -1
  63. package/server/{deacon-swarm-CO_A7xyd.js → deacon-swarm-BPmBmSoU.js} +298 -72
  64. package/server/deacon-swarm-CKyGJng1.js +2 -0
  65. package/server/deacon.js +14 -7
  66. package/server/{devcontainer-renderer-BQolp_qZ.js → devcontainer-renderer-BqGNCzvN.js} +3 -3
  67. package/server/{dist-Bc32rWJB.js → dist-BwlEiLCU.js} +1 -1
  68. package/server/dist-Bx4KkgYL.js +3 -0
  69. package/server/{dist-src-Byxt1ifL.js → dist-src-BE0Denfs.js} +1 -1
  70. package/server/{docker-s-esqZ__.js → docker-CNjXS72x.js} +2 -2
  71. package/server/{ensure-devcontainer-DA4-jF6R.js → ensure-devcontainer-5e9xOve4.js} +4 -4
  72. package/server/{ensure-devcontainer-pUVaT0h3.js → ensure-devcontainer-C6gyUQdE.js} +1 -1
  73. package/server/{esm-shims-CJHy8CZY.js → esm-shims-Cso7IGBC.js} +1 -1
  74. package/server/{event-store-CcJAhKCl.js → event-store-D9UhQyMS.js} +1 -1
  75. package/server/{event-store-DRte2Rwt.js → event-store-KZnWUKWh.js} +3 -3
  76. package/server/{events-Bej1w_k8.js → events-2eFUPdqz.js} +3 -3
  77. package/server/{events-CtsLScn8.js → events-BYbvqZky.js} +1 -1
  78. package/server/{factory-FN727tWr.js → factory-BtaRsHFC.js} +20 -17
  79. package/server/{feature-registry-population-ie-yBcQC.js → feature-registry-population-Ca2UaV0U.js} +4 -4
  80. package/server/{feature-registry-storage-CkZTFSsn.js → feature-registry-storage-DaP-bQxO.js} +2 -2
  81. package/server/{features-Dtm9ABuz.js → features-DqvaoQoa.js} +2 -2
  82. package/server/{feedback-target-BYtpyt1i.js → feedback-target-BXWdOiYI.js} +9 -7
  83. package/server/{feedback-target-DVJQDiE4.js → feedback-target-BblmMe9P.js} +1 -1
  84. package/server/{feedback-writer-DOUhyIW2.js → feedback-writer-CULeOOY4.js} +1 -1
  85. package/server/{feedback-writer-fdE1BzeL.js → feedback-writer-Dn8o9ZyE.js} +4 -4
  86. package/server/{flywheel-Bi82iiNl.js → flywheel-BUWdcfhq.js} +1 -1
  87. package/server/{flywheel-Bv3wv2S6.js → flywheel-CqkO3cDj.js} +15 -28
  88. package/server/{flywheel-CUErLAyN.js → flywheel-D9r1mw2U.js} +13 -13
  89. package/server/{flywheel-merge-order-DXjeBnJ1.js → flywheel-merge-order-LDzQDwSg.js} +1 -1
  90. package/server/{flywheel-merge-order-BaEN3iyN.js → flywheel-merge-order-VtjUweEb.js} +7 -7
  91. package/server/forge-DJy3Zx4s.js +2 -0
  92. package/server/{forge-ButEdJ31.js → forge-wEollMCi.js} +22 -12
  93. package/server/{from-CbtTxftl.js → from-B2tOYhYV.js} +1 -1
  94. package/server/{fts-db-CBgBLZcL.js → fts-db-Bgzn2RO7.js} +2 -2
  95. package/server/{fts-operations-CI9Xuo_1.js → fts-operations-Bj8yutnR.js} +2 -2
  96. package/server/{git-utils-ClfpP47N.js → git-utils-GxlL0tmt.js} +2 -2
  97. package/server/{git-utils-BCLRWQ-y.js → git-utils-q8jNoBts.js} +1 -1
  98. package/server/{github-app-DRD2woO0.js → github-app-BrYwK_Gb.js} +1 -1
  99. package/server/{github-app-DscZ0_B6.js → github-app-tUbb8Ldr.js} +8 -3
  100. package/server/{github-client-DpiZnxGH.js → github-client-BwBU41jK.js} +3 -3
  101. package/server/{github-client-iFRw8f7e.js → github-client-D6CNsftm.js} +1 -1
  102. package/server/{github-pr-lookup-D-EBexxC.js → github-pr-lookup-BS5PqkJb.js} +2 -2
  103. package/server/{harness-resolve-iMjyNUJ2.js → harness-resolve-Dx8hUsvC.js} +3 -3
  104. package/server/{health-events-CRdxDFN2.js → health-events-2DLp-5Xp.js} +2 -2
  105. package/server/{health-events-B_WsQyYm.js → health-events-B0mj6Iae.js} +1 -1
  106. package/server/{hooks-C1ScyVxP.js → hooks-BDcQJDUe.js} +1 -1
  107. package/server/{hooks-C9b7Vqpw.js → hooks-BS_JrvYP.js} +3 -3
  108. package/server/hume-B4seN95Y.js +3 -0
  109. package/server/{hume-TQ5pvOZ_.js → hume-BRhNKyXQ.js} +2 -2
  110. package/server/{idle-stack-reaper-B1kG7B_X.js → idle-stack-reaper-BCITXNW5.js} +2 -2
  111. package/server/{idle-stack-reaper-iFVOAoJy.js → idle-stack-reaper-C1ymihno.js} +1 -1
  112. package/server/{infra-bpq1MHKU.js → infra-_Kx1m9pm.js} +9 -4
  113. package/server/{injection-BLWfwTVZ.js → injection-Bvhyg_MV.js} +1 -1
  114. package/server/{injection-DJqWld1v.js → injection-CCaYkofq.js} +7 -7
  115. package/server/{inspect-agent-DytCsltn.js → inspect-agent-BqyZ4pCo.js} +35 -67
  116. package/server/inspect-agent-DCJQm9Wu.js +2 -0
  117. package/server/{internal-token-CjlhdufS.js → internal-token-Cl-1IxaE.js} +2 -2
  118. package/server/{internal-token-CCaWaTwE.js → internal-token-D-t28de5.js} +1 -1
  119. package/server/{io-BiQ2Cs3k.js → io-DkC4sFSy.js} +42 -56
  120. package/server/{issue-closed-FApNiOOs.js → issue-closed-Cpl4VDUM.js} +41 -6
  121. package/server/{issue-lifecycle-C-K53ASY.js → issue-lifecycle-CFR8TJ22.js} +22 -37
  122. package/server/{issue-lifecycle-BkFWNjlD.js → issue-lifecycle-YNTy3OT3.js} +1 -1
  123. package/server/{issue-service-singleton-bG1WdSdQ.js → issue-service-singleton-D81mCts_.js} +1 -1
  124. package/server/{issue-service-singleton-CuHGpJJP.js → issue-service-singleton-ldvUUXi3.js} +29 -29
  125. package/server/{issues-CwQMphDH.js → issues-DIEHt9sq.js} +2 -2
  126. package/server/{jsonl-parser-dNIjHgym.js → jsonl-parser-Cd5jIqh7.js} +3 -3
  127. package/server/{label-cleanup-DYvAZknd.js → label-cleanup-Btny6Gyo.js} +3 -3
  128. package/server/{state-home-CRowbXqU.js → launcher-generator-BXLM9f4b.js} +7 -127
  129. package/server/{layers-CD6FysRU.js → layers-gCJlK62m.js} +2 -2
  130. package/server/lifecycle-D7aQUnTO.js +12 -0
  131. package/server/{lifecycle-io-C_Wu0_i-.js → lifecycle-io-DQBF-GHO.js} +1 -1
  132. package/server/{lifecycle-io-CeYxP3cE.js → lifecycle-io-FfPUtz-y.js} +36 -47
  133. package/server/{linear-client-EtVyblVz.js → linear-client-CDH2D6Qy.js} +3 -3
  134. package/server/{linear-client-4qoKnuQu.js → linear-client-WrYrUIAH.js} +1 -1
  135. package/server/memory-fts-worker.js +1 -1
  136. package/server/{merge-7rqkTUm2.js → merge-BsDgUxFz.js} +3 -3
  137. package/server/{merge-brm721Es.js → merge-Dervs4BF.js} +1 -1
  138. package/server/{merge-agent-4Zhlgvqp.js → merge-agent-D6DNhe9j.js} +64 -51
  139. package/server/{merge-agent-36wfYm2u.js → merge-agent-DMQTJMP0.js} +1 -1
  140. package/server/{merge-batch-itfnwdsT.js → merge-batch-Bv6Z5K2H.js} +1 -1
  141. package/server/{merge-blockers-Bto_Oe_o.js → merge-blockers-WWWi6Ur2.js} +2 -2
  142. package/server/merge-ops-h9OCnIVz.js +2 -0
  143. package/server/{merge-rebase-Czfqfe4q.js → merge-rebase-Czth9ggm.js} +2 -2
  144. package/server/{merge-set-BIkGQL9j.js → merge-set-DNc4zBqd.js} +3 -3
  145. package/server/{merge-set-G1RHcu3D.js → merge-set-vuE3MEVt.js} +1 -1
  146. package/server/{merge-sync-B43Dybea.js → merge-sync-B2KSk54U.js} +2 -2
  147. package/server/{merge-sync-DIrmlZUr.js → merge-sync-B9nHdUGx.js} +1 -1
  148. package/server/{merge-train-DGqVuNZV.js → merge-train-DJgr81tF.js} +3 -3
  149. package/server/{merge-train-deps-q15XLnAS.js → merge-train-deps-B2OWS0k9.js} +4 -4
  150. package/server/{merge-verification-Cqfxx6bE.js → merge-verification-BBIXUEqa.js} +4 -4
  151. package/server/{multipart-parser-CiPxxfUH.js → multipart-parser-9DhuAkPd.js} +2 -2
  152. package/server/{no-resume-mode-yrPEQ_pC.js → no-resume-mode-Gh5R_YLF.js} +1 -1
  153. package/server/node_modules/.package-lock.json +11 -11
  154. package/server/node_modules/@effect/platform-node/node_modules/@effect/platform-node-shared/package.json +3 -3
  155. package/server/node_modules/toml/README.md +54 -0
  156. package/server/node_modules/toml/index.d.ts +42 -1
  157. package/server/node_modules/toml/index.js +3 -3
  158. package/server/node_modules/toml/lib/compiler.js +35 -3
  159. package/server/node_modules/toml/lib/parser.js +224 -143
  160. package/server/node_modules/toml/package.json +3 -1
  161. package/server/node_modules/ws/lib/receiver.js +15 -32
  162. package/server/node_modules/ws/lib/websocket-server.js +4 -4
  163. package/server/node_modules/ws/lib/websocket.js +4 -4
  164. package/server/node_modules/ws/package.json +5 -1
  165. package/server/{orphan-proposed-reconciler-BWWHn4Lr.js → orphan-proposed-reconciler-B8H41C4a.js} +14 -24
  166. package/server/{orphan-proposed-reconciler-DAD9FJtQ.js → orphan-proposed-reconciler-DbTmT7qy.js} +1 -1
  167. package/server/package-lock.json +12 -12
  168. package/server/package.json +1 -1
  169. package/server/{pan-dir-Bj7Hy8iu.js → pan-dir-BTw4MdSe.js} +4 -4
  170. package/server/{paths-C9DkhIOT.js → paths-Btkc8mzp.js} +2 -2
  171. package/server/{pickup-CKRuGDsB.js → pickup-BtvRBbvI.js} +1 -1
  172. package/server/{pipeline-notifier-BkKbQ8ji.js → pipeline-notifier-3frIvSi3.js} +1 -1
  173. package/server/{pipeline-notifier-CAurUste.js → pipeline-notifier-DTd19f18.js} +2 -2
  174. package/server/{project-pipelines-BRWaGmI4.js → project-pipelines-B95fcAek.js} +2 -2
  175. package/server/projects-Dc9aoVoG.js +3 -0
  176. package/server/{prompts-DfI5qbTW.js → prompts-CIVmjHxn.js} +2 -2
  177. package/server/{providers-D-5txBRc.js → providers-BLLk5IIb.js} +1 -1
  178. package/server/{providers-CWDXoaMA.js → providers-C-yWibCk.js} +7 -7
  179. package/server/public/assets/chunk-BO2N2NFS-DzKZyoKY.js +103 -0
  180. package/server/public/assets/{diagram-OG6HWLK6-Avtvq2qI.js → diagram-OG6HWLK6-Cx8mWUb8.js} +1 -1
  181. package/server/public/assets/{dist-BEHQuasD.js → dist-BqccLhaw.js} +3 -3
  182. package/server/public/assets/{dist-BvmY-qb1.js → dist-rC3nAs_O.js} +1 -1
  183. package/server/public/assets/{ganttDiagram-6RSMTGT7-7KhCd0fH.js → ganttDiagram-6RSMTGT7-DLYPLf5T.js} +1 -1
  184. package/server/public/assets/{highlighted-body-OFNGDK62-iQoBMTf0.js → highlighted-body-OFNGDK62-D4DqixbV.js} +1 -1
  185. package/server/public/assets/index-CPcysUHk.js +796 -0
  186. package/server/public/assets/index-H5M0yhM4.css +1 -0
  187. package/server/public/assets/{linear-CiJHW9g5.js → linear-yqcXkDfj.js} +1 -1
  188. package/server/public/assets/mermaid-GHXKKRXX-CWdZ_heB.js +1 -0
  189. package/server/public/assets/{pieDiagram-4H26LBE5-BOhmvUhW.js → pieDiagram-4H26LBE5-DhraIOaV.js} +1 -1
  190. package/server/public/assets/{prod-DmLBBqat.js → prod-DKUunUdN.js} +4 -4
  191. package/server/public/assets/{quadrantDiagram-W4KKPZXB-Cjku660G.js → quadrantDiagram-W4KKPZXB-DPiRcToa.js} +1 -1
  192. package/server/public/assets/{sankeyDiagram-5OEKKPKP-D52w5oPh.js → sankeyDiagram-5OEKKPKP-3An5HNkZ.js} +1 -1
  193. package/server/public/assets/{xychartDiagram-2RQKCTM6-BTO07_Iv.js → xychartDiagram-2RQKCTM6-lbEOjfqQ.js} +1 -1
  194. package/server/public/index.html +2 -2
  195. package/server/{pull-requests-CKZPWhjD.js → pull-requests-D7AL5woD.js} +1 -1
  196. package/server/{pull-requests-Bmre-Xlz.js → pull-requests-NDDNtqNq.js} +3 -3
  197. package/server/{agents-CsOzAmRg.js → queries-C0LCGG21.js} +743 -399
  198. package/server/{rally-BBd9tQ__.js → rally-4oKvnxLG.js} +3 -3
  199. package/server/rally-DF5a3ioP.js +3 -0
  200. package/server/{rally-api-DxZFObI_.js → rally-api-41tT6ikZ.js} +2 -2
  201. package/server/{rally-api-DDc85HIz.js → rally-api-BsaQ8Vnz.js} +1 -1
  202. package/server/{rally-client-DvvLiAQP.js → rally-client-Bo4hNcCZ.js} +5 -5
  203. package/server/{rally-client-BADHn0m_.js → rally-client-b5PT1x5L.js} +1 -1
  204. package/server/{reap-terminal-sessions-D_xvy-Mk.js → reap-terminal-sessions-BR39qflC.js} +1 -1
  205. package/server/{reap-terminal-sessions-D6JDJsqF.js → reap-terminal-sessions-CFkuquQj.js} +1 -1
  206. package/server/{rebuild-stack-GOsWqXi3.js → rebuild-stack-C7JPUom6.js} +1 -1
  207. package/server/{rebuild-stack-D19i080j.js → rebuild-stack-Ddv6W017.js} +6 -6
  208. package/server/{reconstruct-cache-lQrG4xGg.js → reconstruct-cache-Gqhm---K.js} +6 -6
  209. package/server/record-DLOqfbkI.js +3 -0
  210. package/server/record-update-BltEmPge.js +168 -0
  211. package/server/record-update-CDp7aTaT.js +3 -0
  212. package/server/recovery-trip-Cn7KJVkS.js +44 -0
  213. package/server/{release-engine-DxcMWRuK.js → release-engine-BYzmIBf9.js} +5 -5
  214. package/server/{release-set-BYsgX7o6.js → release-set-7pnxFOWR.js} +3 -3
  215. package/server/{remote-DmeL21dF.js → remote-Cvu4CSDk.js} +3 -3
  216. package/server/{remote-agents-BfQBE6MN.js → remote-agents-C2C6z9tj.js} +6 -33
  217. package/server/{remote-agents-DlvbzO7m.js → remote-agents-DYY5fFpd.js} +1 -1
  218. package/server/{remote-completion-BgkiSQmx.js → remote-completion-2Kb4IDg_.js} +13 -13
  219. package/server/{remote-8W2Yhygw.js → remote-rN0ecjHE.js} +3 -3
  220. package/server/resume-BFLl-yLn.js +3 -0
  221. package/server/{review-agent-D025Wt7w.js → review-agent-BZvk0N5G.js} +31 -23
  222. package/server/{review-agent-Rub0OpZc.js → review-agent-CpSHLzed.js} +1 -1
  223. package/server/{review-artifacts-CX951jTU.js → review-artifacts-PCo53YMn.js} +5 -19
  224. package/server/{review-context-DGGGR_w5.js → review-context-M7QtJlJq.js} +8 -8
  225. package/server/{review-monitor-0_qqMcnS.js → review-monitor-BLC5mTTg.js} +2 -2
  226. package/server/{review-status-Br6lAFGO.js → review-status-77N3PoZr.js} +1 -1
  227. package/server/{review-status-4qqLkRMu.js → review-status-DANr0t-v.js} +33 -59
  228. package/server/{review-status-enrichment-Cr8m3uVL.js → review-status-enrichment-DwOg4UfL.js} +3 -3
  229. package/server/{review-status-sync-D3KUhsdA.js → review-status-sync-Bd08OOFA.js} +1 -1
  230. package/server/{review-status-sync-OC-l1lNt.js → review-status-sync-BjTUJ5fx.js} +2 -2
  231. package/server/{review-verdict-feedback-YOzmsq9G.js → review-verdict-feedback-BsFLjN_6.js} +1 -1
  232. package/server/{review-verdict-feedback-DrWMuRaj.js → review-verdict-feedback-CBf-J_wS.js} +8 -8
  233. package/server/{rollup-CnxbwaLH.js → rollup-Bu0f2EyN.js} +8 -8
  234. package/server/{rollup-DmazRY0j.js → rollup-DTCCdEy4.js} +1 -1
  235. package/server/{scanner-DE1MqAaF.js → scanner-BlZ2vB7j.js} +7 -7
  236. package/server/{sequence-io-C0uoqfGx.js → sequence-io-ylyDtz2J.js} +4 -4
  237. package/server/{sequencer-agent-i-b2u4SJ.js → sequencer-agent-DsWQ-87H.js} +6 -17
  238. package/server/server.js +5670 -6392
  239. package/server/{service-D0_gh2tl.js → service-CLVBC_1Y.js} +116 -90
  240. package/server/{session-fork-DZ12vipL.js → session-fork-DlEIAhRy.js} +2 -2
  241. package/server/session-history-bnOFKMwf.js +168 -0
  242. package/server/settings-CP3GGon2.js +2 -0
  243. package/server/{settings-ByqjPtFO.js → settings-Cqa-8nyj.js} +2 -2
  244. package/server/{settings-DCC4BAqj.js → settings-gJrdLZy2.js} +3 -3
  245. package/server/{shadow-engineering-BhnmtW5m.js → shadow-engineering-fq1YEKV3.js} +3 -3
  246. package/server/{shadow-state-ByXZehsy.js → shadow-state-BqeFAygK.js} +1 -1
  247. package/server/{shadow-state-CKm8xWX9.js → shadow-state-RptE9Cau.js} +2 -2
  248. package/server/shadow-utils-BMWr5tRT.js +39 -0
  249. package/server/{ship-log-DE_0Cv_i.js → ship-log-BRQQi2lU.js} +1 -1
  250. package/server/{smart-compaction-D5Zen3AA.js → smart-compaction-CmoXpjeU.js} +7 -7
  251. package/server/{smart-compaction-h4_ca6Uk.js → smart-compaction-DldUel4B.js} +1 -1
  252. package/server/{spawn-planning-session-Dueit4WX.js → spawn-planning-session-CoS6LTc6.js} +16 -16
  253. package/server/{spawn-planning-session-Bgi7Ub02.js → spawn-planning-session-DT2tSMxo.js} +1 -1
  254. package/server/{specialist-completion-BZ8M-WZ0.js → specialist-completion-pJtESo7m.js} +1 -1
  255. package/server/{specialist-context-BMq83C58.js → specialist-context-CspI4Eog.js} +5 -5
  256. package/server/{specialist-handoff-logger-BxX1YefH.js → specialist-handoff-logger-Duz9RGvk.js} +2 -2
  257. package/server/{specialist-logs-BWZYcJ6P.js → specialist-logs-DtSkPJZK.js} +3 -3
  258. package/server/{specialists-ZkW4Mb7U.js → specialists-CywEMwxk.js} +1 -1
  259. package/server/{specialists-8ZAguXef.js → specialists-D9-dMUtI.js} +1 -1
  260. package/server/{specialists-BudKrjOu.js → specialists-Dax7tqXC.js} +1817 -560
  261. package/server/{specialists-DrPrC9Y9.js → specialists-GF42OOqd.js} +18 -18
  262. package/server/specs-VwcFtEDi.js +3 -0
  263. package/server/{src-DHn4Rd2F.js → src-By3D85Ry.js} +77 -1
  264. package/server/{src-Dv6L03vW.js → src-CqyFg52C.js} +5 -5
  265. package/server/{src-DNUUqkec.js → src-DhxM-eCb.js} +6 -6
  266. package/server/{stack-health-Dclm7_tu.js → stack-health-BsEVrJZ6.js} +1 -1
  267. package/server/{stack-health-DxsTx7II.js → stack-health-C-SnpRHh.js} +2 -2
  268. package/server/stale-task-claims-vTxmRzLi.js +69 -0
  269. package/server/state-home-Bur1y4oH.js +213 -0
  270. package/server/{state-mapping-BvAgFK8n.js → state-mapping-Ci0pkac4.js} +1 -1
  271. package/server/state-migrate-D3VveYfO.js +362 -0
  272. package/server/{stuck-remediation-CFWUP8_I.js → stuck-remediation-B699-p8v.js} +63 -46
  273. package/server/{stuck-remediation-CLHd-lrF.js → stuck-remediation-CaFYYTQW.js} +1 -1
  274. package/server/{summary-fork-CKc01Our.js → summary-fork-BxnF8Xh0.js} +2 -2
  275. package/server/{summary-fork-CtCwBJ2Z.js → summary-fork-COqYpVa3.js} +8 -8
  276. package/server/supervisor/{paths-3R6b989k.js → paths-Bk6hJRH9.js} +4 -3
  277. package/server/supervisor/pty-supervisor.js +2 -2
  278. package/server/supervisor/{pty-token-CScQ32Rj.js → pty-token-BZS5GYdG.js} +2 -2
  279. package/server/supervisor-eWAAmXKu.js +2 -0
  280. package/server/{supervisor-BA5D72Qa.js → supervisor-liMgIPDF.js} +2 -2
  281. package/server/{swarm-readiness-CL5sM9B1.js → swarm-readiness-sXB1vhzo.js} +3 -3
  282. package/server/{system-prerequisites-DmFSXIE5.js → system-prerequisites-Bz6KdkFJ.js} +1 -13
  283. package/server/{teardown-workspace-DWmFJzTw.js → teardown-workspace-DvzqnmUF.js} +14 -71
  284. package/server/{test-agent-queue-DjsQoV5X.js → test-agent-queue-hAU1Ah08.js} +6 -6
  285. package/server/{test-verdict-BSqsQp3G.js → test-verdict-CRsPpByy.js} +1 -1
  286. package/server/{test-verdict-D1AmZ2c0.js → test-verdict-CXMLATGc.js} +1 -1
  287. package/server/{tldr-daemon-C-MGomL8.js → tldr-daemon-Bam7Xhoo.js} +2 -2
  288. package/server/{tldr-daemon-DD9rI5gA.js → tldr-daemon-DhGwd88s.js} +1 -1
  289. package/server/tmux-C-rgxRDA.js +3 -0
  290. package/server/{tmux-C_d-lSIq.js → tmux-U55cNGFG.js} +92 -4
  291. package/server/{token-DfBf1bsd.js → token-Dsa-i36o.js} +2 -2
  292. package/server/{token-util-CROoF5ic.js → token-util-BWNpH96m.js} +1 -1
  293. package/server/token-util-DRt0j2nQ.js +3 -0
  294. package/server/{tracker-config-CNODpd11.js → tracker-config-OkdbaUCm.js} +2 -2
  295. package/server/{tracker-utils-Ch2_5GGf.js → tracker-utils-CghXMF3q.js} +2 -2
  296. package/server/{tracker-utils-B9ZoVA9b.js → tracker-utils-DdzUiPNU.js} +1 -1
  297. package/server/{traefik-connect-BF4x_LUQ.js → traefik-connect-CZYB3dlm.js} +1 -1
  298. package/server/{tunnel-r_5nHAI6.js → tunnel-D1YxesBL.js} +3 -3
  299. package/server/{tunnel-YD80_IHr.js → tunnel-DjlzgTl3.js} +1 -1
  300. package/server/typed-errors-CH4roFIj.js +3 -0
  301. package/server/{typed-errors-BEJhbjJg.js → typed-errors-n_Tnb4Fh.js} +3 -4
  302. package/server/{types-CE1m_daD.js → types-DOjWNv3Z.js} +1 -1
  303. package/server/{types-33JEP4LZ.js → types-DXBMm4mX.js} +1 -1
  304. package/server/{uat-train-DDI00aqD.js → uat-train-CaaWs9Rp.js} +1 -1
  305. package/server/{uat-train-DjkFWXge.js → uat-train-CqN34zFx.js} +24 -25
  306. package/server/{validation-DUTiJnyz.js → validation-4-z2Cw4z.js} +70 -23
  307. package/server/validation-DpTNakM6.js +3 -0
  308. package/server/verification-artifact-DK1xb1zG.js +56 -0
  309. package/server/verification-artifact-JD_edpZ9.js +2 -0
  310. package/server/{verification-runner-inLgeQux.js → verification-runner-BjHN80pJ.js} +338 -58
  311. package/server/verification-runner-ShYu5Ph3.js +2 -0
  312. package/server/{work-agent-prompt-fZljjA1_.js → work-agent-prompt-BiJ43Der.js} +12 -88
  313. package/server/workflows-B4C3lY9E.js +2 -0
  314. package/server/{workflows-oBnJ7Tm0.js → workflows-DkTotz_m.js} +17 -24
  315. package/server/{workspace-CNuW2j41.js → workspace-CuN0ev4Z.js} +3 -3
  316. package/server/{workspace-B2WOW9Hw.js → workspace-DQ4vVivd.js} +1 -1
  317. package/server/{workspace-config-CBgMGhWs.js → workspace-config-C77NKnsV.js} +1 -1
  318. package/server/{workspace-manager-DjUAJCSs.js → workspace-manager-B1mYn0lN.js} +1 -1
  319. package/server/{workspace-manager-CB-R07R3.js → workspace-manager-Beeo2aKF.js} +23 -48
  320. package/server/{workspace-metadata-9Nn5kgRl.js → workspace-metadata-BZC5BmoP.js} +2 -2
  321. package/server/{workspace-metadata-Fd58uUQG.js → workspace-metadata-Cy7_ZACL.js} +1 -1
  322. package/server/{workspaces-BjWXntty.js → workspaces-CGuzW1Ia.js} +191 -153
  323. package/sync-sources/hooks/ask-user-question-hook +7 -1
  324. package/sync-sources/hooks/codex-notify-hook +64 -8
  325. package/sync-sources/hooks/record-cost-event.js +1109 -36
  326. package/sync-sources/hooks/record-cost-event.js.map +1 -1
  327. package/sync-sources/hooks/statusline.sh +2 -2
  328. package/sync-sources/hooks/work-agent-stop-hook +1 -2
  329. package/server/acceptance-criteria-DHZk2ceV.js +0 -33
  330. package/server/agent-runtime-mirror-DrWxopNA.js +0 -37
  331. package/server/agent-state-EP2QFV9n.js +0 -3
  332. package/server/agents-DCvyTXNU.js +0 -3
  333. package/server/auto-commit-QAfsu3Sb.js +0 -3
  334. package/server/bd-process-lock-D3S4T4o-.js +0 -390
  335. package/server/beads-BGGxrkZ5.js +0 -649
  336. package/server/beads-CYsLiyIM.js +0 -2
  337. package/server/beads-query-B1DlJVp9.js +0 -95
  338. package/server/beads-restore-CyygSmmn.js +0 -23
  339. package/server/cli-ClIQwgMI.js +0 -3
  340. package/server/codex-B7DTpwEw.js +0 -3
  341. package/server/compact-beads-CK_hHEnB.js +0 -3
  342. package/server/compact-beads-DKL54y6j.js +0 -94
  343. package/server/deacon-swarm-BY5hKNaU.js +0 -2
  344. package/server/dist-DGTwcZhI.js +0 -3
  345. package/server/export-Dx_qJ3h8.js +0 -87
  346. package/server/forge-XlDgPQWs.js +0 -2
  347. package/server/hume-K1pi8jC4.js +0 -3
  348. package/server/inspect-agent-477VxQim.js +0 -2
  349. package/server/lifecycle-C_73gi-C.js +0 -14
  350. package/server/merge-ops-PzqsW69V.js +0 -2
  351. package/server/projects-KJ94e50I.js +0 -3
  352. package/server/public/assets/chunk-BO2N2NFS-ClukPsZQ.js +0 -103
  353. package/server/public/assets/index-DUFDmvmH.css +0 -1
  354. package/server/public/assets/index-DnTZ2QeN.js +0 -796
  355. package/server/public/assets/mermaid-GHXKKRXX-DZJOwgBG.js +0 -1
  356. package/server/rally-Dc2Wnxw2.js +0 -3
  357. package/server/record-pyB6VA32.js +0 -3
  358. package/server/recovery-trip-Dfh1tNHn.js +0 -98
  359. package/server/resolver-dTpSSzaH.js +0 -158
  360. package/server/resume-DhcIqsTm.js +0 -3
  361. package/server/settings-yydgaqhv.js +0 -2
  362. package/server/specs-DwoAEGrW.js +0 -3
  363. package/server/supervisor-DHjumXlL.js +0 -2
  364. package/server/tmux-x291Gjjb.js +0 -3
  365. package/server/token-util-xNaV-00f.js +0 -3
  366. package/server/tracker-config-DItsdelv.js +0 -3
  367. package/server/typed-errors-DSPn_xWP.js +0 -3
  368. package/server/validation-ict8hnXk.js +0 -3
  369. package/server/verification-runner-ATaLbpr5.js +0 -2
  370. package/server/workflows-tU7KKxQi.js +0 -2
  371. package/server/writer-CQMGzEkw.js +0 -3
  372. package/server/writer-fe1-8iGc.js +0 -251
@@ -1,46 +1,53 @@
1
1
  import { a as __toCommonJS } from "./rolldown-runtime-DNCzzKnI.js";
2
- import { C as init_tier_table, D as canUseHarnessSync, Kn as resolveProjectFromIssueSync, Kr as getOverdeckHome, On as getProjectSync, Pn as init_projects, T as resolveTieredExecutionEnabledForIssue, Z as resolveModel, br as init_errors, er as extractPrefixSync, f as loadConfigSync, hn as shouldCommitLegacyWorkspaceArtifacts, k as init_harness_policy, pn as init_state_read_home, qr as init_paths, rr as parseIssueIdSync, t as init_config_yaml, tr as init_issue_id, ur as FsError, w as resolveTieredExecutionEnabled, zr as encodeClaudeProjectDir } from "./config-yaml-Dcz-8uit.js";
2
+ import { B as piProviderForModel, C as init_tier_table, D as canUseHarnessSync, Fn as init_projects, Jr as getOverdeckHome, N as UnknownModelError, Qr as resolveOhmypiExtensionPath, R as getProviderForModelSync, Sr as init_errors, T as resolveTieredExecutionEnabledForIssue, Vr as encodeClaudeProjectDir, Yr as init_paths, Z as resolveModel, ar as parseIssueIdSync, c as isClaudeCodeChannelsEnabled, f as loadConfigSync, fr as FsError, hn as shouldCommitLegacyWorkspaceArtifacts, k as init_harness_policy, kn as getProjectSync, nr as extractPrefixSync, pn as init_state_read_home, qn as resolveProjectFromIssueSync, rr as init_issue_id, t as init_config_yaml, w as resolveTieredExecutionEnabled, z as init_providers, zn as listProjectsSync } from "./config-yaml-CqaAyn8y.js";
3
3
  import { a as init_activity_logger, n as emitActivityEntrySync } from "./activity-logger-pcbf-e30.js";
4
- import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-bpq1MHKU.js";
5
- import { K as buildChildEnvWithoutTmuxSync, L as sessionExists, R as sessionExistsSync, i as capturePane, k as listSessions, q as init_child_env, w as killSession, x as init_tmux } from "./tmux-C_d-lSIq.js";
6
- import { a as init_event_store, n as getEventStore } from "./event-store-DRte2Rwt.js";
7
- import { C as normalizeChannelReplyPayload, a as getHarnessBehavior, b as DomainEvent, t as init_src } from "./src-DHn4Rd2F.js";
8
- import { D as AGENT_COLUMNS_FOR_DB, M as stateToOverdeckParamsForDb, c as init_review_status, k as init_agent_state_sync, s as getReviewStatusSync, u as loadReviewStatuses, y as setReviewStatusSync } from "./review-status-4qqLkRMu.js";
9
- import { a as EventStoreService, c as ReadModelService, d as init_read_model, i as init_issue_lifecycle, s as init_domain_services, t as IssueLifecycle, u as getClosedIssueIdsForReadSource } from "./issue-lifecycle-C-K53ASY.js";
10
- import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-DQ2ajr_m.js";
11
- import { $n as writeAgentStateJsonSync, An as getAgentDir, Bt as resolveStaffing, C as restartAgent, Ct as deliverAgentPermissionDecision, E as resumeAgent, En as clearAgentTroubled, Et as setAgentDeliveryMethod, F as getActivity, I as getLatestSessionId, Jn as saveAgentStateSync, Jt as getProviderAuthMode, K as stopAgent, L as getLatestSessionIdSync, Lt as transitionIssueToInProgress, M as init_operator_interventions, Mn as getAgentState, N as operatorInterventionEvent, Pn as getAgentStateSync, Qn as wipeAgentStateDirs, R as getSessionId, S as recoverAgent, St as deliverAgentMessage, Vn as markAgentStoppedState, X as listAgentStates, Yn as setAgentPaused, Z as listRunningAgents, _t as init_config, cn as determineModel, dr as normalizeAgentId, er as init_persistent_logger, fn as init_provider_health, g as init_tier_escalation, gr as getAgentRuntimeStateSync, h as decideEscalation, hr as getAgentRuntimeState, j as messageAgent, pn as validateProviderHealth, qn as saveAgentState, t as init_agents, tr as logAgentLifecycleSync, vr as saveAgentRuntimeState, wn as clearAgentPaused, yt as loadCloisterConfigSync, z as saveSessionId, zt as init_staffing } from "./agents-CsOzAmRg.js";
12
- import { t as init_behavior } from "./behavior-Cpj03N4v.js";
13
- import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-D0_gh2tl.js";
14
- import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-CNODpd11.js";
15
- import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-CuHGpJJP.js";
16
- import { a as PAN_DIRNAME, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-CE1m_daD.js";
17
- import { I as generateVBriefFilename, L as init_lifecycle, f as readTierOverrides, g as serializeVBriefDocument, h as recordTierPromotion, m as readWorkspacePlanSync, o as init_io, r as findPlan, u as readPlan } from "./io-BiQ2Cs3k.js";
18
- import { d as getCliproxyAuthDir, g as getCliproxyLogPath, l as decodeJwtPayload, v as init_cliproxy } from "./cliproxy-BsoNNDAj.js";
19
- import { _ as init_codex_conversation_parser, c as parseEntireConversation, d as init_pi_conversation_parser, g as parseOhmypiConversationMessages, m as init_ohmypi_conversation_parser, p as parsePiConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages } from "./conversation-service-BR1OgvxO.js";
20
- import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population-ie-yBcQC.js";
21
- import { c as requireModelOverrideSync, n as init_state_home, o as init_model_validation, r as isStateMigrated, s as normalizeModelOverrideSync } from "./state-home-CRowbXqU.js";
22
- import { a as init_stack_health, i as getWorkspaceStackHealth } from "./stack-health-DxsTx7II.js";
23
- import { s as getDispatchableItems, u as init_dag } from "./dag-CqTehWpY.js";
24
- import { i as queryBeadById, r as init_beads_query } from "./beads-query-B1DlJVp9.js";
25
- import { a as init_lifecycle_io, c as updatePlanStatus, s as transitionVBriefOnMain } from "./lifecycle-io-CeYxP3cE.js";
26
- import { d as getAgentPendingQuestions$1, f as getAgentWorkspace$1, g as normalizeAwaitingInputPrompt, o as getRuntimeForAgent, s as computeAgentEnrichment, u as getAgentJsonlPath$1 } from "./stuck-remediation-CFWUP8_I.js";
27
- import { Ht as getResourceConfig, Lt as classifyMemoryPressure, Q as createInFlightGuard, Ut as getSystemHealthSnapshot } from "./deacon-DrKDBeJL.js";
28
- import { o as stopWorkspaceDocker, r as init_workspace_manager } from "./workspace-manager-CB-R07R3.js";
29
- import { a as init_jsonl_parser, o as normalizeModelName } from "./jsonl-parser-dNIjHgym.js";
30
- import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-9Nn5kgRl.js";
31
- import { c as syncBeadStatusToVBrief, l as withBdMutex } from "./beads-BGGxrkZ5.js";
32
- import { dirname, join } from "node:path";
4
+ import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-_Kx1m9pm.js";
5
+ import { A as listSessions, B as setOption, C as isHarnessProcessAlive, G as BLANKED_PROVIDER_ENV, I as sendKeysAsync, J as init_child_env, L as sendRawKeystroke, R as sessionExists, S as init_tmux, T as killSession, f as exactPaneTarget, i as capturePane, k as listSessionNames, m as findManagedServerPidSync, o as createSession, q as buildChildEnvWithoutTmuxSync, z as sessionExistsSync } from "./tmux-U55cNGFG.js";
6
+ import { a as init_event_store, n as getEventStore } from "./event-store-KZnWUKWh.js";
7
+ import { C as normalizeChannelReplyPayload, a as getHarnessBehavior, b as DomainEvent, t as init_src } from "./src-By3D85Ry.js";
8
+ import { A as stateToOverdeckParamsForDb, D as init_agent_state_sync, T as AGENT_COLUMNS_FOR_DB, c as init_review_status, s as getReviewStatusSync, u as loadReviewStatuses, y as setReviewStatusSync } from "./review-status-DANr0t-v.js";
9
+ import { a as EventStoreService, c as ReadModelService, d as init_read_model, i as init_issue_lifecycle, s as init_domain_services, t as IssueLifecycle, u as getClosedIssueIdsForReadSource } from "./issue-lifecycle-CFR8TJ22.js";
10
+ import { d as isTerminalIssueStage, o as init_agents, u as getIssueStageSync } from "./agents-BHBxr8c9.js";
11
+ import { n as ensureSessionContextBriefingFile, r as init_briefing_freshness } from "./briefing-freshness-DuxIE1yr.js";
12
+ import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-BoqD1Pxz.js";
13
+ import { $n as wipeAgentStateDirs, At as init_bridge_token, Ct as deliverAgentPermissionDecision, Dn as clearAgentTroubled, Et as setAgentDeliveryMethod, Fn as getAgentStateSync, G as saveSessionId, H as getLatestSessionId, Hn as markAgentStoppedState, Jn as saveAgentState, Kt as getAgentRuntimeBaseCommand, L as messageAgent, Lt as transitionIssueToInProgress, M as resumeAgent, Nn as getAgentState, O as recoverAgent, Q as stopAgent, R as init_operator_interventions, S as init_tier_escalation, St as deliverAgentMessage, Tn as clearAgentPaused, U as getLatestSessionIdSync, V as getActivity, Vt as resolveStaffing, W as getSessionId, Xn as setAgentPaused, Yn as saveAgentStateSync, Yt as getProviderAuthMode, _t as init_config, ar as init_pty_supervisor_locate, bn as dismissDevChannelsDialog, br as checkCodexAuthStatus, dn as getProviderExportsForModel, er as writeAgentStateJsonSync, fr as waitForReadySignal, gr as saveAgentRuntimeState, i as listRunningAgents, in as ohmypiFifoPaths, ir as writePtyToken, jn as getAgentDir, jt as writeBridgeTokenSync, k as restartAgent, ln as determineModel, lr as normalizeAgentId, mn as validateProviderHealth, mr as getAgentRuntimeStateSync, n as init_queries, nn as piFifoPaths, or as resolvePtySupervisorScriptPath, pn as init_provider_health, pr as getAgentRuntimeState, r as listAgentStates, rn as init_ohmypi_fifo, rr as init_pty_token, s as init_agents$1, sr as clearReadySignal, tn as init_pi_fifo, un as getProviderEnvForModel, x as decideEscalation, xn as writeChannelsBridgeMcpConfig, xr as init_codex_auth, yt as loadCloisterConfigSync, z as operatorInterventionEvent, zt as init_staffing } from "./queries-C0LCGG21.js";
14
+ import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-CLVBC_1Y.js";
15
+ import { o as init_persistent_logger, s as logAgentLifecycleSync } from "./session-history-bnOFKMwf.js";
16
+ import { t as init_behavior } from "./behavior-CWqNMqE5.js";
17
+ import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-OkdbaUCm.js";
18
+ import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-ldvUUXi3.js";
19
+ import { a as PAN_DIRNAME, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-DOjWNv3Z.js";
20
+ import { F as init_lifecycle, P as generateVBriefFilename, f as readTierOverrides, g as serializeVBriefDocument, h as recordTierPromotion, m as readWorkspacePlanSync, o as init_io, r as findPlan, u as readPlan } from "./io-DkC4sFSy.js";
21
+ import { B as removeFavorite, E as hasOtherActiveConversationOnTmuxSession, F as markConversationEnded, G as setConversationHarness, K as setConversationModel, M as listConversations, P as markConversationActive, Q as updateConversationDeliveryMethod, S as getConversationByName, W as setConversationEffort, g as backfillConversationModel, h as archiveConversation, k as init_conversations, nt as updateLastAttached, rt as updateSpawnError, x as getConversationById, y as createConversation } from "./conversations-BX5aD-6F.js";
22
+ import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population-Ca2UaV0U.js";
23
+ import { a as requireModelOverrideSync, i as normalizeModelOverrideSync, n as init_launcher_generator, r as init_model_validation, t as generateLauncherScriptSync } from "./launcher-generator-BXLM9f4b.js";
24
+ import { a as init_claude_permissions, o as resolvePermissionModeSync, r as getClaudePermissionFlagsStringSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-BB6agcOZ.js";
25
+ import { h as workspaceContextFile, l as piGlobalContextFile, o as init_layers } from "./layers-gCJlK62m.js";
26
+ import { a as init_stack_health, i as getWorkspaceStackHealth } from "./stack-health-C-SnpRHh.js";
27
+ import { l as init_dag, o as getDispatchableItems } from "./dag-C6kYyF17.js";
28
+ import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-Dx8hUsvC.js";
29
+ import { c as isStateMigrated, o as init_state_home } from "./state-home-Bur1y4oH.js";
30
+ import { a as init_lifecycle_io, c as updatePlanStatus, s as transitionVBriefOnMain } from "./lifecycle-io-FfPUtz-y.js";
31
+ import { _ as init_codex_conversation_parser, c as parseEntireConversation, d as init_pi_conversation_parser, g as parseOhmypiConversationMessages, m as init_ohmypi_conversation_parser, p as parsePiConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages } from "./conversation-service-mFVFdDQK.js";
32
+ import { _ as parseCodexApprovalPrompt, d as getAgentPendingQuestions$1, f as getAgentWorkspace$1, g as normalizeAwaitingInputPrompt, m as detectAwaitingInputForAgent, o as getRuntimeForAgent, p as scanPendingInputsPromise, s as computeAgentEnrichment, u as getAgentJsonlPath$1 } from "./stuck-remediation-B699-p8v.js";
33
+ import { Q as createInFlightGuard, Rt as classifyMemoryPressure, Ut as getResourceConfig, Wt as getSystemHealthSnapshot } from "./deacon-Dru5onpk.js";
34
+ import { o as stopWorkspaceDocker, r as init_workspace_manager } from "./workspace-manager-Beeo2aKF.js";
35
+ import { a as init_jsonl_parser, o as normalizeModelName } from "./jsonl-parser-Cd5jIqh7.js";
36
+ import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-BZC5BmoP.js";
37
+ import { basename, delimiter, dirname, join, resolve } from "node:path";
33
38
  import { Cause, Data, Effect, Layer, Option, Schema, Stream } from "effect";
34
39
  import { join as join$1 } from "path";
35
40
  import { homedir } from "node:os";
36
- import { existsSync } from "node:fs";
41
+ import { createReadStream, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
37
42
  import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
38
- import { access, appendFile, cp, lstat, mkdir, open, readFile, readdir, rm, stat as stat$1, writeFile } from "node:fs/promises";
43
+ import { access, appendFile, chmod, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat as stat$1, writeFile } from "node:fs/promises";
39
44
  import { randomUUID } from "crypto";
40
- import { mkdir as mkdir$1, open as open$1, readFile as readFile$1, stat as stat$2, writeFile as writeFile$1 } from "fs/promises";
45
+ import { mkdir as mkdir$1, writeFile as writeFile$1 } from "fs/promises";
41
46
  import { exec, execFile, spawn } from "node:child_process";
42
47
  import { promisify } from "node:util";
43
- import { timingSafeEqual } from "node:crypto";
48
+ import { randomUUID as randomUUID$1, timingSafeEqual } from "node:crypto";
49
+ import { pathToFileURL } from "node:url";
50
+ import { createInterface } from "node:readline";
44
51
  import * as Result$1 from "effect/Result";
45
52
  //#region http-helpers.ts
46
53
  init_paths();
@@ -65,10 +72,11 @@ function jsonStringResponse(json, options) {
65
72
  });
66
73
  }
67
74
  //#endregion
68
- //#region routes/http-handler.ts
75
+ //#region ../../lib/default-cwd.ts
69
76
  init_activity_logger();
70
77
  init_issue_id();
71
78
  init_projects();
79
+ init_providers();
72
80
  init_harness_policy();
73
81
  init_tier_table();
74
82
  init_config_yaml();
@@ -78,13 +86,36 @@ init_infra();
78
86
  init_event_store();
79
87
  init_src();
80
88
  init_agent_state_sync();
89
+ init_agents();
81
90
  init_read_model();
91
+ init_briefing_freshness();
82
92
  init_domain_services();
93
+ init_cost();
94
+ init_behavior();
95
+ init_persistent_logger();
96
+ init_tracker_config();
83
97
  init_types();
84
98
  init_review_status();
85
99
  init_lifecycle();
86
100
  init_io();
87
101
  /**
102
+ * Resolve the default cwd used when no project is selected — the "devroot"
103
+ * fallback. Prefers `~/Projects` if it exists, otherwise the user's home
104
+ * directory.
105
+ *
106
+ * Replaces the deprecated `sync.devroot` config (PAN-1201) for the runtime
107
+ * code paths that still need a sensible default working directory:
108
+ *
109
+ * - New conversations created without a `projectKey` (conversations.ts)
110
+ * - The global editor-launcher in the dashboard top bar (PanOpenInPicker)
111
+ */
112
+ function getDefaultCwd() {
113
+ const candidate = join(homedir(), "Projects");
114
+ return existsSync(candidate) ? candidate : homedir();
115
+ }
116
+ //#endregion
117
+ //#region routes/http-handler.ts
118
+ /**
88
119
  * httpHandler — typed error-to-HTTP mapping wrapper (PAN-470)
89
120
  *
90
121
  * Wraps an Effect route body and catches all typed TaggedErrors,
@@ -110,7 +141,7 @@ let opaqueLastSummaryAt = 0;
110
141
  * TrackerNotConfigured → 503 Service Unavailable
111
142
  * RateLimited → 429 Too Many Requests
112
143
  * AgentAlreadyRunning → 409 Conflict
113
- * BeadsNotInitialized, PlanEmpty → 422 Unprocessable Entity
144
+ * PlanEmpty → 422 Unprocessable Entity
114
145
  * TrackerApiError → 502 Bad Gateway
115
146
  * WorkspaceCreateError, AgentStartError → 500 Internal Server Error
116
147
  * Unknown errors → 500 Internal Server Error
@@ -120,7 +151,7 @@ function httpHandler(effect) {
120
151
  return effect.pipe(Effect.catchTag("IssueNotFound", (err) => Effect.succeed(jsonResponse({ error: `Issue not found: ${err.id}` }, { status: 404 }))), Effect.catchTag("WorkspaceNotFound", (err) => Effect.succeed(jsonResponse({ error: `Workspace not found: ${err.id}` }, { status: 404 }))), Effect.catchTag("TrackerNotConfigured", (err) => Effect.succeed(jsonResponse({ error: `Tracker not configured: ${err.tracker}` }, { status: 503 }))), Effect.catchTag("RateLimited", (err) => Effect.succeed(jsonResponse({
121
152
  error: "Rate limited",
122
153
  retryAfter: err.retryAfter
123
- }, { status: 429 }))), Effect.catchTag("AgentAlreadyRunning", (err) => Effect.succeed(jsonResponse({ error: `Agent already running for issue: ${err.id}` }, { status: 409 }))), Effect.catchTag("BeadsNotInitialized", (err) => Effect.succeed(jsonResponse({ error: `Beads not initialized for workspace: ${err.workspace}` }, { status: 422 }))), Effect.catchTag("PlanEmpty", (err) => Effect.succeed(jsonResponse({ error: `Plan is empty for issue: ${err.id}` }, { status: 422 }))), Effect.catchTag("TrackerApiError", (err) => Effect.succeed(jsonResponse({ error: `Tracker API error (${err.tracker}): ${err.message}` }, { status: 502 }))), Effect.catchTag("WorkspaceCreateError", (err) => Effect.succeed(jsonResponse({ error: `Workspace creation failed for ${err.id}: ${err.message}` }, { status: 500 }))), Effect.catchTag("AgentStartError", (err) => Effect.succeed(jsonResponse({ error: `Agent start failed for ${err.id}: ${err.message}` }, { status: 500 }))), Effect.catchCause((cause) => Effect.gen(function* () {
154
+ }, { status: 429 }))), Effect.catchTag("AgentAlreadyRunning", (err) => Effect.succeed(jsonResponse({ error: `Agent already running for issue: ${err.id}` }, { status: 409 }))), Effect.catchTag("PlanEmpty", (err) => Effect.succeed(jsonResponse({ error: `Plan is empty for issue: ${err.id}` }, { status: 422 }))), Effect.catchTag("TrackerApiError", (err) => Effect.succeed(jsonResponse({ error: `Tracker API error (${err.tracker}): ${err.message}` }, { status: 502 }))), Effect.catchTag("WorkspaceCreateError", (err) => Effect.succeed(jsonResponse({ error: `Workspace creation failed for ${err.id}: ${err.message}` }, { status: 500 }))), Effect.catchTag("AgentStartError", (err) => Effect.succeed(jsonResponse({ error: `Agent start failed for ${err.id}: ${err.message}` }, { status: 500 }))), Effect.catchCause((cause) => Effect.gen(function* () {
124
155
  if (Cause.hasInterruptsOnly(cause)) return jsonResponse({ error: "Request cancelled" }, { status: 499 });
125
156
  const error = Cause.squash(cause);
126
157
  const message = error instanceof Error ? error.message : "Internal server error";
@@ -152,171 +183,27 @@ function httpHandler(effect) {
152
183
  })));
153
184
  }
154
185
  //#endregion
155
- //#region ../../lib/codex-auth.ts
156
- init_cost();
157
- init_tracker_config();
158
- init_cliproxy();
159
- /** Wrapper error for Codex auth probing — preserves the underlying cause. */
160
- var CodexAuthCheckError = class extends Data.TaggedError("CodexAuthCheckError") {};
161
- async function checkCodexAuthStatusPromise(options = {}) {
162
- const credPath = join$1(getCliproxyAuthDir(), "codex-primary.json");
163
- let raw;
164
- let credMtimeMs = null;
165
- try {
166
- raw = await readFile$1(credPath, "utf8");
167
- credMtimeMs = (await stat$2(credPath)).mtimeMs;
168
- } catch {
169
- return { status: "missing" };
170
- }
171
- let creds;
172
- try {
173
- creds = JSON.parse(raw);
174
- } catch {
175
- return {
176
- status: "unknown",
177
- message: "Malformed credential file"
178
- };
179
- }
180
- const accessToken = typeof creds.access_token === "string" ? creds.access_token : null;
181
- if (!accessToken) return {
182
- status: "unknown",
183
- message: "Missing access_token in credential file"
184
- };
185
- const claims = decodeJwtPayload(accessToken);
186
- if (!claims) return {
187
- status: "unknown",
188
- message: "Unable to decode access_token"
189
- };
190
- const expSec = typeof claims.exp === "number" ? claims.exp : null;
191
- if (expSec === null) return {
192
- status: "unknown",
193
- message: "Missing exp claim in access_token"
194
- };
195
- const email = typeof creds.email === "string" ? creds.email : "";
196
- const expiresAt = (/* @__PURE__ */ new Date(expSec * 1e3)).toISOString();
197
- if (expSec * 1e3 <= Date.now()) return {
198
- status: "expired",
199
- email,
200
- expiresAt
201
- };
202
- return await applyBurnedTokenOverride({
203
- status: "valid",
204
- email,
205
- expiresAt
206
- }, email, expiresAt, { ignoreBurnBefore: options.ignoreBurnBefore ?? credMtimeMs ?? void 0 });
207
- }
208
- /** Read the trailing window of the cliproxy log for burn/failure analysis. */
209
- async function readLogTail(path) {
210
- const TAIL_BYTES = 128 * 1024;
211
- const file = await open$1(path, "r");
212
- try {
213
- const stat = await file.stat();
214
- const length = Math.min(stat.size, TAIL_BYTES);
215
- const buffer = Buffer.alloc(length);
216
- await file.read(buffer, 0, length, stat.size - length);
217
- return buffer.toString("utf8");
218
- } finally {
219
- await file.close();
220
- }
221
- }
222
- async function applyBurnedTokenOverride(baseStatus, email, expiresAt, options) {
223
- let logRaw;
224
- try {
225
- logRaw = await readLogTail(getCliproxyLogPath());
226
- } catch {
227
- return baseStatus;
228
- }
229
- return evaluateBurnedFromLog(logRaw, baseStatus, email, expiresAt, options);
230
- }
231
- const BURN_STALENESS_MS = 3600 * 1e3;
232
- /**
233
- * Pure decision: given the cliproxy log tail, decide whether a JWT-valid token
234
- * is actually burned. Exported for regression testing (PAN-1584).
235
- *
236
- * "Auth failure" evidence = either a refresh-token burn line
237
- * (`refresh token has already been used`) OR a `503` on `/v1/messages` /
238
- * `/v1/chat/completions` (the live symptom the agent hits once cliproxy disables
239
- * the provider — and which keeps appearing after the burn line stops, since a
240
- * disabled provider no longer attempts refreshes). "Success" = a `200` on those
241
- * same paths (proves the auth path works right now).
242
- *
243
- * Decision:
244
- * - No failure evidence → trust base status.
245
- * - Success AFTER the last failure → trust base status (recovered).
246
- * - Failure AFTER the credential's last write (ignoreBurnBefore) → BURNED,
247
- * regardless of age. The credential write time is when you last logged in;
248
- * a failure after it means the current token is dead. This is authoritative
249
- * and is the fix for the bug where a >1h-stale burn line was dismissed as
250
- * "valid" during a quiet period even though the token was dead (PAN-1584).
251
- * - Failure BEFORE that cutoff → trust base status (re-authed since).
252
- * - No cutoff available → fall back to the staleness backstop:
253
- * burned only if the last failure is within BURN_STALENESS_MS.
254
- *
255
- * We intentionally don't probe cliproxy with HTTP — `GET /v1/models` always
256
- * 401s (it needs real OAuth, not the local key) and would generate spurious log
257
- * lines on every dashboard load.
258
- */
259
- function evaluateBurnedFromLog(logRaw, baseStatus, email, expiresAt, options = {}) {
260
- const now = options.now ?? Date.now();
261
- const lines = logRaw.split("\n").slice(-500);
262
- const isBurnLine = (l) => l.includes("refresh token has already been used") || l.includes("refresh_token_reused");
263
- const isAuthFailure503 = (l) => /\b503 \|/.test(l) && /POST\s+"\/v1\/(messages|chat\/completions)/.test(l);
264
- const isSuccess = (l) => /\b200 \|/.test(l) && /POST\s+"\/v1\/(messages|chat\/completions)/.test(l);
265
- const timestampAt = (idx) => {
266
- for (let j = idx; j >= Math.max(0, idx - 10); j--) {
267
- const m = lines[j]?.match(/^\[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})\]/);
268
- if (m) {
269
- const t = Date.parse(`${m[1]}T${m[2]}Z`);
270
- if (Number.isFinite(t)) return t;
271
- }
272
- }
273
- return null;
274
- };
275
- let lastFailureIdx = -1;
276
- let lastFailureTimestamp = null;
277
- let lastSuccessIdx = -1;
278
- for (let i = lines.length - 1; i >= 0; i--) {
279
- const line = lines[i] ?? "";
280
- if (lastFailureIdx < 0 && (isBurnLine(line) || isAuthFailure503(line))) {
281
- lastFailureIdx = i;
282
- lastFailureTimestamp = timestampAt(i);
283
- }
284
- if (lastSuccessIdx < 0 && isSuccess(line)) lastSuccessIdx = i;
285
- if (lastFailureIdx >= 0 && lastSuccessIdx >= 0) break;
286
- }
287
- if (lastFailureIdx < 0) return baseStatus;
288
- if (lastSuccessIdx > lastFailureIdx) return baseStatus;
289
- if (options.ignoreBurnBefore !== void 0) {
290
- if (lastFailureTimestamp !== null && lastFailureTimestamp + 1e3 <= options.ignoreBurnBefore) return baseStatus;
291
- return {
292
- status: "burned",
293
- email,
294
- expiresAt
295
- };
296
- }
297
- if (lastFailureTimestamp !== null && now - lastFailureTimestamp > BURN_STALENESS_MS) return baseStatus;
298
- return {
299
- status: "burned",
300
- email,
301
- expiresAt
302
- };
303
- }
304
- /**
305
- * Effect-native checkCodexAuthStatus. The Promise version is designed to
306
- * swallow all I/O errors and report the auth state through the typed status
307
- * union. The Effect variant wraps that to make it composable; it only fails
308
- * with CodexAuthCheckError if the underlying call itself throws unexpectedly
309
- * (i.e., not from the documented "missing/unknown" branches).
310
- */
311
- const checkCodexAuthStatus = (options = {}) => Effect.tryPromise({
312
- try: () => checkCodexAuthStatusPromise(options),
313
- catch: (cause) => new CodexAuthCheckError({
314
- message: cause instanceof Error ? cause.message : String(cause),
315
- cause
316
- })
317
- });
318
- //#endregion
319
186
  //#region routes/origin-validation.ts
187
+ init_conversations();
188
+ init_pty_supervisor_locate();
189
+ init_pty_token();
190
+ init_feature_registry_population();
191
+ init_model_validation();
192
+ init_provider_health();
193
+ init_claude_permissions();
194
+ init_layers();
195
+ init_ohmypi_fifo();
196
+ init_pi_fifo();
197
+ init_stack_health();
198
+ init_launcher_generator();
199
+ init_dag();
200
+ init_staffing();
201
+ init_harness_resolve();
202
+ init_bridge_token();
203
+ init_operator_interventions();
204
+ init_lifecycle_io();
205
+ init_agents$1();
206
+ init_queries();
320
207
  let cachedTrustedOrigins;
321
208
  function addTrustedOrigin(origins, raw) {
322
209
  if (!raw) return;
@@ -408,21 +295,75 @@ function isTrustedOriginForHost(origin, _host) {
408
295
  return getTrustedOrigins().includes(normalized);
409
296
  }
410
297
  //#endregion
298
+ //#region services/pending-respawn.ts
299
+ /**
300
+ * Tracks tmux sessions that are mid-respawn so that `/ws/terminal`
301
+ * reconnects landing in the kill→spawn gap don't get a fatal 4404.
302
+ *
303
+ * Background: switch-model, resume, and restart-all all do
304
+ *
305
+ * await killSessionAsync(name) // old tmux dies, PTY exits,
306
+ * // ws-terminal closes clients with 1000
307
+ * await spawnConversationSession(...) // new tmux session with same name
308
+ * await waitForTmuxSession(name)
309
+ * await waitForReadySignal(name, 30) // up to 30s
310
+ *
311
+ * The terminal frontend (`XTerminal.tsx`) reconnects on close 1000 with
312
+ * exponential backoff (1s, 2s, 4s, 8s, 16s). Every reconnect re-enters
313
+ * `ws-terminal.ts`, which checks the tmux session list upfront and
314
+ * `close(4404, 'session-not-found')` if the name isn't there yet.
315
+ * 4404 is treated as fatal on the client (no retry — the session is
316
+ * presumed gone), so the panel sticks on "Could not reconnect" even
317
+ * after the respawn completes a moment later.
318
+ *
319
+ * This registry lets `ws-terminal.ts` distinguish "transient absence
320
+ * during respawn" from "session is actually gone". Respawn sites mark
321
+ * the name with `markRespawnPending()` for the duration of their kill+
322
+ * spawn block; ws-terminal calls `waitForSessionRespawn()` instead of
323
+ * 4404-ing immediately when the session is missing AND
324
+ * `isRespawnPending()` is true.
325
+ */
326
+ const pendingRespawns = /* @__PURE__ */ new Set();
327
+ /**
328
+ * Mark a tmux session as mid-respawn. The returned `done()` must run in
329
+ * a `finally` so the marker is cleared even if the respawn throws.
330
+ */
331
+ function markRespawnPending(sessionName) {
332
+ pendingRespawns.add(sessionName);
333
+ return { done: () => {
334
+ pendingRespawns.delete(sessionName);
335
+ } };
336
+ }
337
+ /** True if a respawn is currently in progress for this session name. */
338
+ function isRespawnPending(sessionName) {
339
+ return pendingRespawns.has(sessionName);
340
+ }
341
+ const POLL_INTERVAL_MS = 200;
342
+ /**
343
+ * Poll for the session to come back, up to `timeoutMs`. Returns whether
344
+ * it did. Bails early once `pendingRespawns` no longer contains the name
345
+ * (the wrapping respawn block finished, success or failure) and the
346
+ * session still isn't there — at that point the absence is real.
347
+ *
348
+ * `timeoutMs` should comfortably exceed the longest respawn window. The
349
+ * dominant cost is `waitForReadySignal`'s 30s ceiling in the conversation
350
+ * routes, so 35s is the right default for those callers.
351
+ */
352
+ async function waitForSessionRespawn(sessionName, timeoutMs) {
353
+ const deadline = Date.now() + timeoutMs;
354
+ while (Date.now() < deadline) {
355
+ if (await Effect.runPromise(sessionExists(sessionName))) return true;
356
+ if (!pendingRespawns.has(sessionName)) return Effect.runPromise(sessionExists(sessionName));
357
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
358
+ }
359
+ return Effect.runPromise(sessionExists(sessionName));
360
+ }
361
+ //#endregion
411
362
  //#region routes/jsonl-resolver.ts
412
363
  init_codex_conversation_parser();
413
364
  init_ohmypi_conversation_parser();
414
365
  init_pi_conversation_parser();
415
366
  init_conversation_service();
416
- init_persistent_logger();
417
- init_feature_registry_population();
418
- init_model_validation();
419
- init_provider_health();
420
- init_stack_health();
421
- init_dag();
422
- init_staffing();
423
- init_beads_query();
424
- init_operator_interventions();
425
- init_lifecycle_io();
426
367
  /**
427
368
  * JSONL transcript resolver for the Command Deck (PAN-830).
428
369
  *
@@ -435,7 +376,8 @@ init_lifecycle_io();
435
376
  *
436
377
  * For claude-code agents the JSONL filename is the *Claude session ID* (a UUID
437
378
  * written by Claude Code itself), NOT the agent/tmux name. Lookup order:
438
- * 1. session.id — single UUID written by auto-suspend
379
+ * 1. session.id — UUID pinned before fresh work-agent launch and updated
380
+ * by auto-suspend/resume flows
439
381
  * 2. sessions.json — array of UUIDs the agent has used; the heartbeat hook
440
382
  * APPENDS new IDs and dedupes, so once a session has been
441
383
  * seen its array index never moves. After a `pan resume`
@@ -447,8 +389,13 @@ init_lifecycle_io();
447
389
  * Async-only (fs/promises) because this code path runs inside the dashboard
448
390
  * server's event loop.
449
391
  */
450
- init_agents();
451
- init_behavior();
392
+ const transcriptResolutionSignatures = /* @__PURE__ */ new Map();
393
+ function logTranscriptResolution(agentId, signature, message, opts) {
394
+ const signatureKey = `${agentId}:${opts.agentsDirOverride ?? "live"}`;
395
+ if (transcriptResolutionSignatures.get(signatureKey) === signature) return;
396
+ transcriptResolutionSignatures.set(signatureKey, signature);
397
+ (opts.logDiagnostic ?? (opts.agentsDirOverride ? void 0 : logAgentLifecycleSync))?.(agentId, `transcript resolution: ${message}`);
398
+ }
452
399
  async function pathExists$1(p) {
453
400
  return access(p).then(() => true, () => false);
454
401
  }
@@ -629,7 +576,7 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
629
576
  const agentDir = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId);
630
577
  const codexHome = join(agentDir, "codex-home");
631
578
  if (!await pathExists$1(codexHome)) return null;
632
- const { findRolloutPath, findLatestRollout } = await import("./codex-B7DTpwEw.js");
579
+ const { findRolloutPath, findLatestRollout } = await import("./codex-DxLivxri.js");
633
580
  const threadId = (await readOptional(join(agentDir, "codex-thread-id")))?.trim();
634
581
  if (threadId) {
635
582
  const rollout = findRolloutPath(codexHome, threadId);
@@ -681,18 +628,27 @@ async function resolvePiSessionPath(agentId, opts = {}) {
681
628
  * the corresponding JSONL file exists; otherwise null.
682
629
  */
683
630
  async function resolveJsonlPath(agentId, workspacePath, opts = {}) {
684
- const behavior = behaviorForHarness(await resolveAgentHarness(agentId, opts));
631
+ const harness = await resolveAgentHarness(agentId, opts);
632
+ const behavior = behaviorForHarness(harness);
685
633
  if (behavior.transcriptKind === "codex-rollout-jsonl") return resolveCodexRolloutPath(agentId, opts);
686
634
  if (behavior.transcriptKind === "ohmypi-jsonl") return resolvePiSessionPath(agentId, opts);
687
635
  const claudeSessionId = await resolveClaudeSessionId(agentId, opts);
688
- if (!claudeSessionId) return null;
636
+ if (!claudeSessionId) {
637
+ logTranscriptResolution(agentId, `missing-session-id:${harness ?? "unknown"}`, `failed harness=${harness ?? "unknown"} reason=no-session-id checked=session.id,sessions.json,runtime-state`, opts);
638
+ return null;
639
+ }
689
640
  const jsonlPath = join(opts.claudeProjectsDirOverride ?? join(homedir(), ".claude", "projects"), encodeClaudeProjectDir(workspacePath), `${claudeSessionId}.jsonl`);
690
- if (await pathExists$1(jsonlPath)) return jsonlPath;
641
+ if (await pathExists$1(jsonlPath)) {
642
+ logTranscriptResolution(agentId, `resolved:${jsonlPath}`, `resolved harness=${harness ?? "claude-code"} sessionId=${claudeSessionId} path=${jsonlPath}`, opts);
643
+ return jsonlPath;
644
+ }
645
+ logTranscriptResolution(agentId, `missing-jsonl:${jsonlPath}`, `failed harness=${harness ?? "claude-code"} reason=jsonl-missing sessionId=${claudeSessionId} workspace=${workspacePath} expectedPath=${jsonlPath}`, opts);
691
646
  return null;
692
647
  }
693
648
  //#endregion
694
649
  //#region ../../lib/work-agent-lifecycle.ts
695
- init_agents();
650
+ init_agents$1();
651
+ init_tmux();
696
652
  async function pathExists(path) {
697
653
  try {
698
654
  await access(path);
@@ -861,7 +817,7 @@ const getWorkAgentLifecycleState = (agentOrIssueId) => Effect.promise(() => getW
861
817
  *
862
818
  * All I/O is async (execFile / fs.promises). No execSync, no readFileSync.
863
819
  */
864
- const execFileAsync$2 = promisify(execFile);
820
+ const execFileAsync$3 = promisify(execFile);
865
821
  const GIT_INFO_TTL_MS = 3e4;
866
822
  const GIT_INFO_TIMEOUT_MS = 2e3;
867
823
  const cache = /* @__PURE__ */ new Map();
@@ -902,7 +858,7 @@ async function readHeadMtimeMs(absPath) {
902
858
  }
903
859
  }
904
860
  async function readGitInfoFresh(absPath) {
905
- const [branchResult, worktreeResult] = await Promise.all([execFileAsync$2("git", [
861
+ const [branchResult, worktreeResult] = await Promise.all([execFileAsync$3("git", [
906
862
  "-C",
907
863
  absPath,
908
864
  "rev-parse",
@@ -911,7 +867,7 @@ async function readGitInfoFresh(absPath) {
911
867
  ], {
912
868
  encoding: "utf-8",
913
869
  timeout: GIT_INFO_TIMEOUT_MS
914
- }).catch(() => null), execFileAsync$2("git", [
870
+ }).catch(() => null), execFileAsync$3("git", [
915
871
  "-C",
916
872
  absPath,
917
873
  "rev-parse",
@@ -984,10 +940,10 @@ async function resolveAgentGitInfo(workspacePath, expectedBranch) {
984
940
  //#endregion
985
941
  //#region routes/agents/shared.ts
986
942
  init_paths();
987
- init_agents();
943
+ init_agents$1();
988
944
  init_tmux();
989
- const execAsync$1 = promisify(exec);
990
- const execFileAsync$1 = promisify(execFile);
945
+ const execAsync$2 = promisify(exec);
946
+ const execFileAsync$2 = promisify(execFile);
991
947
  function buildPanStartArgs(input) {
992
948
  return [
993
949
  "start",
@@ -1228,7 +1184,7 @@ function buildStoppedAgentLifecycle(agentOrIssueId, state, runtimeData) {
1228
1184
  const agentId = normalizeAgentId(agentOrIssueId);
1229
1185
  const hasAgentState = true;
1230
1186
  const hasLiveTmuxSession = false;
1231
- const hasSavedSession = !!runtimeData.claudeSessionId;
1187
+ const hasSavedSession = !!runtimeData.claudeSessionId || !!getLatestSessionIdSync(agentId);
1232
1188
  const hasWorkspace = typeof state.workspace === "string" && state.workspace.length > 0;
1233
1189
  const agentStatus = state.status || "unknown";
1234
1190
  const runtime = runtimeData.state || "uninitialized";
@@ -1335,9 +1291,9 @@ async function getGitStatusAsync(workspacePath) {
1335
1291
  try {
1336
1292
  if (!existsSync(workspacePath)) return null;
1337
1293
  const [branchResult, uncommittedResult, commitResult] = await Promise.all([
1338
- execAsync$1("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"", { cwd: workspacePath }),
1339
- execAsync$1("git status --porcelain 2>/dev/null | wc -l", { cwd: workspacePath }),
1340
- execAsync$1("git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"", { cwd: workspacePath })
1294
+ execAsync$2("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"", { cwd: workspacePath }),
1295
+ execAsync$2("git status --porcelain 2>/dev/null | wc -l", { cwd: workspacePath }),
1296
+ execAsync$2("git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"", { cwd: workspacePath })
1341
1297
  ]);
1342
1298
  const branch = branchResult.stdout.trim();
1343
1299
  const uncommitted = uncommittedResult.stdout.trim();
@@ -1467,7 +1423,7 @@ const getAgentJsonlPath = getAgentJsonlPath$1;
1467
1423
  const getAgentPendingQuestions = getAgentPendingQuestions$1;
1468
1424
  //#endregion
1469
1425
  //#region routes/agents/listing.ts
1470
- init_agents();
1426
+ init_agents$1();
1471
1427
  init_tmux();
1472
1428
  const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.gen(function* () {
1473
1429
  const now = Date.now();
@@ -1698,7 +1654,7 @@ const getAgentHasSessionRoute = HttpRouter.add("GET", "/api/agents/:id/has-sessi
1698
1654
  //#endregion
1699
1655
  //#region routes/agents/conversation.ts
1700
1656
  init_paths();
1701
- init_agents();
1657
+ init_agents$1();
1702
1658
  init_tmux();
1703
1659
  const getAgentOutputRoute = HttpRouter.add("GET", "/api/agents/:id/output", httpHandler(Effect.gen(function* () {
1704
1660
  const id = (yield* HttpRouter.params)["id"] ?? "";
@@ -1720,7 +1676,7 @@ const getAgentOutputRoute = HttpRouter.add("GET", "/api/agents/:id/output", http
1720
1676
  } catch {}
1721
1677
  let stdout;
1722
1678
  if (isRemote && vmName) {
1723
- const { getRemoteAgentOutput } = await import("./remote-agents-DlvbzO7m.js");
1679
+ const { getRemoteAgentOutput } = await import("./remote-agents-DYY5fFpd.js");
1724
1680
  stdout = await getRemoteAgentOutput(id, vmName, parseInt(String(lines), 10) || 100);
1725
1681
  } else stdout = await Effect.runPromise(capturePane(id, parseInt(String(lines), 10) || 100));
1726
1682
  if (!stdout || stdout.trim() === "" || stdout.trim() === "Session not found") {
@@ -1794,119 +1750,1516 @@ async function buildConversationResponse(id) {
1794
1750
  if (existsSync(candidate)) jsonlPath = candidate;
1795
1751
  }
1796
1752
  }
1797
- if (!jsonlPath) jsonlPath = await Effect.runPromise(getAgentJsonlPath(id));
1798
- if (!jsonlPath || !existsSync(jsonlPath)) return EMPTY_CONVERSATION;
1799
- return {
1800
- ...await parseEntireConversation(jsonlPath),
1801
- streaming: false
1802
- };
1803
- } catch (err) {
1804
- console.error("[conversation] failed for", id, err);
1805
- return EMPTY_CONVERSATION;
1753
+ if (!jsonlPath) jsonlPath = await Effect.runPromise(getAgentJsonlPath(id));
1754
+ if (!jsonlPath || !existsSync(jsonlPath)) return EMPTY_CONVERSATION;
1755
+ return {
1756
+ ...await parseEntireConversation(jsonlPath),
1757
+ streaming: false
1758
+ };
1759
+ } catch (err) {
1760
+ console.error("[conversation] failed for", id, err);
1761
+ return EMPTY_CONVERSATION;
1762
+ }
1763
+ }
1764
+ const getAgentConversationRoute = HttpRouter.add("GET", "/api/agents/:id/conversation", httpHandler(Effect.gen(function* () {
1765
+ const id = (yield* HttpRouter.params)["id"] ?? "";
1766
+ return yield* Effect.promise(async () => jsonResponse(await buildConversationResponse(id)));
1767
+ })));
1768
+ const getAgentActivityRoute = HttpRouter.add("GET", "/api/agents/:id/activity", httpHandler(Effect.gen(function* () {
1769
+ const id = (yield* HttpRouter.params)["id"] ?? "";
1770
+ const request = yield* HttpServerRequest.HttpServerRequest;
1771
+ const urlOpt = HttpServerRequest.toURL(request);
1772
+ const limitStr = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("limit") ?? "100" : "100";
1773
+ return jsonResponse({ activity: getActivity(id, parseInt(limitStr) || 100) });
1774
+ })));
1775
+ const getAgentFilesRoute = HttpRouter.add("GET", "/api/agents/:id/files", httpHandler(Effect.gen(function* () {
1776
+ const agentState = yield* getAgentState((yield* HttpRouter.params)["id"] ?? "");
1777
+ if (!agentState?.workspace) return jsonResponse({ files: [] });
1778
+ const workspacePath = agentState.workspace;
1779
+ if (!existsSync(workspacePath)) return jsonResponse({ files: [] });
1780
+ const { stdout } = yield* Effect.promise(() => execAsync$2("git diff --name-status HEAD 2>/dev/null || git status --porcelain 2>/dev/null || echo \"\"", {
1781
+ cwd: workspacePath,
1782
+ encoding: "utf-8"
1783
+ }));
1784
+ return jsonResponse({ files: stdout.split("\n").filter((l) => l.trim()).map((l) => {
1785
+ const parts = l.trim().split(/\s+/);
1786
+ if (parts.length >= 2) return {
1787
+ status: parts[0],
1788
+ path: parts[parts.length - 1]
1789
+ };
1790
+ return {
1791
+ status: "?",
1792
+ path: l.trim()
1793
+ };
1794
+ }).filter((f) => f.path) });
1795
+ })));
1796
+ const getAgentTimelineRoute = HttpRouter.add("GET", "/api/agents/:id/timeline", httpHandler(Effect.gen(function* () {
1797
+ const id = (yield* HttpRouter.params)["id"] ?? "";
1798
+ const request = yield* HttpServerRequest.HttpServerRequest;
1799
+ const urlOpt = HttpServerRequest.toURL(request);
1800
+ const limitStr = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("limit") ?? "50" : "50";
1801
+ const limit = parseInt(limitStr) || 50;
1802
+ const activity = getActivity(id, limit);
1803
+ const agentState = yield* getAgentState(id);
1804
+ const events = activity.map((a) => ({
1805
+ timestamp: a.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1806
+ type: a.type || "activity",
1807
+ message: a.message || a.content || ""
1808
+ }));
1809
+ if (agentState?.startedAt) events.unshift({
1810
+ timestamp: agentState.startedAt,
1811
+ type: "started",
1812
+ message: "Agent started"
1813
+ });
1814
+ events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
1815
+ return jsonResponse({ timeline: events.slice(0, limit) });
1816
+ })));
1817
+ //#endregion
1818
+ //#region routes/agents/messaging.ts
1819
+ init_agents$1();
1820
+ async function sendAgentMessage(id, message) {
1821
+ const remoteStateFile = join(join(homedir(), ".overdeck", "agents", id), "remote-state.json");
1822
+ let isRemote = false;
1823
+ if (existsSync(remoteStateFile)) try {
1824
+ const state = JSON.parse(await readFile(remoteStateFile, "utf-8"));
1825
+ isRemote = state.location === "remote" && Boolean(state.vmName);
1826
+ } catch {}
1827
+ await messageAgent(id, message, "dashboard:user-message");
1828
+ return isRemote ? {
1829
+ success: true,
1830
+ remote: true
1831
+ } : { success: true };
1832
+ }
1833
+ function validateAgentMessageOrigin(request) {
1834
+ const originCheck = validateOrigin(request);
1835
+ if (!originCheck.ok) return {
1836
+ ok: false,
1837
+ response: jsonResponse({
1838
+ ok: false,
1839
+ error: originCheck.error
1840
+ }, { status: 403 })
1841
+ };
1842
+ return { ok: true };
1843
+ }
1844
+ function postAgentMessageLikeRoute(path) {
1845
+ return HttpRouter.add("POST", path, httpHandler(Effect.gen(function* () {
1846
+ const originCheck = validateAgentMessageOrigin(yield* HttpServerRequest.HttpServerRequest);
1847
+ if (!originCheck.ok) return originCheck.response;
1848
+ const id = (yield* HttpRouter.params)["id"] ?? "";
1849
+ const { message } = yield* readJsonBody$1;
1850
+ if (!message) return jsonResponse({ error: "Message required" }, { status: 400 });
1851
+ return yield* Effect.promise(() => sendAgentMessage(id, message)).pipe(Effect.map((result) => jsonResponse(result)));
1852
+ })));
1853
+ }
1854
+ const postAgentMessageRoute = postAgentMessageLikeRoute("/api/agents/:id/message");
1855
+ const postAgentTellRoute = postAgentMessageLikeRoute("/api/agents/:id/tell");
1856
+ const postAgentPokeRoute = HttpRouter.add("POST", "/api/agents/:id/poke", httpHandler(Effect.gen(function* () {
1857
+ const id = (yield* HttpRouter.params)["id"] ?? "";
1858
+ const { message } = yield* readJsonBody$1;
1859
+ const pokeMsg = message || "You seem to have been inactive for a while. If you're stuck:\n1. Check your current task in continue.vbrief.json\n2. Try an alternative approach if blocked\n3. Ask for help if needed\n\nWhat's your current status?";
1860
+ yield* Effect.promise(() => messageAgent(id, pokeMsg));
1861
+ return jsonResponse({
1862
+ success: true,
1863
+ message: "Agent poked successfully"
1864
+ });
1865
+ })));
1866
+ //#endregion
1867
+ //#region ../../lib/runtimes/conversation-control.ts
1868
+ function conversationControlPaths(agentId, home) {
1869
+ const { agentDir } = ohmypiFifoPaths(agentId, home);
1870
+ return {
1871
+ agentDir,
1872
+ controlDir: join(agentDir, "control")
1873
+ };
1874
+ }
1875
+ async function writeConversationControlCommand(agentId, command, home) {
1876
+ const paths = conversationControlPaths(agentId, home);
1877
+ await mkdir(paths.controlDir, {
1878
+ recursive: true,
1879
+ mode: 448
1880
+ });
1881
+ await chmod(paths.controlDir, 448);
1882
+ const finalPath = join(paths.controlDir, `${Date.now()}-${command.id}-${randomUUID$1()}.json`);
1883
+ const tempPath = `${finalPath}.tmp`;
1884
+ await writeFile(tempPath, `${JSON.stringify(command)}\n`, {
1885
+ encoding: "utf8",
1886
+ mode: 384
1887
+ });
1888
+ await chmod(tempPath, 384);
1889
+ await rename(tempPath, finalPath);
1890
+ await chmod(finalPath, 384);
1891
+ return finalPath;
1892
+ }
1893
+ //#endregion
1894
+ //#region ../../lib/ohmypi-codex-auth.ts
1895
+ /**
1896
+ * oh-my-pi (omp) OAuth helpers for the openai-codex (ChatGPT subscription) provider.
1897
+ * Forked from pi-codex-auth.ts (PAN-1520) for the omp migration (PAN-1989).
1898
+ *
1899
+ * omp stores its general credentials in ~/.omp/agent/agent.db (SQLite). The
1900
+ * openai-codex OAuth credential is persisted separately at ~/.omp/agent/auth.json
1901
+ * (same JSON format as Pi's ~/.pi/agent/auth.json) so that this helper's
1902
+ * read/write logic is unchanged from the Pi side.
1903
+ *
1904
+ * This module:
1905
+ * - reads/writes the openai-codex credential under ~/.omp/agent/auth.json,
1906
+ * - reports auth status (for `pan doctor` and the conversation spawn gate),
1907
+ * - refreshes an expired-but-recoverable token, and
1908
+ * - drives omp's headless device-code login to re-authenticate.
1909
+ *
1910
+ * The OAuth module is loaded from the installed `omp` package dynamically.
1911
+ * A missing or relocated omp OAuth module degrades to 'unavailable' — never throws.
1912
+ */
1913
+ const PI_CODEX_PROVIDER = "openai-codex";
1914
+ function getOhmypiAuthPath() {
1915
+ return join(homedir(), ".omp", "agent", "auth.json");
1916
+ }
1917
+ function readAuthFile() {
1918
+ const path = getOhmypiAuthPath();
1919
+ if (!existsSync(path)) return {};
1920
+ try {
1921
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
1922
+ return parsed && typeof parsed === "object" ? parsed : {};
1923
+ } catch {
1924
+ return {};
1925
+ }
1926
+ }
1927
+ function readOhmypiCodexCredential() {
1928
+ const cred = readAuthFile()[PI_CODEX_PROVIDER];
1929
+ if (cred && typeof cred === "object" && typeof cred["access"] === "string" && typeof cred["refresh"] === "string") return cred;
1930
+ return null;
1931
+ }
1932
+ /** Atomically merge a fresh codex credential into auth.json (mode 0600). */
1933
+ function writeOhmypiCodexCredential(tokens) {
1934
+ const path = getOhmypiAuthPath();
1935
+ mkdirSync(dirname(path), { recursive: true });
1936
+ const auth = readAuthFile();
1937
+ const stored = {
1938
+ type: "oauth",
1939
+ access: tokens.access,
1940
+ refresh: tokens.refresh,
1941
+ expires: tokens.expires,
1942
+ accountId: tokens.accountId
1943
+ };
1944
+ auth[PI_CODEX_PROVIDER] = stored;
1945
+ const tmp = `${path}.tmp-${process.pid}`;
1946
+ writeFileSync(tmp, JSON.stringify(auth, null, 2), { mode: 384 });
1947
+ renameSync(tmp, path);
1948
+ return stored;
1949
+ }
1950
+ /** Locate the `omp` executable on PATH (no subprocess — event-loop safe). */
1951
+ function findOmpBinary() {
1952
+ for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
1953
+ if (!dir) continue;
1954
+ const candidate = join(dir, "omp");
1955
+ if (existsSync(candidate)) return candidate;
1956
+ }
1957
+ return null;
1958
+ }
1959
+ /** omp is @oh-my-pi/pi-coding-agent; the oauth submodule path mirrors pi-ai's layout. */
1960
+ const OMP_AI_OAUTH_SUBPATH = join("@oh-my-pi", "pi-ai", "dist", "utils", "oauth", "openai-codex.js");
1961
+ /**
1962
+ * Resolve omp's openai-codex OAuth submodule by walking node_modules upward
1963
+ * from the real omp binary. Returns null when not found — the caller degrades
1964
+ * gracefully to 'unavailable' status.
1965
+ */
1966
+ function resolveOmpCodexOAuthFile(ompBin) {
1967
+ let dir = dirname(realpathSync(ompBin));
1968
+ for (let i = 0; i < 10; i++) {
1969
+ const candidate = join(dir, "node_modules", OMP_AI_OAUTH_SUBPATH);
1970
+ if (existsSync(candidate)) return candidate;
1971
+ const parent = dirname(dir);
1972
+ if (parent === dir) break;
1973
+ dir = parent;
1974
+ }
1975
+ return null;
1976
+ }
1977
+ let cachedModule = null;
1978
+ /**
1979
+ * Dynamically load omp's openai-codex OAuth submodule from the installed `omp`
1980
+ * package. Returns null on any failure so callers can degrade gracefully.
1981
+ */
1982
+ async function loadOhmypiCodexOAuth() {
1983
+ if (cachedModule) return cachedModule;
1984
+ cachedModule = (async () => {
1985
+ try {
1986
+ const ompBin = findOmpBinary();
1987
+ if (!ompBin) return null;
1988
+ const oauthFile = resolveOmpCodexOAuthFile(ompBin);
1989
+ if (!oauthFile) return null;
1990
+ const mod = await import(pathToFileURL(oauthFile).href);
1991
+ if (typeof mod.refreshOpenAICodexToken !== "function" || typeof mod.loginOpenAICodexDeviceCode !== "function") return null;
1992
+ return mod;
1993
+ } catch {
1994
+ return null;
1995
+ }
1996
+ })();
1997
+ return cachedModule;
1998
+ }
1999
+ const EXPIRY_MARGIN_MS = 6e4;
2000
+ async function getOhmypiCodexAuthStatus(opts) {
2001
+ const cred = readOhmypiCodexCredential();
2002
+ if (!cred) return { status: "missing" };
2003
+ if (cred.expires > Date.now() + EXPIRY_MARGIN_MS) return {
2004
+ status: "ok",
2005
+ expiresAt: cred.expires
2006
+ };
2007
+ if (opts?.refreshIfExpired) {
2008
+ const refreshed = await refreshOhmypiCodexAuth();
2009
+ if (refreshed) return {
2010
+ status: "ok",
2011
+ expiresAt: refreshed.expires
2012
+ };
2013
+ return {
2014
+ status: "expired",
2015
+ expiresAt: cred.expires,
2016
+ refreshFailed: true
2017
+ };
2018
+ }
2019
+ return {
2020
+ status: "expired",
2021
+ expiresAt: cred.expires,
2022
+ refreshFailed: false
2023
+ };
2024
+ }
2025
+ async function refreshOhmypiCodexAuth() {
2026
+ const cred = readOhmypiCodexCredential();
2027
+ if (!cred?.refresh) return null;
2028
+ const mod = await loadOhmypiCodexOAuth();
2029
+ if (!mod) return null;
2030
+ try {
2031
+ return writeOhmypiCodexCredential(await mod.refreshOpenAICodexToken(cred.refresh));
2032
+ } catch {
2033
+ return null;
2034
+ }
2035
+ }
2036
+ //#endregion
2037
+ //#region services/conversation-attachments.ts
2038
+ init_paths();
2039
+ const CONVERSATION_ATTACHMENTS_DIR = "conversation-attachments";
2040
+ /** Run async tasks in bounded batches to avoid unbounded Promise.all
2041
+ * that can exhaust file descriptors or memory under heavy load. */
2042
+ async function runInBatches(items, batchSize, fn) {
2043
+ for (let i = 0; i < items.length; i += batchSize) {
2044
+ const batch = items.slice(i, i + batchSize);
2045
+ await Promise.all(batch.map(fn));
2046
+ }
2047
+ }
2048
+ /** Max JSONL line length to process. Lines larger than this are skipped to
2049
+ * prevent readline from stalling permanently on oversized input (e.g. a
2050
+ * single-line base64 payload >15 MB). */
2051
+ const MAX_JSONL_LINE_LENGTH = 15 * 1024 * 1024;
2052
+ /** Bounded LRU cache for readSessionAttachmentBasenames to avoid full JSONL
2053
+ * rescans when the session file has not changed. Key = sessionFile:mtimeMs. */
2054
+ const SESSION_ATTACHMENT_CACHE_MAX = 100;
2055
+ const sessionAttachmentCache = /* @__PURE__ */ new Map();
2056
+ function getAttachmentCacheKey(sessionFile, mtimeMs) {
2057
+ return `${sessionFile}:${mtimeMs}`;
2058
+ }
2059
+ function setAttachmentCache(sessionFile, mtimeMs, basenames) {
2060
+ const key = getAttachmentCacheKey(sessionFile, mtimeMs);
2061
+ sessionAttachmentCache.set(key, basenames);
2062
+ if (sessionAttachmentCache.size > SESSION_ATTACHMENT_CACHE_MAX) {
2063
+ const firstKey = sessionAttachmentCache.keys().next().value;
2064
+ if (firstKey !== void 0) sessionAttachmentCache.delete(firstKey);
2065
+ }
2066
+ }
2067
+ function getAttachmentCache(sessionFile, mtimeMs) {
2068
+ const key = getAttachmentCacheKey(sessionFile, mtimeMs);
2069
+ return sessionAttachmentCache.get(key);
2070
+ }
2071
+ /** Conversation names are sanitized to [a-zA-Z0-9_-]{1,64} on creation.
2072
+ * Re-validate here for defense-in-depth against path traversal. */
2073
+ function assertSafeName(name) {
2074
+ if (!/^[a-zA-Z0-9_-]{1,64}$/.test(name)) throw new Error("Invalid conversation name");
2075
+ }
2076
+ function getConversationAttachmentsRoot() {
2077
+ return join(getOverdeckHome(), CONVERSATION_ATTACHMENTS_DIR);
2078
+ }
2079
+ function getConversationAttachmentDir(name) {
2080
+ assertSafeName(name);
2081
+ return join(getConversationAttachmentsRoot(), name);
2082
+ }
2083
+ async function ensureConversationAttachmentDir(name) {
2084
+ const dir = getConversationAttachmentDir(name);
2085
+ await mkdir(dir, { recursive: true });
2086
+ return dir;
2087
+ }
2088
+ async function cleanupConversationAttachments(name) {
2089
+ assertSafeName(name);
2090
+ await rm(getConversationAttachmentDir(name), {
2091
+ recursive: true,
2092
+ force: true
2093
+ });
2094
+ }
2095
+ /** Safety-net cleanup: remove attachment directories for conversations that no
2096
+ * longer exist in the database. Catches orphaned files from abandoned/crashed
2097
+ * sessions that the lifecycle poller missed. */
2098
+ async function cleanupOrphanedConversationAttachments() {
2099
+ try {
2100
+ const { listConversations } = await import("./conversations-BajHQ60c.js");
2101
+ const convNames = new Set(listConversations().map((c) => c.name));
2102
+ const root = getConversationAttachmentsRoot();
2103
+ await runInBatches((await readdir(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !convNames.has(entry.name)).map((entry) => join(root, entry.name)), 10, (path) => rm(path, {
2104
+ recursive: true,
2105
+ force: true
2106
+ }).catch(() => {}));
2107
+ } catch {}
2108
+ }
2109
+ async function listConversationAttachmentPaths(name) {
2110
+ const dir = getConversationAttachmentDir(name);
2111
+ try {
2112
+ return (await readdir(dir, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => join(dir, entry.name));
2113
+ } catch {
2114
+ return [];
2115
+ }
2116
+ }
2117
+ /** Synchronous containment check: does the normalized path start with the
2118
+ * pre-resolved attachment directory prefix? Uses resolve() (not realpath)
2119
+ * so the check is I/O-free — safe because we are only cataloguing references
2120
+ * in the JSONL, not accessing the filesystem. */
2121
+ function isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath) {
2122
+ try {
2123
+ return resolve(attachmentPath).startsWith(`${resolvedAttachmentDir}/`);
2124
+ } catch {
2125
+ return false;
2126
+ }
2127
+ }
2128
+ async function readSessionAttachmentBasenames(sessionFile, name) {
2129
+ try {
2130
+ let mtimeMs;
2131
+ try {
2132
+ mtimeMs = (await stat$1(sessionFile)).mtimeMs;
2133
+ } catch {
2134
+ return /* @__PURE__ */ new Set();
2135
+ }
2136
+ const cached = getAttachmentCache(sessionFile, mtimeMs);
2137
+ if (cached) return cached;
2138
+ let resolvedAttachmentDir;
2139
+ try {
2140
+ resolvedAttachmentDir = await realpath(getConversationAttachmentDir(name));
2141
+ } catch {
2142
+ resolvedAttachmentDir = resolve(getConversationAttachmentDir(name));
2143
+ }
2144
+ const referenced = /* @__PURE__ */ new Set();
2145
+ const stream = createReadStream(sessionFile, { encoding: "utf-8" });
2146
+ const rl = createInterface({
2147
+ input: stream,
2148
+ crlfDelay: Infinity
2149
+ });
2150
+ try {
2151
+ for await (const line of rl) {
2152
+ if (!line.trim()) continue;
2153
+ if (line.length > MAX_JSONL_LINE_LENGTH) continue;
2154
+ let text;
2155
+ let structuredFoundPaths = false;
2156
+ try {
2157
+ const entry = JSON.parse(line);
2158
+ if (typeof entry?.message?.content === "string") text = entry.message.content;
2159
+ else if (Array.isArray(entry?.message?.content)) text = entry.message.content.map((c) => typeof c === "string" ? c : c?.text ?? "").join("");
2160
+ else if (typeof entry?.message?.text === "string") text = entry.message.text;
2161
+ else if (typeof entry?.text === "string") text = entry.text;
2162
+ } catch {
2163
+ text = line;
2164
+ }
2165
+ if (text) {
2166
+ for (const attachmentPath of extractConversationAttachmentPaths(text)) if (isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath)) {
2167
+ referenced.add(basename(attachmentPath));
2168
+ structuredFoundPaths = true;
2169
+ }
2170
+ }
2171
+ if (!structuredFoundPaths) for (const match of line.matchAll(/"@([^"]+)"/g)) {
2172
+ const rawValue = match[1];
2173
+ if (!rawValue.startsWith("/") && !rawValue.startsWith("\\/")) continue;
2174
+ let attachmentPath;
2175
+ try {
2176
+ attachmentPath = JSON.parse(`"${rawValue}"`);
2177
+ } catch {
2178
+ attachmentPath = rawValue;
2179
+ }
2180
+ if (typeof attachmentPath === "string" && attachmentPath.startsWith("/")) {
2181
+ if (isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath)) referenced.add(basename(attachmentPath));
2182
+ }
2183
+ }
2184
+ }
2185
+ } finally {
2186
+ rl.close();
2187
+ stream.destroy();
2188
+ }
2189
+ setAttachmentCache(sessionFile, mtimeMs, referenced);
2190
+ return referenced;
2191
+ } catch {
2192
+ return /* @__PURE__ */ new Set();
2193
+ }
2194
+ }
2195
+ async function cleanupUnreferencedConversationAttachments(conversation) {
2196
+ const attachmentPaths = await listConversationAttachmentPaths(conversation.name);
2197
+ if (attachmentPaths.length === 0) return;
2198
+ if (!conversation.sessionFile) {
2199
+ await runInBatches(attachmentPaths, 10, async (attachmentPath) => {
2200
+ await rm(attachmentPath, { force: true });
2201
+ });
2202
+ return;
2203
+ }
2204
+ let sessionMtimeMs;
2205
+ try {
2206
+ sessionMtimeMs = (await stat$1(conversation.sessionFile)).mtimeMs;
2207
+ } catch {
2208
+ return;
2209
+ }
2210
+ const referencedBasenames = await readSessionAttachmentBasenames(conversation.sessionFile, conversation.name);
2211
+ try {
2212
+ const freshStats = await stat$1(conversation.sessionFile);
2213
+ if (freshStats.mtimeMs > sessionMtimeMs) sessionMtimeMs = freshStats.mtimeMs;
2214
+ } catch {}
2215
+ await runInBatches(attachmentPaths, 10, async (attachmentPath) => {
2216
+ if (referencedBasenames.has(basename(attachmentPath))) return;
2217
+ try {
2218
+ if ((await stat$1(attachmentPath)).mtimeMs >= sessionMtimeMs) return;
2219
+ } catch {
2220
+ return;
2221
+ }
2222
+ await rm(attachmentPath, { force: true });
2223
+ });
2224
+ }
2225
+ function extractConversationAttachmentPaths(message) {
2226
+ return Array.from(message.matchAll(/(?<!\S)@(\/[^\s]+)/g), ([, attachmentPath]) => {
2227
+ let path = attachmentPath;
2228
+ while (path.length > 1 && /[.,;:!?)\]}+"']$/.test(path)) path = path.slice(0, -1);
2229
+ return path;
2230
+ });
2231
+ }
2232
+ /** Resolve a path for containment checking, walking parent directories with
2233
+ * realpath to preserve symlink detection even when the target file is missing. */
2234
+ async function resolveForContainment(attachmentPath) {
2235
+ try {
2236
+ return await realpath(attachmentPath);
2237
+ } catch {
2238
+ let dir = dirname(attachmentPath);
2239
+ const remaining = [basename(attachmentPath)];
2240
+ while (dir !== "/" && dir !== ".") try {
2241
+ return join(await realpath(dir), ...remaining);
2242
+ } catch {
2243
+ remaining.unshift(basename(dir));
2244
+ dir = dirname(dir);
2245
+ }
2246
+ return resolve(attachmentPath);
2247
+ }
2248
+ }
2249
+ async function isManagedConversationAttachmentPath(attachmentPath) {
2250
+ try {
2251
+ const attachmentsRoot = await realpath(getConversationAttachmentsRoot());
2252
+ return (await resolveForContainment(attachmentPath)).startsWith(`${attachmentsRoot}/`);
2253
+ } catch {
2254
+ return false;
2255
+ }
2256
+ }
2257
+ async function isConversationAttachmentPath(name, attachmentPath) {
2258
+ try {
2259
+ const attachmentDir = resolve(getConversationAttachmentDir(name));
2260
+ return (await resolveForContainment(attachmentPath)).startsWith(`${attachmentDir}/`);
2261
+ } catch {
2262
+ return false;
2263
+ }
2264
+ }
2265
+ async function hasConversationAttachment(name, attachmentPath) {
2266
+ try {
2267
+ const attachmentDir = await realpath(getConversationAttachmentDir(name));
2268
+ const candidate = await resolveForContainment(attachmentPath);
2269
+ if (!candidate.startsWith(`${attachmentDir}/`)) return false;
2270
+ return existsSync(candidate);
2271
+ } catch {
2272
+ return false;
2273
+ }
2274
+ }
2275
+ async function removeConversationAttachment(name, attachmentPath) {
2276
+ if (!await isConversationAttachmentPath(name, attachmentPath)) return false;
2277
+ await rm(attachmentPath, { force: true });
2278
+ return true;
2279
+ }
2280
+ //#endregion
2281
+ //#region ../../lib/overdeck/conversation-runtime.ts
2282
+ init_projects();
2283
+ init_tmux();
2284
+ init_agents$1();
2285
+ init_behavior();
2286
+ init_paths();
2287
+ const execAsync$1 = promisify(exec);
2288
+ const execFileAsync$1 = promisify(execFile);
2289
+ const PROCESS_CLEANUP_GRACE_MS = 750;
2290
+ const PTY_SUPERVISOR_SOCKET_WAIT_MS = 3e4;
2291
+ function parseProcessTable(output) {
2292
+ return output.split("\n").map((line) => {
2293
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
2294
+ if (!match) return null;
2295
+ return {
2296
+ pid: Number(match[1]),
2297
+ ppid: Number(match[2]),
2298
+ args: match[3] ?? ""
2299
+ };
2300
+ }).filter((row) => row !== null && Number.isFinite(row.pid) && Number.isFinite(row.ppid));
2301
+ }
2302
+ async function readProcessTable() {
2303
+ const { stdout } = await execFileAsync$1("ps", ["-eo", "pid=,ppid=,args="], {
2304
+ encoding: "utf-8",
2305
+ maxBuffer: 10 * 1024 * 1024
2306
+ });
2307
+ return parseProcessTable(stdout);
2308
+ }
2309
+ function collectProcessTree(rootPids, rows) {
2310
+ const childrenByParent = /* @__PURE__ */ new Map();
2311
+ for (const row of rows) {
2312
+ const children = childrenByParent.get(row.ppid) ?? [];
2313
+ children.push(row.pid);
2314
+ childrenByParent.set(row.ppid, children);
2315
+ }
2316
+ const seen = /* @__PURE__ */ new Set();
2317
+ const ordered = [];
2318
+ const visit = (pid) => {
2319
+ if (seen.has(pid) || pid === process.pid) return;
2320
+ seen.add(pid);
2321
+ for (const child of childrenByParent.get(pid) ?? []) visit(child);
2322
+ ordered.push(pid);
2323
+ };
2324
+ for (const pid of rootPids) visit(pid);
2325
+ return ordered;
2326
+ }
2327
+ function isProcessAlive(pid) {
2328
+ try {
2329
+ process.kill(pid, 0);
2330
+ return true;
2331
+ } catch {
2332
+ return false;
2333
+ }
2334
+ }
2335
+ async function terminatePids(pids) {
2336
+ if (pids.length === 0) return;
2337
+ for (const pid of pids) try {
2338
+ process.kill(pid, "SIGTERM");
2339
+ } catch {}
2340
+ await new Promise((resolve) => setTimeout(resolve, PROCESS_CLEANUP_GRACE_MS));
2341
+ for (const pid of pids) {
2342
+ if (!isProcessAlive(pid)) continue;
2343
+ try {
2344
+ process.kill(pid, "SIGKILL");
2345
+ } catch {}
2346
+ }
2347
+ }
2348
+ function conversationRuntimeRootPids(conv, rows) {
2349
+ const launcherScript = join(getOverdeckHome(), "conversations", conv.tmuxSession, "launcher.sh");
2350
+ const sessionId = conv.claudeSessionId?.trim();
2351
+ const sessionNeedles = sessionId ? [`--resume ${sessionId}`, `--session-id ${sessionId}`] : [];
2352
+ const serverPid = findManagedServerPidSync();
2353
+ return rows.filter((row) => {
2354
+ if (row.pid === process.pid) return false;
2355
+ if (serverPid !== void 0 && row.pid === serverPid) return false;
2356
+ if (row.args.includes(launcherScript)) return true;
2357
+ return sessionNeedles.some((needle) => row.args.includes(needle));
2358
+ }).map((row) => row.pid);
2359
+ }
2360
+ async function killConversationRuntimeProcesses(conv) {
2361
+ const rows = await readProcessTable();
2362
+ await terminatePids(collectProcessTree(conversationRuntimeRootPids(conv, rows), rows));
2363
+ }
2364
+ async function stopConversationRuntime(conv, name) {
2365
+ if (hasOtherActiveConversationOnTmuxSession(conv.tmuxSession, name)) return;
2366
+ await Effect.runPromise(killSession(conv.tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
2367
+ try {
2368
+ await killConversationRuntimeProcesses(conv);
2369
+ } catch (error) {
2370
+ const msg = error instanceof Error ? error.message : String(error);
2371
+ console.warn(`[conversations] failed to cleanup runtime processes for ${name}: ${msg}`);
2372
+ }
2373
+ }
2374
+ /** Quote a string for safe use in a bash script using single-quote wrapping. */
2375
+ function shellQuote(str) {
2376
+ return "'" + str.replace(/'/g, "'\"'\"'") + "'";
2377
+ }
2378
+ const SAFE_MODEL_PATTERN = /^[a-zA-Z0-9_.:\/-]+$/;
2379
+ const SAFE_EFFORT_PATTERN = /^(low|medium|high)$/;
2380
+ const SAFE_ISSUE_ID_PATTERN = /^[A-Z0-9]+-[0-9]+$/;
2381
+ const PI_CONVERSATION_SOURCE_CONTRACT = [
2382
+ "Pi conversation source contract:",
2383
+ "A message marked source:'extension' was injected by the Overdeck orchestrator or another agent, not typed by the human operator.",
2384
+ "Treat it as coordination guidance; do not attribute it to the human operator."
2385
+ ].join(" ");
2386
+ async function resolveAllowedHarness(requested, model) {
2387
+ if (!model) return "claude-code";
2388
+ return resolveHarness({
2389
+ model,
2390
+ explicit: requested === "ohmypi" || requested === "claude-code" || requested === "codex" ? requested : void 0
2391
+ });
2392
+ }
2393
+ async function isInsideGitWorkTree(dir) {
2394
+ try {
2395
+ const { stdout } = await execAsync$1("git rev-parse --is-inside-work-tree", {
2396
+ cwd: dir,
2397
+ encoding: "utf-8"
2398
+ });
2399
+ return stdout.trim() === "true";
2400
+ } catch {
2401
+ return false;
2402
+ }
2403
+ }
2404
+ async function validateCwdContainment(cwd) {
2405
+ if (!cwd.startsWith("/")) return false;
2406
+ if (cwd.split("/").filter(Boolean).includes("..")) return false;
2407
+ try {
2408
+ const resolved = await realpath(cwd);
2409
+ if (!(await stat$1(resolved)).isDirectory()) return false;
2410
+ const home = homedir();
2411
+ return resolved.startsWith(`${home}/`) || resolved === home;
2412
+ } catch {
2413
+ return false;
2414
+ }
2415
+ }
2416
+ async function waitForClaudeReady(tmuxSession) {
2417
+ const deadline = Date.now() + 3e4;
2418
+ while (Date.now() < deadline) {
2419
+ if ((await Effect.runPromise(capturePane(tmuxSession, 200))).includes("❯")) {
2420
+ console.log(`[conversations] Claude Code ready in ${tmuxSession}`);
2421
+ return;
2422
+ }
2423
+ await new Promise((r) => setTimeout(r, 500));
2424
+ }
2425
+ console.warn(`[conversations] Timed out waiting for Claude Code prompt in ${tmuxSession}`);
2426
+ }
2427
+ function isPiTuiInputReady(snapshot) {
2428
+ return /^\s*[❯›>]\s/m.test(snapshot) || /(?:^|\s)0(?:\.\d+)?%\s+context\s+used\b/i.test(snapshot) || /⬢[^\n]*◕/.test(snapshot);
2429
+ }
2430
+ async function waitForPiTuiReady(tmuxSession, timeoutMs = 6e4) {
2431
+ const deadline = Date.now() + timeoutMs;
2432
+ while (Date.now() < deadline) {
2433
+ if (isPiTuiInputReady(await Effect.runPromise(capturePane(tmuxSession, 40).pipe(Effect.catch(() => Effect.succeed("")))))) {
2434
+ console.log(`[conversations] Pi TUI ready for ${tmuxSession}`);
2435
+ return true;
2436
+ }
2437
+ await new Promise((r) => setTimeout(r, 250));
2438
+ }
2439
+ console.warn(`[conversations] Timed out waiting for Pi TUI to render in ${tmuxSession}`);
2440
+ return false;
2441
+ }
2442
+ function shouldReportUnresolvedLiveSession(conv) {
2443
+ if (!conv || conv.status !== "active") return false;
2444
+ return getHarnessBehavior(conv.harness).transcriptKind === "claude-jsonl";
2445
+ }
2446
+ function conversationSessionAliveFromState(conv, tmuxSessionAlive) {
2447
+ return conv.status === "active" && !conv.forkStatus && tmuxSessionAlive;
2448
+ }
2449
+ function conversationNeedsRunningRepair(conv, tmuxSessionAlive, harnessProcessAlive) {
2450
+ return conv.status === "ended" && !conv.forkStatus && tmuxSessionAlive && harnessProcessAlive;
2451
+ }
2452
+ /** Generate a default conversation name, e.g. 20260404-1234 */
2453
+ function generateConversationName() {
2454
+ return `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "")}-${Math.floor(Math.random() * 9e3 + 1e3)}`;
2455
+ }
2456
+ async function tmuxSessionExists(sessionName) {
2457
+ return Effect.runPromise(sessionExists(sessionName));
2458
+ }
2459
+ async function waitForTmuxSession(sessionName, timeoutMs = 3e4) {
2460
+ const start = Date.now();
2461
+ while (Date.now() - start < timeoutMs) {
2462
+ if (await Effect.runPromise(sessionExists(sessionName))) return;
2463
+ await new Promise((r) => setTimeout(r, 250));
2464
+ }
2465
+ throw new Error(`Timed out waiting for tmux session ${sessionName}`);
2466
+ }
2467
+ function shouldUseSupervisorForConversation(harness) {
2468
+ return getHarnessBehavior(harness).supportsPtySupervisor && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1";
2469
+ }
2470
+ async function waitForConversationRuntimeReady(tmuxSession, harness, mode) {
2471
+ const transcriptKind = getHarnessBehavior(harness).transcriptKind;
2472
+ if (transcriptKind === "ohmypi-jsonl") {
2473
+ if (!await waitForPiTuiReady(tmuxSession)) throw new Error(`Pi (ohmypi) did not become interactive in ${tmuxSession} within the startup window — MCP server connections are likely still in progress. Retry once MCP servers finish connecting, or check ~/.claude/mcp.json for a slow/hanging server.`);
2474
+ } else if (transcriptKind !== "codex-rollout-jsonl" && mode === "spawn") {
2475
+ await waitForClaudeReady(tmuxSession);
2476
+ console.log(`[conversations] Claude ready in ${tmuxSession}`);
2477
+ } else if (transcriptKind !== "codex-rollout-jsonl") await waitForReadySignal(tmuxSession, 30);
2478
+ }
2479
+ async function handleConversationSwitchModel(name, body) {
2480
+ const conv = getConversationByName(name);
2481
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
2482
+ const model = typeof body["model"] === "string" && body["model"].trim() ? body["model"].trim() : conv.model ?? void 0;
2483
+ const currentHarness = conv.harness ?? "claude-code";
2484
+ const requestedHarness = body["harness"];
2485
+ let harness = currentHarness;
2486
+ if (requestedHarness === "ohmypi" || requestedHarness === "pi" || requestedHarness === "claude-code" || requestedHarness === "codex") {
2487
+ const requestedRuntime = requestedHarness === "pi" ? "ohmypi" : requestedHarness;
2488
+ if (requestedRuntime !== currentHarness) {
2489
+ const policyModel = model ?? conv.model ?? "";
2490
+ const decision = canUseHarnessSync(requestedRuntime, policyModel, await getProviderAuthMode(policyModel));
2491
+ if (!decision.allowed) return jsonResponse({ error: decision.reason ?? "Harness not allowed for this model" }, { status: 400 });
2492
+ }
2493
+ harness = requestedRuntime;
2494
+ }
2495
+ const harnessChanged = harness !== currentHarness;
2496
+ if (!await validateCwdContainment(conv.cwd)) return jsonResponse({ error: "Invalid cwd" }, { status: 400 });
2497
+ if (model && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
2498
+ const livePiSwitch = isPiControlChannelHarness(currentHarness) && conv.status !== "ended";
2499
+ if (conv.claudeSessionId && harnessChanged) return jsonResponse({ error: "Conversation harness is locked once a conversation has started" }, { status: 409 });
2500
+ if (conv.claudeSessionId && !livePiSwitch) return jsonResponse({ error: "Conversation model is locked once a conversation has started" }, { status: 409 });
2501
+ if (model) {
2502
+ if (livePiSwitch) await sendConversationControlCommand(conv, {
2503
+ type: "set_model",
2504
+ model
2505
+ });
2506
+ setConversationModel(name, model);
2507
+ }
2508
+ if (harnessChanged) setConversationHarness(name, harness);
2509
+ const updated = getConversationByName(name) ?? conv;
2510
+ return jsonResponse({
2511
+ ...updated,
2512
+ model: model ?? updated.model,
2513
+ harness,
2514
+ sessionAlive: false
2515
+ });
2516
+ }
2517
+ function getPtySupervisorSocketPath(agentId) {
2518
+ return join(getOverdeckHome(), "sockets", `pty-${agentId}.sock`);
2519
+ }
2520
+ async function waitForPtySupervisorSocket(agentId, timeoutMs = PTY_SUPERVISOR_SOCKET_WAIT_MS) {
2521
+ const socketPath = getPtySupervisorSocketPath(agentId);
2522
+ const start = Date.now();
2523
+ while (Date.now() - start < timeoutMs) {
2524
+ try {
2525
+ if (((await stat$1(socketPath)).mode & 511) === 384) return;
2526
+ } catch {}
2527
+ await new Promise((r) => setTimeout(r, 250));
2528
+ }
2529
+ throw new Error(`Timed out waiting for PTY supervisor socket ${socketPath}`);
2530
+ }
2531
+ async function extractModelFromSessionFile(sessionFile) {
2532
+ try {
2533
+ if (!existsSync(sessionFile)) return null;
2534
+ const stream = createReadStream(sessionFile, { encoding: "utf-8" });
2535
+ const rl = createInterface({
2536
+ input: stream,
2537
+ crlfDelay: Infinity
2538
+ });
2539
+ try {
2540
+ for await (const line of rl) {
2541
+ if (!line.trim()) continue;
2542
+ const entry = JSON.parse(line);
2543
+ if (entry.type === "assistant" && entry.message?.model) return entry.message.model;
2544
+ }
2545
+ } finally {
2546
+ rl.close();
2547
+ stream.destroy();
2548
+ }
2549
+ } catch {}
2550
+ return null;
2551
+ }
2552
+ let backfillRunning = false;
2553
+ async function backfillConversationModels(resolveSessionFile) {
2554
+ if (backfillRunning) return;
2555
+ backfillRunning = true;
2556
+ try {
2557
+ const convs = listConversations();
2558
+ const probe = await Promise.all(convs.map(async (conv) => !conv.model ? await resolveSessionFile(conv) : null));
2559
+ const candidates = convs.filter((_, i) => probe[i] !== null);
2560
+ const BATCH_SIZE = 10;
2561
+ let backfilled = 0;
2562
+ for (let i = 0; i < candidates.length; i += BATCH_SIZE) {
2563
+ const batch = candidates.slice(i, i + BATCH_SIZE);
2564
+ const results = await Promise.all(batch.map(async (conv) => {
2565
+ const sessionFile = await resolveSessionFile(conv);
2566
+ if (!sessionFile) return false;
2567
+ const model = await extractModelFromSessionFile(sessionFile);
2568
+ if (model && SAFE_MODEL_PATTERN.test(model)) {
2569
+ backfillConversationModel(conv.name, model);
2570
+ return true;
2571
+ }
2572
+ return false;
2573
+ }));
2574
+ backfilled += results.filter(Boolean).length;
2575
+ }
2576
+ if (backfilled > 0) console.log(`[conversations] Backfilled model for ${backfilled} conversation(s)`);
2577
+ } finally {
2578
+ backfillRunning = false;
2579
+ }
2580
+ }
2581
+ function startConversationModelBackfill(resolveSessionFile) {
2582
+ backfillConversationModels(resolveSessionFile).catch((err) => {
2583
+ console.error("[conversations] Model backfill failed:", err);
2584
+ });
2585
+ }
2586
+ async function claudeConversationSystemPromptFiles(cwd) {
2587
+ const files = [];
2588
+ const contextFile = workspaceContextFile(cwd);
2589
+ try {
2590
+ await stat$1(contextFile);
2591
+ files.push(contextFile);
2592
+ } catch (error) {
2593
+ if (!isNotFound(error)) throw error;
2594
+ }
2595
+ files.push(await ensureSessionContextBriefingFile());
2596
+ return files;
2597
+ }
2598
+ async function ensurePiConversationSourceContractFile() {
2599
+ const contextDir = join(getOverdeckHome(), "context");
2600
+ await mkdir(contextDir, { recursive: true });
2601
+ const path = join(contextDir, "pi-conversation-source-contract.md");
2602
+ await writeFile(path, `${PI_CONVERSATION_SOURCE_CONTRACT}\n`, "utf-8");
2603
+ return path;
2604
+ }
2605
+ async function piConversationSystemPromptFiles(cwd) {
2606
+ const files = [];
2607
+ const globalFile = piGlobalContextFile();
2608
+ try {
2609
+ await stat$1(globalFile);
2610
+ files.push(globalFile);
2611
+ } catch (error) {
2612
+ if (!isNotFound(error)) throw error;
2613
+ }
2614
+ files.push(await ensurePiConversationSourceContractFile());
2615
+ const contextFile = workspaceContextFile(cwd);
2616
+ try {
2617
+ await stat$1(contextFile);
2618
+ files.push(contextFile);
2619
+ } catch (error) {
2620
+ if (!isNotFound(error)) throw error;
2621
+ }
2622
+ files.push(await ensureSessionContextBriefingFile());
2623
+ return files;
2624
+ }
2625
+ function isNotFound(error) {
2626
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2627
+ }
2628
+ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, resume = false, harness = "claude-code", plainFork = false) {
2629
+ const behavior = getHarnessBehavior(harness);
2630
+ const stateDir = join(getOverdeckHome(), "conversations", tmuxSession);
2631
+ await mkdir(stateDir, { recursive: true });
2632
+ clearReadySignal(tmuxSession);
2633
+ const launcherScript = join(stateDir, "launcher.sh");
2634
+ let runtimeCommand = `claude ${getClaudePermissionFlagsStringSync()}`;
2635
+ let providerExportsStr = "";
2636
+ let piFields;
2637
+ let codexFields;
2638
+ if (model) {
2639
+ if (!SAFE_MODEL_PATTERN.test(model)) throw new Error("Invalid model name");
2640
+ runtimeCommand = await getAgentRuntimeBaseCommand(model, void 0, void 0, harness);
2641
+ const mode = resolvePermissionModeSync();
2642
+ if (!runtimeCommand.includes("--permission-mode")) runtimeCommand = `${runtimeCommand} --permission-mode ${mode === "auto" ? "auto" : BYPASS_PERMISSION_MODE}`;
2643
+ providerExportsStr = (await getProviderExportsForModel(model)).trim();
2644
+ await getProviderEnvForModel(model);
2645
+ if (behavior.transcriptKind === "ohmypi-jsonl") {
2646
+ if (getProviderForModelSync(model).name === "openai") {
2647
+ const auth = await getOhmypiCodexAuthStatus({ refreshIfExpired: true });
2648
+ if (auth.status === "missing" || auth.status === "expired") throw new Error("ohmypi ChatGPT/Codex login (openai-codex) has expired and could not be refreshed. Re-authenticate with `pan pi-auth login`, then retry.");
2649
+ }
2650
+ const paths = piFifoPaths(tmuxSession);
2651
+ const piSessionDir = join(paths.agentDir, "sessions");
2652
+ await mkdir(paths.agentDir, {
2653
+ recursive: true,
2654
+ mode: 448
2655
+ });
2656
+ await mkdir(piSessionDir, {
2657
+ recursive: true,
2658
+ mode: 448
2659
+ });
2660
+ const storedPiSessionId = resume ? await readFile(join(paths.agentDir, "session.id"), "utf-8").then((s) => s.trim()).catch(() => void 0) : void 0;
2661
+ piFields = {
2662
+ harness: "ohmypi",
2663
+ piMode: "tui",
2664
+ piExtensionPath: resolveOhmypiExtensionPath() ?? resolve(process.cwd(), "packages/ohmypi-extension/dist/index.js"),
2665
+ piSessionDir,
2666
+ resumeSessionId: storedPiSessionId || void 0
2667
+ };
2668
+ } else if (behavior.usesCodexHome) {
2669
+ const codexHome = join(getOverdeckHome(), "agents", tmuxSession, "codex-home");
2670
+ const codexPermMode = loadConfigSync().config.codex?.permissionMode ?? "workspace";
2671
+ const codexApprovalPolicy = codexPermMode === "full-access" ? "never" : "on-request";
2672
+ const codexSandboxMode = codexPermMode === "full-access" ? "danger-full-access" : codexPermMode === "read-only" ? "read-only" : "workspace-write";
2673
+ const codexApprovalsReviewer = codexPermMode === "auto-review" ? "auto_review" : void 0;
2674
+ const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-DxLivxri.js");
2675
+ initCodexHome(codexHome, {
2676
+ trustedDir: cwd,
2677
+ approvalPolicy: codexApprovalPolicy,
2678
+ sandboxMode: codexSandboxMode,
2679
+ approvalsReviewer: codexApprovalsReviewer
2680
+ });
2681
+ const resumeSessionId = resume ? await resolveCodexRolloutPath(tmuxSession, { agentsDirOverride: join(getOverdeckHome(), "agents") }).then((rollout) => rollout ? extractThreadIdFromRollout(rollout) ?? void 0 : void 0) : void 0;
2682
+ codexFields = {
2683
+ harness: "codex",
2684
+ codexMode: "tui",
2685
+ codexHome,
2686
+ codexSessionDir: join(codexHome, "sessions"),
2687
+ resumeSessionId
2688
+ };
2689
+ }
2690
+ }
2691
+ let launcherModel = model;
2692
+ if (behavior.contextLayerKind === "pi" && model) {
2693
+ const piProvider = piProviderForModel(model);
2694
+ if (piProvider) launcherModel = `${piProvider}/${model}`;
2695
+ }
2696
+ if (effort && !SAFE_EFFORT_PATTERN.test(effort)) throw new Error("Invalid effort level");
2697
+ const useSupervisor = shouldUseSupervisorForConversation(harness);
2698
+ let supervisorScriptPath;
2699
+ if (useSupervisor) {
2700
+ supervisorScriptPath = resolvePtySupervisorScriptPath();
2701
+ await writePtyToken(tmuxSession);
2702
+ }
2703
+ let channelsBridgeMcpConfig;
2704
+ if (!piFields && !codexFields && !plainFork && isClaudeCodeChannelsEnabled() && (!model || getProviderForModelSync(model).name === "anthropic") && process.env.CLAUDE_CODE_USE_BEDROCK !== "1" && process.env.CLAUDE_CODE_USE_VERTEX !== "1" && process.env.CLAUDE_CODE_USE_FOUNDRY !== "1" && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1") {
2705
+ channelsBridgeMcpConfig = join(stateDir, "agent-mcp.json");
2706
+ writeBridgeTokenSync(tmuxSession);
2707
+ await writeChannelsBridgeMcpConfig(channelsBridgeMcpConfig, tmuxSession);
2708
+ }
2709
+ const launcherTmp = `${launcherScript}.${randomUUID$1()}.tmp`;
2710
+ await writeFile(launcherTmp, generateLauncherScriptSync({
2711
+ role: "work",
2712
+ spawnMode: "conversation",
2713
+ workingDir: cwd,
2714
+ setTerminalEnv: true,
2715
+ unsetProviderEnv: true,
2716
+ overdeckEnv: {
2717
+ ...issueId ? { issueId } : {},
2718
+ ...piFields || codexFields || useSupervisor ? { agentId: tmuxSession } : {}
2719
+ },
2720
+ extraEnvExports: [`export OVERDECK_DASHBOARD_URL="http://127.0.0.1:${process.env["API_PORT"] ?? process.env["PORT"] ?? "3011"}"`],
2721
+ providerExports: providerExportsStr || void 0,
2722
+ trapHup: true,
2723
+ baseCommand: runtimeCommand,
2724
+ appendSystemPromptFiles: piFields ? await piConversationSystemPromptFiles(cwd) : codexFields ? [] : await claudeConversationSystemPromptFiles(cwd),
2725
+ model: launcherModel,
2726
+ ...piFields ?? codexFields ?? {
2727
+ resumeSessionId: resume ? claudeSessionId : void 0,
2728
+ sessionId: resume ? void 0 : claudeSessionId
2729
+ },
2730
+ extraArgs: !piFields && effort ? `--effort "${effort}"` : void 0,
2731
+ keepAlive: true,
2732
+ fileMode: 448,
2733
+ channelsBridgeMcpConfig,
2734
+ useSupervisor,
2735
+ supervisorScriptPath
2736
+ }), { mode: 448 });
2737
+ await rename(launcherTmp, launcherScript);
2738
+ try {
2739
+ await Effect.runPromise(killSession(tmuxSession));
2740
+ } catch {}
2741
+ console.log(`[claude-invoke] purpose=conversation-session | model=${model || "default"} | source=conversations.ts:spawnConversationSession | session=${tmuxSession} | resume=${resume} | command="${runtimeCommand}"`);
2742
+ try {
2743
+ const { preTrustDirectory } = await import("./workspace-manager-B1mYn0lN.js");
2744
+ preTrustDirectory(cwd);
2745
+ } catch {}
2746
+ try {
2747
+ await Effect.runPromise(createSession(tmuxSession, cwd, `bash ${shellQuote(launcherScript)}`, { env: {
2748
+ ...BLANKED_PROVIDER_ENV,
2749
+ TERM: "xterm-256color"
2750
+ } }));
2751
+ } catch (err) {
2752
+ if (err?.code === "ENOENT") throw new Error("tmux is not installed. Install it with: brew install tmux (macOS) or sudo apt-get install tmux (Linux)");
2753
+ throw err;
2754
+ }
2755
+ if (useSupervisor) await waitForPtySupervisorSocket(tmuxSession);
2756
+ if (behavior.usesCodexHome && codexFields?.codexHome) {
2757
+ const codexHomeDir = codexFields.codexHome;
2758
+ (async () => {
2759
+ try {
2760
+ const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-DxLivxri.js");
2761
+ const rollout = await waitForCodexRollout(codexHomeDir, 12e4);
2762
+ if (rollout) {
2763
+ const threadId = extractThreadIdFromRollout(rollout);
2764
+ if (threadId) writeThreadId(tmuxSession, threadId);
2765
+ }
2766
+ } catch {}
2767
+ })();
2768
+ }
2769
+ if (channelsBridgeMcpConfig) dismissDevChannelsDialog(tmuxSession).catch((err) => {
2770
+ const msg = err instanceof Error ? err.message : String(err);
2771
+ console.error(`[conversations] dismissDevChannelsDialog failed for ${tmuxSession}: ${msg}`);
2772
+ });
2773
+ await Effect.runPromise(setOption(tmuxSession, "destroy-unattached", "off"));
2774
+ await Effect.runPromise(setOption(exactPaneTarget(tmuxSession), "remain-on-exit", "on"));
2775
+ }
2776
+ /**
2777
+ * Resolve a conversation's cwd from a project identifier.
2778
+ *
2779
+ * The Command Deck identifies projects by display name, not yaml key
2780
+ * (PAN-2590) — accept either, like GET /api/session-trees does.
2781
+ */
2782
+ function resolveProjectCwd(projectKey) {
2783
+ const projectConfig = getProjectSync(projectKey) ?? listProjectsSync().find((p) => p.config.name === projectKey)?.config ?? null;
2784
+ if (!projectConfig) return { error: `Unknown project: ${projectKey}` };
2785
+ if (!projectConfig.path || !existsSync(projectConfig.path)) return { error: `Project path does not exist: ${projectConfig.path || "(unset)"} (project: ${projectKey})` };
2786
+ return { cwd: projectConfig.path };
2787
+ }
2788
+ async function handleConversationCreate(body, deps) {
2789
+ try {
2790
+ const message = typeof body["message"] === "string" ? body["message"].trim() : "";
2791
+ const model = typeof body["model"] === "string" ? body["model"].trim() : void 0;
2792
+ const effort = typeof body["effort"] === "string" ? body["effort"].trim() : void 0;
2793
+ const harness = await resolveAllowedHarness(body["harness"], model);
2794
+ const issueId = typeof body["issueId"] === "string" ? body["issueId"] : void 0;
2795
+ const projectKey = typeof body["projectKey"] === "string" ? body["projectKey"].trim() : void 0;
2796
+ if (issueId && !SAFE_ISSUE_ID_PATTERN.test(issueId)) return jsonResponse({ error: "Invalid issueId" }, { status: 400 });
2797
+ if (model && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
2798
+ if (effort && !SAFE_EFFORT_PATTERN.test(effort)) return jsonResponse({ error: "Invalid effort" }, { status: 400 });
2799
+ let cwd = getDefaultCwd();
2800
+ if (projectKey) {
2801
+ const resolved = resolveProjectCwd(projectKey);
2802
+ if ("error" in resolved) return jsonResponse({ error: resolved.error }, { status: 400 });
2803
+ cwd = resolved.cwd;
2804
+ }
2805
+ if (message && message.length > 5e4) return jsonResponse({ error: "message exceeds maximum length of 50000 characters" }, { status: 400 });
2806
+ let name = generateConversationName();
2807
+ for (let i = 0; i < 5 && getConversationByName(name); i++) name = generateConversationName();
2808
+ const tmuxSession = `conv-${name}`;
2809
+ const claudeSessionId = randomUUID$1();
2810
+ console.log(`[conversations] Creating conversation "${name}" with model=${model ?? "default"} effort=${effort ?? "default"} cwd=${cwd}`);
2811
+ const MAX_TITLE_LEN = 60;
2812
+ const title = message ? message.slice(0, MAX_TITLE_LEN) + (message.length > MAX_TITLE_LEN ? "…" : "") : "New conversation";
2813
+ const conv = createConversation({
2814
+ name,
2815
+ tmuxSession,
2816
+ cwd,
2817
+ issueId,
2818
+ claudeSessionId,
2819
+ title,
2820
+ titleSource: message ? "auto" : "default",
2821
+ titleSeed: title,
2822
+ model,
2823
+ effort,
2824
+ harness
2825
+ });
2826
+ getEventStore().emitOnly({
2827
+ type: "conversation.created",
2828
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2829
+ payload: { conversationName: name }
2830
+ });
2831
+ (async () => {
2832
+ try {
2833
+ await spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, false, harness);
2834
+ console.log(`[conversations] tmux session ${tmuxSession} spawned, sessionId: ${claudeSessionId}`);
2835
+ await waitForConversationRuntimeReady(tmuxSession, harness, "spawn");
2836
+ if (message) await deliverAgentMessage(tmuxSession, message, "conversation-message", resolveConversationDeliveryMethod(conv));
2837
+ } catch (spawnErr) {
2838
+ const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
2839
+ console.error(`[conversations] background spawn failed for ${tmuxSession}: ${msg}`);
2840
+ updateSpawnError(name, msg);
2841
+ getEventStore().emitOnly({
2842
+ type: "conversation.created",
2843
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2844
+ payload: { conversationName: name }
2845
+ });
2846
+ }
2847
+ })();
2848
+ if (message) deps.generateAiTitle(name, message).catch((err) => {
2849
+ const msg = err instanceof Error ? err.message : String(err);
2850
+ console.error(`[TITLE-GEN-FAILED] AI title generation FAILED for "${name}" — NO RETRY, NO FALLBACK:`, msg);
2851
+ });
2852
+ return jsonResponse({
2853
+ ...conv,
2854
+ sessionAlive: false
2855
+ }, { status: 201 });
2856
+ } catch (error) {
2857
+ const msg = error instanceof Error ? error.message : String(error);
2858
+ console.error("[conversations] create conversation failed:", msg);
2859
+ return jsonResponse({ error: msg || "Internal server error" }, { status: error instanceof UnknownModelError ? 400 : 500 });
2860
+ }
2861
+ }
2862
+ async function handleConversationStop(name, deps) {
2863
+ try {
2864
+ const conv = getConversationByName(name);
2865
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
2866
+ await stopConversationRuntime(conv, name);
2867
+ markConversationEnded(name);
2868
+ (async () => {
2869
+ await new Promise((r) => setTimeout(r, 500));
2870
+ await cleanupUnreferencedConversationAttachments({
2871
+ name: conv.name,
2872
+ sessionFile: deps.resolveSessionFileForCleanup?.(conv) ?? null
2873
+ });
2874
+ })();
2875
+ return jsonResponse({ success: true });
2876
+ } catch (error) {
2877
+ const msg = error instanceof Error ? error.message : String(error);
2878
+ console.error("[conversations] stop conversation failed:", msg);
2879
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
2880
+ }
2881
+ }
2882
+ async function handleConversationResume(name, body, deps) {
2883
+ try {
2884
+ const conv = getConversationByName(name);
2885
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
2886
+ const model = typeof body["model"] === "string" && body["model"].trim() ? body["model"].trim() : conv.model ?? void 0;
2887
+ const effort = typeof body["effort"] === "string" && body["effort"].trim() ? body["effort"].trim() : conv.effort ?? void 0;
2888
+ if (await isHarnessProcessAlive(conv.tmuxSession)) {
2889
+ updateLastAttached(name);
2890
+ markConversationActive(name);
2891
+ return jsonResponse({
2892
+ ...conv,
2893
+ status: "active",
2894
+ reattached: true
2895
+ });
2896
+ }
2897
+ const oldSessionId = conv.claudeSessionId;
2898
+ const harness = conv.harness ?? "claude-code";
2899
+ const modelChanged = !!model && model !== conv.model;
2900
+ if (!await validateCwdContainment(conv.cwd)) return jsonResponse({ error: "Invalid cwd" }, { status: 400 });
2901
+ if (model && modelChanged && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
2902
+ if (model && modelChanged) setConversationModel(name, model);
2903
+ let canResume = !!oldSessionId;
2904
+ if (oldSessionId) {
2905
+ const resumeFile = await deps.resolveSessionFile(conv);
2906
+ if (!resumeFile || !existsSync(resumeFile)) {
2907
+ canResume = false;
2908
+ console.error(`[conversations] SESSION-LOST ${name} harness=${harness} claudeSessionId=${oldSessionId} resolved=${resumeFile ?? "null"} — resuming with a fresh session`);
2909
+ }
2910
+ }
2911
+ const respawn = markRespawnPending(conv.tmuxSession);
2912
+ try {
2913
+ await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), model, effort, conv.issueId ?? void 0, canResume, harness);
2914
+ await waitForTmuxSession(conv.tmuxSession);
2915
+ await waitForConversationRuntimeReady(conv.tmuxSession, harness, "respawn");
2916
+ markConversationActive(name);
2917
+ return jsonResponse({
2918
+ ...conv,
2919
+ status: "active",
2920
+ model: model ?? conv.model,
2921
+ harness,
2922
+ reattached: false,
2923
+ sessionAlive: true
2924
+ });
2925
+ } finally {
2926
+ respawn.done();
2927
+ }
2928
+ } catch (error) {
2929
+ const msg = error instanceof Error ? error.message : String(error);
2930
+ console.error("[conversations] resume conversation failed:", msg);
2931
+ return jsonResponse({ error: msg || "Internal server error" }, { status: 500 });
2932
+ }
2933
+ }
2934
+ async function handleConversationDelete(name, deps) {
2935
+ try {
2936
+ const conv = getConversationByName(name);
2937
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
2938
+ await stopConversationRuntime(conv, name);
2939
+ markConversationEnded(name);
2940
+ archiveConversation(name);
2941
+ removeFavorite("conversation", name);
2942
+ deps.invalidateFavoritesCache();
2943
+ await cleanupConversationAttachments(name);
2944
+ return jsonResponse({ success: true });
2945
+ } catch (error) {
2946
+ const msg = error instanceof Error ? error.message : String(error);
2947
+ console.error("[conversations] delete conversation failed:", msg);
2948
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
2949
+ }
2950
+ }
2951
+ async function handleConversationRestartAll(deps) {
2952
+ try {
2953
+ const allConvs = listConversations();
2954
+ const liveSessionNames = new Set(await Effect.runPromise(listSessionNames()));
2955
+ const convs = allConvs.filter((c) => liveSessionNames.has(c.tmuxSession));
2956
+ const results = [];
2957
+ for (const conv of convs) {
2958
+ const respawn = markRespawnPending(conv.tmuxSession);
2959
+ try {
2960
+ await Effect.runPromise(killSession(conv.tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
2961
+ const oldSessionId = conv.claudeSessionId;
2962
+ const sessionFileForResume = await deps.resolveSessionFile(conv);
2963
+ const canResume = !!oldSessionId && !!sessionFileForResume && existsSync(sessionFileForResume);
2964
+ const harness = await resolveAllowedHarness(conv.harness, conv.model);
2965
+ await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), conv.model ?? void 0, conv.effort ?? void 0, conv.issueId ?? void 0, canResume, harness);
2966
+ setConversationHarness(conv.name, harness);
2967
+ markConversationActive(conv.name);
2968
+ results.push({
2969
+ name: conv.name,
2970
+ model: conv.model,
2971
+ status: "restarted"
2972
+ });
2973
+ } catch (err) {
2974
+ const msg = err instanceof Error ? err.message : String(err);
2975
+ console.error(`[conversations] Failed to restart ${conv.name}:`, msg);
2976
+ results.push({
2977
+ name: conv.name,
2978
+ model: conv.model,
2979
+ status: "failed"
2980
+ });
2981
+ } finally {
2982
+ respawn.done();
2983
+ }
2984
+ }
2985
+ console.log(`[conversations] Restarted ${results.filter((r) => r.status === "restarted").length}/${convs.length} conversations`);
2986
+ return jsonResponse({
2987
+ restarted: results.length,
2988
+ results
2989
+ });
2990
+ } catch (error) {
2991
+ const msg = error instanceof Error ? error.message : String(error);
2992
+ console.error("[conversations] restart conversations failed:", msg);
2993
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
1806
2994
  }
1807
2995
  }
1808
- const getAgentConversationRoute = HttpRouter.add("GET", "/api/agents/:id/conversation", httpHandler(Effect.gen(function* () {
1809
- const id = (yield* HttpRouter.params)["id"] ?? "";
1810
- return yield* Effect.promise(async () => jsonResponse(await buildConversationResponse(id)));
1811
- })));
1812
- const getAgentActivityRoute = HttpRouter.add("GET", "/api/agents/:id/activity", httpHandler(Effect.gen(function* () {
1813
- const id = (yield* HttpRouter.params)["id"] ?? "";
1814
- const request = yield* HttpServerRequest.HttpServerRequest;
1815
- const urlOpt = HttpServerRequest.toURL(request);
1816
- const limitStr = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("limit") ?? "100" : "100";
1817
- return jsonResponse({ activity: getActivity(id, parseInt(limitStr) || 100) });
1818
- })));
1819
- const getAgentFilesRoute = HttpRouter.add("GET", "/api/agents/:id/files", httpHandler(Effect.gen(function* () {
1820
- const agentState = yield* getAgentState((yield* HttpRouter.params)["id"] ?? "");
1821
- if (!agentState?.workspace) return jsonResponse({ files: [] });
1822
- const workspacePath = agentState.workspace;
1823
- if (!existsSync(workspacePath)) return jsonResponse({ files: [] });
1824
- const { stdout } = yield* Effect.promise(() => execAsync$1("git diff --name-status HEAD 2>/dev/null || git status --porcelain 2>/dev/null || echo \"\"", {
1825
- cwd: workspacePath,
1826
- encoding: "utf-8"
1827
- }));
1828
- return jsonResponse({ files: stdout.split("\n").filter((l) => l.trim()).map((l) => {
1829
- const parts = l.trim().split(/\s+/);
1830
- if (parts.length >= 2) return {
1831
- status: parts[0],
1832
- path: parts[parts.length - 1]
1833
- };
1834
- return {
1835
- status: "?",
1836
- path: l.trim()
1837
- };
1838
- }).filter((f) => f.path) });
1839
- })));
1840
- const getAgentTimelineRoute = HttpRouter.add("GET", "/api/agents/:id/timeline", httpHandler(Effect.gen(function* () {
1841
- const id = (yield* HttpRouter.params)["id"] ?? "";
1842
- const request = yield* HttpServerRequest.HttpServerRequest;
1843
- const urlOpt = HttpServerRequest.toURL(request);
1844
- const limitStr = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("limit") ?? "50" : "50";
1845
- const limit = parseInt(limitStr) || 50;
1846
- const activity = getActivity(id, limit);
1847
- const agentState = yield* getAgentState(id);
1848
- const events = activity.map((a) => ({
1849
- timestamp: a.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1850
- type: a.type || "activity",
1851
- message: a.message || a.content || ""
1852
- }));
1853
- if (agentState?.startedAt) events.unshift({
1854
- timestamp: agentState.startedAt,
1855
- type: "started",
1856
- message: "Agent started"
1857
- });
1858
- events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
1859
- return jsonResponse({ timeline: events.slice(0, limit) });
1860
- })));
1861
2996
  //#endregion
1862
- //#region routes/agents/messaging.ts
1863
- init_agents();
1864
- async function sendAgentMessage(id, message) {
1865
- const remoteStateFile = join(join(homedir(), ".overdeck", "agents", id), "remote-state.json");
1866
- let isRemote = false;
1867
- if (existsSync(remoteStateFile)) try {
1868
- const state = JSON.parse(await readFile(remoteStateFile, "utf-8"));
1869
- isRemote = state.location === "remote" && Boolean(state.vmName);
1870
- } catch {}
1871
- await messageAgent(id, message, "dashboard:user-message");
1872
- return isRemote ? {
1873
- success: true,
1874
- remote: true
1875
- } : { success: true };
2997
+ //#region ../../lib/overdeck/conversation-delivery.ts
2998
+ init_conversations();
2999
+ init_behavior();
3000
+ init_tmux();
3001
+ init_agents$1();
3002
+ const CONTROL_ACK_TIMEOUT_MS = 1e4;
3003
+ const pendingConversationControlAcks = /* @__PURE__ */ new Map();
3004
+ const PLAN_ACTION_KEYSTROKES = {
3005
+ "approve-auto": "1",
3006
+ "approve-manual": "2",
3007
+ "reject-ultraplan": "3"
3008
+ };
3009
+ /**
3010
+ * PAN-1520 (FR-4) — deliver a native plan-menu action to any tmux session
3011
+ * (conversation or agent). The plan menu is answered with raw keystrokes
3012
+ * (1/2/3/4), not a text message; `reject-feedback` follows the '4' keystroke
3013
+ * with the operator's feedback through the normal message pipeline.
3014
+ * Returns null on success, or an error string for an invalid action.
3015
+ */
3016
+ async function deliverPlanActionToSession(sessionName, action, feedback, deliveryMethod = "auto") {
3017
+ if (action === "reject-feedback") {
3018
+ await Effect.runPromise(sendRawKeystroke(sessionName, "4", "plan-action-reject"));
3019
+ if (feedback) {
3020
+ await new Promise((r) => setTimeout(r, 300));
3021
+ await deliverAgentMessage(sessionName, feedback, "plan-action-feedback", deliveryMethod);
3022
+ }
3023
+ return null;
3024
+ }
3025
+ const keystroke = PLAN_ACTION_KEYSTROKES[action];
3026
+ if (!keystroke) return `Invalid action: ${action}`;
3027
+ await Effect.runPromise(sendRawKeystroke(sessionName, keystroke, `plan-action-${action}`));
3028
+ return null;
1876
3029
  }
1877
- function validateAgentMessageOrigin(request) {
1878
- const originCheck = validateOrigin(request);
1879
- if (!originCheck.ok) return {
1880
- ok: false,
1881
- response: jsonResponse({
1882
- ok: false,
1883
- error: originCheck.error
1884
- }, { status: 403 })
3030
+ function registerConversationControlAck(commandId, timeoutMs = CONTROL_ACK_TIMEOUT_MS) {
3031
+ const existing = pendingConversationControlAcks.get(commandId);
3032
+ if (existing) {
3033
+ clearTimeout(existing.timer);
3034
+ existing.reject(/* @__PURE__ */ new Error(`Replaced pending conversation control ack ${commandId}`));
3035
+ pendingConversationControlAcks.delete(commandId);
3036
+ }
3037
+ return new Promise((resolve, reject) => {
3038
+ const timer = setTimeout(() => {
3039
+ pendingConversationControlAcks.delete(commandId);
3040
+ reject(/* @__PURE__ */ new Error(`Timed out waiting for conversation control ack ${commandId}`));
3041
+ }, timeoutMs);
3042
+ pendingConversationControlAcks.set(commandId, {
3043
+ resolve: () => {
3044
+ clearTimeout(timer);
3045
+ pendingConversationControlAcks.delete(commandId);
3046
+ resolve();
3047
+ },
3048
+ reject: (error) => {
3049
+ clearTimeout(timer);
3050
+ pendingConversationControlAcks.delete(commandId);
3051
+ reject(error);
3052
+ },
3053
+ timer
3054
+ });
3055
+ });
3056
+ }
3057
+ function resolveConversationControlAck(ack) {
3058
+ const pending = pendingConversationControlAcks.get(ack.id);
3059
+ if (!pending) return "unknown";
3060
+ if (ack.ok) {
3061
+ pending.resolve();
3062
+ return "resolved";
3063
+ }
3064
+ pending.reject(new Error(ack.error || `Conversation control command ${ack.id} failed`));
3065
+ return "rejected";
3066
+ }
3067
+ function handleConversationControlAck(body) {
3068
+ const id = typeof body["id"] === "string" ? body["id"].trim() : "";
3069
+ if (!id) return {
3070
+ status: 400,
3071
+ body: { error: "id is required" }
3072
+ };
3073
+ const ok = body["ok"] === true;
3074
+ const error = typeof body["error"] === "string" ? body["error"] : void 0;
3075
+ return {
3076
+ status: 200,
3077
+ body: {
3078
+ ok: true,
3079
+ outcome: resolveConversationControlAck({
3080
+ id,
3081
+ ok,
3082
+ ...error !== void 0 ? { error } : {}
3083
+ })
3084
+ }
1885
3085
  };
1886
- return { ok: true };
1887
3086
  }
1888
- function postAgentMessageLikeRoute(path) {
1889
- return HttpRouter.add("POST", path, httpHandler(Effect.gen(function* () {
1890
- const originCheck = validateAgentMessageOrigin(yield* HttpServerRequest.HttpServerRequest);
1891
- if (!originCheck.ok) return originCheck.response;
1892
- const id = (yield* HttpRouter.params)["id"] ?? "";
1893
- const { message } = yield* readJsonBody$1;
1894
- if (!message) return jsonResponse({ error: "Message required" }, { status: 400 });
1895
- return yield* Effect.promise(() => sendAgentMessage(id, message)).pipe(Effect.map((result) => jsonResponse(result)));
1896
- })));
3087
+ function validateConversationControlAckOrigin(headers, method = "POST") {
3088
+ const origin = getHeaderFromMap(headers, "origin");
3089
+ const referer = getHeaderFromMap(headers, "referer");
3090
+ if (!origin && !referer) return { ok: true };
3091
+ return validateOriginHeaders(headers, method);
1897
3092
  }
1898
- const postAgentMessageRoute = postAgentMessageLikeRoute("/api/agents/:id/message");
1899
- const postAgentTellRoute = postAgentMessageLikeRoute("/api/agents/:id/tell");
1900
- const postAgentPokeRoute = HttpRouter.add("POST", "/api/agents/:id/poke", httpHandler(Effect.gen(function* () {
1901
- const id = (yield* HttpRouter.params)["id"] ?? "";
1902
- const { message } = yield* readJsonBody$1;
1903
- const pokeMsg = message || "You seem to have been inactive for a while. If you're stuck:\n1. Check your current task in continue.vbrief.json\n2. Try an alternative approach if blocked\n3. Ask for help if needed\n\nWhat's your current status?";
1904
- yield* Effect.promise(() => messageAgent(id, pokeMsg));
3093
+ async function handleConversationCodexApproval(rawId, body) {
3094
+ try {
3095
+ const optionNumber = Number(body.optionNumber);
3096
+ if (!Number.isInteger(optionNumber) || optionNumber < 1 || optionNumber > 9) return jsonResponse({ error: "optionNumber must be an integer 1-9" }, { status: 400 });
3097
+ const numericId = Number(rawId);
3098
+ const conv = !Number.isNaN(numericId) && /^\d+$/.test(rawId) ? getConversationById(numericId) : getConversationByName(rawId);
3099
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3100
+ if (getHarnessBehavior(conv.harness).transcriptKind !== "codex-rollout-jsonl") return jsonResponse({ error: "Not a Codex conversation" }, { status: 400 });
3101
+ if (!await tmuxSessionExists(conv.tmuxSession)) return jsonResponse({ error: "Conversation session is not running" }, { status: 409 });
3102
+ const detection = await Effect.runPromise(detectAwaitingInputForAgent(conv.tmuxSession, {
3103
+ isPlanning: false,
3104
+ cache: false
3105
+ }));
3106
+ const parsed = detection ? parseCodexApprovalPrompt(detection.prompt) : null;
3107
+ if (!parsed) return jsonResponse({ error: "No Codex approval prompt is currently pending" }, { status: 409 });
3108
+ if (optionNumber > parsed.options.length) return jsonResponse({ error: `optionNumber out of range (1-${parsed.options.length})` }, { status: 400 });
3109
+ await deliverCodexApprovalChoice(conv.tmuxSession, optionNumber);
3110
+ return jsonResponse({
3111
+ ok: true,
3112
+ optionNumber
3113
+ });
3114
+ } catch (error) {
3115
+ const msg = error instanceof Error ? error.message : String(error);
3116
+ console.error("[conversations] codex approval failed:", msg);
3117
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
3118
+ }
3119
+ }
3120
+ async function handleConversationPlanAction(name, body) {
3121
+ try {
3122
+ const conv = getConversationByName(name);
3123
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3124
+ const action = typeof body["action"] === "string" ? body["action"] : "";
3125
+ const feedback = typeof body["feedback"] === "string" ? body["feedback"].trim() : "";
3126
+ const error = await deliverPlanActionToSession(conv.tmuxSession, action, feedback, resolveConversationDeliveryMethod(conv));
3127
+ if (error) return jsonResponse({ error }, { status: 400 });
3128
+ return jsonResponse({ ok: true });
3129
+ } catch (error) {
3130
+ const msg = error instanceof Error ? error.message : String(error);
3131
+ console.error("[conversations] plan action failed:", msg);
3132
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
3133
+ }
3134
+ }
3135
+ const THINKING_LEVELS = [
3136
+ "off",
3137
+ "minimal",
3138
+ "low",
3139
+ "medium",
3140
+ "high",
3141
+ "xhigh"
3142
+ ];
3143
+ const PI_CONVERSATION_ABORT_KEY = "Escape";
3144
+ function isPiControlChannelHarness(harness) {
3145
+ return harness === "ohmypi" || harness === "pi";
3146
+ }
3147
+ function parseThinkingLevel(value) {
3148
+ return typeof value === "string" && THINKING_LEVELS.includes(value) ? value : null;
3149
+ }
3150
+ function pickDeliverAs(bodyDeliverAs) {
3151
+ if (bodyDeliverAs === "follow_up") return "follow_up";
3152
+ return "steer";
3153
+ }
3154
+ function resolveConversationDeliveryMethod(conv) {
3155
+ const harness = conv.harness ?? "claude-code";
3156
+ if (isPiControlChannelHarness(harness)) return "auto";
3157
+ return conv.deliveryMethod ?? (getHarnessBehavior(harness).deliveryKind === "rpc-fifo" ? "tmux" : "auto");
3158
+ }
3159
+ async function sendConversationControlCommand(conv, commandInput) {
3160
+ const id = randomUUID$1();
3161
+ const ackPromise = registerConversationControlAck(id);
3162
+ const command = {
3163
+ id,
3164
+ ...commandInput
3165
+ };
3166
+ try {
3167
+ await writeConversationControlCommand(conv.tmuxSession, command);
3168
+ } catch (err) {
3169
+ resolveConversationControlAck({
3170
+ id,
3171
+ ok: false,
3172
+ error: err instanceof Error ? err.message : String(err)
3173
+ });
3174
+ }
3175
+ await ackPromise;
3176
+ }
3177
+ async function deliverConversationViaControlChannel(conv, message, options) {
3178
+ await sendConversationControlCommand(conv, {
3179
+ type: options.deliverAs,
3180
+ message,
3181
+ source: options.source
3182
+ });
3183
+ }
3184
+ async function handleConversationThinkingLevel(name, body) {
3185
+ const conv = getConversationByName(name);
3186
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3187
+ if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Thinking level control is only supported for Pi conversations" }, { status: 400 });
3188
+ if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
3189
+ const level = parseThinkingLevel(body["level"]);
3190
+ if (!level) return jsonResponse({ error: "Invalid thinking level" }, { status: 400 });
3191
+ await sendConversationControlCommand(conv, {
3192
+ type: "set_thinking_level",
3193
+ level
3194
+ });
3195
+ setConversationEffort(name, level);
1905
3196
  return jsonResponse({
1906
- success: true,
1907
- message: "Agent poked successfully"
3197
+ ok: true,
3198
+ effort: (getConversationByName(name) ?? conv).effort ?? level
1908
3199
  });
1909
- })));
3200
+ }
3201
+ async function handleConversationCompact(name) {
3202
+ const conv = getConversationByName(name);
3203
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3204
+ if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Compact control endpoint is only supported for Pi conversations" }, { status: 400 });
3205
+ if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
3206
+ await sendConversationControlCommand(conv, { type: "compact" });
3207
+ return jsonResponse({ ok: true });
3208
+ }
3209
+ async function handleConversationAbort(name) {
3210
+ const conv = getConversationByName(name);
3211
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3212
+ if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Abort control endpoint is only supported for Pi conversations" }, { status: 400 });
3213
+ if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
3214
+ await sendKeysAsync(conv.tmuxSession, PI_CONVERSATION_ABORT_KEY, "conversation-abort");
3215
+ return jsonResponse({
3216
+ ok: true,
3217
+ key: PI_CONVERSATION_ABORT_KEY
3218
+ });
3219
+ }
3220
+ async function handleConversationDeliveryMethod(name, body) {
3221
+ if (!getConversationByName(name)) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3222
+ const deliveryMethod = body["deliveryMethod"] ?? body["method"];
3223
+ if (deliveryMethod !== "auto" && deliveryMethod !== "channels" && deliveryMethod !== "tmux" && deliveryMethod !== null) return jsonResponse({ error: "deliveryMethod must be 'auto', 'channels', 'tmux', or null" }, { status: 400 });
3224
+ updateConversationDeliveryMethod(name, deliveryMethod);
3225
+ return jsonResponse({
3226
+ ok: true,
3227
+ deliveryMethod
3228
+ });
3229
+ }
3230
+ const CODEX_APPROVAL_TOOL_PREFIX = "codex-approval:";
3231
+ async function codexConversationPendingInput(conv, sessionAlive, askedAt) {
3232
+ if (!sessionAlive || getHarnessBehavior(conv.harness).transcriptKind !== "codex-rollout-jsonl") return { kinds: [] };
3233
+ try {
3234
+ const detection = await Effect.runPromise(detectAwaitingInputForAgent(conv.tmuxSession, { isPlanning: false }));
3235
+ if (!detection) return { kinds: [] };
3236
+ if (detection.reason === "session_resume") return { kinds: ["sessionResume"] };
3237
+ const parsed = parseCodexApprovalPrompt(detection.prompt);
3238
+ if (parsed) return {
3239
+ kinds: ["permissionRequest"],
3240
+ approval: {
3241
+ toolUseId: `${CODEX_APPROVAL_TOOL_PREFIX}${conv.tmuxSession}`,
3242
+ askedAt,
3243
+ questions: [{
3244
+ question: parsed.detail ? `${parsed.header}\n\n${parsed.detail}` : parsed.header,
3245
+ header: "Codex approval",
3246
+ multiSelect: false,
3247
+ options: parsed.options.map((o) => ({ label: `${o.number}. ${o.label}` }))
3248
+ }]
3249
+ }
3250
+ };
3251
+ return { kinds: ["permissionRequest"] };
3252
+ } catch {
3253
+ return { kinds: [] };
3254
+ }
3255
+ }
3256
+ async function deliverCodexApprovalChoice(tmuxSession, optionNumber) {
3257
+ for (let i = 1; i < optionNumber; i += 1) {
3258
+ await Effect.runPromise(sendRawKeystroke(tmuxSession, "Down", "codex-approval"));
3259
+ await new Promise((r) => setTimeout(r, 60));
3260
+ }
3261
+ await Effect.runPromise(sendRawKeystroke(tmuxSession, "Enter", "codex-approval"));
3262
+ }
1910
3263
  //#endregion
1911
3264
  //#region ../../lib/channels/permission-payload.ts
1912
3265
  const CHANNEL_PERMISSION_LIMITS = {
@@ -2095,7 +3448,7 @@ async function processPermissionResponse(deps, args) {
2095
3448
  }
2096
3449
  //#endregion
2097
3450
  //#region routes/agents/permissions.ts
2098
- init_agents();
3451
+ init_agents$1();
2099
3452
  init_activity_logger();
2100
3453
  init_read_model();
2101
3454
  const getAgentPendingQuestionsRoute = HttpRouter.add("GET", "/api/agents/:id/pending-questions", httpHandler(Effect.gen(function* () {
@@ -2127,6 +3480,30 @@ const postAgentAnswerQuestionRoute = HttpRouter.add("POST", "/api/agents/:id/ans
2127
3480
  delivered: answers.length
2128
3481
  });
2129
3482
  })));
3483
+ const postAgentPlanActionRoute = HttpRouter.add("POST", "/api/agents/:id/plan-action", httpHandler(Effect.gen(function* () {
3484
+ const id = (yield* HttpRouter.params)["id"] ?? "";
3485
+ if (!id.trim()) return jsonResponse({ error: "missing agent id" }, { status: 400 });
3486
+ const body = yield* readJsonBody$1;
3487
+ const action = typeof body["action"] === "string" ? body["action"] : "";
3488
+ const feedback = typeof body["feedback"] === "string" ? body["feedback"].trim() : "";
3489
+ const agentState = yield* getAgentState(id).pipe(Effect.catch(() => Effect.succeed(null)));
3490
+ if (!agentState) return jsonResponse({ error: "Agent not found" }, { status: 404 });
3491
+ const jsonlPath = yield* getAgentJsonlPath$1(id).pipe(Effect.catch(() => Effect.succeed(null)));
3492
+ if (!(jsonlPath ? yield* Effect.promise(() => scanPendingInputsPromise(jsonlPath)) : null)?.exitPlanModePending) return jsonResponse({ error: "No pending plan approval for this agent" }, { status: 409 });
3493
+ const error = yield* Effect.promise(() => deliverPlanActionToSession(id, action, feedback));
3494
+ if (error) return jsonResponse({ error }, { status: 400 });
3495
+ emitActivityEntrySync({
3496
+ source: "dashboard",
3497
+ level: "info",
3498
+ issueId: agentState.issueId,
3499
+ message: `Operator ${action.startsWith("approve") ? "approved" : "requested changes to"} ${id}'s plan from the dashboard`
3500
+ });
3501
+ return jsonResponse({
3502
+ success: true,
3503
+ agentId: id,
3504
+ action
3505
+ });
3506
+ })));
2130
3507
  const postInternalAgentPermissionRequestRoute = HttpRouter.add("POST", "/api/internal/agents/:id/permissions/request", httpHandler(Effect.gen(function* () {
2131
3508
  const id = (yield* HttpRouter.params)["id"] ?? "";
2132
3509
  if (!id.trim()) return jsonResponse({
@@ -2134,7 +3511,7 @@ const postInternalAgentPermissionRequestRoute = HttpRouter.add("POST", "/api/int
2134
3511
  error: "missing agent id"
2135
3512
  }, { status: 400 });
2136
3513
  const request = yield* HttpServerRequest.HttpServerRequest;
2137
- const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-CCaWaTwE.js"));
3514
+ const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-D-t28de5.js"));
2138
3515
  const expected = getInternalTokenSync();
2139
3516
  if (!expected) return jsonResponse({
2140
3517
  ok: false,
@@ -2401,7 +3778,7 @@ const getAgentHealthHistoryRoute = HttpRouter.add("GET", "/api/agents/:id/health
2401
3778
  const request = yield* HttpServerRequest.HttpServerRequest;
2402
3779
  const urlOpt = HttpServerRequest.toURL(request);
2403
3780
  const hours = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("hours") ?? "24" : "24";
2404
- const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-B_WsQyYm.js"));
3781
+ const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-B0mj6Iae.js"));
2405
3782
  const endTime = /* @__PURE__ */ new Date();
2406
3783
  const startTime = /* @__PURE__ */ new Date(endTime.getTime() - parseInt(hours) * 60 * 60 * 1e3);
2407
3784
  const events = getHealthHistory(id, startTime.toISOString(), endTime.toISOString());
@@ -2442,7 +3819,7 @@ function emitAgentRuntimeEvent(id, body, timestamp) {
2442
3819
  detail: String(decoded.failure)
2443
3820
  }, { status: 400 })
2444
3821
  };
2445
- const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-C_2ZAQRP.js"));
3822
+ const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-BUfovFfB.js"));
2446
3823
  yield* (yield* AgentStateService).emit(decoded.success);
2447
3824
  return {
2448
3825
  ok: true,
@@ -2508,14 +3885,14 @@ const postAgentStuckRoute = HttpRouter.add("POST", "/api/agents/:id/stuck", http
2508
3885
  });
2509
3886
  })));
2510
3887
  function hasNegatedCompletionOutput(output) {
2511
- return /\b(not|never|no|cannot|can't|blocked|waiting|needs input|not yet|isn't|aren't)\b.{0,48}\b(implementation complete|all beads closed|ready for review|work complete)\b/i.test(output);
3888
+ return /\b(not|never|no|cannot|can't|blocked|waiting|needs input|not yet|isn't|aren't)\b.{0,48}\b(implementation complete|all tasks closed|ready for review|work complete)\b/i.test(output);
2512
3889
  }
2513
3890
  const postAgentClassifyCompletionRoute = HttpRouter.add("POST", "/api/agents/:id/classify-completion", httpHandler(Effect.gen(function* () {
2514
3891
  const body = yield* readJsonBody$1;
2515
3892
  const output = typeof body["output"] === "string" ? body["output"] : "";
2516
3893
  return jsonResponse({
2517
3894
  success: true,
2518
- verdict: /blocked|needs input|waiting for|not ready for review/i.test(output) || hasNegatedCompletionOutput(output) ? "STOPPED_FOR_INPUT" : /Implementation complete|all beads closed|ready for review|work complete/i.test(output) ? "FORGOT_COMPLETION" : "UNCLEAR"
3895
+ verdict: /blocked|needs input|waiting for|not ready for review/i.test(output) || hasNegatedCompletionOutput(output) ? "STOPPED_FOR_INPUT" : /Implementation complete|all tasks closed|ready for review|work complete/i.test(output) ? "FORGOT_COMPLETION" : "UNCLEAR"
2519
3896
  });
2520
3897
  })));
2521
3898
  const getAgentRuntimeRoute = HttpRouter.add("GET", "/api/agents/:id/runtime", httpHandler(Effect.gen(function* () {
@@ -2524,7 +3901,7 @@ const getAgentRuntimeRoute = HttpRouter.add("GET", "/api/agents/:id/runtime", ht
2524
3901
  success: false,
2525
3902
  error: "missing agent id"
2526
3903
  }, { status: 400 });
2527
- const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-C_2ZAQRP.js"));
3904
+ const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-BUfovFfB.js"));
2528
3905
  const snapshot = yield* (yield* AgentStateService).get(id);
2529
3906
  if (!snapshot) return jsonResponse({
2530
3907
  success: false,
@@ -2550,7 +3927,9 @@ init_workspace_manager();
2550
3927
  * commit, the persisted event is emitted to the event store's subscribers so
2551
3928
  * the in-memory read model stays current.
2552
3929
  */
2553
- init_agents();
3930
+ init_event_store();
3931
+ init_agents$1();
3932
+ init_persistent_logger();
2554
3933
  function prepareAgentStateForSave(state) {
2555
3934
  if (state.status === "running" || state.status === "starting") delete state.stoppedAt;
2556
3935
  else if (state.status === "stopped" && !state.stoppedAt) state.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2617,7 +3996,7 @@ function saveAgentStateAndEmitEventWithDeps(db, eventStore, state, event) {
2617
3996
  }
2618
3997
  //#endregion
2619
3998
  //#region routes/agents/lifecycle-stop.ts
2620
- init_agents();
3999
+ init_agents$1();
2621
4000
  init_activity_logger();
2622
4001
  init_tmux();
2623
4002
  init_domain_services();
@@ -2635,8 +4014,8 @@ function createAgentStopHandler(lifecycleEvent) {
2635
4014
  yield* stopAgent(id);
2636
4015
  if (stateBeforeStop?.issueId) yield* Effect.promise(async () => {
2637
4016
  try {
2638
- const { resolveProjectFromIssueSync } = await import("./projects-KJ94e50I.js");
2639
- const { findWorkspacePath } = await import("./archive-planning-CTILQRnV.js");
4017
+ const { resolveProjectFromIssueSync } = await import("./projects-Dc9aoVoG.js");
4018
+ const { findWorkspacePath } = await import("./archive-planning-D-QsBufn.js");
2640
4019
  const issueLower = stateBeforeStop.issueId.toLowerCase();
2641
4020
  const workspacePath = findWorkspacePath(resolveProjectFromIssueSync(stateBeforeStop.issueId)?.projectPath ?? process.cwd(), issueLower);
2642
4021
  if (workspacePath) {
@@ -2806,7 +4185,9 @@ const postAgentUntroubledRoute = HttpRouter.add("POST", "/api/agents/:id/untroub
2806
4185
  })));
2807
4186
  //#endregion
2808
4187
  //#region routes/agents/lifecycle-restart.ts
2809
- init_agents();
4188
+ init_config_yaml();
4189
+ init_agents$1();
4190
+ init_harness_policy();
2810
4191
  init_operator_interventions();
2811
4192
  init_projects();
2812
4193
  init_tmux();
@@ -3092,9 +4473,14 @@ const postAgentRestartFreshRoute = HttpRouter.add("POST", "/api/agents/:id/resta
3092
4473
  const agentState = yield* getAgentState(id);
3093
4474
  if (!agentState) return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });
3094
4475
  const issueId = agentState.issueId ?? id.replace(/^agent-/, "").toUpperCase();
4476
+ const issueStage = getIssueStageSync(issueId);
4477
+ if (wantsSpawn && isTerminalIssueStage(issueStage)) return jsonResponse({
4478
+ error: `${issueId} is already ${issueStage?.replaceAll("_", " ")}. Reopen the issue before starting fresh work.`,
4479
+ issueStage
4480
+ }, { status: 409 });
3095
4481
  const lifecycle = yield* getWorkAgentLifecycleState(id);
3096
4482
  if (lifecycle.hasLiveTmuxSession) return jsonResponse({
3097
- error: `Agent ${id} has a live tmux session. Run 'pan kill ${issueId}' first, then retry.`,
4483
+ error: `Agent ${id} has a live tmux session. Run 'pan kill ${id}' to stop only this work agent, then retry.`,
3098
4484
  lifecycle
3099
4485
  }, { status: 409 });
3100
4486
  yield* Effect.promise(() => appendAgentLifecycleLog(id, "agent.restart_fresh_requested", {
@@ -3157,9 +4543,21 @@ const postAgentRestartFreshRoute = HttpRouter.add("POST", "/api/agents/:id/resta
3157
4543
  cwd: workspacePath
3158
4544
  }));
3159
4545
  } catch (err) {
4546
+ saveAgentStateSync({
4547
+ id: agentSessionName,
4548
+ issueId,
4549
+ workspace: workspacePath,
4550
+ harness: effectiveHarness ?? agentState.harness ?? "claude-code",
4551
+ role: "work",
4552
+ model: spawnModel,
4553
+ status: "stopped",
4554
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4555
+ stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
4556
+ });
3160
4557
  return jsonResponse({
3161
4558
  success: false,
3162
- error: `Agent dir wiped but spawn failed: ${err?.message ?? String(err)}`,
4559
+ error: "The old agent state was cleared, but the fresh agent could not start. Resolve the startup blocker, then try again.",
4560
+ details: err?.message ?? String(err),
3163
4561
  wiped: wipeResult.removed
3164
4562
  }, { status: 500 });
3165
4563
  }
@@ -3345,6 +4743,7 @@ async function resolveCurrentStaffing(agentId, agentState, issueId) {
3345
4743
  const staffing = resolveStaffing(workItem, {
3346
4744
  planMetadata: plan.plan.metadata,
3347
4745
  spawnKey: `work:${issueId.toLowerCase()}`,
4746
+ issueId,
3348
4747
  config: {
3349
4748
  ...config,
3350
4749
  tieredExecution: {
@@ -3441,7 +4840,7 @@ init_config();
3441
4840
  init_jsonl_parser();
3442
4841
  init_model_validation();
3443
4842
  init_paths();
3444
- init_agents();
4843
+ init_agents$1();
3445
4844
  const getAgentCloisterHealthRoute = HttpRouter.add("GET", "/api/agents/:id/cloister-health", httpHandler(Effect.gen(function* () {
3446
4845
  const id = (yield* HttpRouter.params)["id"] ?? "";
3447
4846
  const health = getCloisterService().getAgentHealth(id);
@@ -3593,6 +4992,7 @@ const postAgentSwitchModelRoute = HttpRouter.add("POST", "/api/agents/:id/switch
3593
4992
  //#endregion
3594
4993
  //#region ../../lib/vbrief/auto-synthesize.ts
3595
4994
  init_state_read_home();
4995
+ init_codex_auth();
3596
4996
  init_state_home();
3597
4997
  init_io();
3598
4998
  function cleanMarkdownLine(line) {
@@ -3711,19 +5111,20 @@ const writeAutoStartVBrief = (projectRoot, workspacePath, issue) => Effect.tryPr
3711
5111
  //#endregion
3712
5112
  //#region routes/agents/spawn-helpers.ts
3713
5113
  init_issue_lifecycle();
3714
- init_agents();
5114
+ init_child_env();
5115
+ init_agents$1();
3715
5116
  function handleRemoteAgentSpawn(input) {
3716
5117
  return Effect.gen(function* () {
3717
5118
  const { issueId, workspacePath, workspaceMetadata, spawnModel, projectPath, spawnGuardrails, lifecycle } = input;
3718
- const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-DlvbzO7m.js"));
3719
- const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-DmeL21dF.js"));
3720
- const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-BRNQJXhA.js"));
5119
+ const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-DYY5fFpd.js"));
5120
+ const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-Cvu4CSDk.js"));
5121
+ const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-6MCHOZ2N.js"));
3721
5122
  const panConfig = loadPanConfig();
3722
5123
  const spendCap = checkRemoteSpendCap(panConfig);
3723
5124
  if (!spendCap.allowed) return jsonResponse({ error: spendCap.message }, { status: 429 });
3724
5125
  const fly = createFlyProviderFromConfig(panConfig.remote);
3725
5126
  yield* Effect.promise(() => fly.syncAllCredentials(workspaceMetadata.vmName));
3726
- const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-fZljjA1_.js"));
5127
+ const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-BiJ43Der.js"));
3727
5128
  const trackerContext = yield* Effect.promise(() => getTrackerContext(issueId, workspacePath));
3728
5129
  const agentPrompt = yield* Effect.promise(() => buildWorkAgentPrompt({
3729
5130
  issueId,
@@ -3809,7 +5210,7 @@ function handleContainerOrchestration(input) {
3809
5210
  if (existsSync(workspacePath) && existsSync(devScript)) {
3810
5211
  let dockerRunning = false;
3811
5212
  try {
3812
- yield* Effect.promise(() => execAsync$1("docker info >/dev/null 2>&1", { encoding: "utf-8" }));
5213
+ yield* Effect.promise(() => execAsync$2("docker info >/dev/null 2>&1", { encoding: "utf-8" }));
3813
5214
  dockerRunning = true;
3814
5215
  } catch {}
3815
5216
  if (dockerRunning) {
@@ -3852,7 +5253,7 @@ function handleContainerOrchestration(input) {
3852
5253
  }));
3853
5254
  let containersReady = false;
3854
5255
  try {
3855
- const { stdout: existing } = yield* Effect.promise(() => execFileAsync$1("docker", [
5256
+ const { stdout: existing } = yield* Effect.promise(() => execFileAsync$2("docker", [
3856
5257
  "ps",
3857
5258
  "--filter",
3858
5259
  `name=${featureName}`,
@@ -3918,7 +5319,7 @@ function handleContainerOrchestration(input) {
3918
5319
  let healthy = false;
3919
5320
  while (Date.now() - startTime < maxWaitMs) {
3920
5321
  try {
3921
- const { stdout } = await execFileAsync$1("docker", [
5322
+ const { stdout } = await execFileAsync$2("docker", [
3922
5323
  "ps",
3923
5324
  "--filter",
3924
5325
  `name=${featureName}`,
@@ -4038,7 +5439,7 @@ function handleContainerOrchestration(input) {
4038
5439
  }
4039
5440
  //#endregion
4040
5441
  //#region routes/agents/spawn.ts
4041
- init_agents();
5442
+ init_agents$1();
4042
5443
  init_child_env();
4043
5444
  init_harness_policy();
4044
5445
  init_activity_logger();
@@ -4070,45 +5471,6 @@ function spawnGuardrailResourcesHint(hint) {
4070
5471
  const resourcesHint = "Open /resources to inspect Machine Room pressure before retrying.";
4071
5472
  return hint ? `${hint} ${resourcesHint}` : resourcesHint;
4072
5473
  }
4073
- /**
4074
- * Count materialized beads for an issue by shelling out to `bd list`.
4075
- *
4076
- * Returns 0 on ANY bd failure — non-zero exit (e.g. bd refusing with "Linear
4077
- * data has never been pulled"), an unparseable JSON body, or the 10s timeout.
4078
- * The caller treats 0 as "no beads yet" and falls through to auto-recovery
4079
- * (createBeadsFromVBrief) and, failing that, a clean 422 `beads_required`.
4080
- *
4081
- * The failure is folded into the success channel with `Effect.matchCause`. A
4082
- * bare `try/catch` around `yield* withBdMutex(...)` does NOT work: withBdMutex
4083
- * converts the child-process rejection into an Effect error-channel failure
4084
- * (via its internal `Effect.tryPromise`), which short-circuits the fiber and is
4085
- * invisible to a JS `try/catch`. Before this helper, that escaped as an
4086
- * unhandled 500 on POST /api/agents instead of the designed 422.
4087
- */
4088
- const countBeadsForIssue = (workspacePath, issueLower) => withBdMutex(() => Effect.promise(() => execFileAsync$1("bd", [
4089
- "list",
4090
- "--json",
4091
- "-l",
4092
- issueLower,
4093
- "--status",
4094
- "all",
4095
- "--limit",
4096
- "0"
4097
- ], {
4098
- cwd: workspacePath,
4099
- encoding: "utf-8",
4100
- timeout: 1e4
4101
- }))).pipe(Effect.matchCause({
4102
- onFailure: () => 0,
4103
- onSuccess: ({ stdout }) => {
4104
- try {
4105
- const bdTasks = JSON.parse(stdout.trim() || "[]");
4106
- return Array.isArray(bdTasks) ? bdTasks.length : 0;
4107
- } catch {
4108
- return 0;
4109
- }
4110
- }
4111
- }));
4112
5474
  const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect.gen(function* () {
4113
5475
  const originCheck = validateOrigin(yield* HttpServerRequest.HttpServerRequest);
4114
5476
  if (!originCheck.ok) return jsonResponse({
@@ -4176,7 +5538,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4176
5538
  const workspacePath = join(projectPath, "workspaces", `feature-${issueLower}`);
4177
5539
  if (!existsSync(workspacePath)) try {
4178
5540
  const nodeDir = dirname(process.execPath);
4179
- yield* Effect.promise(() => execAsync$1(`pan workspace create ${issueId} --local`, {
5541
+ yield* Effect.promise(() => execAsync$2(`pan workspace create ${issueId} --local`, {
4180
5542
  cwd: projectPath,
4181
5543
  encoding: "utf-8",
4182
5544
  timeout: 6e4,
@@ -4190,13 +5552,6 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4190
5552
  }
4191
5553
  const workspacePanDir = join(workspacePath, PAN_DIRNAME);
4192
5554
  const workspacePanContinuePath = join(workspacePanDir, PAN_CONTINUE_FILENAME);
4193
- const workspaceBeadsDir = join(workspacePath, ".beads");
4194
- if (!existsSync(workspaceBeadsDir)) {
4195
- const projectRootBeadsDir = join(projectPath, ".beads");
4196
- if (existsSync(projectRootBeadsDir)) try {
4197
- yield* Effect.promise(() => cp(projectRootBeadsDir, workspaceBeadsDir, { recursive: true }));
4198
- } catch {}
4199
- }
4200
5555
  let planPath = yield* findPlan(workspacePath);
4201
5556
  if (autoStart && !planPath) {
4202
5557
  yield* writeAutoStartVBrief(projectPath, workspacePath, {
@@ -4208,7 +5563,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4208
5563
  planPath = yield* findPlan(workspacePath);
4209
5564
  }
4210
5565
  if (!planPath) return jsonResponse({
4211
- error: `No workspace vBRIEF found for ${issueId}. Work agents require a finalized plan with matching beads.`,
5566
+ error: `No workspace vBRIEF found for ${issueId}. Work agents require a finalized plan.`,
4212
5567
  hint: "Run planning first, or use auto-start to synthesize a plan before starting the work agent.",
4213
5568
  issueId
4214
5569
  }, { status: 422 });
@@ -4239,44 +5594,11 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4239
5594
  expectedIssue: issueId,
4240
5595
  actualIssue: planIssueId.toUpperCase()
4241
5596
  }, { status: 422 });
4242
- const planItemCount = planDoc?.plan?.items?.length ?? 0;
4243
- if (planItemCount === 0) return jsonResponse({
5597
+ if ((planDoc?.plan?.items?.length ?? 0) === 0) return jsonResponse({
4244
5598
  error: "Plan exists but contains no items. Planning may have failed or produced an empty plan.",
4245
5599
  hint: "Re-run planning to produce a plan with tasks and acceptance criteria.",
4246
5600
  issueId
4247
5601
  }, { status: 422 });
4248
- let beadCount = yield* countBeadsForIssue(workspacePath, issueLower);
4249
- let hasBeads = beadCount > 0;
4250
- if (planItemCount !== null && planItemCount > 0 && beadCount !== planItemCount) hasBeads = false;
4251
- let recoveryError = null;
4252
- if (!hasBeads) {
4253
- console.log(`[agents] No beads for ${issueId} — attempting auto-recovery via createBeadsFromVBrief`);
4254
- try {
4255
- const { createBeadsFromVBrief } = yield* Effect.promise(() => import("./beads-CYsLiyIM.js"));
4256
- const recovery = yield* createBeadsFromVBrief(workspacePath);
4257
- beadCount = recovery.created.length;
4258
- hasBeads = recovery.created.length > 0 && (planItemCount === null || recovery.created.length === planItemCount);
4259
- if (hasBeads) console.log(`[agents] Auto-recovery created ${recovery.created.length} beads for ${issueId}`);
4260
- else if (recovery.created.length > 0 && planItemCount !== null) recoveryError = `created ${recovery.created.length} beads, but vBRIEF has ${planItemCount} plan items`;
4261
- else if (recovery.errors.length > 0) {
4262
- recoveryError = recovery.errors[0] ?? "Unknown error during beads creation";
4263
- console.warn(`[agents] Auto-recovery errors: ${recovery.errors.join(", ")}`);
4264
- } else recoveryError = "createBeadsFromVBrief returned no beads and no errors";
4265
- } catch (recoveryErr) {
4266
- recoveryError = recoveryErr.message;
4267
- console.warn(`[agents] Auto-recovery failed: ${recoveryErr.message}`);
4268
- }
4269
- }
4270
- if (!hasBeads) {
4271
- const detail = recoveryError ? ` Beads recovery failed: ${recoveryError}.` : " No beads exist for this issue.";
4272
- console.warn(`[agents] Refusing to start work agent for ${issueId} without beads.${detail}`);
4273
- return jsonResponse({
4274
- success: false,
4275
- error: "beads_required",
4276
- message: `Cannot start work agent for ${issueId} without beads.${detail} Re-run planning so beads can be materialized from the vBRIEF plan, then retry.`,
4277
- ...recoveryError ? { recoveryError } : {}
4278
- }, { status: 422 });
4279
- }
4280
5602
  const health = yield* readModel.getSnapshot.pipe(Effect.flatMap((snapshot) => Effect.promise(() => getSystemHealthSnapshot(snapshot))));
4281
5603
  emitStartAgentPhase(issueId, "guardrails", "start", "evaluating spawn guardrails");
4282
5604
  const spawnGuardrails = evaluateSpawnGuardrails(health);
@@ -4320,7 +5642,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4320
5642
  return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });
4321
5643
  }
4322
5644
  if ((yield* Effect.promise(() => getProviderAuthMode(spawnModel))) === "subscription") {
4323
- const codexAuth = yield* checkCodexAuthStatus();
5645
+ const codexAuth = yield* checkCodexAuthStatus({ agentStates: listAgentStates() });
4324
5646
  if (codexAuth.status === "expired" || codexAuth.status === "burned") return jsonResponse({
4325
5647
  success: false,
4326
5648
  blocked: true,
@@ -4398,24 +5720,20 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4398
5720
  if (shouldCommitLegacyWorkspaceArtifacts(projectConfig ? yield* Effect.promise(() => isStateMigrated(projectConfig)) : false) && (existsSync(workspacePanContinuePath) || existsSync(workspacePanDir))) yield* Effect.gen(function* () {
4399
5721
  const gitRoot = workspacePath;
4400
5722
  if (existsSync(join(gitRoot, ".pan"))) {
4401
- yield* Effect.promise(() => execAsync$1(`git add .pan/`, {
5723
+ yield* Effect.promise(() => execAsync$2(`git add .pan/`, {
4402
5724
  cwd: gitRoot,
4403
5725
  encoding: "utf-8"
4404
5726
  }));
4405
- yield* Effect.promise(() => execAsync$1(`git reset HEAD -- .pan/kickoff.md .pan/continue.json .pan/handoff-*.md .pan/spec.vbrief.json`, {
5727
+ yield* Effect.promise(() => execAsync$2(`git reset HEAD -- .pan/kickoff.md .pan/continue.json .pan/handoff-*.md .pan/spec.vbrief.json`, {
4406
5728
  cwd: gitRoot,
4407
5729
  encoding: "utf-8"
4408
5730
  }));
4409
5731
  }
4410
- if (existsSync(join(gitRoot, ".beads"))) yield* Effect.promise(() => execAsync$1(`git add .beads/`, {
4411
- cwd: gitRoot,
4412
- encoding: "utf-8"
4413
- }));
4414
- if (yield* Effect.promise(() => execAsync$1(`git diff --cached --quiet`, {
5732
+ if (yield* Effect.promise(() => execAsync$2(`git diff --cached --quiet`, {
4415
5733
  cwd: gitRoot,
4416
5734
  encoding: "utf-8"
4417
5735
  }).then(() => false).catch(() => true))) {
4418
- yield* Effect.promise(() => execAsync$1(`git commit -m "chore: planning artifacts for ${issueId} before agent start"`, {
5736
+ yield* Effect.promise(() => execAsync$2(`git commit -m "chore: planning artifacts for ${issueId} before agent start"`, {
4419
5737
  cwd: gitRoot,
4420
5738
  encoding: "utf-8"
4421
5739
  }));
@@ -4442,7 +5760,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4442
5760
  console.warn(`[start-agent] Failed to set plan.status=running (non-fatal): ${planStatusErr?.message ?? planStatusErr}`);
4443
5761
  }
4444
5762
  try {
4445
- const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = yield* Effect.promise(() => import("./record-pyB6VA32.js"));
5763
+ const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = yield* Effect.promise(() => import("./record-DLOqfbkI.js"));
4446
5764
  const recordProject = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);
4447
5765
  yield* Effect.promise(() => appendSessionEntry(recordProject, issueId, {
4448
5766
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4491,12 +5809,12 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4491
5809
  console.log(`[start-agent] Killed stale tmux session ${agentSessionName}`);
4492
5810
  }).pipe(Effect.catch(() => Effect.void));
4493
5811
  if (!(body.acknowledgeDirtyWorkspace === true)) try {
4494
- const { stdout: statusOut } = yield* Effect.promise(() => execAsync$1("git status --porcelain", {
5812
+ const { stdout: statusOut } = yield* Effect.promise(() => execAsync$2("git status --porcelain", {
4495
5813
  cwd: workspacePath,
4496
5814
  encoding: "utf-8"
4497
5815
  }));
4498
5816
  if (statusOut.trim()) {
4499
- const { stdout: diffOut } = yield* Effect.promise(() => execAsync$1("git diff HEAD --stat", {
5817
+ const { stdout: diffOut } = yield* Effect.promise(() => execAsync$2("git diff HEAD --stat", {
4500
5818
  cwd: workspacePath,
4501
5819
  encoding: "utf-8"
4502
5820
  }).catch(() => ({ stdout: "" })));
@@ -4688,39 +6006,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4688
6006
  * Composes all /api/agents/* route layers and preserves the public helper
4689
6007
  * exports that external callers historically imported from routes/agents.js.
4690
6008
  */
4691
- const agentsRouteLayer = Layer.mergeAll(getAgentsRoute, getAgentOutputRoute, getAgentConversationRoute, postAgentMessageRoute, postAgentTellRoute, deleteAgentRoute, postAgentStopRoute, getAgentHealthHistoryRoute, postAgentPokeRoute, getAgentPendingQuestionsRoute, postAgentAnswerQuestionRoute, postAgentHeartbeatRoute, postAgentWorkCompleteRoute, postAgentStuckRoute, postAgentClassifyCompletionRoute, postInternalAgentPermissionRequestRoute, postAgentPermissionResponseRoute, getAgentRuntimeRoute, getAgentGitInfoRoute, getAgentActivityRoute, getAgentFilesRoute, getAgentTimelineRoute, postAgentSuspendRoute, postAgentPauseRoute, postAgentUnpauseRoute, postAgentUntroubledRoute, postAgentResumeRoute, postAgentRecoverRoute, postAgentRestartRoute, getAgentCloisterHealthRoute, getAgentHandoffSuggestionRoute, postAgentHandoffRoute, getAgentCostRoute, postAgentsRoute, postAgentsRestartAllRoute, getAgentTmuxAliveRoute, getAgentHasSessionRoute, postAgentResetSessionRoute, postAgentSwitchModelRoute, postAgentRestartFreshRoute, postAgentDeliveryMethodRoute, getAgentsRestartConfigRoute, postAgentsRestartWithConfigRoute);
4692
- //#endregion
4693
- //#region ../../lib/cloister/task-readiness.ts
4694
- init_io();
4695
- const TERMINAL_STATUSES = ["completed", "cancelled"];
4696
- /**
4697
- * Returns the list of item IDs that are newly unblocked after `justCompletedId`
4698
- * reaches a terminal status.
4699
- *
4700
- * "Newly unblocked" means: the item was not ready before (had at least one
4701
- * non-terminal blocker) and is now ready after this completion.
4702
- *
4703
- * Returns [] when no plan exists.
4704
- */
4705
- function getUnblockedItemsSync(workspacePath, justCompletedId) {
4706
- const doc = readWorkspacePlanSync(workspacePath);
4707
- if (!doc) return [];
4708
- const itemById = new Map(doc.plan.items.map((i) => [i.id, i]));
4709
- const directlyUnblocking = doc.plan.edges.filter((e) => e.type === "blocks" && e.from === justCompletedId).map((e) => e.to);
4710
- const unblocked = [];
4711
- for (const candidateId of directlyUnblocking) {
4712
- const candidate = itemById.get(candidateId);
4713
- if (!candidate) continue;
4714
- if (TERMINAL_STATUSES.includes(candidate.status)) continue;
4715
- if (doc.plan.edges.filter((e) => e.type === "blocks" && e.to === candidateId).map((e) => e.from).every((blockerId) => {
4716
- const blocker = itemById.get(blockerId);
4717
- if (!blocker) return true;
4718
- if (blockerId === justCompletedId) return true;
4719
- return TERMINAL_STATUSES.includes(blocker.status);
4720
- })) unblocked.push(candidateId);
4721
- }
4722
- return unblocked;
4723
- }
6009
+ const agentsRouteLayer = Layer.mergeAll(getAgentsRoute, getAgentOutputRoute, getAgentConversationRoute, postAgentMessageRoute, postAgentTellRoute, deleteAgentRoute, postAgentStopRoute, getAgentHealthHistoryRoute, postAgentPokeRoute, getAgentPendingQuestionsRoute, postAgentAnswerQuestionRoute, postAgentPlanActionRoute, postAgentHeartbeatRoute, postAgentWorkCompleteRoute, postAgentStuckRoute, postAgentClassifyCompletionRoute, postInternalAgentPermissionRequestRoute, postAgentPermissionResponseRoute, getAgentRuntimeRoute, getAgentGitInfoRoute, getAgentActivityRoute, getAgentFilesRoute, getAgentTimelineRoute, postAgentSuspendRoute, postAgentPauseRoute, postAgentUnpauseRoute, postAgentUntroubledRoute, postAgentResumeRoute, postAgentRecoverRoute, postAgentRestartRoute, getAgentCloisterHealthRoute, getAgentHandoffSuggestionRoute, postAgentHandoffRoute, getAgentCostRoute, postAgentsRoute, postAgentsRestartAllRoute, getAgentTmuxAliveRoute, getAgentHasSessionRoute, postAgentResetSessionRoute, postAgentSwitchModelRoute, postAgentRestartFreshRoute, postAgentDeliveryMethodRoute, getAgentsRestartConfigRoute, postAgentsRestartWithConfigRoute);
4724
6010
  //#endregion
4725
6011
  //#region routes/tiered-inspect-escalation.ts
4726
6012
  init_tier_escalation();
@@ -4745,18 +6031,18 @@ function handleTieredInspectFailureEscalation(issueId, notes, deps = {}) {
4745
6031
  if (!exists(workspacePath)) return null;
4746
6032
  const doc = readPlan(workspacePath);
4747
6033
  if (!resolveTieredExecutionEnabled(tiered, doc?.plan.metadata)) return null;
4748
- const beadId = notes?.match(/[Bb]ead\s+(\S+)/)?.[1];
4749
- if (!doc || !beadId) return null;
4750
- const item = doc.plan.items.find((candidate) => candidate.id === beadId);
6034
+ const taskId = notes?.match(/[Bb]ead\s+(\S+)/)?.[1];
6035
+ if (!doc || !taskId) return null;
6036
+ const item = doc.plan.items.find((candidate) => candidate.id === taskId);
4751
6037
  if (!item) return null;
4752
6038
  const overrides = readOverrides(workspacePath);
4753
6039
  const decision = decide({
4754
6040
  kind: "supervisor-blocked",
4755
- beadId,
6041
+ taskId,
4756
6042
  sha: getReviewStatusSync(issueId)?.reviewedAtCommit ?? "unknown",
4757
6043
  attemptsAtCurrentTier: tiered.escalation.retries_at_tier
4758
6044
  }, item, tiered.escalation, overrides);
4759
- if (decision.action === "promote") recordPromotion(workspacePath, beadId, decision.from, decision.to, decision.reason);
6045
+ if (decision.action === "promote") recordPromotion(workspacePath, taskId, decision.from, decision.to, decision.reason);
4760
6046
  return decision;
4761
6047
  }
4762
6048
  async function reportTieredInspectFailureEscalation(issueId, notes, deps = {}) {
@@ -4860,10 +6146,10 @@ const _serverManagedMerges = /* @__PURE__ */ new Set();
4860
6146
  function firePostMergeLifecycle(issueId, options) {
4861
6147
  const started = postMergeGuard.run(issueId, async () => {
4862
6148
  const projectPath = getProjectPathForIssue(extractPrefixSync(issueId) ?? issueId.split("-")[0]);
4863
- const { postMergeLifecycle } = await import("./merge-agent-36wfYm2u.js");
6149
+ const { postMergeLifecycle } = await import("./merge-agent-DMQTJMP0.js");
4864
6150
  await postMergeLifecycle(issueId, projectPath, options?.sourceBranch, options?.verifiedMergedRef ? { verifiedMergedRef: options.verifiedMergedRef } : void 0);
4865
6151
  console.log(`[merge] post-merge lifecycle completed for ${issueId}`);
4866
- const { runMergeTrainReconcile } = await import("./merge-train-DGqVuNZV.js");
6152
+ const { runMergeTrainReconcile } = await import("./merge-train-DJgr81tF.js");
4867
6153
  const outcomes = await runMergeTrainReconcile(issueId);
4868
6154
  if (outcomes.length > 0) console.log(`[merge-train] reconciled ${outcomes.length} sibling(s) after ${issueId}: ` + outcomes.map((o) => `${o.issueId}=${o.result}`).join(", "));
4869
6155
  }, (err) => console.error(`[merge] post-merge lifecycle failed for ${issueId}:`, err));
@@ -4877,22 +6163,21 @@ function getProjectPathForIssue(issuePrefix) {
4877
6163
  }
4878
6164
  //#endregion
4879
6165
  //#region routes/specialists/legacy-routes.ts
4880
- init_agents();
6166
+ init_agents$1();
4881
6167
  init_projects();
4882
6168
  init_review_status();
4883
- init_io();
4884
6169
  init_domain_services();
4885
6170
  init_tmux();
4886
6171
  const getSpecialistsRoute = HttpRouter.add("GET", "/api/specialists", httpHandler(Effect.gen(function* () {
4887
- const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6172
+ const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
4888
6173
  return jsonResponse({
4889
6174
  specialists: yield* Effect.promise(() => getAllSpecialistStatus()),
4890
6175
  projects: yield* Effect.promise(() => getAllProjectSpecialistStatuses())
4891
6176
  });
4892
6177
  })));
4893
6178
  const postSpecialistsResetAllRoute = HttpRouter.add("POST", "/api/specialists/reset-all", httpHandler(Effect.gen(function* () {
4894
- const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
4895
- const { clearHookSync } = yield* Effect.promise(() => import("./hooks-C1ScyVxP.js"));
6179
+ const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
6180
+ const { clearHookSync } = yield* Effect.promise(() => import("./hooks-BDcQJDUe.js"));
4896
6181
  const specialists = getAllSpecialists();
4897
6182
  const results = [];
4898
6183
  for (const specialist of specialists) {
@@ -4942,7 +6227,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
4942
6227
  const normalizedIssueId = issueId.toUpperCase();
4943
6228
  console.log(`[specialists/done] ${specialist} signaling ${status} for ${normalizedIssueId}`);
4944
6229
  if (specialist === "merge") {
4945
- const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-BZ8M-WZ0.js"));
6230
+ const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-pJtESo7m.js"));
4946
6231
  if (yield* reportSpecialistCompletion(normalizedIssueId, {
4947
6232
  status,
4948
6233
  notes
@@ -4989,7 +6274,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
4989
6274
  if (specialist === "inspect" && status === "failed") yield* Effect.promise(() => reportTieredInspectFailureEscalation(normalizedIssueId, notes));
4990
6275
  yield* Effect.promise(async () => {
4991
6276
  try {
4992
- const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-8ZAguXef.js");
6277
+ const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-D9-dMUtI.js");
4993
6278
  const projectKey = resolveProjectFromIssueSync(normalizedIssueId)?.projectKey;
4994
6279
  const tmuxSession = projectKey ? getTmuxSessionName(`${specialist}-agent`, projectKey, normalizedIssueId) : getTmuxSessionName(`${specialist}-agent`);
4995
6280
  saveAgentRuntimeState(tmuxSession, {
@@ -5008,7 +6293,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5008
6293
  });
5009
6294
  console.log(`[specialists/done] Cleared registry lock for ${registryKey} (${projectKey})`);
5010
6295
  }
5011
- const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-BxX1YefH.js");
6296
+ const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-Duz9RGvk.js");
5012
6297
  if (await Effect.runPromise(updateSpecialistHandoffStatus(normalizedIssueId, `${specialist}-agent`, status === "passed" ? "completed" : "failed", status === "passed" ? "success" : "failure"))) console.log(`[specialists/done] Updated handoff log: ${specialist}-agent ${normalizedIssueId} → ${status}`);
5013
6298
  } catch (err) {
5014
6299
  console.error(`[specialists/done] Error managing specialist state:`, err);
@@ -5020,7 +6305,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5020
6305
  if (project) {
5021
6306
  const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
5022
6307
  if (existsSync(workspacePath)) {
5023
- const { getWorkspaceGitInfo } = await import("./git-utils-BCLRWQ-y.js");
6308
+ const { getWorkspaceGitInfo } = await import("./git-utils-q8jNoBts.js");
5024
6309
  const { HEAD } = await Effect.runPromise(getWorkspaceGitInfo(workspacePath));
5025
6310
  setReviewStatusSync(normalizedIssueId, { reviewedAtCommit: HEAD });
5026
6311
  console.log(`[specialists/done] Snapshotted reviewedAtCommit=${HEAD.substring(0, 8)} for ${normalizedIssueId}`);
@@ -5032,40 +6317,12 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5032
6317
  });
5033
6318
  if (specialist === "inspect" && status === "passed") yield* Effect.promise(async () => {
5034
6319
  try {
5035
- const { onInspectComplete } = await import("./inspect-agent-477VxQim.js");
5036
- const beadId = (notes?.match(/[Bb]ead\s+(\S+)/))?.[1] || "unknown";
6320
+ const { onInspectComplete } = await import("./inspect-agent-DCJQm9Wu.js");
6321
+ const taskId = (notes?.match(/[Bb]ead\s+(\S+)/))?.[1] || "unknown";
5037
6322
  const project = resolveProjectFromIssueSync(normalizedIssueId);
5038
6323
  if (project) {
5039
6324
  const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
5040
- if (existsSync(workspacePath)) {
5041
- await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, beadId, "passed", workspacePath));
5042
- try {
5043
- const beadData = await Effect.runPromise(queryBeadById(workspacePath, beadId));
5044
- const updatedItemId = await Effect.runPromise(syncBeadStatusToVBrief(beadId, workspacePath, "completed", beadData?.title));
5045
- if (updatedItemId) try {
5046
- const unblockedItems = getUnblockedItemsSync(workspacePath, updatedItemId);
5047
- if (unblockedItems.length > 0) {
5048
- console.log(`[auto-wake] ${normalizedIssueId}: items unblocked after "${updatedItemId}": ${unblockedItems.join(", ")}`);
5049
- const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;
5050
- const doc = readWorkspacePlanSync(workspacePath);
5051
- const unblockedTitles = unblockedItems.map((id) => {
5052
- const it = doc?.plan.items.find((i) => i.id === id);
5053
- return it ? `"${it.title}"` : `"${id}"`;
5054
- }).join(", ");
5055
- await messageAgent(workAgentId, `DAG SCHEDULER: Task${unblockedItems.length > 1 ? "s" : ""} now unblocked after completing "${updatedItemId}": ${unblockedTitles}. Pick up the next available task.`).catch((err) => {
5056
- const errMsg = err instanceof Error ? err.message : String(err);
5057
- console.log(`[auto-wake] Could not wake ${workAgentId} (may not be running): ${errMsg}`);
5058
- });
5059
- }
5060
- } catch (wakeErr) {
5061
- const errMsg = wakeErr instanceof Error ? wakeErr.message : String(wakeErr);
5062
- console.warn(`[auto-wake] Failed to check unblocked items: ${errMsg}`);
5063
- }
5064
- } catch (syncErr) {
5065
- const errMsg = syncErr instanceof Error ? syncErr.message : String(syncErr);
5066
- console.warn(`[specialists/done] vBRIEF sync failed: ${errMsg}`);
5067
- }
5068
- }
6325
+ if (existsSync(workspacePath)) await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, taskId, "passed", workspacePath));
5069
6326
  }
5070
6327
  } catch (err) {
5071
6328
  console.error(`[specialists/done] Error saving inspect checkpoint:`, err);
@@ -5077,7 +6334,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5077
6334
  if (project) {
5078
6335
  if (existsSync(join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`))) {
5079
6336
  setReviewStatusSync(normalizedIssueId, { testStatus: "passed" });
5080
- const { initEventStore } = await import("./event-store-CcJAhKCl.js");
6337
+ const { initEventStore } = await import("./event-store-D9UhQyMS.js");
5081
6338
  await (await initEventStore()).appendAsync({
5082
6339
  type: "test.passed",
5083
6340
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5130,8 +6387,8 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5130
6387
  if (notes?.toLowerCase().includes("conflict")) yield* Effect.promise(async () => {
5131
6388
  try {
5132
6389
  const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;
5133
- const { sessionExists } = await import("./tmux-x291Gjjb.js");
5134
- const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-DCvyTXNU.js");
6390
+ const { sessionExists } = await import("./tmux-C-rgxRDA.js");
6391
+ const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-BA15AQqo.js");
5135
6392
  if (await Effect.runPromise(sessionExists(workAgentId))) {
5136
6393
  await messageAgent(workAgentId, `MERGE CONFLICT: The merge-agent could not rebase your branch onto main due to conflicts. Please fix this now:\n\n1. git fetch origin main\n2. git rebase origin/main\n3. Resolve any conflicts (git add <file> && git rebase --continue)\n4. git push --force-with-lease\n5. Resubmit: curl -s -X POST http://localhost:3011/api/review/${normalizedIssueId}/request -H "Content-Type: application/json" -d "{}"\n\nConflict details: ${notes}`);
5137
6394
  console.log(`[specialists/done] Sent rebase instructions to ${workAgentId}`);
@@ -5145,7 +6402,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5145
6402
  try {
5146
6403
  const project = resolveProjectFromIssueSync(normalizedIssueId);
5147
6404
  const workspacePath = project ? join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`) : void 0;
5148
- const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-YOzmsq9G.js");
6405
+ const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-BsFLjN_6.js");
5149
6406
  const result = await Effect.runPromise(deliverReviewVerdictFeedback({
5150
6407
  issueId: normalizedIssueId,
5151
6408
  verdict: status === "failed" ? "failed" : "blocked",
@@ -5186,7 +6443,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5186
6443
  });
5187
6444
  })));
5188
6445
  const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/specialists/logs/cleanup-all", httpHandler(Effect.gen(function* () {
5189
- const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-BWZYcJ6P.js"));
6446
+ const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-DtSkPJZK.js"));
5190
6447
  const results = cleanupAllLogsSync();
5191
6448
  return jsonResponse({
5192
6449
  success: true,
@@ -5196,7 +6453,7 @@ const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/speciali
5196
6453
  });
5197
6454
  })));
5198
6455
  const getSpecialistsProjectsRoute = HttpRouter.add("GET", "/api/specialists/projects", httpHandler(Effect.gen(function* () {
5199
- const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6456
+ const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5200
6457
  return jsonResponse(yield* Effect.promise(() => getAllProjectSpecialistStatuses()));
5201
6458
  })));
5202
6459
  const postSpecialistWakeRoute = HttpRouter.add("POST", "/api/specialists/:name/wake", httpHandler(Effect.gen(function* () {
@@ -5240,7 +6497,7 @@ const postSpecialistReportStatusRoute = HttpRouter.add("POST", "/api/specialists
5240
6497
  "blocked",
5241
6498
  "failed"
5242
6499
  ].includes(status)) {
5243
- const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6500
+ const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5244
6501
  saveAgentRuntimeState(getTmuxSessionName(name), {
5245
6502
  state: "idle",
5246
6503
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
@@ -5342,7 +6599,7 @@ init_config_yaml();
5342
6599
  init_projects();
5343
6600
  init_review_status();
5344
6601
  init_tmux();
5345
- init_agents();
6602
+ init_agents$1();
5346
6603
  const getProjectSpecialistStatusRoute = HttpRouter.add("GET", "/api/specialists/:project/:issueId/:type/status", httpHandler(Effect.gen(function* () {
5347
6604
  const params = yield* HttpRouter.params;
5348
6605
  return jsonResponse({
@@ -5361,7 +6618,7 @@ const postProjectSpecialistKillRoute = HttpRouter.add("POST", "/api/specialists/
5361
6618
  const issueId = params["issueId"];
5362
6619
  const type = params["type"];
5363
6620
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
5364
- const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6621
+ const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5365
6622
  const tmuxSession = getRunMetadata(project, makeSpecialistRegistryKey(type, issueId)).tmuxSession ?? getTmuxSessionName(type, project, issueId);
5366
6623
  yield* Effect.promise(() => Effect.runPromise(killSession(tmuxSession)).catch(() => {}));
5367
6624
  saveAgentRuntimeState(tmuxSession, {
@@ -5388,7 +6645,7 @@ const getProjectSpecialistRunsRoute = HttpRouter.add("GET", "/api/specialists/:p
5388
6645
  if (limitParam) limit = parseInt(limitParam, 10);
5389
6646
  if (offsetParam) offset = parseInt(offsetParam, 10);
5390
6647
  }
5391
- const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-BWZYcJ6P.js"));
6648
+ const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-DtSkPJZK.js"));
5392
6649
  return jsonResponse(listRunLogsSync(project, type, {
5393
6650
  limit,
5394
6651
  offset
@@ -5399,7 +6656,7 @@ const getProjectSpecialistRunStreamRoute = HttpRouter.add("GET", "/api/specialis
5399
6656
  const project = params["project"];
5400
6657
  const type = params["type"];
5401
6658
  const runId = params["runId"];
5402
- const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-BWZYcJ6P.js"));
6659
+ const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-DtSkPJZK.js"));
5403
6660
  const logPath = getRunLogPath(project, type, runId);
5404
6661
  if (!existsSync(logPath)) return jsonResponse({ error: "Run log not found" }, { status: 404 });
5405
6662
  const encoder = new TextEncoder();
@@ -5458,7 +6715,7 @@ const getProjectSpecialistRunRoute = HttpRouter.add("GET", "/api/specialists/:pr
5458
6715
  const project = params["project"];
5459
6716
  const type = params["type"];
5460
6717
  const runId = params["runId"];
5461
- const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-BWZYcJ6P.js"));
6718
+ const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-DtSkPJZK.js"));
5462
6719
  const content = getRunLogSync(project, type, runId);
5463
6720
  if (!content) return jsonResponse({ error: "Run log not found" }, { status: 404 });
5464
6721
  return jsonResponse({
@@ -5472,7 +6729,7 @@ const postProjectSpecialistRunTerminateRoute = HttpRouter.add("POST", "/api/spec
5472
6729
  const project = params["project"];
5473
6730
  const type = params["type"];
5474
6731
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
5475
- const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6732
+ const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5476
6733
  yield* Effect.promise(() => terminateSpecialist(project, type));
5477
6734
  return jsonResponse({
5478
6735
  success: true,
@@ -5484,7 +6741,7 @@ const postProjectSpecialistGracePauseRoute = HttpRouter.add("POST", "/api/specia
5484
6741
  const project = params["project"];
5485
6742
  const type = params["type"];
5486
6743
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
5487
- const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6744
+ const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5488
6745
  if (pauseGracePeriod(project, type)) return jsonResponse({
5489
6746
  success: true,
5490
6747
  message: "Grace period paused"
@@ -5496,7 +6753,7 @@ const postProjectSpecialistGraceResumeRoute = HttpRouter.add("POST", "/api/speci
5496
6753
  const project = params["project"];
5497
6754
  const type = params["type"];
5498
6755
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
5499
- const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6756
+ const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5500
6757
  if (resumeGracePeriod(project, type)) return jsonResponse({
5501
6758
  success: true,
5502
6759
  message: "Grace period resumed"
@@ -5508,7 +6765,7 @@ const postProjectSpecialistGraceExitRoute = HttpRouter.add("POST", "/api/special
5508
6765
  const project = params["project"];
5509
6766
  const type = params["type"];
5510
6767
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
5511
- const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6768
+ const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5512
6769
  exitGracePeriod(project, type);
5513
6770
  return jsonResponse({
5514
6771
  success: true,
@@ -5520,7 +6777,7 @@ const getProjectSpecialistGraceRoute = HttpRouter.add("GET", "/api/specialists/:
5520
6777
  const project = params["project"];
5521
6778
  const type = params["type"];
5522
6779
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
5523
- const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6780
+ const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5524
6781
  const state = getGracePeriodState(project, type);
5525
6782
  if (state) return jsonResponse(state);
5526
6783
  else return jsonResponse({ error: "No active grace period" }, { status: 404 });
@@ -5529,7 +6786,7 @@ const getProjectSpecialistContextRoute = HttpRouter.add("GET", "/api/specialists
5529
6786
  const params = yield* HttpRouter.params;
5530
6787
  const project = params["project"];
5531
6788
  const type = params["type"];
5532
- const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-BMq83C58.js"));
6789
+ const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-CspI4Eog.js"));
5533
6790
  const digest = loadContextDigest(project, type);
5534
6791
  if (digest) return jsonResponse({ digest });
5535
6792
  else return jsonResponse({ error: "No context digest found" }, { status: 404 });
@@ -5538,7 +6795,7 @@ const postProjectSpecialistContextRegenerateRoute = HttpRouter.add("POST", "/api
5538
6795
  const params = yield* HttpRouter.params;
5539
6796
  const project = params["project"];
5540
6797
  const type = params["type"];
5541
- const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-BMq83C58.js"));
6798
+ const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-CspI4Eog.js"));
5542
6799
  const digest = yield* Effect.promise(() => regenerateContextDigest(project, type));
5543
6800
  if (digest) return jsonResponse({
5544
6801
  digest,
@@ -5557,7 +6814,7 @@ const postProjectSpecialistCompleteRoute = HttpRouter.add("POST", "/api/speciali
5557
6814
  "blocked"
5558
6815
  ].includes(status)) return jsonResponse({ error: "Valid status (passed/failed/blocked) is required" }, { status: 400 });
5559
6816
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
5560
- const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-8ZAguXef.js"));
6817
+ const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-D9-dMUtI.js"));
5561
6818
  signalSpecialistCompletion(project, type, {
5562
6819
  status,
5563
6820
  notes
@@ -5591,8 +6848,8 @@ const postProjectSpecialistLogsCleanupRoute = HttpRouter.add("POST", "/api/speci
5591
6848
  const params = yield* HttpRouter.params;
5592
6849
  const project = params["project"];
5593
6850
  const type = params["type"];
5594
- const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-BWZYcJ6P.js"));
5595
- const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-KJ94e50I.js"));
6851
+ const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-DtSkPJZK.js"));
6852
+ const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-Dc9aoVoG.js"));
5596
6853
  const retention = getSpecialistRetention(project);
5597
6854
  const deleted = cleanupOldLogsSync(project, type, {
5598
6855
  maxDays: retention.max_days,
@@ -5613,7 +6870,7 @@ const postProjectReviewRestartRoute = HttpRouter.add("POST", "/api/specialists/:
5613
6870
  const project = params["project"];
5614
6871
  const issueId = params["issueId"];
5615
6872
  const { model, harness } = yield* readJsonBody;
5616
- const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-Rub0OpZc.js"));
6873
+ const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-CpSHLzed.js"));
5617
6874
  const killResult = yield* killAllReviewerSessions(project, issueId);
5618
6875
  const projectConfig = resolveProjectFromIssueSync(issueId);
5619
6876
  if (!projectConfig) return jsonResponse({ error: `Cannot resolve project for ${issueId}` }, { status: 404 });
@@ -5627,7 +6884,7 @@ const postProjectReviewRestartRoute = HttpRouter.add("POST", "/api/specialists/:
5627
6884
  }));
5628
6885
  branch = stdout.trim() || "unknown";
5629
6886
  } catch {}
5630
- const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-Rub0OpZc.js"));
6887
+ const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-CpSHLzed.js"));
5631
6888
  const prUrl = getReviewStatusSync(issueId)?.prUrl;
5632
6889
  const result = yield* spawnReviewRoleForIssue({
5633
6890
  issueId,
@@ -5776,6 +7033,6 @@ const specialistsProjectRouteLayer = Layer.mergeAll(getProjectSpecialistStatusRo
5776
7033
  */
5777
7034
  const specialistsRouteLayer = Layer.mergeAll(specialistsLegacyRouteLayer, specialistsProjectRouteLayer);
5778
7035
  //#endregion
5779
- export { validateOrigin as C, jsonResponse as D, httpHandler as E, jsonStringResponse as O, normalizeOrigin as S, checkCodexAuthStatus as T, resolveJsonlPath as _, agentsRouteLayer as a, getTrustedOrigins as b, evaluateSpawnGuardrails as c, resolveConversationGitInfo as d, getWorkAgentLifecycleStateSync as f, resolveCodexRolloutPath as g, resolveClaudeSessionId as h, validateSpecialistAutoCompleteMetadata as i, invalidateAgentsCache as l, resolveAgentHarness as m, _serverManagedMerges as n, saveAgentStateAndEmitEvent as o, readLauncherPinnedSessionId as p, firePostMergeLifecycle as r, saveAgentStateAndEmitEventProgram as s, specialistsRouteLayer as t, resolveAgentGitInfo as u, resolvePiSessionPath as v, validateOriginHeaders as w, isTrustedOriginForHost as x, getHeaderFromMap as y };
7036
+ export { readLauncherPinnedSessionId as $, resolveAllowedHarness as A, cleanupUnreferencedConversationAttachments as B, handleConversationCreate as C, handleConversationStop as D, handleConversationResume as E, tmuxSessionExists as F, isManagedConversationAttachmentPath as G, extractConversationAttachmentPaths as H, waitForPiTuiReady as I, evaluateSpawnGuardrails as J, removeConversationAttachment as K, waitForTmuxSession as L, spawnConversationSession as M, startConversationModelBackfill as N, handleConversationSwitchModel as O, stopConversationRuntime as P, getWorkAgentLifecycleStateSync as Q, cleanupConversationAttachments as R, conversationSessionAliveFromState as S, handleConversationRestartAll as T, getConversationAttachmentsRoot as U, ensureConversationAttachmentDir as V, hasConversationAttachment as W, resolveAgentGitInfo as X, invalidateAgentsCache as Y, resolveConversationGitInfo as Z, isPiControlChannelHarness as _, agentsRouteLayer as a, isRespawnPending as at, validateConversationControlAckOrigin as b, codexConversationPendingInput as c, getTrustedOrigins as ct, handleConversationCodexApproval as d, validateOrigin as dt, resolveAgentHarness as et, handleConversationCompact as f, validateOriginHeaders as ft, handleConversationThinkingLevel as g, jsonStringResponse as gt, handleConversationPlanAction as h, jsonResponse as ht, validateSpecialistAutoCompleteMetadata as i, resolvePiSessionPath as it, shouldReportUnresolvedLiveSession as j, isInsideGitWorkTree as k, deliverConversationViaControlChannel as l, isTrustedOriginForHost as lt, handleConversationDeliveryMethod as m, getDefaultCwd as mt, _serverManagedMerges as n, resolveCodexRolloutPath as nt, saveAgentStateAndEmitEvent as o, waitForSessionRespawn as ot, handleConversationControlAck as p, httpHandler as pt, runInBatches as q, firePostMergeLifecycle as r, resolveJsonlPath as rt, saveAgentStateAndEmitEventProgram as s, getHeaderFromMap as st, specialistsRouteLayer as t, resolveClaudeSessionId as tt, handleConversationAbort as u, normalizeOrigin as ut, pickDeliverAs as v, handleConversationDelete as w, conversationNeedsRunningRepair as x, resolveConversationDeliveryMethod as y, cleanupOrphanedConversationAttachments as z };
5780
7037
 
5781
- //# sourceMappingURL=specialists-BudKrjOu.js.map
7038
+ //# sourceMappingURL=specialists-Dax7tqXC.js.map