@overdeck/desktop 0.45.24 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/drizzle/overdeck/0000_overdeck_init.sql +36 -1
  2. package/package.json +1 -1
  3. package/server/{acceptance-criteria-DDCnXF58.js → acceptance-criteria-10Ur5Dne.js} +2 -2
  4. package/server/activity-logger-CMthG6Vd.js +3 -0
  5. package/server/{activity-logger-DDlOIAML.js → activity-logger-CyteR4JO.js} +24 -13
  6. package/server/{advancing-selfheal-BVwW4qRn.js → advancing-selfheal-BNwqxseD.js} +6 -6
  7. package/server/{agent-gc-CHcnvonI.js → agent-gc-C3h9Or3a.js} +5 -5
  8. package/server/agent-state-CYXJVG72.js +3 -0
  9. package/server/agent-state-service-C63p_ftq.js +2 -0
  10. package/server/{agent-state-service-BsgWxZuB.js → agent-state-service-Ds6OeFvo.js} +15 -9
  11. package/server/{agents-EbI0zCJl.js → agents-J5mEnekq.js} +23 -16
  12. package/server/agents-ePFVoZbu.js +3 -0
  13. package/server/archive-planning-B2XplFzT.js +2 -0
  14. package/server/{archive-planning-Du25Jtst.js → archive-planning-BwhxeP7C.js} +22 -26
  15. package/server/{artifact-index-jobs-CbEDfCDu.js → artifact-index-jobs-D1q_vR3V.js} +2 -2
  16. package/server/{assemble-J2ZZ2C15.js → assemble-Bgwqv-Ro.js} +2 -2
  17. package/server/auto-spawn-consent-CQ9Bi9q6.js +1141 -0
  18. package/server/{backlog-auto-trigger-2R2jog7n.js → backlog-auto-trigger-n1lzPgN4.js} +3 -3
  19. package/server/{behavior-B3exJ42O.js → behavior-td6-xhQk.js} +2 -2
  20. package/server/boot-reconciled-stop-events-C7gGe3oB.js +44 -0
  21. package/server/bridge-pool-patrol-CYH2OLb5.js +136 -0
  22. package/server/{briefing-freshness-D1wzUIWv.js → briefing-freshness-DRqZ8lAz.js} +2 -2
  23. package/server/{build-info-Ie_D0_BT.js → build-info-7yiEpl6u.js} +5 -3
  24. package/server/{build-info-CZk_TITh.js → build-info-CpG__c63.js} +1 -1
  25. package/server/{checkpoint-manager-DwbQoqrC.js → checkpoint-manager-CkKmB0wZ.js} +1 -1
  26. package/server/{checkpoint-manager-BI1Wxoku.js → checkpoint-manager-DBcyNR8w.js} +2 -2
  27. package/server/checkpoint-worker.js +1 -1
  28. package/server/{checkpoints-CQB1h765.js → checkpoints-D95apurL.js} +2 -2
  29. package/server/{checkpoints-BJIs5QIA.js → checkpoints-Wm963lm8.js} +1 -1
  30. package/server/{claude-hooks-provision-8Ngo8TpT.js → claude-hooks-provision-4RPxNkE8.js} +1 -1
  31. package/server/{claude-hooks-provision-D8GXF3_s.js → claude-hooks-provision-Bjitl9aI.js} +13 -2
  32. package/server/{claude-permissions-DNWEVV6z.js → claude-permissions-CpbpvUQ2.js} +2 -2
  33. package/server/{claude-settings-overlay-DWy-Bb4Y.js → claude-settings-overlay-BzkHYDwR.js} +1 -1
  34. package/server/{claude-settings-overlay-CEdaqpWI.js → claude-settings-overlay-_H9Yrzab.js} +2 -2
  35. package/server/cli-CjOCSnfj.js +3 -0
  36. package/server/{cli-RoOx5DUf.js → cli-Cte9ZFGn.js} +9 -9
  37. package/server/{cliproxy-C8a1vtTU.js → cliproxy-C5fMJ7rY.js} +2 -2
  38. package/server/{cliproxy-fHfIeiGd.js → cliproxy-DaAbWMjC.js} +1 -1
  39. package/server/{close-issue-DoOifFIK.js → close-issue-BPh1ARCF.js} +5 -5
  40. package/server/{closed-issue-reaper-CktPXo8m.js → closed-issue-reaper-DHRfRMrp.js} +1 -1
  41. package/server/{closed-issue-reaper-Bk3SU4RC.js → closed-issue-reaper-DoOZqGFK.js} +88 -17
  42. package/server/{codex-BZXGhUVB.js → codex-BzGDW_d3.js} +36 -7
  43. package/server/{codex-GcHuUF-k.js → codex-o8FJwzmy.js} +1 -1
  44. package/server/{codex-parser-CFZ1_1Jl.js → codex-parser-Dt-ycU8L.js} +2 -2
  45. package/server/compaction-continuation-CvXpbZ4w.js +200 -0
  46. package/server/{config-CKxYiCmX.js → config-Bi3u1VK7.js} +4 -3
  47. package/server/{config-BZ0sHTju.js → config-BvLe652W.js} +2 -2
  48. package/server/{config-DzV30TOm.js → config-CL3Gyvja.js} +1 -1
  49. package/server/{config-yaml-DySPNmKN.js → config-yaml-CxdwhAIA.js} +276 -14
  50. package/server/conflict-gate-CX80JYYS.js +3 -0
  51. package/server/{conflict-gate-BaCVrH-N.js → conflict-gate-E4nt7ZeS.js} +60 -14
  52. package/server/{context-layers-igZ4FI4_.js → context-layers-C_IIysnm.js} +3 -3
  53. package/server/{context-layers-BrPmE__h.js → context-layers-DAG83UZt.js} +2 -2
  54. package/server/{control-settings-BQp7_4uD.js → control-settings-CiqGxXnc.js} +56 -19
  55. package/server/{conversation-compaction-eQB5CkOe.js → conversation-compaction-BE3CNtHF.js} +1 -1
  56. package/server/{conversation-compaction-CgrR8YYp.js → conversation-compaction-D6-yLtqt.js} +8 -8
  57. package/server/{conversation-service-DPJ71F33.js → conversation-service-DBxoYMDl.js} +10 -8
  58. package/server/conversation-service-D_TS2b1r.js +3 -0
  59. package/server/{conversations-CAsJF9kh.js → conversations-BKDVpB-3.js} +1 -1
  60. package/server/{conversations-6AfUfbwc.js → conversations-D0UEdLtk.js} +4 -4
  61. package/server/{cost-R-m6Yi-W.js → cost-DS1s0DbG.js} +3 -3
  62. package/server/{cost-B2ddP6wq.js → cost-DpgYmYqR.js} +12 -2
  63. package/server/{cost-sync-BsN6ER0n.js → cost-sync-tOCCGnEt.js} +3 -3
  64. package/server/{dag-B1ucnjDU.js → dag-BtkgLeqY.js} +2 -2
  65. package/server/{dangerous-git-ops-BXqeWVSf.js → dangerous-git-ops-CFLykHWt.js} +1 -1
  66. package/server/dashboard-db-worker.js +7 -7
  67. package/server/deacon-BIblC-WX.js +4 -0
  68. package/server/{deacon-nudge-log-6wVqAaqz.js → deacon-nudge-log-B08gF0mh.js} +3 -3
  69. package/server/{deacon-CU_cAYSR.js → deacon-pLRLbPvR.js} +3308 -2707
  70. package/server/deacon-pending-lifecycle-B-Io84AP.js +33 -0
  71. package/server/deacon-swarm-B1XkIvfr.js +2 -0
  72. package/server/{deacon-swarm-BXZZ1Z2-.js → deacon-swarm-v5PMun-m.js} +15 -14
  73. package/server/deacon.js +14 -19
  74. package/server/dead-end-trip-D8A3msX9.js +13 -0
  75. package/server/delivery-BX9HR4wQ.js +3 -0
  76. package/server/{deploy-patrol-AiFoP8dw.js → deploy-patrol-sYgOxLeb.js} +69 -18
  77. package/server/deploy-queue-VfohaPN_.js +169 -0
  78. package/server/deploy-queue-gHJBycaF.js +3 -0
  79. package/server/deploy-window-CFRZ0-kt.js +3 -0
  80. package/server/{deploy-window-C3F-tihF.js → deploy-window-D4DQb8VP.js} +40 -16
  81. package/server/{dev-supervisor-bcOWqdQE.js → dev-supervisor-DiOBDM1c.js} +2 -2
  82. package/server/{devcontainer-renderer-_d6XhyND.js → devcontainer-renderer-BUOIlfgD.js} +3 -3
  83. package/server/dist-B3YPQa5A.js +3 -0
  84. package/server/{dist-Cb_0uvpS.js → dist-C3A8B8wM.js} +1 -1
  85. package/server/{dist-src-BSoS7mkd.js → dist-src-C0adpegX.js} +1 -1
  86. package/server/{docker-DcMTFTUC.js → docker-BJzlkqYB.js} +2 -2
  87. package/server/docker-Y3PElJb0.js +3 -0
  88. package/server/dod-DlNtcH6n.js +91 -0
  89. package/server/durable-review-pipeline-Cibps-Rg.js +20 -0
  90. package/server/durable-review-pipeline-DP6bIcOY.js +3 -0
  91. package/server/{ensure-devcontainer-DNi-ono3.js → ensure-devcontainer-BAofzBPv.js} +4 -4
  92. package/server/{ensure-devcontainer-BZgOhRo7.js → ensure-devcontainer-DMAbWqtQ.js} +1 -1
  93. package/server/{esm-shims-OSHAYaS1.js → esm-shims-BeObUkaw.js} +1 -1
  94. package/server/{event-store-EV2cMiXj.js → event-store-5UKzh9Jf.js} +1 -1
  95. package/server/{event-store-CQL6NeqB.js → event-store-CrHcVC5_.js} +23 -4
  96. package/server/{events-DTfAe4My.js → events-BNrvchBg.js} +3 -3
  97. package/server/{events-BTdrdEq8.js → events-CF8QDbuV.js} +1 -1
  98. package/server/{factory-CFibJuaw.js → factory-Cmm9Hsho.js} +5 -5
  99. package/server/{feature-registry-population-DKZFMbFt.js → feature-registry-population-B6HpNWRy.js} +4 -4
  100. package/server/{feature-registry-storage-BdPuLcBX.js → feature-registry-storage-DJEFx1Pf.js} +2 -2
  101. package/server/{features-ez2271oH.js → features-D5km80UI.js} +2 -2
  102. package/server/{feedback-target-CoFDHiM8.js → feedback-target-BJ5ZADsu.js} +1 -1
  103. package/server/{feedback-target-7mqqwrX6.js → feedback-target-DSxEL6lP.js} +8 -8
  104. package/server/{feedback-writer-CY5L1lB7.js → feedback-writer-C27Hak8a.js} +5 -5
  105. package/server/{feedback-writer-D3a22RCd.js → feedback-writer-CoB9Z6aU.js} +1 -1
  106. package/server/{flywheel-DHrgCphM.js → flywheel-BHx8XuMr.js} +47 -20
  107. package/server/{flywheel-SVF02Jpq.js → flywheel-DZZdtiOL.js} +1 -1
  108. package/server/{flywheel-DzGtWnJ1.js → flywheel-f662Q-y2.js} +16 -15
  109. package/server/flywheel-merge-order-D_xLMbJU.js +462 -0
  110. package/server/flywheel-merge-order-Dil0LmdN.js +2 -0
  111. package/server/forge-B5o319qI.js +3 -0
  112. package/server/forge-BaKGCya6.js +383 -0
  113. package/server/{from-kNxT0bZC.js → from-B3tZbB55.js} +1 -1
  114. package/server/fs-lock-Bre3sGoz.js +130 -0
  115. package/server/{fts-db-CE46vDkZ.js → fts-db-IVc4_M3f.js} +2 -2
  116. package/server/{fts-operations-CqY6zl-i.js → fts-operations-B9jG8wa3.js} +2 -2
  117. package/server/{git-utils-CRw2zLPC.js → git-utils-CsvYfJU3.js} +30 -5
  118. package/server/git-utils-Xg4PFN45.js +3 -0
  119. package/server/{github-app-ClZ2lk8D.js → github-app-Bmg4R9mv.js} +1 -1
  120. package/server/{github-app-C3QrAUd0.js → github-app-CmNk0zLU.js} +3 -3
  121. package/server/{github-client-DfQYVUaw.js → github-client-B6KM9_wA.js} +1 -1
  122. package/server/{github-client-BnIsSrEN.js → github-client-qazkPrg0.js} +3 -3
  123. package/server/github-pr-lookup-DAcVeRLK.js +58 -0
  124. package/server/{harness-binary-DPUi-Pv8.js → harness-binary-BSpiXaLw.js} +2 -2
  125. package/server/{harness-resolve-3lNZkyT4.js → harness-resolve-BAJu5MX8.js} +4 -4
  126. package/server/{health-events-BDOHby5Q.js → health-events-2Z6Wa9SM.js} +1 -1
  127. package/server/{health-events-BBcTYQKU.js → health-events-BwX32k8d.js} +2 -2
  128. package/server/{hooks-DPVX1rwQ.js → hooks-D1g1jKLI.js} +1 -1
  129. package/server/{hooks-CKZgXlJb.js → hooks-DbtbimEf.js} +2 -2
  130. package/server/{hume-Cz8igP9E.js → hume-DXNiu6nQ.js} +2 -2
  131. package/server/hume-VUCNbTgn.js +3 -0
  132. package/server/{idle-stack-reaper-CuLF0j2H.js → idle-stack-reaper-CRA1e0jr.js} +1 -1
  133. package/server/{idle-stack-reaper-DDIRwxUY.js → idle-stack-reaper-Cy-dQzW4.js} +99 -6
  134. package/server/in-flight-guard-D-sSkADM.js +45 -0
  135. package/server/{infra-BEqFxlJc.js → infra-BDQDRopn.js} +81 -5
  136. package/server/{injection-C8aiVJrF.js → injection-6AGnXxlj.js} +7 -7
  137. package/server/{injection-CPUIqNhI.js → injection-Bl8d_jHZ.js} +1 -1
  138. package/server/inspect-agent-CUwVmr2h.js +2 -0
  139. package/server/{inspect-agent-BLuJ1AkT.js → inspect-agent-nXdIptyh.js} +26 -25
  140. package/server/{internal-token-s0-oKybJ.js → internal-token-DzK385vD.js} +2 -2
  141. package/server/{internal-token-BrcU6qfe.js → internal-token-Ias7N8Kg.js} +1 -1
  142. package/server/{io-C9kF1wtJ.js → io-B00PBNku.js} +44 -27
  143. package/server/{issue-closed-CDJk4pIp.js → issue-closed-BehTG1be.js} +8 -8
  144. package/server/{issue-lifecycle-ojZAzDBP.js → issue-lifecycle-Birk-ifu.js} +42 -30
  145. package/server/{issue-lifecycle-B8sZHVHh.js → issue-lifecycle-BqSuQvbn.js} +1 -1
  146. package/server/{issue-service-singleton-BvJ09SaR.js → issue-service-singleton-B2wXiIlw.js} +1 -1
  147. package/server/{issue-service-singleton-Yx5DdmVJ.js → issue-service-singleton-C6ZduvuY.js} +10 -10
  148. package/server/{issues-BMmP_77e.js → issues-DQ5-LatB.js} +2 -2
  149. package/server/{jsonl-parser-BXSlHwEh.js → jsonl-parser-BDpnGFCl.js} +5 -4
  150. package/server/{label-cleanup-B0UJwnv5.js → label-cleanup-Dwnky8FG.js} +3 -3
  151. package/server/{lifecycle-xmBfK2GJ.js → lifecycle-B3s4sM1G.js} +6 -7
  152. package/server/{lifecycle-yPd___5E.js → lifecycle-B4Xdb4cY.js} +2 -2
  153. package/server/{lifecycle-io-DPo7x11I.js → lifecycle-io-CzTHEooc.js} +1 -1
  154. package/server/{lifecycle-io-BREGpnLk.js → lifecycle-io-DCo6s6ks.js} +5 -5
  155. package/server/{linear-client-BsuUvkdA.js → linear-client-9uq7fhiV.js} +3 -3
  156. package/server/{linear-client-CTskEeT_.js → linear-client-CnbtHiic.js} +1 -1
  157. package/server/linear-mcp-auth-BxUOzswy.js +280 -0
  158. package/server/linear-mcp-auth-D46fBCLf.js +2 -0
  159. package/server/memory-fts-worker.js +1 -1
  160. package/server/{merge-CefQNAnq.js → merge-DiW9RdSB.js} +13 -11
  161. package/server/{merge-agent-4WOWlSYA.js → merge-agent-BLUgRsz3.js} +234 -84
  162. package/server/{merge-agent-CKkgIBPB.js → merge-agent-DQTXZk8z.js} +1 -1
  163. package/server/{merge-batch-ClFn2T-y.js → merge-batch-Cp_j9BGk.js} +1 -1
  164. package/server/merge-completeness-B46KqhtY.js +277 -0
  165. package/server/merge-completeness-BLbB5-6n.js +3 -0
  166. package/server/merge-ops-1abz6O_P.js +2 -0
  167. package/server/merge-rebase-CJJVAJuz.js +2 -0
  168. package/server/{merge-rebase-BfaTyXsw.js → merge-rebase-DIkBjgwq.js} +3 -3
  169. package/server/{merge-set-DJ8-UErs.js → merge-set-CBgHfwM0.js} +11 -5
  170. package/server/merge-set-DeUxFsC3.js +3 -0
  171. package/server/{merge-B1DiziGU.js → merge-sr8Nrnmi.js} +1 -1
  172. package/server/merge-sync-CpGmk6Ch.js +3 -0
  173. package/server/{merge-sync-VPV28g0g.js → merge-sync-kr4vqyjM.js} +409 -153
  174. package/server/{merge-train-CHopycP0.js → merge-train-Dmrcus12.js} +18 -8
  175. package/server/{merge-train-deps-D1cOZzy2.js → merge-train-deps-D_uGX91G.js} +21 -15
  176. package/server/merge-verification-NvaAd8CZ.js +220 -0
  177. package/server/merged-docker-cleanup-worker-DH_hrGPI.js +3 -0
  178. package/server/merged-docker-cleanup-worker-Y1R06TEl.js +107 -0
  179. package/server/{multipart-parser-duPAfSBw.js → multipart-parser-R-oC8dQg.js} +2 -2
  180. package/server/{no-resume-mode-Nw29vaFb.js → no-resume-mode-BgD0JUmM.js} +1 -1
  181. package/server/node_modules/.package-lock.json +30 -30
  182. package/server/node_modules/@effect/platform-node/node_modules/@effect/platform-node-shared/package.json +3 -3
  183. package/server/node_modules/@google-cloud/common/package.json +2 -2
  184. package/server/node_modules/@types/node/README.md +1 -1
  185. package/server/node_modules/@types/node/crypto.d.ts +1 -1
  186. package/server/node_modules/@types/node/package.json +2 -2
  187. package/server/node_modules/gaxios/build/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  188. package/server/node_modules/gaxios/build/esm/tsconfig.tsbuildinfo +1 -1
  189. package/server/node_modules/gaxios/package.json +1 -1
  190. package/server/node_modules/google-auth-library/build/src/auth/googleauth.js +24 -19
  191. package/server/node_modules/google-auth-library/package.json +1 -1
  192. package/server/node_modules/google-gax/build/protos/google/api/auth.proto +2 -1
  193. package/server/node_modules/google-gax/build/protos/google/api/backend.proto +12 -1
  194. package/server/node_modules/google-gax/build/protos/google/api/billing.proto +1 -1
  195. package/server/node_modules/google-gax/build/protos/google/api/client.proto +165 -53
  196. package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1/resources.proto +1 -1
  197. package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1beta/quota_adjuster_settings.proto +7 -3
  198. package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1beta/resources.proto +1 -1
  199. package/server/node_modules/google-gax/build/protos/google/api/config_change.proto +1 -1
  200. package/server/node_modules/google-gax/build/protos/google/api/context.proto +1 -1
  201. package/server/node_modules/google-gax/build/protos/google/api/control.proto +3 -3
  202. package/server/node_modules/google-gax/build/protos/google/api/distribution.proto +1 -1
  203. package/server/node_modules/google-gax/build/protos/google/api/documentation.proto +1 -1
  204. package/server/node_modules/google-gax/build/protos/google/api/endpoint.proto +1 -1
  205. package/server/node_modules/google-gax/build/protos/google/api/error_reason.proto +186 -1
  206. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/checked.proto +31 -3
  207. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/eval.proto +8 -9
  208. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/explain.proto +1 -2
  209. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/syntax.proto +20 -22
  210. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/value.proto +2 -3
  211. package/server/node_modules/google-gax/build/protos/google/api/field_behavior.proto +1 -1
  212. package/server/node_modules/google-gax/build/protos/google/api/field_info.proto +1 -1
  213. package/server/node_modules/google-gax/build/protos/google/api/http.proto +1 -1
  214. package/server/node_modules/google-gax/build/protos/google/api/httpbody.proto +1 -1
  215. package/server/node_modules/google-gax/build/protos/google/api/label.proto +1 -1
  216. package/server/node_modules/google-gax/build/protos/google/api/launch_stage.proto +1 -1
  217. package/server/node_modules/google-gax/build/protos/google/api/log.proto +1 -1
  218. package/server/node_modules/google-gax/build/protos/google/api/logging.proto +1 -1
  219. package/server/node_modules/google-gax/build/protos/google/api/metric.proto +1 -1
  220. package/server/node_modules/google-gax/build/protos/google/api/monitored_resource.proto +1 -1
  221. package/server/node_modules/google-gax/build/protos/google/api/monitoring.proto +1 -1
  222. package/server/node_modules/google-gax/build/protos/google/api/policy.proto +1 -1
  223. package/server/node_modules/google-gax/build/protos/google/api/quota.proto +1 -1
  224. package/server/node_modules/google-gax/build/protos/google/api/resource.proto +1 -1
  225. package/server/node_modules/google-gax/build/protos/google/api/routing.proto +8 -4
  226. package/server/node_modules/google-gax/build/protos/google/api/service.proto +2 -3
  227. package/server/node_modules/google-gax/build/protos/google/api/source_info.proto +1 -1
  228. package/server/node_modules/google-gax/build/protos/google/api/system_parameter.proto +1 -1
  229. package/server/node_modules/google-gax/build/protos/google/api/usage.proto +7 -27
  230. package/server/node_modules/google-gax/build/protos/google/api/visibility.proto +1 -1
  231. package/server/node_modules/google-gax/build/protos/google/longrunning/operations.proto +20 -1
  232. package/server/node_modules/google-gax/build/protos/google/protobuf/any.proto +52 -108
  233. package/server/node_modules/google-gax/build/protos/google/protobuf/compiler/ruby/ruby_generated_code.proto +2 -0
  234. package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_features.proto +21 -1
  235. package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_file_options.proto +24 -0
  236. package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_options.proto +15 -0
  237. package/server/node_modules/google-gax/build/protos/google/protobuf/descriptor.proto +118 -30
  238. package/server/node_modules/google-gax/build/protos/google/protobuf/field_mask.proto +16 -18
  239. package/server/node_modules/google-gax/build/protos/google/protobuf/internal_options.proto +8 -1
  240. package/server/node_modules/google-gax/build/protos/google/protobuf/json/json_enumval_custom_string.proto +26 -0
  241. package/server/node_modules/google-gax/build/protos/google/protobuf/json_enumvalue_options.proto +29 -0
  242. package/server/node_modules/google-gax/build/protos/google/protobuf/json_options.proto +14 -0
  243. package/server/node_modules/google-gax/build/protos/google/protobuf/struct.proto +38 -22
  244. package/server/node_modules/google-gax/build/protos/google/protobuf/test_protos/forced_micro_string.proto +3 -0
  245. package/server/node_modules/google-gax/build/protos/google/protobuf/test_protos/repeated_field_proxy_import_message.proto +14 -0
  246. package/server/node_modules/google-gax/build/protos/google/protobuf/timestamp.proto +9 -8
  247. package/server/node_modules/google-gax/build/protos/google/protobuf/util/json_format_proto3.proto +18 -0
  248. package/server/node_modules/google-gax/build/protos/google/rpc/code.proto +1 -1
  249. package/server/node_modules/google-gax/build/protos/google/rpc/context/attribute_context.proto +7 -2
  250. package/server/node_modules/google-gax/build/protos/google/rpc/context/audit_context.proto +1 -1
  251. package/server/node_modules/google-gax/build/protos/google/rpc/error_details.proto +5 -5
  252. package/server/node_modules/google-gax/build/protos/google/rpc/http.proto +1 -1
  253. package/server/node_modules/google-gax/build/protos/google/rpc/status.proto +1 -2
  254. package/server/node_modules/google-gax/build/protos/google/type/calendar_period.proto +1 -1
  255. package/server/node_modules/google-gax/build/protos/google/type/color.proto +9 -10
  256. package/server/node_modules/google-gax/build/protos/google/type/date.proto +11 -9
  257. package/server/node_modules/google-gax/build/protos/google/type/datetime.proto +17 -17
  258. package/server/node_modules/google-gax/build/protos/google/type/dayofweek.proto +1 -1
  259. package/server/node_modules/google-gax/build/protos/google/type/decimal.proto +11 -13
  260. package/server/node_modules/google-gax/build/protos/google/type/expr.proto +1 -1
  261. package/server/node_modules/google-gax/build/protos/google/type/fraction.proto +1 -1
  262. package/server/node_modules/google-gax/build/protos/google/type/interval.proto +1 -2
  263. package/server/node_modules/google-gax/build/protos/google/type/latlng.proto +4 -5
  264. package/server/node_modules/google-gax/build/protos/google/type/localized_text.proto +3 -3
  265. package/server/node_modules/google-gax/build/protos/google/type/money.proto +1 -2
  266. package/server/node_modules/google-gax/build/protos/google/type/month.proto +1 -1
  267. package/server/node_modules/google-gax/build/protos/google/type/phone_number.proto +20 -15
  268. package/server/node_modules/google-gax/build/protos/google/type/postal_address.proto +39 -39
  269. package/server/node_modules/google-gax/build/protos/google/type/quaternion.proto +4 -4
  270. package/server/node_modules/google-gax/build/protos/google/type/timeofday.proto +11 -8
  271. package/server/node_modules/google-gax/package.json +5 -5
  272. package/server/node_modules/retry-request/package.json +4 -4
  273. package/server/node_modules/teeny-request/package.json +5 -5
  274. package/server/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
  275. package/server/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
  276. package/server/node_modules/undici/lib/core/request.js +12 -1
  277. package/server/node_modules/undici/lib/dispatcher/client-h1.js +11 -2
  278. package/server/node_modules/undici/lib/global.js +36 -16
  279. package/server/node_modules/undici/lib/handler/cache-handler.js +236 -116
  280. package/server/node_modules/undici/lib/handler/cache-revalidation-handler.js +3 -3
  281. package/server/node_modules/undici/lib/handler/retry-handler.js +24 -0
  282. package/server/node_modules/undici/lib/interceptor/cache.js +152 -62
  283. package/server/node_modules/undici/lib/interceptor/decompress.js +5 -2
  284. package/server/node_modules/undici/lib/util/cache.js +328 -66
  285. package/server/node_modules/undici/lib/util/date.js +23 -6
  286. package/server/node_modules/undici/lib/web/cookies/util.js +78 -8
  287. package/server/node_modules/undici/package.json +1 -1
  288. package/server/{orphan-proposed-reconciler-BvKkWMJQ.js → orphan-proposed-reconciler-B6vJHxtX.js} +242 -34
  289. package/server/{orphan-proposed-reconciler-CTSGcK8p.js → orphan-proposed-reconciler-DcgTPPFY.js} +1 -1
  290. package/server/package-lock.json +31 -31
  291. package/server/package.json +1 -1
  292. package/server/{pan-dir-B0c3w0Ya.js → pan-dir-DMzF5wne.js} +25 -11
  293. package/server/{paths-D-xWGHdh.js → paths-sJBSb7Ul.js} +2 -2
  294. package/server/pending-lifecycle-claim-DilzGfgk.js +110 -0
  295. package/server/persistent-logger-H_VjuOu7.js +57 -0
  296. package/server/{pickup-DUCLWrJY.js → pickup-DWhqzliO.js} +4 -3
  297. package/server/pipeline-membership-C88VBnhJ.js +3 -0
  298. package/server/pipeline-membership-D9SBvItj.js +57 -0
  299. package/server/pipeline-membership-gather-4eR-7Tqy.js +3 -0
  300. package/server/pipeline-membership-gather-CeVGnNJ2.js +593 -0
  301. package/server/{pipeline-notifier-BXkJzDC4.js → pipeline-notifier-D0ALV6Nw.js} +2 -2
  302. package/server/{pipeline-notifier-BJr8gQX6.js → pipeline-notifier-DT6xLFao.js} +1 -1
  303. package/server/post-merge-lifecycle-worker-8HIeS_J6.js +50 -0
  304. package/server/{project-pipelines-C0MWUeCU.js → project-pipelines-9SKt2bEc.js} +2 -2
  305. package/server/{project-repos-B6GycDh9.js → project-repos-B5pIphKg.js} +7 -3
  306. package/server/project-repos-DPjOdfPa.js +3 -0
  307. package/server/projects-_n3Zui6K.js +3 -0
  308. package/server/{prompts-DxzFXuLl.js → prompts-CgzMBvQF.js} +2 -2
  309. package/server/{providers-u4Ga0jOr.js → providers-BI1mOBQ4.js} +1 -1
  310. package/server/{providers-qQAwGQDW.js → providers-BJQSnAAp.js} +7 -7
  311. package/server/public/assets/{chunk-BO2N2NFS-BamJdinX.js → chunk-BO2N2NFS-D_pGACYN.js} +4 -4
  312. package/server/public/assets/{diagram-OG6HWLK6-DfOB8-w4.js → diagram-OG6HWLK6-BO6fe3hH.js} +1 -1
  313. package/server/public/assets/{dist-ByIuzMQW.js → dist-5zingMiA.js} +3 -3
  314. package/server/public/assets/{dist-D7zYSEA6.js → dist-CVxhbI2E.js} +1 -1
  315. package/server/public/assets/{ganttDiagram-6RSMTGT7-EdmRyXL3.js → ganttDiagram-6RSMTGT7-Bgj4A7kp.js} +1 -1
  316. package/server/public/assets/{highlighted-body-OFNGDK62-MerOfTX8.js → highlighted-body-OFNGDK62-DNPK7_cm.js} +1 -1
  317. package/server/public/assets/index-DEpY-D1O.js +807 -0
  318. package/server/public/assets/index-Ddkcz3WT.css +1 -0
  319. package/server/public/assets/{linear-BBbArIcK.js → linear-BB0jQT16.js} +1 -1
  320. package/server/public/assets/mermaid-GHXKKRXX-BzPfaQJ1.js +1 -0
  321. package/server/public/assets/{pieDiagram-4H26LBE5-BKIddiJN.js → pieDiagram-4H26LBE5-DeGIzzpZ.js} +1 -1
  322. package/server/public/assets/{prod-B8vYiu52.js → prod-XBLvikex.js} +4 -4
  323. package/server/public/assets/{quadrantDiagram-W4KKPZXB-D6RtoULk.js → quadrantDiagram-W4KKPZXB-BV9SLeWz.js} +1 -1
  324. package/server/public/assets/{sankeyDiagram-5OEKKPKP-CLrCedZX.js → sankeyDiagram-5OEKKPKP-Dwja0gj8.js} +1 -1
  325. package/server/public/assets/{xychartDiagram-2RQKCTM6-hznR8DYl.js → xychartDiagram-2RQKCTM6-DCB4qvfw.js} +1 -1
  326. package/server/public/index.html +2 -2
  327. package/server/{pull-requests-_lHueOHW.js → pull-requests-7BgLDsIo.js} +19 -10
  328. package/server/{pull-requests-7N8WKseP.js → pull-requests-Dq0y6IxE.js} +1 -1
  329. package/server/{queries-CjNIf-Ub.js → queries-C-MUZaIy.js} +824 -192
  330. package/server/{rally-CyWT_lNO.js → rally-CPysR4T_.js} +3 -3
  331. package/server/rally-D_VW4bJv.js +3 -0
  332. package/server/{rally-api-C8e_1JRi.js → rally-api-8Ptlp2os.js} +2 -2
  333. package/server/{rally-api-ClOSd-Tt.js → rally-api-CgosKdKL.js} +1 -1
  334. package/server/{rally-client-sgtVqzNU.js → rally-client-CCX0hADj.js} +1 -1
  335. package/server/{rally-client-lc-nokqr.js → rally-client-iYMHWQD7.js} +5 -5
  336. package/server/{reap-terminal-sessions-7KeHv1R1.js → reap-terminal-sessions-4Xg9NGA8.js} +2 -2
  337. package/server/{reap-terminal-sessions-BtHPo6Xc.js → reap-terminal-sessions-Qf-9LVQc.js} +2 -2
  338. package/server/{rebuild-stack-lwtsW0GO.js → rebuild-stack-CMdgaylx.js} +16 -7
  339. package/server/{rebuild-stack-CFvcBHsS.js → rebuild-stack-DqcOVmJO.js} +2 -2
  340. package/server/{reconstruct-cache-C3Ui7ojV.js → reconstruct-cache-CMZJ_Vjq.js} +9 -7
  341. package/server/record-BTwWhtBb.js +3 -0
  342. package/server/{record-update-dE1Aw83j.js → record-update-DaOKS7Wb.js} +124 -157
  343. package/server/record-update-DhI7eySG.js +3 -0
  344. package/server/{recovery-trip-BvRWfUXM.js → recovery-trip-DDH-02ng.js} +2 -2
  345. package/server/{release-engine-Cx48W7z8.js → release-engine-B2X9AfFC.js} +5 -5
  346. package/server/{release-set-CQfuN9-v.js → release-set-G4XKyQ8K.js} +3 -3
  347. package/server/{remote-C_2hasl7.js → remote-BXarMvqf.js} +3 -3
  348. package/server/{remote-ChgimyYa.js → remote-CUhyqjXk.js} +3 -3
  349. package/server/{remote-agents-By1ige5O.js → remote-agents-Bba-MgSA.js} +311 -48
  350. package/server/{remote-agents-CA3Ihlxj.js → remote-agents-CLZB3Coc.js} +2 -2
  351. package/server/{remote-completion-fVe9e_3u.js → remote-completion-CwAp79EC.js} +13 -13
  352. package/server/resume-mOREjlYu.js +3 -0
  353. package/server/{review-agent-CIGOQYBD.js → review-agent--pOptAEV.js} +65 -50
  354. package/server/{review-agent-DNboMyW4.js → review-agent-BTCNkq2h.js} +1 -1
  355. package/server/{review-artifacts-nRDPuMxd.js → review-artifacts-BNmw8dWE.js} +7 -6
  356. package/server/{review-context-2uj-YQ8J.js → review-context-DFxYOOJW.js} +9 -9
  357. package/server/{in-flight-guard-CAAkqPLu.js → review-convoy-liveness-BgU_1jJf.js} +3 -44
  358. package/server/review-status-CGvlA4o_.js +7737 -0
  359. package/server/review-status-Dma9SFaN.js +3 -0
  360. package/server/review-status-emit-DURrmGYX.js +50 -0
  361. package/server/{review-status-enrichment-Dci-ZwEt.js → review-status-enrichment-8NiNXi5W.js} +4 -4
  362. package/server/review-status-source-BLx_1siH.js +3 -0
  363. package/server/{review-status-source-CxuEjLGg.js → review-status-source-DIk1JSow.js} +24 -3
  364. package/server/{review-status-sync-BTOpxtTC.js → review-status-sync-0GxOqeqF.js} +11 -6
  365. package/server/{review-status-sync-DSDK3OMy.js → review-status-sync-muT-EBp2.js} +1 -1
  366. package/server/{review-verdict-feedback-CTd4QMBu.js → review-verdict-feedback-HwF6ODFy.js} +1 -1
  367. package/server/{review-verdict-feedback-CCjVA7Uk.js → review-verdict-feedback-xJNdqBnX.js} +65 -11
  368. package/server/{rollup-FGsKki9R.js → rollup-BQaol5TX.js} +8 -8
  369. package/server/{rollup-BKQC7jp4.js → rollup-BR4t_wck.js} +1 -1
  370. package/server/{scanner-CnqEZeDy.js → scanner-C-D2cFdX.js} +8 -7
  371. package/server/{sequence-io-D909zgeH.js → sequence-io-Bmf1EZM5.js} +4 -4
  372. package/server/{sequencer-agent-CzkcK6P5.js → sequencer-agent-CigMyKh2.js} +7 -6
  373. package/server/server.js +2005 -614
  374. package/server/{service-CLeAoU6G.js → service-CD0DvFvD.js} +114 -68
  375. package/server/{session-fork-CHI1XvL9.js → session-fork-B6cQ5eya.js} +2 -2
  376. package/server/{session-history-BT3IvqhC.js → session-history-3ON_IJjm.js} +7 -55
  377. package/server/settings-CVmWiJ-r.js +2 -0
  378. package/server/{settings-C0zgxOGf.js → settings-PilbFZAM.js} +3 -2
  379. package/server/{settings-BTMvpNFU.js → settings-VN0qOh4U.js} +3 -3
  380. package/server/{shadow-engineering-BJfgm-hX.js → shadow-engineering-DXvx77oE.js} +3 -3
  381. package/server/{shadow-state-7a-524T8.js → shadow-state-BBk_zacw.js} +2 -2
  382. package/server/{shadow-state-DnDyjujH.js → shadow-state-CZBp7pwq.js} +1 -1
  383. package/server/{shadow-utils-Db3oroFG.js → shadow-utils-DdZ05AJS.js} +2 -2
  384. package/server/{ship-log-D9MlVzYU.js → ship-log-7kJfjUX2.js} +1 -1
  385. package/server/{smart-compaction-CV2ukKND.js → smart-compaction-8fA5k5XN.js} +1 -1
  386. package/server/{smart-compaction-KCYScAU1.js → smart-compaction-ByhL9cRS.js} +8 -7
  387. package/server/{spawn-planning-session-DKpdFyOC.js → spawn-planning-session-pcF-YeFE.js} +38 -50
  388. package/server/spawn-planning-session-vLUJoFjy.js +3 -0
  389. package/server/{specialist-completion-Ky9wCDpb.js → specialist-completion-B07XjWD2.js} +1 -1
  390. package/server/{specialist-context-Bn3dCGS2.js → specialist-context-DJ-zFyL3.js} +5 -5
  391. package/server/{specialist-handoff-logger-CMEwYxa2.js → specialist-handoff-logger-aKSxbIgD.js} +2 -2
  392. package/server/{specialist-logs-C5p8G7tx.js → specialist-logs-Dae-n52_.js} +3 -3
  393. package/server/{specialists-CC4r-c2s.js → specialists-6PWdtWcg.js} +1177 -311
  394. package/server/{specialists-DM6bWjsi.js → specialists-Bz-wFT7R.js} +1 -1
  395. package/server/{specialists-jiuuyVkp.js → specialists-DUiExaxb.js} +18 -18
  396. package/server/{specialists-CShuVGWT.js → specialists-KR3pKulN.js} +1 -1
  397. package/server/specs--enInima.js +3 -0
  398. package/server/src-B948bGVR.js +11452 -0
  399. package/server/{src-CXmtj0im.js → src-rac0Mepv.js} +6 -6
  400. package/server/{src-CdmnTM2m.js → src-vHy2-oCv.js} +3 -3
  401. package/server/{stack-health-Bn3o8FBX.js → stack-health-BikqHSjn.js} +1 -1
  402. package/server/{stack-health-DxSdG9cW.js → stack-health-DqLTiEu4.js} +7 -5
  403. package/server/{stale-task-claims-KJKxxiiQ.js → stale-task-claims-Cab_Fo_Z.js} +5 -4
  404. package/server/{staleness-DPm7ClwA.js → staleness-C-a5rBxF.js} +1 -1
  405. package/server/{staleness-CT34nwr9.js → staleness-CGTZyp6V.js} +1 -1
  406. package/server/{state-home-Cn_Qu8IX.js → state-home-722Dkx92.js} +2 -2
  407. package/server/{state-mapping-BL_-3Yt8.js → state-mapping-CybR81Uf.js} +1 -1
  408. package/server/{state-migrate-BGg2ZaOt.js → state-migrate-2u8wIpoL.js} +4 -4
  409. package/server/{stuck-remediation-DYuPRYYw.js → stuck-remediation-CC6q3eqQ.js} +84 -26
  410. package/server/{stuck-remediation-s9_2qfi6.js → stuck-remediation-r1TJu68G.js} +1 -1
  411. package/server/{substrate-bug-weights-service-B-FwCs5V.js → substrate-bug-weights-service-CzIyyWFT.js} +3 -3
  412. package/server/{summary-fork-DpcxN9tf.js → summary-fork-BqgmgPDt.js} +2 -2
  413. package/server/{summary-fork-D1RAk9rJ.js → summary-fork-Df2WFijM.js} +8 -8
  414. package/server/supervisor/{injection-budget-Csw5tqfJ.js → injection-budget-Cg_KIb3o.js} +1 -1
  415. package/server/supervisor/{paths-DehGY2Fj.js → paths-BBOnCYlO.js} +10 -2
  416. package/server/supervisor/pty-supervisor.js +50 -4
  417. package/server/supervisor/{pty-token-x-ea28Us.js → pty-token-BbjwEUUM.js} +3 -3
  418. package/server/supervisor-B4anCCSm.js +2 -0
  419. package/server/{supervisor-wM4ooB5O.js → supervisor-BxqSpjcN.js} +3 -3
  420. package/server/{swarm-readiness-DlP_UjHq.js → swarm-readiness-DfFxa9-n.js} +3 -3
  421. package/server/{system-prerequisites-DUDj1YHf.js → system-prerequisites-ICLSkhhZ.js} +2 -2
  422. package/server/{teardown-workspace-DeTTImn-.js → teardown-workspace-DRdi_CPa.js} +15 -16
  423. package/server/{test-agent-queue-MbxX5vqq.js → test-agent-queue-A_-p-i0I.js} +8 -7
  424. package/server/{test-verdict-gscuksPv.js → test-verdict-BHgTAwIG.js} +1 -1
  425. package/server/{test-verdict-bWYBtqBg.js → test-verdict-C4i9aqEP.js} +1 -1
  426. package/server/{tldr-daemon-BYFkts95.js → tldr-daemon-CvWXBhX1.js} +1 -1
  427. package/server/{tldr-daemon-C-69c3OI.js → tldr-daemon-wvsU6bKv.js} +2 -2
  428. package/server/{tmux-R7N7y-Mh.js → tmux-CfDHob9M.js} +174 -4
  429. package/server/{tmux-B6z7h_vZ.js → tmux-Ctk6RV5l.js} +1 -1
  430. package/server/{token-BK6t8BIT.js → token-DrxwytsZ.js} +2 -2
  431. package/server/{token-util-DxXu-vd1.js → token-util-B48BlCba.js} +1 -1
  432. package/server/token-util-D411CAq9.js +3 -0
  433. package/server/{tracker-config-G6latr8C.js → tracker-config-BcPMElRB.js} +2 -2
  434. package/server/{tracker-utils-DHv5pSDT.js → tracker-utils-CJ6BHjcD.js} +3 -3
  435. package/server/{traefik-connect-Df2uXPsQ.js → traefik-connect-DZ6nMYMj.js} +1 -1
  436. package/server/{tunnel-DGdUkGoZ.js → tunnel-Bp-dRHZz.js} +1 -1
  437. package/server/{tunnel-CDVKJpRv.js → tunnel-DUbUPElU.js} +3 -3
  438. package/server/{typed-errors-BHhE85Ul.js → typed-errors-CKeoDdU3.js} +1 -1
  439. package/server/{typed-errors-2BTMxkZq.js → typed-errors-DyjseiB6.js} +1 -1
  440. package/server/{types-hraoaQIp.js → types-Cm5ASJbq.js} +1 -1
  441. package/server/{types-5M0lupud.js → types-Di8wdakq.js} +1 -1
  442. package/server/uat-promote-verification-CoJuh7XO.js +92 -0
  443. package/server/uat-train-4Q0k06CQ.js +0 -0
  444. package/server/uat-train-Dewoatps.js +2 -0
  445. package/server/{validate-IfDGkaVR.js → validate-B0C8p66t.js} +8 -6
  446. package/server/{validation-Dw1pzDzr.js → validation-B_kIRfNe.js} +4 -4
  447. package/server/{validation-CC9OPqJZ.js → validation-hVOwQk8T.js} +1 -1
  448. package/server/{verification-artifact-B3itKTxu.js → verification-artifact-BxlxTiYK.js} +1 -1
  449. package/server/{verification-artifact-TVFSz_q1.js → verification-artifact-D70AtckY.js} +1 -1
  450. package/server/{verification-runner-BW4HOKAz.js → verification-runner-Bc3cJvpC.js} +1 -1
  451. package/server/{verification-runner-CkgX6EtI.js → verification-runner-KZTz6u72.js} +40 -18
  452. package/server/{vtt-parser-DYPbKcIJ.js → vtt-parser-CE_i3Bcw.js} +1 -1
  453. package/server/{work-agent-prompt-Du8Vh2aJ.js → work-agent-prompt-ByrPAzOy.js} +20 -9
  454. package/server/{workflows-mbQzh6mQ.js → workflows-D0vP1WrK.js} +252 -99
  455. package/server/workflows-DtDX08ed.js +2 -0
  456. package/server/{workspace--8tnhDI0.js → workspace-COWvdibH.js} +1 -1
  457. package/server/{workspace-BPUws1-_.js → workspace-DQkRPLlQ.js} +3 -3
  458. package/server/workspace-anchor-drift-DXYIAHzr.js +224 -0
  459. package/server/workspace-anchor-drift-pwz23TI1.js +3 -0
  460. package/server/{workspace-config-CIVWZOQr.js → workspace-config-BdZM4PvZ.js} +1 -1
  461. package/server/{workspace-manager-CBhNdzE4.js → workspace-manager-BPgTrG66.js} +157 -118
  462. package/server/workspace-manager-C1F3XsnS.js +3 -0
  463. package/server/{workspace-metadata-BzHnwwX4.js → workspace-metadata-CdjzAP8x.js} +1 -1
  464. package/server/{workspace-metadata-_WygnyoX.js → workspace-metadata-DkO_iTLo.js} +2 -2
  465. package/server/{workspaces-Do_9J0LQ.js → workspaces-CRBJikLp.js} +749 -299
  466. package/sync-sources/dev-skills/pan-dashboard-restart/SKILL.md +34 -10
  467. package/sync-sources/hooks/linear-mcp-auth-hook +97 -0
  468. package/sync-sources/rules/ask-user-question-self-contained.md +9 -0
  469. package/sync-sources/skills/pan-close/SKILL.md +5 -3
  470. package/sync-sources/skills/pan-merge/SKILL.md +7 -5
  471. package/sync-sources/skills/pan-peer-review/SKILL.md +195 -0
  472. package/sync-sources/skills/pan-release/SKILL.md +57 -0
  473. package/sync-sources/skills/pan-reload/SKILL.md +3 -3
  474. package/sync-sources/skills/pan-restart/SKILL.md +5 -4
  475. package/sync-sources/skills/pan-review/SKILL.md +7 -1
  476. package/sync-sources/skills/pan-sync-main/SKILL.md +9 -8
  477. package/sync-sources/skills/pipeline-status/SKILL.md +10 -1
  478. package/sync-sources/skills/workspace-add-repo/SKILL.md +16 -1
  479. package/server/activity-logger-OEUBdfZ8.js +0 -3
  480. package/server/agent-state-Bc3tzUC8.js +0 -3
  481. package/server/agent-state-service-D5UGKIJL.js +0 -2
  482. package/server/agents-wi9NWzs2.js +0 -3
  483. package/server/archive-planning-BZDgzoEj.js +0 -3
  484. package/server/cli-B86rejVV.js +0 -3
  485. package/server/conflict-gate-BhAY7TUd.js +0 -3
  486. package/server/conversation-service-BLd3MYsG.js +0 -3
  487. package/server/deacon-kiQrb4ZT.js +0 -4
  488. package/server/deacon-swarm-C-GwfjJy.js +0 -2
  489. package/server/deploy-window-DkZraDyj.js +0 -3
  490. package/server/dist-Bzy0y69j.js +0 -3
  491. package/server/dod-C6n9RV3G.js +0 -66
  492. package/server/flywheel-merge-order-UU0tiUld.js +0 -245
  493. package/server/flywheel-merge-order-UUcsCDb6.js +0 -2
  494. package/server/forge-CNFnyqps.js +0 -270
  495. package/server/forge-DXnS_YFH.js +0 -2
  496. package/server/git-utils-D0_yhCgF.js +0 -3
  497. package/server/github-pr-lookup-D16NGsSO.js +0 -44
  498. package/server/hume-ccTf4wTX.js +0 -3
  499. package/server/inspect-agent-Brtz0URj.js +0 -2
  500. package/server/label-reconciler-B4iOAjlC.js +0 -113
  501. package/server/launcher-generator-BBWG_SiS.js +0 -473
  502. package/server/merge-ops-CR44oU9-.js +0 -2
  503. package/server/merge-set-bUqbm3yc.js +0 -3
  504. package/server/merge-sync-BJYeOODr.js +0 -3
  505. package/server/merge-verification-BkaOqPsw.js +0 -124
  506. package/server/pipeline-membership-mY3GprlK.js +0 -420
  507. package/server/project-repos-DXKQsc_m.js +0 -3
  508. package/server/projects-vjjcmiN8.js +0 -3
  509. package/server/public/assets/index-CWeMerGO.js +0 -804
  510. package/server/public/assets/index-CenuJ3Cw.css +0 -1
  511. package/server/public/assets/mermaid-GHXKKRXX-CpPWAFwE.js +0 -1
  512. package/server/rally-zhhtXuDP.js +0 -3
  513. package/server/record-CN0pj4_5.js +0 -3
  514. package/server/record-update-bLJxAXH4.js +0 -3
  515. package/server/resume-BFv1PGpA.js +0 -3
  516. package/server/review-status-btEYlaxA.js +0 -3
  517. package/server/review-status-kZj_KyCk.js +0 -1470
  518. package/server/settings-66A6uvIA.js +0 -2
  519. package/server/spawn-planning-session-qUC2VWu3.js +0 -2
  520. package/server/specs-C8Wsn2XO.js +0 -3
  521. package/server/src-DtcUOZCq.js +0 -4826
  522. package/server/supervisor-FTJAIsAK.js +0 -2
  523. package/server/token-util-DuPuNriz.js +0 -3
  524. package/server/tracker-utils-B4ylXZQK.js +0 -3
  525. package/server/uat-train-B05BWkp-.js +0 -2
  526. package/server/uat-train-DDSrSoV1.js +0 -2152
  527. package/server/workflows-BQu4RFQ-.js +0 -2
  528. package/server/workspace-manager-CWT55qeI.js +0 -3
package/server/server.js CHANGED
@@ -1,110 +1,115 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "./rolldown-runtime-DNCzzKnI.js";
2
- import { A as canUseHarnessSync, Br as getOverdeckHome, Cr as LOGS_DIR, D as resolveTieredExecutionEnabled, E as resolveTieredExecutionBlock, Et as getIssueWorkspacePath, Fn as renameProject, Fr as getDocsDisableStatePath, Gr as sessionFilePath, H as init_providers, Hr as isDevMode, Ht as flushAutoCommits, I as UnknownModelError, Ir as getDocsIndexPath, J as defaultBackgroundAiFeatures, Jn as extractPrefixSync, Ln as resolveInfraRepo, Lr as getDocsTelemetryPath, M as init_harness_policy, Nr as encodeClaudeProjectDir, Nt as readIssueRecordSync, On as listProjects, Ot as init_record, Pn as registerProjectSync, Pr as getDocsBudgetStatePath, Q as computeModelOrigin, R as getBuiltInDefaultHarness, Rn as resolveProjectFromIssue, T as init_tier_table, Tn as init_projects, Un as setProjectAutoMergeDefaultSync, Ur as packageRoot, Ut as init_auto_commit, V as getProviderForModelSync, Vr as init_paths, Vt as flushAllPendingAutoCommits, Wn as setProjectSwarmPolicySync, X as DEFAULT_ROLES, Y as init_registry, Yn as init_issue_id, Z as DEFAULT_WORKHORSES, Zn as parseIssueIdSync, _ as getDefaultDocsConfig, at as getModelEffortLevelsSync, br as CLAUDE_DIR, cn as ProjectRenameError, ct as modelSupportsImagesSync, d as loadConfigNoMigration, er as require_dist$1, f as loadConfigSync, fn as findProjectByPathSync, fr as TrackerError, g as mergeConfigs, gn as getIssuePrefix, h as jsYaml, hr as AGENTS_DIR, ht as ROLE_EFFORTS, i as getConversationsConfig, ir as FsError, jn as loadProjectsConfigSync, jr as SYNC_SOURCES, k as validateTieredExecutionConfig, kn as listProjectsSync, lt as resolveModelIdSync, m as init_js_yaml, mn as findProjectByTeamSync, mr as init_errors, mt as PARENT_MODEL_REF, n as clearConfigCache, nn as init_state_read_home, nt as MODEL_CAPABILITIES, o as getGlobalConfigPath, or as GitHubApiError, ot as hasModelCapabilitySync, p as updateConversationsConfig, pt as COMPLIANCE_MODES, r as getConversationSearchConfigSync, rn as resolveStateReadHomeSync, rt as MODEL_DEPRECATIONS, s as init_load, st as init_model_capabilities, t as init_config_yaml, tt as resolveModel, u as isTldrEnabledSync, un as extractTeamPrefix, ur as ProcessSpawnError, v as getDefaultTtsDaemonConfig, w as TieredExecutionConfigError, wr as OVERDECK_HOME, y as mergeTtsDaemonConfigs, yn as getProjectSync, z as getKimiAnthropicBaseUrl, zn as resolveProjectFromIssueSync, zr as getLegacyHome } from "./config-yaml-DySPNmKN.js";
3
- import { a as shouldRefuseHostDashboardPort, i as readHostDashboardApiPort, n as getDashboardIdentity, r as isNonPrimaryCheckoutRoot } from "./deacon-nudge-log-6wVqAaqz.js";
4
- import { $ as panCliInvocation, A as resolveAllowedHarness, B as cleanupUnreferencedConversationAttachments, C as handleConversationCreate, Ct as jsonStringResponse, D as handleConversationStop, E as handleConversationResume, F as tmuxSessionExists, G as isManagedConversationAttachmentPath, H as extractConversationAttachmentPaths, I as waitForPiTuiReady, J as evaluateSpawnGuardrails, K as removeConversationAttachment, L as waitForTmuxSession$1, M as spawnConversationSession, N as startConversationModelBackfill, O as handleConversationSwitchModel, P as stopConversationRuntime, R as cleanupConversationAttachments, S as conversationSessionAliveFromState, St as jsonResponse, T as handleConversationRestartAll, U as getConversationAttachmentsRoot, V as ensureConversationAttachmentDir, W as hasConversationAttachment, X as resolveAgentGitInfo, Y as invalidateAgentsCache, Z as resolveConversationGitInfo, _ as isPiControlChannelHarness, _t as isTrustedOriginForHost, a as agentsRouteLayer, at as resolveCodexRolloutPath, b as validateConversationControlAckOrigin, bt as httpHandler, c as codexConversationPendingInput, ct as dashboardCsrfToken, d as handleConversationCodexApproval, dt as rejectUnauthorizedDashboardRequest, f as handleConversationCompact, ft as rejectUnauthorizedDashboardSessionMintRequest, g as handleConversationThinkingLevel, gt as getHeaderFromMap, h as handleConversationPlanAction, ht as waitForSessionRespawn, it as resolveClaudeSessionId, j as shouldReportUnresolvedLiveSession, k as isInsideGitWorkTree, l as deliverConversationViaControlChannel, lt as dashboardSessionCookieHeader, m as handleConversationDeliveryMethod, mt as isRespawnPending, nt as resolveAcpTranscriptPath, o as saveAgentStateAndEmitEvent, ot as resolveJsonlPath, p as handleConversationControlAck, pt as rejectUnsafeDashboardMutationRequest, q as runInBatches, rt as resolveAgentHarness, s as saveAgentStateAndEmitEventProgram, st as resolvePiSessionPath, t as specialistsRouteLayer, tt as readLauncherPinnedSessionId, u as handleConversationAbort, ut as hasDashboardInternalToken, v as pickDeliverAs, vt as validateOrigin$1, w as handleConversationDelete, x as conversationNeedsRunningRepair, xt as getDefaultCwd, y as resolveConversationDeliveryMethod, yt as validateOriginHeaders, z as cleanupOrphanedConversationAttachments } from "./specialists-CC4r-c2s.js";
5
- import { a as init_activity_logger, i as emitDashboardLifecycleSync, n as emitActivityEntrySync, r as emitActivityTtsSync } from "./activity-logger-DDlOIAML.js";
6
- import { A as init_paths$1, O as OVERDECK_MIGRATION_PATH, f as init_infra, i as DbLive, k as getOverdeckDatabasePath, u as TmuxLive } from "./infra-BEqFxlJc.js";
2
+ import { A as canUseHarnessSync, An as extractTeamPrefix, Cn as resolveStateReadHomeSync, D as resolveTieredExecutionEnabled, Dr as GitHubApiError, E as resolveTieredExecutionBlock, Fr as init_errors, Gn as init_projects, Gr as OVERDECK_HOME, H as init_providers, I as UnknownModelError, In as getIssuePrefix, Ir as AGENTS_DIR, J as defaultBackgroundAiFeatures, Jn as listProjects, M as init_harness_policy, Mn as findProjectByPathSync, Mt as getIssueWorkspacePath, Nr as TrackerError, On as ProjectRenameError, Pn as findProjectByTeamSync, Pt as init_record, Q as computeModelOrigin, Qr as SYNC_SOURCES, R as getBuiltInDefaultHarness, Sn as init_state_read_home, T as init_tier_table, Tr as FsError, V as getProviderForModelSync, Vr as CLAUDE_DIR, Wr as LOGS_DIR, X as DEFAULT_ROLES, Y as init_registry, Yn as listProjectsSync, Z as DEFAULT_WORKHORSES, Zn as loadProjectsConfigSync, _ as getDefaultDocsConfig, ai as getDocsTelemetryPath, ar as resolveProjectFromIssueSync, at as getModelEffortLevelsSync, ci as getOverdeckHome, cn as flushAutoCommits, ct as modelSupportsImagesSync, d as loadConfigNoMigration, di as packageRoot, dr as setProjectSwarmPolicySync, er as registerProjectSync, f as loadConfigSync, g as mergeConfigs, gr as init_issue_id, gt as ROLE_EFFORTS, h as jsYaml, hr as extractPrefixSync, ht as PARENT_MODEL_REF, i as getConversationsConfig, ii as getDocsIndexPath, ir as resolveProjectFromIssue, jr as ProcessSpawnError, k as validateTieredExecutionConfig, li as init_paths, ln as init_auto_commit, lr as setProjectAutoMergeDefaultSync, lt as resolveModelIdSync, m as init_js_yaml, mt as COMPLIANCE_MODES, n as clearConfigCache, ni as getDocsBudgetStatePath, nt as MODEL_CAPABILITIES, o as getGlobalConfigPath, ot as hasModelCapabilitySync, p as updateConversationsConfig, pi as sessionFilePath, r as getConversationSearchConfigSync, ri as getDocsDisableStatePath, rr as resolveInfraRepo, rt as MODEL_DEPRECATIONS, s as init_load, si as getLegacyHome, sn as flushAllPendingAutoCommits, st as init_model_capabilities, t as init_config_yaml, ti as encodeClaudeProjectDir, tr as renameProject, tt as resolveModel, u as isTldrEnabledSync, ui as isDevMode, ur as setProjectMergeTrainSync, v as getDefaultTtsDaemonConfig, vr as parseIssueIdSync, w as TieredExecutionConfigError, xr as require_dist$1, y as mergeTtsDaemonConfigs, z as getKimiAnthropicBaseUrl, zn as getProjectSync, zt as readIssueRecordSync } from "./config-yaml-CxdwhAIA.js";
3
+ import { a as shouldRefuseHostDashboardPort, i as readHostDashboardApiPort, n as getDashboardIdentity, r as isNonPrimaryCheckoutRoot } from "./deacon-nudge-log-B08gF0mh.js";
4
+ import { $ as composerCommandConfirmationFromBody, A as resolveAllowedHarness, At as jsonResponse, B as cleanupUnreferencedConversationAttachments, C as handleConversationCreate, Ct as waitForSessionRespawn, D as handleConversationStop, Dt as validateOriginHeaders, E as handleConversationResume, Et as validateOrigin$1, F as tmuxSessionExists, G as isManagedConversationAttachmentPath, H as extractConversationAttachmentPaths, I as waitForPiTuiReady, J as composerCommandResultHttpStatus, K as removeConversationAttachment, L as waitForTmuxSession$1, M as spawnConversationSession, N as startConversationModelBackfill, O as handleConversationSwitchModel, Ot as httpHandler, P as stopConversationRuntime, Q as ComposerCommandConfirmationError, R as cleanupConversationAttachments, S as conversationSessionAliveFromState, St as isRespawnPending, T as handleConversationRestartAll, Tt as isTrustedOriginForHost, U as getConversationAttachmentsRoot, V as ensureConversationAttachmentDir, W as hasConversationAttachment, X as resolvePolicy, Y as handleComposerCommandMessage, Z as ComposerCommandParseError, _ as isPiControlChannelHarness, _t as dashboardSessionCookieHeader, a as agentsRouteLayer, at as resolveConversationGitInfo, b as validateConversationControlAckOrigin, bt as rejectUnauthorizedDashboardSessionMintRequest, c as codexConversationPendingInput, d as handleConversationCodexApproval, dt as resolveAgentHarness, et as evaluateSpawnGuardrails, f as handleConversationCompact, ft as resolveClaudeSessionId, g as handleConversationThinkingLevel, gt as dashboardCsrfToken, h as handleConversationPlanAction, ht as resolvePiSessionPath, it as resolveAgentGitInfo, j as shouldReportUnresolvedLiveSession, jt as jsonStringResponse, k as isInsideGitWorkTree, kt as getDefaultCwd, l as deliverConversationViaControlChannel, lt as readLauncherPinnedSessionId, m as handleConversationDeliveryMethod, mt as resolveJsonlPath, nt as isInternalAgentRequest, o as saveAgentStateAndEmitEvent, p as handleConversationControlAck, pt as resolveCodexRolloutPath, q as runInBatches, rt as resolveRequestedStartedBy, s as saveAgentStateAndEmitEventProgram, st as panCliInvocation, t as specialistsRouteLayer, tt as invalidateAgentsCache, u as handleConversationAbort, ut as resolveAcpTranscriptPath, v as pickDeliverAs, vt as hasDashboardInternalToken, w as handleConversationDelete, wt as getHeaderFromMap, x as conversationNeedsRunningRepair, xt as rejectUnsafeDashboardMutationRequest, y as resolveConversationDeliveryMethod, yt as rejectUnauthorizedDashboardRequest, z as cleanupOrphanedConversationAttachments } from "./specialists-6PWdtWcg.js";
5
+ import { a as emitDashboardLifecycleSync, i as emitActivityTtsSync, o as init_activity_logger, r as emitActivityEntrySync } from "./activity-logger-CyteR4JO.js";
6
+ import { A as init_paths$1, O as OVERDECK_MIGRATION_PATH, f as init_infra, i as DbLive, k as getOverdeckDatabasePath, u as TmuxLive } from "./infra-BDQDRopn.js";
7
7
  import { n as openDatabase, t as init_driver } from "./driver-CKVGi51s.js";
8
- import { n as updateIssueRecord, t as init_record_update } from "./record-update-dE1Aw83j.js";
9
- import { $ as init_child_env, B as sendKeysAsync, C as getWindowDimensions, D as killSession, H as sessionExists, J as init_ui_theme, L as resizeWindow, M as listSessionNames, Q as buildChildEnvWithoutTmuxSync, T as isHarnessProcessAlive, Y as setUiTheme, Z as buildChildEnvSync, d as ensureManagedTmuxContextOnce, i as capturePane, l as deliveryVerifyLine, n as buildTmuxArgs, o as capturePaneText, s as createSession, w as init_tmux, z as sendKeys } from "./tmux-R7N7y-Mh.js";
10
- import { a as init_event_store, i as initEventStore, n as getEventStore } from "./event-store-CQL6NeqB.js";
11
- import { A as AgentSnapshot, C as EDITORS, D as MemoryObservation, S as FlywheelStatus, T as SystemHealthSnapshot, b as WS_METHODS, c as ContextLayerSaveRequest, f as FlywheelStats, j as ReviewStatusSnapshot, k as AgentRuntimeSnapshot, l as ContextPreviewRequest, n as compareIssueIds, o as getHarnessBehavior, t as init_src, v as PanRpcError, w as DomainEvent, x as FlywheelRunId, y as PanRpcGroup } from "./src-DtcUOZCq.js";
12
- import { a as fixStuckCommentedReviews, b as setReviewStatusSync, c as getReviewStatusSync, d as loadReviewStatuses, l as init_review_status, n as clearReviewStatus, o as fixStuckReadyForMerge, p as markWorkspaceStuck, r as clearStuckMergeStatuses, s as getReviewStatus, u as loadReadyForMergeFlags, y as setReviewStatus } from "./review-status-kZj_KyCk.js";
13
- import { a as EventStoreService, c as ReadModelService, d as init_read_model, f as toAgentResolution, i as init_issue_lifecycle, l as ReadModelServiceLive, m as toRole, n as IssueLifecycleLive, o as EventStoreServiceLive, p as toAgentStatus, s as init_domain_services, t as IssueLifecycle } from "./issue-lifecycle-ojZAzDBP.js";
14
- import { c as listAllAgentsSync, n as AgentsResolverLive, o as init_agents } from "./agents-EbI0zCJl.js";
15
- import { i as init_paths$2, l as resolveObservationsFile, r as ensureParentDir, s as resolveIssueMemoryRoot, t as assertMemorySafeSegment } from "./paths-D-xWGHdh.js";
16
- import { i as recordBriefingSessionStart, n as ensureSessionContextBriefingFile, r as init_briefing_freshness, t as appendFreshBriefingUpdate } from "./briefing-freshness-D1wzUIWv.js";
17
- import { n as listFeatureRegistryEntries, t as init_feature_registry_storage } from "./feature-registry-storage-BdPuLcBX.js";
18
- import { a as diffAgainstMain, c as diffCheckpoints, d as diffPatchSinceCommit, f as findCommitAtTime, h as listCheckpoints, l as diffFilesAgainstHead, m as init_checkpoint_manager, o as diffAgainstMainFiles, s as diffCheckpointFiles, t as captureCheckpoint, u as diffPatchFilesAgainstHead } from "./checkpoint-manager-BI1Wxoku.js";
19
- import { Cn as getProviderEnvForModel, Cr as normalizeAgentId, D as init_tier_supervisor, Dr as getAgentRuntimeState, E as deliverCommitForReview, Er as waitForReadySignal, Fr as init_codex_auth, Ft as init_transcript_landing, Gn as getAgentDir, It as probeTranscriptSince, On as getOpenAIAuthStatus, Or as getAgentRuntimeStateSync, Pr as checkCodexAuthStatus, Pt as captureTranscriptUserRecordSnapshot, Q as operatorInterventionEvent, X as messageAgent, Yn as getAgentStateSync, Yt as resolveImplicitStaffing, Z as init_operator_interventions, _t as emergencyBrake, a as listRunningAgentsSync, an as getProviderAuthMode, b as init_agents$1, c as getRuntimeCensus, cn as injectPiConversationMemory, d as panePidsForSession, f as refreshRuntimeCensus, gr as readAgentHarnessModelRecordSync, hr as init_agent_record_sync, i as listRunningAgents, j as supervisorAgentId, kn as init_openai_auth, kt as deliverAgentMessage, l as getRuntimeCensusSnapshot, lr as saveAgentStateSync, n as init_queries, p as runtimeCensusHasHarnessProcess, qn as getAgentState, qt as init_staffing, r as listAgentStates, u as init_runtime_census, vn as getClaudeAuthStatus, yn as init_claude_auth, yt as init_concurrency } from "./queries-CjNIf-Ub.js";
20
- import { a as getCostForIssueAggregateSync, c as getDailyTrendsSync, d as init_cost_sync, l as getModelRollupSync, n as getBackgroundCostBySourceSync, p as queryCostEventsSync, r as getCavemanExperimentDataSync, s as getCostsByIssueSync, u as getTodayCostSync } from "./cost-sync-BsN6ER0n.js";
21
- import { c as getAgentsNeedingAttention, d as CostResolverLive, f as CostWriter, l as CostDoorLive, n as readCloisterStateFile, o as generateHealthSummary, s as getAgentHealth, t as getCloisterService, u as CostResolver } from "./service-CLeAoU6G.js";
22
- import { u as init_agent_runtime_mirror } from "./session-history-BT3IvqhC.js";
23
- import { t as init_behavior } from "./behavior-B3exJ42O.js";
24
- import { n as AgentStateServiceLive } from "./agent-state-service-BsgWxZuB.js";
25
- import { a as init_tracker_config, i as initTrackerConfigCache, n as getLinearApiKey, r as getRallyConfig, t as getGitHubConfig } from "./tracker-config-G6latr8C.js";
26
- import { d as init_typed_errors, i as PlanEmpty, l as WorkspaceCreateError, n as AgentStartError, s as TrackerApiError, t as AgentAlreadyRunning, u as WorkspaceNotFound } from "./typed-errors-2BTMxkZq.js";
27
- import { i as init_linear_client, r as LinearClientOptionalLive, t as LinearClient } from "./linear-client-BsuUvkdA.js";
28
- import { i as init_github_client, r as GitHubClientOptionalLive, t as GitHubClient } from "./github-client-BnIsSrEN.js";
29
- import { i as init_rally_client, r as RallyClientOptionalLive, t as RallyClient } from "./rally-client-lc-nokqr.js";
30
- import { a as resolveTrackerTypeSync, i as resolveGitHubIssueSync, n as init_tracker_utils } from "./tracker-utils-DHv5pSDT.js";
31
- import { a as CacheService, i as startSharedIssueService, n as init_issue_service_singleton, o as init_cache_service, r as issue_service_singleton_exports, s as parseIntegerHeader, t as getSharedIssueService } from "./issue-service-singleton-Yx5DdmVJ.js";
32
- import { i as ensureInternalTokenSync, o as getInternalTokenSync, s as init_internal_token, t as INTERNAL_TOKEN_HEADER } from "./internal-token-s0-oKybJ.js";
33
- import { a as setPipelineHandlerSync, t as init_pipeline_notifier } from "./pipeline-notifier-BXkJzDC4.js";
34
- import { c as init_review_status_sync, i as getMergeBlockerReconcileCandidates } from "./review-status-sync-BTOpxtTC.js";
35
- import { n as init_review_status_source, t as classifyAdvancingSessionLifecycle } from "./review-status-source-CxuEjLGg.js";
36
- import { a as PAN_DIRNAME, d as WORKSPACE_RUNTIME_DIRNAME, f as asPanSpecDocument, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-hraoaQIp.js";
37
- import { C as transitionToMerging, _ as markFailed, c as isFlywheelGloballyPaused, d as listDuePendingAutoMerges, f as listProblemAutoMerges, g as markBlocked, n as getActionableAutoMerge, o as init_merge_sync, t as cancelPending, u as listActiveAutoMerges, v as markMerged, x as scheduleAutoMergeWithResult, y as requeueToPending } from "./merge-sync-VPV28g0g.js";
38
- import { C as init_specs, D as writeSpecDocument, O as writeSpecForIssue, a as findWorkspaceDraftPlan, m as readWorkspacePlanSync, o as init_io, p as readWorkspacePlan, r as findPlan, s as isPlanningComplete, u as readPlan, v as init_types$1, x as findSpecByIssue, y as subItemsOf } from "./io-C9kF1wtJ.js";
39
- import { C as restartCliproxy, S as readPidFile, T as stopCliproxy, b as isCliproxyInstalled, d as getCliproxyAuthDir, o as bridgeCodexAuthToCliproxy, v as init_cliproxy, w as startCliproxy, x as isCliproxyRunning, y as installCliproxy } from "./cliproxy-C8a1vtTU.js";
40
- import { $ as updateConversationForkFallbackReason, A as isAgentConversationName, B as removeFavorite, C as getConversationByTmuxSession, D as importLegacyConversation, F as markConversationEnded, H as setClearedToConvId, I as markConversationRunning, J as setForkRequest, M as listConversations, N as listFavoritedIds, O as incrementForkRetryCount, P as markConversationActive, S as getConversationByName, T as getStuckForks, U as setConversationClaudeSessionId, X as unarchiveConversation, Y as setImportedConversationLinks, Z as updateConversationCost, _ as canRefineTitle, b as getConversationByClaudeSessionId, et as updateConversationTitle, gt as getDiscoveredSessionBySessionId, h as archiveConversation, j as listArchivedConversationsWithEnrichment, k as init_conversations, q as setFavorite, tt as updateForkStatus, v as canReplaceTitle, w as getConversationLedgerCosts, x as getConversationById, y as createConversation, yt as init_discovered_sessions, z as recordConversationHandoff } from "./conversations-6AfUfbwc.js";
41
- import { n as isBackgroundFeatureEnabled, t as init_features } from "./features-ez2271oH.js";
42
- import { i as init_settings, r as getMemoryWorkerConcurrency, t as areMemoryObservationsEnabled } from "./settings-BTMvpNFU.js";
43
- import { i as updateMemoryHealth, n as extractWithProviderPolicy, r as init_health, t as init_providers$1 } from "./providers-qQAwGQDW.js";
44
- import { a as normalizeModelOverrideSync, i as init_model_validation, n as init_launcher_generator, o as requireModelOverrideSync, r as MODEL_ID_PATTERN, t as generateLauncherScriptSync } from "./launcher-generator-BBWG_SiS.js";
45
- import { a as init_claude_permissions, r as getClaudePermissionFlagsStringSync } from "./claude-permissions-DNWEVV6z.js";
46
- import { C as ensureProjectLayer, E as init_layers, I as workspaceContextFile, N as resolveProjectContextFile, P as resolveWorkspaceContextFile, S as codexGlobalContextFile, T as globalContextFile$1, b as validateTemplate, c as init_render, d as userContentOutsideRegion, i as workspaceContextWithoutProjectLayer, r as init_assemble, s as hasManagedRegion, v as init_harness, y as renderForHarness } from "./assemble-J2ZZ2C15.js";
47
- import { l as init_codex, s as findRolloutPath } from "./codex-BZXGhUVB.js";
48
- import { i as prepareHarnessLaunch, r as init_harness_binary } from "./harness-binary-DPUi-Pv8.js";
49
- import { f as init_config, m as loadConfigSync$1, s as getDashboardApiUrlSync } from "./config-BZ0sHTju.js";
50
- import { C as setMergeTrainEnabled, S as setFlywheelRequireUatBeforeMerge, b as setFlywheelAutoPickupBacklog, c as isCloisterSpawnsPausedSync, d as isFlywheelAutoPickupBacklog, f as isFlywheelGloballyPaused$1, g as setCloisterSpawnsPausedSync, h as setBootReconciliationDecision, m as isMergeTrainEnabled, o as getSetting, p as isFlywheelRequireUatBeforeMerge, r as getBootReconciliationState, s as init_control_settings, u as isDeaconGloballyPausedSync, v as setDeaconGloballyPausedSync, w as setSetting } from "./control-settings-BQp7_4uD.js";
51
- import { l as init_dag, r as compileGlob } from "./dag-B1ucnjDU.js";
52
- import { a as loadCloisterConfigSync, o as saveCloisterConfigSync, r as init_config$1, s as shouldAutoStart } from "./config-CKxYiCmX.js";
53
- import { c as isStateMigrated, i as ensureStateWorktree, o as init_state_home, r as clearStateMigrationCache, s as inspectStateMigration } from "./state-home-Cn_Qu8IX.js";
54
- import { i as checkPrdGateSync, s as promoteWorkspacePrdDraft, t as init_pan_dir } from "./pan-dir-B0c3w0Ya.js";
55
- import { a as init_lifecycle_io, t as appendContinueSessionEntryForIssue } from "./lifecycle-io-BREGpnLk.js";
56
- import { r as init_swarm_readiness, t as analyzeSwarmReadiness } from "./swarm-readiness-DlP_UjHq.js";
57
- import { a as buildMatchQuery, c as init_query_expansion, i as resolveKnowledgeBundleRoot, n as init_injection, o as init_search, r as injectPromptTimeMemory, s as clearQueryExpansionCache } from "./injection-C8aiVJrF.js";
58
- import { i as runMemoryFtsTransaction, n as init_fts_db, r as runMemoryFtsStatement, t as closeMemoryFtsDatabases } from "./fts-db-CE46vDkZ.js";
59
- import { n as require_auth_errors, r as require_token_error, t as require_token_util } from "./token-util-DxXu-vd1.js";
60
- import { A as createSettledTtlPromiseCache, D as init_advisory_checks, E as verifyAppCanMerge, M as withConcurrencyLimit, O as isAdvisoryCheckName, d as getPullRequestState, f as init_github_app, j as init_concurrency$1, k as createPromiseConcurrencyLimiter, l as getMergeBackendStatus, p as isGitHubAppConfigured } from "./github-app-C3QrAUd0.js";
61
- import { C as getWorkspacePathForIssue, E as stopAgentOutputService, S as resolveIssueHeadlineCost, T as startAgentOutputService, _ as getCostsForIssueSync, b as processPendingFeedbackDeliveries, d as resumeQueuedMerges, f as syncWalFromAllProjects, g as migrateAllSessionsSync, h as reclassifyUnknownCostEventsSync, l as internalEventsRouteLayer, m as reconcilePiTranscripts, p as reconcile$1, t as workspacesRouteLayer, u as eventsRouteLayer, v as rebuildCacheSync, w as retainAgentOutputInterest, x as getCachedRunningAgents, y as syncCacheSync } from "./workspaces-Do_9J0LQ.js";
62
- import { _ as init_codex_conversation_parser, a as contextUsageFromParseResult, b as parseAcpConversationMessages, c as parseEntireConversation, d as init_pi_conversation_parser, f as isPiSessionFile, g as parseOhmypiConversationMessages, h as isOhmypiSessionFile, i as computeContextUsage, l as parseFromLastCompactBoundary, m as init_ohmypi_conversation_parser, n as gateSnapshotEmission, o as summarizeConversationActivity, p as parsePiConversationMessages, r as watchConversation, s as parseConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages, y as init_acp_conversation_parser } from "./conversation-service-DPJ71F33.js";
63
- import { c as countPendingAskUserQuestionsForAgent, h as detectAwaitingInputFromPaneSync, l as getAgentJsonlMtime, m as detectAwaitingInputForAgent, o as getRuntimeForAgent, p as scanPendingInputsPromise, s as computeAgentEnrichment } from "./stuck-remediation-DYuPRYYw.js";
64
- import { i as CostThresholdError, l as embed, s as parseRelativeTime } from "./scanner-CnqEZeDy.js";
65
- import { c as tailEventsSync, n as deduplicateEventsSync, o as readEventsSync } from "./events-DTfAe4My.js";
66
- import { n as recordBackgroundAiCost, t as init_cost } from "./cost-R-m6Yi-W.js";
67
- import { a as getByIssueId, i as parseFlywheelStatsWindow, n as derivePipelineRunStatsInputs, o as markFixed, r as computeFlywheelStats, s as upsert } from "./substrate-bug-weights-service-B-FwCs5V.js";
68
- import { c as startUatTrainReconciler, d as openFlywheelRunReportForDashboard, f as pauseFlywheelRunForDashboard, g as runDashboardDbJob, h as startFlywheelRunForDashboard, l as stopUatTrainReconciler, m as resumeFlywheelRunForDashboard, p as readCurrentFlywheelStatusForDashboard, u as abortFlywheelRunForDashboard } from "./uat-train-DDSrSoV1.js";
69
- import { n as parseSequenceMd, r as writeSequenceMd, t as init_sequence_io } from "./sequence-io-D909zgeH.js";
70
- import { _ as selectNeedsPlanning, c as classifyIssue, d as computeLanes, f as computeStats, g as normalizeGate, l as computeCohort, m as init_pickup, p as computeWaves, u as computeEpicGroups } from "./pickup-DUCLWrJY.js";
71
- import { B as getBook, C as readCurrentLatestFlywheelStatus, E as subscribeLatestFlywheelStatus, F as setItemRequirements, H as liveOrderIssueLookup, I as setSettings, L as setStatus, M as moveItem, N as removeItem, O as writeLatestFlywheelStatus, P as renameBook, R as backlogCandidates, T as resolveLiveFlywheelRunId, V as listBooks, b as isFlywheelRunId, j as createBook, k as addItems, p as buildClassifyLookups, v as getFlywheelRunDetail, x as listFlywheelRuns, z as computeBookProgress } from "./flywheel-DHrgCphM.js";
72
- import { At as getContainerHistory, Ct as paneShowsClaudeBootBlockingScreen, F as setAgentStatusChangedNotifier, Ft as getSystemHealthEvidenceSnapshot, I as setAgentStoppedNotifier, J as startBootReconciliation, K as isBootReconciliationCandidate, L as setMergeReadyNotifier, Lt as isSmeeConfiguredSync, Mt as getAcceptedSystemHealthSnapshot, Nt as getDeployStaleness, O as loadConfig, Ot as getResourceStacks, Rt as startSmeeProcessSync, jt as getCurrentDockerStats, k as loadState, kt as formatUptime, n as assessDeaconPatrolFreshness, q as listBootReconciliationCandidates, t as applyBootReconciliationDecision, x as getDeaconLogs, zt as classifyAgentHealth } from "./deacon-CU_cAYSR.js";
73
- import { d as init_platform, u as detectPlatform } from "./workspace-manager-CBhNdzE4.js";
74
- import { a as writeFeedbackFile, i as init_feedback_writer } from "./feedback-writer-CY5L1lB7.js";
75
- import { r as init_in_flight_guard, t as createInFlightGuard } from "./in-flight-guard-CAAkqPLu.js";
76
- import { V as getReviewerSessionName, t as init_specialists } from "./specialists-jiuuyVkp.js";
77
- import { c as findPrdAnywhereSync, l as init_prd_locations } from "./archive-planning-Du25Jtst.js";
78
- import { a as getShadowState, s as init_shadow_state } from "./shadow-state-7a-524T8.js";
79
- import { n as init_issue_closed, t as clearIssueClosedCache } from "./issue-closed-CDJk4pIp.js";
80
- import { b as resolveSwarmPolicy } from "./deacon-swarm-BXZZ1Z2-.js";
81
- import { a as init_boot_gates, i as formatBootGateState, o as resolveBootGates } from "./no-resume-mode-Nw29vaFb.js";
82
- import { a as applyIssueObjectionLabel, c as applyIssueReleasedLabel, d as removeIssueBlocksMainLabel, f as removeIssueObjectionLabel, g as removeIssueVetoedLabel, h as removeIssueReleasedLabel, i as applyIssueBlocksMainLabel, l as applyIssueVetoedLabel, m as removeIssueReadyLabel, o as applyIssueParkedLabel, p as removeIssueParkedLabel, s as applyIssueReadyLabel, u as init_label_ops } from "./label-reconciler-B4iOAjlC.js";
83
- import { c as init_resource_utils, i as gatherProjectLensSignals, l as parseIssueIdFromTextSync, n as init_pipeline_membership, o as init_pipeline_membership_gather, r as resolvePipelineMembership, s as listOpenPullRequestsSnapshot } from "./pipeline-membership-mY3GprlK.js";
84
- import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-_WygnyoX.js";
85
- import { i as resolveAutoSpawnOnFinalize, o as spawnPlanningSession } from "./spawn-planning-session-DKpdFyOC.js";
86
- import { d as killRemoteAgent, m as loadRemoteAgentState, o as getRemoteAgentOutput, r as checkRemoteSpendCap, s as init_remote_agents, v as sendToRemoteAgent, y as spawnRemoteAgent } from "./remote-agents-By1ige5O.js";
87
- import { r as spawnInspectAgent } from "./inspect-agent-BLuJ1AkT.js";
88
- import { n as init_github_pr_lookup, r as lookupPullRequestNumberForBranch, t as githubPrLookupSource } from "./github-pr-lookup-D16NGsSO.js";
89
- import { a as fetchIssuePullRequestDiff, c as getCachedIssuePrTabResponse, d as setCachedIssuePrTabResponse, i as fetchIssuePullRequestDetails, l as getIssuePrTabCacheGeneration, n as fetchIssueCheckRuns, o as init_pull_requests, r as fetchIssuePullRequest, s as bumpIssuePrTabCacheGeneration, u as init_pr_tab_cache } from "./pull-requests-_lHueOHW.js";
90
- import { t as DOD_ROWS } from "./dod-C6n9RV3G.js";
91
- import { t as enrichReviewStatus } from "./review-status-enrichment-Dci-ZwEt.js";
92
- import { c as init_pending, l as maybeTriggerStatusRollup, o as readRecentObservations, r as init_rollup, u as writePendingTurn } from "./rollup-FGsKki9R.js";
93
- import { n as getTldrDaemonServiceSync, r as init_tldr_daemon } from "./tldr-daemon-C-69c3OI.js";
94
- import { T as listGitOperationsSync, w as init_git_activity } from "./merge-agent-4WOWlSYA.js";
95
- import { t as readVerificationArtifact } from "./verification-artifact-TVFSz_q1.js";
96
- import { t as getAgentCommandSync } from "./settings-C0zgxOGf.js";
97
- import { n as init_review_agent, r as isExtendedReviewEnabled } from "./review-agent-CIGOQYBD.js";
98
- import { r as isRemoteAvailable, t as createFlyProviderFromConfig } from "./remote-C_2hasl7.js";
99
- import { n as init_claude_settings_overlay, t as detectProviderEnvConflicts } from "./claude-settings-overlay-CEdaqpWI.js";
100
- import { _ as init_transcript_adapter, a as generateFallbackSummary, c as handoffPreconditionFallbackReason, d as prependFallbackFocus, f as requestHandoffFromAgent, g as getTranscriptAdapter, i as copySessionFromCompactBoundary, l as init_summary_fork, o as generateSummaryForFork, p as reserveSummaryForkSession, r as authorHandoffExternal, s as handoffFailureReason, u as logHandoffFallback } from "./summary-fork-D1RAk9rJ.js";
101
- import { a as init_conversation_compaction, c as shouldInterceptManualCompact, n as compactConversationNative, o as isCompacting } from "./conversation-compaction-CgrR8YYp.js";
102
- import { t as init_context_layers } from "./context-layers-igZ4FI4_.js";
103
- import { r as parseArtifactRef } from "./forge-CNFnyqps.js";
104
- import { i as getMergeBlockersPayload, n as hasOrderIssuePrd, r as validateBookForStart, t as createOrderPrdLookup } from "./validate-IfDGkaVR.js";
105
- import { a as spawnSequencerAgent, i as init_sequencer_agent, n as clearFinishedSequencerRun, r as getSequencerRunStatus, t as SEQUENCER_AGENT_ID } from "./sequencer-agent-CzkcK6P5.js";
106
- import "./supervisor-wM4ooB5O.js";
107
- import { i as listTranscriptCheckpoints, r as getTranscriptCheckpoint$1 } from "./checkpoints-CQB1h765.js";
8
+ import { i as updateIssueRecord, n as clearRecordPipelineClosedOutSync, r as init_record_update } from "./record-update-DaOKS7Wb.js";
9
+ import { B as sendKeysAsync, C as getWindowDimensions, D as killSession, E as isPaneDead, H as sessionExists, J as init_pane_choice_menu, L as resizeWindow, M as listSessionNames, T as isHarnessProcessAlive, V as sendRawKeystroke, Y as paneChoiceMenuSignature, Z as parsePaneChoiceMenu, at as buildChildEnvWithoutTmuxSync, d as ensureManagedTmuxContextOnce, i as capturePane, it as buildChildEnvSync, l as deliveryVerifyLine, n as buildTmuxArgs, nt as setUiTheme, o as capturePaneText, ot as init_child_env, q as buildChoiceKeystrokes, s as createSession, tt as init_ui_theme, w as init_tmux, z as sendKeys } from "./tmux-CfDHob9M.js";
10
+ import { a as init_event_store, i as initEventStore, n as getEventStore } from "./event-store-CrHcVC5_.js";
11
+ import { A as AgentRuntimeSnapshot, C as FlywheelStatus, E as SystemHealthSnapshot, M as ReviewStatusSnapshot, O as MemoryObservation, S as FlywheelRunId, T as DomainEvent, b as PanRpcGroup, j as AgentSnapshot, l as ContextLayerSaveRequest, n as COMPOSER_COMMAND_MANIFEST, p as FlywheelStats, r as compareIssueIds, s as getHarnessBehavior, t as init_src, u as ContextPreviewRequest, w as EDITORS, x as WS_METHODS, y as PanRpcError } from "./src-B948bGVR.js";
12
+ import { A as synchronizeAnalyticsServices, C as setReviewStatusSync, F as telemetryEnvironmentForcesOff, M as init_install_id, N as init_config, O as getAnalyticsService, P as resolveTelemetryEnabled, S as setReviewStatus, _ as markWorkspaceStuck, a as clearStuckMergeStatuses, c as fixStuckCommentedReviews, d as getReviewStatusSync, h as loadReviewStatuses, i as clearReviewStatus, j as getOrCreateInstallId, k as init_service, l as fixStuckReadyForMerge, m as loadReadyForMergeFlags, p as init_review_status, u as getReviewStatus } from "./review-status-CGvlA4o_.js";
13
+ import { a as EventStoreService, c as ReadModelService, f as init_read_model, h as toRole, i as init_issue_lifecycle, l as ReadModelServiceLive, m as toAgentStatus, n as IssueLifecycleLive, o as EventStoreServiceLive, p as toAgentResolution, s as init_domain_services, t as IssueLifecycle, u as activityEntriesFromStoredEvents } from "./issue-lifecycle-Birk-ifu.js";
14
+ import { n as logAgentLifecycleSync, t as init_persistent_logger } from "./persistent-logger-H_VjuOu7.js";
15
+ import { c as listAllAgentsSync, n as AgentsResolverLive, o as init_agents } from "./agents-J5mEnekq.js";
16
+ import { i as init_paths$2, l as resolveObservationsFile, r as ensureParentDir, s as resolveIssueMemoryRoot, t as assertMemorySafeSegment } from "./paths-sJBSb7Ul.js";
17
+ import { i as recordBriefingSessionStart, n as ensureSessionContextBriefingFile, r as init_briefing_freshness, t as appendFreshBriefingUpdate } from "./briefing-freshness-DRqZ8lAz.js";
18
+ import { n as listFeatureRegistryEntries, t as init_feature_registry_storage } from "./feature-registry-storage-DJEFx1Pf.js";
19
+ import { a as diffAgainstMain, c as diffCheckpoints, d as diffPatchSinceCommit, f as findCommitAtTime, h as listCheckpoints, l as diffFilesAgainstHead, m as init_checkpoint_manager, o as diffAgainstMainFiles, s as diffCheckpointFiles, t as captureCheckpoint, u as diffPatchFilesAgainstHead } from "./checkpoint-manager-DBcyNR8w.js";
20
+ import { $ as operatorInterventionEvent, $n as getAgentDir, Bt as init_transcript_landing, Cr as init_agent_record_sync, D as init_tier_supervisor, E as deliverCommitForReview, Fn as init_openai_auth, Gr as init_codex_auth, Ir as waitForReadySignal, Lr as getAgentRuntimeState, Mt as deliverAgentMessage, Nr as normalizeAgentId, Pn as getOpenAIAuthStatus, Q as init_operator_interventions, Qt as init_staffing, Rr as getAgentRuntimeStateSync, Tn as init_claude_auth, Vt as probeTranscriptSince, Wr as checkCodexAuthStatus, X as messageAgent, Y as init_messaging, _r as saveAgentStateSync, a as listRunningAgentsSync, b as init_agents$1, bt as init_concurrency, c as getRuntimeCensus, d as panePidsForSession, dn as getProviderAuthMode, en as resolveImplicitStaffing, f as refreshRuntimeCensus, i as listRunningAgents, j as supervisorAgentId, kn as getProviderEnvForModel, l as getRuntimeCensusSnapshot, mn as injectPiConversationMemory, n as init_queries, p as runtimeCensusHasHarnessProcess, r as listAgentStates, rr as getAgentStateSync, tr as getAgentState, u as init_runtime_census, vt as emergencyBrake, wn as getClaudeAuthStatus, wr as readAgentHarnessModelRecordSync, zt as captureTranscriptUserRecordSnapshot } from "./queries-C-MUZaIy.js";
21
+ import { a as getCostForIssueAggregateSync, c as getDailyTrendsSync, d as init_cost_sync, l as getModelRollupSync, n as getBackgroundCostBySourceSync, p as queryCostEventsSync, r as getCavemanExperimentDataSync, s as getCostsByIssueSync, u as getTodayCostSync } from "./cost-sync-tOCCGnEt.js";
22
+ import { c as getAgentsNeedingAttention, d as CostResolverLive, f as CostWriter, l as CostDoorLive, n as readCloisterStateFile, o as generateHealthSummary, s as getAgentHealth, t as getCloisterService, u as CostResolver } from "./service-CD0DvFvD.js";
23
+ import { s as init_agent_runtime_mirror } from "./session-history-3ON_IJjm.js";
24
+ import { t as init_behavior } from "./behavior-td6-xhQk.js";
25
+ import { n as AgentStateServiceLive } from "./agent-state-service-Ds6OeFvo.js";
26
+ import { a as init_tracker_config, i as initTrackerConfigCache, n as getLinearApiKey, r as getRallyConfig, t as getGitHubConfig } from "./tracker-config-BcPMElRB.js";
27
+ import { d as init_typed_errors, i as PlanEmpty, l as WorkspaceCreateError, n as AgentStartError, s as TrackerApiError, t as AgentAlreadyRunning, u as WorkspaceNotFound } from "./typed-errors-DyjseiB6.js";
28
+ import { i as init_linear_client, r as LinearClientOptionalLive, t as LinearClient } from "./linear-client-9uq7fhiV.js";
29
+ import { i as init_github_client, r as GitHubClientOptionalLive, t as GitHubClient } from "./github-client-qazkPrg0.js";
30
+ import { i as init_rally_client, r as RallyClientOptionalLive, t as RallyClient } from "./rally-client-iYMHWQD7.js";
31
+ import { n as resolveGitHubIssueSync, r as resolveTrackerTypeSync, t as init_tracker_utils } from "./tracker-utils-CJ6BHjcD.js";
32
+ import { a as CacheService, i as startSharedIssueService, n as init_issue_service_singleton, o as init_cache_service, r as issue_service_singleton_exports, s as parseIntegerHeader, t as getSharedIssueService } from "./issue-service-singleton-C6ZduvuY.js";
33
+ import { i as ensureInternalTokenSync, o as getInternalTokenSync, s as init_internal_token, t as INTERNAL_TOKEN_HEADER } from "./internal-token-DzK385vD.js";
34
+ import { a as setPipelineHandlerSync, t as init_pipeline_notifier } from "./pipeline-notifier-D0ALV6Nw.js";
35
+ import { c as init_review_status_sync, i as getMergeBlockerReconcileCandidates } from "./review-status-sync-0GxOqeqF.js";
36
+ import { n as init_review_status_source, t as classifyAdvancingSessionLifecycle } from "./review-status-source-DIk1JSow.js";
37
+ import { a as PAN_DIRNAME, d as WORKSPACE_RUNTIME_DIRNAME, f as asPanSpecDocument, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-Cm5ASJbq.js";
38
+ import { C as scheduleAutoMergeWithResult, _ as markMerged, c as init_merge_sync, f as listActiveAutoMerges, g as markFailed, h as markBlocked, i as getActionableAutoMerge, l as isFlywheelGloballyPaused, m as listProblemAutoMerges, n as countActionableAutoMerges, p as listDuePendingAutoMerges, t as cancelPending, v as markMergingBlocked, w as transitionToMerging, x as requeueToPending } from "./merge-sync-kr4vqyjM.js";
39
+ import { D as writeSpecForIssue, E as writeSpecDocument, S as init_specs, a as findWorkspaceDraftPlan, m as readWorkspacePlanSync, o as init_io, p as readWorkspacePlan, r as findPlan, s as isPlanningComplete, u as readPlan, v as init_types$1, x as findSpecByIssue, y as subItemsOf } from "./io-B00PBNku.js";
40
+ import { C as restartCliproxy, S as readPidFile, T as stopCliproxy, b as isCliproxyInstalled, d as getCliproxyAuthDir, o as bridgeCodexAuthToCliproxy, v as init_cliproxy, w as startCliproxy, x as isCliproxyRunning, y as installCliproxy } from "./cliproxy-C5fMJ7rY.js";
41
+ import { $ as updateConversationForkFallbackReason, A as isAgentConversationName, B as removeFavorite, C as getConversationByTmuxSession, D as importLegacyConversation, F as markConversationEnded, H as setClearedToConvId, I as markConversationRunning, J as setForkRequest, M as listConversations, N as listFavoritedIds, O as incrementForkRetryCount, P as markConversationActive, S as getConversationByName, T as getStuckForks, U as setConversationClaudeSessionId, X as unarchiveConversation, Y as setImportedConversationLinks, Z as updateConversationCost, _ as canRefineTitle, _t as getDiscoveredStats, at as aggregateDiscoveredSessionCost, b as getConversationByClaudeSessionId, ct as countDiscoveredSessions, et as updateConversationTitle, ft as findDiscoveredSessions, gt as getDiscoveredSessionBySessionId, h as archiveConversation, j as listArchivedConversationsWithEnrichment, k as init_conversations, mt as getDiscoveredSessionById, ot as aggregateDiscoveredSessionCostBy, q as setFavorite, tt as updateForkStatus, v as canReplaceTitle, w as getConversationLedgerCosts, x as getConversationById, y as createConversation, yt as init_discovered_sessions, z as recordConversationHandoff } from "./conversations-D0UEdLtk.js";
42
+ import { n as isBackgroundFeatureEnabled, t as init_features } from "./features-D5km80UI.js";
43
+ import { i as init_settings, r as getMemoryWorkerConcurrency, t as areMemoryObservationsEnabled } from "./settings-VN0qOh4U.js";
44
+ import { i as updateMemoryHealth, n as extractWithProviderPolicy, r as init_health, t as init_providers$1 } from "./providers-BJQSnAAp.js";
45
+ import { C as init_launcher_generator, D as requireModelOverrideSync, E as normalizeModelOverrideSync, S as generateLauncherScriptSync, T as init_model_validation, w as MODEL_ID_PATTERN } from "./auto-spawn-consent-CQ9Bi9q6.js";
46
+ import { a as init_claude_permissions, r as getClaudePermissionFlagsStringSync } from "./claude-permissions-CpbpvUQ2.js";
47
+ import { C as ensureProjectLayer, E as init_layers, I as workspaceContextFile, N as resolveProjectContextFile, P as resolveWorkspaceContextFile, S as codexGlobalContextFile, T as globalContextFile$1, b as validateTemplate, c as init_render, d as userContentOutsideRegion, i as workspaceContextWithoutProjectLayer, r as init_assemble, s as hasManagedRegion, v as init_harness, y as renderForHarness } from "./assemble-Bgwqv-Ro.js";
48
+ import { l as init_codex, s as findRolloutPath } from "./codex-BzGDW_d3.js";
49
+ import { i as prepareHarnessLaunch, r as init_harness_binary } from "./harness-binary-BSpiXaLw.js";
50
+ import { f as init_config$1, m as loadConfigSync$1, s as getDashboardApiUrlSync } from "./config-BvLe652W.js";
51
+ import { D as setSetting, E as setMergeTrainEnabled, S as setFlywheelAutoPickupBacklog, T as setLastCleanShutdownAt, b as setDeaconGloballyPausedSync, c as init_control_settings, d as isDeaconGloballyPausedSync, f as isFlywheelAutoPickupBacklog, g as setBootReconciliationDecision, h as isMergeTrainEnabled, l as isCloisterSpawnsPausedSync, m as isFlywheelRequireUatBeforeMerge, n as FLYWHEEL_GLOBAL_PAUSE_KEY, p as isFlywheelGloballyPaused$1, r as getBootReconciliationState, s as getSetting, t as FLYWHEEL_ACTIVE_RUN_ID_KEY, v as setCloisterSpawnsPausedSync, w as setFlywheelRequireUatBeforeMerge } from "./control-settings-CiqGxXnc.js";
52
+ import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-BAJu5MX8.js";
53
+ import { a as loadCloisterConfigSync, o as saveCloisterConfigSync, r as init_config$2, s as shouldAutoStart } from "./config-Bi3u1VK7.js";
54
+ import { c as isStateMigrated, i as ensureStateWorktree, o as init_state_home, r as clearStateMigrationCache, s as inspectStateMigration } from "./state-home-722Dkx92.js";
55
+ import { i as checkPrdGateSync, s as promoteWorkspacePrdDraft, t as init_pan_dir } from "./pan-dir-DMzF5wne.js";
56
+ import { a as init_lifecycle_io, t as appendContinueSessionEntryForIssue } from "./lifecycle-io-DCo6s6ks.js";
57
+ import { r as init_swarm_readiness, t as analyzeSwarmReadiness } from "./swarm-readiness-DfFxa9-n.js";
58
+ import { a as buildMatchQuery, c as init_query_expansion, i as resolveKnowledgeBundleRoot, n as init_injection, o as init_search, r as injectPromptTimeMemory, s as clearQueryExpansionCache } from "./injection-6AGnXxlj.js";
59
+ import { i as runMemoryFtsTransaction, n as init_fts_db, r as runMemoryFtsStatement, t as closeMemoryFtsDatabases } from "./fts-db-IVc4_M3f.js";
60
+ import { n as require_auth_errors, r as require_token_error, t as require_token_util } from "./token-util-B48BlCba.js";
61
+ import { A as createSettledTtlPromiseCache, D as init_advisory_checks, E as verifyAppCanMerge, M as withConcurrencyLimit, O as isAdvisoryCheckName, d as getPullRequestState, f as init_github_app, j as init_concurrency$1, k as createPromiseConcurrencyLimiter, l as getMergeBackendStatus, p as isGitHubAppConfigured } from "./github-app-CmNk0zLU.js";
62
+ import { C as getWorkspacePathForIssue, E as stopAgentOutputService, S as resolveIssueHeadlineCost, T as startAgentOutputService, _ as getCostsForIssueSync, b as processPendingFeedbackDeliveries, d as resumeQueuedMerges, f as syncWalFromAllProjects, g as migrateAllSessionsSync, h as reclassifyUnknownCostEventsSync, l as internalEventsRouteLayer, m as reconcilePiTranscripts, p as reconcile$1, t as workspacesRouteLayer, u as eventsRouteLayer, v as rebuildCacheSync, w as retainAgentOutputInterest, x as getCachedRunningAgents, y as syncCacheSync } from "./workspaces-CRBJikLp.js";
63
+ import { _ as init_codex_conversation_parser, a as contextUsageFromParseResult, b as parseAcpConversationMessages, c as parseEntireConversation, d as init_pi_conversation_parser, f as isPiSessionFile, g as parseOhmypiConversationMessages, h as isOhmypiSessionFile, i as computeContextUsage, l as parseFromLastCompactBoundary, m as init_ohmypi_conversation_parser, n as gateSnapshotEmission, o as summarizeConversationActivity, p as parsePiConversationMessages, r as watchConversation, s as parseConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages, y as init_acp_conversation_parser } from "./conversation-service-DBxoYMDl.js";
64
+ import { c as countPendingAskUserQuestionsForAgent, g as detectAwaitingInputFromPaneSync, h as detectAwaitingInputForAgent, l as getAgentJsonlMtime, m as scanPendingInputsPromise, o as getRuntimeForAgent, s as computeAgentEnrichment } from "./stuck-remediation-CC6q3eqQ.js";
65
+ import { a as enrichSessions, c as searchSessions, i as CostThresholdError, l as embed, n as getSessionsFeedFacets, o as embedSessions, r as listSessionsFeed, s as parseRelativeTime, t as scan } from "./scanner-C-D2cFdX.js";
66
+ import { c as tailEventsSync, n as deduplicateEventsSync, o as readEventsSync } from "./events-BNrvchBg.js";
67
+ import { n as recordBackgroundAiCost, t as init_cost } from "./cost-DS1s0DbG.js";
68
+ import { a as getByIssueId, i as parseFlywheelStatsWindow, n as derivePipelineRunStatsInputs, o as markFixed, r as computeFlywheelStats, s as upsert, t as listSubstrateBugWeights } from "./substrate-bug-weights-service-CzIyyWFT.js";
69
+ import { n as parseSequenceMd, r as writeSequenceMd, t as init_sequence_io } from "./sequence-io-Bmf1EZM5.js";
70
+ import { _ as normalizeGate, d as computeEpicGroups, f as computeLanes, h as init_pickup, l as classifyIssue, m as computeWaves, p as computeStats, u as computeCohort, v as selectNeedsPlanning } from "./pickup-DWhqzliO.js";
71
+ import { B as getBook, C as readCurrentLatestFlywheelStatus, D as writeFlywheelLaunchMetadata, E as subscribeLatestFlywheelStatus, F as setItemRequirements, H as liveOrderIssueLookup, I as setSettings, L as setStatus, M as moveItem, N as removeItem, O as writeLatestFlywheelStatus, P as renameBook, R as backlogCandidates, S as nextFlywheelRunId, T as resolveLiveFlywheelRunId, V as listBooks, a as isFlywheelDevcontainerRuntime, b as isFlywheelRunId, f as spawnFlywheelAgent, g as abortFlywheelRun, j as createBook, k as addItems, p as buildClassifyLookups, s as loadResumeSessionId, t as FLYWHEEL_ORCHESTRATOR_AGENT_ID, u as saveResumeSessionId, v as getFlywheelRunDetail, w as readFlywheelLaunchMetadata, x as listFlywheelRuns, z as computeBookProgress } from "./flywheel-BHx8XuMr.js";
72
+ import { Bt as isSmeeConfiguredSync, D as loadConfig, Et as paneShowsClaudeBootBlockingScreen, F as setAgentStoppedNotifier, Ft as getAcceptedSystemHealthSnapshot, G as isAutoResumableRole, Ht as classifyAgentHealth, I as setMergeReadyNotifier, It as getDeployStaleness, J as startBootReconciliation, K as isBootReconciliationCandidate, Mt as formatUptime, Nt as getContainerHistory, O as loadState, P as setAgentStatusChangedNotifier, Pt as getCurrentDockerStats, Rt as getSystemHealthEvidenceSnapshot, Vt as startSmeeProcessSync, W as extendBootReconciliationGrace, b as getDeaconLogs, jt as getResourceStacks, n as assessDeaconPatrolFreshness, q as listBootReconciliationCandidates, t as applyBootReconciliationDecision } from "./deacon-pLRLbPvR.js";
73
+ import { c as installGitHooksInDir, f as detectPlatform, p as init_platform, s as init_git_hooks } from "./workspace-manager-BPgTrG66.js";
74
+ import { a as getShadowState, s as init_shadow_state } from "./shadow-state-BBk_zacw.js";
75
+ import { n as init_issue_closed, t as clearIssueClosedCache } from "./issue-closed-BehTG1be.js";
76
+ import { i as parseArtifactRef, r as init_forge } from "./forge-BaKGCya6.js";
77
+ import { a as writeFeedbackFile, i as init_feedback_writer } from "./feedback-writer-C27Hak8a.js";
78
+ import { V as getReviewerSessionName, t as init_specialists } from "./specialists-DUiExaxb.js";
79
+ import { s as findPrdAnywhereSync } from "./archive-planning-BwhxeP7C.js";
80
+ import { A as removeIssueReleasedLabel, C as applyIssueReleasedLabel, D as removeIssueObjectionLabel, E as removeIssueBlocksMainLabel, O as removeIssueParkedLabel, S as applyIssueReadyLabel, T as init_label_ops, a as gatherProjectLensSignals, b as applyIssueObjectionLabel, g as parseIssueIdFromTextSync, h as init_resource_utils, j as removeIssueVetoedLabel, k as removeIssueReadyLabel, n as PipelineMembershipUnavailableError, s as init_pipeline_membership_gather, u as listOpenPullRequestsSnapshot, w as applyIssueVetoedLabel, x as applyIssueParkedLabel, y as applyIssueBlocksMainLabel } from "./pipeline-membership-gather-CeVGnNJ2.js";
81
+ import { n as init_pipeline_membership, r as resolvePipelineMembership } from "./pipeline-membership-D9SBvItj.js";
82
+ import { b as resolveSwarmPolicy } from "./deacon-swarm-v5PMun-m.js";
83
+ import { a as init_boot_gates, i as formatBootGateState, o as resolveBootGates } from "./no-resume-mode-BgD0JUmM.js";
84
+ import { r as init_in_flight_guard, t as createInFlightGuard } from "./in-flight-guard-D-sSkADM.js";
85
+ import { a as spawnPlanningSession, r as resolveAutoSpawnOnFinalize } from "./spawn-planning-session-pcF-YeFE.js";
86
+ import { r as computeMergeQueueFromCandidates, s as listEligibleCandidatesByProject } from "./flywheel-merge-order-D_xLMbJU.js";
87
+ import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-DkO_iTLo.js";
88
+ import { d as killRemoteAgent, m as loadRemoteAgentState, o as getRemoteAgentOutput, r as checkRemoteSpendCap, s as init_remote_agents, v as sendToRemoteAgent, y as spawnRemoteAgent } from "./remote-agents-Bba-MgSA.js";
89
+ import { r as spawnInspectAgent } from "./inspect-agent-nXdIptyh.js";
90
+ import { i as lookupPullRequestNumberForBranch, n as init_github_pr_lookup, t as githubPrLookupSource } from "./github-pr-lookup-DAcVeRLK.js";
91
+ import { a as fetchIssuePullRequestDiff, c as getCachedIssuePrTabResponse, d as setCachedIssuePrTabResponse, i as fetchIssuePullRequestDetails, l as getIssuePrTabCacheGeneration, n as fetchIssueCheckRuns, o as init_pull_requests, r as fetchIssuePullRequest, s as bumpIssuePrTabCacheGeneration, u as init_pr_tab_cache } from "./pull-requests-7BgLDsIo.js";
92
+ import { n as DOD_ROWS } from "./dod-DlNtcH6n.js";
93
+ import { c as init_pending, l as maybeTriggerStatusRollup, o as readRecentObservations, r as init_rollup, u as writePendingTurn } from "./rollup-BQaol5TX.js";
94
+ import { n as getTldrDaemonServiceSync, r as init_tldr_daemon } from "./tldr-daemon-wvsU6bKv.js";
95
+ import { T as listGitOperationsSync, w as init_git_activity } from "./merge-agent-BLUgRsz3.js";
96
+ import { t as readVerificationArtifact } from "./verification-artifact-D70AtckY.js";
97
+ import { i as readPendingDeploy, n as init_deploy_queue } from "./deploy-queue-VfohaPN_.js";
98
+ import { t as getAgentCommandSync } from "./settings-PilbFZAM.js";
99
+ import { n as init_review_agent, r as isExtendedReviewEnabled } from "./review-agent--pOptAEV.js";
100
+ import { r as isRemoteAvailable, t as createFlyProviderFromConfig } from "./remote-BXarMvqf.js";
101
+ import { n as init_claude_settings_overlay, t as detectProviderEnvConflicts } from "./claude-settings-overlay-_H9Yrzab.js";
102
+ import { _ as init_transcript_adapter, a as generateFallbackSummary, c as handoffPreconditionFallbackReason, d as prependFallbackFocus, f as requestHandoffFromAgent, g as getTranscriptAdapter, i as copySessionFromCompactBoundary, l as init_summary_fork, o as generateSummaryForFork, p as reserveSummaryForkSession, r as authorHandoffExternal, s as handoffFailureReason, u as logHandoffFallback } from "./summary-fork-Df2WFijM.js";
103
+ import { a as init_conversation_compaction, c as shouldInterceptManualCompact, n as compactConversationNative, o as isCompacting } from "./conversation-compaction-D6-yLtqt.js";
104
+ import { t as init_context_layers } from "./context-layers-C_IIysnm.js";
105
+ import { a as appendLinearMcpAuthHealthyEvent, r as appendLinearMcpAuthCallbackRelayedEvent, u as resolveLinearMcpAuthIntervention } from "./linear-mcp-auth-BxUOzswy.js";
106
+ import { i as getMergeBlockersPayload, n as hasOrderIssuePrd, r as validateBookForStart, t as createOrderPrdLookup } from "./validate-B0C8p66t.js";
107
+ import { a as spawnSequencerAgent, i as init_sequencer_agent, n as clearFinishedSequencerRun, r as getSequencerRunStatus, t as SEQUENCER_AGENT_ID } from "./sequencer-agent-CigMyKh2.js";
108
+ import { n as sameCanonicalReviewStatus, t as emitReviewStatusChanged } from "./review-status-emit-DURrmGYX.js";
109
+ import "./supervisor-BxqSpjcN.js";
110
+ import { l as startUatTrainReconciler, u as stopUatTrainReconciler } from "./uat-train-4Q0k06CQ.js";
111
+ import { n as settlePendingLifecycleClaim, t as claimPendingLifecycleFile } from "./pending-lifecycle-claim-DilzGfgk.js";
112
+ import { i as listTranscriptCheckpoints, r as getTranscriptCheckpoint$1 } from "./checkpoints-D95apurL.js";
108
113
  import { createRequire } from "node:module";
109
114
  import * as sp from "node:path";
110
115
  import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
@@ -112,8 +117,8 @@ import { Cause, Context, Data, Duration, Effect, Exit, Fiber, FileSystem, Layer,
112
117
  import { createReadStream, createWriteStream, existsSync, fstatSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
113
118
  import { basename as basename$1, dirname as dirname$1, join as join$1, resolve as resolve$1 } from "path";
114
119
  import { homedir } from "os";
115
- import { homedir as homedir$1, type } from "node:os";
116
- import { constants, createReadStream as createReadStream$1, existsSync as existsSync$1, mkdirSync as mkdirSync$1, promises as promises$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, readlinkSync, renameSync as renameSync$1, rmSync as rmSync$1, stat, statSync as statSync$1, symlinkSync as symlinkSync$1, unwatchFile, watch, watchFile } from "node:fs";
120
+ import { freemem as freemem$1, homedir as homedir$1, totalmem as totalmem$1, type } from "node:os";
121
+ import { constants, createReadStream as createReadStream$1, existsSync as existsSync$1, mkdirSync as mkdirSync$1, promises as promises$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, rmSync as rmSync$1, stat, statSync as statSync$1, unwatchFile, watch, watchFile } from "node:fs";
117
122
  import { FetchHttpClient, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
118
123
  import { EventEmitter } from "node:events";
119
124
  import { access, appendFile, chmod, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat as stat$1, unlink, writeFile } from "node:fs/promises";
@@ -329,9 +334,9 @@ const ServerConfigLayer = Layer.effect(ServerConfig, Effect.sync(() => {
329
334
  const identity = getDashboardIdentity();
330
335
  const hostDashboardApiPort = readHostDashboardApiPort();
331
336
  const overrideRequested = process.env["OVERDECK_WORKSPACE_DASHBOARD_ALLOW_PRIMARY"] === "1";
332
- const agentId = process.env["OVERDECK_AGENT_ID"];
337
+ const agentId = process.env["OVERDECK_DASHBOARD_SPAWNED_BY"];
333
338
  if (overrideRequested && agentId !== void 0 && !agentId.startsWith("conv-")) {
334
- const msg = `Refusing host dashboard port override for pipeline-role identity OVERDECK_AGENT_ID=${agentId}. Work, planning, review, and flywheel agents must never bind the host dashboard port; use the workspace container endpoint instead. Only an operator-supervised conversation (conv-*) or a process with no agent identity may use this emergency override.`;
339
+ const msg = `Refusing host dashboard port override for pipeline-role identity OVERDECK_DASHBOARD_SPAWNED_BY=${agentId}. Work, planning, review, and flywheel agents must never bind the host dashboard port; use the workspace container endpoint instead. Only an operator-supervised conversation (conv-*) or a process with no agent identity may use this emergency override.`;
335
340
  console.error(`[overdeck] ${msg}`);
336
341
  throw new ServerConfigError("API_PORT", msg);
337
342
  }
@@ -399,8 +404,10 @@ init_js_yaml();
399
404
  init_load();
400
405
  init_config_yaml();
401
406
  init_ui_theme();
407
+ init_pane_choice_menu();
402
408
  init_infra();
403
409
  init_event_store();
410
+ init_persistent_logger();
404
411
  init_agents();
405
412
  init_read_model();
406
413
  init_checkpoint_manager();
@@ -713,7 +720,7 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
713
720
  if (!existsSync$1(workspacePath)) throw new WorkspaceNotFound({ id: issueId });
714
721
  const plan = readWorkspacePlanSync(workspacePath);
715
722
  if (!plan || plan.plan.items.length === 0) throw new PlanEmpty({ id: issueId });
716
- const { getAgentState, spawnAgent, normalizeAgentId } = await import("./agents-wi9NWzs2.js");
723
+ const { getAgentState, spawnAgent, normalizeAgentId } = await import("./agents-ePFVoZbu.js");
717
724
  const normalizedId = normalizeAgentId(issueId);
718
725
  if ((await Effect.runPromise(getAgentState(normalizedId)))?.status === "running") throw new AgentAlreadyRunning({ id: issueId });
719
726
  return {
@@ -723,7 +730,8 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
723
730
  model: opts.model,
724
731
  role: "work",
725
732
  prompt: opts.prompt,
726
- allowHost: opts.allowHost
733
+ allowHost: opts.allowHost,
734
+ startedBy: "dashboard:agent-spawner"
727
735
  })).id ?? normalizedId,
728
736
  issueId,
729
737
  sessionName: normalizedId,
@@ -744,7 +752,7 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
744
752
  const { workspacePath, projectPath, issue } = opts;
745
753
  if (!existsSync$1(workspacePath)) throw new WorkspaceNotFound({ id: issueId });
746
754
  const sessionName = opts.sessionName ?? `planning-${issueId.toLowerCase()}`;
747
- const { spawnPlanningSession } = await import("./spawn-planning-session-qUC2VWu3.js");
755
+ const { spawnPlanningSession } = await import("./spawn-planning-session-vLUJoFjy.js");
748
756
  const result = await spawnPlanningSession({
749
757
  issue: {
750
758
  id: issue.id,
@@ -759,7 +767,8 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
759
767
  sessionName,
760
768
  workspaceLocation: "local",
761
769
  shadowMode: opts.shadowMode ?? false,
762
- autoSpawnOnFinalize: false
770
+ autoSpawnOnFinalize: false,
771
+ startedBy: "dashboard:agent-spawner"
763
772
  });
764
773
  if (!result.success) throw new AgentStartError({
765
774
  id: issueId,
@@ -777,14 +786,14 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
777
786
  }),
778
787
  kill: (agentId) => Effect.tryPromise({
779
788
  try: async () => {
780
- const { stopAgent } = await import("./agents-wi9NWzs2.js");
789
+ const { stopAgent } = await import("./agents-ePFVoZbu.js");
781
790
  await Effect.runPromise(stopAgent(agentId));
782
791
  },
783
792
  catch: () => void 0
784
793
  }).pipe(Effect.ignore),
785
794
  message: (agentId, msg) => Effect.tryPromise({
786
795
  try: async () => {
787
- const { messageAgent } = await import("./agents-wi9NWzs2.js");
796
+ const { messageAgent } = await import("./agents-ePFVoZbu.js");
788
797
  await messageAgent(agentId, msg);
789
798
  },
790
799
  catch: (err) => new AgentStartError({
@@ -795,8 +804,8 @@ const AgentSpawnerLive = Layer.effect(AgentSpawner, Effect.sync(() => ({
795
804
  }),
796
805
  deepWipe: (issueId, opts) => Effect.tryPromise({
797
806
  try: async () => {
798
- const { deepWipe } = await import("./workflows-BQu4RFQ-.js");
799
- const { resolveProjectFromIssueSync } = await import("./projects-vjjcmiN8.js");
807
+ const { deepWipe } = await import("./workflows-DtDX08ed.js");
808
+ const { resolveProjectFromIssueSync } = await import("./projects-_n3Zui6K.js");
800
809
  const project = resolveProjectFromIssueSync(issueId);
801
810
  const projectPath = project?.projectPath ?? process.cwd();
802
811
  await Effect.runPromise(deepWipe({
@@ -828,7 +837,7 @@ init_types$1();
828
837
  * Route handlers and AgentSpawner use this instead of calling workspace-manager directly.
829
838
  */
830
839
  init_typed_errors();
831
- const execAsync$10 = promisify(exec);
840
+ const execAsync$11 = promisify(exec);
832
841
  var WorkspaceService = class extends Context.Service()("overdeck/dashboard/WorkspaceService") {};
833
842
  const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
834
843
  function getWorkspacePath(issueId) {
@@ -855,8 +864,8 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
855
864
  const { workspacePath, branch } = getWorkspacePath(issueId);
856
865
  const issueLower = issueId.toLowerCase();
857
866
  if (existsSync$1(workspacePath)) return workspacePath;
858
- const { createWorkspace } = await import("./workspace-manager-CWT55qeI.js");
859
- const { loadProjectsConfigSync } = await import("./projects-vjjcmiN8.js");
867
+ const { createWorkspace } = await import("./workspace-manager-C1F3XsnS.js");
868
+ const { loadProjectsConfigSync } = await import("./projects-_n3Zui6K.js");
860
869
  const { projects } = loadProjectsConfigSync();
861
870
  const project = resolveProjectFromIssueSync(issueId);
862
871
  if (!project) throw new WorkspaceCreateError({
@@ -891,7 +900,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
891
900
  const { workspacePath } = getWorkspacePath(issueId);
892
901
  const issueLower = issueId.toLowerCase();
893
902
  if (!existsSync$1(workspacePath)) throw new WorkspaceNotFound({ id: issueId });
894
- const { removeWorkspace } = await import("./workspace-manager-CWT55qeI.js");
903
+ const { removeWorkspace } = await import("./workspace-manager-C1F3XsnS.js");
895
904
  const project = resolveProjectFromIssueSync(issueId);
896
905
  if (!project) throw new WorkspaceCreateError({
897
906
  id: issueId,
@@ -922,7 +931,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
922
931
  try: async () => {
923
932
  const { workspacePath } = getWorkspacePath(issueId);
924
933
  const issueLower = issueId.toLowerCase();
925
- const { stopWorkspaceDocker } = await import("./workspace-manager-CWT55qeI.js");
934
+ const { stopWorkspaceDocker } = await import("./workspace-manager-C1F3XsnS.js");
926
935
  await Effect.runPromise(stopWorkspaceDocker(workspacePath, issueLower));
927
936
  },
928
937
  catch: () => void 0
@@ -991,7 +1000,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
991
1000
  join(workspacePath, ".devcontainer", "docker-compose.devcontainer.yml")
992
1001
  ];
993
1002
  if (!composePaths.find((p) => existsSync$1(p))) {
994
- const { ensureDevcontainerSync } = await import("./ensure-devcontainer-BZgOhRo7.js");
1003
+ const { ensureDevcontainerSync } = await import("./ensure-devcontainer-DMAbWqtQ.js");
995
1004
  const ensure = ensureDevcontainerSync({
996
1005
  workspacePath,
997
1006
  issueId
@@ -1002,7 +1011,7 @@ const WorkspaceServiceLive = Layer.effect(WorkspaceService, Effect.sync(() => {
1002
1011
  });
1003
1012
  }
1004
1013
  const finalComposePath = composePaths.find((p) => existsSync$1(p));
1005
- if (finalComposePath) await execAsync$10(`docker compose -f "${finalComposePath}" up -d --build`, {
1014
+ if (finalComposePath) await execAsync$11(`docker compose -f "${finalComposePath}" up -d --build`, {
1006
1015
  cwd: dirname(finalComposePath),
1007
1016
  timeout: 3e5
1008
1017
  });
@@ -1148,7 +1157,7 @@ const OpenRouterServiceLive = Layer.effect(OpenRouterService, Effect.sync(() =>
1148
1157
  //#endregion
1149
1158
  //#region services/open.ts
1150
1159
  init_src();
1151
- const execAsync$9 = promisify(exec);
1160
+ const execAsync$10 = promisify(exec);
1152
1161
  function getFileManagerCommand() {
1153
1162
  switch (process.platform) {
1154
1163
  case "linux": return "xdg-open";
@@ -1181,7 +1190,7 @@ function launchDetached(launch) {
1181
1190
  }
1182
1191
  async function isCommandAvailable(command) {
1183
1192
  try {
1184
- await execAsync$9(`${process.platform === "win32" ? "where" : "which"} ${command}`);
1193
+ await execAsync$10(`${process.platform === "win32" ? "where" : "which"} ${command}`);
1185
1194
  return true;
1186
1195
  } catch {
1187
1196
  return false;
@@ -4858,14 +4867,14 @@ init_codex_auth();
4858
4867
  init_queries();
4859
4868
  init_cliproxy();
4860
4869
  init_tmux();
4861
- init_config();
4870
+ init_config$1();
4862
4871
  const reauthSessions = /* @__PURE__ */ new Map();
4863
4872
  const SESSION_MAX_AGE_MS = 3600 * 1e3;
4864
- const readJsonBody$19 = Effect.gen(function* () {
4873
+ const readJsonBody$20 = Effect.gen(function* () {
4865
4874
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
4866
4875
  return text ? JSON.parse(text) : {};
4867
4876
  });
4868
- function rejectInvalidOrigin(request) {
4877
+ function rejectInvalidOrigin$1(request) {
4869
4878
  const originCheck = validateOrigin$1(request);
4870
4879
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
4871
4880
  return null;
@@ -4940,7 +4949,7 @@ async function getExistingLiveReauthSession() {
4940
4949
  return null;
4941
4950
  }
4942
4951
  const postCodexReauthRoute = HttpRouter.add("POST", "/api/settings/codex-reauth", httpHandler(Effect.gen(function* () {
4943
- const originError = rejectInvalidOrigin(yield* HttpServerRequest.HttpServerRequest);
4952
+ const originError = rejectInvalidOrigin$1(yield* HttpServerRequest.HttpServerRequest);
4944
4953
  if (originError) return originError;
4945
4954
  const existing = yield* Effect.promise(() => getExistingLiveReauthSession());
4946
4955
  if (existing) {
@@ -4964,9 +4973,9 @@ const postCodexReauthRoute = HttpRouter.add("POST", "/api/settings/codex-reauth"
4964
4973
  }, { headers: { "Set-Cookie": buildTerminalCookie(sessionName, terminalToken) } });
4965
4974
  })));
4966
4975
  const postCodexReauthStatusRoute = HttpRouter.add("POST", "/api/settings/codex-reauth/status", httpHandler(Effect.gen(function* () {
4967
- const originError = rejectInvalidOrigin(yield* HttpServerRequest.HttpServerRequest);
4976
+ const originError = rejectInvalidOrigin$1(yield* HttpServerRequest.HttpServerRequest);
4968
4977
  if (originError) return originError;
4969
- const body = yield* readJsonBody$19;
4978
+ const body = yield* readJsonBody$20;
4970
4979
  const sessionName = typeof body.session === "string" ? body.session : "";
4971
4980
  const token = typeof body.token === "string" ? body.token : "";
4972
4981
  const session = getLiveReauthSession(sessionName);
@@ -5491,6 +5500,7 @@ init_codex();
5491
5500
  init_launcher_generator();
5492
5501
  init_transcript_landing();
5493
5502
  init_operator_interventions();
5503
+ init_messaging();
5494
5504
  init_agents$1();
5495
5505
  init_runtime_census();
5496
5506
  const MINIMUM_NODE_MAJOR = 24;
@@ -17255,7 +17265,7 @@ var require_get_vercel_oidc_token = /* @__PURE__ */ __commonJSMin(((exports, mod
17255
17265
  err = error;
17256
17266
  }
17257
17267
  try {
17258
- const [{ getTokenPayload, isExpired }, { refreshToken }] = await Promise.all([await import("./token-util-DuPuNriz.js").then((m) => /* @__PURE__ */ __toESM(m.default)), await import("./token-BK6t8BIT.js").then((m) => /* @__PURE__ */ __toESM(m.default))]);
17268
+ const [{ getTokenPayload, isExpired }, { refreshToken }] = await Promise.all([await import("./token-util-D411CAq9.js").then((m) => /* @__PURE__ */ __toESM(m.default)), await import("./token-DrxwytsZ.js").then((m) => /* @__PURE__ */ __toESM(m.default))]);
17259
17269
  if (!token || isExpired(getTokenPayload(token), options?.expirationBufferMs)) {
17260
17270
  await refreshToken(options);
17261
17271
  token = getVercelOidcTokenSync();
@@ -29572,7 +29582,7 @@ var GoogleCloudTranscription = class {
29572
29582
  }
29573
29583
  async start(apiKey, model) {
29574
29584
  try {
29575
- const { SpeechClient } = await import("./src-CXmtj0im.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1)).then((module) => module.v1p1beta1 ?? module.v1);
29585
+ const { SpeechClient } = await import("./src-rac0Mepv.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1)).then((module) => module.v1p1beta1 ?? module.v1);
29576
29586
  if (this.closed) return;
29577
29587
  const stream = new SpeechClient({ apiKey }).streamingRecognize({
29578
29588
  config: {
@@ -29836,7 +29846,7 @@ const DEFAULT_VOICE_SETTINGS = {
29836
29846
  model: "gpt-4.1-mini"
29837
29847
  }
29838
29848
  };
29839
- const readJsonBody$18 = Effect.gen(function* () {
29849
+ const readJsonBody$19 = Effect.gen(function* () {
29840
29850
  const request = yield* HttpServerRequest.HttpServerRequest;
29841
29851
  const contentLength = Number(request.headers["content-length"] ?? 0);
29842
29852
  if (Number.isFinite(contentLength) && contentLength > MAX_VOICE_SETTINGS_BODY_BYTES) return {
@@ -29959,7 +29969,7 @@ const getVoiceSettingsRoute = HttpRouter.add("GET", "/api/voice/settings", httpH
29959
29969
  const putVoiceSettingsRoute = HttpRouter.add("PUT", "/api/voice/settings", httpHandler(Effect.gen(function* () {
29960
29970
  const originError = requireTrustedOrigin$2(yield* HttpServerRequest.HttpServerRequest);
29961
29971
  if (originError) return originError;
29962
- const parsed = yield* readJsonBody$18;
29972
+ const parsed = yield* readJsonBody$19;
29963
29973
  if (!parsed.ok) return jsonResponse({ error: parsed.error }, { status: 413 });
29964
29974
  const body = parsed.body;
29965
29975
  if (!isVoiceSettings(body)) return jsonResponse({ error: "Invalid voice settings payload" }, { status: 400 });
@@ -30252,12 +30262,219 @@ function streamAgentOutput(eventStore, agentId) {
30252
30262
  })))));
30253
30263
  }
30254
30264
  //#endregion
30255
- //#region services/read-workspace-file.ts
30265
+ //#region services/dashboard-db-task.ts
30256
30266
  init_acp_conversation_parser();
30257
30267
  init_codex_conversation_parser();
30258
30268
  init_ohmypi_conversation_parser();
30259
30269
  init_pi_conversation_parser();
30260
30270
  init_conversation_service();
30271
+ init_control_settings();
30272
+ const MAX_PENDING_JOBS = 32;
30273
+ const SEMANTIC_SEARCH_TIMEOUT_MS = Number.parseInt(process.env["OVERDECK_SEMANTIC_SEARCH_TIMEOUT_MS"] ?? "15000", 10);
30274
+ const COALESCED_OPERATIONS = new Set([
30275
+ "scanConversations",
30276
+ "enrichSessions",
30277
+ "embedSessions",
30278
+ "searchSessionsSemantic",
30279
+ "listSubstrateBugWeights"
30280
+ ]);
30281
+ const workers = {
30282
+ read: null,
30283
+ long: null,
30284
+ semantic: null
30285
+ };
30286
+ const pending = /* @__PURE__ */ new Map();
30287
+ const sharedJobs = /* @__PURE__ */ new Map();
30288
+ let latestSemanticJobId = null;
30289
+ function workerScriptUrl() {
30290
+ return import.meta.url.endsWith(".ts") ? new URL("./dashboard-db-worker.ts", import.meta.url) : new URL("./dashboard-db-worker.js", import.meta.url);
30291
+ }
30292
+ function failPendingForLane(lane, err) {
30293
+ for (const [id, job] of pending.entries()) {
30294
+ if (job.lane !== lane) continue;
30295
+ if (job.timeout) clearTimeout(job.timeout);
30296
+ job.reject(err);
30297
+ pending.delete(id);
30298
+ }
30299
+ for (const [key, job] of sharedJobs.entries()) if (job.lane === lane) sharedJobs.delete(key);
30300
+ if (lane === "semantic") latestSemanticJobId = null;
30301
+ }
30302
+ function stableStringify(value) {
30303
+ if (value == null || typeof value !== "object") return JSON.stringify(value);
30304
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
30305
+ return `{${Object.entries(value).filter(([, v]) => typeof v !== "function" && v !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
30306
+ }
30307
+ function coalescingKey(operation, payload) {
30308
+ if (!COALESCED_OPERATIONS.has(operation)) return null;
30309
+ return `${operation}:${stableStringify(payload)}`;
30310
+ }
30311
+ function workerLane(operation) {
30312
+ if (operation === "searchSessionsSemantic") return "semantic";
30313
+ return COALESCED_OPERATIONS.has(operation) ? "long" : "read";
30314
+ }
30315
+ function cancelOlderSemanticSearches() {
30316
+ if (!latestSemanticJobId && !workers.semantic) return;
30317
+ failPendingForLane("semantic", /* @__PURE__ */ new Error("Superseded by a newer semantic search"));
30318
+ workers.semantic?.terminate();
30319
+ workers.semantic = null;
30320
+ }
30321
+ function aggregateDiscoveredSessionCostByPayload(payload) {
30322
+ if (typeof payload === "string") return aggregateDiscoveredSessionCostBy(payload);
30323
+ const input = payload;
30324
+ return aggregateDiscoveredSessionCostBy(input?.groupBy ?? "workspace", input?.filter ?? {});
30325
+ }
30326
+ function getWorker(lane) {
30327
+ const existing = workers[lane];
30328
+ if (existing) return existing;
30329
+ const worker = new Worker(workerScriptUrl(), { execArgv: process.execArgv.filter((arg) => !arg.startsWith("--inspect")) });
30330
+ workers[lane] = worker;
30331
+ worker.on("message", (message) => {
30332
+ const job = pending.get(message.id);
30333
+ if (!job) return;
30334
+ if (message.progress !== void 0) {
30335
+ job.progressChain = job.progressChain.then(async () => {
30336
+ for (const listener of job.progressListeners) await listener(message.progress);
30337
+ });
30338
+ return;
30339
+ }
30340
+ pending.delete(message.id);
30341
+ if (job.timeout) clearTimeout(job.timeout);
30342
+ if (job.lane === "semantic" && latestSemanticJobId === message.id) latestSemanticJobId = null;
30343
+ if (message.ok) {
30344
+ job.progressChain.then(() => job.resolve(message.result), job.reject);
30345
+ return;
30346
+ }
30347
+ const err = message.error?.name === "CostThresholdError" ? new CostThresholdError(message.error.estimatedCost ?? 0, message.error.threshold ?? 0, message.error.sessionCount ?? 0) : new Error(message.error?.message ?? "Dashboard database worker failed");
30348
+ err.name = message.error?.name ?? "DashboardDatabaseWorkerError";
30349
+ err.stack = message.error?.stack;
30350
+ job.progressChain.then(() => job.reject(err), job.reject);
30351
+ });
30352
+ worker.on("error", (err) => {
30353
+ failPendingForLane(lane, err);
30354
+ if (workers[lane] === worker) workers[lane] = null;
30355
+ });
30356
+ worker.on("exit", (code) => {
30357
+ if (code !== 0) failPendingForLane(lane, /* @__PURE__ */ new Error(`Dashboard database ${lane} worker exited with code ${code}`));
30358
+ if (workers[lane] === worker) workers[lane] = null;
30359
+ });
30360
+ return worker;
30361
+ }
30362
+ async function runInline$1(operation, payload, onProgress) {
30363
+ switch (operation) {
30364
+ case "getDiscoveredStats": return getDiscoveredStats();
30365
+ case "listDiscoveredSessions": {
30366
+ const filter = payload;
30367
+ return {
30368
+ sessions: findDiscoveredSessions(filter),
30369
+ total: countDiscoveredSessions({
30370
+ ...filter,
30371
+ limit: void 0,
30372
+ offset: void 0
30373
+ })
30374
+ };
30375
+ }
30376
+ case "listSessionsFeed": return listSessionsFeed(payload);
30377
+ case "getSessionsFeedFacets": return getSessionsFeedFacets(payload);
30378
+ case "getDiscoveredSessionById": return getDiscoveredSessionById(payload);
30379
+ case "aggregateDiscoveredSessionCost": return aggregateDiscoveredSessionCost(payload);
30380
+ case "aggregateDiscoveredSessionCostBy": return aggregateDiscoveredSessionCostByPayload(payload);
30381
+ case "searchSessions":
30382
+ case "searchSessionsSemantic": return searchSessions(payload);
30383
+ case "scanConversations": return scan({
30384
+ ...payload,
30385
+ onProgress
30386
+ });
30387
+ case "enrichSessions": return enrichSessions({
30388
+ ...payload,
30389
+ onProgress
30390
+ });
30391
+ case "embedSessions": return embedSessions({
30392
+ ...payload,
30393
+ autoInstall: true,
30394
+ onProgress
30395
+ });
30396
+ case "getConversationByName": return getConversationByName(payload);
30397
+ case "getSetting": return getSetting(payload);
30398
+ case "setSetting": {
30399
+ const input = payload;
30400
+ setSetting(input.key, input.value);
30401
+ return null;
30402
+ }
30403
+ case "listSubstrateBugWeights": {
30404
+ const input = payload;
30405
+ return listSubstrateBugWeights(input.window, {
30406
+ limit: input.limit,
30407
+ offset: input.offset
30408
+ });
30409
+ }
30410
+ case "getArtifactBySlug": {
30411
+ const { getArtifactBySlugJob } = await import("./artifact-index-jobs-D1q_vR3V.js");
30412
+ return getArtifactBySlugJob(payload);
30413
+ }
30414
+ case "listArtifactsForWorkspaceOrIssue": {
30415
+ const { listArtifactsForWorkspaceOrIssueJob } = await import("./artifact-index-jobs-D1q_vR3V.js");
30416
+ return listArtifactsForWorkspaceOrIssueJob(payload);
30417
+ }
30418
+ case "unshareArtifactBySlug": {
30419
+ const { unshareArtifactBySlugJob } = await import("./artifact-index-jobs-D1q_vR3V.js");
30420
+ return unshareArtifactBySlugJob(payload);
30421
+ }
30422
+ }
30423
+ }
30424
+ function runDashboardDbJob(operation, payload, onProgress) {
30425
+ if (import.meta.url.endsWith(".ts") && process.env["VITEST"]) return runInline$1(operation, payload, onProgress);
30426
+ const key = coalescingKey(operation, payload);
30427
+ const existing = key ? sharedJobs.get(key) : void 0;
30428
+ if (existing) {
30429
+ if (onProgress) existing.progressListeners.add(onProgress);
30430
+ return existing.promise;
30431
+ }
30432
+ if (pending.size >= MAX_PENDING_JOBS) return Promise.reject(/* @__PURE__ */ new Error("Dashboard database worker queue is full"));
30433
+ const id = randomUUID$1();
30434
+ const lane = workerLane(operation);
30435
+ if (operation === "searchSessionsSemantic") {
30436
+ cancelOlderSemanticSearches();
30437
+ latestSemanticJobId = id;
30438
+ }
30439
+ const progressListeners = /* @__PURE__ */ new Set();
30440
+ if (onProgress) progressListeners.add(onProgress);
30441
+ const promise = new Promise((resolve, reject) => {
30442
+ const timeout = operation === "searchSessionsSemantic" ? setTimeout(() => {
30443
+ if (!pending.get(id)) return;
30444
+ pending.delete(id);
30445
+ for (const [sharedKey, sharedJob] of sharedJobs.entries()) if (sharedJob.promise === promise) sharedJobs.delete(sharedKey);
30446
+ if (latestSemanticJobId === id) latestSemanticJobId = null;
30447
+ reject(/* @__PURE__ */ new Error("Semantic search timed out"));
30448
+ workers.semantic?.terminate();
30449
+ workers.semantic = null;
30450
+ }, Number.isFinite(SEMANTIC_SEARCH_TIMEOUT_MS) ? SEMANTIC_SEARCH_TIMEOUT_MS : 15e3) : null;
30451
+ pending.set(id, {
30452
+ lane,
30453
+ operation,
30454
+ resolve,
30455
+ reject,
30456
+ progressListeners,
30457
+ progressChain: Promise.resolve(),
30458
+ timeout
30459
+ });
30460
+ getWorker(lane).postMessage({
30461
+ id,
30462
+ operation,
30463
+ payload
30464
+ });
30465
+ });
30466
+ if (key) {
30467
+ sharedJobs.set(key, {
30468
+ lane,
30469
+ promise,
30470
+ progressListeners
30471
+ });
30472
+ promise.then(() => sharedJobs.delete(key), () => sharedJobs.delete(key));
30473
+ }
30474
+ return promise;
30475
+ }
30476
+ //#endregion
30477
+ //#region services/read-workspace-file.ts
30261
30478
  init_src();
30262
30479
  init_projects();
30263
30480
  const MAX_WORKSPACE_FILE_BYTES = 256 * 1024;
@@ -30590,6 +30807,98 @@ function normalizeSessionsFeedFilter(input) {
30590
30807
  };
30591
30808
  }
30592
30809
  //#endregion
30810
+ //#region services/conversation/subagents.ts
30811
+ /**
30812
+ * Read-only discovery and streaming support for Claude Code conversation subagents.
30813
+ * See docs/CONVERSATION-SUBAGENTS.md for the file layout and transport contract.
30814
+ */
30815
+ const metaCache = /* @__PURE__ */ new Map();
30816
+ const SAFE_AGENT_ID = /^[A-Za-z0-9_-]+$/;
30817
+ function subagentsDirFor(sessionFile) {
30818
+ return join(sessionFile.endsWith(".jsonl") ? sessionFile.slice(0, -6) : sessionFile, "subagents");
30819
+ }
30820
+ function parseMeta(agentId, value) {
30821
+ if (typeof value !== "object" || value === null || !("agentType" in value) || typeof value.agentType !== "string" || !("description" in value) || typeof value.description !== "string" || !("toolUseId" in value) || typeof value.toolUseId !== "string" || !("spawnDepth" in value) || typeof value.spawnDepth !== "number") throw new Error("invalid subagent metadata");
30822
+ return {
30823
+ agentId,
30824
+ agentType: value.agentType,
30825
+ description: value.description,
30826
+ toolUseId: value.toolUseId,
30827
+ spawnDepth: value.spawnDepth
30828
+ };
30829
+ }
30830
+ async function readMeta(metaPath, agentId) {
30831
+ const cached = metaCache.get(metaPath);
30832
+ if (cached !== void 0) return cached;
30833
+ try {
30834
+ const parsed = parseMeta(agentId, JSON.parse(await readFile(metaPath, "utf8")));
30835
+ metaCache.set(metaPath, parsed);
30836
+ return parsed;
30837
+ } catch (error) {
30838
+ console.warn(`[conversation-subagents] Failed to parse ${metaPath}:`, error);
30839
+ return null;
30840
+ }
30841
+ }
30842
+ async function listSubagentMetas(sessionFile) {
30843
+ const subagentsDir = subagentsDirFor(sessionFile);
30844
+ let entries;
30845
+ try {
30846
+ entries = await readdir(subagentsDir);
30847
+ } catch (error) {
30848
+ if (error.code === "ENOENT") return [];
30849
+ throw error;
30850
+ }
30851
+ const metas = [];
30852
+ for (const entry of entries.sort()) {
30853
+ const match = /^agent-(.+)\.meta\.json$/.exec(entry);
30854
+ if (!match?.[1]) continue;
30855
+ const meta = await readMeta(join(subagentsDir, entry), match[1]);
30856
+ if (meta) metas.push(meta);
30857
+ }
30858
+ return metas;
30859
+ }
30860
+ function subagentTranscriptPath(sessionFile, agentId) {
30861
+ if (!SAFE_AGENT_ID.test(agentId)) return null;
30862
+ const subagentsDir = resolve(subagentsDirFor(sessionFile));
30863
+ const transcriptPath = resolve(subagentsDir, `agent-${agentId}.jsonl`);
30864
+ if (dirname(transcriptPath) !== subagentsDir || !transcriptPath.startsWith(`${subagentsDir}${sep}`)) return null;
30865
+ return transcriptPath;
30866
+ }
30867
+ async function startSubagentListPolling(sessionFile, pendingToolUseIds, emit) {
30868
+ let stopped = false;
30869
+ let lastSerialized = null;
30870
+ let refreshChain = Promise.resolve();
30871
+ const refresh = () => {
30872
+ refreshChain = refreshChain.then(async () => {
30873
+ if (stopped) return;
30874
+ const pending = pendingToolUseIds();
30875
+ const subagents = (await listSubagentMetas(sessionFile)).map((meta) => ({
30876
+ ...meta,
30877
+ status: pending.has(meta.toolUseId) ? "running" : "done"
30878
+ }));
30879
+ const serialized = JSON.stringify(subagents);
30880
+ if (serialized === lastSerialized) return;
30881
+ lastSerialized = serialized;
30882
+ emit({
30883
+ kind: "subagents",
30884
+ subagents
30885
+ });
30886
+ }).catch((error) => {
30887
+ console.warn(`[conversation-subagents] Failed to refresh ${sessionFile}:`, error);
30888
+ });
30889
+ return refreshChain;
30890
+ };
30891
+ await refresh();
30892
+ const interval = setInterval(() => void refresh(), 2e3);
30893
+ return {
30894
+ refresh,
30895
+ stop: () => {
30896
+ stopped = true;
30897
+ clearInterval(interval);
30898
+ }
30899
+ };
30900
+ }
30901
+ //#endregion
30593
30902
  //#region ws-rpc.ts
30594
30903
  /**
30595
30904
  * WebSocket RPC handlers — implements PanRpcGroup using Effect (PAN-428 B5)
@@ -30599,6 +30908,7 @@ function normalizeSessionsFeedFilter(input) {
30599
30908
  * are implemented via TerminalService (dual-runtime PTY, B20).
30600
30909
  */
30601
30910
  init_src();
30911
+ init_conversations();
30602
30912
  init_paths();
30603
30913
  init_projects();
30604
30914
  function storedToDomainEvent(stored) {
@@ -31101,7 +31411,8 @@ const PanRpcLayer = PanRpcGroup.toLayer(Effect.gen(function* () {
31101
31411
  if (getHarnessBehavior(conv.harness).transcriptKind !== "claude-jsonl") return conversationDiscoveringStream();
31102
31412
  const launcherTmuxSession = conv.tmuxSession;
31103
31413
  const resolvedSessionId = (launcherTmuxSession ? yield* Effect.promise(() => readLauncherPinnedSessionId(launcherTmuxSession)) : null) ?? conv.claudeSessionId;
31104
- const sessionFile = resolvedSessionId ? sessionFilePath(conv.cwd, resolvedSessionId) : null;
31414
+ const parentSessionFile = resolvedSessionId ? sessionFilePath(conv.cwd, resolvedSessionId) : null;
31415
+ const sessionFile = parentSessionFile && input.agentId ? subagentTranscriptPath(parentSessionFile, input.agentId) : parentSessionFile;
31105
31416
  const model = conv.model ?? null;
31106
31417
  if (!sessionFile) return conversationDiscoveringStream();
31107
31418
  if (isPiSessionFile(sessionFile)) return conversationDiscoveringStream();
@@ -31140,11 +31451,15 @@ const PanRpcLayer = PanRpcGroup.toLayer(Effect.gen(function* () {
31140
31451
  compactBoundaries: initial.compactBoundaries && initial.compactBoundaries.length > 0 ? initial.compactBoundaries : void 0,
31141
31452
  contextUsage: currentContextUsage
31142
31453
  });
31143
- return watchConversation(sessionFile, (result) => {
31454
+ let pendingSubagentToolUseIds = new Set(initial.pendingToolUse.keys());
31455
+ const subagentPoller = input.agentId ? null : await startSubagentListPolling(sessionFile, () => pendingSubagentToolUseIds, offer);
31456
+ const handle = watchConversation(sessionFile, (result) => {
31144
31457
  const gate = gateSnapshotEmission(result.byteOffset < currentByteOffset, result.messages.length, highWaterCount);
31145
31458
  highWaterCount = gate.highWaterCount;
31146
31459
  currentByteOffset = result.byteOffset;
31147
31460
  currentContextUsage = contextUsageFromParseResult(result, model);
31461
+ pendingSubagentToolUseIds = new Set(result.pendingToolUse.keys());
31462
+ subagentPoller?.refresh();
31148
31463
  if (gate.suppressedShrink) console.warn(`[conv-stream] suppressed shrinking reset for ${input.conversationName}: byteOffset ${currentByteOffset} (re-parsed ${result.messages.length} msgs) < high-water ${highWaterCount} — treating as transient rewrite, merging instead of replacing`);
31149
31464
  offer({
31150
31465
  kind: "messages",
@@ -31160,6 +31475,10 @@ const PanRpcLayer = PanRpcGroup.toLayer(Effect.gen(function* () {
31160
31475
  byteOffset: initial.byteOffset,
31161
31476
  priorState
31162
31477
  });
31478
+ return { stop() {
31479
+ handle.stop();
31480
+ subagentPoller?.stop();
31481
+ } };
31163
31482
  }), (handle) => Effect.sync(() => {
31164
31483
  handle.stop();
31165
31484
  })));
@@ -31372,7 +31691,6 @@ init_state_home();
31372
31691
  init_pan_dir();
31373
31692
  init_in_flight_guard();
31374
31693
  init_remote_agents();
31375
- init_dag();
31376
31694
  init_swarm_readiness();
31377
31695
  var PlanQualityLintError = class extends Error {
31378
31696
  constructor(issues) {
@@ -31487,9 +31805,6 @@ const PARALLEL_SAFE_REASON_FIELDS = [
31487
31805
  "readinessReason",
31488
31806
  "readiness_reason"
31489
31807
  ];
31490
- const FILE_SIZE_BASELINE_PATH = "scripts/file-size-baseline.txt";
31491
- const FILE_SIZE_RECONCILIATION_PATTERN = /\b(?:drop(?:ped|ping)?|lower(?:ed|ing)?|reconcil(?:e|ed|es|ing|iation)|remov(?:e|ed|es|ing)|updat(?:e|ed|es|ing))\b/i;
31492
- const FILE_SIZE_LINT_COMMAND_PATTERN = /(?:\b(?:bun|npm|pnpm|yarn)\s+(?:run\s+)?lint(?::file-size)?(?=$|[\s;&|])|scripts\/lint-file-size\.sh)/i;
31493
31808
  function issue(itemId, rule, message) {
31494
31809
  return {
31495
31810
  itemId,
@@ -31630,44 +31945,6 @@ function lintDocsCoverage(doc) {
31630
31945
  if (doc.plan.items.filter((item) => item.status !== "cancelled").some(documentsSomething)) return [];
31631
31946
  return [issue(null, "docs-item-missing", "Plan has no documentation work item: no active item sets metadata.kind \"docs\" or names a documentation file (.md/.mdx, or a docs/ path) in metadata.files_scope. Add the item naming the exact doc files it touches, or set plan.metadata.docsJustification to state why the change alters no documented surface.")];
31632
31947
  }
31633
- function itemSearchText(item) {
31634
- return [
31635
- item.title,
31636
- item.narrative?.Action,
31637
- ...item.metadata?.expected_outputs ?? [],
31638
- ...acceptanceCriteria(item).map((ac) => ac.title)
31639
- ].filter((value) => typeof value === "string").join("\n");
31640
- }
31641
- function scopeIncludesPath(item, filePath) {
31642
- return (item.metadata?.files_scope ?? []).some((scope) => compileGlob(scope).regex.test(filePath));
31643
- }
31644
- function requiresFileSizeLint(item) {
31645
- if ((item.metadata?.verify_commands ?? []).some((command) => typeof command === "string" && FILE_SIZE_LINT_COMMAND_PATTERN.test(command))) return true;
31646
- return acceptanceCriteria(item).some((ac) => FILE_SIZE_LINT_COMMAND_PATTERN.test(ac.title));
31647
- }
31648
- function lintDeferredFileSizeRatchet(doc) {
31649
- const activeItems = doc.plan.items.filter((item) => item.status !== "cancelled");
31650
- const readiness = analyzeSwarmReadiness(doc);
31651
- const issues = [];
31652
- const seen = /* @__PURE__ */ new Set();
31653
- for (const reconciliation of activeItems) {
31654
- if (!scopeIncludesPath(reconciliation, FILE_SIZE_BASELINE_PATH)) continue;
31655
- const reconciliationText = itemSearchText(reconciliation);
31656
- if (!reconciliationText.includes(FILE_SIZE_BASELINE_PATH) || !FILE_SIZE_RECONCILIATION_PATTERN.test(reconciliationText)) continue;
31657
- const reconciledSourceScopes = (reconciliation.metadata?.files_scope ?? []).filter((scope) => scope.startsWith("src/") && reconciliationText.includes(scope));
31658
- if (reconciledSourceScopes.length === 0) continue;
31659
- for (const candidate of activeItems) {
31660
- if (candidate.id === reconciliation.id || scopeIncludesPath(candidate, FILE_SIZE_BASELINE_PATH) || !requiresFileSizeLint(candidate)) continue;
31661
- const deferredFile = (readiness.overlapMatrix[candidate.id]?.[reconciliation.id] ?? []).find((filePath) => reconciledSourceScopes.some((scope) => compileGlob(scope).regex.test(filePath)));
31662
- if (!deferredFile) continue;
31663
- const key = `${candidate.id}\0${deferredFile}`;
31664
- if (seen.has(key)) continue;
31665
- seen.add(key);
31666
- issues.push(issue(candidate.id, "deferred-file-size-ratchet", `Item ${candidate.id} requires file-size-backed lint after changing baselined file ${deferredFile}, but defers ${FILE_SIZE_BASELINE_PATH} reconciliation to item ${reconciliation.id}. Include the baseline in ${candidate.id} metadata.files_scope and reconcile it in the same commit, or use an intermediate verification command that excludes the file-size ratchet. For an active plan, return the issue to planning, preserve stable item IDs, apply one of these repairs, and re-finalize before rerunning inspection.`));
31667
- }
31668
- }
31669
- return issues;
31670
- }
31671
31948
  function orderedPairKey(left, right) {
31672
31949
  return [left, right].sort().join("\0");
31673
31950
  }
@@ -31723,13 +32000,34 @@ function hasBlocksCycle(doc) {
31723
32000
  }
31724
32001
  return visited < itemIds.size;
31725
32002
  }
32003
+ function lintProjectedSurface(doc) {
32004
+ const issues = [];
32005
+ const files = /* @__PURE__ */ new Set();
32006
+ const subsystems = /* @__PURE__ */ new Set();
32007
+ for (const item of doc.plan.items) {
32008
+ if (item.status === "cancelled") continue;
32009
+ const filesScope = item.metadata?.files_scope;
32010
+ if (Array.isArray(filesScope)) for (const scope of filesScope) {
32011
+ const normalized = scope.replace(/\/\*+$/, "");
32012
+ files.add(normalized);
32013
+ const parts = normalized.split("/");
32014
+ if (parts.length >= 2) subsystems.add(`${parts[0]}/${parts[1]}`);
32015
+ else if (parts.length === 1) subsystems.add(parts[0]);
32016
+ }
32017
+ }
32018
+ const fileCount = files.size;
32019
+ const subsystemCount = subsystems.size;
32020
+ const hasJustification = typeof doc.plan.metadata?.sizeJustification === "string" && doc.plan.metadata.sizeJustification.trim().length > 0;
32021
+ if ((fileCount > 25 || subsystemCount > 6) && !hasJustification) issues.push(issue(null, "projected-surface", `Plan touches ${fileCount} files (limit: 25) and ${subsystemCount} subsystems (limit: 6). Split into independently-shippable sibling issues per the swarm contract, or set plan.metadata.sizeJustification for genuinely inseparable work.`));
32022
+ return issues;
32023
+ }
31726
32024
  function lintPlanQuality(doc, options = {}) {
31727
32025
  return [
32026
+ ...lintProjectedSurface(doc),
31728
32027
  ...doc.plan.items.flatMap((item) => item.status === "cancelled" ? [] : lintItem(item)),
31729
32028
  ...lintDocumentReferences(doc),
31730
32029
  ...lintDocsCoverage(doc),
31731
32030
  ...lintTraceCoverage(doc, options.prdText),
31732
- ...lintDeferredFileSizeRatchet(doc),
31733
32031
  ...lintOverlapAudit(doc, options)
31734
32032
  ];
31735
32033
  }
@@ -31746,7 +32044,7 @@ const execFileAsync$18 = promisify(execFile);
31746
32044
  async function loadTrackerIssues() {
31747
32045
  const map = /* @__PURE__ */ new Map();
31748
32046
  try {
31749
- const { getSharedIssueService } = await import("./issue-service-singleton-BvJ09SaR.js");
32047
+ const { getSharedIssueService } = await import("./issue-service-singleton-B2wXiIlw.js");
31750
32048
  const issues = (await getSharedIssueService()).getIssues();
31751
32049
  for (const issue of issues) if (issue.identifier) map.set(issue.identifier.toUpperCase(), issue);
31752
32050
  } catch {}
@@ -31769,7 +32067,7 @@ async function loadDockerContainers() {
31769
32067
  }
31770
32068
  async function loadActiveRemoteAgentStates() {
31771
32069
  try {
31772
- const { listActiveRemoteAgentStates } = await import("./remote-agents-CA3Ihlxj.js");
32070
+ const { listActiveRemoteAgentStates } = await import("./remote-agents-CLZB3Coc.js");
31773
32071
  return listActiveRemoteAgentStates();
31774
32072
  } catch {
31775
32073
  return [];
@@ -31824,9 +32122,11 @@ function refreshMembershipSnapshot(project, snapshot, getMembership, now) {
31824
32122
  snapshot.value = value;
31825
32123
  snapshot.refreshedAt = now();
31826
32124
  snapshot.lastError = void 0;
32125
+ snapshot.lastErrorReason = void 0;
31827
32126
  snapshot.lastErrorAt = void 0;
31828
32127
  }).catch((error) => {
31829
32128
  snapshot.lastError = error instanceof Error ? error.message : String(error);
32129
+ snapshot.lastErrorReason = error instanceof PipelineMembershipUnavailableError ? error.reason : "gather_failed";
31830
32130
  snapshot.lastErrorAt = now();
31831
32131
  console.warn(`[pipeline-membership] refresh failed for ${project.name ?? project.path}; keeping last-good snapshot:`, snapshot.lastError);
31832
32132
  }).finally(() => {
@@ -31844,7 +32144,8 @@ function readPipelineMembershipSnapshotsForProjects(projects) {
31844
32144
  };
31845
32145
  return {
31846
32146
  project,
31847
- error: /* @__PURE__ */ new Error(snapshot?.lastError ? `Pipeline membership refresh failed: ${snapshot.lastError}` : "Pipeline membership snapshot is loading")
32147
+ error: /* @__PURE__ */ new Error(snapshot?.lastError ? `Pipeline membership refresh failed: ${snapshot.lastError}` : "Pipeline membership snapshot is loading"),
32148
+ unavailableReason: snapshot?.lastErrorReason
31848
32149
  };
31849
32150
  });
31850
32151
  }
@@ -32927,15 +33228,19 @@ async function completePlanningAutoSpawn(options) {
32927
33228
  },
32928
33229
  body: JSON.stringify({
32929
33230
  issueId: options.issueId,
32930
- role: "work"
33231
+ role: "work",
33232
+ startedBy: "planning-auto-handoff",
33233
+ autoSpawnConsentRequired: true
32931
33234
  })
32932
33235
  });
32933
33236
  const body = await response.json().catch(() => ({}));
32934
33237
  const agentId = typeof body["agentId"] === "string" ? body["agentId"] : `agent-${options.issueId.toLowerCase()}`;
32935
33238
  if (response.ok && body["success"] !== false) {
32936
- emitCompletePlanningPhase(options.issueId, "autoSpawn", "success", "work agent spawn requested", { agentId });
33239
+ const workAgentQueued = body["startingContainers"] === true;
33240
+ emitCompletePlanningPhase(options.issueId, "autoSpawn", "success", workAgentQueued ? "container startup queued before work-agent spawn" : "work agent spawn requested", { agentId });
32937
33241
  return {
32938
33242
  workAgentSpawned: true,
33243
+ ...workAgentQueued ? { workAgentQueued: true } : {},
32939
33244
  workAgentSession: agentId
32940
33245
  };
32941
33246
  }
@@ -32945,9 +33250,14 @@ async function completePlanningAutoSpawn(options) {
32945
33250
  const recovery = await (options.fetchImpl ?? fetch)(new URL(`/api/workspaces/${encodeURIComponent(options.issueId)}/rebuild-and-start`, dashboardOrigin), {
32946
33251
  method: "POST",
32947
33252
  headers: {
33253
+ "content-type": "application/json",
32948
33254
  origin: dashboardOrigin,
32949
33255
  ...internalTokenHeaders
32950
- }
33256
+ },
33257
+ body: JSON.stringify({
33258
+ startedBy: "planning-auto-handoff",
33259
+ autoSpawnConsentRequired: true
33260
+ })
32951
33261
  });
32952
33262
  const recoveryBody = await recovery.json().catch(() => ({}));
32953
33263
  if (recovery.ok && recoveryBody["success"] !== false) {
@@ -33008,7 +33318,7 @@ async function completePlanningForIssue(options) {
33008
33318
  const { request, id, body, eventStore, linear, lifecycle } = options;
33009
33319
  const skipKill = body?.skipKill === true;
33010
33320
  const bodyAutoSpawn = body?.autoSpawn;
33011
- const autoSpawn = resolveAutoSpawnOnFinalize(bodyAutoSpawn, id);
33321
+ const autoSpawn = await resolveAutoSpawnOnFinalize(bodyAutoSpawn, id);
33012
33322
  const noPrd = body?.noPrd === true;
33013
33323
  if (bodyAutoSpawn === true) {
33014
33324
  const originCheck = validateOrigin$1(request);
@@ -33096,8 +33406,10 @@ async function completePlanningForIssue(options) {
33096
33406
  workspacePath,
33097
33407
  issueId: id
33098
33408
  }));
33099
- if (draftPromotion.promoted) emitCompletePlanningPhase(id, "prdPromote", "success", `promoted ${draftPromotion.source} -> ${draftPromotion.path}`);
33100
- else emitCompletePlanningPhase(id, "prdPromote", "skipped", draftPromotion.reason);
33409
+ if (draftPromotion.promoted) {
33410
+ const removalNote = draftPromotion.sourceRemoved ? "" : " (workspace copy left in place)";
33411
+ emitCompletePlanningPhase(id, "prdPromote", "success", `promoted ${draftPromotion.source} -> ${draftPromotion.path}${removalNote}`);
33412
+ } else emitCompletePlanningPhase(id, "prdPromote", "skipped", draftPromotion.reason);
33101
33413
  } catch (error) {
33102
33414
  const reason = error instanceof Error ? error.message : String(error);
33103
33415
  emitCompletePlanningPhase(id, "prdPromote", "failure", reason);
@@ -33374,6 +33686,10 @@ async function reopenWorkspaceStatePromise(issueId, workspacePath, options = {})
33374
33686
  });
33375
33687
  result.specialistStatesReset = true;
33376
33688
  const resolved = resolveProjectFromIssueSync(issueId);
33689
+ if (resolved) {
33690
+ const project = getProjectSync(resolved.projectKey);
33691
+ if (project) clearRecordPipelineClosedOutSync(project, issueId.toUpperCase());
33692
+ }
33377
33693
  if (resolved) try {
33378
33694
  const noteParts = [`Reopened on ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`];
33379
33695
  if (options.reason) noteParts.push(`reason: ${options.reason}`);
@@ -33408,7 +33724,7 @@ init_projects();
33408
33724
  init_tracker_utils();
33409
33725
  init_agents$1();
33410
33726
  init_issue_id();
33411
- const execAsync$8 = promisify(exec);
33727
+ const execAsync$9 = promisify(exec);
33412
33728
  const execFileAsync$15 = promisify(execFile);
33413
33729
  function isGitHubIssue$4(issueId) {
33414
33730
  const resolved = resolveGitHubIssueSync(issueId);
@@ -33480,7 +33796,7 @@ async function closeIssuePullRequest(issueId, reason = "Canceled via Overdeck")
33480
33796
  timeout: 15e3
33481
33797
  });
33482
33798
  try {
33483
- const { setReviewStatusSync } = await import("./review-status-btEYlaxA.js");
33799
+ const { setReviewStatusSync } = await import("./review-status-Dma9SFaN.js");
33484
33800
  setReviewStatusSync(issueId.toUpperCase(), { prUrl: void 0 });
33485
33801
  } catch {}
33486
33802
  return [`Closed PR #${prNumber} on ${githubCheck.owner}/${githubCheck.repo}`];
@@ -33534,7 +33850,7 @@ async function runDestructiveIssueLifecycle(id, mode, opts = {}) {
33534
33850
  const { ctx, projectConfig } = buildLifecycleContext(id, getIssueDataService$8().getIssueSource(id) ?? void 0);
33535
33851
  const deleteWorkspace = opts.deleteWorkspace ?? true;
33536
33852
  cleanupLog.push(...await closeIssuePullRequest(id, mode === "cancel" ? "Canceled via Overdeck" : "Reset to Todo via Overdeck"));
33537
- const { resetToTodo, cancelIssueWorkflow } = await import("./lifecycle-xmBfK2GJ.js");
33853
+ const { resetToTodo, cancelIssueWorkflow } = await import("./lifecycle-B3s4sM1G.js");
33538
33854
  const workflow = mode === "cancel" ? cancelIssueWorkflow : resetToTodo;
33539
33855
  const result = await Effect.runPromise(workflow(ctx, {
33540
33856
  deleteWorkspace,
@@ -33549,7 +33865,7 @@ async function runDestructiveIssueLifecycle(id, mode, opts = {}) {
33549
33865
  }));
33550
33866
  cleanupLog.push(...result.steps.flatMap((step) => step.details || [step.error].filter(Boolean)));
33551
33867
  if (mode === "cancel") try {
33552
- const { transitionXBriefOnMain } = await import("./lifecycle-io-DPo7x11I.js");
33868
+ const { transitionXBriefOnMain } = await import("./lifecycle-io-CzTHEooc.js");
33553
33869
  const tx = await Effect.runPromise(transitionXBriefOnMain(ctx.projectPath, id, "cancelled", "cancelled", `scope: cancel ${id.toUpperCase()} xBRIEF`));
33554
33870
  if (tx.moved) cleanupLog.push(`xBRIEF moved ${tx.fromDir} → cancelled`);
33555
33871
  if (tx.committed) cleanupLog.push(`Committed xBRIEF cancellation on main`);
@@ -33557,8 +33873,8 @@ async function runDestructiveIssueLifecycle(id, mode, opts = {}) {
33557
33873
  cleanupLog.push(`xBRIEF cancel transition failed (non-fatal): ${err?.message ?? err}`);
33558
33874
  }
33559
33875
  try {
33560
- const { killAllReviewerSessions } = await import("./review-agent-DNboMyW4.js");
33561
- const { resolveProjectFromIssueSync } = await import("./projects-vjjcmiN8.js");
33876
+ const { killAllReviewerSessions } = await import("./review-agent-BTCNkq2h.js");
33877
+ const { resolveProjectFromIssueSync } = await import("./projects-_n3Zui6K.js");
33562
33878
  const projectKey = resolveProjectFromIssueSync(id)?.projectKey;
33563
33879
  if (projectKey) {
33564
33880
  const { killed } = await Effect.runPromise(killAllReviewerSessions(projectKey, id.toUpperCase()));
@@ -33572,7 +33888,7 @@ async function runDestructiveIssueLifecycle(id, mode, opts = {}) {
33572
33888
  cleanupLog.push("Cleared review status");
33573
33889
  } catch {}
33574
33890
  try {
33575
- const { resetPostMergeState } = await import("./merge-agent-CKkgIBPB.js");
33891
+ const { resetPostMergeState } = await import("./merge-agent-DQTXZk8z.js");
33576
33892
  resetPostMergeState(id);
33577
33893
  resetPostMergeState(id.toUpperCase());
33578
33894
  cleanupLog.push("Cleared merge state");
@@ -33592,7 +33908,7 @@ function closeIssueTransition(options) {
33592
33908
  const { issueId, body, eventStore } = options;
33593
33909
  const { reason } = body;
33594
33910
  const projectPath = getProjectPath$3(void 0, extractPrefixSync(issueId) ?? issueId.split("-")[0]);
33595
- const { close: closeWorkflow } = yield* Effect.promise(() => import("./lifecycle-xmBfK2GJ.js"));
33911
+ const { close: closeWorkflow } = yield* Effect.promise(() => import("./lifecycle-B3s4sM1G.js"));
33596
33912
  const githubCheck = isGitHubIssue$4(issueId);
33597
33913
  const issueDataService = getIssueDataService$8();
33598
33914
  const issueSource = issueDataService.getIssueSource(issueId);
@@ -33615,7 +33931,7 @@ function closeIssueTransition(options) {
33615
33931
  };
33616
33932
  }
33617
33933
  const result = yield* closeWorkflow(ctx, { reason });
33618
- if (githubCheck.isGitHub) execAsync$8("pan sync", {
33934
+ if (githubCheck.isGitHub) execAsync$9("pan sync", {
33619
33935
  encoding: "utf-8",
33620
33936
  timeout: 3e4
33621
33937
  }).catch(() => {});
@@ -33826,13 +34142,13 @@ function reopenIssueTransition(options) {
33826
34142
  }
33827
34143
  yield* Effect.promise(async () => {
33828
34144
  try {
33829
- const { stdout } = await execAsync$8(`gh pr list --head ${`feature/${id.toLowerCase()}`} --state closed --json number,mergedAt --limit 1`, {
34145
+ const { stdout } = await execAsync$9(`gh pr list --head ${`feature/${id.toLowerCase()}`} --state closed --json number,mergedAt --limit 1`, {
33830
34146
  encoding: "utf-8",
33831
34147
  timeout: 15e3
33832
34148
  });
33833
34149
  const prs = JSON.parse(stdout.trim() || "[]");
33834
34150
  if (prs.length > 0 && !prs[0].mergedAt) {
33835
- await execAsync$8(`gh pr reopen ${prs[0].number}`, {
34151
+ await execAsync$9(`gh pr reopen ${prs[0].number}`, {
33836
34152
  encoding: "utf-8",
33837
34153
  timeout: 15e3
33838
34154
  });
@@ -33859,7 +34175,7 @@ function reopenIssueTransition(options) {
33859
34175
  else clearReviewStatus(id.toUpperCase());
33860
34176
  } catch {}
33861
34177
  try {
33862
- const { resetPostMergeState } = await import("./merge-agent-CKkgIBPB.js");
34178
+ const { resetPostMergeState } = await import("./merge-agent-DQTXZk8z.js");
33863
34179
  resetPostMergeState(id);
33864
34180
  resetPostMergeState(id.toUpperCase());
33865
34181
  } catch {}
@@ -33923,7 +34239,7 @@ function moveIssueStatus(options) {
33923
34239
  "done"
33924
34240
  ];
33925
34241
  if (!targetStatus || !validStatuses.includes(targetStatus)) return jsonResponse({ error: `Invalid targetStatus. Must be one of: ${validStatuses.join(", ")}` }, { status: 400 });
33926
- const { updateShadowState } = yield* Effect.promise(() => import("./shadow-state-DnDyjujH.js"));
34242
+ const { updateShadowState } = yield* Effect.promise(() => import("./shadow-state-CZBp7pwq.js"));
33927
34243
  const issueState = {
33928
34244
  backlog: "open",
33929
34245
  todo: "open",
@@ -33986,7 +34302,7 @@ function moveIssueStatus(options) {
33986
34302
  init_issue_service_singleton();
33987
34303
  init_issue_id();
33988
34304
  init_agents$1();
33989
- const execAsync$7 = promisify(exec);
34305
+ const execAsync$8 = promisify(exec);
33990
34306
  function getIssueDataService$7() {
33991
34307
  return getSharedIssueService();
33992
34308
  }
@@ -34011,28 +34327,28 @@ async function cleanupWorkspaceForIssue(rawId, eventStore) {
34011
34327
  if (projectRoot) {
34012
34328
  const workspacePath = join(projectRoot, "workspaces", `feature-${issueLower}`);
34013
34329
  try {
34014
- if ((await execAsync$7("git worktree list --porcelain", {
34330
+ if ((await execAsync$8("git worktree list --porcelain", {
34015
34331
  cwd: projectRoot,
34016
34332
  encoding: "utf-8"
34017
34333
  })).stdout.includes(workspacePath)) {
34018
- await execAsync$7(`git worktree remove "${workspacePath}" --force`, {
34334
+ await execAsync$8(`git worktree remove "${workspacePath}" --force`, {
34019
34335
  cwd: projectRoot,
34020
34336
  encoding: "utf-8"
34021
34337
  });
34022
34338
  cleanupLog.push(`Removed git worktree: ${workspacePath}`);
34023
34339
  } else if (existsSync$1(workspacePath)) {
34024
- await execAsync$7(`rm -rf "${workspacePath}"`, { encoding: "utf-8" });
34340
+ await execAsync$8(`rm -rf "${workspacePath}"`, { encoding: "utf-8" });
34025
34341
  cleanupLog.push(`Removed directory: ${workspacePath}`);
34026
34342
  }
34027
34343
  } catch {
34028
34344
  if (existsSync$1(workspacePath)) {
34029
- await execAsync$7(`rm -rf "${workspacePath}"`, { encoding: "utf-8" });
34345
+ await execAsync$8(`rm -rf "${workspacePath}"`, { encoding: "utf-8" });
34030
34346
  cleanupLog.push(`Removed directory: ${workspacePath}`);
34031
34347
  }
34032
34348
  }
34033
34349
  const branchName = `feature/${issueLower}`;
34034
34350
  try {
34035
- await execAsync$7(`git branch -D "${branchName}" 2>/dev/null || true`, {
34351
+ await execAsync$8(`git branch -D "${branchName}" 2>/dev/null || true`, {
34036
34352
  cwd: projectRoot,
34037
34353
  encoding: "utf-8"
34038
34354
  });
@@ -34041,7 +34357,7 @@ async function cleanupWorkspaceForIssue(rawId, eventStore) {
34041
34357
  }
34042
34358
  const agentDir = join(homedir$1(), ".overdeck", "agents", `agent-${issueLower}`);
34043
34359
  if (existsSync$1(agentDir)) {
34044
- await execAsync$7(`rm -rf "${agentDir}"`, { encoding: "utf-8" });
34360
+ await execAsync$8(`rm -rf "${agentDir}"`, { encoding: "utf-8" });
34045
34361
  cleanupLog.push(`Removed agent state: ${agentDir}`);
34046
34362
  }
34047
34363
  await Effect.runPromise(eventStore.append({
@@ -34150,7 +34466,7 @@ async function copySettingsToWorkspace(id) {
34150
34466
  success: false,
34151
34467
  error: "Workspace not found"
34152
34468
  }, { status: 404 });
34153
- const { copyOverdeckSettingsToWorkspaceSync } = await import("./workspace-manager-CWT55qeI.js");
34469
+ const { copyOverdeckSettingsToWorkspaceSync } = await import("./workspace-manager-C1F3XsnS.js");
34154
34470
  const result = copyOverdeckSettingsToWorkspaceSync(workspacePath);
34155
34471
  return jsonResponse({
34156
34472
  success: result.errors.length === 0 || result.copied.length > 0,
@@ -34200,10 +34516,11 @@ init_lifecycle_io();
34200
34516
  init_io();
34201
34517
  init_issue_id();
34202
34518
  init_projects();
34519
+ init_record_update();
34203
34520
  init_tracker_utils();
34204
34521
  init_tmux();
34205
34522
  init_typed_errors();
34206
- const execAsync$6 = promisify(exec);
34523
+ const execAsync$7 = promisify(exec);
34207
34524
  const execFileAsync$14 = promisify(execFile);
34208
34525
  function getIssueDataService$6() {
34209
34526
  return getSharedIssueService();
@@ -34258,7 +34575,7 @@ function getProjectPath$2(linearProjectId, issuePrefix) {
34258
34575
  }
34259
34576
  function startPlanningForIssue(options) {
34260
34577
  return Effect.gen(function* () {
34261
- const { id, body, eventStore, linear, github, rally, lifecycle } = options;
34578
+ const { id, body, eventStore, linear, github, rally, lifecycle, startedBy } = options;
34262
34579
  const { skipWorkspace = false, startDocker = false, workspaceLocation = "local", shadowMode = false, model: modelOverride, effort, auto = false, autoStart = false, probe = false, harness = "claude-code" } = body;
34263
34580
  const requestedHarness = harness === "ohmypi" || harness === "claude-code" || harness === "codex" || harness === "acp" ? harness : "claude-code";
34264
34581
  const workModelRaw = body.workModel;
@@ -34364,7 +34681,8 @@ function startPlanningForIssue(options) {
34364
34681
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
34365
34682
  role: "plan",
34366
34683
  model: "",
34367
- auto: auto === true
34684
+ auto: auto === true,
34685
+ startedBy
34368
34686
  });
34369
34687
  return Promise.resolve();
34370
34688
  });
@@ -34439,6 +34757,7 @@ function startPlanningForIssue(options) {
34439
34757
  auto: auto === true,
34440
34758
  probe: probe === true,
34441
34759
  autoSpawnOnFinalize: autoStart === true,
34760
+ startedBy,
34442
34761
  onProgress: (event) => {
34443
34762
  console.log(`[start-planning] Progress: step=${event.step} label="${event.label}" status=${event.status} detail="${event.detail}"`);
34444
34763
  sendEvent({
@@ -34631,10 +34950,10 @@ function restartFromPlan(options) {
34631
34950
  });
34632
34951
  const resetResult = yield* Effect.promise(async () => {
34633
34952
  try {
34634
- const { runGitResetHard } = await import("./dangerous-git-ops-BXqeWVSf.js");
34953
+ const { runGitResetHard } = await import("./dangerous-git-ops-CFLykHWt.js");
34635
34954
  async function findPlanningCommit(grep, label) {
34636
34955
  try {
34637
- const { stdout } = await execAsync$6(`git log --grep="${grep.replace(/"/g, "\\\"")}" --format=%H -1`, {
34956
+ const { stdout } = await execAsync$7(`git log --grep="${grep.replace(/"/g, "\\\"")}" --format=%H -1`, {
34638
34957
  cwd: workspacePath,
34639
34958
  encoding: "utf-8",
34640
34959
  timeout: 1e4
@@ -34650,7 +34969,7 @@ function restartFromPlan(options) {
34650
34969
  }
34651
34970
  const found = await findPlanningCommit(`Complete planning for ${id}`, "complete-planning message") ?? await findPlanningCommit(`chore: planning artifacts for ${id}`, "agent-start message") ?? await (async () => {
34652
34971
  try {
34653
- const { stdout } = await execAsync$6(`git log --diff-filter=A --format=%H -1 -- .pan/spec.vbrief.json`, {
34972
+ const { stdout } = await execAsync$7(`git log --diff-filter=A --format=%H -1 -- .pan/spec.vbrief.json`, {
34654
34973
  cwd: workspacePath,
34655
34974
  encoding: "utf-8",
34656
34975
  timeout: 1e4
@@ -34783,7 +35102,7 @@ init_github_pr_lookup();
34783
35102
  init_tracker_utils();
34784
35103
  init_tracker_config();
34785
35104
  init_pr_tab_cache();
34786
- const execAsync$5 = promisify(exec);
35105
+ const execAsync$6 = promisify(exec);
34787
35106
  function isGitHubIssue$2(issueId) {
34788
35107
  const resolved = resolveGitHubIssueSync(issueId);
34789
35108
  if (resolved.isGitHub) return {
@@ -34831,7 +35150,7 @@ async function fetchIssueDiscussions(issueId, deps = {}) {
34831
35150
  if (githubCheck.isGitHub && githubCheck.owner && githubCheck.repo && githubCheck.number) {
34832
35151
  const { owner, repo, number } = githubCheck;
34833
35152
  try {
34834
- const { stdout } = await execAsync$5(`gh api "repos/${owner}/${repo}/issues/${number}/comments?per_page=100"`, {
35153
+ const { stdout } = await execAsync$6(`gh api "repos/${owner}/${repo}/issues/${number}/comments?per_page=100"`, {
34835
35154
  encoding: "utf-8",
34836
35155
  timeout: 15e3,
34837
35156
  maxBuffer: 8 * 1024 * 1024
@@ -34894,7 +35213,7 @@ async function fetchIssueDiscussions(issueId, deps = {}) {
34894
35213
  const collectedItems = [];
34895
35214
  const prConversation = (async () => {
34896
35215
  try {
34897
- const { stdout } = await execAsync$5(`gh api "repos/${prOwner}/${prRepo}/issues/${prNumber}/comments?per_page=100"`, {
35216
+ const { stdout } = await execAsync$6(`gh api "repos/${prOwner}/${prRepo}/issues/${prNumber}/comments?per_page=100"`, {
34898
35217
  encoding: "utf-8",
34899
35218
  timeout: 15e3,
34900
35219
  maxBuffer: 8 * 1024 * 1024
@@ -34915,7 +35234,7 @@ async function fetchIssueDiscussions(issueId, deps = {}) {
34915
35234
  })();
34916
35235
  const prReviews = (async () => {
34917
35236
  try {
34918
- const { stdout } = await execAsync$5(`gh api "repos/${prOwner}/${prRepo}/pulls/${prNumber}/reviews?per_page=100"`, {
35237
+ const { stdout } = await execAsync$6(`gh api "repos/${prOwner}/${prRepo}/pulls/${prNumber}/reviews?per_page=100"`, {
34919
35238
  encoding: "utf-8",
34920
35239
  timeout: 15e3,
34921
35240
  maxBuffer: 8 * 1024 * 1024
@@ -34940,7 +35259,7 @@ async function fetchIssueDiscussions(issueId, deps = {}) {
34940
35259
  })();
34941
35260
  const prInlineComments = (async () => {
34942
35261
  try {
34943
- const { stdout } = await execAsync$5(`gh api "repos/${prOwner}/${prRepo}/pulls/${prNumber}/comments?per_page=100"`, {
35262
+ const { stdout } = await execAsync$6(`gh api "repos/${prOwner}/${prRepo}/pulls/${prNumber}/comments?per_page=100"`, {
34944
35263
  encoding: "utf-8",
34945
35264
  timeout: 15e3,
34946
35265
  maxBuffer: 8 * 1024 * 1024
@@ -35083,7 +35402,7 @@ function closeOutIssue(id, opts = {}) {
35083
35402
  }
35084
35403
  const closeOutResult = yield* Effect.promise(async () => {
35085
35404
  try {
35086
- const { closeOut } = await import("./lifecycle-xmBfK2GJ.js");
35405
+ const { closeOut } = await import("./lifecycle-B3s4sM1G.js");
35087
35406
  return {
35088
35407
  ok: true,
35089
35408
  result: await Effect.runPromise(closeOut(ctx, {
@@ -35172,7 +35491,7 @@ function bulkCloseOut(body) {
35172
35491
  const invalidIds = issueIds.filter((id) => !isValidIssueId(id));
35173
35492
  if (invalidIds.length > 0) return jsonResponse({ error: `Invalid issue ID format: ${invalidIds.join(", ")}` }, { status: 400 });
35174
35493
  const eventStore = yield* EventStoreService;
35175
- const { closeOut } = yield* Effect.promise(() => import("./lifecycle-xmBfK2GJ.js"));
35494
+ const { closeOut } = yield* Effect.promise(() => import("./lifecycle-B3s4sM1G.js"));
35176
35495
  const issueDataService = getIssueDataService$5();
35177
35496
  const tasks = [];
35178
35497
  const agentChecks = yield* withConcurrencyLimit(issueIds.map((id) => Effect.promise(async () => {
@@ -35340,7 +35659,7 @@ const execFileAsync$13 = promisify(execFile);
35340
35659
  function getIssueDataService$4() {
35341
35660
  return getSharedIssueService();
35342
35661
  }
35343
- const readJsonBody$17 = Effect.gen(function* () {
35662
+ const readJsonBody$18 = Effect.gen(function* () {
35344
35663
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
35345
35664
  try {
35346
35665
  return text ? JSON.parse(text) : {};
@@ -35398,8 +35717,8 @@ const getIssueShipLogRoute = HttpRouter.add("GET", "/api/issues/:id/ship-log", h
35398
35717
  const id = (yield* HttpRouter.params)["id"] ?? "";
35399
35718
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35400
35719
  return yield* Effect.promise(async () => {
35401
- const { getShipLog } = await import("./ship-log-D9MlVzYU.js");
35402
- const { getReviewStatusSync } = await import("./review-status-btEYlaxA.js");
35720
+ const { getShipLog } = await import("./ship-log-7kJfjUX2.js");
35721
+ const { getReviewStatusSync } = await import("./review-status-Dma9SFaN.js");
35403
35722
  const log = getShipLog(id);
35404
35723
  const rs = getReviewStatusSync(id);
35405
35724
  return jsonResponse({
@@ -35414,8 +35733,8 @@ const getIssueVerificationRoute = HttpRouter.add("GET", "/api/issues/:id/verific
35414
35733
  const id = (yield* HttpRouter.params)["id"] ?? "";
35415
35734
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35416
35735
  return yield* Effect.promise(async () => {
35417
- const { readVerificationArtifact } = await import("./verification-artifact-B3itKTxu.js");
35418
- const { getReviewStatusSync } = await import("./review-status-btEYlaxA.js");
35736
+ const { readVerificationArtifact } = await import("./verification-artifact-BxlxTiYK.js");
35737
+ const { getReviewStatusSync } = await import("./review-status-Dma9SFaN.js");
35419
35738
  const { join } = await import("node:path");
35420
35739
  const resolved = resolveProjectFromIssueSync(id);
35421
35740
  const workspacePath = resolved ? join(resolved.projectPath, "workspaces", `feature-${id.toLowerCase()}`) : null;
@@ -35433,21 +35752,35 @@ const postIssueCloseRoute = HttpRouter.add("POST", "/api/issues/:issueId/close",
35433
35752
  if (!parseIssueIdSync(issueId)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35434
35753
  return yield* closeIssueTransition({
35435
35754
  issueId,
35436
- body: yield* readJsonBody$17,
35755
+ body: yield* readJsonBody$18,
35437
35756
  eventStore: yield* EventStoreService
35438
35757
  });
35439
35758
  })));
35440
35759
  const postIssueStartPlanningRoute = HttpRouter.add("POST", "/api/issues/:id/start-planning", httpHandler(Effect.gen(function* () {
35760
+ const request = yield* HttpServerRequest.HttpServerRequest;
35441
35761
  const id = (yield* HttpRouter.params)["id"] ?? "";
35442
35762
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35763
+ const body = yield* readJsonBody$18;
35764
+ const eventStore = yield* EventStoreService;
35765
+ const linear = yield* LinearClient;
35766
+ const github = yield* GitHubClient;
35767
+ const rally = yield* RallyClient;
35768
+ const lifecycle = yield* IssueLifecycle;
35769
+ let startedBy;
35770
+ try {
35771
+ startedBy = resolveRequestedStartedBy(body.startedBy, yield* Effect.promise(() => isInternalAgentRequest(request)));
35772
+ } catch (error) {
35773
+ return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, { status: 400 });
35774
+ }
35443
35775
  return yield* startPlanningForIssue({
35444
35776
  id,
35445
- body: yield* readJsonBody$17,
35446
- eventStore: yield* EventStoreService,
35447
- linear: yield* LinearClient,
35448
- github: yield* GitHubClient,
35449
- rally: yield* RallyClient,
35450
- lifecycle: yield* IssueLifecycle
35777
+ body,
35778
+ eventStore,
35779
+ linear,
35780
+ github,
35781
+ rally,
35782
+ lifecycle,
35783
+ startedBy
35451
35784
  });
35452
35785
  })));
35453
35786
  const postIssueAbortPlanningRoute = HttpRouter.add("POST", "/api/issues/:id/abort-planning", httpHandler(Effect.gen(function* () {
@@ -35455,7 +35788,7 @@ const postIssueAbortPlanningRoute = HttpRouter.add("POST", "/api/issues/:id/abor
35455
35788
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35456
35789
  return yield* abortPlanningForIssue({
35457
35790
  id,
35458
- body: yield* readJsonBody$17,
35791
+ body: yield* readJsonBody$18,
35459
35792
  lifecycle: yield* IssueLifecycle,
35460
35793
  linear: yield* LinearClient,
35461
35794
  eventStore: yield* EventStoreService
@@ -35465,7 +35798,7 @@ const postIssueCompletePlanningRoute = HttpRouter.add("POST", "/api/issues/:id/c
35465
35798
  const request = yield* HttpServerRequest.HttpServerRequest;
35466
35799
  const id = (yield* HttpRouter.params)["id"] ?? "";
35467
35800
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35468
- const body = yield* readJsonBody$17;
35801
+ const body = yield* readJsonBody$18;
35469
35802
  const eventStore = yield* EventStoreService;
35470
35803
  const linear = yield* LinearClient;
35471
35804
  const lifecycle = yield* IssueLifecycle;
@@ -35491,7 +35824,7 @@ const postIssueResetRoute = HttpRouter.add("POST", "/api/issues/:id/reset", http
35491
35824
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35492
35825
  return yield* resetIssueTransition({
35493
35826
  id,
35494
- body: yield* readJsonBody$17,
35827
+ body: yield* readJsonBody$18,
35495
35828
  eventStore: yield* EventStoreService
35496
35829
  });
35497
35830
  })));
@@ -35515,7 +35848,7 @@ const postIssueCancelRoute = HttpRouter.add("POST", "/api/issues/:id/cancel", ht
35515
35848
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35516
35849
  return yield* cancelIssueTransition({
35517
35850
  id,
35518
- body: yield* readJsonBody$17,
35851
+ body: yield* readJsonBody$18,
35519
35852
  eventStore: yield* EventStoreService
35520
35853
  });
35521
35854
  })));
@@ -35526,7 +35859,7 @@ const postIssueReopenRoute = HttpRouter.add("POST", "/api/issues/:id/reopen", ht
35526
35859
  if (authError) return authError;
35527
35860
  return yield* reopenIssueTransition({
35528
35861
  id,
35529
- body: yield* readJsonBody$17,
35862
+ body: yield* readJsonBody$18,
35530
35863
  lifecycle: yield* IssueLifecycle,
35531
35864
  linear: yield* LinearClient,
35532
35865
  eventStore: yield* EventStoreService
@@ -35546,7 +35879,7 @@ const postIssueMoveStatusRoute = HttpRouter.add("POST", "/api/issues/:id/move-st
35546
35879
  if (!parseIssueIdSync(id)) return jsonResponse({ error: "Invalid issue ID" }, { status: 400 });
35547
35880
  return yield* moveIssueStatus({
35548
35881
  id,
35549
- body: yield* readJsonBody$17,
35882
+ body: yield* readJsonBody$18,
35550
35883
  eventStore: yield* EventStoreService,
35551
35884
  lifecycle: yield* IssueLifecycle
35552
35885
  });
@@ -35558,7 +35891,7 @@ const postIssueCleanupWorkspaceRoute = HttpRouter.add("POST", "/api/issues/:id/c
35558
35891
  })));
35559
35892
  const postIssueDeepWipeRoute = HttpRouter.add("POST", "/api/issues/:id/deep-wipe", httpHandler(Effect.gen(function* () {
35560
35893
  const id = (yield* HttpRouter.params)["id"] ?? "";
35561
- const body = yield* readJsonBody$17;
35894
+ const body = yield* readJsonBody$18;
35562
35895
  const eventStore = yield* EventStoreService;
35563
35896
  return yield* Effect.promise(() => deepWipeIssue(id, body, eventStore));
35564
35897
  })));
@@ -35614,7 +35947,7 @@ const postIssueTaskInspectRoute = HttpRouter.add("POST", "/api/issues/:id/tasks/
35614
35947
  return yield* inspectIssueTask({
35615
35948
  id: (params["id"] ?? "").toUpperCase(),
35616
35949
  itemId: params["itemId"] ?? "",
35617
- body: yield* readJsonBody$17
35950
+ body: yield* readJsonBody$18
35618
35951
  });
35619
35952
  })));
35620
35953
  const getIssuePlanningStateRoute = HttpRouter.add("GET", "/api/issues/:id/planning-state", httpHandler(Effect.gen(function* () {
@@ -35756,7 +36089,13 @@ const getPipelineMembershipRoute = HttpRouter.add("GET", "/api/pipeline/membersh
35756
36089
  if (!project) return jsonResponse({ error: `Project not found: ${projectKey}` }, { status: 404 });
35757
36090
  const snapshot = readPipelineMembershipSnapshotsForProjects([project])[0];
35758
36091
  if (snapshot?.memberships) return jsonResponse(snapshot.memberships);
35759
- return jsonResponse({ error: snapshot?.error instanceof Error ? snapshot.error.message : "Pipeline membership snapshot is loading" }, { status: 503 });
36092
+ if (snapshot?.error && snapshot.unavailableReason) return jsonResponse({
36093
+ status: "unavailable",
36094
+ reason: snapshot.unavailableReason ?? "gather_failed",
36095
+ message: snapshot.error instanceof Error ? snapshot.error.message : String(snapshot.error),
36096
+ projectKey
36097
+ });
36098
+ return jsonResponse({ error: "Pipeline membership snapshot is loading" }, { status: 503 });
35760
36099
  })));
35761
36100
  const postPipelineMembershipRefreshRoute = HttpRouter.add("POST", "/api/pipeline/membership/refresh", httpHandler(Effect.gen(function* () {
35762
36101
  const request = yield* HttpServerRequest.HttpServerRequest;
@@ -35769,7 +36108,12 @@ const postPipelineMembershipRefreshRoute = HttpRouter.add("POST", "/api/pipeline
35769
36108
  yield* Effect.promise(() => refreshMembershipSnapshotsForProjects([project]));
35770
36109
  const snapshot = readPipelineMembershipSnapshotsForProjects([project])[0];
35771
36110
  if (snapshot?.memberships) return jsonResponse(snapshot.memberships);
35772
- return jsonResponse({ error: snapshot?.error instanceof Error ? snapshot.error.message : "Pipeline membership refresh failed" }, { status: 502 });
36111
+ return jsonResponse({
36112
+ status: "unavailable",
36113
+ reason: snapshot?.unavailableReason ?? "gather_failed",
36114
+ message: snapshot?.error instanceof Error ? snapshot.error.message : "Pipeline membership refresh failed",
36115
+ projectKey
36116
+ });
35773
36117
  })));
35774
36118
  const pipelineMembershipRouteLayer = Layer.mergeAll(getPipelineMembershipRoute, postPipelineMembershipRefreshRoute);
35775
36119
  //#endregion
@@ -36050,8 +36394,7 @@ const getCostsBackgroundRoute = HttpRouter.add("GET", "/api/costs/background", h
36050
36394
  const costsRouteLayer = Layer.mergeAll(getCostsSummaryRoute, getCostsByIssueRoute, getCostsBackgroundRoute, postCostsRebuildRoute, postCostsDeduplicateRoute, getCostsStreamRoute, getCostsTrendsRoute, getCostsByModelRoute, getCostsIssueRoute, getCostsByAgentRoute, postCostsSyncWalRoute, postCostsReconcileRoute, getCostsExperimentsRoute);
36051
36395
  //#endregion
36052
36396
  //#region services/deacon-supervisor.ts
36053
- init_control_settings();
36054
- init_config$1();
36397
+ init_config$2();
36055
36398
  init_concurrency();
36056
36399
  init_activity_logger();
36057
36400
  const DEFAULT_RESTART_WINDOW_MS = 6e4;
@@ -36222,6 +36565,7 @@ const isChildRunning = defaultSupervisor.isChildRunning;
36222
36565
  //#endregion
36223
36566
  //#region services/cloister-control-surface.ts
36224
36567
  init_agents$1();
36568
+ init_control_settings();
36225
36569
  init_paths();
36226
36570
  function readDurableCloisterStatus(deps = {}) {
36227
36571
  const cloisterState = (deps.readCloisterStateFile ?? readCloisterStateFile)();
@@ -36306,9 +36650,9 @@ function reloadDurableCloisterConfig(deps = {}) {
36306
36650
  * PUT /api/cloister/config
36307
36651
  * GET /api/cloister/agents/health
36308
36652
  */
36309
- init_config$1();
36653
+ init_config$2();
36310
36654
  init_agents$1();
36311
- const readJsonBody$16 = Effect.gen(function* () {
36655
+ const readJsonBody$17 = Effect.gen(function* () {
36312
36656
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
36313
36657
  try {
36314
36658
  return text ? JSON.parse(text) : {};
@@ -36411,7 +36755,7 @@ const getCloisterConfigRoute = HttpRouter.add("GET", "/api/cloister/config", htt
36411
36755
  catch: (err) => new Error(err instanceof Error ? err.message : String(err))
36412
36756
  })));
36413
36757
  const putCloisterConfigRoute = HttpRouter.add("PUT", "/api/cloister/config", httpHandler(Effect.gen(function* () {
36414
- const updates = yield* readJsonBody$16;
36758
+ const updates = yield* readJsonBody$17;
36415
36759
  yield* Effect.try({
36416
36760
  try: () => {
36417
36761
  saveCloisterConfigSync(updates);
@@ -37784,7 +38128,7 @@ const getStackTeardownEstimateRoute = HttpRouter.add("GET", "/api/resources/stac
37784
38128
  })));
37785
38129
  const postStackTeardownRoute = HttpRouter.add("POST", "/api/resources/stacks/:issueId/teardown", httpHandler(Effect.gen(function* () {
37786
38130
  const params = yield* HttpRouter.params;
37787
- const body = yield* readJsonBody$15;
38131
+ const body = yield* readJsonBody$16;
37788
38132
  return yield* postStackTeardownEffect(params["issueId"] ?? "", body);
37789
38133
  })));
37790
38134
  function getStackTeardownEstimateEffect(issue) {
@@ -37958,7 +38302,7 @@ function lines(value) {
37958
38302
  function isDockerIdentifier(value) {
37959
38303
  return /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(value);
37960
38304
  }
37961
- const readJsonBody$15 = Effect.gen(function* () {
38305
+ const readJsonBody$16 = Effect.gen(function* () {
37962
38306
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
37963
38307
  try {
37964
38308
  return text ? JSON.parse(text) : {};
@@ -38067,7 +38411,6 @@ async function deriveSessionPresence(agentId, rtState, tmuxSessionNames) {
38067
38411
  }
38068
38412
  //#endregion
38069
38413
  //#region ../../lib/settings-api-tiered-execution.ts
38070
- init_prd_locations();
38071
38414
  function tieredExecutionConfigForSave(config, providerAuth) {
38072
38415
  if (config === void 0) return void 0;
38073
38416
  const validated = validateTieredExecutionConfig(config, { providerAuth });
@@ -38093,6 +38436,17 @@ function validateTieredExecutionSettings(config, providerAuth) {
38093
38436
  }
38094
38437
  }
38095
38438
  //#endregion
38439
+ //#region ../../lib/settings-api-telemetry.ts
38440
+ function validateApiTelemetryConfig(value) {
38441
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return ["telemetry must be an object"];
38442
+ const telemetry = value;
38443
+ const errors = [];
38444
+ if (typeof telemetry.enabled !== "boolean") errors.push("telemetry.enabled must be a boolean");
38445
+ if (telemetry.effectiveEnabled !== void 0 && typeof telemetry.effectiveEnabled !== "boolean") errors.push("telemetry.effectiveEnabled must be a boolean");
38446
+ if (telemetry.installId !== void 0 && typeof telemetry.installId !== "string") errors.push("telemetry.installId must be a string");
38447
+ return errors;
38448
+ }
38449
+ //#endregion
38096
38450
  //#region ../../lib/settings-api.ts
38097
38451
  /**
38098
38452
  * Settings API Adapter
@@ -38101,6 +38455,9 @@ function validateTieredExecutionSettings(config, providerAuth) {
38101
38455
  * Converts between YAML config format and frontend API format.
38102
38456
  */
38103
38457
  init_config_yaml();
38458
+ init_config();
38459
+ init_install_id();
38460
+ init_service();
38104
38461
  const API_TTS_KEYS = [
38105
38462
  "enabled",
38106
38463
  "voice",
@@ -38480,6 +38837,11 @@ function loadSettingsApi() {
38480
38837
  rtk: { enabled: config.rtk?.enabled ?? false },
38481
38838
  tldr: { enabled: config.tldr?.enabled ?? true }
38482
38839
  },
38840
+ telemetry: {
38841
+ enabled: config.telemetry?.enabled ?? true,
38842
+ effectiveEnabled: resolveTelemetryEnabled(),
38843
+ installId: getOrCreateInstallId()
38844
+ },
38483
38845
  tts: toApiTtsConfig(config.tts),
38484
38846
  tts_summarizer: {
38485
38847
  model: config.ttsSummarizer?.model,
@@ -38561,6 +38923,7 @@ async function writeYamlConfigPreservingComments(yamlConfig) {
38561
38923
  ["memory", config.memory],
38562
38924
  ["background_ai", config.background_ai],
38563
38925
  ["tracker_keys", config.tracker_keys],
38926
+ ["telemetry", config.telemetry],
38564
38927
  ["experimental", config.experimental],
38565
38928
  ["claude", config.claude],
38566
38929
  ["codex", config.codex],
@@ -38660,6 +39023,7 @@ async function saveSettingsApiPromise(settings) {
38660
39023
  features: settings.background_ai.features
38661
39024
  } : void 0,
38662
39025
  tracker_keys: settings.tracker_keys,
39026
+ telemetry: settings.telemetry ? { enabled: settings.telemetry.enabled } : void 0,
38663
39027
  experimental: settings.experimental ? {
38664
39028
  experimentalFeatures: settings.experimental.experimentalFeatures,
38665
39029
  claudeCodeChannels: settings.experimental.claudeCodeChannels,
@@ -38673,6 +39037,7 @@ async function saveSettingsApiPromise(settings) {
38673
39037
  tiered_execution: tieredExecutionConfigForSave(settings.tiered_execution, currentConfig.providerAuth)
38674
39038
  });
38675
39039
  clearConfigCache();
39040
+ await synchronizeAnalyticsServices();
38676
39041
  }
38677
39042
  async function updateSettingsApiPromise(updates) {
38678
39043
  const current = loadSettingsApi();
@@ -38750,6 +39115,11 @@ async function updateSettingsApiPromise(updates) {
38750
39115
  ...current.tracker_keys,
38751
39116
  ...updates.tracker_keys
38752
39117
  },
39118
+ telemetry: {
39119
+ enabled: updates.telemetry?.enabled ?? current.telemetry?.enabled ?? true,
39120
+ effectiveEnabled: !telemetryEnvironmentForcesOff() && (updates.telemetry?.enabled ?? current.telemetry?.enabled ?? true),
39121
+ installId: current.telemetry?.installId
39122
+ },
38753
39123
  experimental: {
38754
39124
  ...current.experimental,
38755
39125
  ...updates.experimental
@@ -38821,6 +39191,7 @@ function validateSettingsApi(settings) {
38821
39191
  if (settings.memory.sidebar_refresh_interval_ms !== void 0 && (!Number.isInteger(settings.memory.sidebar_refresh_interval_ms) || settings.memory.sidebar_refresh_interval_ms < 1)) errors.push("memory.sidebar_refresh_interval_ms must be a positive integer");
38822
39192
  if (settings.memory.worker_concurrency !== void 0 && (!Number.isInteger(settings.memory.worker_concurrency) || settings.memory.worker_concurrency < 1)) errors.push("memory.worker_concurrency must be a positive integer");
38823
39193
  }
39194
+ if (settings.telemetry !== void 0) errors.push(...validateApiTelemetryConfig(settings.telemetry));
38824
39195
  if (settings.experimental !== void 0) if (typeof settings.experimental !== "object" || settings.experimental === null) errors.push("experimental must be an object");
38825
39196
  else {
38826
39197
  const experimental = settings.experimental;
@@ -39288,7 +39659,7 @@ init_linear_client();
39288
39659
  init_config_yaml();
39289
39660
  init_issue_service_singleton();
39290
39661
  init_io();
39291
- const execAsync$4 = promisify(exec);
39662
+ const execAsync$5 = promisify(exec);
39292
39663
  const execFileAsync$8 = promisify(execFile);
39293
39664
  function getIssueDataService$3() {
39294
39665
  return getSharedIssueService();
@@ -39342,7 +39713,7 @@ function getProjectPath$1(issuePrefix) {
39342
39713
  }
39343
39714
  return setProjectPathCache(issuePrefix, join(homedir$1(), "Projects"));
39344
39715
  }
39345
- const readJsonBody$14 = Effect.gen(function* () {
39716
+ const readJsonBody$15 = Effect.gen(function* () {
39346
39717
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
39347
39718
  try {
39348
39719
  return text ? JSON.parse(text) : {};
@@ -39613,16 +39984,18 @@ async function fetchActivityDataWithContext(issueId, context = {}) {
39613
39984
  const isShipStage = ss.type === "merge";
39614
39985
  const nodeType = isShipStage ? "ship" : "test";
39615
39986
  const specialistSessionId = isShipStage ? `agent-${issueLower}-ship` : `agent-${issueLower}-test`;
39987
+ const specialistIsLive = tmuxSessionNames.has(specialistSessionId);
39988
+ const specialistState = getAgentStateSync(specialistSessionId);
39989
+ if (isShipStage && !specialistIsLive && !specialistState) continue;
39616
39990
  if (includeTranscripts && ss.status === "running") try {
39617
39991
  const output = (await Effect.runPromise(capturePane(specialistSessionId, 100))).trim();
39618
39992
  if (output && (output.includes(issueId.toUpperCase()) || output.includes(issueId) || output.includes(issueLower))) transcriptParts.push(`\n--- Live Output ---\n${output}`);
39619
39993
  else if (output) transcriptParts.push(`\n--- Waiting ---\nSpecialist is processing another issue. Will update when it reaches ${issueId}.`);
39620
39994
  } catch {}
39621
- const specialistIsLive = tmuxSessionNames.has(specialistSessionId);
39622
39995
  const specialistIsZombie = specialistIsLive && (ss.status === "completed" || ss.status === "failed");
39623
39996
  const specialistPresence = specialistIsLive && !specialistIsZombie ? ss.status === "running" ? "active" : "idle" : specialistIsZombie ? "idle" : "ended";
39624
39997
  const specialistJsonlPath = await resolveJsonlPath(specialistSessionId, workspacePath);
39625
- const specialistState = getAgentStateSync(specialistSessionId);
39998
+ if (isShipStage && !specialistIsLive && !specialistJsonlPath) continue;
39626
39999
  sections.push({
39627
40000
  type: nodeType,
39628
40001
  sessionId: specialistSessionId,
@@ -39841,7 +40214,7 @@ async function fetchPlanningData(issueId, options = {}) {
39841
40214
  const cached = stashCountCache.get(cacheKey);
39842
40215
  if (cached && cached.timestamp > Date.now() - STASH_COUNT_CACHE_TTL_MS) result.stashCount = cached.count;
39843
40216
  else {
39844
- const { stdout: stashList } = await execAsync$4("git stash list", {
40217
+ const { stdout: stashList } = await execAsync$5("git stash list", {
39845
40218
  cwd: workspacePath,
39846
40219
  encoding: "utf-8"
39847
40220
  });
@@ -39929,7 +40302,7 @@ async function generateStatusReview(issueId) {
39929
40302
  };
39930
40303
  const execSafe = async (cmd, opts) => {
39931
40304
  try {
39932
- const { stdout } = await execAsync$4(cmd, {
40305
+ const { stdout } = await execAsync$5(cmd, {
39933
40306
  encoding: "utf-8",
39934
40307
  timeout: 1e4,
39935
40308
  ...opts
@@ -40031,7 +40404,7 @@ Be specific: reference actual file names, function names, requirement text, disc
40031
40404
  const { command: cliCmd, args: cliArgs } = getAgentCommandSync(statusModelId);
40032
40405
  const modelFlag = cliArgs.length > 0 ? ` ${cliArgs.join(" ")}` : "";
40033
40406
  const promptFile = join(planningDir, ".status-review-prompt.tmp");
40034
- const { getProviderForModelSync, getProviderEnvSync } = await import("./providers-u4Ga0jOr.js");
40407
+ const { getProviderForModelSync, getProviderEnvSync } = await import("./providers-BI1mOBQ4.js");
40035
40408
  let providerEnvStr = "";
40036
40409
  let providerEnv = {};
40037
40410
  const statusProvider = getProviderForModelSync(statusModelId);
@@ -40048,7 +40421,7 @@ Be specific: reference actual file names, function names, requirement text, disc
40048
40421
  try {
40049
40422
  const env = buildChildEnvSync(process.env, providerEnv);
40050
40423
  const promptContent = await readFile(promptFile, "utf-8");
40051
- const { stdout: aiReview } = await execAsync$4(`${cliCmd} -p${modelFlag} --no-session-persistence`, {
40424
+ const { stdout: aiReview } = await execAsync$5(`${cliCmd} -p${modelFlag} --no-session-persistence`, {
40052
40425
  encoding: "utf-8",
40053
40426
  timeout: 12e4,
40054
40427
  maxBuffer: 1024 * 1024,
@@ -40106,7 +40479,7 @@ ${issueContext ? `## Issue Tracker Data\n${issueContext}\n` : ""}---
40106
40479
  }
40107
40480
  const postMissionControlUploadRoute = HttpRouter.add("POST", "/api/command-deck/planning/:issueId/upload", httpHandler(Effect.gen(function* () {
40108
40481
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
40109
- const { type, filename, content } = yield* readJsonBody$14;
40482
+ const { type, filename, content } = yield* readJsonBody$15;
40110
40483
  const issueLower = issueId.toLowerCase();
40111
40484
  const issuePrefix = extractPrefixSync(issueId) ?? issueId.split("-")[0];
40112
40485
  if (!type || !filename || !content) return jsonResponse({ error: "type, filename, and content are required" }, { status: 400 });
@@ -40114,7 +40487,7 @@ const postMissionControlUploadRoute = HttpRouter.add("POST", "/api/command-deck/
40114
40487
  let safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "-");
40115
40488
  let processedContent = content;
40116
40489
  if (safeName.endsWith(".vtt")) {
40117
- const { vttToMarkdown } = yield* Effect.promise(() => import("./vtt-parser-DYPbKcIJ.js"));
40490
+ const { vttToMarkdown } = yield* Effect.promise(() => import("./vtt-parser-CE_i3Bcw.js"));
40118
40491
  processedContent = vttToMarkdown(content);
40119
40492
  safeName = safeName.replace(/\.vtt$/, ".md");
40120
40493
  }
@@ -40135,7 +40508,7 @@ const postMissionControlUploadRoute = HttpRouter.add("POST", "/api/command-deck/
40135
40508
  const postMissionControlSyncDiscussionsRoute = HttpRouter.add("POST", "/api/command-deck/planning/:issueId/sync-discussions", httpHandler(Effect.gen(function* () {
40136
40509
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
40137
40510
  if (!parseIssueIdSync(issueId)) return jsonResponse({ error: "Invalid issue id: " + issueId }, { status: 400 });
40138
- const body = yield* readJsonBody$14;
40511
+ const body = yield* readJsonBody$15;
40139
40512
  const linear = yield* LinearClient;
40140
40513
  const { tracker } = body;
40141
40514
  const issueLower = issueId.toLowerCase();
@@ -40281,7 +40654,7 @@ const postMissionControlSyncDiscussionsRoute = HttpRouter.add("POST", "/api/comm
40281
40654
  })));
40282
40655
  const postMissionControlPlanningInitRoute = HttpRouter.add("POST", "/api/command-deck/planning/:issueId/init", httpHandler(Effect.gen(function* () {
40283
40656
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
40284
- const body = yield* readJsonBody$14;
40657
+ const body = yield* readJsonBody$15;
40285
40658
  const eventStore = yield* EventStoreService;
40286
40659
  const { shadow } = body;
40287
40660
  const issueLower = issueId.toLowerCase();
@@ -40340,7 +40713,7 @@ const commandDeckRouteLayer = Layer.mergeAll(getMissionControlActivityRoute, get
40340
40713
  * GET /api/remote/workspaces/:issueId/agent/output
40341
40714
  * POST /api/remote/workspaces/:issueId/agent/tell
40342
40715
  */
40343
- init_config();
40716
+ init_config$1();
40344
40717
  init_domain_services();
40345
40718
  async function loadRemoteWorkspaceMetadata(issueId) {
40346
40719
  const normalizedId = issueId.toLowerCase().replace(/[^a-z0-9-]/g, "-");
@@ -40366,7 +40739,7 @@ async function listRemoteWorkspaceMetadata() {
40366
40739
  }
40367
40740
  return workspaces;
40368
40741
  }
40369
- const readJsonBody$13 = Effect.gen(function* () {
40742
+ const readJsonBody$14 = Effect.gen(function* () {
40370
40743
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
40371
40744
  try {
40372
40745
  return text ? JSON.parse(text) : {};
@@ -40481,7 +40854,7 @@ const stopRemoteWorkspaceRoute = HttpRouter.add("POST", "/api/remote/workspaces/
40481
40854
  })));
40482
40855
  const startRemoteAgentRoute = HttpRouter.add("POST", "/api/remote/workspaces/:issueId/agent/start", httpHandler(Effect.gen(function* () {
40483
40856
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
40484
- const body = yield* readJsonBody$13;
40857
+ const body = yield* readJsonBody$14;
40485
40858
  const eventStore = yield* EventStoreService;
40486
40859
  const { prompt, model } = body;
40487
40860
  const metadata = yield* Effect.promise(() => loadRemoteWorkspaceMetadata(issueId));
@@ -40497,6 +40870,7 @@ const startRemoteAgentRoute = HttpRouter.add("POST", "/api/remote/workspaces/:is
40497
40870
  workspace: metadata,
40498
40871
  prompt,
40499
40872
  model,
40873
+ startedBy: "operator:dashboard",
40500
40874
  tier: fly.getResiliencyTier()
40501
40875
  });
40502
40876
  },
@@ -40545,7 +40919,7 @@ const getRemoteAgentOutputRoute = HttpRouter.add("GET", "/api/remote/workspaces/
40545
40919
  })));
40546
40920
  const tellRemoteAgentRoute = HttpRouter.add("POST", "/api/remote/workspaces/:issueId/agent/tell", httpHandler(Effect.gen(function* () {
40547
40921
  const issueId = (yield* HttpRouter.params)["issueId"] ?? "";
40548
- const { message } = yield* readJsonBody$13;
40922
+ const { message } = yield* readJsonBody$14;
40549
40923
  if (!message) return jsonResponse({ error: "Message is required" }, { status: 400 });
40550
40924
  const metadata = yield* Effect.promise(() => loadRemoteWorkspaceMetadata(issueId));
40551
40925
  if (!metadata) return jsonResponse({ error: "Remote workspace not found" }, { status: 404 });
@@ -40562,6 +40936,24 @@ const remoteRouteLayer = Layer.mergeAll(getRemoteStatusRoute, listRemoteWorkspac
40562
40936
  init_providers();
40563
40937
  init_harness_policy();
40564
40938
  /**
40939
+ * Model ids legal in the `?models=` query of GET /api/settings/harness-policy.
40940
+ *
40941
+ * Square brackets are significant: the long-context variants are named
40942
+ * `k3[1m]` and `claude-opus-5[1m]`. The pickers batch every known model id
40943
+ * into one request, so rejecting a single bracketed id 400s the whole batch
40944
+ * and leaves every model without a policy decision.
40945
+ */
40946
+ const SAFE_MODEL_PATTERN$2 = /^[a-zA-Z0-9_.:\/[\]-]+$/;
40947
+ const MAX_HARNESS_POLICY_MODELS = 250;
40948
+ const MAX_HARNESS_POLICY_MODEL_LENGTH = 200;
40949
+ /** Parse and validate the `models` query value; `null` means reject with 400. */
40950
+ function parseHarnessPolicyModels(rawModels) {
40951
+ const models = (rawModels ?? "").split(",").map((model) => model.trim()).filter(Boolean);
40952
+ if (models.length === 0 || models.length > MAX_HARNESS_POLICY_MODELS) return null;
40953
+ if (models.some((model) => model.length > MAX_HARNESS_POLICY_MODEL_LENGTH || !SAFE_MODEL_PATTERN$2.test(model))) return null;
40954
+ return models;
40955
+ }
40956
+ /**
40565
40957
  * Resolve a per-model harness-policy decision map for `models`.
40566
40958
  *
40567
40959
  * Caches the auth-mode resolver per provider so multiple models under one
@@ -44686,7 +45078,7 @@ function consumeReindexConfirmationNonce(nonce, estimatedUsd) {
44686
45078
  reindexConfirmationNonces.delete(nonce);
44687
45079
  return !!entry && entry.expiresAt > Date.now() && Math.abs(entry.estimatedUsd - estimatedUsd) < 1e-6;
44688
45080
  }
44689
- const readJsonBody$12 = Effect.gen(function* () {
45081
+ const readJsonBody$13 = Effect.gen(function* () {
44690
45082
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
44691
45083
  try {
44692
45084
  return text ? JSON.parse(text) : {};
@@ -44759,7 +45151,7 @@ const getOpenAIAuthRoute = HttpRouter.add("GET", "/api/settings/openai-auth", ht
44759
45151
  return jsonResponse(yield* getOpenAIAuthStatus());
44760
45152
  })));
44761
45153
  const postTestApiKeyRoute = HttpRouter.add("POST", "/api/settings/test-api-key", httpHandler(Effect.gen(function* () {
44762
- const { provider, apiKey, model } = yield* readJsonBody$12;
45154
+ const { provider, apiKey, model } = yield* readJsonBody$13;
44763
45155
  if (!provider || !apiKey) return jsonResponse({ error: "Provider and apiKey are required" }, { status: 400 });
44764
45156
  return yield* Effect.promise(async () => {
44765
45157
  let success = false;
@@ -45069,7 +45461,7 @@ const postTestApiKeyRoute = HttpRouter.add("POST", "/api/settings/test-api-key",
45069
45461
  });
45070
45462
  })));
45071
45463
  const postValidateApiKeyRoute = HttpRouter.add("POST", "/api/settings/validate-api-key", httpHandler(Effect.gen(function* () {
45072
- const { provider, apiKey } = yield* readJsonBody$12;
45464
+ const { provider, apiKey } = yield* readJsonBody$13;
45073
45465
  if (!provider || !apiKey) return jsonResponse({ error: "Provider and apiKey are required" }, { status: 400 });
45074
45466
  if (![
45075
45467
  "openai",
@@ -45304,7 +45696,7 @@ const getConversationSearchReindexEstimateRoute = HttpRouter.add("GET", "/api/se
45304
45696
  const postConversationSearchReindexRoute = HttpRouter.add("POST", "/api/settings/conversation-search/reindex", httpHandler(Effect.gen(function* () {
45305
45697
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
45306
45698
  if (authError) return authError;
45307
- const body = yield* readJsonBody$12;
45699
+ const body = yield* readJsonBody$13;
45308
45700
  return yield* Effect.promise(async () => {
45309
45701
  if (activeConversationSearchReindex) return jsonResponse({ error: "Conversation search reindex already in progress" }, { status: 409 });
45310
45702
  const job = (async () => {
@@ -45354,7 +45746,7 @@ const getConversationSearchReindexProgressRoute = HttpRouter.add("GET", "/api/se
45354
45746
  return jsonResponse(conversationReindexProgress);
45355
45747
  })));
45356
45748
  const putSettingsRoute = HttpRouter.add("PUT", "/api/settings", httpHandler(Effect.gen(function* () {
45357
- const body = yield* readJsonBody$12;
45749
+ const body = yield* readJsonBody$13;
45358
45750
  return yield* Effect.promise(async () => {
45359
45751
  try {
45360
45752
  const newSettings = body;
@@ -45375,7 +45767,7 @@ const putSettingsRoute = HttpRouter.add("PUT", "/api/settings", httpHandler(Effe
45375
45767
  });
45376
45768
  })));
45377
45769
  const putUiThemeRoute = HttpRouter.add("PUT", "/api/settings/ui-theme", httpHandler(Effect.gen(function* () {
45378
- const body = yield* readJsonBody$12;
45770
+ const body = yield* readJsonBody$13;
45379
45771
  return yield* Effect.promise(async () => {
45380
45772
  const theme = body?.theme;
45381
45773
  if (theme !== "light" && theme !== "dark") return jsonResponse({ error: "theme must be 'light' or 'dark'" }, { status: 400 });
@@ -45390,7 +45782,7 @@ const getOpenRouterModelsRoute = HttpRouter.add("GET", "/api/settings/openrouter
45390
45782
  });
45391
45783
  })));
45392
45784
  const putOpenRouterFavoritesRoute = HttpRouter.add("PUT", "/api/settings/openrouter/favorites", httpHandler(Effect.gen(function* () {
45393
- const { favorites } = yield* readJsonBody$12;
45785
+ const { favorites } = yield* readJsonBody$13;
45394
45786
  if (!Array.isArray(favorites)) return jsonResponse({ error: "favorites must be an array of model IDs" }, { status: 400 });
45395
45787
  const modelIds = favorites.filter((f) => typeof f === "string");
45396
45788
  return yield* Effect.promise(async () => {
@@ -45406,7 +45798,7 @@ const putOpenRouterFavoritesRoute = HttpRouter.add("PUT", "/api/settings/openrou
45406
45798
  });
45407
45799
  })));
45408
45800
  const putOpenRouterApiKeyRoute = HttpRouter.add("PUT", "/api/settings/openrouter/api-key", httpHandler(Effect.gen(function* () {
45409
- const { apiKey } = yield* readJsonBody$12;
45801
+ const { apiKey } = yield* readJsonBody$13;
45410
45802
  if (apiKey !== void 0 && typeof apiKey !== "string") return jsonResponse({ error: "apiKey must be a string when provided" }, { status: 400 });
45411
45803
  return yield* Effect.promise(async () => {
45412
45804
  try {
@@ -45421,18 +45813,16 @@ const putOpenRouterApiKeyRoute = HttpRouter.add("PUT", "/api/settings/openrouter
45421
45813
  });
45422
45814
  })));
45423
45815
  const postOpenRouterTestKeyRoute = HttpRouter.add("POST", "/api/settings/openrouter/test-key", httpHandler(Effect.gen(function* () {
45424
- const { apiKey } = yield* readJsonBody$12;
45816
+ const { apiKey } = yield* readJsonBody$13;
45425
45817
  if (!apiKey) return jsonResponse({ error: "apiKey is required" }, { status: 400 });
45426
45818
  return jsonResponse(yield* (yield* OpenRouterService).validateApiKey(apiKey));
45427
45819
  })));
45428
- const SAFE_MODEL_PATTERN$1 = /^[a-zA-Z0-9_.:\/-]+$/;
45429
- const MAX_HARNESS_POLICY_MODELS = 250;
45430
- const MAX_HARNESS_POLICY_MODEL_LENGTH = 200;
45820
+ const SAFE_MODEL_PATTERN$1 = /^[a-zA-Z0-9_.:\/[\]-]+$/;
45431
45821
  const getHarnessPolicyRoute = HttpRouter.add("GET", "/api/settings/harness-policy", httpHandler(Effect.gen(function* () {
45432
45822
  const request = yield* HttpServerRequest.HttpServerRequest;
45433
45823
  return yield* Effect.promise(async () => {
45434
- const models = (new URL(request.url, "http://localhost").searchParams.get("models") ?? "").split(",").map((model) => model.trim()).filter(Boolean);
45435
- if (models.length === 0 || models.length > MAX_HARNESS_POLICY_MODELS || models.some((model) => model.length > MAX_HARNESS_POLICY_MODEL_LENGTH || !SAFE_MODEL_PATTERN$1.test(model))) return jsonResponse({ error: "Valid models parameter is required" }, { status: 400 });
45824
+ const models = parseHarnessPolicyModels(new URL(request.url, "http://localhost").searchParams.get("models"));
45825
+ if (!models) return jsonResponse({ error: "Valid models parameter is required" }, { status: 400 });
45436
45826
  return jsonResponse({ decisions: await buildHarnessPolicyDecisions(models, getProviderAuthMode) });
45437
45827
  });
45438
45828
  })));
@@ -45474,7 +45864,7 @@ const getLegacyImportPreviewRoute = HttpRouter.add("GET", "/api/settings/legacy-
45474
45864
  const postLegacyImportRoute = HttpRouter.add("POST", "/api/settings/legacy-import/conversations", httpHandler(Effect.gen(function* () {
45475
45865
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
45476
45866
  if (authError) return authError;
45477
- const body = yield* readJsonBody$12;
45867
+ const body = yield* readJsonBody$13;
45478
45868
  return yield* Effect.try({
45479
45869
  try: () => {
45480
45870
  const { path, names } = body;
@@ -45486,7 +45876,7 @@ const postLegacyImportRoute = HttpRouter.add("POST", "/api/settings/legacy-impor
45486
45876
  const settingsRouteLayer = Layer.mergeAll(getSettingsRoute, getAvailableModelsRoute, getOptimalDefaultsRoute, getMiniMaxDefaultsRoute, getClaudeAuthRoute, getOpenAIAuthRoute, postTestApiKeyRoute, postValidateApiKeyRoute, getConversationSearchStatusRoute, getConversationSearchReindexEstimateRoute, postConversationSearchReindexRoute, getConversationSearchReindexProgressRoute, putSettingsRoute, putUiThemeRoute, getOpenRouterModelsRoute, putOpenRouterFavoritesRoute, putOpenRouterApiKeyRoute, postOpenRouterTestKeyRoute, getHarnessPolicyRoute, getProviderEnvConflictsRoute, getLegacyImportPreviewRoute, postLegacyImportRoute);
45487
45877
  //#endregion
45488
45878
  //#region routes/autopreso.ts
45489
- const readJsonBody$11 = Effect.gen(function* () {
45879
+ const readJsonBody$12 = Effect.gen(function* () {
45490
45880
  const request = yield* HttpServerRequest.HttpServerRequest;
45491
45881
  const contentLength = Number(request.headers["content-length"] ?? 0);
45492
45882
  if (Number.isFinite(contentLength) && contentLength > 256e3) return {
@@ -45517,7 +45907,7 @@ function requireTrustedOrigin$1(request) {
45517
45907
  const startRoute = HttpRouter.add("POST", "/api/autopreso/start", httpHandler(Effect.gen(function* () {
45518
45908
  const originError = requireTrustedOrigin$1(yield* HttpServerRequest.HttpServerRequest);
45519
45909
  if (originError) return originError;
45520
- const parsed = yield* readJsonBody$11;
45910
+ const parsed = yield* readJsonBody$12;
45521
45911
  if (!parsed.ok) return jsonResponse({ error: parsed.error }, { status: 413 });
45522
45912
  const elements = readElementLikes(parsed.body);
45523
45913
  const validation = validateAutoPresoCanvasElements(elements);
@@ -45550,6 +45940,7 @@ const autopresoRouteLayer = Layer.mergeAll(startRoute, backToStagingRoute, reset
45550
45940
  * GET /api/activity/:id
45551
45941
  */
45552
45942
  init_domain_services();
45943
+ init_read_model();
45553
45944
  init_agents$1();
45554
45945
  init_review_status();
45555
45946
  init_cost_sync();
@@ -45630,16 +46021,7 @@ const getMetricsStuckRoute = HttpRouter.add("GET", "/api/metrics/stuck", httpHan
45630
46021
  });
45631
46022
  })));
45632
46023
  const getActivityRoute = HttpRouter.add("GET", "/api/activity", httpHandler(Effect.gen(function* () {
45633
- return jsonResponse((yield* (yield* EventStoreService).queryByType("activity.entry", 100)).map((e) => ({
45634
- id: e.payload["id"],
45635
- timestamp: e.timestamp,
45636
- source: e.payload["source"],
45637
- level: e.payload["level"],
45638
- message: e.payload["message"],
45639
- details: e.payload["details"],
45640
- issueId: e.payload["issueId"],
45641
- link: e.payload["link"]
45642
- })));
46024
+ return jsonResponse(activityEntriesFromStoredEvents(yield* (yield* EventStoreService).queryByType("activity.entry", 300), 100));
45643
46025
  })));
45644
46026
  const getActivityDetailedRoute = HttpRouter.add("GET", "/api/activity/detailed", httpHandler(Effect.gen(function* () {
45645
46027
  return jsonResponse((yield* (yield* EventStoreService).queryByType("activity.detailed", 200)).map((e) => ({
@@ -45666,16 +46048,19 @@ const getActivityByIdRoute = HttpRouter.add("GET", "/api/activity/:id", httpHand
45666
46048
  const params = yield* HttpRouter.params;
45667
46049
  const eventStore = yield* EventStoreService;
45668
46050
  const id = params["id"] ?? "";
45669
- const activity = (yield* eventStore.queryByType("activity.entry", 1e3)).find((e) => e.payload["id"] === id);
46051
+ const activity = activityEntriesFromStoredEvents(yield* eventStore.queryByType("activity.entry", 1e3), 1e3).find((entry) => entry["id"] === id);
45670
46052
  if (!activity) return jsonResponse({ error: "Activity not found" }, { status: 404 });
45671
46053
  return jsonResponse({
45672
- id: activity.payload["id"],
45673
- timestamp: activity.timestamp,
45674
- source: activity.payload["source"],
45675
- level: activity.payload["level"],
45676
- message: activity.payload["message"],
45677
- details: activity.payload["details"],
45678
- issueId: activity.payload["issueId"]
46054
+ id: activity["id"],
46055
+ timestamp: activity["timestamp"],
46056
+ source: activity["source"],
46057
+ level: activity["level"],
46058
+ status: activity["status"],
46059
+ command: activity["command"],
46060
+ message: activity["message"],
46061
+ details: activity["details"],
46062
+ output: activity["output"],
46063
+ issueId: activity["issueId"]
45679
46064
  });
45680
46065
  })));
45681
46066
  /** Parse and validate query params for GET /api/git-activity. Exported for unit testing. */
@@ -45723,7 +46108,7 @@ const metricsRouteLayer = Layer.mergeAll(getMetricsSummaryRoute, getMetricsCosts
45723
46108
  //#region routes/boot-reconciliation.ts
45724
46109
  init_tmux();
45725
46110
  init_control_settings();
45726
- const readJsonBody$10 = Effect.gen(function* () {
46111
+ const readJsonBody$11 = Effect.gen(function* () {
45727
46112
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
45728
46113
  try {
45729
46114
  return text ? JSON.parse(text) : {};
@@ -45734,7 +46119,7 @@ const readJsonBody$10 = Effect.gen(function* () {
45734
46119
  function bootReconciliationWhyStopped(agent) {
45735
46120
  if (agent.paused === true) return agent.pausedReason ? `paused: ${agent.pausedReason}` : "paused";
45736
46121
  if (agent.troubled === true) return "troubled";
45737
- if (agent.stoppedByUser === true && !isBootReconciliationCandidate(agent)) return "stopped by operator";
46122
+ if (agent.stoppedByUser === true) return "stopped by operator";
45738
46123
  if (agent.status === "stopped" && agent.sessionId && !sessionExists(agent.sessionId)) return "orphaned: tmux session missing";
45739
46124
  if (agent.status === "stopped") return "stopped cleanly";
45740
46125
  if (agent.hostOverride) return "running remote";
@@ -45752,7 +46137,7 @@ function bootReconciliationReadOnly(agent) {
45752
46137
  const getBootReconciliationRoute = HttpRouter.add("GET", "/api/boot-reconciliation", Effect.sync(() => {
45753
46138
  const state = getBootReconciliationState();
45754
46139
  const candidateIds = new Set(listBootReconciliationCandidates().map((agent) => agent.id));
45755
- const agents = listAllAgentsSync().filter((agent) => agent.role === "work" && (candidateIds.has(agent.id) || agent.paused === true || agent.troubled === true || Boolean(agent.hostOverride))).map((agent) => ({
46140
+ const toRow = (agent) => ({
45756
46141
  id: agent.id,
45757
46142
  issueId: agent.issueId,
45758
46143
  role: agent.role,
@@ -45762,13 +46147,32 @@ const getBootReconciliationRoute = HttpRouter.add("GET", "/api/boot-reconciliati
45762
46147
  lastActivity: agent.lastActivity ?? agent.updatedAt ?? null,
45763
46148
  cost: agent.costSoFar ?? null,
45764
46149
  remote: Boolean(agent.hostOverride),
46150
+ stoppedByUser: agent.stoppedByUser === true,
45765
46151
  readOnly: bootReconciliationReadOnly(agent)
45766
- }));
46152
+ });
46153
+ const autoResumableAgents = listAllAgentsSync().filter((agent) => isAutoResumableRole(agent.role));
46154
+ const set = autoResumableAgents.filter((agent) => candidateIds.has(agent.id)).map(toRow);
46155
+ const context = autoResumableAgents.filter((agent) => !candidateIds.has(agent.id) && (agent.paused === true || agent.troubled === true || Boolean(agent.hostOverride))).map(toRow);
46156
+ let heldCount = 0;
46157
+ if (state.decision === "hold_all") heldCount = set.length;
46158
+ else if (state.decision === "per_agent") heldCount = set.filter((agent) => state.perAgent[agent.issueId] !== "resume").length;
45767
46159
  return jsonResponse({
45768
46160
  ...state,
45769
- set: agents
46161
+ maxGraceExtensions: 3,
46162
+ heldCount,
46163
+ set,
46164
+ context
45770
46165
  });
45771
46166
  }));
46167
+ /**
46168
+ * Push the grace deadline out while the operator is answering a different
46169
+ * blocking dialog. Bounded server-side by MAX_BOOT_RECONCILIATION_GRACE_EXTENSIONS;
46170
+ * the client may call this freely and the cap decides.
46171
+ */
46172
+ const postBootReconciliationExtendRoute = HttpRouter.add("POST", "/api/boot-reconciliation/extend", httpHandler(Effect.sync(() => jsonResponse({
46173
+ ok: true,
46174
+ ...extendBootReconciliationGrace()
46175
+ }))));
45772
46176
  function isBootReconciliationDecision(value) {
45773
46177
  return value === "resume_all" || value === "hold_all" || value === "per_agent";
45774
46178
  }
@@ -45779,14 +46183,14 @@ function parsePerAgentMap(value) {
45779
46183
  return parsed;
45780
46184
  }
45781
46185
  const postBootReconciliationDecisionRoute = HttpRouter.add("POST", "/api/boot-reconciliation/decision", httpHandler(Effect.gen(function* () {
45782
- const body = yield* readJsonBody$10;
46186
+ const body = yield* readJsonBody$11;
45783
46187
  if (!isBootReconciliationDecision(body.decision)) return jsonResponse({
45784
46188
  ok: false,
45785
46189
  error: "Body must include decision resume_all, hold_all, or per_agent"
45786
46190
  }, { status: 400 });
45787
46191
  const perAgent = body.decision === "per_agent" ? parsePerAgentMap(body.perAgent) : {};
45788
46192
  setBootReconciliationDecision(body.decision, perAgent);
45789
- const result = yield* Effect.promise(() => applyBootReconciliationDecision());
46193
+ const result = yield* Effect.promise(() => applyBootReconciliationDecision({ origin: "operator" }));
45790
46194
  const resumed = result.resumed;
45791
46195
  return jsonResponse({
45792
46196
  ok: true,
@@ -45799,7 +46203,7 @@ const postBootReconciliationDecisionRoute = HttpRouter.add("POST", "/api/boot-re
45799
46203
  count: resumed.length
45800
46204
  });
45801
46205
  })));
45802
- const bootReconciliationRouteLayer = Layer.mergeAll(getBootReconciliationRoute, postBootReconciliationDecisionRoute);
46206
+ const bootReconciliationRouteLayer = Layer.mergeAll(getBootReconciliationRoute, postBootReconciliationDecisionRoute, postBootReconciliationExtendRoute);
45803
46207
  //#endregion
45804
46208
  //#region routes/misc/shared.ts
45805
46209
  init_projects();
@@ -45876,7 +46280,7 @@ function getGitHubLocalPaths() {
45876
46280
  }
45877
46281
  return out;
45878
46282
  }
45879
- const readJsonBody$9 = Effect.gen(function* () {
46283
+ const readJsonBody$10 = Effect.gen(function* () {
45880
46284
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
45881
46285
  try {
45882
46286
  return text ? JSON.parse(text) : {};
@@ -45888,7 +46292,7 @@ const readJsonBody$9 = Effect.gen(function* () {
45888
46292
  //#region routes/misc/project-mappings.ts
45889
46293
  const getProjectMappingsRoute = HttpRouter.add("GET", "/api/project-mappings", httpHandler(Effect.promise(() => getProjectMappings().then((m) => jsonResponse(m)))));
45890
46294
  const putProjectMappingsRoute = HttpRouter.add("PUT", "/api/project-mappings", Effect.gen(function* () {
45891
- const mappings = yield* readJsonBody$9;
46295
+ const mappings = yield* readJsonBody$10;
45892
46296
  if (!Array.isArray(mappings)) return jsonResponse({ error: "Expected array of mappings" }, { status: 400 });
45893
46297
  yield* Effect.promise(() => saveProjectMappings(mappings));
45894
46298
  return jsonResponse({
@@ -45897,7 +46301,7 @@ const putProjectMappingsRoute = HttpRouter.add("PUT", "/api/project-mappings", E
45897
46301
  });
45898
46302
  }));
45899
46303
  const postProjectMappingsRoute = HttpRouter.add("POST", "/api/project-mappings", Effect.gen(function* () {
45900
- const { linearProjectId, linearProjectName, linearPrefix, localPath } = yield* readJsonBody$9;
46304
+ const { linearProjectId, linearProjectName, linearPrefix, localPath } = yield* readJsonBody$10;
45901
46305
  if (!linearProjectId || !localPath) return jsonResponse({ error: "linearProjectId and localPath required" }, { status: 400 });
45902
46306
  return yield* Effect.promise(async () => {
45903
46307
  try {
@@ -45924,7 +46328,7 @@ const postProjectMappingsRoute = HttpRouter.add("POST", "/api/project-mappings",
45924
46328
  const projectMappingsRouteLayer = Layer.mergeAll(getProjectMappingsRoute, putProjectMappingsRoute, postProjectMappingsRoute);
45925
46329
  //#endregion
45926
46330
  //#region routes/misc/trackers.ts
45927
- init_config();
46331
+ init_config$1();
45928
46332
  init_config_yaml();
45929
46333
  init_projects();
45930
46334
  init_tracker_config();
@@ -46004,14 +46408,14 @@ const getTrackerStatusRoute = HttpRouter.add("GET", "/api/tracker-status", Effec
46004
46408
  }
46005
46409
  }));
46006
46410
  const postRallyValidateRoute = HttpRouter.add("POST", "/api/rally/validate", Effect.gen(function* () {
46007
- const { apiKey, server, workspace, project } = yield* readJsonBody$9;
46411
+ const { apiKey, server, workspace, project } = yield* readJsonBody$10;
46008
46412
  if (!apiKey) return jsonResponse({
46009
46413
  valid: false,
46010
46414
  error: "API key is required"
46011
46415
  }, { status: 400 });
46012
46416
  return yield* Effect.promise(async () => {
46013
46417
  try {
46014
- const { RallyRestApi } = await import("./rally-api-ClOSd-Tt.js");
46418
+ const { RallyRestApi } = await import("./rally-api-CgosKdKL.js");
46015
46419
  const api = new RallyRestApi({
46016
46420
  apiKey,
46017
46421
  server: server || "https://rally1.rallydev.com"
@@ -46353,7 +46757,7 @@ const getDeaconPauseRoute = HttpRouter.add("GET", "/api/deacon/pause", Effect.tr
46353
46757
  * Persists to `app_settings` so the flag survives dashboard restarts.
46354
46758
  */
46355
46759
  const postDeaconPauseRoute = HttpRouter.add("POST", "/api/deacon/pause", httpHandler(Effect.gen(function* () {
46356
- const body = yield* readJsonBody$9;
46760
+ const body = yield* readJsonBody$10;
46357
46761
  if (typeof body.paused !== "boolean") return jsonResponse({ error: "Body must include { paused: boolean }" }, { status: 400 });
46358
46762
  setDeaconGloballyPausedSync(body.paused);
46359
46763
  console.log(`[deacon] Global pause flag set to ${body.paused}`);
@@ -46365,6 +46769,7 @@ const deaconRouteLayer = Layer.mergeAll(getNoResumeModeRoute, postResumeAllRoute
46365
46769
  init_config_yaml();
46366
46770
  init_issue_id();
46367
46771
  init_types();
46772
+ init_paths();
46368
46773
  init_projects();
46369
46774
  init_remote_agents();
46370
46775
  init_tmux();
@@ -46396,7 +46801,7 @@ const getPlanningStatusRoute = HttpRouter.add("GET", "/api/planning/:issueId/sta
46396
46801
  const remoteState = loadRemoteAgentState(sessionName);
46397
46802
  const isRemote = !!remoteState;
46398
46803
  const vmName = remoteState?.vmName ?? "";
46399
- const { getAgentStateSync } = await import("./agents-wi9NWzs2.js");
46804
+ const { getAgentStateSync } = await import("./agents-ePFVoZbu.js");
46400
46805
  const agentStarting = getAgentStateSync(sessionName)?.status === "starting";
46401
46806
  let tmuxSessionAlive = false;
46402
46807
  if (!isRemote) try {
@@ -46448,7 +46853,7 @@ const postPlanningMessageRoute = HttpRouter.add("POST", "/api/planning/:issueId/
46448
46853
  const issueId = new URL(request.url, "http://localhost").pathname.split("/")[3] || "";
46449
46854
  const sessionName = `planning-${issueId.toLowerCase()}`;
46450
46855
  const issueLower = issueId.toLowerCase();
46451
- const { message } = yield* readJsonBody$9;
46856
+ const { message } = yield* readJsonBody$10;
46452
46857
  const eventStore = yield* EventStoreService;
46453
46858
  if (!message) return jsonResponse({ error: "Message required" }, { status: 400 });
46454
46859
  return yield* Effect.promise(async () => {
@@ -46540,7 +46945,7 @@ Continue the PLANNING session. Do NOT implement anything.
46540
46945
  const agentCwd = workspacePath;
46541
46946
  const harnessLaunch = await prepareHarnessLaunch("claude-code");
46542
46947
  if (existsSync$1(outputFile)) await rename(outputFile, join(planningDir, `output-${Date.now()}.jsonl`));
46543
- const { getAgentCommandSync } = await import("./settings-66A6uvIA.js");
46948
+ const { getAgentCommandSync } = await import("./settings-CVmWiJ-r.js");
46544
46949
  let msgPlanningModel = "claude-sonnet-5";
46545
46950
  try {
46546
46951
  msgPlanningModel = resolveModel("plan", void 0, loadConfigSync().config);
@@ -46548,6 +46953,7 @@ Continue the PLANNING session. Do NOT implement anything.
46548
46953
  const msgAgentCmd = getAgentCommandSync(msgPlanningModel);
46549
46954
  const msgPermissionFlags = getClaudePermissionFlagsStringSync();
46550
46955
  const msgCmdWithArgs = msgAgentCmd.args.length > 0 ? `${msgAgentCmd.command} ${msgAgentCmd.args.join(" ")} ${msgPermissionFlags}` : `${msgAgentCmd.command} ${msgPermissionFlags}`;
46956
+ const agentStateDir = join(getOverdeckHome(), "agents", sessionName);
46551
46957
  const launcherScript = join(agentStateDir, "continuation-launcher.sh");
46552
46958
  await mkdir(agentStateDir, { recursive: true });
46553
46959
  await writeFile(launcherScript, generateLauncherScriptSync({
@@ -46662,7 +47068,7 @@ async function getIndexStats(rootPath, isMain) {
46662
47068
  }
46663
47069
  const getTldrStatusRoute = HttpRouter.add("GET", "/api/services/tldr/status", Effect.promise(async () => {
46664
47070
  try {
46665
- const { getTldrDaemonServiceSync } = await import("./tldr-daemon-BYFkts95.js");
47071
+ const { getTldrDaemonServiceSync } = await import("./tldr-daemon-CvWXBhX1.js");
46666
47072
  const projectRoot = process.cwd();
46667
47073
  const venvPath = join(projectRoot, ".venv");
46668
47074
  const results = [];
@@ -46707,7 +47113,7 @@ const getTldrStatusRoute = HttpRouter.add("GET", "/api/services/tldr/status", Ef
46707
47113
  }));
46708
47114
  const postTldrStartRoute = HttpRouter.add("POST", "/api/services/tldr/start", Effect.promise(async () => {
46709
47115
  try {
46710
- const { getTldrDaemonServiceSync } = await import("./tldr-daemon-BYFkts95.js");
47116
+ const { getTldrDaemonServiceSync } = await import("./tldr-daemon-CvWXBhX1.js");
46711
47117
  const projectRoot = process.cwd();
46712
47118
  const venvPath = join(projectRoot, ".venv");
46713
47119
  if (!existsSync$1(venvPath)) return jsonResponse({ error: "No .venv found in project root" }, { status: 404 });
@@ -46724,7 +47130,7 @@ const postTldrStartRoute = HttpRouter.add("POST", "/api/services/tldr/start", Ef
46724
47130
  }));
46725
47131
  const postTldrStopRoute = HttpRouter.add("POST", "/api/services/tldr/stop", Effect.promise(async () => {
46726
47132
  try {
46727
- const { getTldrDaemonServiceSync } = await import("./tldr-daemon-BYFkts95.js");
47133
+ const { getTldrDaemonServiceSync } = await import("./tldr-daemon-CvWXBhX1.js");
46728
47134
  const projectRoot = process.cwd();
46729
47135
  const venvPath = join(projectRoot, ".venv");
46730
47136
  if (!existsSync$1(venvPath)) return jsonResponse({ error: "No .venv found in project root" }, { status: 404 });
@@ -46741,7 +47147,7 @@ const postTldrStopRoute = HttpRouter.add("POST", "/api/services/tldr/stop", Effe
46741
47147
  }));
46742
47148
  const postTldrReloadRoute = HttpRouter.add("POST", "/api/services/tldr/reload", Effect.promise(async () => {
46743
47149
  try {
46744
- const { getTldrDaemonServiceSync } = await import("./tldr-daemon-BYFkts95.js");
47150
+ const { getTldrDaemonServiceSync } = await import("./tldr-daemon-CvWXBhX1.js");
46745
47151
  const enabled = isTldrEnabledSync();
46746
47152
  const projectRoot = process.cwd();
46747
47153
  const targets = [];
@@ -46904,7 +47310,7 @@ init_tracker_config();
46904
47310
  init_issue_id();
46905
47311
  init_projects();
46906
47312
  init_tmux();
46907
- const execAsync$3 = promisify(exec);
47313
+ const execAsync$4 = promisify(exec);
46908
47314
  const execFileAsync$6 = promisify(execFile);
46909
47315
  function getIssueDataService$1() {
46910
47316
  const { getSharedIssueService } = (init_issue_service_singleton(), __toCommonJS(issue_service_singleton_exports));
@@ -46929,7 +47335,7 @@ const getVersionRoute = HttpRouter.add("GET", "/api/version", Effect.promise(asy
46929
47335
  const version = await getOverdeckVersion();
46930
47336
  let supervisorUrl = null;
46931
47337
  try {
46932
- const { getSupervisorUrlSync } = await import("./supervisor-FTJAIsAK.js");
47338
+ const { getSupervisorUrlSync } = await import("./supervisor-B4anCCSm.js");
46933
47339
  supervisorUrl = getSupervisorUrlSync();
46934
47340
  } catch {}
46935
47341
  return jsonResponse({
@@ -46939,11 +47345,11 @@ const getVersionRoute = HttpRouter.add("GET", "/api/version", Effect.promise(asy
46939
47345
  });
46940
47346
  }));
46941
47347
  const getPrerequisitesRoute = HttpRouter.add("GET", "/api/prerequisites", Effect.promise(async () => {
46942
- const { checkSystemPrerequisites } = await import("./system-prerequisites-DUDj1YHf.js");
47348
+ const { checkSystemPrerequisites } = await import("./system-prerequisites-ICLSkhhZ.js");
46943
47349
  return jsonResponse(await checkSystemPrerequisites());
46944
47350
  }));
46945
47351
  const getSetupDiagnosticsRoute = HttpRouter.add("GET", "/api/diagnostics/setup", Effect.promise(async () => {
46946
- const { collectSetupDiagnostics } = await import("./system-prerequisites-DUDj1YHf.js");
47352
+ const { collectSetupDiagnostics } = await import("./system-prerequisites-ICLSkhhZ.js");
46947
47353
  return jsonResponse(await collectSetupDiagnostics(await getOverdeckVersion()));
46948
47354
  }));
46949
47355
  const getSyncStatusRoute = HttpRouter.add("GET", "/api/sync-status", Effect.sync(() => {
@@ -46975,6 +47381,7 @@ const getRegisteredProjectsRoute = HttpRouter.add("GET", "/api/registered-projec
46975
47381
  name: p.config.name,
46976
47382
  path: p.config.path,
46977
47383
  linearTeam: getIssuePrefix(p.config) || null,
47384
+ membershipQueryable: Boolean(getIssuePrefix(p.config)),
46978
47385
  githubRepo: p.config.github_repo || null,
46979
47386
  linearProject: p.config.linear_project || null
46980
47387
  })));
@@ -46987,7 +47394,7 @@ const getConfirmationsRoute = HttpRouter.add("GET", "/api/confirmations", Effect
46987
47394
  const postConfirmationRespondRoute = HttpRouter.add("POST", "/api/confirmations/:id/respond", Effect.gen(function* () {
46988
47395
  const request = yield* HttpServerRequest.HttpServerRequest;
46989
47396
  const id = new URL(request.url, "http://localhost").pathname.split("/")[3] || "";
46990
- const { confirmed } = yield* readJsonBody$9;
47397
+ const { confirmed } = yield* readJsonBody$10;
46991
47398
  const confirmationRequest = pendingConfirmations.get(id);
46992
47399
  if (!confirmationRequest) return jsonResponse({ error: "Confirmation request not found" }, { status: 404 });
46993
47400
  return yield* Effect.promise(async () => {
@@ -47123,7 +47530,7 @@ const postShadowMonitorRoute = HttpRouter.add("POST", "/api/shadow/:issueId/moni
47123
47530
  const projectPath = await getProjectPath(issuePrefix);
47124
47531
  const workspacePath = join(projectPath, "workspaces", `feature-${issueLower}`);
47125
47532
  if (!existsSync$1(workspacePath)) return jsonResponse({ error: "Workspace not found" }, { status: 404 });
47126
- const { gatherArtifacts, generateBasicInference, updateInferenceDocumentSync } = await import("./shadow-engineering-BJfgm-hX.js");
47533
+ const { gatherArtifacts, generateBasicInference, updateInferenceDocumentSync } = await import("./shadow-engineering-DXvx77oE.js");
47127
47534
  const config = {
47128
47535
  issueId,
47129
47536
  workspacePath,
@@ -47145,7 +47552,7 @@ const postShadowObserveRoute = HttpRouter.add("POST", "/api/shadow/:issueId/obse
47145
47552
  const issueId = new URL(request.url, "http://localhost").pathname.split("/")[3] || "";
47146
47553
  const issueLower = issueId.toLowerCase();
47147
47554
  const issuePrefix = extractPrefixSync(issueId) ?? issueId.split("-")[0];
47148
- const { mode } = yield* readJsonBody$9;
47555
+ const { mode } = yield* readJsonBody$10;
47149
47556
  return yield* Effect.promise(async () => {
47150
47557
  try {
47151
47558
  const projectPath = await getProjectPath(issuePrefix);
@@ -47153,7 +47560,7 @@ const postShadowObserveRoute = HttpRouter.add("POST", "/api/shadow/:issueId/obse
47153
47560
  if (!existsSync$1(workspacePath)) return jsonResponse({ error: "Workspace not found" }, { status: 404 });
47154
47561
  const ghConfig = getGitHubConfig();
47155
47562
  if (!ghConfig) return jsonResponse({ error: "GitHub not configured - Observer requires GitHub" }, { status: 400 });
47156
- const { runObserverCycle } = await import("./shadow-engineering-BJfgm-hX.js");
47563
+ const { runObserverCycle } = await import("./shadow-engineering-DXvx77oE.js");
47157
47564
  const firstRepo = ghConfig.repos[0];
47158
47565
  const config = {
47159
47566
  issueId,
@@ -47185,7 +47592,7 @@ const postDevRebuildRoute = HttpRouter.add("POST", "/api/dev/rebuild", Effect.ge
47185
47592
  if (!overdeckDevMode || !overdeckProjectRoot) return jsonResponse({ error: "Rebuild only available in dev mode" }, { status: 403 });
47186
47593
  return yield* Effect.promise(async () => {
47187
47594
  try {
47188
- const { stdout, stderr } = await execAsync$3("npm run build", {
47595
+ const { stdout, stderr } = await execAsync$4("npm run build", {
47189
47596
  cwd: overdeckProjectRoot,
47190
47597
  timeout: 12e4
47191
47598
  });
@@ -48538,6 +48945,7 @@ async function getConversationDiffTurn(name, turnId, fileFilter, deps) {
48538
48945
  }
48539
48946
  //#endregion
48540
48947
  //#region ../../lib/conversations/discovered-session-file.ts
48948
+ init_discovered_sessions();
48541
48949
  async function resolveDiscoveredSessionFile(locator) {
48542
48950
  if (!locator) return null;
48543
48951
  const discovered = getDiscoveredSessionBySessionId(locator);
@@ -48668,8 +49076,12 @@ function titleTranscriptWindow(transcript) {
48668
49076
  * production code into the live checkout (main saw `fs-lock.ts` appear from a
48669
49077
  * dead session). Three independent gates:
48670
49078
  *
48671
- * - `--bare` skips hooks, settings allowlists, MCP, and CLAUDE.md from the
48672
- * cwd and `~/.claude` — a repo's permissive settings can never re-arm it;
49079
+ * - `--safe-mode` + `--setting-sources ''` skip hooks, settings allowlists,
49080
+ * MCP, and CLAUDE.md from the cwd and `~/.claude` — a repo's permissive
49081
+ * settings can never re-arm it. (Formerly `--bare`, but as of Claude Code
49082
+ * 2.1.209 `--bare` also skips credential reads, so every spawn failed with
49083
+ * "Not logged in" under subscription OAuth; `--safe-mode` explicitly keeps
49084
+ * auth working.)
48673
49085
  * - `--tools ''` + `--disallowedTools mcp__*` strip every built-in and MCP
48674
49086
  * tool so the model never sees a tool definition;
48675
49087
  * - `--permission-mode dontAsk` auto-denies (never prompts, never hangs) if
@@ -48681,7 +49093,9 @@ function titleTranscriptWindow(transcript) {
48681
49093
  function buildStructuredClaudeArgs(schema, model) {
48682
49094
  return [
48683
49095
  "-p",
48684
- "--bare",
49096
+ "--safe-mode",
49097
+ "--setting-sources",
49098
+ "",
48685
49099
  "--tools",
48686
49100
  "",
48687
49101
  "--disallowedTools",
@@ -48896,6 +49310,153 @@ function isNonEmptyJsonlLine(buffer) {
48896
49310
  return buffer.subarray(0, end).some((byte) => byte !== 32 && byte !== 9);
48897
49311
  }
48898
49312
  //#endregion
49313
+ //#region ../../lib/overdeck/conversation-pane-choice.ts
49314
+ init_transcript_adapter();
49315
+ init_summary_fork();
49316
+ init_conversation_compaction();
49317
+ /**
49318
+ * PAN-3113 — surface blocking numbered-choice menus in claude-code
49319
+ * conversation panes (the session-resume gate is the driving case) as
49320
+ * actionable pending input, and answer them from the dashboard.
49321
+ *
49322
+ * Detection is request-driven, not a patrol: the pending-input feed already
49323
+ * polls every few seconds, so detection rides that cycle — when the menu
49324
+ * disappears (answered from the dashboard OR directly in the terminal) the
49325
+ * next poll simply stops reporting it. No server-side state machine, no
49326
+ * stale records.
49327
+ *
49328
+ * Answering re-captures uncached, verifies the menu the operator saw is still
49329
+ * the menu on screen (signature match), drives the selection with literal
49330
+ * keystrokes (Up/Down + Enter via sendRawKeystroke), then re-captures to
49331
+ * confirm the menu dismissed — the same contract as the Codex TUI approval
49332
+ * flow (`handleConversationCodexApproval`).
49333
+ */
49334
+ init_conversations();
49335
+ init_behavior();
49336
+ init_tmux();
49337
+ const PANE_CAPTURE_LINES = 90;
49338
+ const KEYSTROKE_GAP_MS = 60;
49339
+ const DELIVERY_CONFIRM_WAIT_MS = 700;
49340
+ function isClaudeCodeConversation(conv) {
49341
+ return getHarnessBehavior(conv.harness).transcriptKind === "claude-jsonl";
49342
+ }
49343
+ /**
49344
+ * Parse the conversation's current pane for a blocking numbered-choice menu.
49345
+ * Returns null when the conversation is not claude-code, the capture fails,
49346
+ * or no menu is on screen. Detection-only callers (the pending-input feed)
49347
+ * use the shared cached capture; the answer path captures uncached.
49348
+ */
49349
+ async function claudeConversationPaneChoice(conv, options = {}) {
49350
+ if (!isClaudeCodeConversation(conv)) return null;
49351
+ const capture = options.capture ?? ((sessionName, lines) => capturePaneText(sessionName, lines));
49352
+ let pane;
49353
+ try {
49354
+ pane = await capture(conv.tmuxSession, PANE_CAPTURE_LINES);
49355
+ } catch {
49356
+ return null;
49357
+ }
49358
+ const menu = parsePaneChoiceMenu(pane);
49359
+ if (!menu) return null;
49360
+ return {
49361
+ signature: paneChoiceMenuSignature(menu),
49362
+ title: menu.title,
49363
+ contextLines: menu.contextLines,
49364
+ options: menu.options.map((o) => ({
49365
+ number: o.number,
49366
+ label: o.label,
49367
+ recommended: o.recommended
49368
+ })),
49369
+ selectedIndex: menu.selectedIndex,
49370
+ footerHint: menu.footerHint,
49371
+ confidence: menu.confidence
49372
+ };
49373
+ }
49374
+ /**
49375
+ * POST /api/conversations/:id/pane-choice — answer the live pane menu.
49376
+ *
49377
+ * Body: `{ selectedIndex: number (0-based), signature: string }`. The
49378
+ * signature must match the menu currently on screen, or the operator answered
49379
+ * a stale card and we refuse (409) rather than send keystrokes into a
49380
+ * different prompt. Returns a plain `{ body, status }` for the route to
49381
+ * serialize — the same shape as the conversation-read handlers, so the
49382
+ * contract is unit-testable without an HTTP layer.
49383
+ */
49384
+ async function handleConversationPaneChoiceAnswer(rawId, body, deps = {}) {
49385
+ try {
49386
+ const selectedIndex = Number(body["selectedIndex"]);
49387
+ const signature = typeof body["signature"] === "string" ? body["signature"] : "";
49388
+ if (!Number.isInteger(selectedIndex) || selectedIndex < 0 || selectedIndex > 8) return {
49389
+ body: { error: "selectedIndex must be an integer 0-8" },
49390
+ status: 400
49391
+ };
49392
+ if (!signature) return {
49393
+ body: { error: "signature is required" },
49394
+ status: 400
49395
+ };
49396
+ const numericId = Number(rawId);
49397
+ const conv = !Number.isNaN(numericId) && /^\d+$/.test(rawId) ? getConversationById(numericId) : getConversationByName(rawId);
49398
+ if (!conv) return {
49399
+ body: { error: "Conversation not found" },
49400
+ status: 404
49401
+ };
49402
+ if (!isClaudeCodeConversation(conv)) return {
49403
+ body: { error: "Not a Claude Code conversation" },
49404
+ status: 400
49405
+ };
49406
+ if (!await (deps.sessionExists ?? tmuxSessionExists)(conv.tmuxSession)) return {
49407
+ body: { error: "Conversation session is not running" },
49408
+ status: 409
49409
+ };
49410
+ const capture = deps.capture ?? ((sessionName, lines) => capturePaneText(sessionName, lines));
49411
+ const sendKey = deps.sendKey ?? ((sessionName, key) => Effect.runPromise(sendRawKeystroke(sessionName, key, "pane-choice")));
49412
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
49413
+ const menu = parsePaneChoiceMenu(await capture(conv.tmuxSession, PANE_CAPTURE_LINES));
49414
+ if (!menu) return {
49415
+ body: {
49416
+ error: "The choice menu is no longer on screen",
49417
+ code: "menu-gone"
49418
+ },
49419
+ status: 409
49420
+ };
49421
+ if (paneChoiceMenuSignature(menu) !== signature) return {
49422
+ body: {
49423
+ error: "The choice menu changed since the card was rendered — refresh and re-answer",
49424
+ code: "menu-changed"
49425
+ },
49426
+ status: 409
49427
+ };
49428
+ if (selectedIndex >= menu.options.length) return {
49429
+ body: { error: `selectedIndex out of range (0-${menu.options.length - 1})` },
49430
+ status: 400
49431
+ };
49432
+ const keys = buildChoiceKeystrokes(menu, selectedIndex);
49433
+ for (const key of keys) {
49434
+ await sendKey(conv.tmuxSession, key);
49435
+ await sleep(KEYSTROKE_GAP_MS);
49436
+ }
49437
+ await sleep(DELIVERY_CONFIRM_WAIT_MS);
49438
+ const after = parsePaneChoiceMenu(await capture(conv.tmuxSession, PANE_CAPTURE_LINES));
49439
+ if (after && paneChoiceMenuSignature(after) === signature) return {
49440
+ body: {
49441
+ error: "Keystrokes were sent but the menu is still on screen — answer it from the terminal",
49442
+ code: "delivery-unconfirmed"
49443
+ },
49444
+ status: 409
49445
+ };
49446
+ return { body: {
49447
+ ok: true,
49448
+ answeredLabel: menu.options[selectedIndex].label
49449
+ } };
49450
+ } catch (error) {
49451
+ const msg = error instanceof Error ? error.message : String(error);
49452
+ console.error("[conversations] pane-choice answer failed:", msg);
49453
+ return {
49454
+ body: { error: "Internal server error" },
49455
+ status: 500
49456
+ };
49457
+ }
49458
+ }
49459
+ //#endregion
48899
49460
  //#region ../../lib/overdeck/conversation-reads.ts
48900
49461
  init_concurrency$1();
48901
49462
  init_behavior();
@@ -48907,7 +49468,6 @@ init_conversation_service();
48907
49468
  init_pi_conversation_parser();
48908
49469
  init_ohmypi_conversation_parser();
48909
49470
  init_codex_conversation_parser();
48910
- init_conversation_compaction();
48911
49471
  function result(body, status) {
48912
49472
  return status === void 0 ? { body } : {
48913
49473
  body,
@@ -49130,13 +49690,16 @@ async function getConversationsPendingInputFeed(deps) {
49130
49690
  const codex = await codexConversationPendingInput(conv, true, lastActivityAt ?? (/* @__PURE__ */ new Date()).toISOString());
49131
49691
  if (codex.approval) pending = codex.approval;
49132
49692
  }
49133
- if (!pending && !pendingPlan) return null;
49693
+ let paneChoice = null;
49694
+ if (!pending && !pendingPlan) paneChoice = await claudeConversationPaneChoice(conv);
49695
+ if (!pending && !pendingPlan && !paneChoice) return null;
49134
49696
  return {
49135
49697
  name: conv.name,
49136
49698
  title: conv.title ?? null,
49137
49699
  issueId: conv.issueId ?? null,
49138
49700
  ...pending ? { pendingAskUserQuestion: pending } : {},
49139
- ...pendingPlan ? { pendingProposedPlan: pendingPlan } : {}
49701
+ ...pendingPlan ? { pendingProposedPlan: pendingPlan } : {},
49702
+ ...paneChoice ? { pendingPaneChoice: paneChoice } : {}
49140
49703
  };
49141
49704
  })), 8))).filter((row) => row !== null));
49142
49705
  } catch (error) {
@@ -49182,6 +49745,14 @@ async function getConversationRead(rawId, deps) {
49182
49745
  if (codex.approval) pendingAskUserQuestion = codex.approval;
49183
49746
  }
49184
49747
  }
49748
+ let pendingPaneChoice = null;
49749
+ if (sessionAlive && pendingInputCount === 0) {
49750
+ pendingPaneChoice = await claudeConversationPaneChoice(conv);
49751
+ if (pendingPaneChoice) {
49752
+ pendingInputKinds = [...pendingInputKinds, "paneChoice"];
49753
+ pendingInputCount = pendingInputKinds.length;
49754
+ }
49755
+ }
49185
49756
  return result({
49186
49757
  ...conv,
49187
49758
  sessionAlive,
@@ -49191,6 +49762,7 @@ async function getConversationRead(rawId, deps) {
49191
49762
  pendingInputCount,
49192
49763
  pendingInputKinds,
49193
49764
  pendingAskUserQuestion,
49765
+ ...pendingPaneChoice ? { pendingPaneChoice } : {},
49194
49766
  transcriptMissing: conversationTranscriptMissing(conv, sessionAlive, convSf),
49195
49767
  needsTerminal: await conversationNeedsTerminal(conv, sessionAlive, convSf)
49196
49768
  });
@@ -49272,7 +49844,7 @@ async function resolveSpecialistSessionFile(name) {
49272
49844
  } catch {}
49273
49845
  return null;
49274
49846
  }
49275
- async function getConversationMessagesRead(name, deps) {
49847
+ async function getConversationMessagesRead(name, deps, agentId) {
49276
49848
  try {
49277
49849
  const conv = getConversationByName(name);
49278
49850
  let sessionFile = conv ? await deps.resolveSessionFile(conv) : void 0;
@@ -49293,15 +49865,24 @@ async function getConversationMessagesRead(name, deps) {
49293
49865
  streaming: false
49294
49866
  });
49295
49867
  }
49868
+ const parentSessionFile = sessionFile;
49869
+ if (agentId !== void 0) {
49870
+ sessionFile = subagentTranscriptPath(parentSessionFile, agentId);
49871
+ if (!sessionFile) return result({ error: "Invalid subagent id" }, 400);
49872
+ }
49296
49873
  try {
49297
49874
  const parsed = await getCachedMessages(sessionFile, false);
49298
- if (conv && (parsed.totalCost > 0 || parsed.totalTokens > 0)) updateConversationCost(name, parsed.totalCost, parsed.totalTokens);
49875
+ if (agentId === void 0 && conv && (parsed.totalCost > 0 || parsed.totalTokens > 0)) updateConversationCost(name, parsed.totalCost, parsed.totalTokens);
49299
49876
  let contextUsage = null;
49300
49877
  if (conv) try {
49301
49878
  contextUsage = await computeContextUsage(sessionFile, conv.model);
49302
49879
  } catch {
49303
49880
  contextUsage = null;
49304
49881
  }
49882
+ const subagents = agentId === void 0 ? (await listSubagentMetas(parentSessionFile)).map((meta) => ({
49883
+ ...meta,
49884
+ status: "done"
49885
+ })) : void 0;
49305
49886
  return result({
49306
49887
  messages: parsed.messages,
49307
49888
  workLog: parsed.workLog,
@@ -49311,7 +49892,8 @@ async function getConversationMessagesRead(name, deps) {
49311
49892
  proposedPlan: parsed.proposedPlan,
49312
49893
  compactBoundaries: (parsed.compactBoundaries?.length ?? 0) > 0 ? parsed.compactBoundaries : void 0,
49313
49894
  compacting: isCompacting(sessionFile) || void 0,
49314
- contextUsage
49895
+ contextUsage,
49896
+ subagents
49315
49897
  });
49316
49898
  } catch (parseErr) {
49317
49899
  if (parseErr?.code === "ENOENT") return result({
@@ -49483,8 +50065,6 @@ async function getConversationAbout(name, forceRefresh, deps) {
49483
50065
  }
49484
50066
  //#endregion
49485
50067
  //#region ../../lib/overdeck/conversation-list.ts
49486
- init_transcript_adapter();
49487
- init_summary_fork();
49488
50068
  init_agents$1();
49489
50069
  init_concurrency$1();
49490
50070
  init_behavior();
@@ -49912,7 +50492,35 @@ async function handleConversationMessage(name, body, deps = {
49912
50492
  const message = typeof body["message"] === "string" ? body["message"].trim() : "";
49913
50493
  if (!message) return jsonResponse({ error: "Message is required" }, { status: 400 });
49914
50494
  if (message.length > MAX_MESSAGE_LENGTH) return jsonResponse({ error: `Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters` }, { status: 400 });
49915
- if (getHarnessBehavior(conv.harness).transcriptKind === "claude-jsonl" && shouldInterceptManualCompact(message)) {
50495
+ try {
50496
+ const result = await handleComposerCommandMessage({
50497
+ message,
50498
+ confirmation: composerCommandConfirmationFromBody(body),
50499
+ target: {
50500
+ kind: "conversation",
50501
+ id: conv.name,
50502
+ harness: conv.harness ?? "claude-code",
50503
+ cwd: conv.cwd,
50504
+ issueId: conv.issueId ?? void 0
50505
+ }
50506
+ });
50507
+ if (result !== null) return jsonResponse(result, { status: composerCommandResultHttpStatus(result) });
50508
+ } catch (error) {
50509
+ if (error instanceof ComposerCommandConfirmationError) return jsonResponse({
50510
+ error: error.message,
50511
+ code: error.code
50512
+ }, { status: 422 });
50513
+ if (!(error instanceof ComposerCommandParseError)) throw error;
50514
+ const status = error.code === "unknown-command" ? 404 : error.code === "unknown-flag" ? 422 : 400;
50515
+ return jsonResponse({
50516
+ error: error.message,
50517
+ code: error.code,
50518
+ token: error.token,
50519
+ expected: error.expected
50520
+ }, { status });
50521
+ }
50522
+ const interceptManualCompact = deps.shouldInterceptManualCompact ?? shouldInterceptManualCompact;
50523
+ if (getHarnessBehavior(conv.harness).transcriptKind === "claude-jsonl" && interceptManualCompact(message)) {
49916
50524
  const compactSessionFile = await deps.resolveSessionFile(conv);
49917
50525
  if (!compactSessionFile || !existsSync$1(compactSessionFile)) return jsonResponse({ error: `No session file found for conversation ${conv.name}` }, { status: 400 });
49918
50526
  const compactResult = await compactConversationNative(compactSessionFile, conv.name);
@@ -49951,7 +50559,7 @@ async function handleConversationMessage(name, body, deps = {
49951
50559
  const effectiveAttachmentPaths = partition.effectiveAttachmentPaths;
49952
50560
  const droppedImageCount = partition.droppedImageCount;
49953
50561
  if (droppedImageCount > 0 && !outboundMessage && effectiveAttachmentPaths.length === 0) return jsonResponse({ error: `${conv.model ?? "This model"} can't read images. Switch to a vision-capable model (e.g. mimo-v2.5) to send images.` }, { status: 422 });
49954
- let deliveredMessage = transformMessageForHarness(outboundMessage, harness, effectiveAttachmentPaths);
50562
+ let deliveredMessage = (deps.transformMessageForHarness ?? transformMessageForHarness)(outboundMessage, harness, effectiveAttachmentPaths);
49955
50563
  if (behavior.injectsPromptTimeMemory) deliveredMessage = await injectPiConversationMemory({
49956
50564
  cwd: conv.cwd,
49957
50565
  issueId: conv.issueId,
@@ -50352,7 +50960,7 @@ async function refreshMergeStateFromGitHub(issueId, repo, prNumber) {
50352
50960
  let mergeState;
50353
50961
  let isDraft;
50354
50962
  let checksFailed;
50355
- const { isGitHubAppConfigured, getPullRequestState } = await import("./github-app-ClZ2lk8D.js");
50963
+ const { isGitHubAppConfigured, getPullRequestState } = await import("./github-app-Bmg4R9mv.js");
50356
50964
  if (isGitHubAppConfigured()) {
50357
50965
  const prState = await Effect.runPromise(getPullRequestState(owner, repoName, prNumber));
50358
50966
  mergeable = prState.mergeable === null ? "UNKNOWN" : prState.mergeable ? "MERGEABLE" : "CONFLICTING";
@@ -50503,8 +51111,8 @@ async function handlePullRequestPromise(payload) {
50503
51111
  bumpIssuePrTabCacheGeneration(issueId);
50504
51112
  const repo = payload.repository.full_name;
50505
51113
  if (payload.action === "closed" && pr.merged === true) try {
50506
- const { postMergeLifecycle } = await import("./merge-agent-CKkgIBPB.js");
50507
- const { resolveProjectFromIssueSync } = await import("./projects-vjjcmiN8.js");
51114
+ const { postMergeLifecycle } = await import("./merge-agent-DQTXZk8z.js");
51115
+ const { resolveProjectFromIssueSync } = await import("./projects-_n3Zui6K.js");
50508
51116
  const project = resolveProjectFromIssueSync(issueId);
50509
51117
  if (project) {
50510
51118
  const branchName = pr.head.ref;
@@ -51351,7 +51959,7 @@ startConversationModelBackfill(resolveSessionFile);
51351
51959
  function conversationReadJson(response) {
51352
51960
  return jsonResponse(response.body, response.status === void 0 ? void 0 : { status: response.status });
51353
51961
  }
51354
- const readJsonBody$8 = Effect.gen(function* () {
51962
+ const readJsonBody$9 = Effect.gen(function* () {
51355
51963
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
51356
51964
  try {
51357
51965
  return text ? JSON.parse(text) : {};
@@ -51411,7 +52019,7 @@ const postConversationRoute = HttpRouter.add("POST", "/api/conversations", Effec
51411
52019
  const request = yield* HttpServerRequest.HttpServerRequest;
51412
52020
  const originCheck = validateOrigin$1(request);
51413
52021
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51414
- const body = yield* readJsonBody$8;
52022
+ const body = yield* readJsonBody$9;
51415
52023
  const reqOrigin = getHeader(request, "origin") ?? "none";
51416
52024
  const reqReferer = getHeader(request, "referer") ?? "none";
51417
52025
  const reqUserAgent = getHeader(request, "user-agent") ?? "none";
@@ -51430,14 +52038,14 @@ const postConversationResumeRoute = HttpRouter.add("POST", "/api/conversations/:
51430
52038
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51431
52039
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51432
52040
  const name = (yield* HttpRouter.params)["name"] ?? "";
51433
- const body = yield* readJsonBody$8;
52041
+ const body = yield* readJsonBody$9;
51434
52042
  return yield* Effect.promise(async () => handleConversationResume(name, body, { resolveSessionFile }));
51435
52043
  }));
51436
52044
  const postConversationSwitchModelRoute = HttpRouter.add("POST", "/api/conversations/:name/switch-model", Effect.gen(function* () {
51437
52045
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51438
52046
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51439
52047
  const name = (yield* HttpRouter.params)["name"] ?? "";
51440
- const body = yield* readJsonBody$8;
52048
+ const body = yield* readJsonBody$9;
51441
52049
  return yield* Effect.promise(async () => {
51442
52050
  try {
51443
52051
  return await handleConversationSwitchModel(name, body);
@@ -51452,7 +52060,7 @@ const postConversationThinkingLevelRoute = HttpRouter.add("POST", "/api/conversa
51452
52060
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51453
52061
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51454
52062
  const name = (yield* HttpRouter.params)["name"] ?? "";
51455
- const body = yield* readJsonBody$8;
52063
+ const body = yield* readJsonBody$9;
51456
52064
  return yield* Effect.promise(async () => {
51457
52065
  try {
51458
52066
  return await handleConversationThinkingLevel(name, body);
@@ -51492,11 +52100,12 @@ const postConversationAbortRoute = HttpRouter.add("POST", "/api/conversations/:n
51492
52100
  });
51493
52101
  }));
51494
52102
  const getConversationMessagesRoute = HttpRouter.add("GET", "/api/conversations/:name/messages", Effect.gen(function* () {
51495
- const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
52103
+ const request = yield* HttpServerRequest.HttpServerRequest;
52104
+ const originCheck = validateOrigin$1(request);
51496
52105
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51497
52106
  const name = (yield* HttpRouter.params)["name"] ?? "";
51498
52107
  return yield* Effect.promise(async () => {
51499
- return conversationReadJson(await getConversationMessagesRead(name, conversationReadDependencies));
52108
+ return conversationReadJson(await getConversationMessagesRead(name, conversationReadDependencies, new URL(request.url, "http://localhost").searchParams.get("agentId") ?? void 0));
51500
52109
  });
51501
52110
  }));
51502
52111
  const getConversationMessageLocatorRoute = HttpRouter.add("GET", "/api/conversations/:name/message-locator", Effect.gen(function* () {
@@ -51548,7 +52157,7 @@ const postConversationDeleteImageRoute = HttpRouter.add("POST", "/api/conversati
51548
52157
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51549
52158
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51550
52159
  const name = (yield* HttpRouter.params)["name"] ?? "";
51551
- const body = yield* readJsonBody$8;
52160
+ const body = yield* readJsonBody$9;
51552
52161
  return yield* Effect.promise(async () => {
51553
52162
  try {
51554
52163
  return await handleConversationImageDelete(name, body);
@@ -51563,7 +52172,7 @@ const postConversationMessageRoute = HttpRouter.add("POST", "/api/conversations/
51563
52172
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51564
52173
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51565
52174
  const name = (yield* HttpRouter.params)["name"] ?? "";
51566
- const body = yield* readJsonBody$8;
52175
+ const body = yield* readJsonBody$9;
51567
52176
  return yield* Effect.promise(async () => {
51568
52177
  try {
51569
52178
  return await handleConversationMessage(name, body, conversationMessageDependencies);
@@ -51583,14 +52192,24 @@ const postConversationCodexApprovalRoute = HttpRouter.add("POST", "/api/conversa
51583
52192
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51584
52193
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51585
52194
  const rawId = (yield* HttpRouter.params)["id"] ?? "";
51586
- const body = yield* readJsonBody$8;
52195
+ const body = yield* readJsonBody$9;
51587
52196
  return yield* Effect.promise(() => handleConversationCodexApproval(rawId, body));
51588
52197
  }));
52198
+ const postConversationPaneChoiceRoute = HttpRouter.add("POST", "/api/conversations/:id/pane-choice", Effect.gen(function* () {
52199
+ const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
52200
+ if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
52201
+ const rawId = (yield* HttpRouter.params)["id"] ?? "";
52202
+ const body = yield* readJsonBody$9;
52203
+ return yield* Effect.promise(async () => {
52204
+ const result = await handleConversationPaneChoiceAnswer(rawId, body);
52205
+ return jsonResponse(result.body, { status: result.status });
52206
+ });
52207
+ }));
51589
52208
  const postConversationDeliveryMethodRoute = HttpRouter.add("POST", "/api/conversations/:name/delivery-method", Effect.gen(function* () {
51590
52209
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51591
52210
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51592
52211
  const name = (yield* HttpRouter.params)["name"] ?? "";
51593
- const body = yield* readJsonBody$8;
52212
+ const body = yield* readJsonBody$9;
51594
52213
  return yield* Effect.promise(async () => {
51595
52214
  try {
51596
52215
  return await handleConversationDeliveryMethod(name, body);
@@ -51605,7 +52224,7 @@ const postConversationControlAckRoute = HttpRouter.add("POST", "/api/conversatio
51605
52224
  const request = yield* HttpServerRequest.HttpServerRequest;
51606
52225
  const originCheck = validateConversationControlAckOrigin(request.headers, request.method);
51607
52226
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51608
- const body = yield* readJsonBody$8;
52227
+ const body = yield* readJsonBody$9;
51609
52228
  return yield* Effect.promise(async () => {
51610
52229
  try {
51611
52230
  const result = handleConversationControlAck(body);
@@ -51621,7 +52240,7 @@ const patchConversationRoute = HttpRouter.add("PATCH", "/api/conversations/:name
51621
52240
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51622
52241
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51623
52242
  const name = (yield* HttpRouter.params)["name"] ?? "";
51624
- const body = yield* readJsonBody$8;
52243
+ const body = yield* readJsonBody$9;
51625
52244
  return yield* Effect.promise(async () => {
51626
52245
  try {
51627
52246
  const result = patchConversationTitle(name, body);
@@ -51708,14 +52327,14 @@ const postConversationSummaryForkRoute = HttpRouter.add("POST", "/api/conversati
51708
52327
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51709
52328
  const params = yield* HttpRouter.params;
51710
52329
  const name = decodeURIComponent(params["name"] ?? "");
51711
- const body = yield* readJsonBody$8;
52330
+ const body = yield* readJsonBody$9;
51712
52331
  return yield* Effect.promise(() => handleConversationSummaryFork(name, body));
51713
52332
  }));
51714
52333
  const postConversationPlanActionRoute = HttpRouter.add("POST", "/api/conversations/:name/plan-action", Effect.gen(function* () {
51715
52334
  const originCheck = validateOrigin$1(yield* HttpServerRequest.HttpServerRequest);
51716
52335
  if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
51717
52336
  const name = (yield* HttpRouter.params)["name"] ?? "";
51718
- const body = yield* readJsonBody$8;
52337
+ const body = yield* readJsonBody$9;
51719
52338
  return yield* Effect.promise(() => handleConversationPlanAction(name, body));
51720
52339
  }));
51721
52340
  const conversationDiffDependencies = {
@@ -51770,7 +52389,7 @@ const getConversationAboutRoute = HttpRouter.add("GET", "/api/conversations/:nam
51770
52389
  return conversationReadJson(await getConversationAbout(name, new URL(request.url, "http://localhost").searchParams.get("refresh") === "1", conversationReadDependencies));
51771
52390
  });
51772
52391
  }));
51773
- const conversationsRouteLayer = Layer.mergeAll(getConversationsRoute, getConversationsPendingInputRoute, getArchivedConversationsRoute, getConversationRoute, getConversationHandoffDocRoute, postConversationRoute, patchConversationRoute, deleteConversationRoute, postConversationStopRoute, postConversationResumeRoute, postConversationSwitchModelRoute, postConversationThinkingLevelRoute, postConversationCompactRoute, postConversationAbortRoute, postConversationRestartAllRoute, postConversationArchiveRoute, postConversationUnarchiveRoute, getConversationMessagesRoute, getConversationMessageLocatorRoute, postConversationUploadImageRoute, postConversationDeleteImageRoute, postConversationMessageRoute, postConversationCodexApprovalRoute, postConversationDeliveryMethodRoute, postConversationControlAckRoute, postConversationFavoriteRoute, deleteConversationFavoriteRoute, postConversationSummaryForkRoute, postConversationPlanActionRoute, getConversationDiffsRoute, getConversationDiffFullRoute, getConversationDiffTurnRoute, postConversationRetitleRoute, getConversationAboutRoute);
52392
+ const conversationsRouteLayer = Layer.mergeAll(getConversationsRoute, getConversationsPendingInputRoute, getArchivedConversationsRoute, getConversationRoute, getConversationHandoffDocRoute, postConversationRoute, patchConversationRoute, deleteConversationRoute, postConversationStopRoute, postConversationResumeRoute, postConversationSwitchModelRoute, postConversationThinkingLevelRoute, postConversationCompactRoute, postConversationAbortRoute, postConversationRestartAllRoute, postConversationArchiveRoute, postConversationUnarchiveRoute, getConversationMessagesRoute, getConversationMessageLocatorRoute, postConversationUploadImageRoute, postConversationDeleteImageRoute, postConversationMessageRoute, postConversationCodexApprovalRoute, postConversationPaneChoiceRoute, postConversationDeliveryMethodRoute, postConversationControlAckRoute, postConversationFavoriteRoute, deleteConversationFavoriteRoute, postConversationSummaryForkRoute, postConversationPlanActionRoute, getConversationDiffsRoute, getConversationDiffFullRoute, getConversationDiffTurnRoute, postConversationRetitleRoute, getConversationAboutRoute);
51774
52393
  //#endregion
51775
52394
  //#region routes/show.ts
51776
52395
  /**
@@ -51817,7 +52436,7 @@ const showRouteLayer = Layer.mergeAll(getShowRoute, getShowShadowRoute, getShowH
51817
52436
  //#endregion
51818
52437
  //#region routes/project-rename.ts
51819
52438
  init_projects();
51820
- const readJsonBody$7 = Effect.gen(function* () {
52439
+ const readJsonBody$8 = Effect.gen(function* () {
51821
52440
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
51822
52441
  try {
51823
52442
  return text ? JSON.parse(text) : {};
@@ -51829,7 +52448,7 @@ const postProjectRenameRoute = HttpRouter.add("POST", "/api/projects/:projectKey
51829
52448
  const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
51830
52449
  if (authError) return authError;
51831
52450
  const projectIdentifier = (yield* HttpRouter.params)["projectKey"] ?? "";
51832
- const body = yield* readJsonBody$7;
52451
+ const body = yield* readJsonBody$8;
51833
52452
  if (typeof body.name !== "string") return jsonResponse({ error: "Project name must be a string" }, { status: 400 });
51834
52453
  const result = yield* Effect.result(renameProject(projectIdentifier, body.name));
51835
52454
  if (Result$1.isFailure(result)) {
@@ -51848,9 +52467,8 @@ const postProjectRenameRoute = HttpRouter.add("POST", "/api/projects/:projectKey
51848
52467
  * No chalk, no console, no execSync. Import and use from API routes or CLI.
51849
52468
  */
51850
52469
  init_projects();
51851
- init_paths();
51852
52470
  init_context_layers();
51853
- const BUNDLED_HOOKS_DIR = SYNC_SOURCES.gitHooks;
52471
+ init_git_hooks();
51854
52472
  var DuplicateProjectError = class extends Error {
51855
52473
  constructor(key, existingPath) {
51856
52474
  super(`Project already registered with key: ${key} (path: ${existingPath})`);
@@ -51859,31 +52477,6 @@ var DuplicateProjectError = class extends Error {
51859
52477
  this.name = "DuplicateProjectError";
51860
52478
  }
51861
52479
  };
51862
- function installGitHooksInDir(gitDir) {
51863
- const hooksTarget = join(gitDir, "hooks");
51864
- let installed = 0;
51865
- if (!existsSync$1(hooksTarget)) mkdirSync$1(hooksTarget, { recursive: true });
51866
- if (!existsSync$1(BUNDLED_HOOKS_DIR)) return 0;
51867
- try {
51868
- const hooks = readdirSync$1(BUNDLED_HOOKS_DIR).filter((f) => {
51869
- const p = join(BUNDLED_HOOKS_DIR, f);
51870
- return existsSync$1(p) && statSync$1(p).isFile();
51871
- });
51872
- for (const hook of hooks) {
51873
- const source = join(BUNDLED_HOOKS_DIR, hook);
51874
- const target = join(hooksTarget, hook);
51875
- if (existsSync$1(target)) {
51876
- try {
51877
- if (readlinkSync(target) === source) continue;
51878
- } catch {}
51879
- renameSync$1(target, `${target}.backup`);
51880
- }
51881
- symlinkSync$1(source, target);
51882
- installed++;
51883
- }
51884
- } catch {}
51885
- return installed;
51886
- }
51887
52480
  /**
51888
52481
  * Register a project from a filesystem path.
51889
52482
  *
@@ -51904,7 +52497,7 @@ async function registerProjectFromPath(opts) {
51904
52497
  registerProjectSync(key, projectConfig);
51905
52498
  const seededContextLayer = ensureProjectLayer(fullPath);
51906
52499
  try {
51907
- const { preTrustDirectorySync } = await import("./workspace-manager-CWT55qeI.js");
52500
+ const { preTrustDirectorySync } = await import("./workspace-manager-C1F3XsnS.js");
51908
52501
  preTrustDirectorySync(fullPath);
51909
52502
  } catch {}
51910
52503
  let hooksInstalled = 0;
@@ -51943,9 +52536,9 @@ init_pan_dir();
51943
52536
  init_io();
51944
52537
  init_specs();
51945
52538
  init_paths();
51946
- const execAsync$2 = promisify(exec);
52539
+ const execAsync$3 = promisify(exec);
51947
52540
  async function getIssueDataService() {
51948
- const { getSharedIssueService } = await import("./issue-service-singleton-BvJ09SaR.js");
52541
+ const { getSharedIssueService } = await import("./issue-service-singleton-B2wXiIlw.js");
51949
52542
  return getSharedIssueService();
51950
52543
  }
51951
52544
  /** Returns true if the path exists (any type). */
@@ -52237,8 +52830,9 @@ async function collectSessionTreeNodes(issueId, workspacePath, projectPath, cont
52237
52830
  if (latestMerge) {
52238
52831
  const shipSessionName = `agent-${issueLower}-ship`;
52239
52832
  const shipIsLive = context.tmuxSessionNames.has(shipSessionName);
52240
- const shipJsonlPath = await resolveJsonlPath(shipSessionName, workspacePath);
52241
- sections.push({
52833
+ const shipState = getAgentStateSync(shipSessionName);
52834
+ const shipJsonlPath = shipIsLive || shipState ? await resolveJsonlPath(shipSessionName, workspacePath) : null;
52835
+ if (shipIsLive || shipJsonlPath) sections.push({
52242
52836
  type: "ship",
52243
52837
  sessionId: shipSessionName,
52244
52838
  model: "specialist",
@@ -52321,11 +52915,22 @@ async function fetchProjectSessionTree(projectKey, sharedContext) {
52321
52915
  const features = [];
52322
52916
  const issueTitles = sharedContext?.issueTitles ?? await buildIssueTitleMap();
52323
52917
  if (await pathExists$1(workspacesDir)) {
52324
- const featureCandidates = (await readdir(workspacesDir, { withFileTypes: true }).catch(() => [])).filter((e) => e.isDirectory() && e.name.startsWith("feature-")).map((e) => ({
52325
- name: e.name,
52326
- issueLower: e.name.replace("feature-", ""),
52327
- issueId: e.name.replace("feature-", "").toUpperCase()
52328
- })).filter((c) => /^[a-z]+-\d+$/.test(c.issueLower));
52918
+ const entries = await readdir(workspacesDir, { withFileTypes: true }).catch(() => []);
52919
+ const candidateIssueLowers = /* @__PURE__ */ new Set();
52920
+ for (const entry of entries) {
52921
+ if (!entry.isDirectory()) continue;
52922
+ const match = /^feature-([a-z]+-\d+)(?:-strike|-slot-\d+)?$/.exec(entry.name);
52923
+ if (match) candidateIssueLowers.add(match[1]);
52924
+ }
52925
+ for (const issueLower of liveTmuxIssueIds) {
52926
+ if (candidateIssueLowers.has(issueLower)) continue;
52927
+ if (resolveProjectFromIssueSync(issueLower.toUpperCase())?.projectKey === project.key) candidateIssueLowers.add(issueLower);
52928
+ }
52929
+ const featureCandidates = [...candidateIssueLowers].map((issueLower) => ({
52930
+ name: `feature-${issueLower}`,
52931
+ issueLower,
52932
+ issueId: issueLower.toUpperCase()
52933
+ }));
52329
52934
  const results = await Effect.runPromise(withConcurrencyLimit(featureCandidates.map((c) => Effect.promise(async () => {
52330
52935
  const agentDir = join(getOverdeckHome(), "agents", `agent-${c.issueLower}`);
52331
52936
  const planningAgentDir = join(getOverdeckHome(), "agents", `planning-${c.issueLower}`);
@@ -52403,7 +53008,7 @@ const getProjectReleaseStatusRoute = HttpRouter.add("GET", "/api/projects/:proje
52403
53008
  const projectKey = (yield* HttpRouter.params)["projectKey"] ?? "";
52404
53009
  return jsonResponse(yield* Effect.tryPromise({
52405
53010
  try: async () => {
52406
- const { fetchProjectReleaseStatus } = await import("./project-pipelines-C0MWUeCU.js");
53011
+ const { fetchProjectReleaseStatus } = await import("./project-pipelines-9SKt2bEc.js");
52407
53012
  return fetchProjectReleaseStatus(projectKey);
52408
53013
  },
52409
53014
  catch: (err) => new Error(err instanceof Error ? err.message : String(err))
@@ -52613,7 +53218,7 @@ const postProjectsRoute = HttpRouter.add("POST", "/api/projects", httpHandler(Ef
52613
53218
  if ((await readdir(target).catch(() => [])).length > 0) return jsonResponse({ error: `target directory already exists and is non-empty: ${target}` }, { status: 409 });
52614
53219
  }
52615
53220
  await mkdir(target, { recursive: true });
52616
- await execAsync$2("git init", { cwd: target });
53221
+ await execAsync$3("git init", { cwd: target });
52617
53222
  try {
52618
53223
  const result = await registerProjectFromPath({
52619
53224
  path: target,
@@ -52638,6 +53243,271 @@ const postProjectsRoute = HttpRouter.add("POST", "/api/projects", httpHandler(Ef
52638
53243
  })));
52639
53244
  const projectsRouteLayer = Layer.mergeAll(getProjectSessionTreeRoute, getAllSessionTreesRoute, getProjectReleaseStatusRoute, getProjectAutoMergeDefaultRoute, postProjectAutoMergeDefaultRoute, postProjectRenameRoute, getProjectSwarmPolicyRoute, postProjectSwarmPolicyRoute, getIssueSwarmPolicyRoute, postIssueSwarmPolicyRoute, getIssueStaffingRoute, postIssueStaffingRoute, postProjectsRoute);
52640
53245
  //#endregion
53246
+ //#region routes/projects-merge-train.ts
53247
+ /** PAN-1696: Per-project merge-train override endpoints (GET/POST) */
53248
+ init_projects();
53249
+ const getProjectMergeTrainRoute = HttpRouter.add("GET", "/api/projects/:projectKey/merge-train", httpHandler(Effect.gen(function* () {
53250
+ const config = getProjectSync((yield* HttpRouter.params)["projectKey"] ?? "");
53251
+ if (!config) return jsonResponse({ error: "Project not found" }, { status: 404 });
53252
+ const { isMergeTrainEnabledForProject } = yield* Effect.promise(() => import("./merge-sync-CpGmk6Ch.js"));
53253
+ return jsonResponse({
53254
+ value: config.merge_train ?? null,
53255
+ effective: isMergeTrainEnabledForProject(config)
53256
+ });
53257
+ })));
53258
+ const postProjectMergeTrainRoute = HttpRouter.add("POST", "/api/projects/:projectKey/merge-train", httpHandler(Effect.gen(function* () {
53259
+ const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
53260
+ if (authError) return authError;
53261
+ const key = (yield* HttpRouter.params)["projectKey"] ?? "";
53262
+ if (!getProjectSync(key)) return jsonResponse({ error: "Project not found" }, { status: 404 });
53263
+ const v = (yield* readProjectJsonBody).value;
53264
+ if (v !== "enabled" && v !== "disabled" && v !== null) return jsonResponse({ error: "value must be 'enabled', 'disabled', or null" }, { status: 400 });
53265
+ const { isMergeTrainEnabledForProject } = yield* Effect.promise(() => import("./merge-sync-CpGmk6Ch.js"));
53266
+ setProjectMergeTrainSync(key, v);
53267
+ const updated = getProjectSync(key);
53268
+ return jsonResponse({
53269
+ value: v,
53270
+ effective: updated ? isMergeTrainEnabledForProject(updated) : false
53271
+ });
53272
+ })));
53273
+ const projectsMergeTrainRouteLayer = Layer.mergeAll(getProjectMergeTrainRoute, postProjectMergeTrainRoute);
53274
+ //#endregion
53275
+ //#region routes/merge-train.ts
53276
+ /**
53277
+ * PAN-1696 merge-train-routes: the aggregate `/api/merge-train/*` namespace.
53278
+ *
53279
+ * The merge-train (conflict-aware merge order + UAT batch trains) is a
53280
+ * per-project pipeline concern, but its only HTTP surface used to live under
53281
+ * `/api/flywheel/*` and answered for the Overdeck repo alone. These routes
53282
+ * answer for EVERY tracked project and require no active flywheel run: the
53283
+ * ready set comes from the review-status records via
53284
+ * `listEligibleCandidatesByProject`, not from a run's `activePipeline`.
53285
+ *
53286
+ * The legacy `/api/flywheel/*` merge-train routes stay in place until the
53287
+ * frontend migrates — removing them is the remove-legacy-routes item.
53288
+ */
53289
+ init_projects();
53290
+ const readUnknownJsonBody$1 = Effect.gen(function* () {
53291
+ const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
53292
+ try {
53293
+ return {
53294
+ ok: true,
53295
+ body: text ? JSON.parse(text) : {}
53296
+ };
53297
+ } catch {
53298
+ return {
53299
+ ok: false,
53300
+ error: "Request body must be valid JSON"
53301
+ };
53302
+ }
53303
+ });
53304
+ /** A plain JSON object — not null, not an array, not a primitive. */
53305
+ function isJsonObject(value) {
53306
+ return typeof value === "object" && value !== null && !Array.isArray(value);
53307
+ }
53308
+ /**
53309
+ * A disabled project reports an empty queue rather than being omitted, so the
53310
+ * multi-project view can render it as an explicitly-off row instead of making
53311
+ * "off" and "no ready work" look identical.
53312
+ */
53313
+ async function queueEntryForProject(key, config, enabled) {
53314
+ const base = {
53315
+ projectKey: key,
53316
+ projectName: config.name,
53317
+ enabled
53318
+ };
53319
+ if (!enabled) return {
53320
+ ...base,
53321
+ queue: []
53322
+ };
53323
+ const projectPath = resolve(config.path);
53324
+ const candidates = listEligibleCandidatesByProject(projectPath);
53325
+ if (candidates.length === 0) return {
53326
+ ...base,
53327
+ queue: []
53328
+ };
53329
+ const queue = await Effect.runPromise(computeMergeQueueFromCandidates(candidates, projectPath).pipe(Effect.provide(layer)));
53330
+ return {
53331
+ ...base,
53332
+ queue
53333
+ };
53334
+ }
53335
+ /**
53336
+ * PAN-1696 AC1: one entry per tracked project, with that project's effective
53337
+ * flag and queue, with no flywheel run active. One project's git failure must
53338
+ * not blank the whole response, so a failed project reports an empty queue.
53339
+ */
53340
+ async function getMergeTrainQueuesPayload() {
53341
+ const { isMergeTrainEnabledForProject } = await import("./merge-sync-CpGmk6Ch.js");
53342
+ const projects = listProjectsSync();
53343
+ return (await Promise.allSettled(projects.map(({ key, config }) => queueEntryForProject(key, config, isMergeTrainEnabledForProject(config))))).flatMap((outcome, i) => {
53344
+ if (outcome.status === "fulfilled") return [outcome.value];
53345
+ const entry = projects[i];
53346
+ if (!entry) return [];
53347
+ const reason = outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
53348
+ console.warn(`[merge-train] queue for project ${entry.key} failed: ${reason}`);
53349
+ return [{
53350
+ projectKey: entry.key,
53351
+ projectName: entry.config.name,
53352
+ enabled: true,
53353
+ queue: []
53354
+ }];
53355
+ });
53356
+ }
53357
+ /** PAN-1696 AC2: per-project generation chains across every tracked project. */
53358
+ async function getMergeTrainGenerationsPayload() {
53359
+ const { isMergeTrainEnabledForProject } = await import("./merge-sync-CpGmk6Ch.js");
53360
+ const { getUatGenerationsPayload } = await import("./uat-train-Dewoatps.js");
53361
+ return (await Promise.allSettled(listProjectsSync().map(async ({ key, config }) => ({
53362
+ projectKey: key,
53363
+ projectName: config.name,
53364
+ enabled: isMergeTrainEnabledForProject(config),
53365
+ generations: await getUatGenerationsPayload(resolve(config.path))
53366
+ })))).flatMap((outcome) => outcome.status === "fulfilled" ? [outcome.value] : []);
53367
+ }
53368
+ async function defaultOrderedIssueIdsForProject(projectPath) {
53369
+ const candidates = listEligibleCandidatesByProject(projectPath);
53370
+ if (candidates.length === 0) return [];
53371
+ return (await Effect.runPromise(computeMergeQueueFromCandidates(candidates, projectPath).pipe(Effect.provide(layer)))).map((item) => item.issueId);
53372
+ }
53373
+ async function defaultMergeOne(issueId) {
53374
+ const { triggerMerge } = await import("./merge-ops-1abz6O_P.js");
53375
+ const r = await triggerMerge(issueId);
53376
+ return r.success ? { ok: true } : {
53377
+ ok: false,
53378
+ reason: r.error ?? r.message ?? "merge failed"
53379
+ };
53380
+ }
53381
+ async function postMergeTrainMergeNextPayload(payload, deps = {}) {
53382
+ if (!isJsonObject(payload)) return {
53383
+ status: 400,
53384
+ body: { error: "body must be a JSON object: { n, project }" }
53385
+ };
53386
+ const body = payload;
53387
+ const n = typeof body.n === "number" && Number.isFinite(body.n) ? Math.floor(body.n) : 0;
53388
+ if (n <= 0) return {
53389
+ status: 400,
53390
+ body: { error: "n must be a positive integer" }
53391
+ };
53392
+ if (typeof body.project !== "string" || body.project.trim() === "") return {
53393
+ status: 400,
53394
+ body: { error: "project must be a non-empty string" }
53395
+ };
53396
+ const projectKey = body.project.trim();
53397
+ const config = getProjectSync(projectKey);
53398
+ if (!config) return {
53399
+ status: 404,
53400
+ body: { error: `Unknown project key: ${projectKey}` }
53401
+ };
53402
+ const { isMergeTrainEnabledForProject } = await import("./merge-sync-CpGmk6Ch.js");
53403
+ if (!isMergeTrainEnabledForProject(config)) return {
53404
+ status: 409,
53405
+ body: { error: `merge-train is disabled for project ${projectKey}` }
53406
+ };
53407
+ const issueIds = (await (deps.getOrderedIssueIds ?? defaultOrderedIssueIdsForProject)(resolve(config.path))).slice(0, n);
53408
+ const { shipMergeBatch } = await import("./merge-batch-Cp_j9BGk.js");
53409
+ return {
53410
+ status: 200,
53411
+ body: {
53412
+ projectKey,
53413
+ outcomes: await shipMergeBatch(issueIds, { merge: deps.merge ?? defaultMergeOne })
53414
+ }
53415
+ };
53416
+ }
53417
+ /**
53418
+ * PAN-1696: forced reconcile. With `{ project }` it rebuilds that one project's
53419
+ * generation; with no body it reconciles every merge-train-enabled project.
53420
+ */
53421
+ async function postMergeTrainAssemblePayload(payload) {
53422
+ if (!isJsonObject(payload)) return {
53423
+ status: 400,
53424
+ body: { error: "body must be a JSON object: {} for all projects, or { project }" }
53425
+ };
53426
+ if ("project" in payload) {
53427
+ const raw = payload["project"];
53428
+ if (typeof raw !== "string" || raw.trim() === "") return {
53429
+ status: 400,
53430
+ body: { error: "project must be a non-empty string; omit the field entirely to reconcile every project" }
53431
+ };
53432
+ const projectKey = raw.trim();
53433
+ const config = getProjectSync(projectKey);
53434
+ if (!config) return {
53435
+ status: 404,
53436
+ body: { error: `Unknown project key: ${projectKey}` }
53437
+ };
53438
+ const { runUatTrainReconcile } = await import("./uat-train-Dewoatps.js");
53439
+ return {
53440
+ status: 200,
53441
+ body: { projects: [{
53442
+ projectKey,
53443
+ result: await runUatTrainReconcile({
53444
+ force: true,
53445
+ projectRoot: resolve(config.path)
53446
+ })
53447
+ }] }
53448
+ };
53449
+ }
53450
+ const { runUatTrainReconcileAllProjects } = await import("./uat-train-Dewoatps.js");
53451
+ return {
53452
+ status: 200,
53453
+ body: { projects: await runUatTrainReconcileAllProjects({ force: true }) }
53454
+ };
53455
+ }
53456
+ /**
53457
+ * PAN-1737 generation names contain a slash (`uat/pan-otter-0610`); URL params
53458
+ * carry the name WITHOUT the `uat/` prefix and handlers reconstitute it. Same
53459
+ * reconstruction as the legacy flywheel routes so links stay interchangeable.
53460
+ */
53461
+ function uatGenerationNameFromParam(param) {
53462
+ const decoded = decodeURIComponent(param);
53463
+ return decoded.startsWith("uat/") ? decoded : `uat/${decoded}`;
53464
+ }
53465
+ const getMergeTrainQueuesRoute = HttpRouter.add("GET", "/api/merge-train/queues", httpHandler(Effect.gen(function* () {
53466
+ return jsonResponse(yield* Effect.promise(() => getMergeTrainQueuesPayload()));
53467
+ })));
53468
+ const getMergeTrainGenerationsRoute = HttpRouter.add("GET", "/api/merge-train/generations", httpHandler(Effect.gen(function* () {
53469
+ return jsonResponse(yield* Effect.promise(() => getMergeTrainGenerationsPayload()));
53470
+ })));
53471
+ const postMergeTrainGenerationStackRoute = HttpRouter.add("POST", "/api/merge-train/generations/:name/stack", httpHandler(Effect.gen(function* () {
53472
+ const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
53473
+ if (authError) return authError;
53474
+ const name = uatGenerationNameFromParam((yield* HttpRouter.params)["name"] ?? "");
53475
+ const { postUatGenerationStackPayload } = yield* Effect.promise(() => import("./uat-train-Dewoatps.js"));
53476
+ const result = yield* Effect.promise(() => postUatGenerationStackPayload(name));
53477
+ if (!result.ok) return jsonResponse({ error: result.error }, { status: result.status });
53478
+ return jsonResponse({
53479
+ frontendUrl: result.frontendUrl,
53480
+ evicted: result.evicted
53481
+ });
53482
+ })));
53483
+ const postMergeTrainGenerationPromoteRoute = HttpRouter.add("POST", "/api/merge-train/generations/:name/promote", httpHandler(Effect.gen(function* () {
53484
+ const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
53485
+ if (authError) return authError;
53486
+ const name = uatGenerationNameFromParam((yield* HttpRouter.params)["name"] ?? "");
53487
+ const { postUatGenerationPromotePayload } = yield* Effect.promise(() => import("./uat-train-Dewoatps.js"));
53488
+ const { firePostMergeLifecycle } = yield* Effect.promise(() => import("./specialists-KR3pKulN.js"));
53489
+ const result = yield* Effect.promise(() => postUatGenerationPromotePayload(name, firePostMergeLifecycle));
53490
+ if (!result.success) return jsonResponse(result, { status: result.reason === "not-found" ? 404 : result.reason === "merge-failed" ? 500 : 409 });
53491
+ return jsonResponse(result);
53492
+ })));
53493
+ const postMergeTrainAssembleRoute = HttpRouter.add("POST", "/api/merge-train/assemble", httpHandler(Effect.gen(function* () {
53494
+ const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
53495
+ if (authError) return authError;
53496
+ const parsed = yield* readUnknownJsonBody$1;
53497
+ if (!parsed.ok) return jsonResponse({ error: parsed.error }, { status: 400 });
53498
+ const result = yield* Effect.promise(() => postMergeTrainAssemblePayload(parsed.body));
53499
+ return jsonResponse(result.body, { status: result.status });
53500
+ })));
53501
+ const postMergeTrainMergeNextRoute = HttpRouter.add("POST", "/api/merge-train/merge-next", httpHandler(Effect.gen(function* () {
53502
+ const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
53503
+ if (authError) return authError;
53504
+ const parsed = yield* readUnknownJsonBody$1;
53505
+ if (!parsed.ok) return jsonResponse({ error: parsed.error }, { status: 400 });
53506
+ const result = yield* Effect.promise(() => postMergeTrainMergeNextPayload(parsed.body));
53507
+ return jsonResponse(result.body, { status: result.status });
53508
+ })));
53509
+ const mergeTrainRouteLayer = Layer.mergeAll(getMergeTrainQueuesRoute, getMergeTrainGenerationsRoute, postMergeTrainGenerationStackRoute, postMergeTrainGenerationPromoteRoute, postMergeTrainAssembleRoute, postMergeTrainMergeNextRoute);
53510
+ //#endregion
52641
53511
  //#region routes/context.ts
52642
53512
  init_src();
52643
53513
  init_layers();
@@ -53037,7 +53907,7 @@ function issueIdFromWorkspacePath(workspacePath) {
53037
53907
  const match = /(?:^|[^a-z0-9])([a-z]+-\d+)(?:[^a-z0-9]|$)/i.exec(workspacePath);
53038
53908
  return match ? match[1].toUpperCase() : null;
53039
53909
  }
53040
- function readJsonBody$6() {
53910
+ function readJsonBody$7() {
53041
53911
  return Effect.gen(function* () {
53042
53912
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
53043
53913
  return text ? JSON.parse(text) : {};
@@ -53051,7 +53921,7 @@ const getContextLayersRoute = HttpRouter.add("GET", "/api/context/layers", httpH
53051
53921
  return jsonResponse(yield* Effect.promise(() => loadContextLayers(projects)));
53052
53922
  })));
53053
53923
  const postContextPreviewRoute = HttpRouter.add("POST", "/api/context/preview", httpHandler(Effect.gen(function* () {
53054
- const body = yield* readJsonBody$6();
53924
+ const body = yield* readJsonBody$7();
53055
53925
  const parsed = yield* Effect.try({
53056
53926
  try: () => decodePreviewRequest(body),
53057
53927
  catch: (error) => new Error(error instanceof Error ? error.message : String(error))
@@ -53060,7 +53930,7 @@ const postContextPreviewRoute = HttpRouter.add("POST", "/api/context/preview", h
53060
53930
  return jsonResponse(yield* Effect.promise(() => previewContextLayers(projects, parsed)));
53061
53931
  })));
53062
53932
  const putContextLayerRoute = HttpRouter.add("PUT", "/api/context/layers", httpHandler(Effect.gen(function* () {
53063
- const body = yield* readJsonBody$6();
53933
+ const body = yield* readJsonBody$7();
53064
53934
  const parsed = yield* Effect.try({
53065
53935
  try: () => decodeSaveRequest(body),
53066
53936
  catch: (error) => new Error(error instanceof Error ? error.message : String(error))
@@ -53084,6 +53954,56 @@ const postContextSyncRoute = HttpRouter.add("POST", "/api/context/sync", httpHan
53084
53954
  })));
53085
53955
  const contextRouteLayer = Layer.mergeAll(getContextLayersRoute, postContextPreviewRoute, putContextLayerRoute, postContextSyncRoute);
53086
53956
  //#endregion
53957
+ //#region routes/commands.ts
53958
+ init_src();
53959
+ const COMMANDS_API_HARNESSES = [
53960
+ "claude-code",
53961
+ "codex",
53962
+ "ohmypi",
53963
+ "acp"
53964
+ ];
53965
+ const OVERDECK_COMMANDS = COMPOSER_COMMAND_MANIFEST.map((entry) => {
53966
+ const policy = resolvePolicy(entry.path);
53967
+ return {
53968
+ ...entry,
53969
+ mode: policy.mode,
53970
+ safety: policy.safety
53971
+ };
53972
+ });
53973
+ const COMMAND_PAYLOADS = Object.fromEntries(COMMANDS_API_HARNESSES.map((harness) => [harness, {
53974
+ harness,
53975
+ overdeck: OVERDECK_COMMANDS,
53976
+ native: getHarnessBehavior(harness).nativeCommands ?? []
53977
+ }]));
53978
+ function getCommandsPayload(harness) {
53979
+ return COMMAND_PAYLOADS[harness];
53980
+ }
53981
+ function isCommandsApiHarness(value) {
53982
+ return COMMANDS_API_HARNESSES.includes(value);
53983
+ }
53984
+ function resolveCommandsRequest(harness) {
53985
+ const requestedHarness = harness ?? "claude-code";
53986
+ if (!isCommandsApiHarness(requestedHarness)) return {
53987
+ ok: false,
53988
+ error: `Unknown harness "${requestedHarness}". Expected one of: ${COMMANDS_API_HARNESSES.join(", ")}.`,
53989
+ accepted: COMMANDS_API_HARNESSES
53990
+ };
53991
+ return {
53992
+ ok: true,
53993
+ payload: getCommandsPayload(requestedHarness)
53994
+ };
53995
+ }
53996
+ const getCommandsRoute = HttpRouter.add("GET", "/api/commands", httpHandler(Effect.gen(function* () {
53997
+ const request = yield* HttpServerRequest.HttpServerRequest;
53998
+ const result = resolveCommandsRequest(new URL(request.url, "http://localhost").searchParams.get("harness"));
53999
+ if (!result.ok) return jsonResponse({
54000
+ error: result.error,
54001
+ accepted: result.accepted
54002
+ }, { status: 400 });
54003
+ return jsonResponse(result.payload);
54004
+ })));
54005
+ const commandsRouteLayer = Layer.mergeAll(getCommandsRoute);
54006
+ //#endregion
53087
54007
  //#region routes/admin.ts
53088
54008
  init_tldr_daemon();
53089
54009
  init_projects();
@@ -53193,7 +54113,7 @@ const adminRouteLayer = Layer.mergeAll(getAdminTldrRoute, postAdminBackfillTitle
53193
54113
  * requiring a heavy `pan install` step before first use.
53194
54114
  */
53195
54115
  init_platform();
53196
- const execAsync$1 = promisify(exec);
54116
+ const execAsync$2 = promisify(exec);
53197
54117
  const execFileAsync$2 = promisify(execFile);
53198
54118
  const PREREQ_REGISTRY = {
53199
54119
  addGitHubProject: ["gh"],
@@ -53252,7 +54172,7 @@ function getLinuxManualInstallHint(tool, packageManager) {
53252
54172
  async function installLinuxPackage(tool) {
53253
54173
  const packageManager = await detectLinuxPackageManager();
53254
54174
  if (!packageManager) throw new Error(`No supported Linux package manager found. Install ${getLinuxManualInstallHint(tool, null)}`);
53255
- await execAsync$1(getLinuxInstallCommand(tool, packageManager), { timeout: 12e4 });
54175
+ await execAsync$2(getLinuxInstallCommand(tool, packageManager), { timeout: 12e4 });
53256
54176
  }
53257
54177
  async function isToolInstalled(tool) {
53258
54178
  if (tool === "docker") return checkCommand("docker", ["info"]);
@@ -53295,7 +54215,7 @@ async function installTool(tool) {
53295
54215
  }
53296
54216
  }
53297
54217
  async function installTmux() {
53298
- if (await Effect.runPromise(detectPlatform()) === "darwin") await execAsync$1("brew install tmux", { timeout: 12e4 });
54218
+ if (await Effect.runPromise(detectPlatform()) === "darwin") await execAsync$2("brew install tmux", { timeout: 12e4 });
53299
54219
  else await installLinuxPackage("tmux");
53300
54220
  return {
53301
54221
  tool: "tmux",
@@ -53309,14 +54229,14 @@ async function installTtyd() {
53309
54229
  mkdirSync$1(binDir, { recursive: true });
53310
54230
  const ttydPath = join(binDir, "ttyd");
53311
54231
  if (plat === "darwin") try {
53312
- await execAsync$1("brew install ttyd", { timeout: 12e4 });
54232
+ await execAsync$2("brew install ttyd", { timeout: 12e4 });
53313
54233
  return {
53314
54234
  tool: "ttyd",
53315
54235
  success: true,
53316
54236
  message: "ttyd installed via Homebrew"
53317
54237
  };
53318
54238
  } catch {}
53319
- await execAsync$1(`curl -sL "https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64" -o "${ttydPath}" && chmod +x "${ttydPath}"`, { timeout: 6e4 });
54239
+ await execAsync$2(`curl -sL "https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64" -o "${ttydPath}" && chmod +x "${ttydPath}"`, { timeout: 6e4 });
53320
54240
  return {
53321
54241
  tool: "ttyd",
53322
54242
  success: true,
@@ -53325,8 +54245,8 @@ async function installTtyd() {
53325
54245
  }
53326
54246
  async function installMkcert() {
53327
54247
  if (await Effect.runPromise(detectPlatform()) === "darwin") {
53328
- await execAsync$1("brew install mkcert", { timeout: 12e4 });
53329
- await execAsync$1("mkcert -install", { timeout: 3e4 });
54248
+ await execAsync$2("brew install mkcert", { timeout: 12e4 });
54249
+ await execAsync$2("mkcert -install", { timeout: 3e4 });
53330
54250
  return {
53331
54251
  tool: "mkcert",
53332
54252
  success: true,
@@ -53336,8 +54256,8 @@ async function installMkcert() {
53336
54256
  const binDir = join(homedir$1(), ".local", "bin");
53337
54257
  mkdirSync$1(binDir, { recursive: true });
53338
54258
  const mkcertPath = join(binDir, "mkcert");
53339
- await execAsync$1(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${process.arch === "x64" ? "amd64" : process.arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, { timeout: 6e4 });
53340
- await execAsync$1("mkcert -install", { timeout: 3e4 });
54259
+ await execAsync$2(`curl -sL "https://github.com/FiloSottile/mkcert/releases/latest/download/mkcert-v1.4.4-linux-${process.arch === "x64" ? "amd64" : process.arch}" -o "${mkcertPath}" && chmod +x "${mkcertPath}"`, { timeout: 6e4 });
54260
+ await execAsync$2("mkcert -install", { timeout: 3e4 });
53341
54261
  return {
53342
54262
  tool: "mkcert",
53343
54263
  success: true,
@@ -53345,7 +54265,7 @@ async function installMkcert() {
53345
54265
  };
53346
54266
  }
53347
54267
  async function installJq() {
53348
- if (await Effect.runPromise(detectPlatform()) === "darwin") await execAsync$1("brew install jq", { timeout: 12e4 });
54268
+ if (await Effect.runPromise(detectPlatform()) === "darwin") await execAsync$2("brew install jq", { timeout: 12e4 });
53349
54269
  else await installLinuxPackage("jq");
53350
54270
  return {
53351
54271
  tool: "jq",
@@ -53360,7 +54280,7 @@ async function installOx() {
53360
54280
  const binDir = join(homedir$1(), ".local", "bin");
53361
54281
  const oxPath = join(binDir, "ox");
53362
54282
  mkdirSync$1(binDir, { recursive: true });
53363
- await execAsync$1(`curl -sL "https://github.com/eltmon/ox/releases/download/latest/ox-${platform}-${arch}" -o "${oxPath}" && chmod +x "${oxPath}"`, { timeout: 6e4 });
54283
+ await execAsync$2(`curl -sL "https://github.com/eltmon/ox/releases/download/latest/ox-${platform}-${arch}" -o "${oxPath}" && chmod +x "${oxPath}"`, { timeout: 6e4 });
53364
54284
  return {
53365
54285
  tool: "ox",
53366
54286
  success: true,
@@ -53377,7 +54297,7 @@ async function installOx() {
53377
54297
  * GET /api/prereqs/:feature — check which tools are missing for a feature
53378
54298
  * POST /api/prereqs/install — install a specific tool by name
53379
54299
  */
53380
- const readJsonBody$5 = Effect.gen(function* () {
54300
+ const readJsonBody$6 = Effect.gen(function* () {
53381
54301
  const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
53382
54302
  try {
53383
54303
  return text ? JSON.parse(text) : {};
@@ -53414,7 +54334,7 @@ const checkFeatureRoute = HttpRouter.add("GET", "/api/prereqs/:feature", httpHan
53414
54334
  });
53415
54335
  })));
53416
54336
  const installToolRoute = HttpRouter.add("POST", "/api/prereqs/install", httpHandler(Effect.gen(function* () {
53417
- const tool = (yield* readJsonBody$5).tool;
54337
+ const tool = (yield* readJsonBody$6).tool;
53418
54338
  if (!tool) return jsonResponse({ error: "Missing 'tool' in request body" }, { status: 400 });
53419
54339
  if (!INSTALLABLE_TOOLS.some((t) => t === tool)) return jsonResponse({ error: `Tool '${tool}' is not auto-installable` }, { status: 400 });
53420
54340
  const result = yield* Effect.promise(() => installTool(tool));
@@ -54075,7 +54995,7 @@ function getCheckpointWorker() {
54075
54995
  return worker;
54076
54996
  }
54077
54997
  async function runInline(operation, payload) {
54078
- const { claimTranscriptRange, commitTranscriptRange, releaseTranscriptRange, getTranscriptCheckpoint, listTranscriptCheckpoints } = await import("./checkpoints-BJIs5QIA.js");
54998
+ const { claimTranscriptRange, commitTranscriptRange, releaseTranscriptRange, getTranscriptCheckpoint, listTranscriptCheckpoints } = await import("./checkpoints-Wm963lm8.js");
54079
54999
  switch (operation) {
54080
55000
  case "claimTranscriptRange": return claimTranscriptRange(payload);
54081
55001
  case "commitTranscriptRange": return commitTranscriptRange(payload);
@@ -55229,7 +56149,7 @@ function buildPendingTurn(input, fromOffset, toOffset, compressed) {
55229
56149
  };
55230
56150
  }
55231
56151
  async function emitMemoryObservationCreated(observation, timestamp) {
55232
- const { initEventStore } = await import("./event-store-EV2cMiXj.js");
56152
+ const { initEventStore } = await import("./event-store-5UKzh9Jf.js");
55233
56153
  await (await initEventStore()).appendAsync({
55234
56154
  type: "memory.observation_created",
55235
56155
  timestamp,
@@ -56360,7 +57280,16 @@ async function handleMemorySessionStartBody(body, options = {}) {
56360
57280
  statusCode: 422,
56361
57281
  error: "memory identity could not be resolved"
56362
57282
  };
56363
- const fileStat = await (options.statTranscript ?? getTranscriptStat$1)(trustedTranscriptPath);
57283
+ let fileStat;
57284
+ try {
57285
+ fileStat = await (options.statTranscript ?? getTranscriptStat$1)(trustedTranscriptPath);
57286
+ } catch (error) {
57287
+ if (error.code === "ENOENT") return {
57288
+ status: "transcript-missing",
57289
+ sessionId
57290
+ };
57291
+ throw error;
57292
+ }
56364
57293
  (options.registerTranscript ?? registerTranscriptForPolling)({
56365
57294
  agentId: stringField(body.agentId) ?? stringField(body.agent_id) ?? identity.runId,
56366
57295
  sessionId,
@@ -56491,7 +57420,7 @@ const postMemorySessionStartRoute = HttpRouter.add("POST", "/api/memory/session/
56491
57420
  }
56492
57421
  const readModel = yield* ReadModelService;
56493
57422
  const result = yield* Effect.promise(() => handleMemorySessionStartBody(body, { resolveAgentIdBySessionId: async (sessionId) => Effect.runPromise(readModel.getAgentIdBySessionId(sessionId)) }));
56494
- if (result.status === "subagent" || result.status === "disabled") return HttpServerResponse.text("", { status: 204 });
57423
+ if (result.status === "subagent" || result.status === "disabled" || result.status === "transcript-missing") return HttpServerResponse.text("", { status: 204 });
56495
57424
  if (result.status === "error") return jsonResponse({
56496
57425
  ok: false,
56497
57426
  error: result.error
@@ -56895,6 +57824,93 @@ const postTestCheckpointRoute = HttpRouter.add("POST", "/api/agents/:agentId/dif
56895
57824
  }));
56896
57825
  const diffsRouteLayer = Layer.mergeAll(getDiffsRoute, getTurnDiffRoute, getFullDiffRoute, getVsMainDiffRoute, postTestCheckpointRoute);
56897
57826
  //#endregion
57827
+ //#region routes/linear-mcp-auth.ts
57828
+ init_issue_service_singleton();
57829
+ const LINEAR_MCP_AUTH_CALLBACK_COPY_PREFIX = "The operator completed the Linear OAuth authorization in their browser. Finish the flow now: call mcp__linear__complete_authentication with callback_url set to exactly this URL:";
57830
+ const readJsonBody$5 = Effect.gen(function* () {
57831
+ const text = yield* (yield* HttpServerRequest.HttpServerRequest).text;
57832
+ return text ? JSON.parse(text) : {};
57833
+ });
57834
+ function rejectInvalidOrigin(request) {
57835
+ const originCheck = validateOrigin$1(request);
57836
+ if (!originCheck.ok) return jsonResponse({ error: originCheck.error }, { status: 403 });
57837
+ return null;
57838
+ }
57839
+ function isValidLinearMcpCallbackUrl(value) {
57840
+ try {
57841
+ const url = new URL(value);
57842
+ const localHost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
57843
+ return url.protocol === "http:" && localHost && !!url.searchParams.get("code") && !!url.searchParams.get("state");
57844
+ } catch {
57845
+ return false;
57846
+ }
57847
+ }
57848
+ function callbackCopy(callbackUrl) {
57849
+ return `${LINEAR_MCP_AUTH_CALLBACK_COPY_PREFIX} ${callbackUrl} — then re-check Linear access and resume your canonical task.`;
57850
+ }
57851
+ /**
57852
+ * Attach each blocked agent's canonical tracker URL (Linear web URL, GitHub
57853
+ * html_url) from the issues read door so the banner can link every blocked
57854
+ * agent's issue — including Linear-tracked ones the frontend cannot derive a
57855
+ * URL for. Best-effort: an unresolvable issue falls back to null and the
57856
+ * banner renders its own fallback.
57857
+ */
57858
+ function withIssueUrls(intervention) {
57859
+ let urlByIdentifier;
57860
+ try {
57861
+ urlByIdentifier = /* @__PURE__ */ new Map();
57862
+ const issues = getSharedIssueService().getIssues({ includeCompleted: true });
57863
+ for (const issue of issues) if (typeof issue.identifier === "string" && typeof issue.url === "string" && issue.url !== "") urlByIdentifier.set(issue.identifier.toLowerCase(), issue.url);
57864
+ } catch {
57865
+ return intervention;
57866
+ }
57867
+ return {
57868
+ ...intervention,
57869
+ blockedAgents: intervention.blockedAgents.map((agent) => ({
57870
+ ...agent,
57871
+ issueUrl: agent.issueId ? urlByIdentifier.get(agent.issueId.toLowerCase()) ?? null : null
57872
+ }))
57873
+ };
57874
+ }
57875
+ const getLinearMcpAuthRoute = HttpRouter.add("GET", "/api/linear-mcp-auth", httpHandler(Effect.gen(function* () {
57876
+ return jsonResponse(withIssueUrls(yield* Effect.promise(() => resolveLinearMcpAuthIntervention())));
57877
+ })));
57878
+ const postLinearMcpAuthCallbackRoute = HttpRouter.add("POST", "/api/linear-mcp-auth/callback", httpHandler(Effect.gen(function* () {
57879
+ const originError = rejectInvalidOrigin(yield* HttpServerRequest.HttpServerRequest);
57880
+ if (originError) return originError;
57881
+ const body = yield* readJsonBody$5;
57882
+ const callbackUrl = typeof body["callbackUrl"] === "string" ? body["callbackUrl"] : "";
57883
+ if (!isValidLinearMcpCallbackUrl(callbackUrl)) return jsonResponse({
57884
+ success: false,
57885
+ error: "callbackUrl must be a localhost OAuth callback URL with code and state parameters"
57886
+ }, { status: 400 });
57887
+ const intervention = yield* Effect.promise(() => resolveLinearMcpAuthIntervention());
57888
+ if (intervention.authUrlAgentId === null) return jsonResponse({
57889
+ success: false,
57890
+ error: "No blocked agent owns the active Linear authorization URL"
57891
+ }, { status: 409 });
57892
+ yield* Effect.promise(() => messageAgent(intervention.authUrlAgentId, callbackCopy(callbackUrl), "linear-mcp-auth-callback"));
57893
+ yield* Effect.promise(() => appendLinearMcpAuthCallbackRelayedEvent({
57894
+ agentId: intervention.authUrlAgentId,
57895
+ issueId: intervention.blockedAgents.find((agent) => agent.agentId === intervention.authUrlAgentId)?.issueId ?? null
57896
+ }));
57897
+ return jsonResponse({
57898
+ success: true,
57899
+ relayedTo: intervention.authUrlAgentId
57900
+ });
57901
+ })));
57902
+ const postLinearMcpAuthCompleteRoute = HttpRouter.add("POST", "/api/linear-mcp-auth/complete", httpHandler(Effect.gen(function* () {
57903
+ const originError = rejectInvalidOrigin(yield* HttpServerRequest.HttpServerRequest);
57904
+ if (originError) return originError;
57905
+ yield* Effect.promise(() => appendLinearMcpAuthHealthyEvent({
57906
+ agentId: "operator",
57907
+ issueId: null,
57908
+ source: "operator"
57909
+ }));
57910
+ return jsonResponse({ success: true });
57911
+ })));
57912
+ const linearMcpAuthRouteLayer = Layer.mergeAll(getLinearMcpAuthRoute, postLinearMcpAuthCallbackRoute, postLinearMcpAuthCompleteRoute);
57913
+ //#endregion
56898
57914
  //#region routes/terminals.ts
56899
57915
  /**
56900
57916
  * Terminal routes (PAN-1545 / PAN-1561) — ad-hoc tmux sessions for the
@@ -57644,6 +58660,287 @@ const getSubstrateBugWeightsRoute = HttpRouter.add("GET", "/api/flywheel/substra
57644
58660
  })));
57645
58661
  })));
57646
58662
  //#endregion
58663
+ //#region services/flywheel-actions.ts
58664
+ init_config_yaml();
58665
+ init_harness_resolve();
58666
+ init_control_settings();
58667
+ init_tmux();
58668
+ const DEFAULT_BRIEF_PATH$1 = "docs/flywheel-brief.md";
58669
+ const execAsync$1 = promisify(exec);
58670
+ function mb(bytes) {
58671
+ return Math.round(bytes / 1024 / 1024);
58672
+ }
58673
+ function isInsideRoot$1(projectRoot, candidate) {
58674
+ const relativePath = relative(projectRoot, candidate);
58675
+ return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath);
58676
+ }
58677
+ function resolveFlywheelStartBriefPath(cwd, requestedPath) {
58678
+ const rawPath = requestedPath?.trim() || DEFAULT_BRIEF_PATH$1;
58679
+ if (rawPath.includes("\0")) throw new Error("Brief path is invalid");
58680
+ const root = resolve(cwd);
58681
+ const absolutePath = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
58682
+ if (!isInsideRoot$1(root, absolutePath)) throw new Error("Brief path must stay inside the project root");
58683
+ const normalizedRoot = root.endsWith(sep) ? root : `${root}${sep}`;
58684
+ const displayPath = absolutePath === root ? "." : relative(root, absolutePath);
58685
+ return {
58686
+ absolutePath,
58687
+ displayPath: absolutePath.startsWith(normalizedRoot) ? displayPath : absolutePath
58688
+ };
58689
+ }
58690
+ async function assertExistingPathInsideRoot$1(projectRoot, candidate) {
58691
+ const [realRoot, realCandidate] = await Promise.all([realpath(projectRoot), realpath(candidate)]);
58692
+ if (!isInsideRoot$1(realRoot, realCandidate)) throw new Error("Brief path must stay inside the project root");
58693
+ }
58694
+ async function requireFlywheelBrief(cwd, requestedPath) {
58695
+ const resolved = resolveFlywheelStartBriefPath(cwd, requestedPath);
58696
+ try {
58697
+ await assertExistingPathInsideRoot$1(cwd, resolved.absolutePath);
58698
+ await readFile(resolved.absolutePath, "utf8");
58699
+ return resolved;
58700
+ } catch (error) {
58701
+ if ((typeof error === "object" && error !== null && "code" in error ? error.code : void 0) === "ENOENT") throw new Error(`Flywheel brief not found: ${resolved.displayPath}`);
58702
+ throw error;
58703
+ }
58704
+ }
58705
+ async function dashboardSetting(key) {
58706
+ return runDashboardDbJob("getSetting", key);
58707
+ }
58708
+ async function setDashboardSetting(key, value) {
58709
+ await runDashboardDbJob("setSetting", {
58710
+ key,
58711
+ value
58712
+ });
58713
+ }
58714
+ async function getActiveRunId() {
58715
+ const value = await dashboardSetting(FLYWHEEL_ACTIVE_RUN_ID_KEY);
58716
+ return value && value.trim() ? value : null;
58717
+ }
58718
+ async function setActiveRunId(runId) {
58719
+ await setDashboardSetting(FLYWHEEL_ACTIVE_RUN_ID_KEY, runId ?? "");
58720
+ }
58721
+ async function isPaused() {
58722
+ return await dashboardSetting(FLYWHEEL_GLOBAL_PAUSE_KEY) === "true";
58723
+ }
58724
+ async function setPaused(paused) {
58725
+ await setDashboardSetting(FLYWHEEL_GLOBAL_PAUSE_KEY, paused ? "true" : "false");
58726
+ }
58727
+ async function readGateSnapshot() {
58728
+ const [paused, activeRunId] = await Promise.all([isPaused(), getActiveRunId()]);
58729
+ return {
58730
+ paused,
58731
+ activeRunId
58732
+ };
58733
+ }
58734
+ async function resolveFlywheelRoleConfig() {
58735
+ const { config } = await Effect.runPromise(loadConfigNoMigration());
58736
+ const flywheel = config.roles?.flywheel;
58737
+ const model = resolveModel("flywheel", void 0, config);
58738
+ return {
58739
+ harness: await resolveHarness({ model }),
58740
+ model,
58741
+ effort: flywheel?.effort ?? "high",
58742
+ minAgents: flywheel?.minAgents ?? 20,
58743
+ maxAgents: flywheel?.maxAgents ?? 30,
58744
+ scope: flywheel?.scope ?? "pan-only"
58745
+ };
58746
+ }
58747
+ async function gitOutput(command, cwd) {
58748
+ const { stdout } = await execAsync$1(command, {
58749
+ cwd,
58750
+ encoding: "utf8"
58751
+ });
58752
+ return stdout.trim();
58753
+ }
58754
+ async function createInitialFlywheelStatus(runId, startedAt, cwd, agentModel, agentHarness, roleConfig) {
58755
+ const ramTotalMb = mb(totalmem$1());
58756
+ return {
58757
+ runId,
58758
+ startedAt,
58759
+ elapsedMs: 0,
58760
+ orchestrator: {
58761
+ harness: agentHarness ?? roleConfig.harness,
58762
+ model: agentModel ?? roleConfig.model,
58763
+ effort: roleConfig.effort,
58764
+ ctxPercent: 0
58765
+ },
58766
+ headline: {
58767
+ bugsFixed: 0,
58768
+ swarmItemsMerged: 0,
58769
+ swarmItemsTotal: 0,
58770
+ prsMerged: 0,
58771
+ awaitingUat: 0
58772
+ },
58773
+ scope: roleConfig.scope,
58774
+ activePipeline: [],
58775
+ substrateBugs: [],
58776
+ agents: [{
58777
+ id: FLYWHEEL_ORCHESTRATOR_AGENT_ID,
58778
+ label: "flywheel-orchestrator",
58779
+ status: "running",
58780
+ role: "flywheel",
58781
+ model: agentModel
58782
+ }],
58783
+ parked: [],
58784
+ suggestions: [],
58785
+ system: {
58786
+ mainHead: await gitOutput("git rev-parse --short HEAD", cwd).catch(() => "unknown"),
58787
+ ramUsedMb: Math.max(0, ramTotalMb - mb(freemem$1())),
58788
+ ramTotalMb,
58789
+ swapUsedMb: 0,
58790
+ swapTotalMb: 0,
58791
+ agentsActive: 1,
58792
+ agentsCap: roleConfig.maxAgents
58793
+ },
58794
+ openQuestions: [],
58795
+ ticks: 0,
58796
+ lastTickAt: startedAt
58797
+ };
58798
+ }
58799
+ async function startFlywheelRunForDashboard(options = {}) {
58800
+ const cwd = options.cwd ?? process.cwd();
58801
+ if (isFlywheelDevcontainerRuntime()) throw new Error("Refusing to spawn flywheel-orchestrator inside a workspace devcontainer");
58802
+ const activeRunId = await resolveLiveFlywheelRunId();
58803
+ if (activeRunId) if (!await Effect.runPromise(sessionExists("flywheel-orchestrator")) || await Effect.runPromise(isPaneDead("flywheel-orchestrator"))) {
58804
+ console.log(`[flywheel] ${activeRunId} gate stuck — orchestrator pane is dead; auto-aborting`);
58805
+ await Effect.runPromise(killSession(FLYWHEEL_ORCHESTRATOR_AGENT_ID)).catch(() => {});
58806
+ await abortFlywheelRun(activeRunId);
58807
+ } else throw new Error(`Flywheel run ${activeRunId} is already active; pause, resume, or report it before starting another run`);
58808
+ const brief = await requireFlywheelBrief(cwd, options.brief ?? DEFAULT_BRIEF_PATH$1);
58809
+ const runId = await nextFlywheelRunId();
58810
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
58811
+ const roleConfig = await resolveFlywheelRoleConfig();
58812
+ await writeFlywheelLaunchMetadata({
58813
+ version: 1,
58814
+ runId,
58815
+ workspace: cwd,
58816
+ briefPath: brief.absolutePath,
58817
+ briefDisplayPath: brief.displayPath,
58818
+ scope: roleConfig.scope
58819
+ });
58820
+ const agent = await spawnFlywheelAgent(runId, {
58821
+ briefPath: brief.absolutePath,
58822
+ workspace: cwd,
58823
+ model: roleConfig.model,
58824
+ harness: roleConfig.harness,
58825
+ effort: roleConfig.effort,
58826
+ minAgents: roleConfig.minAgents,
58827
+ maxAgents: roleConfig.maxAgents,
58828
+ scope: roleConfig.scope
58829
+ });
58830
+ await setActiveRunId(runId);
58831
+ await setPaused(false);
58832
+ await writeLatestFlywheelStatus(await createInitialFlywheelStatus(runId, startedAt, cwd, agent.model, agent.harness, roleConfig));
58833
+ return {
58834
+ runId,
58835
+ briefDisplayPath: resolveFlywheelStartBriefPath(cwd, options.brief ?? DEFAULT_BRIEF_PATH$1).displayPath,
58836
+ agentModel: agent.model
58837
+ };
58838
+ }
58839
+ async function pauseFlywheelRunForDashboard() {
58840
+ const before = await readGateSnapshot();
58841
+ if (before.paused) return {
58842
+ before,
58843
+ after: before,
58844
+ changed: false
58845
+ };
58846
+ if (before.activeRunId) try {
58847
+ const { readFile } = await import("node:fs/promises");
58848
+ const { join } = await import("node:path");
58849
+ const { getAgentDir } = await import("./agents-ePFVoZbu.js");
58850
+ const sessionId = (await readFile(join(getAgentDir(FLYWHEEL_ORCHESTRATOR_AGENT_ID), "session.id"), "utf-8")).trim();
58851
+ if (sessionId) await saveResumeSessionId(before.activeRunId, sessionId);
58852
+ } catch {}
58853
+ await setPaused(true);
58854
+ await import("./agents-ePFVoZbu.js").then(({ stopAgent }) => Effect.runPromise(stopAgent(FLYWHEEL_ORCHESTRATOR_AGENT_ID)));
58855
+ return {
58856
+ before,
58857
+ after: await readGateSnapshot(),
58858
+ changed: true
58859
+ };
58860
+ }
58861
+ async function abortFlywheelRunForDashboard() {
58862
+ const candidate = await getActiveRunId();
58863
+ if (!candidate) return { aborted: await resolveLiveFlywheelRunId() ?? null };
58864
+ const { stopAgent } = await import("./agents-ePFVoZbu.js");
58865
+ await Effect.runPromise(stopAgent(FLYWHEEL_ORCHESTRATOR_AGENT_ID));
58866
+ await abortFlywheelRun(candidate);
58867
+ return { aborted: candidate };
58868
+ }
58869
+ async function resumeFlywheelRunForDashboard() {
58870
+ const before = await readGateSnapshot();
58871
+ if (!before.paused && await Effect.runPromise(sessionExists("flywheel-orchestrator"))) return {
58872
+ before,
58873
+ after: before,
58874
+ changed: false
58875
+ };
58876
+ if (!before.activeRunId) throw new Error("No active flywheel run to resume");
58877
+ const launch = await readFlywheelLaunchMetadata(before.activeRunId);
58878
+ if (!launch) throw new Error(`Flywheel run ${before.activeRunId} is missing launch metadata; cannot resume safely`);
58879
+ const brief = await requireFlywheelBrief(launch.workspace, launch.briefPath);
58880
+ const roleConfig = await resolveFlywheelRoleConfig();
58881
+ if (launch.scope !== roleConfig.scope) await writeFlywheelLaunchMetadata({
58882
+ ...launch,
58883
+ version: 1,
58884
+ scope: roleConfig.scope
58885
+ });
58886
+ const resumeSessionId = await loadResumeSessionId(before.activeRunId) ?? void 0;
58887
+ await spawnFlywheelAgent(before.activeRunId, {
58888
+ workspace: launch.workspace,
58889
+ briefPath: brief.absolutePath,
58890
+ model: roleConfig.model,
58891
+ harness: roleConfig.harness,
58892
+ effort: roleConfig.effort,
58893
+ minAgents: roleConfig.minAgents,
58894
+ maxAgents: roleConfig.maxAgents,
58895
+ scope: roleConfig.scope,
58896
+ resumeSessionId
58897
+ });
58898
+ await setPaused(false);
58899
+ return {
58900
+ before,
58901
+ after: await readGateSnapshot(),
58902
+ changed: true
58903
+ };
58904
+ }
58905
+ function getPlatformOpenCommand() {
58906
+ switch (process.platform) {
58907
+ case "linux": return "xdg-open";
58908
+ case "darwin": return "open";
58909
+ case "win32": return "explorer";
58910
+ default: throw new Error(`Opening files is not supported on ${process.platform}`);
58911
+ }
58912
+ }
58913
+ function openPathDetached(path) {
58914
+ spawn(getPlatformOpenCommand(), [path], {
58915
+ detached: true,
58916
+ stdio: "ignore"
58917
+ }).unref();
58918
+ }
58919
+ async function resolveReportOpenRunId(runId) {
58920
+ if (runId) return runId;
58921
+ const runs = await listFlywheelRuns();
58922
+ const run = runs.find((candidate) => candidate.status === "complete") ?? runs[0];
58923
+ if (!run) throw new Error("no flywheel run report to open");
58924
+ return run.id;
58925
+ }
58926
+ async function openFlywheelRunReportForDashboard(options = {}) {
58927
+ const runId = await resolveReportOpenRunId(options.runId);
58928
+ const detail = await getFlywheelRunDetail(runId);
58929
+ if (!detail) throw new Error(`Flywheel run not found: ${runId}`);
58930
+ if (!detail.paths.report) throw new Error(`No report exists for ${runId}`);
58931
+ await (options.opener ?? openPathDetached)(detail.paths.report);
58932
+ return {
58933
+ runId,
58934
+ path: detail.paths.report
58935
+ };
58936
+ }
58937
+ async function readCurrentFlywheelStatusForDashboard() {
58938
+ const activeRunId = await resolveLiveFlywheelRunId();
58939
+ if (!activeRunId) return null;
58940
+ const activeRun = await getFlywheelRunDetail(activeRunId);
58941
+ return activeRun?.status === "running" ? activeRun.latest : null;
58942
+ }
58943
+ //#endregion
57647
58944
  //#region services/flywheel-state.ts
57648
58945
  const FLYWHEEL_STATE_RELATIVE_PATH = "docs/FLYWHEEL-STATE.md";
57649
58946
  function resolveStatePath(projectRoot) {
@@ -57679,6 +58976,7 @@ async function readFlywheelState(options = {}) {
57679
58976
  const AUTO_MERGE_COOLDOWN_MS = 3e5;
57680
58977
  //#endregion
57681
58978
  //#region ../../lib/cloister/auto-merge-eligibility.ts
58979
+ init_forge();
57682
58980
  init_review_status();
57683
58981
  init_tracker_utils();
57684
58982
  const execFileAsync$1 = promisify(execFile);
@@ -57938,6 +59236,7 @@ init_src();
57938
59236
  init_activity_logger();
57939
59237
  init_tmux();
57940
59238
  init_control_settings();
59239
+ init_forge();
57941
59240
  init_review_status();
57942
59241
  init_projects();
57943
59242
  const DEFAULT_BRIEF_PATH = "docs/flywheel-brief.md";
@@ -58225,7 +59524,8 @@ function deleteAutoMergePayload(issueIdParam, deps = {}) {
58225
59524
  ...entry,
58226
59525
  status: "cancelled",
58227
59526
  cancelledAt,
58228
- cancelledBy: "operator"
59527
+ cancelledBy: "operator",
59528
+ remainingActionable: (deps.countRemaining ?? countActionableAutoMerges)(issueId)
58229
59529
  }
58230
59530
  };
58231
59531
  }
@@ -58242,7 +59542,7 @@ async function getFlywheelConversationPayload() {
58242
59542
  };
58243
59543
  }
58244
59544
  async function postFlywheelReportPayload() {
58245
- const { flywheelReportCommand } = await import("./flywheel-DzGtWnJ1.js");
59545
+ const { flywheelReportCommand } = await import("./flywheel-f662Q-y2.js");
58246
59546
  await flywheelReportCommand({ cwd: process.cwd() });
58247
59547
  return {
58248
59548
  status: 200,
@@ -58397,39 +59697,6 @@ const getAutoMergeProblemsRoute = HttpRouter.add("GET", "/api/flywheel/auto-merg
58397
59697
  const getMergeBlockersRoute = HttpRouter.add("GET", "/api/flywheel/merge-blockers", httpHandler(Effect.gen(function* () {
58398
59698
  return jsonResponse(getMergeBlockersPayload());
58399
59699
  })));
58400
- async function defaultGetOrderedIssueIds() {
58401
- const status = await readCurrentFlywheelStatusForDashboard();
58402
- if (!status) return [];
58403
- const { computeMergeQueue } = await import("./flywheel-merge-order-UUcsCDb6.js");
58404
- return (await Effect.runPromise(computeMergeQueue(status.activePipeline, process.cwd()).pipe(Effect.provide(layer)))).map((i) => i.issueId);
58405
- }
58406
- async function defaultMergeOne(issueId) {
58407
- const { triggerMerge } = await import("./merge-ops-CR44oU9-.js");
58408
- const r = await triggerMerge(issueId);
58409
- return r.success ? { ok: true } : {
58410
- ok: false,
58411
- reason: r.error ?? r.message ?? "merge failed"
58412
- };
58413
- }
58414
- /**
58415
- * PAN-1691 "merge next N" / "ship the UAT candidate": merge the first N issues
58416
- * of the conflict-aware merge order, one at a time, stopping at the first
58417
- * failure (the rest would need re-rebasing). Pure-ish + DI for testing.
58418
- */
58419
- async function postFlywheelMergeNextPayload(payload, deps = {}) {
58420
- const body = payload ?? {};
58421
- const n = typeof body.n === "number" && Number.isFinite(body.n) ? Math.floor(body.n) : 0;
58422
- if (n <= 0) return {
58423
- status: 400,
58424
- body: { error: "n must be a positive integer" }
58425
- };
58426
- const issueIds = (await (deps.getOrderedIssueIds ?? defaultGetOrderedIssueIds)()).slice(0, n);
58427
- const { shipMergeBatch } = await import("./merge-batch-ClFn2T-y.js");
58428
- return {
58429
- status: 200,
58430
- body: { outcomes: await shipMergeBatch(issueIds, { merge: deps.merge ?? defaultMergeOne }) }
58431
- };
58432
- }
58433
59700
  const postAutoMergeScheduleRoute = HttpRouter.add("POST", "/api/flywheel/auto-merge/schedule", httpHandler(Effect.gen(function* () {
58434
59701
  const originError = requireTrustedOrigin(yield* HttpServerRequest.HttpServerRequest);
58435
59702
  if (originError) return originError;
@@ -58438,14 +59705,6 @@ const postAutoMergeScheduleRoute = HttpRouter.add("POST", "/api/flywheel/auto-me
58438
59705
  const result = yield* Effect.promise(() => postAutoMergeSchedulePayload(parsed.body));
58439
59706
  return jsonResponse(result.body, { status: result.status });
58440
59707
  })));
58441
- const postFlywheelMergeNextRoute = HttpRouter.add("POST", "/api/flywheel/merge-next", httpHandler(Effect.gen(function* () {
58442
- const originError = requireTrustedOrigin(yield* HttpServerRequest.HttpServerRequest);
58443
- if (originError) return originError;
58444
- const parsed = yield* readUnknownJsonBody;
58445
- if (!parsed.ok) return jsonResponse({ error: parsed.error }, { status: 400 });
58446
- const result = yield* Effect.promise(() => postFlywheelMergeNextPayload(parsed.body));
58447
- return jsonResponse(result.body, { status: result.status });
58448
- })));
58449
59708
  const deleteAutoMergeRoute = HttpRouter.add("DELETE", "/api/flywheel/auto-merge/:id", httpHandler(Effect.gen(function* () {
58450
59709
  const originError = requireTrustedOrigin(yield* HttpServerRequest.HttpServerRequest);
58451
59710
  if (originError) return originError;
@@ -58577,68 +59836,14 @@ const postFlywheelBriefRoute = HttpRouter.add("POST", "/api/flywheel/brief", htt
58577
59836
  });
58578
59837
  });
58579
59838
  })));
58580
- const getFlywheelMergeQueueRoute = HttpRouter.add("GET", "/api/flywheel/merge-queue", httpHandler(Effect.gen(function* () {
58581
- return yield* Effect.promise(async () => {
58582
- const status = await readCurrentFlywheelStatusForDashboard();
58583
- if (!status) return jsonResponse([]);
58584
- const { computeMergeQueue, resolveMergeQueuePrUrl } = await import("./flywheel-merge-order-UUcsCDb6.js");
58585
- return jsonResponse(await Effect.runPromise(computeMergeQueue(status.activePipeline, process.cwd(), { getPrUrl: resolveMergeQueuePrUrl }).pipe(Effect.provide(layer))));
58586
- });
58587
- })));
58588
- /**
58589
- * PAN-1737 UAT batch trains. Generation names contain a slash
58590
- * (`uat/pan-otter-0610`); URL params carry the name WITHOUT the `uat/`
58591
- * prefix and handlers reconstitute it.
58592
- */
58593
- function uatGenerationNameFromParam(param) {
58594
- const decoded = decodeURIComponent(param);
58595
- return decoded.startsWith("uat/") ? decoded : `uat/${decoded}`;
58596
- }
58597
- const getUatGenerationsRoute = HttpRouter.add("GET", "/api/flywheel/uat-generations", httpHandler(Effect.gen(function* () {
58598
- const { getUatGenerationsPayload } = yield* Effect.promise(() => import("./uat-train-B05BWkp-.js"));
58599
- return jsonResponse(yield* Effect.promise(() => getUatGenerationsPayload()));
58600
- })));
58601
59839
  const getUatCandidateRoute = HttpRouter.add("GET", "/api/flywheel/uat-candidate", httpHandler(Effect.gen(function* () {
58602
- const { getUatCandidatePayload } = yield* Effect.promise(() => import("./uat-train-B05BWkp-.js"));
59840
+ const { getUatCandidatePayload } = yield* Effect.promise(() => import("./uat-train-Dewoatps.js"));
58603
59841
  return jsonResponse(yield* Effect.promise(() => getUatCandidatePayload()));
58604
59842
  })));
58605
- const postUatGenerationStackRoute = HttpRouter.add("POST", "/api/flywheel/uat-generations/:name/stack", httpHandler(Effect.gen(function* () {
58606
- const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
58607
- if (authError) return authError;
58608
- const name = uatGenerationNameFromParam((yield* HttpRouter.params)["name"] ?? "");
58609
- const { postUatGenerationStackPayload } = yield* Effect.promise(() => import("./uat-train-B05BWkp-.js"));
58610
- const result = yield* Effect.promise(() => postUatGenerationStackPayload(name));
58611
- if (!result.ok) return jsonResponse({ error: result.error }, { status: result.status });
58612
- return jsonResponse({
58613
- frontendUrl: result.frontendUrl,
58614
- evicted: result.evicted
58615
- });
58616
- })));
58617
- const postUatGenerationPromoteRoute = HttpRouter.add("POST", "/api/flywheel/uat-generations/:name/promote", httpHandler(Effect.gen(function* () {
58618
- const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
58619
- if (authError) return authError;
58620
- const name = uatGenerationNameFromParam((yield* HttpRouter.params)["name"] ?? "");
58621
- const { postUatGenerationPromotePayload } = yield* Effect.promise(() => import("./uat-train-B05BWkp-.js"));
58622
- const { firePostMergeLifecycle } = yield* Effect.promise(() => import("./specialists-CShuVGWT.js"));
58623
- const result = yield* Effect.promise(() => postUatGenerationPromotePayload(name, firePostMergeLifecycle));
58624
- if (!result.success) return jsonResponse(result, { status: result.reason === "not-found" ? 404 : result.reason === "merge-failed" ? 500 : 409 });
58625
- return jsonResponse(result);
58626
- })));
58627
- /**
58628
- * PAN-1737: repurposed from the PAN-1691 one-shot candidate assembly. Now a
58629
- * FORCED reconcile — rebuild the current generation even when the chain
58630
- * already answers the ready set (operator suspects staleness).
58631
- */
58632
- const postFlywheelAssembleUatRoute = HttpRouter.add("POST", "/api/flywheel/assemble-uat", httpHandler(Effect.gen(function* () {
58633
- const authError = rejectUnsafeDashboardMutationRequest(yield* HttpServerRequest.HttpServerRequest);
58634
- if (authError) return authError;
58635
- const { runUatTrainReconcile } = yield* Effect.promise(() => import("./uat-train-B05BWkp-.js"));
58636
- return jsonResponse(yield* Effect.promise(() => runUatTrainReconcile({ force: true })));
58637
- })));
58638
59843
  const getFlywheelStateRoute = HttpRouter.add("GET", "/api/flywheel/state", httpHandler(Effect.gen(function* () {
58639
59844
  return yield* Effect.promise(async () => jsonResponse(await readFlywheelState()));
58640
59845
  })));
58641
- const flywheelRouteLayer = Layer.mergeAll(getFlywheelRunsRoute, getFlywheelRunRoute, getFlywheelConversationRoute, getFlywheelCurrentRoute, getFlywheelStatsRoute, getSubstrateBugWeightsRoute, getFlywheelConfigRoute, postFlywheelConfigRoute, getPendingAutoMergeRoute, getAutoMergeProblemsRoute, getMergeBlockersRoute, getMergeBackendRoute, postAutoMergeScheduleRoute, postFlywheelMergeNextRoute, deleteAutoMergeRoute, getFlywheelMergeQueueRoute, getUatGenerationsRoute, getUatCandidateRoute, postUatGenerationStackRoute, postUatGenerationPromoteRoute, postFlywheelAssembleUatRoute, getFlywheelStateRoute, postFlywheelStatusRoute, postFlywheelStartRoute, postFlywheelPauseRoute, postFlywheelResumeRoute, postFlywheelAbortRoute, postFlywheelReportRoute, postFlywheelReportOpenRoute, getFlywheelBriefRoute, postFlywheelBriefRoute);
59846
+ const flywheelRouteLayer = Layer.mergeAll(getFlywheelRunsRoute, getFlywheelRunRoute, getFlywheelConversationRoute, getFlywheelCurrentRoute, getFlywheelStatsRoute, getSubstrateBugWeightsRoute, getFlywheelConfigRoute, postFlywheelConfigRoute, getPendingAutoMergeRoute, getAutoMergeProblemsRoute, getMergeBlockersRoute, getMergeBackendRoute, postAutoMergeScheduleRoute, deleteAutoMergeRoute, getUatCandidateRoute, getFlywheelStateRoute, postFlywheelStatusRoute, postFlywheelStartRoute, postFlywheelPauseRoute, postFlywheelResumeRoute, postFlywheelAbortRoute, postFlywheelReportRoute, postFlywheelReportOpenRoute, getFlywheelBriefRoute, postFlywheelBriefRoute);
58642
59847
  //#endregion
58643
59848
  //#region routes/orders.ts
58644
59849
  init_projects();
@@ -58827,7 +60032,7 @@ function previewBrief(book) {
58827
60032
  return `${lines.join("\n")}\n`;
58828
60033
  }
58829
60034
  async function defaultStartOrderBook(book) {
58830
- const { startFlywheelRun } = await import("./flywheel-DzGtWnJ1.js");
60035
+ const { startFlywheelRun } = await import("./flywheel-f662Q-y2.js");
58831
60036
  return startFlywheelRun({
58832
60037
  cwd: process.cwd(),
58833
60038
  orders: book.id
@@ -59505,6 +60710,7 @@ function agentStateToDbAgent(state) {
59505
60710
  lastFailureReason: state.lastFailureReason ?? null,
59506
60711
  lastFailureNextRetryAt: state.lastFailureNextRetryAt ?? null,
59507
60712
  flywheelRunId: state.flywheelRunId ?? null,
60713
+ startedBy: state.startedBy ?? null,
59508
60714
  roleRunHead: state.roleRunHead ?? null,
59509
60715
  reviewSubRole: state.reviewSubRole ?? null,
59510
60716
  reviewRunId: state.reviewRunId ?? null,
@@ -59581,6 +60787,7 @@ const COLUMN_MAP = {
59581
60787
  lastFailureReason: "last_failure_reason",
59582
60788
  lastFailureNextRetryAt: "last_failure_next_retry_at",
59583
60789
  flywheelRunId: "flywheel_run_id",
60790
+ startedBy: "started_by",
59584
60791
  roleRunHead: "role_run_head",
59585
60792
  reviewSubRole: "review_sub_role",
59586
60793
  reviewRunId: "review_run_id",
@@ -59653,7 +60860,7 @@ function backfillAgentsFromStateJsonSync(db, options) {
59653
60860
  const readHarnessModel = options?.readHarnessModel ?? readAgentHarnessModelRecordSync;
59654
60861
  let processed = 0;
59655
60862
  let skipped = 0;
59656
- let markedStopped = 0;
60863
+ const markedStoppedIds = [];
59657
60864
  let entries = [];
59658
60865
  try {
59659
60866
  entries = readdirSync$1(agentsDir);
@@ -59661,7 +60868,8 @@ function backfillAgentsFromStateJsonSync(db, options) {
59661
60868
  return {
59662
60869
  processed,
59663
60870
  skipped,
59664
- markedStopped
60871
+ markedStopped: 0,
60872
+ markedStoppedIds
59665
60873
  };
59666
60874
  }
59667
60875
  const columns = Object.values(COLUMN_MAP);
@@ -59690,7 +60898,10 @@ function backfillAgentsFromStateJsonSync(db, options) {
59690
60898
  continue;
59691
60899
  }
59692
60900
  const reconciled = reconcileAgentStatus(state, liveSessions);
59693
- if (reconciled.status === "stopped" && state.status !== "stopped") markedStopped++;
60901
+ const reconciledStop = reconciled.status === "stopped" && state.status !== "stopped" ? {
60902
+ id: state.id,
60903
+ previousStatus: state.status
60904
+ } : void 0;
59694
60905
  let effective = reconciled;
59695
60906
  if (reconciled.issueId) {
59696
60907
  const rec = readHarnessModel(reconciled.issueId);
@@ -59702,14 +60913,17 @@ function backfillAgentsFromStateJsonSync(db, options) {
59702
60913
  }
59703
60914
  const row = agentStateToDbAgent(effective);
59704
60915
  upsert.run(buildNamedParams(row));
60916
+ if (reconciledStop) markedStoppedIds.push(reconciledStop);
59705
60917
  processed++;
59706
60918
  if (options?.verbose) console.log(`[backfill] ${row.id} -> ${row.status}`);
59707
60919
  }
59708
60920
  })();
60921
+ for (const { id, previousStatus } of markedStoppedIds) logAgentLifecycleSync(id, `status changed: ${previousStatus} → stopped (boot backfill reconcile: no live tmux session)`);
59709
60922
  return {
59710
60923
  processed,
59711
60924
  skipped,
59712
- markedStopped
60925
+ markedStopped: markedStoppedIds.length,
60926
+ markedStoppedIds
59713
60927
  };
59714
60928
  }
59715
60929
  function reconcileAgentStatus(state, liveSessions) {
@@ -59949,6 +61163,8 @@ function initSchema(db) {
59949
61163
  review_spawned_at TEXT,
59950
61164
  -- PAN-1765: timestamp when conflict resolution was dispatched
59951
61165
  conflict_resolution_dispatched_at TEXT,
61166
+ -- PAN-3154: main-head SHA/paths that first made this branch conflict (JSON)
61167
+ conflicts_since TEXT,
59952
61168
  -- PAN-699: number of test-agent dispatch retries (circuit breaker)
59953
61169
  test_retry_count INTEGER DEFAULT 0,
59954
61170
  -- PAN-794: parallel-review re-dispatch retry counter (scoped to current recovery cycle)
@@ -59966,7 +61182,9 @@ function initSchema(db) {
59966
61182
  -- PAN-938: current merge pipeline step
59967
61183
  merge_step TEXT,
59968
61184
  -- PAN-1691: per-issue merge-train routing key (NULL=project default, 1=auto-merge, 0=hold-for-UAT)
59969
- auto_merge INTEGER
61185
+ auto_merge INTEGER,
61186
+ -- PAN-3151: JSON array of review cycle history entries for convergence detection
61187
+ review_cycle_history TEXT
59970
61188
  );
59971
61189
 
59972
61190
  CREATE INDEX IF NOT EXISTS idx_review_status_updated
@@ -60152,7 +61370,7 @@ function initSchema(db) {
60152
61370
  last_failure_at TEXT,
60153
61371
  last_failure_reason TEXT,
60154
61372
  last_failure_next_retry_at TEXT,
60155
- flywheel_run_id TEXT,
61373
+ flywheel_run_id TEXT, started_by TEXT,
60156
61374
  role_run_head TEXT,
60157
61375
  review_sub_role TEXT,
60158
61376
  review_run_id TEXT,
@@ -60471,7 +61689,7 @@ function initSchema(db) {
60471
61689
  CREATE INDEX IF NOT EXISTS idx_backlog_sequence_project_rank
60472
61690
  ON backlog_sequence(project_key, rank);
60473
61691
  `);
60474
- db.pragma(`user_version = 61`);
61692
+ db.pragma(`user_version = 63`);
60475
61693
  }
60476
61694
  /**
60477
61695
  * Run schema migrations if the database version is older than SCHEMA_VERSION.
@@ -60479,12 +61697,18 @@ function initSchema(db) {
60479
61697
  */
60480
61698
  function runMigrations(db, dbPath) {
60481
61699
  const currentVersion = db.pragma("user_version", { simple: true });
60482
- if (currentVersion === 61) tryIdempotentDdl(db, 61, "ALTER TABLE flywheel_substrate_bugs ADD COLUMN affected_criteria TEXT");
60483
- if (currentVersion >= 61) return;
60484
61700
  if (currentVersion === 0) {
60485
61701
  initSchema(db);
60486
61702
  return;
60487
61703
  }
61704
+ if (currentVersion === 63) {
61705
+ tryIdempotentDdl(db, 61, "ALTER TABLE flywheel_substrate_bugs ADD COLUMN affected_criteria TEXT");
61706
+ tryIdempotentDdl(db, 61, "ALTER TABLE agents ADD COLUMN started_by TEXT");
61707
+ tryIdempotentDdl(db, 62, "ALTER TABLE review_status ADD COLUMN review_cycle_history TEXT");
61708
+ tryIdempotentDdl(db, 62, "ALTER TABLE review_status ADD COLUMN conflicts_since TEXT");
61709
+ return;
61710
+ }
61711
+ if (currentVersion > 63) return;
60488
61712
  if (currentVersion < 2) db.exec(`
60489
61713
  DELETE FROM status_history
60490
61714
  WHERE id NOT IN (
@@ -61138,8 +62362,17 @@ function runMigrations(db, dbPath) {
61138
62362
  tryIdempotentDdl(db, 60, `ALTER TABLE review_status ADD COLUMN release_status TEXT`);
61139
62363
  tryIdempotentDdl(db, 60, `ALTER TABLE review_status ADD COLUMN release_notes TEXT`);
61140
62364
  }
61141
- if (currentVersion < 61) tryIdempotentDdl(db, 61, "ALTER TABLE flywheel_substrate_bugs ADD COLUMN affected_criteria TEXT");
61142
- db.pragma(`user_version = 61`);
62365
+ tryIdempotentDdl(db, 61, "ALTER TABLE flywheel_substrate_bugs ADD COLUMN affected_criteria TEXT");
62366
+ tryIdempotentDdl(db, 61, "ALTER TABLE agents ADD COLUMN started_by TEXT");
62367
+ if (currentVersion < 62) {
62368
+ tryIdempotentDdl(db, 62, "ALTER TABLE review_status ADD COLUMN review_cycle_history TEXT");
62369
+ tryIdempotentDdl(db, 62, "ALTER TABLE review_status ADD COLUMN conflicts_since TEXT");
62370
+ }
62371
+ if (currentVersion < 63) {
62372
+ tryIdempotentDdl(db, 63, "ALTER TABLE review_status ADD COLUMN review_cycle_history TEXT");
62373
+ tryIdempotentDdl(db, 63, "ALTER TABLE review_status ADD COLUMN conflicts_since TEXT");
62374
+ }
62375
+ db.pragma(`user_version = 63`);
61143
62376
  }
61144
62377
  //#endregion
61145
62378
  //#region ../../lib/database/index.ts
@@ -61985,7 +63218,8 @@ const staticRouteLayer = HttpRouter.add("GET", "*", Effect.gen(function* () {
61985
63218
  const projectRoot = selfPath.includes("/dist/") ? pathService.resolve(selfPath, "..", "..") : pathService.resolve(selfPath, "..", "..", "..");
61986
63219
  const staticDir = process.env["OVERDECK_FRONTEND_DIR"] ?? pathService.resolve(projectRoot, "dist", "dashboard", "public");
61987
63220
  const staticRoot = pathService.resolve(staticDir);
61988
- const rawRelative = (url.value.pathname === "/" ? "/index.html" : url.value.pathname).replace(/^[/\\]+/, "");
63221
+ const urlPath = url.value.pathname === "/" ? "/index.html" : url.value.pathname;
63222
+ const rawRelative = urlPath.replace(/^[/\\]+/, "");
61989
63223
  const normalized = pathService.normalize(rawRelative).replace(/^[/\\]+/, "");
61990
63224
  if (normalized.length === 0 || normalized.startsWith("..") || normalized.includes("\0")) return HttpServerResponse.text("Invalid path", { status: 400 });
61991
63225
  const sep = pathService.sep;
@@ -61998,6 +63232,7 @@ const staticRouteLayer = HttpRouter.add("GET", "*", Effect.gen(function* () {
61998
63232
  }
61999
63233
  const fileInfo = yield* fileSystem.stat(filePath).pipe(Effect.catch(() => Effect.succeed(null)));
62000
63234
  if (!fileInfo || fileInfo.type !== "File") {
63235
+ if (pathService.extname(urlPath)) return HttpServerResponse.text("Not Found", { status: 404 });
62001
63236
  const indexPath = pathService.resolve(staticRoot, "index.html");
62002
63237
  const indexInfo = yield* fileSystem.stat(indexPath).pipe(Effect.catch(() => Effect.succeed(null)));
62003
63238
  if (!indexInfo || indexInfo.type !== "File") return HttpServerResponse.text("Not Found", { status: 404 });
@@ -62007,7 +63242,7 @@ const staticRouteLayer = HttpRouter.add("GET", "*", Effect.gen(function* () {
62007
63242
  if (filePath.endsWith("index.html")) return HttpServerResponse.setHeader(res, "Cache-Control", "no-cache, no-store, must-revalidate");
62008
63243
  return res;
62009
63244
  }));
62010
- const makeRoutesLayer = Layer.mergeAll(healthRouteLayer, dashboardSessionPreflightRouteLayer, dashboardSessionRouteLayer, websocketRpcRouteLayer, issuesRouteLayer, pipelineMembershipRouteLayer, agentsRouteLayer, workspacesRouteLayer, specialistsRouteLayer, costsRouteLayer, cloisterRouteLayer, resourcesRouteLayer, commandDeckRouteLayer, remoteRouteLayer, settingsRouteLayer, voiceRouteLayer, autopresoRouteLayer, metricsRouteLayer, miscRouteLayer, paletteRouteLayer, conversationsRouteLayer, eventsRouteLayer, showRouteLayer, projectsRouteLayer, contextRouteLayer, adminRouteLayer, prereqsRouteLayer, cliproxyRouteLayer, ttsRouteLayer, webhooksRouteLayer, hooksRouteLayer, diffsRouteLayer, codexAuthRouteLayer, terminalsRouteLayer, discoveredSessionsRouteLayer, flywheelRouteLayer, ordersRouteLayer, artifactsRouteLayer, featureRegistryRouteLayer, fsRouteLayer, tieredCalloutsRouteLayer, backlogRouteLayer, internalEventsRouteLayer, knowledgeViewerRouteLayer, staticRouteLayer);
63245
+ const makeRoutesLayer = Layer.mergeAll(healthRouteLayer, dashboardSessionPreflightRouteLayer, dashboardSessionRouteLayer, websocketRpcRouteLayer, issuesRouteLayer, pipelineMembershipRouteLayer, agentsRouteLayer, workspacesRouteLayer, specialistsRouteLayer, costsRouteLayer, cloisterRouteLayer, resourcesRouteLayer, commandDeckRouteLayer, remoteRouteLayer, settingsRouteLayer, voiceRouteLayer, autopresoRouteLayer, metricsRouteLayer, miscRouteLayer, paletteRouteLayer, conversationsRouteLayer, eventsRouteLayer, showRouteLayer, projectsRouteLayer, projectsMergeTrainRouteLayer, mergeTrainRouteLayer, contextRouteLayer, commandsRouteLayer, adminRouteLayer, prereqsRouteLayer, cliproxyRouteLayer, ttsRouteLayer, webhooksRouteLayer, hooksRouteLayer, diffsRouteLayer, codexAuthRouteLayer, linearMcpAuthRouteLayer, terminalsRouteLayer, discoveredSessionsRouteLayer, flywheelRouteLayer, ordersRouteLayer, artifactsRouteLayer, featureRegistryRouteLayer, fsRouteLayer, tieredCalloutsRouteLayer, backlogRouteLayer, internalEventsRouteLayer, knowledgeViewerRouteLayer, staticRouteLayer);
62011
63246
  const OverdeckAgentsResolverLive = AgentsResolverLive.pipe(Layer.provide(Layer.mergeAll(DbLive, TmuxLive)));
62012
63247
  const OverdeckCostResolverLive = CostResolverLive.pipe(Layer.provide(DbLive));
62013
63248
  const ReadModelWithOverdeckLive = ReadModelServiceLive.pipe(Layer.provide(OverdeckAgentsResolverLive));
@@ -62060,6 +63295,7 @@ const runServer = Layer.launch(makeServerLayer);
62060
63295
  init_agents$1();
62061
63296
  init_review_status();
62062
63297
  init_concurrency$1();
63298
+ init_runtime_census();
62063
63299
  init_event_store();
62064
63300
  init_activity_logger();
62065
63301
  init_read_model();
@@ -62078,7 +63314,43 @@ function buildAwaitingInputActivityMessage(agentId, issueId, kinds) {
62078
63314
  const kindList = kinds.length > 0 ? kinds.join(", ") : "input";
62079
63315
  return issueId ? `${agentId} on ${issueId} is waiting for ${kindList}` : `${agentId} is waiting for ${kindList}`;
62080
63316
  }
63317
+ function shouldSkipEnrichmentCycle(census) {
63318
+ return !census.tmuxAvailable;
63319
+ }
63320
+ function hasReapablePendingInput(enrichment) {
63321
+ return enrichment.pendingInputCount > 0 || enrichment.pendingAskUserQuestion != null || enrichment.pendingProposedPlan != null;
63322
+ }
63323
+ function buildPendingReapEvent(agentId, previous) {
63324
+ return {
63325
+ type: "agent.enrichment_changed",
63326
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
63327
+ payload: {
63328
+ agentId,
63329
+ role: previous.role,
63330
+ hasPendingQuestion: false,
63331
+ pendingQuestionCount: 0,
63332
+ pendingQuestionPrompt: void 0,
63333
+ pendingQuestionReason: void 0,
63334
+ pendingInputCount: 0,
63335
+ pendingInputKinds: [],
63336
+ pendingAskUserQuestion: void 0,
63337
+ pendingProposedPlan: void 0,
63338
+ resolution: previous.resolution,
63339
+ resolutionCount: previous.resolutionCount
63340
+ }
63341
+ };
63342
+ }
63343
+ function buildExpiredQuestionActivityMessage(agentId, issueId) {
63344
+ return `${issueId ? `${agentId} on ${issueId}` : agentId} stopped with its question unanswered — the question has expired and is no longer actionable`;
63345
+ }
62081
63346
  async function pollOnce(state) {
63347
+ let census;
63348
+ try {
63349
+ census = await getRuntimeCensus();
63350
+ } catch {
63351
+ return;
63352
+ }
63353
+ if (shouldSkipEnrichmentCycle(census)) return;
62082
63354
  let runningAgents;
62083
63355
  try {
62084
63356
  runningAgents = await Effect.runPromise(listRunningAgents());
@@ -62200,7 +63472,20 @@ async function pollOnce(state) {
62200
63472
  } catch {}
62201
63473
  })), 4));
62202
63474
  const activeIds = new Set(activeAgents.map((a) => a.id));
62203
- for (const id of state.lastEnrichment.keys()) if (!activeIds.has(id)) {
63475
+ for (const [id, previousEnrichment] of state.lastEnrichment) if (!activeIds.has(id)) {
63476
+ if (hasReapablePendingInput(previousEnrichment)) {
63477
+ try {
63478
+ await eventStore.appendAsync(buildPendingReapEvent(id, previousEnrichment));
63479
+ } catch {}
63480
+ const agentRecord = runningAgents.find((agent) => agent.id === id);
63481
+ const issueId = agentRecord?.issueId;
63482
+ emitActivityEntrySync({
63483
+ source: toRole(agentRecord?.role) ?? "work",
63484
+ level: "info",
63485
+ message: buildExpiredQuestionActivityMessage(id, issueId),
63486
+ issueId
63487
+ });
63488
+ }
62204
63489
  state.lastEnrichment.delete(id);
62205
63490
  state.lastScan.delete(id);
62206
63491
  }
@@ -62208,7 +63493,7 @@ async function pollOnce(state) {
62208
63493
  /** Poll interval: 10 seconds (was 3s). The enrichment data changes slowly —
62209
63494
  * pending questions, resolution state, phase — none of these need sub-second
62210
63495
  * latency. 10s eliminates ~70% of poller I/O without any user-visible lag. */
62211
- const POLL_INTERVAL_MS$3 = 1e4;
63496
+ const POLL_INTERVAL_MS$4 = 1e4;
62212
63497
  const serviceState$2 = {
62213
63498
  timer: null,
62214
63499
  lastEnrichment: /* @__PURE__ */ new Map(),
@@ -62220,7 +63505,7 @@ function startAgentEnrichmentService() {
62220
63505
  if (serviceState$2.timer !== null) return;
62221
63506
  serviceState$2.timer = setInterval(() => {
62222
63507
  pollOnce(serviceState$2).catch(() => {});
62223
- }, POLL_INTERVAL_MS$3);
63508
+ }, POLL_INTERVAL_MS$4);
62224
63509
  }
62225
63510
  function stopAgentEnrichmentService() {
62226
63511
  if (serviceState$2.timer !== null) {
@@ -62248,7 +63533,7 @@ function stopAgentEnrichmentService() {
62248
63533
  * Bounded + cheap: each issue has a throttle. Ready/no-blocker rows use the PAN-1620
62249
63534
  * 3-minute cadence; already-blocked mergeability rows use a 10-minute cadence.
62250
63535
  */
62251
- const POLL_INTERVAL_MS$2 = 6e4;
63536
+ const POLL_INTERVAL_MS$3 = 6e4;
62252
63537
  /** Re-check each ready-to-merge PR at most this often, to bound `gh` calls. */
62253
63538
  const RECHECK_INTERVAL_MS = 18e4;
62254
63539
  /** Re-check already-flagged mergeability blockers less often; they are not mergeable yet. */
@@ -62280,7 +63565,7 @@ function parsePrUrl$1(url) {
62280
63565
  function hasMergeabilityBlocker(blockers) {
62281
63566
  return blockers?.some((blocker) => MERGEABILITY_BLOCKERS.has(blocker.type)) ?? false;
62282
63567
  }
62283
- async function reconcileOnce(state) {
63568
+ async function reconcileOnce$1(state) {
62284
63569
  const candidates = await Effect.runPromise(getMergeBlockerReconcileCandidates());
62285
63570
  const now = Date.now();
62286
63571
  for (const candidate of candidates) {
@@ -62316,8 +63601,8 @@ async function reconcileOnce(state) {
62316
63601
  function startMergeBlockerReconcileService() {
62317
63602
  if (serviceState$1.timer !== null) return;
62318
63603
  serviceState$1.timer = setInterval(() => {
62319
- reconcileOnce(serviceState$1).catch(() => {});
62320
- }, POLL_INTERVAL_MS$2);
63604
+ reconcileOnce$1(serviceState$1).catch(() => {});
63605
+ }, POLL_INTERVAL_MS$3);
62321
63606
  serviceState$1.timer.unref?.();
62322
63607
  }
62323
63608
  function enqueueRefresh(state, task) {
@@ -62341,6 +63626,51 @@ function drainRefreshQueue(state) {
62341
63626
  }
62342
63627
  }
62343
63628
  //#endregion
63629
+ //#region services/review-status-reconcile-service.ts
63630
+ init_review_status();
63631
+ init_event_store();
63632
+ const POLL_INTERVAL_MS$2 = 6e4;
63633
+ let reconcileTimer = null;
63634
+ function reviewStatusPayload(event) {
63635
+ if (!event.payload || typeof event.payload !== "object") return null;
63636
+ const payload = event.payload;
63637
+ if (typeof payload.issueId !== "string" || !payload.status || typeof payload.status !== "object") return null;
63638
+ return payload;
63639
+ }
63640
+ async function reconcileOnce() {
63641
+ const cached = loadReviewStatuses();
63642
+ const canonicalByIssue = /* @__PURE__ */ new Map();
63643
+ for (const issueId of Object.keys(cached)) {
63644
+ const canonical = getReviewStatusSync(issueId);
63645
+ if (!canonical) continue;
63646
+ canonicalByIssue.set(issueId.toUpperCase(), canonical);
63647
+ }
63648
+ const store = getEventStore();
63649
+ const latestByIssue = /* @__PURE__ */ new Map();
63650
+ for (const event of store.queryLatestPerIssue("review.status_changed")) {
63651
+ const payload = reviewStatusPayload(event);
63652
+ if (payload) latestByIssue.set(payload.issueId.toUpperCase(), event);
63653
+ }
63654
+ for (const [issueId, canonical] of canonicalByIssue) {
63655
+ const latest = latestByIssue.get(issueId);
63656
+ const latestPayload = latest ? reviewStatusPayload(latest) : null;
63657
+ const lastUpdatedAt = latestPayload?.status.updatedAt ?? "";
63658
+ if (lastUpdatedAt > canonical.updatedAt) continue;
63659
+ if (lastUpdatedAt === canonical.updatedAt && latestPayload && sameCanonicalReviewStatus(latestPayload.status, canonical)) continue;
63660
+ console.log(`[review-status-reconcile] re-emitting status for ${issueId} (canonical differs from last event — healing lost status_changed)`);
63661
+ emitReviewStatusChanged((event) => store.append(event), issueId, canonical);
63662
+ }
63663
+ }
63664
+ function startReviewStatusReconcileService() {
63665
+ if (getDashboardIdentity().mode !== "primary") return false;
63666
+ if (reconcileTimer !== null) return true;
63667
+ reconcileTimer = setInterval(() => {
63668
+ reconcileOnce().catch(() => {});
63669
+ }, POLL_INTERVAL_MS$2);
63670
+ reconcileTimer.unref?.();
63671
+ return true;
63672
+ }
63673
+ //#endregion
62344
63674
  //#region services/project-resource-refresh-queue.ts
62345
63675
  init_projects();
62346
63676
  const PROJECT_RESOURCE_CONVERGENCE_INTERVAL_MS = 5 * 6e4;
@@ -64028,14 +65358,42 @@ const RESTART_MARKER = join$1(homedir(), ".overdeck", "dashboard-restarting.json
64028
65358
  * loads new content-hashed chunk filenames (no ERR_MODULE_NOT_FOUND after rebuild).
64029
65359
  */
64030
65360
  async function defaultLifecycleRunner(pending) {
64031
- const { postMergeLifecycle, notifyTldrDaemon } = await import("./merge-agent-CKkgIBPB.js");
65361
+ const { postMergeLifecycle, notifyTldrDaemon } = await import("./merge-agent-DQTXZk8z.js");
64032
65362
  await postMergeLifecycle(pending.issueId, pending.projectPath, pending.sourceBranch, { skipDeploy: true });
64033
65363
  if (pending.sourceBranch) await notifyTldrDaemon(pending.projectPath, pending.sourceBranch);
64034
65364
  }
65365
+ async function runClaimedPendingLifecycle(claim, pending, runner, lifecycleDelayMs, startTime) {
65366
+ let succeeded = false;
65367
+ try {
65368
+ if (lifecycleDelayMs > 0) await new Promise((resolve) => setTimeout(resolve, lifecycleDelayMs));
65369
+ await runner(pending);
65370
+ succeeded = true;
65371
+ emitDashboardLifecycleSync("completed", {
65372
+ reason: pending.reason ?? "post-merge",
65373
+ issueId: pending.issueId,
65374
+ durationMs: Date.now() - startTime
65375
+ });
65376
+ } catch (error) {
65377
+ const message = error instanceof Error ? error.message : String(error);
65378
+ console.error(`[overdeck] Post-merge lifecycle failed for ${pending.issueId}: ${message}`);
65379
+ emitDashboardLifecycleSync("failed", {
65380
+ reason: pending.reason ?? "post-merge",
65381
+ issueId: pending.issueId,
65382
+ error: message
65383
+ });
65384
+ } finally {
65385
+ try {
65386
+ await settlePendingLifecycleClaim(claim, pending.issueId, succeeded);
65387
+ } catch (error) {
65388
+ const message = error instanceof Error ? error.message : String(error);
65389
+ console.warn(`[overdeck] Failed to settle pending lifecycle claim for ${pending.issueId}: ${message}`);
65390
+ }
65391
+ }
65392
+ }
64035
65393
  /**
64036
65394
  * Check for and process a pending post-merge lifecycle file.
64037
- * Reads, validates, deletes the file, then schedules lifecycle execution.
64038
- * Safe to call unconditionally on server startup — no-op if file absent.
65395
+ * Atomically claims the file before execution so dashboard and Deacon cannot
65396
+ * run the same handoff. The durable mergeStep marker owns retries after failure.
64039
65397
  *
64040
65398
  * Also checks for a RESTART_MARKER file (written by deploy script before killing
64041
65399
  * the old server) and emits dashboard.lifecycle_started if found.
@@ -64061,35 +65419,25 @@ async function processPendingLifecycle(options) {
64061
65419
  } catch (err) {
64062
65420
  console.warn(`[overdeck] Failed to process restart marker: ${err.message}`);
64063
65421
  }
64064
- if (!existsSync(pendingFile)) return;
64065
65422
  const startTime = Date.now();
64066
65423
  try {
64067
- const raw = await readFile$1(pendingFile, "utf-8");
64068
- await unlink$1(pendingFile);
64069
- const pending = JSON.parse(raw);
65424
+ const claim = await claimPendingLifecycleFile(pendingFile);
65425
+ if (!claim) return;
65426
+ let pending;
65427
+ try {
65428
+ pending = JSON.parse(claim.raw);
65429
+ } catch (error) {
65430
+ await claim.discard();
65431
+ throw error;
65432
+ }
64070
65433
  const age = (options?.now ?? Date.now()) - (pending.timestamp ?? 0);
64071
65434
  if (age > staleThresholdMs) {
65435
+ await claim.discard();
64072
65436
  console.warn(`[overdeck] Ignoring stale pending-post-merge.json (age: ${Math.round(age / 6e4)}min) for ${pending.issueId}`);
64073
65437
  return;
64074
65438
  }
64075
- console.log(`[overdeck] Found pending post-merge lifecycle for ${pending.issueId} — scheduling in ${lifecycleDelayMs}ms`);
64076
- setTimeout(async () => {
64077
- try {
64078
- await runner(pending);
64079
- emitDashboardLifecycleSync("completed", {
64080
- reason: pending.reason ?? "post-merge",
64081
- issueId: pending.issueId,
64082
- durationMs: Date.now() - startTime
64083
- });
64084
- } catch (err) {
64085
- console.error(`[overdeck] Post-merge lifecycle failed for ${pending.issueId}: ${err.message}`);
64086
- emitDashboardLifecycleSync("failed", {
64087
- reason: pending.reason ?? "post-merge",
64088
- issueId: pending.issueId,
64089
- error: err.message
64090
- });
64091
- }
64092
- }, lifecycleDelayMs);
65439
+ console.log(`[overdeck] Found pending post-merge lifecycle for ${pending.issueId} — running in ${lifecycleDelayMs}ms`);
65440
+ runClaimedPendingLifecycle(claim, pending, runner, lifecycleDelayMs, startTime);
64093
65441
  } catch (err) {
64094
65442
  console.warn(`[overdeck] Failed to process pending-post-merge.json: ${err.message}`);
64095
65443
  }
@@ -64337,6 +65685,8 @@ const cleanupClosedIssueAgentDirectories = (options) => Effect.tryPromise({
64337
65685
  //#region services/auto-merge-executor.ts
64338
65686
  init_activity_logger();
64339
65687
  init_merge_sync();
65688
+ init_deploy_queue();
65689
+ init_review_status();
64340
65690
  const AUTO_MERGE_EXECUTOR_INTERVAL_MS = 3e4;
64341
65691
  const REQUEUE_BACKOFF_MS = 6e4;
64342
65692
  let timer$1 = null;
@@ -64349,7 +65699,7 @@ function failureReason(result) {
64349
65699
  return result.error ?? result.message ?? `merge returned status ${result.statusCode ?? "unknown"}`;
64350
65700
  }
64351
65701
  async function defaultMergeIssue(issueId) {
64352
- const { triggerMerge } = await import("./merge-ops-CR44oU9-.js");
65702
+ const { triggerMerge } = await import("./merge-ops-1abz6O_P.js");
64353
65703
  return triggerMerge(issueId);
64354
65704
  }
64355
65705
  function defaultAnnounceFailure(issueId, reason) {
@@ -64371,6 +65721,10 @@ async function tickAutoMergeExecutor(deps = {}) {
64371
65721
  log("[auto-merge] flywheel paused, skipping tick");
64372
65722
  return;
64373
65723
  }
65724
+ if (await (deps.hasPendingDeploy ?? (async () => await readPendingDeploy() !== null))()) {
65725
+ log(`[auto-merge] dashboard deploy queued, deferring ${entries.length} merge(s) before preparation`);
65726
+ return;
65727
+ }
64374
65728
  for (const entry of entries) {
64375
65729
  if (isPaused()) {
64376
65730
  log("[auto-merge] flywheel paused, skipping tick");
@@ -64397,6 +65751,25 @@ async function tickAutoMergeExecutor(deps = {}) {
64397
65751
  continue;
64398
65752
  }
64399
65753
  const reason = failureReason(result);
65754
+ if (result.deferred) {
65755
+ const retryAt = new Date(nowDate.getTime() + REQUEUE_BACKOFF_MS).toISOString();
65756
+ log((deps.requeueToPending ?? requeueToPending)(entry.id, retryAt) ? `[auto-merge] merge deferred for ${entry.issueId}; requeued without consuming retry budget for ${retryAt}` : `[auto-merge] failed to requeue deferred merge for ${entry.issueId} (#${entry.id})`);
65757
+ continue;
65758
+ }
65759
+ if (result.retryable) {
65760
+ const retryCount = (deps.getMergeRetryCount ?? ((issueId) => getReviewStatusSync(issueId)?.mergeRetryCount ?? 0))(entry.issueId);
65761
+ if (retryCount >= 3) {
65762
+ const blockedReason = `Auto-merge for ${entry.issueId} blocked: ${reason} (retried ${retryCount} times — fix the underlying cause and re-schedule)`;
65763
+ if ((deps.markMergingBlocked ?? markMergingBlocked)(entry.id, blockedReason)) (deps.announceFailure ?? defaultAnnounceFailure)(entry.issueId, blockedReason);
65764
+ else log(`[auto-merge] lost circuit-breaker block race for ${entry.issueId} (#${entry.id}), skipping announcement`);
65765
+ } else {
65766
+ const nextRetryCount = retryCount + 1;
65767
+ (deps.setMergeRetryCount ?? ((issueId, count) => setReviewStatusSync(issueId, { mergeRetryCount: count })))(entry.issueId, nextRetryCount);
65768
+ const retryAt = new Date(nowDate.getTime() + REQUEUE_BACKOFF_MS).toISOString();
65769
+ log((deps.requeueToPending ?? requeueToPending)(entry.id, retryAt) ? `[auto-merge] retryable merge failure for ${entry.issueId}; requeued attempt ${nextRetryCount}/3 for ${retryAt}` : `[auto-merge] failed to requeue retryable merge for ${entry.issueId} (#${entry.id})`);
65770
+ }
65771
+ continue;
65772
+ }
64400
65773
  (deps.markFailed ?? markFailed)(entry.id, reason);
64401
65774
  (deps.announceFailure ?? defaultAnnounceFailure)(entry.issueId, reason);
64402
65775
  } catch (error) {
@@ -64561,17 +65934,46 @@ function createOverdeckDatabase(options = {}) {
64561
65934
  try {
64562
65935
  applyMigration(db, migrationPath);
64563
65936
  const tables = userTableNames(db);
64564
- if (tables.length !== 32) throw new Error(`Expected 32 overdeck tables, found ${tables.length}: ${tables.join(", ")}`);
65937
+ if (tables.length !== 34) throw new Error(`Expected 34 overdeck tables, found ${tables.length}: ${tables.join(", ")}`);
64565
65938
  assertEmptyTables(db, tables);
64566
65939
  } finally {
64567
65940
  db.close();
64568
65941
  }
64569
65942
  return {
64570
65943
  dbPath,
64571
- tableCount: 32
65944
+ tableCount: 34
64572
65945
  };
64573
65946
  }
64574
65947
  //#endregion
65948
+ //#region telemetry.ts
65949
+ init_agents$1();
65950
+ init_projects();
65951
+ init_service();
65952
+ const defaultDeps = {
65953
+ analytics: getAnalyticsService("server"),
65954
+ listProjects: listProjectsSync,
65955
+ listAgents: () => Effect.runPromise(listRunningAgents())
65956
+ };
65957
+ function bucketServerCount(value) {
65958
+ if (value <= 0) return "0";
65959
+ if (value <= 2) return "1-2";
65960
+ if (value <= 5) return "3-5";
65961
+ if (value <= 10) return "6-10";
65962
+ return "11+";
65963
+ }
65964
+ async function captureServerBootTelemetry(deps = defaultDeps) {
65965
+ try {
65966
+ const [projects, agents] = await Promise.all([Promise.resolve(deps.listProjects()), deps.listAgents()]);
65967
+ deps.analytics.capture("server_boot", {
65968
+ project_count: bucketServerCount(projects.length),
65969
+ active_agent_count: bucketServerCount(agents.filter((agent) => agent.tmuxActive).length)
65970
+ });
65971
+ } catch {}
65972
+ }
65973
+ function startServerBootTelemetry(deps = defaultDeps) {
65974
+ captureServerBootTelemetry(deps);
65975
+ }
65976
+ //#endregion
64575
65977
  //#region ../../lib/state-auto-migrate.ts
64576
65978
  init_projects();
64577
65979
  init_state_home();
@@ -64596,7 +65998,7 @@ const defaultDependencies = {
64596
65998
  inspect: inspectStateMigration,
64597
65999
  ensureWorktree: ensureStateWorktree,
64598
66000
  migrate: async (projectKey, project, signal) => {
64599
- const { migrateProjectState } = await import("./state-migrate-BGg2ZaOt.js");
66001
+ const { migrateProjectState } = await import("./state-migrate-2u8wIpoL.js");
64600
66002
  await migrateProjectState(projectKey, { signal }, project);
64601
66003
  },
64602
66004
  clearCache: clearStateMigrationCache,
@@ -64728,11 +66130,12 @@ init_internal_token();
64728
66130
  init_review_status();
64729
66131
  init_event_store();
64730
66132
  init_activity_logger();
64731
- init_config$1();
66133
+ init_config$2();
64732
66134
  init_agents$1();
64733
66135
  init_paths();
64734
66136
  init_tmux();
64735
66137
  init_fts_db();
66138
+ init_control_settings();
64736
66139
  init_paths$1();
64737
66140
  init_auto_commit();
64738
66141
  init_projects();
@@ -64763,6 +66166,7 @@ if (typeof Bun !== "undefined") {
64763
66166
  runMain(main);
64764
66167
  }
64765
66168
  await whenDashboardListening();
66169
+ startServerBootTelemetry();
64766
66170
  startEventLoopMonitor();
64767
66171
  console.log("[overdeck] Event loop delay monitor started");
64768
66172
  warnIfAutonomousMergeBackendUnavailable();
@@ -64798,13 +66202,15 @@ startAgentOutputService();
64798
66202
  console.log("[overdeck] AgentOutputService started");
64799
66203
  startMergeBlockerReconcileService();
64800
66204
  console.log("[overdeck] MergeBlockerReconcileService started");
66205
+ if (startReviewStatusReconcileService()) console.log("[overdeck] ReviewStatusReconcileService started");
66206
+ else console.log("[overdeck] ReviewStatusReconcileService skipped — non-primary dashboard process");
64801
66207
  if (shouldStartStaleCheckRetriggerService()) {
64802
66208
  startStaleCheckRetriggerService();
64803
66209
  console.log("[overdeck] StaleCheckRetriggerService started");
64804
66210
  }
64805
66211
  if (process.env.OVERDECK_MODE === "desktop") (async () => {
64806
66212
  try {
64807
- const { provisionClaudeHooks } = await import("./claude-hooks-provision-8Ngo8TpT.js");
66213
+ const { provisionClaudeHooks } = await import("./claude-hooks-provision-4RPxNkE8.js");
64808
66214
  const result = await provisionClaudeHooks();
64809
66215
  if (!result.ok) console.warn(`[overdeck] Claude hook provisioning skipped: ${result.reason}`);
64810
66216
  else if (result.changed) console.log(`[overdeck] Claude hooks provisioned: ${result.binariesSynced} scripts, registered ${result.registered.length} hook(s)${result.pruned.length ? `, pruned ${result.pruned.length} stale` : ""}`);
@@ -64816,21 +66222,11 @@ if (process.env.OVERDECK_MODE === "desktop") (async () => {
64816
66222
  setPipelineHandlerSync((event) => {
64817
66223
  switch (event.type) {
64818
66224
  case "status_changed":
64819
- (async () => {
64820
- try {
64821
- const enriched = await Effect.runPromise(enrichReviewStatus(event.issueId, event.status));
64822
- getEventStore().append({
64823
- type: "review.status_changed",
64824
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
64825
- payload: {
64826
- issueId: event.issueId,
64827
- status: enriched
64828
- }
64829
- });
64830
- } catch (err) {
64831
- console.error("[pipeline] Failed to append status_changed event:", err);
64832
- }
64833
- })();
66225
+ try {
66226
+ emitReviewStatusChanged((domainEvent) => getEventStore().append(domainEvent), event.issueId, event.status);
66227
+ } catch (err) {
66228
+ console.error("[pipeline] Failed to append status_changed event:", err);
66229
+ }
64834
66230
  return;
64835
66231
  case "review.approved":
64836
66232
  case "test.passed":
@@ -65012,21 +66408,11 @@ console.log("[overdeck] Agent stopped/status notifiers → domain events wired")
65012
66408
  setMergeReadyNotifier((issueId) => {
65013
66409
  const status = getReviewStatusSync(issueId);
65014
66410
  if (!status) return;
65015
- (async () => {
65016
- try {
65017
- const enriched = await Effect.runPromise(enrichReviewStatus(issueId, status));
65018
- getEventStore().append({
65019
- type: "review.status_changed",
65020
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
65021
- payload: {
65022
- issueId,
65023
- status: enriched
65024
- }
65025
- });
65026
- } catch (err) {
65027
- console.error("[pipeline] Failed to append merge-ready event:", err);
65028
- }
65029
- })();
66411
+ try {
66412
+ emitReviewStatusChanged((domainEvent) => getEventStore().append(domainEvent), issueId, status);
66413
+ } catch (err) {
66414
+ console.error("[pipeline] Failed to append merge-ready event:", err);
66415
+ }
65030
66416
  });
65031
66417
  console.log("[overdeck] Merge-ready notifier → domain events wired");
65032
66418
  startConversationLifecycleService();
@@ -65119,6 +66505,11 @@ let shuttingDown = false;
65119
66505
  const handleShutdownSignal = async (signal) => {
65120
66506
  if (shuttingDown) return;
65121
66507
  shuttingDown = true;
66508
+ try {
66509
+ setLastCleanShutdownAt((/* @__PURE__ */ new Date()).toISOString());
66510
+ } catch (err) {
66511
+ console.warn("[overdeck] failed to record clean shutdown marker:", err);
66512
+ }
65122
66513
  console.log(`[overdeck] received ${signal} (pid=${process.pid} ppid=${process.ppid}) — shutting down`);
65123
66514
  try {
65124
66515
  const notifiedClients = broadcastServerRestarting();
@@ -65148,7 +66539,7 @@ const handleShutdownSignal = async (signal) => {
65148
66539
  await stopAllKnowledgeViewers().catch((err) => console.warn("[knowledge-viewer] shutdown failed:", err?.message ?? err));
65149
66540
  await stopDeaconChild().catch((err) => console.warn("[deacon-supervisor] child shutdown failed:", err?.message ?? err));
65150
66541
  try {
65151
- const { killAllGateProcessGroups } = await import("./validation-CC9OPqJZ.js");
66542
+ const { killAllGateProcessGroups } = await import("./validation-hVOwQk8T.js");
65152
66543
  const reaped = killAllGateProcessGroups();
65153
66544
  if (reaped > 0) console.log(`[overdeck] reaped ${reaped} in-flight gate process group(s)`);
65154
66545
  } catch (err) {
@@ -65203,7 +66594,7 @@ reconcileStaleGitHubBlockers().then((n) => {
65203
66594
  }
65204
66595
  }).catch((err) => console.warn(`[overdeck] Startup blocker reconciliation failed: ${err.message}`));
65205
66596
  try {
65206
- const { resetProcessingToQueued } = await import("./merge-sync-BJYeOODr.js");
66597
+ const { resetProcessingToQueued } = await import("./merge-sync-CpGmk6Ch.js");
65207
66598
  const resetCount = resetProcessingToQueued();
65208
66599
  if (resetCount > 0) {
65209
66600
  console.log(`[overdeck] Reset ${resetCount} stuck merge queue entries to queued`);
@@ -65225,7 +66616,7 @@ else {
65225
66616
  console.log("[overdeck] Auto-merge executor started");
65226
66617
  }
65227
66618
  try {
65228
- const { reconcileInterruptedVerifications } = await import("./verification-runner-BW4HOKAz.js");
66619
+ const { reconcileInterruptedVerifications } = await import("./verification-runner-Bc3cJvpC.js");
65229
66620
  const interrupted = reconcileInterruptedVerifications();
65230
66621
  if (interrupted > 0) console.log(`[overdeck] recovered ${interrupted} orphaned verification worker(s)`);
65231
66622
  } catch (err) {