@overdeck/desktop 0.45.13 → 0.45.16

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 (336) hide show
  1. package/package.json +1 -1
  2. package/server/{acceptance-criteria-CfujPRrz.js → acceptance-criteria-BZ_nQS1f.js} +2 -2
  3. package/server/{advancing-selfheal-Bn5QiLtn.js → advancing-selfheal-DJYAvGv-.js} +6 -5
  4. package/server/{agent-gc-Cl9Nm0Zu.js → agent-gc-D0wDIeV1.js} +3 -3
  5. package/server/{agent-state-C6Rm6FPR.js → agent-state-lGvgPP4m.js} +1 -1
  6. package/server/{agent-state-service-U8feBf8t.js → agent-state-service-CZrayD_G.js} +1 -1
  7. package/server/{agent-state-service-JhFzYCP_.js → agent-state-service-DFslS5go.js} +9 -52
  8. package/server/{agents-DqQa4Dq_.js → agents-B9GwfCyp.js} +155 -153
  9. package/server/{agents-CxbuBjID.js → agents-CEcaxSkR.js} +1 -1
  10. package/server/{archive-planning-CPitEZo4.js → archive-planning-CbghE9-o.js} +5 -5
  11. package/server/{archive-planning-DmpT1_14.js → archive-planning-CoyIbgGK.js} +1 -1
  12. package/server/{artifact-index-jobs-sMnNtPhy.js → artifact-index-jobs-CcSrUoSE.js} +2 -2
  13. package/server/auto-commit-IW-eik_b.js +3 -0
  14. package/server/{backlog-auto-trigger-Cf9C8fwD.js → backlog-auto-trigger-BfgDsII0.js} +3 -3
  15. package/server/{bd-process-lock-D1aJxeYv.js → bd-process-lock-XAXAet9O.js} +32 -11
  16. package/server/{beads-CivpXztb.js → beads-B1u04n3o.js} +1 -1
  17. package/server/{beads-DRoj6-e1.js → beads-BeSMEjGT.js} +96 -196
  18. package/server/{beads-query-tOsAYP5h.js → beads-query-Dl4FhT8J.js} +24 -99
  19. package/server/beads-restore-BUItPOjI.js +23 -0
  20. package/server/behavior-BTpYbAv4.js +10 -0
  21. package/server/{briefing-freshness-BL3Q6p7R.js → briefing-freshness-DESTCWo-.js} +4 -4
  22. package/server/{checkpoint-manager-DCxjQvP_.js → checkpoint-manager-BwFdegJF.js} +2 -2
  23. package/server/{checkpoint-manager-j5URlFY7.js → checkpoint-manager-DWSXooZV.js} +1 -1
  24. package/server/checkpoint-worker.js +1 -1
  25. package/server/{checkpoints-BoX1H2Hf.js → checkpoints-BtUonbs3.js} +2 -2
  26. package/server/{checkpoints-Bu_M_Q9W.js → checkpoints-fjadfOKF.js} +1 -1
  27. package/server/{claude-hooks-provision-D-jGXmu1.js → claude-hooks-provision-DAtxutOs.js} +132 -114
  28. package/server/claude-hooks-provision-DiK62u0k.js +3 -0
  29. package/server/{claude-permissions-DxIjkOVl.js → claude-permissions-CThLgf8d.js} +2 -2
  30. package/server/{claude-settings-overlay-BPDS6GfE.js → claude-settings-overlay-BF9MPIwx.js} +2 -2
  31. package/server/{claude-settings-overlay-5b9X2S0C.js → claude-settings-overlay-Dx_Uqcmv.js} +1 -1
  32. package/server/{cli-s7bPWy-E.js → cli-ByKIIMfT.js} +9 -9
  33. package/server/cli-D9cUTbBr.js +3 -0
  34. package/server/{cliproxy-BituqWtR.js → cliproxy-B4c1BUvs.js} +2 -2
  35. package/server/{cliproxy-D3sHB0sI.js → cliproxy-C_GuT5DU.js} +1 -1
  36. package/server/{close-issue-CrBE7voz.js → close-issue-BOuLKr6e.js} +5 -5
  37. package/server/{closed-issue-reaper-BRQBc7Xa.js → closed-issue-reaper-CksfuiFD.js} +1 -1
  38. package/server/{closed-issue-reaper-DX9mYUji.js → closed-issue-reaper-DCAYzNXW.js} +102 -30
  39. package/server/{codex-Bi1ITSOo.js → codex-BYPz6ZdF.js} +1 -1
  40. package/server/{codex-CO0HrK3j.js → codex-BbqIRZjd.js} +6 -5
  41. package/server/{behavior-D3d2mVyD.js → codex-parser-mFYZh8vR.js} +3 -9
  42. package/server/{compact-beads-DUkCUFxA.js → compact-beads-CyJtoXJi.js} +15 -8
  43. package/server/{compact-beads-CrA8-k10.js → compact-beads-N00E4EVa.js} +1 -1
  44. package/server/{config-DsCxzjXK.js → config-DVLdmnMJ.js} +2 -2
  45. package/server/{config-B4_d7z7z.js → config-Dj6N8EP9.js} +1 -1
  46. package/server/{config-yaml-C_djSurJ.js → config-yaml-cNOZcEww.js} +118 -23
  47. package/server/{conflict-gate-D47Kl78u.js → conflict-gate-BMKm_5DV.js} +3 -3
  48. package/server/{conflict-gate-CpOn9KC-.js → conflict-gate-CuXUv-bP.js} +1 -1
  49. package/server/{context-layers-CHl3Nroz.js → context-layers-CeAwSYNt.js} +2 -2
  50. package/server/{context-layers-BNhUBTkf.js → context-layers-c0JqYHcS.js} +3 -3
  51. package/server/{control-settings-KyYNXZLv.js → control-settings-D4EFsRBd.js} +4 -4
  52. package/server/{conversation-compaction-D3aS5PnS.js → conversation-compaction-BCggyih4.js} +8 -8
  53. package/server/{conversation-compaction-D4Wed1BJ.js → conversation-compaction-kHVfVRg3.js} +1 -1
  54. package/server/{conversation-service-CFZIjs-M.js → conversation-service-B8LFQQm1.js} +6 -5
  55. package/server/{conversation-service-DcDB5usR.js → conversation-service-BTzObqGi.js} +1 -1
  56. package/server/{conversations-BSOU5qnu.js → conversations-B-lcHv-5.js} +4 -4
  57. package/server/{conversations-BbDPrz89.js → conversations-BM7zlXjr.js} +1 -1
  58. package/server/{cost-BE_ulOrw.js → cost-BcpRQ-7o.js} +3 -3
  59. package/server/{cost-CaiG63FG.js → cost-D9kPIY_5.js} +2 -2
  60. package/server/{cost-sync-D6AX8jv-.js → cost-sync-DF6odxK9.js} +3 -3
  61. package/server/{dag-BQFtXOuJ.js → dag-DKI3sOmX.js} +3 -3
  62. package/server/{dangerous-git-ops-04Ba-Qu3.js → dangerous-git-ops-Bn18PEcN.js} +1 -1
  63. package/server/dashboard-db-worker.js +6 -6
  64. package/server/{deacon-Bz-6Wjmn.js → deacon-DONuopff.js} +3 -3
  65. package/server/{deacon-2EvA9Ci3.js → deacon-DdARMjZq.js} +188 -1601
  66. package/server/{deacon-swarm-DYYOzHRH.js → deacon-swarm-BNGkmYR_.js} +12 -105
  67. package/server/deacon-swarm-CvdFekIO.js +2 -0
  68. package/server/deacon.js +7 -7
  69. package/server/{devcontainer-renderer-Dx0C7VWJ.js → devcontainer-renderer-C-34Sxyz.js} +3 -3
  70. package/server/{dist-ya66D9ze.js → dist-BMXvKiZP.js} +1 -1
  71. package/server/dist-BNwrgsUO.js +3 -0
  72. package/server/{dist-src-Byxt1ifL.js → dist-src-BgGUblXg.js} +1 -1
  73. package/server/{docker-D2TbXDO6.js → docker-apZ9pBYw.js} +67 -28
  74. package/server/{ensure-devcontainer-BTiMfFp5.js → ensure-devcontainer-Bqi1EF1P.js} +4 -4
  75. package/server/{ensure-devcontainer-BIShzs3M.js → ensure-devcontainer-Dq3KoOIl.js} +1 -1
  76. package/server/{esm-shims-DiV9jHa_.js → esm-shims-BEjvINX3.js} +1 -1
  77. package/server/{event-store-C02Gprug.js → event-store-CqDgfbAs.js} +1 -1
  78. package/server/{event-store-C71x6h0i.js → event-store-D89fMfXS.js} +3 -3
  79. package/server/{events-BFDuPV6n.js → events-S_C0pvl6.js} +3 -3
  80. package/server/{events-D2gP2-Yv.js → events-rIGncmP1.js} +1 -1
  81. package/server/export-BJHSVwKP.js +87 -0
  82. package/server/{factory-ByfbrCab.js → factory-DV18m0rc.js} +5 -5
  83. package/server/{feature-registry-population-CSH66UO8.js → feature-registry-population-CqZSnqQi.js} +4 -4
  84. package/server/{feature-registry-storage-DW7dUefc.js → feature-registry-storage-BeXmNBwK.js} +2 -2
  85. package/server/{features-x_aLR7U9.js → features-u5XeMaOK.js} +2 -2
  86. package/server/{feedback-target-BFo9fScH.js → feedback-target-CwyWf0Dg.js} +6 -6
  87. package/server/{feedback-target-C4VMPTZu.js → feedback-target-DDG1PWl3.js} +1 -1
  88. package/server/{feedback-writer-DCyd75yJ.js → feedback-writer-BsJiw5aT.js} +1 -1
  89. package/server/{feedback-writer-B9ZqnnX3.js → feedback-writer-D20HSXre.js} +4 -4
  90. package/server/{flywheel-DwQCoZQG.js → flywheel-9Y1X7IM-.js} +13 -13
  91. package/server/{flywheel-D9NKvWqb.js → flywheel-Dczmgchd.js} +23 -15
  92. package/server/{flywheel-DB48X7Nn.js → flywheel-RCnHAgc4.js} +1 -1
  93. package/server/{flywheel-merge-order-DrjmFXg4.js → flywheel-merge-order-CtCYsFJP.js} +7 -7
  94. package/server/{flywheel-merge-order-f1bi5XIf.js → flywheel-merge-order-DEtEd3aK.js} +1 -1
  95. package/server/forge-8y6aEPTB.js +2 -0
  96. package/server/{forge-C-SoKy8U.js → forge-DFDThFil.js} +2 -2
  97. package/server/{from-fu7ZumnY.js → from-NSJypkI8.js} +1 -1
  98. package/server/{fts-db-CL-YzSRG.js → fts-db-C5A_TTOM.js} +2 -2
  99. package/server/{fts-operations-Bylu5NjM.js → fts-operations-DvCSimfU.js} +2 -2
  100. package/server/{git-utils-Ym380hJZ.js → git-utils-C0neHvQx.js} +1 -1
  101. package/server/{git-utils-C8koBs8j.js → git-utils-Dr0L7mFk.js} +2 -2
  102. package/server/{github-app-DIr70YLg.js → github-app-Bwt9lK4A.js} +1 -1
  103. package/server/{github-app-DhycHVLF.js → github-app-u9-SX7Gc.js} +2 -2
  104. package/server/{github-client-7gkcXcBS.js → github-client-Btyy3uan.js} +3 -3
  105. package/server/{github-client-Ji9lUTNF.js → github-client-Do3k-N1W.js} +1 -1
  106. package/server/{github-pr-lookup-rt7T9FwA.js → github-pr-lookup-DOF1U2WW.js} +2 -2
  107. package/server/{harness-resolve-CvGXUnAF.js → harness-resolve-C6g2R0Cd.js} +3 -3
  108. package/server/{health-events-D4E1DLN-.js → health-events-B4u-2a9m.js} +2 -2
  109. package/server/{health-events-DDfkoO5a.js → health-events-Bos3sk5g.js} +1 -1
  110. package/server/{hooks-D_6pAbuJ.js → hooks-BCoOjL_R.js} +2 -2
  111. package/server/{hooks-DvdnCVv6.js → hooks-uEr8WTx_.js} +1 -1
  112. package/server/{hume-DkVOs1Qi.js → hume-BqxYKbUf.js} +2 -2
  113. package/server/hume-CerGi2RH.js +3 -0
  114. package/server/{idle-stack-reaper-cDRXDpgE.js → idle-stack-reaper-DbZ8ukMd.js} +2 -2
  115. package/server/{idle-stack-reaper-DNbV9-fj.js → idle-stack-reaper-H0W6XtHh.js} +1 -1
  116. package/server/{infra-Clew3DpF.js → infra-Ba9mS6e6.js} +3 -3
  117. package/server/{injection-QCIHOpk6.js → injection-C1DKXeIZ.js} +1 -1
  118. package/server/{injection-ql0Nddcn.js → injection-yrMBxzVq.js} +7 -7
  119. package/server/{inspect-agent-DY18KEx4.js → inspect-agent-B-mnb4uh.js} +9 -9
  120. package/server/inspect-agent-CWkeAB7J.js +2 -0
  121. package/server/{internal-token-C90jBp-y.js → internal-token-BPYEaXFh.js} +1 -1
  122. package/server/{internal-token-BeXjPLKy.js → internal-token-BtqSbKTx.js} +2 -2
  123. package/server/{io-CbAFXQge.js → io-VEGSAqsd.js} +15 -22
  124. package/server/{issue-closed-BYC2TCk-.js → issue-closed-BxcePXL-.js} +4 -4
  125. package/server/{issue-lifecycle-BoJa6qIt.js → issue-lifecycle--8MRxrG1.js} +22 -22
  126. package/server/{issue-lifecycle-CLHbj8RS.js → issue-lifecycle-CdMUauyb.js} +1 -1
  127. package/server/{issue-service-singleton-BS9NENuR.js → issue-service-singleton-CkTUI-eu.js} +10 -10
  128. package/server/{issue-service-singleton-3g_CCY-1.js → issue-service-singleton-MZsDhAob.js} +1 -1
  129. package/server/{issues-BPcqPqfV.js → issues-CBAsuoAO.js} +2 -2
  130. package/server/jsonl-parser-BUW5_sSK.js +245 -0
  131. package/server/{label-cleanup-CwiCSZrb.js → label-cleanup-htK6fBRU.js} +3 -3
  132. package/server/{layers-OHHLphl0.js → layers-jjL16At6.js} +2 -2
  133. package/server/{lifecycle-B8IdvUbt.js → lifecycle-Ck2UR27D.js} +7 -7
  134. package/server/{lifecycle-io-BMCH1Bxg.js → lifecycle-io-DXv8Utct.js} +1 -1
  135. package/server/{lifecycle-io-C73m5e6R.js → lifecycle-io-IehcRt2v.js} +36 -47
  136. package/server/{linear-client-BMxgycoy.js → linear-client-By7hjwuN.js} +1 -1
  137. package/server/{linear-client-CgQlM4aT.js → linear-client-D9cSQfvD.js} +3 -3
  138. package/server/memory-fts-worker.js +1 -1
  139. package/server/{merge-BRkVaIx-.js → merge-BLcEZNXz.js} +1 -1
  140. package/server/{merge-B7Oi-g6k.js → merge-BN8FGJ2q.js} +3 -3
  141. package/server/{merge-agent-DNFFsiEm.js → merge-agent-B3u2szOL.js} +29 -29
  142. package/server/{merge-agent-Bwm34MZ5.js → merge-agent-ejC7Wos8.js} +1 -1
  143. package/server/{merge-batch-BpYXUaqJ.js → merge-batch-DyqlgYGp.js} +1 -1
  144. package/server/{merge-blockers-DIOZmqdK.js → merge-blockers-cq-aS2KG.js} +2 -2
  145. package/server/merge-ops-CFkQtXEj.js +2 -0
  146. package/server/{merge-rebase-C8WH39MB.js → merge-rebase-Dl7yIxtV.js} +2 -2
  147. package/server/{merge-set-CsEE2CyQ.js → merge-set-CZ9Nbay0.js} +3 -3
  148. package/server/{merge-set-DOsBTBQc.js → merge-set-Dy0D0r3i.js} +1 -1
  149. package/server/{merge-sync-CcLM2vwW.js → merge-sync-CG9kDCdT.js} +1 -1
  150. package/server/{merge-sync-C2B322Ot.js → merge-sync-D7jWLgCC.js} +2 -2
  151. package/server/{merge-train-BIm7-7cy.js → merge-train-CH33XJOH.js} +3 -3
  152. package/server/{merge-train-deps-DG1oQ2DT.js → merge-train-deps-JoVdGnOg.js} +4 -4
  153. package/server/{merge-verification-BAbjxEd5.js → merge-verification-D4wTChdX.js} +4 -4
  154. package/server/{multipart-parser-jKtKAXtl.js → multipart-parser-Csn8U-K9.js} +2 -2
  155. package/server/{no-resume-mode-yrPEQ_pC.js → no-resume-mode-Gh5R_YLF.js} +1 -1
  156. package/server/node_modules/.package-lock.json +1 -1
  157. package/server/{orphan-proposed-reconciler-BqVGaOIj.js → orphan-proposed-reconciler-BIcNu4tl.js} +1 -1
  158. package/server/{orphan-proposed-reconciler-zHrwJ3xr.js → orphan-proposed-reconciler-DI_IS1p_.js} +15 -39
  159. package/server/package-lock.json +2 -2
  160. package/server/package.json +1 -1
  161. package/server/{pan-dir-BlNrLzW7.js → pan-dir-CQIfKG8c.js} +4 -4
  162. package/server/{paths-CiVeo4N_.js → paths-Blj6A9AJ.js} +2 -2
  163. package/server/{pickup-KLRUhThD.js → pickup-BtvRBbvI.js} +1 -1
  164. package/server/{pipeline-notifier-nkWCe042.js → pipeline-notifier-BH4PAi-w.js} +1 -1
  165. package/server/{pipeline-notifier-DBF6dHxH.js → pipeline-notifier-D_WYtEwl.js} +2 -2
  166. package/server/project-pipelines-jMTitpeO.js +162 -0
  167. package/server/projects-CrBE6IhN.js +3 -0
  168. package/server/{prompts-CXbQvevY.js → prompts-B3njaImU.js} +2 -2
  169. package/server/{providers-CUdAb5Qu.js → providers-BbEyFhPX.js} +7 -7
  170. package/server/providers-DnPxkHoG.js +3 -0
  171. package/server/public/assets/chunk-BO2N2NFS-3qh9TUJi.js +103 -0
  172. package/server/public/assets/{diagram-OG6HWLK6-NACO1xW5.js → diagram-OG6HWLK6-D8UVTPj7.js} +1 -1
  173. package/server/public/assets/{dist-DQD-ZjQY.js → dist-BvLLn1YC.js} +3 -3
  174. package/server/public/assets/{dist-D1FjqGgm.js → dist-CyubL7Wj.js} +1 -1
  175. package/server/public/assets/{ganttDiagram-6RSMTGT7-CEMAKgmE.js → ganttDiagram-6RSMTGT7-BI4E1LbQ.js} +1 -1
  176. package/server/public/assets/{highlighted-body-OFNGDK62-D5lC7FW8.js → highlighted-body-OFNGDK62-D_bapWkF.js} +1 -1
  177. package/server/public/assets/index-BAZxZ-8I.js +796 -0
  178. package/server/public/assets/index-FBG3JBfq.css +1 -0
  179. package/server/public/assets/{linear-DDHAMRPB.js → linear-CEdobObr.js} +1 -1
  180. package/server/public/assets/mermaid-GHXKKRXX-BYP8MI37.js +1 -0
  181. package/server/public/assets/{pieDiagram-4H26LBE5-DwBdeKBb.js → pieDiagram-4H26LBE5-DT79L01f.js} +1 -1
  182. package/server/public/assets/{prod-DAZt0c4G.js → prod-CR_K2DFV.js} +4 -4
  183. package/server/public/assets/{quadrantDiagram-W4KKPZXB-CNDLp-_m.js → quadrantDiagram-W4KKPZXB-BEkKXXrL.js} +1 -1
  184. package/server/public/assets/{sankeyDiagram-5OEKKPKP-oIUL_uO1.js → sankeyDiagram-5OEKKPKP-BxSCqn-j.js} +1 -1
  185. package/server/public/assets/{xychartDiagram-2RQKCTM6-B-IM6Cck.js → xychartDiagram-2RQKCTM6-DicwJGG_.js} +1 -1
  186. package/server/public/index.html +2 -2
  187. package/server/{pull-requests-DZ-V7_LB.js → pull-requests-43LPDfM8.js} +1 -1
  188. package/server/{pull-requests-kTRl7cWC.js → pull-requests-DyUkQl4W.js} +3 -3
  189. package/server/{rally-xCTZ-UcI.js → rally-BtWjybOa.js} +3 -3
  190. package/server/{rally-api-jOk4vit_.js → rally-api-B-YA_oXx.js} +1 -1
  191. package/server/{rally-api-DpZo3WwF.js → rally-api-B-osULNL.js} +2 -2
  192. package/server/{rally-client-BQjm3aaq.js → rally-client-CzFymP7P.js} +1 -1
  193. package/server/{rally-client-CoWZeVEj.js → rally-client-sErNQBO0.js} +5 -5
  194. package/server/rally-oggyp4cs.js +3 -0
  195. package/server/{reap-terminal-sessions-CAmURC2h.js → reap-terminal-sessions-2s-fvilz.js} +1 -1
  196. package/server/{reap-terminal-sessions-BwJb2DLJ.js → reap-terminal-sessions-DnDQ7pf9.js} +1 -1
  197. package/server/{rebuild-stack-BZ7j27xY.js → rebuild-stack-CoU-HqZj.js} +6 -6
  198. package/server/{rebuild-stack-r-cxcr26.js → rebuild-stack-nGD_nPZo.js} +1 -1
  199. package/server/{reconstruct-cache-13EkMg_i.js → reconstruct-cache-Wj8GrYB0.js} +6 -6
  200. package/server/record-lEYK__LD.js +3 -0
  201. package/server/recovery-trip-BT7nb9OV.js +98 -0
  202. package/server/{release-engine-YLTnEO_K.js → release-engine-CBLQXDlH.js} +5 -5
  203. package/server/{release-set-DwvT_Opu.js → release-set-BNiqi7hy.js} +3 -3
  204. package/server/{remote-B062S1Kc.js → remote-DW_TWQnr.js} +3 -3
  205. package/server/{remote-9u9WL0NJ.js → remote-DcO9An_f.js} +3 -3
  206. package/server/{remote-agents-BBTg-vLT.js → remote-agents-C4cyxHQS.js} +6 -6
  207. package/server/{remote-agents-B4eWgwNd.js → remote-agents-Cv-U-_V8.js} +1 -1
  208. package/server/{remote-completion-B0rmBnbL.js → remote-completion-PhXGGFQy.js} +13 -13
  209. package/server/resolver-C7dLn8OK.js +158 -0
  210. package/server/resume-BYZBIab-.js +3 -0
  211. package/server/{review-agent-tfR5RDzG.js → review-agent-Dn-yn7Zj.js} +23 -23
  212. package/server/{review-agent-Bf9XjLnl.js → review-agent-u5q3ip7H.js} +1 -1
  213. package/server/{review-artifacts-D0_IE6DQ.js → review-artifacts-D4T04aYI.js} +6 -6
  214. package/server/{review-context-CXO5ml5D.js → review-context-Rc0VDLG_.js} +8 -8
  215. package/server/{review-monitor-DfFUSx0J.js → review-monitor-BC--miCO.js} +2 -2
  216. package/server/{review-status-B0Axo-Ma.js → review-status-CG0iDuWa.js} +1 -1
  217. package/server/{review-status-BcfwcAcO.js → review-status-Dc1l4dVz.js} +18 -18
  218. package/server/{review-status-enrichment-Cp5kFTGl.js → review-status-enrichment-C1a1OG_1.js} +3 -3
  219. package/server/{review-status-sync-CEFty1pW.js → review-status-sync-CAr-hfnJ.js} +2 -2
  220. package/server/{review-status-sync-Bc0c_u12.js → review-status-sync-CfSixhhX.js} +1 -1
  221. package/server/{review-verdict-feedback-BOKRLLDf.js → review-verdict-feedback-CVDbIDBe.js} +7 -7
  222. package/server/{review-verdict-feedback-BnGtT0vN.js → review-verdict-feedback-D2Q960wX.js} +1 -1
  223. package/server/{rollup-BVqFuiS9.js → rollup-CakjztPl.js} +1 -1
  224. package/server/{rollup-C22QDVxh.js → rollup-DctoIu-A.js} +8 -8
  225. package/server/{scanner-BLCnuXqY.js → scanner-BTM1jc_u.js} +7 -7
  226. package/server/{sequence-io-BozlUb6f.js → sequence-io-pnoIDGv4.js} +4 -4
  227. package/server/{sequencer-agent-BEDEHfd2.js → sequencer-agent-9Soi-WL-.js} +10 -6
  228. package/server/server.js +1239 -1836
  229. package/server/{service-C8jKYZ0w.js → service-DfTVoqJg.js} +1668 -1408
  230. package/server/{session-fork-BDGksK4e.js → session-fork-C4jAFHj8.js} +2 -2
  231. package/server/session-history-DanhhiCw.js +168 -0
  232. package/server/{settings-CSLhh6qR.js → settings-Cuc12oP_.js} +2 -2
  233. package/server/settings-Cy7SCFO9.js +2 -0
  234. package/server/{settings-DrRQCren.js → settings-DfT3f86D.js} +3 -3
  235. package/server/{shadow-engineering-BMcBN-Of.js → shadow-engineering-bKuKTCn-.js} +3 -3
  236. package/server/{shadow-state-CHqWHD3T.js → shadow-state-CVHpRWlb.js} +1 -1
  237. package/server/{shadow-state-B4ayC7nx.js → shadow-state-DJz6_pb_.js} +2 -2
  238. package/server/{ship-log-CDVmOlZP.js → ship-log-CwqPFfqx.js} +1 -1
  239. package/server/{smart-compaction-V9DBrGcr.js → smart-compaction-B4KlpZGN.js} +1 -1
  240. package/server/{smart-compaction-C-F5sP9P.js → smart-compaction-CiWfGfRZ.js} +7 -7
  241. package/server/{spawn-planning-session-dSpOU0lP.js → spawn-planning-session-CU-DwyDl.js} +15 -15
  242. package/server/{spawn-planning-session-Bz4_JES2.js → spawn-planning-session-D5N3RuLG.js} +1 -1
  243. package/server/{specialist-completion-Coz3eTSr.js → specialist-completion-CJcv-NXH.js} +1 -1
  244. package/server/{specialist-context-fmb03vXQ.js → specialist-context-B2HlbNxS.js} +5 -5
  245. package/server/{specialist-handoff-logger-B9HK_lRu.js → specialist-handoff-logger-CQyF1ygu.js} +2 -2
  246. package/server/{specialist-logs-B24R2B9Y.js → specialist-logs-CdcUpfjj.js} +3 -3
  247. package/server/{specialists-CL7E8yUg.js → specialists-CFgw_0Q6.js} +1657 -113
  248. package/server/{specialists-BGNZ0_ZW.js → specialists-CwzFdapR.js} +20 -256
  249. package/server/{specialists-B6f5-5nG.js → specialists-D7AcFi1u.js} +1 -1
  250. package/server/{specialists-Chvvwf5z.js → specialists-k22_Btaq.js} +1 -1
  251. package/server/specs-RzcDgozr.js +3 -0
  252. package/server/{src-DGAEtABV.js → src-B4TTzNSr.js} +84 -2
  253. package/server/{src-BVxZuHMg.js → src-BvpMg4EB.js} +3 -3
  254. package/server/{src-D-VXCC9C.js → src-D4YUZrwR.js} +6 -6
  255. package/server/{stack-health-C-le5erA.js → stack-health-CSgcYcr8.js} +2 -2
  256. package/server/{stack-health-BKbTTOT2.js → stack-health-DVN7zRnL.js} +1 -1
  257. package/server/{state-home-CoyWsECg.js → state-home-CEvD_v5F.js} +6 -6
  258. package/server/{state-mapping-BvAgFK8n.js → state-mapping-C13UaCL7.js} +1 -1
  259. package/server/stuck-remediation-CxsyxwFO.js +1513 -0
  260. package/server/stuck-remediation-nTIvhGOR.js +2 -0
  261. package/server/{summary-fork-BF_0vxrq.js → summary-fork-CnRMPCYd.js} +2 -2
  262. package/server/{summary-fork-BlE30Q-I.js → summary-fork-gKSoFAnK.js} +8 -8
  263. package/server/{supervisor-BSeML_p0.js → supervisor-CZMs8s0M.js} +2 -2
  264. package/server/supervisor-CfX192ma.js +2 -0
  265. package/server/{swarm-readiness-CQ4sjrXc.js → swarm-readiness-h6-pwY8w.js} +3 -3
  266. package/server/{system-prerequisites-DnmLYOnU.js → system-prerequisites-DZZTi2G6.js} +1 -1
  267. package/server/{teardown-workspace-CpcjDrZ4.js → teardown-workspace-B4D-oCVg.js} +37 -68
  268. package/server/{test-agent-queue-Bq298zZl.js → test-agent-queue-CnuYku4B.js} +6 -6
  269. package/server/{test-verdict-CCDd25uN.js → test-verdict-CRsPpByy.js} +1 -1
  270. package/server/{test-verdict-Bm4uv_AA.js → test-verdict-CXMLATGc.js} +1 -1
  271. package/server/{tldr-daemon-Rn1Zue0P.js → tldr-daemon-CgZQ3jmZ.js} +1 -1
  272. package/server/{tldr-daemon-CaSUXd9r.js → tldr-daemon-CuA8hz21.js} +2 -2
  273. package/server/{tmux-CbcrkUx5.js → tmux-By8E_Yv_.js} +1 -1
  274. package/server/{tmux-BVc-NeKM.js → tmux-C3g9VFcg.js} +2 -2
  275. package/server/{token-D2OC_9ms.js → token-g_sJ19g3.js} +2 -2
  276. package/server/token-util-C9u4An3K.js +3 -0
  277. package/server/{token-util-CROoF5ic.js → token-util-DiwnnbJo.js} +1 -1
  278. package/server/{tracker-config-EsBoOWEy.js → tracker-config-BzfVj41Z.js} +1 -1
  279. package/server/{tracker-config-C1Y_2CIo.js → tracker-config-dQxC15vH.js} +2 -2
  280. package/server/{tracker-utils-lph9C3Pw.js → tracker-utils--z8FLSWn.js} +2 -2
  281. package/server/{tracker-utils-DSb9cfrF.js → tracker-utils-DkKyRT0H.js} +1 -1
  282. package/server/{traefik-connect-5EP1WS_Z.js → traefik-connect-CZYB3dlm.js} +1 -1
  283. package/server/{tunnel-BFBCeo97.js → tunnel-BpWZs0Jy.js} +3 -3
  284. package/server/{tunnel-DDe-ATFF.js → tunnel-CN2px87h.js} +1 -1
  285. package/server/{typed-errors-DSPn_xWP.js → typed-errors-CrN1AByg.js} +1 -1
  286. package/server/{typed-errors-BEJhbjJg.js → typed-errors-sLpGfegz.js} +1 -1
  287. package/server/{types-CE1m_daD.js → types-BLS_Zbmd.js} +1 -1
  288. package/server/{types-B5u3NxMl.js → types-tlXf80x3.js} +1 -1
  289. package/server/{uat-train-DtKHHy1U.js → uat-train-BRo6DM7p.js} +24 -24
  290. package/server/{uat-train-8T24rPzc.js → uat-train-utGVmqdm.js} +1 -1
  291. package/server/{validation-lyudzPvD.js → validation-CsPVsoA0.js} +4 -4
  292. package/server/{validation-COOXwNXU.js → validation-Dq_wwcw3.js} +1 -1
  293. package/server/{verification-runner-DO0nYTJx.js → verification-runner-CMT5WRMS.js} +12 -12
  294. package/server/{verification-runner-Dd1FX8Qk.js → verification-runner-D0Uh-9jc.js} +1 -1
  295. package/server/{vtt-parser-C57tEF7-.js → vtt-parser-C_XPlifj.js} +1 -1
  296. package/server/{work-agent-prompt-B2F3ZwKm.js → work-agent-prompt-ZTAb2Hyf.js} +12 -12
  297. package/server/workflows-BLwxUMP7.js +2 -0
  298. package/server/{workflows-Clz_AFwR.js → workflows-CXRwhw_o.js} +14 -14
  299. package/server/{workspace-D0rCFemz.js → workspace-9UEsKuiP.js} +1 -1
  300. package/server/{workspace-CY_2-Ddv.js → workspace-CNfuj1-5.js} +3 -3
  301. package/server/{workspace-config-DA8U97k9.js → workspace-config-ChWOECy0.js} +1 -1
  302. package/server/{workspace-manager-BIfpzqdU.js → workspace-manager-BO-6k3sP.js} +1 -1
  303. package/server/{workspace-manager-D6NgGkiF.js → workspace-manager-D8AByFIT.js} +13 -13
  304. package/server/{workspace-metadata-C8yn8kAH.js → workspace-metadata-CvC268MN.js} +2 -2
  305. package/server/{workspace-metadata-DfgR40YU.js → workspace-metadata-DZiJtnuF.js} +1 -1
  306. package/server/{workspaces-DOeHyKmA.js → workspaces-D-uIoqbg.js} +104 -122
  307. package/server/writer-BUweuRPx.js +3 -0
  308. package/server/writer-CrP2xuBn.js +251 -0
  309. package/sync-sources/hooks/ask-user-question-hook +7 -1
  310. package/sync-sources/hooks/record-cost-event.js +4 -3
  311. package/sync-sources/hooks/record-cost-event.js.map +1 -1
  312. package/server/agent-runtime-mirror-DrWxopNA.js +0 -37
  313. package/server/auto-commit-wZ9X7Isd.js +0 -3
  314. package/server/bd-mutex-BItGvLQ8.js +0 -35
  315. package/server/beads-restore-CMbDSB5R.js +0 -60
  316. package/server/cli-CqgiSAU8.js +0 -3
  317. package/server/deacon-swarm-B8pgK2a7.js +0 -2
  318. package/server/dist-BYSj075e.js +0 -3
  319. package/server/forge-CUCYK7h_.js +0 -2
  320. package/server/hume-x9voULey.js +0 -3
  321. package/server/inspect-agent-D8UBDgfR.js +0 -2
  322. package/server/merge-ops-B7m-hBEZ.js +0 -2
  323. package/server/projects-BqRMlDOv.js +0 -3
  324. package/server/providers-gZ0cUgQM.js +0 -3
  325. package/server/public/assets/chunk-BO2N2NFS-C-6y8VFI.js +0 -103
  326. package/server/public/assets/index-BDqWyuZQ.js +0 -796
  327. package/server/public/assets/index-CZiADHcL.css +0 -1
  328. package/server/public/assets/mermaid-GHXKKRXX-DRM2ANcR.js +0 -1
  329. package/server/rally-CRteqXe0.js +0 -3
  330. package/server/record-CJsjvRO4.js +0 -3
  331. package/server/resume-R7xkSmLW.js +0 -3
  332. package/server/settings-CSaXtkoA.js +0 -2
  333. package/server/specs-oaaYaA5c.js +0 -3
  334. package/server/supervisor-03Tj_Wra.js +0 -2
  335. package/server/token-util-xNaV-00f.js +0 -3
  336. package/server/workflows-B4fIN8VP.js +0 -2
