@overdeck/desktop 0.45.23 → 0.46.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 (549) hide show
  1. package/drizzle/overdeck/0000_overdeck_init.sql +34 -0
  2. package/package.json +1 -1
  3. package/server/{acceptance-criteria-DOA0KgXx.js → acceptance-criteria-CCwFGICj.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-BBu6DSNm.js → advancing-selfheal-De1L-Sb4.js} +6 -6
  7. package/server/{agent-gc-BV1q_2EV.js → agent-gc-CBULruzm.js} +5 -5
  8. package/server/agent-state-VQk8fY9F.js +3 -0
  9. package/server/{agent-state-service-HoK8U4jW.js → agent-state-service-BlnSkRFA.js} +5 -5
  10. package/server/{agent-state-service-CnzFKSAQ.js → agent-state-service-hZpxIw1E.js} +1 -1
  11. package/server/agents-BKKkxKgK.js +3 -0
  12. package/server/{agents-B4Bjbkfv.js → agents-C5rWYhb6.js} +4 -4
  13. package/server/archive-planning-0FJ6Xd3b.js +2 -0
  14. package/server/{archive-planning-DYrYojxl.js → archive-planning-C0lAbNB_.js} +16 -24
  15. package/server/{artifact-index-jobs-C-xy7Z5v.js → artifact-index-jobs-ug6ZxIP5.js} +2 -2
  16. package/server/assemble-DnffNpOo.js +339 -0
  17. package/server/{backlog-auto-trigger-GtG71X8H.js → backlog-auto-trigger-Dh-i9PQf.js} +3 -3
  18. package/server/{behavior-B_-4EKF3.js → behavior-CfIg6pKp.js} +2 -2
  19. package/server/bridge-pool-patrol-CUFmjVDN.js +136 -0
  20. package/server/{briefing-freshness-DWdW7vUm.js → briefing-freshness-DHmCSLar.js} +2 -2
  21. package/server/{build-info-DgTqZkL5.js → build-info-BP160gU7.js} +1 -1
  22. package/server/{build-info-BT096lEl.js → build-info-CuFfxZxp.js} +5 -3
  23. package/server/{checkpoint-manager-DDqcGyu8.js → checkpoint-manager-DEWD82qK.js} +2 -2
  24. package/server/{checkpoint-manager-B7Bt29J0.js → checkpoint-manager-Dxh0FYsv.js} +1 -1
  25. package/server/checkpoint-worker.js +1 -1
  26. package/server/{checkpoints-C-AXLfYT.js → checkpoints-CZvbtLnr.js} +2 -2
  27. package/server/{checkpoints-CpgCjAlN.js → checkpoints-DMTgXuLG.js} +1 -1
  28. package/server/{claude-hooks-provision-C8wdRnN1.js → claude-hooks-provision-CN-xo-do.js} +1 -1
  29. package/server/{claude-hooks-provision-but-_qGY.js → claude-hooks-provision-fvy4RD2b.js} +13 -2
  30. package/server/{claude-permissions-H_5wHQZQ.js → claude-permissions-CN0cSCPp.js} +2 -2
  31. package/server/{claude-settings-overlay-CbSz0Dvb.js → claude-settings-overlay-CJn35FHQ.js} +2 -2
  32. package/server/{claude-settings-overlay-BTENVmYf.js → claude-settings-overlay-Dabi9slj.js} +1 -1
  33. package/server/cli-DmXETsw-.js +3 -0
  34. package/server/{cli-vBeXbPrq.js → cli-oCWYA1pE.js} +9 -9
  35. package/server/{cliproxy-Bgb2XCqi.js → cliproxy-C8mB3KuB.js} +2 -2
  36. package/server/{cliproxy-DW0V86F8.js → cliproxy-PGwTX_o8.js} +1 -1
  37. package/server/{close-issue-Dn_4c4G8.js → close-issue-D5MF5GqF.js} +5 -5
  38. package/server/{closed-issue-reaper-CWpY2uBw.js → closed-issue-reaper-CJ24u0Zt.js} +1 -1
  39. package/server/{closed-issue-reaper-hANY_ehE.js → closed-issue-reaper-Cltx86x5.js} +37 -16
  40. package/server/{codex-Dg7Is5yb.js → codex-BdEsUFWh.js} +1 -1
  41. package/server/{codex-TmSi-yTv.js → codex-Drlf5Xhf.js} +37 -8
  42. package/server/{codex-parser-C4gSH-IR.js → codex-parser-DpB89Vqa.js} +2 -2
  43. package/server/compaction-continuation-K2AxQX4H.js +200 -0
  44. package/server/{config-DjLOdbgT.js → config-B2IMcBxx.js} +4 -3
  45. package/server/{config-DUIhMN_T.js → config-DV-grfQc.js} +1 -1
  46. package/server/{config-CCvjQf27.js → config-Dr3pW6w6.js} +2 -2
  47. package/server/{config-yaml-DvyMuOle.js → config-yaml-CmOPzmBb.js} +290 -22
  48. package/server/{conflict-gate-CmrYpa47.js → conflict-gate-B9cN3SBi.js} +4 -4
  49. package/server/{conflict-gate-BSJkNTdp.js → conflict-gate-Dh754uyA.js} +1 -1
  50. package/server/context-layers-D6LZo3l1.js +4 -0
  51. package/server/context-layers-dR5YxHYB.js +37 -0
  52. package/server/{control-settings-aP1ANIlI.js → control-settings-C7DUehE1.js} +49 -19
  53. package/server/{conversation-compaction-CNbTLB86.js → conversation-compaction-BoqdDXWF.js} +8 -8
  54. package/server/{conversation-compaction-DiG5zWQq.js → conversation-compaction-Dj4FAKoZ.js} +1 -1
  55. package/server/{conversation-service-Du-MTNpJ.js → conversation-service-5aCnq1zR.js} +331 -20
  56. package/server/conversation-service-BCxl4f8Z.js +3 -0
  57. package/server/{conversations-DbOs1e9Q.js → conversations-BUpYsOZg.js} +5 -5
  58. package/server/{conversations-6mPANqvL.js → conversations-C34dyIb9.js} +1 -1
  59. package/server/{cost-Ca0YTWDw.js → cost-Bgcub5kW.js} +12 -2
  60. package/server/{cost-CW4WSe72.js → cost-CvxRrucd.js} +3 -3
  61. package/server/{cost-sync-C0VdGme1.js → cost-sync-Dv3LUaNW.js} +3 -3
  62. package/server/{dag-CdOjJVDT.js → dag-ombYYt4s.js} +2 -2
  63. package/server/{dangerous-git-ops-CT-IypG3.js → dangerous-git-ops-BcJPC5Rr.js} +1 -1
  64. package/server/dashboard-db-worker.js +7 -7
  65. package/server/deacon-DrXqT9LQ.js +4 -0
  66. package/server/{deacon-pRnyD5w9.js → deacon-hi9KLPLT.js} +2998 -2659
  67. package/server/{deacon-nudge-log-Bjg8U4Y9.js → deacon-nudge-log-BNBebzeL.js} +3 -3
  68. package/server/deacon-pending-lifecycle-Cpk0k7Hi.js +33 -0
  69. package/server/{deacon-swarm-DftYRDo8.js → deacon-swarm-DBxUaO-A.js} +13 -13
  70. package/server/deacon-swarm-I2JfX5Tg.js +2 -0
  71. package/server/deacon.js +14 -19
  72. package/server/dead-end-trip-CQcP70ND.js +13 -0
  73. package/server/delivery-Da0zEMY4.js +3 -0
  74. package/server/{deploy-patrol-Bjsho_ha.js → deploy-patrol-Df-Tmaq0.js} +69 -18
  75. package/server/deploy-queue-9SrhZR63.js +3 -0
  76. package/server/deploy-queue-UjKGYsDe.js +169 -0
  77. package/server/deploy-window-jOiPRHL0.js +3 -0
  78. package/server/{deploy-window-DeP_OIRk.js → deploy-window-rpqpWqHA.js} +40 -16
  79. package/server/{dev-supervisor-BpMoHlkR.js → dev-supervisor-CC46Ig8q.js} +2 -2
  80. package/server/{devcontainer-renderer-CtUgrVKW.js → devcontainer-renderer-68CSCRnu.js} +3 -3
  81. package/server/{dist-DyRx4cI7.js → dist-CJy72uEg.js} +1 -1
  82. package/server/dist-DSlxIU1H.js +3 -0
  83. package/server/{dist-src-BSoS7mkd.js → dist-src-Bik83hFi.js} +1 -1
  84. package/server/docker-BMVTSFL5.js +3 -0
  85. package/server/{docker-BK7HIcBj.js → docker-Dam7mbIB.js} +2 -2
  86. package/server/{dod-BrSfUHm0.js → dod-Dar-yheg.js} +8 -4
  87. package/server/durable-review-pipeline-CGZ5QBxh.js +17 -0
  88. package/server/durable-review-pipeline-jpgCJZFp.js +3 -0
  89. package/server/{ensure-devcontainer-DseivYEy.js → ensure-devcontainer-B057YA81.js} +4 -4
  90. package/server/{ensure-devcontainer-DRqaAkBR.js → ensure-devcontainer-C98V8yW-.js} +1 -1
  91. package/server/{esm-shims-CbI3BVkl.js → esm-shims-Br9BPdkR.js} +1 -1
  92. package/server/{event-store-Cr5PQwFC.js → event-store-ELqkVOn0.js} +1 -1
  93. package/server/{event-store-tdiY5eDZ.js → event-store-zY5MJHgF.js} +23 -4
  94. package/server/{events-CvvGK6LT.js → events-D6pyqhzM.js} +1 -1
  95. package/server/{events-CC1dgUpx.js → events-D9PR1IC0.js} +3 -3
  96. package/server/{factory-CynerjXO.js → factory-CRoOpGsF.js} +5 -5
  97. package/server/{feature-registry-population-B0pmAz0a.js → feature-registry-population--YUXU999.js} +4 -4
  98. package/server/{feature-registry-storage-_wUBRD3J.js → feature-registry-storage-BORBTo0y.js} +2 -2
  99. package/server/{features-Dhxz-wLc.js → features--nFkJY8v.js} +2 -2
  100. package/server/{feedback-target-iygZwmMc.js → feedback-target-C0ZzyHvU.js} +1 -1
  101. package/server/{feedback-target-CPS15Vte.js → feedback-target-Dt9BsapX.js} +8 -8
  102. package/server/{feedback-writer-C9b6ccN1.js → feedback-writer-BG0RUZiu.js} +5 -5
  103. package/server/{feedback-writer-qmiMbqTZ.js → feedback-writer-D1fm-5Z8.js} +1 -1
  104. package/server/flywheel-BekukH4i.js +2 -0
  105. package/server/flywheel-BiT3odRb.js +1092 -0
  106. package/server/flywheel-CLVB55Be.js +478 -0
  107. package/server/flywheel-merge-order-BJv1DZz1.js +462 -0
  108. package/server/flywheel-merge-order-BKw9o6ku.js +2 -0
  109. package/server/forge-CZbSg6Ds.js +383 -0
  110. package/server/forge-DK3blQb_.js +3 -0
  111. package/server/{from-CJ_aH5jD.js → from-D_4_dk7i.js} +1 -1
  112. package/server/{fts-db-CKgg0VKg.js → fts-db-BeOomFa-.js} +2 -2
  113. package/server/{fts-operations-BrL_VTRr.js → fts-operations-BdewJ_g9.js} +2 -2
  114. package/server/git-utils-CI6cwyyC.js +3 -0
  115. package/server/{git-utils-BVa07a4a.js → git-utils-ameCgDdy.js} +93 -3
  116. package/server/{github-app-CMBdIFBQ.js → github-app-CQ1Lo6LA.js} +19 -8
  117. package/server/{github-app-CPfoFAdV.js → github-app-DtAsvwzK.js} +1 -1
  118. package/server/{github-client--XVlDpwM.js → github-client-BfyDxuRN.js} +2 -2
  119. package/server/{github-client-B4QsdE-X.js → github-client-Cj0yl_07.js} +1 -1
  120. package/server/github-pr-lookup-BDq6Wznz.js +58 -0
  121. package/server/{harness-binary-CVio2m-u.js → harness-binary-DqkftwTb.js} +32 -6
  122. package/server/{harness-resolve-qHJBhy3Z.js → harness-resolve-CqMqM1Mb.js} +18 -9
  123. package/server/{health-events-C9B8y1Hm.js → health-events-CazRxJUS.js} +2 -2
  124. package/server/{health-events-CpfvuVmm.js → health-events-_xfTaOUs.js} +1 -1
  125. package/server/{hooks-DUXw8A0-.js → hooks-BO3QT1sn.js} +2 -2
  126. package/server/{hooks-ltecuFqZ.js → hooks-BnJsxlSq.js} +1 -1
  127. package/server/{hume-BEq0bUDg.js → hume-CDYJxqx-.js} +2 -2
  128. package/server/hume-Dq--7rOQ.js +3 -0
  129. package/server/{idle-stack-reaper-D7w9t3fE.js → idle-stack-reaper-BYqERdkE.js} +1 -1
  130. package/server/{idle-stack-reaper-w1ISSlhs.js → idle-stack-reaper-CnGx_PO_.js} +99 -6
  131. package/server/in-flight-guard-Bq3epG58.js +45 -0
  132. package/server/{infra-CzaSOgul.js → infra-BjuxvjdS.js} +80 -5
  133. package/server/{injection-oNZeoDhL.js → injection-DZxlV2al.js} +1 -1
  134. package/server/{injection-DZ0PfYa4.js → injection-UCf8EsjX.js} +191 -183
  135. package/server/{inspect-agent-DJKLWcVT.js → inspect-agent-6kylZnrm.js} +139 -82
  136. package/server/inspect-agent-BVwDrVrz.js +2 -0
  137. package/server/{internal-token-Bb4XeV2x.js → internal-token-DvMTQSOS.js} +2 -2
  138. package/server/{internal-token-DXdfprGr.js → internal-token-M9omeXM1.js} +1 -1
  139. package/server/{io-B3_AHDfr.js → io-VHvc0Ssp.js} +43 -26
  140. package/server/{issue-closed-zhdvQicO.js → issue-closed-B6KJkmHZ.js} +7 -7
  141. package/server/{issue-lifecycle-Dy4vd0Oa.js → issue-lifecycle-9gk3fnXN.js} +1 -1
  142. package/server/{issue-lifecycle-CreaCkjW.js → issue-lifecycle-C4Zj0WmE.js} +34 -28
  143. package/server/{issue-service-singleton-B5ushCvE.js → issue-service-singleton-DmCh7EHj.js} +10 -10
  144. package/server/{issue-service-singleton-DabvBWUp.js → issue-service-singleton-PMx8F1jr.js} +1 -1
  145. package/server/{issues-B6ToV6x1.js → issues-CgnQ1OGQ.js} +2 -2
  146. package/server/{jsonl-parser-Dhflo1SH.js → jsonl-parser-DJOH_O-w.js} +5 -4
  147. package/server/{label-cleanup-BKPJm4Hv.js → label-cleanup-Dv7AV_R1.js} +3 -3
  148. package/server/{label-reconciler-DdWbHEBu.js → label-reconciler--F85rufp.js} +4 -4
  149. package/server/{lifecycle-DfeyBDwA.js → lifecycle-CTdOWYMi.js} +2 -2
  150. package/server/{lifecycle-sycO16hj.js → lifecycle-DDN8ApVY.js} +6 -7
  151. package/server/{lifecycle-io-D8iNBcux.js → lifecycle-io-D-lGdzK3.js} +5 -5
  152. package/server/{lifecycle-io-Bsnr2K4F.js → lifecycle-io-D_zTLvP1.js} +1 -1
  153. package/server/{linear-client-D4zqMnX7.js → linear-client-BQU36QqE.js} +2 -2
  154. package/server/{linear-client-DqhDQO9k.js → linear-client-CY4O_Tu3.js} +1 -1
  155. package/server/linear-mcp-auth-BnU4TYfY.js +280 -0
  156. package/server/linear-mcp-auth-NYBq_oHZ.js +2 -0
  157. package/server/memory-fts-worker.js +1 -1
  158. package/server/{merge-C_ZEhbqI.js → merge-Bpx4jjht.js} +13 -11
  159. package/server/{merge-agent-XrB80vfT.js → merge-agent-DdMVSV2Y.js} +233 -83
  160. package/server/{merge-agent-BQM3a8z5.js → merge-agent-DrXa1CpW.js} +1 -1
  161. package/server/{merge-batch-Bwoyr8Je.js → merge-batch-C5q1c1GX.js} +1 -1
  162. package/server/merge-completeness-BMm8tMyZ.js +277 -0
  163. package/server/merge-completeness-D0ljXbk3.js +3 -0
  164. package/server/{merge-DK23JEdS.js → merge-idOG--29.js} +1 -1
  165. package/server/merge-ops-DdpTiEMt.js +2 -0
  166. package/server/merge-rebase-DGQpYJU3.js +2 -0
  167. package/server/{merge-rebase-C1KIVr0s.js → merge-rebase-xLnMlw5h.js} +3 -3
  168. package/server/merge-set-DH7ot3yz.js +89 -0
  169. package/server/merge-set-DzsOTBg5.js +3 -0
  170. package/server/{merge-sync-DZ9dojJn.js → merge-sync-CQ-LgZ1_.js} +406 -153
  171. package/server/merge-sync-Ca7F5JnQ.js +3 -0
  172. package/server/{merge-train-DxDfBTjr.js → merge-train-DTEMyIu4.js} +18 -8
  173. package/server/{merge-train-deps-CyYQUksx.js → merge-train-deps-CNPJzepw.js} +4 -4
  174. package/server/merge-verification-x3JO8Dtz.js +220 -0
  175. package/server/merged-docker-cleanup-worker-CTpC27Bs.js +107 -0
  176. package/server/merged-docker-cleanup-worker-Cdi9apm-.js +3 -0
  177. package/server/{multipart-parser-Dt8YPN5S.js → multipart-parser-IMSBfePV.js} +2 -2
  178. package/server/{no-resume-mode-Cx3K0hHi.js → no-resume-mode-C5ULAXwO.js} +1 -1
  179. package/server/node_modules/.package-lock.json +27 -27
  180. package/server/node_modules/@effect/platform-node/node_modules/@effect/platform-node-shared/package.json +3 -3
  181. package/server/node_modules/@google-cloud/common/package.json +2 -2
  182. package/server/node_modules/gaxios/build/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  183. package/server/node_modules/gaxios/build/esm/tsconfig.tsbuildinfo +1 -1
  184. package/server/node_modules/gaxios/package.json +1 -1
  185. package/server/node_modules/google-auth-library/build/src/auth/googleauth.js +24 -19
  186. package/server/node_modules/google-auth-library/package.json +1 -1
  187. package/server/node_modules/google-gax/build/protos/google/api/auth.proto +2 -1
  188. package/server/node_modules/google-gax/build/protos/google/api/backend.proto +12 -1
  189. package/server/node_modules/google-gax/build/protos/google/api/billing.proto +1 -1
  190. package/server/node_modules/google-gax/build/protos/google/api/client.proto +165 -53
  191. package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1/resources.proto +1 -1
  192. package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1beta/quota_adjuster_settings.proto +7 -3
  193. package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1beta/resources.proto +1 -1
  194. package/server/node_modules/google-gax/build/protos/google/api/config_change.proto +1 -1
  195. package/server/node_modules/google-gax/build/protos/google/api/context.proto +1 -1
  196. package/server/node_modules/google-gax/build/protos/google/api/control.proto +3 -3
  197. package/server/node_modules/google-gax/build/protos/google/api/distribution.proto +1 -1
  198. package/server/node_modules/google-gax/build/protos/google/api/documentation.proto +1 -1
  199. package/server/node_modules/google-gax/build/protos/google/api/endpoint.proto +1 -1
  200. package/server/node_modules/google-gax/build/protos/google/api/error_reason.proto +186 -1
  201. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/checked.proto +31 -3
  202. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/eval.proto +8 -9
  203. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/explain.proto +1 -2
  204. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/syntax.proto +20 -22
  205. package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/value.proto +2 -3
  206. package/server/node_modules/google-gax/build/protos/google/api/field_behavior.proto +1 -1
  207. package/server/node_modules/google-gax/build/protos/google/api/field_info.proto +1 -1
  208. package/server/node_modules/google-gax/build/protos/google/api/http.proto +1 -1
  209. package/server/node_modules/google-gax/build/protos/google/api/httpbody.proto +1 -1
  210. package/server/node_modules/google-gax/build/protos/google/api/label.proto +1 -1
  211. package/server/node_modules/google-gax/build/protos/google/api/launch_stage.proto +1 -1
  212. package/server/node_modules/google-gax/build/protos/google/api/log.proto +1 -1
  213. package/server/node_modules/google-gax/build/protos/google/api/logging.proto +1 -1
  214. package/server/node_modules/google-gax/build/protos/google/api/metric.proto +1 -1
  215. package/server/node_modules/google-gax/build/protos/google/api/monitored_resource.proto +1 -1
  216. package/server/node_modules/google-gax/build/protos/google/api/monitoring.proto +1 -1
  217. package/server/node_modules/google-gax/build/protos/google/api/policy.proto +1 -1
  218. package/server/node_modules/google-gax/build/protos/google/api/quota.proto +1 -1
  219. package/server/node_modules/google-gax/build/protos/google/api/resource.proto +1 -1
  220. package/server/node_modules/google-gax/build/protos/google/api/routing.proto +8 -4
  221. package/server/node_modules/google-gax/build/protos/google/api/service.proto +2 -3
  222. package/server/node_modules/google-gax/build/protos/google/api/source_info.proto +1 -1
  223. package/server/node_modules/google-gax/build/protos/google/api/system_parameter.proto +1 -1
  224. package/server/node_modules/google-gax/build/protos/google/api/usage.proto +7 -27
  225. package/server/node_modules/google-gax/build/protos/google/api/visibility.proto +1 -1
  226. package/server/node_modules/google-gax/build/protos/google/longrunning/operations.proto +20 -1
  227. package/server/node_modules/google-gax/build/protos/google/protobuf/any.proto +52 -108
  228. package/server/node_modules/google-gax/build/protos/google/protobuf/compiler/ruby/ruby_generated_code.proto +2 -0
  229. package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_features.proto +21 -1
  230. package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_file_options.proto +24 -0
  231. package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_options.proto +15 -0
  232. package/server/node_modules/google-gax/build/protos/google/protobuf/descriptor.proto +118 -30
  233. package/server/node_modules/google-gax/build/protos/google/protobuf/field_mask.proto +16 -18
  234. package/server/node_modules/google-gax/build/protos/google/protobuf/internal_options.proto +8 -1
  235. package/server/node_modules/google-gax/build/protos/google/protobuf/json/json_enumval_custom_string.proto +26 -0
  236. package/server/node_modules/google-gax/build/protos/google/protobuf/json_enumvalue_options.proto +29 -0
  237. package/server/node_modules/google-gax/build/protos/google/protobuf/json_options.proto +14 -0
  238. package/server/node_modules/google-gax/build/protos/google/protobuf/struct.proto +38 -22
  239. package/server/node_modules/google-gax/build/protos/google/protobuf/test_protos/forced_micro_string.proto +3 -0
  240. package/server/node_modules/google-gax/build/protos/google/protobuf/test_protos/repeated_field_proxy_import_message.proto +14 -0
  241. package/server/node_modules/google-gax/build/protos/google/protobuf/timestamp.proto +9 -8
  242. package/server/node_modules/google-gax/build/protos/google/protobuf/util/json_format_proto3.proto +18 -0
  243. package/server/node_modules/google-gax/build/protos/google/rpc/code.proto +1 -1
  244. package/server/node_modules/google-gax/build/protos/google/rpc/context/attribute_context.proto +7 -2
  245. package/server/node_modules/google-gax/build/protos/google/rpc/context/audit_context.proto +1 -1
  246. package/server/node_modules/google-gax/build/protos/google/rpc/error_details.proto +5 -5
  247. package/server/node_modules/google-gax/build/protos/google/rpc/http.proto +1 -1
  248. package/server/node_modules/google-gax/build/protos/google/rpc/status.proto +1 -2
  249. package/server/node_modules/google-gax/build/protos/google/type/calendar_period.proto +1 -1
  250. package/server/node_modules/google-gax/build/protos/google/type/color.proto +9 -10
  251. package/server/node_modules/google-gax/build/protos/google/type/date.proto +11 -9
  252. package/server/node_modules/google-gax/build/protos/google/type/datetime.proto +17 -17
  253. package/server/node_modules/google-gax/build/protos/google/type/dayofweek.proto +1 -1
  254. package/server/node_modules/google-gax/build/protos/google/type/decimal.proto +11 -13
  255. package/server/node_modules/google-gax/build/protos/google/type/expr.proto +1 -1
  256. package/server/node_modules/google-gax/build/protos/google/type/fraction.proto +1 -1
  257. package/server/node_modules/google-gax/build/protos/google/type/interval.proto +1 -2
  258. package/server/node_modules/google-gax/build/protos/google/type/latlng.proto +4 -5
  259. package/server/node_modules/google-gax/build/protos/google/type/localized_text.proto +3 -3
  260. package/server/node_modules/google-gax/build/protos/google/type/money.proto +1 -2
  261. package/server/node_modules/google-gax/build/protos/google/type/month.proto +1 -1
  262. package/server/node_modules/google-gax/build/protos/google/type/phone_number.proto +20 -15
  263. package/server/node_modules/google-gax/build/protos/google/type/postal_address.proto +39 -39
  264. package/server/node_modules/google-gax/build/protos/google/type/quaternion.proto +4 -4
  265. package/server/node_modules/google-gax/build/protos/google/type/timeofday.proto +11 -8
  266. package/server/node_modules/google-gax/package.json +5 -5
  267. package/server/node_modules/retry-request/package.json +4 -4
  268. package/server/node_modules/teeny-request/package.json +5 -5
  269. package/server/node_modules/undici/README.md +5 -0
  270. package/server/node_modules/undici/docs/docs/api/Client.md +4 -0
  271. package/server/node_modules/undici/docs/docs/api/Dispatcher.md +6 -0
  272. package/server/node_modules/undici/docs/docs/api/Interceptors.md +59 -0
  273. package/server/node_modules/undici/docs/docs/api/MockAgent.md +27 -0
  274. package/server/node_modules/undici/docs/docs/api/MockPool.md +28 -1
  275. package/server/node_modules/undici/docs/docs/index.md +5 -0
  276. package/server/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
  277. package/server/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
  278. package/server/node_modules/undici/lib/core/request.js +13 -2
  279. package/server/node_modules/undici/lib/core/util.js +6 -1
  280. package/server/node_modules/undici/lib/dispatcher/agent.js +3 -3
  281. package/server/node_modules/undici/lib/dispatcher/client-h1.js +42 -8
  282. package/server/node_modules/undici/lib/dispatcher/client-h2.js +34 -43
  283. package/server/node_modules/undici/lib/global.js +36 -16
  284. package/server/node_modules/undici/lib/handler/cache-handler.js +269 -61
  285. package/server/node_modules/undici/lib/handler/cache-revalidation-handler.js +13 -3
  286. package/server/node_modules/undici/lib/handler/redirect-handler.js +5 -2
  287. package/server/node_modules/undici/lib/handler/retry-handler.js +30 -4
  288. package/server/node_modules/undici/lib/interceptor/cache.js +159 -52
  289. package/server/node_modules/undici/lib/interceptor/decompress.js +9 -2
  290. package/server/node_modules/undici/lib/mock/mock-interceptor.js +25 -7
  291. package/server/node_modules/undici/lib/mock/mock-utils.js +39 -10
  292. package/server/node_modules/undici/lib/util/cache.js +372 -68
  293. package/server/node_modules/undici/lib/util/date.js +23 -6
  294. package/server/node_modules/undici/lib/web/cookies/util.js +78 -8
  295. package/server/node_modules/undici/lib/web/fetch/body.js +2 -1
  296. package/server/node_modules/undici/lib/web/fetch/response.js +1 -3
  297. package/server/node_modules/undici/package.json +2 -2
  298. package/server/node_modules/undici/types/client.d.ts +2 -2
  299. package/server/node_modules/undici/types/dispatcher.d.ts +3 -3
  300. package/server/node_modules/undici/types/mock-interceptor.d.ts +5 -1
  301. package/server/{orphan-proposed-reconciler-BTkDrl4R.js → orphan-proposed-reconciler-CWmOdM9-.js} +1 -1
  302. package/server/{orphan-proposed-reconciler-neK8dHQk.js → orphan-proposed-reconciler-Dg6U4ADZ.js} +24 -15
  303. package/server/package-lock.json +28 -28
  304. package/server/package.json +1 -1
  305. package/server/{pan-dir-DeVUgwA7.js → pan-dir-ubraCiUg.js} +24 -10
  306. package/server/{paths-_zEts6wc.js → paths-cXmOwSbh.js} +2 -2
  307. package/server/pending-lifecycle-claim-DEteYdS9.js +110 -0
  308. package/server/{pickup-Dd0Znxxy.js → pickup-D8xtG1J2.js} +17 -16
  309. package/server/pipeline-membership-BGSvkT2M.js +3 -0
  310. package/server/pipeline-membership-CrAPLYtf.js +57 -0
  311. package/server/{pipeline-membership-0HaEGDlH.js → pipeline-membership-gather-CET40frr.js} +177 -109
  312. package/server/pipeline-membership-gather-DTfcgxLl.js +3 -0
  313. package/server/{pipeline-notifier-D0ykk59O.js → pipeline-notifier-BuQ0e4hF.js} +2 -2
  314. package/server/{pipeline-notifier-6iCJplFZ.js → pipeline-notifier-ImNXknf0.js} +1 -1
  315. package/server/post-merge-lifecycle-worker-DbO10pum.js +50 -0
  316. package/server/{project-pipelines-2sK1apsO.js → project-pipelines-Bim6bThI.js} +2 -2
  317. package/server/project-repos-C0nuoBGv.js +98 -0
  318. package/server/project-repos-CRE6Ykow.js +3 -0
  319. package/server/projects-B03Wr-xc.js +3 -0
  320. package/server/{prompts-Dp4Tpk39.js → prompts-BEx08AIu.js} +2 -2
  321. package/server/{providers-DKkBXhxU.js → providers-D7IluJe0.js} +1 -1
  322. package/server/{providers-DV7ec8l7.js → providers-evXhiQeg.js} +7 -7
  323. package/server/public/assets/{chunk-BO2N2NFS-CltymLRg.js → chunk-BO2N2NFS-D1s4-Sqp.js} +4 -4
  324. package/server/public/assets/{diagram-OG6HWLK6-D2f6pjgA.js → diagram-OG6HWLK6-1gdMoSF7.js} +1 -1
  325. package/server/public/assets/{dist-BWR4qFiV.js → dist-CxAOHbil.js} +1 -1
  326. package/server/public/assets/{dist-DEmlBBNQ.js → dist-DqDRYW6P.js} +3 -3
  327. package/server/public/assets/{ganttDiagram-6RSMTGT7-DnfiViPK.js → ganttDiagram-6RSMTGT7-BYex-Ec7.js} +1 -1
  328. package/server/public/assets/{highlighted-body-OFNGDK62-B8Wrmj9H.js → highlighted-body-OFNGDK62-DlVkiG0S.js} +1 -1
  329. package/server/public/assets/index-Cc7cnFC4.css +1 -0
  330. package/server/public/assets/index-K0hAYCgV.js +807 -0
  331. package/server/public/assets/{linear-C9zFQpkX.js → linear-B4cuEFmO.js} +1 -1
  332. package/server/public/assets/mermaid-GHXKKRXX-DyDHFXHV.js +1 -0
  333. package/server/public/assets/{pieDiagram-4H26LBE5-lR8H7Yjd.js → pieDiagram-4H26LBE5-CBHNBIJQ.js} +1 -1
  334. package/server/public/assets/{prod-B1jCQihw.js → prod-CN2nHBvX.js} +4 -4
  335. package/server/public/assets/{quadrantDiagram-W4KKPZXB-CVQ2guem.js → quadrantDiagram-W4KKPZXB-Bom87VOQ.js} +1 -1
  336. package/server/public/assets/{sankeyDiagram-5OEKKPKP-eD1RB4gm.js → sankeyDiagram-5OEKKPKP-UrBeyLxk.js} +1 -1
  337. package/server/public/assets/{xychartDiagram-2RQKCTM6-ChEC6dQn.js → xychartDiagram-2RQKCTM6-DhkbrIxR.js} +1 -1
  338. package/server/public/index.html +2 -2
  339. package/server/{pull-requests--Ib2jTgy.js → pull-requests-Cv7LxzUN.js} +1 -1
  340. package/server/{pull-requests-CXIhdCYD.js → pull-requests-DHWiKxo7.js} +19 -10
  341. package/server/{queries-6d7Cdtm7.js → queries-DHJm7bTd.js} +1134 -203
  342. package/server/{rally-B708-nZn.js → rally-B71M9f4o.js} +3 -3
  343. package/server/rally-YDjU513q.js +3 -0
  344. package/server/{rally-api-yjQ0TfHd.js → rally-api-D5uXN08-.js} +1 -1
  345. package/server/{rally-api-nY0ZANw3.js → rally-api-Dkqmok65.js} +2 -2
  346. package/server/{rally-client-B1EJhfV0.js → rally-client-Bfqm6XI4.js} +4 -4
  347. package/server/{rally-client-CULK2XAY.js → rally-client-CxsA6ExK.js} +1 -1
  348. package/server/{reap-terminal-sessions-MWnqdLCJ.js → reap-terminal-sessions-BQ23nriz.js} +2 -2
  349. package/server/{reap-terminal-sessions-N3BTkmX_.js → reap-terminal-sessions-CvmwaqKv.js} +2 -2
  350. package/server/{rebuild-stack-Dp4eLkt4.js → rebuild-stack-0EEYReVZ.js} +36 -36
  351. package/server/rebuild-stack-qeRqHZxH.js +4 -0
  352. package/server/{reconstruct-cache-uWbZfjms.js → reconstruct-cache-BbYFsqLa.js} +7 -6
  353. package/server/record-CtroO946.js +3 -0
  354. package/server/record-update-DH6dLJID.js +3 -0
  355. package/server/{record-update-Dq0t2uBG.js → record-update-Z0QCxmMp.js} +124 -34
  356. package/server/{recovery-trip-BJtIQuag.js → recovery-trip-eJe26h-f.js} +2 -2
  357. package/server/{release-engine-DZpmGGzb.js → release-engine-lPUp-MTd.js} +5 -5
  358. package/server/{release-set-hnXd_kga.js → release-set-BwGeyZKX.js} +3 -3
  359. package/server/{remote-Br-ZWVIP.js → remote-BCd02Es1.js} +3 -3
  360. package/server/{remote-4tiENDaI.js → remote-CVGUhXMV.js} +3 -3
  361. package/server/{remote-agents-CRCg1L4H.js → remote-agents-C1bssi41.js} +2 -2
  362. package/server/{remote-agents-nmty1mLu.js → remote-agents-C_VxssLU.js} +300 -46
  363. package/server/{remote-completion-BbWNd10K.js → remote-completion-DAB0P706.js} +12 -12
  364. package/server/resume-u9byntrq.js +3 -0
  365. package/server/{review-agent-DJRO8EG8.js → review-agent-CD4RCI0Y.js} +1 -1
  366. package/server/{review-agent-sKkE1FEL.js → review-agent-CHhI66UP.js} +61 -44
  367. package/server/{review-artifacts-BLbSC4ke.js → review-artifacts-DSMRIfuJ.js} +7 -6
  368. package/server/{review-context-BgX3lcCx.js → review-context-oFuBbiEE.js} +69 -25
  369. package/server/{in-flight-guard-aQ1IxeI3.js → review-convoy-liveness-CpzrcJ5F.js} +3 -44
  370. package/server/review-status-BZH8h-6W.js +3 -0
  371. package/server/review-status-emit-CUExtHd5.js +50 -0
  372. package/server/{review-status-enrichment-tbOn_A_u.js → review-status-enrichment-QV_hQkgS.js} +4 -4
  373. package/server/{review-status-source-CxuEjLGg.js → review-status-source-CpvJnvnQ.js} +24 -3
  374. package/server/review-status-source-ylw5QZUR.js +3 -0
  375. package/server/{review-status-sync-BhSQ0d0l.js → review-status-sync-483sujRo.js} +10 -5
  376. package/server/{review-status-sync-To3-iS-N.js → review-status-sync-NaWvI9Xf.js} +1 -1
  377. package/server/review-status-yljtTd-q.js +7521 -0
  378. package/server/{review-verdict-feedback-C1Kg8n7t.js → review-verdict-feedback-BPDn4li_.js} +1 -1
  379. package/server/{review-verdict-feedback-CTRNtzXO.js → review-verdict-feedback-CELeUinh.js} +48 -8
  380. package/server/{rollup-WA0VD-Rr.js → rollup-DTNvelz9.js} +8 -8
  381. package/server/{rollup-B53Y81xK.js → rollup-DYZfq1c4.js} +1 -1
  382. package/server/{scanner-D2743cp8.js → scanner-CV35oSn4.js} +42 -14
  383. package/server/{sequence-io-Cg0zYj_E.js → sequence-io-DF_8iuhV.js} +4 -4
  384. package/server/{sequencer-agent-BGY8gjg-.js → sequencer-agent-B2WeCtGi.js} +5 -5
  385. package/server/server.js +3899 -672
  386. package/server/{service-DCpB_SGd.js → service-Br9rpBNA.js} +83 -65
  387. package/server/{session-fork-BQ8_6H32.js → session-fork-D0LY152N.js} +2 -2
  388. package/server/{session-history-CVZDpS3c.js → session-history-BUyK29eq.js} +4 -4
  389. package/server/{settings-BZ5m1UCl.js → settings-Bse1Pdcj.js} +3 -3
  390. package/server/settings-CdvIF84R.js +2 -0
  391. package/server/{settings-ylxwqwQG.js → settings-CvVGqNEs.js} +3 -2
  392. package/server/{shadow-engineering-BlxG6Xye.js → shadow-engineering-BWLa8ODz.js} +2 -2
  393. package/server/{shadow-state-Dh-HzF2J.js → shadow-state-B4fpKxOO.js} +2 -2
  394. package/server/{shadow-state-C2_epgx2.js → shadow-state-FV6PHfEW.js} +1 -1
  395. package/server/{shadow-utils-4bvjn9b3.js → shadow-utils-D7asqxOZ.js} +2 -2
  396. package/server/{ship-log-ClSMIzZX.js → ship-log-xplJ8t75.js} +1 -1
  397. package/server/{smart-compaction-CHIxLr_d.js → smart-compaction-CPp2Ykf6.js} +1 -1
  398. package/server/{smart-compaction-r4pHQBIr.js → smart-compaction-D3U_79f-.js} +8 -7
  399. package/server/{spawn-planning-session-KC_UduxD.js → spawn-planning-session-1nFi3gfk.js} +1 -1
  400. package/server/{spawn-planning-session-DWAorh2B.js → spawn-planning-session-DGfZ5B8D.js} +30 -23
  401. package/server/{specialist-completion-BXhav38U.js → specialist-completion-Br48eiDP.js} +1 -1
  402. package/server/{specialist-context-DtrYvGJ-.js → specialist-context-cVbGnTa3.js} +5 -5
  403. package/server/{specialist-handoff-logger-C45ouoZ6.js → specialist-handoff-logger-DK-GTz4s.js} +2 -2
  404. package/server/{specialist-logs-DJ3RzKxW.js → specialist-logs-Cl5-hqT8.js} +3 -3
  405. package/server/{specialists-JVO00bs7.js → specialists-CNfYO3G2.js} +1634 -457
  406. package/server/{specialists-DyFeedjn.js → specialists-Cicieuzu.js} +21 -19
  407. package/server/{specialists-D5fRZXmN.js → specialists-JHsdU3pA.js} +1 -1
  408. package/server/{specialists-DVj0hvaL.js → specialists-Tdezg_6N.js} +1 -1
  409. package/server/specs-Bt7WJDoC.js +3 -0
  410. package/server/src-CRmCHlma.js +11408 -0
  411. package/server/{src-CG9mCkP6.js → src-Ck028hjO.js} +6 -6
  412. package/server/{src-CMTQGyCK.js → src-UWcGe0Ix.js} +5 -5
  413. package/server/stack-health-CJLMrLBl.js +3 -0
  414. package/server/{stack-health-puygdaMd.js → stack-health-DqSqx0zv.js} +68 -12
  415. package/server/{stale-task-claims-BbFwCTb0.js → stale-task-claims-CYFOUNlT.js} +4 -4
  416. package/server/{staleness-Cbl_w4Bw.js → staleness-BQ-0gMYK.js} +1 -1
  417. package/server/{staleness-Bpy41ngS.js → staleness-D47CUJok.js} +1 -1
  418. package/server/{state-home-CIGjwNfJ.js → state-home-BYV7qZTW.js} +2 -2
  419. package/server/{state-mapping-BL_-3Yt8.js → state-mapping-BVldLhBf.js} +1 -1
  420. package/server/{state-migrate-Dkzaxvny.js → state-migrate-CvvBtS0Y.js} +4 -4
  421. package/server/{stuck-remediation-sY_MHHgQ.js → stuck-remediation-Wt0UVvu1.js} +1 -1
  422. package/server/{stuck-remediation-CzEov_fq.js → stuck-remediation-mkLC6G_t.js} +360 -26
  423. package/server/{substrate-bug-weights-service-BH66lhm7.js → substrate-bug-weights-service-XfLAKnmK.js} +3 -3
  424. package/server/{summary-fork-D8bXjCr7.js → summary-fork-CD7GOts0.js} +2 -2
  425. package/server/{summary-fork-DHhJIsAL.js → summary-fork-xiXBnWG9.js} +78 -12
  426. package/server/supervisor/{injection-budget-C1iPirFS.js → injection-budget-B5SbvDx2.js} +2 -41
  427. package/server/supervisor/{paths-DehGY2Fj.js → paths-K3noE4N_.js} +1 -1
  428. package/server/supervisor/pty-supervisor.js +49 -2
  429. package/server/supervisor/pty-token-B7lp0ElT.js +43 -0
  430. package/server/{supervisor-BO-Zk_h-.js → supervisor-DL788vos.js} +3 -3
  431. package/server/supervisor-UWiQNK-y.js +2 -0
  432. package/server/{swarm-readiness-CQ_fwUWF.js → swarm-readiness-BV14DF5N.js} +3 -3
  433. package/server/{system-prerequisites-Bza0ffAh.js → system-prerequisites-pdWG6ZQs.js} +56 -29
  434. package/server/{teardown-workspace-CEgauF4U.js → teardown-workspace-l2F4_zim.js} +15 -16
  435. package/server/{test-agent-queue-DXDt0Xa-.js → test-agent-queue-qakFIIGz.js} +6 -6
  436. package/server/{test-verdict-CmUBK5cT.js → test-verdict-BJYDggMm.js} +1 -1
  437. package/server/{test-verdict-DFjjIHR5.js → test-verdict-Gw-btEfb.js} +1 -1
  438. package/server/{tldr-daemon-DL566HbH.js → tldr-daemon-BKOYB616.js} +1 -1
  439. package/server/{tldr-daemon-BDtLOIlO.js → tldr-daemon-CBaWkV9k.js} +2 -2
  440. package/server/{tmux-ftABRAmc.js → tmux-B1Wbxz5o.js} +3 -3
  441. package/server/{tmux-DoThH6aj.js → tmux-DQ6jB76i.js} +1 -1
  442. package/server/tmux-dedup-DbN6MGzM.js +892 -0
  443. package/server/{token-CsOhvw42.js → token-Cxbk023d.js} +2 -2
  444. package/server/{token-util-CTmczIfW.js → token-util-CLq2MmQt.js} +1 -1
  445. package/server/token-util-zlz9U0ki.js +3 -0
  446. package/server/{tracker-config-B4itFzDo.js → tracker-config-DZ8Zn2nQ.js} +2 -2
  447. package/server/{tracker-utils-tnxxqROg.js → tracker-utils-L0FKtjq2.js} +3 -3
  448. package/server/{traefik-connect-m73vGebA.js → traefik-connect-bNX6jKkb.js} +1 -1
  449. package/server/{tunnel-B2vxjx_b.js → tunnel-BAm1XgjG.js} +3 -3
  450. package/server/{tunnel-Cgj-IGIl.js → tunnel-CjSnHdTc.js} +1 -1
  451. package/server/{types-BBNjPcqU.js → types-q67TYYg5.js} +1 -1
  452. package/server/uat-promote-verification-CKB9ZrKC.js +92 -0
  453. package/server/uat-train-Cu-4nvbf.js +2 -0
  454. package/server/uat-train-D5UjcyAa.js +0 -0
  455. package/server/validate-Cxsx5Uz1.js +179 -0
  456. package/server/{validation-CIi6pnI2.js → validation-DEaTZafj.js} +1 -1
  457. package/server/{validation-B5JxoPUS.js → validation-VZgJxmqc.js} +4 -4
  458. package/server/{verification-artifact-C-lIHfUa.js → verification-artifact-C85cRTc_.js} +1 -1
  459. package/server/{verification-artifact-BcnIbigL.js → verification-artifact-g3yuxQDL.js} +1 -1
  460. package/server/{verification-runner-CDj4lD4t.js → verification-runner-DlZI8yhw.js} +1 -1
  461. package/server/{verification-runner-Br0S_Ous.js → verification-runner-KDZIsBj4.js} +114 -107
  462. package/server/{vtt-parser-DKpNNW5i.js → vtt-parser-Jr94BF3Y.js} +1 -1
  463. package/server/{work-agent-prompt-Yb2bd3Fq.js → work-agent-prompt-C-vuP3_J.js} +20 -9
  464. package/server/workflows-CHrx_hcW.js +2 -0
  465. package/server/{workflows-C9WmILql.js → workflows-D0oip4eV.js} +161 -78
  466. package/server/{workspace-DuUdndHe.js → workspace-BqFg__wH.js} +1 -1
  467. package/server/{workspace-BVl4rihN.js → workspace-C0fH3aek.js} +3 -3
  468. package/server/workspace-anchor-drift-B4gMRHl4.js +3 -0
  469. package/server/workspace-anchor-drift-CZKofakh.js +224 -0
  470. package/server/{workspace-config-cEGcO6UT.js → workspace-config-B123aZl-.js} +1 -1
  471. package/server/workspace-manager-CDTKjjdU.js +3 -0
  472. package/server/{workspace-manager-hLUv3ADC.js → workspace-manager-CwO-Hek7.js} +206 -116
  473. package/server/{workspace-metadata-CskwzzhL.js → workspace-metadata-CQv00QwK.js} +2 -2
  474. package/server/{workspace-metadata-DNpMixRq.js → workspace-metadata-D4M8BM4W.js} +1 -1
  475. package/server/{workspaces-hWvy9eKs.js → workspaces-j1e_6aAT.js} +736 -290
  476. package/sync-sources/dev-skills/pan-dashboard-restart/SKILL.md +34 -10
  477. package/sync-sources/hooks/linear-mcp-auth-hook +97 -0
  478. package/sync-sources/hooks/record-cost-event.js +6 -4
  479. package/sync-sources/rules/ask-user-question-self-contained.md +9 -0
  480. package/sync-sources/skills/okf/README.md +11 -3
  481. package/sync-sources/skills/okf/SKILL.md +12 -1
  482. package/sync-sources/skills/okf/docs/USAGE.md +15 -0
  483. package/sync-sources/skills/okf/references/overdeck.md +28 -0
  484. package/sync-sources/skills/pan-close/SKILL.md +5 -3
  485. package/sync-sources/skills/pan-flywheel/SKILL.md +18 -1
  486. package/sync-sources/skills/pan-knowledge/SKILL.md +10 -0
  487. package/sync-sources/skills/pan-orders/SKILL.md +33 -0
  488. package/sync-sources/skills/pan-peer-review/SKILL.md +195 -0
  489. package/sync-sources/skills/pan-reload/SKILL.md +3 -3
  490. package/sync-sources/skills/pan-restart/SKILL.md +5 -4
  491. package/sync-sources/skills/pan-review/SKILL.md +7 -1
  492. package/sync-sources/skills/pan-start/SKILL.md +4 -0
  493. package/sync-sources/skills/pan-sync-main/SKILL.md +9 -8
  494. package/sync-sources/skills/pipeline-status/SKILL.md +10 -1
  495. package/sync-sources/skills/workspace-add-repo/SKILL.md +16 -1
  496. package/sync-sources/templates/traefik/dynamic/overdeck.yml.template +10 -0
  497. package/server/activity-logger-OEUBdfZ8.js +0 -3
  498. package/server/agent-state-Bg0lhqwj.js +0 -3
  499. package/server/agents-FlIgQ3ql.js +0 -3
  500. package/server/archive-planning-zkJb3BeM.js +0 -3
  501. package/server/cli-BsVIHwzC.js +0 -3
  502. package/server/context-layers-B3Nt58jl.js +0 -4
  503. package/server/context-layers-ZYFcWiyc.js +0 -165
  504. package/server/conversation-service-Up7FEzIC.js +0 -3
  505. package/server/deacon-DYSEJb48.js +0 -4
  506. package/server/deacon-swarm-9jRLS5Ke.js +0 -2
  507. package/server/deploy-window-CM6W1-c6.js +0 -3
  508. package/server/dist-Bd3vn0Hw.js +0 -3
  509. package/server/flywheel-DMCUzDWg.js +0 -2
  510. package/server/flywheel-DSSpWmqF.js +0 -522
  511. package/server/flywheel-DcBkQvgE.js +0 -207
  512. package/server/flywheel-merge-order-C-6zRLvM.js +0 -2
  513. package/server/flywheel-merge-order-DxbQKE1d.js +0 -244
  514. package/server/forge-B-ZWyTRm.js +0 -2
  515. package/server/forge-Ch9gCFod.js +0 -270
  516. package/server/git-utils-Dry_Ka_D.js +0 -3
  517. package/server/github-pr-lookup-BRbDW-qk.js +0 -44
  518. package/server/hume-B_DbflNM.js +0 -3
  519. package/server/inspect-agent-ch-F-5Ci.js +0 -2
  520. package/server/launcher-generator-CHVm7bGT.js +0 -379
  521. package/server/layers-DzxSTGNA.js +0 -93
  522. package/server/merge-blockers-BsFjGrQ8.js +0 -41
  523. package/server/merge-ops-DlB1IM0Y.js +0 -2
  524. package/server/merge-set-DLz0MkGT.js +0 -139
  525. package/server/merge-set-DnSYdBEp.js +0 -3
  526. package/server/merge-sync-SSv3E-pl.js +0 -3
  527. package/server/merge-verification-CSizH3HY.js +0 -124
  528. package/server/projects-9SkRpES9.js +0 -3
  529. package/server/public/assets/index-CUr1xv8w.css +0 -1
  530. package/server/public/assets/index-CsSuZQBI.js +0 -802
  531. package/server/public/assets/mermaid-GHXKKRXX-BUAB8hqt.js +0 -1
  532. package/server/rally-njs_mJrt.js +0 -3
  533. package/server/rebuild-stack-kA7XngID.js +0 -3
  534. package/server/record-t6V-3Dcj.js +0 -3
  535. package/server/record-update-DACtKh6_.js +0 -3
  536. package/server/resume-CcKqnTvE.js +0 -3
  537. package/server/review-status-A7quS5KJ.js +0 -1454
  538. package/server/review-status-C6n6-9f_.js +0 -3
  539. package/server/settings-D0OSwos5.js +0 -2
  540. package/server/specs-nN45DG2X.js +0 -3
  541. package/server/src-U7_kTPEE.js +0 -4733
  542. package/server/stack-health-Df0YEZKa.js +0 -3
  543. package/server/supervisor-BPy-o1A8.js +0 -2
  544. package/server/token-util-DrHg8abn.js +0 -3
  545. package/server/tracker-utils-BePq_oj-.js +0 -3
  546. package/server/uat-train-CuEn0t9M.js +0 -2152
  547. package/server/uat-train-cKfM-_WW.js +0 -2
  548. package/server/workflows-D90rMOya.js +0 -2
  549. package/server/workspace-manager-ZTdJAy9M.js +0 -3
