@overdeck/core 0.50.3 → 0.50.4

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 (208) hide show
  1. package/dist/agents-DNMRPC8n.js.map +1 -1
  2. package/dist/{build-info-BJuEVWOh.js → build-info-ioRdOmqT.js} +3 -3
  3. package/dist/{build-info-BJuEVWOh.js.map → build-info-ioRdOmqT.js.map} +1 -1
  4. package/dist/build-info-kMMitDOP.js +2 -0
  5. package/dist/cli/index.js +4 -4
  6. package/dist/conversation-compaction-GFfO5UZL.js.map +1 -1
  7. package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js → advancing-selfheal-Dp5_Gai3.js} +3 -3
  8. package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js.map → advancing-selfheal-Dp5_Gai3.js.map} +1 -1
  9. package/dist/dashboard/{agent-gc-Coz8zQt_.js → agent-gc-JzoSs5Pi.js} +3 -3
  10. package/dist/dashboard/{agent-gc-Coz8zQt_.js.map → agent-gc-JzoSs5Pi.js.map} +1 -1
  11. package/dist/dashboard/{agent-idle-CAwB-K6M.js → agent-idle-D-Qy5BN_.js} +2 -2
  12. package/dist/dashboard/{agent-idle-CAwB-K6M.js.map → agent-idle-D-Qy5BN_.js.map} +1 -1
  13. package/dist/dashboard/{agent-state-Ci6PhjOE.js → agent-state-B196fj1h.js} +38 -16
  14. package/dist/dashboard/agent-state-B196fj1h.js.map +1 -0
  15. package/dist/dashboard/{agent-state-CbUEwUEQ.js → agent-state-IpWnw8lv.js} +1 -1
  16. package/dist/dashboard/{agent-state-service-D6IVrX3B.js → agent-state-service-CCo5-9DR.js} +3 -3
  17. package/dist/dashboard/{agent-state-service-D6IVrX3B.js.map → agent-state-service-CCo5-9DR.js.map} +1 -1
  18. package/dist/dashboard/{agent-state-service-DcN1j6eK.js → agent-state-service-D_F3VZ23.js} +1 -1
  19. package/dist/dashboard/{agents-BVvL4o_4.js → agents-CkGhnAmu.js} +1 -1
  20. package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js → backlog-auto-trigger-B5Z2zBtC.js} +2 -2
  21. package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js.map → backlog-auto-trigger-B5Z2zBtC.js.map} +1 -1
  22. package/dist/dashboard/build-info-BTjOB0Xr.js +2 -0
  23. package/dist/dashboard/{build-info-Cj3Y0K87.js → build-info-D0EJNj6C.js} +3 -3
  24. package/dist/dashboard/{build-info-Cj3Y0K87.js.map → build-info-D0EJNj6C.js.map} +1 -1
  25. package/dist/dashboard/cli-CIznFrcE.js +2 -0
  26. package/dist/dashboard/{cli-DIhQieif.js → cli-DCbVrSsg.js} +2 -2
  27. package/dist/dashboard/{cli-DIhQieif.js.map → cli-DCbVrSsg.js.map} +1 -1
  28. package/dist/dashboard/{close-issue-CRlmrE5g.js → close-issue-Bmu9aOIx.js} +2 -2
  29. package/dist/dashboard/{close-issue-CRlmrE5g.js.map → close-issue-Bmu9aOIx.js.map} +1 -1
  30. package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js → closed-issue-reaper-COxfTtLz.js} +5 -5
  31. package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js.map → closed-issue-reaper-COxfTtLz.js.map} +1 -1
  32. package/dist/dashboard/{closed-issue-reaper-DWlw2EtL.js → closed-issue-reaper-DwMwG_Pz.js} +1 -1
  33. package/dist/dashboard/{compaction-continuation-DhYgsXmF.js → compaction-continuation-Bqc212Zm.js} +2 -2
  34. package/dist/dashboard/{compaction-continuation-DhYgsXmF.js.map → compaction-continuation-Bqc212Zm.js.map} +1 -1
  35. package/dist/dashboard/{conflict-gate-vczgdVTa.js → conflict-gate-9mPVg7-5.js} +1 -1
  36. package/dist/dashboard/{conflict-gate-Bt-lLgYf.js → conflict-gate-CGQTcR0I.js} +3 -3
  37. package/dist/dashboard/{conflict-gate-Bt-lLgYf.js.map → conflict-gate-CGQTcR0I.js.map} +1 -1
  38. package/dist/dashboard/{conversation-compaction-CsdCbcQB.js → conversation-compaction-C7P6V8Iw.js} +5 -5
  39. package/dist/dashboard/{conversation-compaction-CsdCbcQB.js.map → conversation-compaction-C7P6V8Iw.js.map} +1 -1
  40. package/dist/dashboard/{conversation-compaction-D0zgWM1f.js → conversation-compaction-Cck5oi-V.js} +1 -1
  41. package/dist/dashboard/{conversation-service-6LaymUV-.js → conversation-service-CsOpwI9Y.js} +37 -5
  42. package/dist/dashboard/{conversation-service-6LaymUV-.js.map → conversation-service-CsOpwI9Y.js.map} +1 -1
  43. package/dist/dashboard/conversation-service-plOFkoMo.js +3 -0
  44. package/dist/dashboard/dashboard-db-worker.js +1 -1
  45. package/dist/dashboard/{deacon-CU47dU7U.js → deacon-B8oMMIgN.js} +62 -62
  46. package/dist/dashboard/{deacon-CU47dU7U.js.map → deacon-B8oMMIgN.js.map} +1 -1
  47. package/dist/dashboard/{deacon-DYsOa_ss.js → deacon-b7J57Svt.js} +2 -2
  48. package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js → deacon-pending-lifecycle-3NIJ7ew5.js} +2 -2
  49. package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js.map → deacon-pending-lifecycle-3NIJ7ew5.js.map} +1 -1
  50. package/dist/dashboard/deacon.js +5 -5
  51. package/dist/dashboard/{delivery-DMYxIE0J.js → delivery-lnI-gQKo.js} +1 -1
  52. package/dist/dashboard/{deploy-patrol-C4eXco1S.js → deploy-patrol-u3v2krg9.js} +2 -2
  53. package/dist/dashboard/{deploy-patrol-C4eXco1S.js.map → deploy-patrol-u3v2krg9.js.map} +1 -1
  54. package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js → feedback-stuck-retirement-CoFNqjCc.js} +2 -2
  55. package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js.map → feedback-stuck-retirement-CoFNqjCc.js.map} +1 -1
  56. package/dist/dashboard/{feedback-target-BzdJ2wIR.js → feedback-target-BBCh3RRq.js} +1 -1
  57. package/dist/dashboard/{feedback-target-BcsA_GRV.js → feedback-target-CeCq6EhW.js} +5 -5
  58. package/dist/dashboard/{feedback-target-BcsA_GRV.js.map → feedback-target-CeCq6EhW.js.map} +1 -1
  59. package/dist/dashboard/{flywheel-Is3w2lcT.js → flywheel-DKldI63J.js} +7 -7
  60. package/dist/dashboard/{flywheel-Is3w2lcT.js.map → flywheel-DKldI63J.js.map} +1 -1
  61. package/dist/dashboard/{flywheel-D0guNqkh.js → flywheel-DpRPLErh.js} +7 -7
  62. package/dist/dashboard/{flywheel-D0guNqkh.js.map → flywheel-DpRPLErh.js.map} +1 -1
  63. package/dist/dashboard/{flywheel-B34cdfbd.js → flywheel-jeZFYvm-.js} +1 -1
  64. package/dist/dashboard/{flywheel-merge-order-6HiPR2WG.js → flywheel-merge-order-0OnoosaO.js} +1 -1
  65. package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js → flywheel-merge-order-PG5op4j7.js} +2 -2
  66. package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js.map → flywheel-merge-order-PG5op4j7.js.map} +1 -1
  67. package/dist/dashboard/{harness-resolve-CzTFVZ5l.js → harness-resolve-C0HWj3H9.js} +2 -2
  68. package/dist/dashboard/{harness-resolve-CzTFVZ5l.js.map → harness-resolve-C0HWj3H9.js.map} +1 -1
  69. package/dist/dashboard/{identity-Dc7rQLln.js → identity-Cot1Hg4G.js} +2 -2
  70. package/dist/dashboard/{identity-Dc7rQLln.js.map → identity-Cot1Hg4G.js.map} +1 -1
  71. package/dist/dashboard/{inspect-agent-CP7Bu47O.js → inspect-agent-BDmLWbGx.js} +3 -3
  72. package/dist/dashboard/{inspect-agent-CP7Bu47O.js.map → inspect-agent-BDmLWbGx.js.map} +1 -1
  73. package/dist/dashboard/inspect-agent-BcOkA-hC.js +2 -0
  74. package/dist/dashboard/{issue-lifecycle-BPbczKyy.js → issue-lifecycle-1B_v8Rf5.js} +6 -6
  75. package/dist/dashboard/{issue-lifecycle-BPbczKyy.js.map → issue-lifecycle-1B_v8Rf5.js.map} +1 -1
  76. package/dist/dashboard/{issue-lifecycle-FaugTi39.js → issue-lifecycle-CIrfbIbj.js} +1 -1
  77. package/dist/dashboard/{issue-service-singleton-Dsad9NTt.js → issue-service-singleton-BVwPzjwF.js} +1 -1
  78. package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js → issue-service-singleton-KcRrOiFD.js} +2 -2
  79. package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js.map → issue-service-singleton-KcRrOiFD.js.map} +1 -1
  80. package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js → jsonl-resolver-jxbif7MD.js} +3 -3
  81. package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js.map → jsonl-resolver-jxbif7MD.js.map} +1 -1
  82. package/dist/dashboard/{kimi-code-Cx5YUJpS.js → kimi-code-BZh3NgcN.js} +1 -1
  83. package/dist/dashboard/{lifecycle-DZFHmpGI.js → lifecycle-CvcZ4_fA.js} +4 -4
  84. package/dist/dashboard/{lifecycle-DZFHmpGI.js.map → lifecycle-CvcZ4_fA.js.map} +1 -1
  85. package/dist/dashboard/{linear-mcp-auth-DylPJABZ.js → linear-mcp-auth-CZoj_-d7.js} +1 -1
  86. package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js → linear-mcp-auth-Dx74yCAY.js} +2 -2
  87. package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js.map → linear-mcp-auth-Dx74yCAY.js.map} +1 -1
  88. package/dist/dashboard/{memory-governor-B7uDWRo9.js → memory-governor-Bv3LAqk3.js} +5 -5
  89. package/dist/dashboard/{memory-governor-B7uDWRo9.js.map → memory-governor-Bv3LAqk3.js.map} +1 -1
  90. package/dist/dashboard/{merge-agent-ddEcZDZD.js → merge-agent-1zVLl6k_.js} +1 -1
  91. package/dist/dashboard/{merge-agent-ttRY053V.js → merge-agent-8zrQu48t.js} +8 -8
  92. package/dist/dashboard/{merge-agent-ttRY053V.js.map → merge-agent-8zrQu48t.js.map} +1 -1
  93. package/dist/dashboard/merge-ops-CFJaMPLu.js +2 -0
  94. package/dist/dashboard/{merge-train-f_un38UJ.js → merge-train-BYH_cF7G.js} +2 -2
  95. package/dist/dashboard/{merge-train-f_un38UJ.js.map → merge-train-BYH_cF7G.js.map} +1 -1
  96. package/dist/dashboard/{merge-train-deps-GPlHOjK6.js → merge-train-deps-BTfXM5AR.js} +2 -2
  97. package/dist/dashboard/{merge-train-deps-GPlHOjK6.js.map → merge-train-deps-BTfXM5AR.js.map} +1 -1
  98. package/dist/dashboard/{messaging-BasbftDu.js → messaging-Bk6vGsMw.js} +1 -1
  99. package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js → orphan-proposed-reconciler-B898fk26.js} +6 -6
  100. package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js.map → orphan-proposed-reconciler-B898fk26.js.map} +1 -1
  101. package/dist/dashboard/{orphan-proposed-reconciler-DzAxrM99.js → orphan-proposed-reconciler-Bx_aWS4B.js} +1 -1
  102. package/dist/dashboard/{parked-residue-DMtbxJml.js → parked-residue-q2kzrYSm.js} +9 -9
  103. package/dist/dashboard/{parked-residue-DMtbxJml.js.map → parked-residue-q2kzrYSm.js.map} +1 -1
  104. package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js → post-merge-lifecycle-worker-Cl7-uf4g.js} +2 -2
  105. package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js.map → post-merge-lifecycle-worker-Cl7-uf4g.js.map} +1 -1
  106. package/dist/dashboard/{project-resource-refresh-queue-cqOeJ0fB.js → project-resource-refresh-queue-O_dRfPHn.js} +1 -1
  107. package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js → project-resource-refresh-queue-se3T9IYP.js} +4 -4
  108. package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js.map → project-resource-refresh-queue-se3T9IYP.js.map} +1 -1
  109. package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js → reconstruct-cache-XpWC9L0F.js} +2 -2
  110. package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js.map → reconstruct-cache-XpWC9L0F.js.map} +1 -1
  111. package/dist/dashboard/{release-engine-78iCajIc.js → release-engine-BuzxOamf.js} +2 -2
  112. package/dist/dashboard/{release-engine-78iCajIc.js.map → release-engine-BuzxOamf.js.map} +1 -1
  113. package/dist/dashboard/{remote-completion-D5wzZCte.js → remote-completion-BCZgeKPS.js} +3 -3
  114. package/dist/dashboard/{remote-completion-D5wzZCte.js.map → remote-completion-BCZgeKPS.js.map} +1 -1
  115. package/dist/dashboard/{resolver-BGZh3lXA.js → resolver-BeNrqNou.js} +5 -5
  116. package/dist/dashboard/{resolver-BGZh3lXA.js.map → resolver-BeNrqNou.js.map} +1 -1
  117. package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js → resource-pressure-patrol-Ccg_aCgO.js} +3 -3
  118. package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js.map → resource-pressure-patrol-Ccg_aCgO.js.map} +1 -1
  119. package/dist/dashboard/{resume-BKyB4hA1.js → resume-KGCrn0DV.js} +1 -1
  120. package/dist/dashboard/{review-agent-ZdP-HmwY.js → review-agent-BmM6YMZW.js} +1 -1
  121. package/dist/dashboard/{review-agent-DHs-JngN.js → review-agent-DRWVU8fP.js} +13 -13
  122. package/dist/dashboard/{review-agent-DHs-JngN.js.map → review-agent-DRWVU8fP.js.map} +1 -1
  123. package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js → review-convoy-liveness-EvCDv8Q_.js} +3 -3
  124. package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js.map → review-convoy-liveness-EvCDv8Q_.js.map} +1 -1
  125. package/dist/dashboard/{review-run-recovery-BYalQzbh.js → review-run-recovery-Ctm48Snt.js} +1 -1
  126. package/dist/dashboard/{review-run-recovery-BvVG3oUr.js → review-run-recovery-DNDI58cM.js} +2 -2
  127. package/dist/dashboard/{review-run-recovery-BvVG3oUr.js.map → review-run-recovery-DNDI58cM.js.map} +1 -1
  128. package/dist/dashboard/{review-status-DXTFzCmR.js → review-status-FnlvQuPI.js} +1 -1
  129. package/dist/dashboard/{review-status-emit-C9YW6SQL.js → review-status-emit-CeMS31DX.js} +2 -2
  130. package/dist/dashboard/{review-status-emit-C9YW6SQL.js.map → review-status-emit-CeMS31DX.js.map} +1 -1
  131. package/dist/dashboard/{review-status-BegAr86G.js → review-status-yY5qt7Rl.js} +5 -5
  132. package/dist/dashboard/{review-status-BegAr86G.js.map → review-status-yY5qt7Rl.js.map} +1 -1
  133. package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js → review-verdict-feedback-BNgRj1e3.js} +4 -4
  134. package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js.map → review-verdict-feedback-BNgRj1e3.js.map} +1 -1
  135. package/dist/dashboard/{review-verdict-feedback-Dp139Lmv.js → review-verdict-feedback-D39cuSV1.js} +1 -1
  136. package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js → review-verdict-writer-DvFfKAn-.js} +2 -2
  137. package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js.map → review-verdict-writer-DvFfKAn-.js.map} +1 -1
  138. package/dist/dashboard/{review-verdict-writer-BVco3WoI.js → review-verdict-writer-PcGBkvJ4.js} +1 -1
  139. package/dist/dashboard/{sequence-io-iljWlslY.js → sequence-io-DZyschsf.js} +2 -2
  140. package/dist/dashboard/{sequence-io-iljWlslY.js.map → sequence-io-DZyschsf.js.map} +1 -1
  141. package/dist/dashboard/{sequencer-agent-BGGVKxtw.js → sequencer-agent-BXDw5cNx.js} +5 -5
  142. package/dist/dashboard/{sequencer-agent-BGGVKxtw.js.map → sequencer-agent-BXDw5cNx.js.map} +1 -1
  143. package/dist/dashboard/server.js +79 -72
  144. package/dist/dashboard/server.js.map +1 -1
  145. package/dist/dashboard/{service-kzhN0DLg.js → service-DP50Omvn.js} +33 -33
  146. package/dist/dashboard/{service-kzhN0DLg.js.map → service-DP50Omvn.js.map} +1 -1
  147. package/dist/dashboard/{smart-compaction-owKHJDbL.js → smart-compaction-944GL8PN.js} +1 -1
  148. package/dist/dashboard/{smart-compaction-C96F2aFQ.js → smart-compaction-BzDfF6yb.js} +2 -2
  149. package/dist/dashboard/{smart-compaction-C96F2aFQ.js.map → smart-compaction-BzDfF6yb.js.map} +1 -1
  150. package/dist/dashboard/{spawn-planning-session-CiJdOHTD.js → spawn-planning-session-BcVnHJjm.js} +1 -1
  151. package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js → spawn-planning-session-CH8IYli7.js} +3 -3
  152. package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js.map → spawn-planning-session-CH8IYli7.js.map} +1 -1
  153. package/dist/dashboard/{specialist-context-_ootmzn2.js → specialist-context-DhDTXVht.js} +3 -3
  154. package/dist/dashboard/{specialist-context-_ootmzn2.js.map → specialist-context-DhDTXVht.js.map} +1 -1
  155. package/dist/dashboard/{specialist-logs-BbAX221-.js → specialist-logs-C1bcnZ-G.js} +2 -2
  156. package/dist/dashboard/{specialist-logs-BbAX221-.js.map → specialist-logs-C1bcnZ-G.js.map} +1 -1
  157. package/dist/dashboard/{specialists-COZoYaiz.js → specialists-BKmReEWT.js} +53 -53
  158. package/dist/dashboard/{specialists-COZoYaiz.js.map → specialists-BKmReEWT.js.map} +1 -1
  159. package/dist/dashboard/{specialists-RPLJnYc1.js → specialists-Bvrvt0br.js} +12 -12
  160. package/dist/dashboard/{specialists-RPLJnYc1.js.map → specialists-Bvrvt0br.js.map} +1 -1
  161. package/dist/dashboard/{specialists-DTAOVZpU.js → specialists-CD6GEKXk.js} +1 -1
  162. package/dist/dashboard/{specialists-D8sYZnPb.js → specialists-eFWpJRBc.js} +1 -1
  163. package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js → stall-sweeper-D3fZvg7O.js} +3 -3
  164. package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js.map → stall-sweeper-D3fZvg7O.js.map} +1 -1
  165. package/dist/dashboard/{state-migrate-J3tFJnbd.js → state-migrate-KZ2goscQ.js} +2 -2
  166. package/dist/dashboard/{state-migrate-J3tFJnbd.js.map → state-migrate-KZ2goscQ.js.map} +1 -1
  167. package/dist/dashboard/{stuck-remediation-BvTK_NYN.js → stuck-remediation-Cb2V1roj.js} +1 -1
  168. package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js → stuck-remediation-CnoV0kcv.js} +5 -5
  169. package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js.map → stuck-remediation-CnoV0kcv.js.map} +1 -1
  170. package/dist/dashboard/{summary-fork-PLcckTRE.js → summary-fork-CxuNBM0F.js} +2 -2
  171. package/dist/dashboard/{summary-fork-D-cylknJ.js → summary-fork-lC2vMXly.js} +4 -4
  172. package/dist/dashboard/{summary-fork-D-cylknJ.js.map → summary-fork-lC2vMXly.js.map} +1 -1
  173. package/dist/dashboard/{teardown-workspace-CTL16rnG.js → teardown-workspace-C-9murBw.js} +4 -4
  174. package/dist/dashboard/{teardown-workspace-CTL16rnG.js.map → teardown-workspace-C-9murBw.js.map} +1 -1
  175. package/dist/dashboard/{test-agent-queue-CMgYZiR_.js → test-agent-queue-YCBRzaAB.js} +3 -3
  176. package/dist/dashboard/{test-agent-queue-CMgYZiR_.js.map → test-agent-queue-YCBRzaAB.js.map} +1 -1
  177. package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js → uat-failure-feedback-B-1mL8Zp.js} +3 -3
  178. package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js.map → uat-failure-feedback-B-1mL8Zp.js.map} +1 -1
  179. package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js → uat-promote-verification-BEBPcpPg.js} +2 -2
  180. package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js.map → uat-promote-verification-BEBPcpPg.js.map} +1 -1
  181. package/dist/dashboard/{uat-train-BoA9r6_I.js → uat-train-BFo3OQUB.js} +1 -1
  182. package/dist/dashboard/{uat-train-Cd8CJeNe.js → uat-train-COgoJomz.js} +8 -8
  183. package/dist/dashboard/{uat-train-Cd8CJeNe.js.map → uat-train-COgoJomz.js.map} +1 -1
  184. package/dist/dashboard/{validate-BdmvTIym.js → validate-069_N1e2.js} +2 -2
  185. package/dist/dashboard/{validate-BdmvTIym.js.map → validate-069_N1e2.js.map} +1 -1
  186. package/dist/dashboard/{verification-runner-BO0K3xNA.js → verification-runner-CQnZyC5A.js} +1 -1
  187. package/dist/dashboard/{verification-runner-fEY1Qj-l.js → verification-runner-fy3wNrWn.js} +4 -4
  188. package/dist/dashboard/{verification-runner-fEY1Qj-l.js.map → verification-runner-fy3wNrWn.js.map} +1 -1
  189. package/dist/dashboard/workflows-Bk6Kt0ZQ.js +2 -0
  190. package/dist/dashboard/{workflows-CxVnygYU.js → workflows-N4QMkDzN.js} +10 -10
  191. package/dist/dashboard/{workflows-CxVnygYU.js.map → workflows-N4QMkDzN.js.map} +1 -1
  192. package/dist/dashboard/{workspaces-Cpz0xJWQ.js → workspaces-DmoyfHoU.js} +33 -33
  193. package/dist/dashboard/{workspaces-Cpz0xJWQ.js.map → workspaces-DmoyfHoU.js.map} +1 -1
  194. package/dist/{lifecycle-C6k0HWEO.js → lifecycle-DCFcjg7y.js} +1 -1
  195. package/dist/{lifecycle-D5oRpvvB.js → lifecycle-GkaHQJNK.js} +2 -2
  196. package/dist/{lifecycle-D5oRpvvB.js.map → lifecycle-GkaHQJNK.js.map} +1 -1
  197. package/dist/{merge-agent-Drufc7tU.js → merge-agent-CogNFasY.js} +2 -2
  198. package/dist/{merge-agent-Drufc7tU.js.map → merge-agent-CogNFasY.js.map} +1 -1
  199. package/dist/{merge-agent-CBOVY_eC.js → merge-agent-eteKQ9Rb.js} +1 -1
  200. package/package.json +1 -1
  201. package/dist/build-info-BgrwLUy6.js +0 -2
  202. package/dist/dashboard/agent-state-Ci6PhjOE.js.map +0 -1
  203. package/dist/dashboard/build-info-CYlxvZSQ.js +0 -2
  204. package/dist/dashboard/cli-ZAS502uC.js +0 -2
  205. package/dist/dashboard/conversation-service-Dlks_hSS.js +0 -3
  206. package/dist/dashboard/inspect-agent-DTuSg95i.js +0 -2
  207. package/dist/dashboard/merge-ops-Bgc-EScO.js +0 -2
  208. package/dist/dashboard/workflows-CgGNyl1z.js +0 -2