@@ -1,46 +1,54 @@
1
1
  import { a as __toCommonJS } from "./rolldown-runtime-DNCzzKnI.js";
2
- import { $n as extractPrefixSync, C as resolveTieredExecutionEnabled, Dn as getProjectSync, E as canUseHarnessSync, Gn as resolveProjectFromIssueSync, Hr as init_paths, Nn as init_projects, O as init_harness_policy, Rr as encodeClaudeProjectDir, S as init_tier_table, Vr as getOverdeckHome, X as resolveModel, er as init_issue_id, f as loadConfigSync, fn as init_state_read_home, lr as FsError, mn as shouldCommitLegacyWorkspaceArtifacts, nr as parseIssueIdSync, t as init_config_yaml, w as resolveTieredExecutionEnabledForIssue, yr as init_errors } from "./config-yaml-C_djSurJ.js";
2
+ import { B as piProviderForModel, Br as encodeClaudeProjectDir, C as init_tier_table, D as canUseHarnessSync, Fn as init_projects, Jr as init_paths, N as UnknownModelError, R as getProviderForModelSync, T as resolveTieredExecutionEnabledForIssue, Z as resolveModel, Zr as resolveOhmypiExtensionPath, c as isClaudeCodeChannelsEnabled, dr as FsError, f as loadConfigSync, gn as shouldCommitLegacyWorkspaceArtifacts, ir as parseIssueIdSync, k as init_harness_policy, kn as getProjectSync, mn as init_state_read_home, nr as init_issue_id, qn as resolveProjectFromIssueSync, qr as getOverdeckHome, t as init_config_yaml, tr as extractPrefixSync, w as resolveTieredExecutionEnabled, xr as init_errors, z as init_providers, zn as listProjectsSync } from "./config-yaml-cNOZcEww.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-Clew3DpF.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-BVc-NeKM.js";
6
- import { a as init_event_store, n as getEventStore } from "./event-store-C71x6h0i.js";
7
- import { C as normalizeChannelReplyPayload, a as getHarnessBehavior, b as DomainEvent, t as init_src } from "./src-DGAEtABV.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-BcfwcAcO.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-BoJa6qIt.js";
10
- import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-CaiG63FG.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-DqQa4Dq_.js";
12
- import { t as init_behavior } from "./behavior-D3d2mVyD.js";
13
- import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-C8jKYZ0w.js";
14
- import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-C1Y_2CIo.js";
15
- import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-BS9NENuR.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-CbAFXQge.js";
18
- import { d as getCliproxyAuthDir, g as getCliproxyLogPath, l as decodeJwtPayload, v as init_cliproxy } from "./cliproxy-BituqWtR.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-CFZIjs-M.js";
20
- import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population-CSH66UO8.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-CoyWsECg.js";
22
- import { a as init_stack_health, i as getWorkspaceStackHealth } from "./stack-health-C-le5erA.js";
23
- import { s as getDispatchableItems, u as init_dag } from "./dag-BQFtXOuJ.js";
24
- import { i as queryBeadById, r as init_beads_query } from "./beads-query-tOsAYP5h.js";
25
- import { a as init_lifecycle_io, c as updatePlanStatus, s as transitionVBriefOnMain } from "./lifecycle-io-C73m5e6R.js";
26
- import { Lt as getRuntimeForAgent, Q as createInFlightGuard, Qt as getAgentWorkspace$1, Rt as classifyMemoryPressure, Ut as getResourceConfig, Wt as getSystemHealthSnapshot, Xt as getAgentJsonlPath$1, Zt as getAgentPendingQuestions$1, nn as normalizeAwaitingInputPrompt, qt as computeAgentEnrichment } from "./deacon-2EvA9Ci3.js";
27
- import { o as stopWorkspaceDocker, r as init_workspace_manager } from "./workspace-manager-D6NgGkiF.js";
28
- import { at as init_jsonl_parser, ot as normalizeModelName } from "./specialists-BGNZ0_ZW.js";
29
- import { t as withBdMutex } from "./bd-mutex-BItGvLQ8.js";
30
- import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-C8yn8kAH.js";
31
- import { c as syncBeadStatusToVBrief } from "./beads-DRoj6-e1.js";
32
- import { dirname, join } from "node:path";
4
+ import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-Ba9mS6e6.js";
5
+ import { F as sendKeysAsync, I as sendRawKeystroke, K as buildChildEnvWithoutTmuxSync, L as sessionExists, O as listSessionNames, R as sessionExistsSync, S as isHarnessProcessAlive, W as BLANKED_PROVIDER_ENV, d as exactPaneTarget, i as capturePane, k as listSessions, o as createSession, p as findManagedServerPidSync, q as init_child_env, w as killSession, x as init_tmux, z as setOption } from "./tmux-C3g9VFcg.js";
6
+ import { a as init_event_store, n as getEventStore } from "./event-store-D89fMfXS.js";
7
+ import { C as normalizeChannelReplyPayload, a as getHarnessBehavior, b as DomainEvent, t as init_src } from "./src-B4TTzNSr.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-Dc1l4dVz.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--8MRxrG1.js";
10
+ import { n as ensureSessionContextBriefingFile, r as init_briefing_freshness } from "./briefing-freshness-DESTCWo-.js";
11
+ import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-D9kPIY_5.js";
12
+ import { $n as writeAgentStateJsonSync, An as getAgentDir, At as init_bridge_token, Bt as resolveStaffing, C as restartAgent, Ct as deliverAgentPermissionDecision, E as resumeAgent, En as clearAgentTroubled, Et as setAgentDeliveryMethod, F as getActivity, Gt as getAgentRuntimeBaseCommand, 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, ar as resolvePtySupervisorScriptPath, bn as writeChannelsBridgeMcpConfig, cn as determineModel, cr as normalizeAgentId, dr as waitForReadySignal, en as init_pi_fifo, fn as init_provider_health, fr as getAgentRuntimeState, g as init_tier_escalation, h as decideEscalation, hr as saveAgentRuntimeState, ir as init_pty_supervisor_locate, j as messageAgent, jt as writeBridgeTokenSync, ln as getProviderEnvForModel, nn as init_ohmypi_fifo, nr as init_pty_token, or as clearReadySignal, pn as validateProviderHealth, pr as getAgentRuntimeStateSync, qn as saveAgentState, rn as ohmypiFifoPaths, rr as writePtyToken, t as init_agents, tn as piFifoPaths, un as getProviderExportsForModel, wn as clearAgentPaused, yn as dismissDevChannelsDialog, yt as loadCloisterConfigSync, z as saveSessionId, zt as init_staffing } from "./agents-B9GwfCyp.js";
13
+ import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-DfTVoqJg.js";
14
+ import { o as init_persistent_logger, s as logAgentLifecycleSync } from "./session-history-DanhhiCw.js";
15
+ import { t as init_behavior } from "./behavior-BTpYbAv4.js";
16
+ import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-dQxC15vH.js";
17
+ import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-CkTUI-eu.js";
18
+ import { a as PAN_DIRNAME, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-BLS_Zbmd.js";
19
+ import { F as generateVBriefFilename, I 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-VEGSAqsd.js";
20
+ import { d as getCliproxyAuthDir, g as getCliproxyLogPath, l as decodeJwtPayload, v as init_cliproxy } from "./cliproxy-B4c1BUvs.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-B-lcHv-5.js";
22
+ 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-B8LFQQm1.js";
23
+ import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population-CqZSnqQi.js";
24
+ import { a as init_launcher_generator, c as requireModelOverrideSync, i as generateLauncherScriptSync, n as init_state_home, o as init_model_validation, r as isStateMigrated, s as normalizeModelOverrideSync } from "./state-home-CEvD_v5F.js";
25
+ import { a as init_claude_permissions, o as resolvePermissionModeSync, r as getClaudePermissionFlagsStringSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-CThLgf8d.js";
26
+ import { h as workspaceContextFile, l as piGlobalContextFile, o as init_layers } from "./layers-jjL16At6.js";
27
+ import { a as init_stack_health, i as getWorkspaceStackHealth } from "./stack-health-CSgcYcr8.js";
28
+ import { s as getDispatchableItems, u as init_dag } from "./dag-DKI3sOmX.js";
29
+ import { i as queryBeadById, r as init_beads_query } from "./beads-query-Dl4FhT8J.js";
30
+ import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-C6g2R0Cd.js";
31
+ import { a as init_lifecycle_io, c as updatePlanStatus, s as transitionVBriefOnMain } from "./lifecycle-io-IehcRt2v.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-CxsyxwFO.js";
33
+ import { Ht as getResourceConfig, Lt as classifyMemoryPressure, Q as createInFlightGuard, Ut as getSystemHealthSnapshot } from "./deacon-DdARMjZq.js";
34
+ import { o as stopWorkspaceDocker, r as init_workspace_manager } from "./workspace-manager-D8AByFIT.js";
35
+ import { a as init_jsonl_parser, o as normalizeModelName } from "./jsonl-parser-BUW5_sSK.js";
36
+ import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-CvC268MN.js";
37
+ import { c as syncBeadStatusToVBrief, l as withBdMutex } from "./beads-BeSMEjGT.js";
38
+ import { basename, delimiter, dirname, join, resolve } from "node:path";
33
39
  import { Cause, Data, Effect, Layer, Option, Schema, Stream } from "effect";
34
40
  import { join as join$1 } from "path";
35
41
  import { homedir } from "node:os";
36
- import { existsSync } from "node:fs";
42
+ import { createReadStream, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
37
43
  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";
44
+ import { access, appendFile, chmod, cp, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat as stat$1, writeFile } from "node:fs/promises";
39
45
  import { randomUUID } from "crypto";
40
46
  import { mkdir as mkdir$1, open as open$1, readFile as readFile$1, stat as stat$2, writeFile as writeFile$1 } from "fs/promises";
41
47
  import { exec, execFile, spawn } from "node:child_process";
42
48
  import { promisify } from "node:util";
43
- import { timingSafeEqual } from "node:crypto";
49
+ import { randomUUID as randomUUID$1, timingSafeEqual } from "node:crypto";
50
+ import { pathToFileURL } from "node:url";
51
+ import { createInterface } from "node:readline";
44
52
  import * as Result$1 from "effect/Result";
45
53
  //#region http-helpers.ts
46
54
  init_paths();
@@ -65,10 +73,11 @@ function jsonStringResponse(json, options) {
65
73
  });
66
74
  }
67
75
  //#endregion
68
- //#region routes/http-handler.ts
76
+ //#region ../../lib/default-cwd.ts
69
77
  init_activity_logger();
70
78
  init_issue_id();
71
79
  init_projects();
80
+ init_providers();
72
81
  init_harness_policy();
73
82
  init_tier_table();
74
83
  init_config_yaml();
@@ -79,12 +88,34 @@ init_event_store();
79
88
  init_src();
80
89
  init_agent_state_sync();
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,
@@ -153,8 +184,6 @@ function httpHandler(effect) {
153
184
  }
154
185
  //#endregion
155
186
  //#region ../../lib/codex-auth.ts
156
- init_cost();
157
- init_tracker_config();
158
187
  init_cliproxy();
159
188
  /** Wrapper error for Codex auth probing — preserves the underlying cause. */
160
189
  var CodexAuthCheckError = class extends Data.TaggedError("CodexAuthCheckError") {};
@@ -408,19 +437,92 @@ function isTrustedOriginForHost(origin, _host) {
408
437
  return getTrustedOrigins().includes(normalized);
409
438
  }
410
439
  //#endregion
440
+ //#region services/pending-respawn.ts
441
+ /**
442
+ * Tracks tmux sessions that are mid-respawn so that `/ws/terminal`
443
+ * reconnects landing in the kill→spawn gap don't get a fatal 4404.
444
+ *
445
+ * Background: switch-model, resume, and restart-all all do
446
+ *
447
+ * await killSessionAsync(name) // old tmux dies, PTY exits,
448
+ * // ws-terminal closes clients with 1000
449
+ * await spawnConversationSession(...) // new tmux session with same name
450
+ * await waitForTmuxSession(name)
451
+ * await waitForReadySignal(name, 30) // up to 30s
452
+ *
453
+ * The terminal frontend (`XTerminal.tsx`) reconnects on close 1000 with
454
+ * exponential backoff (1s, 2s, 4s, 8s, 16s). Every reconnect re-enters
455
+ * `ws-terminal.ts`, which checks the tmux session list upfront and
456
+ * `close(4404, 'session-not-found')` if the name isn't there yet.
457
+ * 4404 is treated as fatal on the client (no retry — the session is
458
+ * presumed gone), so the panel sticks on "Could not reconnect" even
459
+ * after the respawn completes a moment later.
460
+ *
461
+ * This registry lets `ws-terminal.ts` distinguish "transient absence
462
+ * during respawn" from "session is actually gone". Respawn sites mark
463
+ * the name with `markRespawnPending()` for the duration of their kill+
464
+ * spawn block; ws-terminal calls `waitForSessionRespawn()` instead of
465
+ * 4404-ing immediately when the session is missing AND
466
+ * `isRespawnPending()` is true.
467
+ */
468
+ const pendingRespawns = /* @__PURE__ */ new Set();
469
+ /**
470
+ * Mark a tmux session as mid-respawn. The returned `done()` must run in
471
+ * a `finally` so the marker is cleared even if the respawn throws.
472
+ */
473
+ function markRespawnPending(sessionName) {
474
+ pendingRespawns.add(sessionName);
475
+ return { done: () => {
476
+ pendingRespawns.delete(sessionName);
477
+ } };
478
+ }
479
+ /** True if a respawn is currently in progress for this session name. */
480
+ function isRespawnPending(sessionName) {
481
+ return pendingRespawns.has(sessionName);
482
+ }
483
+ const POLL_INTERVAL_MS = 200;
484
+ /**
485
+ * Poll for the session to come back, up to `timeoutMs`. Returns whether
486
+ * it did. Bails early once `pendingRespawns` no longer contains the name
487
+ * (the wrapping respawn block finished, success or failure) and the
488
+ * session still isn't there — at that point the absence is real.
489
+ *
490
+ * `timeoutMs` should comfortably exceed the longest respawn window. The
491
+ * dominant cost is `waitForReadySignal`'s 30s ceiling in the conversation
492
+ * routes, so 35s is the right default for those callers.
493
+ */
494
+ async function waitForSessionRespawn(sessionName, timeoutMs) {
495
+ const deadline = Date.now() + timeoutMs;
496
+ while (Date.now() < deadline) {
497
+ if (await Effect.runPromise(sessionExists(sessionName))) return true;
498
+ if (!pendingRespawns.has(sessionName)) return Effect.runPromise(sessionExists(sessionName));
499
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
500
+ }
501
+ return Effect.runPromise(sessionExists(sessionName));
502
+ }
503
+ //#endregion
411
504
  //#region routes/jsonl-resolver.ts
505
+ init_conversations();
412
506
  init_codex_conversation_parser();
413
507
  init_ohmypi_conversation_parser();
414
508
  init_pi_conversation_parser();
415
509
  init_conversation_service();
416
- init_persistent_logger();
510
+ init_pty_supervisor_locate();
511
+ init_pty_token();
417
512
  init_feature_registry_population();
418
513
  init_model_validation();
419
514
  init_provider_health();
515
+ init_claude_permissions();
516
+ init_layers();
517
+ init_ohmypi_fifo();
518
+ init_pi_fifo();
420
519
  init_stack_health();
520
+ init_launcher_generator();
421
521
  init_dag();
422
522
  init_staffing();
423
523
  init_beads_query();
524
+ init_harness_resolve();
525
+ init_bridge_token();
424
526
  init_operator_interventions();
425
527
  init_lifecycle_io();
426
528
  /**
@@ -435,7 +537,8 @@ init_lifecycle_io();
435
537
  *
436
538
  * For claude-code agents the JSONL filename is the *Claude session ID* (a UUID
437
539
  * written by Claude Code itself), NOT the agent/tmux name. Lookup order:
438
- * 1. session.id — single UUID written by auto-suspend
540
+ * 1. session.id — UUID pinned before fresh work-agent launch and updated
541
+ * by auto-suspend/resume flows
439
542
  * 2. sessions.json — array of UUIDs the agent has used; the heartbeat hook
440
543
  * APPENDS new IDs and dedupes, so once a session has been
441
544
  * seen its array index never moves. After a `pan resume`
@@ -448,7 +551,13 @@ init_lifecycle_io();
448
551
  * server's event loop.
449
552
  */
450
553
  init_agents();
451
- init_behavior();
554
+ const transcriptResolutionSignatures = /* @__PURE__ */ new Map();
555
+ function logTranscriptResolution(agentId, signature, message, opts) {
556
+ const signatureKey = `${agentId}:${opts.agentsDirOverride ?? "live"}`;
557
+ if (transcriptResolutionSignatures.get(signatureKey) === signature) return;
558
+ transcriptResolutionSignatures.set(signatureKey, signature);
559
+ (opts.logDiagnostic ?? (opts.agentsDirOverride ? void 0 : logAgentLifecycleSync))?.(agentId, `transcript resolution: ${message}`);
560
+ }
452
561
  async function pathExists$1(p) {
453
562
  return access(p).then(() => true, () => false);
454
563
  }
@@ -629,7 +738,7 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
629
738
  const agentDir = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId);
630
739
  const codexHome = join(agentDir, "codex-home");
631
740
  if (!await pathExists$1(codexHome)) return null;
632
- const { findRolloutPath, findLatestRollout } = await import("./codex-Bi1ITSOo.js");
741
+ const { findRolloutPath, findLatestRollout } = await import("./codex-BYPz6ZdF.js");
633
742
  const threadId = (await readOptional(join(agentDir, "codex-thread-id")))?.trim();
634
743
  if (threadId) {
635
744
  const rollout = findRolloutPath(codexHome, threadId);
@@ -681,18 +790,27 @@ async function resolvePiSessionPath(agentId, opts = {}) {
681
790
  * the corresponding JSONL file exists; otherwise null.
682
791
  */
683
792
  async function resolveJsonlPath(agentId, workspacePath, opts = {}) {
684
- const behavior = behaviorForHarness(await resolveAgentHarness(agentId, opts));
793
+ const harness = await resolveAgentHarness(agentId, opts);
794
+ const behavior = behaviorForHarness(harness);
685
795
  if (behavior.transcriptKind === "codex-rollout-jsonl") return resolveCodexRolloutPath(agentId, opts);
686
796
  if (behavior.transcriptKind === "ohmypi-jsonl") return resolvePiSessionPath(agentId, opts);
687
797
  const claudeSessionId = await resolveClaudeSessionId(agentId, opts);
688
- if (!claudeSessionId) return null;
798
+ if (!claudeSessionId) {
799
+ logTranscriptResolution(agentId, `missing-session-id:${harness ?? "unknown"}`, `failed harness=${harness ?? "unknown"} reason=no-session-id checked=session.id,sessions.json,runtime-state`, opts);
800
+ return null;
801
+ }
689
802
  const jsonlPath = join(opts.claudeProjectsDirOverride ?? join(homedir(), ".claude", "projects"), encodeClaudeProjectDir(workspacePath), `${claudeSessionId}.jsonl`);
690
- if (await pathExists$1(jsonlPath)) return jsonlPath;
803
+ if (await pathExists$1(jsonlPath)) {
804
+ logTranscriptResolution(agentId, `resolved:${jsonlPath}`, `resolved harness=${harness ?? "claude-code"} sessionId=${claudeSessionId} path=${jsonlPath}`, opts);
805
+ return jsonlPath;
806
+ }
807
+ logTranscriptResolution(agentId, `missing-jsonl:${jsonlPath}`, `failed harness=${harness ?? "claude-code"} reason=jsonl-missing sessionId=${claudeSessionId} workspace=${workspacePath} expectedPath=${jsonlPath}`, opts);
691
808
  return null;
692
809
  }
693
810
  //#endregion
694
811
  //#region ../../lib/work-agent-lifecycle.ts
695
812
  init_agents();
813
+ init_tmux();
696
814
  async function pathExists(path) {
697
815
  try {
698
816
  await access(path);
@@ -861,7 +979,7 @@ const getWorkAgentLifecycleState = (agentOrIssueId) => Effect.promise(() => getW
861
979
  *
862
980
  * All I/O is async (execFile / fs.promises). No execSync, no readFileSync.
863
981
  */
864
- const execFileAsync$2 = promisify(execFile);
982
+ const execFileAsync$3 = promisify(execFile);
865
983
  const GIT_INFO_TTL_MS = 3e4;
866
984
  const GIT_INFO_TIMEOUT_MS = 2e3;
867
985
  const cache = /* @__PURE__ */ new Map();
@@ -902,7 +1020,7 @@ async function readHeadMtimeMs(absPath) {
902
1020
  }
903
1021
  }
904
1022
  async function readGitInfoFresh(absPath) {
905
- const [branchResult, worktreeResult] = await Promise.all([execFileAsync$2("git", [
1023
+ const [branchResult, worktreeResult] = await Promise.all([execFileAsync$3("git", [
906
1024
  "-C",
907
1025
  absPath,
908
1026
  "rev-parse",
@@ -911,7 +1029,7 @@ async function readGitInfoFresh(absPath) {
911
1029
  ], {
912
1030
  encoding: "utf-8",
913
1031
  timeout: GIT_INFO_TIMEOUT_MS
914
- }).catch(() => null), execFileAsync$2("git", [
1032
+ }).catch(() => null), execFileAsync$3("git", [
915
1033
  "-C",
916
1034
  absPath,
917
1035
  "rev-parse",
@@ -986,8 +1104,8 @@ async function resolveAgentGitInfo(workspacePath, expectedBranch) {
986
1104
  init_paths();
987
1105
  init_agents();
988
1106
  init_tmux();
989
- const execAsync$1 = promisify(exec);
990
- const execFileAsync$1 = promisify(execFile);
1107
+ const execAsync$2 = promisify(exec);
1108
+ const execFileAsync$2 = promisify(execFile);
991
1109
  function buildPanStartArgs(input) {
992
1110
  return [
993
1111
  "start",
@@ -1335,9 +1453,9 @@ async function getGitStatusAsync(workspacePath) {
1335
1453
  try {
1336
1454
  if (!existsSync(workspacePath)) return null;
1337
1455
  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 })
1456
+ execAsync$2("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"", { cwd: workspacePath }),
1457
+ execAsync$2("git status --porcelain 2>/dev/null | wc -l", { cwd: workspacePath }),
1458
+ execAsync$2("git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"", { cwd: workspacePath })
1341
1459
  ]);
1342
1460
  const branch = branchResult.stdout.trim();
1343
1461
  const uncommitted = uncommittedResult.stdout.trim();
@@ -1720,7 +1838,7 @@ const getAgentOutputRoute = HttpRouter.add("GET", "/api/agents/:id/output", http
1720
1838
  } catch {}
1721
1839
  let stdout;
1722
1840
  if (isRemote && vmName) {
1723
- const { getRemoteAgentOutput } = await import("./remote-agents-B4eWgwNd.js");
1841
+ const { getRemoteAgentOutput } = await import("./remote-agents-Cv-U-_V8.js");
1724
1842
  stdout = await getRemoteAgentOutput(id, vmName, parseInt(String(lines), 10) || 100);
1725
1843
  } else stdout = await Effect.runPromise(capturePane(id, parseInt(String(lines), 10) || 100));
1726
1844
  if (!stdout || stdout.trim() === "" || stdout.trim() === "Session not found") {
@@ -1821,7 +1939,7 @@ const getAgentFilesRoute = HttpRouter.add("GET", "/api/agents/:id/files", httpHa
1821
1939
  if (!agentState?.workspace) return jsonResponse({ files: [] });
1822
1940
  const workspacePath = agentState.workspace;
1823
1941
  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 \"\"", {
1942
+ const { stdout } = yield* Effect.promise(() => execAsync$2("git diff --name-status HEAD 2>/dev/null || git status --porcelain 2>/dev/null || echo \"\"", {
1825
1943
  cwd: workspacePath,
1826
1944
  encoding: "utf-8"
1827
1945
  }));
@@ -1908,6 +2026,1403 @@ const postAgentPokeRoute = HttpRouter.add("POST", "/api/agents/:id/poke", httpHa
1908
2026
  });
1909
2027
  })));
1910
2028
  //#endregion
2029
+ //#region ../../lib/runtimes/conversation-control.ts
2030
+ function conversationControlPaths(agentId, home) {
2031
+ const { agentDir } = ohmypiFifoPaths(agentId, home);
2032
+ return {
2033
+ agentDir,
2034
+ controlDir: join(agentDir, "control")
2035
+ };
2036
+ }
2037
+ async function writeConversationControlCommand(agentId, command, home) {
2038
+ const paths = conversationControlPaths(agentId, home);
2039
+ await mkdir(paths.controlDir, {
2040
+ recursive: true,
2041
+ mode: 448
2042
+ });
2043
+ await chmod(paths.controlDir, 448);
2044
+ const finalPath = join(paths.controlDir, `${Date.now()}-${command.id}-${randomUUID$1()}.json`);
2045
+ const tempPath = `${finalPath}.tmp`;
2046
+ await writeFile(tempPath, `${JSON.stringify(command)}\n`, {
2047
+ encoding: "utf8",
2048
+ mode: 384
2049
+ });
2050
+ await chmod(tempPath, 384);
2051
+ await rename(tempPath, finalPath);
2052
+ await chmod(finalPath, 384);
2053
+ return finalPath;
2054
+ }
2055
+ //#endregion
2056
+ //#region ../../lib/ohmypi-codex-auth.ts
2057
+ /**
2058
+ * oh-my-pi (omp) OAuth helpers for the openai-codex (ChatGPT subscription) provider.
2059
+ * Forked from pi-codex-auth.ts (PAN-1520) for the omp migration (PAN-1989).
2060
+ *
2061
+ * omp stores its general credentials in ~/.omp/agent/agent.db (SQLite). The
2062
+ * openai-codex OAuth credential is persisted separately at ~/.omp/agent/auth.json
2063
+ * (same JSON format as Pi's ~/.pi/agent/auth.json) so that this helper's
2064
+ * read/write logic is unchanged from the Pi side.
2065
+ *
2066
+ * This module:
2067
+ * - reads/writes the openai-codex credential under ~/.omp/agent/auth.json,
2068
+ * - reports auth status (for `pan doctor` and the conversation spawn gate),
2069
+ * - refreshes an expired-but-recoverable token, and
2070
+ * - drives omp's headless device-code login to re-authenticate.
2071
+ *
2072
+ * The OAuth module is loaded from the installed `omp` package dynamically.
2073
+ * A missing or relocated omp OAuth module degrades to 'unavailable' — never throws.
2074
+ */
2075
+ const PI_CODEX_PROVIDER = "openai-codex";
2076
+ function getOhmypiAuthPath() {
2077
+ return join(homedir(), ".omp", "agent", "auth.json");
2078
+ }
2079
+ function readAuthFile() {
2080
+ const path = getOhmypiAuthPath();
2081
+ if (!existsSync(path)) return {};
2082
+ try {
2083
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
2084
+ return parsed && typeof parsed === "object" ? parsed : {};
2085
+ } catch {
2086
+ return {};
2087
+ }
2088
+ }
2089
+ function readOhmypiCodexCredential() {
2090
+ const cred = readAuthFile()[PI_CODEX_PROVIDER];
2091
+ if (cred && typeof cred === "object" && typeof cred["access"] === "string" && typeof cred["refresh"] === "string") return cred;
2092
+ return null;
2093
+ }
2094
+ /** Atomically merge a fresh codex credential into auth.json (mode 0600). */
2095
+ function writeOhmypiCodexCredential(tokens) {
2096
+ const path = getOhmypiAuthPath();
2097
+ mkdirSync(dirname(path), { recursive: true });
2098
+ const auth = readAuthFile();
2099
+ const stored = {
2100
+ type: "oauth",
2101
+ access: tokens.access,
2102
+ refresh: tokens.refresh,
2103
+ expires: tokens.expires,
2104
+ accountId: tokens.accountId
2105
+ };
2106
+ auth[PI_CODEX_PROVIDER] = stored;
2107
+ const tmp = `${path}.tmp-${process.pid}`;
2108
+ writeFileSync(tmp, JSON.stringify(auth, null, 2), { mode: 384 });
2109
+ renameSync(tmp, path);
2110
+ return stored;
2111
+ }
2112
+ /** Locate the `omp` executable on PATH (no subprocess — event-loop safe). */
2113
+ function findOmpBinary() {
2114
+ for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
2115
+ if (!dir) continue;
2116
+ const candidate = join(dir, "omp");
2117
+ if (existsSync(candidate)) return candidate;
2118
+ }
2119
+ return null;
2120
+ }
2121
+ /** omp is @oh-my-pi/pi-coding-agent; the oauth submodule path mirrors pi-ai's layout. */
2122
+ const OMP_AI_OAUTH_SUBPATH = join("@oh-my-pi", "pi-ai", "dist", "utils", "oauth", "openai-codex.js");
2123
+ /**
2124
+ * Resolve omp's openai-codex OAuth submodule by walking node_modules upward
2125
+ * from the real omp binary. Returns null when not found — the caller degrades
2126
+ * gracefully to 'unavailable' status.
2127
+ */
2128
+ function resolveOmpCodexOAuthFile(ompBin) {
2129
+ let dir = dirname(realpathSync(ompBin));
2130
+ for (let i = 0; i < 10; i++) {
2131
+ const candidate = join(dir, "node_modules", OMP_AI_OAUTH_SUBPATH);
2132
+ if (existsSync(candidate)) return candidate;
2133
+ const parent = dirname(dir);
2134
+ if (parent === dir) break;
2135
+ dir = parent;
2136
+ }
2137
+ return null;
2138
+ }
2139
+ let cachedModule = null;
2140
+ /**
2141
+ * Dynamically load omp's openai-codex OAuth submodule from the installed `omp`
2142
+ * package. Returns null on any failure so callers can degrade gracefully.
2143
+ */
2144
+ async function loadOhmypiCodexOAuth() {
2145
+ if (cachedModule) return cachedModule;
2146
+ cachedModule = (async () => {
2147
+ try {
2148
+ const ompBin = findOmpBinary();
2149
+ if (!ompBin) return null;
2150
+ const oauthFile = resolveOmpCodexOAuthFile(ompBin);
2151
+ if (!oauthFile) return null;
2152
+ const mod = await import(pathToFileURL(oauthFile).href);
2153
+ if (typeof mod.refreshOpenAICodexToken !== "function" || typeof mod.loginOpenAICodexDeviceCode !== "function") return null;
2154
+ return mod;
2155
+ } catch {
2156
+ return null;
2157
+ }
2158
+ })();
2159
+ return cachedModule;
2160
+ }
2161
+ const EXPIRY_MARGIN_MS = 6e4;
2162
+ async function getOhmypiCodexAuthStatus(opts) {
2163
+ const cred = readOhmypiCodexCredential();
2164
+ if (!cred) return { status: "missing" };
2165
+ if (cred.expires > Date.now() + EXPIRY_MARGIN_MS) return {
2166
+ status: "ok",
2167
+ expiresAt: cred.expires
2168
+ };
2169
+ if (opts?.refreshIfExpired) {
2170
+ const refreshed = await refreshOhmypiCodexAuth();
2171
+ if (refreshed) return {
2172
+ status: "ok",
2173
+ expiresAt: refreshed.expires
2174
+ };
2175
+ return {
2176
+ status: "expired",
2177
+ expiresAt: cred.expires,
2178
+ refreshFailed: true
2179
+ };
2180
+ }
2181
+ return {
2182
+ status: "expired",
2183
+ expiresAt: cred.expires,
2184
+ refreshFailed: false
2185
+ };
2186
+ }
2187
+ async function refreshOhmypiCodexAuth() {
2188
+ const cred = readOhmypiCodexCredential();
2189
+ if (!cred?.refresh) return null;
2190
+ const mod = await loadOhmypiCodexOAuth();
2191
+ if (!mod) return null;
2192
+ try {
2193
+ return writeOhmypiCodexCredential(await mod.refreshOpenAICodexToken(cred.refresh));
2194
+ } catch {
2195
+ return null;
2196
+ }
2197
+ }
2198
+ //#endregion
2199
+ //#region services/conversation-attachments.ts
2200
+ init_paths();
2201
+ const CONVERSATION_ATTACHMENTS_DIR = "conversation-attachments";
2202
+ /** Run async tasks in bounded batches to avoid unbounded Promise.all
2203
+ * that can exhaust file descriptors or memory under heavy load. */
2204
+ async function runInBatches(items, batchSize, fn) {
2205
+ for (let i = 0; i < items.length; i += batchSize) {
2206
+ const batch = items.slice(i, i + batchSize);
2207
+ await Promise.all(batch.map(fn));
2208
+ }
2209
+ }
2210
+ /** Max JSONL line length to process. Lines larger than this are skipped to
2211
+ * prevent readline from stalling permanently on oversized input (e.g. a
2212
+ * single-line base64 payload >15 MB). */
2213
+ const MAX_JSONL_LINE_LENGTH = 15 * 1024 * 1024;
2214
+ /** Bounded LRU cache for readSessionAttachmentBasenames to avoid full JSONL
2215
+ * rescans when the session file has not changed. Key = sessionFile:mtimeMs. */
2216
+ const SESSION_ATTACHMENT_CACHE_MAX = 100;
2217
+ const sessionAttachmentCache = /* @__PURE__ */ new Map();
2218
+ function getAttachmentCacheKey(sessionFile, mtimeMs) {
2219
+ return `${sessionFile}:${mtimeMs}`;
2220
+ }
2221
+ function setAttachmentCache(sessionFile, mtimeMs, basenames) {
2222
+ const key = getAttachmentCacheKey(sessionFile, mtimeMs);
2223
+ sessionAttachmentCache.set(key, basenames);
2224
+ if (sessionAttachmentCache.size > SESSION_ATTACHMENT_CACHE_MAX) {
2225
+ const firstKey = sessionAttachmentCache.keys().next().value;
2226
+ if (firstKey !== void 0) sessionAttachmentCache.delete(firstKey);
2227
+ }
2228
+ }
2229
+ function getAttachmentCache(sessionFile, mtimeMs) {
2230
+ const key = getAttachmentCacheKey(sessionFile, mtimeMs);
2231
+ return sessionAttachmentCache.get(key);
2232
+ }
2233
+ /** Conversation names are sanitized to [a-zA-Z0-9_-]{1,64} on creation.
2234
+ * Re-validate here for defense-in-depth against path traversal. */
2235
+ function assertSafeName(name) {
2236
+ if (!/^[a-zA-Z0-9_-]{1,64}$/.test(name)) throw new Error("Invalid conversation name");
2237
+ }
2238
+ function getConversationAttachmentsRoot() {
2239
+ return join(getOverdeckHome(), CONVERSATION_ATTACHMENTS_DIR);
2240
+ }
2241
+ function getConversationAttachmentDir(name) {
2242
+ assertSafeName(name);
2243
+ return join(getConversationAttachmentsRoot(), name);
2244
+ }
2245
+ async function ensureConversationAttachmentDir(name) {
2246
+ const dir = getConversationAttachmentDir(name);
2247
+ await mkdir(dir, { recursive: true });
2248
+ return dir;
2249
+ }
2250
+ async function cleanupConversationAttachments(name) {
2251
+ assertSafeName(name);
2252
+ await rm(getConversationAttachmentDir(name), {
2253
+ recursive: true,
2254
+ force: true
2255
+ });
2256
+ }
2257
+ /** Safety-net cleanup: remove attachment directories for conversations that no
2258
+ * longer exist in the database. Catches orphaned files from abandoned/crashed
2259
+ * sessions that the lifecycle poller missed. */
2260
+ async function cleanupOrphanedConversationAttachments() {
2261
+ try {
2262
+ const { listConversations } = await import("./conversations-BM7zlXjr.js");
2263
+ const convNames = new Set(listConversations().map((c) => c.name));
2264
+ const root = getConversationAttachmentsRoot();
2265
+ 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, {
2266
+ recursive: true,
2267
+ force: true
2268
+ }).catch(() => {}));
2269
+ } catch {}
2270
+ }
2271
+ async function listConversationAttachmentPaths(name) {
2272
+ const dir = getConversationAttachmentDir(name);
2273
+ try {
2274
+ return (await readdir(dir, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => join(dir, entry.name));
2275
+ } catch {
2276
+ return [];
2277
+ }
2278
+ }
2279
+ /** Synchronous containment check: does the normalized path start with the
2280
+ * pre-resolved attachment directory prefix? Uses resolve() (not realpath)
2281
+ * so the check is I/O-free — safe because we are only cataloguing references
2282
+ * in the JSONL, not accessing the filesystem. */
2283
+ function isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath) {
2284
+ try {
2285
+ return resolve(attachmentPath).startsWith(`${resolvedAttachmentDir}/`);
2286
+ } catch {
2287
+ return false;
2288
+ }
2289
+ }
2290
+ async function readSessionAttachmentBasenames(sessionFile, name) {
2291
+ try {
2292
+ let mtimeMs;
2293
+ try {
2294
+ mtimeMs = (await stat$1(sessionFile)).mtimeMs;
2295
+ } catch {
2296
+ return /* @__PURE__ */ new Set();
2297
+ }
2298
+ const cached = getAttachmentCache(sessionFile, mtimeMs);
2299
+ if (cached) return cached;
2300
+ let resolvedAttachmentDir;
2301
+ try {
2302
+ resolvedAttachmentDir = await realpath(getConversationAttachmentDir(name));
2303
+ } catch {
2304
+ resolvedAttachmentDir = resolve(getConversationAttachmentDir(name));
2305
+ }
2306
+ const referenced = /* @__PURE__ */ new Set();
2307
+ const stream = createReadStream(sessionFile, { encoding: "utf-8" });
2308
+ const rl = createInterface({
2309
+ input: stream,
2310
+ crlfDelay: Infinity
2311
+ });
2312
+ try {
2313
+ for await (const line of rl) {
2314
+ if (!line.trim()) continue;
2315
+ if (line.length > MAX_JSONL_LINE_LENGTH) continue;
2316
+ let text;
2317
+ let structuredFoundPaths = false;
2318
+ try {
2319
+ const entry = JSON.parse(line);
2320
+ if (typeof entry?.message?.content === "string") text = entry.message.content;
2321
+ else if (Array.isArray(entry?.message?.content)) text = entry.message.content.map((c) => typeof c === "string" ? c : c?.text ?? "").join("");
2322
+ else if (typeof entry?.message?.text === "string") text = entry.message.text;
2323
+ else if (typeof entry?.text === "string") text = entry.text;
2324
+ } catch {
2325
+ text = line;
2326
+ }
2327
+ if (text) {
2328
+ for (const attachmentPath of extractConversationAttachmentPaths(text)) if (isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath)) {
2329
+ referenced.add(basename(attachmentPath));
2330
+ structuredFoundPaths = true;
2331
+ }
2332
+ }
2333
+ if (!structuredFoundPaths) for (const match of line.matchAll(/"@([^"]+)"/g)) {
2334
+ const rawValue = match[1];
2335
+ if (!rawValue.startsWith("/") && !rawValue.startsWith("\\/")) continue;
2336
+ let attachmentPath;
2337
+ try {
2338
+ attachmentPath = JSON.parse(`"${rawValue}"`);
2339
+ } catch {
2340
+ attachmentPath = rawValue;
2341
+ }
2342
+ if (typeof attachmentPath === "string" && attachmentPath.startsWith("/")) {
2343
+ if (isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath)) referenced.add(basename(attachmentPath));
2344
+ }
2345
+ }
2346
+ }
2347
+ } finally {
2348
+ rl.close();
2349
+ stream.destroy();
2350
+ }
2351
+ setAttachmentCache(sessionFile, mtimeMs, referenced);
2352
+ return referenced;
2353
+ } catch {
2354
+ return /* @__PURE__ */ new Set();
2355
+ }
2356
+ }
2357
+ async function cleanupUnreferencedConversationAttachments(conversation) {
2358
+ const attachmentPaths = await listConversationAttachmentPaths(conversation.name);
2359
+ if (attachmentPaths.length === 0) return;
2360
+ if (!conversation.sessionFile) {
2361
+ await runInBatches(attachmentPaths, 10, async (attachmentPath) => {
2362
+ await rm(attachmentPath, { force: true });
2363
+ });
2364
+ return;
2365
+ }
2366
+ let sessionMtimeMs;
2367
+ try {
2368
+ sessionMtimeMs = (await stat$1(conversation.sessionFile)).mtimeMs;
2369
+ } catch {
2370
+ return;
2371
+ }
2372
+ const referencedBasenames = await readSessionAttachmentBasenames(conversation.sessionFile, conversation.name);
2373
+ try {
2374
+ const freshStats = await stat$1(conversation.sessionFile);
2375
+ if (freshStats.mtimeMs > sessionMtimeMs) sessionMtimeMs = freshStats.mtimeMs;
2376
+ } catch {}
2377
+ await runInBatches(attachmentPaths, 10, async (attachmentPath) => {
2378
+ if (referencedBasenames.has(basename(attachmentPath))) return;
2379
+ try {
2380
+ if ((await stat$1(attachmentPath)).mtimeMs >= sessionMtimeMs) return;
2381
+ } catch {
2382
+ return;
2383
+ }
2384
+ await rm(attachmentPath, { force: true });
2385
+ });
2386
+ }
2387
+ function extractConversationAttachmentPaths(message) {
2388
+ return Array.from(message.matchAll(/(?<!\S)@(\/[^\s]+)/g), ([, attachmentPath]) => {
2389
+ let path = attachmentPath;
2390
+ while (path.length > 1 && /[.,;:!?)\]}+"']$/.test(path)) path = path.slice(0, -1);
2391
+ return path;
2392
+ });
2393
+ }
2394
+ /** Resolve a path for containment checking, walking parent directories with
2395
+ * realpath to preserve symlink detection even when the target file is missing. */
2396
+ async function resolveForContainment(attachmentPath) {
2397
+ try {
2398
+ return await realpath(attachmentPath);
2399
+ } catch {
2400
+ let dir = dirname(attachmentPath);
2401
+ const remaining = [basename(attachmentPath)];
2402
+ while (dir !== "/" && dir !== ".") try {
2403
+ return join(await realpath(dir), ...remaining);
2404
+ } catch {
2405
+ remaining.unshift(basename(dir));
2406
+ dir = dirname(dir);
2407
+ }
2408
+ return resolve(attachmentPath);
2409
+ }
2410
+ }
2411
+ async function isManagedConversationAttachmentPath(attachmentPath) {
2412
+ try {
2413
+ const attachmentsRoot = await realpath(getConversationAttachmentsRoot());
2414
+ return (await resolveForContainment(attachmentPath)).startsWith(`${attachmentsRoot}/`);
2415
+ } catch {
2416
+ return false;
2417
+ }
2418
+ }
2419
+ async function isConversationAttachmentPath(name, attachmentPath) {
2420
+ try {
2421
+ const attachmentDir = resolve(getConversationAttachmentDir(name));
2422
+ return (await resolveForContainment(attachmentPath)).startsWith(`${attachmentDir}/`);
2423
+ } catch {
2424
+ return false;
2425
+ }
2426
+ }
2427
+ async function hasConversationAttachment(name, attachmentPath) {
2428
+ try {
2429
+ const attachmentDir = await realpath(getConversationAttachmentDir(name));
2430
+ const candidate = await resolveForContainment(attachmentPath);
2431
+ if (!candidate.startsWith(`${attachmentDir}/`)) return false;
2432
+ return existsSync(candidate);
2433
+ } catch {
2434
+ return false;
2435
+ }
2436
+ }
2437
+ async function removeConversationAttachment(name, attachmentPath) {
2438
+ if (!await isConversationAttachmentPath(name, attachmentPath)) return false;
2439
+ await rm(attachmentPath, { force: true });
2440
+ return true;
2441
+ }
2442
+ //#endregion
2443
+ //#region ../../lib/overdeck/conversation-runtime.ts
2444
+ init_projects();
2445
+ init_tmux();
2446
+ init_agents();
2447
+ init_behavior();
2448
+ init_paths();
2449
+ const execAsync$1 = promisify(exec);
2450
+ const execFileAsync$1 = promisify(execFile);
2451
+ const PROCESS_CLEANUP_GRACE_MS = 750;
2452
+ const PTY_SUPERVISOR_SOCKET_WAIT_MS = 3e4;
2453
+ function parseProcessTable(output) {
2454
+ return output.split("\n").map((line) => {
2455
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
2456
+ if (!match) return null;
2457
+ return {
2458
+ pid: Number(match[1]),
2459
+ ppid: Number(match[2]),
2460
+ args: match[3] ?? ""
2461
+ };
2462
+ }).filter((row) => row !== null && Number.isFinite(row.pid) && Number.isFinite(row.ppid));
2463
+ }
2464
+ async function readProcessTable() {
2465
+ const { stdout } = await execFileAsync$1("ps", ["-eo", "pid=,ppid=,args="], {
2466
+ encoding: "utf-8",
2467
+ maxBuffer: 10 * 1024 * 1024
2468
+ });
2469
+ return parseProcessTable(stdout);
2470
+ }
2471
+ function collectProcessTree(rootPids, rows) {
2472
+ const childrenByParent = /* @__PURE__ */ new Map();
2473
+ for (const row of rows) {
2474
+ const children = childrenByParent.get(row.ppid) ?? [];
2475
+ children.push(row.pid);
2476
+ childrenByParent.set(row.ppid, children);
2477
+ }
2478
+ const seen = /* @__PURE__ */ new Set();
2479
+ const ordered = [];
2480
+ const visit = (pid) => {
2481
+ if (seen.has(pid) || pid === process.pid) return;
2482
+ seen.add(pid);
2483
+ for (const child of childrenByParent.get(pid) ?? []) visit(child);
2484
+ ordered.push(pid);
2485
+ };
2486
+ for (const pid of rootPids) visit(pid);
2487
+ return ordered;
2488
+ }
2489
+ function isProcessAlive(pid) {
2490
+ try {
2491
+ process.kill(pid, 0);
2492
+ return true;
2493
+ } catch {
2494
+ return false;
2495
+ }
2496
+ }
2497
+ async function terminatePids(pids) {
2498
+ if (pids.length === 0) return;
2499
+ for (const pid of pids) try {
2500
+ process.kill(pid, "SIGTERM");
2501
+ } catch {}
2502
+ await new Promise((resolve) => setTimeout(resolve, PROCESS_CLEANUP_GRACE_MS));
2503
+ for (const pid of pids) {
2504
+ if (!isProcessAlive(pid)) continue;
2505
+ try {
2506
+ process.kill(pid, "SIGKILL");
2507
+ } catch {}
2508
+ }
2509
+ }
2510
+ function conversationRuntimeRootPids(conv, rows) {
2511
+ const launcherScript = join(getOverdeckHome(), "conversations", conv.tmuxSession, "launcher.sh");
2512
+ const sessionId = conv.claudeSessionId?.trim();
2513
+ const sessionNeedles = sessionId ? [`--resume ${sessionId}`, `--session-id ${sessionId}`] : [];
2514
+ const serverPid = findManagedServerPidSync();
2515
+ return rows.filter((row) => {
2516
+ if (row.pid === process.pid) return false;
2517
+ if (serverPid !== void 0 && row.pid === serverPid) return false;
2518
+ if (row.args.includes(launcherScript)) return true;
2519
+ return sessionNeedles.some((needle) => row.args.includes(needle));
2520
+ }).map((row) => row.pid);
2521
+ }
2522
+ async function killConversationRuntimeProcesses(conv) {
2523
+ const rows = await readProcessTable();
2524
+ await terminatePids(collectProcessTree(conversationRuntimeRootPids(conv, rows), rows));
2525
+ }
2526
+ async function stopConversationRuntime(conv, name) {
2527
+ if (hasOtherActiveConversationOnTmuxSession(conv.tmuxSession, name)) return;
2528
+ await Effect.runPromise(killSession(conv.tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
2529
+ try {
2530
+ await killConversationRuntimeProcesses(conv);
2531
+ } catch (error) {
2532
+ const msg = error instanceof Error ? error.message : String(error);
2533
+ console.warn(`[conversations] failed to cleanup runtime processes for ${name}: ${msg}`);
2534
+ }
2535
+ }
2536
+ /** Quote a string for safe use in a bash script using single-quote wrapping. */
2537
+ function shellQuote(str) {
2538
+ return "'" + str.replace(/'/g, "'\"'\"'") + "'";
2539
+ }
2540
+ const SAFE_MODEL_PATTERN = /^[a-zA-Z0-9_.:\/-]+$/;
2541
+ const SAFE_EFFORT_PATTERN = /^(low|medium|high)$/;
2542
+ const SAFE_ISSUE_ID_PATTERN = /^[A-Z0-9]+-[0-9]+$/;
2543
+ const PI_CONVERSATION_SOURCE_CONTRACT = [
2544
+ "Pi conversation source contract:",
2545
+ "A message marked source:'extension' was injected by the Overdeck orchestrator or another agent, not typed by the human operator.",
2546
+ "Treat it as coordination guidance; do not attribute it to the human operator."
2547
+ ].join(" ");
2548
+ async function resolveAllowedHarness(requested, model) {
2549
+ if (!model) return "claude-code";
2550
+ return resolveHarness({
2551
+ model,
2552
+ explicit: requested === "ohmypi" || requested === "claude-code" || requested === "codex" ? requested : void 0
2553
+ });
2554
+ }
2555
+ async function isInsideGitWorkTree(dir) {
2556
+ try {
2557
+ const { stdout } = await execAsync$1("git rev-parse --is-inside-work-tree", {
2558
+ cwd: dir,
2559
+ encoding: "utf-8"
2560
+ });
2561
+ return stdout.trim() === "true";
2562
+ } catch {
2563
+ return false;
2564
+ }
2565
+ }
2566
+ async function validateCwdContainment(cwd) {
2567
+ if (!cwd.startsWith("/")) return false;
2568
+ if (cwd.split("/").filter(Boolean).includes("..")) return false;
2569
+ try {
2570
+ const resolved = await realpath(cwd);
2571
+ if (!(await stat$1(resolved)).isDirectory()) return false;
2572
+ const home = homedir();
2573
+ return resolved.startsWith(`${home}/`) || resolved === home;
2574
+ } catch {
2575
+ return false;
2576
+ }
2577
+ }
2578
+ async function waitForClaudeReady(tmuxSession) {
2579
+ const deadline = Date.now() + 3e4;
2580
+ while (Date.now() < deadline) {
2581
+ if ((await Effect.runPromise(capturePane(tmuxSession, 200))).includes("❯")) {
2582
+ console.log(`[conversations] Claude Code ready in ${tmuxSession}`);
2583
+ return;
2584
+ }
2585
+ await new Promise((r) => setTimeout(r, 500));
2586
+ }
2587
+ console.warn(`[conversations] Timed out waiting for Claude Code prompt in ${tmuxSession}`);
2588
+ }
2589
+ function isPiTuiInputReady(snapshot) {
2590
+ return /^\s*[❯›>]\s/m.test(snapshot) || /(?:^|\s)0(?:\.\d+)?%\s+context\s+used\b/i.test(snapshot) || /⬢[^\n]*◕/.test(snapshot);
2591
+ }
2592
+ async function waitForPiTuiReady(tmuxSession, timeoutMs = 6e4) {
2593
+ const deadline = Date.now() + timeoutMs;
2594
+ while (Date.now() < deadline) {
2595
+ if (isPiTuiInputReady(await Effect.runPromise(capturePane(tmuxSession, 40).pipe(Effect.catch(() => Effect.succeed("")))))) {
2596
+ console.log(`[conversations] Pi TUI ready for ${tmuxSession}`);
2597
+ return true;
2598
+ }
2599
+ await new Promise((r) => setTimeout(r, 250));
2600
+ }
2601
+ console.warn(`[conversations] Timed out waiting for Pi TUI to render in ${tmuxSession}`);
2602
+ return false;
2603
+ }
2604
+ function shouldReportUnresolvedLiveSession(conv) {
2605
+ if (!conv || conv.status !== "active") return false;
2606
+ return getHarnessBehavior(conv.harness).transcriptKind === "claude-jsonl";
2607
+ }
2608
+ function conversationSessionAliveFromState(conv, tmuxSessionAlive) {
2609
+ return conv.status === "active" && !conv.forkStatus && tmuxSessionAlive;
2610
+ }
2611
+ function conversationNeedsRunningRepair(conv, tmuxSessionAlive, harnessProcessAlive) {
2612
+ return conv.status === "ended" && !conv.forkStatus && tmuxSessionAlive && harnessProcessAlive;
2613
+ }
2614
+ /** Generate a default conversation name, e.g. 20260404-1234 */
2615
+ function generateConversationName() {
2616
+ return `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "")}-${Math.floor(Math.random() * 9e3 + 1e3)}`;
2617
+ }
2618
+ async function tmuxSessionExists(sessionName) {
2619
+ return Effect.runPromise(sessionExists(sessionName));
2620
+ }
2621
+ async function waitForTmuxSession(sessionName, timeoutMs = 3e4) {
2622
+ const start = Date.now();
2623
+ while (Date.now() - start < timeoutMs) {
2624
+ if (await Effect.runPromise(sessionExists(sessionName))) return;
2625
+ await new Promise((r) => setTimeout(r, 250));
2626
+ }
2627
+ throw new Error(`Timed out waiting for tmux session ${sessionName}`);
2628
+ }
2629
+ function shouldUseSupervisorForConversation(harness) {
2630
+ return getHarnessBehavior(harness).supportsPtySupervisor && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1";
2631
+ }
2632
+ async function waitForConversationRuntimeReady(tmuxSession, harness, mode) {
2633
+ const transcriptKind = getHarnessBehavior(harness).transcriptKind;
2634
+ if (transcriptKind === "ohmypi-jsonl") {
2635
+ 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.`);
2636
+ } else if (transcriptKind !== "codex-rollout-jsonl" && mode === "spawn") {
2637
+ await waitForClaudeReady(tmuxSession);
2638
+ console.log(`[conversations] Claude ready in ${tmuxSession}`);
2639
+ } else if (transcriptKind !== "codex-rollout-jsonl") await waitForReadySignal(tmuxSession, 30);
2640
+ }
2641
+ async function handleConversationSwitchModel(name, body) {
2642
+ const conv = getConversationByName(name);
2643
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
2644
+ const model = typeof body["model"] === "string" && body["model"].trim() ? body["model"].trim() : conv.model ?? void 0;
2645
+ const currentHarness = conv.harness ?? "claude-code";
2646
+ const requestedHarness = body["harness"];
2647
+ let harness = currentHarness;
2648
+ if (requestedHarness === "ohmypi" || requestedHarness === "pi" || requestedHarness === "claude-code" || requestedHarness === "codex") {
2649
+ const requestedRuntime = requestedHarness === "pi" ? "ohmypi" : requestedHarness;
2650
+ if (requestedRuntime !== currentHarness) {
2651
+ const policyModel = model ?? conv.model ?? "";
2652
+ const decision = canUseHarnessSync(requestedRuntime, policyModel, await getProviderAuthMode(policyModel));
2653
+ if (!decision.allowed) return jsonResponse({ error: decision.reason ?? "Harness not allowed for this model" }, { status: 400 });
2654
+ }
2655
+ harness = requestedRuntime;
2656
+ }
2657
+ const harnessChanged = harness !== currentHarness;
2658
+ if (!await validateCwdContainment(conv.cwd)) return jsonResponse({ error: "Invalid cwd" }, { status: 400 });
2659
+ if (model && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
2660
+ const livePiSwitch = isPiControlChannelHarness(currentHarness) && conv.status !== "ended";
2661
+ if (conv.claudeSessionId && harnessChanged) return jsonResponse({ error: "Conversation harness is locked once a conversation has started" }, { status: 409 });
2662
+ if (conv.claudeSessionId && !livePiSwitch) return jsonResponse({ error: "Conversation model is locked once a conversation has started" }, { status: 409 });
2663
+ if (model) {
2664
+ if (livePiSwitch) await sendConversationControlCommand(conv, {
2665
+ type: "set_model",
2666
+ model
2667
+ });
2668
+ setConversationModel(name, model);
2669
+ }
2670
+ if (harnessChanged) setConversationHarness(name, harness);
2671
+ const updated = getConversationByName(name) ?? conv;
2672
+ return jsonResponse({
2673
+ ...updated,
2674
+ model: model ?? updated.model,
2675
+ harness,
2676
+ sessionAlive: false
2677
+ });
2678
+ }
2679
+ function getPtySupervisorSocketPath(agentId) {
2680
+ return join(getOverdeckHome(), "sockets", `pty-${agentId}.sock`);
2681
+ }
2682
+ async function waitForPtySupervisorSocket(agentId, timeoutMs = PTY_SUPERVISOR_SOCKET_WAIT_MS) {
2683
+ const socketPath = getPtySupervisorSocketPath(agentId);
2684
+ const start = Date.now();
2685
+ while (Date.now() - start < timeoutMs) {
2686
+ try {
2687
+ if (((await stat$1(socketPath)).mode & 511) === 384) return;
2688
+ } catch {}
2689
+ await new Promise((r) => setTimeout(r, 250));
2690
+ }
2691
+ throw new Error(`Timed out waiting for PTY supervisor socket ${socketPath}`);
2692
+ }
2693
+ async function extractModelFromSessionFile(sessionFile) {
2694
+ try {
2695
+ if (!existsSync(sessionFile)) return null;
2696
+ const stream = createReadStream(sessionFile, { encoding: "utf-8" });
2697
+ const rl = createInterface({
2698
+ input: stream,
2699
+ crlfDelay: Infinity
2700
+ });
2701
+ try {
2702
+ for await (const line of rl) {
2703
+ if (!line.trim()) continue;
2704
+ const entry = JSON.parse(line);
2705
+ if (entry.type === "assistant" && entry.message?.model) return entry.message.model;
2706
+ }
2707
+ } finally {
2708
+ rl.close();
2709
+ stream.destroy();
2710
+ }
2711
+ } catch {}
2712
+ return null;
2713
+ }
2714
+ let backfillRunning = false;
2715
+ async function backfillConversationModels(resolveSessionFile) {
2716
+ if (backfillRunning) return;
2717
+ backfillRunning = true;
2718
+ try {
2719
+ const convs = listConversations();
2720
+ const probe = await Promise.all(convs.map(async (conv) => !conv.model ? await resolveSessionFile(conv) : null));
2721
+ const candidates = convs.filter((_, i) => probe[i] !== null);
2722
+ const BATCH_SIZE = 10;
2723
+ let backfilled = 0;
2724
+ for (let i = 0; i < candidates.length; i += BATCH_SIZE) {
2725
+ const batch = candidates.slice(i, i + BATCH_SIZE);
2726
+ const results = await Promise.all(batch.map(async (conv) => {
2727
+ const sessionFile = await resolveSessionFile(conv);
2728
+ if (!sessionFile) return false;
2729
+ const model = await extractModelFromSessionFile(sessionFile);
2730
+ if (model && SAFE_MODEL_PATTERN.test(model)) {
2731
+ backfillConversationModel(conv.name, model);
2732
+ return true;
2733
+ }
2734
+ return false;
2735
+ }));
2736
+ backfilled += results.filter(Boolean).length;
2737
+ }
2738
+ if (backfilled > 0) console.log(`[conversations] Backfilled model for ${backfilled} conversation(s)`);
2739
+ } finally {
2740
+ backfillRunning = false;
2741
+ }
2742
+ }
2743
+ function startConversationModelBackfill(resolveSessionFile) {
2744
+ backfillConversationModels(resolveSessionFile).catch((err) => {
2745
+ console.error("[conversations] Model backfill failed:", err);
2746
+ });
2747
+ }
2748
+ async function claudeConversationSystemPromptFiles(cwd) {
2749
+ const files = [];
2750
+ const contextFile = workspaceContextFile(cwd);
2751
+ try {
2752
+ await stat$1(contextFile);
2753
+ files.push(contextFile);
2754
+ } catch (error) {
2755
+ if (!isNotFound(error)) throw error;
2756
+ }
2757
+ files.push(await ensureSessionContextBriefingFile());
2758
+ return files;
2759
+ }
2760
+ async function ensurePiConversationSourceContractFile() {
2761
+ const contextDir = join(getOverdeckHome(), "context");
2762
+ await mkdir(contextDir, { recursive: true });
2763
+ const path = join(contextDir, "pi-conversation-source-contract.md");
2764
+ await writeFile(path, `${PI_CONVERSATION_SOURCE_CONTRACT}\n`, "utf-8");
2765
+ return path;
2766
+ }
2767
+ async function piConversationSystemPromptFiles(cwd) {
2768
+ const files = [];
2769
+ const globalFile = piGlobalContextFile();
2770
+ try {
2771
+ await stat$1(globalFile);
2772
+ files.push(globalFile);
2773
+ } catch (error) {
2774
+ if (!isNotFound(error)) throw error;
2775
+ }
2776
+ files.push(await ensurePiConversationSourceContractFile());
2777
+ const contextFile = workspaceContextFile(cwd);
2778
+ try {
2779
+ await stat$1(contextFile);
2780
+ files.push(contextFile);
2781
+ } catch (error) {
2782
+ if (!isNotFound(error)) throw error;
2783
+ }
2784
+ files.push(await ensureSessionContextBriefingFile());
2785
+ return files;
2786
+ }
2787
+ function isNotFound(error) {
2788
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2789
+ }
2790
+ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, resume = false, harness = "claude-code", plainFork = false) {
2791
+ const behavior = getHarnessBehavior(harness);
2792
+ const stateDir = join(getOverdeckHome(), "conversations", tmuxSession);
2793
+ await mkdir(stateDir, { recursive: true });
2794
+ clearReadySignal(tmuxSession);
2795
+ const launcherScript = join(stateDir, "launcher.sh");
2796
+ let runtimeCommand = `claude ${getClaudePermissionFlagsStringSync()}`;
2797
+ let providerExportsStr = "";
2798
+ let piFields;
2799
+ let codexFields;
2800
+ if (model) {
2801
+ if (!SAFE_MODEL_PATTERN.test(model)) throw new Error("Invalid model name");
2802
+ runtimeCommand = await getAgentRuntimeBaseCommand(model, void 0, void 0, harness);
2803
+ const mode = resolvePermissionModeSync();
2804
+ if (!runtimeCommand.includes("--permission-mode")) runtimeCommand = `${runtimeCommand} --permission-mode ${mode === "auto" ? "auto" : BYPASS_PERMISSION_MODE}`;
2805
+ providerExportsStr = (await getProviderExportsForModel(model)).trim();
2806
+ await getProviderEnvForModel(model);
2807
+ if (behavior.transcriptKind === "ohmypi-jsonl") {
2808
+ if (getProviderForModelSync(model).name === "openai") {
2809
+ const auth = await getOhmypiCodexAuthStatus({ refreshIfExpired: true });
2810
+ 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.");
2811
+ }
2812
+ const paths = piFifoPaths(tmuxSession);
2813
+ const piSessionDir = join(paths.agentDir, "sessions");
2814
+ await mkdir(paths.agentDir, {
2815
+ recursive: true,
2816
+ mode: 448
2817
+ });
2818
+ await mkdir(piSessionDir, {
2819
+ recursive: true,
2820
+ mode: 448
2821
+ });
2822
+ const storedPiSessionId = resume ? await readFile(join(paths.agentDir, "session.id"), "utf-8").then((s) => s.trim()).catch(() => void 0) : void 0;
2823
+ piFields = {
2824
+ harness: "ohmypi",
2825
+ piMode: "tui",
2826
+ piExtensionPath: resolveOhmypiExtensionPath() ?? resolve(process.cwd(), "packages/ohmypi-extension/dist/index.js"),
2827
+ piSessionDir,
2828
+ resumeSessionId: storedPiSessionId || void 0
2829
+ };
2830
+ } else if (behavior.usesCodexHome) {
2831
+ const codexHome = join(getOverdeckHome(), "agents", tmuxSession, "codex-home");
2832
+ const codexPermMode = loadConfigSync().config.codex?.permissionMode ?? "workspace";
2833
+ const codexApprovalPolicy = codexPermMode === "full-access" ? "never" : "on-request";
2834
+ const codexSandboxMode = codexPermMode === "full-access" ? "danger-full-access" : codexPermMode === "read-only" ? "read-only" : "workspace-write";
2835
+ const codexApprovalsReviewer = codexPermMode === "auto-review" ? "auto_review" : void 0;
2836
+ const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-BYPz6ZdF.js");
2837
+ initCodexHome(codexHome, {
2838
+ trustedDir: cwd,
2839
+ approvalPolicy: codexApprovalPolicy,
2840
+ sandboxMode: codexSandboxMode,
2841
+ approvalsReviewer: codexApprovalsReviewer
2842
+ });
2843
+ const resumeSessionId = resume ? await resolveCodexRolloutPath(tmuxSession, { agentsDirOverride: join(getOverdeckHome(), "agents") }).then((rollout) => rollout ? extractThreadIdFromRollout(rollout) ?? void 0 : void 0) : void 0;
2844
+ codexFields = {
2845
+ harness: "codex",
2846
+ codexMode: "tui",
2847
+ codexHome,
2848
+ codexSessionDir: join(codexHome, "sessions"),
2849
+ resumeSessionId
2850
+ };
2851
+ }
2852
+ }
2853
+ let launcherModel = model;
2854
+ if (behavior.contextLayerKind === "pi" && model) {
2855
+ const piProvider = piProviderForModel(model);
2856
+ if (piProvider) launcherModel = `${piProvider}/${model}`;
2857
+ }
2858
+ if (effort && !SAFE_EFFORT_PATTERN.test(effort)) throw new Error("Invalid effort level");
2859
+ const useSupervisor = shouldUseSupervisorForConversation(harness);
2860
+ let supervisorScriptPath;
2861
+ if (useSupervisor) {
2862
+ supervisorScriptPath = resolvePtySupervisorScriptPath();
2863
+ await writePtyToken(tmuxSession);
2864
+ }
2865
+ let channelsBridgeMcpConfig;
2866
+ 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") {
2867
+ channelsBridgeMcpConfig = join(stateDir, "agent-mcp.json");
2868
+ writeBridgeTokenSync(tmuxSession);
2869
+ await writeChannelsBridgeMcpConfig(channelsBridgeMcpConfig, tmuxSession);
2870
+ }
2871
+ const launcherTmp = `${launcherScript}.${randomUUID$1()}.tmp`;
2872
+ await writeFile(launcherTmp, generateLauncherScriptSync({
2873
+ role: "work",
2874
+ spawnMode: "conversation",
2875
+ workingDir: cwd,
2876
+ setTerminalEnv: true,
2877
+ unsetProviderEnv: true,
2878
+ overdeckEnv: {
2879
+ ...issueId ? { issueId } : {},
2880
+ ...piFields || codexFields || useSupervisor ? { agentId: tmuxSession } : {}
2881
+ },
2882
+ extraEnvExports: [`export OVERDECK_DASHBOARD_URL="http://127.0.0.1:${process.env["API_PORT"] ?? process.env["PORT"] ?? "3011"}"`],
2883
+ providerExports: providerExportsStr || void 0,
2884
+ trapHup: true,
2885
+ baseCommand: runtimeCommand,
2886
+ appendSystemPromptFiles: piFields ? await piConversationSystemPromptFiles(cwd) : codexFields ? [] : await claudeConversationSystemPromptFiles(cwd),
2887
+ model: launcherModel,
2888
+ ...piFields ?? codexFields ?? {
2889
+ resumeSessionId: resume ? claudeSessionId : void 0,
2890
+ sessionId: resume ? void 0 : claudeSessionId
2891
+ },
2892
+ extraArgs: !piFields && effort ? `--effort "${effort}"` : void 0,
2893
+ keepAlive: true,
2894
+ fileMode: 448,
2895
+ channelsBridgeMcpConfig,
2896
+ useSupervisor,
2897
+ supervisorScriptPath
2898
+ }), { mode: 448 });
2899
+ await rename(launcherTmp, launcherScript);
2900
+ try {
2901
+ await Effect.runPromise(killSession(tmuxSession));
2902
+ } catch {}
2903
+ console.log(`[claude-invoke] purpose=conversation-session | model=${model || "default"} | source=conversations.ts:spawnConversationSession | session=${tmuxSession} | resume=${resume} | command="${runtimeCommand}"`);
2904
+ try {
2905
+ const { preTrustDirectory } = await import("./workspace-manager-BO-6k3sP.js");
2906
+ preTrustDirectory(cwd);
2907
+ } catch {}
2908
+ try {
2909
+ await Effect.runPromise(createSession(tmuxSession, cwd, `bash ${shellQuote(launcherScript)}`, { env: {
2910
+ ...BLANKED_PROVIDER_ENV,
2911
+ TERM: "xterm-256color"
2912
+ } }));
2913
+ } catch (err) {
2914
+ 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)");
2915
+ throw err;
2916
+ }
2917
+ if (useSupervisor) await waitForPtySupervisorSocket(tmuxSession);
2918
+ if (behavior.usesCodexHome && codexFields?.codexHome) {
2919
+ const codexHomeDir = codexFields.codexHome;
2920
+ (async () => {
2921
+ try {
2922
+ const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-BYPz6ZdF.js");
2923
+ const rollout = await waitForCodexRollout(codexHomeDir, 12e4);
2924
+ if (rollout) {
2925
+ const threadId = extractThreadIdFromRollout(rollout);
2926
+ if (threadId) writeThreadId(tmuxSession, threadId);
2927
+ }
2928
+ } catch {}
2929
+ })();
2930
+ }
2931
+ if (channelsBridgeMcpConfig) dismissDevChannelsDialog(tmuxSession).catch((err) => {
2932
+ const msg = err instanceof Error ? err.message : String(err);
2933
+ console.error(`[conversations] dismissDevChannelsDialog failed for ${tmuxSession}: ${msg}`);
2934
+ });
2935
+ await Effect.runPromise(setOption(tmuxSession, "destroy-unattached", "off"));
2936
+ await Effect.runPromise(setOption(exactPaneTarget(tmuxSession), "remain-on-exit", "on"));
2937
+ }
2938
+ /**
2939
+ * Resolve a conversation's cwd from a project identifier.
2940
+ *
2941
+ * The Command Deck identifies projects by display name, not yaml key
2942
+ * (PAN-2590) — accept either, like GET /api/session-trees does.
2943
+ */
2944
+ function resolveProjectCwd(projectKey) {
2945
+ const projectConfig = getProjectSync(projectKey) ?? listProjectsSync().find((p) => p.config.name === projectKey)?.config ?? null;
2946
+ if (!projectConfig) return { error: `Unknown project: ${projectKey}` };
2947
+ if (!projectConfig.path || !existsSync(projectConfig.path)) return { error: `Project path does not exist: ${projectConfig.path || "(unset)"} (project: ${projectKey})` };
2948
+ return { cwd: projectConfig.path };
2949
+ }
2950
+ async function handleConversationCreate(body, deps) {
2951
+ try {
2952
+ const message = typeof body["message"] === "string" ? body["message"].trim() : "";
2953
+ const model = typeof body["model"] === "string" ? body["model"].trim() : void 0;
2954
+ const effort = typeof body["effort"] === "string" ? body["effort"].trim() : void 0;
2955
+ const harness = await resolveAllowedHarness(body["harness"], model);
2956
+ const issueId = typeof body["issueId"] === "string" ? body["issueId"] : void 0;
2957
+ const projectKey = typeof body["projectKey"] === "string" ? body["projectKey"].trim() : void 0;
2958
+ if (issueId && !SAFE_ISSUE_ID_PATTERN.test(issueId)) return jsonResponse({ error: "Invalid issueId" }, { status: 400 });
2959
+ if (model && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
2960
+ if (effort && !SAFE_EFFORT_PATTERN.test(effort)) return jsonResponse({ error: "Invalid effort" }, { status: 400 });
2961
+ let cwd = getDefaultCwd();
2962
+ if (projectKey) {
2963
+ const resolved = resolveProjectCwd(projectKey);
2964
+ if ("error" in resolved) return jsonResponse({ error: resolved.error }, { status: 400 });
2965
+ cwd = resolved.cwd;
2966
+ }
2967
+ if (message && message.length > 5e4) return jsonResponse({ error: "message exceeds maximum length of 50000 characters" }, { status: 400 });
2968
+ let name = generateConversationName();
2969
+ for (let i = 0; i < 5 && getConversationByName(name); i++) name = generateConversationName();
2970
+ const tmuxSession = `conv-${name}`;
2971
+ const claudeSessionId = randomUUID$1();
2972
+ console.log(`[conversations] Creating conversation "${name}" with model=${model ?? "default"} effort=${effort ?? "default"} cwd=${cwd}`);
2973
+ const MAX_TITLE_LEN = 60;
2974
+ const title = message ? message.slice(0, MAX_TITLE_LEN) + (message.length > MAX_TITLE_LEN ? "…" : "") : "New conversation";
2975
+ const conv = createConversation({
2976
+ name,
2977
+ tmuxSession,
2978
+ cwd,
2979
+ issueId,
2980
+ claudeSessionId,
2981
+ title,
2982
+ titleSource: message ? "auto" : "default",
2983
+ titleSeed: title,
2984
+ model,
2985
+ effort,
2986
+ harness
2987
+ });
2988
+ getEventStore().emitOnly({
2989
+ type: "conversation.created",
2990
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2991
+ payload: { conversationName: name }
2992
+ });
2993
+ (async () => {
2994
+ try {
2995
+ await spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, false, harness);
2996
+ console.log(`[conversations] tmux session ${tmuxSession} spawned, sessionId: ${claudeSessionId}`);
2997
+ await waitForConversationRuntimeReady(tmuxSession, harness, "spawn");
2998
+ if (message) await deliverAgentMessage(tmuxSession, message, "conversation-message", resolveConversationDeliveryMethod(conv));
2999
+ } catch (spawnErr) {
3000
+ const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
3001
+ console.error(`[conversations] background spawn failed for ${tmuxSession}: ${msg}`);
3002
+ updateSpawnError(name, msg);
3003
+ getEventStore().emitOnly({
3004
+ type: "conversation.created",
3005
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3006
+ payload: { conversationName: name }
3007
+ });
3008
+ }
3009
+ })();
3010
+ if (message) deps.generateAiTitle(name, message).catch((err) => {
3011
+ const msg = err instanceof Error ? err.message : String(err);
3012
+ console.error(`[TITLE-GEN-FAILED] AI title generation FAILED for "${name}" — NO RETRY, NO FALLBACK:`, msg);
3013
+ });
3014
+ return jsonResponse({
3015
+ ...conv,
3016
+ sessionAlive: false
3017
+ }, { status: 201 });
3018
+ } catch (error) {
3019
+ const msg = error instanceof Error ? error.message : String(error);
3020
+ console.error("[conversations] create conversation failed:", msg);
3021
+ return jsonResponse({ error: msg || "Internal server error" }, { status: error instanceof UnknownModelError ? 400 : 500 });
3022
+ }
3023
+ }
3024
+ async function handleConversationStop(name, deps) {
3025
+ try {
3026
+ const conv = getConversationByName(name);
3027
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3028
+ await stopConversationRuntime(conv, name);
3029
+ markConversationEnded(name);
3030
+ (async () => {
3031
+ await new Promise((r) => setTimeout(r, 500));
3032
+ await cleanupUnreferencedConversationAttachments({
3033
+ name: conv.name,
3034
+ sessionFile: deps.resolveSessionFileForCleanup?.(conv) ?? null
3035
+ });
3036
+ })();
3037
+ return jsonResponse({ success: true });
3038
+ } catch (error) {
3039
+ const msg = error instanceof Error ? error.message : String(error);
3040
+ console.error("[conversations] stop conversation failed:", msg);
3041
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
3042
+ }
3043
+ }
3044
+ async function handleConversationResume(name, body, deps) {
3045
+ try {
3046
+ const conv = getConversationByName(name);
3047
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3048
+ const model = typeof body["model"] === "string" && body["model"].trim() ? body["model"].trim() : conv.model ?? void 0;
3049
+ const effort = typeof body["effort"] === "string" && body["effort"].trim() ? body["effort"].trim() : conv.effort ?? void 0;
3050
+ if (await isHarnessProcessAlive(conv.tmuxSession)) {
3051
+ updateLastAttached(name);
3052
+ markConversationActive(name);
3053
+ return jsonResponse({
3054
+ ...conv,
3055
+ status: "active",
3056
+ reattached: true
3057
+ });
3058
+ }
3059
+ const oldSessionId = conv.claudeSessionId;
3060
+ const harness = conv.harness ?? "claude-code";
3061
+ const modelChanged = !!model && model !== conv.model;
3062
+ if (!await validateCwdContainment(conv.cwd)) return jsonResponse({ error: "Invalid cwd" }, { status: 400 });
3063
+ if (model && modelChanged && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
3064
+ if (model && modelChanged) setConversationModel(name, model);
3065
+ let canResume = !!oldSessionId;
3066
+ if (oldSessionId) {
3067
+ const resumeFile = await deps.resolveSessionFile(conv);
3068
+ if (!resumeFile || !existsSync(resumeFile)) {
3069
+ canResume = false;
3070
+ console.error(`[conversations] SESSION-LOST ${name} harness=${harness} claudeSessionId=${oldSessionId} resolved=${resumeFile ?? "null"} — resuming with a fresh session`);
3071
+ }
3072
+ }
3073
+ const respawn = markRespawnPending(conv.tmuxSession);
3074
+ try {
3075
+ await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), model, effort, conv.issueId ?? void 0, canResume, harness);
3076
+ await waitForTmuxSession(conv.tmuxSession);
3077
+ await waitForConversationRuntimeReady(conv.tmuxSession, harness, "respawn");
3078
+ markConversationActive(name);
3079
+ return jsonResponse({
3080
+ ...conv,
3081
+ status: "active",
3082
+ model: model ?? conv.model,
3083
+ harness,
3084
+ reattached: false,
3085
+ sessionAlive: true
3086
+ });
3087
+ } finally {
3088
+ respawn.done();
3089
+ }
3090
+ } catch (error) {
3091
+ const msg = error instanceof Error ? error.message : String(error);
3092
+ console.error("[conversations] resume conversation failed:", msg);
3093
+ return jsonResponse({ error: msg || "Internal server error" }, { status: 500 });
3094
+ }
3095
+ }
3096
+ async function handleConversationDelete(name, deps) {
3097
+ try {
3098
+ const conv = getConversationByName(name);
3099
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3100
+ await stopConversationRuntime(conv, name);
3101
+ markConversationEnded(name);
3102
+ archiveConversation(name);
3103
+ removeFavorite("conversation", name);
3104
+ deps.invalidateFavoritesCache();
3105
+ await cleanupConversationAttachments(name);
3106
+ return jsonResponse({ success: true });
3107
+ } catch (error) {
3108
+ const msg = error instanceof Error ? error.message : String(error);
3109
+ console.error("[conversations] delete conversation failed:", msg);
3110
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
3111
+ }
3112
+ }
3113
+ async function handleConversationRestartAll(deps) {
3114
+ try {
3115
+ const allConvs = listConversations();
3116
+ const liveSessionNames = new Set(await Effect.runPromise(listSessionNames()));
3117
+ const convs = allConvs.filter((c) => liveSessionNames.has(c.tmuxSession));
3118
+ const results = [];
3119
+ for (const conv of convs) {
3120
+ const respawn = markRespawnPending(conv.tmuxSession);
3121
+ try {
3122
+ await Effect.runPromise(killSession(conv.tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
3123
+ const oldSessionId = conv.claudeSessionId;
3124
+ const sessionFileForResume = await deps.resolveSessionFile(conv);
3125
+ const canResume = !!oldSessionId && !!sessionFileForResume && existsSync(sessionFileForResume);
3126
+ const harness = await resolveAllowedHarness(conv.harness, conv.model);
3127
+ await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), conv.model ?? void 0, conv.effort ?? void 0, conv.issueId ?? void 0, canResume, harness);
3128
+ setConversationHarness(conv.name, harness);
3129
+ markConversationActive(conv.name);
3130
+ results.push({
3131
+ name: conv.name,
3132
+ model: conv.model,
3133
+ status: "restarted"
3134
+ });
3135
+ } catch (err) {
3136
+ const msg = err instanceof Error ? err.message : String(err);
3137
+ console.error(`[conversations] Failed to restart ${conv.name}:`, msg);
3138
+ results.push({
3139
+ name: conv.name,
3140
+ model: conv.model,
3141
+ status: "failed"
3142
+ });
3143
+ } finally {
3144
+ respawn.done();
3145
+ }
3146
+ }
3147
+ console.log(`[conversations] Restarted ${results.filter((r) => r.status === "restarted").length}/${convs.length} conversations`);
3148
+ return jsonResponse({
3149
+ restarted: results.length,
3150
+ results
3151
+ });
3152
+ } catch (error) {
3153
+ const msg = error instanceof Error ? error.message : String(error);
3154
+ console.error("[conversations] restart conversations failed:", msg);
3155
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
3156
+ }
3157
+ }
3158
+ //#endregion
3159
+ //#region ../../lib/overdeck/conversation-delivery.ts
3160
+ init_conversations();
3161
+ init_behavior();
3162
+ init_tmux();
3163
+ init_agents();
3164
+ const CONTROL_ACK_TIMEOUT_MS = 1e4;
3165
+ const pendingConversationControlAcks = /* @__PURE__ */ new Map();
3166
+ const PLAN_ACTION_KEYSTROKES = {
3167
+ "approve-auto": "1",
3168
+ "approve-manual": "2",
3169
+ "reject-ultraplan": "3"
3170
+ };
3171
+ /**
3172
+ * PAN-1520 (FR-4) — deliver a native plan-menu action to any tmux session
3173
+ * (conversation or agent). The plan menu is answered with raw keystrokes
3174
+ * (1/2/3/4), not a text message; `reject-feedback` follows the '4' keystroke
3175
+ * with the operator's feedback through the normal message pipeline.
3176
+ * Returns null on success, or an error string for an invalid action.
3177
+ */
3178
+ async function deliverPlanActionToSession(sessionName, action, feedback, deliveryMethod = "auto") {
3179
+ if (action === "reject-feedback") {
3180
+ await Effect.runPromise(sendRawKeystroke(sessionName, "4", "plan-action-reject"));
3181
+ if (feedback) {
3182
+ await new Promise((r) => setTimeout(r, 300));
3183
+ await deliverAgentMessage(sessionName, feedback, "plan-action-feedback", deliveryMethod);
3184
+ }
3185
+ return null;
3186
+ }
3187
+ const keystroke = PLAN_ACTION_KEYSTROKES[action];
3188
+ if (!keystroke) return `Invalid action: ${action}`;
3189
+ await Effect.runPromise(sendRawKeystroke(sessionName, keystroke, `plan-action-${action}`));
3190
+ return null;
3191
+ }
3192
+ function registerConversationControlAck(commandId, timeoutMs = CONTROL_ACK_TIMEOUT_MS) {
3193
+ const existing = pendingConversationControlAcks.get(commandId);
3194
+ if (existing) {
3195
+ clearTimeout(existing.timer);
3196
+ existing.reject(/* @__PURE__ */ new Error(`Replaced pending conversation control ack ${commandId}`));
3197
+ pendingConversationControlAcks.delete(commandId);
3198
+ }
3199
+ return new Promise((resolve, reject) => {
3200
+ const timer = setTimeout(() => {
3201
+ pendingConversationControlAcks.delete(commandId);
3202
+ reject(/* @__PURE__ */ new Error(`Timed out waiting for conversation control ack ${commandId}`));
3203
+ }, timeoutMs);
3204
+ pendingConversationControlAcks.set(commandId, {
3205
+ resolve: () => {
3206
+ clearTimeout(timer);
3207
+ pendingConversationControlAcks.delete(commandId);
3208
+ resolve();
3209
+ },
3210
+ reject: (error) => {
3211
+ clearTimeout(timer);
3212
+ pendingConversationControlAcks.delete(commandId);
3213
+ reject(error);
3214
+ },
3215
+ timer
3216
+ });
3217
+ });
3218
+ }
3219
+ function resolveConversationControlAck(ack) {
3220
+ const pending = pendingConversationControlAcks.get(ack.id);
3221
+ if (!pending) return "unknown";
3222
+ if (ack.ok) {
3223
+ pending.resolve();
3224
+ return "resolved";
3225
+ }
3226
+ pending.reject(new Error(ack.error || `Conversation control command ${ack.id} failed`));
3227
+ return "rejected";
3228
+ }
3229
+ function handleConversationControlAck(body) {
3230
+ const id = typeof body["id"] === "string" ? body["id"].trim() : "";
3231
+ if (!id) return {
3232
+ status: 400,
3233
+ body: { error: "id is required" }
3234
+ };
3235
+ const ok = body["ok"] === true;
3236
+ const error = typeof body["error"] === "string" ? body["error"] : void 0;
3237
+ return {
3238
+ status: 200,
3239
+ body: {
3240
+ ok: true,
3241
+ outcome: resolveConversationControlAck({
3242
+ id,
3243
+ ok,
3244
+ ...error !== void 0 ? { error } : {}
3245
+ })
3246
+ }
3247
+ };
3248
+ }
3249
+ function validateConversationControlAckOrigin(headers, method = "POST") {
3250
+ const origin = getHeaderFromMap(headers, "origin");
3251
+ const referer = getHeaderFromMap(headers, "referer");
3252
+ if (!origin && !referer) return { ok: true };
3253
+ return validateOriginHeaders(headers, method);
3254
+ }
3255
+ async function handleConversationCodexApproval(rawId, body) {
3256
+ try {
3257
+ const optionNumber = Number(body.optionNumber);
3258
+ if (!Number.isInteger(optionNumber) || optionNumber < 1 || optionNumber > 9) return jsonResponse({ error: "optionNumber must be an integer 1-9" }, { status: 400 });
3259
+ const numericId = Number(rawId);
3260
+ const conv = !Number.isNaN(numericId) && /^\d+$/.test(rawId) ? getConversationById(numericId) : getConversationByName(rawId);
3261
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3262
+ if (getHarnessBehavior(conv.harness).transcriptKind !== "codex-rollout-jsonl") return jsonResponse({ error: "Not a Codex conversation" }, { status: 400 });
3263
+ if (!await tmuxSessionExists(conv.tmuxSession)) return jsonResponse({ error: "Conversation session is not running" }, { status: 409 });
3264
+ const detection = await Effect.runPromise(detectAwaitingInputForAgent(conv.tmuxSession, {
3265
+ isPlanning: false,
3266
+ cache: false
3267
+ }));
3268
+ const parsed = detection ? parseCodexApprovalPrompt(detection.prompt) : null;
3269
+ if (!parsed) return jsonResponse({ error: "No Codex approval prompt is currently pending" }, { status: 409 });
3270
+ if (optionNumber > parsed.options.length) return jsonResponse({ error: `optionNumber out of range (1-${parsed.options.length})` }, { status: 400 });
3271
+ await deliverCodexApprovalChoice(conv.tmuxSession, optionNumber);
3272
+ return jsonResponse({
3273
+ ok: true,
3274
+ optionNumber
3275
+ });
3276
+ } catch (error) {
3277
+ const msg = error instanceof Error ? error.message : String(error);
3278
+ console.error("[conversations] codex approval failed:", msg);
3279
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
3280
+ }
3281
+ }
3282
+ async function handleConversationPlanAction(name, body) {
3283
+ try {
3284
+ const conv = getConversationByName(name);
3285
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3286
+ const action = typeof body["action"] === "string" ? body["action"] : "";
3287
+ const feedback = typeof body["feedback"] === "string" ? body["feedback"].trim() : "";
3288
+ const error = await deliverPlanActionToSession(conv.tmuxSession, action, feedback, resolveConversationDeliveryMethod(conv));
3289
+ if (error) return jsonResponse({ error }, { status: 400 });
3290
+ return jsonResponse({ ok: true });
3291
+ } catch (error) {
3292
+ const msg = error instanceof Error ? error.message : String(error);
3293
+ console.error("[conversations] plan action failed:", msg);
3294
+ return jsonResponse({ error: "Internal server error" }, { status: 500 });
3295
+ }
3296
+ }
3297
+ const THINKING_LEVELS = [
3298
+ "off",
3299
+ "minimal",
3300
+ "low",
3301
+ "medium",
3302
+ "high",
3303
+ "xhigh"
3304
+ ];
3305
+ const PI_CONVERSATION_ABORT_KEY = "Escape";
3306
+ function isPiControlChannelHarness(harness) {
3307
+ return harness === "ohmypi" || harness === "pi";
3308
+ }
3309
+ function parseThinkingLevel(value) {
3310
+ return typeof value === "string" && THINKING_LEVELS.includes(value) ? value : null;
3311
+ }
3312
+ function pickDeliverAs(bodyDeliverAs) {
3313
+ if (bodyDeliverAs === "follow_up") return "follow_up";
3314
+ return "steer";
3315
+ }
3316
+ function resolveConversationDeliveryMethod(conv) {
3317
+ const harness = conv.harness ?? "claude-code";
3318
+ if (isPiControlChannelHarness(harness)) return "auto";
3319
+ return conv.deliveryMethod ?? (getHarnessBehavior(harness).deliveryKind === "rpc-fifo" ? "tmux" : "auto");
3320
+ }
3321
+ async function sendConversationControlCommand(conv, commandInput) {
3322
+ const id = randomUUID$1();
3323
+ const ackPromise = registerConversationControlAck(id);
3324
+ const command = {
3325
+ id,
3326
+ ...commandInput
3327
+ };
3328
+ try {
3329
+ await writeConversationControlCommand(conv.tmuxSession, command);
3330
+ } catch (err) {
3331
+ resolveConversationControlAck({
3332
+ id,
3333
+ ok: false,
3334
+ error: err instanceof Error ? err.message : String(err)
3335
+ });
3336
+ }
3337
+ await ackPromise;
3338
+ }
3339
+ async function deliverConversationViaControlChannel(conv, message, options) {
3340
+ await sendConversationControlCommand(conv, {
3341
+ type: options.deliverAs,
3342
+ message,
3343
+ source: options.source
3344
+ });
3345
+ }
3346
+ async function handleConversationThinkingLevel(name, body) {
3347
+ const conv = getConversationByName(name);
3348
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3349
+ if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Thinking level control is only supported for Pi conversations" }, { status: 400 });
3350
+ if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
3351
+ const level = parseThinkingLevel(body["level"]);
3352
+ if (!level) return jsonResponse({ error: "Invalid thinking level" }, { status: 400 });
3353
+ await sendConversationControlCommand(conv, {
3354
+ type: "set_thinking_level",
3355
+ level
3356
+ });
3357
+ setConversationEffort(name, level);
3358
+ return jsonResponse({
3359
+ ok: true,
3360
+ effort: (getConversationByName(name) ?? conv).effort ?? level
3361
+ });
3362
+ }
3363
+ async function handleConversationCompact(name) {
3364
+ const conv = getConversationByName(name);
3365
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3366
+ if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Compact control endpoint is only supported for Pi conversations" }, { status: 400 });
3367
+ if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
3368
+ await sendConversationControlCommand(conv, { type: "compact" });
3369
+ return jsonResponse({ ok: true });
3370
+ }
3371
+ async function handleConversationAbort(name) {
3372
+ const conv = getConversationByName(name);
3373
+ if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3374
+ if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Abort control endpoint is only supported for Pi conversations" }, { status: 400 });
3375
+ if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
3376
+ await sendKeysAsync(conv.tmuxSession, PI_CONVERSATION_ABORT_KEY, "conversation-abort");
3377
+ return jsonResponse({
3378
+ ok: true,
3379
+ key: PI_CONVERSATION_ABORT_KEY
3380
+ });
3381
+ }
3382
+ async function handleConversationDeliveryMethod(name, body) {
3383
+ if (!getConversationByName(name)) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
3384
+ const deliveryMethod = body["deliveryMethod"] ?? body["method"];
3385
+ if (deliveryMethod !== "auto" && deliveryMethod !== "channels" && deliveryMethod !== "tmux" && deliveryMethod !== null) return jsonResponse({ error: "deliveryMethod must be 'auto', 'channels', 'tmux', or null" }, { status: 400 });
3386
+ updateConversationDeliveryMethod(name, deliveryMethod);
3387
+ return jsonResponse({
3388
+ ok: true,
3389
+ deliveryMethod
3390
+ });
3391
+ }
3392
+ const CODEX_APPROVAL_TOOL_PREFIX = "codex-approval:";
3393
+ async function codexConversationPendingInput(conv, sessionAlive, askedAt) {
3394
+ if (!sessionAlive || getHarnessBehavior(conv.harness).transcriptKind !== "codex-rollout-jsonl") return { kinds: [] };
3395
+ try {
3396
+ const detection = await Effect.runPromise(detectAwaitingInputForAgent(conv.tmuxSession, { isPlanning: false }));
3397
+ if (!detection) return { kinds: [] };
3398
+ if (detection.reason === "session_resume") return { kinds: ["sessionResume"] };
3399
+ const parsed = parseCodexApprovalPrompt(detection.prompt);
3400
+ if (parsed) return {
3401
+ kinds: ["permissionRequest"],
3402
+ approval: {
3403
+ toolUseId: `${CODEX_APPROVAL_TOOL_PREFIX}${conv.tmuxSession}`,
3404
+ askedAt,
3405
+ questions: [{
3406
+ question: parsed.detail ? `${parsed.header}\n\n${parsed.detail}` : parsed.header,
3407
+ header: "Codex approval",
3408
+ multiSelect: false,
3409
+ options: parsed.options.map((o) => ({ label: `${o.number}. ${o.label}` }))
3410
+ }]
3411
+ }
3412
+ };
3413
+ return { kinds: ["permissionRequest"] };
3414
+ } catch {
3415
+ return { kinds: [] };
3416
+ }
3417
+ }
3418
+ async function deliverCodexApprovalChoice(tmuxSession, optionNumber) {
3419
+ for (let i = 1; i < optionNumber; i += 1) {
3420
+ await Effect.runPromise(sendRawKeystroke(tmuxSession, "Down", "codex-approval"));
3421
+ await new Promise((r) => setTimeout(r, 60));
3422
+ }
3423
+ await Effect.runPromise(sendRawKeystroke(tmuxSession, "Enter", "codex-approval"));
3424
+ }
3425
+ //#endregion
1911
3426
  //#region ../../lib/channels/permission-payload.ts
1912
3427
  const CHANNEL_PERMISSION_LIMITS = {
1913
3428
  requestIdBytes: 128,
@@ -2127,6 +3642,30 @@ const postAgentAnswerQuestionRoute = HttpRouter.add("POST", "/api/agents/:id/ans
2127
3642
  delivered: answers.length
2128
3643
  });
2129
3644
  })));
3645
+ const postAgentPlanActionRoute = HttpRouter.add("POST", "/api/agents/:id/plan-action", httpHandler(Effect.gen(function* () {
3646
+ const id = (yield* HttpRouter.params)["id"] ?? "";
3647
+ if (!id.trim()) return jsonResponse({ error: "missing agent id" }, { status: 400 });
3648
+ const body = yield* readJsonBody$1;
3649
+ const action = typeof body["action"] === "string" ? body["action"] : "";
3650
+ const feedback = typeof body["feedback"] === "string" ? body["feedback"].trim() : "";
3651
+ const agentState = yield* getAgentState(id).pipe(Effect.catch(() => Effect.succeed(null)));
3652
+ if (!agentState) return jsonResponse({ error: "Agent not found" }, { status: 404 });
3653
+ const jsonlPath = yield* getAgentJsonlPath$1(id).pipe(Effect.catch(() => Effect.succeed(null)));
3654
+ if (!(jsonlPath ? yield* Effect.promise(() => scanPendingInputsPromise(jsonlPath)) : null)?.exitPlanModePending) return jsonResponse({ error: "No pending plan approval for this agent" }, { status: 409 });
3655
+ const error = yield* Effect.promise(() => deliverPlanActionToSession(id, action, feedback));
3656
+ if (error) return jsonResponse({ error }, { status: 400 });
3657
+ emitActivityEntrySync({
3658
+ source: "dashboard",
3659
+ level: "info",
3660
+ issueId: agentState.issueId,
3661
+ message: `Operator ${action.startsWith("approve") ? "approved" : "requested changes to"} ${id}'s plan from the dashboard`
3662
+ });
3663
+ return jsonResponse({
3664
+ success: true,
3665
+ agentId: id,
3666
+ action
3667
+ });
3668
+ })));
2130
3669
  const postInternalAgentPermissionRequestRoute = HttpRouter.add("POST", "/api/internal/agents/:id/permissions/request", httpHandler(Effect.gen(function* () {
2131
3670
  const id = (yield* HttpRouter.params)["id"] ?? "";
2132
3671
  if (!id.trim()) return jsonResponse({
@@ -2134,7 +3673,7 @@ const postInternalAgentPermissionRequestRoute = HttpRouter.add("POST", "/api/int
2134
3673
  error: "missing agent id"
2135
3674
  }, { status: 400 });
2136
3675
  const request = yield* HttpServerRequest.HttpServerRequest;
2137
- const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-C90jBp-y.js"));
3676
+ const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-BPYEaXFh.js"));
2138
3677
  const expected = getInternalTokenSync();
2139
3678
  if (!expected) return jsonResponse({
2140
3679
  ok: false,
@@ -2401,7 +3940,7 @@ const getAgentHealthHistoryRoute = HttpRouter.add("GET", "/api/agents/:id/health
2401
3940
  const request = yield* HttpServerRequest.HttpServerRequest;
2402
3941
  const urlOpt = HttpServerRequest.toURL(request);
2403
3942
  const hours = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("hours") ?? "24" : "24";
2404
- const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-DDfkoO5a.js"));
3943
+ const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-Bos3sk5g.js"));
2405
3944
  const endTime = /* @__PURE__ */ new Date();
2406
3945
  const startTime = /* @__PURE__ */ new Date(endTime.getTime() - parseInt(hours) * 60 * 60 * 1e3);
2407
3946
  const events = getHealthHistory(id, startTime.toISOString(), endTime.toISOString());
@@ -2442,7 +3981,7 @@ function emitAgentRuntimeEvent(id, body, timestamp) {
2442
3981
  detail: String(decoded.failure)
2443
3982
  }, { status: 400 })
2444
3983
  };
2445
- const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-U8feBf8t.js"));
3984
+ const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-CZrayD_G.js"));
2446
3985
  yield* (yield* AgentStateService).emit(decoded.success);
2447
3986
  return {
2448
3987
  ok: true,
@@ -2524,7 +4063,7 @@ const getAgentRuntimeRoute = HttpRouter.add("GET", "/api/agents/:id/runtime", ht
2524
4063
  success: false,
2525
4064
  error: "missing agent id"
2526
4065
  }, { status: 400 });
2527
- const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-U8feBf8t.js"));
4066
+ const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-CZrayD_G.js"));
2528
4067
  const snapshot = yield* (yield* AgentStateService).get(id);
2529
4068
  if (!snapshot) return jsonResponse({
2530
4069
  success: false,
@@ -2550,7 +4089,9 @@ init_workspace_manager();
2550
4089
  * commit, the persisted event is emitted to the event store's subscribers so
2551
4090
  * the in-memory read model stays current.
2552
4091
  */
4092
+ init_event_store();
2553
4093
  init_agents();
4094
+ init_persistent_logger();
2554
4095
  function prepareAgentStateForSave(state) {
2555
4096
  if (state.status === "running" || state.status === "starting") delete state.stoppedAt;
2556
4097
  else if (state.status === "stopped" && !state.stoppedAt) state.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2635,8 +4176,8 @@ function createAgentStopHandler(lifecycleEvent) {
2635
4176
  yield* stopAgent(id);
2636
4177
  if (stateBeforeStop?.issueId) yield* Effect.promise(async () => {
2637
4178
  try {
2638
- const { resolveProjectFromIssueSync } = await import("./projects-BqRMlDOv.js");
2639
- const { findWorkspacePath } = await import("./archive-planning-DmpT1_14.js");
4179
+ const { resolveProjectFromIssueSync } = await import("./projects-CrBE6IhN.js");
4180
+ const { findWorkspacePath } = await import("./archive-planning-CoyIbgGK.js");
2640
4181
  const issueLower = stateBeforeStop.issueId.toLowerCase();
2641
4182
  const workspacePath = findWorkspacePath(resolveProjectFromIssueSync(stateBeforeStop.issueId)?.projectPath ?? process.cwd(), issueLower);
2642
4183
  if (workspacePath) {
@@ -2806,7 +4347,9 @@ const postAgentUntroubledRoute = HttpRouter.add("POST", "/api/agents/:id/untroub
2806
4347
  })));
2807
4348
  //#endregion
2808
4349
  //#region routes/agents/lifecycle-restart.ts
4350
+ init_config_yaml();
2809
4351
  init_agents();
4352
+ init_harness_policy();
2810
4353
  init_operator_interventions();
2811
4354
  init_projects();
2812
4355
  init_tmux();
@@ -3711,19 +5254,20 @@ const writeAutoStartVBrief = (projectRoot, workspacePath, issue) => Effect.tryPr
3711
5254
  //#endregion
3712
5255
  //#region routes/agents/spawn-helpers.ts
3713
5256
  init_issue_lifecycle();
5257
+ init_child_env();
3714
5258
  init_agents();
3715
5259
  function handleRemoteAgentSpawn(input) {
3716
5260
  return Effect.gen(function* () {
3717
5261
  const { issueId, workspacePath, workspaceMetadata, spawnModel, projectPath, spawnGuardrails, lifecycle } = input;
3718
- const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-B4eWgwNd.js"));
3719
- const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-B062S1Kc.js"));
3720
- const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-B4_d7z7z.js"));
5262
+ const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-Cv-U-_V8.js"));
5263
+ const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-DW_TWQnr.js"));
5264
+ const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-Dj6N8EP9.js"));
3721
5265
  const panConfig = loadPanConfig();
3722
5266
  const spendCap = checkRemoteSpendCap(panConfig);
3723
5267
  if (!spendCap.allowed) return jsonResponse({ error: spendCap.message }, { status: 429 });
3724
5268
  const fly = createFlyProviderFromConfig(panConfig.remote);
3725
5269
  yield* Effect.promise(() => fly.syncAllCredentials(workspaceMetadata.vmName));
3726
- const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-B2F3ZwKm.js"));
5270
+ const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-ZTAb2Hyf.js"));
3727
5271
  const trackerContext = yield* Effect.promise(() => getTrackerContext(issueId, workspacePath));
3728
5272
  const agentPrompt = yield* Effect.promise(() => buildWorkAgentPrompt({
3729
5273
  issueId,
@@ -3809,7 +5353,7 @@ function handleContainerOrchestration(input) {
3809
5353
  if (existsSync(workspacePath) && existsSync(devScript)) {
3810
5354
  let dockerRunning = false;
3811
5355
  try {
3812
- yield* Effect.promise(() => execAsync$1("docker info >/dev/null 2>&1", { encoding: "utf-8" }));
5356
+ yield* Effect.promise(() => execAsync$2("docker info >/dev/null 2>&1", { encoding: "utf-8" }));
3813
5357
  dockerRunning = true;
3814
5358
  } catch {}
3815
5359
  if (dockerRunning) {
@@ -3852,7 +5396,7 @@ function handleContainerOrchestration(input) {
3852
5396
  }));
3853
5397
  let containersReady = false;
3854
5398
  try {
3855
- const { stdout: existing } = yield* Effect.promise(() => execFileAsync$1("docker", [
5399
+ const { stdout: existing } = yield* Effect.promise(() => execFileAsync$2("docker", [
3856
5400
  "ps",
3857
5401
  "--filter",
3858
5402
  `name=${featureName}`,
@@ -3918,7 +5462,7 @@ function handleContainerOrchestration(input) {
3918
5462
  let healthy = false;
3919
5463
  while (Date.now() - startTime < maxWaitMs) {
3920
5464
  try {
3921
- const { stdout } = await execFileAsync$1("docker", [
5465
+ const { stdout } = await execFileAsync$2("docker", [
3922
5466
  "ps",
3923
5467
  "--filter",
3924
5468
  `name=${featureName}`,
@@ -4085,7 +5629,7 @@ function spawnGuardrailResourcesHint(hint) {
4085
5629
  * invisible to a JS `try/catch`. Before this helper, that escaped as an
4086
5630
  * unhandled 500 on POST /api/agents instead of the designed 422.
4087
5631
  */
4088
- const countBeadsForIssue = (workspacePath, issueLower) => withBdMutex(() => Effect.promise(() => execFileAsync$1("bd", [
5632
+ const countBeadsForIssue = (workspacePath, issueLower) => withBdMutex(() => Effect.promise(() => execFileAsync$2("bd", [
4089
5633
  "list",
4090
5634
  "--json",
4091
5635
  "-l",
@@ -4176,7 +5720,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4176
5720
  const workspacePath = join(projectPath, "workspaces", `feature-${issueLower}`);
4177
5721
  if (!existsSync(workspacePath)) try {
4178
5722
  const nodeDir = dirname(process.execPath);
4179
- yield* Effect.promise(() => execAsync$1(`pan workspace create ${issueId} --local`, {
5723
+ yield* Effect.promise(() => execAsync$2(`pan workspace create ${issueId} --local`, {
4180
5724
  cwd: projectPath,
4181
5725
  encoding: "utf-8",
4182
5726
  timeout: 6e4,
@@ -4252,7 +5796,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4252
5796
  if (!hasBeads) {
4253
5797
  console.log(`[agents] No beads for ${issueId} — attempting auto-recovery via createBeadsFromVBrief`);
4254
5798
  try {
4255
- const { createBeadsFromVBrief } = yield* Effect.promise(() => import("./beads-CivpXztb.js"));
5799
+ const { createBeadsFromVBrief } = yield* Effect.promise(() => import("./beads-B1u04n3o.js"));
4256
5800
  const recovery = yield* createBeadsFromVBrief(workspacePath);
4257
5801
  beadCount = recovery.created.length;
4258
5802
  hasBeads = recovery.created.length > 0 && (planItemCount === null || recovery.created.length === planItemCount);
@@ -4398,24 +5942,24 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4398
5942
  if (shouldCommitLegacyWorkspaceArtifacts(projectConfig ? yield* Effect.promise(() => isStateMigrated(projectConfig)) : false) && (existsSync(workspacePanContinuePath) || existsSync(workspacePanDir))) yield* Effect.gen(function* () {
4399
5943
  const gitRoot = workspacePath;
4400
5944
  if (existsSync(join(gitRoot, ".pan"))) {
4401
- yield* Effect.promise(() => execAsync$1(`git add .pan/`, {
5945
+ yield* Effect.promise(() => execAsync$2(`git add .pan/`, {
4402
5946
  cwd: gitRoot,
4403
5947
  encoding: "utf-8"
4404
5948
  }));
4405
- yield* Effect.promise(() => execAsync$1(`git reset HEAD -- .pan/kickoff.md .pan/continue.json .pan/handoff-*.md .pan/spec.vbrief.json`, {
5949
+ yield* Effect.promise(() => execAsync$2(`git reset HEAD -- .pan/kickoff.md .pan/continue.json .pan/handoff-*.md .pan/spec.vbrief.json`, {
4406
5950
  cwd: gitRoot,
4407
5951
  encoding: "utf-8"
4408
5952
  }));
4409
5953
  }
4410
- if (existsSync(join(gitRoot, ".beads"))) yield* Effect.promise(() => execAsync$1(`git add .beads/`, {
5954
+ if (existsSync(join(gitRoot, ".beads"))) yield* Effect.promise(() => execAsync$2(`git add .beads/`, {
4411
5955
  cwd: gitRoot,
4412
5956
  encoding: "utf-8"
4413
5957
  }));
4414
- if (yield* Effect.promise(() => execAsync$1(`git diff --cached --quiet`, {
5958
+ if (yield* Effect.promise(() => execAsync$2(`git diff --cached --quiet`, {
4415
5959
  cwd: gitRoot,
4416
5960
  encoding: "utf-8"
4417
5961
  }).then(() => false).catch(() => true))) {
4418
- yield* Effect.promise(() => execAsync$1(`git commit -m "chore: planning artifacts for ${issueId} before agent start"`, {
5962
+ yield* Effect.promise(() => execAsync$2(`git commit -m "chore: planning artifacts for ${issueId} before agent start"`, {
4419
5963
  cwd: gitRoot,
4420
5964
  encoding: "utf-8"
4421
5965
  }));
@@ -4442,7 +5986,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4442
5986
  console.warn(`[start-agent] Failed to set plan.status=running (non-fatal): ${planStatusErr?.message ?? planStatusErr}`);
4443
5987
  }
4444
5988
  try {
4445
- const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = yield* Effect.promise(() => import("./record-CJsjvRO4.js"));
5989
+ const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = yield* Effect.promise(() => import("./record-lEYK__LD.js"));
4446
5990
  const recordProject = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);
4447
5991
  yield* Effect.promise(() => appendSessionEntry(recordProject, issueId, {
4448
5992
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4491,12 +6035,12 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4491
6035
  console.log(`[start-agent] Killed stale tmux session ${agentSessionName}`);
4492
6036
  }).pipe(Effect.catch(() => Effect.void));
4493
6037
  if (!(body.acknowledgeDirtyWorkspace === true)) try {
4494
- const { stdout: statusOut } = yield* Effect.promise(() => execAsync$1("git status --porcelain", {
6038
+ const { stdout: statusOut } = yield* Effect.promise(() => execAsync$2("git status --porcelain", {
4495
6039
  cwd: workspacePath,
4496
6040
  encoding: "utf-8"
4497
6041
  }));
4498
6042
  if (statusOut.trim()) {
4499
- const { stdout: diffOut } = yield* Effect.promise(() => execAsync$1("git diff HEAD --stat", {
6043
+ const { stdout: diffOut } = yield* Effect.promise(() => execAsync$2("git diff HEAD --stat", {
4500
6044
  cwd: workspacePath,
4501
6045
  encoding: "utf-8"
4502
6046
  }).catch(() => ({ stdout: "" })));
@@ -4688,7 +6232,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
4688
6232
  * Composes all /api/agents/* route layers and preserves the public helper
4689
6233
  * exports that external callers historically imported from routes/agents.js.
4690
6234
  */
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);
6235
+ 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);
4692
6236
  //#endregion
4693
6237
  //#region ../../lib/cloister/task-readiness.ts
4694
6238
  init_io();
@@ -4860,10 +6404,10 @@ const _serverManagedMerges = /* @__PURE__ */ new Set();
4860
6404
  function firePostMergeLifecycle(issueId, options) {
4861
6405
  const started = postMergeGuard.run(issueId, async () => {
4862
6406
  const projectPath = getProjectPathForIssue(extractPrefixSync(issueId) ?? issueId.split("-")[0]);
4863
- const { postMergeLifecycle } = await import("./merge-agent-Bwm34MZ5.js");
6407
+ const { postMergeLifecycle } = await import("./merge-agent-ejC7Wos8.js");
4864
6408
  await postMergeLifecycle(issueId, projectPath, options?.sourceBranch, options?.verifiedMergedRef ? { verifiedMergedRef: options.verifiedMergedRef } : void 0);
4865
6409
  console.log(`[merge] post-merge lifecycle completed for ${issueId}`);
4866
- const { runMergeTrainReconcile } = await import("./merge-train-BIm7-7cy.js");
6410
+ const { runMergeTrainReconcile } = await import("./merge-train-CH33XJOH.js");
4867
6411
  const outcomes = await runMergeTrainReconcile(issueId);
4868
6412
  if (outcomes.length > 0) console.log(`[merge-train] reconciled ${outcomes.length} sibling(s) after ${issueId}: ` + outcomes.map((o) => `${o.issueId}=${o.result}`).join(", "));
4869
6413
  }, (err) => console.error(`[merge] post-merge lifecycle failed for ${issueId}:`, err));
@@ -4884,15 +6428,15 @@ init_io();
4884
6428
  init_domain_services();
4885
6429
  init_tmux();
4886
6430
  const getSpecialistsRoute = HttpRouter.add("GET", "/api/specialists", httpHandler(Effect.gen(function* () {
4887
- const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-Chvvwf5z.js"));
6431
+ const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
4888
6432
  return jsonResponse({
4889
6433
  specialists: yield* Effect.promise(() => getAllSpecialistStatus()),
4890
6434
  projects: yield* Effect.promise(() => getAllProjectSpecialistStatuses())
4891
6435
  });
4892
6436
  })));
4893
6437
  const postSpecialistsResetAllRoute = HttpRouter.add("POST", "/api/specialists/reset-all", httpHandler(Effect.gen(function* () {
4894
- const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-Chvvwf5z.js"));
4895
- const { clearHookSync } = yield* Effect.promise(() => import("./hooks-DvdnCVv6.js"));
6438
+ const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
6439
+ const { clearHookSync } = yield* Effect.promise(() => import("./hooks-uEr8WTx_.js"));
4896
6440
  const specialists = getAllSpecialists();
4897
6441
  const results = [];
4898
6442
  for (const specialist of specialists) {
@@ -4942,7 +6486,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
4942
6486
  const normalizedIssueId = issueId.toUpperCase();
4943
6487
  console.log(`[specialists/done] ${specialist} signaling ${status} for ${normalizedIssueId}`);
4944
6488
  if (specialist === "merge") {
4945
- const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-Coz3eTSr.js"));
6489
+ const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-CJcv-NXH.js"));
4946
6490
  if (yield* reportSpecialistCompletion(normalizedIssueId, {
4947
6491
  status,
4948
6492
  notes
@@ -4989,7 +6533,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
4989
6533
  if (specialist === "inspect" && status === "failed") yield* Effect.promise(() => reportTieredInspectFailureEscalation(normalizedIssueId, notes));
4990
6534
  yield* Effect.promise(async () => {
4991
6535
  try {
4992
- const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-Chvvwf5z.js");
6536
+ const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-k22_Btaq.js");
4993
6537
  const projectKey = resolveProjectFromIssueSync(normalizedIssueId)?.projectKey;
4994
6538
  const tmuxSession = projectKey ? getTmuxSessionName(`${specialist}-agent`, projectKey, normalizedIssueId) : getTmuxSessionName(`${specialist}-agent`);
4995
6539
  saveAgentRuntimeState(tmuxSession, {
@@ -5008,7 +6552,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5008
6552
  });
5009
6553
  console.log(`[specialists/done] Cleared registry lock for ${registryKey} (${projectKey})`);
5010
6554
  }
5011
- const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-B9HK_lRu.js");
6555
+ const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-CQyF1ygu.js");
5012
6556
  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
6557
  } catch (err) {
5014
6558
  console.error(`[specialists/done] Error managing specialist state:`, err);
@@ -5020,7 +6564,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5020
6564
  if (project) {
5021
6565
  const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
5022
6566
  if (existsSync(workspacePath)) {
5023
- const { getWorkspaceGitInfo } = await import("./git-utils-Ym380hJZ.js");
6567
+ const { getWorkspaceGitInfo } = await import("./git-utils-C0neHvQx.js");
5024
6568
  const { HEAD } = await Effect.runPromise(getWorkspaceGitInfo(workspacePath));
5025
6569
  setReviewStatusSync(normalizedIssueId, { reviewedAtCommit: HEAD });
5026
6570
  console.log(`[specialists/done] Snapshotted reviewedAtCommit=${HEAD.substring(0, 8)} for ${normalizedIssueId}`);
@@ -5032,7 +6576,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5032
6576
  });
5033
6577
  if (specialist === "inspect" && status === "passed") yield* Effect.promise(async () => {
5034
6578
  try {
5035
- const { onInspectComplete } = await import("./inspect-agent-D8UBDgfR.js");
6579
+ const { onInspectComplete } = await import("./inspect-agent-CWkeAB7J.js");
5036
6580
  const beadId = (notes?.match(/[Bb]ead\s+(\S+)/))?.[1] || "unknown";
5037
6581
  const project = resolveProjectFromIssueSync(normalizedIssueId);
5038
6582
  if (project) {
@@ -5077,7 +6621,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5077
6621
  if (project) {
5078
6622
  if (existsSync(join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`))) {
5079
6623
  setReviewStatusSync(normalizedIssueId, { testStatus: "passed" });
5080
- const { initEventStore } = await import("./event-store-C02Gprug.js");
6624
+ const { initEventStore } = await import("./event-store-CqDgfbAs.js");
5081
6625
  await (await initEventStore()).appendAsync({
5082
6626
  type: "test.passed",
5083
6627
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5130,8 +6674,8 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5130
6674
  if (notes?.toLowerCase().includes("conflict")) yield* Effect.promise(async () => {
5131
6675
  try {
5132
6676
  const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;
5133
- const { sessionExists } = await import("./tmux-CbcrkUx5.js");
5134
- const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-CxbuBjID.js");
6677
+ const { sessionExists } = await import("./tmux-By8E_Yv_.js");
6678
+ const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-CEcaxSkR.js");
5135
6679
  if (await Effect.runPromise(sessionExists(workAgentId))) {
5136
6680
  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
6681
  console.log(`[specialists/done] Sent rebase instructions to ${workAgentId}`);
@@ -5145,7 +6689,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5145
6689
  try {
5146
6690
  const project = resolveProjectFromIssueSync(normalizedIssueId);
5147
6691
  const workspacePath = project ? join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`) : void 0;
5148
- const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-BnGtT0vN.js");
6692
+ const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-D2Q960wX.js");
5149
6693
  const result = await Effect.runPromise(deliverReviewVerdictFeedback({
5150
6694
  issueId: normalizedIssueId,
5151
6695
  verdict: status === "failed" ? "failed" : "blocked",
@@ -5186,7 +6730,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
5186
6730
  });
5187
6731
  })));
5188
6732
  const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/specialists/logs/cleanup-all", httpHandler(Effect.gen(function* () {
5189
- const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-B24R2B9Y.js"));
6733
+ const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
5190
6734
  const results = cleanupAllLogsSync();
5191
6735
  return jsonResponse({
5192
6736
  success: true,
@@ -5196,7 +6740,7 @@ const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/speciali
5196
6740
  });
5197
6741
  })));
5198
6742
  const getSpecialistsProjectsRoute = HttpRouter.add("GET", "/api/specialists/projects", httpHandler(Effect.gen(function* () {
5199
- const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-Chvvwf5z.js"));
6743
+ const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5200
6744
  return jsonResponse(yield* Effect.promise(() => getAllProjectSpecialistStatuses()));
5201
6745
  })));
5202
6746
  const postSpecialistWakeRoute = HttpRouter.add("POST", "/api/specialists/:name/wake", httpHandler(Effect.gen(function* () {
@@ -5240,7 +6784,7 @@ const postSpecialistReportStatusRoute = HttpRouter.add("POST", "/api/specialists
5240
6784
  "blocked",
5241
6785
  "failed"
5242
6786
  ].includes(status)) {
5243
- const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-Chvvwf5z.js"));
6787
+ const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5244
6788
  saveAgentRuntimeState(getTmuxSessionName(name), {
5245
6789
  state: "idle",
5246
6790
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
@@ -5361,7 +6905,7 @@ const postProjectSpecialistKillRoute = HttpRouter.add("POST", "/api/specialists/
5361
6905
  const issueId = params["issueId"];
5362
6906
  const type = params["type"];
5363
6907
  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-Chvvwf5z.js"));
6908
+ const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5365
6909
  const tmuxSession = getRunMetadata(project, makeSpecialistRegistryKey(type, issueId)).tmuxSession ?? getTmuxSessionName(type, project, issueId);
5366
6910
  yield* Effect.promise(() => Effect.runPromise(killSession(tmuxSession)).catch(() => {}));
5367
6911
  saveAgentRuntimeState(tmuxSession, {
@@ -5388,7 +6932,7 @@ const getProjectSpecialistRunsRoute = HttpRouter.add("GET", "/api/specialists/:p
5388
6932
  if (limitParam) limit = parseInt(limitParam, 10);
5389
6933
  if (offsetParam) offset = parseInt(offsetParam, 10);
5390
6934
  }
5391
- const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-B24R2B9Y.js"));
6935
+ const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
5392
6936
  return jsonResponse(listRunLogsSync(project, type, {
5393
6937
  limit,
5394
6938
  offset
@@ -5399,7 +6943,7 @@ const getProjectSpecialistRunStreamRoute = HttpRouter.add("GET", "/api/specialis
5399
6943
  const project = params["project"];
5400
6944
  const type = params["type"];
5401
6945
  const runId = params["runId"];
5402
- const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-B24R2B9Y.js"));
6946
+ const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
5403
6947
  const logPath = getRunLogPath(project, type, runId);
5404
6948
  if (!existsSync(logPath)) return jsonResponse({ error: "Run log not found" }, { status: 404 });
5405
6949
  const encoder = new TextEncoder();
@@ -5458,7 +7002,7 @@ const getProjectSpecialistRunRoute = HttpRouter.add("GET", "/api/specialists/:pr
5458
7002
  const project = params["project"];
5459
7003
  const type = params["type"];
5460
7004
  const runId = params["runId"];
5461
- const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-B24R2B9Y.js"));
7005
+ const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
5462
7006
  const content = getRunLogSync(project, type, runId);
5463
7007
  if (!content) return jsonResponse({ error: "Run log not found" }, { status: 404 });
5464
7008
  return jsonResponse({
@@ -5472,7 +7016,7 @@ const postProjectSpecialistRunTerminateRoute = HttpRouter.add("POST", "/api/spec
5472
7016
  const project = params["project"];
5473
7017
  const type = params["type"];
5474
7018
  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-Chvvwf5z.js"));
7019
+ const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5476
7020
  yield* Effect.promise(() => terminateSpecialist(project, type));
5477
7021
  return jsonResponse({
5478
7022
  success: true,
@@ -5484,7 +7028,7 @@ const postProjectSpecialistGracePauseRoute = HttpRouter.add("POST", "/api/specia
5484
7028
  const project = params["project"];
5485
7029
  const type = params["type"];
5486
7030
  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-Chvvwf5z.js"));
7031
+ const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5488
7032
  if (pauseGracePeriod(project, type)) return jsonResponse({
5489
7033
  success: true,
5490
7034
  message: "Grace period paused"
@@ -5496,7 +7040,7 @@ const postProjectSpecialistGraceResumeRoute = HttpRouter.add("POST", "/api/speci
5496
7040
  const project = params["project"];
5497
7041
  const type = params["type"];
5498
7042
  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-Chvvwf5z.js"));
7043
+ const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5500
7044
  if (resumeGracePeriod(project, type)) return jsonResponse({
5501
7045
  success: true,
5502
7046
  message: "Grace period resumed"
@@ -5508,7 +7052,7 @@ const postProjectSpecialistGraceExitRoute = HttpRouter.add("POST", "/api/special
5508
7052
  const project = params["project"];
5509
7053
  const type = params["type"];
5510
7054
  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-Chvvwf5z.js"));
7055
+ const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5512
7056
  exitGracePeriod(project, type);
5513
7057
  return jsonResponse({
5514
7058
  success: true,
@@ -5520,7 +7064,7 @@ const getProjectSpecialistGraceRoute = HttpRouter.add("GET", "/api/specialists/:
5520
7064
  const project = params["project"];
5521
7065
  const type = params["type"];
5522
7066
  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-Chvvwf5z.js"));
7067
+ const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5524
7068
  const state = getGracePeriodState(project, type);
5525
7069
  if (state) return jsonResponse(state);
5526
7070
  else return jsonResponse({ error: "No active grace period" }, { status: 404 });
@@ -5529,7 +7073,7 @@ const getProjectSpecialistContextRoute = HttpRouter.add("GET", "/api/specialists
5529
7073
  const params = yield* HttpRouter.params;
5530
7074
  const project = params["project"];
5531
7075
  const type = params["type"];
5532
- const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-fmb03vXQ.js"));
7076
+ const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-B2HlbNxS.js"));
5533
7077
  const digest = loadContextDigest(project, type);
5534
7078
  if (digest) return jsonResponse({ digest });
5535
7079
  else return jsonResponse({ error: "No context digest found" }, { status: 404 });
@@ -5538,7 +7082,7 @@ const postProjectSpecialistContextRegenerateRoute = HttpRouter.add("POST", "/api
5538
7082
  const params = yield* HttpRouter.params;
5539
7083
  const project = params["project"];
5540
7084
  const type = params["type"];
5541
- const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-fmb03vXQ.js"));
7085
+ const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-B2HlbNxS.js"));
5542
7086
  const digest = yield* Effect.promise(() => regenerateContextDigest(project, type));
5543
7087
  if (digest) return jsonResponse({
5544
7088
  digest,
@@ -5557,7 +7101,7 @@ const postProjectSpecialistCompleteRoute = HttpRouter.add("POST", "/api/speciali
5557
7101
  "blocked"
5558
7102
  ].includes(status)) return jsonResponse({ error: "Valid status (passed/failed/blocked) is required" }, { status: 400 });
5559
7103
  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-Chvvwf5z.js"));
7104
+ const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
5561
7105
  signalSpecialistCompletion(project, type, {
5562
7106
  status,
5563
7107
  notes
@@ -5591,8 +7135,8 @@ const postProjectSpecialistLogsCleanupRoute = HttpRouter.add("POST", "/api/speci
5591
7135
  const params = yield* HttpRouter.params;
5592
7136
  const project = params["project"];
5593
7137
  const type = params["type"];
5594
- const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-B24R2B9Y.js"));
5595
- const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-BqRMlDOv.js"));
7138
+ const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
7139
+ const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-CrBE6IhN.js"));
5596
7140
  const retention = getSpecialistRetention(project);
5597
7141
  const deleted = cleanupOldLogsSync(project, type, {
5598
7142
  maxDays: retention.max_days,
@@ -5613,7 +7157,7 @@ const postProjectReviewRestartRoute = HttpRouter.add("POST", "/api/specialists/:
5613
7157
  const project = params["project"];
5614
7158
  const issueId = params["issueId"];
5615
7159
  const { model, harness } = yield* readJsonBody;
5616
- const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-Bf9XjLnl.js"));
7160
+ const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-u5q3ip7H.js"));
5617
7161
  const killResult = yield* killAllReviewerSessions(project, issueId);
5618
7162
  const projectConfig = resolveProjectFromIssueSync(issueId);
5619
7163
  if (!projectConfig) return jsonResponse({ error: `Cannot resolve project for ${issueId}` }, { status: 404 });
@@ -5627,7 +7171,7 @@ const postProjectReviewRestartRoute = HttpRouter.add("POST", "/api/specialists/:
5627
7171
  }));
5628
7172
  branch = stdout.trim() || "unknown";
5629
7173
  } catch {}
5630
- const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-Bf9XjLnl.js"));
7174
+ const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-u5q3ip7H.js"));
5631
7175
  const prUrl = getReviewStatusSync(issueId)?.prUrl;
5632
7176
  const result = yield* spawnReviewRoleForIssue({
5633
7177
  issueId,
@@ -5776,6 +7320,6 @@ const specialistsProjectRouteLayer = Layer.mergeAll(getProjectSpecialistStatusRo
5776
7320
  */
5777
7321
  const specialistsRouteLayer = Layer.mergeAll(specialistsLegacyRouteLayer, specialistsProjectRouteLayer);
5778
7322
  //#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 };
7323
+ 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 _, jsonStringResponse as _t, 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, jsonResponse as gt, handleConversationPlanAction as h, getDefaultCwd as ht, validateSpecialistAutoCompleteMetadata as i, resolvePiSessionPath as it, shouldReportUnresolvedLiveSession as j, isInsideGitWorkTree as k, deliverConversationViaControlChannel as l, isTrustedOriginForHost as lt, handleConversationDeliveryMethod as m, httpHandler as mt, _serverManagedMerges as n, resolveCodexRolloutPath as nt, saveAgentStateAndEmitEvent as o, waitForSessionRespawn as ot, handleConversationControlAck as p, checkCodexAuthStatus 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
7324
 
5781
- //# sourceMappingURL=specialists-CL7E8yUg.js.map
7325
+ //# sourceMappingURL=specialists-CFgw_0Q6.js.map