@@ -1,45 +1,47 @@
1
1
  import { a as __toCommonJS } from "./rolldown-runtime-DNCzzKnI.js";
2
- import { A as canUseHarnessSync, Br as getOverdeckHome, D as resolveTieredExecutionEnabled, H as init_providers, I as UnknownModelError, Jn as extractPrefixSync, M as init_harness_policy, Nr as encodeClaudeProjectDir, O as resolveTieredExecutionEnabledForIssue, Qt as parsePorcelainStatusPaths, T as init_tier_table, Tn as init_projects, U as piProviderForModel, Ur as packageRoot, V as getProviderForModelSync, Vr as init_paths, Wr as resolveOhmypiExtensionPath, Yn as init_issue_id, Yt as init_state_plane, Zn as parseIssueIdSync, c as isClaudeCodeChannelsEnabled, f as loadConfigSync, in as shouldCommitLegacyWorkspaceArtifacts, ir as FsError, kn as listProjectsSync, mr as init_errors, nn as init_state_read_home, t as init_config_yaml, tt as resolveModel, yn as getProjectSync, zn as resolveProjectFromIssueSync } from "./config-yaml-DvyMuOle.js";
3
- import { a as init_activity_logger, n as emitActivityEntrySync } from "./activity-logger-DDlOIAML.js";
4
- import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-CzaSOgul.js";
5
- import { $ as init_child_env, B as sendKeysAsync, D as killSession, H as sessionExists, M as listSessionNames, N as listSessions, Q as buildChildEnvWithoutTmuxSync, T as isHarnessProcessAlive, U as sessionExistsSync, V as sendRawKeystroke, W as setOption, X as BLANKED_PROVIDER_ENV, g as findManagedServerPidSync, i as capturePane, m as exactPaneTarget, s as createSession, w as init_tmux } from "./tmux-ftABRAmc.js";
6
- import { a as init_event_store, n as getEventStore } from "./event-store-tdiY5eDZ.js";
7
- import { k as normalizeChannelReplyPayload, o as getHarnessBehavior, t as init_src, x as DomainEvent } from "./src-U7_kTPEE.js";
8
- import { E as init_agent_state_sync, b as setReviewStatusSync, c as getReviewStatusSync, d as loadReviewStatuses, i as clearWorkspaceStuck, k as stateToOverdeckParamsForDb, l as init_review_status, w as AGENT_COLUMNS_FOR_DB } from "./review-status-A7quS5KJ.js";
9
- import { a as EventStoreService, c as ReadModelService, d as init_read_model, i as init_issue_lifecycle, s as init_domain_services, t as IssueLifecycle, u as getClosedIssueIdsForReadSource } from "./issue-lifecycle-CreaCkjW.js";
10
- import { d as isTerminalIssueStage, o as init_agents, u as getIssueStageSync } from "./agents-B4Bjbkfv.js";
11
- import { n as ensureSessionContextBriefingFile, r as init_briefing_freshness } from "./briefing-freshness-DWdW7vUm.js";
12
- import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-Ca0YTWDw.js";
13
- import { An as writeChannelsBridgeMcpConfig, Ar as init_codex_auth, At as deliverAgentPermissionDecision, Bn as getAgentDir, Cn as validateProviderHealth, Cr as getAgentRuntimeStateSync, F as init_tier_escalation, G as resumeAgent, Gt as transitionIssueToInProgress, H as restartAgent, Hn as getAgentState, In as clearAgentTroubled, Lt as BRIDGE_TOKEN_HEADER, Nt as setAgentDeliveryMethod, P as decideEscalation, Pn as clearAgentPaused, Q as operatorInterventionEvent, Rt as init_bridge_token, Sn as init_provider_health, Sr as getAgentRuntimeState, Tr as saveAgentRuntimeState, V as recoverAgent, Wn as getAgentStateSync, X as messageAgent, Xt as resolveStaffing, Z as init_operator_interventions, Zn as markAgentStoppedState, ar as setAgentPaused, at as saveSessionId, b as init_agents$1, bn as getProviderExportsForModel, cr as wipeAgentStateDirs, dn as piFifoPaths, et as getActivity, fn as init_ohmypi_fifo, fr as init_pty_token, ft as stopAgent, gr as clearReadySignal, hr as resolvePtySupervisorScriptPath, i as listRunningAgents, in as getProviderAuthMode, ir as saveAgentStateSync, kn as dismissDevChannelsDialog, kr as checkCodexAuthStatus, kt as deliverAgentMessage, lr as writeAgentStateJsonSync, mr as init_pty_supervisor_locate, nt as getLatestSessionIdSync, pn as ohmypiFifoPaths, pr as writePtyToken, qt as init_staffing, r as listAgentStates, rr as saveAgentState, rt as getSessionId, tn as getAgentRuntimeBaseCommand, tt as getLatestSessionId, un as init_pi_fifo, vn as determineModel, vr as normalizeAgentId, xr as waitForReadySignal, yn as getProviderEnvForModel, zt as writeBridgeTokenSync } from "./queries-6d7Cdtm7.js";
14
- import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-DCpB_SGd.js";
15
- import { o as init_persistent_logger, s as logAgentLifecycleSync } from "./session-history-CVZDpS3c.js";
16
- import { t as init_behavior } from "./behavior-B_-4EKF3.js";
17
- import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-B4itFzDo.js";
18
- import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-B5ushCvE.js";
19
- import { o as getInternalTokenSync, s as init_internal_token } from "./internal-token-Bb4XeV2x.js";
2
+ import { A as canUseHarnessSync, Cn as resolveStateReadHomeSync, D as resolveTieredExecutionEnabled, Fr as init_errors, Gn as init_projects, H as init_providers, I as UnknownModelError, M as init_harness_policy, Mn as findProjectByPathSync, O as resolveTieredExecutionEnabledForIssue, Sn as init_state_read_home, T as init_tier_table, Tr as FsError, U as piProviderForModel, V as getProviderForModelSync, Yn as listProjectsSync, ar as resolveProjectFromIssueSync, c as isClaudeCodeChannelsEnabled, ci as init_paths, di as resolveOhmypiExtensionPath, ei as encodeClaudeProjectDir, f as loadConfigSync, gr as init_issue_id, h as jsYaml, hn as init_state_plane, hr as extractPrefixSync, m as init_js_yaml, si as getOverdeckHome, t as init_config_yaml, tt as resolveModel, ui as packageRoot, vn as parsePorcelainStatusPaths, vr as parseIssueIdSync, wn as shouldCommitLegacyWorkspaceArtifacts, zn as getProjectSync } from "./config-yaml-CmOPzmBb.js";
3
+ import { n as emitActivityEntryDurable, o as init_activity_logger, r as emitActivityEntrySync } from "./activity-logger-CyteR4JO.js";
4
+ import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-BjuxvjdS.js";
5
+ import { B as sendKeysAsync, D as killSession, H as sessionExists, M as listSessionNames, N as listSessions, Q as BLANKED_PROVIDER_ENV, T as isHarnessProcessAlive, U as sessionExistsSync, V as sendRawKeystroke, W as setOption, et as buildChildEnvWithoutTmuxSync, g as findManagedServerPidSync, i as capturePane, m as exactPaneTarget, o as capturePaneText, s as createSession, tt as init_child_env, w as init_tmux } from "./tmux-B1Wbxz5o.js";
6
+ import { a as init_event_store, n as getEventStore } from "./event-store-zY5MJHgF.js";
7
+ import { N as normalizeChannelReplyPayload, T as DomainEvent, n as COMPOSER_COMMAND_MANIFEST, s as getHarnessBehavior, t as init_src } from "./src-CRmCHlma.js";
8
+ import { K as stateToOverdeckParamsForDb, S as setReviewStatusSync, U as init_agent_state_sync, V as AGENT_COLUMNS_FOR_DB, f as init_review_status, m as loadReviewStatuses, o as clearWorkspaceStuck, u as getReviewStatusSync } from "./review-status-yljtTd-q.js";
9
+ import { a as EventStoreService, c as ReadModelService, d as getClosedIssueIdsForReadSource, f as init_read_model, i as init_issue_lifecycle, s as init_domain_services, t as IssueLifecycle } from "./issue-lifecycle-C4Zj0WmE.js";
10
+ import { d as isTerminalIssueStage, o as init_agents, u as getIssueStageSync } from "./agents-C5rWYhb6.js";
11
+ import { n as ensureSessionContextBriefingFile, r as init_briefing_freshness } from "./briefing-freshness-DHmCSLar.js";
12
+ import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-Bgcub5kW.js";
13
+ import { $ as operatorInterventionEvent, $n as getAgentDir, An as getProviderExportsForModel, Bn as dismissDevChannelsDialog, Dr as init_pty_supervisor_locate, Er as writePtyToken, F as init_tier_escalation, Fr as getAgentRuntimeState, G as resumeAgent, Gt as assertWorkspaceStackHealthyForSpawn, H as restartAgent, Hr as checkCodexAuthStatus, Ht as BRIDGE_TOKEN_HEADER, Ir as getAgentRuntimeStateSync, Jt as init_spawn_prep, Mn as init_provider_health, Mt as deliverAgentMessage, Nn as validateProviderHealth, Nt as deliverAgentPermissionDecision, On as determineModel, Or as resolvePtySupervisorScriptPath, P as decideEscalation, Pr as waitForReadySignal, Q as init_operator_interventions, Qt as init_staffing, Rr as saveAgentRuntimeState, Rt as setAgentDeliveryMethod, Sr as writeAgentStateJsonSync, Tr as init_pty_token, Un as writeChannelsBridgeMcpConfig, Ur as init_codex_auth, Ut as init_bridge_token, V as recoverAgent, Wt as writeBridgeTokenSync, X as messageAgent, Xt as transitionIssueToInProgress, Yn as clearAgentTroubled, _r as saveAgentStateSync, b as init_agents$1, bn as init_ohmypi_fifo, cn as getAgentRuntimeBaseCommand, dn as getProviderAuthMode, gn as waitForAcpHostReady, gr as saveAgentState, i as listRunningAgents, it as getSessionId, jr as normalizeAgentId, kr as clearReadySignal, nn as resolveStaffing, nt as getLatestSessionId, ot as saveSessionId, pn as init_runtime_command, pt as stopAgent, qn as clearAgentPaused, r as listAgentStates, rr as getAgentStateSync, rt as getLatestSessionIdSync, sn as getAcpLauncherFields, tn as resolveIssueWorkModel, tr as getAgentState, tt as getActivity, ur as markAgentStoppedState, vn as init_pi_fifo, vr as setAgentPaused, xn as ohmypiFifoPaths, xr as wipeAgentStateDirs, yn as piFifoPaths } from "./queries-DHJm7bTd.js";
14
+ import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-Br9rpBNA.js";
15
+ import { o as init_persistent_logger, s as logAgentLifecycleSync } from "./session-history-BUyK29eq.js";
16
+ import { t as init_behavior } from "./behavior-CfIg6pKp.js";
17
+ import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-DZ8Zn2nQ.js";
18
+ import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-DmCh7EHj.js";
19
+ import { o as getInternalTokenSync, s as init_internal_token } from "./internal-token-DvMTQSOS.js";
20
20
  import { a as PAN_DIRNAME, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-hraoaQIp.js";
21
- import { a as generateXBriefFilename, o as init_lifecycle } from "./lifecycle-DfeyBDwA.js";
22
- import { f as readTierOverrides, g as serializeXBriefDocument, h as recordTierPromotion, m as readWorkspacePlanSync, o as init_io, r as findPlan, u as readPlan } from "./io-B3_AHDfr.js";
23
- import { B as removeFavorite, E as hasOtherActiveConversationOnTmuxSession, F as markConversationEnded, G as setConversationHarness, K as setConversationModel, M as listConversations, P as markConversationActive, Q as updateConversationDeliveryMethod, S as getConversationByName, W as setConversationEffort, g as backfillConversationModel, h as archiveConversation, k as init_conversations, nt as updateLastAttached, rt as updateSpawnError, x as getConversationById, y as createConversation } from "./conversations-DbOs1e9Q.js";
24
- import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population-B0pmAz0a.js";
25
- import { a as requireModelOverrideSync, i as normalizeModelOverrideSync, n as init_launcher_generator, r as init_model_validation, t as generateLauncherScriptSync } from "./launcher-generator-CHVm7bGT.js";
26
- import { a as init_claude_permissions, o as resolvePermissionModeSync, r as getClaudePermissionFlagsStringSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-H_5wHQZQ.js";
27
- import { h as workspaceContextFile, l as piGlobalContextFile, o as init_layers } from "./layers-DzxSTGNA.js";
28
- import { n as init_harness_binary, r as prepareHarnessLaunch } from "./harness-binary-CVio2m-u.js";
29
- import { a as init_stack_health, i as getWorkspaceStackHealth } from "./stack-health-puygdaMd.js";
30
- import { l as init_dag, o as getDispatchableItems } from "./dag-CdOjJVDT.js";
31
- import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-qHJBhy3Z.js";
32
- import { a as loadCloisterConfigSync, r as init_config } from "./config-DjLOdbgT.js";
33
- import { c as isStateMigrated, o as init_state_home } from "./state-home-CIGjwNfJ.js";
34
- import { a as init_lifecycle_io, c as updatePlanStatus, s as transitionXBriefOnMain } from "./lifecycle-io-D8iNBcux.js";
35
- import { _ as init_codex_conversation_parser, c as parseEntireConversation, d as init_pi_conversation_parser, g as parseOhmypiConversationMessages, m as init_ohmypi_conversation_parser, p as parsePiConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages } from "./conversation-service-Du-MTNpJ.js";
36
- import { _ as parseCodexApprovalPrompt, d as getAgentPendingQuestions$1, f as getAgentWorkspace$1, g as normalizeAwaitingInputPrompt, m as detectAwaitingInputForAgent, o as getRuntimeForAgent, p as scanPendingInputsPromise, s as computeAgentEnrichment, u as getAgentJsonlPath$1 } from "./stuck-remediation-CzEov_fq.js";
37
- import { Dt as classifyMemoryPressure, It as getSystemHealthSnapshot, Pt as getResourceConfig } from "./deacon-pRnyD5w9.js";
38
- import { o as stopWorkspaceDocker, r as init_workspace_manager } from "./workspace-manager-hLUv3ADC.js";
39
- import { a as init_jsonl_parser, o as normalizeModelName } from "./jsonl-parser-Dhflo1SH.js";
40
- import { r as init_in_flight_guard, t as createInFlightGuard } from "./in-flight-guard-aQ1IxeI3.js";
41
- import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-CskwzzhL.js";
42
- import { basename, delimiter, dirname, join, resolve } from "node:path";
21
+ import { l as resolveWorkspaceRepoRootsSync, o as resolvePrimaryWorkspaceRepoDirSync, r as init_project_repos } from "./project-repos-C0nuoBGv.js";
22
+ import { a as generateXBriefFilename, o as init_lifecycle } from "./lifecycle-CTdOWYMi.js";
23
+ import { f as readTierOverrides, g as serializeXBriefDocument, h as recordTierPromotion, m as readWorkspacePlanSync, o as init_io, r as findPlan, u as readPlan } from "./io-VHvc0Ssp.js";
24
+ import { B as removeFavorite, E as hasOtherActiveConversationOnTmuxSession, F as markConversationEnded, G as setConversationHarness, K as setConversationModel, M as listConversations, P as markConversationActive, Q as updateConversationDeliveryMethod, S as getConversationByName, W as setConversationEffort, g as backfillConversationModel, h as archiveConversation, k as init_conversations, nt as updateLastAttached, rt as updateSpawnError, x as getConversationById, y as createConversation } from "./conversations-BUpYsOZg.js";
25
+ import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population--YUXU999.js";
26
+ import { d as generateLauncherScriptSync, f as init_launcher_generator, g as requireModelOverrideSync, h as normalizeModelOverrideSync, m as init_model_validation, p as MODEL_ID_PATTERN } from "./tmux-dedup-DbN6MGzM.js";
27
+ import { a as init_claude_permissions, o as resolvePermissionModeSync, r as getClaudePermissionFlagsStringSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-CN0cSCPp.js";
28
+ import { E as init_layers, I as workspaceContextFile, k as piGlobalContextFile } from "./assemble-DnffNpOo.js";
29
+ import { i as prepareHarnessLaunch, r as init_harness_binary } from "./harness-binary-DqkftwTb.js";
30
+ import { a as getWorkspaceStackHealth, s as init_stack_health } from "./stack-health-DqSqx0zv.js";
31
+ import { l as init_dag, o as getDispatchableItems } from "./dag-ombYYt4s.js";
32
+ import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-CqMqM1Mb.js";
33
+ import { a as loadCloisterConfigSync, r as init_config } from "./config-B2IMcBxx.js";
34
+ import { c as isStateMigrated, o as init_state_home } from "./state-home-BYV7qZTW.js";
35
+ import { a as init_lifecycle_io, c as updatePlanStatus, s as transitionXBriefOnMain } from "./lifecycle-io-D-lGdzK3.js";
36
+ import { _ as init_codex_conversation_parser, b as parseAcpConversationMessages, c as parseEntireConversation, d as init_pi_conversation_parser, g as parseOhmypiConversationMessages, m as init_ohmypi_conversation_parser, p as parsePiConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages, y as init_acp_conversation_parser } from "./conversation-service-5aCnq1zR.js";
37
+ import { _ as normalizeAwaitingInputPrompt, d as getAgentPendingQuestions$1, f as getAgentWorkspace$1, h as detectAwaitingInputForAgent, m as scanPendingInputsPromise, o as getRuntimeForAgent, p as isBlockedOnPendingInput, s as computeAgentEnrichment, u as getAgentJsonlPath$1, v as parseCodexApprovalPrompt } from "./stuck-remediation-mkLC6G_t.js";
38
+ import { B as getBook, H as liveOrderIssueLookup, T as resolveLiveFlywheelRunId, w as readFlywheelLaunchMetadata, y as getFlywheelRunDir, z as computeBookProgress } from "./flywheel-BiT3odRb.js";
39
+ import { Ft as getResourceConfig, Lt as getSystemHealthSnapshot, Ot as classifyMemoryPressure } from "./deacon-hi9KLPLT.js";
40
+ import { o as stopWorkspaceDocker, r as init_workspace_manager } from "./workspace-manager-CwO-Hek7.js";
41
+ import { a as init_jsonl_parser, o as normalizeModelName } from "./jsonl-parser-DJOH_O-w.js";
42
+ import { r as init_in_flight_guard, t as createInFlightGuard } from "./in-flight-guard-Bq3epG58.js";
43
+ import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-CQv00QwK.js";
44
+ import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
43
45
  import { Cause, Data, Effect, Layer, Option, Schema, Stream } from "effect";
44
46
  import { join as join$1 } from "path";
45
47
  import { homedir, networkInterfaces } from "node:os";
@@ -51,9 +53,9 @@ import { mkdir as mkdir$1, writeFile as writeFile$1 } from "fs/promises";
51
53
  import { exec, execFile, spawn } from "node:child_process";
52
54
  import { promisify } from "node:util";
53
55
  import { createHash as createHash$1, createHmac, randomBytes, randomUUID as randomUUID$1, timingSafeEqual } from "node:crypto";
56
+ import { createInterface } from "node:readline";
54
57
  import { pathToFileURL } from "node:url";
55
58
  import { request } from "node:http";
56
- import { createInterface } from "node:readline";
57
59
  import * as Result$1 from "effect/Result";
58
60
  //#region http-helpers.ts
59
61
  init_paths();
@@ -85,6 +87,7 @@ init_projects();
85
87
  init_providers();
86
88
  init_harness_policy();
87
89
  init_tier_table();
90
+ init_js_yaml();
88
91
  init_config_yaml();
89
92
  init_child_env();
90
93
  init_tmux();
@@ -100,6 +103,7 @@ init_cost();
100
103
  init_behavior();
101
104
  init_persistent_logger();
102
105
  init_tracker_config();
106
+ init_project_repos();
103
107
  init_lifecycle();
104
108
  init_review_status();
105
109
  init_io();
@@ -200,10 +204,12 @@ init_layers();
200
204
  init_harness_binary();
201
205
  init_ohmypi_fifo();
202
206
  init_pi_fifo();
207
+ init_runtime_command();
203
208
  init_stack_health();
204
209
  init_launcher_generator();
205
210
  init_dag();
206
211
  init_staffing();
212
+ init_spawn_prep();
207
213
  init_harness_resolve();
208
214
  init_bridge_token();
209
215
  init_operator_interventions();
@@ -215,6 +221,27 @@ function addTrustedOrigin(origins, raw) {
215
221
  const normalized = normalizeOrigin(raw.trim());
216
222
  if (normalized) origins.add(normalized);
217
223
  }
224
+ /**
225
+ * Last-resort origin config for launches that carry no origin env at all
226
+ * (manual `node dist/...`, ad-hoc re-exec). `pan up` builds its launch env from
227
+ * this same config.yaml, so reading it here keeps every launch path trusting the
228
+ * same Traefik domain instead of 403ing /ws/* into a "Reconnecting" loop.
229
+ */
230
+ function readTraefikConfigFromYaml() {
231
+ try {
232
+ const configPath = join(process.env["OVERDECK_HOME"] || join(homedir(), ".overdeck"), "config.yaml");
233
+ if (!existsSync(configPath)) return null;
234
+ const traefik = jsYaml.load(readFileSync(configPath, "utf8"))?.traefik;
235
+ if (!traefik || typeof traefik !== "object") return null;
236
+ if (typeof traefik.domain !== "string" || traefik.domain.trim() === "") return null;
237
+ return {
238
+ enabled: traefik.enabled === true,
239
+ domain: traefik.domain.trim()
240
+ };
241
+ } catch {
242
+ return null;
243
+ }
244
+ }
218
245
  function getTrustedOrigins() {
219
246
  if (cachedTrustedOrigins !== void 0) return cachedTrustedOrigins;
220
247
  const port = parseInt(process.env["API_PORT"] ?? process.env["PORT"] ?? "3011", 10);
@@ -229,6 +256,10 @@ function getTrustedOrigins() {
229
256
  for (const origin of trustedOrigins?.split(",") ?? []) addTrustedOrigin(origins, origin);
230
257
  const traefikDomain = process.env["OVERDECK_TRAEFIK_DOMAIN"] ?? process.env["TRAEFIK_DOMAIN"];
231
258
  if (process.env["OVERDECK_TRAEFIK_ENABLED"] === "1" && traefikDomain) addTrustedOrigin(origins, `https://${traefikDomain}`);
259
+ if (!(process.env["OVERDECK_TRAEFIK_ENABLED"] !== void 0 || traefikDomain !== void 0 || process.env["OVERDECK_TRUSTED_ORIGINS"] !== void 0)) {
260
+ const fromYaml = readTraefikConfigFromYaml();
261
+ if (fromYaml?.enabled) addTrustedOrigin(origins, `https://${fromYaml.domain}`);
262
+ }
232
263
  if (process.env["NODE_ENV"] === "development") {
233
264
  addTrustedOrigin(origins, "http://localhost:3000");
234
265
  addTrustedOrigin(origins, "http://127.0.0.1:3000");
@@ -364,7 +395,215 @@ async function waitForSessionRespawn(sessionName, timeoutMs) {
364
395
  return Effect.runPromise(sessionExists(sessionName));
365
396
  }
366
397
  //#endregion
398
+ //#region routes/dashboard-auth.ts
399
+ init_state_read_home();
400
+ init_state_plane();
401
+ init_types();
402
+ init_issue_lifecycle();
403
+ init_codex_auth();
404
+ init_config();
405
+ init_state_home();
406
+ init_internal_token();
407
+ const DASHBOARD_SESSION_COOKIE = "overdeck_session";
408
+ const DASHBOARD_CSRF_HEADER = "x-overdeck-csrf-token";
409
+ const DASHBOARD_SESSION_MAX_AGE_SECONDS = 3600 * 24 * 30;
410
+ let browserSessionToken;
411
+ let browserCsrfToken;
412
+ function getDashboardSessionToken() {
413
+ if (browserSessionToken) return browserSessionToken;
414
+ const override = process.env["OVERDECK_DASHBOARD_SESSION_TOKEN"];
415
+ if (override) {
416
+ browserSessionToken = override;
417
+ return browserSessionToken;
418
+ }
419
+ const internal = getInternalTokenSync();
420
+ browserSessionToken = internal ? createHmac("sha256", internal).update("overdeck-dashboard-session-v1").digest("base64url") : randomBytes(32).toString("base64url");
421
+ return browserSessionToken;
422
+ }
423
+ function dashboardCsrfToken() {
424
+ if (browserCsrfToken) return browserCsrfToken;
425
+ const override = process.env["OVERDECK_DASHBOARD_CSRF_TOKEN"];
426
+ if (override) {
427
+ browserCsrfToken = override;
428
+ return browserCsrfToken;
429
+ }
430
+ const internal = getInternalTokenSync();
431
+ browserCsrfToken = internal ? createHmac("sha256", internal).update("overdeck-dashboard-csrf-v1").digest("base64url") : randomBytes(32).toString("base64url");
432
+ return browserCsrfToken;
433
+ }
434
+ function constantTimeTokenEqual$1(provided, expected) {
435
+ if (!provided) return false;
436
+ return timingSafeEqual(createHash$1("sha256").update(provided).digest(), createHash$1("sha256").update(expected).digest());
437
+ }
438
+ function cookieValue(cookieHeader, name) {
439
+ if (!cookieHeader) return void 0;
440
+ for (const part of cookieHeader.split(";")) {
441
+ const [rawKey, ...rawValue] = part.trim().split("=");
442
+ if (rawKey !== name) continue;
443
+ try {
444
+ return decodeURIComponent(rawValue.join("="));
445
+ } catch {
446
+ return;
447
+ }
448
+ }
449
+ }
450
+ function dashboardSessionCookieHeader(options = {}) {
451
+ const secure = options.secure ? "; Secure" : "";
452
+ return `${DASHBOARD_SESSION_COOKIE}=${encodeURIComponent(getDashboardSessionToken())}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${DASHBOARD_SESSION_MAX_AGE_SECONDS}${secure}`;
453
+ }
454
+ function hasDashboardInternalTokenHeaders(headers) {
455
+ const expected = getInternalTokenSync();
456
+ if (!expected) return false;
457
+ if (constantTimeTokenEqual$1(getHeaderFromMap(headers, "x-overdeck-internal-token"), expected)) return true;
458
+ const authorization = getHeaderFromMap(headers, "authorization");
459
+ if (authorization) {
460
+ const [scheme, token] = authorization.split(/\s+/);
461
+ if (scheme?.toLowerCase() === "bearer" && constantTimeTokenEqual$1(token, expected)) return true;
462
+ }
463
+ return false;
464
+ }
465
+ function hasDashboardInternalToken(request) {
466
+ return hasDashboardInternalTokenHeaders(request.headers);
467
+ }
468
+ function hasDashboardAuthHeaders(headers) {
469
+ return hasDashboardInternalTokenHeaders(headers) || constantTimeTokenEqual$1(cookieValue(getHeaderFromMap(headers, "cookie"), "overdeck_session"), getDashboardSessionToken());
470
+ }
471
+ function hasDashboardAuth(request) {
472
+ return hasDashboardAuthHeaders(request.headers);
473
+ }
474
+ function isJsonContentType(headers) {
475
+ const contentType = getHeaderFromMap(headers, "content-type");
476
+ if (!contentType) return false;
477
+ const [mime] = contentType.toLowerCase().split(";");
478
+ return mime.trim() === "application/json";
479
+ }
480
+ function hasTrustedExactOrigin(headers) {
481
+ const origin = getHeaderFromMap(headers, "origin");
482
+ if (!origin) return false;
483
+ const normalized = normalizeOrigin(origin);
484
+ return !!normalized && getTrustedOrigins().includes(normalized);
485
+ }
486
+ function hasValidCsrfToken(headers) {
487
+ return constantTimeTokenEqual$1(getHeaderFromMap(headers, DASHBOARD_CSRF_HEADER), dashboardCsrfToken());
488
+ }
489
+ function rejectUnsafeDashboardMutationRequest(request) {
490
+ const authError = rejectUnauthorizedDashboardRequest(request);
491
+ if (authError) return authError;
492
+ const headers = request.headers;
493
+ if (!isJsonContentType(headers)) return jsonResponse({ error: "Content-Type must be application/json" }, { status: 400 });
494
+ if (hasDashboardInternalToken(request)) return null;
495
+ if (getHeaderFromMap(headers, "origin") && !hasTrustedExactOrigin(headers)) return jsonResponse({ error: "Invalid origin" }, { status: 403 });
496
+ if (!hasValidCsrfToken(headers)) return jsonResponse({ error: "Invalid CSRF token" }, { status: 403 });
497
+ return null;
498
+ }
499
+ function ipv4ToInt(value) {
500
+ const parts = value.split(".");
501
+ if (parts.length !== 4) return null;
502
+ let result = 0;
503
+ for (const part of parts) {
504
+ const octet = Number(part);
505
+ if (!Number.isInteger(octet) || octet < 0 || octet > 255) return null;
506
+ result = (result << 8 | octet) >>> 0;
507
+ }
508
+ return result;
509
+ }
510
+ /** RFC1918 private ranges — Docker bridges always fall inside one of these. */
511
+ function isRfc1918(ip) {
512
+ return (ip & 4278190080) >>> 0 === 167772160 || (ip & 4293918720) >>> 0 === 2886729728 || (ip & 4294901760) >>> 0 === 3232235520;
513
+ }
514
+ /**
515
+ * True when `addr` is the IP of a container on one of THIS host's Docker bridge
516
+ * interfaces (docker0 / br-*). Such a peer can only be a process running on this
517
+ * machine — our own host-local Traefik fronting overdeck.localhost, or another
518
+ * Overdeck container — never a LAN client (a direct LAN hit to the 0.0.0.0
519
+ * API port arrives with a LAN peer that is not inside any Docker bridge subnet).
520
+ *
521
+ * Pure for testability: the interface map is injected. We only ever trust
522
+ * RFC1918 bridge subnets, so a misconfigured public-range br-* cannot widen
523
+ * trust beyond the local Docker fabric.
524
+ */
525
+ function peerIsHostLocalDockerBridge(addr, interfaces) {
526
+ const peer = ipv4ToInt(addr);
527
+ if (peer === null) return false;
528
+ for (const [name, addrs] of Object.entries(interfaces)) {
529
+ if (name !== "docker0" && !name.startsWith("br-")) continue;
530
+ for (const info of addrs ?? []) {
531
+ if (info.family !== "IPv4") continue;
532
+ const ifaceIp = ipv4ToInt(info.address);
533
+ const mask = ipv4ToInt(info.netmask);
534
+ if (ifaceIp === null || mask === null) continue;
535
+ const network = (ifaceIp & mask) >>> 0;
536
+ if (!isRfc1918(network)) continue;
537
+ if ((peer & mask) >>> 0 === network) return true;
538
+ }
539
+ }
540
+ return false;
541
+ }
542
+ /**
543
+ * True when `addr` shares an RFC1918 subnet with one of THIS process's own
544
+ * network interfaces. This is the in-container counterpart of
545
+ * peerIsHostLocalDockerBridge: inside a container the Docker bridge is
546
+ * invisible — the container sees only its veth endpoints (eth0/eth1), so the
547
+ * docker0/br-* name filter never matches and the host-local Traefik fronting
548
+ * the stack 401'd every session mint (first hit live on the PAN-1737 UAT
549
+ * stack). A peer inside one of the container's attached Docker-network
550
+ * subnets can only be another container on those networks — the server
551
+ * publishes no host port (Traefik labels only), so no LAN client can ever
552
+ * present such an address. Only consulted when running inside a container.
553
+ */
554
+ function peerIsLocalContainerNetwork(addr, interfaces) {
555
+ const peer = ipv4ToInt(addr);
556
+ if (peer === null) return false;
557
+ for (const addrs of Object.values(interfaces)) for (const info of addrs ?? []) {
558
+ if (info.family !== "IPv4" || info.internal) continue;
559
+ const ifaceIp = ipv4ToInt(info.address);
560
+ const mask = ipv4ToInt(info.netmask);
561
+ if (ifaceIp === null || mask === null) continue;
562
+ const network = (ifaceIp & mask) >>> 0;
563
+ if (!isRfc1918(network)) continue;
564
+ if ((peer & mask) >>> 0 === network) return true;
565
+ }
566
+ return false;
567
+ }
568
+ /** Docker creates /.dockerenv in every container — stable in-container signal. */
569
+ const runningInContainer = existsSync("/.dockerenv");
570
+ /**
571
+ * A request we trust to mint a session because it originated from this machine —
572
+ * the local browser hitting the dashboard directly (127.0.0.1), or the
573
+ * host-local Traefik that fronts overdeck.localhost. In Docker, that Traefik reaches
574
+ * the host server from a Docker-bridge IP (e.g. 172.18.0.2), NOT 127.0.0.1, so
575
+ * a literal-loopback check alone silently breaks the zero-step bootstrap for
576
+ * every overdeck.localhost user — their session mint 401s and no cookie is ever set.
577
+ *
578
+ * This is the auto-bootstrap that removes the manual one-time #overdeck_token
579
+ * step: any browser on overdeck.localhost gets a session with no user action. It is
580
+ * NOT a security downgrade for the LAN — the raw API ports bind 0.0.0.0, but a
581
+ * direct LAN hit has a non-loopback, non-Docker-bridge peer and is rejected. We
582
+ * read ONLY the real TCP peer (request.remoteAddress), never X-Forwarded-For,
583
+ * which a caller could spoof.
584
+ */
585
+ function isLoopbackPeer(request) {
586
+ const remoteAddress = request.remoteAddress;
587
+ const raw = remoteAddress && typeof remoteAddress === "object" && "_tag" in remoteAddress ? Option.getOrElse(remoteAddress, () => "") : typeof remoteAddress === "string" ? remoteAddress : "";
588
+ if (!raw) return false;
589
+ const addr = raw.startsWith("::ffff:") ? raw.slice(7) : raw;
590
+ if (addr === "127.0.0.1" || addr === "::1") return true;
591
+ if (peerIsHostLocalDockerBridge(addr, networkInterfaces())) return true;
592
+ return runningInContainer && peerIsLocalContainerNetwork(addr, networkInterfaces());
593
+ }
594
+ function rejectUnauthorizedDashboardSessionMintRequest(request) {
595
+ if (!getInternalTokenSync()) return jsonResponse({ error: "dashboard session token not configured" }, { status: 503 });
596
+ if (!hasDashboardInternalToken(request) && !isLoopbackPeer(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
597
+ return null;
598
+ }
599
+ function rejectUnauthorizedDashboardRequest(request) {
600
+ if (!getInternalTokenSync()) return jsonResponse({ error: "dashboard session token not configured" }, { status: 503 });
601
+ if (!hasDashboardAuth(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
602
+ return null;
603
+ }
604
+ //#endregion
367
605
  //#region routes/jsonl-resolver.ts
606
+ init_acp_conversation_parser();
368
607
  init_codex_conversation_parser();
369
608
  init_ohmypi_conversation_parser();
370
609
  init_pi_conversation_parser();
@@ -390,6 +629,8 @@ init_conversation_service();
390
629
  * order no longer reflects time-recency. We disambiguate
391
630
  * across all listed IDs by JSONL mtime.
392
631
  * 3. runtime state — in-process mirror, populated by hooks
632
+ * 4. agents registry — the DB row's session_id recorded at spawn; survives
633
+ * janitor removal of the agent directory
393
634
  *
394
635
  * Async-only (fs/promises) because this code path runs inside the dashboard
395
636
  * server's event loop.
@@ -475,6 +716,10 @@ async function resolveClaudeSessionId(agentId, opts = {}) {
475
716
  const runtimeState = await (opts.getRuntimeStateAsync ?? ((id) => Effect.runPromise(getAgentRuntimeState(id))))(agentId);
476
717
  if (runtimeState?.claudeSessionId) return runtimeState.claudeSessionId;
477
718
  } catch {}
719
+ if (!opts.agentsDirOverride) {
720
+ const registrySessionId = getAgentStateSync(agentId)?.sessionId?.trim();
721
+ if (registrySessionId) return registrySessionId;
722
+ }
478
723
  return null;
479
724
  }
480
725
  /**
@@ -532,7 +777,7 @@ async function readRecordedState(agentId, opts) {
532
777
  * Resolve the harness for transcript routing. Reads state.json, then
533
778
  * self-corrects when the recorded value is stale.
534
779
  *
535
- * Trust an explicit non-default harness (codex / pi / ohmypi) — it was chosen
780
+ * Trust an explicit non-default harness (codex / pi / ohmypi / acp) — it was chosen
536
781
  * by resolveHarness at spawn time and is not the generic fallback. The
537
782
  * 'claude-code' default, however, goes stale in one observed case: a
538
783
  * wipe-and-respawn that changed the provider-default harness left state.json
@@ -550,8 +795,9 @@ async function readRecordedState(agentId, opts) {
550
795
  */
551
796
  async function resolveAgentHarness(agentId, opts = {}) {
552
797
  const recorded = (await readRecordedState(agentId, opts)).harness;
553
- if (recorded === "codex" || recorded === "pi" || recorded === "ohmypi") return recorded;
798
+ if (recorded === "codex" || recorded === "pi" || recorded === "ohmypi" || recorded === "acp") return recorded;
554
799
  if (await agentHasClaudeTranscript(agentId, opts)) return recorded ?? "claude-code";
800
+ if (await resolveAcpTranscriptPath(agentId, opts)) return "acp";
555
801
  if (await resolveCodexRolloutPath(agentId, opts)) return "codex";
556
802
  if (await resolvePiSessionPath(agentId, opts)) return "ohmypi";
557
803
  return recorded;
@@ -581,7 +827,7 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
581
827
  const agentDir = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId);
582
828
  const codexHome = join(agentDir, "codex-home");
583
829
  if (!await pathExists$1(codexHome)) return null;
584
- const { findRolloutPath, findLatestRollout } = await import("./codex-Dg7Is5yb.js");
830
+ const { findRolloutPath, findLatestRollout } = await import("./codex-BdEsUFWh.js");
585
831
  const threadId = (await readOptional(join(agentDir, "codex-thread-id")))?.trim();
586
832
  if (threadId) {
587
833
  const rollout = findRolloutPath(codexHome, threadId);
@@ -589,6 +835,11 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
589
835
  }
590
836
  return findLatestRollout(codexHome);
591
837
  }
838
+ /** Resolve the normalized transcript written by the persistent ACP host. */
839
+ async function resolveAcpTranscriptPath(agentId, opts = {}) {
840
+ const transcriptPath = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId, "acp-session.jsonl");
841
+ return await pathExists$1(transcriptPath) ? transcriptPath : null;
842
+ }
592
843
  /**
593
844
  * Resolve the pi/kimi session JSONL (PAN-1908). Pi writes its session transcript
594
845
  * as `<iso-ts>_<session-id>.jsonl` either in the agent dir's `sessions/` subdir
@@ -599,6 +850,7 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
599
850
  async function resolvePiSessionPath(agentId, opts = {}) {
600
851
  const agentDir = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId);
601
852
  const NON_TRANSCRIPT = new Set([
853
+ "acp-session.jsonl",
602
854
  "cost-events.jsonl",
603
855
  "activity.jsonl",
604
856
  "pending-events.jsonl"
@@ -637,6 +889,7 @@ async function resolveJsonlPath(agentId, workspacePath, opts = {}) {
637
889
  const behavior = behaviorForHarness(harness);
638
890
  if (behavior.transcriptKind === "codex-rollout-jsonl") return resolveCodexRolloutPath(agentId, opts);
639
891
  if (behavior.transcriptKind === "ohmypi-jsonl") return resolvePiSessionPath(agentId, opts);
892
+ if (behavior.transcriptKind === "acp-jsonl") return resolveAcpTranscriptPath(agentId, opts);
640
893
  const claudeSessionId = await resolveClaudeSessionId(agentId, opts);
641
894
  if (!claudeSessionId) {
642
895
  logTranscriptResolution(agentId, `missing-session-id:${harness ?? "unknown"}`, `failed harness=${harness ?? "unknown"} reason=no-session-id checked=session.id,sessions.json,runtime-state`, opts);
@@ -808,7 +1061,7 @@ async function getWorkAgentLifecycleStateSnapshot(agentOrIssueId) {
808
1061
  runtimeState: runtime,
809
1062
  agentStatus,
810
1063
  canStartFresh: (!hasLiveTmuxSession || hasLiveTmuxSession && isStopped) && (!requiresSessionResetBeforeFreshStart || isOrphaned),
811
- canResumeSession: !isRunning && hasResumableBackingState && (isStopped || isCrashed),
1064
+ canResumeSession: !isRunning && hasSavedSession && hasResumableBackingState && (isStopped || isCrashed),
812
1065
  canRestartWithContext: hasAgentState && hasWorkspace,
813
1066
  canResetSession: hasSavedSession && hasResumableBackingState,
814
1067
  requiresSessionResetBeforeFreshStart,
@@ -960,41 +1213,130 @@ async function resolveAgentGitInfo(workspacePath, expectedBranch) {
960
1213
  };
961
1214
  }
962
1215
  //#endregion
963
- //#region routes/agents/shared.ts
1216
+ //#region ../../lib/composer-commands/executors.ts
1217
+ const CAPTURED_COMMAND_MAX_OUTPUT_BYTES = 64 * 1024;
1218
+ const TRUNCATION_MESSAGE = "\n\nOutput was truncated after 65,536 bytes.";
1219
+ function runCapturedCommand(argv, dependencies = {}) {
1220
+ const command = `/pan ${argv.join(" ")}`;
1221
+ const spawnCommand = dependencies.spawnPanCli ?? spawnPanCli;
1222
+ const timeoutMs = dependencies.timeoutMs ?? 3e4;
1223
+ const child = spawnCommand(argv, { stdio: [
1224
+ "ignore",
1225
+ "pipe",
1226
+ "pipe"
1227
+ ] });
1228
+ return new Promise((resolve) => {
1229
+ const chunks = [];
1230
+ let bufferedBytes = 0;
1231
+ let settled = false;
1232
+ let timeout;
1233
+ let truncated = false;
1234
+ const appendOutput = (data) => {
1235
+ if (truncated) return;
1236
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
1237
+ const remaining = CAPTURED_COMMAND_MAX_OUTPUT_BYTES - Buffer.byteLength(TRUNCATION_MESSAGE) - bufferedBytes;
1238
+ if (buffer.length <= remaining) {
1239
+ chunks.push(buffer);
1240
+ bufferedBytes += buffer.length;
1241
+ return;
1242
+ }
1243
+ if (remaining > 0) {
1244
+ chunks.push(buffer.subarray(0, remaining));
1245
+ bufferedBytes += remaining;
1246
+ }
1247
+ truncated = true;
1248
+ };
1249
+ const output = () => {
1250
+ const captured = Buffer.concat(chunks, bufferedBytes).toString("utf8");
1251
+ return truncated ? `${captured}${TRUNCATION_MESSAGE}` : captured;
1252
+ };
1253
+ const finish = (result) => {
1254
+ if (settled) return;
1255
+ settled = true;
1256
+ if (timeout !== void 0) clearTimeout(timeout);
1257
+ resolve(result);
1258
+ };
1259
+ child.stdout?.on("data", appendOutput);
1260
+ child.stderr?.on("data", appendOutput);
1261
+ child.once("error", (error) => {
1262
+ finish({
1263
+ kind: "captured",
1264
+ status: "failed",
1265
+ command,
1266
+ output: `Command ${command} could not start: ${error.message}`,
1267
+ truncated: false
1268
+ });
1269
+ });
1270
+ child.once("close", (code) => {
1271
+ const captured = output();
1272
+ finish({
1273
+ kind: "captured",
1274
+ status: code === 0 ? "completed" : "failed",
1275
+ command,
1276
+ output: captured || `Command ${command} ${code === 0 ? "completed without output." : `failed with exit code ${code ?? "unknown"}.`}`,
1277
+ truncated
1278
+ });
1279
+ });
1280
+ timeout = setTimeout(() => {
1281
+ stopChild(child);
1282
+ finish({
1283
+ kind: "captured",
1284
+ status: "failed",
1285
+ command,
1286
+ output: `Command ${command} timed out after ${Math.round(timeoutMs / 1e3)} seconds and was stopped.`,
1287
+ truncated: false
1288
+ });
1289
+ }, timeoutMs);
1290
+ });
1291
+ }
1292
+ function stopChild(child) {
1293
+ child.kill("SIGTERM");
1294
+ }
1295
+ //#endregion
1296
+ //#region ../../lib/composer-commands/detached.ts
964
1297
  init_paths();
965
- init_agents$1();
966
- init_tmux();
967
- const execAsync$2 = promisify(exec);
968
- const execFileAsync$2 = promisify(execFile);
969
- function buildPanStartArgs(input) {
970
- return [
971
- "start",
972
- input.issueId,
973
- "--local",
974
- "--model",
975
- input.model,
976
- ...input.harness ? ["--harness", input.harness] : [],
977
- ...input.allowHost ? ["--host", "--yes"] : []
978
- ];
1298
+ const DETACHED_TRUNCATION_MESSAGE = "\n\nOutput was truncated after 65,536 bytes.";
1299
+ function resolveAgentDirectory(overdeckHome, agentId) {
1300
+ if (agentId.length === 0 || agentId === "." || agentId === ".." || agentId.includes("/") || agentId.includes("\\") || isAbsolute(agentId)) throw new Error("Agent ID must be a single filesystem-safe path segment.");
1301
+ return join(resolve(overdeckHome, "agents"), agentId);
1302
+ }
1303
+ async function readBoundedSpawnOutput(spawnLogPath, startOffset) {
1304
+ const maximumPrefixBytes = CAPTURED_COMMAND_MAX_OUTPUT_BYTES - Buffer.byteLength(DETACHED_TRUNCATION_MESSAGE);
1305
+ const handle = await open(spawnLogPath, "r");
1306
+ try {
1307
+ const stats = await handle.stat();
1308
+ const outputBytes = Math.max(0, stats.size - startOffset);
1309
+ if (outputBytes === 0) return "";
1310
+ const bytesToRead = Math.min(outputBytes, maximumPrefixBytes);
1311
+ const buffer = Buffer.alloc(bytesToRead);
1312
+ const { bytesRead } = await handle.read(buffer, 0, bytesToRead, startOffset);
1313
+ const output = buffer.subarray(0, bytesRead).toString("utf8");
1314
+ return outputBytes > maximumPrefixBytes ? `${output}${DETACHED_TRUNCATION_MESSAGE}` : output;
1315
+ } finally {
1316
+ await handle.close().catch(() => void 0);
1317
+ }
979
1318
  }
980
- /**
981
- * PAN-1985: detached-spawn helper for `pan <args>`, shared between the
982
- * standard work-spawn route and the restart-fresh route. Opens a spawn.log
983
- * inside the agent dir (creating the dir if missing — the dir may have been
984
- * just wiped), spawns `pan` detached with stdio to that log, and resolves
985
- * with an activity id once the child closes with code 0. Throws an Error
986
- * with the log contents attached on non-zero exit.
987
- */
988
- async function spawnPanCommandDetached(input) {
1319
+ async function appendAgentLifecycleLog(agentId, event, details = {}, overdeckHome = getOverdeckHome()) {
1320
+ const agentDir = resolveAgentDirectory(overdeckHome, agentId);
1321
+ await mkdir(agentDir, { recursive: true });
1322
+ const logLine = JSON.stringify({
1323
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1324
+ event,
1325
+ ...details
1326
+ });
1327
+ await appendFile(join(agentDir, "lifecycle.log"), `${logLine}\n`);
1328
+ }
1329
+ async function launchPanCommandDetached(input, dependencies = {}) {
989
1330
  const { agentSessionName, issueId, role, workspacePath, args } = input;
990
1331
  const cwd = input.cwd ?? workspacePath;
991
- const activityId = `activity-${Date.now()}`;
992
- const agentDir = join(homedir(), ".overdeck", "agents", agentSessionName);
1332
+ const overdeckHome = dependencies.overdeckHome ?? getOverdeckHome();
1333
+ const activityId = input.activityId ?? `activity-${(dependencies.now ?? Date.now)()}`;
1334
+ const agentDir = resolveAgentDirectory(overdeckHome, agentSessionName);
993
1335
  await mkdir(agentDir, { recursive: true });
994
1336
  const spawnLogPath = join(agentDir, "spawn.log");
995
1337
  const spawnLogHandle = await open(spawnLogPath, "a");
996
- const invocation = panCliInvocation(args);
997
- const child = spawn(invocation.command, invocation.args, {
1338
+ const spawnLogStart = (await spawnLogHandle.stat()).size;
1339
+ const child = (dependencies.spawnPanCli ?? spawnPanCli)(args, {
998
1340
  cwd,
999
1341
  detached: true,
1000
1342
  stdio: [
@@ -1003,67 +1345,200 @@ async function spawnPanCommandDetached(input) {
1003
1345
  spawnLogHandle.fd
1004
1346
  ]
1005
1347
  });
1348
+ let spawned = false;
1349
+ let settleSpawn;
1350
+ let rejectSpawn;
1351
+ const spawnPromise = new Promise((resolve, reject) => {
1352
+ settleSpawn = resolve;
1353
+ rejectSpawn = reject;
1354
+ });
1355
+ const completion = commandCompletion(child, {
1356
+ agentSessionName,
1357
+ issueId,
1358
+ role,
1359
+ workspacePath,
1360
+ activityId,
1361
+ args,
1362
+ cwd,
1363
+ spawnLogPath,
1364
+ spawnLogStart,
1365
+ spawnLogHandle,
1366
+ overdeckHome,
1367
+ onSpawn: () => {
1368
+ spawned = true;
1369
+ settleSpawn?.();
1370
+ },
1371
+ onError: (error) => {
1372
+ if (!spawned) rejectSpawn?.(error);
1373
+ }
1374
+ });
1375
+ completion.catch(() => void 0);
1376
+ await spawnPromise;
1377
+ child.unref();
1378
+ return {
1379
+ activityId,
1380
+ spawnLogPath,
1381
+ spawnLogStart,
1382
+ completion
1383
+ };
1384
+ }
1385
+ function commandCompletion(child, context) {
1006
1386
  child.once("spawn", () => {
1007
- appendAgentLifecycleLog(agentSessionName, "agent.work_spawn_process_spawned", {
1008
- issueId,
1009
- role,
1010
- workspacePath,
1011
- activityId,
1387
+ appendAgentLifecycleLog(context.agentSessionName, "agent.work_spawn_process_spawned", {
1388
+ issueId: context.issueId,
1389
+ role: context.role,
1390
+ workspacePath: context.workspacePath,
1391
+ activityId: context.activityId,
1012
1392
  pid: child.pid,
1013
- args,
1014
- cwd,
1015
- spawnLogPath
1016
- }).catch(() => void 0);
1393
+ args: context.args,
1394
+ cwd: context.cwd,
1395
+ spawnLogPath: context.spawnLogPath
1396
+ }, context.overdeckHome).catch(() => void 0).finally(context.onSpawn);
1017
1397
  });
1018
- try {
1019
- const result = await new Promise((resolve, reject) => {
1020
- child.once("error", (error) => {
1021
- appendAgentLifecycleLog(agentSessionName, "agent.work_spawn_process_error", {
1022
- issueId,
1023
- role,
1024
- workspacePath,
1025
- activityId,
1398
+ return new Promise((resolve, reject) => {
1399
+ let settled = false;
1400
+ const finish = async (error) => {
1401
+ if (settled) return;
1402
+ settled = true;
1403
+ await context.spawnLogHandle.close().catch(() => void 0);
1404
+ if (error) reject(error);
1405
+ else resolve();
1406
+ };
1407
+ child.once("error", (error) => {
1408
+ (async () => {
1409
+ await appendAgentLifecycleLog(context.agentSessionName, "agent.work_spawn_process_error", {
1410
+ issueId: context.issueId,
1411
+ role: context.role,
1412
+ workspacePath: context.workspacePath,
1413
+ activityId: context.activityId,
1026
1414
  error: error.message,
1027
- args,
1028
- cwd,
1029
- spawnLogPath
1030
- }).catch(() => void 0);
1031
- reject(error);
1032
- });
1033
- child.once("close", (code, signal) => {
1034
- appendAgentLifecycleLog(agentSessionName, "agent.work_spawn_process_closed", {
1035
- issueId,
1036
- role,
1037
- workspacePath,
1038
- activityId,
1415
+ args: context.args,
1416
+ cwd: context.cwd,
1417
+ spawnLogPath: context.spawnLogPath
1418
+ }, context.overdeckHome).catch(() => void 0);
1419
+ context.onError(error);
1420
+ await finish(error);
1421
+ })();
1422
+ });
1423
+ child.once("close", (code, signal) => {
1424
+ (async () => {
1425
+ await appendAgentLifecycleLog(context.agentSessionName, "agent.work_spawn_process_closed", {
1426
+ issueId: context.issueId,
1427
+ role: context.role,
1428
+ workspacePath: context.workspacePath,
1429
+ activityId: context.activityId,
1039
1430
  code,
1040
1431
  signal,
1041
- args,
1042
- cwd,
1043
- spawnLogPath
1044
- }).catch(() => void 0);
1045
- resolve({
1432
+ args: context.args,
1433
+ cwd: context.cwd,
1434
+ spawnLogPath: context.spawnLogPath
1435
+ }, context.overdeckHome).catch(() => void 0);
1436
+ if (code === 0) {
1437
+ await finish();
1438
+ return;
1439
+ }
1440
+ const output = await readBoundedSpawnOutput(context.spawnLogPath, context.spawnLogStart).catch(() => "");
1441
+ const error = new Error(output.trim() || `pan ${context.args.join(" ")} exited with code ${code ?? "null"}`);
1442
+ Object.assign(error, {
1443
+ activityId: context.activityId,
1444
+ spawnLogPath: context.spawnLogPath,
1046
1445
  code,
1047
- signal
1446
+ signal,
1447
+ output
1048
1448
  });
1049
- });
1449
+ await finish(error);
1450
+ })();
1050
1451
  });
1051
- if (result.code !== 0) {
1052
- const output = await readFile(spawnLogPath, "utf-8").catch(() => "");
1053
- const error = new Error(output.trim() || `pan ${args.join(" ")} exited with code ${result.code ?? "null"}`);
1054
- Object.assign(error, {
1055
- activityId,
1056
- spawnLogPath,
1057
- code: result.code,
1058
- signal: result.signal,
1059
- output
1060
- });
1061
- throw error;
1062
- }
1063
- return activityId;
1064
- } finally {
1065
- await spawnLogHandle.close();
1452
+ });
1453
+ }
1454
+ async function runDetachedCommand(argv, dependencies = {}) {
1455
+ const issueId = argv[1] ?? "";
1456
+ const parsedIssueId = parseIssueIdSync(issueId);
1457
+ if (!parsedIssueId) return {
1458
+ kind: "terminal-only",
1459
+ status: "rejected",
1460
+ message: `/pan ${argv[0] ?? "command"} requires a canonical issue ID such as PAN-1525.`
1461
+ };
1462
+ const role = argv[0] === "plan" ? "plan" : "work";
1463
+ const agentSessionName = `${role === "plan" ? "planning" : "agent"}-${parsedIssueId.normalized}`;
1464
+ const command = `/pan ${argv.join(" ")}`;
1465
+ const activityId = `activity-${(dependencies.now ?? Date.now)()}`;
1466
+ const emitActivity = dependencies.emitActivity ?? emitActivityEntryDurable;
1467
+ const source = role === "plan" ? "planning-agent" : "work-agent";
1468
+ const emitTransition = (status, level, message, output) => emitActivity({
1469
+ id: activityId,
1470
+ source,
1471
+ level,
1472
+ status,
1473
+ command,
1474
+ message,
1475
+ details: output,
1476
+ output,
1477
+ issueId
1478
+ });
1479
+ await emitTransition("accepted", "info", `Accepted ${command} for ${issueId} as activity ${activityId}.`);
1480
+ let launch;
1481
+ try {
1482
+ launch = await launchPanCommandDetached({
1483
+ agentSessionName,
1484
+ issueId,
1485
+ role,
1486
+ workspacePath: process.cwd(),
1487
+ args: argv,
1488
+ activityId
1489
+ }, dependencies);
1490
+ } catch (error) {
1491
+ const output = error instanceof Error ? error.message : String(error);
1492
+ await emitTransition("failed", "error", `Activity ${activityId} failed to start ${command} for ${issueId}.`, output).catch(() => void 0);
1493
+ throw error;
1066
1494
  }
1495
+ await emitTransition("running", "info", `Activity ${activityId} is running ${command} for ${issueId}.`).catch(() => void 0);
1496
+ launch.completion.then(async () => {
1497
+ const output = await readBoundedSpawnOutput(launch.spawnLogPath, launch.spawnLogStart).catch(() => "");
1498
+ await emitTransition("completed", "success", `Activity ${activityId} completed ${command} for ${issueId}.`, output || `Command ${command} completed without output.`);
1499
+ }, async (error) => {
1500
+ const activityError = error;
1501
+ const output = activityError.output?.trim() || activityError.message;
1502
+ await emitTransition("failed", "error", `Activity ${activityId} failed ${command} for ${issueId}.`, output);
1503
+ }).catch(() => void 0);
1504
+ return {
1505
+ kind: "activity",
1506
+ status: "accepted",
1507
+ command,
1508
+ activityId: launch.activityId,
1509
+ message: `Started ${command} for ${issueId}. Watch activity ${launch.activityId} or the ${issueId} issue view for progress.`
1510
+ };
1511
+ }
1512
+ //#endregion
1513
+ //#region routes/agents/shared.ts
1514
+ init_paths();
1515
+ init_agents$1();
1516
+ init_activity_logger();
1517
+ init_tmux();
1518
+ const execAsync$2 = promisify(exec);
1519
+ const execFileAsync$2 = promisify(execFile);
1520
+ function buildPanStartArgs(input) {
1521
+ return [
1522
+ "start",
1523
+ input.issueId,
1524
+ "--local",
1525
+ "--model",
1526
+ input.model,
1527
+ ...input.harness ? ["--harness", input.harness] : [],
1528
+ ...input.allowHost ? ["--host", "--yes"] : [],
1529
+ ...input.offBook ? ["--off-book"] : []
1530
+ ];
1531
+ }
1532
+ /**
1533
+ * PAN-1985: detached-spawn helper for `pan <args>`, shared between the
1534
+ * standard work-spawn route and the restart-fresh route. The neutral launcher
1535
+ * owns process/log lifecycle; this route-compatible wrapper preserves the
1536
+ * original behavior by waiting for a successful child exit.
1537
+ */
1538
+ async function spawnPanCommandDetached(input, dependencies = {}) {
1539
+ const launch = await launchPanCommandDetached(input, dependencies);
1540
+ await launch.completion;
1541
+ return launch.activityId;
1067
1542
  }
1068
1543
  function emitStartAgentPhase(issueId, phase, status, reason, details = {}) {
1069
1544
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -1083,7 +1558,7 @@ function emitStartAgentPhase(issueId, phase, status, reason, details = {}) {
1083
1558
  });
1084
1559
  }
1085
1560
  const INTERNAL_TOKEN_HEADER = "x-overdeck-internal-token";
1086
- function constantTimeTokenEqual$1(provided, expected) {
1561
+ function constantTimeTokenEqual(provided, expected) {
1087
1562
  if (!provided) return false;
1088
1563
  const providedBuffer = Buffer.from(provided, "utf8");
1089
1564
  const expectedBuffer = Buffer.from(expected, "utf8");
@@ -1109,7 +1584,7 @@ async function validateAgentRuntimeEventAuth(request) {
1109
1584
  error: "internal token not configured"
1110
1585
  }, { status: 503 })
1111
1586
  };
1112
- if (constantTimeTokenEqual$1(getHeaderFromMap(request.headers, INTERNAL_TOKEN_HEADER), expected)) return { ok: true };
1587
+ if (constantTimeTokenEqual(getHeaderFromMap(request.headers, INTERNAL_TOKEN_HEADER), expected)) return { ok: true };
1113
1588
  return {
1114
1589
  ok: false,
1115
1590
  response: jsonResponse({
@@ -1118,16 +1593,6 @@ async function validateAgentRuntimeEventAuth(request) {
1118
1593
  }, { status: 403 })
1119
1594
  };
1120
1595
  }
1121
- async function appendAgentLifecycleLog(agentId, event, details = {}) {
1122
- const agentDir = join(homedir(), ".overdeck", "agents", agentId);
1123
- await mkdir(agentDir, { recursive: true });
1124
- const logLine = JSON.stringify({
1125
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1126
- event,
1127
- ...details
1128
- });
1129
- await appendFile(join(agentDir, "lifecycle.log"), logLine + "\n");
1130
- }
1131
1596
  function updateRegistryForAgentStart(issueId, workspacePath, agentId) {
1132
1597
  recordFeatureRegistryLifecycle({
1133
1598
  issueId,
@@ -1311,13 +1776,14 @@ async function getWorkspaceLocation(issueId) {
1311
1776
  }
1312
1777
  } catch {}
1313
1778
  }
1314
- async function getGitStatusAsync(workspacePath) {
1779
+ async function getGitStatusAsync(issueId, workspacePath) {
1315
1780
  try {
1316
1781
  if (!existsSync(workspacePath)) return null;
1782
+ const repoDir = resolvePrimaryWorkspaceRepoDirSync(issueId, workspacePath);
1317
1783
  const [branchResult, uncommittedResult, commitResult] = await Promise.all([
1318
- execAsync$2("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"", { cwd: workspacePath }),
1319
- execAsync$2("git status --porcelain 2>/dev/null | wc -l", { cwd: workspacePath }),
1320
- execAsync$2("git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"", { cwd: workspacePath })
1784
+ execAsync$2("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"", { cwd: repoDir }),
1785
+ execAsync$2("git status --porcelain 2>/dev/null | wc -l", { cwd: repoDir }),
1786
+ execAsync$2("git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"", { cwd: repoDir })
1321
1787
  ]);
1322
1788
  const branch = branchResult.stdout.trim();
1323
1789
  const uncommitted = uncommittedResult.stdout.trim();
@@ -1474,6 +1940,22 @@ const getAgentPendingQuestions = getAgentPendingQuestions$1;
1474
1940
  //#region routes/agents/listing.ts
1475
1941
  init_agents$1();
1476
1942
  init_tmux();
1943
+ /**
1944
+ * PAN-3070 — the health status a live agent row reports.
1945
+ *
1946
+ * A live tmux session used to mean `healthy` unconditionally, so an agent frozen
1947
+ * for hours on an unanswered tool-permission prompt was reported
1948
+ * `status: healthy` / `resolution: working` while the Decisions surface
1949
+ * simultaneously showed it as needing the operator. Two surfaces answering "is
1950
+ * this agent stuck?" from different evidence, and this one stale.
1951
+ *
1952
+ * `warning` is the value `src/lib/health.ts` already reports for an agent
1953
+ * waiting on a human, and it stays inside the fleet-view status set, so a
1954
+ * blocked agent is flagged without disappearing from any existing list.
1955
+ */
1956
+ function liveAgentHealthStatus(enrichment) {
1957
+ return isBlockedOnPendingInput(enrichment) ? "warning" : "healthy";
1958
+ }
1477
1959
  const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.gen(function* () {
1478
1960
  const now = Date.now();
1479
1961
  if (agentsCache.data && now - agentsCache.timestamp < 5e3) return jsonResponse(agentsCache.data);
@@ -1607,7 +2089,7 @@ const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.g
1607
2089
  const enrichment = await Effect.runPromise(computeAgentEnrichment(name, startedAt, hasActiveSpecialist));
1608
2090
  const workspaceLocation = isRemote ? "remote" : await getWorkspaceLocation(issueId);
1609
2091
  const workspace = isRemote && remoteState.vmName ? `/workspace (${String(remoteState.vmName)})` : state.workspace || null;
1610
- const gitStatus = workspace && !isRemote ? await getGitStatusAsync(workspace) : null;
2092
+ const gitStatus = workspace && !isRemote ? await getGitStatusAsync(issueId, workspace) : null;
1611
2093
  let contextPercent = null;
1612
2094
  let initialContextPercent = null;
1613
2095
  const agentCtxDir = join(homedir(), ".overdeck", "agents", name);
@@ -1617,12 +2099,13 @@ const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.g
1617
2099
  const initCtxFile = join(agentCtxDir, "initial-context-pct");
1618
2100
  initialContextPercent = parseInt((await readFile(initCtxFile, "utf-8").catch(() => "")).trim(), 10) || null;
1619
2101
  } catch {}
2102
+ const blockedOnPendingInput = isBlockedOnPendingInput(enrichment);
1620
2103
  return {
1621
2104
  id: name,
1622
2105
  issueId,
1623
2106
  runtime: state.harness ?? "claude-code",
1624
2107
  model: state.model || (isPlanning ? "opus" : "sonnet"),
1625
- status: "healthy",
2108
+ status: liveAgentHealthStatus(enrichment),
1626
2109
  startedAt,
1627
2110
  ...buildAgentGateFailureSnapshot(state),
1628
2111
  killCount: health.killCount || 0,
@@ -1639,7 +2122,7 @@ const getAgentsRoute = HttpRouter.add("GET", "/api/agents", httpHandler(Effect.g
1639
2122
  pendingInputCount: enrichment.pendingInputCount,
1640
2123
  pendingInputKinds: enrichment.pendingInputKinds,
1641
2124
  pendingAskUserQuestion: enrichment.pendingAskUserQuestion,
1642
- resolution: runtimeData?.resolution || enrichment.resolution || "working",
2125
+ resolution: blockedOnPendingInput ? "needs_input" : runtimeData?.resolution || enrichment.resolution || "working",
1643
2126
  resolutionCount: runtimeData?.resolutionCount || enrichment.resolutionCount || 0,
1644
2127
  contextPercent,
1645
2128
  initialContextPercent,
@@ -1725,7 +2208,7 @@ const getAgentOutputRoute = HttpRouter.add("GET", "/api/agents/:id/output", http
1725
2208
  } catch {}
1726
2209
  let stdout;
1727
2210
  if (isRemote && vmName) {
1728
- const { getRemoteAgentOutput } = await import("./remote-agents-CRCg1L4H.js");
2211
+ const { getRemoteAgentOutput } = await import("./remote-agents-C1bssi41.js");
1729
2212
  stdout = await getRemoteAgentOutput(id, vmName, parseInt(String(lines), 10) || 100);
1730
2213
  } else stdout = await Effect.runPromise(capturePane(id, parseInt(String(lines), 10) || 100));
1731
2214
  if (!stdout || stdout.trim() === "" || stdout.trim() === "Session not found") {
@@ -1790,6 +2273,20 @@ async function buildConversationResponse(id) {
1790
2273
  streaming: false
1791
2274
  };
1792
2275
  }
2276
+ if (harness === "acp") {
2277
+ const sessionFile = await resolveAcpTranscriptPath(id);
2278
+ if (!sessionFile || !existsSync(sessionFile)) return EMPTY_CONVERSATION;
2279
+ const result = await parseAcpConversationMessages(sessionFile);
2280
+ return {
2281
+ ...result,
2282
+ messages: result.messages.map((message) => message.role === "assistant" ? {
2283
+ ...message,
2284
+ completedAt: message.completedAt ?? message.createdAt,
2285
+ streaming: false
2286
+ } : message),
2287
+ streaming: false
2288
+ };
2289
+ }
1793
2290
  let jsonlPath = null;
1794
2291
  const pinnedSessionId = await readLauncherPinnedSessionId(id);
1795
2292
  if (pinnedSessionId) {
@@ -1864,6 +2361,352 @@ const getAgentTimelineRoute = HttpRouter.add("GET", "/api/agents/:id/timeline",
1864
2361
  return jsonResponse({ timeline: events.slice(0, limit) });
1865
2362
  })));
1866
2363
  //#endregion
2364
+ //#region ../../lib/composer-commands/confirmations.ts
2365
+ const COMPOSER_CONFIRMATION_TTL_MS = 300 * 1e3;
2366
+ function composerCommandConfirmationFromBody(body) {
2367
+ if (!("confirmationNonce" in body) && !("confirmationText" in body)) return void 0;
2368
+ return {
2369
+ nonce: body["confirmationNonce"],
2370
+ typedText: body["confirmationText"]
2371
+ };
2372
+ }
2373
+ var ComposerCommandConfirmationError = class extends Error {
2374
+ code = "invalid-confirmation";
2375
+ constructor(message) {
2376
+ super(message);
2377
+ this.name = "ComposerCommandConfirmationError";
2378
+ }
2379
+ };
2380
+ function createComposerConfirmationStore(dependencies = {}) {
2381
+ const entries = /* @__PURE__ */ new Map();
2382
+ const now = dependencies.now ?? Date.now;
2383
+ const createNonce = dependencies.createNonce ?? (() => randomBytes(24).toString("base64url"));
2384
+ function pruneExpired() {
2385
+ const currentTime = now();
2386
+ for (const [nonce, entry] of entries) if (entry.expiresAt <= currentTime) entries.delete(nonce);
2387
+ }
2388
+ return {
2389
+ issue({ target, argv, policy, display }) {
2390
+ if (policy.safety === "destructive" && !policy.typedConfirmation) throw new Error(`Destructive composer policy for ${display} must define typedConfirmation.`);
2391
+ pruneExpired();
2392
+ const nonce = createNonce();
2393
+ entries.set(nonce, {
2394
+ targetKind: target.kind,
2395
+ targetId: target.id,
2396
+ argv: [...argv],
2397
+ safety: policy.safety,
2398
+ typedText: policy.typedConfirmation,
2399
+ expiresAt: now() + COMPOSER_CONFIRMATION_TTL_MS
2400
+ });
2401
+ return {
2402
+ kind: "confirmation",
2403
+ status: "confirmation_required",
2404
+ nonce,
2405
+ consequence: policy.confirmationText ?? `Running ${display} requires confirmation before Overdeck continues.`,
2406
+ ...policy.typedConfirmation ? { typedText: policy.typedConfirmation } : {}
2407
+ };
2408
+ },
2409
+ consume({ target, argv, policy, confirmation }) {
2410
+ if (typeof confirmation.nonce !== "string") throw new ComposerCommandConfirmationError("A valid confirmation nonce is required before this command can run.");
2411
+ const entry = entries.get(confirmation.nonce);
2412
+ entries.delete(confirmation.nonce);
2413
+ if (!entry) throw new ComposerCommandConfirmationError("This command confirmation is missing, invalid, or has already been used. Request a new confirmation and try again.");
2414
+ if (entry.expiresAt <= now()) throw new ComposerCommandConfirmationError("This command confirmation has expired. Request a new confirmation and try again.");
2415
+ if (entry.targetKind !== target.kind || entry.targetId !== target.id || entry.safety !== policy.safety || !sameArgv(entry.argv, argv)) throw new ComposerCommandConfirmationError("This command confirmation does not match the current target or command. Request a new confirmation and try again.");
2416
+ if (policy.safety === "destructive" && (typeof confirmation.typedText !== "string" || confirmation.typedText !== entry.typedText)) throw new ComposerCommandConfirmationError(`Type "${entry.typedText ?? ""}" exactly to confirm this destructive command.`);
2417
+ }
2418
+ };
2419
+ }
2420
+ function sameArgv(left, right) {
2421
+ return left.length === right.length && left.every((value, index) => value === right[index]);
2422
+ }
2423
+ const composerConfirmationStore = createComposerConfirmationStore();
2424
+ //#endregion
2425
+ //#region ../../lib/composer-commands/parser.ts
2426
+ var ComposerCommandParseError = class extends Error {
2427
+ constructor(code, message, token, expected) {
2428
+ super(message);
2429
+ this.code = code;
2430
+ this.token = token;
2431
+ this.expected = expected;
2432
+ this.name = "ComposerCommandParseError";
2433
+ }
2434
+ };
2435
+ const FORBIDDEN_TOKEN = /\$\(|[;|&<>`]/;
2436
+ function expectedSyntax(entry) {
2437
+ const args = entry.args.map((argument) => {
2438
+ const name = argument.variadic ? `${argument.name}...` : argument.name;
2439
+ return argument.required ? `<${name}>` : `[${name}]`;
2440
+ });
2441
+ const options = entry.options.length > 0 ? ["[options]"] : [];
2442
+ return [
2443
+ entry.display,
2444
+ ...args,
2445
+ ...options
2446
+ ].join(" ");
2447
+ }
2448
+ function tokenize(input) {
2449
+ const tokens = [];
2450
+ let current = "";
2451
+ let quote = null;
2452
+ let escaped = false;
2453
+ const pushCurrent = () => {
2454
+ if (current.length === 0) return;
2455
+ tokens.push(current);
2456
+ current = "";
2457
+ };
2458
+ for (const character of input) {
2459
+ if (escaped) {
2460
+ current += character;
2461
+ escaped = false;
2462
+ continue;
2463
+ }
2464
+ if (character === "\\") {
2465
+ escaped = true;
2466
+ continue;
2467
+ }
2468
+ if (quote !== null) {
2469
+ if (character === quote) quote = null;
2470
+ else current += character;
2471
+ continue;
2472
+ }
2473
+ if (character === "\"" || character === "'") {
2474
+ quote = character;
2475
+ continue;
2476
+ }
2477
+ if (/\s/.test(character)) {
2478
+ pushCurrent();
2479
+ continue;
2480
+ }
2481
+ current += character;
2482
+ }
2483
+ if (escaped) current += "\\";
2484
+ if (quote !== null) throw new ComposerCommandParseError("missing-argument", `Unclosed ${quote} quote. Close the quoted value before submitting the command.`, quote, `a closing ${quote}`);
2485
+ pushCurrent();
2486
+ return tokens;
2487
+ }
2488
+ function aliasesForPath(entry) {
2489
+ let candidates = [[]];
2490
+ for (let index = 0; index < entry.path.length; index += 1) {
2491
+ const prefix = entry.path.slice(0, index + 1);
2492
+ const prefixEntry = COMPOSER_COMMAND_MANIFEST.find((candidate) => candidate.path.length === prefix.length && candidate.path.every((part, partIndex) => part === prefix[partIndex]));
2493
+ const names = [entry.path[index], ...prefixEntry?.aliases ?? []];
2494
+ candidates = candidates.flatMap((candidate) => names.map((name) => [...candidate, name]));
2495
+ }
2496
+ return candidates;
2497
+ }
2498
+ function resolveEntry(tokens) {
2499
+ const match = COMPOSER_COMMAND_MANIFEST.flatMap((entry) => aliasesForPath(entry).filter((path) => path.every((part, index) => tokens[index] === part)).map((path) => ({
2500
+ entry,
2501
+ pathLength: path.length
2502
+ }))).sort((left, right) => right.pathLength - left.pathLength)[0];
2503
+ if (match) return match;
2504
+ const token = tokens[0] ?? "";
2505
+ throw new ComposerCommandParseError("unknown-command", token ? `Unknown Overdeck command "${token}". Type /pan to list available commands.` : "Missing an Overdeck command after /pan. Type /pan to list available commands.", token, "/pan <command>");
2506
+ }
2507
+ function optionDefinitions(entry) {
2508
+ const definitions = /* @__PURE__ */ new Map();
2509
+ for (const option of entry.options) {
2510
+ const flags = option.flags.split(/[ ,|]+/).filter((part) => part.startsWith("-"));
2511
+ const canonicalFlag = flags.find((flag) => flag.startsWith("--")) ?? flags[0];
2512
+ for (const flag of flags) definitions.set(flag, {
2513
+ option,
2514
+ canonicalFlag
2515
+ });
2516
+ }
2517
+ return definitions;
2518
+ }
2519
+ function splitFlag(token) {
2520
+ const separator = token.indexOf("=");
2521
+ if (separator === -1) return { flag: token };
2522
+ return {
2523
+ flag: token.slice(0, separator),
2524
+ inlineValue: token.slice(separator + 1)
2525
+ };
2526
+ }
2527
+ function parseOverdeckComposerCommand(message) {
2528
+ if (!/^\/pan(?:\s|$)/.test(message)) return null;
2529
+ const forbidden = message.match(FORBIDDEN_TOKEN)?.[0];
2530
+ if (forbidden !== void 0) throw new ComposerCommandParseError("forbidden-token", `The token "${forbidden}" is not allowed in /pan commands. Run compound shell syntax in a terminal instead.`, forbidden, "plain arguments without shell operators");
2531
+ const tokens = tokenize(message.slice(4).trim());
2532
+ const { entry, pathLength } = resolveEntry(tokens);
2533
+ const remaining = tokens.slice(pathLength);
2534
+ const definitions = optionDefinitions(entry);
2535
+ const positionals = [];
2536
+ const flags = [];
2537
+ for (let index = 0; index < remaining.length; index += 1) {
2538
+ const token = remaining[index];
2539
+ if (!token.startsWith("-")) {
2540
+ positionals.push(token);
2541
+ continue;
2542
+ }
2543
+ const { flag, inlineValue } = splitFlag(token);
2544
+ const definition = definitions.get(flag);
2545
+ if (definition === void 0) throw new ComposerCommandParseError("unknown-flag", `Unknown flag "${flag}" for ${entry.display}. Expected syntax: ${expectedSyntax(entry)}.`, flag, expectedSyntax(entry));
2546
+ flags.push(definition.canonicalFlag);
2547
+ if (definition.option.valueHint === null) {
2548
+ if (inlineValue !== void 0) throw new ComposerCommandParseError("unknown-flag", `Flag "${flag}" does not accept a value. Expected syntax: ${expectedSyntax(entry)}.`, token, expectedSyntax(entry));
2549
+ continue;
2550
+ }
2551
+ if (inlineValue !== void 0) {
2552
+ if (inlineValue.length === 0 && definition.option.required) throw new ComposerCommandParseError("missing-argument", `Flag "${flag}" requires <${definition.option.valueHint}>. Expected syntax: ${expectedSyntax(entry)}.`, flag, expectedSyntax(entry));
2553
+ if (inlineValue.length > 0) flags.push(inlineValue);
2554
+ continue;
2555
+ }
2556
+ const next = remaining[index + 1];
2557
+ const nextIsFlag = next?.startsWith("-") === true;
2558
+ if (definition.option.required && (next === void 0 || nextIsFlag)) throw new ComposerCommandParseError("missing-argument", `Flag "${flag}" requires <${definition.option.valueHint}>. Expected syntax: ${expectedSyntax(entry)}.`, flag, expectedSyntax(entry));
2559
+ if (next !== void 0 && !nextIsFlag) {
2560
+ flags.push(next);
2561
+ index += 1;
2562
+ }
2563
+ }
2564
+ if (!(entry.args.at(-1)?.variadic === true) && positionals.length > entry.args.length) {
2565
+ const token = positionals[entry.args.length];
2566
+ throw new ComposerCommandParseError("unknown-command", `Unexpected token "${token}" for ${entry.display}. Expected syntax: ${expectedSyntax(entry)}.`, token, expectedSyntax(entry));
2567
+ }
2568
+ const missing = entry.args.find((argument, index) => argument.required && positionals[index] === void 0);
2569
+ if (missing !== void 0) throw new ComposerCommandParseError("missing-argument", `Missing required argument <${missing.name}> for ${entry.display}. Expected syntax: ${expectedSyntax(entry)}.`, missing.name, expectedSyntax(entry));
2570
+ return {
2571
+ entry,
2572
+ argv: [
2573
+ ...entry.path,
2574
+ ...positionals,
2575
+ ...flags
2576
+ ]
2577
+ };
2578
+ }
2579
+ //#endregion
2580
+ //#region ../../lib/composer-commands/policy.ts
2581
+ init_src();
2582
+ const COMPOSER_COMMAND_POLICIES = {
2583
+ start: {
2584
+ mode: "detached",
2585
+ safety: "safe"
2586
+ },
2587
+ plan: {
2588
+ mode: "detached",
2589
+ safety: "safe"
2590
+ },
2591
+ show: {
2592
+ mode: "captured",
2593
+ safety: "safe"
2594
+ },
2595
+ status: {
2596
+ mode: "captured",
2597
+ safety: "safe"
2598
+ },
2599
+ tell: {
2600
+ mode: "captured",
2601
+ safety: "safe"
2602
+ },
2603
+ handoff: {
2604
+ mode: "ui",
2605
+ safety: "dialog",
2606
+ uiAction: "handoff",
2607
+ confirmationText: "This opens the handoff dialog for the selected conversation. Review the destination and focus before continuing."
2608
+ },
2609
+ fork: {
2610
+ mode: "ui",
2611
+ safety: "dialog",
2612
+ uiAction: "fork",
2613
+ confirmationText: "This opens the fork dialog for the selected conversation. Review the fork mode and destination before continuing."
2614
+ }
2615
+ };
2616
+ const DEFAULT_POLICY = {
2617
+ mode: "terminal-only",
2618
+ safety: "safe"
2619
+ };
2620
+ function assertPolicyOverlayIntegrity(policies = COMPOSER_COMMAND_POLICIES, manifest = COMPOSER_COMMAND_MANIFEST) {
2621
+ for (const path of Object.keys(policies)) {
2622
+ const matches = manifest.filter((entry) => entry.path.join(" ") === path);
2623
+ if (matches.length !== 1) throw new Error(`Composer command policy path "${path}" must match exactly one manifest entry; found ${matches.length}.`);
2624
+ }
2625
+ }
2626
+ function resolvePolicy(path) {
2627
+ return COMPOSER_COMMAND_POLICIES[path.join(" ")] ?? DEFAULT_POLICY;
2628
+ }
2629
+ assertPolicyOverlayIntegrity();
2630
+ //#endregion
2631
+ //#region ../../lib/composer-commands/router.ts
2632
+ const UI_COMMAND_PATTERNS = [{
2633
+ action: "handoff",
2634
+ pattern: /^\/(?:handoff|pan-handoff|pan\s+handoff)(?:\s+([\s\S]+))?$/i
2635
+ }, {
2636
+ action: "fork",
2637
+ pattern: /^\/pan\s+fork(?:\s+([\s\S]+))?$/i
2638
+ }];
2639
+ function isComposerCommandMessage(message) {
2640
+ return UI_COMMAND_PATTERNS.some(({ pattern }) => pattern.test(message)) || /^\/pan(?:\s|$)/.test(message);
2641
+ }
2642
+ async function handleComposerCommandMessage({ message, target, confirmation }, dependencies = {}) {
2643
+ for (const { action, pattern } of UI_COMMAND_PATTERNS) {
2644
+ const match = message.match(pattern);
2645
+ if (match) {
2646
+ const parsed = parseOverdeckComposerCommand(`/pan ${action}`);
2647
+ const focus = match[1]?.trim();
2648
+ if (focus) parsed.argv.push(focus);
2649
+ return handleComposerCommand({
2650
+ parsed,
2651
+ target,
2652
+ confirmation
2653
+ }, dependencies);
2654
+ }
2655
+ }
2656
+ const parsed = parseOverdeckComposerCommand(message);
2657
+ if (parsed === null) return null;
2658
+ return handleComposerCommand({
2659
+ parsed,
2660
+ target,
2661
+ confirmation
2662
+ }, dependencies);
2663
+ }
2664
+ async function handleComposerCommand({ parsed, target, confirmation }, dependencies = {}) {
2665
+ const policy = (dependencies.resolvePolicy ?? resolvePolicy)(parsed.entry.path);
2666
+ if (policy.safety !== "safe") {
2667
+ const confirmationStore = dependencies.confirmationStore ?? composerConfirmationStore;
2668
+ if (confirmation === void 0) return confirmationStore.issue({
2669
+ target,
2670
+ argv: parsed.argv,
2671
+ policy,
2672
+ display: parsed.entry.display
2673
+ });
2674
+ confirmationStore.consume({
2675
+ target,
2676
+ argv: parsed.argv,
2677
+ policy,
2678
+ confirmation
2679
+ });
2680
+ }
2681
+ if (policy.mode === "captured") return runCapturedCommand(parsed.argv);
2682
+ if (policy.mode === "detached") return runDetachedCommand(parsed.argv);
2683
+ if (policy.mode === "ui" && policy.uiAction) return uiCommandResult(policy.uiAction, parsed.argv.slice(parsed.entry.path.length).join(" ") || void 0);
2684
+ if (policy.mode === "terminal-only") return {
2685
+ kind: "terminal-only",
2686
+ status: "rejected",
2687
+ message: `${parsed.entry.display} must run in a terminal. Composer execution has not been enabled for this command.`
2688
+ };
2689
+ return missingExecutorResult(parsed.entry.display, policy);
2690
+ }
2691
+ function uiCommandResult(action, focus) {
2692
+ return {
2693
+ kind: "ui",
2694
+ status: "requires_ui",
2695
+ action,
2696
+ args: focus ? { focus } : {}
2697
+ };
2698
+ }
2699
+ function missingExecutorResult(display, policy) {
2700
+ return {
2701
+ kind: "terminal-only",
2702
+ status: "rejected",
2703
+ message: `${display} is recognized as a ${policy.mode} command, but its composer executor is not registered yet. Run it in a terminal for now.`
2704
+ };
2705
+ }
2706
+ function composerCommandResultHttpStatus(result) {
2707
+ return result.kind === "terminal-only" ? 422 : 200;
2708
+ }
2709
+ //#endregion
1867
2710
  //#region routes/agents/messaging.ts
1868
2711
  init_agents$1();
1869
2712
  async function sendAgentMessage(id, message) {
@@ -1879,6 +2722,55 @@ async function sendAgentMessage(id, message) {
1879
2722
  remote: true
1880
2723
  } : { success: true };
1881
2724
  }
2725
+ async function handleAgentMessage(id, message, confirmation) {
2726
+ try {
2727
+ if (isComposerCommandMessage(message)) {
2728
+ let agentState;
2729
+ try {
2730
+ agentState = await Effect.runPromise(getAgentState(id));
2731
+ } catch {
2732
+ return jsonResponse({
2733
+ error: `Failed to resolve agent target: ${id}`,
2734
+ code: "agent-resolution-failed"
2735
+ }, { status: 503 });
2736
+ }
2737
+ if (!agentState) return jsonResponse({
2738
+ error: `Agent not found: ${id}`,
2739
+ code: "agent-not-found"
2740
+ }, { status: 404 });
2741
+ if (!agentState.harness) return jsonResponse({
2742
+ error: `Agent harness could not be resolved: ${id}`,
2743
+ code: "agent-harness-unresolved"
2744
+ }, { status: 503 });
2745
+ const result = await handleComposerCommandMessage({
2746
+ message,
2747
+ confirmation,
2748
+ target: {
2749
+ kind: "agent",
2750
+ id,
2751
+ harness: agentState.harness,
2752
+ cwd: agentState.workspace,
2753
+ issueId: agentState.issueId
2754
+ }
2755
+ });
2756
+ if (result !== null) return jsonResponse(result, { status: composerCommandResultHttpStatus(result) });
2757
+ }
2758
+ } catch (error) {
2759
+ if (error instanceof ComposerCommandConfirmationError) return jsonResponse({
2760
+ error: error.message,
2761
+ code: error.code
2762
+ }, { status: 422 });
2763
+ if (!(error instanceof ComposerCommandParseError)) throw error;
2764
+ const status = error.code === "unknown-command" ? 404 : error.code === "unknown-flag" ? 422 : 400;
2765
+ return jsonResponse({
2766
+ error: error.message,
2767
+ code: error.code,
2768
+ token: error.token,
2769
+ expected: error.expected
2770
+ }, { status });
2771
+ }
2772
+ return jsonResponse(await sendAgentMessage(id, message));
2773
+ }
1882
2774
  function validateAgentMessageOrigin(request) {
1883
2775
  const originCheck = validateOrigin(request);
1884
2776
  if (!originCheck.ok) return {
@@ -1895,9 +2787,10 @@ function postAgentMessageLikeRoute(path) {
1895
2787
  const originCheck = validateAgentMessageOrigin(yield* HttpServerRequest.HttpServerRequest);
1896
2788
  if (!originCheck.ok) return originCheck.response;
1897
2789
  const id = (yield* HttpRouter.params)["id"] ?? "";
1898
- const { message } = yield* readJsonBody$1;
2790
+ const body = yield* readJsonBody$1;
2791
+ const { message } = body;
1899
2792
  if (!message) return jsonResponse({ error: "Message required" }, { status: 400 });
1900
- return yield* Effect.promise(() => sendAgentMessage(id, message)).pipe(Effect.map((result) => jsonResponse(result)));
2793
+ return yield* Effect.promise(() => handleAgentMessage(id, message, composerCommandConfirmationFromBody(body)));
1901
2794
  })));
1902
2795
  }
1903
2796
  const postAgentMessageRoute = postAgentMessageLikeRoute("/api/agents/:id/message");
@@ -2146,7 +3039,7 @@ async function cleanupConversationAttachments(name) {
2146
3039
  * sessions that the lifecycle poller missed. */
2147
3040
  async function cleanupOrphanedConversationAttachments() {
2148
3041
  try {
2149
- const { listConversations } = await import("./conversations-6mPANqvL.js");
3042
+ const { listConversations } = await import("./conversations-C34dyIb9.js");
2150
3043
  const convNames = new Set(listConversations().map((c) => c.name));
2151
3044
  const root = getConversationAttachmentsRoot();
2152
3045
  await runInBatches((await readdir(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !convNames.has(entry.name)).map((entry) => join(root, entry.name)), 10, (path) => rm(path, {
@@ -2424,7 +3317,7 @@ async function stopConversationRuntime(conv, name) {
2424
3317
  function shellQuote(str) {
2425
3318
  return "'" + str.replace(/'/g, "'\"'\"'") + "'";
2426
3319
  }
2427
- const SAFE_MODEL_PATTERN = /^[a-zA-Z0-9_.:\/-]+$/;
3320
+ const SAFE_MODEL_PATTERN = MODEL_ID_PATTERN;
2428
3321
  const SAFE_EFFORT_PATTERN = /^(low|medium|high)$/;
2429
3322
  const SAFE_ISSUE_ID_PATTERN = /^[A-Z0-9]+-[0-9]+$/;
2430
3323
  const PI_CONVERSATION_SOURCE_CONTRACT = [
@@ -2436,7 +3329,7 @@ async function resolveAllowedHarness(requested, model) {
2436
3329
  if (!model) return "claude-code";
2437
3330
  return resolveHarness({
2438
3331
  model,
2439
- explicit: requested === "ohmypi" || requested === "claude-code" || requested === "codex" ? requested : void 0
3332
+ explicit: requested === "ohmypi" || requested === "claude-code" || requested === "codex" || requested === "acp" ? requested : void 0
2440
3333
  });
2441
3334
  }
2442
3335
  async function isInsideGitWorkTree(dir) {
@@ -2518,6 +3411,10 @@ function shouldUseSupervisorForConversation(harness, options = {}) {
2518
3411
  return getHarnessBehavior(harness).supportsPtySupervisor && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1";
2519
3412
  }
2520
3413
  async function waitForConversationRuntimeReady(tmuxSession, harness, mode) {
3414
+ if (harness === "acp") {
3415
+ await waitForAcpHostReady(tmuxSession);
3416
+ return;
3417
+ }
2521
3418
  const transcriptKind = getHarnessBehavior(harness).transcriptKind;
2522
3419
  if (transcriptKind === "ohmypi-jsonl") {
2523
3420
  if (!await waitForPiTuiReady(tmuxSession)) throw new Error(`Pi (ohmypi) did not become interactive in ${tmuxSession} within the startup window — MCP server connections are likely still in progress. Retry once MCP servers finish connecting, or check ~/.claude/mcp.json for a slow/hanging server.`);
@@ -2533,7 +3430,7 @@ async function handleConversationSwitchModel(name, body) {
2533
3430
  const currentHarness = conv.harness ?? "claude-code";
2534
3431
  const requestedHarness = body["harness"];
2535
3432
  let harness = currentHarness;
2536
- if (requestedHarness === "ohmypi" || requestedHarness === "pi" || requestedHarness === "claude-code" || requestedHarness === "codex") {
3433
+ if (requestedHarness === "ohmypi" || requestedHarness === "pi" || requestedHarness === "claude-code" || requestedHarness === "codex" || requestedHarness === "acp") {
2537
3434
  const requestedRuntime = requestedHarness === "pi" ? "ohmypi" : requestedHarness;
2538
3435
  if (requestedRuntime !== currentHarness) {
2539
3436
  const policyModel = model ?? conv.model ?? "";
@@ -2567,6 +3464,21 @@ async function handleConversationSwitchModel(name, body) {
2567
3464
  function getPtySupervisorSocketPath(agentId) {
2568
3465
  return join(getOverdeckHome(), "sockets", `pty-${agentId}.sock`);
2569
3466
  }
3467
+ /**
3468
+ * Pull the supervisor's own failure out of the tmux pane it died in.
3469
+ *
3470
+ * A supervisor that cannot load its native node-pty exits instantly with an
3471
+ * ERR_MODULE_NOT_FOUND on stderr; all the spawn path sees is the socket that
3472
+ * never appeared. Reporting only the timeout hides the actual cause and sent
3473
+ * an operator hunting the memory governor instead (PAN-3172).
3474
+ */
3475
+ function extractSupervisorFailure(paneText) {
3476
+ const lines = paneText.split("\n").map((line) => line.trim()).filter(Boolean);
3477
+ const errors = lines.filter((line) => /error|cannot find|ERR_[A-Z_]+|pty-supervisor:/i.test(line));
3478
+ const chosen = (errors.length > 0 ? errors : lines).slice(-3);
3479
+ if (chosen.length === 0) return null;
3480
+ return chosen.join(" | ").slice(0, 500);
3481
+ }
2570
3482
  async function waitForPtySupervisorSocket(agentId, timeoutMs = PTY_SUPERVISOR_SOCKET_WAIT_MS) {
2571
3483
  const socketPath = getPtySupervisorSocketPath(agentId);
2572
3484
  const start = Date.now();
@@ -2576,7 +3488,8 @@ async function waitForPtySupervisorSocket(agentId, timeoutMs = PTY_SUPERVISOR_SO
2576
3488
  } catch {}
2577
3489
  await new Promise((r) => setTimeout(r, 250));
2578
3490
  }
2579
- throw new Error(`Timed out waiting for PTY supervisor socket ${socketPath}`);
3491
+ const failure = extractSupervisorFailure(await capturePaneText(agentId, 40));
3492
+ throw new Error(`Timed out waiting for PTY supervisor socket ${socketPath}` + (failure ? ` — supervisor output: ${failure}` : ""));
2580
3493
  }
2581
3494
  async function extractModelFromSessionFile(sessionFile) {
2582
3495
  try {
@@ -2686,14 +3599,25 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
2686
3599
  let providerExportsStr = "";
2687
3600
  let piFields;
2688
3601
  let codexFields;
3602
+ let acpFields;
2689
3603
  let codexTransport;
2690
- if (model) {
3604
+ if (behavior.launchCommandKind === "acp-host") {
3605
+ if (!model) throw new Error("ACP conversation requires a model");
3606
+ if (!SAFE_MODEL_PATTERN.test(model)) throw new Error("Invalid model name");
3607
+ const sessionIdPath = join(getOverdeckHome(), "agents", tmuxSession, "acp-session-id");
3608
+ const resumeSessionId = resume ? await readFile(sessionIdPath, "utf-8").then((value) => value.trim() || void 0).catch(() => void 0) : void 0;
3609
+ await rm(sessionIdPath, { force: true });
3610
+ acpFields = {
3611
+ ...getAcpLauncherFields(tmuxSession, model, cwd, harnessLaunch.binaryPath, "work"),
3612
+ resumeSessionId
3613
+ };
3614
+ runtimeCommand = "acp-host";
3615
+ } else if (model) {
2691
3616
  if (!SAFE_MODEL_PATTERN.test(model)) throw new Error("Invalid model name");
2692
3617
  runtimeCommand = await getAgentRuntimeBaseCommand(model, void 0, void 0, harness);
2693
3618
  const mode = resolvePermissionModeSync();
2694
3619
  if (!runtimeCommand.includes("--permission-mode")) runtimeCommand = `${runtimeCommand} --permission-mode ${mode === "auto" ? "auto" : BYPASS_PERMISSION_MODE}`;
2695
3620
  providerExportsStr = (await getProviderExportsForModel(model)).trim();
2696
- await getProviderEnvForModel(model);
2697
3621
  if (behavior.transcriptKind === "ohmypi-jsonl") {
2698
3622
  if (getProviderForModelSync(model).name === "openai") {
2699
3623
  const auth = await getOhmypiCodexAuthStatus({ refreshIfExpired: true });
@@ -2725,7 +3649,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
2725
3649
  const codexApprovalPolicy = codexPermMode === "full-access" ? "never" : "on-request";
2726
3650
  const codexSandboxMode = codexPermMode === "full-access" ? "danger-full-access" : codexPermMode === "read-only" ? "read-only" : "workspace-write";
2727
3651
  const codexApprovalsReviewer = codexPermMode === "auto-review" ? "auto_review" : void 0;
2728
- const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-Dg7Is5yb.js");
3652
+ const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-BdEsUFWh.js");
2729
3653
  initCodexHome(codexHome, {
2730
3654
  trustedDir: cwd,
2731
3655
  approvalPolicy: codexApprovalPolicy,
@@ -2755,7 +3679,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
2755
3679
  await writePtyToken(tmuxSession);
2756
3680
  }
2757
3681
  let channelsBridgeMcpConfig;
2758
- if (!piFields && !codexFields && !plainFork && isClaudeCodeChannelsEnabled() && (!model || getProviderForModelSync(model).name === "anthropic") && process.env.CLAUDE_CODE_USE_BEDROCK !== "1" && process.env.CLAUDE_CODE_USE_VERTEX !== "1" && process.env.CLAUDE_CODE_USE_FOUNDRY !== "1" && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1") {
3682
+ if (!piFields && !codexFields && !acpFields && !plainFork && isClaudeCodeChannelsEnabled() && (!model || getProviderForModelSync(model).name === "anthropic") && process.env.CLAUDE_CODE_USE_BEDROCK !== "1" && process.env.CLAUDE_CODE_USE_VERTEX !== "1" && process.env.CLAUDE_CODE_USE_FOUNDRY !== "1" && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1") {
2759
3683
  channelsBridgeMcpConfig = join(stateDir, "agent-mcp.json");
2760
3684
  writeBridgeTokenSync(tmuxSession);
2761
3685
  await writeChannelsBridgeMcpConfig(channelsBridgeMcpConfig, tmuxSession);
@@ -2769,19 +3693,19 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
2769
3693
  unsetProviderEnv: true,
2770
3694
  overdeckEnv: {
2771
3695
  ...issueId ? { issueId } : {},
2772
- ...piFields || codexFields || useSupervisor ? { agentId: tmuxSession } : {}
3696
+ ...piFields || codexFields || acpFields || useSupervisor ? { agentId: tmuxSession } : {}
2773
3697
  },
2774
3698
  extraEnvExports: [harnessLaunch.pathExport, `export OVERDECK_DASHBOARD_URL="http://127.0.0.1:${process.env["API_PORT"] ?? process.env["PORT"] ?? "3011"}"`],
2775
3699
  providerExports: providerExportsStr || void 0,
2776
3700
  trapHup: true,
2777
3701
  baseCommand: runtimeCommand,
2778
- appendSystemPromptFiles: piFields ? await piConversationSystemPromptFiles(cwd) : codexFields ? [] : await claudeConversationSystemPromptFiles(cwd),
3702
+ appendSystemPromptFiles: piFields ? await piConversationSystemPromptFiles(cwd) : codexFields || acpFields ? [] : await claudeConversationSystemPromptFiles(cwd),
2779
3703
  model: launcherModel,
2780
- ...piFields ?? codexFields ?? {
3704
+ ...piFields ?? codexFields ?? acpFields ?? {
2781
3705
  resumeSessionId: resume ? claudeSessionId : void 0,
2782
3706
  sessionId: resume ? void 0 : claudeSessionId
2783
3707
  },
2784
- extraArgs: !piFields && effort ? `--effort "${effort}"` : void 0,
3708
+ extraArgs: !piFields && !acpFields && effort ? `--effort "${effort}"` : void 0,
2785
3709
  keepAlive: true,
2786
3710
  fileMode: 448,
2787
3711
  channelsBridgeMcpConfig,
@@ -2794,7 +3718,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
2794
3718
  } catch {}
2795
3719
  console.log(`[claude-invoke] purpose=conversation-session | model=${model || "default"} | source=conversations.ts:spawnConversationSession | session=${tmuxSession} | resume=${resume} | command="${runtimeCommand}"`);
2796
3720
  try {
2797
- const { preTrustDirectory } = await import("./workspace-manager-ZTdJAy9M.js");
3721
+ const { preTrustDirectory } = await import("./workspace-manager-CDTKjjdU.js");
2798
3722
  preTrustDirectory(cwd);
2799
3723
  } catch {}
2800
3724
  try {
@@ -2811,7 +3735,7 @@ async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model
2811
3735
  const codexHomeDir = codexFields.codexHome;
2812
3736
  (async () => {
2813
3737
  try {
2814
- const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-Dg7Is5yb.js");
3738
+ const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-BdEsUFWh.js");
2815
3739
  const rollout = await waitForCodexRollout(codexHomeDir, 12e4);
2816
3740
  if (rollout) {
2817
3741
  const threadId = extractThreadIdFromRollout(rollout);
@@ -2887,10 +3811,14 @@ async function handleConversationCreate(body, deps) {
2887
3811
  await spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, false, harness);
2888
3812
  console.log(`[conversations] tmux session ${tmuxSession} spawned, sessionId: ${claudeSessionId}`);
2889
3813
  await waitForConversationRuntimeReady(tmuxSession, harness, "spawn");
2890
- if (message) await deliverAgentMessage(tmuxSession, message, "conversation-message", resolveConversationDeliveryMethod(conv));
3814
+ if (message) {
3815
+ const delivery = await deliverAgentMessage(tmuxSession, message, "conversation-message", resolveConversationDeliveryMethod(conv));
3816
+ if (harness === "acp" && !delivery.ok) throw new Error(`ACP initial prompt did not land: ${delivery.failure ?? "unknown failure"}`);
3817
+ }
2891
3818
  } catch (spawnErr) {
2892
3819
  const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
2893
3820
  console.error(`[conversations] background spawn failed for ${tmuxSession}: ${msg}`);
3821
+ if (harness === "acp") await stopConversationRuntime(conv, name);
2894
3822
  updateSpawnError(name, msg);
2895
3823
  getEventStore().emitOnly({
2896
3824
  type: "conversation.created",
@@ -2976,6 +3904,9 @@ async function handleConversationResume(name, body, deps) {
2976
3904
  reattached: false,
2977
3905
  sessionAlive: true
2978
3906
  });
3907
+ } catch (error) {
3908
+ if (harness === "acp") await stopConversationRuntime(conv, name);
3909
+ throw error;
2979
3910
  } finally {
2980
3911
  respawn.done();
2981
3912
  }
@@ -3010,13 +3941,16 @@ async function handleConversationRestartAll(deps) {
3010
3941
  const results = [];
3011
3942
  for (const conv of convs) {
3012
3943
  const respawn = markRespawnPending(conv.tmuxSession);
3944
+ let attemptedHarness = conv.harness ?? "claude-code";
3013
3945
  try {
3014
3946
  await Effect.runPromise(killSession(conv.tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
3015
3947
  const oldSessionId = conv.claudeSessionId;
3016
3948
  const sessionFileForResume = await deps.resolveSessionFile(conv);
3017
3949
  const canResume = !!oldSessionId && !!sessionFileForResume && existsSync(sessionFileForResume);
3018
3950
  const harness = await resolveAllowedHarness(conv.harness, conv.model);
3951
+ attemptedHarness = harness;
3019
3952
  await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), conv.model ?? void 0, conv.effort ?? void 0, conv.issueId ?? void 0, canResume, harness);
3953
+ if (harness === "acp") await waitForConversationRuntimeReady(conv.tmuxSession, harness, "respawn");
3020
3954
  setConversationHarness(conv.name, harness);
3021
3955
  markConversationActive(conv.name);
3022
3956
  results.push({
@@ -3027,6 +3961,7 @@ async function handleConversationRestartAll(deps) {
3027
3961
  } catch (err) {
3028
3962
  const msg = err instanceof Error ? err.message : String(err);
3029
3963
  console.error(`[conversations] Failed to restart ${conv.name}:`, msg);
3964
+ if (attemptedHarness === "acp") await stopConversationRuntime(conv, conv.name);
3030
3965
  results.push({
3031
3966
  name: conv.name,
3032
3967
  model: conv.model,
@@ -3230,6 +4165,7 @@ function pickDeliverAs(bodyDeliverAs) {
3230
4165
  }
3231
4166
  function resolveConversationDeliveryMethod(conv) {
3232
4167
  const harness = conv.harness ?? "claude-code";
4168
+ if (harness === "acp") return "auto";
3233
4169
  if (isPiControlChannelHarness(harness)) return "auto";
3234
4170
  if (harness === "codex" && loadConfigSync().config.codex?.transport !== "tui") return "auto";
3235
4171
  return conv.deliveryMethod ?? (getHarnessBehavior(harness).deliveryKind === "rpc-fifo" ? "tmux" : "auto");
@@ -3690,14 +4626,14 @@ const postInternalAgentPermissionRequestRoute = HttpRouter.add("POST", "/api/int
3690
4626
  error: "missing agent id"
3691
4627
  }, { status: 400 });
3692
4628
  const request = yield* HttpServerRequest.HttpServerRequest;
3693
- const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-DXdfprGr.js"));
4629
+ const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-M9omeXM1.js"));
3694
4630
  const expected = getInternalTokenSync();
3695
4631
  if (!expected) return jsonResponse({
3696
4632
  ok: false,
3697
4633
  error: "internal token not configured"
3698
4634
  }, { status: 503 });
3699
4635
  const rawHeader = request.headers[INTERNAL_TOKEN_HEADER];
3700
- if (!constantTimeTokenEqual$1(Array.isArray(rawHeader) ? rawHeader[0] : rawHeader, expected)) return jsonResponse({
4636
+ if (!constantTimeTokenEqual(Array.isArray(rawHeader) ? rawHeader[0] : rawHeader, expected)) return jsonResponse({
3701
4637
  ok: false,
3702
4638
  error: "forbidden"
3703
4639
  }, { status: 403 });
@@ -3826,6 +4762,7 @@ const postAgentPermissionResponseRoute = HttpRouter.add("POST", "/api/agents/:id
3826
4762
  * pollers (PAN-1134). Extracted from routes/agents.ts so Pi event polling
3827
4763
  * can reuse bodyToEvent without importing a heavy route module.
3828
4764
  */
4765
+ init_src();
3829
4766
  /**
3830
4767
  * Translate a decoded body into an unsigned DomainEvent (no sequence yet).
3831
4768
  * Returns null if the body doesn't map to a runtime event.
@@ -3946,6 +4883,25 @@ const bodyToEvent = (agentId, body, timestamp) => {
3946
4883
  contextSaturatedAt: source["contextSaturatedAt"]
3947
4884
  }
3948
4885
  };
4886
+ case "linear_mcp_auth_required": return {
4887
+ type: "linear_mcp_auth.required",
4888
+ timestamp,
4889
+ payload: {
4890
+ agentId,
4891
+ issueId: source["issueId"] ?? null,
4892
+ authUrl: source["authUrl"] ?? null,
4893
+ expiresAt: source["expiresAt"] ?? null
4894
+ }
4895
+ };
4896
+ case "linear_mcp_auth_healthy": return {
4897
+ type: "linear_mcp_auth.healthy",
4898
+ timestamp,
4899
+ payload: {
4900
+ agentId,
4901
+ issueId: source["issueId"] ?? null,
4902
+ source: "hook"
4903
+ }
4904
+ };
3949
4905
  default: return null;
3950
4906
  }
3951
4907
  };
@@ -3957,7 +4913,7 @@ const getAgentHealthHistoryRoute = HttpRouter.add("GET", "/api/agents/:id/health
3957
4913
  const request = yield* HttpServerRequest.HttpServerRequest;
3958
4914
  const urlOpt = HttpServerRequest.toURL(request);
3959
4915
  const hours = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("hours") ?? "24" : "24";
3960
- const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-CpfvuVmm.js"));
4916
+ const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-_xfTaOUs.js"));
3961
4917
  const endTime = /* @__PURE__ */ new Date();
3962
4918
  const startTime = /* @__PURE__ */ new Date(endTime.getTime() - parseInt(hours) * 60 * 60 * 1e3);
3963
4919
  const events = getHealthHistory(id, startTime.toISOString(), endTime.toISOString());
@@ -3970,9 +4926,18 @@ const getAgentHealthHistoryRoute = HttpRouter.add("GET", "/api/agents/:id/health
3970
4926
  })));
3971
4927
  function emitAgentRuntimeEvent(id, body, timestamp) {
3972
4928
  return Effect.gen(function* () {
4929
+ let eventBody = body;
4930
+ if (body["kind"] === "linear_mcp_auth_required" || body["kind"] === "linear_mcp_auth_healthy") {
4931
+ const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-hZpxIw1E.js"));
4932
+ const snapshot = yield* (yield* AgentStateService).get(id);
4933
+ eventBody = {
4934
+ ...body,
4935
+ issueId: snapshot?.currentIssue ?? null
4936
+ };
4937
+ }
3973
4938
  let raw;
3974
4939
  try {
3975
- raw = bodyToEvent(id, body, timestamp);
4940
+ raw = bodyToEvent(id, eventBody, timestamp);
3976
4941
  } catch (error) {
3977
4942
  return {
3978
4943
  ok: false,
@@ -3998,7 +4963,7 @@ function emitAgentRuntimeEvent(id, body, timestamp) {
3998
4963
  detail: String(decoded.failure)
3999
4964
  }, { status: 400 })
4000
4965
  };
4001
- const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-CnzFKSAQ.js"));
4966
+ const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-hZpxIw1E.js"));
4002
4967
  yield* (yield* AgentStateService).emit(decoded.success);
4003
4968
  return {
4004
4969
  ok: true,
@@ -4080,7 +5045,7 @@ const getAgentRuntimeRoute = HttpRouter.add("GET", "/api/agents/:id/runtime", ht
4080
5045
  success: false,
4081
5046
  error: "missing agent id"
4082
5047
  }, { status: 400 });
4083
- const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-CnzFKSAQ.js"));
5048
+ const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-hZpxIw1E.js"));
4084
5049
  const snapshot = yield* (yield* AgentStateService).get(id);
4085
5050
  if (!snapshot) return jsonResponse({
4086
5051
  success: false,
@@ -4265,8 +5230,8 @@ function createAgentStopHandler(lifecycleEvent) {
4265
5230
  yield* stopAgent(id);
4266
5231
  if (stateBeforeStop?.issueId) yield* Effect.promise(async () => {
4267
5232
  try {
4268
- const { resolveProjectFromIssueSync } = await import("./projects-9SkRpES9.js");
4269
- const { findWorkspacePath } = await import("./archive-planning-zkJb3BeM.js");
5233
+ const { resolveProjectFromIssueSync } = await import("./projects-B03Wr-xc.js");
5234
+ const { findWorkspacePath } = await import("./archive-planning-0FJ6Xd3b.js");
4270
5235
  const issueLower = stateBeforeStop.issueId.toLowerCase();
4271
5236
  const workspacePath = findWorkspacePath(resolveProjectFromIssueSync(stateBeforeStop.issueId)?.projectPath ?? process.cwd(), issueLower);
4272
5237
  if (workspacePath) {
@@ -4402,9 +5367,18 @@ const postAgentUnpauseRoute = HttpRouter.add("POST", "/api/agents/:id/unpause",
4402
5367
  payload: buildAgentControlEventPayload(updatedState, toAgentStatusPayload(stateBeforeUnpause.status), hasLiveTmuxSession)
4403
5368
  });
4404
5369
  invalidateAgentsCache();
5370
+ let resumeTriggered = false;
5371
+ if ((yield* getWorkAgentLifecycleState(id)).canResumeSession && updatedState.troubled !== true) {
5372
+ resumeTriggered = true;
5373
+ yield* Effect.promise(() => appendAgentLifecycleLog(id, "agent.unpause_immediate_resume_triggered"));
5374
+ import("./resume-u9byntrq.js").then(({ resumeAgent }) => resumeAgent(id)).then((result) => {
5375
+ if (!result.success) console.warn(`[agents] immediate resume after unpause failed for ${id}: ${result.error}`);
5376
+ }).catch((err) => console.warn(`[agents] immediate resume after unpause errored for ${id}:`, err));
5377
+ }
4405
5378
  return jsonResponse({
4406
5379
  success: true,
4407
- agent: updatedState
5380
+ agent: updatedState,
5381
+ resumeTriggered
4408
5382
  });
4409
5383
  })));
4410
5384
  const postAgentUntroubledRoute = HttpRouter.add("POST", "/api/agents/:id/untroubled", httpHandler(Effect.gen(function* () {
@@ -4442,6 +5416,7 @@ const postAgentUntroubledRoute = HttpRouter.add("POST", "/api/agents/:id/untroub
4442
5416
  init_config_yaml();
4443
5417
  init_agents$1();
4444
5418
  init_harness_policy();
5419
+ init_model_validation();
4445
5420
  init_operator_interventions();
4446
5421
  init_projects();
4447
5422
  init_tmux();
@@ -5090,7 +6065,6 @@ const postAgentsRestartWithConfigRoute = HttpRouter.add("POST", "/api/agents/res
5090
6065
  })));
5091
6066
  //#endregion
5092
6067
  //#region routes/agents/control.ts
5093
- init_config();
5094
6068
  init_jsonl_parser();
5095
6069
  init_model_validation();
5096
6070
  init_paths();
@@ -5244,12 +6218,328 @@ const postAgentSwitchModelRoute = HttpRouter.add("POST", "/api/agents/:id/switch
5244
6218
  return jsonResponse({ error: `Agent ${(yield* HttpRouter.params)["id"] ?? ""} model is locked once the agent is spawned` }, { status: 409 });
5245
6219
  })));
5246
6220
  //#endregion
6221
+ //#region ../../lib/orders/eligibility.ts
6222
+ function evaluateOrderDispatchEligibility(input) {
6223
+ const issueId = input.issueId.toUpperCase();
6224
+ const item = input.book.items.find((candidate) => candidate.issue.toUpperCase() === issueId);
6225
+ if (!item) {
6226
+ const overrideUsed = input.offBook === true;
6227
+ return {
6228
+ eligible: overrideUsed,
6229
+ overrideUsed,
6230
+ ...overrideUsed ? {} : {
6231
+ code: "off-book",
6232
+ message: `Issue ${issueId} is not in active order book ${input.book.id}; work-agent dispatch is blocked. Retry with --off-book only for an intentional exception.`
6233
+ },
6234
+ conditions: [{
6235
+ key: "book-membership",
6236
+ met: overrideUsed,
6237
+ detail: overrideUsed ? `Issue ${issueId} is outside the book and the operator supplied --off-book.` : `Issue ${issueId} is not a member of ${input.book.id}.`
6238
+ }]
6239
+ };
6240
+ }
6241
+ const terminalByIssue = new Map(input.progress.items.map((progress) => [progress.issue.toUpperCase(), progress.terminal]));
6242
+ for (const [issue, terminal] of input.prerequisiteTerminal ?? []) if (!terminalByIssue.has(issue.toUpperCase())) terminalByIssue.set(issue.toUpperCase(), terminal);
6243
+ const unmetPrereq = item.prereqs.find((prereq) => terminalByIssue.get(prereq.toUpperCase()) !== true);
6244
+ const prereqsMet = unmetPrereq === void 0;
6245
+ const otherInFlight = input.book.items.filter((candidate) => candidate.issue.toUpperCase() !== issueId && input.inFlightIssues.has(candidate.issue.toUpperCase()));
6246
+ const laneBBlocker = item.lane === "B" ? otherInFlight.find((candidate) => candidate.lane === "B") : void 0;
6247
+ const laneAInFlight = otherInFlight.filter((candidate) => candidate.lane === "A").length;
6248
+ const laneSlotFree = item.lane === "B" ? laneBBlocker === void 0 : laneAInFlight < input.book.settings.laneAConcurrency;
6249
+ const postureOpen = input.book.settings.posture === "open";
6250
+ const prdReverified = !item.reVerify;
6251
+ const conditions = [
6252
+ {
6253
+ key: "book-membership",
6254
+ met: true,
6255
+ detail: `${issueId} is a member of ${input.book.id}.`
6256
+ },
6257
+ {
6258
+ key: "pickup-posture",
6259
+ met: postureOpen,
6260
+ detail: postureOpen ? "Order-book pickup posture is OPEN." : "Order-book pickup posture is DRAIN."
6261
+ },
6262
+ {
6263
+ key: "lane-slot",
6264
+ met: laneSlotFree,
6265
+ detail: item.lane === "B" ? laneBBlocker ? `Lane B is occupied by ${laneBBlocker.issue}.` : "Lane B has no issue in flight." : `${laneAInFlight}/${input.book.settings.laneAConcurrency} Lane A slots are in flight.`
6266
+ },
6267
+ {
6268
+ key: "prerequisites",
6269
+ met: prereqsMet,
6270
+ detail: unmetPrereq ? `Prerequisite ${unmetPrereq.toUpperCase()} is not terminal.` : "Every prerequisite is landed or parked."
6271
+ },
6272
+ {
6273
+ key: "prd-reverified",
6274
+ met: prdReverified,
6275
+ detail: prdReverified ? "No PRD re-verification is pending." : `${issueId} requires PRD re-verification before dispatch.`
6276
+ }
6277
+ ];
6278
+ if (!postureOpen) return {
6279
+ eligible: false,
6280
+ overrideUsed: false,
6281
+ code: "book-draining",
6282
+ message: `Issue ${issueId} cannot start because order book ${input.book.id} is in DRAIN posture; no new book items may dispatch.`,
6283
+ conditions
6284
+ };
6285
+ if (unmetPrereq) return {
6286
+ eligible: false,
6287
+ overrideUsed: false,
6288
+ code: "prerequisite-unmet",
6289
+ message: `Issue ${issueId} cannot start because prerequisite ${unmetPrereq.toUpperCase()} has not landed or been parked.`,
6290
+ conditions
6291
+ };
6292
+ if (!prdReverified) return {
6293
+ eligible: false,
6294
+ overrideUsed: false,
6295
+ code: "reverify-required",
6296
+ message: `Issue ${issueId} cannot start until its PRD has been re-verified against current main.`,
6297
+ conditions
6298
+ };
6299
+ if (laneBBlocker) return {
6300
+ eligible: false,
6301
+ overrideUsed: false,
6302
+ code: "lane-b-busy",
6303
+ message: `Issue ${issueId} cannot start in Lane B because ${laneBBlocker.issue} is still in flight; Lane B allows one active issue at a time.`,
6304
+ conditions
6305
+ };
6306
+ if (item.lane === "A" && !laneSlotFree) return {
6307
+ eligible: false,
6308
+ overrideUsed: false,
6309
+ code: "lane-a-full",
6310
+ message: `Issue ${issueId} cannot start in Lane A because ${laneAInFlight} issues are already in flight and laneAConcurrency is ${input.book.settings.laneAConcurrency}.`,
6311
+ conditions
6312
+ };
6313
+ return {
6314
+ eligible: true,
6315
+ overrideUsed: false,
6316
+ conditions
6317
+ };
6318
+ }
6319
+ //#endregion
6320
+ //#region ../../lib/orders/dispatch-gate.ts
6321
+ init_review_status();
6322
+ init_projects();
6323
+ function stateRootFor(projectRoot) {
6324
+ const project = findProjectByPathSync(projectRoot);
6325
+ if (!project) throw new Error(`No configured project contains ${projectRoot}`);
6326
+ return resolveStateReadHomeSync(project).root;
6327
+ }
6328
+ function defaultInFlightIssues(_bookId, issueIds) {
6329
+ const inFlight = /* @__PURE__ */ new Set();
6330
+ for (const issueId of issueIds) {
6331
+ const lifecycle = getWorkAgentLifecycleStateSync(issueId);
6332
+ const review = getReviewStatusSync(issueId);
6333
+ if (lifecycle.isRunning || review !== null && review.reviewStatus !== "pending") inFlight.add(issueId.toUpperCase());
6334
+ }
6335
+ return inFlight;
6336
+ }
6337
+ const booklessDecision = {
6338
+ eligible: true,
6339
+ overrideUsed: false,
6340
+ conditions: []
6341
+ };
6342
+ async function checkActiveOrderDispatch(projectRoot, issueId, options = {}, deps = {}) {
6343
+ const runId = await (deps.resolveRunId ?? resolveLiveFlywheelRunId)();
6344
+ if (!runId) return {
6345
+ ordersBound: false,
6346
+ decision: booklessDecision
6347
+ };
6348
+ const launch = await (deps.readLaunch ?? readFlywheelLaunchMetadata)(runId);
6349
+ const bookId = launch?.orders?.bookId;
6350
+ if (!bookId) return {
6351
+ ordersBound: false,
6352
+ runId,
6353
+ decision: booklessDecision
6354
+ };
6355
+ const stateRoot = (deps.stateRoot ?? stateRootFor)(launch?.workspace ?? projectRoot);
6356
+ const book = (deps.getOrderBook ?? getBook)(stateRoot, bookId);
6357
+ if (!book) throw new Error(`Active Flywheel run ${runId} references missing order book ${bookId}`);
6358
+ const progress = (deps.computeProgress ?? computeBookProgress)(book);
6359
+ const prerequisiteIds = [...new Set(book.items.flatMap((item) => item.prereqs.map((prereq) => prereq.toUpperCase())))];
6360
+ const prerequisiteState = (deps.issueLookup ?? liveOrderIssueLookup)(prerequisiteIds);
6361
+ const prerequisiteTerminal = new Map(prerequisiteIds.map((prereq) => {
6362
+ const state = prerequisiteState.get(prereq);
6363
+ return [prereq, state ? !state.open || state.parked : false];
6364
+ }));
6365
+ const inFlightIssues = new Set((deps.inFlightIssues ?? defaultInFlightIssues)(book.id, book.items.filter((item) => !progress.items.find((entry) => entry.issue === item.issue)?.terminal).map((item) => item.issue)));
6366
+ for (const reserved of deps.reservedIssues ?? []) inFlightIssues.add(reserved.toUpperCase());
6367
+ return {
6368
+ ordersBound: true,
6369
+ runId,
6370
+ bookId,
6371
+ decision: evaluateOrderDispatchEligibility({
6372
+ book,
6373
+ progress,
6374
+ issueId,
6375
+ inFlightIssues,
6376
+ prerequisiteTerminal,
6377
+ offBook: options.offBook
6378
+ })
6379
+ };
6380
+ }
6381
+ async function recordOffBookOverride(runId, bookId, issueId, actor = process.env["OVERDECK_AGENT_ID"] ?? "operator") {
6382
+ const runDir = getFlywheelRunDir(runId);
6383
+ const path = join(runDir, "orders-overrides.jsonl");
6384
+ await mkdir(runDir, { recursive: true });
6385
+ await appendFile(path, `${JSON.stringify({
6386
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6387
+ runId,
6388
+ bookId,
6389
+ issueId: issueId.toUpperCase(),
6390
+ actor,
6391
+ override: "off-book"
6392
+ })}\n`, "utf8");
6393
+ return path;
6394
+ }
6395
+ //#endregion
6396
+ //#region ../../lib/orders/dispatch-reservation.ts
6397
+ var OrderDispatchReservationError = class extends Error {
6398
+ constructor(check) {
6399
+ super(check.decision.message ?? "Order-book dispatch is blocked.");
6400
+ this.check = check;
6401
+ this.name = "OrderDispatchReservationError";
6402
+ }
6403
+ };
6404
+ const DEFAULT_STALE_MS = 900 * 1e3;
6405
+ const processLocks = /* @__PURE__ */ new Map();
6406
+ let reservationSequence = 0;
6407
+ async function withProcessLock(key, action) {
6408
+ const previous = processLocks.get(key) ?? Promise.resolve();
6409
+ let release;
6410
+ const current = new Promise((resolve) => {
6411
+ release = resolve;
6412
+ });
6413
+ const tail = previous.then(() => current);
6414
+ processLocks.set(key, tail);
6415
+ await previous;
6416
+ try {
6417
+ return await action();
6418
+ } finally {
6419
+ release();
6420
+ if (processLocks.get(key) === tail) processLocks.delete(key);
6421
+ }
6422
+ }
6423
+ function paths(root) {
6424
+ return {
6425
+ state: join(root, "orders-dispatch-reservations.json"),
6426
+ temp: join(root, `orders-dispatch-reservations.${process.pid}.tmp`),
6427
+ lock: join(root, "orders-dispatch-reservations.lock")
6428
+ };
6429
+ }
6430
+ async function acquireFileLock(path, staleMs, now) {
6431
+ try {
6432
+ await mkdir(path);
6433
+ return;
6434
+ } catch (error) {
6435
+ if ((typeof error === "object" && error !== null && "code" in error ? error.code : void 0) !== "EEXIST") throw error;
6436
+ }
6437
+ if (await stat$1(path).then((value) => now.getTime() - value.mtimeMs).catch(() => 0) <= staleMs) throw new Error("Another order-book dispatch admission is in progress; retry after it finishes.");
6438
+ await rm(path, {
6439
+ recursive: true,
6440
+ force: true
6441
+ });
6442
+ await mkdir(path);
6443
+ }
6444
+ async function readReservations(path) {
6445
+ try {
6446
+ const parsed = JSON.parse(await readFile(path, "utf8"));
6447
+ return Array.isArray(parsed) ? parsed : [];
6448
+ } catch (error) {
6449
+ if ((typeof error === "object" && error !== null && "code" in error ? error.code : void 0) === "ENOENT") return [];
6450
+ throw error;
6451
+ }
6452
+ }
6453
+ async function writeReservations(path, tempPath, reservations) {
6454
+ await writeFile(tempPath, `${JSON.stringify(reservations, null, 2)}\n`, {
6455
+ encoding: "utf8",
6456
+ mode: 384
6457
+ });
6458
+ await rename(tempPath, path);
6459
+ }
6460
+ function freshReservations(reservations, now, staleMs) {
6461
+ return reservations.filter((reservation) => {
6462
+ const reservedAt = Date.parse(reservation.reservedAt);
6463
+ return Number.isFinite(reservedAt) && now.getTime() - reservedAt <= staleMs;
6464
+ });
6465
+ }
6466
+ async function reserveActiveOrderDispatch(projectRoot, issueId, options = {}, deps = {}) {
6467
+ const initial = await checkActiveOrderDispatch(projectRoot, issueId, options, deps);
6468
+ if (!initial.ordersBound || !initial.runId || !initial.bookId || !initial.decision.eligible) return {
6469
+ check: initial,
6470
+ release: async () => {}
6471
+ };
6472
+ const root = (deps.reservationRoot ?? getFlywheelRunDir)(initial.runId);
6473
+ await mkdir(root, { recursive: true });
6474
+ const reservationPaths = paths(root);
6475
+ const staleMs = deps.staleMs ?? DEFAULT_STALE_MS;
6476
+ const now = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
6477
+ const reservationId = `${process.pid}-${now.getTime()}-${reservationSequence++}`;
6478
+ let check = initial;
6479
+ await withProcessLock(reservationPaths.lock, async () => {
6480
+ await acquireFileLock(reservationPaths.lock, staleMs, now);
6481
+ try {
6482
+ const current = freshReservations(await readReservations(reservationPaths.state), now, staleMs);
6483
+ check = await checkActiveOrderDispatch(projectRoot, issueId, options, {
6484
+ ...deps,
6485
+ reservedIssues: new Set(current.filter((reservation) => reservation.bookId === initial.bookId).map((reservation) => reservation.issueId))
6486
+ });
6487
+ if (!check.decision.eligible) return;
6488
+ current.push({
6489
+ id: reservationId,
6490
+ bookId: initial.bookId,
6491
+ issueId: issueId.toUpperCase(),
6492
+ reservedAt: now.toISOString()
6493
+ });
6494
+ await writeReservations(reservationPaths.state, reservationPaths.temp, current);
6495
+ } finally {
6496
+ await rm(reservationPaths.lock, {
6497
+ recursive: true,
6498
+ force: true
6499
+ });
6500
+ }
6501
+ });
6502
+ if (!check.decision.eligible) return {
6503
+ check,
6504
+ release: async () => {}
6505
+ };
6506
+ if (options.recordOverride && check.decision.overrideUsed) await (deps.recordOverride ?? recordOffBookOverride)(initial.runId, initial.bookId, issueId);
6507
+ let released = false;
6508
+ return {
6509
+ check,
6510
+ release: async () => {
6511
+ if (released) return;
6512
+ released = true;
6513
+ const releasedAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
6514
+ await withProcessLock(reservationPaths.lock, async () => {
6515
+ await acquireFileLock(reservationPaths.lock, staleMs, releasedAt);
6516
+ try {
6517
+ const current = freshReservations(await readReservations(reservationPaths.state), releasedAt, staleMs).filter((reservation) => reservation.id !== reservationId);
6518
+ await writeReservations(reservationPaths.state, reservationPaths.temp, current);
6519
+ } finally {
6520
+ await rm(reservationPaths.lock, {
6521
+ recursive: true,
6522
+ force: true
6523
+ });
6524
+ }
6525
+ });
6526
+ }
6527
+ };
6528
+ }
6529
+ async function withActiveOrderDispatchReservation(projectRoot, issueId, options, action, deps = {}) {
6530
+ const reservation = await reserveActiveOrderDispatch(projectRoot, issueId, options, deps);
6531
+ if (!reservation.check.decision.eligible) return { check: reservation.check };
6532
+ try {
6533
+ return {
6534
+ check: reservation.check,
6535
+ result: await action()
6536
+ };
6537
+ } finally {
6538
+ await reservation.release();
6539
+ }
6540
+ }
6541
+ //#endregion
5247
6542
  //#region ../../lib/xbrief/auto-synthesize.ts
5248
- init_state_read_home();
5249
- init_state_plane();
5250
- init_types();
5251
- init_codex_auth();
5252
- init_state_home();
5253
6543
  init_io();
5254
6544
  function cleanMarkdownLine(line) {
5255
6545
  return line.replace(/^[-*+]\s+\[[ xX]\]\s+/, "").replace(/^[-*+]\s+/, "").replace(/^#+\s+/, "").replace(/`([^`]+)`/g, "$1").trim();
@@ -5365,222 +6655,21 @@ const writeAutoStartXBrief = (projectRoot, workspacePath, issue) => Effect.tryPr
5365
6655
  })
5366
6656
  });
5367
6657
  //#endregion
5368
- //#region routes/dashboard-auth.ts
5369
- init_issue_lifecycle();
5370
- init_internal_token();
5371
- const DASHBOARD_SESSION_COOKIE = "overdeck_session";
5372
- const DASHBOARD_CSRF_HEADER = "x-overdeck-csrf-token";
5373
- const DASHBOARD_SESSION_MAX_AGE_SECONDS = 3600 * 24 * 30;
5374
- let browserSessionToken;
5375
- let browserCsrfToken;
5376
- function getDashboardSessionToken() {
5377
- if (browserSessionToken) return browserSessionToken;
5378
- const override = process.env["OVERDECK_DASHBOARD_SESSION_TOKEN"];
5379
- if (override) {
5380
- browserSessionToken = override;
5381
- return browserSessionToken;
5382
- }
5383
- const internal = getInternalTokenSync();
5384
- browserSessionToken = internal ? createHmac("sha256", internal).update("overdeck-dashboard-session-v1").digest("base64url") : randomBytes(32).toString("base64url");
5385
- return browserSessionToken;
5386
- }
5387
- function dashboardCsrfToken() {
5388
- if (browserCsrfToken) return browserCsrfToken;
5389
- const override = process.env["OVERDECK_DASHBOARD_CSRF_TOKEN"];
5390
- if (override) {
5391
- browserCsrfToken = override;
5392
- return browserCsrfToken;
5393
- }
5394
- const internal = getInternalTokenSync();
5395
- browserCsrfToken = internal ? createHmac("sha256", internal).update("overdeck-dashboard-csrf-v1").digest("base64url") : randomBytes(32).toString("base64url");
5396
- return browserCsrfToken;
5397
- }
5398
- function constantTimeTokenEqual(provided, expected) {
5399
- if (!provided) return false;
5400
- return timingSafeEqual(createHash$1("sha256").update(provided).digest(), createHash$1("sha256").update(expected).digest());
5401
- }
5402
- function cookieValue(cookieHeader, name) {
5403
- if (!cookieHeader) return void 0;
5404
- for (const part of cookieHeader.split(";")) {
5405
- const [rawKey, ...rawValue] = part.trim().split("=");
5406
- if (rawKey !== name) continue;
5407
- try {
5408
- return decodeURIComponent(rawValue.join("="));
5409
- } catch {
5410
- return;
5411
- }
5412
- }
5413
- }
5414
- function dashboardSessionCookieHeader(options = {}) {
5415
- const secure = options.secure ? "; Secure" : "";
5416
- return `${DASHBOARD_SESSION_COOKIE}=${encodeURIComponent(getDashboardSessionToken())}; Path=/; HttpOnly; SameSite=Strict; Max-Age=${DASHBOARD_SESSION_MAX_AGE_SECONDS}${secure}`;
5417
- }
5418
- function hasDashboardInternalTokenHeaders(headers) {
5419
- const expected = getInternalTokenSync();
5420
- if (!expected) return false;
5421
- if (constantTimeTokenEqual(getHeaderFromMap(headers, "x-overdeck-internal-token"), expected)) return true;
5422
- const authorization = getHeaderFromMap(headers, "authorization");
5423
- if (authorization) {
5424
- const [scheme, token] = authorization.split(/\s+/);
5425
- if (scheme?.toLowerCase() === "bearer" && constantTimeTokenEqual(token, expected)) return true;
5426
- }
5427
- return false;
5428
- }
5429
- function hasDashboardInternalToken(request) {
5430
- return hasDashboardInternalTokenHeaders(request.headers);
5431
- }
5432
- function hasDashboardAuthHeaders(headers) {
5433
- return hasDashboardInternalTokenHeaders(headers) || constantTimeTokenEqual(cookieValue(getHeaderFromMap(headers, "cookie"), "overdeck_session"), getDashboardSessionToken());
5434
- }
5435
- function hasDashboardAuth(request) {
5436
- return hasDashboardAuthHeaders(request.headers);
5437
- }
5438
- function isJsonContentType(headers) {
5439
- const contentType = getHeaderFromMap(headers, "content-type");
5440
- if (!contentType) return false;
5441
- const [mime] = contentType.toLowerCase().split(";");
5442
- return mime.trim() === "application/json";
5443
- }
5444
- function hasTrustedExactOrigin(headers) {
5445
- const origin = getHeaderFromMap(headers, "origin");
5446
- if (!origin) return false;
5447
- const normalized = normalizeOrigin(origin);
5448
- return !!normalized && getTrustedOrigins().includes(normalized);
5449
- }
5450
- function hasValidCsrfToken(headers) {
5451
- return constantTimeTokenEqual(getHeaderFromMap(headers, DASHBOARD_CSRF_HEADER), dashboardCsrfToken());
5452
- }
5453
- function rejectUnsafeDashboardMutationRequest(request) {
5454
- const authError = rejectUnauthorizedDashboardRequest(request);
5455
- if (authError) return authError;
5456
- const headers = request.headers;
5457
- if (!isJsonContentType(headers)) return jsonResponse({ error: "Content-Type must be application/json" }, { status: 400 });
5458
- if (hasDashboardInternalToken(request)) return null;
5459
- if (getHeaderFromMap(headers, "origin") && !hasTrustedExactOrigin(headers)) return jsonResponse({ error: "Invalid origin" }, { status: 403 });
5460
- if (!hasValidCsrfToken(headers)) return jsonResponse({ error: "Invalid CSRF token" }, { status: 403 });
5461
- return null;
5462
- }
5463
- function ipv4ToInt(value) {
5464
- const parts = value.split(".");
5465
- if (parts.length !== 4) return null;
5466
- let result = 0;
5467
- for (const part of parts) {
5468
- const octet = Number(part);
5469
- if (!Number.isInteger(octet) || octet < 0 || octet > 255) return null;
5470
- result = (result << 8 | octet) >>> 0;
5471
- }
5472
- return result;
5473
- }
5474
- /** RFC1918 private ranges — Docker bridges always fall inside one of these. */
5475
- function isRfc1918(ip) {
5476
- return (ip & 4278190080) >>> 0 === 167772160 || (ip & 4293918720) >>> 0 === 2886729728 || (ip & 4294901760) >>> 0 === 3232235520;
5477
- }
5478
- /**
5479
- * True when `addr` is the IP of a container on one of THIS host's Docker bridge
5480
- * interfaces (docker0 / br-*). Such a peer can only be a process running on this
5481
- * machine — our own host-local Traefik fronting overdeck.localhost, or another
5482
- * Overdeck container — never a LAN client (a direct LAN hit to the 0.0.0.0
5483
- * API port arrives with a LAN peer that is not inside any Docker bridge subnet).
5484
- *
5485
- * Pure for testability: the interface map is injected. We only ever trust
5486
- * RFC1918 bridge subnets, so a misconfigured public-range br-* cannot widen
5487
- * trust beyond the local Docker fabric.
5488
- */
5489
- function peerIsHostLocalDockerBridge(addr, interfaces) {
5490
- const peer = ipv4ToInt(addr);
5491
- if (peer === null) return false;
5492
- for (const [name, addrs] of Object.entries(interfaces)) {
5493
- if (name !== "docker0" && !name.startsWith("br-")) continue;
5494
- for (const info of addrs ?? []) {
5495
- if (info.family !== "IPv4") continue;
5496
- const ifaceIp = ipv4ToInt(info.address);
5497
- const mask = ipv4ToInt(info.netmask);
5498
- if (ifaceIp === null || mask === null) continue;
5499
- const network = (ifaceIp & mask) >>> 0;
5500
- if (!isRfc1918(network)) continue;
5501
- if ((peer & mask) >>> 0 === network) return true;
5502
- }
5503
- }
5504
- return false;
5505
- }
5506
- /**
5507
- * True when `addr` shares an RFC1918 subnet with one of THIS process's own
5508
- * network interfaces. This is the in-container counterpart of
5509
- * peerIsHostLocalDockerBridge: inside a container the Docker bridge is
5510
- * invisible — the container sees only its veth endpoints (eth0/eth1), so the
5511
- * docker0/br-* name filter never matches and the host-local Traefik fronting
5512
- * the stack 401'd every session mint (first hit live on the PAN-1737 UAT
5513
- * stack). A peer inside one of the container's attached Docker-network
5514
- * subnets can only be another container on those networks — the server
5515
- * publishes no host port (Traefik labels only), so no LAN client can ever
5516
- * present such an address. Only consulted when running inside a container.
5517
- */
5518
- function peerIsLocalContainerNetwork(addr, interfaces) {
5519
- const peer = ipv4ToInt(addr);
5520
- if (peer === null) return false;
5521
- for (const addrs of Object.values(interfaces)) for (const info of addrs ?? []) {
5522
- if (info.family !== "IPv4" || info.internal) continue;
5523
- const ifaceIp = ipv4ToInt(info.address);
5524
- const mask = ipv4ToInt(info.netmask);
5525
- if (ifaceIp === null || mask === null) continue;
5526
- const network = (ifaceIp & mask) >>> 0;
5527
- if (!isRfc1918(network)) continue;
5528
- if ((peer & mask) >>> 0 === network) return true;
5529
- }
5530
- return false;
5531
- }
5532
- /** Docker creates /.dockerenv in every container — stable in-container signal. */
5533
- const runningInContainer = existsSync("/.dockerenv");
5534
- /**
5535
- * A request we trust to mint a session because it originated from this machine —
5536
- * the local browser hitting the dashboard directly (127.0.0.1), or the
5537
- * host-local Traefik that fronts overdeck.localhost. In Docker, that Traefik reaches
5538
- * the host server from a Docker-bridge IP (e.g. 172.18.0.2), NOT 127.0.0.1, so
5539
- * a literal-loopback check alone silently breaks the zero-step bootstrap for
5540
- * every overdeck.localhost user — their session mint 401s and no cookie is ever set.
5541
- *
5542
- * This is the auto-bootstrap that removes the manual one-time #overdeck_token
5543
- * step: any browser on overdeck.localhost gets a session with no user action. It is
5544
- * NOT a security downgrade for the LAN — the raw API ports bind 0.0.0.0, but a
5545
- * direct LAN hit has a non-loopback, non-Docker-bridge peer and is rejected. We
5546
- * read ONLY the real TCP peer (request.remoteAddress), never X-Forwarded-For,
5547
- * which a caller could spoof.
5548
- */
5549
- function isLoopbackPeer(request) {
5550
- const remoteAddress = request.remoteAddress;
5551
- const raw = remoteAddress && typeof remoteAddress === "object" && "_tag" in remoteAddress ? Option.getOrElse(remoteAddress, () => "") : typeof remoteAddress === "string" ? remoteAddress : "";
5552
- if (!raw) return false;
5553
- const addr = raw.startsWith("::ffff:") ? raw.slice(7) : raw;
5554
- if (addr === "127.0.0.1" || addr === "::1") return true;
5555
- if (peerIsHostLocalDockerBridge(addr, networkInterfaces())) return true;
5556
- return runningInContainer && peerIsLocalContainerNetwork(addr, networkInterfaces());
5557
- }
5558
- function rejectUnauthorizedDashboardSessionMintRequest(request) {
5559
- if (!getInternalTokenSync()) return jsonResponse({ error: "dashboard session token not configured" }, { status: 503 });
5560
- if (!hasDashboardInternalToken(request) && !isLoopbackPeer(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
5561
- return null;
5562
- }
5563
- function rejectUnauthorizedDashboardRequest(request) {
5564
- if (!getInternalTokenSync()) return jsonResponse({ error: "dashboard session token not configured" }, { status: 503 });
5565
- if (!hasDashboardAuth(request)) return jsonResponse({ error: "unauthorized" }, { status: 401 });
5566
- return null;
5567
- }
5568
- //#endregion
5569
6658
  //#region routes/agents/spawn-helpers.ts
5570
6659
  init_child_env();
5571
6660
  init_agents$1();
5572
6661
  function handleRemoteAgentSpawn(input) {
5573
6662
  return Effect.gen(function* () {
5574
6663
  const { issueId, workspacePath, workspaceMetadata, spawnModel, projectPath, spawnGuardrails, lifecycle } = input;
5575
- const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-CRCg1L4H.js"));
5576
- const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-4tiENDaI.js"));
5577
- const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-DUIhMN_T.js"));
6664
+ const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-C1bssi41.js"));
6665
+ const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-CVGUhXMV.js"));
6666
+ const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-DV-grfQc.js"));
5578
6667
  const panConfig = loadPanConfig();
5579
6668
  const spendCap = checkRemoteSpendCap(panConfig);
5580
6669
  if (!spendCap.allowed) return jsonResponse({ error: spendCap.message }, { status: 429 });
5581
6670
  const fly = createFlyProviderFromConfig(panConfig.remote);
5582
6671
  yield* Effect.promise(() => fly.syncAllCredentials(workspaceMetadata.vmName));
5583
- const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-Yb2bd3Fq.js"));
6672
+ const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-C-vuP3_J.js"));
5584
6673
  const trackerContext = yield* Effect.promise(() => getTrackerContext(issueId, workspacePath));
5585
6674
  const agentPrompt = yield* Effect.promise(() => buildWorkAgentPrompt({
5586
6675
  issueId,
@@ -5896,16 +6985,30 @@ function handleContainerOrchestration(input) {
5896
6985
  //#endregion
5897
6986
  //#region routes/agents/spawn.ts
5898
6987
  init_agents$1();
6988
+ init_staffing();
5899
6989
  init_operator_interventions();
5900
6990
  init_child_env();
5901
6991
  init_harness_policy();
5902
6992
  init_activity_logger();
6993
+ init_issue_id();
5903
6994
  init_projects();
5904
6995
  init_review_status();
6996
+ init_state_read_home();
5905
6997
  init_io();
5906
6998
  init_read_model();
5907
6999
  init_domain_services();
5908
7000
  init_tmux();
7001
+ function orderDispatchConflict(decision) {
7002
+ if (decision.eligible) return null;
7003
+ return {
7004
+ status: 409,
7005
+ body: {
7006
+ error: decision.message ?? "Order-book dispatch is blocked.",
7007
+ code: decision.code ?? "order-dispatch-blocked",
7008
+ conditions: decision.conditions
7009
+ }
7010
+ };
7011
+ }
5909
7012
  /**
5910
7013
  * PAN-2386: emit a dashboard activity event when start-agent refuses to spawn
5911
7014
  * because the workspace is dirty. Kept as a pure function so the refusal path
@@ -5925,20 +7028,17 @@ function emitDirtyWorkspaceRefusalActivity(issueId, porcelain) {
5925
7028
  });
5926
7029
  } catch {}
5927
7030
  }
5928
- const LEGACY_WORKSPACE_RUNTIME_FILES = new Set([
5929
- ".pan/continue.json",
5930
- ".pan/spec.vbrief.json",
5931
- ".pan/kickoff.md",
5932
- ".pan/sessions.jsonl",
5933
- ".pan/pipeline-verdict.json",
5934
- ".pan/verification-latest.json",
5935
- ".pan/agent-mcp.json"
5936
- ]);
7031
+ /**
7032
+ * Both workspace runtime directories are Overdeck-owned in full, so both get a
7033
+ * blanket prefix match. The legacy `.pan/` allowlist only matched the collapsed
7034
+ * `?? .pan/` porcelain form plus a closed set of files — but this gate always
7035
+ * runs with `--untracked-files=all`, which expands untracked directories into
7036
+ * individual paths. A pipeline-authored `.pan/drafts/<ISSUE>.md` therefore read
7037
+ * as operator dirt and 409'd the planning→work auto-handoff (PAN-3042).
7038
+ */
5937
7039
  function isOverdeckWorkspaceRuntimePath(path) {
5938
7040
  if (path === ".overdeck" || path.startsWith(".overdeck/")) return true;
5939
- if (path === ".pan" || path === ".pan/") return true;
5940
- if (LEGACY_WORKSPACE_RUNTIME_FILES.has(path)) return true;
5941
- return path.startsWith(".pan/feedback/") || path.startsWith(".pan/review/") || path.startsWith(".pan/test/") || path.startsWith(".pan/handoff-");
7041
+ return path === ".pan" || path === ".pan/" || path.startsWith(".pan/");
5942
7042
  }
5943
7043
  /** True when git porcelain contains only Overdeck-owned workspace runtime files. */
5944
7044
  function isOnlyOverdeckRuntimeWorkspaceChanges(porcelain) {
@@ -5974,6 +7074,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
5974
7074
  const { issueId, projectId } = body;
5975
7075
  const autoStart = body.auto === true;
5976
7076
  const guardrailAcknowledged = body.guardrailAcknowledged === true;
7077
+ const offBook = body.offBook === true;
5977
7078
  const requestedHostOverride = body.host === true || body.allowHost === true;
5978
7079
  if (!issueId) return jsonResponse({ error: "issueId required" }, { status: 400 });
5979
7080
  const legacyFields = [
@@ -6029,6 +7130,8 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6029
7130
  const resolvedProject = resolveProjectFromIssueSync(String(issueId));
6030
7131
  const projectConfig = resolvedProject ? getProjectSync(resolvedProject.projectKey) : null;
6031
7132
  const projectPath = projectConfig?.path ?? getProjectPath(projectId, issuePrefix);
7133
+ const orderConflict = orderDispatchConflict((yield* Effect.promise(() => checkActiveOrderDispatch(projectPath, issueId, { offBook }))).decision);
7134
+ if (orderConflict) return jsonResponse(orderConflict.body, { status: orderConflict.status });
6032
7135
  const workspacePath = join(projectPath, "workspaces", `feature-${issueLower}`);
6033
7136
  if (!existsSync(workspacePath)) try {
6034
7137
  const nodeDir = dirname(process.execPath);
@@ -6128,7 +7231,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6128
7231
  let spawnModel;
6129
7232
  try {
6130
7233
  spawnModel = determineModel({
6131
- model: body.model,
7234
+ model: body.model ?? resolveIssueWorkModel(issueId),
6132
7235
  role,
6133
7236
  spawnKey: `${role}:${issueId}`
6134
7237
  });
@@ -6173,10 +7276,18 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6173
7276
  }
6174
7277
  if (!isRemote) {
6175
7278
  emitStartAgentPhase(issueId, "stackHealthGate", "start", "checking workspace docker stack health", { workspacePath });
6176
- const stackHealth = yield* getWorkspaceStackHealth(issueId, {
7279
+ let stackHealth = yield* getWorkspaceStackHealth(issueId, {
6177
7280
  projectConfig,
6178
7281
  workspacePath
6179
7282
  });
7283
+ if (!stackHealth.healthy && !allowHost) {
7284
+ emitStartAgentPhase(issueId, "stackHealthGate", "start", "stack unhealthy — attempting workspace stack rebuild", { workspacePath });
7285
+ yield* Effect.promise(() => assertWorkspaceStackHealthyForSpawn(issueId, "work", false, workspacePath).catch(() => void 0));
7286
+ stackHealth = yield* getWorkspaceStackHealth(issueId, {
7287
+ projectConfig,
7288
+ workspacePath
7289
+ });
7290
+ }
6180
7291
  if (!stackHealth.healthy) {
6181
7292
  yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, "agent.start_blocked_stack_unhealthy", {
6182
7293
  issueId,
@@ -6278,7 +7389,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6278
7389
  console.warn(`[start-agent] Failed to set plan.status=running (non-fatal): ${planStatusErr?.message ?? planStatusErr}`);
6279
7390
  }
6280
7391
  try {
6281
- const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = await import("./record-t6V-3Dcj.js");
7392
+ const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = await import("./record-CtroO946.js");
6282
7393
  await appendSessionEntry(resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath), issueId, {
6283
7394
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6284
7395
  reason: "start",
@@ -6291,7 +7402,10 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6291
7402
  if (getReviewStatusSync(issueId)?.stuckReason === "planning_auto_handoff_failed") clearWorkspaceStuck(issueId);
6292
7403
  };
6293
7404
  if (isRemote && workspaceMetadata) {
6294
- const response = yield* Effect.promise(() => spawnAfterClearingStartGates({
7405
+ const admitted = yield* Effect.promise(() => withActiveOrderDispatchReservation(projectPath, issueId, {
7406
+ offBook,
7407
+ recordOverride: true
7408
+ }, () => spawnAfterClearingStartGates({
6295
7409
  agentSessionName,
6296
7410
  gate: startGateBlock,
6297
7411
  initialState: initialAgentState,
@@ -6305,7 +7419,10 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6305
7419
  lifecycle
6306
7420
  })),
6307
7421
  isSuccessful: (remoteResponse) => remoteResponse.status >= 200 && remoteResponse.status < 300
6308
- }));
7422
+ })));
7423
+ const admittedConflict = orderDispatchConflict(admitted.check.decision);
7424
+ if (admittedConflict) return jsonResponse(admittedConflict.body, { status: admittedConflict.status });
7425
+ const response = admitted.result;
6309
7426
  if (response.status < 200 || response.status >= 300) return response;
6310
7427
  yield* Effect.promise(commitClearedGates);
6311
7428
  yield* Effect.promise(markWorkStartAccepted);
@@ -6373,11 +7490,14 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6373
7490
  console.warn(`[start-agent] Failed to check workspace status for ${issueId}: ${message}`);
6374
7491
  }
6375
7492
  const bodyHarness = body.harness;
6376
- const userPickedHarness = bodyHarness === "ohmypi" || bodyHarness === "claude-code" || bodyHarness === "codex" ? bodyHarness : null;
7493
+ const userPickedHarness = bodyHarness === "ohmypi" || bodyHarness === "claude-code" || bodyHarness === "codex" || bodyHarness === "acp" ? bodyHarness : null;
6377
7494
  let effectiveHarness = null;
6378
7495
  if (userPickedHarness !== null) effectiveHarness = (yield* Effect.promise(async () => canUseHarnessSync(userPickedHarness, spawnModel, await getProviderAuthMode(spawnModel)))).allowed ? userPickedHarness : "claude-code";
6379
7496
  const spawnPanCommand = async (args, cwd) => {
6380
- const activityId = await spawnAfterClearingStartGates({
7497
+ const admitted = await withActiveOrderDispatchReservation(projectPath, issueId, {
7498
+ offBook,
7499
+ recordOverride: false
7500
+ }, () => spawnAfterClearingStartGates({
6381
7501
  agentSessionName,
6382
7502
  gate: gatesCommitted ? null : startGateBlock,
6383
7503
  initialState: initialAgentState,
@@ -6389,9 +7509,10 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6389
7509
  args,
6390
7510
  cwd
6391
7511
  })
6392
- });
7512
+ }));
7513
+ if (!admitted.check.decision.eligible || !admitted.result) throw new OrderDispatchReservationError(admitted.check);
6393
7514
  await commitClearedGates();
6394
- return activityId;
7515
+ return admitted.result;
6395
7516
  };
6396
7517
  const updateIssueStatus = async () => {
6397
7518
  await Effect.runPromise(lifecycle.transitionTo(issueId, "in_progress").pipe(Effect.catch(() => Effect.void)));
@@ -6475,7 +7596,8 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6475
7596
  issueId,
6476
7597
  model: spawnModel,
6477
7598
  harness: effectiveHarness,
6478
- allowHost
7599
+ allowHost,
7600
+ offBook
6479
7601
  }), workspacePath));
6480
7602
  yield* Effect.promise(markWorkStartAccepted);
6481
7603
  emitStartAgentPhase(issueId, "spawn", "success", "local work agent spawn requested", {
@@ -6506,6 +7628,10 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
6506
7628
  fallbackStatus: fallbackState.status
6507
7629
  }));
6508
7630
  invalidateAgentsCache();
7631
+ if (error instanceof OrderDispatchReservationError) {
7632
+ const conflict = orderDispatchConflict(error.check.decision);
7633
+ return jsonResponse(conflict.body, { status: conflict.status });
7634
+ }
6509
7635
  const output = String(error?.output ?? error?.message ?? "");
6510
7636
  if (output.includes(`Workspace docker stack for ${issueId}`) && output.includes("is not healthy")) {
6511
7637
  const failedStackHealth = yield* getWorkspaceStackHealth(issueId, {
@@ -6713,16 +7839,26 @@ const readJsonBody = Effect.gen(function* () {
6713
7839
  const postMergeGuard = createInFlightGuard();
6714
7840
  const _serverManagedMerges = /* @__PURE__ */ new Set();
6715
7841
  function firePostMergeLifecycle(issueId, options) {
6716
- const started = postMergeGuard.run(issueId, async () => {
6717
- const projectPath = getProjectPathForIssue(extractPrefixSync(issueId) ?? issueId.split("-")[0]);
6718
- const { postMergeLifecycle } = await import("./merge-agent-BQM3a8z5.js");
6719
- await postMergeLifecycle(issueId, projectPath, options?.sourceBranch, options?.verifiedMergedRef ? { verifiedMergedRef: options.verifiedMergedRef } : void 0);
6720
- console.log(`[merge] post-merge lifecycle completed for ${issueId}`);
6721
- const { runMergeTrainReconcile } = await import("./merge-train-DxDfBTjr.js");
6722
- const outcomes = await runMergeTrainReconcile(issueId);
6723
- if (outcomes.length > 0) console.log(`[merge-train] reconciled ${outcomes.length} sibling(s) after ${issueId}: ` + outcomes.map((o) => `${o.issueId}=${o.result}`).join(", "));
6724
- }, (err) => console.error(`[merge] post-merge lifecycle failed for ${issueId}:`, err));
6725
- if (!started) console.log(`[merge] firePostMergeLifecycle: skipping ${issueId} — already in flight`);
7842
+ const parsedIssueId = parseIssueIdSync(issueId.trim());
7843
+ if (!parsedIssueId) {
7844
+ console.error(`[merge] Refusing post-merge lifecycle for invalid issue ID: ${issueId}`);
7845
+ return false;
7846
+ }
7847
+ const normalizedIssueId = parsedIssueId.normalized.toUpperCase();
7848
+ const started = postMergeGuard.run(normalizedIssueId, async () => {
7849
+ const projectPath = getProjectPathForIssue(parsedIssueId.prefix);
7850
+ const { postMergeLifecycle } = await import("./merge-agent-DrXa1CpW.js");
7851
+ const lifecycleOptions = {
7852
+ ...options?.verifiedMergedRef ? { verifiedMergedRef: options.verifiedMergedRef } : {},
7853
+ ...options?.verifiedMergedRepos?.length ? { verifiedMergedRepos: options.verifiedMergedRepos } : {}
7854
+ };
7855
+ await postMergeLifecycle(normalizedIssueId, projectPath, options?.sourceBranch, Object.keys(lifecycleOptions).length > 0 ? lifecycleOptions : void 0);
7856
+ console.log(`[merge] post-merge lifecycle completed for ${normalizedIssueId}`);
7857
+ const { runMergeTrainReconcile } = await import("./merge-train-DTEMyIu4.js");
7858
+ const outcomes = await runMergeTrainReconcile(normalizedIssueId);
7859
+ if (outcomes.length > 0) console.log(`[merge-train] reconciled ${outcomes.length} sibling(s) after ${normalizedIssueId}: ` + outcomes.map((o) => `${o.issueId}=${o.result}`).join(", "));
7860
+ }, (err) => console.error(`[merge] post-merge lifecycle failed for ${normalizedIssueId}:`, err));
7861
+ if (!started) console.log(`[merge] firePostMergeLifecycle: skipping ${normalizedIssueId} — already in flight`);
6726
7862
  return started;
6727
7863
  }
6728
7864
  function getProjectPathForIssue(issuePrefix) {
@@ -6739,15 +7875,15 @@ init_io();
6739
7875
  init_domain_services();
6740
7876
  init_tmux();
6741
7877
  const getSpecialistsRoute = HttpRouter.add("GET", "/api/specialists", httpHandler(Effect.gen(function* () {
6742
- const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
7878
+ const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
6743
7879
  return jsonResponse({
6744
7880
  specialists: yield* Effect.promise(() => getAllSpecialistStatus()),
6745
7881
  projects: yield* Effect.promise(() => getAllProjectSpecialistStatuses())
6746
7882
  });
6747
7883
  })));
6748
7884
  const postSpecialistsResetAllRoute = HttpRouter.add("POST", "/api/specialists/reset-all", httpHandler(Effect.gen(function* () {
6749
- const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
6750
- const { clearHookSync } = yield* Effect.promise(() => import("./hooks-ltecuFqZ.js"));
7885
+ const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7886
+ const { clearHookSync } = yield* Effect.promise(() => import("./hooks-BnJsxlSq.js"));
6751
7887
  const specialists = getAllSpecialists();
6752
7888
  const results = [];
6753
7889
  for (const specialist of specialists) {
@@ -6782,7 +7918,7 @@ const postSpecialistsResetAllRoute = HttpRouter.add("POST", "/api/specialists/re
6782
7918
  const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done", httpHandler(Effect.gen(function* () {
6783
7919
  const body = yield* readJsonBody;
6784
7920
  const eventStore = yield* EventStoreService;
6785
- const { specialist, issueId, status, notes, itemId } = body;
7921
+ const { specialist, issueId, status, notes, itemId, runId } = body;
6786
7922
  const validSpecialists = [
6787
7923
  "review",
6788
7924
  "test",
@@ -6803,7 +7939,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
6803
7939
  }
6804
7940
  console.log(`[specialists/done] ${specialist} signaling ${status} for ${normalizedIssueId}`);
6805
7941
  if (specialist === "merge") {
6806
- const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-BXhav38U.js"));
7942
+ const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-Br48eiDP.js"));
6807
7943
  if (yield* reportSpecialistCompletion(normalizedIssueId, {
6808
7944
  status,
6809
7945
  notes
@@ -6847,10 +7983,27 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
6847
7983
  break;
6848
7984
  }
6849
7985
  const updatedStatus = setReviewStatusSync(normalizedIssueId, update);
6850
- if (specialist === "inspect" && status === "failed") yield* Effect.promise(() => reportTieredInspectFailureEscalation(normalizedIssueId, notes));
7986
+ if (specialist === "inspect" && status === "failed") {
7987
+ yield* Effect.promise(() => reportTieredInspectFailureEscalation(normalizedIssueId, notes));
7988
+ yield* Effect.promise(async () => {
7989
+ try {
7990
+ const failedItemId = itemId ?? getReviewStatusSync(normalizedIssueId)?.inspectBeadId ?? void 0;
7991
+ const project = resolveProjectFromIssueSync(normalizedIssueId);
7992
+ if (project && failedItemId) {
7993
+ const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
7994
+ if (existsSync(workspacePath)) {
7995
+ const { onInspectComplete } = await import("./inspect-agent-BVwDrVrz.js");
7996
+ await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, failedItemId, "failed", workspacePath, notes));
7997
+ }
7998
+ }
7999
+ } catch (err) {
8000
+ console.error(`[specialists/done] Error delivering failed inspect verdict for ${normalizedIssueId}:`, err);
8001
+ }
8002
+ });
8003
+ }
6851
8004
  yield* Effect.promise(async () => {
6852
8005
  try {
6853
- const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-DVj0hvaL.js");
8006
+ const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-Tdezg_6N.js");
6854
8007
  const projectKey = resolveProjectFromIssueSync(normalizedIssueId)?.projectKey;
6855
8008
  const tmuxSession = projectKey ? getTmuxSessionName(`${specialist}-agent`, projectKey, normalizedIssueId) : getTmuxSessionName(`${specialist}-agent`);
6856
8009
  saveAgentRuntimeState(tmuxSession, {
@@ -6869,7 +8022,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
6869
8022
  });
6870
8023
  console.log(`[specialists/done] Cleared registry lock for ${registryKey} (${projectKey})`);
6871
8024
  }
6872
- const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-C45ouoZ6.js");
8025
+ const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-DK-GTz4s.js");
6873
8026
  if (await Effect.runPromise(updateSpecialistHandoffStatus(normalizedIssueId, `${specialist}-agent`, status === "passed" ? "completed" : "failed", status === "passed" ? "success" : "failure"))) console.log(`[specialists/done] Updated handoff log: ${specialist}-agent ${normalizedIssueId} → ${status}`);
6874
8027
  } catch (err) {
6875
8028
  console.error(`[specialists/done] Error managing specialist state:`, err);
@@ -6881,10 +8034,12 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
6881
8034
  if (project) {
6882
8035
  const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
6883
8036
  if (existsSync(workspacePath)) {
6884
- const { getWorkspaceGitInfo } = await import("./git-utils-Dry_Ka_D.js");
6885
- const { HEAD } = await Effect.runPromise(getWorkspaceGitInfo(workspacePath));
6886
- setReviewStatusSync(normalizedIssueId, { reviewedAtCommit: HEAD });
6887
- console.log(`[specialists/done] Snapshotted reviewedAtCommit=${HEAD.substring(0, 8)} for ${normalizedIssueId}`);
8037
+ const { formatAnchorShort, snapshotWorkspaceHeadsPromise } = await import("./git-utils-CI6cwyyC.js");
8038
+ const headAnchor = await snapshotWorkspaceHeadsPromise(normalizedIssueId, workspacePath);
8039
+ if (headAnchor) {
8040
+ setReviewStatusSync(normalizedIssueId, { reviewedAtCommit: headAnchor });
8041
+ console.log(`[specialists/done] Snapshotted reviewedAtCommit=${formatAnchorShort(headAnchor)} for ${normalizedIssueId}`);
8042
+ }
6888
8043
  }
6889
8044
  }
6890
8045
  } catch (err) {
@@ -6893,11 +8048,11 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
6893
8048
  });
6894
8049
  if (specialist === "inspect" && status === "passed") yield* Effect.promise(async () => {
6895
8050
  try {
6896
- const { onInspectComplete } = await import("./inspect-agent-ch-F-5Ci.js");
8051
+ const { onInspectComplete } = await import("./inspect-agent-BVwDrVrz.js");
6897
8052
  const project = resolveProjectFromIssueSync(normalizedIssueId);
6898
8053
  if (project) {
6899
8054
  const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
6900
- if (existsSync(workspacePath)) await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, itemId, "passed", workspacePath));
8055
+ if (existsSync(workspacePath)) await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, itemId, "passed", workspacePath, notes));
6901
8056
  }
6902
8057
  } catch (err) {
6903
8058
  console.error(`[specialists/done] Error saving inspect checkpoint:`, err);
@@ -6909,7 +8064,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
6909
8064
  if (project) {
6910
8065
  if (existsSync(join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`))) {
6911
8066
  setReviewStatusSync(normalizedIssueId, { testStatus: "passed" });
6912
- const { initEventStore } = await import("./event-store-Cr5PQwFC.js");
8067
+ const { initEventStore } = await import("./event-store-ELqkVOn0.js");
6913
8068
  await (await initEventStore()).appendAsync({
6914
8069
  type: "test.passed",
6915
8070
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6962,8 +8117,8 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
6962
8117
  if (notes?.toLowerCase().includes("conflict")) yield* Effect.promise(async () => {
6963
8118
  try {
6964
8119
  const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;
6965
- const { sessionExists } = await import("./tmux-DoThH6aj.js");
6966
- const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-FlIgQ3ql.js");
8120
+ const { sessionExists } = await import("./tmux-DQ6jB76i.js");
8121
+ const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-BKKkxKgK.js");
6967
8122
  if (await Effect.runPromise(sessionExists(workAgentId))) {
6968
8123
  await messageAgent(workAgentId, `MERGE CONFLICT: The merge-agent could not rebase your branch onto main due to conflicts. Please fix this now:\n\n1. git fetch origin main\n2. git rebase origin/main\n3. Resolve any conflicts (git add <file> && git rebase --continue)\n4. git push --force-with-lease\n5. Resubmit: curl -s -X POST http://localhost:3011/api/review/${normalizedIssueId}/request -H "Content-Type: application/json" -d "{}"\n\nConflict details: ${notes}`);
6969
8124
  console.log(`[specialists/done] Sent rebase instructions to ${workAgentId}`);
@@ -6977,19 +8132,38 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
6977
8132
  try {
6978
8133
  const project = resolveProjectFromIssueSync(normalizedIssueId);
6979
8134
  const workspacePath = project ? join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`) : void 0;
6980
- const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-C1Kg8n7t.js");
8135
+ const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-BPDn4li_.js");
6981
8136
  const result = await Effect.runPromise(deliverReviewVerdictFeedback({
6982
8137
  issueId: normalizedIssueId,
6983
8138
  verdict: status === "failed" ? "failed" : "blocked",
6984
8139
  notes,
6985
8140
  workspacePath,
6986
- prUrl: updatedStatus.prUrl
8141
+ prUrl: updatedStatus.prUrl,
8142
+ ...runId ? { runId } : {}
6987
8143
  }));
6988
8144
  console.log(`[specialists/done] Delivered review verdict feedback for ${normalizedIssueId} (feedback=${result.feedbackPath ?? "none"}, synthesis=${result.synthesisPath ?? "none"}, prComment=${result.prCommentPosted})`);
6989
8145
  } catch (err) {
6990
8146
  console.warn(`[specialists/done] Failed to deliver review verdict feedback: ${err.message}`);
6991
8147
  }
6992
8148
  });
8149
+ if (specialist === "review" && status === "failed") yield* Effect.promise(async () => {
8150
+ try {
8151
+ const project = resolveProjectFromIssueSync(normalizedIssueId);
8152
+ if (project) {
8153
+ const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
8154
+ if (existsSync(workspacePath)) {
8155
+ const { formatAnchorShort, snapshotWorkspaceHeadsPromise } = await import("./git-utils-CI6cwyyC.js");
8156
+ const reviewedAtCommit = await snapshotWorkspaceHeadsPromise(normalizedIssueId, workspacePath);
8157
+ if (reviewedAtCommit) {
8158
+ setReviewStatusSync(normalizedIssueId, { reviewedAtCommit });
8159
+ console.log(`[specialists/done] Snapshotted blocked reviewedAtCommit=${formatAnchorShort(reviewedAtCommit)} for ${normalizedIssueId}`);
8160
+ }
8161
+ }
8162
+ }
8163
+ } catch (err) {
8164
+ console.error(`[specialists/done] Failed to snapshot blocked reviewedAtCommit for ${normalizedIssueId}:`, err);
8165
+ }
8166
+ });
6993
8167
  const eventRole = specialistEventRole(specialist);
6994
8168
  if (eventRole) if (status === "passed") yield* eventStore.append({
6995
8169
  type: "specialist.completed",
@@ -7018,7 +8192,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
7018
8192
  });
7019
8193
  })));
7020
8194
  const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/specialists/logs/cleanup-all", httpHandler(Effect.gen(function* () {
7021
- const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-DJ3RzKxW.js"));
8195
+ const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
7022
8196
  const results = cleanupAllLogsSync();
7023
8197
  return jsonResponse({
7024
8198
  success: true,
@@ -7028,7 +8202,7 @@ const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/speciali
7028
8202
  });
7029
8203
  })));
7030
8204
  const getSpecialistsProjectsRoute = HttpRouter.add("GET", "/api/specialists/projects", httpHandler(Effect.gen(function* () {
7031
- const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8205
+ const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7032
8206
  return jsonResponse(yield* Effect.promise(() => getAllProjectSpecialistStatuses()));
7033
8207
  })));
7034
8208
  const postSpecialistWakeRoute = HttpRouter.add("POST", "/api/specialists/:name/wake", httpHandler(Effect.gen(function* () {
@@ -7072,7 +8246,7 @@ const postSpecialistReportStatusRoute = HttpRouter.add("POST", "/api/specialists
7072
8246
  "blocked",
7073
8247
  "failed"
7074
8248
  ].includes(status)) {
7075
- const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8249
+ const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7076
8250
  saveAgentRuntimeState(getTmuxSessionName(name), {
7077
8251
  state: "idle",
7078
8252
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
@@ -7171,6 +8345,7 @@ const specialistsLegacyRouteLayer = Layer.mergeAll(getSpecialistsRoute, getSpeci
7171
8345
  //#endregion
7172
8346
  //#region routes/specialists/project-routes.ts
7173
8347
  init_config_yaml();
8348
+ init_project_repos();
7174
8349
  init_projects();
7175
8350
  init_review_status();
7176
8351
  init_tmux();
@@ -7193,7 +8368,7 @@ const postProjectSpecialistKillRoute = HttpRouter.add("POST", "/api/specialists/
7193
8368
  const issueId = params["issueId"];
7194
8369
  const type = params["type"];
7195
8370
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
7196
- const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8371
+ const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7197
8372
  const tmuxSession = getRunMetadata(project, makeSpecialistRegistryKey(type, issueId)).tmuxSession ?? getTmuxSessionName(type, project, issueId);
7198
8373
  yield* Effect.promise(() => Effect.runPromise(killSession(tmuxSession)).catch(() => {}));
7199
8374
  saveAgentRuntimeState(tmuxSession, {
@@ -7220,7 +8395,7 @@ const getProjectSpecialistRunsRoute = HttpRouter.add("GET", "/api/specialists/:p
7220
8395
  if (limitParam) limit = parseInt(limitParam, 10);
7221
8396
  if (offsetParam) offset = parseInt(offsetParam, 10);
7222
8397
  }
7223
- const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-DJ3RzKxW.js"));
8398
+ const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
7224
8399
  return jsonResponse(listRunLogsSync(project, type, {
7225
8400
  limit,
7226
8401
  offset
@@ -7231,7 +8406,7 @@ const getProjectSpecialistRunStreamRoute = HttpRouter.add("GET", "/api/specialis
7231
8406
  const project = params["project"];
7232
8407
  const type = params["type"];
7233
8408
  const runId = params["runId"];
7234
- const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-DJ3RzKxW.js"));
8409
+ const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
7235
8410
  const logPath = getRunLogPath(project, type, runId);
7236
8411
  if (!existsSync(logPath)) return jsonResponse({ error: "Run log not found" }, { status: 404 });
7237
8412
  const encoder = new TextEncoder();
@@ -7290,7 +8465,7 @@ const getProjectSpecialistRunRoute = HttpRouter.add("GET", "/api/specialists/:pr
7290
8465
  const project = params["project"];
7291
8466
  const type = params["type"];
7292
8467
  const runId = params["runId"];
7293
- const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-DJ3RzKxW.js"));
8468
+ const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
7294
8469
  const content = getRunLogSync(project, type, runId);
7295
8470
  if (!content) return jsonResponse({ error: "Run log not found" }, { status: 404 });
7296
8471
  return jsonResponse({
@@ -7304,7 +8479,7 @@ const postProjectSpecialistRunTerminateRoute = HttpRouter.add("POST", "/api/spec
7304
8479
  const project = params["project"];
7305
8480
  const type = params["type"];
7306
8481
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
7307
- const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8482
+ const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7308
8483
  yield* Effect.promise(() => terminateSpecialist(project, type));
7309
8484
  return jsonResponse({
7310
8485
  success: true,
@@ -7316,7 +8491,7 @@ const postProjectSpecialistGracePauseRoute = HttpRouter.add("POST", "/api/specia
7316
8491
  const project = params["project"];
7317
8492
  const type = params["type"];
7318
8493
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
7319
- const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8494
+ const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7320
8495
  if (pauseGracePeriod(project, type)) return jsonResponse({
7321
8496
  success: true,
7322
8497
  message: "Grace period paused"
@@ -7328,7 +8503,7 @@ const postProjectSpecialistGraceResumeRoute = HttpRouter.add("POST", "/api/speci
7328
8503
  const project = params["project"];
7329
8504
  const type = params["type"];
7330
8505
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
7331
- const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8506
+ const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7332
8507
  if (resumeGracePeriod(project, type)) return jsonResponse({
7333
8508
  success: true,
7334
8509
  message: "Grace period resumed"
@@ -7340,7 +8515,7 @@ const postProjectSpecialistGraceExitRoute = HttpRouter.add("POST", "/api/special
7340
8515
  const project = params["project"];
7341
8516
  const type = params["type"];
7342
8517
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
7343
- const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8518
+ const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7344
8519
  exitGracePeriod(project, type);
7345
8520
  return jsonResponse({
7346
8521
  success: true,
@@ -7352,7 +8527,7 @@ const getProjectSpecialistGraceRoute = HttpRouter.add("GET", "/api/specialists/:
7352
8527
  const project = params["project"];
7353
8528
  const type = params["type"];
7354
8529
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
7355
- const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8530
+ const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7356
8531
  const state = getGracePeriodState(project, type);
7357
8532
  if (state) return jsonResponse(state);
7358
8533
  else return jsonResponse({ error: "No active grace period" }, { status: 404 });
@@ -7361,7 +8536,7 @@ const getProjectSpecialistContextRoute = HttpRouter.add("GET", "/api/specialists
7361
8536
  const params = yield* HttpRouter.params;
7362
8537
  const project = params["project"];
7363
8538
  const type = params["type"];
7364
- const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-DtrYvGJ-.js"));
8539
+ const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-cVbGnTa3.js"));
7365
8540
  const digest = loadContextDigest(project, type);
7366
8541
  if (digest) return jsonResponse({ digest });
7367
8542
  else return jsonResponse({ error: "No context digest found" }, { status: 404 });
@@ -7370,7 +8545,7 @@ const postProjectSpecialistContextRegenerateRoute = HttpRouter.add("POST", "/api
7370
8545
  const params = yield* HttpRouter.params;
7371
8546
  const project = params["project"];
7372
8547
  const type = params["type"];
7373
- const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-DtrYvGJ-.js"));
8548
+ const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-cVbGnTa3.js"));
7374
8549
  const digest = yield* Effect.promise(() => regenerateContextDigest(project, type));
7375
8550
  if (digest) return jsonResponse({
7376
8551
  digest,
@@ -7389,7 +8564,7 @@ const postProjectSpecialistCompleteRoute = HttpRouter.add("POST", "/api/speciali
7389
8564
  "blocked"
7390
8565
  ].includes(status)) return jsonResponse({ error: "Valid status (passed/failed/blocked) is required" }, { status: 400 });
7391
8566
  if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
7392
- const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-DVj0hvaL.js"));
8567
+ const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-Tdezg_6N.js"));
7393
8568
  signalSpecialistCompletion(project, type, {
7394
8569
  status,
7395
8570
  notes
@@ -7423,8 +8598,8 @@ const postProjectSpecialistLogsCleanupRoute = HttpRouter.add("POST", "/api/speci
7423
8598
  const params = yield* HttpRouter.params;
7424
8599
  const project = params["project"];
7425
8600
  const type = params["type"];
7426
- const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-DJ3RzKxW.js"));
7427
- const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-9SkRpES9.js"));
8601
+ const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-Cl5-hqT8.js"));
8602
+ const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-B03Wr-xc.js"));
7428
8603
  const retention = getSpecialistRetention(project);
7429
8604
  const deleted = cleanupOldLogsSync(project, type, {
7430
8605
  maxDays: retention.max_days,
@@ -7445,21 +8620,23 @@ const postProjectReviewRestartRoute = HttpRouter.add("POST", "/api/specialists/:
7445
8620
  const project = params["project"];
7446
8621
  const issueId = params["issueId"];
7447
8622
  const { model, harness } = yield* readJsonBody;
7448
- const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-DJRO8EG8.js"));
8623
+ const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-CD4RCI0Y.js"));
7449
8624
  const killResult = yield* killAllReviewerSessions(project, issueId);
7450
8625
  const projectConfig = resolveProjectFromIssueSync(issueId);
7451
8626
  if (!projectConfig) return jsonResponse({ error: `Cannot resolve project for ${issueId}` }, { status: 404 });
7452
8627
  const workspacePath = join(projectConfig.projectPath, "workspaces", `feature-${issueId.toLowerCase()}`);
7453
8628
  if (!existsSync(workspacePath)) return jsonResponse({ error: `Workspace not found: ${workspacePath}` }, { status: 404 });
7454
- let branch = "unknown";
8629
+ const primaryRepo = resolveWorkspaceRepoRootsSync(issueId, workspacePath)[0];
8630
+ let branch = primaryRepo.sourceBranch;
7455
8631
  try {
7456
- const { stdout } = yield* Effect.promise(() => execAsync(`cd "${workspacePath}" && git branch --show-current`, {
8632
+ const { stdout } = yield* Effect.promise(() => execAsync("git branch --show-current", {
8633
+ cwd: resolvePrimaryWorkspaceRepoDirSync(issueId, workspacePath),
7457
8634
  encoding: "utf-8",
7458
8635
  timeout: 5e3
7459
8636
  }));
7460
- branch = stdout.trim() || "unknown";
8637
+ branch = stdout.trim() || primaryRepo.sourceBranch;
7461
8638
  } catch {}
7462
- const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-DJRO8EG8.js"));
8639
+ const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-CD4RCI0Y.js"));
7463
8640
  const prUrl = getReviewStatusSync(issueId)?.prUrl;
7464
8641
  const result = yield* spawnReviewRoleForIssue({
7465
8642
  issueId,
@@ -7608,6 +8785,6 @@ const specialistsProjectRouteLayer = Layer.mergeAll(getProjectSpecialistStatusRo
7608
8785
  */
7609
8786
  const specialistsRouteLayer = Layer.mergeAll(specialistsLegacyRouteLayer, specialistsProjectRouteLayer);
7610
8787
  //#endregion
7611
- export { runInBatches as $, handleConversationDelete as A, stopConversationRuntime as B, isPiControlChannelHarness as C, conversationNeedsRunningRepair as D, validateConversationControlAckOrigin as E, isInsideGitWorkTree as F, cleanupOrphanedConversationAttachments as G, waitForPiTuiReady as H, resolveAllowedHarness as I, extractConversationAttachmentPaths as J, cleanupUnreferencedConversationAttachments as K, shouldReportUnresolvedLiveSession as L, handleConversationResume as M, handleConversationStop as N, conversationSessionAliveFromState as O, handleConversationSwitchModel as P, removeConversationAttachment as Q, spawnConversationSession as R, handleConversationThinkingLevel as S, jsonStringResponse as St, resolveConversationDeliveryMethod as T, waitForTmuxSession as U, tmuxSessionExists as V, cleanupConversationAttachments as W, hasConversationAttachment as X, getConversationAttachmentsRoot as Y, isManagedConversationAttachmentPath as Z, handleConversationCodexApproval as _, validateOrigin as _t, agentsRouteLayer as a, panCliInvocation as at, handleConversationDeliveryMethod as b, getDefaultCwd as bt, hasDashboardInternalToken as c, resolveAgentHarness as ct, rejectUnsafeDashboardMutationRequest as d, resolveJsonlPath as dt, evaluateSpawnGuardrails as et, saveAgentStateAndEmitEvent as f, resolvePiSessionPath as ft, handleConversationAbort as g, isTrustedOriginForHost as gt, deliverConversationViaControlChannel as h, getHeaderFromMap as ht, validateSpecialistAutoCompleteMetadata as i, getWorkAgentLifecycleStateSync as it, handleConversationRestartAll as j, handleConversationCreate as k, rejectUnauthorizedDashboardRequest as l, resolveClaudeSessionId as lt, codexConversationPendingInput as m, waitForSessionRespawn as mt, _serverManagedMerges as n, resolveAgentGitInfo as nt, dashboardCsrfToken as o, spawnPanCli as ot, saveAgentStateAndEmitEventProgram as p, isRespawnPending as pt, ensureConversationAttachmentDir as q, firePostMergeLifecycle as r, resolveConversationGitInfo as rt, dashboardSessionCookieHeader as s, readLauncherPinnedSessionId as st, specialistsRouteLayer as t, invalidateAgentsCache as tt, rejectUnauthorizedDashboardSessionMintRequest as u, resolveCodexRolloutPath as ut, handleConversationCompact as v, validateOriginHeaders as vt, pickDeliverAs as w, handleConversationPlanAction as x, jsonResponse as xt, handleConversationControlAck as y, httpHandler as yt, startConversationModelBackfill as z };
8788
+ export { composerCommandConfirmationFromBody as $, resolveAllowedHarness as A, cleanupUnreferencedConversationAttachments as B, handleConversationCreate as C, isTrustedOriginForHost as Ct, handleConversationStop as D, getDefaultCwd as Dt, handleConversationResume as E, httpHandler as Et, tmuxSessionExists as F, isManagedConversationAttachmentPath as G, extractConversationAttachmentPaths as H, waitForPiTuiReady as I, composerCommandResultHttpStatus as J, removeConversationAttachment as K, waitForTmuxSession as L, spawnConversationSession as M, startConversationModelBackfill as N, handleConversationSwitchModel as O, jsonResponse as Ot, stopConversationRuntime as P, ComposerCommandConfirmationError as Q, cleanupConversationAttachments as R, conversationSessionAliveFromState as S, getHeaderFromMap as St, handleConversationRestartAll as T, validateOriginHeaders as Tt, getConversationAttachmentsRoot as U, ensureConversationAttachmentDir as V, hasConversationAttachment as W, resolvePolicy as X, handleComposerCommandMessage as Y, ComposerCommandParseError as Z, isPiControlChannelHarness as _, rejectUnauthorizedDashboardRequest as _t, agentsRouteLayer as a, panCliInvocation as at, validateConversationControlAckOrigin as b, isRespawnPending as bt, codexConversationPendingInput as c, resolveAcpTranscriptPath as ct, handleConversationCodexApproval as d, resolveCodexRolloutPath as dt, evaluateSpawnGuardrails as et, handleConversationCompact as f, resolveJsonlPath as ft, handleConversationThinkingLevel as g, hasDashboardInternalToken as gt, handleConversationPlanAction as h, dashboardSessionCookieHeader as ht, validateSpecialistAutoCompleteMetadata as i, getWorkAgentLifecycleStateSync as it, shouldReportUnresolvedLiveSession as j, isInsideGitWorkTree as k, jsonStringResponse as kt, deliverConversationViaControlChannel as l, resolveAgentHarness as lt, handleConversationDeliveryMethod as m, dashboardCsrfToken as mt, _serverManagedMerges as n, resolveAgentGitInfo as nt, saveAgentStateAndEmitEvent as o, spawnPanCli as ot, handleConversationControlAck as p, resolvePiSessionPath as pt, runInBatches as q, firePostMergeLifecycle as r, resolveConversationGitInfo as rt, saveAgentStateAndEmitEventProgram as s, readLauncherPinnedSessionId as st, specialistsRouteLayer as t, invalidateAgentsCache as tt, handleConversationAbort as u, resolveClaudeSessionId as ut, pickDeliverAs as v, rejectUnauthorizedDashboardSessionMintRequest as vt, handleConversationDelete as w, validateOrigin as wt, conversationNeedsRunningRepair as x, waitForSessionRespawn as xt, resolveConversationDeliveryMethod as y, rejectUnsafeDashboardMutationRequest as yt, cleanupOrphanedConversationAttachments as z };
7612
8789
 
7613
- //# sourceMappingURL=specialists-JVO00bs7.js.map
8790
+ //# sourceMappingURL=specialists-CNfYO3G2.js.map