@@ -2,7 +2,7 @@ import { n as __esmMin, r as __exportAll } from "./rolldown-runtime-DNCzzKnI.js"
2
2
  import { Ri as init_paths, di as AGENTS_DIR, vi as OVERDECK_HOME } from "./config-yaml-Dce9VUd6.js";
3
3
  import { C as init_tmux, E as killSession, U as sessionExists, k as listPaneValues } from "./tmux-l-yvNWhg.js";
4
4
  import { a as readCavemanVariant, n as init_workspace } from "./workspace-BWYfzfPV.js";
5
- import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-CzTFVZ5l.js";
5
+ import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-C0HWj3H9.js";
6
6
  import { a as init_jsonl_parser, n as getAllSessionFilesSync, s as parseClaudeSessionSync } from "./jsonl-parser-DSJisyfy.js";
7
7
  import { _ as recordWake, a as getReviewerSessionName, c as initSpecialistsDirectory, f as loadRegistry, g as pruneSpecialistRegistryEntriesForIssue, h as parseSpecialistRegistryKey, i as getProjectSpecialistDir, l as init_specialists_registry, m as parseReviewerSessionName, n as ensureProjectSpecialistDir, o as getSpecialistMetadata, p as makeSpecialistRegistryKey, r as getAllSpecialists, s as getTmuxSessionName, t as REVIEWER_ROLES, u as isProjectSpecialistActivelyRunning, v as saveRegistry, y as updateSpecialistMetadata } from "./specialists-registry-CNbvfOcy.js";
8
8
  import { Effect } from "effect";
@@ -31,7 +31,7 @@ function roleForSpecialistModel(specialistType) {
31
31
  return { role: "work" };
32
32
  }
33
33
  async function buildSpecialistBaseCommand(specialistType, model, sessionName) {
34
- const { getAgentRuntimeBaseCommand } = await import("./agents-BVvL4o_4.js");
34
+ const { getAgentRuntimeBaseCommand } = await import("./agents-CkGhnAmu.js");
35
35
  const role = roleForSpecialistModel(specialistType).role;
36
36
  const harness = await resolveHarness({
37
37
  model,
@@ -186,7 +186,7 @@ async function getSpecialistStatus(name, projectKey) {
186
186
  const sessionId = readRecordedClaudeSessionId(getTmuxSessionName(name, projectKey));
187
187
  const running = await isRunning(name, projectKey);
188
188
  const contextTokens = countContextTokens(name);
189
- const { getAgentRuntimeStateSync } = await import("./agents-BVvL4o_4.js");
189
+ const { getAgentRuntimeStateSync } = await import("./agents-CkGhnAmu.js");
190
190
  const runtimeState = getAgentRuntimeStateSync(getTmuxSessionName(name, projectKey));
191
191
  let state;
192
192
  if (runtimeState) switch (runtimeState.state) {
@@ -378,7 +378,7 @@ function listSpecialistsForProject(projectKey) {
378
378
  */
379
379
  async function getAllProjectSpecialistStatuses() {
380
380
  const registry = loadRegistry();
381
- const { getAgentRuntimeStateSync } = await import("./agents-BVvL4o_4.js");
381
+ const { getAgentRuntimeStateSync } = await import("./agents-CkGhnAmu.js");
382
382
  const results = [];
383
383
  for (const [projectKey, specialists] of Object.entries(registry.projects)) for (const [registryKey, metadata] of Object.entries(specialists)) {
384
384
  const { specialistType, issueId, role } = parseSpecialistRegistryKey(registryKey);
@@ -575,7 +575,7 @@ async function signalSpecialistCompletion(projectKey, specialistType, result, is
575
575
  const resolvedTmuxSession = metadata.tmuxSession ?? getTmuxSessionName(specialistType, projectKey, issueId);
576
576
  updateRunStatus(projectKey, registryKey, result.status);
577
577
  if (metadata.currentRun) try {
578
- const { finalizeRunLogSync } = await import("./specialist-logs-BbAX221-.js");
578
+ const { finalizeRunLogSync } = await import("./specialist-logs-C1bcnZ-G.js");
579
579
  finalizeRunLogSync(projectKey, specialistType, metadata.currentRun, {
580
580
  status: result.status,
581
581
  notes: result.notes
@@ -585,7 +585,7 @@ async function signalSpecialistCompletion(projectKey, specialistType, result, is
585
585
  }
586
586
  setCurrentRun(projectKey, registryKey, null);
587
587
  updateRunMetadata(projectKey, registryKey, { currentActivity: null });
588
- import("./agents-BVvL4o_4.js").then(({ saveAgentRuntimeState }) => {
588
+ import("./agents-CkGhnAmu.js").then(({ saveAgentRuntimeState }) => {
589
589
  saveAgentRuntimeState(resolvedTmuxSession, {
590
590
  state: "idle",
591
591
  lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
@@ -616,7 +616,7 @@ async function terminateSpecialist(projectKey, specialistType, issueId) {
616
616
  console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);
617
617
  }
618
618
  if (metadata.currentRun) {
619
- const { finalizeRunLogSync } = await import("./specialist-logs-BbAX221-.js");
619
+ const { finalizeRunLogSync } = await import("./specialist-logs-C1bcnZ-G.js");
620
620
  try {
621
621
  finalizeRunLogSync(projectKey, specialistType, metadata.currentRun, {
622
622
  status: metadata.lastRunStatus || "incomplete",
@@ -630,12 +630,12 @@ async function terminateSpecialist(projectKey, specialistType, issueId) {
630
630
  updateRunMetadata(projectKey, registryKey, { currentActivity: null });
631
631
  const key = `${projectKey}-${specialistType}`;
632
632
  gracePeriodStates.delete(key);
633
- const { saveAgentRuntimeState } = await import("./agents-BVvL4o_4.js");
633
+ const { saveAgentRuntimeState } = await import("./agents-CkGhnAmu.js");
634
634
  saveAgentRuntimeState(tmuxSession, {
635
635
  state: "suspended",
636
636
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
637
637
  });
638
- const { scheduleDigestGeneration } = await import("./specialist-context-_ootmzn2.js");
638
+ const { scheduleDigestGeneration } = await import("./specialist-context-DhDTXVht.js");
639
639
  scheduleDigestGeneration(projectKey, specialistType);
640
640
  scheduleLogCleanup(projectKey, specialistType);
641
641
  }
@@ -648,7 +648,7 @@ async function terminateSpecialist(projectKey, specialistType, issueId) {
648
648
  function scheduleLogCleanup(projectKey, specialistType) {
649
649
  Promise.resolve().then(async () => {
650
650
  try {
651
- const { cleanupOldLogsSync } = await import("./specialist-logs-BbAX221-.js");
651
+ const { cleanupOldLogsSync } = await import("./specialist-logs-C1bcnZ-G.js");
652
652
  const { getSpecialistRetention } = await import("./projects-DXgHD2Xl.js");
653
653
  const retention = getSpecialistRetention(projectKey);
654
654
  const deleted = cleanupOldLogsSync(projectKey, specialistType, {
@@ -718,7 +718,7 @@ async function sendFeedbackToAgent(feedback) {
718
718
  return false;
719
719
  }
720
720
  try {
721
- const { messageAgent } = await import("./agents-BVvL4o_4.js");
721
+ const { messageAgent } = await import("./agents-CkGhnAmu.js");
722
722
  await messageAgent(agentSession, `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.\n\nMUST READ: ${fileResult.filePath}\n\nUse your Read tool to open this file, read every line, then address the feedback and continue working. Do NOT stop at the prompt.`);
723
723
  console.log(`[specialist] Sent feedback from ${fromSpecialist} to ${agentSession} (file: ${fileResult.filePath})`);
724
724
  return true;
@@ -878,4 +878,4 @@ var init_specialists = __esmMin((() => {
878
878
  //#endregion
879
879
  export { findSessionFile as A, listSpecialistsForProject as C, updateRunMetadata as D, updateProjectSpecialistMetadata as E, isInitialized as F, isRunning as I, buildSpecialistBaseCommand as L, getSpecialistState as M, getSpecialistStatus as N, updateRunStatus as O, initializeEnabledSpecialists as P, buildSpecialistCavemanExports as R, listProjectsWithSpecialists as S, updateContextTokens as T, getEnabledSpecialists as _, sendFeedbackToAgent as a, incrementProjectRunCount as b, getGracePeriodState as c, signalSpecialistCompletion as d, startGracePeriod as f, getAllProjectSpecialistStatuses as g, enableSpecialist as h, getPendingFeedback as i, getAllSpecialistStatus as j, countContextTokens as k, pauseGracePeriod as l, disableSpecialist as m, specialists_exports as n, exitGracePeriod as o, terminateSpecialist as p, getFeedbackStats as r, findActiveRegistryKey as s, init_specialists as t, resumeGracePeriod as u, getProjectSpecialistMetadata as v, setCurrentRun as w, isEnabled as x, getRunMetadata as y };
880
880
 
881
- //# sourceMappingURL=specialists-RPLJnYc1.js.map
881
+ //# sourceMappingURL=specialists-Bvrvt0br.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"specialists-RPLJnYc1.js","names":[],"sources":["../../src/lib/cloister/specialists-spawn.ts","../../src/lib/cloister/specialists-status.ts","../../src/lib/cloister/specialists-project-meta.ts","../../src/lib/cloister/specialists-lifecycle.ts","../../src/lib/cloister/specialists-feedback.ts","../../src/lib/cloister/specialists.ts"],"sourcesContent":["/**\n * Cloister specialist spawn command and environment helpers.\n */\n\nimport { Effect } from 'effect';\nimport { readCavemanVariant } from '../caveman/workspace.js';\nimport { resolveHarness } from '../harness-resolve.js';\n\nfunction roleForSpecialistModel(specialistType: string): { role: 'plan' | 'work' | 'review' | 'test' | 'ship'; subRole?: string } {\n const normalized = specialistType.replace(/-agent$/, '');\n if (normalized === 'inspect') return { role: 'work', subRole: 'inspect' };\n if (normalized === 'inspect-deep') return { role: 'work', subRole: 'inspect-deep' };\n if (normalized === 'review') return { role: 'review' };\n if (normalized === 'test' || normalized === 'uat') return { role: 'test' };\n if (normalized === 'merge' || normalized === 'ship') return { role: 'ship' };\n if (normalized === 'planning' || normalized === 'plan') return { role: 'plan' };\n return { role: 'work' };\n}\n\n\n/**\n * Shell fragment that unsets every provider-routing env var a parent tmux server\n * may have leaked into its child sessions. The overdeck tmux server is long-lived\n * and inherits whatever env existed when it was spawned — so fresh Anthropic-model\n * agents can still see a stale ANTHROPIC_BASE_URL pointing at cliproxy, which\n * responds with \"unknown provider for model claude-*\" (PAN-705). Every launcher\n * script must run this before exec'ing claude.\n */\nconst PROVIDER_ENV_UNSETS = [\n 'ANTHROPIC_BASE_URL',\n 'ANTHROPIC_AUTH_TOKEN',\n 'OPENAI_API_KEY',\n 'GEMINI_API_KEY',\n 'API_TIMEOUT_MS',\n 'CLAUDE_CODE_API_KEY_HELPER_TTL_MS',\n];\nconst PROVIDER_UNSET_CMD = `unset ${PROVIDER_ENV_UNSETS.join(' ')}`;\n\n/**\n * Convert a providerEnv dict to bash export lines.\n * Non-Anthropic models (e.g. gpt-5.4 via cliproxy) need ANTHROPIC_BASE_URL set in the\n * script body after provider env vars are unset.\n */\nfunction buildProviderExportLines(providerEnv: Record<string, string>): string {\n const entries = Object.entries(providerEnv);\n if (entries.length === 0) return '';\n return entries.map(([k, v]) => `export ${k}=\"${v}\"`).join('\\n') + '\\n';\n}\n\n/**\n * Build tmux -e flags for environment variables\n */\nfunction buildTmuxEnvFlags(env: Record<string, string>): string {\n let flags = '';\n for (const [key, value] of Object.entries(env)) {\n flags += ` -e ${key}=\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n return flags;\n}\n\n\nexport async function buildSpecialistBaseCommand(\n specialistType: string,\n model: string,\n sessionName?: string,\n): Promise<string> {\n const { getAgentRuntimeBaseCommand } = await import('../agents.js');\n const role = roleForSpecialistModel(specialistType).role;\n const harness = await resolveHarness({ model, role });\n const agentDefinition = specialistType.startsWith('pan-')\n ? specialistType\n : `pan-${specialistType.endsWith('-agent')\n ? specialistType.slice(0, -'-agent'.length)\n : specialistType}-agent`;\n return getAgentRuntimeBaseCommand(model, sessionName, agentDefinition, harness);\n}\n\n/**\n * Build shell export lines for caveman compression for specialist agents.\n *\n * Excluded: inspect-agent (its INSPECTION PASSED/BLOCKED sentinels are parsed by Cloister).\n * Uses per-specialist-type intensity from config.\n *\n * @param specialistType The specialist type (review-agent, test-agent, etc.)\n * @param workspacePath Workspace path to read the A/B variant from (may be undefined)\n * @param config Normalized caveman config\n * @returns Shell export lines to inject into the inner script\n */\nexport async function buildSpecialistCavemanExports(\n specialistType: string,\n workspacePath: string | undefined,\n config: import('../config-yaml.js').NormalizedCavemanConfig\n): Promise<string> {\n // inspect-agent: never compress — output contains sentinel strings parsed by Cloister\n if (specialistType === 'inspect-agent' || !config.enabled) return '';\n\n // Read the workspace's A/B variant if we have a workspace path\n const variant = workspacePath ? await Effect.runPromise(readCavemanVariant(workspacePath)) : 'off';\n if (variant === 'off') return '';\n if (variant === 'disabled') {\n return `export OVERDECK_CAVEMAN_VARIANT=\"${variant}\"\\n`;\n }\n\n // Map specialist type to caveman intensity mode\n const modeMap: Record<string, keyof typeof config.modes> = {\n 'review-agent': 'review',\n 'test-agent': 'test',\n 'merge-agent': 'merge',\n };\n const modeKey = modeMap[specialistType];\n if (!modeKey) return '';\n\n const mode = config.modes[modeKey];\n if (mode === 'off' || mode === 'disabled') return '';\n\n return `export CAVEMAN_DEFAULT_MODE=\"${mode}\"\\nexport OVERDECK_CAVEMAN_VARIANT=\"${variant}\"\\n`;\n}\n","/**\n * Cloister Specialist Runtime Status\n *\n * Resolves specialist session state, token usage, and startup status.\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { basename, join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from '../paths.js';\nimport { getAllSessionFilesSync, parseClaudeSessionSync } from '../cost-parsers/jsonl-parser.js';\nimport { listPaneValues, sessionExists } from '../tmux.js';\nimport {\n getAllSpecialists,\n getSpecialistMetadata,\n getTmuxSessionName,\n type LegacySpecialistRuntimeStatus,\n type SpecialistAgentName,\n} from './specialists-registry.js';\n\nconst execAsync = promisify(exec);\n\nfunction readRecordedClaudeSessionId(tmuxSession: string): string | null {\n const sessionFile = join(AGENTS_DIR, tmuxSession, 'session.id');\n if (!existsSync(sessionFile)) return null;\n try {\n const sessionId = readFileSync(sessionFile, 'utf-8').trim();\n return sessionId || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if a legacy specialist has a recorded Claude session.\n *\n * @param name - Specialist name\n * @returns True if the specialist has a recorded session id in its agent directory\n */\nexport function isInitialized(name: SpecialistAgentName): boolean {\n return readRecordedClaudeSessionId(getTmuxSessionName(name)) !== null;\n}\n\n/**\n * Get the state of a specialist from recorded agent metadata.\n *\n * Note: This only checks whether a recorded Claude session exists, not if it's actually running.\n * Use getSpecialistStatus() for runtime state.\n *\n * @param name - Specialist name\n * @returns Specialist state\n */\nexport function getSpecialistState(\n name: SpecialistAgentName\n): Exclude<LegacySpecialistRuntimeStatus['state'], 'active'> {\n return isInitialized(name) ? 'sleeping' : 'uninitialized';\n}\n\n/**\n * Find JSONL file for a session ID\n *\n * Searches through Claude Code project directories to find the JSONL file.\n *\n * @param sessionId - Session ID to find\n * @returns Path to JSONL file or null if not found\n */\nexport function findSessionFile(sessionId: string): string | null {\n try {\n const allFiles = getAllSessionFilesSync();\n\n for (const file of allFiles) {\n const fileSessionId = basename(file, '.jsonl');\n if (fileSessionId === sessionId) {\n return file;\n }\n }\n } catch {\n // Session files not available\n }\n\n return null;\n}\n\n/**\n * Count context tokens for a specialist session\n *\n * Reads the JSONL file for the specialist's session and sums all token usage.\n * This gives an approximate count of context size.\n *\n * @param name - Specialist name\n * @returns Total token count or null if session not found\n */\nexport function countContextTokens(name: SpecialistAgentName): number | null {\n const sessionId = readRecordedClaudeSessionId(getTmuxSessionName(name));\n\n if (!sessionId) {\n return null;\n }\n\n const sessionFile = findSessionFile(sessionId);\n\n if (!sessionFile) {\n return null;\n }\n\n const sessionUsage = parseClaudeSessionSync(sessionFile);\n\n if (!sessionUsage) {\n return null;\n }\n\n // Sum all token types for total context\n return (\n sessionUsage.usage.inputTokens +\n sessionUsage.usage.outputTokens +\n (sessionUsage.usage.cacheReadTokens || 0) +\n (sessionUsage.usage.cacheWriteTokens || 0)\n );\n}\n\n/**\n * Check if a specialist is currently running in tmux\n *\n * @param name - Specialist name\n * @param projectKey - Optional project key for per-project specialists\n * @returns True if specialist has an active tmux session\n */\nexport async function isRunning(name: SpecialistAgentName, projectKey?: string): Promise<boolean> {\n const tmuxSession = getTmuxSessionName(name, projectKey);\n\n try {\n const exists = await Effect.runPromise(sessionExists(tmuxSession));\n if (!exists) return false;\n // Session exists — but check if the pane actually has a running process.\n // When Claude Code crashes, the pane's process exits but the tmux session persists,\n // making has-session return success even though nothing is running.\n const panePid = (await Effect.runPromise(listPaneValues(tmuxSession, '#{pane_pid}')))[0]?.trim() ?? '';\n if (!panePid) return false;\n // Check if the pane's process has any child processes (Claude Code / bash)\n const { stdout: children } = await execAsync(\n `ps --ppid ${panePid} --no-headers 2>/dev/null || echo \"\"`,\n { encoding: 'utf-8' }\n );\n return children.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Get complete status for a specialist\n *\n * Combines metadata, session info, and runtime state (PAN-80: uses hook-based state).\n *\n * @param name - Specialist name\n * @param projectKey - Optional project key for per-project specialists\n * @returns Complete specialist status\n */\nexport async function getSpecialistStatus(\n name: SpecialistAgentName,\n projectKey?: string\n): Promise<LegacySpecialistRuntimeStatus> {\n const metadata = getSpecialistMetadata(name) || {\n name,\n displayName: name,\n description: '',\n enabled: false,\n autoWake: false,\n };\n\n const sessionId = readRecordedClaudeSessionId(getTmuxSessionName(name, projectKey));\n const running = await isRunning(name, projectKey);\n const contextTokens = countContextTokens(name);\n\n // Determine state from hook-based runtime state (PAN-80)\n const { getAgentRuntimeStateSync } = await import('../agents.js');\n const tmuxSession = getTmuxSessionName(name, projectKey);\n const runtimeState = getAgentRuntimeStateSync(tmuxSession);\n\n let state: LegacySpecialistRuntimeStatus['state'];\n if (runtimeState) {\n // Map runtime state to specialist state\n switch (runtimeState.state) {\n case 'active':\n state = 'active';\n break;\n case 'idle':\n state = 'sleeping'; // Idle = at prompt waiting\n break;\n case 'suspended':\n state = 'sleeping'; // Suspended = session saved, not running\n break;\n case 'uninitialized':\n default:\n state = 'uninitialized';\n break;\n }\n } else {\n // Fallback if no runtime state available\n if (running && sessionId) {\n state = 'sleeping';\n } else if (sessionId) {\n state = 'sleeping';\n } else {\n state = 'uninitialized';\n }\n }\n\n return {\n ...metadata,\n sessionId: sessionId || undefined,\n contextTokens: contextTokens || undefined,\n state,\n isRunning: running,\n tmuxSession: getTmuxSessionName(name, projectKey),\n currentIssue: running ? runtimeState?.currentIssue : undefined,\n };\n}\n\n/**\n * Get status for all specialists\n *\n * @returns Array of specialist statuses\n */\nexport async function getAllSpecialistStatus(): Promise<LegacySpecialistRuntimeStatus[]> {\n const specialists = getAllSpecialists();\n return Promise.all(specialists.map((metadata) => getSpecialistStatus(metadata.name)));\n}\n\n\n/**\n * Initialize all enabled but uninitialized specialists\n *\n * Called during Cloister startup to ensure specialists are ready.\n *\n * @returns Promise with array of initialization results\n */\nexport async function initializeEnabledSpecialists(): Promise<Array<{\n name: SpecialistAgentName;\n success: boolean;\n message: string;\n}>> {\n const enabled = getAllSpecialists().filter((specialist) => specialist.enabled);\n const results: Array<{ name: SpecialistAgentName; success: boolean; message: string }> = [];\n\n for (const specialist of enabled) {\n results.push({\n name: specialist.name,\n success: true,\n message: 'Legacy global specialist initialization removed; role flows spawn agents on demand.',\n });\n }\n\n return results;\n}\n","/**\n * Cloister Specialist Project Metadata\n *\n * Manages per-project specialist run metadata and configuration.\n */\n\nimport {\n getAllSpecialists,\n getSpecialistMetadata,\n getTmuxSessionName,\n isProjectSpecialistActivelyRunning,\n loadRegistry,\n parseSpecialistRegistryKey,\n saveRegistry,\n updateSpecialistMetadata,\n type LegacySpecialistDefinition,\n type ProjectSpecialistMetadata,\n type SpecialistAgentName,\n} from './specialists-registry.js';\nimport { isRunning } from './specialists-status.js';\n\n/**\n * ===========================================================================\n * Per-Project Specialist Functions\n * ===========================================================================\n */\n\n/**\n * Get metadata for a specific (projectKey, registryKey) pair.\n * registryKey is either a plain specialistType (legacy) or a compound key from makeSpecialistRegistryKey().\n */\nexport function getRunMetadata(\n projectKey: string,\n registryKey: string,\n): ProjectSpecialistMetadata {\n const registry = loadRegistry();\n\n if (!registry.projects[projectKey]) {\n registry.projects[projectKey] = {};\n }\n\n if (!registry.projects[projectKey][registryKey]) {\n registry.projects[projectKey][registryKey] = {\n runCount: 0,\n lastRunAt: null,\n lastRunStatus: null,\n currentRun: null,\n };\n saveRegistry(registry);\n }\n\n return registry.projects[projectKey][registryKey];\n}\n\n/**\n * Update metadata for a specific (projectKey, registryKey) pair.\n */\nexport function updateRunMetadata(\n projectKey: string,\n registryKey: string,\n updates: Partial<ProjectSpecialistMetadata>\n): void {\n const registry = loadRegistry();\n\n if (!registry.projects[projectKey]) {\n registry.projects[projectKey] = {};\n }\n\n if (!registry.projects[projectKey][registryKey]) {\n registry.projects[projectKey][registryKey] = {\n runCount: 0,\n lastRunAt: null,\n lastRunStatus: null,\n currentRun: null,\n };\n }\n\n registry.projects[projectKey][registryKey] = {\n ...registry.projects[projectKey][registryKey],\n ...updates,\n };\n\n saveRegistry(registry);\n}\n\n/**\n * Get per-project specialist metadata — backward-compat wrapper.\n * Searches compound-key entries for this project+type; returns the active run, or most recent.\n */\nexport function getProjectSpecialistMetadata(\n projectKey: string,\n specialistType: SpecialistAgentName\n): ProjectSpecialistMetadata {\n const registry = loadRegistry();\n const projectBucket = registry.projects[projectKey] ?? {};\n\n // Check for exact legacy key first\n if (projectBucket[specialistType]) {\n return projectBucket[specialistType];\n }\n\n // Search compound keys for the most relevant entry\n const prefix = `${specialistType}:`;\n const candidates = Object.entries(projectBucket)\n .filter(([k]) => k.startsWith(prefix))\n .map(([, v]) => v);\n\n // Prefer active run, then most recently started\n const active = candidates.find(c => c.currentRun !== null);\n if (active) return active;\n const sorted = candidates.sort((a, b) =>\n (b.lastRunAt ?? '').localeCompare(a.lastRunAt ?? '')\n );\n if (sorted.length > 0) return sorted[0];\n\n // No entry found — return blank default (don't save it)\n return { runCount: 0, lastRunAt: null, lastRunStatus: null, currentRun: null };\n}\n\n/**\n * Update per-project specialist metadata — backward-compat wrapper.\n * Updates the active compound-key entry, or the legacy plain-key entry.\n */\nexport function updateProjectSpecialistMetadata(\n projectKey: string,\n specialistType: SpecialistAgentName,\n updates: Partial<ProjectSpecialistMetadata>\n): void {\n const registry = loadRegistry();\n const projectBucket = registry.projects[projectKey] ?? {};\n\n // Try legacy key first\n if (projectBucket[specialistType]) {\n updateRunMetadata(projectKey, specialistType, updates);\n return;\n }\n\n // Find the active compound-key entry for this type\n const prefix = `${specialistType}:`;\n const activeKey = Object.keys(projectBucket).find(k =>\n k.startsWith(prefix) && projectBucket[k].currentRun !== null\n );\n if (activeKey) {\n updateRunMetadata(projectKey, activeKey, updates);\n return;\n }\n\n // Fall back to most recent\n const latestKey = Object.keys(projectBucket)\n .filter(k => k.startsWith(prefix))\n .sort((a, b) => (projectBucket[b].lastRunAt ?? '').localeCompare(projectBucket[a].lastRunAt ?? ''))\n .shift();\n if (latestKey) {\n updateRunMetadata(projectKey, latestKey, updates);\n }\n}\n\n/**\n * Increment run count for a project's specialist.\n * registryKey may be a plain specialistType (legacy) or a compound key.\n */\nexport function incrementProjectRunCount(projectKey: string, registryKey: string): void {\n const metadata = getRunMetadata(projectKey, registryKey);\n updateRunMetadata(projectKey, registryKey, {\n runCount: metadata.runCount + 1,\n lastRunAt: new Date().toISOString(),\n });\n}\n\n/**\n * Set current run for a project's specialist.\n * registryKey may be a plain specialistType (legacy) or a compound key.\n */\nexport function setCurrentRun(\n projectKey: string,\n registryKey: string,\n runId: string | null\n): void {\n updateRunMetadata(projectKey, registryKey, { currentRun: runId });\n}\n\n/**\n * Update run status for a project's specialist.\n * registryKey may be a plain specialistType (legacy) or a compound key.\n */\nexport function updateRunStatus(\n projectKey: string,\n registryKey: string,\n status: 'passed' | 'failed' | 'blocked' | null\n): void {\n updateRunMetadata(projectKey, registryKey, { lastRunStatus: status });\n}\n\n/**\n * List all projects that have specialists configured\n */\nexport function listProjectsWithSpecialists(): string[] {\n const registry = loadRegistry();\n return Object.keys(registry.projects);\n}\n\n/**\n * List all specialist types for a project\n */\nexport function listSpecialistsForProject(projectKey: string): SpecialistAgentName[] {\n const registry = loadRegistry();\n const project = registry.projects[projectKey];\n\n if (!project) {\n return [];\n }\n\n return Object.keys(project) as SpecialistAgentName[];\n}\n\n/**\n * Get all per-project specialist statuses (PAN-754: compound-key aware).\n * Walks registry including compound keys (type:issueId[:role]) and returns\n * enriched entries with issueId, model, currentActivity for the Agents page.\n */\nexport async function getAllProjectSpecialistStatuses(): Promise<Array<{\n projectKey: string;\n specialistType: SpecialistAgentName;\n registryKey: string;\n issueId?: string;\n role?: string;\n metadata: ProjectSpecialistMetadata;\n isRunning: boolean;\n tmuxSession: string;\n}>> {\n const registry = loadRegistry();\n const { getAgentRuntimeStateSync } = await import('../agents.js');\n\n const results: Array<{\n projectKey: string;\n specialistType: SpecialistAgentName;\n registryKey: string;\n issueId?: string;\n role?: string;\n metadata: ProjectSpecialistMetadata;\n isRunning: boolean;\n tmuxSession: string;\n }> = [];\n\n for (const [projectKey, specialists] of Object.entries(registry.projects)) {\n for (const [registryKey, metadata] of Object.entries(specialists)) {\n const { specialistType, issueId, role } = parseSpecialistRegistryKey(registryKey);\n\n // Determine tmux session: use stored field when available\n const tmuxSession = metadata.tmuxSession\n ?? getTmuxSessionName(specialistType as SpecialistAgentName, projectKey, issueId);\n\n const runtimeState = getAgentRuntimeStateSync(tmuxSession);\n const sessionRunning = await isRunning(specialistType as SpecialistAgentName, projectKey).catch(() => false);\n const running = isProjectSpecialistActivelyRunning(runtimeState, sessionRunning);\n const effectiveMetadata = running ? metadata : { ...metadata, currentRun: null };\n\n results.push({\n projectKey,\n specialistType: specialistType as SpecialistAgentName,\n registryKey,\n issueId,\n role,\n metadata: effectiveMetadata,\n isRunning: running,\n tmuxSession,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Update context token count for a specialist\n *\n * @param name - Specialist name\n * @param tokens - Total context tokens\n */\nexport function updateContextTokens(name: SpecialistAgentName, tokens: number): void {\n updateSpecialistMetadata(name, { contextTokens: tokens });\n}\n\n/**\n * Enable a specialist\n *\n * @param name - Specialist name\n */\nexport function enableSpecialist(name: SpecialistAgentName): void {\n updateSpecialistMetadata(name, { enabled: true });\n}\n\n/**\n * Disable a specialist\n *\n * @param name - Specialist name\n */\nexport function disableSpecialist(name: SpecialistAgentName): void {\n updateSpecialistMetadata(name, { enabled: false });\n}\n\n/**\n * Check if a specialist is enabled\n *\n * @param name - Specialist name\n * @returns True if specialist is enabled\n */\nexport function isEnabled(name: SpecialistAgentName): boolean {\n const metadata = getSpecialistMetadata(name);\n return metadata?.enabled ?? false;\n}\n\n/**\n * Get all enabled specialists\n *\n * @returns Array of enabled specialists\n */\nexport function getEnabledSpecialists(): LegacySpecialistDefinition[] {\n return getAllSpecialists().filter((s) => s.enabled);\n}\n","/**\n * Cloister Specialist Lifecycle\n *\n * Manages grace periods, completion signaling, and specialist termination.\n */\n\nimport { Effect } from 'effect';\nimport { killSession } from '../tmux.js';\nimport {\n getTmuxSessionName,\n loadRegistry,\n makeSpecialistRegistryKey,\n type SpecialistAgentName,\n} from './specialists-registry.js';\nimport {\n getRunMetadata,\n setCurrentRun,\n updateRunMetadata,\n updateRunStatus,\n} from './specialists-project-meta.js';\n\n/**\n * ===========================================================================\n * Ephemeral Lifecycle Management\n * ===========================================================================\n */\n\n/**\n * Grace period state for a specialist\n */\nexport interface GracePeriodState {\n active: boolean;\n startedAt: string;\n duration: number; // milliseconds\n paused: boolean;\n pausedAt?: string;\n remainingTime?: number; // milliseconds when paused\n}\n\nconst gracePeriodStates = new Map<string, GracePeriodState>();\n\n/**\n * Task context interface for specialist tasks.\n */\nexport interface TaskContext {\n prUrl?: string;\n workspace?: string;\n branch?: string;\n filesChanged?: string[];\n reason?: string;\n targetModel?: string;\n additionalInstructions?: string;\n [key: string]: string | string[] | undefined;\n}\n\n/**\n * PAN-1048 R1: spawnEphemeralSpecialist removed.\n *\n * The function was the generic dispatcher that took an arbitrary\n * SpecialistAgentName ('review-agent' | 'test-agent' | 'merge-agent' |\n * 'inspect-agent' | …) and shelled out a launcher specific to each. Under\n * the role primitive, those flavours are first-class roles\n * (review/test/ship) plus a single work sub-role (inspect / inspect-deep).\n *\n * Replacements:\n * - Review/test/ship runs: spawnRun(issueId, role, opts) in src/lib/agents.ts.\n * Reactive Cloister fires these on lifecycle transitions; the manual\n * re-dispatch in routes/workspaces.ts also uses spawnRun.\n * - Inspect runs: spawnInspectAgent() in cloister/inspect-agent.ts owns\n * its own minimal launcher path (single-bead-scoped, ephemeral).\n *\n * The specialist registry/run-log/grace-period machinery stays in this\n * file because the dashboard read-model and reset/init/grace endpoints\n * still consume it for legacy run history; nothing writes new entries\n * once the dispatcher is gone.\n */\n\n\n/**\n * Start grace period for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param duration - Grace period duration in milliseconds (default: 60000)\n */\nexport function startGracePeriod(\n projectKey: string,\n specialistType: SpecialistAgentName,\n duration: number = 60000\n): void {\n const key = `${projectKey}-${specialistType}`;\n\n gracePeriodStates.set(key, {\n active: true,\n startedAt: new Date().toISOString(),\n duration,\n paused: false,\n });\n\n console.log(`[specialist] Grace period started for ${projectKey}/${specialistType} (${duration}ms)`);\n\n // Schedule termination after grace period\n setTimeout(() => {\n const state = gracePeriodStates.get(key);\n if (state && state.active && !state.paused) {\n terminateSpecialist(projectKey, specialistType);\n }\n }, duration);\n}\n\n/**\n * Pause grace period countdown\n */\nexport function pauseGracePeriod(projectKey: string, specialistType: SpecialistAgentName): boolean {\n const key = `${projectKey}-${specialistType}`;\n const state = gracePeriodStates.get(key);\n\n if (!state || !state.active) {\n return false;\n }\n\n const elapsed = Date.now() - new Date(state.startedAt).getTime();\n const remaining = state.duration - elapsed;\n\n state.paused = true;\n state.pausedAt = new Date().toISOString();\n state.remainingTime = remaining;\n\n gracePeriodStates.set(key, state);\n console.log(`[specialist] Grace period paused for ${projectKey}/${specialistType}`);\n\n return true;\n}\n\n/**\n * Resume grace period countdown\n */\nexport function resumeGracePeriod(projectKey: string, specialistType: SpecialistAgentName): boolean {\n const key = `${projectKey}-${specialistType}`;\n const state = gracePeriodStates.get(key);\n\n if (!state || !state.active || !state.paused) {\n return false;\n }\n\n state.paused = false;\n state.startedAt = new Date().toISOString();\n state.pausedAt = undefined;\n\n gracePeriodStates.set(key, state);\n console.log(`[specialist] Grace period resumed for ${projectKey}/${specialistType}`);\n\n // Schedule termination for remaining time\n setTimeout(() => {\n const currentState = gracePeriodStates.get(key);\n if (currentState && currentState.active && !currentState.paused) {\n terminateSpecialist(projectKey, specialistType);\n }\n }, state.remainingTime || 0);\n\n return true;\n}\n\n/**\n * Exit grace period immediately (terminate now)\n */\nexport function exitGracePeriod(projectKey: string, specialistType: SpecialistAgentName): void {\n const key = `${projectKey}-${specialistType}`;\n gracePeriodStates.delete(key);\n\n terminateSpecialist(projectKey, specialistType);\n}\n\n/**\n * Get grace period state\n */\nexport function getGracePeriodState(\n projectKey: string,\n specialistType: SpecialistAgentName\n): GracePeriodState | null {\n const key = `${projectKey}-${specialistType}`;\n return gracePeriodStates.get(key) || null;\n}\n\n/**\n * Find the active registry key for (projectKey, specialistType).\n * Searches compound keys; falls back to plain specialistType key.\n * Returns undefined if nothing is currently active.\n */\nexport function findActiveRegistryKey(projectKey: string, specialistType: SpecialistAgentName): string | undefined {\n const registry = loadRegistry();\n const bucket = registry.projects[projectKey] ?? {};\n\n // Check compound keys first (new format: \"type:issueId\")\n const prefix = `${specialistType}:`;\n const activeCompound = Object.keys(bucket).find(k =>\n k.startsWith(prefix) && bucket[k].currentRun !== null\n );\n if (activeCompound) return activeCompound;\n\n // Check legacy plain key\n if (bucket[specialistType]?.currentRun !== null) return specialistType;\n\n // Return most recently touched key even if not active\n const allMatching = Object.keys(bucket).filter(k =>\n k === specialistType || k.startsWith(prefix)\n ).sort((a, b) =>\n (bucket[b].lastRunAt ?? '').localeCompare(bucket[a].lastRunAt ?? '')\n );\n return allMatching[0];\n}\n\n/**\n * Signal that a specialist has completed its task\n *\n * This should be called when the specialist finishes its work.\n * It updates the run status and starts the grace period.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param result - Task result\n * @param issueId - Optional: issue being handled (used to compute compound registry key)\n */\nexport async function signalSpecialistCompletion(\n projectKey: string,\n specialistType: SpecialistAgentName,\n result: {\n status: 'passed' | 'failed' | 'blocked';\n notes?: string;\n },\n issueId?: string\n): Promise<void> {\n const registryKey = issueId\n ? makeSpecialistRegistryKey(specialistType, issueId)\n : (findActiveRegistryKey(projectKey, specialistType) ?? specialistType);\n const metadata = getRunMetadata(projectKey, registryKey);\n\n // Derive tmuxSession: use stored field when available, recompute as fallback\n const resolvedTmuxSession = metadata.tmuxSession ?? getTmuxSessionName(specialistType, projectKey, issueId);\n\n // Update status\n updateRunStatus(projectKey, registryKey, result.status);\n\n // Finalize log if there's a current run\n if (metadata.currentRun) {\n try {\n const { finalizeRunLogSync } = await import('./specialist-logs.js');\n finalizeRunLogSync(projectKey, specialistType, metadata.currentRun, {\n status: result.status,\n notes: result.notes,\n });\n } catch (error) {\n console.error(`[specialist] Failed to finalize log:`, error);\n }\n }\n\n // Completion means the run itself is over, even if the tmux session stays alive\n // during the grace period for inspection or manual termination.\n setCurrentRun(projectKey, registryKey, null);\n updateRunMetadata(projectKey, registryKey, { currentActivity: null });\n import('../agents.js')\n .then(({ saveAgentRuntimeState }) => {\n saveAgentRuntimeState(resolvedTmuxSession, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n currentIssue: undefined,\n });\n })\n .catch((error) => {\n console.error(`[specialist] Failed to mark ${projectKey}/${specialistType} idle:`, error);\n });\n\n // Start grace period (60 seconds)\n startGracePeriod(projectKey, specialistType, 60000);\n\n console.log(`[specialist] ${specialistType} completed for ${projectKey} (status: ${result.status})`);\n}\n\n/**\n * Terminate a specialist session\n *\n * Kills the tmux session, finalizes logs, and schedules digest generation.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport async function terminateSpecialist(\n projectKey: string,\n specialistType: SpecialistAgentName,\n issueId?: string\n): Promise<void> {\n const registryKey = issueId\n ? makeSpecialistRegistryKey(specialistType, issueId)\n : (findActiveRegistryKey(projectKey, specialistType) ?? specialistType);\n const metadata = getRunMetadata(projectKey, registryKey);\n\n // Derive tmuxSession: use stored field, or recompute\n const tmuxSession = metadata.tmuxSession ?? getTmuxSessionName(specialistType, projectKey, issueId);\n\n try {\n // Kill tmux session\n await Effect.runPromise(killSession(tmuxSession));\n console.log(`[specialist] Terminated ${projectKey}/${specialistType}`);\n } catch (error) {\n console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);\n }\n\n // Finalize log if there's a current run\n if (metadata.currentRun) {\n const { finalizeRunLogSync } = await import('./specialist-logs.js');\n\n try {\n finalizeRunLogSync(projectKey, specialistType, metadata.currentRun, {\n status: metadata.lastRunStatus || 'incomplete',\n notes: 'Specialist terminated',\n });\n } catch (error) {\n console.error(`[specialist] Failed to finalize log:`, error);\n }\n\n // Clear current run\n setCurrentRun(projectKey, registryKey, null);\n }\n\n updateRunMetadata(projectKey, registryKey, { currentActivity: null });\n\n // Clear grace period state\n const key = `${projectKey}-${specialistType}`;\n gracePeriodStates.delete(key);\n\n // Update runtime state\n const { saveAgentRuntimeState } = await import('../agents.js');\n saveAgentRuntimeState(tmuxSession, {\n state: 'suspended',\n lastActivity: new Date().toISOString(),\n });\n\n // Schedule digest generation (async, fire-and-forget)\n const { scheduleDigestGeneration } = await import('./specialist-context.js');\n scheduleDigestGeneration(projectKey, specialistType);\n\n // Run log cleanup for this project/specialist (async, fire-and-forget)\n scheduleLogCleanup(projectKey, specialistType);\n}\n\n/**\n * Schedule log cleanup for a project's specialist (async, fire-and-forget)\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nfunction scheduleLogCleanup(projectKey: string, specialistType: SpecialistAgentName): void {\n // Run async without awaiting\n Promise.resolve().then(async () => {\n try {\n const { cleanupOldLogsSync } = await import('./specialist-logs.js');\n const { getSpecialistRetention } = await import('../projects.js');\n\n const retention = getSpecialistRetention(projectKey);\n const deleted = cleanupOldLogsSync(projectKey, specialistType, { maxDays: retention.max_days, maxRuns: retention.max_runs });\n\n if (deleted > 0) {\n console.log(`[specialist] Cleaned up ${deleted} old logs for ${projectKey}/${specialistType}`);\n }\n } catch (error) {\n console.error(`[specialist] Log cleanup failed for ${projectKey}/${specialistType}:`, error);\n }\n });\n}\n","/**\n * Cloister Specialist Feedback\n *\n * Persists specialist feedback and delivers it to issue agents.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { OVERDECK_HOME } from '../paths.js';\nimport { type SpecialistAgentName } from './specialists-registry.js';\n\n/**\n * ===========================================================================\n * Specialist Feedback System\n * ===========================================================================\n *\n * Specialists accumulate context and expertise. This system allows them to\n * share learnings back to issue agents, creating a feedback loop that\n * improves the overall system over time.\n */\n\n/**\n * Feedback from a specialist to an issue agent\n */\nexport interface SpecialistFeedback {\n id: string;\n timestamp: string;\n fromSpecialist: SpecialistAgentName;\n toIssueId: string;\n feedbackType: 'success' | 'failure' | 'warning' | 'insight';\n category: 'merge' | 'test' | 'review' | 'general';\n summary: string;\n details: string;\n actionItems?: string[];\n patterns?: string[]; // Patterns the specialist noticed\n suggestions?: string[]; // Suggestions for the issue agent\n}\n\nconst FEEDBACK_DIR = join(OVERDECK_HOME, 'specialists', 'feedback');\nconst FEEDBACK_LOG = join(FEEDBACK_DIR, 'feedback.jsonl');\n\n/**\n * Send feedback from a specialist to an issue agent\n *\n * This is the key mechanism for specialists to share their accumulated\n * expertise back to the issue agents that spawned the work.\n *\n * @param feedback - The feedback to send\n * @returns True if feedback was sent successfully\n */\nexport async function sendFeedbackToAgent(\n feedback: Omit<SpecialistFeedback, 'id' | 'timestamp'>\n): Promise<boolean> {\n const { fromSpecialist, toIssueId, summary, details } = feedback;\n\n // Ensure feedback directory exists\n if (!existsSync(FEEDBACK_DIR)) {\n mkdirSync(FEEDBACK_DIR, { recursive: true });\n }\n\n // Create full feedback record\n const fullFeedback: SpecialistFeedback = {\n ...feedback,\n id: `feedback-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n timestamp: new Date().toISOString(),\n };\n\n // Log feedback to JSONL\n try {\n const line = JSON.stringify(fullFeedback) + '\\n';\n appendFileSync(FEEDBACK_LOG, line, 'utf-8');\n } catch (error) {\n console.error(`[specialist] Failed to log feedback:`, error);\n }\n\n // Try to send feedback to the issue agent\n const agentSession = `agent-${toIssueId.toLowerCase()}`;\n\n // Format feedback message for the agent\n const feedbackMessage = formatFeedbackForAgent(fullFeedback);\n\n // Write feedback to workspace file\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n const specialistMap: Record<string, 'review-agent' | 'test-agent' | 'merge-agent'> = {\n 'review-agent': 'review-agent',\n 'test-agent': 'test-agent',\n 'merge-agent': 'merge-agent',\n };\n const specialist = specialistMap[fromSpecialist] || 'review-agent';\n const outcome = feedback.feedbackType === 'success' ? 'approved' : feedback.feedbackType === 'failure' ? 'failed' : feedback.feedbackType;\n\n const fileResult = await Effect.runPromise(writeFeedbackFile({\n issueId: toIssueId,\n specialist,\n outcome,\n summary: summary.slice(0, 100),\n markdownBody: feedbackMessage,\n }));\n\n if (!fileResult.success) {\n console.error(`[specialist] Failed to write feedback file for ${toIssueId}: ${fileResult.error}`);\n return false;\n }\n\n // Send a short, explicit message with the ABSOLUTE path.\n try {\n const { messageAgent } = await import('../agents.js');\n const msg = `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.\\n\\nMUST READ: ${fileResult.filePath}\\n\\nUse your Read tool to open this file, read every line, then address the feedback and continue working. Do NOT stop at the prompt.`;\n await messageAgent(agentSession, msg);\n console.log(`[specialist] Sent feedback from ${fromSpecialist} to ${agentSession} (file: ${fileResult.filePath})`);\n return true;\n } catch (err) {\n // Agent may be gone — feedback file is still in the workspace for crash recovery\n console.log(`[specialist] Could not send reference to ${agentSession} (file written): ${err}`);\n return true; // File was written successfully, that's the important part\n }\n}\n\n/**\n * Format feedback for display to an agent\n */\nfunction formatFeedbackForAgent(feedback: SpecialistFeedback): string {\n const { fromSpecialist, feedbackType, category, summary, details, actionItems, patterns, suggestions } = feedback;\n\n const typeEmoji = {\n success: '✅',\n failure: '❌',\n warning: '⚠️',\n insight: '💡',\n }[feedbackType];\n\n let message = `\\n${typeEmoji} **Feedback from ${fromSpecialist}** (${category})\\n\\n`;\n message += `**Summary:** ${summary}\\n\\n`;\n message += `**Details:**\\n${details}\\n`;\n\n if (actionItems?.length) {\n message += `\\n**Action Items:**\\n`;\n actionItems.forEach((item, i) => {\n message += `${i + 1}. ${item}\\n`;\n });\n }\n\n if (patterns?.length) {\n message += `\\n**Patterns Noticed:**\\n`;\n patterns.forEach(pattern => {\n message += `- ${pattern}\\n`;\n });\n }\n\n if (suggestions?.length) {\n message += `\\n**Suggestions:**\\n`;\n suggestions.forEach(suggestion => {\n message += `- ${suggestion}\\n`;\n });\n }\n\n return message;\n}\n\n/**\n * Get pending feedback for an issue that hasn't been delivered yet\n *\n * @param issueId - Issue ID to get feedback for\n * @returns Array of feedback records\n */\nexport function getPendingFeedback(issueId: string): SpecialistFeedback[] {\n if (!existsSync(FEEDBACK_LOG)) {\n return [];\n }\n\n try {\n const content = readFileSync(FEEDBACK_LOG, 'utf-8');\n const lines = content.trim().split('\\n').filter(l => l.length > 0);\n const allFeedback = lines.map(line => JSON.parse(line) as SpecialistFeedback);\n\n // Filter to this issue\n return allFeedback.filter(f => f.toIssueId.toLowerCase() === issueId.toLowerCase());\n } catch (error) {\n console.error(`[specialist] Failed to read feedback log:`, error);\n return [];\n }\n}\n\n/**\n * Get feedback statistics for all specialists\n *\n * @returns Feedback stats by specialist and type\n */\nexport function getFeedbackStats(): {\n bySpecialist: Record<SpecialistAgentName, number>;\n byType: Record<string, number>;\n total: number;\n} {\n const stats = {\n bySpecialist: {\n 'merge-agent': 0,\n 'review-agent': 0,\n 'test-agent': 0,\n } as Record<SpecialistAgentName, number>,\n byType: {} as Record<string, number>,\n total: 0,\n };\n\n if (!existsSync(FEEDBACK_LOG)) {\n return stats;\n }\n\n try {\n const content = readFileSync(FEEDBACK_LOG, 'utf-8');\n const lines = content.trim().split('\\n').filter(l => l.length > 0);\n\n for (const line of lines) {\n const feedback = JSON.parse(line) as SpecialistFeedback;\n stats.bySpecialist[feedback.fromSpecialist] = (stats.bySpecialist[feedback.fromSpecialist] || 0) + 1;\n stats.byType[feedback.feedbackType] = (stats.byType[feedback.feedbackType] || 0) + 1;\n stats.total++;\n }\n } catch (error) {\n console.error(`[specialist] Failed to read feedback stats:`, error);\n }\n\n return stats;\n}\n","/**\n * Cloister Specialist Agents\n *\n * Manages long-running specialist agents that can be woken up on demand.\n * Specialists maintain context across invocations via session files.\n */\n\nexport { buildSpecialistBaseCommand, buildSpecialistCavemanExports } from './specialists-spawn.js';\n\n\nexport {\n REVIEWER_ROLES,\n getAllSpecialists,\n ensureProjectSpecialistDir,\n getProjectSpecialistDir,\n getReviewerSessionName,\n getSpecialistMetadata,\n getTmuxSessionName,\n initSpecialistsDirectory,\n isProjectSpecialistActivelyRunning,\n loadRegistry,\n makeSpecialistRegistryKey,\n parseReviewerSessionName,\n parseSpecialistRegistryKey,\n pruneSpecialistRegistryEntriesForIssue,\n recordWake,\n saveRegistry,\n updateSpecialistMetadata,\n type LegacySpecialistDefinition,\n type LegacySpecialistRuntimeStatus,\n type ProjectSpecialistMetadata,\n type ResolutionStep,\n type ReviewerRole,\n type SpecialistAgentName,\n type SpecialistRegistry,\n} from './specialists-registry.js';\n\nexport {\n exitGracePeriod,\n findActiveRegistryKey,\n getGracePeriodState,\n pauseGracePeriod,\n resumeGracePeriod,\n signalSpecialistCompletion,\n startGracePeriod,\n terminateSpecialist,\n type GracePeriodState,\n type TaskContext,\n} from './specialists-lifecycle.js';\n\nexport {\n disableSpecialist,\n enableSpecialist,\n getAllProjectSpecialistStatuses,\n getEnabledSpecialists,\n getProjectSpecialistMetadata,\n getRunMetadata,\n incrementProjectRunCount,\n isEnabled,\n listProjectsWithSpecialists,\n listSpecialistsForProject,\n setCurrentRun,\n updateContextTokens,\n updateProjectSpecialistMetadata,\n updateRunMetadata,\n updateRunStatus,\n} from './specialists-project-meta.js';\n\nexport {\n countContextTokens,\n findSessionFile,\n getAllSpecialistStatus,\n getSpecialistState,\n getSpecialistStatus,\n initializeEnabledSpecialists,\n isInitialized,\n isRunning,\n} from './specialists-status.js';\n\nexport {\n getFeedbackStats,\n getPendingFeedback,\n sendFeedbackToAgent,\n type SpecialistFeedback,\n} from './specialists-feedback.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"specialists-Bvrvt0br.js","names":[],"sources":["../../src/lib/cloister/specialists-spawn.ts","../../src/lib/cloister/specialists-status.ts","../../src/lib/cloister/specialists-project-meta.ts","../../src/lib/cloister/specialists-lifecycle.ts","../../src/lib/cloister/specialists-feedback.ts","../../src/lib/cloister/specialists.ts"],"sourcesContent":["/**\n * Cloister specialist spawn command and environment helpers.\n */\n\nimport { Effect } from 'effect';\nimport { readCavemanVariant } from '../caveman/workspace.js';\nimport { resolveHarness } from '../harness-resolve.js';\n\nfunction roleForSpecialistModel(specialistType: string): { role: 'plan' | 'work' | 'review' | 'test' | 'ship'; subRole?: string } {\n const normalized = specialistType.replace(/-agent$/, '');\n if (normalized === 'inspect') return { role: 'work', subRole: 'inspect' };\n if (normalized === 'inspect-deep') return { role: 'work', subRole: 'inspect-deep' };\n if (normalized === 'review') return { role: 'review' };\n if (normalized === 'test' || normalized === 'uat') return { role: 'test' };\n if (normalized === 'merge' || normalized === 'ship') return { role: 'ship' };\n if (normalized === 'planning' || normalized === 'plan') return { role: 'plan' };\n return { role: 'work' };\n}\n\n\n/**\n * Shell fragment that unsets every provider-routing env var a parent tmux server\n * may have leaked into its child sessions. The overdeck tmux server is long-lived\n * and inherits whatever env existed when it was spawned — so fresh Anthropic-model\n * agents can still see a stale ANTHROPIC_BASE_URL pointing at cliproxy, which\n * responds with \"unknown provider for model claude-*\" (PAN-705). Every launcher\n * script must run this before exec'ing claude.\n */\nconst PROVIDER_ENV_UNSETS = [\n 'ANTHROPIC_BASE_URL',\n 'ANTHROPIC_AUTH_TOKEN',\n 'OPENAI_API_KEY',\n 'GEMINI_API_KEY',\n 'API_TIMEOUT_MS',\n 'CLAUDE_CODE_API_KEY_HELPER_TTL_MS',\n];\nconst PROVIDER_UNSET_CMD = `unset ${PROVIDER_ENV_UNSETS.join(' ')}`;\n\n/**\n * Convert a providerEnv dict to bash export lines.\n * Non-Anthropic models (e.g. gpt-5.4 via cliproxy) need ANTHROPIC_BASE_URL set in the\n * script body after provider env vars are unset.\n */\nfunction buildProviderExportLines(providerEnv: Record<string, string>): string {\n const entries = Object.entries(providerEnv);\n if (entries.length === 0) return '';\n return entries.map(([k, v]) => `export ${k}=\"${v}\"`).join('\\n') + '\\n';\n}\n\n/**\n * Build tmux -e flags for environment variables\n */\nfunction buildTmuxEnvFlags(env: Record<string, string>): string {\n let flags = '';\n for (const [key, value] of Object.entries(env)) {\n flags += ` -e ${key}=\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n return flags;\n}\n\n\nexport async function buildSpecialistBaseCommand(\n specialistType: string,\n model: string,\n sessionName?: string,\n): Promise<string> {\n const { getAgentRuntimeBaseCommand } = await import('../agents.js');\n const role = roleForSpecialistModel(specialistType).role;\n const harness = await resolveHarness({ model, role });\n const agentDefinition = specialistType.startsWith('pan-')\n ? specialistType\n : `pan-${specialistType.endsWith('-agent')\n ? specialistType.slice(0, -'-agent'.length)\n : specialistType}-agent`;\n return getAgentRuntimeBaseCommand(model, sessionName, agentDefinition, harness);\n}\n\n/**\n * Build shell export lines for caveman compression for specialist agents.\n *\n * Excluded: inspect-agent (its INSPECTION PASSED/BLOCKED sentinels are parsed by Cloister).\n * Uses per-specialist-type intensity from config.\n *\n * @param specialistType The specialist type (review-agent, test-agent, etc.)\n * @param workspacePath Workspace path to read the A/B variant from (may be undefined)\n * @param config Normalized caveman config\n * @returns Shell export lines to inject into the inner script\n */\nexport async function buildSpecialistCavemanExports(\n specialistType: string,\n workspacePath: string | undefined,\n config: import('../config-yaml.js').NormalizedCavemanConfig\n): Promise<string> {\n // inspect-agent: never compress — output contains sentinel strings parsed by Cloister\n if (specialistType === 'inspect-agent' || !config.enabled) return '';\n\n // Read the workspace's A/B variant if we have a workspace path\n const variant = workspacePath ? await Effect.runPromise(readCavemanVariant(workspacePath)) : 'off';\n if (variant === 'off') return '';\n if (variant === 'disabled') {\n return `export OVERDECK_CAVEMAN_VARIANT=\"${variant}\"\\n`;\n }\n\n // Map specialist type to caveman intensity mode\n const modeMap: Record<string, keyof typeof config.modes> = {\n 'review-agent': 'review',\n 'test-agent': 'test',\n 'merge-agent': 'merge',\n };\n const modeKey = modeMap[specialistType];\n if (!modeKey) return '';\n\n const mode = config.modes[modeKey];\n if (mode === 'off' || mode === 'disabled') return '';\n\n return `export CAVEMAN_DEFAULT_MODE=\"${mode}\"\\nexport OVERDECK_CAVEMAN_VARIANT=\"${variant}\"\\n`;\n}\n","/**\n * Cloister Specialist Runtime Status\n *\n * Resolves specialist session state, token usage, and startup status.\n */\n\nimport { readFileSync, existsSync } from 'fs';\nimport { basename, join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from '../paths.js';\nimport { getAllSessionFilesSync, parseClaudeSessionSync } from '../cost-parsers/jsonl-parser.js';\nimport { listPaneValues, sessionExists } from '../tmux.js';\nimport {\n getAllSpecialists,\n getSpecialistMetadata,\n getTmuxSessionName,\n type LegacySpecialistRuntimeStatus,\n type SpecialistAgentName,\n} from './specialists-registry.js';\n\nconst execAsync = promisify(exec);\n\nfunction readRecordedClaudeSessionId(tmuxSession: string): string | null {\n const sessionFile = join(AGENTS_DIR, tmuxSession, 'session.id');\n if (!existsSync(sessionFile)) return null;\n try {\n const sessionId = readFileSync(sessionFile, 'utf-8').trim();\n return sessionId || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if a legacy specialist has a recorded Claude session.\n *\n * @param name - Specialist name\n * @returns True if the specialist has a recorded session id in its agent directory\n */\nexport function isInitialized(name: SpecialistAgentName): boolean {\n return readRecordedClaudeSessionId(getTmuxSessionName(name)) !== null;\n}\n\n/**\n * Get the state of a specialist from recorded agent metadata.\n *\n * Note: This only checks whether a recorded Claude session exists, not if it's actually running.\n * Use getSpecialistStatus() for runtime state.\n *\n * @param name - Specialist name\n * @returns Specialist state\n */\nexport function getSpecialistState(\n name: SpecialistAgentName\n): Exclude<LegacySpecialistRuntimeStatus['state'], 'active'> {\n return isInitialized(name) ? 'sleeping' : 'uninitialized';\n}\n\n/**\n * Find JSONL file for a session ID\n *\n * Searches through Claude Code project directories to find the JSONL file.\n *\n * @param sessionId - Session ID to find\n * @returns Path to JSONL file or null if not found\n */\nexport function findSessionFile(sessionId: string): string | null {\n try {\n const allFiles = getAllSessionFilesSync();\n\n for (const file of allFiles) {\n const fileSessionId = basename(file, '.jsonl');\n if (fileSessionId === sessionId) {\n return file;\n }\n }\n } catch {\n // Session files not available\n }\n\n return null;\n}\n\n/**\n * Count context tokens for a specialist session\n *\n * Reads the JSONL file for the specialist's session and sums all token usage.\n * This gives an approximate count of context size.\n *\n * @param name - Specialist name\n * @returns Total token count or null if session not found\n */\nexport function countContextTokens(name: SpecialistAgentName): number | null {\n const sessionId = readRecordedClaudeSessionId(getTmuxSessionName(name));\n\n if (!sessionId) {\n return null;\n }\n\n const sessionFile = findSessionFile(sessionId);\n\n if (!sessionFile) {\n return null;\n }\n\n const sessionUsage = parseClaudeSessionSync(sessionFile);\n\n if (!sessionUsage) {\n return null;\n }\n\n // Sum all token types for total context\n return (\n sessionUsage.usage.inputTokens +\n sessionUsage.usage.outputTokens +\n (sessionUsage.usage.cacheReadTokens || 0) +\n (sessionUsage.usage.cacheWriteTokens || 0)\n );\n}\n\n/**\n * Check if a specialist is currently running in tmux\n *\n * @param name - Specialist name\n * @param projectKey - Optional project key for per-project specialists\n * @returns True if specialist has an active tmux session\n */\nexport async function isRunning(name: SpecialistAgentName, projectKey?: string): Promise<boolean> {\n const tmuxSession = getTmuxSessionName(name, projectKey);\n\n try {\n const exists = await Effect.runPromise(sessionExists(tmuxSession));\n if (!exists) return false;\n // Session exists — but check if the pane actually has a running process.\n // When Claude Code crashes, the pane's process exits but the tmux session persists,\n // making has-session return success even though nothing is running.\n const panePid = (await Effect.runPromise(listPaneValues(tmuxSession, '#{pane_pid}')))[0]?.trim() ?? '';\n if (!panePid) return false;\n // Check if the pane's process has any child processes (Claude Code / bash)\n const { stdout: children } = await execAsync(\n `ps --ppid ${panePid} --no-headers 2>/dev/null || echo \"\"`,\n { encoding: 'utf-8' }\n );\n return children.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Get complete status for a specialist\n *\n * Combines metadata, session info, and runtime state (PAN-80: uses hook-based state).\n *\n * @param name - Specialist name\n * @param projectKey - Optional project key for per-project specialists\n * @returns Complete specialist status\n */\nexport async function getSpecialistStatus(\n name: SpecialistAgentName,\n projectKey?: string\n): Promise<LegacySpecialistRuntimeStatus> {\n const metadata = getSpecialistMetadata(name) || {\n name,\n displayName: name,\n description: '',\n enabled: false,\n autoWake: false,\n };\n\n const sessionId = readRecordedClaudeSessionId(getTmuxSessionName(name, projectKey));\n const running = await isRunning(name, projectKey);\n const contextTokens = countContextTokens(name);\n\n // Determine state from hook-based runtime state (PAN-80)\n const { getAgentRuntimeStateSync } = await import('../agents.js');\n const tmuxSession = getTmuxSessionName(name, projectKey);\n const runtimeState = getAgentRuntimeStateSync(tmuxSession);\n\n let state: LegacySpecialistRuntimeStatus['state'];\n if (runtimeState) {\n // Map runtime state to specialist state\n switch (runtimeState.state) {\n case 'active':\n state = 'active';\n break;\n case 'idle':\n state = 'sleeping'; // Idle = at prompt waiting\n break;\n case 'suspended':\n state = 'sleeping'; // Suspended = session saved, not running\n break;\n case 'uninitialized':\n default:\n state = 'uninitialized';\n break;\n }\n } else {\n // Fallback if no runtime state available\n if (running && sessionId) {\n state = 'sleeping';\n } else if (sessionId) {\n state = 'sleeping';\n } else {\n state = 'uninitialized';\n }\n }\n\n return {\n ...metadata,\n sessionId: sessionId || undefined,\n contextTokens: contextTokens || undefined,\n state,\n isRunning: running,\n tmuxSession: getTmuxSessionName(name, projectKey),\n currentIssue: running ? runtimeState?.currentIssue : undefined,\n };\n}\n\n/**\n * Get status for all specialists\n *\n * @returns Array of specialist statuses\n */\nexport async function getAllSpecialistStatus(): Promise<LegacySpecialistRuntimeStatus[]> {\n const specialists = getAllSpecialists();\n return Promise.all(specialists.map((metadata) => getSpecialistStatus(metadata.name)));\n}\n\n\n/**\n * Initialize all enabled but uninitialized specialists\n *\n * Called during Cloister startup to ensure specialists are ready.\n *\n * @returns Promise with array of initialization results\n */\nexport async function initializeEnabledSpecialists(): Promise<Array<{\n name: SpecialistAgentName;\n success: boolean;\n message: string;\n}>> {\n const enabled = getAllSpecialists().filter((specialist) => specialist.enabled);\n const results: Array<{ name: SpecialistAgentName; success: boolean; message: string }> = [];\n\n for (const specialist of enabled) {\n results.push({\n name: specialist.name,\n success: true,\n message: 'Legacy global specialist initialization removed; role flows spawn agents on demand.',\n });\n }\n\n return results;\n}\n","/**\n * Cloister Specialist Project Metadata\n *\n * Manages per-project specialist run metadata and configuration.\n */\n\nimport {\n getAllSpecialists,\n getSpecialistMetadata,\n getTmuxSessionName,\n isProjectSpecialistActivelyRunning,\n loadRegistry,\n parseSpecialistRegistryKey,\n saveRegistry,\n updateSpecialistMetadata,\n type LegacySpecialistDefinition,\n type ProjectSpecialistMetadata,\n type SpecialistAgentName,\n} from './specialists-registry.js';\nimport { isRunning } from './specialists-status.js';\n\n/**\n * ===========================================================================\n * Per-Project Specialist Functions\n * ===========================================================================\n */\n\n/**\n * Get metadata for a specific (projectKey, registryKey) pair.\n * registryKey is either a plain specialistType (legacy) or a compound key from makeSpecialistRegistryKey().\n */\nexport function getRunMetadata(\n projectKey: string,\n registryKey: string,\n): ProjectSpecialistMetadata {\n const registry = loadRegistry();\n\n if (!registry.projects[projectKey]) {\n registry.projects[projectKey] = {};\n }\n\n if (!registry.projects[projectKey][registryKey]) {\n registry.projects[projectKey][registryKey] = {\n runCount: 0,\n lastRunAt: null,\n lastRunStatus: null,\n currentRun: null,\n };\n saveRegistry(registry);\n }\n\n return registry.projects[projectKey][registryKey];\n}\n\n/**\n * Update metadata for a specific (projectKey, registryKey) pair.\n */\nexport function updateRunMetadata(\n projectKey: string,\n registryKey: string,\n updates: Partial<ProjectSpecialistMetadata>\n): void {\n const registry = loadRegistry();\n\n if (!registry.projects[projectKey]) {\n registry.projects[projectKey] = {};\n }\n\n if (!registry.projects[projectKey][registryKey]) {\n registry.projects[projectKey][registryKey] = {\n runCount: 0,\n lastRunAt: null,\n lastRunStatus: null,\n currentRun: null,\n };\n }\n\n registry.projects[projectKey][registryKey] = {\n ...registry.projects[projectKey][registryKey],\n ...updates,\n };\n\n saveRegistry(registry);\n}\n\n/**\n * Get per-project specialist metadata — backward-compat wrapper.\n * Searches compound-key entries for this project+type; returns the active run, or most recent.\n */\nexport function getProjectSpecialistMetadata(\n projectKey: string,\n specialistType: SpecialistAgentName\n): ProjectSpecialistMetadata {\n const registry = loadRegistry();\n const projectBucket = registry.projects[projectKey] ?? {};\n\n // Check for exact legacy key first\n if (projectBucket[specialistType]) {\n return projectBucket[specialistType];\n }\n\n // Search compound keys for the most relevant entry\n const prefix = `${specialistType}:`;\n const candidates = Object.entries(projectBucket)\n .filter(([k]) => k.startsWith(prefix))\n .map(([, v]) => v);\n\n // Prefer active run, then most recently started\n const active = candidates.find(c => c.currentRun !== null);\n if (active) return active;\n const sorted = candidates.sort((a, b) =>\n (b.lastRunAt ?? '').localeCompare(a.lastRunAt ?? '')\n );\n if (sorted.length > 0) return sorted[0];\n\n // No entry found — return blank default (don't save it)\n return { runCount: 0, lastRunAt: null, lastRunStatus: null, currentRun: null };\n}\n\n/**\n * Update per-project specialist metadata — backward-compat wrapper.\n * Updates the active compound-key entry, or the legacy plain-key entry.\n */\nexport function updateProjectSpecialistMetadata(\n projectKey: string,\n specialistType: SpecialistAgentName,\n updates: Partial<ProjectSpecialistMetadata>\n): void {\n const registry = loadRegistry();\n const projectBucket = registry.projects[projectKey] ?? {};\n\n // Try legacy key first\n if (projectBucket[specialistType]) {\n updateRunMetadata(projectKey, specialistType, updates);\n return;\n }\n\n // Find the active compound-key entry for this type\n const prefix = `${specialistType}:`;\n const activeKey = Object.keys(projectBucket).find(k =>\n k.startsWith(prefix) && projectBucket[k].currentRun !== null\n );\n if (activeKey) {\n updateRunMetadata(projectKey, activeKey, updates);\n return;\n }\n\n // Fall back to most recent\n const latestKey = Object.keys(projectBucket)\n .filter(k => k.startsWith(prefix))\n .sort((a, b) => (projectBucket[b].lastRunAt ?? '').localeCompare(projectBucket[a].lastRunAt ?? ''))\n .shift();\n if (latestKey) {\n updateRunMetadata(projectKey, latestKey, updates);\n }\n}\n\n/**\n * Increment run count for a project's specialist.\n * registryKey may be a plain specialistType (legacy) or a compound key.\n */\nexport function incrementProjectRunCount(projectKey: string, registryKey: string): void {\n const metadata = getRunMetadata(projectKey, registryKey);\n updateRunMetadata(projectKey, registryKey, {\n runCount: metadata.runCount + 1,\n lastRunAt: new Date().toISOString(),\n });\n}\n\n/**\n * Set current run for a project's specialist.\n * registryKey may be a plain specialistType (legacy) or a compound key.\n */\nexport function setCurrentRun(\n projectKey: string,\n registryKey: string,\n runId: string | null\n): void {\n updateRunMetadata(projectKey, registryKey, { currentRun: runId });\n}\n\n/**\n * Update run status for a project's specialist.\n * registryKey may be a plain specialistType (legacy) or a compound key.\n */\nexport function updateRunStatus(\n projectKey: string,\n registryKey: string,\n status: 'passed' | 'failed' | 'blocked' | null\n): void {\n updateRunMetadata(projectKey, registryKey, { lastRunStatus: status });\n}\n\n/**\n * List all projects that have specialists configured\n */\nexport function listProjectsWithSpecialists(): string[] {\n const registry = loadRegistry();\n return Object.keys(registry.projects);\n}\n\n/**\n * List all specialist types for a project\n */\nexport function listSpecialistsForProject(projectKey: string): SpecialistAgentName[] {\n const registry = loadRegistry();\n const project = registry.projects[projectKey];\n\n if (!project) {\n return [];\n }\n\n return Object.keys(project) as SpecialistAgentName[];\n}\n\n/**\n * Get all per-project specialist statuses (PAN-754: compound-key aware).\n * Walks registry including compound keys (type:issueId[:role]) and returns\n * enriched entries with issueId, model, currentActivity for the Agents page.\n */\nexport async function getAllProjectSpecialistStatuses(): Promise<Array<{\n projectKey: string;\n specialistType: SpecialistAgentName;\n registryKey: string;\n issueId?: string;\n role?: string;\n metadata: ProjectSpecialistMetadata;\n isRunning: boolean;\n tmuxSession: string;\n}>> {\n const registry = loadRegistry();\n const { getAgentRuntimeStateSync } = await import('../agents.js');\n\n const results: Array<{\n projectKey: string;\n specialistType: SpecialistAgentName;\n registryKey: string;\n issueId?: string;\n role?: string;\n metadata: ProjectSpecialistMetadata;\n isRunning: boolean;\n tmuxSession: string;\n }> = [];\n\n for (const [projectKey, specialists] of Object.entries(registry.projects)) {\n for (const [registryKey, metadata] of Object.entries(specialists)) {\n const { specialistType, issueId, role } = parseSpecialistRegistryKey(registryKey);\n\n // Determine tmux session: use stored field when available\n const tmuxSession = metadata.tmuxSession\n ?? getTmuxSessionName(specialistType as SpecialistAgentName, projectKey, issueId);\n\n const runtimeState = getAgentRuntimeStateSync(tmuxSession);\n const sessionRunning = await isRunning(specialistType as SpecialistAgentName, projectKey).catch(() => false);\n const running = isProjectSpecialistActivelyRunning(runtimeState, sessionRunning);\n const effectiveMetadata = running ? metadata : { ...metadata, currentRun: null };\n\n results.push({\n projectKey,\n specialistType: specialistType as SpecialistAgentName,\n registryKey,\n issueId,\n role,\n metadata: effectiveMetadata,\n isRunning: running,\n tmuxSession,\n });\n }\n }\n\n return results;\n}\n\n/**\n * Update context token count for a specialist\n *\n * @param name - Specialist name\n * @param tokens - Total context tokens\n */\nexport function updateContextTokens(name: SpecialistAgentName, tokens: number): void {\n updateSpecialistMetadata(name, { contextTokens: tokens });\n}\n\n/**\n * Enable a specialist\n *\n * @param name - Specialist name\n */\nexport function enableSpecialist(name: SpecialistAgentName): void {\n updateSpecialistMetadata(name, { enabled: true });\n}\n\n/**\n * Disable a specialist\n *\n * @param name - Specialist name\n */\nexport function disableSpecialist(name: SpecialistAgentName): void {\n updateSpecialistMetadata(name, { enabled: false });\n}\n\n/**\n * Check if a specialist is enabled\n *\n * @param name - Specialist name\n * @returns True if specialist is enabled\n */\nexport function isEnabled(name: SpecialistAgentName): boolean {\n const metadata = getSpecialistMetadata(name);\n return metadata?.enabled ?? false;\n}\n\n/**\n * Get all enabled specialists\n *\n * @returns Array of enabled specialists\n */\nexport function getEnabledSpecialists(): LegacySpecialistDefinition[] {\n return getAllSpecialists().filter((s) => s.enabled);\n}\n","/**\n * Cloister Specialist Lifecycle\n *\n * Manages grace periods, completion signaling, and specialist termination.\n */\n\nimport { Effect } from 'effect';\nimport { killSession } from '../tmux.js';\nimport {\n getTmuxSessionName,\n loadRegistry,\n makeSpecialistRegistryKey,\n type SpecialistAgentName,\n} from './specialists-registry.js';\nimport {\n getRunMetadata,\n setCurrentRun,\n updateRunMetadata,\n updateRunStatus,\n} from './specialists-project-meta.js';\n\n/**\n * ===========================================================================\n * Ephemeral Lifecycle Management\n * ===========================================================================\n */\n\n/**\n * Grace period state for a specialist\n */\nexport interface GracePeriodState {\n active: boolean;\n startedAt: string;\n duration: number; // milliseconds\n paused: boolean;\n pausedAt?: string;\n remainingTime?: number; // milliseconds when paused\n}\n\nconst gracePeriodStates = new Map<string, GracePeriodState>();\n\n/**\n * Task context interface for specialist tasks.\n */\nexport interface TaskContext {\n prUrl?: string;\n workspace?: string;\n branch?: string;\n filesChanged?: string[];\n reason?: string;\n targetModel?: string;\n additionalInstructions?: string;\n [key: string]: string | string[] | undefined;\n}\n\n/**\n * PAN-1048 R1: spawnEphemeralSpecialist removed.\n *\n * The function was the generic dispatcher that took an arbitrary\n * SpecialistAgentName ('review-agent' | 'test-agent' | 'merge-agent' |\n * 'inspect-agent' | …) and shelled out a launcher specific to each. Under\n * the role primitive, those flavours are first-class roles\n * (review/test/ship) plus a single work sub-role (inspect / inspect-deep).\n *\n * Replacements:\n * - Review/test/ship runs: spawnRun(issueId, role, opts) in src/lib/agents.ts.\n * Reactive Cloister fires these on lifecycle transitions; the manual\n * re-dispatch in routes/workspaces.ts also uses spawnRun.\n * - Inspect runs: spawnInspectAgent() in cloister/inspect-agent.ts owns\n * its own minimal launcher path (single-bead-scoped, ephemeral).\n *\n * The specialist registry/run-log/grace-period machinery stays in this\n * file because the dashboard read-model and reset/init/grace endpoints\n * still consume it for legacy run history; nothing writes new entries\n * once the dispatcher is gone.\n */\n\n\n/**\n * Start grace period for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param duration - Grace period duration in milliseconds (default: 60000)\n */\nexport function startGracePeriod(\n projectKey: string,\n specialistType: SpecialistAgentName,\n duration: number = 60000\n): void {\n const key = `${projectKey}-${specialistType}`;\n\n gracePeriodStates.set(key, {\n active: true,\n startedAt: new Date().toISOString(),\n duration,\n paused: false,\n });\n\n console.log(`[specialist] Grace period started for ${projectKey}/${specialistType} (${duration}ms)`);\n\n // Schedule termination after grace period\n setTimeout(() => {\n const state = gracePeriodStates.get(key);\n if (state && state.active && !state.paused) {\n terminateSpecialist(projectKey, specialistType);\n }\n }, duration);\n}\n\n/**\n * Pause grace period countdown\n */\nexport function pauseGracePeriod(projectKey: string, specialistType: SpecialistAgentName): boolean {\n const key = `${projectKey}-${specialistType}`;\n const state = gracePeriodStates.get(key);\n\n if (!state || !state.active) {\n return false;\n }\n\n const elapsed = Date.now() - new Date(state.startedAt).getTime();\n const remaining = state.duration - elapsed;\n\n state.paused = true;\n state.pausedAt = new Date().toISOString();\n state.remainingTime = remaining;\n\n gracePeriodStates.set(key, state);\n console.log(`[specialist] Grace period paused for ${projectKey}/${specialistType}`);\n\n return true;\n}\n\n/**\n * Resume grace period countdown\n */\nexport function resumeGracePeriod(projectKey: string, specialistType: SpecialistAgentName): boolean {\n const key = `${projectKey}-${specialistType}`;\n const state = gracePeriodStates.get(key);\n\n if (!state || !state.active || !state.paused) {\n return false;\n }\n\n state.paused = false;\n state.startedAt = new Date().toISOString();\n state.pausedAt = undefined;\n\n gracePeriodStates.set(key, state);\n console.log(`[specialist] Grace period resumed for ${projectKey}/${specialistType}`);\n\n // Schedule termination for remaining time\n setTimeout(() => {\n const currentState = gracePeriodStates.get(key);\n if (currentState && currentState.active && !currentState.paused) {\n terminateSpecialist(projectKey, specialistType);\n }\n }, state.remainingTime || 0);\n\n return true;\n}\n\n/**\n * Exit grace period immediately (terminate now)\n */\nexport function exitGracePeriod(projectKey: string, specialistType: SpecialistAgentName): void {\n const key = `${projectKey}-${specialistType}`;\n gracePeriodStates.delete(key);\n\n terminateSpecialist(projectKey, specialistType);\n}\n\n/**\n * Get grace period state\n */\nexport function getGracePeriodState(\n projectKey: string,\n specialistType: SpecialistAgentName\n): GracePeriodState | null {\n const key = `${projectKey}-${specialistType}`;\n return gracePeriodStates.get(key) || null;\n}\n\n/**\n * Find the active registry key for (projectKey, specialistType).\n * Searches compound keys; falls back to plain specialistType key.\n * Returns undefined if nothing is currently active.\n */\nexport function findActiveRegistryKey(projectKey: string, specialistType: SpecialistAgentName): string | undefined {\n const registry = loadRegistry();\n const bucket = registry.projects[projectKey] ?? {};\n\n // Check compound keys first (new format: \"type:issueId\")\n const prefix = `${specialistType}:`;\n const activeCompound = Object.keys(bucket).find(k =>\n k.startsWith(prefix) && bucket[k].currentRun !== null\n );\n if (activeCompound) return activeCompound;\n\n // Check legacy plain key\n if (bucket[specialistType]?.currentRun !== null) return specialistType;\n\n // Return most recently touched key even if not active\n const allMatching = Object.keys(bucket).filter(k =>\n k === specialistType || k.startsWith(prefix)\n ).sort((a, b) =>\n (bucket[b].lastRunAt ?? '').localeCompare(bucket[a].lastRunAt ?? '')\n );\n return allMatching[0];\n}\n\n/**\n * Signal that a specialist has completed its task\n *\n * This should be called when the specialist finishes its work.\n * It updates the run status and starts the grace period.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param result - Task result\n * @param issueId - Optional: issue being handled (used to compute compound registry key)\n */\nexport async function signalSpecialistCompletion(\n projectKey: string,\n specialistType: SpecialistAgentName,\n result: {\n status: 'passed' | 'failed' | 'blocked';\n notes?: string;\n },\n issueId?: string\n): Promise<void> {\n const registryKey = issueId\n ? makeSpecialistRegistryKey(specialistType, issueId)\n : (findActiveRegistryKey(projectKey, specialistType) ?? specialistType);\n const metadata = getRunMetadata(projectKey, registryKey);\n\n // Derive tmuxSession: use stored field when available, recompute as fallback\n const resolvedTmuxSession = metadata.tmuxSession ?? getTmuxSessionName(specialistType, projectKey, issueId);\n\n // Update status\n updateRunStatus(projectKey, registryKey, result.status);\n\n // Finalize log if there's a current run\n if (metadata.currentRun) {\n try {\n const { finalizeRunLogSync } = await import('./specialist-logs.js');\n finalizeRunLogSync(projectKey, specialistType, metadata.currentRun, {\n status: result.status,\n notes: result.notes,\n });\n } catch (error) {\n console.error(`[specialist] Failed to finalize log:`, error);\n }\n }\n\n // Completion means the run itself is over, even if the tmux session stays alive\n // during the grace period for inspection or manual termination.\n setCurrentRun(projectKey, registryKey, null);\n updateRunMetadata(projectKey, registryKey, { currentActivity: null });\n import('../agents.js')\n .then(({ saveAgentRuntimeState }) => {\n saveAgentRuntimeState(resolvedTmuxSession, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n currentIssue: undefined,\n });\n })\n .catch((error) => {\n console.error(`[specialist] Failed to mark ${projectKey}/${specialistType} idle:`, error);\n });\n\n // Start grace period (60 seconds)\n startGracePeriod(projectKey, specialistType, 60000);\n\n console.log(`[specialist] ${specialistType} completed for ${projectKey} (status: ${result.status})`);\n}\n\n/**\n * Terminate a specialist session\n *\n * Kills the tmux session, finalizes logs, and schedules digest generation.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport async function terminateSpecialist(\n projectKey: string,\n specialistType: SpecialistAgentName,\n issueId?: string\n): Promise<void> {\n const registryKey = issueId\n ? makeSpecialistRegistryKey(specialistType, issueId)\n : (findActiveRegistryKey(projectKey, specialistType) ?? specialistType);\n const metadata = getRunMetadata(projectKey, registryKey);\n\n // Derive tmuxSession: use stored field, or recompute\n const tmuxSession = metadata.tmuxSession ?? getTmuxSessionName(specialistType, projectKey, issueId);\n\n try {\n // Kill tmux session\n await Effect.runPromise(killSession(tmuxSession));\n console.log(`[specialist] Terminated ${projectKey}/${specialistType}`);\n } catch (error) {\n console.error(`[specialist] Failed to kill tmux session ${tmuxSession}:`, error);\n }\n\n // Finalize log if there's a current run\n if (metadata.currentRun) {\n const { finalizeRunLogSync } = await import('./specialist-logs.js');\n\n try {\n finalizeRunLogSync(projectKey, specialistType, metadata.currentRun, {\n status: metadata.lastRunStatus || 'incomplete',\n notes: 'Specialist terminated',\n });\n } catch (error) {\n console.error(`[specialist] Failed to finalize log:`, error);\n }\n\n // Clear current run\n setCurrentRun(projectKey, registryKey, null);\n }\n\n updateRunMetadata(projectKey, registryKey, { currentActivity: null });\n\n // Clear grace period state\n const key = `${projectKey}-${specialistType}`;\n gracePeriodStates.delete(key);\n\n // Update runtime state\n const { saveAgentRuntimeState } = await import('../agents.js');\n saveAgentRuntimeState(tmuxSession, {\n state: 'suspended',\n lastActivity: new Date().toISOString(),\n });\n\n // Schedule digest generation (async, fire-and-forget)\n const { scheduleDigestGeneration } = await import('./specialist-context.js');\n scheduleDigestGeneration(projectKey, specialistType);\n\n // Run log cleanup for this project/specialist (async, fire-and-forget)\n scheduleLogCleanup(projectKey, specialistType);\n}\n\n/**\n * Schedule log cleanup for a project's specialist (async, fire-and-forget)\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nfunction scheduleLogCleanup(projectKey: string, specialistType: SpecialistAgentName): void {\n // Run async without awaiting\n Promise.resolve().then(async () => {\n try {\n const { cleanupOldLogsSync } = await import('./specialist-logs.js');\n const { getSpecialistRetention } = await import('../projects.js');\n\n const retention = getSpecialistRetention(projectKey);\n const deleted = cleanupOldLogsSync(projectKey, specialistType, { maxDays: retention.max_days, maxRuns: retention.max_runs });\n\n if (deleted > 0) {\n console.log(`[specialist] Cleaned up ${deleted} old logs for ${projectKey}/${specialistType}`);\n }\n } catch (error) {\n console.error(`[specialist] Log cleanup failed for ${projectKey}/${specialistType}:`, error);\n }\n });\n}\n","/**\n * Cloister Specialist Feedback\n *\n * Persists specialist feedback and delivers it to issue agents.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { OVERDECK_HOME } from '../paths.js';\nimport { type SpecialistAgentName } from './specialists-registry.js';\n\n/**\n * ===========================================================================\n * Specialist Feedback System\n * ===========================================================================\n *\n * Specialists accumulate context and expertise. This system allows them to\n * share learnings back to issue agents, creating a feedback loop that\n * improves the overall system over time.\n */\n\n/**\n * Feedback from a specialist to an issue agent\n */\nexport interface SpecialistFeedback {\n id: string;\n timestamp: string;\n fromSpecialist: SpecialistAgentName;\n toIssueId: string;\n feedbackType: 'success' | 'failure' | 'warning' | 'insight';\n category: 'merge' | 'test' | 'review' | 'general';\n summary: string;\n details: string;\n actionItems?: string[];\n patterns?: string[]; // Patterns the specialist noticed\n suggestions?: string[]; // Suggestions for the issue agent\n}\n\nconst FEEDBACK_DIR = join(OVERDECK_HOME, 'specialists', 'feedback');\nconst FEEDBACK_LOG = join(FEEDBACK_DIR, 'feedback.jsonl');\n\n/**\n * Send feedback from a specialist to an issue agent\n *\n * This is the key mechanism for specialists to share their accumulated\n * expertise back to the issue agents that spawned the work.\n *\n * @param feedback - The feedback to send\n * @returns True if feedback was sent successfully\n */\nexport async function sendFeedbackToAgent(\n feedback: Omit<SpecialistFeedback, 'id' | 'timestamp'>\n): Promise<boolean> {\n const { fromSpecialist, toIssueId, summary, details } = feedback;\n\n // Ensure feedback directory exists\n if (!existsSync(FEEDBACK_DIR)) {\n mkdirSync(FEEDBACK_DIR, { recursive: true });\n }\n\n // Create full feedback record\n const fullFeedback: SpecialistFeedback = {\n ...feedback,\n id: `feedback-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n timestamp: new Date().toISOString(),\n };\n\n // Log feedback to JSONL\n try {\n const line = JSON.stringify(fullFeedback) + '\\n';\n appendFileSync(FEEDBACK_LOG, line, 'utf-8');\n } catch (error) {\n console.error(`[specialist] Failed to log feedback:`, error);\n }\n\n // Try to send feedback to the issue agent\n const agentSession = `agent-${toIssueId.toLowerCase()}`;\n\n // Format feedback message for the agent\n const feedbackMessage = formatFeedbackForAgent(fullFeedback);\n\n // Write feedback to workspace file\n const { writeFeedbackFile } = await import('./feedback-writer.js');\n const specialistMap: Record<string, 'review-agent' | 'test-agent' | 'merge-agent'> = {\n 'review-agent': 'review-agent',\n 'test-agent': 'test-agent',\n 'merge-agent': 'merge-agent',\n };\n const specialist = specialistMap[fromSpecialist] || 'review-agent';\n const outcome = feedback.feedbackType === 'success' ? 'approved' : feedback.feedbackType === 'failure' ? 'failed' : feedback.feedbackType;\n\n const fileResult = await Effect.runPromise(writeFeedbackFile({\n issueId: toIssueId,\n specialist,\n outcome,\n summary: summary.slice(0, 100),\n markdownBody: feedbackMessage,\n }));\n\n if (!fileResult.success) {\n console.error(`[specialist] Failed to write feedback file for ${toIssueId}: ${fileResult.error}`);\n return false;\n }\n\n // Send a short, explicit message with the ABSOLUTE path.\n try {\n const { messageAgent } = await import('../agents.js');\n const msg = `SPECIALIST FEEDBACK: ${fromSpecialist} reported ${feedback.feedbackType.toUpperCase()} for ${toIssueId}.\\n\\nMUST READ: ${fileResult.filePath}\\n\\nUse your Read tool to open this file, read every line, then address the feedback and continue working. Do NOT stop at the prompt.`;\n await messageAgent(agentSession, msg);\n console.log(`[specialist] Sent feedback from ${fromSpecialist} to ${agentSession} (file: ${fileResult.filePath})`);\n return true;\n } catch (err) {\n // Agent may be gone — feedback file is still in the workspace for crash recovery\n console.log(`[specialist] Could not send reference to ${agentSession} (file written): ${err}`);\n return true; // File was written successfully, that's the important part\n }\n}\n\n/**\n * Format feedback for display to an agent\n */\nfunction formatFeedbackForAgent(feedback: SpecialistFeedback): string {\n const { fromSpecialist, feedbackType, category, summary, details, actionItems, patterns, suggestions } = feedback;\n\n const typeEmoji = {\n success: '✅',\n failure: '❌',\n warning: '⚠️',\n insight: '💡',\n }[feedbackType];\n\n let message = `\\n${typeEmoji} **Feedback from ${fromSpecialist}** (${category})\\n\\n`;\n message += `**Summary:** ${summary}\\n\\n`;\n message += `**Details:**\\n${details}\\n`;\n\n if (actionItems?.length) {\n message += `\\n**Action Items:**\\n`;\n actionItems.forEach((item, i) => {\n message += `${i + 1}. ${item}\\n`;\n });\n }\n\n if (patterns?.length) {\n message += `\\n**Patterns Noticed:**\\n`;\n patterns.forEach(pattern => {\n message += `- ${pattern}\\n`;\n });\n }\n\n if (suggestions?.length) {\n message += `\\n**Suggestions:**\\n`;\n suggestions.forEach(suggestion => {\n message += `- ${suggestion}\\n`;\n });\n }\n\n return message;\n}\n\n/**\n * Get pending feedback for an issue that hasn't been delivered yet\n *\n * @param issueId - Issue ID to get feedback for\n * @returns Array of feedback records\n */\nexport function getPendingFeedback(issueId: string): SpecialistFeedback[] {\n if (!existsSync(FEEDBACK_LOG)) {\n return [];\n }\n\n try {\n const content = readFileSync(FEEDBACK_LOG, 'utf-8');\n const lines = content.trim().split('\\n').filter(l => l.length > 0);\n const allFeedback = lines.map(line => JSON.parse(line) as SpecialistFeedback);\n\n // Filter to this issue\n return allFeedback.filter(f => f.toIssueId.toLowerCase() === issueId.toLowerCase());\n } catch (error) {\n console.error(`[specialist] Failed to read feedback log:`, error);\n return [];\n }\n}\n\n/**\n * Get feedback statistics for all specialists\n *\n * @returns Feedback stats by specialist and type\n */\nexport function getFeedbackStats(): {\n bySpecialist: Record<SpecialistAgentName, number>;\n byType: Record<string, number>;\n total: number;\n} {\n const stats = {\n bySpecialist: {\n 'merge-agent': 0,\n 'review-agent': 0,\n 'test-agent': 0,\n } as Record<SpecialistAgentName, number>,\n byType: {} as Record<string, number>,\n total: 0,\n };\n\n if (!existsSync(FEEDBACK_LOG)) {\n return stats;\n }\n\n try {\n const content = readFileSync(FEEDBACK_LOG, 'utf-8');\n const lines = content.trim().split('\\n').filter(l => l.length > 0);\n\n for (const line of lines) {\n const feedback = JSON.parse(line) as SpecialistFeedback;\n stats.bySpecialist[feedback.fromSpecialist] = (stats.bySpecialist[feedback.fromSpecialist] || 0) + 1;\n stats.byType[feedback.feedbackType] = (stats.byType[feedback.feedbackType] || 0) + 1;\n stats.total++;\n }\n } catch (error) {\n console.error(`[specialist] Failed to read feedback stats:`, error);\n }\n\n return stats;\n}\n","/**\n * Cloister Specialist Agents\n *\n * Manages long-running specialist agents that can be woken up on demand.\n * Specialists maintain context across invocations via session files.\n */\n\nexport { buildSpecialistBaseCommand, buildSpecialistCavemanExports } from './specialists-spawn.js';\n\n\nexport {\n REVIEWER_ROLES,\n getAllSpecialists,\n ensureProjectSpecialistDir,\n getProjectSpecialistDir,\n getReviewerSessionName,\n getSpecialistMetadata,\n getTmuxSessionName,\n initSpecialistsDirectory,\n isProjectSpecialistActivelyRunning,\n loadRegistry,\n makeSpecialistRegistryKey,\n parseReviewerSessionName,\n parseSpecialistRegistryKey,\n pruneSpecialistRegistryEntriesForIssue,\n recordWake,\n saveRegistry,\n updateSpecialistMetadata,\n type LegacySpecialistDefinition,\n type LegacySpecialistRuntimeStatus,\n type ProjectSpecialistMetadata,\n type ResolutionStep,\n type ReviewerRole,\n type SpecialistAgentName,\n type SpecialistRegistry,\n} from './specialists-registry.js';\n\nexport {\n exitGracePeriod,\n findActiveRegistryKey,\n getGracePeriodState,\n pauseGracePeriod,\n resumeGracePeriod,\n signalSpecialistCompletion,\n startGracePeriod,\n terminateSpecialist,\n type GracePeriodState,\n type TaskContext,\n} from './specialists-lifecycle.js';\n\nexport {\n disableSpecialist,\n enableSpecialist,\n getAllProjectSpecialistStatuses,\n getEnabledSpecialists,\n getProjectSpecialistMetadata,\n getRunMetadata,\n incrementProjectRunCount,\n isEnabled,\n listProjectsWithSpecialists,\n listSpecialistsForProject,\n setCurrentRun,\n updateContextTokens,\n updateProjectSpecialistMetadata,\n updateRunMetadata,\n updateRunStatus,\n} from './specialists-project-meta.js';\n\nexport {\n countContextTokens,\n findSessionFile,\n getAllSpecialistStatus,\n getSpecialistState,\n getSpecialistStatus,\n initializeEnabledSpecialists,\n isInitialized,\n isRunning,\n} from './specialists-status.js';\n\nexport {\n getFeedbackStats,\n getPendingFeedback,\n sendFeedbackToAgent,\n type SpecialistFeedback,\n} from './specialists-feedback.js';\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { i as validateSpecialistAutoCompleteMetadata, n as _serverManagedMerges, r as firePostMergeLifecycle, t as specialistsRouteLayer } from "./specialists-COZoYaiz.js";
1
+ import { i as validateSpecialistAutoCompleteMetadata, n as _serverManagedMerges, r as firePostMergeLifecycle, t as specialistsRouteLayer } from "./specialists-BKmReEWT.js";
2
2
  export { _serverManagedMerges, specialistsRouteLayer as default, specialistsRouteLayer, firePostMergeLifecycle, validateSpecialistAutoCompleteMetadata };
@@ -1,4 +1,4 @@
1
- import { A as findSessionFile, C as listSpecialistsForProject, D as updateRunMetadata, E as updateProjectSpecialistMetadata, F as isInitialized, I as isRunning, L as buildSpecialistBaseCommand, M as getSpecialistState, N as getSpecialistStatus, O as updateRunStatus, P as initializeEnabledSpecialists, R as buildSpecialistCavemanExports, S as listProjectsWithSpecialists, T as updateContextTokens, _ as getEnabledSpecialists, a as sendFeedbackToAgent, b as incrementProjectRunCount, c as getGracePeriodState, d as signalSpecialistCompletion, f as startGracePeriod, g as getAllProjectSpecialistStatuses, h as enableSpecialist, i as getPendingFeedback, j as getAllSpecialistStatus, k as countContextTokens, l as pauseGracePeriod, m as disableSpecialist, o as exitGracePeriod, p as terminateSpecialist, r as getFeedbackStats, s as findActiveRegistryKey, t as init_specialists, u as resumeGracePeriod, v as getProjectSpecialistMetadata, w as setCurrentRun, x as isEnabled, y as getRunMetadata } from "./specialists-RPLJnYc1.js";
1
+ import { A as findSessionFile, C as listSpecialistsForProject, D as updateRunMetadata, E as updateProjectSpecialistMetadata, F as isInitialized, I as isRunning, L as buildSpecialistBaseCommand, M as getSpecialistState, N as getSpecialistStatus, O as updateRunStatus, P as initializeEnabledSpecialists, R as buildSpecialistCavemanExports, S as listProjectsWithSpecialists, T as updateContextTokens, _ as getEnabledSpecialists, a as sendFeedbackToAgent, b as incrementProjectRunCount, c as getGracePeriodState, d as signalSpecialistCompletion, f as startGracePeriod, g as getAllProjectSpecialistStatuses, h as enableSpecialist, i as getPendingFeedback, j as getAllSpecialistStatus, k as countContextTokens, l as pauseGracePeriod, m as disableSpecialist, o as exitGracePeriod, p as terminateSpecialist, r as getFeedbackStats, s as findActiveRegistryKey, t as init_specialists, u as resumeGracePeriod, v as getProjectSpecialistMetadata, w as setCurrentRun, x as isEnabled, y as getRunMetadata } from "./specialists-Bvrvt0br.js";
2
2
  import { _ as recordWake, a as getReviewerSessionName, c as initSpecialistsDirectory, f as loadRegistry, g as pruneSpecialistRegistryEntriesForIssue, h as parseSpecialistRegistryKey, i as getProjectSpecialistDir, m as parseReviewerSessionName, n as ensureProjectSpecialistDir, o as getSpecialistMetadata, p as makeSpecialistRegistryKey, r as getAllSpecialists, s as getTmuxSessionName, t as REVIEWER_ROLES, u as isProjectSpecialistActivelyRunning, v as saveRegistry, y as updateSpecialistMetadata } from "./specialists-registry-CNbvfOcy.js";
3
3
  init_specialists();
4
4
  export { REVIEWER_ROLES, buildSpecialistBaseCommand, buildSpecialistCavemanExports, countContextTokens, disableSpecialist, enableSpecialist, ensureProjectSpecialistDir, exitGracePeriod, findActiveRegistryKey, findSessionFile, getAllProjectSpecialistStatuses, getAllSpecialistStatus, getAllSpecialists, getEnabledSpecialists, getFeedbackStats, getGracePeriodState, getPendingFeedback, getProjectSpecialistDir, getProjectSpecialistMetadata, getReviewerSessionName, getRunMetadata, getSpecialistMetadata, getSpecialistState, getSpecialistStatus, getTmuxSessionName, incrementProjectRunCount, initSpecialistsDirectory, initializeEnabledSpecialists, isEnabled, isInitialized, isProjectSpecialistActivelyRunning, isRunning, listProjectsWithSpecialists, listSpecialistsForProject, loadRegistry, makeSpecialistRegistryKey, parseReviewerSessionName, parseSpecialistRegistryKey, pauseGracePeriod, pruneSpecialistRegistryEntriesForIssue, recordWake, resumeGracePeriod, saveRegistry, sendFeedbackToAgent, setCurrentRun, signalSpecialistCompletion, startGracePeriod, terminateSpecialist, updateContextTokens, updateProjectSpecialistMetadata, updateRunMetadata, updateRunStatus, updateSpecialistMetadata };
@@ -1,10 +1,10 @@
1
1
  import { Li as getOverdeckHome, Ri as init_paths } from "./config-yaml-Dce9VUd6.js";
2
2
  import { l as init_activity_logger, o as emitActivityEntrySync } from "./activity-logger-DNYDzAOU.js";
3
3
  import { C as init_tmux, W as sessionExistsSync } from "./tmux-l-yvNWhg.js";
4
- import { _ as init_agent_state, g as getAgentStateSync } from "./agent-state-Ci6PhjOE.js";
4
+ import { _ as init_agent_state, g as getAgentStateSync } from "./agent-state-B196fj1h.js";
5
5
  import { c as readMemoizedArtifactVerdict, o as init_synthesis_verdict } from "./synthesis-verdict-BtJAwMS3.js";
6
6
  import { t as getCloisterEventStore } from "./event-store-provider-B2PMoaap.js";
7
- import { n as resolveParkedPopulation, t as PARKED_ORBIT_SEVERITY } from "./resolver-BGZh3lXA.js";
7
+ import { n as resolveParkedPopulation, t as PARKED_ORBIT_SEVERITY } from "./resolver-BeNrqNou.js";
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
9
  import { join } from "path";
10
10
  //#region ../../lib/cloister/stall-sweeper-state.ts
@@ -302,4 +302,4 @@ function reportRow(row, state, now, reporting, outcome) {
302
302
  //#endregion
303
303
  export { runStallSweeperPatrol };
304
304
 
305
- //# sourceMappingURL=stall-sweeper-9bRBZhrQ.js.map
305
+ //# sourceMappingURL=stall-sweeper-D3fZvg7O.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stall-sweeper-9bRBZhrQ.js","names":[],"sources":["../../src/lib/cloister/stall-sweeper-state.ts","../../src/lib/cloister/stall-sweeper.ts"],"sourcesContent":["/**\n * Stall-sweeper per-row reporting state (PAN-3485 phase 2).\n *\n * One small JSON file per (issueId, orbit) under ~/.overdeck/stall-sweeper/.\n * Tracks a parked episode's recommendation cooldown, recommendation count, and\n * operator re-surface TTL. Runtime-plane residue — deleting the directory only\n * resets reporting cadence and never loses pipeline state.\n */\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { getOverdeckHome } from '../paths.js';\n\nexport interface StallSweeperRowState {\n /** Total recommendations emitted for the current park episode. */\n recommendationCount: number;\n /** ISO timestamp of the last recommendation (drives cooldowns). */\n lastRecommendedAt?: string;\n /** ISO timestamp when the sweeper first saw this park episode. */\n episodeStartedAt: string;\n /** ISO timestamp of the last operator re-surface (drives the escalation TTL). */\n lastEscalatedAt?: string;\n}\n\nfunction sweeperDir(): string {\n return join(getOverdeckHome(), 'stall-sweeper');\n}\n\nfunction statePath(issueId: string, orbit: string): string {\n return join(sweeperDir(), `${issueId.toUpperCase()}--${orbit}.json`);\n}\n\nexport function readSweeperRowState(issueId: string, orbit: string): StallSweeperRowState | null {\n const filePath = statePath(issueId, orbit);\n if (!existsSync(filePath)) return null;\n try {\n return JSON.parse(readFileSync(filePath, 'utf-8')) as StallSweeperRowState;\n } catch {\n return null;\n }\n}\n\nexport function writeSweeperRowState(issueId: string, orbit: string, state: StallSweeperRowState): void {\n mkdirSync(sweeperDir(), { recursive: true });\n writeFileSync(statePath(issueId, orbit), JSON.stringify(state, null, 2));\n}\n\n/** Episode over (row resolved or orbit changed) — forget so a future park starts fresh. */\nexport function clearSweeperRowState(issueId: string, orbit: string): void {\n rmSync(statePath(issueId, orbit), { force: true });\n}\n\n/** Population-signature persistence for change-only sweep.scan emission. */\nexport function readSweeperSignature(): string | null {\n const filePath = join(sweeperDir(), 'population-signature');\n if (!existsSync(filePath)) return null;\n try { return readFileSync(filePath, 'utf-8'); } catch { return null; }\n}\n\nexport function writeSweeperSignature(signature: string): void {\n mkdirSync(sweeperDir(), { recursive: true });\n writeFileSync(join(sweeperDir(), 'population-signature'), signature);\n}\n","/**\n * The Stall Sweeper (PAN-3485 phase 2) — the pipeline's stall DETECTOR.\n *\n * ─── OBSERVABILITY ONLY — OPERATOR DIRECTIVE (2026-08-05) ───────────────────\n * This module detects parked/stalled work and reports it with a recommended\n * action. It must NEVER act. Do not re-add spawn/stop/kill/message/dispatch/\n * clear/reset capability here — not behind a flag, not as \"just this one safe\n * case\", not as an opt-in. The kill-and-re-drive incarnation killed a\n * completed PAN-3511 review parent on a lost verdict and re-dispatched on\n * phantom stuck flags (2026-08-05): every failure was the sweeper ACTING on\n * state that had drifted. Detection is trustworthy because it reads; action\n * was not, because it wrote. The operator (or a deliberately invoked door)\n * executes recommendations.\n * ───────────────────────────────────────────────────────────────────────────\n *\n * Every failure path in the pipeline parks the issue somewhere autonomous\n * motion stops (the orbits in src/lib/parked/resolver.ts). This patrol walks\n * the parked population every deacon cycle and emits, per row: an\n * activity-feed sentence and a sweep.recommendation domain event naming the\n * evidence and the recommended remedy. Operator-set gates are never touched;\n * gated and truly exhausted rows are re-surfaced to the operator on a TTL\n * instead of going silent. Every recommendation and escalation emits a\n * sweep.* domain event and an activity-feed sentence, so a sweep is always\n * visible (PAN-3489 wires those events into the God View).\n *\n * Guardrails:\n * - one recommendation per row per cooldown window; SWEEP_MAX_RECOMMENDATIONS_PER_ROW\n * per park episode, then escalate-only;\n * - a global recommendation budget per scan so a graveyard census can't\n * flood the feed at once;\n * - this module holds no door to any mutation — there is nothing to force.\n */\nimport { emitActivityEntrySync, type ActivityLevel } from '../activity-logger.js';\nimport { getAgentStateSync } from '../agents/agent-state.js';\nimport {\n PARKED_ORBIT_SEVERITY,\n resolveParkedPopulation,\n type ParkedOrbit,\n type ParkedRow,\n} from '../parked/resolver.js';\nimport { sessionExistsSync } from '../tmux.js';\nimport { getCloisterEventStore } from './event-store-provider.js';\nimport { readMemoizedArtifactVerdict, type SynthesisArtifactVerdict } from './synthesis-verdict.js';\nimport {\n clearSweeperRowState,\n readSweeperRowState,\n readSweeperSignature,\n writeSweeperRowState,\n writeSweeperSignature,\n type StallSweeperRowState,\n} from './stall-sweeper-state.js';\n\n// ─── Policy constants ─────────────────────────────────────────────────────────\n\n/** Max recommendations per park episode; beyond this the row is escalate-only. */\nexport const SWEEP_MAX_RECOMMENDATIONS_PER_ROW = 8;\n/** Max recommendations per patrol scan — a full graveyard surfaces over cycles, never in one burst. */\nexport const SWEEP_MAX_RECOMMENDATIONS_PER_SCAN = 4;\n/** Operator-gated / exhausted rows re-surface to the operator this often (and stay hands-off otherwise). */\nexport const SWEEP_RESURFACE_TTL_MS = 24 * 60 * 60_000;\n\nconst ORBIT_COOLDOWN_MS: Record<string, number> = {\n 'zombie-session': 15 * 60_000,\n 'merge-failed': 2 * 60 * 60_000,\n 'uat-failed': 2 * 60 * 60_000,\n conflicts: 2 * 60 * 60_000,\n 'stuck-flag': 2 * 60 * 60_000,\n 'idle-running': 30 * 60_000,\n};\n/** An idle-running recommendation repeats at most this often while nothing moves. */\nconst IDLE_RECOMMEND_GRACE_MS = 90 * 60_000;\n\n/** Every feed entry carries this so a recommendation can never be mistaken for an action. */\nconst NO_ACTION_TRAILER = 'Observability-only: no action taken.';\n\n// ─── Deps (injectable for tests) ──────────────────────────────────────────────\n\nexport interface StallSweeperDeps {\n now?: number;\n resolveRows?: () => Promise<ParkedRow[]>;\n readArtifact?: (issueId: string) => SynthesisArtifactVerdict | null;\n isAgentLive?: (agentId: string) => boolean;\n emitActivity?: (entry: { level: ActivityLevel; issueId?: string; message: string }) => void;\n emitEvent?: (type: string, payload: Record<string, unknown>) => void;\n}\n\ninterface ScanOutcome {\n recommendations: string[];\n escalations: string[];\n}\n\n// ─── Event + activity emission ────────────────────────────────────────────────\n\nfunction defaultEmitEvent(type: string, payload: Record<string, unknown>): void {\n try {\n getCloisterEventStore()?.append({\n type,\n timestamp: new Date().toISOString(),\n payload,\n } as Parameters<NonNullable<ReturnType<typeof getCloisterEventStore>>['append']>[0]);\n } catch (error) {\n console.warn(`[sweeper] failed to append ${type}:`, error instanceof Error ? error.message : String(error));\n }\n}\n\nfunction defaultEmitActivity(entry: { level: ActivityLevel; issueId?: string; message: string }): void {\n // Source is cloister — the sweeper is cloister machinery; the 🧹 message\n // prefix carries the sweeper identity in the feed.\n emitActivityEntrySync({\n source: 'cloister',\n level: entry.level,\n ...(entry.issueId ? { issueId: entry.issueId } : {}),\n message: entry.message,\n });\n}\n\n// ─── Per-row helpers ──────────────────────────────────────────────────────────\n\nfunction coolingDown(state: StallSweeperRowState | null, orbit: ParkedOrbit, now: number): boolean {\n if (!state?.lastRecommendedAt) return false;\n const cooldown = ORBIT_COOLDOWN_MS[orbit] ?? 60 * 60_000;\n return now - Date.parse(state.lastRecommendedAt) < cooldown;\n}\n\nfunction dueForResurface(state: StallSweeperRowState | null, now: number): boolean {\n if (!state?.lastEscalatedAt) return true;\n return now - Date.parse(state.lastEscalatedAt) >= SWEEP_RESURFACE_TTL_MS;\n}\n\nfunction recordRecommendation(issueId: string, orbit: ParkedOrbit, state: StallSweeperRowState | null, now: number): void {\n writeSweeperRowState(issueId, orbit, {\n recommendationCount: (state?.recommendationCount ?? 0) + 1,\n lastRecommendedAt: new Date(now).toISOString(),\n episodeStartedAt: state?.episodeStartedAt ?? new Date(now).toISOString(),\n ...(state?.lastEscalatedAt ? { lastEscalatedAt: state.lastEscalatedAt } : {}),\n });\n}\n\nfunction recordEscalation(issueId: string, orbit: ParkedOrbit, state: StallSweeperRowState | null, now: number): void {\n writeSweeperRowState(issueId, orbit, {\n recommendationCount: state?.recommendationCount ?? 0,\n ...(state?.lastRecommendedAt ? { lastRecommendedAt: state.lastRecommendedAt } : {}),\n episodeStartedAt: state?.episodeStartedAt ?? new Date(now).toISOString(),\n lastEscalatedAt: new Date(now).toISOString(),\n });\n}\n\n// ─── The patrol ───────────────────────────────────────────────────────────────\n\nexport async function runStallSweeperPatrol(deps: StallSweeperDeps = {}): Promise<string[]> {\n const now = deps.now ?? Date.now();\n const resolveRows = deps.resolveRows ?? resolveParkedPopulation;\n const readArtifact = deps.readArtifact ?? ((issueId: string) => {\n const review = getAgentStateSync(`agent-${issueId.toLowerCase()}-review`);\n return readMemoizedArtifactVerdict(issueId, {\n runId: review?.reviewRunId,\n workspacePath: review?.workspace,\n });\n });\n const isAgentLive = deps.isAgentLive ?? sessionExistsSync;\n const emitActivity = deps.emitActivity ?? defaultEmitActivity;\n const emitEvent = deps.emitEvent ?? defaultEmitEvent;\n\n const rows = await resolveRows();\n const outcome: ScanOutcome = { recommendations: [], escalations: [] };\n\n // Population signature → sweep.scan fires only on CHANGE (a scan that finds\n // the same population is not news; a scan that finds a different one is).\n const signature = rows.map((row) => `${row.issueId}:${row.orbit}`).sort().join('|');\n if (signature !== readSweeperSignature()) {\n emitEvent('sweep.scan', {\n issueCount: new Set(rows.map((row) => row.issueId)).size,\n rowCount: rows.length,\n rows: rows.map((row) => ({ issueId: row.issueId, orbit: row.orbit, parkedAt: row.parkedAt })),\n });\n writeSweeperSignature(signature);\n }\n\n // Resolved rows get their episode state forgotten so a future park starts fresh.\n // (Rows absent from this scan are resolved by definition of the resolver.)\n\n const severity = (row: ParkedRow) => PARKED_ORBIT_SEVERITY.indexOf(row.orbit);\n const work = [...rows].sort((a, b) => severity(a) - severity(b) || a.parkedAt.localeCompare(b.parkedAt));\n let recommendationBudget = SWEEP_MAX_RECOMMENDATIONS_PER_SCAN;\n\n for (const row of work) {\n const { issueId, orbit } = row;\n const state = readSweeperRowState(issueId, orbit);\n\n // Operator-owned rows: never act, re-surface on TTL.\n if (orbit === 'operator-gate' || orbit === 'deacon-ignored' || orbit === 'needs-you' || orbit === 'circuit-breaker') {\n if (dueForResurface(state, now)) {\n recordEscalation(issueId, orbit, state, now);\n emitEvent('sweep.escalated', { issueId, orbit, reason: row.parkReason });\n emitActivity({ level: 'warn', issueId, message: `🧹 sweeper re-surface: ${issueId} is still parked (${orbit}) — ${row.parkReason}. Release: ${row.unparkCondition}` });\n outcome.escalations.push(`${issueId} (${orbit}) re-surfaced to operator`);\n }\n continue;\n }\n\n if (coolingDown(state, orbit, now)) continue;\n if ((state?.recommendationCount ?? 0) >= SWEEP_MAX_RECOMMENDATIONS_PER_ROW) {\n if (dueForResurface(state, now)) {\n recordEscalation(issueId, orbit, state, now);\n emitEvent('sweep.escalated', { issueId, orbit, reason: `exhausted ${SWEEP_MAX_RECOMMENDATIONS_PER_ROW} sweep recommendations` });\n emitActivity({ level: 'warn', issueId, message: `🧹 sweeper: ${issueId} (${orbit}) exhausted ${SWEEP_MAX_RECOMMENDATIONS_PER_ROW} recommendations — needs a human. ${row.parkReason}` });\n outcome.escalations.push(`${issueId} (${orbit}) exhausted → operator`);\n }\n continue;\n }\n if (recommendationBudget <= 0) continue;\n\n const reported = reportRow(row, state, now, { readArtifact, isAgentLive, emitActivity, emitEvent }, outcome);\n if (reported) {\n recordRecommendation(issueId, orbit, state, now);\n recommendationBudget--;\n }\n }\n\n return [...outcome.recommendations, ...outcome.escalations];\n}\n\n// ─── Per-orbit recommendations ────────────────────────────────────────────────\n\ninterface ReportDeps {\n readArtifact: (issueId: string) => SynthesisArtifactVerdict | null;\n isAgentLive: (agentId: string) => boolean;\n emitActivity: (entry: { level: ActivityLevel; issueId?: string; message: string }) => void;\n emitEvent: (type: string, payload: Record<string, unknown>) => void;\n}\n\n/**\n * Build the per-orbit recommendation. This is the ONLY thing the sweeper does\n * with a row: describe the evidence, name the remedy, emit both. There is no\n * action door — see the file-header law.\n *\n * Recommendations always name the CANONICAL existing door (the same machinery\n * the deacon/operator uses — pan resume/start/tell/unstick/review restart/\n * sync-main/done/close), never a sweeper-invented path. A stall that keeps\n * recurring across an episode is a substrate bug by definition: the\n * recommendation says so, so the flywheel's substrate intake files why it\n * keeps parking instead of the symptom being swept forever.\n */\nfunction reportRow(\n row: ParkedRow,\n state: StallSweeperRowState | null,\n now: number,\n reporting: ReportDeps,\n outcome: ScanOutcome,\n): boolean {\n const { issueId, orbit } = row;\n const recurrence = state?.recommendationCount ?? 0;\n const substrateNote = recurrence >= 1\n ? ` This stall has recurred (${recurrence + 1} sweeps this episode) — that is a substrate bug: file why ${issueId} keeps parking here (flywheel substrate intake), don't just remedy the symptom.`\n : '';\n const recommend = (recommendation: string, evidence: Record<string, unknown> = {}) => {\n reporting.emitEvent('sweep.recommendation', { issueId, orbit, recommendation, recurring: recurrence >= 1, ...evidence });\n reporting.emitActivity({ level: 'warn', issueId, message: `🧹 sweeper recommends: ${recommendation} — ${row.parkReason}.${substrateNote} ${NO_ACTION_TRAILER}` });\n outcome.recommendations.push(`${issueId} (${orbit}) recommended: ${recommendation}`);\n };\n\n switch (orbit) {\n case 'zombie-session': {\n const agentId = String(row.details?.agentId ?? `agent-${issueId.toLowerCase()}`);\n const live = reporting.isAgentLive(agentId);\n recommend(`reap zombie session ${agentId} via the existing door (pan close ${issueId} owns merged/closed teardown; the reaper is the backstop)`, { agentId, sessionCurrentlyLive: live });\n return true;\n }\n\n case 'merge-failed': {\n recommend(`reset ${issueId}'s merge for re-evaluation via pan review resync ${issueId}`);\n return true;\n }\n\n case 'uat-failed': {\n // Reaching this orbit means the relay's feedback-target resurrection ladder failed.\n const notes = typeof row.details?.uatNotes === 'string' ? row.details.uatNotes : 'UAT failed — see the UAT panel for details';\n recommend(`start a work agent for ${issueId} via pan start ${issueId} — the UAT-failure relay found no live target for its feedback`, { uatNotes: notes.slice(0, 400) });\n return true;\n }\n\n case 'stuck-flag': {\n const reason = typeof row.details?.stuckReason === 'string' ? row.details.stuckReason : '';\n\n // PAN-3511: evidence from the active review run prevents a recommendation\n // from re-driving work over a review that has already finished. The\n // sweeper remains observability-only and cannot promote that evidence into\n // a terminal review status.\n const artifact = reporting.readArtifact(issueId);\n if (artifact?.verdict === 'passed') {\n recommend(\n `preserve ${issueId}'s passed review evidence from run ${artifact.runId} and await pan admin specialists done review; do not re-dispatch or resume rework`,\n { artifactVerdict: artifact.verdict, artifactRunId: artifact.runId, artifactHead: artifact.headSha },\n );\n return true;\n }\n if (reason === 'review_infrastructure_failure') {\n recommend(`clear ${issueId}'s infra-failure stuck flag and re-dispatch the review via pan unstick ${issueId} && pan review restart ${issueId}`);\n return true;\n }\n if (reason === 'feedback_delivery_needs_you' || reason === 'verification_stuck') {\n recommend(\n artifact?.notes\n ? `resume ${issueId} rework via pan resume agent-${issueId.toLowerCase()} using the blocker from review run ${artifact.runId}`\n : `resume ${issueId} rework from the pending feedback via pan resume agent-${issueId.toLowerCase()} (feedback is in .pan/feedback)`,\n artifact?.notes ? { artifactVerdict: artifact.verdict, artifactRunId: artifact.runId, reviewNotes: artifact.notes.slice(0, 400) } : {},\n );\n return true;\n }\n // Unknown / dead-end stuck flavors are operator-owned — re-surface on TTL.\n if (dueForResurface(state, now)) {\n recordEscalation(issueId, orbit, state, now);\n reporting.emitEvent('sweep.escalated', { issueId, orbit, reason: row.parkReason });\n reporting.emitActivity({ level: 'warn', issueId, message: `🧹 sweeper re-surface: ${issueId} remains stuck (${reason || 'unknown'}) — ${row.parkReason}` });\n outcome.escalations.push(`${issueId} (stuck:${reason || 'unknown'}) re-surfaced`);\n }\n return false;\n }\n\n case 'idle-running': {\n const agentId = String(row.details?.agentId ?? `agent-${issueId.toLowerCase()}`);\n const lastActivity = typeof row.details?.idleMinutes === 'number' ? row.details.idleMinutes : 0;\n if (state?.lastRecommendedAt && now - Date.parse(state.lastRecommendedAt) < IDLE_RECOMMEND_GRACE_MS) return false;\n const previouslyRecommended = !!state?.lastRecommendedAt;\n recommend(\n previouslyRecommended\n ? `stop or resume ${agentId} via pan kill ${agentId} / pan resume ${agentId} — still idle ${Math.round(lastActivity / 60)}h after a prior recommendation`\n : `nudge ${agentId} via pan tell ${agentId} — idle ${Math.round(lastActivity / 60)}h with no pipeline stage owning its next move`,\n { agentId, idleMinutes: lastActivity, live: reporting.isAgentLive(agentId) },\n );\n return true;\n }\n\n default:\n return false;\n }\n}\n\n/** Forget episode state for rows that left the population (called by tests + future reconcilers). */\nexport function forgetResolvedSweeperRows(currentRows: readonly ParkedRow[]): void {\n const live = new Set(currentRows.map((row) => `${row.issueId}:${row.orbit}`));\n for (const row of currentRows) {\n if (!live.has(`${row.issueId}:${row.orbit}`)) clearSweeperRowState(row.issueId, row.orbit);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stall-sweeper-D3fZvg7O.js","names":[],"sources":["../../src/lib/cloister/stall-sweeper-state.ts","../../src/lib/cloister/stall-sweeper.ts"],"sourcesContent":["/**\n * Stall-sweeper per-row reporting state (PAN-3485 phase 2).\n *\n * One small JSON file per (issueId, orbit) under ~/.overdeck/stall-sweeper/.\n * Tracks a parked episode's recommendation cooldown, recommendation count, and\n * operator re-surface TTL. Runtime-plane residue — deleting the directory only\n * resets reporting cadence and never loses pipeline state.\n */\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { getOverdeckHome } from '../paths.js';\n\nexport interface StallSweeperRowState {\n /** Total recommendations emitted for the current park episode. */\n recommendationCount: number;\n /** ISO timestamp of the last recommendation (drives cooldowns). */\n lastRecommendedAt?: string;\n /** ISO timestamp when the sweeper first saw this park episode. */\n episodeStartedAt: string;\n /** ISO timestamp of the last operator re-surface (drives the escalation TTL). */\n lastEscalatedAt?: string;\n}\n\nfunction sweeperDir(): string {\n return join(getOverdeckHome(), 'stall-sweeper');\n}\n\nfunction statePath(issueId: string, orbit: string): string {\n return join(sweeperDir(), `${issueId.toUpperCase()}--${orbit}.json`);\n}\n\nexport function readSweeperRowState(issueId: string, orbit: string): StallSweeperRowState | null {\n const filePath = statePath(issueId, orbit);\n if (!existsSync(filePath)) return null;\n try {\n return JSON.parse(readFileSync(filePath, 'utf-8')) as StallSweeperRowState;\n } catch {\n return null;\n }\n}\n\nexport function writeSweeperRowState(issueId: string, orbit: string, state: StallSweeperRowState): void {\n mkdirSync(sweeperDir(), { recursive: true });\n writeFileSync(statePath(issueId, orbit), JSON.stringify(state, null, 2));\n}\n\n/** Episode over (row resolved or orbit changed) — forget so a future park starts fresh. */\nexport function clearSweeperRowState(issueId: string, orbit: string): void {\n rmSync(statePath(issueId, orbit), { force: true });\n}\n\n/** Population-signature persistence for change-only sweep.scan emission. */\nexport function readSweeperSignature(): string | null {\n const filePath = join(sweeperDir(), 'population-signature');\n if (!existsSync(filePath)) return null;\n try { return readFileSync(filePath, 'utf-8'); } catch { return null; }\n}\n\nexport function writeSweeperSignature(signature: string): void {\n mkdirSync(sweeperDir(), { recursive: true });\n writeFileSync(join(sweeperDir(), 'population-signature'), signature);\n}\n","/**\n * The Stall Sweeper (PAN-3485 phase 2) — the pipeline's stall DETECTOR.\n *\n * ─── OBSERVABILITY ONLY — OPERATOR DIRECTIVE (2026-08-05) ───────────────────\n * This module detects parked/stalled work and reports it with a recommended\n * action. It must NEVER act. Do not re-add spawn/stop/kill/message/dispatch/\n * clear/reset capability here — not behind a flag, not as \"just this one safe\n * case\", not as an opt-in. The kill-and-re-drive incarnation killed a\n * completed PAN-3511 review parent on a lost verdict and re-dispatched on\n * phantom stuck flags (2026-08-05): every failure was the sweeper ACTING on\n * state that had drifted. Detection is trustworthy because it reads; action\n * was not, because it wrote. The operator (or a deliberately invoked door)\n * executes recommendations.\n * ───────────────────────────────────────────────────────────────────────────\n *\n * Every failure path in the pipeline parks the issue somewhere autonomous\n * motion stops (the orbits in src/lib/parked/resolver.ts). This patrol walks\n * the parked population every deacon cycle and emits, per row: an\n * activity-feed sentence and a sweep.recommendation domain event naming the\n * evidence and the recommended remedy. Operator-set gates are never touched;\n * gated and truly exhausted rows are re-surfaced to the operator on a TTL\n * instead of going silent. Every recommendation and escalation emits a\n * sweep.* domain event and an activity-feed sentence, so a sweep is always\n * visible (PAN-3489 wires those events into the God View).\n *\n * Guardrails:\n * - one recommendation per row per cooldown window; SWEEP_MAX_RECOMMENDATIONS_PER_ROW\n * per park episode, then escalate-only;\n * - a global recommendation budget per scan so a graveyard census can't\n * flood the feed at once;\n * - this module holds no door to any mutation — there is nothing to force.\n */\nimport { emitActivityEntrySync, type ActivityLevel } from '../activity-logger.js';\nimport { getAgentStateSync } from '../agents/agent-state.js';\nimport {\n PARKED_ORBIT_SEVERITY,\n resolveParkedPopulation,\n type ParkedOrbit,\n type ParkedRow,\n} from '../parked/resolver.js';\nimport { sessionExistsSync } from '../tmux.js';\nimport { getCloisterEventStore } from './event-store-provider.js';\nimport { readMemoizedArtifactVerdict, type SynthesisArtifactVerdict } from './synthesis-verdict.js';\nimport {\n clearSweeperRowState,\n readSweeperRowState,\n readSweeperSignature,\n writeSweeperRowState,\n writeSweeperSignature,\n type StallSweeperRowState,\n} from './stall-sweeper-state.js';\n\n// ─── Policy constants ─────────────────────────────────────────────────────────\n\n/** Max recommendations per park episode; beyond this the row is escalate-only. */\nexport const SWEEP_MAX_RECOMMENDATIONS_PER_ROW = 8;\n/** Max recommendations per patrol scan — a full graveyard surfaces over cycles, never in one burst. */\nexport const SWEEP_MAX_RECOMMENDATIONS_PER_SCAN = 4;\n/** Operator-gated / exhausted rows re-surface to the operator this often (and stay hands-off otherwise). */\nexport const SWEEP_RESURFACE_TTL_MS = 24 * 60 * 60_000;\n\nconst ORBIT_COOLDOWN_MS: Record<string, number> = {\n 'zombie-session': 15 * 60_000,\n 'merge-failed': 2 * 60 * 60_000,\n 'uat-failed': 2 * 60 * 60_000,\n conflicts: 2 * 60 * 60_000,\n 'stuck-flag': 2 * 60 * 60_000,\n 'idle-running': 30 * 60_000,\n};\n/** An idle-running recommendation repeats at most this often while nothing moves. */\nconst IDLE_RECOMMEND_GRACE_MS = 90 * 60_000;\n\n/** Every feed entry carries this so a recommendation can never be mistaken for an action. */\nconst NO_ACTION_TRAILER = 'Observability-only: no action taken.';\n\n// ─── Deps (injectable for tests) ──────────────────────────────────────────────\n\nexport interface StallSweeperDeps {\n now?: number;\n resolveRows?: () => Promise<ParkedRow[]>;\n readArtifact?: (issueId: string) => SynthesisArtifactVerdict | null;\n isAgentLive?: (agentId: string) => boolean;\n emitActivity?: (entry: { level: ActivityLevel; issueId?: string; message: string }) => void;\n emitEvent?: (type: string, payload: Record<string, unknown>) => void;\n}\n\ninterface ScanOutcome {\n recommendations: string[];\n escalations: string[];\n}\n\n// ─── Event + activity emission ────────────────────────────────────────────────\n\nfunction defaultEmitEvent(type: string, payload: Record<string, unknown>): void {\n try {\n getCloisterEventStore()?.append({\n type,\n timestamp: new Date().toISOString(),\n payload,\n } as Parameters<NonNullable<ReturnType<typeof getCloisterEventStore>>['append']>[0]);\n } catch (error) {\n console.warn(`[sweeper] failed to append ${type}:`, error instanceof Error ? error.message : String(error));\n }\n}\n\nfunction defaultEmitActivity(entry: { level: ActivityLevel; issueId?: string; message: string }): void {\n // Source is cloister — the sweeper is cloister machinery; the 🧹 message\n // prefix carries the sweeper identity in the feed.\n emitActivityEntrySync({\n source: 'cloister',\n level: entry.level,\n ...(entry.issueId ? { issueId: entry.issueId } : {}),\n message: entry.message,\n });\n}\n\n// ─── Per-row helpers ──────────────────────────────────────────────────────────\n\nfunction coolingDown(state: StallSweeperRowState | null, orbit: ParkedOrbit, now: number): boolean {\n if (!state?.lastRecommendedAt) return false;\n const cooldown = ORBIT_COOLDOWN_MS[orbit] ?? 60 * 60_000;\n return now - Date.parse(state.lastRecommendedAt) < cooldown;\n}\n\nfunction dueForResurface(state: StallSweeperRowState | null, now: number): boolean {\n if (!state?.lastEscalatedAt) return true;\n return now - Date.parse(state.lastEscalatedAt) >= SWEEP_RESURFACE_TTL_MS;\n}\n\nfunction recordRecommendation(issueId: string, orbit: ParkedOrbit, state: StallSweeperRowState | null, now: number): void {\n writeSweeperRowState(issueId, orbit, {\n recommendationCount: (state?.recommendationCount ?? 0) + 1,\n lastRecommendedAt: new Date(now).toISOString(),\n episodeStartedAt: state?.episodeStartedAt ?? new Date(now).toISOString(),\n ...(state?.lastEscalatedAt ? { lastEscalatedAt: state.lastEscalatedAt } : {}),\n });\n}\n\nfunction recordEscalation(issueId: string, orbit: ParkedOrbit, state: StallSweeperRowState | null, now: number): void {\n writeSweeperRowState(issueId, orbit, {\n recommendationCount: state?.recommendationCount ?? 0,\n ...(state?.lastRecommendedAt ? { lastRecommendedAt: state.lastRecommendedAt } : {}),\n episodeStartedAt: state?.episodeStartedAt ?? new Date(now).toISOString(),\n lastEscalatedAt: new Date(now).toISOString(),\n });\n}\n\n// ─── The patrol ───────────────────────────────────────────────────────────────\n\nexport async function runStallSweeperPatrol(deps: StallSweeperDeps = {}): Promise<string[]> {\n const now = deps.now ?? Date.now();\n const resolveRows = deps.resolveRows ?? resolveParkedPopulation;\n const readArtifact = deps.readArtifact ?? ((issueId: string) => {\n const review = getAgentStateSync(`agent-${issueId.toLowerCase()}-review`);\n return readMemoizedArtifactVerdict(issueId, {\n runId: review?.reviewRunId,\n workspacePath: review?.workspace,\n });\n });\n const isAgentLive = deps.isAgentLive ?? sessionExistsSync;\n const emitActivity = deps.emitActivity ?? defaultEmitActivity;\n const emitEvent = deps.emitEvent ?? defaultEmitEvent;\n\n const rows = await resolveRows();\n const outcome: ScanOutcome = { recommendations: [], escalations: [] };\n\n // Population signature → sweep.scan fires only on CHANGE (a scan that finds\n // the same population is not news; a scan that finds a different one is).\n const signature = rows.map((row) => `${row.issueId}:${row.orbit}`).sort().join('|');\n if (signature !== readSweeperSignature()) {\n emitEvent('sweep.scan', {\n issueCount: new Set(rows.map((row) => row.issueId)).size,\n rowCount: rows.length,\n rows: rows.map((row) => ({ issueId: row.issueId, orbit: row.orbit, parkedAt: row.parkedAt })),\n });\n writeSweeperSignature(signature);\n }\n\n // Resolved rows get their episode state forgotten so a future park starts fresh.\n // (Rows absent from this scan are resolved by definition of the resolver.)\n\n const severity = (row: ParkedRow) => PARKED_ORBIT_SEVERITY.indexOf(row.orbit);\n const work = [...rows].sort((a, b) => severity(a) - severity(b) || a.parkedAt.localeCompare(b.parkedAt));\n let recommendationBudget = SWEEP_MAX_RECOMMENDATIONS_PER_SCAN;\n\n for (const row of work) {\n const { issueId, orbit } = row;\n const state = readSweeperRowState(issueId, orbit);\n\n // Operator-owned rows: never act, re-surface on TTL.\n if (orbit === 'operator-gate' || orbit === 'deacon-ignored' || orbit === 'needs-you' || orbit === 'circuit-breaker') {\n if (dueForResurface(state, now)) {\n recordEscalation(issueId, orbit, state, now);\n emitEvent('sweep.escalated', { issueId, orbit, reason: row.parkReason });\n emitActivity({ level: 'warn', issueId, message: `🧹 sweeper re-surface: ${issueId} is still parked (${orbit}) — ${row.parkReason}. Release: ${row.unparkCondition}` });\n outcome.escalations.push(`${issueId} (${orbit}) re-surfaced to operator`);\n }\n continue;\n }\n\n if (coolingDown(state, orbit, now)) continue;\n if ((state?.recommendationCount ?? 0) >= SWEEP_MAX_RECOMMENDATIONS_PER_ROW) {\n if (dueForResurface(state, now)) {\n recordEscalation(issueId, orbit, state, now);\n emitEvent('sweep.escalated', { issueId, orbit, reason: `exhausted ${SWEEP_MAX_RECOMMENDATIONS_PER_ROW} sweep recommendations` });\n emitActivity({ level: 'warn', issueId, message: `🧹 sweeper: ${issueId} (${orbit}) exhausted ${SWEEP_MAX_RECOMMENDATIONS_PER_ROW} recommendations — needs a human. ${row.parkReason}` });\n outcome.escalations.push(`${issueId} (${orbit}) exhausted → operator`);\n }\n continue;\n }\n if (recommendationBudget <= 0) continue;\n\n const reported = reportRow(row, state, now, { readArtifact, isAgentLive, emitActivity, emitEvent }, outcome);\n if (reported) {\n recordRecommendation(issueId, orbit, state, now);\n recommendationBudget--;\n }\n }\n\n return [...outcome.recommendations, ...outcome.escalations];\n}\n\n// ─── Per-orbit recommendations ────────────────────────────────────────────────\n\ninterface ReportDeps {\n readArtifact: (issueId: string) => SynthesisArtifactVerdict | null;\n isAgentLive: (agentId: string) => boolean;\n emitActivity: (entry: { level: ActivityLevel; issueId?: string; message: string }) => void;\n emitEvent: (type: string, payload: Record<string, unknown>) => void;\n}\n\n/**\n * Build the per-orbit recommendation. This is the ONLY thing the sweeper does\n * with a row: describe the evidence, name the remedy, emit both. There is no\n * action door — see the file-header law.\n *\n * Recommendations always name the CANONICAL existing door (the same machinery\n * the deacon/operator uses — pan resume/start/tell/unstick/review restart/\n * sync-main/done/close), never a sweeper-invented path. A stall that keeps\n * recurring across an episode is a substrate bug by definition: the\n * recommendation says so, so the flywheel's substrate intake files why it\n * keeps parking instead of the symptom being swept forever.\n */\nfunction reportRow(\n row: ParkedRow,\n state: StallSweeperRowState | null,\n now: number,\n reporting: ReportDeps,\n outcome: ScanOutcome,\n): boolean {\n const { issueId, orbit } = row;\n const recurrence = state?.recommendationCount ?? 0;\n const substrateNote = recurrence >= 1\n ? ` This stall has recurred (${recurrence + 1} sweeps this episode) — that is a substrate bug: file why ${issueId} keeps parking here (flywheel substrate intake), don't just remedy the symptom.`\n : '';\n const recommend = (recommendation: string, evidence: Record<string, unknown> = {}) => {\n reporting.emitEvent('sweep.recommendation', { issueId, orbit, recommendation, recurring: recurrence >= 1, ...evidence });\n reporting.emitActivity({ level: 'warn', issueId, message: `🧹 sweeper recommends: ${recommendation} — ${row.parkReason}.${substrateNote} ${NO_ACTION_TRAILER}` });\n outcome.recommendations.push(`${issueId} (${orbit}) recommended: ${recommendation}`);\n };\n\n switch (orbit) {\n case 'zombie-session': {\n const agentId = String(row.details?.agentId ?? `agent-${issueId.toLowerCase()}`);\n const live = reporting.isAgentLive(agentId);\n recommend(`reap zombie session ${agentId} via the existing door (pan close ${issueId} owns merged/closed teardown; the reaper is the backstop)`, { agentId, sessionCurrentlyLive: live });\n return true;\n }\n\n case 'merge-failed': {\n recommend(`reset ${issueId}'s merge for re-evaluation via pan review resync ${issueId}`);\n return true;\n }\n\n case 'uat-failed': {\n // Reaching this orbit means the relay's feedback-target resurrection ladder failed.\n const notes = typeof row.details?.uatNotes === 'string' ? row.details.uatNotes : 'UAT failed — see the UAT panel for details';\n recommend(`start a work agent for ${issueId} via pan start ${issueId} — the UAT-failure relay found no live target for its feedback`, { uatNotes: notes.slice(0, 400) });\n return true;\n }\n\n case 'stuck-flag': {\n const reason = typeof row.details?.stuckReason === 'string' ? row.details.stuckReason : '';\n\n // PAN-3511: evidence from the active review run prevents a recommendation\n // from re-driving work over a review that has already finished. The\n // sweeper remains observability-only and cannot promote that evidence into\n // a terminal review status.\n const artifact = reporting.readArtifact(issueId);\n if (artifact?.verdict === 'passed') {\n recommend(\n `preserve ${issueId}'s passed review evidence from run ${artifact.runId} and await pan admin specialists done review; do not re-dispatch or resume rework`,\n { artifactVerdict: artifact.verdict, artifactRunId: artifact.runId, artifactHead: artifact.headSha },\n );\n return true;\n }\n if (reason === 'review_infrastructure_failure') {\n recommend(`clear ${issueId}'s infra-failure stuck flag and re-dispatch the review via pan unstick ${issueId} && pan review restart ${issueId}`);\n return true;\n }\n if (reason === 'feedback_delivery_needs_you' || reason === 'verification_stuck') {\n recommend(\n artifact?.notes\n ? `resume ${issueId} rework via pan resume agent-${issueId.toLowerCase()} using the blocker from review run ${artifact.runId}`\n : `resume ${issueId} rework from the pending feedback via pan resume agent-${issueId.toLowerCase()} (feedback is in .pan/feedback)`,\n artifact?.notes ? { artifactVerdict: artifact.verdict, artifactRunId: artifact.runId, reviewNotes: artifact.notes.slice(0, 400) } : {},\n );\n return true;\n }\n // Unknown / dead-end stuck flavors are operator-owned — re-surface on TTL.\n if (dueForResurface(state, now)) {\n recordEscalation(issueId, orbit, state, now);\n reporting.emitEvent('sweep.escalated', { issueId, orbit, reason: row.parkReason });\n reporting.emitActivity({ level: 'warn', issueId, message: `🧹 sweeper re-surface: ${issueId} remains stuck (${reason || 'unknown'}) — ${row.parkReason}` });\n outcome.escalations.push(`${issueId} (stuck:${reason || 'unknown'}) re-surfaced`);\n }\n return false;\n }\n\n case 'idle-running': {\n const agentId = String(row.details?.agentId ?? `agent-${issueId.toLowerCase()}`);\n const lastActivity = typeof row.details?.idleMinutes === 'number' ? row.details.idleMinutes : 0;\n if (state?.lastRecommendedAt && now - Date.parse(state.lastRecommendedAt) < IDLE_RECOMMEND_GRACE_MS) return false;\n const previouslyRecommended = !!state?.lastRecommendedAt;\n recommend(\n previouslyRecommended\n ? `stop or resume ${agentId} via pan kill ${agentId} / pan resume ${agentId} — still idle ${Math.round(lastActivity / 60)}h after a prior recommendation`\n : `nudge ${agentId} via pan tell ${agentId} — idle ${Math.round(lastActivity / 60)}h with no pipeline stage owning its next move`,\n { agentId, idleMinutes: lastActivity, live: reporting.isAgentLive(agentId) },\n );\n return true;\n }\n\n default:\n return false;\n }\n}\n\n/** Forget episode state for rows that left the population (called by tests + future reconcilers). */\nexport function forgetResolvedSweeperRows(currentRows: readonly ParkedRow[]): void {\n const live = new Set(currentRows.map((row) => `${row.issueId}:${row.orbit}`));\n for (const row of currentRows) {\n if (!live.has(`${row.issueId}:${row.orbit}`)) clearSweeperRowState(row.issueId, row.orbit);\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { Bt as init_state_migration_lock, En as resolveInfraRepo, Mt as init_state_plane, Ri as init_paths, Tt as init_auto_commit, fn as init_projects, kt as STATE_BRANCH_PATHS, on as getProjectSync, wt as flushAutoCommits, zt as acquireStateMigrationLock } from "./config-yaml-Dce9VUd6.js";
2
2
  import { c as init_agents } from "./agents-CkngXhQr.js";
3
- import { An as init_session_history, G as init_runtime_state, Hn as init_state_dir_removal, _ as init_agent_state, ba as init_agents$1, in as init_event_reads, tn as init_activity } from "./agent-state-Ci6PhjOE.js";
3
+ import { An as init_session_history, G as init_runtime_state, Hn as init_state_dir_removal, Sa as init_agents$1, _ as init_agent_state, in as init_event_reads, tn as init_activity } from "./agent-state-B196fj1h.js";
4
4
  import { o as init_lifecycle } from "./lifecycle-BSQIQ1Sq.js";
5
5
  import { a as init_state_home, n as STATE_BRANCH, r as clearStateMigrationCache, t as MIGRATION_COMPLETE_MARKER, u as stateWorktreePath } from "./state-home-CWu1dx9L.js";
6
6
  import { dirname, join } from "node:path";
@@ -498,4 +498,4 @@ async function migrateProjectState(projectKey, options = {}, projectOverride) {
498
498
  //#endregion
499
499
  export { migrateProjectState };
500
500
 
501
- //# sourceMappingURL=state-migrate-J3tFJnbd.js.map
501
+ //# sourceMappingURL=state-migrate-KZ2goscQ.js.map