claude-mux 0.30.13 → 0.30.14

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 (361) hide show
  1. package/CLAUDE.md +14 -3
  2. package/dist/fleet-places.d.ts +7 -1
  3. package/dist/fleet-places.d.ts.map +1 -1
  4. package/dist/fleet-places.js +14 -1
  5. package/dist/fleet-places.js.map +1 -1
  6. package/dist/server/dictation-marks.d.ts.map +1 -1
  7. package/dist/server/dictation-marks.js +23 -1
  8. package/dist/server/dictation-marks.js.map +1 -1
  9. package/dist/transcript/parser.d.ts +7 -0
  10. package/dist/transcript/parser.d.ts.map +1 -1
  11. package/dist/transcript/parser.js +19 -3
  12. package/dist/transcript/parser.js.map +1 -1
  13. package/dist/transcript/past-sessions.d.ts.map +1 -1
  14. package/dist/transcript/past-sessions.js +4 -1
  15. package/dist/transcript/past-sessions.js.map +1 -1
  16. package/dist/utils/version.d.ts +1 -1
  17. package/dist/utils/version.js +1 -1
  18. package/dist/web/client/_app/immutable/chunks/{sULMfRFG.js → B4FaHfAQ.js} +2 -2
  19. package/dist/web/client/_app/immutable/chunks/{sULMfRFG.js.map → B4FaHfAQ.js.map} +1 -1
  20. package/dist/web/client/_app/immutable/chunks/{W5_HMZ5E.js → B5Hkb2u5.js} +2 -2
  21. package/dist/web/client/_app/immutable/chunks/{W5_HMZ5E.js.map → B5Hkb2u5.js.map} +1 -1
  22. package/{web/.svelte-kit/output/client/_app/immutable/chunks/BQzv8cqz.js → dist/web/client/_app/immutable/chunks/BOpEbBRb.js} +2 -2
  23. package/{web/.svelte-kit/output/client/_app/immutable/chunks/BQzv8cqz.js.map → dist/web/client/_app/immutable/chunks/BOpEbBRb.js.map} +1 -1
  24. package/dist/web/client/_app/immutable/chunks/{Q5yhzux8.js → BeYjnogg.js} +2 -2
  25. package/{web/.svelte-kit/output/client/_app/immutable/chunks/Q5yhzux8.js.map → dist/web/client/_app/immutable/chunks/BeYjnogg.js.map} +1 -1
  26. package/dist/web/client/_app/immutable/chunks/{VceCNUd7.js → C00rm3mY.js} +2 -2
  27. package/{web/.svelte-kit/output/client/_app/immutable/chunks/VceCNUd7.js.map → dist/web/client/_app/immutable/chunks/C00rm3mY.js.map} +1 -1
  28. package/dist/web/client/_app/immutable/chunks/{CtjkAirr.js → CKg7gbPm.js} +9 -9
  29. package/dist/web/client/_app/immutable/chunks/CKg7gbPm.js.map +1 -0
  30. package/dist/web/client/_app/immutable/chunks/CPEphjdk.js +4 -0
  31. package/dist/web/client/_app/immutable/chunks/CPEphjdk.js.map +1 -0
  32. package/dist/web/client/_app/immutable/chunks/{BQQ3-rqs.js → CPlFo88J.js} +2 -2
  33. package/dist/web/client/_app/immutable/chunks/{BQQ3-rqs.js.map → CPlFo88J.js.map} +1 -1
  34. package/dist/web/client/_app/immutable/chunks/{BvX4FYxp.js → CtnjySwS.js} +2 -2
  35. package/dist/web/client/_app/immutable/chunks/{BvX4FYxp.js.map → CtnjySwS.js.map} +1 -1
  36. package/dist/web/client/_app/immutable/chunks/{XgSsj0QY.js → D3EW0jRH.js} +2 -2
  37. package/{web/.svelte-kit/output/client/_app/immutable/chunks/XgSsj0QY.js.map → dist/web/client/_app/immutable/chunks/D3EW0jRH.js.map} +1 -1
  38. package/dist/web/client/_app/immutable/chunks/{CtlIfY1C.js → D3umr-Qr.js} +2 -2
  39. package/{web/.svelte-kit/output/client/_app/immutable/chunks/CtlIfY1C.js.map → dist/web/client/_app/immutable/chunks/D3umr-Qr.js.map} +1 -1
  40. package/dist/web/client/_app/immutable/chunks/{CM0CI9GL.js → D8-_Y-M6.js} +3 -3
  41. package/dist/web/client/_app/immutable/chunks/{CM0CI9GL.js.map → D8-_Y-M6.js.map} +1 -1
  42. package/dist/web/client/_app/immutable/chunks/{cvsGFvDx.js → DDvK7Qo9.js} +2 -2
  43. package/{web/.svelte-kit/output/client/_app/immutable/chunks/cvsGFvDx.js.map → dist/web/client/_app/immutable/chunks/DDvK7Qo9.js.map} +1 -1
  44. package/dist/web/client/_app/immutable/chunks/{CiU8KBun.js → _VFi-7U7.js} +2 -2
  45. package/dist/web/client/_app/immutable/chunks/{CiU8KBun.js.map → _VFi-7U7.js.map} +1 -1
  46. package/dist/web/client/_app/immutable/chunks/{DlENCZqZ.js → mQ6hkLuy.js} +2 -2
  47. package/dist/web/client/_app/immutable/chunks/{DlENCZqZ.js.map → mQ6hkLuy.js.map} +1 -1
  48. package/dist/web/client/_app/immutable/entry/{app.BwTbN8wB.js → app.CTFza_OS.js} +3 -3
  49. package/{web/.svelte-kit/output/client/_app/immutable/entry/app.BwTbN8wB.js.map → dist/web/client/_app/immutable/entry/app.CTFza_OS.js.map} +1 -1
  50. package/dist/web/client/_app/immutable/entry/start.B-zlkyrt.js +2 -0
  51. package/dist/web/client/_app/immutable/entry/start.B-zlkyrt.js.map +1 -0
  52. package/{web/.svelte-kit/output/client/_app/immutable/nodes/0.BnHV-ElR.js → dist/web/client/_app/immutable/nodes/0.Cztuq8Ih.js} +2 -2
  53. package/dist/web/client/_app/immutable/nodes/{0.BnHV-ElR.js.map → 0.Cztuq8Ih.js.map} +1 -1
  54. package/dist/web/client/_app/immutable/nodes/{1.Dc3ryOCf.js → 1.BOOL0V7K.js} +2 -2
  55. package/dist/web/client/_app/immutable/nodes/{1.Dc3ryOCf.js.map → 1.BOOL0V7K.js.map} +1 -1
  56. package/dist/web/client/_app/immutable/nodes/{2.DUrpAceY.js → 2.CArUpZNe.js} +2 -2
  57. package/dist/web/client/_app/immutable/nodes/{2.DUrpAceY.js.map → 2.CArUpZNe.js.map} +1 -1
  58. package/dist/web/client/_app/immutable/nodes/{3.BaCofjAn.js → 3.DSvIWalA.js} +2 -2
  59. package/dist/web/client/_app/immutable/nodes/{3.BaCofjAn.js.map → 3.DSvIWalA.js.map} +1 -1
  60. package/dist/web/client/_app/immutable/nodes/{4.v1wYkrvS.js → 4.H6RMG_cy.js} +2 -2
  61. package/dist/web/client/_app/immutable/nodes/{4.v1wYkrvS.js.map → 4.H6RMG_cy.js.map} +1 -1
  62. package/dist/web/client/_app/immutable/nodes/{5.Bn4A77jK.js → 5.BHSt0Suo.js} +2 -2
  63. package/dist/web/client/_app/immutable/nodes/{5.Bn4A77jK.js.map → 5.BHSt0Suo.js.map} +1 -1
  64. package/dist/web/client/_app/immutable/nodes/{6.C2hEoITj.js → 6.CzLoA0w7.js} +2 -2
  65. package/dist/web/client/_app/immutable/nodes/{6.C2hEoITj.js.map → 6.CzLoA0w7.js.map} +1 -1
  66. package/dist/web/client/_app/immutable/nodes/{7.BcHOjWdp.js → 7.fqq2vVa9.js} +2 -2
  67. package/dist/web/client/_app/immutable/nodes/{7.BcHOjWdp.js.map → 7.fqq2vVa9.js.map} +1 -1
  68. package/dist/web/client/_app/immutable/nodes/{8.D7NtqIYy.js → 8.DNFFjTla.js} +2 -2
  69. package/dist/web/client/_app/immutable/nodes/{8.D7NtqIYy.js.map → 8.DNFFjTla.js.map} +1 -1
  70. package/dist/web/client/_app/immutable/nodes/{9.D-211z9-.js → 9.0kMPrp2-.js} +2 -2
  71. package/dist/web/client/_app/immutable/nodes/{9.D-211z9-.js.map → 9.0kMPrp2-.js.map} +1 -1
  72. package/dist/web/client/_app/version.json +1 -1
  73. package/dist/web/server/chunks/{0-CvglVGCY.js → 0-B_KxXX-N.js} +14 -14
  74. package/dist/web/server/chunks/{0-CvglVGCY.js.map → 0-B_KxXX-N.js.map} +1 -1
  75. package/dist/web/server/chunks/{1-D-gJDFrG.js → 1-BmEKuUDK.js} +4 -4
  76. package/dist/web/server/chunks/{1-D-gJDFrG.js.map → 1-BmEKuUDK.js.map} +1 -1
  77. package/dist/web/server/chunks/{2-BCmEU5Qq.js → 2-BJ3dzws2.js} +13 -13
  78. package/dist/web/server/chunks/{2-BCmEU5Qq.js.map → 2-BJ3dzws2.js.map} +1 -1
  79. package/dist/web/server/chunks/{3-CHn5Kw-T.js → 3-CLYKGzT3.js} +10 -10
  80. package/dist/web/server/chunks/{3-CHn5Kw-T.js.map → 3-CLYKGzT3.js.map} +1 -1
  81. package/dist/web/server/chunks/{4-Cxg7VcZ7.js → 4-CsoWHVgX.js} +12 -12
  82. package/dist/web/server/chunks/{4-Cxg7VcZ7.js.map → 4-CsoWHVgX.js.map} +1 -1
  83. package/dist/web/server/chunks/{5-DSgvnHF_.js → 5-oPM6ncgT.js} +9 -9
  84. package/dist/web/server/chunks/{5-DSgvnHF_.js.map → 5-oPM6ncgT.js.map} +1 -1
  85. package/dist/web/server/chunks/{6-B0rymWRR.js → 6-gvgju2ZN.js} +5 -5
  86. package/dist/web/server/chunks/{6-B0rymWRR.js.map → 6-gvgju2ZN.js.map} +1 -1
  87. package/dist/web/server/chunks/{7-CNomBJux.js → 7-D8hylKrz.js} +10 -10
  88. package/dist/web/server/chunks/{7-CNomBJux.js.map → 7-D8hylKrz.js.map} +1 -1
  89. package/dist/web/server/chunks/{8-TYbX-1bO.js → 8-Br4hTtby.js} +12 -12
  90. package/dist/web/server/chunks/{8-TYbX-1bO.js.map → 8-Br4hTtby.js.map} +1 -1
  91. package/dist/web/server/chunks/{9-XGQQvTLc.js → 9-9uvmfsEs.js} +9 -9
  92. package/dist/web/server/chunks/{9-XGQQvTLc.js.map → 9-9uvmfsEs.js.map} +1 -1
  93. package/dist/web/server/chunks/{_layout.svelte-yrPX5VFm.js → _layout.svelte-DamAAFpK.js} +58 -37
  94. package/dist/web/server/chunks/_layout.svelte-DamAAFpK.js.map +1 -0
  95. package/dist/web/server/chunks/{_page.svelte-C_kFA0Lq.js → _page.svelte-DFE4jmT9.js} +4 -4
  96. package/dist/web/server/chunks/{_page.svelte-C_kFA0Lq.js.map → _page.svelte-DFE4jmT9.js.map} +1 -1
  97. package/dist/web/server/chunks/{_page.svelte-DvxpwsgS.js → _page.svelte-DHPGbCS1.js} +6 -6
  98. package/dist/web/server/chunks/{_page.svelte-DvxpwsgS.js.map → _page.svelte-DHPGbCS1.js.map} +1 -1
  99. package/dist/web/server/chunks/{_server.ts-Dz01Gvhk.js → _server.ts-0RT4n9LX.js} +2 -2
  100. package/dist/web/server/chunks/{_server.ts-Dz01Gvhk.js.map → _server.ts-0RT4n9LX.js.map} +1 -1
  101. package/dist/web/server/chunks/{_server.ts-D2Tvcncu.js → _server.ts-3kbpguQ1.js} +3 -3
  102. package/dist/web/server/chunks/{_server.ts-D2Tvcncu.js.map → _server.ts-3kbpguQ1.js.map} +1 -1
  103. package/dist/web/server/chunks/{_server.ts-TN_Ka6cR.js → _server.ts-4m17ESHV.js} +2 -2
  104. package/dist/web/server/chunks/{_server.ts-TN_Ka6cR.js.map → _server.ts-4m17ESHV.js.map} +1 -1
  105. package/dist/web/server/chunks/{_server.ts-Bj1Eewby.js → _server.ts-8Bs2jUgD.js} +2 -2
  106. package/dist/web/server/chunks/{_server.ts-Bj1Eewby.js.map → _server.ts-8Bs2jUgD.js.map} +1 -1
  107. package/dist/web/server/chunks/{_server.ts-BWVtnpJh.js → _server.ts-95cbXKdT.js} +3 -3
  108. package/dist/web/server/chunks/{_server.ts-BWVtnpJh.js.map → _server.ts-95cbXKdT.js.map} +1 -1
  109. package/dist/web/server/chunks/{_server.ts-CyOA1rOq.js → _server.ts-B56-XF-Z.js} +3 -3
  110. package/dist/web/server/chunks/{_server.ts-CyOA1rOq.js.map → _server.ts-B56-XF-Z.js.map} +1 -1
  111. package/dist/web/server/chunks/{_server.ts-1ZhqL5o8.js → _server.ts-BAC_No-1.js} +2 -2
  112. package/dist/web/server/chunks/{_server.ts-1ZhqL5o8.js.map → _server.ts-BAC_No-1.js.map} +1 -1
  113. package/dist/web/server/chunks/{_server.ts-ji7L8HUR.js → _server.ts-BCpfYsYw.js} +3 -3
  114. package/dist/web/server/chunks/{_server.ts-ji7L8HUR.js.map → _server.ts-BCpfYsYw.js.map} +1 -1
  115. package/dist/web/server/chunks/{_server.ts-9iRpDv9e.js → _server.ts-BGToJuLp.js} +2 -2
  116. package/dist/web/server/chunks/{_server.ts-9iRpDv9e.js.map → _server.ts-BGToJuLp.js.map} +1 -1
  117. package/dist/web/server/chunks/{_server.ts-DQJ-tygD.js → _server.ts-BW87LYkE.js} +2 -2
  118. package/dist/web/server/chunks/{_server.ts-DQJ-tygD.js.map → _server.ts-BW87LYkE.js.map} +1 -1
  119. package/dist/web/server/chunks/{_server.ts-VgbTPrqV.js → _server.ts-BXjUy61V.js} +2 -2
  120. package/dist/web/server/chunks/{_server.ts-VgbTPrqV.js.map → _server.ts-BXjUy61V.js.map} +1 -1
  121. package/dist/web/server/chunks/{_server.ts-FoPsfjo3.js → _server.ts-BYyaN7ik.js} +2 -2
  122. package/dist/web/server/chunks/{_server.ts-FoPsfjo3.js.map → _server.ts-BYyaN7ik.js.map} +1 -1
  123. package/dist/web/server/chunks/{_server.ts-CNBy2p2U.js → _server.ts-Bjzi1JAu.js} +2 -2
  124. package/dist/web/server/chunks/{_server.ts-CNBy2p2U.js.map → _server.ts-Bjzi1JAu.js.map} +1 -1
  125. package/dist/web/server/chunks/{_server.ts-CSK1ReRS.js → _server.ts-CBU5vhD8.js} +2 -2
  126. package/dist/web/server/chunks/{_server.ts-CSK1ReRS.js.map → _server.ts-CBU5vhD8.js.map} +1 -1
  127. package/dist/web/server/chunks/{_server.ts-Cv8SUkC5.js → _server.ts-CG-7TORV.js} +3 -3
  128. package/dist/web/server/chunks/{_server.ts-Cv8SUkC5.js.map → _server.ts-CG-7TORV.js.map} +1 -1
  129. package/dist/web/server/chunks/{_server.ts-DPctaiWC.js → _server.ts-COCrEIiW.js} +2 -2
  130. package/dist/web/server/chunks/{_server.ts-DPctaiWC.js.map → _server.ts-COCrEIiW.js.map} +1 -1
  131. package/dist/web/server/chunks/{_server.ts-C7uhdF7Y.js → _server.ts-CPCsEz_B.js} +4 -4
  132. package/dist/web/server/chunks/{_server.ts-C7uhdF7Y.js.map → _server.ts-CPCsEz_B.js.map} +1 -1
  133. package/dist/web/server/chunks/{_server.ts-DsyqZudf.js → _server.ts-CPGa_LDW.js} +2 -2
  134. package/dist/web/server/chunks/{_server.ts-DsyqZudf.js.map → _server.ts-CPGa_LDW.js.map} +1 -1
  135. package/dist/web/server/chunks/{_server.ts-B9RfKfyc.js → _server.ts-CT1m86tm.js} +2 -2
  136. package/dist/web/server/chunks/{_server.ts-B9RfKfyc.js.map → _server.ts-CT1m86tm.js.map} +1 -1
  137. package/dist/web/server/chunks/{_server.ts-CwFbAWCg.js → _server.ts-CXzAFIps.js} +2 -2
  138. package/dist/web/server/chunks/{_server.ts-CwFbAWCg.js.map → _server.ts-CXzAFIps.js.map} +1 -1
  139. package/dist/web/server/chunks/{_server.ts-DHuyjZQx.js → _server.ts-C_H2gKKu.js} +2 -2
  140. package/dist/web/server/chunks/{_server.ts-DHuyjZQx.js.map → _server.ts-C_H2gKKu.js.map} +1 -1
  141. package/dist/web/server/chunks/{_server.ts-DVgdt6Zu.js → _server.ts-CjH9MxGk.js} +3 -3
  142. package/dist/web/server/chunks/{_server.ts-DVgdt6Zu.js.map → _server.ts-CjH9MxGk.js.map} +1 -1
  143. package/dist/web/server/chunks/{_server.ts-DrFiP32b.js → _server.ts-CrQk3PtI.js} +3 -3
  144. package/dist/web/server/chunks/{_server.ts-DrFiP32b.js.map → _server.ts-CrQk3PtI.js.map} +1 -1
  145. package/dist/web/server/chunks/{_server.ts-CA8nszPb.js → _server.ts-Cy5Matw4.js} +3 -3
  146. package/dist/web/server/chunks/{_server.ts-CA8nszPb.js.map → _server.ts-Cy5Matw4.js.map} +1 -1
  147. package/dist/web/server/chunks/{_server.ts-CovkmnvY.js → _server.ts-D9PPu5p4.js} +3 -3
  148. package/dist/web/server/chunks/{_server.ts-CovkmnvY.js.map → _server.ts-D9PPu5p4.js.map} +1 -1
  149. package/dist/web/server/chunks/{_server.ts-vhti5VvF.js → _server.ts-DGvNQ4Mq.js} +3 -3
  150. package/dist/web/server/chunks/{_server.ts-vhti5VvF.js.map → _server.ts-DGvNQ4Mq.js.map} +1 -1
  151. package/dist/web/server/chunks/{_server.ts-iK8ih9Bi.js → _server.ts-DJOSnwXV.js} +2 -2
  152. package/dist/web/server/chunks/{_server.ts-iK8ih9Bi.js.map → _server.ts-DJOSnwXV.js.map} +1 -1
  153. package/dist/web/server/chunks/{_server.ts-DK-6ddNq.js → _server.ts-DPoluZc6.js} +3 -3
  154. package/dist/web/server/chunks/{_server.ts-DK-6ddNq.js.map → _server.ts-DPoluZc6.js.map} +1 -1
  155. package/dist/web/server/chunks/{_server.ts-BozsVs8t.js → _server.ts-DS6kibxL.js} +2 -2
  156. package/dist/web/server/chunks/{_server.ts-BozsVs8t.js.map → _server.ts-DS6kibxL.js.map} +1 -1
  157. package/dist/web/server/chunks/{_server.ts-DozhhYwq.js → _server.ts-DUl0ucUP.js} +3 -3
  158. package/dist/web/server/chunks/{_server.ts-DozhhYwq.js.map → _server.ts-DUl0ucUP.js.map} +1 -1
  159. package/dist/web/server/chunks/{_server.ts-78nODRDJ.js → _server.ts-DWj22oj9.js} +2 -2
  160. package/dist/web/server/chunks/{_server.ts-78nODRDJ.js.map → _server.ts-DWj22oj9.js.map} +1 -1
  161. package/dist/web/server/chunks/{_server.ts-CA1j9wWk.js → _server.ts-DZrVaelC.js} +2 -2
  162. package/dist/web/server/chunks/{_server.ts-CA1j9wWk.js.map → _server.ts-DZrVaelC.js.map} +1 -1
  163. package/dist/web/server/chunks/{_server.ts-CzcknkWq.js → _server.ts-DrG3j6Ao.js} +2 -2
  164. package/dist/web/server/chunks/{_server.ts-CzcknkWq.js.map → _server.ts-DrG3j6Ao.js.map} +1 -1
  165. package/dist/web/server/chunks/{_server.ts-AAllMdwT.js → _server.ts-Dwy38J9J.js} +2 -2
  166. package/dist/web/server/chunks/{_server.ts-AAllMdwT.js.map → _server.ts-Dwy38J9J.js.map} +1 -1
  167. package/dist/web/server/chunks/{_server.ts-CgChlIKq.js → _server.ts-DxGjRezI.js} +2 -2
  168. package/dist/web/server/chunks/{_server.ts-CgChlIKq.js.map → _server.ts-DxGjRezI.js.map} +1 -1
  169. package/dist/web/server/chunks/{_server.ts-KbQyErIR.js → _server.ts-XMUjHO61.js} +2 -2
  170. package/dist/web/server/chunks/{_server.ts-KbQyErIR.js.map → _server.ts-XMUjHO61.js.map} +1 -1
  171. package/dist/web/server/chunks/{_server.ts-DGTSZoLn.js → _server.ts-dvT2bXEB.js} +2 -2
  172. package/dist/web/server/chunks/{_server.ts-DGTSZoLn.js.map → _server.ts-dvT2bXEB.js.map} +1 -1
  173. package/dist/web/server/chunks/{_server.ts-BxscGs8D.js → _server.ts-m835uDm6.js} +2 -2
  174. package/dist/web/server/chunks/{_server.ts-BxscGs8D.js.map → _server.ts-m835uDm6.js.map} +1 -1
  175. package/dist/web/server/chunks/{checkbox-DiJTAtHi.js → checkbox-BYzlWW56.js} +2 -2
  176. package/dist/web/server/chunks/{checkbox-DiJTAtHi.js.map → checkbox-BYzlWW56.js.map} +1 -1
  177. package/dist/web/server/chunks/{create-id-DpoSQWrl.js → create-id-s6T_m-1j.js} +3 -3
  178. package/dist/web/server/chunks/{create-id-DpoSQWrl.js.map → create-id-s6T_m-1j.js.map} +1 -1
  179. package/dist/web/server/chunks/{discover-DjR28OqK.js → discover-DykzHz0t.js} +2 -2
  180. package/dist/web/server/chunks/{discover-DjR28OqK.js.map → discover-DykzHz0t.js.map} +1 -1
  181. package/dist/web/server/chunks/{drafts.svelte-DsFAtH-s.js → drafts.svelte-c94J-DBB.js} +3 -3
  182. package/dist/web/server/chunks/{drafts.svelte-DsFAtH-s.js.map → drafts.svelte-c94J-DBB.js.map} +1 -1
  183. package/dist/web/server/chunks/{end-session-BbBa9yD0.js → end-session-Czy_WW42.js} +2 -2
  184. package/dist/web/server/chunks/{end-session-BbBa9yD0.js.map → end-session-Czy_WW42.js.map} +1 -1
  185. package/dist/web/server/chunks/{files-4i3igWea.js → files-DNH191y7.js} +2 -2
  186. package/dist/web/server/chunks/{files-4i3igWea.js.map → files-DNH191y7.js.map} +1 -1
  187. package/dist/web/server/chunks/{fleet-DZJSFWuH.js → fleet-CWHlZjSd.js} +4 -4
  188. package/dist/web/server/chunks/{fleet-DZJSFWuH.js.map → fleet-CWHlZjSd.js.map} +1 -1
  189. package/dist/web/server/chunks/{git-D9dLEKUF.js → git-CHO9UIOQ.js} +13 -5
  190. package/dist/web/server/chunks/git-CHO9UIOQ.js.map +1 -0
  191. package/dist/web/server/chunks/{git-history-De9t0yF-.js → git-history-CxyH4jQb.js} +2 -2
  192. package/dist/web/server/chunks/{git-history-De9t0yF-.js.map → git-history-CxyH4jQb.js.map} +1 -1
  193. package/dist/web/server/chunks/{hooks.server-C3Uglne7.js → hooks.server-DgHxEf6H.js} +6 -6
  194. package/dist/web/server/chunks/{hooks.server-C3Uglne7.js.map → hooks.server-DgHxEf6H.js.map} +1 -1
  195. package/dist/web/server/chunks/{message-queue-sFcJCRjz.js → message-queue-DhLshpoF.js} +13 -2
  196. package/dist/web/server/chunks/message-queue-DhLshpoF.js.map +1 -0
  197. package/dist/web/server/chunks/{project-CbPa8Mu7.js → project-CBPdIh2n.js} +2 -2
  198. package/dist/web/server/chunks/{project-CbPa8Mu7.js.map → project-CBPdIh2n.js.map} +1 -1
  199. package/dist/web/server/chunks/{project-icons-CSb1bfT9.js → project-icons-bWhaFSVy.js} +4 -4
  200. package/dist/web/server/chunks/{project-icons-CSb1bfT9.js.map → project-icons-bWhaFSVy.js.map} +1 -1
  201. package/dist/web/server/chunks/{push-BJ7P9DGd.js → push-LMlCp4Lc.js} +5 -5
  202. package/dist/web/server/chunks/{push-BJ7P9DGd.js.map → push-LMlCp4Lc.js.map} +1 -1
  203. package/dist/web/server/chunks/{remote-control-C1d9R3w4.js → remote-control-BvYkVDhx.js} +2 -2
  204. package/dist/web/server/chunks/{remote-control-C1d9R3w4.js.map → remote-control-BvYkVDhx.js.map} +1 -1
  205. package/dist/web/server/chunks/{start-session-CKbupnLS.js → start-session-HljgutDe.js} +3 -3
  206. package/dist/web/server/chunks/{start-session-CKbupnLS.js.map → start-session-HljgutDe.js.map} +1 -1
  207. package/dist/web/server/chunks/{version-BqEJmw2g.js → version-ox9yLZ0P.js} +2 -2
  208. package/dist/web/server/chunks/version-ox9yLZ0P.js.map +1 -0
  209. package/dist/web/server/chunks/{web-preview-Bi5fjydK.js → web-preview-H5eiWlBD.js} +2 -2
  210. package/dist/web/server/chunks/{web-preview-Bi5fjydK.js.map → web-preview-H5eiWlBD.js.map} +1 -1
  211. package/dist/web/server/chunks/{ws-handlers-zemeJ8Si.js → ws-handlers-k_sOue-0.js} +6 -5
  212. package/dist/web/server/chunks/ws-handlers-k_sOue-0.js.map +1 -0
  213. package/dist/web/server/chunks/{ws-managers-C5VXKb_r.js → ws-managers-D2qC6Gla.js} +2 -2
  214. package/dist/web/server/chunks/{ws-managers-C5VXKb_r.js.map → ws-managers-D2qC6Gla.js.map} +1 -1
  215. package/dist/web/server/chunks/{ws-messages-jOSwT1Yq.js → ws-messages-B81v2RXg.js} +7 -2
  216. package/dist/web/server/chunks/ws-messages-B81v2RXg.js.map +1 -0
  217. package/dist/web/server/index.js +2 -2
  218. package/dist/web/server/index.js.map +1 -1
  219. package/dist/web/server/manifest.js +53 -53
  220. package/dist/web/server/manifest.js.map +1 -1
  221. package/package.json +1 -1
  222. package/src/fleet-places.ts +17 -1
  223. package/src/server/dictation-marks.ts +24 -1
  224. package/src/transcript/parser.ts +20 -3
  225. package/src/transcript/past-sessions.ts +3 -1
  226. package/src/utils/version.ts +1 -1
  227. package/tests/fleet-places.test.ts +20 -0
  228. package/tests/server/dictation-marks.test.ts +16 -0
  229. package/tests/transcript/parser.test.ts +34 -0
  230. package/tests/transcript/past-sessions.test.ts +5 -0
  231. package/web/.svelte-kit/adapter-bun/chunks/git.js +13 -4
  232. package/web/.svelte-kit/adapter-bun/chunks/git.js.map +1 -1
  233. package/web/.svelte-kit/adapter-bun/chunks/internal2.js +1 -1
  234. package/web/.svelte-kit/adapter-bun/chunks/internal2.js.map +1 -1
  235. package/web/.svelte-kit/adapter-bun/chunks/message-queue.js +14 -1
  236. package/web/.svelte-kit/adapter-bun/chunks/message-queue.js.map +1 -1
  237. package/web/.svelte-kit/adapter-bun/chunks/version.js +1 -1
  238. package/web/.svelte-kit/adapter-bun/chunks/version.js.map +1 -1
  239. package/web/.svelte-kit/adapter-bun/chunks/ws-handlers.js +2 -1
  240. package/web/.svelte-kit/adapter-bun/chunks/ws-handlers.js.map +1 -1
  241. package/web/.svelte-kit/adapter-bun/chunks/ws-messages.js +6 -1
  242. package/web/.svelte-kit/adapter-bun/chunks/ws-messages.js.map +1 -1
  243. package/web/.svelte-kit/adapter-bun/entries/pages/_layout.svelte.js +72 -45
  244. package/web/.svelte-kit/adapter-bun/entries/pages/_layout.svelte.js.map +1 -1
  245. package/web/.svelte-kit/adapter-bun/manifest-full.js +1 -1
  246. package/web/.svelte-kit/adapter-bun/manifest.js +1 -1
  247. package/web/.svelte-kit/adapter-bun/nodes/0.js +1 -1
  248. package/web/.svelte-kit/adapter-bun/nodes/1.js +1 -1
  249. package/web/.svelte-kit/adapter-bun/nodes/2.js +1 -1
  250. package/web/.svelte-kit/adapter-bun/nodes/3.js +1 -1
  251. package/web/.svelte-kit/adapter-bun/nodes/4.js +1 -1
  252. package/web/.svelte-kit/adapter-bun/nodes/5.js +1 -1
  253. package/web/.svelte-kit/adapter-bun/nodes/6.js +1 -1
  254. package/web/.svelte-kit/adapter-bun/nodes/7.js +1 -1
  255. package/web/.svelte-kit/adapter-bun/nodes/8.js +1 -1
  256. package/web/.svelte-kit/adapter-bun/nodes/9.js +1 -1
  257. package/web/.svelte-kit/generated/server/internal.js +1 -1
  258. package/web/.svelte-kit/output/client/.vite/manifest.json +237 -237
  259. package/web/.svelte-kit/output/client/_app/immutable/chunks/{sULMfRFG.js → B4FaHfAQ.js} +2 -2
  260. package/web/.svelte-kit/output/client/_app/immutable/chunks/{sULMfRFG.js.map → B4FaHfAQ.js.map} +1 -1
  261. package/web/.svelte-kit/output/client/_app/immutable/chunks/{W5_HMZ5E.js → B5Hkb2u5.js} +2 -2
  262. package/web/.svelte-kit/output/client/_app/immutable/chunks/{W5_HMZ5E.js.map → B5Hkb2u5.js.map} +1 -1
  263. package/{dist/web/client/_app/immutable/chunks/BQzv8cqz.js → web/.svelte-kit/output/client/_app/immutable/chunks/BOpEbBRb.js} +2 -2
  264. package/{dist/web/client/_app/immutable/chunks/BQzv8cqz.js.map → web/.svelte-kit/output/client/_app/immutable/chunks/BOpEbBRb.js.map} +1 -1
  265. package/web/.svelte-kit/output/client/_app/immutable/chunks/{Q5yhzux8.js → BeYjnogg.js} +2 -2
  266. package/{dist/web/client/_app/immutable/chunks/Q5yhzux8.js.map → web/.svelte-kit/output/client/_app/immutable/chunks/BeYjnogg.js.map} +1 -1
  267. package/web/.svelte-kit/output/client/_app/immutable/chunks/{VceCNUd7.js → C00rm3mY.js} +2 -2
  268. package/{dist/web/client/_app/immutable/chunks/VceCNUd7.js.map → web/.svelte-kit/output/client/_app/immutable/chunks/C00rm3mY.js.map} +1 -1
  269. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CtjkAirr.js → CKg7gbPm.js} +9 -9
  270. package/web/.svelte-kit/output/client/_app/immutable/chunks/CKg7gbPm.js.map +1 -0
  271. package/web/.svelte-kit/output/client/_app/immutable/chunks/CPEphjdk.js +4 -0
  272. package/web/.svelte-kit/output/client/_app/immutable/chunks/CPEphjdk.js.map +1 -0
  273. package/web/.svelte-kit/output/client/_app/immutable/chunks/{BQQ3-rqs.js → CPlFo88J.js} +2 -2
  274. package/web/.svelte-kit/output/client/_app/immutable/chunks/{BQQ3-rqs.js.map → CPlFo88J.js.map} +1 -1
  275. package/web/.svelte-kit/output/client/_app/immutable/chunks/{BvX4FYxp.js → CtnjySwS.js} +2 -2
  276. package/web/.svelte-kit/output/client/_app/immutable/chunks/{BvX4FYxp.js.map → CtnjySwS.js.map} +1 -1
  277. package/web/.svelte-kit/output/client/_app/immutable/chunks/{XgSsj0QY.js → D3EW0jRH.js} +2 -2
  278. package/{dist/web/client/_app/immutable/chunks/XgSsj0QY.js.map → web/.svelte-kit/output/client/_app/immutable/chunks/D3EW0jRH.js.map} +1 -1
  279. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CtlIfY1C.js → D3umr-Qr.js} +2 -2
  280. package/{dist/web/client/_app/immutable/chunks/CtlIfY1C.js.map → web/.svelte-kit/output/client/_app/immutable/chunks/D3umr-Qr.js.map} +1 -1
  281. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CM0CI9GL.js → D8-_Y-M6.js} +3 -3
  282. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CM0CI9GL.js.map → D8-_Y-M6.js.map} +1 -1
  283. package/web/.svelte-kit/output/client/_app/immutable/chunks/{cvsGFvDx.js → DDvK7Qo9.js} +2 -2
  284. package/{dist/web/client/_app/immutable/chunks/cvsGFvDx.js.map → web/.svelte-kit/output/client/_app/immutable/chunks/DDvK7Qo9.js.map} +1 -1
  285. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CiU8KBun.js → _VFi-7U7.js} +2 -2
  286. package/web/.svelte-kit/output/client/_app/immutable/chunks/{CiU8KBun.js.map → _VFi-7U7.js.map} +1 -1
  287. package/web/.svelte-kit/output/client/_app/immutable/chunks/{DlENCZqZ.js → mQ6hkLuy.js} +2 -2
  288. package/web/.svelte-kit/output/client/_app/immutable/chunks/{DlENCZqZ.js.map → mQ6hkLuy.js.map} +1 -1
  289. package/web/.svelte-kit/output/client/_app/immutable/entry/{app.BwTbN8wB.js → app.CTFza_OS.js} +3 -3
  290. package/{dist/web/client/_app/immutable/entry/app.BwTbN8wB.js.map → web/.svelte-kit/output/client/_app/immutable/entry/app.CTFza_OS.js.map} +1 -1
  291. package/web/.svelte-kit/output/client/_app/immutable/entry/start.B-zlkyrt.js +2 -0
  292. package/web/.svelte-kit/output/client/_app/immutable/entry/start.B-zlkyrt.js.map +1 -0
  293. package/{dist/web/client/_app/immutable/nodes/0.BnHV-ElR.js → web/.svelte-kit/output/client/_app/immutable/nodes/0.Cztuq8Ih.js} +2 -2
  294. package/web/.svelte-kit/output/client/_app/immutable/nodes/{0.BnHV-ElR.js.map → 0.Cztuq8Ih.js.map} +1 -1
  295. package/web/.svelte-kit/output/client/_app/immutable/nodes/{1.Dc3ryOCf.js → 1.BOOL0V7K.js} +2 -2
  296. package/web/.svelte-kit/output/client/_app/immutable/nodes/{1.Dc3ryOCf.js.map → 1.BOOL0V7K.js.map} +1 -1
  297. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.DUrpAceY.js → 2.CArUpZNe.js} +2 -2
  298. package/web/.svelte-kit/output/client/_app/immutable/nodes/{2.DUrpAceY.js.map → 2.CArUpZNe.js.map} +1 -1
  299. package/web/.svelte-kit/output/client/_app/immutable/nodes/{3.BaCofjAn.js → 3.DSvIWalA.js} +2 -2
  300. package/web/.svelte-kit/output/client/_app/immutable/nodes/{3.BaCofjAn.js.map → 3.DSvIWalA.js.map} +1 -1
  301. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.H6RMG_cy.js +2 -0
  302. package/web/.svelte-kit/output/client/_app/immutable/nodes/{4.v1wYkrvS.js.map → 4.H6RMG_cy.js.map} +1 -1
  303. package/web/.svelte-kit/output/client/_app/immutable/nodes/5.BHSt0Suo.js +2 -0
  304. package/web/.svelte-kit/output/client/_app/immutable/nodes/{5.Bn4A77jK.js.map → 5.BHSt0Suo.js.map} +1 -1
  305. package/web/.svelte-kit/output/client/_app/immutable/nodes/{6.C2hEoITj.js → 6.CzLoA0w7.js} +2 -2
  306. package/web/.svelte-kit/output/client/_app/immutable/nodes/{6.C2hEoITj.js.map → 6.CzLoA0w7.js.map} +1 -1
  307. package/web/.svelte-kit/output/client/_app/immutable/nodes/{7.BcHOjWdp.js → 7.fqq2vVa9.js} +2 -2
  308. package/web/.svelte-kit/output/client/_app/immutable/nodes/{7.BcHOjWdp.js.map → 7.fqq2vVa9.js.map} +1 -1
  309. package/web/.svelte-kit/output/client/_app/immutable/nodes/8.DNFFjTla.js +2 -0
  310. package/web/.svelte-kit/output/client/_app/immutable/nodes/{8.D7NtqIYy.js.map → 8.DNFFjTla.js.map} +1 -1
  311. package/web/.svelte-kit/output/client/_app/immutable/nodes/9.0kMPrp2-.js +2 -0
  312. package/web/.svelte-kit/output/client/_app/immutable/nodes/{9.D-211z9-.js.map → 9.0kMPrp2-.js.map} +1 -1
  313. package/web/.svelte-kit/output/client/_app/version.json +1 -1
  314. package/web/.svelte-kit/output/server/chunks/git.js +13 -4
  315. package/web/.svelte-kit/output/server/chunks/git.js.map +1 -1
  316. package/web/.svelte-kit/output/server/chunks/internal2.js +1 -1
  317. package/web/.svelte-kit/output/server/chunks/internal2.js.map +1 -1
  318. package/web/.svelte-kit/output/server/chunks/message-queue.js +14 -1
  319. package/web/.svelte-kit/output/server/chunks/message-queue.js.map +1 -1
  320. package/web/.svelte-kit/output/server/chunks/version.js +1 -1
  321. package/web/.svelte-kit/output/server/chunks/version.js.map +1 -1
  322. package/web/.svelte-kit/output/server/chunks/ws-handlers.js +2 -1
  323. package/web/.svelte-kit/output/server/chunks/ws-handlers.js.map +1 -1
  324. package/web/.svelte-kit/output/server/chunks/ws-messages.js +6 -1
  325. package/web/.svelte-kit/output/server/chunks/ws-messages.js.map +1 -1
  326. package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js +72 -45
  327. package/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js.map +1 -1
  328. package/web/.svelte-kit/output/server/manifest-full.js +1 -1
  329. package/web/.svelte-kit/output/server/manifest.js +1 -1
  330. package/web/.svelte-kit/output/server/nodes/0.js +1 -1
  331. package/web/.svelte-kit/output/server/nodes/1.js +1 -1
  332. package/web/.svelte-kit/output/server/nodes/2.js +1 -1
  333. package/web/.svelte-kit/output/server/nodes/3.js +1 -1
  334. package/web/.svelte-kit/output/server/nodes/4.js +1 -1
  335. package/web/.svelte-kit/output/server/nodes/5.js +1 -1
  336. package/web/.svelte-kit/output/server/nodes/6.js +1 -1
  337. package/web/.svelte-kit/output/server/nodes/7.js +1 -1
  338. package/web/.svelte-kit/output/server/nodes/8.js +1 -1
  339. package/web/.svelte-kit/output/server/nodes/9.js +1 -1
  340. package/web/src/lib/components/AllSessionsPanel.svelte +9 -1
  341. package/web/src/lib/components/FolderPicker.svelte +49 -21
  342. package/dist/web/client/_app/immutable/chunks/CtjkAirr.js.map +0 -1
  343. package/dist/web/client/_app/immutable/chunks/Dck--FBb.js +0 -4
  344. package/dist/web/client/_app/immutable/chunks/Dck--FBb.js.map +0 -1
  345. package/dist/web/client/_app/immutable/entry/start.B2ZPizAt.js +0 -2
  346. package/dist/web/client/_app/immutable/entry/start.B2ZPizAt.js.map +0 -1
  347. package/dist/web/server/chunks/_layout.svelte-yrPX5VFm.js.map +0 -1
  348. package/dist/web/server/chunks/git-D9dLEKUF.js.map +0 -1
  349. package/dist/web/server/chunks/message-queue-sFcJCRjz.js.map +0 -1
  350. package/dist/web/server/chunks/version-BqEJmw2g.js.map +0 -1
  351. package/dist/web/server/chunks/ws-handlers-zemeJ8Si.js.map +0 -1
  352. package/dist/web/server/chunks/ws-messages-jOSwT1Yq.js.map +0 -1
  353. package/web/.svelte-kit/output/client/_app/immutable/chunks/CtjkAirr.js.map +0 -1
  354. package/web/.svelte-kit/output/client/_app/immutable/chunks/Dck--FBb.js +0 -4
  355. package/web/.svelte-kit/output/client/_app/immutable/chunks/Dck--FBb.js.map +0 -1
  356. package/web/.svelte-kit/output/client/_app/immutable/entry/start.B2ZPizAt.js +0 -2
  357. package/web/.svelte-kit/output/client/_app/immutable/entry/start.B2ZPizAt.js.map +0 -1
  358. package/web/.svelte-kit/output/client/_app/immutable/nodes/4.v1wYkrvS.js +0 -2
  359. package/web/.svelte-kit/output/client/_app/immutable/nodes/5.Bn4A77jK.js +0 -2
  360. package/web/.svelte-kit/output/client/_app/immutable/nodes/8.D7NtqIYy.js +0 -2
  361. package/web/.svelte-kit/output/client/_app/immutable/nodes/9.D-211z9-.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ws-handlers.js","sources":["../../../../../src/db/projects-json.ts","../../../../../src/db/settings-json.ts","../../../../../src/db/visits-json.ts","../../../../../src/transcript/subagent.ts","../../../../../src/server/disk.ts","../../../../../src/tmux/geometry.ts","../../../../../src/tmux/login.ts","../../../../../src/tmux/resize.ts","../../../../../src/tmux/snapshot.ts","../../../../../src/server/plugins.ts","../../../../../src/transcript/past-sessions.ts","../../../../../src/server/ws-handlers.ts"],"sourcesContent":["/**\n * The projects the sidebar groups sessions under, kept on the server.\n *\n * A project is a directory a session has run in. The sidebar used to remember\n * them in the browser's localStorage, which meant every device kept its own\n * list, and \"Reset data\" — a button about that one browser's cache — took\n * every project without a live session with it. A directory belongs to the\n * machine it is on, so the machine remembers it: `~/.claude-mux/projects.json`\n * next to the sessions, shared by every browser that connects, and untouched\n * by anything the browser clears.\n *\n * The file is written atomically (tmp + rename), like a session file, so a\n * poll reading it mid-write sees the old list rather than half of the new.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { homedir } from \"os\";\nimport { getSessionsDir } from \"./sessions-json.js\";\n\n/** Overridable for tests; null means \"next to the sessions directory\". */\nlet projectsPath: string | null = null;\n\nexport function setProjectsPath(path: string | null): void {\n projectsPath = path;\n}\n\nfunction resolveProjectsPath(): string {\n return projectsPath ?? join(dirname(getSessionsDir()), \"projects.json\");\n}\n\ninterface ProjectsFile {\n v: 1;\n /** Absolute directories, in the order they were first seen. */\n projects: string[];\n}\n\nfunction readFile(): ProjectsFile {\n const path = resolveProjectsPath();\n if (!existsSync(path)) return { v: 1, projects: [] };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<ProjectsFile>;\n const projects = Array.isArray(parsed.projects)\n ? parsed.projects.filter((p): p is string => typeof p === \"string\" && p.length > 0)\n : [];\n return { v: 1, projects: [...new Set(projects)] };\n } catch {\n // A torn or hand-edited file must not take the sidebar down with it.\n return { v: 1, projects: [] };\n }\n}\n\nfunction writeFile(file: ProjectsFile): void {\n const path = resolveProjectsPath();\n mkdirSync(dirname(path), { recursive: true });\n const tmp = `${path}.${process.pid}.tmp`;\n writeFileSync(tmp, JSON.stringify(file, null, 2));\n renameSync(tmp, path);\n}\n\n/** Every remembered project, oldest first. */\nexport function getSavedProjects(): string[] {\n return readFile().projects;\n}\n\n/**\n * Directories that are nobody's project: the home directory, the root, the\n * places everything else lives under. A session opened in one of them is a\n * session without a project — it must not become a group of its own, and it\n * must never become the parent every real project nests under, which is what\n * happens the first time someone runs an agent in `~` by accident.\n */\nexport function isForbiddenRoot(cwd: string, home: string = homedir()): boolean {\n const path = cwd.replace(/\\/+$/, \"\") || \"/\";\n if (path === \"/\" || path === home) return true;\n if ([\"/tmp\", \"/home\", \"/Users\", \"/root\", \"/mnt\", \"/media\", \"/opt\", \"/var\", \"/srv\"].includes(path)) return true;\n // One level under a mount root is still a disk, not a project: /mnt/tudao.\n if (/^\\/(?:mnt|media|Volumes)\\/[^/]+$/.test(path)) return true;\n return false;\n}\n\n/** Whether `child` sits somewhere under `parent`. */\nfunction isUnder(child: string, parent: string): boolean {\n return child.startsWith(parent.endsWith(\"/\") ? parent : `${parent}/`);\n}\n\n/**\n * Remember these directories as projects. Returns whether anything new was\n * written, so a caller that runs every poll can tell a no-op from a change\n * worth telling clients about.\n *\n * Only roots are kept: a directory under a remembered project is that\n * project's subfolder, not a project — the sidebar shows its sessions inside\n * the parent's card with the relative path — and a directory that turns out\n * to be above a remembered one takes it over, so the file always holds the\n * shallowest cwd of each tree. A forbidden root is never remembered at all.\n */\nexport function saveProjects(cwds: Iterable<string>): boolean {\n const file = readFile();\n let changed = false;\n for (const raw of cwds) {\n const cwd = raw?.replace(/\\/+$/, \"\");\n if (!cwd || isForbiddenRoot(cwd)) continue;\n if (file.projects.some((p) => p === cwd || isUnder(cwd, p))) continue;\n const kept = file.projects.filter((p) => !isUnder(p, cwd));\n if (kept.length !== file.projects.length) changed = true;\n file.projects = [...kept, cwd];\n changed = true;\n }\n if (changed) writeFile(file);\n return changed;\n}\n\nexport function saveProject(cwd: string): boolean {\n return saveProjects([cwd]);\n}\n\n/** Forget a directory. Returns whether it was there. */\nexport function removeProject(cwd: string): boolean {\n const file = readFile();\n const next = file.projects.filter((p) => p !== cwd);\n if (next.length === file.projects.length) return false;\n writeFile({ ...file, projects: next });\n return true;\n}\n","/**\n * Settings that belong to the machine, not the browser: how this server\n * treats the sessions it creates. Kept in `~/.claude-mux/settings.json`\n * next to the sessions and projects, so every browser sees the same switch\n * and `claude-mux new-session` on the command line honours it too.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getSessionsDir } from \"./sessions-json.js\";\n\nexport interface Settings {\n /**\n * Run `/rc` in every session claude-mux itself creates, once its prompt is\n * up. Remote Control is what lets sessions on different machines see and\n * message each other (`ListAgents`, `SendMessage`) — but only for sessions\n * that have it on, which is easy to forget. Workers other tools spawn\n * straight into tmux are not claude-mux's to touch and are left alone.\n */\n autoRemoteControl: boolean;\n}\n\nexport const DEFAULT_SETTINGS: Settings = {\n autoRemoteControl: false,\n};\n\nlet settingsPath: string | null = null;\n\n/** Overridable for tests; null means \"next to the sessions directory\". */\nexport function setSettingsPath(path: string | null): void {\n settingsPath = path;\n}\n\nfunction resolveSettingsPath(): string {\n return settingsPath ?? join(dirname(getSessionsDir()), \"settings.json\");\n}\n\nexport function getSettings(): Settings {\n const path = resolveSettingsPath();\n if (!existsSync(path)) return { ...DEFAULT_SETTINGS };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<Settings>;\n return {\n autoRemoteControl:\n typeof parsed.autoRemoteControl === \"boolean\"\n ? parsed.autoRemoteControl\n : DEFAULT_SETTINGS.autoRemoteControl,\n };\n } catch {\n return { ...DEFAULT_SETTINGS };\n }\n}\n\n/** Change some settings; returns the whole set as it now stands. */\nexport function updateSettings(patch: Partial<Settings>): Settings {\n const next: Settings = { ...getSettings() };\n if (typeof patch.autoRemoteControl === \"boolean\") next.autoRemoteControl = patch.autoRemoteControl;\n const path = resolveSettingsPath();\n mkdirSync(dirname(path), { recursive: true });\n const tmp = `${path}.${process.pid}.tmp`;\n writeFileSync(tmp, JSON.stringify(next, null, 2));\n renameSync(tmp, path);\n return next;\n}\n\n/**\n * Whether a tmux session name is one claude-mux gave: `<project>-<agent>-<ms>`.\n * Anything else — `quita-b1`, a hand-made `main` — was started by someone\n * else, a person or an orchestrator, and is not for claude-mux to configure.\n */\nexport function isClaudeMuxSessionName(name: string): boolean {\n return /-(?:claude|gemini|copilot)-\\d{10,}$/.test(name);\n}\n","/**\n * When someone last looked at each session, kept in `~/.claude-mux/visits.json`.\n *\n * A turn that ends while nobody is watching is unread \"Done\" until somebody\n * opens the session, on any browser: the watermark lives on the server for\n * exactly that reason, so a phone that read it clears the desktop's badge.\n * The hook never touches this file — it stamps `turn_completed_at` on the\n * session, and the server compares the two.\n */\n\nimport { existsSync, readFileSync, statSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getSessionsDir } from \"./sessions-json.js\";\nimport { writeFileAtomic } from \"../utils/atomic-write.js\";\n\n/** Session id → last visit (epoch ms). */\nexport type Visits = Record<string, number>;\n\n/** An entry this old whose session file is gone is dropped on the next write. */\nconst KEEP_MS = 30 * 24 * 60 * 60 * 1000;\n\nlet visitsPath: string | null = null;\nlet cache: { mtimeMs: number; visits: Visits } | null = null;\n\n/** Overridable for tests; null means \"next to the sessions directory\". */\nexport function setVisitsPath(path: string | null): void {\n visitsPath = path;\n cache = null;\n}\n\nfunction resolveVisitsPath(): string {\n return visitsPath ?? join(dirname(getSessionsDir()), \"visits.json\");\n}\n\n/** Every recorded visit. Read on each session poll, so parsed only when the file changed. */\nexport function getVisits(): Visits {\n const path = resolveVisitsPath();\n try {\n const { mtimeMs } = statSync(path);\n if (cache?.mtimeMs === mtimeMs) return cache.visits;\n const parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n const visits: Visits = {};\n if (parsed && typeof parsed === \"object\") {\n for (const [id, at] of Object.entries(parsed)) {\n if (typeof at === \"number\" && Number.isFinite(at)) visits[id] = at;\n }\n }\n cache = { mtimeMs, visits };\n return visits;\n } catch {\n // Missing is empty. Unreadable (a hand edit, a cut-off write) keeps the\n // last good copy, so the next visit does not write every other\n // session's watermark away.\n return cache?.visits ?? {};\n }\n}\n\n/** Someone is looking at the session now. */\nexport function recordVisit(id: string, at: number = Date.now()): void {\n const now = Date.now();\n const next: Visits = {};\n for (const [other, when] of Object.entries(getVisits())) {\n // A session can live for months without being opened; only a gone one goes.\n if (now - when < KEEP_MS || existsSync(join(getSessionsDir(), `${other}.json`))) next[other] = when;\n }\n next[id] = at;\n const path = resolveVisitsPath();\n writeFileAtomic(path, JSON.stringify(next, null, 2));\n // Keep what was just written, so the next poll need not parse it back.\n cache = { mtimeMs: statSync(path).mtimeMs, visits: next };\n}\n\n/**\n * Put the session's last turn back to unread: the watermark rewinds to just\n * before the turn ended. A session with no finished turn has nothing to mark.\n */\nexport function markUnread(id: string, turnCompletedAt: number | null | undefined): boolean {\n if (!turnCompletedAt) return false;\n recordVisit(id, turnCompletedAt - 1);\n return true;\n}\n","/**\n * Shaping a subagent's transcript for display: what it ran, what it reported,\n * and whether it is still working. Lives beside the parser rather than in the\n * WebSocket layer — it is transcript logic, not transport.\n */\nimport type { SubagentMeta } from \"./tailer.js\";\nimport type { JsonlTailer } from \"./tailer.js\";\nimport type { TranscriptBuilder } from \"./parser.js\";\n\n/** What the manager holds per subagent; only these fields are read here. */\nexport interface SubagentSource {\n tailer: Pick<JsonlTailer, \"mtimeMs\">;\n builder: Pick<TranscriptBuilder, \"entries\">;\n meta: SubagentMeta;\n}\n\n/**\n * A subagent's work, condensed for the Task card that spawned it: what it ran\n * (summaries only — the parent card doesn't need every tool's full input) plus\n * the report it returned.\n */\nexport interface SubagentPayload {\n agentId: string;\n /** Parent Task tool_use id — the card this belongs under. */\n toolUseId: string | null;\n agentType: string | null;\n description: string | null;\n model: string | null;\n activity: { id: string; name: string; summary: string; ok: boolean | null }[];\n /** Older activity entries dropped from the list above. */\n trimmed: number;\n /** The subagent's final message: what it reported back. */\n report: string | null;\n /** The brief the parent wrote it (the Task prompt); full payloads only. */\n prompt: string | null;\n running: boolean;\n /**\n * Whether this carries the whole activity list and the report. A lean\n * payload has the tail of the activity only; the full one is sent when a\n * reader opens the card.\n */\n full: boolean;\n}\n\n/** Longest activity list sent per subagent; real agents reach 150+ tools. */\nexport const SUBAGENT_ACTIVITY_LIMIT = 120;\n/**\n * What a card that nobody has opened carries: enough to say what the agent\n * is doing now. A session with forty agents was sending forty lists of a\n * hundred calls in every snapshot, most of them inside closed cards.\n */\nexport const SUBAGENT_ACTIVITY_LEAN = 8;\n\n/**\n * Fallback liveness window, used only until the harness reports the agent as\n * finished (see TranscriptBuilder.finishedAgents). Covers the gap for agents\n * whose notification never arrives — the launching tool call returns\n * immediately for background agents, so writes are the only other signal.\n */\nconst SUBAGENT_LIVE_WINDOW_MS = 45_000;\n\n/** Condense a subagent's parsed transcript into what its Task card shows. */\nexport function subagentPayload(\n agentId: string,\n state: SubagentSource,\n finished: boolean,\n full = true\n): SubagentPayload {\n const limit = full ? SUBAGENT_ACTIVITY_LIMIT : SUBAGENT_ACTIVITY_LEAN;\n const activity: SubagentPayload['activity'] = [];\n let report: string | null = null;\n let prompt: string | null = null;\n for (const entry of state.builder.entries) {\n if (entry.kind === 'user' && prompt === null) prompt = entry.text;\n if (entry.kind === 'tool') {\n activity.push({\n id: entry.id,\n name: entry.name,\n summary: entry.summary,\n ok: entry.result ? entry.result.ok : null\n });\n } else if (entry.kind === 'text') {\n report = entry.text;\n }\n }\n const mtime = state.tailer.mtimeMs();\n const running =\n !finished && mtime !== null && Date.now() - mtime < SUBAGENT_LIVE_WINDOW_MS;\n const trimmed = Math.max(0, activity.length - limit);\n return {\n agentId,\n toolUseId: state.meta.toolUseId ?? null,\n agentType: state.meta.agentType ?? null,\n description: state.meta.description ?? null,\n model: state.meta.model ?? null,\n // Keep the newest calls — the tail is what \"what is it doing\" needs.\n activity: activity.slice(-limit),\n trimmed,\n report: full ? report : null,\n prompt: full ? prompt : null,\n running,\n full\n };\n}\n\n/**\n * Streams parsed session transcripts (from ~/.claude/projects JSONL files) to\n * clients, keyed by claude-mux session id. Mirrors TerminalWsManager's\n * lifecycle: first client for a session starts the tailer, last one stops it.\n */\n","import { statfs } from 'fs/promises';\nimport { homedir } from 'os';\n\n/** The block counts of `statfs` that `df` reads its \"Use%\" from. */\nexport interface BlockCounts {\n\tblocks: number;\n\tbfree: number;\n\tbavail: number;\n}\n\n/**\n * Share of a filesystem in use, as `df` reports it: the blocks reserved for\n * root count as neither used nor available, so a disk an ordinary user can no\n * longer write to reads 100%, and a fraction rounds up.\n */\nexport function usedPercent({ blocks, bfree, bavail }: BlockCounts): number {\n\tconst used = blocks - bfree;\n\tconst usable = used + bavail;\n\treturn usable > 0 ? Math.ceil((used / usable) * 100) : 0;\n}\n\n/**\n * Use of the filesystem holding the home directory, or null where it cannot be\n * read. Asynchronous, so a home on a network mount that stops answering never\n * holds up the server.\n */\nexport async function homeDiskPercent(path: string = homedir()): Promise<number | null> {\n\ttry {\n\t\treturn usedPercent(await statfs(path));\n\t} catch {\n\t\treturn null;\n\t}\n}\n","/**\n * The size a pane is held at while nobody is looking at it.\n *\n * Everything claude-mux reads off a pane — the prompt box, the dialog rows,\n * the queue — is read from the screen as Claude Code draws it, and Claude\n * Code draws to the width it has. A session started detached gets tmux's\n * default 80x24, and one left behind by a phone-sized terminal keeps that\n * terminal's width, so a dialog's rows wrap and a description lands on the\n * line the reader takes for the next row. Holding a detached window at one\n * generous size means the readers see the same layout every time.\n *\n * A window with a client attached is left alone: the client's terminal is the\n * right size for the person at it, and tmux resizes the window to it anyway.\n * When that client detaches, the next poll widens the window again.\n */\n\nimport { execFile } from \"child_process\";\nimport { promisify } from \"util\";\n\nconst execFileAsync = promisify(execFile);\n\n/** Wide enough that a model picker row, description and all, fits on one line. */\nexport const STANDARD_PANE_WIDTH = 200;\n\n/** Tall enough that a question with previews and a long prompt above it fits. */\nexport const STANDARD_PANE_HEIGHT = 50;\n\n/**\n * Below this a pane is worth widening: dialog rows start wrapping around the\n * width of a phone terminal, and 80 is what tmux hands a detached session.\n */\nexport const MIN_USEFUL_PANE_WIDTH = 160;\n\n/** The `new-session` arguments that start a detached session at the standard size. */\nexport function sizeArgsForNewSession(): string[] {\n return [\"-x\", String(STANDARD_PANE_WIDTH), \"-y\", String(STANDARD_PANE_HEIGHT)];\n}\n\n/**\n * Whether a pane should be widened: nobody is attached to it and it is\n * narrower than the readers want.\n */\nexport function wantsWidening(pane: { attached: number; width: number }): boolean {\n return pane.attached === 0 && pane.width < MIN_USEFUL_PANE_WIDTH;\n}\n\n/**\n * Widen a detached window to the standard size.\n *\n * `resize-window` switches the window to a manual size, which would make the\n * next terminal to attach see a clipped 200-column window rather than its\n * own. Setting `window-size` back to `latest` afterwards keeps the size that\n * was just applied — with no client there is nothing to follow — and lets\n * the next client take the window over as usual.\n *\n * @returns false when tmux refused, e.g. because the window is gone.\n */\nexport async function widenDetachedWindow(target: string): Promise<boolean> {\n try {\n await execFileAsync(\n \"tmux\",\n [\"resize-window\", \"-t\", target, \"-x\", String(STANDARD_PANE_WIDTH), \"-y\", String(STANDARD_PANE_HEIGHT)],\n { timeout: 1000 },\n );\n await execFileAsync(\"tmux\", [\"set-option\", \"-w\", \"-t\", target, \"window-size\", \"latest\"], {\n timeout: 1000,\n });\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Claude Code's sign-in, as the pane draws it.\n *\n * `/login` never reaches the transcript: the link, the field the code goes\n * into and the verdict on it exist only on screen, and the link is printed\n * wider than the pane, so tmux wraps it over several lines. Reading them here\n * lets the transcript offer the link as a link and take the code in a field\n * of its own.\n */\n\nexport type LoginScreen =\n /** The code field is open: the link to sign in with, what is typed so far, and why the last code failed. */\n | { step: \"code\"; url: string | null; typed: string; error: string | null }\n /** The code was refused; Enter goes back to a fresh link and code field. */\n | { step: \"failed\"; error: string }\n /** The code was taken; Claude Code waits for Enter to carry on. */\n | { step: \"done\" }\n /** A turn failed for want of a sign-in, and nothing has happened since. */\n | { step: \"expired\" }\n | null;\n\n/** The field's own label, followed by whatever has been typed into it. */\nconst CODE_FIELD = /Paste code here if prompted >\\s?(.*)$/;\n\n/** The line above the link. */\nconst LINK_INTRO = /Browser didn't open\\?/;\n\n/** The link itself, which starts a line of its own. */\nconst LINK_START = /^\\s*(https:\\/\\/\\S+)$/;\n\n/** A wrapped piece of the link: a line of URL characters, flush left. */\nconst LINK_REST = /^[A-Za-z0-9%&=_.~+/:?#-]+$/;\n\nconst CODE_ERROR = /(Invalid code\\. Please make sure the full code was copied\\.?|OAuth error: .+)$/;\n\nconst SIGNED_IN = /Login successful\\. Press .*continue/i;\n\n/** The sign-in's own retry prompt, drawn under the error that ended it. */\nconst RETRY = /Press Enter to retry/i;\n\n/** What Claude Code answers a turn with when no sign-in is left. */\nconst EXPIRED = /Please run \\/login|run \\/login to restore|Not logged in · Please run \\/login/i;\n\n/** A message bullet: anything Claude Code printed into the conversation. */\nconst BULLET = /^\\s*●\\s/;\n\n/** How far up from the foot the sign-in screen may begin. */\nconst LOGIN_REACH = 40;\n\n/** Read the sign-in screen off a pane capture taken without colour. */\nexport function readLoginScreen(content: string): LoginScreen {\n if (!content) return null;\n const lines = content.split(\"\\n\");\n let last = lines.length - 1;\n while (last >= 0 && !lines[last].trim()) last--;\n if (last < 0) return null;\n const from = Math.max(0, last - LOGIN_REACH);\n\n for (let i = last; i >= from; i--) {\n if (SIGNED_IN.test(lines[i])) return { step: \"done\" };\n if (RETRY.test(lines[i])) {\n const above = lines.slice(Math.max(from, i - 3), i).map((l) => l.trim().match(CODE_ERROR)?.[1]).filter(Boolean);\n return { step: \"failed\", error: above.at(-1) ?? \"The sign-in failed.\" };\n }\n const field = lines[i].match(CODE_FIELD);\n if (!field) continue;\n let error: string | null = null;\n for (let j = i + 1; j <= last; j++) {\n const m = lines[j].trim().match(CODE_ERROR);\n if (m) error = m[1];\n }\n return { step: \"code\", url: readLink(lines, from, i), typed: field[1].trim(), error };\n }\n\n // The notice counts only while it is the last thing Claude Code said: any\n // message after it means a turn has run since.\n for (let i = last; i >= from; i--) {\n if (!BULLET.test(lines[i])) continue;\n return EXPIRED.test(lines[i]) ? { step: \"expired\" } : null;\n }\n return null;\n}\n\n/** The sign-in link between its introduction and the code field, joined back across the wrap. */\nfunction readLink(lines: string[], from: number, field: number): string | null {\n let start = -1;\n for (let i = field - 1; i >= from; i--) {\n if (LINK_START.test(lines[i])) start = i;\n if (LINK_INTRO.test(lines[i])) break;\n }\n if (start === -1) return null;\n let url = lines[start].trim();\n for (let i = start + 1; i < field; i++) {\n const piece = lines[i].replace(/\\s+$/, \"\");\n if (!LINK_REST.test(piece)) break;\n url += piece;\n }\n return url;\n}\n","import { execFileSync } from \"child_process\";\n\n/**\n * Resize a tmux window to the specified dimensions.\n * Uses resize-window instead of resize-pane for detached sessions.\n *\n * @param target - The tmux target in format \"session:window.pane\"\n * @param cols - Number of columns (clamped to 20-500)\n * @param rows - Number of rows (clamped to 5-200)\n */\nexport function resizeTmuxWindow(target: string, cols: number, rows: number): void {\n try {\n const safeCols = Math.max(20, Math.min(500, Math.floor(cols)));\n const safeRows = Math.max(5, Math.min(200, Math.floor(rows)));\n // Extract window target (session:window) from full target (session:window.pane)\n const windowTarget = target.replace(/\\.\\d+$/, \"\");\n execFileSync(\n \"tmux\",\n [\"resize-window\", \"-t\", windowTarget, \"-x\", String(safeCols), \"-y\", String(safeRows)],\n {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 2000,\n }\n );\n } catch {\n // Pane may not exist or tmux error - ignore\n }\n}\n","/**\n * Consistent pane snapshots for the terminal stream.\n *\n * The web terminal renders two regions:\n * - history: lines that scrolled off the top of the pane (immutable in tmux,\n * addressed by absolute index 0..history_size-1, 0 = oldest)\n * - screen: the visible pane (pane_height rows, redrawn constantly)\n *\n * `snapshotPane` reads `#{history_size}`, the last TAIL history lines and the\n * screen in ONE tmux invocation so the three are mutually consistent. The\n * manager diffs history_size between ticks and forwards only the new history\n * lines (taken from the tail) plus the screen when it changed.\n */\nimport { execFileSync } from \"child_process\";\n\nexport const HISTORY_TAIL = 64;\nconst SEP = \"@@CLAUDE_MUX_SEP@@\";\n\nexport interface PaneSnapshot {\n historySize: number;\n alt: boolean;\n cols: number;\n rows: number;\n /** Last min(HISTORY_TAIL, historySize) history lines, oldest first */\n tail: string[];\n /** Visible pane, exactly `rows` lines */\n screen: string[];\n}\n\nfunction run(args: string[]): string {\n return execFileSync(\"tmux\", args, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 2000,\n maxBuffer: 16 * 1024 * 1024,\n });\n}\n\n/** Split capture output into lines, dropping the single trailing newline tmux adds. */\nfunction splitLines(s: string): string[] {\n if (s === \"\") return [];\n const lines = s.split(\"\\n\");\n if (lines[lines.length - 1] === \"\") lines.pop();\n return lines;\n}\n\nexport function snapshotPane(target: string): PaneSnapshot | null {\n let out: string;\n try {\n out = run([\n \"display-message\", \"-p\", \"-t\", target,\n \"#{history_size} #{alternate_on} #{pane_width} #{pane_height}\",\n \";\",\n \"capture-pane\", \"-p\", \"-e\", \"-t\", target, \"-S\", `-${HISTORY_TAIL}`, \"-E\", \"-1\",\n \";\",\n \"display-message\", \"-p\", SEP,\n \";\",\n \"capture-pane\", \"-p\", \"-e\", \"-t\", target,\n ]);\n } catch {\n return null;\n }\n const nl = out.indexOf(\"\\n\");\n if (nl === -1) return null;\n const header = out.slice(0, nl).trim().split(/\\s+/);\n if (header.length < 4) return null;\n const historySize = Number(header[0]) || 0;\n const alt = header[1] === \"1\";\n const cols = Number(header[2]) || 0;\n const rows = Number(header[3]) || 0;\n\n const body = out.slice(nl + 1);\n const sepIdx = body.indexOf(SEP + \"\\n\");\n if (sepIdx === -1) return null;\n const tailRaw = body.slice(0, sepIdx);\n const screenRaw = body.slice(sepIdx + SEP.length + 1);\n\n let tail = splitLines(tailRaw);\n // When history_size < TAIL, tmux clamps -S and (for 0 history) the range\n // -S -64 -E -1 yields nothing useful. Trust history_size.\n if (historySize === 0) tail = [];\n else if (tail.length > historySize) tail = tail.slice(tail.length - historySize);\n\n return { historySize, alt, cols, rows, tail, screen: splitLines(screenRaw) };\n}\n\n/**\n * Fetch history lines [start, end) by absolute index.\n * tmux addresses history relative to the *current* history_size, which may have\n * moved since the caller last saw it. We read history_size in the same batch and,\n * if it drifted, retry with the fresh value (a few times) so the returned range\n * is exactly the one asked for. If it keeps drifting we return the range the\n * lines actually correspond to, labelled correctly.\n */\nexport function fetchHistoryRange(\n target: string,\n start: number,\n end: number,\n knownHistorySize: number,\n): { start: number; lines: string[]; historySize: number } | null {\n if (end <= start) return { start, lines: [], historySize: knownHistorySize };\n let known = knownHistorySize;\n let last: { start: number; lines: string[]; historySize: number } | null = null;\n for (let attempt = 0; attempt < 4; attempt++) {\n const relStart = start - known; // negative\n const relEnd = end - 1 - known; // negative, inclusive\n if (relEnd >= 0) {\n // Range (partly) past the end of history as we know it — cap to history.\n return { start, lines: [], historySize: known };\n }\n let out: string;\n try {\n out = run([\n \"display-message\", \"-p\", \"-t\", target, \"#{history_size}\",\n \";\",\n \"capture-pane\", \"-p\", \"-e\", \"-t\", target, \"-S\", String(relStart), \"-E\", String(relEnd),\n ]);\n } catch {\n return null;\n }\n const nl = out.indexOf(\"\\n\");\n if (nl === -1) return null;\n const historySize = Number(out.slice(0, nl).trim()) || 0;\n const lines = splitLines(out.slice(nl + 1));\n const drift = historySize - known;\n if (drift === 0) return { start, lines, historySize };\n // Offsets were interpreted against `historySize`, so lines begin at start+drift.\n last = { start: Math.max(0, start + drift), lines, historySize };\n known = historySize;\n }\n return last;\n}\n","/**\n * This machine's Claude Code plugins, for the fleet to keep in step\n * (`src/fleet-plugins.ts`): its marketplaces with the commit each is at and\n * the latest commit its source has, and its installed plugins with their\n * version. Read through the `claude plugin` CLI, never off its files, so the\n * report says what Claude Code itself would.\n *\n * Reading runs the CLI and asks GitHub, so it is never done on the poll's\n * time: `PluginReader.current()` answers with what is known and reads again in the\n * background once the last attempt is older than REFRESH_MS.\n * `startAlign()` runs \"Bring in line\" as a job in the background (updating\n * the marketplaces that are behind, then each install and enable, then a\n * fresh read) and answers at once: a marketplace update fetches a whole git\n * checkout and can take minutes, far past the 10 s Bun gives an idle request.\n * The page reads the job with `alignJob()` until it ends.\n *\n * Only user-scope plugins are reported; what a project turns on is its own.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { delimiter, join } from \"node:path\";\nimport { normaliseRemote } from \"../fleet-projects.js\";\nimport { isRepoSlug } from \"../github-ref.js\";\nimport type { AlignJob, AlignRequest, AlignStep, MarketplaceReport, PluginReport, PluginsReport } from \"../fleet-plugins.js\";\n\n/** How long a report stands before it is read again. */\nconst REFRESH_MS = 5 * 60_000;\n/** How long the latest commit of a marketplace's source stands. */\nconst LATEST_MS = 10 * 60_000;\nconst READ_TIMEOUT_MS = 30_000;\nconst STEP_TIMEOUT_MS = 90_000;\n/** A marketplace fetch, or an install that adds one: a large checkout far behind its remote takes minutes. */\nexport const FETCH_TIMEOUT_MS = 10 * 60_000;\n/** How much of a step's output a job keeps, in characters. */\nconst OUTPUT_TAIL = 8_000;\n\nexport interface RunResult {\n code: number;\n stdout: string;\n stderr: string;\n /** Stopped for running past its limit. */\n timedOut?: boolean;\n}\n\nexport type Runner = (cmd: string, args: string[], timeoutMs: number) => Promise<RunResult>;\n\ninterface PluginDeps {\n run?: Runner;\n now?: () => number;\n /** Reads a file's text; null when it is not there. */\n readText?: (path: string) => Promise<string | null>;\n}\n\n/**\n * `claude` is usually in `~/.local/bin`, which a service's PATH may leave\n * out, so that and Claude Code's own install folder are searched too.\n */\nconst RUN_PATH = [process.env.PATH ?? \"\", join(homedir(), \".local\", \"bin\"), join(homedir(), \".claude\", \"local\")]\n .filter(Boolean)\n .join(delimiter);\n\nconst defaultRun: Runner = (cmd, args, timeoutMs) =>\n new Promise((done) => {\n execFile(\n cmd,\n args,\n {\n timeout: timeoutMs,\n maxBuffer: 16 * 1024 * 1024,\n // git asking for a password or a host key would wait out the timeout.\n env: { ...process.env, PATH: RUN_PATH, GIT_TERMINAL_PROMPT: \"0\", GIT_SSH_COMMAND: \"ssh -o BatchMode=yes\" },\n },\n (err, stdout, stderr) => {\n const code = err ? (typeof err.code === \"number\" ? err.code : -1) : 0;\n // Killed for printing past maxBuffer is a failure of its own, not a timeout.\n const timedOut = !!err && err.killed === true && !!err.signal && err.code !== \"ERR_CHILD_PROCESS_STDIO_MAXBUFFER\";\n done({ code, stdout: String(stdout), stderr: String(stderr || (err && code === -1 && !timedOut ? err.message : \"\")), timedOut });\n },\n );\n });\n\nconst defaultReadText = (path: string) => readFile(path, \"utf8\").catch(() => null);\n\ninterface CliMarketplace {\n name?: unknown;\n source?: unknown;\n repo?: unknown;\n url?: unknown;\n installLocation?: unknown;\n}\n\ninterface CliPlugin {\n id?: unknown;\n version?: unknown;\n scope?: unknown;\n enabled?: unknown;\n}\n\nfunction parseJson<T>(text: string): T[] {\n try {\n const data = JSON.parse(text);\n return Array.isArray(data) ? data.filter((x) => x && typeof x === \"object\") : [];\n } catch {\n return [];\n }\n}\n\nconst SHA = /^[0-9a-f]{40}$/;\n\n/**\n * The commit a marketplace's checkout is at: its git HEAD, or the `.gcs-sha`\n * Claude Code writes beside one it downloaded as an archive.\n */\nasync function localCommit(location: string, run: Runner, readText: (path: string) => Promise<string | null>): Promise<string | null> {\n const head = await run(\"git\", [\"-C\", location, \"rev-parse\", \"HEAD\"], READ_TIMEOUT_MS);\n const sha = head.stdout.trim();\n if (head.code === 0 && SHA.test(sha)) return sha;\n const gcs = (await readText(join(location, \".gcs-sha\")))?.trim() ?? \"\";\n return SHA.test(gcs) ? gcs : null;\n}\n\n/** GitHub's own name for a repo (a renamed one answers under its new name) and its default branch's head. */\nasync function askGithub(repo: string, run: Runner): Promise<{ repo: string; latest: string } | null> {\n const res = await run(\"gh\", [\"api\", `repos/${repo}`, \"--jq\", \".full_name\"], READ_TIMEOUT_MS);\n const name = res.stdout.trim();\n if (res.code !== 0 || !isRepoSlug(name)) return null;\n const head = await run(\"gh\", [\"api\", `repos/${name}/commits/HEAD`, \"--jq\", \".sha\"], READ_TIMEOUT_MS);\n const sha = head.stdout.trim();\n return head.code === 0 && SHA.test(sha) ? { repo: name, latest: sha } : null;\n}\n\n/** A source's head through git itself, for a machine without `gh`. */\nasync function lsRemote(url: string, run: Runner): Promise<string | null> {\n const res = await run(\"git\", [\"ls-remote\", url, \"HEAD\"], READ_TIMEOUT_MS);\n const sha = res.stdout.split(/\\s/)[0];\n return res.code === 0 && SHA.test(sha) ? sha : null;\n}\n\ntype Source = { repo: string; latest: string | null };\n\nexport class PluginReader {\n private report: PluginsReport | null = null;\n private reading: Promise<PluginsReport | null> | null = null;\n private job: AlignJob | null = null;\n /** When the last read started, so a CLI that fails is not run again on every broadcast. */\n private attemptedAt = -Infinity;\n private sources = new Map<string, { at: number; source: Source }>();\n private readonly run: Runner;\n private readonly now: () => number;\n private readonly readText: (path: string) => Promise<string | null>;\n\n constructor(deps: PluginDeps = {}) {\n this.run = deps.run ?? defaultRun;\n this.now = deps.now ?? Date.now;\n this.readText = deps.readText ?? defaultReadText;\n }\n\n /** What is known now, starting a read in the background when the last one is stale. */\n current(): PluginsReport | null {\n if (this.now() - this.attemptedAt > REFRESH_MS && !this.reading) void this.read().catch((err) => console.warn(\"[plugins] read failed:\", err));\n return this.report;\n }\n\n /** Read the report now; a read already under way is joined. When the CLI cannot be run, the last report (null before any). */\n read(): Promise<PluginsReport | null> {\n if (!this.reading) this.attemptedAt = this.now();\n this.reading ??= this.readNow().finally(() => {\n this.reading = null;\n });\n return this.reading;\n }\n\n /** Read again after whatever read is under way, so the report reflects everything done before the call. */\n async refresh(): Promise<PluginsReport | null> {\n await this.reading;\n return this.read();\n }\n\n /**\n * Start \"Bring in line\" unless a run is under way, which is joined instead:\n * two runs at once would both rewrite Claude Code's plugin files. Answers\n * the job as it stands; `onDone` is called once it has ended and the report\n * has been read again.\n */\n startAlign(plan: AlignRequest, onDone: () => void = () => {}): { started: boolean; job: AlignJob } {\n if (this.job?.state === \"running\") return { started: false, job: snapshot(this.job) };\n const runs = alignRuns(plan);\n const job: AlignJob = { state: \"running\", startedAt: this.now(), finishedAt: null, steps: runs.map((r) => r.step) };\n this.job = job;\n void this.runJob(job, runs, onDone);\n return { started: true, job: snapshot(job) };\n }\n\n /** The last \"Bring in line\" run, null before any. */\n alignJob(): AlignJob | null {\n return this.job && snapshot(this.job);\n }\n\n /** Each step runs whatever the one before did; the report is read again at the end so the fleet sees the result. */\n private async runJob(job: AlignJob, runs: AlignRun[], onDone: () => void): Promise<void> {\n try {\n for (const { step, args, limit } of runs) {\n step.state = \"running\";\n step.startedAt = this.now();\n const res = await this.run(\"claude\", args, limit).catch(\n (err): RunResult => ({ code: -1, stdout: \"\", stderr: String(err) }),\n );\n step.finishedAt = this.now();\n // The page reads the job every second, through the relay for another machine.\n const output = `${res.stdout}${res.stderr}`.trim().slice(-OUTPUT_TAIL);\n if (res.timedOut) {\n step.state = \"timeout\";\n step.output = `${output}\\n(stopped after ${Math.round(limit / 60_000)} min without finishing)`.trimStart();\n } else {\n step.state = res.code === 0 ? \"ok\" : \"failed\";\n step.output = output;\n }\n }\n await this.refresh().catch((err) => console.warn(\"[plugins] read failed:\", err));\n } finally {\n job.state = \"done\";\n job.finishedAt = this.now();\n onDone();\n }\n }\n\n private async readNow(): Promise<PluginsReport | null> {\n const [mps, pls] = await Promise.all([\n this.run(\"claude\", [\"plugin\", \"marketplace\", \"list\", \"--json\"], READ_TIMEOUT_MS),\n this.run(\"claude\", [\"plugin\", \"list\", \"--json\"], READ_TIMEOUT_MS),\n ]);\n if (mps.code !== 0 || pls.code !== 0) {\n console.warn(\"[plugins] could not read:\", (mps.stderr || pls.stderr).trim().split(\"\\n\")[0]);\n return this.report;\n }\n const marketplaces = await Promise.all(parseJson<CliMarketplace>(mps.stdout).map((m) => this.marketplace(m)));\n const plugins: PluginReport[] = parseJson<CliPlugin>(pls.stdout)\n .filter((p) => typeof p.id === \"string\" && (p.scope === undefined || p.scope === \"user\"))\n .map((p) => ({\n id: p.id as string,\n version: typeof p.version === \"string\" ? p.version : null,\n enabled: p.enabled !== false,\n }))\n .sort((a, b) => a.id.localeCompare(b.id));\n const report: PluginsReport = {\n marketplaces: marketplaces.filter((m): m is MarketplaceReport => m !== null),\n plugins,\n readAt: this.now(),\n };\n this.report = report;\n return report;\n }\n\n private async marketplace(m: CliMarketplace): Promise<MarketplaceReport | null> {\n if (typeof m.name !== \"string\") return null;\n const location = typeof m.installLocation === \"string\" ? m.installLocation : null;\n // A GitHub repo added by URL is asked about as one added from GitHub.\n const github =\n m.source === \"github\" && typeof m.repo === \"string\"\n ? m.repo\n : typeof m.url === \"string\"\n ? /^github\\.com\\/([^/]+\\/[^/]+)$/.exec(normaliseRemote(m.url) ?? \"\")?.[1]\n : undefined;\n const written = github ?? (typeof m.url === \"string\" ? m.url : null);\n const [commit, source] = await Promise.all([\n location ? localCommit(location, this.run, this.readText) : null,\n this.source(github, written),\n ]);\n return { name: m.name, repo: source?.repo ?? written, commit, latest: source?.latest ?? null };\n }\n\n private async source(github: string | undefined, written: string | null): Promise<Source | null> {\n if (!written) return null;\n const cached = this.sources.get(written);\n if (cached && this.now() - cached.at < LATEST_MS) return cached.source;\n let source: Source | null = null;\n if (github) {\n source = (await askGithub(github, this.run)) ?? { repo: github, latest: await lsRemote(`https://github.com/${github}.git`, this.run) };\n } else {\n source = { repo: written, latest: await lsRemote(written, this.run) };\n }\n if (source) this.sources.set(written, { at: this.now(), source });\n return source;\n }\n}\n\n/** A step of a job with the `claude` arguments it runs and how long they may take. */\ninterface AlignRun {\n step: AlignStep;\n args: string[];\n limit: number;\n}\n\n/** The plan as the steps it runs, in order: marketplace updates, installs, enables. */\nfunction alignRuns(plan: AlignRequest): AlignRun[] {\n const run = (kind: AlignStep[\"kind\"], label: string, args: string[], limit: number): AlignRun => ({\n step: { kind, label, command: `claude ${args.join(\" \")}`, state: \"pending\", startedAt: null, finishedAt: null, output: \"\" },\n args,\n limit,\n });\n return [\n ...plan.update.map((name) => run(\"update\", `Updating ${name}`, [\"plugin\", \"marketplace\", \"update\", name], FETCH_TIMEOUT_MS)),\n ...plan.install.map((s) =>\n \"marketplace\" in s\n ? run(\"install\", `Installing ${s.name}`, [\"plugin\", \"install\", `${s.name}@${s.marketplace}`], STEP_TIMEOUT_MS)\n : // Adds the marketplace, which clones it.\n run(\"install\", `Installing ${s.name}`, [\"plugin\", \"install\", s.name, \"--marketplace\", s.repo], FETCH_TIMEOUT_MS),\n ),\n ...plan.enable.map((id) => run(\"enable\", `Enabling ${id}`, [\"plugin\", \"enable\", id, \"--scope\", \"user\"], STEP_TIMEOUT_MS)),\n ];\n}\n\nfunction snapshot(job: AlignJob): AlignJob {\n return { ...job, steps: job.steps.map((s) => ({ ...s })) };\n}\n\ntype GlobalWithPlugins = typeof globalThis & { __claudeMuxPlugins?: PluginReader };\n\n/** This process's one reader (the dev server loads modules more than once). */\nexport function pluginReader(): PluginReader {\n const g = globalThis as GlobalWithPlugins;\n return (g.__claudeMuxPlugins ??= new PluginReader());\n}\n","/**\n * Past sessions: the Claude Code conversations a machine still holds on disk\n * (GLOSSARY.md, \"Past session\"), read from their transcripts.\n *\n * A transcript is `~/.claude/projects/<launch dir>/<session id>.jsonl`; its\n * subagents sit below it in `<session id>/subagents/`, so only the files at\n * the top of each project directory are conversations. Each is read at its\n * two ends, never whole: the head gives where it ran and the first prompt,\n * the tail when it was last active, its latest folder and branch, and its\n * title (a `custom-title` set with /rename, else Claude Code's `ai-title`).\n * A title written mid-file is missed, and the first prompt stands in.\n *\n * Claude Code deletes transcripts after 30 days; what is on disk is the list.\n * The format is Claude Code's own and changes between versions, so every\n * field is optional and a file that cannot be read is left out.\n */\n\nimport { open, readdir, readFile, stat } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport { type PastSession } from \"../past-session.js\";\nimport { parseSlashCommand } from \"./parser.js\";\nimport { projectsRoot } from \"./tailer.js\";\n\n\n/** How much of each end of a transcript is read. */\nexport const END_BYTES = 64 * 1024;\n/** A prompt or title longer than this is cut; the list shows one line. */\nexport const MAX_LABEL = 160;\n\nconst SESSION_FILE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.jsonl$/i;\n\n\ntype Entry = Record<string, unknown>;\n\n/** The whole JSON lines in `text`; with `cutStart`/`cutEnd` the first or last line is a fragment and is dropped. */\nfunction parseLines(text: string, cutStart: boolean, cutEnd: boolean): Entry[] {\n const lines = text.split(\"\\n\");\n if (cutStart) lines.shift();\n if (cutEnd) lines.pop();\n const out: Entry[] = [];\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed: unknown = JSON.parse(line);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) out.push(parsed as Entry);\n } catch {\n // A torn or foreign line: skip it.\n }\n }\n return out;\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === \"string\" && value.trim() ? value : null;\n}\n\nfunction clip(text: string): string {\n const flat = text.replace(/\\s+/g, \" \").trim();\n return flat.length > MAX_LABEL ? `${flat.slice(0, MAX_LABEL - 1)}…` : flat;\n}\n\n/**\n * What a person typed in a user entry, or null for anything else: a tool\n * result, a meta line, a hook's or a command's output. A slash command reads\n * as typed (`/ak:maestro-worker 102`).\n */\nexport function promptText(entry: Entry): string | null {\n if (entry.type !== \"user\" || entry.isMeta === true || entry.isSidechain === true) return null;\n const message = entry.message as { content?: unknown } | undefined;\n let text: string | null = null;\n if (typeof message?.content === \"string\") text = message.content;\n else if (Array.isArray(message?.content)) {\n const block = message.content.find(\n (b): b is { type: \"text\"; text: string } => !!b && typeof b === \"object\" && b.type === \"text\" && typeof b.text === \"string\"\n );\n if (!block || message.content.some((b) => b?.type === \"tool_result\")) return null;\n text = block.text;\n }\n if (!text?.trim()) return null;\n const command = parseSlashCommand(text);\n if (command) return clip(command.args ? `${command.name} ${command.args}` : command.name);\n if (/^\\s*<(local-command|command-message|system-reminder|bash-|user-memory)/.test(text) || text.startsWith(\"Caveat:\")) {\n return null;\n }\n return clip(text);\n}\n\nfunction timestampOf(entry: Entry): number | null {\n const ts = typeof entry.timestamp === \"string\" ? Date.parse(entry.timestamp) : NaN;\n return Number.isFinite(ts) ? ts : null;\n}\n\n/**\n * Read one transcript's two ends into a past session, or null when it is not\n * one a person would reopen: a subagent's (sidechain), one run by the SDK or\n * `claude -p` rather than at the prompt, or one where nothing was ever asked.\n */\nexport async function readPastSession(path: string): Promise<PastSession | null> {\n let handle;\n try {\n handle = await open(path, \"r\");\n } catch {\n return null;\n }\n let head: string;\n let tail: string;\n let size: number;\n let mtimeMs: number;\n try {\n const info = await handle.stat();\n size = info.size;\n mtimeMs = info.mtimeMs;\n const headBuf = Buffer.alloc(Math.min(size, END_BYTES));\n await handle.read(headBuf, 0, headBuf.length, 0);\n head = headBuf.toString(\"utf8\");\n if (size > END_BYTES) {\n const tailLength = Math.min(size - END_BYTES, END_BYTES);\n const tailBuf = Buffer.alloc(tailLength);\n await handle.read(tailBuf, 0, tailLength, size - tailLength);\n tail = tailBuf.toString(\"utf8\");\n } else {\n tail = \"\";\n }\n } catch {\n return null;\n } finally {\n await handle.close();\n }\n\n const headEntries = parseLines(head, false, size > END_BYTES);\n // The tail's first line is whole only when it starts right after the head.\n const tailEntries = tail ? parseLines(tail, size > 2 * END_BYTES || !head.endsWith(\"\\n\"), false) : [];\n const all = [...headEntries, ...tailEntries];\n\n let cwd: string | null = null;\n let branch: string | null = null;\n let prompt: string | null = null;\n let customTitle: string | null = null;\n let aiTitle: string | null = null;\n let lastPrompt: string | null = null;\n let last: number | null = null;\n for (const entry of all) {\n // A subagent's line inside the conversation is not the conversation's.\n if (entry.isSidechain === true) continue;\n const entrypoint = str(entry.entrypoint);\n if (entrypoint?.startsWith(\"sdk\")) return null;\n cwd = str(entry.cwd) ?? cwd;\n branch = str(entry.gitBranch) ?? branch;\n prompt ??= promptText(entry);\n if (entry.type === \"custom-title\") customTitle = str(entry.customTitle) ?? customTitle;\n if (entry.type === \"ai-title\") aiTitle = str(entry.aiTitle) ?? aiTitle;\n if (entry.type === \"last-prompt\") lastPrompt = str(entry.lastPrompt) ?? lastPrompt;\n last = timestampOf(entry) ?? last;\n }\n prompt ??= lastPrompt ? clip(lastPrompt) : null;\n // /rename also leaves the title beside the transcript.\n customTitle = (await readTitleFile(path)) ?? customTitle;\n const title = customTitle ?? aiTitle;\n if (!prompt && !title) return null;\n return {\n id: basename(path, \".jsonl\"),\n cwd,\n branch,\n title: title ? clip(title) : null,\n prompt,\n lastActivity: last ?? mtimeMs,\n };\n}\n\nasync function readTitleFile(transcriptPath: string): Promise<string | null> {\n try {\n const raw = JSON.parse(await readFile(join(transcriptPath.replace(/\\.jsonl$/, \"\"), \"custom-title.json\"), \"utf8\"));\n return str(raw?.customTitle);\n } catch {\n return null;\n }\n}\n\n/** The conversation transcripts under `root`: the top of each project directory, never a subagent's. */\nexport async function listTranscripts(root = projectsRoot()): Promise<{ path: string; mtimeMs: number; size: number }[]> {\n let dirs: string[];\n try {\n dirs = await readdir(root);\n } catch {\n return [];\n }\n const out: { path: string; mtimeMs: number; size: number }[] = [];\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(join(root, dir));\n } catch {\n continue;\n }\n for (const name of names) {\n if (!SESSION_FILE.test(name)) continue;\n const path = join(root, dir, name);\n try {\n const info = await stat(path);\n if (info.isFile()) out.push({ path, mtimeMs: info.mtimeMs, size: info.size });\n } catch {\n // Gone between the listing and the stat.\n }\n }\n }\n return out;\n}\n\n/**\n * Every past session on this machine, kept between reads: a transcript is\n * read again only when its size or time changes, so a listing after the\n * first costs a directory walk and a stat per file.\n */\nexport class PastSessionIndex {\n private cache = new Map<string, { mtimeMs: number; size: number; session: PastSession | null }>();\n\n constructor(private readonly root: () => string = projectsRoot) {}\n\n /** Every past session, newest first. A session id found twice (a transcript moved by /cd) keeps the newer. */\n async list(): Promise<PastSession[]> {\n const files = await listTranscripts(this.root());\n const seen = new Set<string>();\n for (const file of files) {\n seen.add(file.path);\n const known = this.cache.get(file.path);\n if (known && known.mtimeMs === file.mtimeMs && known.size === file.size) continue;\n this.cache.set(file.path, { mtimeMs: file.mtimeMs, size: file.size, session: await readPastSession(file.path) });\n }\n for (const path of this.cache.keys()) if (!seen.has(path)) this.cache.delete(path);\n const byId = new Map<string, PastSession>();\n for (const { session } of this.cache.values()) {\n if (!session) continue;\n const other = byId.get(session.id);\n if (!other || other.lastActivity < session.lastActivity) byId.set(session.id, session);\n }\n return [...byId.values()].sort((a, b) => b.lastActivity - a.lastActivity);\n }\n}\n\n/** The past sessions that are not live: any session this machine's broadcast lists is left out. */\nexport function withoutLive(past: readonly PastSession[], live: ReadonlySet<string>): PastSession[] {\n return past.filter((p) => !live.has(p.id));\n}\n","/**\n * Shared WebSocket handler logic for both dev (ws) and production (Bun) servers.\n * Abstracts the WebSocket-specific APIs behind a simple interface.\n *\n * Features:\n * - Max clients limit to prevent server overload\n * - Structured error logging\n * - Backpressure handling for slow clients\n */\n\nimport { execFileSync } from 'child_process';\nimport { existsSync, readFileSync } from 'fs';\nimport { getAllSessions, getSession, updateSession, readLinks, cleanupStaleSessions, sanitizeDisplayName, getVisits, type Session } from '../db/index.js';\nimport { type ContextUsage } from '../transcript/context.js';\nimport { TranscriptBuilder, type TranscriptEntry } from '../transcript/parser.js';\nimport { subagentPayload, type SubagentPayload } from '../transcript/subagent.js';\nimport { isDictated } from './dictation-marks.js';\nimport { homeDiskPercent } from './disk.js';\nimport { findTranscriptById, JsonlTailer, listSubagents, resolveTranscriptPath, type SubagentMeta } from '../transcript/tailer.js';\nimport { currentRemoteControlUrl, answerRemoteControlDialog, setRemoteControl } from './remote-control.js';\nimport { listAllPanes, type PaneInfo, capturePaneContentAsync, readActivity, readActivityLine, isPaneShowingIdlePrompt, detectRecentInterruption, readPromptBox, readQueuedMessages, readPromptOptions, readUpdateNotice, stripAnsi } from '../tmux/pane.js';\nimport type { PaneActivity, PromptChoice, UpdateNotice } from '../tmux/pane.js';\nimport { wantsWidening, widenDetachedWindow } from '../tmux/geometry.js';\nimport { readLoginScreen, type LoginScreen } from '../tmux/login.js';\nimport { readRewindScreen, type RewindScreen } from '../tmux/rewind.js';\nimport { upgradeStatus } from './restart-claude.js';\nimport { getSavedProjects, saveProjects } from '../db/projects-json.js';\nimport { peekContextPercent, peekTranscriptTitle } from '../transcript/context-peek.js';\n\n/**\n * What the session poll reads off the pane itself and rides the broadcast —\n * never written to the session JSON.\n *\n * Every field here also needs a line in `EnrichedSessionSchema`\n * (`src/types/ws-messages.ts`), or Zod strips it on the way into the browser\n * and the UI silently never sees it.\n */\ntype LivePaneFields = {\n\tpane_title: string | null;\n\tpane_alive: boolean;\n\tdraft_input: string | null;\n\tdraft_kind: 'typed' | 'suggestion' | null;\n\tpane_queue: string[];\n\tpane_choice: PromptChoice;\n\t/** Claude Code's footer notice about its own update; null when it shows none. */\n\tpane_update: UpdateNotice;\n\t/** Claude Code's sign-in screen, which the transcript never sees; null when none is drawn. */\n\tpane_login: LoginScreen;\n\tpane_rewind: RewindScreen;\n\t/** The upgrade the status line's button started, while it runs or after it ended without a restart. */\n\tupgrade: UpgradeStatus | null;\n\t/** The spinner line while a turn runs, split into its parts; null when none is drawn. */\n\tpane_activity: LiveActivity | null;\n\t/** Share of the context window in use as of the latest reply; null when unknown. */\n\tcontext_pct: number | null;\n\t/** The GitHub issue a maestro worker owns, as last read; null otherwise. */\n\tissue: IssueInfo | null;\n\t/** Every message claude-mux holds for the pane, next out first. */\n\tqueue: QueuedMessageInfo[];\n\t/** What the queue and steers handed the pane lately, so the transcript can label it. */\n\tdelivered: DeliveryInfo[];\n\t/** When anyone last had the session open, from `visits.json`; null when nobody has. */\n\tlast_visited_at: number | null;\n\t/** What the session has changed: git's count when it is in a repo, else its log's; null until known. */\n\tchanges: ChangesInfo | null;\n};\nimport { resizeTmuxWindow } from '../tmux/resize.js';\nimport { snapshotPane, fetchHistoryRange } from '../tmux/snapshot.js';\nimport { sessionWatcher } from './watcher.js';\nimport { getQueue, getDeliveries } from './message-queue.js';\nimport { getSettings, isClaudeMuxSessionName } from '../db/settings-json.js';\nimport type { ChangesInfo, DeliveryInfo, IssueInfo, QueuedMessageInfo, SessionsWsMessage, SystemStatsMessage, UpgradeStatus } from '../types/ws-messages.js';\nimport { inboxTickets, issueFor, watchSessionRepos } from './github.js';\nimport { projectReports } from './project-icons.js';\nimport { pluginReader } from './plugins.js';\nimport { PastSessionIndex, withoutLive } from '../transcript/past-sessions.js';\nimport type { PastSession } from '../past-session.js';\nimport type { PlainPane } from '../fleet-places.js';\nimport { peekGitSummary } from './git.js';\nimport { sessionChanges } from '../transcript/changes.js';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface WsConfig {\n\t/** Max clients for sessions endpoint (default: 50) */\n\tmaxSessionsClients?: number;\n\t/** Max clients per terminal target (default: 10) */\n\tmaxTerminalClientsPerTarget?: number;\n\t/** Max total terminal clients across all targets (default: 100) */\n\tmaxTerminalClientsTotal?: number;\n\t/** Max queued messages before dropping slow client (default: 100) */\n\tmaxQueuedMessages?: number;\n\t/** Enable debug logging (default: false) */\n\tdebug?: boolean;\n}\n\nconst DEFAULT_CONFIG: Required<WsConfig> = {\n\tmaxSessionsClients: 50,\n\tmaxTerminalClientsPerTarget: 10,\n\tmaxTerminalClientsTotal: 100,\n\tmaxQueuedMessages: 100,\n\tdebug: false\n};\n\n// ============================================================================\n// Logging\n// ============================================================================\n\ntype LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\ninterface LogEntry {\n\tlevel: LogLevel;\n\tcomponent: string;\n\tmessage: string;\n\tdata?: Record<string, unknown>;\n}\n\nfunction log(entry: LogEntry, config: Required<WsConfig>): void {\n\tif (entry.level === 'debug' && !config.debug) return;\n\n\tconst prefix = `[ws:${entry.component}]`;\n\tconst msg = entry.data\n\t\t? `${prefix} ${entry.message} ${JSON.stringify(entry.data)}`\n\t\t: `${prefix} ${entry.message}`;\n\n\tswitch (entry.level) {\n\t\tcase 'debug':\n\t\t\tconsole.debug(msg);\n\t\t\tbreak;\n\t\tcase 'info':\n\t\t\tconsole.info(msg);\n\t\t\tbreak;\n\t\tcase 'warn':\n\t\t\tconsole.warn(msg);\n\t\t\tbreak;\n\t\tcase 'error':\n\t\t\tconsole.error(msg);\n\t\t\tbreak;\n\t}\n}\n\n// ============================================================================\n// Generic WebSocket client interface\n// ============================================================================\n\nexport interface WsClient {\n\tsend: (data: string) => void;\n\tisOpen: () => boolean;\n\tclose: () => void;\n\t/** Optional: Get buffered amount for backpressure detection */\n\tgetBufferedAmount?: () => number;\n\t/** Optional: send without per-message deflate, for payloads that are compressed already. */\n\tsendUncompressed?: (data: string) => void;\n}\n\n// ============================================================================\n// Message types (sessions WS uses shared schemas from types/ws-messages.ts)\n// ============================================================================\n\nexport type { SessionsWsMessage, SystemStatsMessage } from '../types/ws-messages.js';\nexport type { SubagentPayload } from '../transcript/subagent.js';\n\n/** Visible pane changed (or initial). `historySize` is the absolute index right after the last history line. */\nexport interface ScreenMessage {\n\ttype: 'screen';\n\tlines: string[];\n\thistorySize: number;\n\talt: boolean;\n\tcols: number;\n\trows: number;\n\ttimestamp: number;\n}\n\n/** A contiguous block of history lines [start, start+lines.length). */\nexport interface HistoryMessage {\n\ttype: 'history';\n\tstart: number;\n\tlines: string[];\n\thistorySize: number;\n\ttimestamp: number;\n}\n\n/** History became invalid (pane reflowed on resize, cleared, alt-screen toggled). Client drops history and re-requests. */\nexport interface ResetMessage {\n\ttype: 'reset';\n\thistorySize: number;\n\talt: boolean;\n\tcols: number;\n\trows: number;\n\ttimestamp: number;\n}\n\nexport type TerminalMessage = ScreenMessage | HistoryMessage | ResetMessage;\n\nexport interface ResizeMessage {\n\ttype: 'resize';\n\tcols: number;\n\trows: number;\n}\n\n/** Client asks for `count` history lines ending right before absolute index `before`. */\ninterface HistoryRequestMessage {\n\ttype: 'history_request';\n\tbefore: number;\n\tcount: number;\n}\n\ninterface SubagentRequestMessage {\n\ttype: 'subagent_request';\n\tagentId: string;\n}\n\nconst DEFAULT_HISTORY_DEPTH = 200;\nconst MAX_HISTORY_DEPTH = 5000;\n/** Max lines served per history_request */\nconst MAX_HISTORY_REQUEST = 1000;\n\n// ============================================================================\n// Session Helpers\n// ============================================================================\n\n\nfunction deduplicateByTmuxTarget<T extends { tmux_target: string | null; last_update: number }>(\n\tsessions: T[]\n): T[] {\n\tconst byTarget = new Map<string, T>();\n\tconst noTarget: T[] = [];\n\tfor (const session of sessions) {\n\t\tif (!session.tmux_target) {\n\t\t\tnoTarget.push(session);\n\t\t\tcontinue;\n\t\t}\n\t\tconst existing = byTarget.get(session.tmux_target);\n\t\tif (!existing || session.last_update > existing.last_update) {\n\t\t\tbyTarget.set(session.tmux_target, session);\n\t\t}\n\t}\n\treturn [...byTarget.values(), ...noTarget];\n}\n\n/** Consecutive ticks a busy session's pane has looked idle (Esc edge case). */\nconst idleLookTicks = new Map<string, number>();\n\n/**\n * Capture each target's pane once per refresh and run all content-derived\n * checks (interruption, spinner, RC URL) off that single capture instead of\n * re-spawning `tmux capture-pane` three times per session per tick.\n */\n/**\n * The hook said the turn is paused on a background agent, and its tool events\n * keep the file fresh while it works. A count that has not been touched for\n * this long is a leftover — an agent that died, a file written by an older\n * hook — and the pane's ready prompt is believed over it, or the session\n * would sit busy forever and its queue never drain.\n */\nconst PAUSE_TRUST_MS = 10 * 60 * 1000;\n\nfunction pausedOnBackground(session: Session): boolean {\n\treturn (session.background_tasks ?? 0) > 0 && Date.now() - session.last_update < PAUSE_TRUST_MS;\n}\n\nasync function captureAndSyncSessions(): Promise<{\n\tcaptures: Map<string, string>;\n\t/** Same captures with ANSI intact, for reading the prompt box. */\n\trawCaptures: Map<string, string>;\n\tsessions: Session[];\n}> {\n\tconst sessions = getAllSessions();\n\tconst captures = new Map<string, string>();\n\tconst rawCaptures = new Map<string, string>();\n\n\t/** Persist and mirror onto the in-memory copy, so callers need no re-read. */\n\tconst apply = (session: Session, patch: Partial<Session>) => {\n\t\tupdateSession(session.id, patch);\n\t\tObject.assign(session, patch);\n\t};\n\n\tawait Promise.all(\n\t\tsessions.map(async (session) => {\n\t\t\tif (!session.tmux_target) return;\n\t\t\t// Captured with colour: the prompt box needs ANSI to tell text the\n\t\t\t// user typed from Claude Code's faint ghost text. Every other check\n\t\t\t// reads the stripped copy.\n\t\t\tconst raw = await capturePaneContentAsync(session.tmux_target, true);\n\t\t\tif (!raw) return;\n\t\t\tconst content = stripAnsi(raw);\n\t\t\tcaptures.set(session.tmux_target, content);\n\t\t\trawCaptures.set(session.tmux_target, raw);\n\n\t\t\tif (session.state !== 'idle' && detectRecentInterruption(content)) {\n\t\t\t\tapply(session, { state: 'idle', current_action: null, prompt_text: null });\n\t\t\t} else if (\n\t\t\t\tsession.state === 'busy' &&\n\t\t\t\t!pausedOnBackground(session) &&\n\t\t\t\tisPaneShowingIdlePrompt(content)\n\t\t\t) {\n\t\t\t\t// Esc during thinking leaves no \"Interrupted\" marker; the pane just\n\t\t\t\t// returns to the ready prompt. Debounce two ticks to avoid flapping\n\t\t\t\t// on transient frames between tools. A turn paused on a background\n\t\t\t\t// agent shows that same ready prompt on purpose; the hook said so.\n\t\t\t\tconst ticks = (idleLookTicks.get(session.id) ?? 0) + 1;\n\t\t\t\tif (ticks >= 2) {\n\t\t\t\t\tidleLookTicks.delete(session.id);\n\t\t\t\t\tapply(session, { state: 'idle', current_action: null, prompt_text: null });\n\t\t\t\t} else {\n\t\t\t\t\tidleLookTicks.set(session.id, ticks);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tidleLookTicks.delete(session.id);\n\t\t\t}\n\t\t\t// The hooks say idle but the pane is spinning: compaction, which fires\n\t\t\t// no hook. The spinner's own words are the action.\n\t\t\tif (session.state === 'idle') {\n\t\t\t\tconst activity = readActivityLine(content);\n\t\t\t\tif (activity) apply(session, { state: 'busy', current_action: activity });\n\t\t\t}\n\t\t})\n\t);\n\n\t// Drop debounce counters for sessions that vanished mid-count.\n\tif (idleLookTicks.size > 0) {\n\t\tconst live = new Set(sessions.map((s) => s.id));\n\t\tfor (const id of idleLookTicks.keys()) {\n\t\t\tif (!live.has(id)) idleLookTicks.delete(id);\n\t\t}\n\t}\n\n\treturn { captures, rawCaptures, sessions };\n}\n\n/**\n * The spinner as it rides the broadcast: the turn's start in place of its\n * elapsed count, which moves every second and would otherwise send the whole\n * sessions message to every client every tick. The browser counts up itself.\n */\ntype LiveActivity = Omit<PaneActivity, 'elapsed_s'> & { started_at: number | null };\n\n/** When each running turn started, as first worked out from the pane. */\nconst turnStarts = new Map<string, number>();\n\nfunction liveActivity(sessionId: string, activity: PaneActivity | null): LiveActivity | null {\n\tif (!activity) {\n\t\tturnStarts.delete(sessionId);\n\t\treturn null;\n\t}\n\tconst { elapsed_s, ...rest } = activity;\n\tif (elapsed_s === null) return { ...rest, started_at: turnStarts.get(sessionId) ?? null };\n\t// Whole seconds read at poll time put the start anywhere in a window of a\n\t// second or so; a reading inside the tolerance is the same turn.\n\tconst start = Date.now() - elapsed_s * 1000;\n\tconst known = turnStarts.get(sessionId);\n\tif (known !== undefined && Math.abs(start - known) <= 2000) return { ...rest, started_at: known };\n\tturnStarts.set(sessionId, start);\n\treturn { ...rest, started_at: start };\n}\n\n/**\n * Async version of getEnrichedSessions. Uses batched tmux calls\n * and concurrent interruption checks to avoid blocking the event loop.\n */\n/** Panes with a widen in flight, so a slow tmux is not asked twice. */\nconst wideningNow = new Set<string>();\n\n/** Sessions this process has already seen, so `/rc` goes only to new ones. */\nconst rcSeen = new Set<string>();\n/** False on the first poll, which only primes `rcSeen` with what already runs. */\nlet rcPrimed = false;\n\n/**\n * The transcript title each session was last seen to carry, so a name is\n * applied when Claude Code changes it and not on every tick that it differs.\n */\nconst transcriptTitleSeen = new Map<string, string>();\n\n/**\n * A `/rename` typed inside Claude Code goes through none of the hooks — slash\n * commands never reach UserPromptSubmit — so the only trace is the\n * `custom-title` record in the transcript. Mirror a change there into\n * `display_name`. The other direction (renamed from the dashboard) sends the\n * same `/rename` into the pane, so the record it produces already matches\n * and nothing is written twice.\n */\nfunction syncTranscriptTitle(s: Session): void {\n\tif ((s.agent ?? 'claude') !== 'claude') return;\n\tconst title = peekTranscriptTitle(s);\n\tif (title === undefined || transcriptTitleSeen.get(s.id) === title) return;\n\ttranscriptTitleSeen.set(s.id, title);\n\tconst name = sanitizeDisplayName(title);\n\tif (name === (s.display_name ?? null)) return;\n\tupdateSession(s.id, { display_name: name });\n\ts.display_name = name;\n}\n\n/**\n * The change count the sidebar shows: git's when the session is in a repo,\n * since git sees Bash edits too; the session's own log otherwise. Neither\n * waits: git is read in the background and answers on a later tick.\n */\nfunction liveChanges(s: Session): ChangesInfo | null {\n\tconst git = peekGitSummary(s.cwd);\n\tif (git) return { ...git, source: 'git' };\n\tif (git === undefined) return null;\n\tconst summary = sessionChanges(s)?.summary();\n\treturn summary ? { ...summary, source: 'session' } : null;\n}\n\nexport async function getEnrichedSessionsAsync(): Promise<(Session & LivePaneFields)[]> {\n\treturn (await readSessionsAndPanes()).sessions;\n}\n\n/**\n * The tmux panes no Claude session runs in (a shell, a dev server), which the\n * drawer places under their projects beside the sessions.\n */\nfunction plainPanes(panes: Map<string, PaneInfo>, sessions: Session[]): PlainPane[] {\n\tconst claude = new Set(sessions.map((s) => s.tmux_target));\n\tconst out: PlainPane[] = [];\n\tfor (const [target, pane] of panes) {\n\t\tif (claude.has(target)) continue;\n\t\tout.push({ target, session: target.split(':')[0], command: pane.command, cwd: pane.cwd, title: pane.title });\n\t}\n\treturn out;\n}\n\n/** This machine's sessions, enriched with what their panes show, and its plain panes, off one `list-panes`. */\nasync function readSessionsAndPanes(): Promise<{ sessions: (Session & LivePaneFields)[]; panes: PlainPane[] }> {\n\tconst [{ captures, rawCaptures, sessions }, allPanes] = await Promise.all([\n\t\tcaptureAndSyncSessions(),\n\t\tlistAllPanes()\n\t]);\n\n\tconst links = readLinks();\n\n\t// A pane nobody is attached to is held at the standard width, so the\n\t// screen the readers above parse is laid out the same way every time.\n\t// Fire-and-forget: the resize lands before the next tick's capture.\n\tfor (const s of sessions) {\n\t\tconst pane = s.tmux_target ? allPanes.get(s.tmux_target) : undefined;\n\t\tif (pane && wantsWidening(pane) && !wideningNow.has(s.tmux_target!)) {\n\t\t\tconst target = s.tmux_target!;\n\t\t\twideningNow.add(target);\n\t\t\tvoid widenDetachedWindow(target).finally(() => wideningNow.delete(target));\n\t\t}\n\t}\n\n\t// Remote Control for the sessions claude-mux itself made, when asked to.\n\t// A session is \"new\" when this process first sees it; everything alive at\n\t// startup is left as it is. Workers other tools start straight in tmux do\n\t// not carry a claude-mux name and are not touched.\n\tconst auto = getSettings().autoRemoteControl;\n\tfor (const s of sessions) {\n\t\tif (rcSeen.has(s.id)) continue;\n\t\trcSeen.add(s.id);\n\t\tif (!rcPrimed || !auto) continue;\n\t\tif (s.rc_url || !s.tmux_target || (s.agent ?? 'claude') !== 'claude') continue;\n\t\tif (!isClaudeMuxSessionName(s.tmux_target.split(':')[0])) continue;\n\t\tsetRemoteControl(s, true);\n\t}\n\trcPrimed = true;\n\n\tfor (const s of sessions) syncTranscriptTitle(s);\n\n\t// GitHub holds the other half of what a session is waiting on: the issue a\n\t// maestro worker owns, and the tickets in its repo that want a person.\n\twatchSessionRepos(sessions);\n\n\t// Remote Control: Claude Code's own record of the process says whether it\n\t// is on, the pane only where that record is silent. The dialog `/rc` draws\n\t// is answered here too, since nobody at the dashboard can see it.\n\tfor (const s of sessions) {\n\t\tif (!s.tmux_target) continue;\n\t\tconst content = captures.get(s.tmux_target);\n\t\tif (!content) continue;\n\t\tconst rcUrl = currentRemoteControlUrl(s, content);\n\t\tif (rcUrl !== (s.rc_url ?? null)) {\n\t\t\tupdateSession(s.id, { rc_url: rcUrl });\n\t\t\ts.rc_url = rcUrl;\n\t\t}\n\t\tvoid answerRemoteControlDialog(s.tmux_target, content, rcUrl);\n\t}\n\n\tconst visits = getVisits();\n\tconst enrichedSessions = sessions.map((s) => {\n\t\tconst paneTitle = s.tmux_target ? (allPanes.get(s.tmux_target)?.title ?? null) : null;\n\t\tconst raw = s.tmux_target ? rawCaptures.get(s.tmux_target) : undefined;\n\t\t// Stripped once per tick by the caller; the option reader needs no colour.\n\t\tconst stripped = s.tmux_target ? captures.get(s.tmux_target) : undefined;\n\t\t// Live only: what sits in the prompt box right now, so the transcript\n\t\t// view can show it without the terminal mirror.\n\t\tconst box = raw ? readPromptBox(raw) : null;\n\t\tconst enriched: Session & LivePaneFields = {\n\t\t\t...s,\n\t\t\tpane_title: paneTitle,\n\t\t\tpane_alive: s.tmux_target ? allPanes.has(s.tmux_target) : true,\n\t\t\tdraft_input: box?.text ?? null,\n\t\t\tdraft_kind: box?.kind ?? null,\n\t\t\t// Messages the user typed into the pane while it was busy, waiting\n\t\t\t// their turn in Claude Code's own queue.\n\t\t\tpane_queue: raw ? readQueuedMessages(raw) : [],\n\t\t\t// The dialog's own numbered rows, so the composer can offer them as\n\t\t\t// answers instead of making you drive arrows at a screen you can't see.\n\t\t\t// When the hooks report a dialog, any run at the foot is one. When\n\t\t\t// they don't — a local command's dialog such as `/model` leaves the\n\t\t\t// session idle, and the permission notification can trail the dialog\n\t\t\t// by a tick — only a dialog that names its own keys is believed.\n\t\t\tpane_choice: stripped\n\t\t\t\t? readPromptOptions(stripped, {\n\t\t\t\t\t\tdeclaredOnly: s.state !== 'waiting' && s.state !== 'permission'\n\t\t\t\t\t})\n\t\t\t\t: null,\n\t\t\t// Claude Code says so only in its footer, which the transcript never sees.\n\t\t\tpane_update: stripped ? readUpdateNotice(stripped) : null,\n\t\t\tpane_login: stripped ? readLoginScreen(stripped) : null,\n\t\t\tpane_rewind: stripped ? readRewindScreen(stripped) : null,\n\t\t\tupgrade: upgradeStatus(s.id),\n\t\t\t// The spinner says more than the hooks do: what runs, for how long,\n\t\t\t// how many tokens. Busy only, so a frame left over never outlives the turn.\n\t\t\tpane_activity: liveActivity(s.id, stripped && s.state === 'busy' ? readActivity(stripped) : null),\n\t\t\t// One stat per session per tick; the file is only read when it grew.\n\t\t\tcontext_pct: peekContextPercent(s),\n\t\t\tissue: issueFor(s.git_root, s.maestro_issue),\n\t\t\t// Whole, so the composer can draw and edit it without polling the queue route.\n\t\t\tqueue: s.tmux_target\n\t\t\t\t? getQueue(s.tmux_target).map(({ attempts: _attempts, ...info }) => info)\n\t\t\t\t: [],\n\t\t\tdelivered: s.tmux_target ? getDeliveries(s.tmux_target) : [],\n\t\t\tlast_visited_at: visits[s.id] ?? null,\n\t\t\tchanges: liveChanges(s),\n\t\t};\n\n\t\tif (s.tmux_target && links[s.tmux_target]) {\n\t\t\tconst mainTarget = links[s.tmux_target];\n\t\t\tconst mainSession = sessions.find((m) => m.tmux_target === mainTarget);\n\t\t\tif (mainSession) {\n\t\t\t\tenriched.linked_to = mainSession.id;\n\t\t\t}\n\t\t}\n\n\t\treturn enriched;\n\t});\n\n\treturn { sessions: deduplicateByTmuxTarget(enrichedSessions), panes: plainPanes(allPanes, sessions) };\n}\n\n// ============================================================================\n// Terminal Helpers\n// ============================================================================\n\nexport function capturePaneOutput(target: string, depth: number = DEFAULT_HISTORY_DEPTH): string | null {\n\ttry {\n\t\tconst lines = Math.max(1, Math.min(MAX_HISTORY_DEPTH, Math.floor(depth)));\n\t\treturn execFileSync('tmux', ['capture-pane', '-t', target, '-p', '-e', '-S', `-${lines}`], {\n\t\t\tencoding: 'utf-8',\n\t\t\tstdio: ['pipe', 'pipe', 'pipe'],\n\t\t\ttimeout: 2000\n\t\t});\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function resizePane(target: string, cols: number, rows: number): void {\n\tresizeTmuxWindow(target, cols, rows);\n}\n\n// ============================================================================\n// System Stats (CPU/RAM/Swap from /proc, disk from statfs, cached every 10s)\n// ============================================================================\n\nlet cachedSystemStats: { cpu: number; ram: number; swap: number; ramTotal: number; swapTotal: number; disk: number | null } = {\n\tcpu: 0,\n\tram: 0,\n\tswap: 0,\n\tramTotal: 0,\n\tswapTotal: 0,\n\tdisk: null\n};\nlet prevCpuIdle = 0;\nlet prevCpuTotal = 0;\nlet systemStatsTimer: ReturnType<typeof setInterval> | null = null;\nlet systemStatsRefCount = 0;\n\nfunction refreshSystemStats(): void {\n\tvoid homeDiskPercent().then((disk) => {\n\t\tcachedSystemStats.disk = disk;\n\t});\n\ttry {\n\t\tconst stat = readFileSync('/proc/stat', 'utf-8');\n\t\tconst meminfo = readFileSync('/proc/meminfo', 'utf-8');\n\n\t\t// CPU\n\t\tconst cpuLine = stat.split('\\n')[0];\n\t\tconst cpuParts = cpuLine.split(/\\s+/).slice(1).map(Number);\n\t\tconst idle = cpuParts[3] + (cpuParts[4] || 0);\n\t\tconst total = cpuParts.reduce((a, b) => a + b, 0);\n\t\tconst diffIdle = idle - prevCpuIdle;\n\t\tconst diffTotal = total - prevCpuTotal;\n\t\tconst cpuPercent = diffTotal > 0 ? Math.round((1 - diffIdle / diffTotal) * 100) : 0;\n\t\tprevCpuIdle = idle;\n\t\tprevCpuTotal = total;\n\n\t\t// Memory\n\t\tconst mem: Record<string, number> = {};\n\t\tfor (const line of meminfo.split('\\n')) {\n\t\t\tconst match = line.match(/^(\\w+):\\s+(\\d+)/);\n\t\t\tif (match) mem[match[1]] = parseInt(match[2], 10);\n\t\t}\n\t\tconst memTotal = mem['MemTotal'] || 1;\n\t\tconst memAvailable = mem['MemAvailable'] || 0;\n\t\tconst swapTotal = mem['SwapTotal'] || 0;\n\t\tconst swapFree = mem['SwapFree'] || 0;\n\n\t\tcachedSystemStats = {\n\t\t\tcpu: cpuPercent,\n\t\t\tram: Math.round(((memTotal - memAvailable) / memTotal) * 100),\n\t\t\tswap: swapTotal > 0 ? Math.round(((swapTotal - swapFree) / swapTotal) * 100) : 0,\n\t\t\tramTotal: Math.round(memTotal / 1024),\n\t\t\tswapTotal: Math.round(swapTotal / 1024),\n\t\t\tdisk: cachedSystemStats.disk\n\t\t};\n\t} catch {\n\t\t// /proc not available (non-Linux), keep last values\n\t}\n}\n\nfunction startSystemStats(): void {\n\tsystemStatsRefCount++;\n\tif (systemStatsRefCount === 1) {\n\t\trefreshSystemStats(); // initial read\n\t\tsystemStatsTimer = setInterval(refreshSystemStats, 10_000);\n\t}\n}\n\nfunction stopSystemStats(): void {\n\tsystemStatsRefCount--;\n\tif (systemStatsRefCount <= 0) {\n\t\tsystemStatsRefCount = 0;\n\t\tif (systemStatsTimer) {\n\t\t\tclearInterval(systemStatsTimer);\n\t\t\tsystemStatsTimer = null;\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Sessions WebSocket Manager\n// ============================================================================\n\n/**\n * Where the fleet message comes from: the fleet state, which the web server\n * starts and hands over (`web/src/lib/server/fleet.ts`).\n */\nexport interface FleetSource {\n\tselfId: string;\n\tsnapshot(): unknown[];\n\tsubscribe(listener: () => void): () => void;\n\t/** Every other machine's past sessions, by machine id; they change seldom and go in a message of their own. */\n\tpast(): Record<string, unknown[]>;\n\tsubscribePast(listener: () => void): () => void;\n}\n\nconst FLEET_COALESCE_MS = 200;\n/** How often the transcripts on disk are listed again for the past sessions. */\nconst PAST_REFRESH_MS = 30_000;\n\n/**\n * Where the fleet preferences come from (`src/server/fleet-prefs.ts`). Unlike\n * the fleet, they go to every client: a page reads them, and a peer that\n * follows this machine merges them into its own. Anyone but a peer is sent\n * them without the keys the servers keep among themselves (`forPages()`).\n */\nexport interface PrefsSource {\n\tall(): unknown;\n\tforPages(): unknown;\n\tsubscribe(listener: () => void): () => void;\n}\n\nexport class SessionsWsManager {\n\tprivate clients = new Set<WsClient>();\n\t/** The clients that asked for the fleet too (`?fleet=1`): pages, not peers. */\n\tprivate fleetClients = new Set<WsClient>();\n\t/** Other machines' fleet states (`isPeerLink()`): the only clients sent the whole preference set. */\n\tprivate peerClients = new Set<WsClient>();\n\tprivate fleetSource: FleetSource | null = null;\n\tprivate stopFleet: (() => void) | null = null;\n\tprivate prefsSource: PrefsSource | null = null;\n\tprivate stopPrefs: (() => void) | null = null;\n\tprivate unsubscribe: (() => void) | null = null;\n\tprivate interruptCheckTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate systemStatsTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate lastHash = '';\n\tprivate lastStatsHash = '';\n\tprivate config: Required<WsConfig>;\n\tprivate droppedClients = 0;\n\tprivate refreshing = false;\n\tprivate refreshQueued = false;\n\t/** This machine's transcripts, listed in the background (`src/transcript/past-sessions.ts`). */\n\tprivate pastIndex = new PastSessionIndex();\n\t/** Every transcript on disk, live ones included; null until first listed. */\n\tprivate pastAll: PastSession[] | null = null;\n\t/** The sessions the last broadcast listed, which are not past. */\n\tprivate liveIds: Set<string> | null = null;\n\tprivate lastPastHash = '';\n\t/** What the last past check saw, so a tick that changed neither skips the filter and the hash. */\n\tprivate lastPastInput: { all: PastSession[]; live: string } | null = null;\n\tprivate pastTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate stopFleetPast: (() => void) | null = null;\n\n\tconstructor(config?: WsConfig) {\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\t/** Get current stats */\n\tgetStats(): { clients: number; droppedClients: number } {\n\t\treturn {\n\t\t\tclients: this.clients.size,\n\t\t\tdroppedClients: this.droppedClients\n\t\t};\n\t}\n\n\t/**\n\t * Add a client. Returns false if max clients reached.\n\t */\n\taddClient(client: WsClient, options: { fleet?: boolean; peer?: boolean } = {}): boolean {\n\t\tif (this.clients.size >= this.config.maxSessionsClients) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\tcomponent: 'sessions',\n\t\t\t\t\tmessage: 'Max clients reached, rejecting connection',\n\t\t\t\t\tdata: { current: this.clients.size, max: this.config.maxSessionsClients }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.clients.add(client);\n\t\tif (options.fleet) this.fleetClients.add(client);\n\t\tif (options.peer) this.peerClients.add(client);\n\t\tlog(\n\t\t\t{\n\t\t\t\tlevel: 'debug',\n\t\t\t\tcomponent: 'sessions',\n\t\t\t\tmessage: 'Client connected',\n\t\t\t\tdata: { total: this.clients.size }\n\t\t\t},\n\t\t\tthis.config\n\t\t);\n\n\t\tif (this.clients.size === 1 && !this.unsubscribe) {\n\t\t\tconsole.log('[ws:sessions] First client, subscribing to watcher');\n\t\t\tthis.unsubscribe = sessionWatcher.subscribe(() => this.refreshAndBroadcast());\n\t\t\t// Start interrupt check timer - runs independently of file changes\n\t\t\t// to catch interruptions triggered by web UI (Escape key).\n\t\t\t// Uses async getEnrichedSessionsAsync to avoid blocking the event loop.\n\t\t\tthis.interruptCheckTimer = setInterval(() => {\n\t\t\t\tthis.refreshAndBroadcast();\n\t\t\t}, 500);\n\t\t\t// Cleanup stale sessions (dead PIDs) periodically\n\t\t\ttry { cleanupStaleSessions(); } catch { /* best-effort */ }\n\t\t\tthis.cleanupTimer = setInterval(() => {\n\t\t\t\ttry { cleanupStaleSessions(); } catch { /* best-effort */ }\n\t\t\t}, 10_000);\n\t\t\t// Start system stats: refresh every 10s, broadcast separately\n\t\t\tstartSystemStats();\n\t\t\tthis.systemStatsTimer = setInterval(() => {\n\t\t\t\tthis.broadcastSystemStats();\n\t\t\t}, 10_000);\n\t\t\tvoid this.refreshPast();\n\t\t\tthis.pastTimer = setInterval(() => void this.refreshPast(), PAST_REFRESH_MS);\n\t\t} else {\n\t\t\tconsole.log('[ws:sessions] addClient: clients=', this.clients.size, 'hasUnsubscribe=', !!this.unsubscribe);\n\t\t}\n\t\t// Send initial state asynchronously (batched tmux call is more reliable for pane_alive)\n\t\tthis.createSessionsMessageAsync('connected').then((msg) => {\n\t\t\tif (client.isOpen()) {\n\t\t\t\tthis.sendToClient(client, msg);\n\t\t\t\tthis.sendToClient(client, this.createSystemStatsMessage());\n\t\t\t\tif (options.fleet && this.fleetSource) {\n\t\t\t\t\tthis.sendToClient(client, this.createFleetMessage(this.fleetSource));\n\t\t\t\t\tthis.sendToClient(client, this.createFleetPastMessage(this.fleetSource));\n\t\t\t\t}\n\t\t\t\tif (this.prefsSource) this.sendToClient(client, this.createPrefsMessage(this.prefsSource, !!options.peer));\n\t\t\t\tif (this.pastAll && this.liveIds) this.sendToClient(client, this.createPastMessage(this.pastAll, this.liveIds));\n\t\t\t}\n\t\t});\n\t\treturn true;\n\t}\n\n\t/** Start sending the fleet to the pages that ask for it, from `source` from now on. */\n\tsetFleetSource(source: FleetSource): void {\n\t\tthis.stopFleet?.();\n\t\tthis.stopFleetPast?.();\n\t\tthis.fleetSource = source;\n\t\tthis.stopFleet = source.subscribe(() => this.scheduleFleet());\n\t\tthis.stopFleetPast = source.subscribePast(() => {\n\t\t\tif (this.fleetClients.size > 0) this.broadcastTo(this.fleetClients, this.createFleetPastMessage(source));\n\t\t});\n\t}\n\n\tprivate createFleetPastMessage(source: FleetSource) {\n\t\treturn { type: 'fleet-past' as const, machines: source.past(), timestamp: Date.now() };\n\t}\n\n\t/**\n\t * This machine's past sessions: the transcripts on disk that no session in\n\t * the broadcast holds. Sent to every client, so a peer keeps them in its\n\t * fleet state and a page lists them, on connect and whenever they change.\n\t */\n\tprivate createPastMessage(all: PastSession[], live: ReadonlySet<string>) {\n\t\treturn { type: 'past' as const, sessions: withoutLive(all, live), timestamp: Date.now() };\n\t}\n\n\tprivate pastListing = false;\n\n\tprivate async refreshPast(): Promise<void> {\n\t\t// A first listing of a large disk can outlast the interval; never run two.\n\t\tif (this.pastListing) return;\n\t\tthis.pastListing = true;\n\t\ttry {\n\t\t\tthis.pastAll = await this.pastIndex.list();\n\t\t} catch (err) {\n\t\t\tconsole.error('[ws:sessions] listing past sessions failed:', err);\n\t\t\treturn;\n\t\t} finally {\n\t\t\tthis.pastListing = false;\n\t\t}\n\t\tthis.broadcastPastIfChanged();\n\t}\n\n\tprivate broadcastPastIfChanged(): void {\n\t\t// Not before the sessions are known, or a live one would pass for past.\n\t\tif (!this.pastAll || !this.liveIds || this.clients.size === 0) return;\n\t\tconst live = [...this.liveIds].sort().join('\\n');\n\t\tif (this.lastPastInput?.all === this.pastAll && this.lastPastInput.live === live) return;\n\t\tthis.lastPastInput = { all: this.pastAll, live };\n\t\tconst message = this.createPastMessage(this.pastAll, this.liveIds);\n\t\tconst hash = JSON.stringify(message.sessions);\n\t\tif (hash === this.lastPastHash) return;\n\t\tthis.lastPastHash = hash;\n\t\tthis.broadcastTo(this.clients, message);\n\t}\n\n\t/** Send the fleet preferences to every client, from `source` from now on, and again on each change. */\n\tsetPrefsSource(source: PrefsSource): void {\n\t\tthis.stopPrefs?.();\n\t\tthis.prefsSource = source;\n\t\tthis.stopPrefs = source.subscribe(() => this.broadcastPrefs());\n\t}\n\n\t/** The set as a peer, or anyone else, is sent it. */\n\tprivate createPrefsMessage(source: PrefsSource, peer: boolean) {\n\t\treturn { type: 'prefs' as const, prefs: peer ? source.all() : source.forPages(), timestamp: Date.now() };\n\t}\n\n\tprivate broadcastPrefs(): void {\n\t\tif (!this.prefsSource || this.clients.size === 0) return;\n\t\tconst others = new Set([...this.clients].filter((c) => !this.peerClients.has(c)));\n\t\tthis.broadcastTo(this.peerClients, this.createPrefsMessage(this.prefsSource, true));\n\t\tthis.broadcastTo(others, this.createPrefsMessage(this.prefsSource, false));\n\t}\n\n\tprivate fleetTimer: ReturnType<typeof setTimeout> | null = null;\n\n\t/** Peers change together (each polls every 500ms), so their changes go out as one message. */\n\tprivate scheduleFleet(): void {\n\t\tif (this.fleetTimer) return;\n\t\tthis.fleetTimer = setTimeout(() => {\n\t\t\tthis.fleetTimer = null;\n\t\t\tthis.broadcastFleet();\n\t\t}, FLEET_COALESCE_MS);\n\t}\n\n\tprivate createFleetMessage(source: FleetSource) {\n\t\treturn { type: 'fleet' as const, self: source.selfId, machines: source.snapshot(), timestamp: Date.now() };\n\t}\n\n\tprivate broadcastFleet(): void {\n\t\tif (!this.fleetSource || this.fleetClients.size === 0) return;\n\t\tthis.broadcastTo(this.fleetClients, this.createFleetMessage(this.fleetSource));\n\t}\n\n\t/** Send `message` to each of `clients`, dropping any that cannot take it; returns those dropped. */\n\tprivate broadcastTo(clients: Set<WsClient>, message: SessionsWsMessage | Record<string, unknown>): WsClient[] {\n\t\tconst data = JSON.stringify(message);\n\t\tconst drops = [...clients].filter((client) => !this.sendToClient(client, message, data));\n\t\tfor (const client of drops) this.dropClient(client);\n\t\treturn drops;\n\t}\n\n\t/**\n\t * A client whose send failed: out of both sets, counted, and closed, so a\n\t * page reconnects and a peer's fleet state sees it go rather than keep a\n\t * socket that answers pings and is sent nothing.\n\t */\n\tprivate dropClient(client: WsClient): void {\n\t\tthis.clients.delete(client);\n\t\tthis.fleetClients.delete(client);\n\t\tthis.peerClients.delete(client);\n\t\tthis.droppedClients++;\n\t\ttry {\n\t\t\tclient.close();\n\t\t} catch {\n\t\t\t// Already gone.\n\t\t}\n\t}\n\n\tremoveClient(client: WsClient): void {\n\t\tthis.fleetClients.delete(client);\n\t\tthis.peerClients.delete(client);\n\t\tconst had = this.clients.delete(client);\n\t\tif (had) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'debug',\n\t\t\t\t\tcomponent: 'sessions',\n\t\t\t\t\tmessage: 'Client disconnected',\n\t\t\t\t\tdata: { total: this.clients.size }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t}\n\n\t\tif (this.clients.size === 0 && this.unsubscribe) {\n\t\t\tthis.unsubscribe();\n\t\t\tthis.unsubscribe = null;\n\t\t\tthis.lastHash = '';\n\t\t\t// Stop interrupt check timer\n\t\t\tif (this.interruptCheckTimer) {\n\t\t\t\tclearInterval(this.interruptCheckTimer);\n\t\t\t\tthis.interruptCheckTimer = null;\n\t\t\t}\n\t\t\t// Stop cleanup timer\n\t\t\tif (this.cleanupTimer) {\n\t\t\t\tclearInterval(this.cleanupTimer);\n\t\t\t\tthis.cleanupTimer = null;\n\t\t\t}\n\t\t\t// Stop system stats broadcast + polling\n\t\t\tif (this.systemStatsTimer) {\n\t\t\t\tclearInterval(this.systemStatsTimer);\n\t\t\t\tthis.systemStatsTimer = null;\n\t\t\t}\n\t\t\tstopSystemStats();\n\t\t\tif (this.pastTimer) {\n\t\t\t\tclearInterval(this.pastTimer);\n\t\t\t\tthis.pastTimer = null;\n\t\t\t}\n\t\t\tthis.lastPastHash = '';\n\t\t\tthis.lastPastInput = null;\n\t\t}\n\t}\n\n\tprivate async createSessionsMessageAsync(type: 'sessions' | 'connected') {\n\t\tconst { sessions, panes } = await readSessionsAndPanes();\n\t\t// A closed pane's row is not live: its conversation can be resumed from the past list.\n\t\tthis.liveIds = new Set(sessions.filter((s) => s.pane_alive !== false).map((s) => s.id));\n\t\t// The server remembers every directory a session has run in, so the\n\t\t// sidebar's groups outlive their last session and are the same from\n\t\t// every browser. Writes only when a directory is new.\n\t\tsaveProjects(sessions.map((s) => s.cwd).filter((cwd): cwd is string => !!cwd));\n\t\tconst projects = getSavedProjects();\n\t\treturn {\n\t\t\ttype,\n\t\t\tsessions,\n\t\t\tcount: sessions.length,\n\t\t\t// Its panes that run no Claude session, for the drawer to place under their projects.\n\t\t\tpanes,\n\t\t\tprojects,\n\t\t\t// What the fleet matches one project across machines by, and its icon.\n\t\t\tprojectReports: projectReports(projects),\n\t\t\t// Its Claude Code marketplaces and plugins, for the fleet to keep in step.\n\t\t\tplugins: pluginReader().current(),\n\t\t\tsettings: getSettings(),\n\t\t\tinbox: inboxTickets(\n\t\t\t\tnew Set(\n\t\t\t\t\tsessions\n\t\t\t\t\t\t.filter((s) => s.pane_alive && s.maestro_issue && s.git_root)\n\t\t\t\t\t\t.map((s) => `${s.git_root}#${s.maestro_issue}`)\n\t\t\t\t)\n\t\t\t),\n\t\t\ttimestamp: Date.now()\n\t\t};\n\t}\n\n\tprivate createSystemStatsMessage(): SystemStatsMessage {\n\t\treturn { type: 'systemStats' as const, ...cachedSystemStats, timestamp: Date.now() };\n\t}\n\n\t/**\n\t * Public trigger for an immediate fresh broadcast. Used by API mutation\n\t * endpoints (rename/kill/restart) so the UI updates instantly instead of\n\t * waiting up to 500ms for the file watcher poll to detect mtime change.\n\t */\n\tbroadcastNow(): void {\n\t\tthis.refreshAndBroadcast();\n\t}\n\n\tprivate refreshAndBroadcast(): void {\n\t\t// If already refreshing, queue a follow-up refresh so we don't lose\n\t\t// watcher notifications that arrive while async tmux calls are in-flight.\n\t\tif (this.refreshing) {\n\t\t\tthis.refreshQueued = true;\n\t\t\treturn;\n\t\t}\n\t\tthis.refreshing = true;\n\n\t\tthis.createSessionsMessageAsync('sessions')\n\t\t\t.then((message) => {\n\t\t\t\t// (queue draining runs in its own server loop — see message-queue.ts ensureDrainLoop)\n\n\t\t\t\t// The inbox changes on GitHub's clock, not the sessions', so it counts\n\t\t\t\t// too, and so do the project reports and plugins, read in the background\n\t\t\t\t// (a plugins report is never changed once read, so its time stands for it).\n\t\t\t\t// A pane's title is only a tooltip and some programs animate it, so it alone sends nothing.\n\t\t\t\tconst panes = message.panes.map(({ title: _title, ...pane }) => pane);\n\t\t\t\tconst hash = JSON.stringify([message.sessions, panes, message.inbox, message.projectReports, message.plugins?.readAt]);\n\t\t\t\tif (hash === this.lastHash) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// A session that ended is past now, and one resumed is not.\n\t\t\t\tthis.broadcastPastIfChanged();\n\t\t\t\tthis.lastHash = hash;\n\n\t\t\t\tconst dropped = this.broadcastTo(this.clients, message).length;\n\t\t\t\tif (dropped > 0) {\n\t\t\t\t\tlog(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\t\t\tcomponent: 'sessions',\n\t\t\t\t\t\t\tmessage: 'Dropped slow client',\n\t\t\t\t\t\t\tdata: { dropped, total: this.clients.size }\n\t\t\t\t\t\t},\n\t\t\t\t\t\tthis.config\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tlog(\n\t\t\t\t\t{\n\t\t\t\t\t\tlevel: 'error',\n\t\t\t\t\t\tcomponent: 'sessions',\n\t\t\t\t\t\tmessage: 'Refresh failed',\n\t\t\t\t\t\tdata: { error: String(err) }\n\t\t\t\t\t},\n\t\t\t\t\tthis.config\n\t\t\t\t);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.refreshing = false;\n\t\t\t\tif (this.refreshQueued) {\n\t\t\t\t\tthis.refreshQueued = false;\n\t\t\t\t\tthis.refreshAndBroadcast();\n\t\t\t\t}\n\t\t\t});\n\t}\n\n\tprivate broadcastSystemStats(): void {\n\t\tconst message = this.createSystemStatsMessage();\n\t\tconst hash = JSON.stringify(message);\n\t\tif (hash === this.lastStatsHash) return;\n\t\tthis.lastStatsHash = hash;\n\t\tthis.broadcastTo(this.clients, message);\n\t}\n\n\tprivate sendToClient(\n\t\tclient: WsClient,\n\t\tmessage: SessionsWsMessage | Record<string, unknown>,\n\t\tdata?: string\n\t): boolean {\n\t\t// The session list is a small, always-superseded payload: a client that\n\t\t// cannot keep up is dropped rather than waited for.\n\t\treturn (\n\t\t\tsendJson(client, message, this.config, {\n\t\t\t\tcomponent: 'sessions',\n\t\t\t\tmaxBuffered: 64 * 1024,\n\t\t\t\tdata\n\t\t\t}) === 'sent'\n\t\t);\n\t}\n}\n\n/**\n * 'sent' — handed to the socket. 'backpressure' — skipped, because the client\n * is still draining an earlier message. 'closed' — the socket is gone.\n */\ntype SendResult = 'sent' | 'backpressure' | 'closed';\n\n/**\n * Send one JSON message to a client. Every channel manager goes through this,\n * so the backpressure ceiling has one definition per channel and one place to\n * read; what a channel *does* about a slow client is its own call — the\n * session list and the terminal drop it, the transcript catches it up.\n */\nfunction sendJson(\n\tclient: WsClient,\n\tmessage: unknown,\n\tconfig: Required<WsConfig>,\n\topts: { component: string; maxBuffered: number; data?: string }\n): SendResult {\n\ttry {\n\t\tif (!client.isOpen()) return 'closed';\n\t\tif (client.getBufferedAmount) {\n\t\t\tconst buffered = client.getBufferedAmount();\n\t\t\tif (buffered > opts.maxBuffered) {\n\t\t\t\tlog(\n\t\t\t\t\t{\n\t\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\t\tcomponent: opts.component,\n\t\t\t\t\t\tmessage: 'Client backpressure detected',\n\t\t\t\t\t\tdata: { buffered }\n\t\t\t\t\t},\n\t\t\t\t\tconfig\n\t\t\t\t);\n\t\t\t\treturn 'backpressure';\n\t\t\t}\n\t\t}\n\t\tclient.send(opts.data ?? JSON.stringify(message));\n\t\treturn 'sent';\n\t} catch (err) {\n\t\tlog(\n\t\t\t{\n\t\t\t\tlevel: 'error',\n\t\t\t\tcomponent: opts.component,\n\t\t\t\tmessage: 'Failed to send to client',\n\t\t\t\tdata: { error: String(err) }\n\t\t\t},\n\t\t\tconfig\n\t\t);\n\t\treturn 'closed';\n\t}\n}\n\n// ============================================================================\n// Terminal WebSocket Manager\n// ============================================================================\n\ninterface TargetState {\n\thistorySize: number;\n\talt: boolean;\n\tcols: number;\n\trows: number;\n\tscreenKey: string;\n}\n\nexport class TerminalWsManager {\n\tprivate clients = new Map<string, Set<WsClient>>();\n\tprivate pollTimers = new Map<string, ReturnType<typeof setInterval>>();\n\tprivate states = new Map<string, TargetState>();\n\tprivate config: Required<WsConfig>;\n\tprivate totalClients = 0;\n\tprivate droppedClients = 0;\n\n\tconstructor(config?: WsConfig) {\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\t/** Get current stats */\n\tgetStats(): { totalClients: number; targets: number; droppedClients: number } {\n\t\treturn {\n\t\t\ttotalClients: this.totalClients,\n\t\t\ttargets: this.clients.size,\n\t\t\tdroppedClients: this.droppedClients\n\t\t};\n\t}\n\n\t/**\n\t * Add a client for a target. Returns false if max clients reached.\n\t */\n\taddClient(client: WsClient, target: string): boolean {\n\t\t// Check total limit\n\t\tif (this.totalClients >= this.config.maxTerminalClientsTotal) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\tcomponent: 'terminal',\n\t\t\t\t\tmessage: 'Max total clients reached',\n\t\t\t\t\tdata: { current: this.totalClients, max: this.config.maxTerminalClientsTotal }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check per-target limit\n\t\tconst targetClients = this.clients.get(target);\n\t\tif (targetClients && targetClients.size >= this.config.maxTerminalClientsPerTarget) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\tcomponent: 'terminal',\n\t\t\t\t\tmessage: 'Max clients per target reached',\n\t\t\t\t\tdata: { target, current: targetClients.size, max: this.config.maxTerminalClientsPerTarget }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!this.clients.has(target)) {\n\t\t\tthis.clients.set(target, new Set());\n\t\t}\n\t\tthis.clients.get(target)!.add(client);\n\t\tthis.totalClients++;\n\n\t\tlog(\n\t\t\t{\n\t\t\t\tlevel: 'debug',\n\t\t\t\tcomponent: 'terminal',\n\t\t\t\tmessage: 'Client connected',\n\t\t\t\tdata: { target, targetClients: this.clients.get(target)!.size, total: this.totalClients }\n\t\t\t},\n\t\t\tthis.config\n\t\t);\n\n\t\tif (this.clients.get(target)!.size === 1) {\n\t\t\tthis.startPolling(target);\n\t\t}\n\n\t\t// Initial state: screen + the last DEFAULT_HISTORY_DEPTH history lines.\n\t\tconst snap = snapshotPane(target);\n\t\tconst now = Date.now();\n\t\tif (snap) {\n\t\t\tthis.states.set(target, {\n\t\t\t\thistorySize: snap.historySize,\n\t\t\t\talt: snap.alt,\n\t\t\t\tcols: snap.cols,\n\t\t\t\trows: snap.rows,\n\t\t\t\tscreenKey: snap.screen.join('\\n')\n\t\t\t});\n\t\t\tthis.sendToClient(client, {\n\t\t\t\ttype: 'screen',\n\t\t\t\tlines: snap.screen,\n\t\t\t\thistorySize: snap.historySize,\n\t\t\t\talt: snap.alt,\n\t\t\t\tcols: snap.cols,\n\t\t\t\trows: snap.rows,\n\t\t\t\ttimestamp: now\n\t\t\t});\n\t\t\tif (!snap.alt && snap.historySize > 0) {\n\t\t\t\tthis.sendHistoryTail(client, target, snap.historySize, DEFAULT_HISTORY_DEPTH);\n\t\t\t}\n\t\t} else {\n\t\t\t// Pane gone / not capturable: send an empty screen so the UI renders\n\t\t\tthis.sendToClient(client, {\n\t\t\t\ttype: 'screen',\n\t\t\t\tlines: [],\n\t\t\t\thistorySize: 0,\n\t\t\t\talt: false,\n\t\t\t\tcols: 0,\n\t\t\t\trows: 0,\n\t\t\t\ttimestamp: now\n\t\t\t});\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Serve a client's request for `count` history lines before absolute index `before`.\n\t * Replies only to the requesting client (each viewer scrolls independently).\n\t */\n\trequestHistory(client: WsClient, target: string, before: number, count: number): void {\n\t\tconst state = this.states.get(target);\n\t\tif (!state || state.alt) return;\n\t\tconst n = Math.max(1, Math.min(MAX_HISTORY_REQUEST, Math.floor(count)));\n\t\tconst end = Math.max(0, Math.min(Math.floor(before), state.historySize));\n\t\tif (end === 0) return;\n\t\tthis.sendHistoryTail(client, target, end, n);\n\t}\n\n\tprivate sendHistoryTail(client: WsClient, target: string, end: number, count: number): void {\n\t\tconst state = this.states.get(target);\n\t\tconst known = state?.historySize ?? end;\n\t\tconst start = Math.max(0, end - count);\n\t\tconst res = fetchHistoryRange(target, start, end, known);\n\t\tif (!res) return;\n\t\tthis.sendToClient(client, {\n\t\t\ttype: 'history',\n\t\t\tstart: res.start,\n\t\t\tlines: res.lines,\n\t\t\thistorySize: res.historySize,\n\t\t\ttimestamp: Date.now()\n\t\t});\n\t}\n\n\tremoveClient(client: WsClient, target?: string): void {\n\t\tif (target) {\n\t\t\tconst targetClients = this.clients.get(target);\n\t\t\tif (targetClients && targetClients.delete(client)) {\n\t\t\t\tthis.totalClients--;\n\t\t\t\tlog(\n\t\t\t\t\t{\n\t\t\t\t\t\tlevel: 'debug',\n\t\t\t\t\t\tcomponent: 'terminal',\n\t\t\t\t\t\tmessage: 'Client disconnected',\n\t\t\t\t\t\tdata: { target, targetClients: targetClients.size, total: this.totalClients }\n\t\t\t\t\t},\n\t\t\t\t\tthis.config\n\t\t\t\t);\n\n\t\t\t\tif (targetClients.size === 0) {\n\t\t\t\t\tthis.stopPolling(target);\n\t\t\t\t\tthis.clients.delete(target);\n\t\t\t\t\tthis.states.delete(target);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Search all targets for the client\n\t\tfor (const [t, clients] of this.clients) {\n\t\t\tif (clients.delete(client)) {\n\t\t\t\tthis.totalClients--;\n\t\t\t\tif (clients.size === 0) {\n\t\t\t\t\tthis.stopPolling(t);\n\t\t\t\t\tthis.clients.delete(t);\n\t\t\t\t\tthis.states.delete(t);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate startPolling(target: string): void {\n\t\tif (this.pollTimers.has(target)) return;\n\t\tconst timer = setInterval(() => this.pollAndBroadcast(target), 200);\n\t\tthis.pollTimers.set(target, timer);\n\t}\n\n\tprivate stopPolling(target: string): void {\n\t\tconst timer = this.pollTimers.get(target);\n\t\tif (timer) {\n\t\t\tclearInterval(timer);\n\t\t\tthis.pollTimers.delete(target);\n\t\t}\n\t}\n\n\tprivate pollAndBroadcast(target: string): void {\n\t\tconst snap = snapshotPane(target);\n\t\tif (!snap) return; // pane gone; keep last state, clients will see pane_alive=false via sessions stream\n\t\tconst prev = this.states.get(target);\n\t\tconst now = Date.now();\n\t\tconst messages: TerminalMessage[] = [];\n\t\tconst screenKey = snap.screen.join('\\n');\n\n\t\tif (!prev) {\n\t\t\tthis.states.set(target, { historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, screenKey });\n\t\t\tmessages.push({ type: 'reset', historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t\tmessages.push({ type: 'screen', lines: snap.screen, historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t} else {\n\t\t\tconst reflowed =\n\t\t\t\tsnap.alt !== prev.alt ||\n\t\t\t\tsnap.cols !== prev.cols ||\n\t\t\t\tsnap.rows !== prev.rows ||\n\t\t\t\tsnap.historySize < prev.historySize;\n\t\t\tif (reflowed) {\n\t\t\t\t// Resize reflows history in tmux; `clear` drops it; alt-screen swaps buffers.\n\t\t\t\tmessages.push({ type: 'reset', historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t\t} else if (!snap.alt && snap.historySize > prev.historySize) {\n\t\t\t\tconst n = snap.historySize - prev.historySize;\n\t\t\t\tlet lines: string[] | null = null;\n\t\t\t\tlet start = prev.historySize;\n\t\t\t\tif (n <= snap.tail.length) {\n\t\t\t\t\tlines = snap.tail.slice(snap.tail.length - n);\n\t\t\t\t} else {\n\t\t\t\t\t// Burst larger than the tail we snapshot each tick — fetch the rest explicitly.\n\t\t\t\t\tconst res = fetchHistoryRange(target, prev.historySize, snap.historySize, snap.historySize);\n\t\t\t\t\tif (res) {\n\t\t\t\t\t\tlines = res.lines;\n\t\t\t\t\t\tstart = res.start;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (lines) {\n\t\t\t\t\tmessages.push({ type: 'history', start, lines, historySize: snap.historySize, timestamp: now });\n\t\t\t\t} else {\n\t\t\t\t\tmessages.push({ type: 'reset', historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (screenKey !== prev.screenKey || reflowed) {\n\t\t\t\tmessages.push({ type: 'screen', lines: snap.screen, historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t\t}\n\t\t\tthis.states.set(target, { historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, screenKey });\n\t\t}\n\n\t\tif (messages.length === 0) return;\n\t\tconst clients = this.clients.get(target);\n\t\tif (!clients) return;\n\t\tfor (const message of messages) {\n\t\t\tconst data = JSON.stringify(message);\n\t\t\tconst drops: WsClient[] = [];\n\t\t\tfor (const client of [...clients]) {\n\t\t\t\tif (!this.sendToClient(client, message, data)) drops.push(client);\n\t\t\t}\n\t\t\tfor (const client of drops) {\n\t\t\t\tclients.delete(client);\n\t\t\t\tthis.totalClients--;\n\t\t\t\tthis.droppedClients++;\n\t\t\t\tlog(\n\t\t\t\t\t{\n\t\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\t\tcomponent: 'terminal',\n\t\t\t\t\t\tmessage: 'Dropped slow client',\n\t\t\t\t\t\tdata: { target, total: this.totalClients }\n\t\t\t\t\t},\n\t\t\t\t\tthis.config\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (clients.size === 0) {\n\t\t\t\tthis.stopPolling(target);\n\t\t\t\tthis.clients.delete(target);\n\t\t\t\tthis.states.delete(target);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate sendToClient(client: WsClient, message: TerminalMessage, data?: string): boolean {\n\t\t// Terminal frames are small and always superseded by the next screen,\n\t\t// so a client that cannot keep up is dropped rather than waited for.\n\t\treturn (\n\t\t\tsendJson(client, message, this.config, {\n\t\t\t\tcomponent: 'terminal',\n\t\t\t\tmaxBuffered: 64 * 1024,\n\t\t\t\tdata\n\t\t\t}) === 'sent'\n\t\t);\n\t}\n}\n\n// ============================================================================\n// Transcript WebSocket Manager\n// ============================================================================\n\nexport type TranscriptWsMessage =\n\t| {\n\t\t\ttype: 'snapshot';\n\t\t\t/** The tail of the transcript — see SNAPSHOT_ENTRIES. */\n\t\t\tentries: TranscriptEntry[];\n\t\t\t/** Position of `entries[0]` in the whole transcript. */\n\t\t\tfirstIndex: number;\n\t\t\tsubagents: SubagentPayload[];\n\t\t\tcontext: ContextUsage | null;\n\t\t\t/** The model on the latest assistant line, as the API names it. */\n\t\t\tmodel: string | null;\n\t\t\tavailable: boolean;\n\t\t\ttimestamp: number;\n\t\t}\n\t/** An older slice, sent to one client that asked for it. */\n\t| { type: 'history'; entries: TranscriptEntry[]; firstIndex: number; timestamp: number }\n\t/**\n\t * Added or updated entries. `indices` runs parallel to `entries`: a client\n\t * holding only the tail uses it to tell a new entry from an update to one\n\t * it never received.\n\t */\n\t| { type: 'entries'; entries: TranscriptEntry[]; indices: number[]; timestamp: number }\n\t| { type: 'subagents'; subagents: SubagentPayload[]; timestamp: number }\n\t/**\n\t * Context-window usage, which only this channel carries: it is read off\n\t * the transcript, and transcripts are only tailed while a client watches\n\t * the session. A list-wide gauge would need the tailer hoisted out of this\n\t * manager into a registry both channels read — not a second reader.\n\t */\n\t| { type: 'context'; context: ContextUsage; timestamp: number }\n\t/** The model changed — `/model` mid-session — as seen on the next reply. */\n\t| { type: 'model'; model: string; timestamp: number };\n\ninterface SubagentState {\n\ttailer: JsonlTailer;\n\tbuilder: TranscriptBuilder;\n\tmeta: SubagentMeta;\n\t/** Payload as of the last sync, reused by snapshots. */\n\tpayload: SubagentPayload | null;\n\t/** Serialized payload last sent, to skip unchanged broadcasts. */\n\tlastSent: string;\n\t/** Entry ids added or updated since the last poll, for readers following this agent. */\n\tchanged: Set<string>;\n\t/** The file was replaced since the last poll: followers need a fresh snapshot. */\n\treset: boolean;\n}\n\n/**\n * A reader that opened one subagent as a window rather than its parent's\n * transcript. It rides the parent's session state — the same tailer that\n * feeds the Task cards — and is sent that agent's entries instead.\n */\ninterface AgentFollow {\n\tagentId: string;\n\t/** The agent's file had been found when this reader was last sent a snapshot. */\n\tfound: boolean;\n}\n\ninterface TranscriptSessionState {\n\tsessionId: string;\n\t/** Null until the session's JSONL has been located (see resolveIn). */\n\ttailer: JsonlTailer | null;\n\tbuilder: TranscriptBuilder;\n\ttimer: ReturnType<typeof setInterval>;\n\tavailable: boolean;\n\t/** Ticks until the next attempt to locate a still-unfound transcript. */\n\tresolveIn: number;\n\t/** Ticks to wait after the next miss, doubling up to RESOLVE_TICKS_MAX. */\n\tresolveBackoff: number;\n\tsubagents: Map<string, SubagentState>;\n\t/** Ticks until the next subagents directory scan. */\n\tdiscoverIn: number;\n\t/** Agents a reader has opened: their payloads go out in full from then on. */\n\texpanded: Set<string>;\n\t/**\n\t * The first read of a large transcript, in progress. It runs in slices\n\t * that yield between them, so the server keeps answering; the poll and\n\t * the snapshot wait on it.\n\t */\n\tattaching: Promise<void> | null;\n\t/** Set when the last client left; the state is kept warm until it ages out. */\n\tidleSince: number | null;\n\t/** Serialized context usage last sent, to skip unchanged broadcasts. */\n\tsentContext: string;\n\t/** Model id last sent, likewise. */\n\tsentModel: string | null;\n\t/** Clients that missed a broadcast to backpressure; each is resent a snapshot. */\n\tstale: Set<WsClient>;\n}\n\nconst TRANSCRIPT_POLL_MS = 500;\n/** Scan for newly spawned subagents every N polls (they appear rarely). */\nconst SUBAGENT_DISCOVER_TICKS = 4;\n/**\n * Re-attempt to locate a missing transcript after N polls, doubling up to\n * RESOLVE_TICKS_MAX. A session that has not yet answered its first prompt has\n * no file, and every attempt stats each project directory — worth doing\n * promptly at first and rarely once it is clear nothing is being written.\n * The backoff is only the fallback: a hook writing the session JSON is the\n * signal that Claude Code is at work (see sessionsChanged).\n */\nconst RESOLVE_TICKS = 4;\nconst RESOLVE_TICKS_MAX = 60;\n/**\n * Entries in the first snapshot. A long-running session holds thousands, and\n * shipping all of them costs megabytes on the wire and seconds of layout on a\n * phone for history the reader has to scroll back through anyway. The client\n * asks for older slices when the reader asks for them.\n */\nconst SNAPSHOT_ENTRIES = 120;\n/**\n * How long a session's parsed transcript is kept after its last reader\n * leaves. Switching between sessions is the common case, and a 46 MB\n * transcript with forty subagents costs over a second to parse cold, with\n * the server frozen for it; warm, coming back is one incremental read.\n */\nconst SESSION_KEEP_MS = 10 * 60 * 1000;\nconst SWEEP_MS = 60 * 1000;\n/** Bytes read per slice of a cold attach, between yields to the event loop. */\nconst ATTACH_SLICE_BYTES = 4 * 1024 * 1024;\n/** Ceiling on one history request, so a client cannot ask for the world. */\nconst MAX_HISTORY_ENTRIES = 500;\n\n/** The tail a snapshot carries of a builder: its last entries, where they start, and its context and model. */\nfunction tailOf(builder: TranscriptBuilder) {\n\tconst all = builder.entries;\n\tconst firstIndex = Math.max(0, all.length - SNAPSHOT_ENTRIES);\n\treturn {\n\t\tentries: firstIndex > 0 ? all.slice(firstIndex) : all,\n\t\tfirstIndex,\n\t\tcontext: builder.context,\n\t\tmodel: builder.model\n\t};\n}\n\n/** An `entries` delta for the given changed ids, each sent once with its index. */\nfunction entriesDelta(\n\tbuilder: TranscriptBuilder,\n\tids: Iterable<string>,\n\ttimestamp: number\n): TranscriptWsMessage {\n\tconst entries: TranscriptEntry[] = [];\n\tconst indices: number[] = [];\n\tfor (const id of new Set(ids)) {\n\t\tconst index = builder.indexOf(id);\n\t\tif (index === undefined) continue;\n\t\tentries.push(builder.entries[index]);\n\t\tindices.push(index);\n\t}\n\treturn { type: 'entries', entries, indices, timestamp };\n}\n\nexport class TranscriptWsManager {\n\tprivate clients = new Map<string, Set<WsClient>>();\n\tprivate sessions = new Map<string, TranscriptSessionState>();\n\t/** Clients reading one subagent rather than the whole session. */\n\tprivate follows = new Map<WsClient, AgentFollow>();\n\tprivate config: Required<WsConfig>;\n\tprivate unwatch: (() => void) | null = null;\n\n\tconstructor(config?: WsConfig) {\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\t/**\n\t * Attach a reader to a session's transcript, or with `agentId` to one of\n\t * its subagents: the same messages, carrying that agent's entries.\n\t */\n\taddClient(client: WsClient, sessionId: string, agentId?: string): boolean {\n\t\t// Agent windows and the session's own readers are capped apart, so a\n\t\t// handful of open agents cannot lock the session's transcript out.\n\t\tconst current = [...(this.clients.get(sessionId) ?? [])].filter(\n\t\t\t(c) => this.follows.has(c) === Boolean(agentId)\n\t\t).length;\n\t\tif (current >= this.config.maxTerminalClientsPerTarget) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\tcomponent: 'transcript',\n\t\t\t\t\tmessage: 'Max clients per session reached',\n\t\t\t\t\tdata: { sessionId, agentId, current }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!this.clients.has(sessionId)) this.clients.set(sessionId, new Set());\n\t\tthis.clients.get(sessionId)!.add(client);\n\t\tif (agentId) this.follows.set(client, { agentId, found: false });\n\n\t\tlet state = this.sessions.get(sessionId);\n\t\tif (!state) {\n\t\t\tstate = this.startSession(sessionId);\n\t\t\tthis.sessions.set(sessionId, state);\n\t\t} else if (state.idleSince !== null) {\n\t\t\tthis.wake(sessionId, state);\n\t\t}\n\t\tconst send = () => {\n\t\t\tif (!agentId) state.sentContext = JSON.stringify(state.builder.context);\n\t\t\tthis.sendToClient(client, this.snapshot(state, client));\n\t\t};\n\t\t// A cold attach is still reading: the snapshot goes when it has caught up.\n\t\tif (state.attaching) void state.attaching.then(send);\n\t\telse send();\n\t\treturn true;\n\t}\n\n\t/** A parked session has a reader again: catch up on what was written meanwhile. */\n\tprivate wake(sessionId: string, state: TranscriptSessionState): void {\n\t\tstate.idleSince = null;\n\t\tstate.timer = setInterval(() => this.poll(sessionId), TRANSCRIPT_POLL_MS);\n\t\tif (!state.tailer || state.attaching) return;\n\t\tconst result = state.tailer.read();\n\t\tif (result.status === 'reset') {\n\t\t\tthis.consume(state, result);\n\t\t\tthis.consume(state, state.tailer.read());\n\t\t} else {\n\t\t\tthis.consume(state, result);\n\t\t}\n\t\tthis.syncSubagents(state);\n\t\t// Whoever woke it is sent a snapshot, which already holds these.\n\t\tthis.forgetSubagentChanges(state);\n\t}\n\n\tremoveClient(client: WsClient, sessionId?: string): void {\n\t\tthis.follows.delete(client);\n\t\tconst drop = (id: string, clients: Set<WsClient>) => {\n\t\t\tif (!clients.delete(client)) return false;\n\t\t\tif (clients.size === 0) this.park(id);\n\t\t\treturn true;\n\t\t};\n\t\tif (sessionId) {\n\t\t\tconst clients = this.clients.get(sessionId);\n\t\t\tif (clients) drop(sessionId, clients);\n\t\t\treturn;\n\t\t}\n\t\tfor (const [id, clients] of this.clients) {\n\t\t\tif (drop(id, clients)) break;\n\t\t}\n\t}\n\n\t/** A main-transcript builder that stamps voice-injected prompts (see dictation-marks). */\n\tprivate mainBuilder(sessionId: string): TranscriptBuilder {\n\t\treturn new TranscriptBuilder(false, (text, ts) => isDictated(sessionId, text, ts));\n\t}\n\n\tprivate startSession(sessionId: string): TranscriptSessionState {\n\t\tconst state: TranscriptSessionState = {\n\t\t\tsessionId,\n\t\t\ttailer: null,\n\t\t\tbuilder: this.mainBuilder(sessionId),\n\t\t\tavailable: false,\n\t\t\tresolveIn: RESOLVE_TICKS,\n\t\t\tresolveBackoff: RESOLVE_TICKS,\n\t\t\tsubagents: new Map(),\n\t\t\tdiscoverIn: 0,\n\t\t\texpanded: new Set(),\n\t\t\tattaching: null,\n\t\t\tidleSince: null,\n\t\t\tsentContext: '',\n\t\t\tsentModel: null,\n\t\t\tstale: new Set(),\n\t\t\ttimer: setInterval(() => this.poll(sessionId), TRANSCRIPT_POLL_MS)\n\t\t};\n\t\tconst path = this.locate(sessionId);\n\t\tif (path) this.attach(state, path);\n\t\tthis.ensureSweeper();\n\t\treturn state;\n\t}\n\n\t/**\n\t * Hooks wrote these sessions' JSON. Claude Code creates the transcript the\n\t * moment a prompt is submitted, and the UserPromptSubmit hook lands in the\n\t * same second, so a session still without a transcript whose recorded\n\t * path now exists attaches on its next poll rather than when the backoff,\n\t * by then up to 30s long, runs out. Checking that one path costs a stat;\n\t * a busy session writes its JSON on every tool call, and a full locate\n\t * on each of those would scan every project directory.\n\t */\n\tsessionsChanged(changed: Iterable<string>): void {\n\t\tfor (const id of changed) {\n\t\t\tconst state = this.sessions.get(id);\n\t\t\tif (!state || state.tailer) continue;\n\t\t\tconst recorded = getSession(id)?.transcript_path;\n\t\t\tif (recorded && existsSync(recorded)) state.resolveIn = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Point the state at a transcript file and read what is already in it, so\n\t * the next snapshot carries history rather than starting from empty. A\n\t * long transcript is read in slices with a yield between each, and its\n\t * subagents one per yield, so a 46 MB session does not hold the server\n\t * for the second it takes; `state.attaching` is what to wait on.\n\t */\n\tprivate attach(state: TranscriptSessionState, path: string): Promise<void> {\n\t\tconst tailer = new JsonlTailer(path);\n\t\tstate.tailer = tailer;\n\t\tconst yieldNow = () => new Promise<void>((resolve) => setImmediate(resolve));\n\t\tstate.attaching = (async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst result = tailer.read(ATTACH_SLICE_BYTES);\n\t\t\t\tthis.consume(state, result);\n\t\t\t\tif (result.status !== 'lines' || !result.more) break;\n\t\t\t\tawait yieldNow();\n\t\t\t}\n\t\t\tthis.discoverSubagents(state);\n\t\t\tfor (const [agentId, sub] of state.subagents) {\n\t\t\t\tthis.tailSubagent(state, agentId, sub);\n\t\t\t\tawait yieldNow();\n\t\t\t}\n\t\t\tthis.forgetSubagentChanges(state);\n\t\t})().finally(() => {\n\t\t\tif (state.tailer === tailer) state.attaching = null;\n\t\t});\n\t\treturn state.attaching;\n\t}\n\n\t/**\n\t * Where this session's JSONL lives, or null if it is not on disk yet. A\n\t * past session has no session JSON, and is found by its id alone.\n\t */\n\tprivate locate(sessionId: string): string | null {\n\t\tconst session = getSession(sessionId);\n\t\treturn session ? resolveTranscriptPath(session, sessionId) : findTranscriptById(sessionId);\n\t}\n\n\t/**\n\t * Pick up newly spawned subagents and tail the known ones. Returns the\n\t * payloads that changed since the last call.\n\t */\n\tprivate syncSubagents(state: TranscriptSessionState): SubagentPayload[] {\n\t\tif (!state.tailer) return [];\n\n\t\tif (state.discoverIn <= 0) {\n\t\t\tstate.discoverIn = SUBAGENT_DISCOVER_TICKS;\n\t\t\tthis.discoverSubagents(state);\n\t\t} else {\n\t\t\tstate.discoverIn--;\n\t\t}\n\n\t\tconst changed: SubagentPayload[] = [];\n\t\tfor (const [agentId, sub] of state.subagents) {\n\t\t\tconst payload = this.tailSubagent(state, agentId, sub);\n\t\t\tif (payload) changed.push(payload);\n\t\t}\n\t\treturn changed;\n\t}\n\n\t/** Register subagent files that appeared beside the transcript. */\n\tprivate discoverSubagents(state: TranscriptSessionState): void {\n\t\tif (!state.tailer) return;\n\t\tfor (const file of listSubagents(state.tailer.path, new Set(state.subagents.keys()))) {\n\t\t\tstate.subagents.set(file.agentId, {\n\t\t\t\ttailer: new JsonlTailer(file.path),\n\t\t\t\tbuilder: new TranscriptBuilder(true),\n\t\t\t\tmeta: file.meta,\n\t\t\t\tpayload: null,\n\t\t\t\tlastSent: '',\n\t\t\t\tchanged: new Set(),\n\t\t\t\treset: false\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Read one subagent's new lines and rebuild its payload — lean unless a\n\t * reader has opened its card. Returns the payload when it changed.\n\t */\n\tprivate tailSubagent(\n\t\tstate: TranscriptSessionState,\n\t\tagentId: string,\n\t\tsub: SubagentState\n\t): SubagentPayload | null {\n\t\tconst result = sub.tailer.read();\n\t\tif (result.status === 'reset') {\n\t\t\tsub.builder = new TranscriptBuilder(true);\n\t\t\tsub.changed.clear();\n\t\t\tsub.reset = true;\n\t\t} else if (result.status === 'lines') {\n\t\t\tfor (const line of result.lines) {\n\t\t\t\tfor (const id of sub.builder.feed(line)) sub.changed.add(id);\n\t\t\t}\n\t\t}\n\t\tconst payload = subagentPayload(\n\t\t\tagentId,\n\t\t\tsub,\n\t\t\tstate.builder.finishedAgents.has(agentId),\n\t\t\tstate.expanded.has(agentId)\n\t\t);\n\t\tsub.payload = payload;\n\t\tconst encoded = JSON.stringify(payload);\n\t\tif (encoded === sub.lastSent) return null;\n\t\tsub.lastSent = encoded;\n\t\treturn payload;\n\t}\n\n\t/**\n\t * A reader opened an agent's card: send everything it ran and reported,\n\t * and keep sending it in full while the session lives.\n\t */\n\trequestSubagent(client: WsClient, sessionId: string, agentId: string): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tconst sub = state?.subagents.get(agentId);\n\t\tif (!state || !sub) return;\n\t\tstate.expanded.add(agentId);\n\t\tconst payload = subagentPayload(agentId, sub, state.builder.finishedAgents.has(agentId), true);\n\t\tsub.payload = payload;\n\t\tsub.lastSent = JSON.stringify(payload);\n\t\tthis.sendToClient(client, { type: 'subagents', subagents: [payload], timestamp: Date.now() });\n\t}\n\n\t/**\n\t * Send one client the slice of `count` entries that ends just before\n\t * `before` — the index of the oldest entry it already holds.\n\t */\n\trequestHistory(client: WsClient, sessionId: string, before: number, count: number): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tif (!state) return;\n\t\tconst follow = this.follows.get(client);\n\t\tconst builder = follow ? state.subagents.get(follow.agentId)?.builder : state.builder;\n\t\tif (!builder) return;\n\t\tconst end = Math.max(0, Math.min(Math.floor(before), builder.entries.length));\n\t\tif (end === 0) return;\n\t\tconst n = Math.max(1, Math.min(MAX_HISTORY_ENTRIES, Math.floor(count)));\n\t\tconst start = Math.max(0, end - n);\n\t\tthis.sendToClient(client, {\n\t\t\ttype: 'history',\n\t\t\tentries: builder.entries.slice(start, end),\n\t\t\tfirstIndex: start,\n\t\t\ttimestamp: Date.now()\n\t\t});\n\t}\n\n\t/**\n\t * The last reader left. The parsed transcript stays, its poll stops, and\n\t * the sweeper drops it once nobody has come back for SESSION_KEEP_MS.\n\t */\n\tprivate park(sessionId: string): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tthis.clients.delete(sessionId);\n\t\tif (!state) return;\n\t\tclearInterval(state.timer);\n\t\tstate.idleSince = Date.now();\n\t\tstate.stale.clear();\n\t}\n\n\tprivate stopSession(sessionId: string): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tif (state) clearInterval(state.timer);\n\t\tthis.sessions.delete(sessionId);\n\t\tthis.clients.delete(sessionId);\n\t}\n\n\tprivate sweeper: ReturnType<typeof setInterval> | null = null;\n\n\tprivate ensureSweeper(): void {\n\t\tif (this.sweeper) return;\n\t\tthis.unwatch = sessionWatcher.subscribe((changed) => this.sessionsChanged(changed));\n\t\tthis.sweeper = setInterval(() => {\n\t\t\tconst now = Date.now();\n\t\t\tfor (const [id, state] of this.sessions) {\n\t\t\t\tif (state.idleSince !== null && now - state.idleSince > SESSION_KEEP_MS) {\n\t\t\t\t\tthis.stopSession(id);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.sessions.size === 0 && this.sweeper) {\n\t\t\t\tclearInterval(this.sweeper);\n\t\t\t\tthis.sweeper = null;\n\t\t\t\tthis.unwatch?.();\n\t\t\t\tthis.unwatch = null;\n\t\t\t}\n\t\t}, SWEEP_MS);\n\t}\n\n\t/** Apply a tail result to the builder; returns changed entry ids. */\n\tprivate consume(\n\t\tstate: TranscriptSessionState,\n\t\tresult: ReturnType<JsonlTailer['read']>\n\t): string[] {\n\t\tswitch (result.status) {\n\t\t\tcase 'lines': {\n\t\t\t\tstate.available = true;\n\t\t\t\tconst changed: string[] = [];\n\t\t\t\tfor (const line of result.lines) changed.push(...state.builder.feed(line));\n\t\t\t\treturn changed;\n\t\t\t}\n\t\t\tcase 'reset':\n\t\t\t\t// File replaced: rebuild on the next polls from offset 0.\n\t\t\t\tstate.builder = this.mainBuilder(state.sessionId);\n\t\t\t\treturn [];\n\t\t\tdefault:\n\t\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate poll(sessionId: string): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tconst clients = this.clients.get(sessionId);\n\t\tif (!state || !clients || clients.size === 0) return;\n\n\t\tif (state.attaching) return;\n\t\tif (!state.tailer) {\n\t\t\tif (--state.resolveIn > 0) return;\n\t\t\tconst path = this.locate(sessionId);\n\t\t\tif (!path) {\n\t\t\t\tstate.resolveBackoff = Math.min(state.resolveBackoff * 2, RESOLVE_TICKS_MAX);\n\t\t\t\tstate.resolveIn = state.resolveBackoff;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvoid this.attach(state, path).then(() => {\n\t\t\t\tif (this.sessions.get(sessionId) === state) this.resend(sessionId, state);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis.recoverStale(sessionId, state);\n\n\t\tconst result = state.tailer.read();\n\t\tif (result.status === 'reset') {\n\t\t\tthis.consume(state, result);\n\t\t\t// Immediately re-read the fresh file and resend full snapshots.\n\t\t\tthis.consume(state, state.tailer.read());\n\t\t\tthis.resend(sessionId, state);\n\t\t\treturn;\n\t\t}\n\n\t\tconst changedIds = this.consume(state, result);\n\t\tif (changedIds.length > 0) {\n\t\t\tthis.broadcast(sessionId, entriesDelta(state.builder, changedIds, Date.now()));\n\t\t}\n\n\t\tconst subagents = this.syncSubagents(state);\n\t\tif (subagents.length > 0) {\n\t\t\tthis.broadcast(sessionId, { type: 'subagents', subagents, timestamp: Date.now() });\n\t\t}\n\t\tthis.feedFollowers(sessionId, state, subagents);\n\n\t\tconst context = state.builder.context;\n\t\tconst encoded = JSON.stringify(context);\n\t\tif (context && encoded !== state.sentContext) {\n\t\t\tstate.sentContext = encoded;\n\t\t\tthis.broadcast(sessionId, { type: 'context', context, timestamp: Date.now() });\n\t\t}\n\t\tconst model = state.builder.model;\n\t\tif (model && model !== state.sentModel) {\n\t\t\tstate.sentModel = model;\n\t\t\tthis.broadcast(sessionId, { type: 'model', model, timestamp: Date.now() });\n\t\t}\n\t}\n\n\t/** Re-broadcast the whole transcript, e.g. after a file reset or a re-attach. */\n\tprivate resend(sessionId: string, state: TranscriptSessionState): void {\n\t\tstate.sentContext = JSON.stringify(state.builder.context);\n\t\tthis.broadcast(sessionId, this.snapshot(state));\n\t\tfor (const client of this.followersOf(sessionId)) {\n\t\t\tthis.sendToClient(client, this.snapshot(state, client));\n\t\t}\n\t}\n\n\t/** The clients of a session that follow one of its agents. */\n\tprivate followersOf(sessionId: string): WsClient[] {\n\t\tif (this.follows.size === 0) return [];\n\t\treturn [...(this.clients.get(sessionId) ?? [])].filter((c) => this.follows.has(c));\n\t}\n\n\t/**\n\t * Send each agent's followers what changed in it this poll: its new and\n\t * updated entries, its payload (which carries running and the tool\n\t * count), and its context. A follower whose agent had not been written\n\t * when it attached is sent a snapshot the poll it turns up.\n\t */\n\tprivate feedFollowers(\n\t\tsessionId: string,\n\t\tstate: TranscriptSessionState,\n\t\tpayloads: SubagentPayload[]\n\t): void {\n\t\tconst clients = this.clients.get(sessionId);\n\t\tfor (const client of this.followersOf(sessionId)) {\n\t\t\tconst follow = this.follows.get(client)!;\n\t\t\tconst sub = state.subagents.get(follow.agentId);\n\t\t\t// A client still catching up is sent a whole snapshot by recoverStale.\n\t\t\tif (!sub || state.stale.has(client)) continue;\n\t\t\tconst messages: TranscriptWsMessage[] = [];\n\t\t\tconst now = Date.now();\n\t\t\tif (!follow.found || sub.reset) {\n\t\t\t\tmessages.push(this.snapshot(state, client));\n\t\t\t} else {\n\t\t\t\tif (sub.changed.size > 0) {\n\t\t\t\t\tmessages.push(entriesDelta(sub.builder, sub.changed, now));\n\t\t\t\t\tconst context = sub.builder.context;\n\t\t\t\t\tif (context) messages.push({ type: 'context', context, timestamp: now });\n\t\t\t\t}\n\t\t\t\t// The broadcast payloads are lean unless the card is open; a window wants it whole.\n\t\t\t\tif (payloads.some((p) => p.agentId === follow.agentId)) {\n\t\t\t\t\tconst finished = state.builder.finishedAgents.has(follow.agentId);\n\t\t\t\t\tconst payload = subagentPayload(follow.agentId, sub, finished, true);\n\t\t\t\t\tmessages.push({ type: 'subagents', subagents: [payload], timestamp: now });\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const message of messages) {\n\t\t\t\tconst result = this.sendToClient(client, message);\n\t\t\t\tif (result === 'sent') continue;\n\t\t\t\tif (result === 'backpressure') {\n\t\t\t\t\tstate.stale.add(client);\n\t\t\t\t} else {\n\t\t\t\t\tclients?.delete(client);\n\t\t\t\t\tthis.follows.delete(client);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tthis.forgetSubagentChanges(state);\n\t\tif (clients?.size === 0) this.park(sessionId);\n\t}\n\n\t/** Every follower has been sent what the agents' builders hold so far. */\n\tprivate forgetSubagentChanges(state: TranscriptSessionState): void {\n\t\tfor (const sub of state.subagents.values()) {\n\t\t\tsub.changed.clear();\n\t\t\tsub.reset = false;\n\t\t}\n\t}\n\n\t/**\n\t * Catch up the clients that missed a delta while their socket was backed\n\t * up. A snapshot costs the whole transcript tail, so it goes only to the\n\t * client that missed something, and only once its buffer has drained.\n\t */\n\tprivate recoverStale(sessionId: string, state: TranscriptSessionState): void {\n\t\tif (state.stale.size === 0) return;\n\t\tconst clients = this.clients.get(sessionId);\n\t\tconst message = this.snapshot(state);\n\t\tconst data = JSON.stringify(message);\n\t\tfor (const client of [...state.stale]) {\n\t\t\tif (!clients?.has(client)) {\n\t\t\t\tstate.stale.delete(client);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst result = this.follows.has(client)\n\t\t\t\t? this.sendToClient(client, this.snapshot(state, client))\n\t\t\t\t: this.sendToClient(client, message, data);\n\t\t\tif (result === 'backpressure') continue; // still draining; try next tick\n\t\t\tstate.stale.delete(client);\n\t\t\tif (result === 'closed') clients.delete(client);\n\t\t}\n\t}\n\n\t/**\n\t * Everything a freshly attached client needs. Context rides along here so\n\t * the client starts with a gauge instead of waiting for the next response;\n\t * callers mark it sent with `sentContext`.\n\t */\n\tprivate snapshot(state: TranscriptSessionState, client?: WsClient): TranscriptWsMessage {\n\t\tconst follow = client && this.follows.get(client);\n\t\tif (follow) return this.agentSnapshot(state, follow);\n\t\treturn {\n\t\t\ttype: 'snapshot',\n\t\t\t...tailOf(state.builder),\n\t\t\tsubagents: [...state.subagents].map(\n\t\t\t\t([id, sub]) =>\n\t\t\t\t\tsub.payload ??\n\t\t\t\t\tsubagentPayload(id, sub, state.builder.finishedAgents.has(id), state.expanded.has(id))\n\t\t\t),\n\t\t\tavailable: state.available,\n\t\t\ttimestamp: Date.now()\n\t\t};\n\t}\n\n\t/**\n\t * One agent's transcript, shaped like a session's: its entries' tail, its\n\t * own payload in full as the only subagent, and its context. An agent\n\t * whose file is not on disk (yet, or at all) is `available: false`.\n\t */\n\tprivate agentSnapshot(state: TranscriptSessionState, follow: AgentFollow): TranscriptWsMessage {\n\t\tconst sub = state.subagents.get(follow.agentId);\n\t\tfollow.found = sub !== undefined;\n\t\tif (!sub) {\n\t\t\treturn {\n\t\t\t\ttype: 'snapshot',\n\t\t\t\tentries: [],\n\t\t\t\tfirstIndex: 0,\n\t\t\t\tsubagents: [],\n\t\t\t\tcontext: null,\n\t\t\t\tmodel: null,\n\t\t\t\tavailable: false,\n\t\t\t\ttimestamp: Date.now()\n\t\t\t};\n\t\t}\n\t\tconst payload = subagentPayload(\n\t\t\tfollow.agentId,\n\t\t\tsub,\n\t\t\tstate.builder.finishedAgents.has(follow.agentId),\n\t\t\ttrue\n\t\t);\n\t\treturn {\n\t\t\ttype: 'snapshot',\n\t\t\t...tailOf(sub.builder),\n\t\t\tsubagents: [payload],\n\t\t\tavailable: true,\n\t\t\ttimestamp: Date.now()\n\t\t};\n\t}\n\n\t/** Send to the session's readers of the whole transcript; agent followers are fed apart. */\n\tprivate broadcast(sessionId: string, message: TranscriptWsMessage): void {\n\t\tconst clients = this.clients.get(sessionId);\n\t\tif (!clients) return;\n\t\tconst state = this.sessions.get(sessionId);\n\t\tconst data = JSON.stringify(message);\n\t\tfor (const client of [...clients]) {\n\t\t\tif (this.follows.has(client)) continue;\n\t\t\tconst result = this.sendToClient(client, message, data);\n\t\t\t// A skipped delta is a hole in that client's list, and deltas are\n\t\t\t// never replayed: it is sent a fresh snapshot once its socket\n\t\t\t// drains. Only a closed socket costs a client its place.\n\t\t\tif (result === 'backpressure') state?.stale.add(client);\n\t\t\telse if (result === 'closed') clients.delete(client);\n\t\t}\n\t\tif (clients.size === 0) this.park(sessionId);\n\t}\n\n\tprivate sendToClient(client: WsClient, message: TranscriptWsMessage, data?: string): SendResult {\n\t\treturn sendJson(client, message, this.config, {\n\t\t\tcomponent: 'transcript',\n\t\t\t// A snapshot of a long session is megabytes; the ceiling is here to\n\t\t\t// catch a socket that has stopped draining, not to size a message.\n\t\t\tmaxBuffered: 8 * 1024 * 1024,\n\t\t\tdata\n\t\t});\n\t}\n}\n\n// ============================================================================\n// Message Handling\n// ============================================================================\n\nexport interface WsMessageHandlers {\n\tresize?: (cols: number, rows: number) => void;\n\thistoryRequest?: (before: number, count: number) => void;\n\t/** Transcript: a reader opened an agent's card and wants all of it. */\n\tsubagentRequest?: (agentId: string) => void;\n\t/** Browser view: everything but the ping is the channel's own, handed over as it came. */\n\tinput?: (text: string) => void;\n}\n\nexport function handleWsMessage(msgStr: string, handlers?: WsMessageHandlers): 'pong' | null {\n\tif (msgStr === 'ping') return 'pong';\n\tif (!handlers) return null;\n\tif (handlers.input) {\n\t\thandlers.input(msgStr);\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst msg = JSON.parse(msgStr) as ResizeMessage | HistoryRequestMessage | SubagentRequestMessage;\n\t\tswitch (msg.type) {\n\t\t\tcase 'subagent_request':\n\t\t\t\tif (handlers.subagentRequest && typeof msg.agentId === 'string') {\n\t\t\t\t\thandlers.subagentRequest(msg.agentId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'resize':\n\t\t\t\tif (handlers.resize && typeof msg.cols === 'number' && typeof msg.rows === 'number') {\n\t\t\t\t\thandlers.resize(msg.cols, msg.rows);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'history_request':\n\t\t\t\tif (handlers.historyRequest && typeof msg.before === 'number' && typeof msg.count === 'number') {\n\t\t\t\t\thandlers.historyRequest(msg.before, msg.count);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t} catch {\n\t\t// malformed JSON\n\t}\n\n\treturn null;\n}\n\n// ============================================================================\n// URL Parsing\n// ============================================================================\n\nexport type WsPathResult =\n\t/** `fleet`: the page asked for every other machine as well (`?fleet=1`). */\n\t| { type: 'sessions'; fleet?: boolean }\n\t| { type: 'terminal'; target: string }\n\t| { type: 'transcript'; target: string; agent?: string }\n\t/** The Web pane's view of a remote browser tab; `target` is the tab's id. */\n\t| { type: 'browser'; target: string }\n\t| null;\n\nexport function parseWsPath(pathname: string, search = ''): WsPathResult {\n\tif (pathname === '/api/sessions/stream') {\n\t\treturn new URLSearchParams(search).has('fleet') ? { type: 'sessions', fleet: true } : { type: 'sessions' };\n\t}\n\n\tconst browserMatch = pathname.match(/^\\/api\\/browser\\/([^/]+)\\/stream$/);\n\tif (browserMatch) {\n\t\treturn { type: 'browser', target: decodeURIComponent(browserMatch[1]) };\n\t}\n\n\tconst agentMatch = pathname.match(/^\\/api\\/sessions\\/([^/]+)\\/agents\\/([^/]+)\\/transcript\\/stream$/);\n\tif (agentMatch) {\n\t\treturn {\n\t\t\ttype: 'transcript',\n\t\t\ttarget: decodeURIComponent(agentMatch[1]),\n\t\t\tagent: decodeURIComponent(agentMatch[2])\n\t\t};\n\t}\n\n\tconst transcriptMatch = pathname.match(/^\\/api\\/sessions\\/([^/]+)\\/transcript\\/stream$/);\n\tif (transcriptMatch) {\n\t\treturn { type: 'transcript', target: decodeURIComponent(transcriptMatch[1]) };\n\t}\n\n\tconst termMatch = pathname.match(/^\\/api\\/sessions\\/([^/]+)\\/stream$/);\n\tif (termMatch) {\n\t\treturn { type: 'terminal', target: decodeURIComponent(termMatch[1]) };\n\t}\n\n\treturn null;\n}\n\n"],"names":["join","homedir","execFile","readFile","run","stat"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAS,sBAA8B;AACrC,SAAuB,KAAK,QAAQ,eAAA,CAAgB,GAAG,eAAe;AACxE;AAQA,SAAS,WAAyB;AAChC,QAAM,OAAO,oBAAA;AACb,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,EAAE,GAAG,GAAG,UAAU,GAAC;AACjD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AACrD,UAAM,WAAW,MAAM,QAAQ,OAAO,QAAQ,IAC1C,OAAO,SAAS,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC,IAChF,CAAA;AACJ,WAAO,EAAE,GAAG,GAAG,UAAU,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC,EAAA;AAAA,EAChD,QAAQ;AAEN,WAAO,EAAE,GAAG,GAAG,UAAU,CAAA,EAAC;AAAA,EAC5B;AACF;AAEA,SAAS,UAAU,MAA0B;AAC3C,QAAM,OAAO,oBAAA;AACb,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,MAAM;AAC5C,QAAM,MAAM,GAAG,IAAI,IAAI,QAAQ,GAAG;AAClC,gBAAc,KAAK,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAChD,aAAW,KAAK,IAAI;AACtB;AAGO,SAAS,mBAA6B;AAC3C,SAAO,WAAW;AACpB;AASO,SAAS,gBAAgB,KAAa,OAAe,WAAoB;AAC9E,QAAM,OAAO,IAAI,QAAQ,QAAQ,EAAE,KAAK;AACxC,MAAI,SAAS,OAAO,SAAS,KAAM,QAAO;AAC1C,MAAI,CAAC,QAAQ,SAAS,UAAU,SAAS,QAAQ,UAAU,QAAQ,QAAQ,MAAM,EAAE,SAAS,IAAI,EAAG,QAAO;AAE1G,MAAI,mCAAmC,KAAK,IAAI,EAAG,QAAO;AAC1D,SAAO;AACT;AAGA,SAAS,QAAQ,OAAe,QAAyB;AACvD,SAAO,MAAM,WAAW,OAAO,SAAS,GAAG,IAAI,SAAS,GAAG,MAAM,GAAG;AACtE;AAaO,SAAS,aAAa,MAAiC;AAC5D,QAAM,OAAO,SAAA;AACb,MAAI,UAAU;AACd,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,KAAK,QAAQ,QAAQ,EAAE;AACnC,QAAI,CAAC,OAAO,gBAAgB,GAAG,EAAG;AAClC,QAAI,KAAK,SAAS,KAAK,CAAC,MAAM,MAAM,OAAO,QAAQ,KAAK,CAAC,CAAC,EAAG;AAC7D,UAAM,OAAO,KAAK,SAAS,OAAO,CAAC,MAAM,CAAC,QAAQ,GAAG,GAAG,CAAC;AACzD,QAAI,KAAK,WAAW,KAAK,SAAS,OAAQ,WAAU;AACpD,SAAK,WAAW,CAAC,GAAG,MAAM,GAAG;AAC7B,cAAU;AAAA,EACZ;AACA,MAAI,mBAAmB,IAAI;AAC3B,SAAO;AACT;AAEO,SAAS,YAAY,KAAsB;AAChD,SAAO,aAAa,CAAC,GAAG,CAAC;AAC3B;AAGO,SAAS,cAAc,KAAsB;AAClD,QAAM,OAAO,SAAA;AACb,QAAM,OAAO,KAAK,SAAS,OAAO,CAAC,MAAM,MAAM,GAAG;AAClD,MAAI,KAAK,WAAW,KAAK,SAAS,OAAQ,QAAO;AACjD,YAAU,EAAE,GAAG,MAAM,UAAU,MAAM;AACrC,SAAO;AACT;ACtGO,MAAM,mBAA6B;AAAA,EACxC,mBAAmB;AACrB;AASA,SAAS,sBAA8B;AACrC,SAAuB,KAAK,QAAQ,eAAA,CAAgB,GAAG,eAAe;AACxE;AAEO,SAAS,cAAwB;AACtC,QAAM,OAAO,oBAAA;AACb,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,EAAE,GAAG,iBAAA;AACnC,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AACrD,WAAO;AAAA,MACL,mBACE,OAAO,OAAO,sBAAsB,YAChC,OAAO,oBACP,iBAAiB;AAAA,IAAA;AAAA,EAE3B,QAAQ;AACN,WAAO,EAAE,GAAG,iBAAA;AAAA,EACd;AACF;AAGO,SAAS,eAAe,OAAoC;AACjE,QAAM,OAAiB,EAAE,GAAG,cAAY;AACxC,MAAI,OAAO,MAAM,sBAAsB,UAAW,MAAK,oBAAoB,MAAM;AACjF,QAAM,OAAO,oBAAA;AACb,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,MAAM;AAC5C,QAAM,MAAM,GAAG,IAAI,IAAI,QAAQ,GAAG;AAClC,gBAAc,KAAK,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAChD,aAAW,KAAK,IAAI;AACpB,SAAO;AACT;AAOO,SAAS,uBAAuB,MAAuB;AAC5D,SAAO,sCAAsC,KAAK,IAAI;AACxD;ACrDA,MAAM,UAAU,KAAK,KAAK,KAAK,KAAK;AAGpC,IAAI,QAAoD;AAQxD,SAAS,oBAA4B;AACnC,SAAqB,KAAK,QAAQ,eAAA,CAAgB,GAAG,aAAa;AACpE;AAGO,SAAS,YAAoB;AAClC,QAAM,OAAO,kBAAA;AACb,MAAI;AACF,UAAM,EAAE,QAAA,IAAY,SAAS,IAAI;AACjC,QAAI,OAAO,YAAY,QAAS,QAAO,MAAM;AAC7C,UAAM,SAAS,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AACrD,UAAM,SAAiB,CAAA;AACvB,QAAI,UAAU,OAAO,WAAW,UAAU;AACxC,iBAAW,CAAC,IAAI,EAAE,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC7C,YAAI,OAAO,OAAO,YAAY,OAAO,SAAS,EAAE,EAAG,QAAO,EAAE,IAAI;AAAA,MAClE;AAAA,IACF;AACA,YAAQ,EAAE,SAAS,OAAA;AACnB,WAAO;AAAA,EACT,QAAQ;AAIN,WAAO,OAAO,UAAU,CAAA;AAAA,EAC1B;AACF;AAGO,SAAS,YAAY,IAAY,KAAa,KAAK,OAAa;AACrE,QAAM,MAAM,KAAK,IAAA;AACjB,QAAM,OAAe,CAAA;AACrB,aAAW,CAAC,OAAO,IAAI,KAAK,OAAO,QAAQ,UAAA,CAAW,GAAG;AAEvD,QAAI,MAAM,OAAO,WAAW,WAAW,KAAK,eAAA,GAAkB,GAAG,KAAK,OAAO,CAAC,EAAG,MAAK,KAAK,IAAI;AAAA,EACjG;AACA,OAAK,EAAE,IAAI;AACX,QAAM,OAAO,kBAAA;AACb,kBAAgB,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAEnD,UAAQ,EAAE,SAAS,SAAS,IAAI,EAAE,SAAS,QAAQ,KAAA;AACrD;AAMO,SAAS,WAAW,IAAY,iBAAqD;AAC1F,MAAI,CAAC,gBAAiB,QAAO;AAC7B,cAAY,IAAI,kBAAkB,CAAC;AACnC,SAAO;AACT;ACnCO,MAAM,0BAA0B;AAMhC,MAAM,yBAAyB;AAQtC,MAAM,0BAA0B;AAGzB,SAAS,gBACd,SACA,OACA,UACA,OAAO,MACU;AACjB,QAAM,QAAQ,OAAO,0BAA0B;AAC/C,QAAM,WAAwC,CAAA;AAC9C,MAAI,SAAwB;AAC5B,MAAI,SAAwB;AAC5B,aAAW,SAAS,MAAM,QAAQ,SAAS;AACzC,QAAI,MAAM,SAAS,UAAU,WAAW,eAAe,MAAM;AAC7D,QAAI,MAAM,SAAS,QAAQ;AACzB,eAAS,KAAK;AAAA,QACZ,IAAI,MAAM;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,IAAI,MAAM,SAAS,MAAM,OAAO,KAAK;AAAA,MAAA,CACtC;AAAA,IACH,WAAW,MAAM,SAAS,QAAQ;AAChC,eAAS,MAAM;AAAA,IACjB;AAAA,EACF;AACA,QAAM,QAAQ,MAAM,OAAO,QAAA;AAC3B,QAAM,UACJ,CAAC,YAAY,UAAU,QAAQ,KAAK,QAAQ,QAAQ;AACtD,QAAM,UAAU,KAAK,IAAI,GAAG,SAAS,SAAS,KAAK;AACnD,SAAO;AAAA,IACL;AAAA,IACA,WAAW,MAAM,KAAK,aAAa;AAAA,IACnC,WAAW,MAAM,KAAK,aAAa;AAAA,IACnC,aAAa,MAAM,KAAK,eAAe;AAAA,IACvC,OAAO,MAAM,KAAK,SAAS;AAAA;AAAA,IAE3B,UAAU,SAAS,MAAM,CAAC,KAAK;AAAA,IAC/B;AAAA,IACA,QAAQ,OAAO,SAAS;AAAA,IACxB,QAAQ,OAAO,SAAS;AAAA,IACxB;AAAA,IACA;AAAA,EAAA;AAEJ;ACxFO,SAAS,YAAY,EAAE,QAAQ,OAAO,UAA+B;AAC3E,QAAM,OAAO,SAAS;AACtB,QAAM,SAAS,OAAO;AACtB,SAAO,SAAS,IAAI,KAAK,KAAM,OAAO,SAAU,GAAG,IAAI;AACxD;AAOA,eAAsB,gBAAgB,OAAe,WAAmC;AACvF,MAAI;AACH,WAAO,YAAY,MAAM,OAAO,IAAI,CAAC;AAAA,EACtC,QAAQ;AACP,WAAO;AAAA,EACR;AACD;ACbA,MAAM,gBAAgB,UAAU,QAAQ;AAGjC,MAAM,sBAAsB;AAG5B,MAAM,uBAAuB;AAM7B,MAAM,wBAAwB;AAG9B,SAAS,wBAAkC;AAChD,SAAO,CAAC,MAAM,OAAO,mBAAmB,GAAG,MAAM,OAAO,oBAAoB,CAAC;AAC/E;AAMO,SAAS,cAAc,MAAoD;AAChF,SAAO,KAAK,aAAa,KAAK,KAAK,QAAQ;AAC7C;AAaA,eAAsB,oBAAoB,QAAkC;AAC1E,MAAI;AACF,UAAM;AAAA,MACJ;AAAA,MACA,CAAC,iBAAiB,MAAM,QAAQ,MAAM,OAAO,mBAAmB,GAAG,MAAM,OAAO,oBAAoB,CAAC;AAAA,MACrG,EAAE,SAAS,IAAA;AAAA,IAAK;AAElB,UAAM,cAAc,QAAQ,CAAC,cAAc,MAAM,MAAM,QAAQ,eAAe,QAAQ,GAAG;AAAA,MACvF,SAAS;AAAA,IAAA,CACV;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;ACjDA,MAAM,aAAa;AAGnB,MAAM,aAAa;AAGnB,MAAM,aAAa;AAGnB,MAAM,YAAY;AAElB,MAAM,aAAa;AAEnB,MAAM,YAAY;AAGlB,MAAM,QAAQ;AAGd,MAAM,UAAU;AAGhB,MAAM,SAAS;AAGf,MAAM,cAAc;AAGb,SAAS,gBAAgB,SAA8B;AAC5D,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,MAAI,OAAO,MAAM,SAAS;AAC1B,SAAO,QAAQ,KAAK,CAAC,MAAM,IAAI,EAAE,OAAQ;AACzC,MAAI,OAAO,EAAG,QAAO;AACrB,QAAM,OAAO,KAAK,IAAI,GAAG,OAAO,WAAW;AAE3C,WAAS,IAAI,MAAM,KAAK,MAAM,KAAK;AACjC,QAAI,UAAU,KAAK,MAAM,CAAC,CAAC,EAAG,QAAO,EAAE,MAAM,OAAA;AAC7C,QAAI,MAAM,KAAK,MAAM,CAAC,CAAC,GAAG;AACxB,YAAM,QAAQ,MAAM,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,KAAA,EAAO,MAAM,UAAU,IAAI,CAAC,CAAC,EAAE,OAAO,OAAO;AAC9G,aAAO,EAAE,MAAM,UAAU,OAAO,MAAM,GAAG,EAAE,KAAK,sBAAA;AAAA,IAClD;AACA,UAAM,QAAQ,MAAM,CAAC,EAAE,MAAM,UAAU;AACvC,QAAI,CAAC,MAAO;AACZ,QAAI,QAAuB;AAC3B,aAAS,IAAI,IAAI,GAAG,KAAK,MAAM,KAAK;AAClC,YAAM,IAAI,MAAM,CAAC,EAAE,KAAA,EAAO,MAAM,UAAU;AAC1C,UAAI,EAAG,SAAQ,EAAE,CAAC;AAAA,IACpB;AACA,WAAO,EAAE,MAAM,QAAQ,KAAK,SAAS,OAAO,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,EAAE,KAAA,GAAQ,MAAA;AAAA,EAChF;AAIA,WAAS,IAAI,MAAM,KAAK,MAAM,KAAK;AACjC,QAAI,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,EAAG;AAC5B,WAAO,QAAQ,KAAK,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,UAAA,IAAc;AAAA,EACxD;AACA,SAAO;AACT;AAGA,SAAS,SAAS,OAAiB,MAAc,OAA8B;AAC7E,MAAI,QAAQ;AACZ,WAAS,IAAI,QAAQ,GAAG,KAAK,MAAM,KAAK;AACtC,QAAI,WAAW,KAAK,MAAM,CAAC,CAAC,EAAG,SAAQ;AACvC,QAAI,WAAW,KAAK,MAAM,CAAC,CAAC,EAAG;AAAA,EACjC;AACA,MAAI,UAAU,GAAI,QAAO;AACzB,MAAI,MAAM,MAAM,KAAK,EAAE,KAAA;AACvB,WAAS,IAAI,QAAQ,GAAG,IAAI,OAAO,KAAK;AACtC,UAAM,QAAQ,MAAM,CAAC,EAAE,QAAQ,QAAQ,EAAE;AACzC,QAAI,CAAC,UAAU,KAAK,KAAK,EAAG;AAC5B,WAAO;AAAA,EACT;AACA,SAAO;AACT;ACxFO,SAAS,iBAAiB,QAAgB,MAAc,MAAoB;AACjF,MAAI;AACF,UAAM,WAAW,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7D,UAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC;AAE5D,UAAM,eAAe,OAAO,QAAQ,UAAU,EAAE;AAChD;AAAA,MACE;AAAA,MACA,CAAC,iBAAiB,MAAM,cAAc,MAAM,OAAO,QAAQ,GAAG,MAAM,OAAO,QAAQ,CAAC;AAAA,MACpF;AAAA,QACE,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,QAC9B,SAAS;AAAA,MAAA;AAAA,IACX;AAAA,EAEJ,QAAQ;AAAA,EAER;AACF;ACZO,MAAM,eAAe;AAC5B,MAAM,MAAM;AAaZ,SAAS,IAAI,MAAwB;AACnC,SAAO,aAAa,QAAQ,MAAM;AAAA,IAChC,UAAU;AAAA,IACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAC9B,SAAS;AAAA,IACT,WAAW,KAAK,OAAO;AAAA,EAAA,CACxB;AACH;AAGA,SAAS,WAAW,GAAqB;AACvC,MAAI,MAAM,GAAI,QAAO,CAAA;AACrB,QAAM,QAAQ,EAAE,MAAM,IAAI;AAC1B,MAAI,MAAM,MAAM,SAAS,CAAC,MAAM,UAAU,IAAA;AAC1C,SAAO;AACT;AAEO,SAAS,aAAa,QAAqC;AAChE,MAAI;AACJ,MAAI;AACF,UAAM,IAAI;AAAA,MACR;AAAA,MAAmB;AAAA,MAAM;AAAA,MAAM;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MAAgB;AAAA,MAAM;AAAA,MAAM;AAAA,MAAM;AAAA,MAAQ;AAAA,MAAM,IAAI,YAAY;AAAA,MAAI;AAAA,MAAM;AAAA,MAC1E;AAAA,MACA;AAAA,MAAmB;AAAA,MAAM;AAAA,MACzB;AAAA,MACA;AAAA,MAAgB;AAAA,MAAM;AAAA,MAAM;AAAA,MAAM;AAAA,IAAA,CACnC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,KAAK,IAAI,QAAQ,IAAI;AAC3B,MAAI,OAAO,GAAI,QAAO;AACtB,QAAM,SAAS,IAAI,MAAM,GAAG,EAAE,EAAE,KAAA,EAAO,MAAM,KAAK;AAClD,MAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,QAAM,cAAc,OAAO,OAAO,CAAC,CAAC,KAAK;AACzC,QAAM,MAAM,OAAO,CAAC,MAAM;AAC1B,QAAM,OAAO,OAAO,OAAO,CAAC,CAAC,KAAK;AAClC,QAAM,OAAO,OAAO,OAAO,CAAC,CAAC,KAAK;AAElC,QAAM,OAAO,IAAI,MAAM,KAAK,CAAC;AAC7B,QAAM,SAAS,KAAK,QAAQ,MAAM,IAAI;AACtC,MAAI,WAAW,GAAI,QAAO;AAC1B,QAAM,UAAU,KAAK,MAAM,GAAG,MAAM;AACpC,QAAM,YAAY,KAAK,MAAM,SAAS,IAAI,SAAS,CAAC;AAEpD,MAAI,OAAO,WAAW,OAAO;AAG7B,MAAI,gBAAgB,EAAG,QAAO,CAAA;AAAA,WACrB,KAAK,SAAS,YAAa,QAAO,KAAK,MAAM,KAAK,SAAS,WAAW;AAE/E,SAAO,EAAE,aAAa,KAAK,MAAM,MAAM,MAAM,QAAQ,WAAW,SAAS,EAAA;AAC3E;AAUO,SAAS,kBACd,QACA,OACA,KACA,kBACgE;AAChE,MAAI,OAAO,MAAO,QAAO,EAAE,OAAO,OAAO,CAAA,GAAI,aAAa,iBAAA;AAC1D,MAAI,QAAQ;AACZ,MAAI,OAAuE;AAC3E,WAAS,UAAU,GAAG,UAAU,GAAG,WAAW;AAC5C,UAAM,WAAW,QAAQ;AACzB,UAAM,SAAS,MAAM,IAAI;AACzB,QAAI,UAAU,GAAG;AAEf,aAAO,EAAE,OAAO,OAAO,CAAA,GAAI,aAAa,MAAA;AAAA,IAC1C;AACA,QAAI;AACJ,QAAI;AACF,YAAM,IAAI;AAAA,QACR;AAAA,QAAmB;AAAA,QAAM;AAAA,QAAM;AAAA,QAAQ;AAAA,QACvC;AAAA,QACA;AAAA,QAAgB;AAAA,QAAM;AAAA,QAAM;AAAA,QAAM;AAAA,QAAQ;AAAA,QAAM,OAAO,QAAQ;AAAA,QAAG;AAAA,QAAM,OAAO,MAAM;AAAA,MAAA,CACtF;AAAA,IACH,QAAQ;AACN,aAAO;AAAA,IACT;AACA,UAAM,KAAK,IAAI,QAAQ,IAAI;AAC3B,QAAI,OAAO,GAAI,QAAO;AACtB,UAAM,cAAc,OAAO,IAAI,MAAM,GAAG,EAAE,EAAE,KAAA,CAAM,KAAK;AACvD,UAAM,QAAQ,WAAW,IAAI,MAAM,KAAK,CAAC,CAAC;AAC1C,UAAM,QAAQ,cAAc;AAC5B,QAAI,UAAU,EAAG,QAAO,EAAE,OAAO,OAAO,YAAA;AAExC,WAAO,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,KAAK,GAAG,OAAO,YAAA;AACnD,YAAQ;AAAA,EACV;AACA,SAAO;AACT;ACvGA,MAAM,aAAa,IAAI;AAEvB,MAAM,YAAY,KAAK;AACvB,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AAEjB,MAAM,mBAAmB,KAAK;AAErC,MAAM,cAAc;AAuBpB,MAAM,WAAW,CAAC,QAAQ,IAAI,QAAQ,IAAIA,OAAKC,aAAW,UAAU,KAAK,GAAGD,OAAKC,UAAA,GAAW,WAAW,OAAO,CAAC,EAC5G,OAAO,OAAO,EACd,KAAK,SAAS;AAEjB,MAAM,aAAqB,CAAC,KAAK,MAAM,cACrC,IAAI,QAAQ,CAAC,SAAS;AACpBC;AAAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,MACE,SAAS;AAAA,MACT,WAAW,KAAK,OAAO;AAAA;AAAA,MAEvB,KAAK,EAAE,GAAG,QAAQ,KAAK,MAAM,UAAU,qBAAqB,KAAK,iBAAiB,uBAAA;AAAA,IAAuB;AAAA,IAE3G,CAAC,KAAK,QAAQ,WAAW;AACvB,YAAM,OAAO,MAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,KAAM;AAEpE,YAAM,WAAW,CAAC,CAAC,OAAO,IAAI,WAAW,QAAQ,CAAC,CAAC,IAAI,UAAU,IAAI,SAAS;AAC9E,WAAK,EAAE,MAAM,QAAQ,OAAO,MAAM,GAAG,QAAQ,OAAO,WAAW,OAAO,SAAS,MAAM,CAAC,WAAW,IAAI,UAAU,GAAG,GAAG,UAAU;AAAA,IACjI;AAAA,EAAA;AAEJ,CAAC;AAEH,MAAM,kBAAkB,CAAC,SAAiBC,WAAS,MAAM,MAAM,EAAE,MAAM,MAAM,IAAI;AAiBjF,SAAS,UAAa,MAAmB;AACvC,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,WAAO,MAAM,QAAQ,IAAI,IAAI,KAAK,OAAO,CAAC,MAAM,KAAK,OAAO,MAAM,QAAQ,IAAI,CAAA;AAAA,EAChF,QAAQ;AACN,WAAO,CAAA;AAAA,EACT;AACF;AAEA,MAAM,MAAM;AAMZ,eAAe,YAAY,UAAkBC,MAAa,UAA4E;AACpI,QAAM,OAAO,MAAMA,KAAI,OAAO,CAAC,MAAM,UAAU,aAAa,MAAM,GAAG,eAAe;AACpF,QAAM,MAAM,KAAK,OAAO,KAAA;AACxB,MAAI,KAAK,SAAS,KAAK,IAAI,KAAK,GAAG,EAAG,QAAO;AAC7C,QAAM,OAAO,MAAM,SAASJ,OAAK,UAAU,UAAU,CAAC,IAAI,KAAA,KAAU;AACpE,SAAO,IAAI,KAAK,GAAG,IAAI,MAAM;AAC/B;AAGA,eAAe,UAAU,MAAcI,MAA+D;AACpG,QAAM,MAAM,MAAMA,KAAI,MAAM,CAAC,OAAO,SAAS,IAAI,IAAI,QAAQ,YAAY,GAAG,eAAe;AAC3F,QAAM,OAAO,IAAI,OAAO,KAAA;AACxB,MAAI,IAAI,SAAS,KAAK,CAAC,WAAW,IAAI,EAAG,QAAO;AAChD,QAAM,OAAO,MAAMA,KAAI,MAAM,CAAC,OAAO,SAAS,IAAI,iBAAiB,QAAQ,MAAM,GAAG,eAAe;AACnG,QAAM,MAAM,KAAK,OAAO,KAAA;AACxB,SAAO,KAAK,SAAS,KAAK,IAAI,KAAK,GAAG,IAAI,EAAE,MAAM,MAAM,QAAQ,IAAA,IAAQ;AAC1E;AAGA,eAAe,SAAS,KAAaA,MAAqC;AACxE,QAAM,MAAM,MAAMA,KAAI,OAAO,CAAC,aAAa,KAAK,MAAM,GAAG,eAAe;AACxE,QAAM,MAAM,IAAI,OAAO,MAAM,IAAI,EAAE,CAAC;AACpC,SAAO,IAAI,SAAS,KAAK,IAAI,KAAK,GAAG,IAAI,MAAM;AACjD;AAIO,MAAM,aAAa;AAAA,EAChB,SAA+B;AAAA,EAC/B,UAAgD;AAAA,EAChD,MAAuB;AAAA;AAAA,EAEvB,cAAc;AAAA,EACd,8BAAc,IAAA;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,OAAmB,IAAI;AACjC,SAAK,MAAM,KAAK,OAAO;AACvB,SAAK,MAAM,KAAK,OAAO,KAAK;AAC5B,SAAK,WAAW,KAAK,YAAY;AAAA,EACnC;AAAA;AAAA,EAGA,UAAgC;AAC9B,QAAI,KAAK,QAAQ,KAAK,cAAc,cAAc,CAAC,KAAK,QAAS,MAAK,KAAK,KAAA,EAAO,MAAM,CAAC,QAAQ,QAAQ,KAAK,0BAA0B,GAAG,CAAC;AAC5I,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,OAAsC;AACpC,QAAI,CAAC,KAAK,QAAS,MAAK,cAAc,KAAK,IAAA;AAC3C,SAAK,YAAY,KAAK,QAAA,EAAU,QAAQ,MAAM;AAC5C,WAAK,UAAU;AAAA,IACjB,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,MAAM,UAAyC;AAC7C,UAAM,KAAK;AACX,WAAO,KAAK,KAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAW,MAAoB,SAAqB,MAAM;AAAA,EAAC,GAAwC;AACjG,QAAI,KAAK,KAAK,UAAU,UAAW,QAAO,EAAE,SAAS,OAAO,KAAK,SAAS,KAAK,GAAG,EAAA;AAClF,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,MAAgB,EAAE,OAAO,WAAW,WAAW,KAAK,OAAO,YAAY,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,EAAA;AAChH,SAAK,MAAM;AACX,SAAK,KAAK,OAAO,KAAK,MAAM,MAAM;AAClC,WAAO,EAAE,SAAS,MAAM,KAAK,SAAS,GAAG,EAAA;AAAA,EAC3C;AAAA;AAAA,EAGA,WAA4B;AAC1B,WAAO,KAAK,OAAO,SAAS,KAAK,GAAG;AAAA,EACtC;AAAA;AAAA,EAGA,MAAc,OAAO,KAAe,MAAkB,QAAmC;AACvF,QAAI;AACF,iBAAW,EAAE,MAAM,MAAM,MAAA,KAAW,MAAM;AACxC,aAAK,QAAQ;AACb,aAAK,YAAY,KAAK,IAAA;AACtB,cAAM,MAAM,MAAM,KAAK,IAAI,UAAU,MAAM,KAAK,EAAE;AAAA,UAChD,CAAC,SAAoB,EAAE,MAAM,IAAI,QAAQ,IAAI,QAAQ,OAAO,GAAG,EAAA;AAAA,QAAE;AAEnE,aAAK,aAAa,KAAK,IAAA;AAEvB,cAAM,SAAS,GAAG,IAAI,MAAM,GAAG,IAAI,MAAM,GAAG,KAAA,EAAO,MAAM,CAAC,WAAW;AACrE,YAAI,IAAI,UAAU;AAChB,eAAK,QAAQ;AACb,eAAK,SAAS,GAAG,MAAM;AAAA,iBAAoB,KAAK,MAAM,QAAQ,GAAM,CAAC,0BAA0B,UAAA;AAAA,QACjG,OAAO;AACL,eAAK,QAAQ,IAAI,SAAS,IAAI,OAAO;AACrC,eAAK,SAAS;AAAA,QAChB;AAAA,MACF;AACA,YAAM,KAAK,QAAA,EAAU,MAAM,CAAC,QAAQ,QAAQ,KAAK,0BAA0B,GAAG,CAAC;AAAA,IACjF,UAAA;AACE,UAAI,QAAQ;AACZ,UAAI,aAAa,KAAK,IAAA;AACtB,aAAA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,UAAyC;AACrD,UAAM,CAAC,KAAK,GAAG,IAAI,MAAM,QAAQ,IAAI;AAAA,MACnC,KAAK,IAAI,UAAU,CAAC,UAAU,eAAe,QAAQ,QAAQ,GAAG,eAAe;AAAA,MAC/E,KAAK,IAAI,UAAU,CAAC,UAAU,QAAQ,QAAQ,GAAG,eAAe;AAAA,IAAA,CACjE;AACD,QAAI,IAAI,SAAS,KAAK,IAAI,SAAS,GAAG;AACpC,cAAQ,KAAK,8BAA8B,IAAI,UAAU,IAAI,QAAQ,KAAA,EAAO,MAAM,IAAI,EAAE,CAAC,CAAC;AAC1F,aAAO,KAAK;AAAA,IACd;AACA,UAAM,eAAe,MAAM,QAAQ,IAAI,UAA0B,IAAI,MAAM,EAAE,IAAI,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC;AAC5G,UAAM,UAA0B,UAAqB,IAAI,MAAM,EAC5D,OAAO,CAAC,MAAM,OAAO,EAAE,OAAO,aAAa,EAAE,UAAU,UAAa,EAAE,UAAU,OAAO,EACvF,IAAI,CAAC,OAAO;AAAA,MACX,IAAI,EAAE;AAAA,MACN,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU;AAAA,MACrD,SAAS,EAAE,YAAY;AAAA,IAAA,EACvB,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AAC1C,UAAM,SAAwB;AAAA,MAC5B,cAAc,aAAa,OAAO,CAAC,MAA8B,MAAM,IAAI;AAAA,MAC3E;AAAA,MACA,QAAQ,KAAK,IAAA;AAAA,IAAI;AAEnB,SAAK,SAAS;AACd,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,YAAY,GAAsD;AAC9E,QAAI,OAAO,EAAE,SAAS,SAAU,QAAO;AACvC,UAAM,WAAW,OAAO,EAAE,oBAAoB,WAAW,EAAE,kBAAkB;AAE7E,UAAM,SACJ,EAAE,WAAW,YAAY,OAAO,EAAE,SAAS,WACvC,EAAE,OACF,OAAO,EAAE,QAAQ,WACf,gCAAgC,KAAK,gBAAgB,EAAE,GAAG,KAAK,EAAE,IAAI,CAAC,IACtE;AACR,UAAM,UAAU,WAAW,OAAO,EAAE,QAAQ,WAAW,EAAE,MAAM;AAC/D,UAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,MACzC,WAAW,YAAY,UAAU,KAAK,KAAK,KAAK,QAAQ,IAAI;AAAA,MAC5D,KAAK,OAAO,QAAQ,OAAO;AAAA,IAAA,CAC5B;AACD,WAAO,EAAE,MAAM,EAAE,MAAM,MAAM,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,QAAQ,UAAU,KAAA;AAAA,EAC1F;AAAA,EAEA,MAAc,OAAO,QAA4B,SAAgD;AAC/F,QAAI,CAAC,QAAS,QAAO;AACrB,UAAM,SAAS,KAAK,QAAQ,IAAI,OAAO;AACvC,QAAI,UAAU,KAAK,IAAA,IAAQ,OAAO,KAAK,kBAAkB,OAAO;AAChE,QAAI,SAAwB;AAC5B,QAAI,QAAQ;AACV,eAAU,MAAM,UAAU,QAAQ,KAAK,GAAG,KAAM,EAAE,MAAM,QAAQ,QAAQ,MAAM,SAAS,sBAAsB,MAAM,QAAQ,KAAK,GAAG,EAAA;AAAA,IACrI,OAAO;AACL,eAAS,EAAE,MAAM,SAAS,QAAQ,MAAM,SAAS,SAAS,KAAK,GAAG,EAAA;AAAA,IACpE;AACA,QAAI,OAAQ,MAAK,QAAQ,IAAI,SAAS,EAAE,IAAI,KAAK,IAAA,GAAO,OAAA,CAAQ;AAChE,WAAO;AAAA,EACT;AACF;AAUA,SAAS,UAAU,MAAgC;AACjD,QAAMA,OAAM,CAAC,MAAyB,OAAe,MAAgB,WAA6B;AAAA,IAChG,MAAM,EAAE,MAAM,OAAO,SAAS,UAAU,KAAK,KAAK,GAAG,CAAC,IAAI,OAAO,WAAW,WAAW,MAAM,YAAY,MAAM,QAAQ,GAAA;AAAA,IACvH;AAAA,IACA;AAAA,EAAA;AAEF,SAAO;AAAA,IACL,GAAG,KAAK,OAAO,IAAI,CAAC,SAASA,KAAI,UAAU,YAAY,IAAI,IAAI,CAAC,UAAU,eAAe,UAAU,IAAI,GAAG,gBAAgB,CAAC;AAAA,IAC3H,GAAG,KAAK,QAAQ;AAAA,MAAI,CAAC,MACnB,iBAAiB,IACbA,KAAI,WAAW,cAAc,EAAE,IAAI,IAAI,CAAC,UAAU,WAAW,GAAG,EAAE,IAAI,IAAI,EAAE,WAAW,EAAE,GAAG,eAAe;AAAA;AAAA,QAE3GA,KAAI,WAAW,cAAc,EAAE,IAAI,IAAI,CAAC,UAAU,WAAW,EAAE,MAAM,iBAAiB,EAAE,IAAI,GAAG,gBAAgB;AAAA;AAAA,IAAA;AAAA,IAErH,GAAG,KAAK,OAAO,IAAI,CAAC,OAAOA,KAAI,UAAU,YAAY,EAAE,IAAI,CAAC,UAAU,UAAU,IAAI,WAAW,MAAM,GAAG,eAAe,CAAC;AAAA,EAAA;AAE5H;AAEA,SAAS,SAAS,KAAyB;AACzC,SAAO,EAAE,GAAG,KAAK,OAAO,IAAI,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,EAAA,EAAI,EAAA;AACzD;AAKO,SAAS,eAA6B;AAC3C,QAAM,IAAI;AACV,SAAQ,EAAE,uBAAuB,IAAI,aAAA;AACvC;AC3SO,MAAM,YAAY,KAAK;AAEvB,MAAM,YAAY;AAEzB,MAAM,eAAe;AAMrB,SAAS,WAAW,MAAc,UAAmB,QAA0B;AAC7E,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,MAAI,gBAAgB,MAAA;AACpB,MAAI,cAAc,IAAA;AAClB,QAAM,MAAe,CAAA;AACrB,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,OAAQ;AAClB,QAAI;AACF,YAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,UAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,EAAG,KAAI,KAAK,MAAe;AAAA,IAC9F,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,IAAI,OAA+B;AAC1C,SAAO,OAAO,UAAU,YAAY,MAAM,KAAA,IAAS,QAAQ;AAC7D;AAEA,SAAS,KAAK,MAAsB;AAClC,QAAM,OAAO,KAAK,QAAQ,QAAQ,GAAG,EAAE,KAAA;AACvC,SAAO,KAAK,SAAS,YAAY,GAAG,KAAK,MAAM,GAAG,YAAY,CAAC,CAAC,MAAM;AACxE;AAOO,SAAS,WAAW,OAA6B;AACtD,MAAI,MAAM,SAAS,UAAU,MAAM,WAAW,QAAQ,MAAM,gBAAgB,KAAM,QAAO;AACzF,QAAM,UAAU,MAAM;AACtB,MAAI,OAAsB;AAC1B,MAAI,OAAO,SAAS,YAAY,iBAAiB,QAAQ;AAAA,WAChD,MAAM,QAAQ,SAAS,OAAO,GAAG;AACxC,UAAM,QAAQ,QAAQ,QAAQ;AAAA,MAC5B,CAAC,MAA2C,CAAC,CAAC,KAAK,OAAO,MAAM,YAAY,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS;AAAA,IAAA;AAErH,QAAI,CAAC,SAAS,QAAQ,QAAQ,KAAK,CAAC,MAAM,GAAG,SAAS,aAAa,EAAG,QAAO;AAC7E,WAAO,MAAM;AAAA,EACf;AACA,MAAI,CAAC,MAAM,KAAA,EAAQ,QAAO;AAC1B,QAAM,UAAU,kBAAkB,IAAI;AACtC,MAAI,QAAS,QAAO,KAAK,QAAQ,OAAO,GAAG,QAAQ,IAAI,IAAI,QAAQ,IAAI,KAAK,QAAQ,IAAI;AACxF,MAAI,yEAAyE,KAAK,IAAI,KAAK,KAAK,WAAW,SAAS,GAAG;AACrH,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI;AAClB;AAEA,SAAS,YAAY,OAA6B;AAChD,QAAM,KAAK,OAAO,MAAM,cAAc,WAAW,KAAK,MAAM,MAAM,SAAS,IAAI;AAC/E,SAAO,OAAO,SAAS,EAAE,IAAI,KAAK;AACpC;AAOA,eAAsB,gBAAgB,MAA2C;AAC/E,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,KAAK,MAAM,GAAG;AAAA,EAC/B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACF,UAAM,OAAO,MAAM,OAAO,KAAA;AAC1B,WAAO,KAAK;AACZ,cAAU,KAAK;AACf,UAAM,UAAU,OAAO,MAAM,KAAK,IAAI,MAAM,SAAS,CAAC;AACtD,UAAM,OAAO,KAAK,SAAS,GAAG,QAAQ,QAAQ,CAAC;AAC/C,WAAO,QAAQ,SAAS,MAAM;AAC9B,QAAI,OAAO,WAAW;AACpB,YAAM,aAAa,KAAK,IAAI,OAAO,WAAW,SAAS;AACvD,YAAM,UAAU,OAAO,MAAM,UAAU;AACvC,YAAM,OAAO,KAAK,SAAS,GAAG,YAAY,OAAO,UAAU;AAC3D,aAAO,QAAQ,SAAS,MAAM;AAAA,IAChC,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT,UAAA;AACE,UAAM,OAAO,MAAA;AAAA,EACf;AAEA,QAAM,cAAc,WAAW,MAAM,OAAO,OAAO,SAAS;AAE5D,QAAM,cAAc,OAAO,WAAW,MAAM,OAAO,IAAI,aAAa,CAAC,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,CAAA;AACnG,QAAM,MAAM,CAAC,GAAG,aAAa,GAAG,WAAW;AAE3C,MAAI,MAAqB;AACzB,MAAI,SAAwB;AAC5B,MAAI,SAAwB;AAC5B,MAAI,cAA6B;AACjC,MAAI,UAAyB;AAC7B,MAAI,aAA4B;AAChC,MAAI,OAAsB;AAC1B,aAAW,SAAS,KAAK;AAEvB,QAAI,MAAM,gBAAgB,KAAM;AAChC,UAAM,aAAa,IAAI,MAAM,UAAU;AACvC,QAAI,YAAY,WAAW,KAAK,EAAG,QAAO;AAC1C,UAAM,IAAI,MAAM,GAAG,KAAK;AACxB,aAAS,IAAI,MAAM,SAAS,KAAK;AACjC,eAAW,WAAW,KAAK;AAC3B,QAAI,MAAM,SAAS,8BAA8B,IAAI,MAAM,WAAW,KAAK;AAC3E,QAAI,MAAM,SAAS,sBAAsB,IAAI,MAAM,OAAO,KAAK;AAC/D,QAAI,MAAM,SAAS,4BAA4B,IAAI,MAAM,UAAU,KAAK;AACxE,WAAO,YAAY,KAAK,KAAK;AAAA,EAC/B;AACA,aAAW,aAAa,KAAK,UAAU,IAAI;AAE3C,gBAAe,MAAM,cAAc,IAAI,KAAM;AAC7C,QAAM,QAAQ,eAAe;AAC7B,MAAI,CAAC,UAAU,CAAC,MAAO,QAAO;AAC9B,SAAO;AAAA,IACL,IAAI,SAAS,MAAM,QAAQ;AAAA,IAC3B;AAAA,IACA;AAAA,IACA,OAAO,QAAQ,KAAK,KAAK,IAAI;AAAA,IAC7B;AAAA,IACA,cAAc,QAAQ;AAAA,EAAA;AAE1B;AAEA,eAAe,cAAc,gBAAgD;AAC3E,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,MAAMD,WAASH,OAAK,eAAe,QAAQ,YAAY,EAAE,GAAG,mBAAmB,GAAG,MAAM,CAAC;AAChH,WAAO,IAAI,KAAK,WAAW;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,gBAAgB,OAAO,gBAA4E;AACvH,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,QAAQ,IAAI;AAAA,EAC3B,QAAQ;AACN,WAAO,CAAA;AAAA,EACT;AACA,QAAM,MAAyD,CAAA;AAC/D,aAAW,OAAO,MAAM;AACtB,QAAI;AACJ,QAAI;AACF,cAAQ,MAAM,QAAQA,OAAK,MAAM,GAAG,CAAC;AAAA,IACvC,QAAQ;AACN;AAAA,IACF;AACA,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,aAAa,KAAK,IAAI,EAAG;AAC9B,YAAM,OAAOA,OAAK,MAAM,KAAK,IAAI;AACjC,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,IAAI;AAC5B,YAAI,KAAK,OAAA,EAAU,KAAI,KAAK,EAAE,MAAM,SAAS,KAAK,SAAS,MAAM,KAAK,MAAM;AAAA,MAC9E,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOO,MAAM,iBAAiB;AAAA,EAG5B,YAA6B,OAAqB,cAAc;AAAnC,SAAA,OAAA;AAAA,EAAoC;AAAA,EAApC;AAAA,EAFrB,4BAAY,IAAA;AAAA;AAAA,EAKpB,MAAM,OAA+B;AACnC,UAAM,QAAQ,MAAM,gBAAgB,KAAK,MAAM;AAC/C,UAAM,2BAAW,IAAA;AACjB,eAAW,QAAQ,OAAO;AACxB,WAAK,IAAI,KAAK,IAAI;AAClB,YAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,IAAI;AACtC,UAAI,SAAS,MAAM,YAAY,KAAK,WAAW,MAAM,SAAS,KAAK,KAAM;AACzE,WAAK,MAAM,IAAI,KAAK,MAAM,EAAE,SAAS,KAAK,SAAS,MAAM,KAAK,MAAM,SAAS,MAAM,gBAAgB,KAAK,IAAI,GAAG;AAAA,IACjH;AACA,eAAW,QAAQ,KAAK,MAAM,KAAA,EAAQ,KAAI,CAAC,KAAK,IAAI,IAAI,EAAG,MAAK,MAAM,OAAO,IAAI;AACjF,UAAM,2BAAW,IAAA;AACjB,eAAW,EAAE,QAAA,KAAa,KAAK,MAAM,UAAU;AAC7C,UAAI,CAAC,QAAS;AACd,YAAM,QAAQ,KAAK,IAAI,QAAQ,EAAE;AACjC,UAAI,CAAC,SAAS,MAAM,eAAe,QAAQ,aAAc,MAAK,IAAI,QAAQ,IAAI,OAAO;AAAA,IACvF;AACA,WAAO,CAAC,GAAG,KAAK,OAAA,CAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,eAAe,EAAE,YAAY;AAAA,EAC1E;AACF;AAGO,SAAS,YAAY,MAA8B,MAA0C;AAClG,SAAO,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;AAC3C;AChJA,MAAM,iBAAqC;AAAA,EAC1C,oBAAoB;AAAA,EACpB,6BAA6B;AAAA,EAC7B,yBAAyB;AAAA,EACzB,mBAAmB;AAAA,EACnB,OAAO;AACR;AAeA,SAAS,IAAI,OAAiB,QAAkC;AAC/D,MAAI,MAAM,UAAU,WAAW,CAAC,OAAO,MAAO;AAE9C,QAAM,SAAS,OAAO,MAAM,SAAS;AACrC,QAAM,MAAM,MAAM,OACf,GAAG,MAAM,IAAI,MAAM,OAAO,IAAI,KAAK,UAAU,MAAM,IAAI,CAAC,KACxD,GAAG,MAAM,IAAI,MAAM,OAAO;AAE7B,UAAQ,MAAM,OAAA;AAAA,IACb,KAAK;AACJ,cAAQ,MAAM,GAAG;AACjB;AAAA,IACD,KAAK;AACJ,cAAQ,KAAK,GAAG;AAChB;AAAA,IACD,KAAK;AACJ,cAAQ,KAAK,GAAG;AAChB;AAAA,IACD,KAAK;AACJ,cAAQ,MAAM,GAAG;AACjB;AAAA,EAAA;AAEH;AAyEA,MAAM,wBAAwB;AAG9B,MAAM,sBAAsB;AAO5B,SAAS,wBACR,UACM;AACN,QAAM,+BAAe,IAAA;AACrB,QAAM,WAAgB,CAAA;AACtB,aAAW,WAAW,UAAU;AAC/B,QAAI,CAAC,QAAQ,aAAa;AACzB,eAAS,KAAK,OAAO;AACrB;AAAA,IACD;AACA,UAAM,WAAW,SAAS,IAAI,QAAQ,WAAW;AACjD,QAAI,CAAC,YAAY,QAAQ,cAAc,SAAS,aAAa;AAC5D,eAAS,IAAI,QAAQ,aAAa,OAAO;AAAA,IAC1C;AAAA,EACD;AACA,SAAO,CAAC,GAAG,SAAS,OAAA,GAAU,GAAG,QAAQ;AAC1C;AAGA,MAAM,oCAAoB,IAAA;AAc1B,MAAM,iBAAiB,KAAK,KAAK;AAEjC,SAAS,mBAAmB,SAA2B;AACtD,UAAQ,QAAQ,oBAAoB,KAAK,KAAK,KAAK,IAAA,IAAQ,QAAQ,cAAc;AAClF;AAEA,eAAe,yBAKZ;AACF,QAAM,WAAW,eAAA;AACjB,QAAM,+BAAe,IAAA;AACrB,QAAM,kCAAkB,IAAA;AAGxB,QAAM,QAAQ,CAAC,SAAkB,UAA4B;AAC5D,kBAAc,QAAQ,IAAI,KAAK;AAC/B,WAAO,OAAO,SAAS,KAAK;AAAA,EAC7B;AAEA,QAAM,QAAQ;AAAA,IACb,SAAS,IAAI,OAAO,YAAY;AAC/B,UAAI,CAAC,QAAQ,YAAa;AAI1B,YAAM,MAAM,MAAM,wBAAwB,QAAQ,aAAa,IAAI;AACnE,UAAI,CAAC,IAAK;AACV,YAAM,UAAU,UAAU,GAAG;AAC7B,eAAS,IAAI,QAAQ,aAAa,OAAO;AACzC,kBAAY,IAAI,QAAQ,aAAa,GAAG;AAExC,UAAI,QAAQ,UAAU,UAAU,yBAAyB,OAAO,GAAG;AAClE,cAAM,SAAS,EAAE,OAAO,QAAQ,gBAAgB,MAAM,aAAa,MAAM;AAAA,MAC1E,WACC,QAAQ,UAAU,UAClB,CAAC,mBAAmB,OAAO,KAC3B,wBAAwB,OAAO,GAC9B;AAKD,cAAM,SAAS,cAAc,IAAI,QAAQ,EAAE,KAAK,KAAK;AACrD,YAAI,SAAS,GAAG;AACf,wBAAc,OAAO,QAAQ,EAAE;AAC/B,gBAAM,SAAS,EAAE,OAAO,QAAQ,gBAAgB,MAAM,aAAa,MAAM;AAAA,QAC1E,OAAO;AACN,wBAAc,IAAI,QAAQ,IAAI,KAAK;AAAA,QACpC;AAAA,MACD,OAAO;AACN,sBAAc,OAAO,QAAQ,EAAE;AAAA,MAChC;AAGA,UAAI,QAAQ,UAAU,QAAQ;AAC7B,cAAM,WAAW,iBAAiB,OAAO;AACzC,YAAI,gBAAgB,SAAS,EAAE,OAAO,QAAQ,gBAAgB,UAAU;AAAA,MACzE;AAAA,IACD,CAAC;AAAA,EAAA;AAIF,MAAI,cAAc,OAAO,GAAG;AAC3B,UAAM,OAAO,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC9C,eAAW,MAAM,cAAc,QAAQ;AACtC,UAAI,CAAC,KAAK,IAAI,EAAE,EAAG,eAAc,OAAO,EAAE;AAAA,IAC3C;AAAA,EACD;AAEA,SAAO,EAAE,UAAU,aAAa,SAAA;AACjC;AAUA,MAAM,iCAAiB,IAAA;AAEvB,SAAS,aAAa,WAAmB,UAAoD;AAC5F,MAAI,CAAC,UAAU;AACd,eAAW,OAAO,SAAS;AAC3B,WAAO;AAAA,EACR;AACA,QAAM,EAAE,WAAW,GAAG,KAAA,IAAS;AAC/B,MAAI,cAAc,KAAM,QAAO,EAAE,GAAG,MAAM,YAAY,WAAW,IAAI,SAAS,KAAK,KAAA;AAGnF,QAAM,QAAQ,KAAK,IAAA,IAAQ,YAAY;AACvC,QAAM,QAAQ,WAAW,IAAI,SAAS;AACtC,MAAI,UAAU,UAAa,KAAK,IAAI,QAAQ,KAAK,KAAK,IAAM,QAAO,EAAE,GAAG,MAAM,YAAY,MAAA;AAC1F,aAAW,IAAI,WAAW,KAAK;AAC/B,SAAO,EAAE,GAAG,MAAM,YAAY,MAAA;AAC/B;AAOA,MAAM,kCAAkB,IAAA;AAGxB,MAAM,6BAAa,IAAA;AAEnB,IAAI,WAAW;AAMf,MAAM,0CAA0B,IAAA;AAUhC,SAAS,oBAAoB,GAAkB;AAC9C,OAAK,EAAE,SAAS,cAAc,SAAU;AACxC,QAAM,QAAQ,oBAAoB,CAAC;AACnC,MAAI,UAAU,UAAa,oBAAoB,IAAI,EAAE,EAAE,MAAM,MAAO;AACpE,sBAAoB,IAAI,EAAE,IAAI,KAAK;AACnC,QAAM,OAAO,oBAAoB,KAAK;AACtC,MAAI,UAAU,EAAE,gBAAgB,MAAO;AACvC,gBAAc,EAAE,IAAI,EAAE,cAAc,MAAM;AAC1C,IAAE,eAAe;AAClB;AAOA,SAAS,YAAY,GAAgC;AACpD,QAAM,MAAM,eAAe,EAAE,GAAG;AAChC,MAAI,IAAK,QAAO,EAAE,GAAG,KAAK,QAAQ,MAAA;AAClC,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,UAAU,eAAe,CAAC,GAAG,QAAA;AACnC,SAAO,UAAU,EAAE,GAAG,SAAS,QAAQ,cAAc;AACtD;AAEA,eAAsB,2BAAkE;AACvF,UAAQ,MAAM,wBAAwB;AACvC;AAMA,SAAS,WAAW,OAA8B,UAAkC;AACnF,QAAM,SAAS,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC;AACzD,QAAM,MAAmB,CAAA;AACzB,aAAW,CAAC,QAAQ,IAAI,KAAK,OAAO;AACnC,QAAI,OAAO,IAAI,MAAM,EAAG;AACxB,QAAI,KAAK,EAAE,QAAQ,SAAS,OAAO,MAAM,GAAG,EAAE,CAAC,GAAG,SAAS,KAAK,SAAS,KAAK,KAAK,KAAK,OAAO,KAAK,OAAO;AAAA,EAC5G;AACA,SAAO;AACR;AAGA,eAAe,uBAAgG;AAC9G,QAAM,CAAC,EAAE,UAAU,aAAa,SAAA,GAAY,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,IACzE,uBAAA;AAAA,IACA,aAAA;AAAA,EAAa,CACb;AAED,QAAM,QAAQ,UAAA;AAKd,aAAW,KAAK,UAAU;AACzB,UAAM,OAAO,EAAE,cAAc,SAAS,IAAI,EAAE,WAAW,IAAI;AAC3D,QAAI,QAAQ,cAAc,IAAI,KAAK,CAAC,YAAY,IAAI,EAAE,WAAY,GAAG;AACpE,YAAM,SAAS,EAAE;AACjB,kBAAY,IAAI,MAAM;AACtB,WAAK,oBAAoB,MAAM,EAAE,QAAQ,MAAM,YAAY,OAAO,MAAM,CAAC;AAAA,IAC1E;AAAA,EACD;AAMA,QAAM,OAAO,cAAc;AAC3B,aAAW,KAAK,UAAU;AACzB,QAAI,OAAO,IAAI,EAAE,EAAE,EAAG;AACtB,WAAO,IAAI,EAAE,EAAE;AACf,QAAI,CAAC,YAAY,CAAC,KAAM;AACxB,QAAI,EAAE,UAAU,CAAC,EAAE,gBAAgB,EAAE,SAAS,cAAc,SAAU;AACtE,QAAI,CAAC,uBAAuB,EAAE,YAAY,MAAM,GAAG,EAAE,CAAC,CAAC,EAAG;AAC1D,qBAAiB,GAAG,IAAI;AAAA,EACzB;AACA,aAAW;AAEX,aAAW,KAAK,SAAU,qBAAoB,CAAC;AAI/C,oBAAkB,QAAQ;AAK1B,aAAW,KAAK,UAAU;AACzB,QAAI,CAAC,EAAE,YAAa;AACpB,UAAM,UAAU,SAAS,IAAI,EAAE,WAAW;AAC1C,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ,wBAAwB,GAAG,OAAO;AAChD,QAAI,WAAW,EAAE,UAAU,OAAO;AACjC,oBAAc,EAAE,IAAI,EAAE,QAAQ,OAAO;AACrC,QAAE,SAAS;AAAA,IACZ;AACA,SAAK,0BAA0B,EAAE,aAAa,SAAS,KAAK;AAAA,EAC7D;AAEA,QAAM,SAAS,UAAA;AACf,QAAM,mBAAmB,SAAS,IAAI,CAAC,MAAM;AAC5C,UAAM,YAAY,EAAE,cAAe,SAAS,IAAI,EAAE,WAAW,GAAG,SAAS,OAAQ;AACjF,UAAM,MAAM,EAAE,cAAc,YAAY,IAAI,EAAE,WAAW,IAAI;AAE7D,UAAM,WAAW,EAAE,cAAc,SAAS,IAAI,EAAE,WAAW,IAAI;AAG/D,UAAM,MAAM,MAAM,cAAc,GAAG,IAAI;AACvC,UAAM,WAAqC;AAAA,MAC1C,GAAG;AAAA,MACH,YAAY;AAAA,MACZ,YAAY,EAAE,cAAc,SAAS,IAAI,EAAE,WAAW,IAAI;AAAA,MAC1D,aAAa,KAAK,QAAQ;AAAA,MAC1B,YAAY,KAAK,QAAQ;AAAA;AAAA;AAAA,MAGzB,YAAY,MAAM,mBAAmB,GAAG,IAAI,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAO5C,aAAa,WACV,kBAAkB,UAAU;AAAA,QAC5B,cAAc,EAAE,UAAU,aAAa,EAAE,UAAU;AAAA,MAAA,CACnD,IACA;AAAA;AAAA,MAEH,aAAa,WAAW,iBAAiB,QAAQ,IAAI;AAAA,MACrD,YAAY,WAAW,gBAAgB,QAAQ,IAAI;AAAA,MACnD,aAAa,WAAW,iBAAiB,QAAQ,IAAI;AAAA,MACrD,SAAS,cAAc,EAAE,EAAE;AAAA;AAAA;AAAA,MAG3B,eAAe,aAAa,EAAE,IAAI,YAAY,EAAE,UAAU,SAAS,aAAa,QAAQ,IAAI,IAAI;AAAA;AAAA,MAEhG,aAAa,mBAAmB,CAAC;AAAA,MACjC,OAAO,SAAS,EAAE,UAAU,EAAE,aAAa;AAAA;AAAA,MAE3C,OAAO,EAAE,cACN,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,UAAU,WAAW,GAAG,WAAW,IAAI,IACtE,CAAA;AAAA,MACH,WAAW,EAAE,cAAc,cAAc,EAAE,WAAW,IAAI,CAAA;AAAA,MAC1D,iBAAiB,OAAO,EAAE,EAAE,KAAK;AAAA,MACjC,SAAS,YAAY,CAAC;AAAA,IAAA;AAGvB,QAAI,EAAE,eAAe,MAAM,EAAE,WAAW,GAAG;AAC1C,YAAM,aAAa,MAAM,EAAE,WAAW;AACtC,YAAM,cAAc,SAAS,KAAK,CAAC,MAAM,EAAE,gBAAgB,UAAU;AACrE,UAAI,aAAa;AAChB,iBAAS,YAAY,YAAY;AAAA,MAClC;AAAA,IACD;AAEA,WAAO;AAAA,EACR,CAAC;AAED,SAAO,EAAE,UAAU,wBAAwB,gBAAgB,GAAG,OAAO,WAAW,UAAU,QAAQ,EAAA;AACnG;AAmBO,SAAS,WAAW,QAAgB,MAAc,MAAoB;AAC5E,mBAAiB,QAAQ,MAAM,IAAI;AACpC;AAMA,IAAI,oBAA0H;AAAA,EAC7H,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,UAAU;AAAA,EACV,WAAW;AAAA,EACX,MAAM;AACP;AACA,IAAI,cAAc;AAClB,IAAI,eAAe;AACnB,IAAI,mBAA0D;AAC9D,IAAI,sBAAsB;AAE1B,SAAS,qBAA2B;AACnC,OAAK,gBAAA,EAAkB,KAAK,CAAC,SAAS;AACrC,sBAAkB,OAAO;AAAA,EAC1B,CAAC;AACD,MAAI;AACH,UAAMK,QAAO,aAAa,cAAc,OAAO;AAC/C,UAAM,UAAU,aAAa,iBAAiB,OAAO;AAGrD,UAAM,UAAUA,MAAK,MAAM,IAAI,EAAE,CAAC;AAClC,UAAM,WAAW,QAAQ,MAAM,KAAK,EAAE,MAAM,CAAC,EAAE,IAAI,MAAM;AACzD,UAAM,OAAO,SAAS,CAAC,KAAK,SAAS,CAAC,KAAK;AAC3C,UAAM,QAAQ,SAAS,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AAChD,UAAM,WAAW,OAAO;AACxB,UAAM,YAAY,QAAQ;AAC1B,UAAM,aAAa,YAAY,IAAI,KAAK,OAAO,IAAI,WAAW,aAAa,GAAG,IAAI;AAClF,kBAAc;AACd,mBAAe;AAGf,UAAM,MAA8B,CAAA;AACpC,eAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACvC,YAAM,QAAQ,KAAK,MAAM,iBAAiB;AAC1C,UAAI,MAAO,KAAI,MAAM,CAAC,CAAC,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,IACjD;AACA,UAAM,WAAW,IAAI,UAAU,KAAK;AACpC,UAAM,eAAe,IAAI,cAAc,KAAK;AAC5C,UAAM,YAAY,IAAI,WAAW,KAAK;AACtC,UAAM,WAAW,IAAI,UAAU,KAAK;AAEpC,wBAAoB;AAAA,MACnB,KAAK;AAAA,MACL,KAAK,KAAK,OAAQ,WAAW,gBAAgB,WAAY,GAAG;AAAA,MAC5D,MAAM,YAAY,IAAI,KAAK,OAAQ,YAAY,YAAY,YAAa,GAAG,IAAI;AAAA,MAC/E,UAAU,KAAK,MAAM,WAAW,IAAI;AAAA,MACpC,WAAW,KAAK,MAAM,YAAY,IAAI;AAAA,MACtC,MAAM,kBAAkB;AAAA,IAAA;AAAA,EAE1B,QAAQ;AAAA,EAER;AACD;AAEA,SAAS,mBAAyB;AACjC;AACA,MAAI,wBAAwB,GAAG;AAC9B,uBAAA;AACA,uBAAmB,YAAY,oBAAoB,GAAM;AAAA,EAC1D;AACD;AAEA,SAAS,kBAAwB;AAChC;AACA,MAAI,uBAAuB,GAAG;AAC7B,0BAAsB;AACtB,QAAI,kBAAkB;AACrB,oBAAc,gBAAgB;AAC9B,yBAAmB;AAAA,IACpB;AAAA,EACD;AACD;AAmBA,MAAM,oBAAoB;AAE1B,MAAM,kBAAkB;AAcjB,MAAM,kBAAkB;AAAA,EACtB,8BAAc,IAAA;AAAA;AAAA,EAEd,mCAAmB,IAAA;AAAA;AAAA,EAEnB,kCAAkB,IAAA;AAAA,EAClB,cAAkC;AAAA,EAClC,YAAiC;AAAA,EACjC,cAAkC;AAAA,EAClC,YAAiC;AAAA,EACjC,cAAmC;AAAA,EACnC,sBAA6D;AAAA,EAC7D,eAAsD;AAAA,EACtD,mBAA0D;AAAA,EAC1D,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB;AAAA,EACA,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,gBAAgB;AAAA;AAAA,EAEhB,YAAY,IAAI,iBAAA;AAAA;AAAA,EAEhB,UAAgC;AAAA;AAAA,EAEhC,UAA8B;AAAA,EAC9B,eAAe;AAAA;AAAA,EAEf,gBAA6D;AAAA,EAC7D,YAAmD;AAAA,EACnD,gBAAqC;AAAA,EAE7C,YAAY,QAAmB;AAC9B,SAAK,SAAS,EAAE,GAAG,gBAAgB,GAAG,OAAA;AAAA,EACvC;AAAA;AAAA,EAGA,WAAwD;AACvD,WAAO;AAAA,MACN,SAAS,KAAK,QAAQ;AAAA,MACtB,gBAAgB,KAAK;AAAA,IAAA;AAAA,EAEvB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAkB,UAA+C,IAAa;AACvF,QAAI,KAAK,QAAQ,QAAQ,KAAK,OAAO,oBAAoB;AACxD;AAAA,QACC;AAAA,UACC,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,MAAM,EAAE,SAAS,KAAK,QAAQ,MAAM,KAAK,KAAK,OAAO,mBAAA;AAAA,QAAmB;AAAA,QAEzE,KAAK;AAAA,MAAA;AAEN,aAAO;AAAA,IACR;AAEA,SAAK,QAAQ,IAAI,MAAM;AACvB,QAAI,QAAQ,MAAO,MAAK,aAAa,IAAI,MAAM;AAC/C,QAAI,QAAQ,KAAM,MAAK,YAAY,IAAI,MAAM;AAC7C;AAAA,MACC;AAAA,QACC,OAAO;AAAA,QACP,WAAW;AAAA,QACX,SAAS;AAAA,QACT,MAAM,EAAE,OAAO,KAAK,QAAQ,KAAA;AAAA,MAAK;AAAA,MAElC,KAAK;AAAA,IAAA;AAGN,QAAI,KAAK,QAAQ,SAAS,KAAK,CAAC,KAAK,aAAa;AACjD,cAAQ,IAAI,oDAAoD;AAChE,WAAK,cAAc,eAAe,UAAU,MAAM,KAAK,qBAAqB;AAI5E,WAAK,sBAAsB,YAAY,MAAM;AAC5C,aAAK,oBAAA;AAAA,MACN,GAAG,GAAG;AAEN,UAAI;AAAE,6BAAA;AAAA,MAAwB,QAAQ;AAAA,MAAoB;AAC1D,WAAK,eAAe,YAAY,MAAM;AACrC,YAAI;AAAE,+BAAA;AAAA,QAAwB,QAAQ;AAAA,QAAoB;AAAA,MAC3D,GAAG,GAAM;AAET,uBAAA;AACA,WAAK,mBAAmB,YAAY,MAAM;AACzC,aAAK,qBAAA;AAAA,MACN,GAAG,GAAM;AACT,WAAK,KAAK,YAAA;AACV,WAAK,YAAY,YAAY,MAAM,KAAK,KAAK,YAAA,GAAe,eAAe;AAAA,IAC5E,OAAO;AACN,cAAQ,IAAI,qCAAqC,KAAK,QAAQ,MAAM,mBAAmB,CAAC,CAAC,KAAK,WAAW;AAAA,IAC1G;AAEA,SAAK,2BAA2B,WAAW,EAAE,KAAK,CAAC,QAAQ;AAC1D,UAAI,OAAO,UAAU;AACpB,aAAK,aAAa,QAAQ,GAAG;AAC7B,aAAK,aAAa,QAAQ,KAAK,yBAAA,CAA0B;AACzD,YAAI,QAAQ,SAAS,KAAK,aAAa;AACtC,eAAK,aAAa,QAAQ,KAAK,mBAAmB,KAAK,WAAW,CAAC;AACnE,eAAK,aAAa,QAAQ,KAAK,uBAAuB,KAAK,WAAW,CAAC;AAAA,QACxE;AACA,YAAI,KAAK,YAAa,MAAK,aAAa,QAAQ,KAAK,mBAAmB,KAAK,aAAa,CAAC,CAAC,QAAQ,IAAI,CAAC;AACzG,YAAI,KAAK,WAAW,KAAK,QAAS,MAAK,aAAa,QAAQ,KAAK,kBAAkB,KAAK,SAAS,KAAK,OAAO,CAAC;AAAA,MAC/G;AAAA,IACD,CAAC;AACD,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,eAAe,QAA2B;AACzC,SAAK,YAAA;AACL,SAAK,gBAAA;AACL,SAAK,cAAc;AACnB,SAAK,YAAY,OAAO,UAAU,MAAM,KAAK,eAAe;AAC5D,SAAK,gBAAgB,OAAO,cAAc,MAAM;AAC/C,UAAI,KAAK,aAAa,OAAO,EAAG,MAAK,YAAY,KAAK,cAAc,KAAK,uBAAuB,MAAM,CAAC;AAAA,IACxG,CAAC;AAAA,EACF;AAAA,EAEQ,uBAAuB,QAAqB;AACnD,WAAO,EAAE,MAAM,cAAuB,UAAU,OAAO,QAAQ,WAAW,KAAK,MAAI;AAAA,EACpF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,kBAAkB,KAAoB,MAA2B;AACxE,WAAO,EAAE,MAAM,QAAiB,UAAU,YAAY,KAAK,IAAI,GAAG,WAAW,KAAK,IAAA,EAAI;AAAA,EACvF;AAAA,EAEQ,cAAc;AAAA,EAEtB,MAAc,cAA6B;AAE1C,QAAI,KAAK,YAAa;AACtB,SAAK,cAAc;AACnB,QAAI;AACH,WAAK,UAAU,MAAM,KAAK,UAAU,KAAA;AAAA,IACrC,SAAS,KAAK;AACb,cAAQ,MAAM,+CAA+C,GAAG;AAChE;AAAA,IACD,UAAA;AACC,WAAK,cAAc;AAAA,IACpB;AACA,SAAK,uBAAA;AAAA,EACN;AAAA,EAEQ,yBAA+B;AAEtC,QAAI,CAAC,KAAK,WAAW,CAAC,KAAK,WAAW,KAAK,QAAQ,SAAS,EAAG;AAC/D,UAAM,OAAO,CAAC,GAAG,KAAK,OAAO,EAAE,KAAA,EAAO,KAAK,IAAI;AAC/C,QAAI,KAAK,eAAe,QAAQ,KAAK,WAAW,KAAK,cAAc,SAAS,KAAM;AAClF,SAAK,gBAAgB,EAAE,KAAK,KAAK,SAAS,KAAA;AAC1C,UAAM,UAAU,KAAK,kBAAkB,KAAK,SAAS,KAAK,OAAO;AACjE,UAAM,OAAO,KAAK,UAAU,QAAQ,QAAQ;AAC5C,QAAI,SAAS,KAAK,aAAc;AAChC,SAAK,eAAe;AACpB,SAAK,YAAY,KAAK,SAAS,OAAO;AAAA,EACvC;AAAA;AAAA,EAGA,eAAe,QAA2B;AACzC,SAAK,YAAA;AACL,SAAK,cAAc;AACnB,SAAK,YAAY,OAAO,UAAU,MAAM,KAAK,gBAAgB;AAAA,EAC9D;AAAA;AAAA,EAGQ,mBAAmB,QAAqB,MAAe;AAC9D,WAAO,EAAE,MAAM,SAAkB,OAAO,OAAO,OAAO,QAAQ,OAAO,SAAA,GAAY,WAAW,KAAK,MAAI;AAAA,EACtG;AAAA,EAEQ,iBAAuB;AAC9B,QAAI,CAAC,KAAK,eAAe,KAAK,QAAQ,SAAS,EAAG;AAClD,UAAM,SAAS,IAAI,IAAI,CAAC,GAAG,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC;AAChF,SAAK,YAAY,KAAK,aAAa,KAAK,mBAAmB,KAAK,aAAa,IAAI,CAAC;AAClF,SAAK,YAAY,QAAQ,KAAK,mBAAmB,KAAK,aAAa,KAAK,CAAC;AAAA,EAC1E;AAAA,EAEQ,aAAmD;AAAA;AAAA,EAGnD,gBAAsB;AAC7B,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa,WAAW,MAAM;AAClC,WAAK,aAAa;AAClB,WAAK,eAAA;AAAA,IACN,GAAG,iBAAiB;AAAA,EACrB;AAAA,EAEQ,mBAAmB,QAAqB;AAC/C,WAAO,EAAE,MAAM,SAAkB,MAAM,OAAO,QAAQ,UAAU,OAAO,SAAA,GAAY,WAAW,KAAK,MAAI;AAAA,EACxG;AAAA,EAEQ,iBAAuB;AAC9B,QAAI,CAAC,KAAK,eAAe,KAAK,aAAa,SAAS,EAAG;AACvD,SAAK,YAAY,KAAK,cAAc,KAAK,mBAAmB,KAAK,WAAW,CAAC;AAAA,EAC9E;AAAA;AAAA,EAGQ,YAAY,SAAwB,SAAkE;AAC7G,UAAM,OAAO,KAAK,UAAU,OAAO;AACnC,UAAM,QAAQ,CAAC,GAAG,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,aAAa,QAAQ,SAAS,IAAI,CAAC;AACvF,eAAW,UAAU,MAAO,MAAK,WAAW,MAAM;AAClD,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,WAAW,QAAwB;AAC1C,SAAK,QAAQ,OAAO,MAAM;AAC1B,SAAK,aAAa,OAAO,MAAM;AAC/B,SAAK,YAAY,OAAO,MAAM;AAC9B,SAAK;AACL,QAAI;AACH,aAAO,MAAA;AAAA,IACR,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEA,aAAa,QAAwB;AACpC,SAAK,aAAa,OAAO,MAAM;AAC/B,SAAK,YAAY,OAAO,MAAM;AAC9B,UAAM,MAAM,KAAK,QAAQ,OAAO,MAAM;AACtC,QAAI,KAAK;AACR;AAAA,QACC;AAAA,UACC,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,MAAM,EAAE,OAAO,KAAK,QAAQ,KAAA;AAAA,QAAK;AAAA,QAElC,KAAK;AAAA,MAAA;AAAA,IAEP;AAEA,QAAI,KAAK,QAAQ,SAAS,KAAK,KAAK,aAAa;AAChD,WAAK,YAAA;AACL,WAAK,cAAc;AACnB,WAAK,WAAW;AAEhB,UAAI,KAAK,qBAAqB;AAC7B,sBAAc,KAAK,mBAAmB;AACtC,aAAK,sBAAsB;AAAA,MAC5B;AAEA,UAAI,KAAK,cAAc;AACtB,sBAAc,KAAK,YAAY;AAC/B,aAAK,eAAe;AAAA,MACrB;AAEA,UAAI,KAAK,kBAAkB;AAC1B,sBAAc,KAAK,gBAAgB;AACnC,aAAK,mBAAmB;AAAA,MACzB;AACA,sBAAA;AACA,UAAI,KAAK,WAAW;AACnB,sBAAc,KAAK,SAAS;AAC5B,aAAK,YAAY;AAAA,MAClB;AACA,WAAK,eAAe;AACpB,WAAK,gBAAgB;AAAA,IACtB;AAAA,EACD;AAAA,EAEA,MAAc,2BAA2B,MAAgC;AACxE,UAAM,EAAE,UAAU,MAAA,IAAU,MAAM,qBAAA;AAElC,SAAK,UAAU,IAAI,IAAI,SAAS,OAAO,CAAC,MAAM,EAAE,eAAe,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAItF,iBAAa,SAAS,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,QAAuB,CAAC,CAAC,GAAG,CAAC;AAC7E,UAAM,WAAW,iBAAA;AACjB,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA,OAAO,SAAS;AAAA;AAAA,MAEhB;AAAA,MACA;AAAA;AAAA,MAEA,gBAAgB,eAAe,QAAQ;AAAA;AAAA,MAEvC,SAAS,aAAA,EAAe,QAAA;AAAA,MACxB,UAAU,YAAA;AAAA,MACV,OAAO;AAAA,QACN,IAAI;AAAA,UACH,SACE,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,iBAAiB,EAAE,QAAQ,EAC3D,IAAI,CAAC,MAAM,GAAG,EAAE,QAAQ,IAAI,EAAE,aAAa,EAAE;AAAA,QAAA;AAAA,MAChD;AAAA,MAED,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAEtB;AAAA,EAEQ,2BAA+C;AACtD,WAAO,EAAE,MAAM,eAAwB,GAAG,mBAAmB,WAAW,KAAK,MAAI;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAqB;AACpB,SAAK,oBAAA;AAAA,EACN;AAAA,EAEQ,sBAA4B;AAGnC,QAAI,KAAK,YAAY;AACpB,WAAK,gBAAgB;AACrB;AAAA,IACD;AACA,SAAK,aAAa;AAElB,SAAK,2BAA2B,UAAU,EACxC,KAAK,CAAC,YAAY;AAOlB,YAAM,QAAQ,QAAQ,MAAM,IAAI,CAAC,EAAE,OAAO,QAAQ,GAAG,KAAA,MAAW,IAAI;AACpE,YAAM,OAAO,KAAK,UAAU,CAAC,QAAQ,UAAU,OAAO,QAAQ,OAAO,QAAQ,gBAAgB,QAAQ,SAAS,MAAM,CAAC;AACrH,UAAI,SAAS,KAAK,UAAU;AAC3B;AAAA,MACD;AAEA,WAAK,uBAAA;AACL,WAAK,WAAW;AAEhB,YAAM,UAAU,KAAK,YAAY,KAAK,SAAS,OAAO,EAAE;AACxD,UAAI,UAAU,GAAG;AAChB;AAAA,UACC;AAAA,YACC,OAAO;AAAA,YACP,WAAW;AAAA,YACX,SAAS;AAAA,YACT,MAAM,EAAE,SAAS,OAAO,KAAK,QAAQ,KAAA;AAAA,UAAK;AAAA,UAE3C,KAAK;AAAA,QAAA;AAAA,MAEP;AAAA,IACD,CAAC,EACA,MAAM,CAAC,QAAQ;AACf;AAAA,QACC;AAAA,UACC,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,MAAM,EAAE,OAAO,OAAO,GAAG,EAAA;AAAA,QAAE;AAAA,QAE5B,KAAK;AAAA,MAAA;AAAA,IAEP,CAAC,EACA,QAAQ,MAAM;AACd,WAAK,aAAa;AAClB,UAAI,KAAK,eAAe;AACvB,aAAK,gBAAgB;AACrB,aAAK,oBAAA;AAAA,MACN;AAAA,IACD,CAAC;AAAA,EACH;AAAA,EAEQ,uBAA6B;AACpC,UAAM,UAAU,KAAK,yBAAA;AACrB,UAAM,OAAO,KAAK,UAAU,OAAO;AACnC,QAAI,SAAS,KAAK,cAAe;AACjC,SAAK,gBAAgB;AACrB,SAAK,YAAY,KAAK,SAAS,OAAO;AAAA,EACvC;AAAA,EAEQ,aACP,QACA,SACA,MACU;AAGV,WACC,SAAS,QAAQ,SAAS,KAAK,QAAQ;AAAA,MACtC,WAAW;AAAA,MACX,aAAa,KAAK;AAAA,MAClB;AAAA,IAAA,CACA,MAAM;AAAA,EAET;AACD;AAcA,SAAS,SACR,QACA,SACA,QACA,MACa;AACb,MAAI;AACH,QAAI,CAAC,OAAO,OAAA,EAAU,QAAO;AAC7B,QAAI,OAAO,mBAAmB;AAC7B,YAAM,WAAW,OAAO,kBAAA;AACxB,UAAI,WAAW,KAAK,aAAa;AAChC;AAAA,UACC;AAAA,YACC,OAAO;AAAA,YACP,WAAW,KAAK;AAAA,YAChB,SAAS;AAAA,YACT,MAAM,EAAE,SAAA;AAAA,UAAS;AAAA,UAElB;AAAA,QAAA;AAED,eAAO;AAAA,MACR;AAAA,IACD;AACA,WAAO,KAAK,KAAK,QAAQ,KAAK,UAAU,OAAO,CAAC;AAChD,WAAO;AAAA,EACR,SAAS,KAAK;AACb;AAAA,MACC;AAAA,QACC,OAAO;AAAA,QACP,WAAW,KAAK;AAAA,QAChB,SAAS;AAAA,QACT,MAAM,EAAE,OAAO,OAAO,GAAG,EAAA;AAAA,MAAE;AAAA,MAE5B;AAAA,IAAA;AAED,WAAO;AAAA,EACR;AACD;AAcO,MAAM,kBAAkB;AAAA,EACtB,8BAAc,IAAA;AAAA,EACd,iCAAiB,IAAA;AAAA,EACjB,6BAAa,IAAA;AAAA,EACb;AAAA,EACA,eAAe;AAAA,EACf,iBAAiB;AAAA,EAEzB,YAAY,QAAmB;AAC9B,SAAK,SAAS,EAAE,GAAG,gBAAgB,GAAG,OAAA;AAAA,EACvC;AAAA;AAAA,EAGA,WAA8E;AAC7E,WAAO;AAAA,MACN,cAAc,KAAK;AAAA,MACnB,SAAS,KAAK,QAAQ;AAAA,MACtB,gBAAgB,KAAK;AAAA,IAAA;AAAA,EAEvB;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAkB,QAAyB;AAEpD,QAAI,KAAK,gBAAgB,KAAK,OAAO,yBAAyB;AAC7D;AAAA,QACC;AAAA,UACC,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,MAAM,EAAE,SAAS,KAAK,cAAc,KAAK,KAAK,OAAO,wBAAA;AAAA,QAAwB;AAAA,QAE9E,KAAK;AAAA,MAAA;AAEN,aAAO;AAAA,IACR;AAGA,UAAM,gBAAgB,KAAK,QAAQ,IAAI,MAAM;AAC7C,QAAI,iBAAiB,cAAc,QAAQ,KAAK,OAAO,6BAA6B;AACnF;AAAA,QACC;AAAA,UACC,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,MAAM,EAAE,QAAQ,SAAS,cAAc,MAAM,KAAK,KAAK,OAAO,4BAAA;AAAA,QAA4B;AAAA,QAE3F,KAAK;AAAA,MAAA;AAEN,aAAO;AAAA,IACR;AAEA,QAAI,CAAC,KAAK,QAAQ,IAAI,MAAM,GAAG;AAC9B,WAAK,QAAQ,IAAI,QAAQ,oBAAI,KAAK;AAAA,IACnC;AACA,SAAK,QAAQ,IAAI,MAAM,EAAG,IAAI,MAAM;AACpC,SAAK;AAEL;AAAA,MACC;AAAA,QACC,OAAO;AAAA,QACP,WAAW;AAAA,QACX,SAAS;AAAA,QACT,MAAM,EAAE,QAAQ,eAAe,KAAK,QAAQ,IAAI,MAAM,EAAG,MAAM,OAAO,KAAK,aAAA;AAAA,MAAa;AAAA,MAEzF,KAAK;AAAA,IAAA;AAGN,QAAI,KAAK,QAAQ,IAAI,MAAM,EAAG,SAAS,GAAG;AACzC,WAAK,aAAa,MAAM;AAAA,IACzB;AAGA,UAAM,OAAO,aAAa,MAAM;AAChC,UAAM,MAAM,KAAK,IAAA;AACjB,QAAI,MAAM;AACT,WAAK,OAAO,IAAI,QAAQ;AAAA,QACvB,aAAa,KAAK;AAAA,QAClB,KAAK,KAAK;AAAA,QACV,MAAM,KAAK;AAAA,QACX,MAAM,KAAK;AAAA,QACX,WAAW,KAAK,OAAO,KAAK,IAAI;AAAA,MAAA,CAChC;AACD,WAAK,aAAa,QAAQ;AAAA,QACzB,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,KAAK,KAAK;AAAA,QACV,MAAM,KAAK;AAAA,QACX,MAAM,KAAK;AAAA,QACX,WAAW;AAAA,MAAA,CACX;AACD,UAAI,CAAC,KAAK,OAAO,KAAK,cAAc,GAAG;AACtC,aAAK,gBAAgB,QAAQ,QAAQ,KAAK,aAAa,qBAAqB;AAAA,MAC7E;AAAA,IACD,OAAO;AAEN,WAAK,aAAa,QAAQ;AAAA,QACzB,MAAM;AAAA,QACN,OAAO,CAAA;AAAA,QACP,aAAa;AAAA,QACb,KAAK;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,QACN,WAAW;AAAA,MAAA,CACX;AAAA,IACF;AACA,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,QAAkB,QAAgB,QAAgB,OAAqB;AACrF,UAAM,QAAQ,KAAK,OAAO,IAAI,MAAM;AACpC,QAAI,CAAC,SAAS,MAAM,IAAK;AACzB,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,qBAAqB,KAAK,MAAM,KAAK,CAAC,CAAC;AACtE,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC;AACvE,QAAI,QAAQ,EAAG;AACf,SAAK,gBAAgB,QAAQ,QAAQ,KAAK,CAAC;AAAA,EAC5C;AAAA,EAEQ,gBAAgB,QAAkB,QAAgB,KAAa,OAAqB;AAC3F,UAAM,QAAQ,KAAK,OAAO,IAAI,MAAM;AACpC,UAAM,QAAQ,OAAO,eAAe;AACpC,UAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,KAAK;AACrC,UAAM,MAAM,kBAAkB,QAAQ,OAAO,KAAK,KAAK;AACvD,QAAI,CAAC,IAAK;AACV,SAAK,aAAa,QAAQ;AAAA,MACzB,MAAM;AAAA,MACN,OAAO,IAAI;AAAA,MACX,OAAO,IAAI;AAAA,MACX,aAAa,IAAI;AAAA,MACjB,WAAW,KAAK,IAAA;AAAA,IAAI,CACpB;AAAA,EACF;AAAA,EAEA,aAAa,QAAkB,QAAuB;AACrD,QAAI,QAAQ;AACX,YAAM,gBAAgB,KAAK,QAAQ,IAAI,MAAM;AAC7C,UAAI,iBAAiB,cAAc,OAAO,MAAM,GAAG;AAClD,aAAK;AACL;AAAA,UACC;AAAA,YACC,OAAO;AAAA,YACP,WAAW;AAAA,YACX,SAAS;AAAA,YACT,MAAM,EAAE,QAAQ,eAAe,cAAc,MAAM,OAAO,KAAK,aAAA;AAAA,UAAa;AAAA,UAE7E,KAAK;AAAA,QAAA;AAGN,YAAI,cAAc,SAAS,GAAG;AAC7B,eAAK,YAAY,MAAM;AACvB,eAAK,QAAQ,OAAO,MAAM;AAC1B,eAAK,OAAO,OAAO,MAAM;AAAA,QAC1B;AAAA,MACD;AACA;AAAA,IACD;AAGA,eAAW,CAAC,GAAG,OAAO,KAAK,KAAK,SAAS;AACxC,UAAI,QAAQ,OAAO,MAAM,GAAG;AAC3B,aAAK;AACL,YAAI,QAAQ,SAAS,GAAG;AACvB,eAAK,YAAY,CAAC;AAClB,eAAK,QAAQ,OAAO,CAAC;AACrB,eAAK,OAAO,OAAO,CAAC;AAAA,QACrB;AACA;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,aAAa,QAAsB;AAC1C,QAAI,KAAK,WAAW,IAAI,MAAM,EAAG;AACjC,UAAM,QAAQ,YAAY,MAAM,KAAK,iBAAiB,MAAM,GAAG,GAAG;AAClE,SAAK,WAAW,IAAI,QAAQ,KAAK;AAAA,EAClC;AAAA,EAEQ,YAAY,QAAsB;AACzC,UAAM,QAAQ,KAAK,WAAW,IAAI,MAAM;AACxC,QAAI,OAAO;AACV,oBAAc,KAAK;AACnB,WAAK,WAAW,OAAO,MAAM;AAAA,IAC9B;AAAA,EACD;AAAA,EAEQ,iBAAiB,QAAsB;AAC9C,UAAM,OAAO,aAAa,MAAM;AAChC,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,KAAK,OAAO,IAAI,MAAM;AACnC,UAAM,MAAM,KAAK,IAAA;AACjB,UAAM,WAA8B,CAAA;AACpC,UAAM,YAAY,KAAK,OAAO,KAAK,IAAI;AAEvC,QAAI,CAAC,MAAM;AACV,WAAK,OAAO,IAAI,QAAQ,EAAE,aAAa,KAAK,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,WAAW;AACrH,eAAS,KAAK,EAAE,MAAM,SAAS,aAAa,KAAK,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,WAAW,KAAK;AAC/H,eAAS,KAAK,EAAE,MAAM,UAAU,OAAO,KAAK,QAAQ,aAAa,KAAK,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,WAAW,KAAK;AAAA,IACrJ,OAAO;AACN,YAAM,WACL,KAAK,QAAQ,KAAK,OAClB,KAAK,SAAS,KAAK,QACnB,KAAK,SAAS,KAAK,QACnB,KAAK,cAAc,KAAK;AACzB,UAAI,UAAU;AAEb,iBAAS,KAAK,EAAE,MAAM,SAAS,aAAa,KAAK,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,WAAW,KAAK;AAAA,MAChI,WAAW,CAAC,KAAK,OAAO,KAAK,cAAc,KAAK,aAAa;AAC5D,cAAM,IAAI,KAAK,cAAc,KAAK;AAClC,YAAI,QAAyB;AAC7B,YAAI,QAAQ,KAAK;AACjB,YAAI,KAAK,KAAK,KAAK,QAAQ;AAC1B,kBAAQ,KAAK,KAAK,MAAM,KAAK,KAAK,SAAS,CAAC;AAAA,QAC7C,OAAO;AAEN,gBAAM,MAAM,kBAAkB,QAAQ,KAAK,aAAa,KAAK,aAAa,KAAK,WAAW;AAC1F,cAAI,KAAK;AACR,oBAAQ,IAAI;AACZ,oBAAQ,IAAI;AAAA,UACb;AAAA,QACD;AACA,YAAI,OAAO;AACV,mBAAS,KAAK,EAAE,MAAM,WAAW,OAAO,OAAO,aAAa,KAAK,aAAa,WAAW,IAAA,CAAK;AAAA,QAC/F,OAAO;AACN,mBAAS,KAAK,EAAE,MAAM,SAAS,aAAa,KAAK,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,WAAW,KAAK;AAAA,QAChI;AAAA,MACD;AACA,UAAI,cAAc,KAAK,aAAa,UAAU;AAC7C,iBAAS,KAAK,EAAE,MAAM,UAAU,OAAO,KAAK,QAAQ,aAAa,KAAK,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,WAAW,KAAK;AAAA,MACrJ;AACA,WAAK,OAAO,IAAI,QAAQ,EAAE,aAAa,KAAK,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,WAAW;AAAA,IACtH;AAEA,QAAI,SAAS,WAAW,EAAG;AAC3B,UAAM,UAAU,KAAK,QAAQ,IAAI,MAAM;AACvC,QAAI,CAAC,QAAS;AACd,eAAW,WAAW,UAAU;AAC/B,YAAM,OAAO,KAAK,UAAU,OAAO;AACnC,YAAM,QAAoB,CAAA;AAC1B,iBAAW,UAAU,CAAC,GAAG,OAAO,GAAG;AAClC,YAAI,CAAC,KAAK,aAAa,QAAQ,SAAS,IAAI,EAAG,OAAM,KAAK,MAAM;AAAA,MACjE;AACA,iBAAW,UAAU,OAAO;AAC3B,gBAAQ,OAAO,MAAM;AACrB,aAAK;AACL,aAAK;AACL;AAAA,UACC;AAAA,YACC,OAAO;AAAA,YACP,WAAW;AAAA,YACX,SAAS;AAAA,YACT,MAAM,EAAE,QAAQ,OAAO,KAAK,aAAA;AAAA,UAAa;AAAA,UAE1C,KAAK;AAAA,QAAA;AAAA,MAEP;AACA,UAAI,QAAQ,SAAS,GAAG;AACvB,aAAK,YAAY,MAAM;AACvB,aAAK,QAAQ,OAAO,MAAM;AAC1B,aAAK,OAAO,OAAO,MAAM;AACzB;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,aAAa,QAAkB,SAA0B,MAAwB;AAGxF,WACC,SAAS,QAAQ,SAAS,KAAK,QAAQ;AAAA,MACtC,WAAW;AAAA,MACX,aAAa,KAAK;AAAA,MAClB;AAAA,IAAA,CACA,MAAM;AAAA,EAET;AACD;AAgGA,MAAM,qBAAqB;AAE3B,MAAM,0BAA0B;AAShC,MAAM,gBAAgB;AACtB,MAAM,oBAAoB;AAO1B,MAAM,mBAAmB;AAOzB,MAAM,kBAAkB,KAAK,KAAK;AAClC,MAAM,WAAW,KAAK;AAEtB,MAAM,qBAAqB,IAAI,OAAO;AAEtC,MAAM,sBAAsB;AAG5B,SAAS,OAAO,SAA4B;AAC3C,QAAM,MAAM,QAAQ;AACpB,QAAM,aAAa,KAAK,IAAI,GAAG,IAAI,SAAS,gBAAgB;AAC5D,SAAO;AAAA,IACN,SAAS,aAAa,IAAI,IAAI,MAAM,UAAU,IAAI;AAAA,IAClD;AAAA,IACA,SAAS,QAAQ;AAAA,IACjB,OAAO,QAAQ;AAAA,EAAA;AAEjB;AAGA,SAAS,aACR,SACA,KACA,WACsB;AACtB,QAAM,UAA6B,CAAA;AACnC,QAAM,UAAoB,CAAA;AAC1B,aAAW,MAAM,IAAI,IAAI,GAAG,GAAG;AAC9B,UAAM,QAAQ,QAAQ,QAAQ,EAAE;AAChC,QAAI,UAAU,OAAW;AACzB,YAAQ,KAAK,QAAQ,QAAQ,KAAK,CAAC;AACnC,YAAQ,KAAK,KAAK;AAAA,EACnB;AACA,SAAO,EAAE,MAAM,WAAW,SAAS,SAAS,UAAA;AAC7C;AAEO,MAAM,oBAAoB;AAAA,EACxB,8BAAc,IAAA;AAAA,EACd,+BAAe,IAAA;AAAA;AAAA,EAEf,8BAAc,IAAA;AAAA,EACd;AAAA,EACA,UAA+B;AAAA,EAEvC,YAAY,QAAmB;AAC9B,SAAK,SAAS,EAAE,GAAG,gBAAgB,GAAG,OAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,QAAkB,WAAmB,SAA2B;AAGzE,UAAM,UAAU,CAAC,GAAI,KAAK,QAAQ,IAAI,SAAS,KAAK,CAAA,CAAG,EAAE;AAAA,MACxD,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,QAAQ,OAAO;AAAA,IAAA,EAC7C;AACF,QAAI,WAAW,KAAK,OAAO,6BAA6B;AACvD;AAAA,QACC;AAAA,UACC,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,MAAM,EAAE,WAAW,SAAS,QAAA;AAAA,QAAQ;AAAA,QAErC,KAAK;AAAA,MAAA;AAEN,aAAO;AAAA,IACR;AAEA,QAAI,CAAC,KAAK,QAAQ,IAAI,SAAS,EAAG,MAAK,QAAQ,IAAI,WAAW,oBAAI,IAAA,CAAK;AACvE,SAAK,QAAQ,IAAI,SAAS,EAAG,IAAI,MAAM;AACvC,QAAI,cAAc,QAAQ,IAAI,QAAQ,EAAE,SAAS,OAAO,OAAO;AAE/D,QAAI,QAAQ,KAAK,SAAS,IAAI,SAAS;AACvC,QAAI,CAAC,OAAO;AACX,cAAQ,KAAK,aAAa,SAAS;AACnC,WAAK,SAAS,IAAI,WAAW,KAAK;AAAA,IACnC,WAAW,MAAM,cAAc,MAAM;AACpC,WAAK,KAAK,WAAW,KAAK;AAAA,IAC3B;AACA,UAAM,OAAO,MAAM;AAClB,UAAI,CAAC,QAAS,OAAM,cAAc,KAAK,UAAU,MAAM,QAAQ,OAAO;AACtE,WAAK,aAAa,QAAQ,KAAK,SAAS,OAAO,MAAM,CAAC;AAAA,IACvD;AAEA,QAAI,MAAM,UAAW,MAAK,MAAM,UAAU,KAAK,IAAI;AAAA,QAC9C,MAAA;AACL,WAAO;AAAA,EACR;AAAA;AAAA,EAGQ,KAAK,WAAmB,OAAqC;AACpE,UAAM,YAAY;AAClB,UAAM,QAAQ,YAAY,MAAM,KAAK,KAAK,SAAS,GAAG,kBAAkB;AACxE,QAAI,CAAC,MAAM,UAAU,MAAM,UAAW;AACtC,UAAM,SAAS,MAAM,OAAO,KAAA;AAC5B,QAAI,OAAO,WAAW,SAAS;AAC9B,WAAK,QAAQ,OAAO,MAAM;AAC1B,WAAK,QAAQ,OAAO,MAAM,OAAO,MAAM;AAAA,IACxC,OAAO;AACN,WAAK,QAAQ,OAAO,MAAM;AAAA,IAC3B;AACA,SAAK,cAAc,KAAK;AAExB,SAAK,sBAAsB,KAAK;AAAA,EACjC;AAAA,EAEA,aAAa,QAAkB,WAA0B;AACxD,SAAK,QAAQ,OAAO,MAAM;AAC1B,UAAM,OAAO,CAAC,IAAY,YAA2B;AACpD,UAAI,CAAC,QAAQ,OAAO,MAAM,EAAG,QAAO;AACpC,UAAI,QAAQ,SAAS,EAAG,MAAK,KAAK,EAAE;AACpC,aAAO;AAAA,IACR;AACA,QAAI,WAAW;AACd,YAAM,UAAU,KAAK,QAAQ,IAAI,SAAS;AAC1C,UAAI,QAAS,MAAK,WAAW,OAAO;AACpC;AAAA,IACD;AACA,eAAW,CAAC,IAAI,OAAO,KAAK,KAAK,SAAS;AACzC,UAAI,KAAK,IAAI,OAAO,EAAG;AAAA,IACxB;AAAA,EACD;AAAA;AAAA,EAGQ,YAAY,WAAsC;AACzD,WAAO,IAAI,kBAAkB,OAAO,CAAC,MAAM,OAAO,WAAW,WAAW,MAAM,EAAE,CAAC;AAAA,EAClF;AAAA,EAEQ,aAAa,WAA2C;AAC/D,UAAM,QAAgC;AAAA,MACrC;AAAA,MACA,QAAQ;AAAA,MACR,SAAS,KAAK,YAAY,SAAS;AAAA,MACnC,WAAW;AAAA,MACX,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,+BAAe,IAAA;AAAA,MACf,YAAY;AAAA,MACZ,8BAAc,IAAA;AAAA,MACd,WAAW;AAAA,MACX,WAAW;AAAA,MACX,aAAa;AAAA,MACb,WAAW;AAAA,MACX,2BAAW,IAAA;AAAA,MACX,OAAO,YAAY,MAAM,KAAK,KAAK,SAAS,GAAG,kBAAkB;AAAA,IAAA;AAElE,UAAM,OAAO,KAAK,OAAO,SAAS;AAClC,QAAI,KAAM,MAAK,OAAO,OAAO,IAAI;AACjC,SAAK,cAAA;AACL,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,gBAAgB,SAAiC;AAChD,eAAW,MAAM,SAAS;AACzB,YAAM,QAAQ,KAAK,SAAS,IAAI,EAAE;AAClC,UAAI,CAAC,SAAS,MAAM,OAAQ;AAC5B,YAAM,WAAW,WAAW,EAAE,GAAG;AACjC,UAAI,YAAY,WAAW,QAAQ,SAAS,YAAY;AAAA,IACzD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,OAAO,OAA+B,MAA6B;AAC1E,UAAM,SAAS,IAAI,YAAY,IAAI;AACnC,UAAM,SAAS;AACf,UAAM,WAAW,MAAM,IAAI,QAAc,CAAC,YAAY,aAAa,OAAO,CAAC;AAC3E,UAAM,aAAa,YAAY;AAC9B,iBAAS;AACR,cAAM,SAAS,OAAO,KAAK,kBAAkB;AAC7C,aAAK,QAAQ,OAAO,MAAM;AAC1B,YAAI,OAAO,WAAW,WAAW,CAAC,OAAO,KAAM;AAC/C,cAAM,SAAA;AAAA,MACP;AACA,WAAK,kBAAkB,KAAK;AAC5B,iBAAW,CAAC,SAAS,GAAG,KAAK,MAAM,WAAW;AAC7C,aAAK,aAAa,OAAO,SAAS,GAAG;AACrC,cAAM,SAAA;AAAA,MACP;AACA,WAAK,sBAAsB,KAAK;AAAA,IACjC,GAAA,EAAK,QAAQ,MAAM;AAClB,UAAI,MAAM,WAAW,OAAQ,OAAM,YAAY;AAAA,IAChD,CAAC;AACD,WAAO,MAAM;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,OAAO,WAAkC;AAChD,UAAM,UAAU,WAAW,SAAS;AACpC,WAAO,UAAU,sBAAsB,SAAS,SAAS,IAAI,mBAAmB,SAAS;AAAA,EAC1F;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,cAAc,OAAkD;AACvE,QAAI,CAAC,MAAM,OAAQ,QAAO,CAAA;AAE1B,QAAI,MAAM,cAAc,GAAG;AAC1B,YAAM,aAAa;AACnB,WAAK,kBAAkB,KAAK;AAAA,IAC7B,OAAO;AACN,YAAM;AAAA,IACP;AAEA,UAAM,UAA6B,CAAA;AACnC,eAAW,CAAC,SAAS,GAAG,KAAK,MAAM,WAAW;AAC7C,YAAM,UAAU,KAAK,aAAa,OAAO,SAAS,GAAG;AACrD,UAAI,QAAS,SAAQ,KAAK,OAAO;AAAA,IAClC;AACA,WAAO;AAAA,EACR;AAAA;AAAA,EAGQ,kBAAkB,OAAqC;AAC9D,QAAI,CAAC,MAAM,OAAQ;AACnB,eAAW,QAAQ,cAAc,MAAM,OAAO,MAAM,IAAI,IAAI,MAAM,UAAU,KAAA,CAAM,CAAC,GAAG;AACrF,YAAM,UAAU,IAAI,KAAK,SAAS;AAAA,QACjC,QAAQ,IAAI,YAAY,KAAK,IAAI;AAAA,QACjC,SAAS,IAAI,kBAAkB,IAAI;AAAA,QACnC,MAAM,KAAK;AAAA,QACX,SAAS;AAAA,QACT,UAAU;AAAA,QACV,6BAAa,IAAA;AAAA,QACb,OAAO;AAAA,MAAA,CACP;AAAA,IACF;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aACP,OACA,SACA,KACyB;AACzB,UAAM,SAAS,IAAI,OAAO,KAAA;AAC1B,QAAI,OAAO,WAAW,SAAS;AAC9B,UAAI,UAAU,IAAI,kBAAkB,IAAI;AACxC,UAAI,QAAQ,MAAA;AACZ,UAAI,QAAQ;AAAA,IACb,WAAW,OAAO,WAAW,SAAS;AACrC,iBAAW,QAAQ,OAAO,OAAO;AAChC,mBAAW,MAAM,IAAI,QAAQ,KAAK,IAAI,EAAG,KAAI,QAAQ,IAAI,EAAE;AAAA,MAC5D;AAAA,IACD;AACA,UAAM,UAAU;AAAA,MACf;AAAA,MACA;AAAA,MACA,MAAM,QAAQ,eAAe,IAAI,OAAO;AAAA,MACxC,MAAM,SAAS,IAAI,OAAO;AAAA,IAAA;AAE3B,QAAI,UAAU;AACd,UAAM,UAAU,KAAK,UAAU,OAAO;AACtC,QAAI,YAAY,IAAI,SAAU,QAAO;AACrC,QAAI,WAAW;AACf,WAAO;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAgB,QAAkB,WAAmB,SAAuB;AAC3E,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,UAAM,MAAM,OAAO,UAAU,IAAI,OAAO;AACxC,QAAI,CAAC,SAAS,CAAC,IAAK;AACpB,UAAM,SAAS,IAAI,OAAO;AAC1B,UAAM,UAAU,gBAAgB,SAAS,KAAK,MAAM,QAAQ,eAAe,IAAI,OAAO,GAAG,IAAI;AAC7F,QAAI,UAAU;AACd,QAAI,WAAW,KAAK,UAAU,OAAO;AACrC,SAAK,aAAa,QAAQ,EAAE,MAAM,aAAa,WAAW,CAAC,OAAO,GAAG,WAAW,KAAK,IAAA,GAAO;AAAA,EAC7F;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,QAAkB,WAAmB,QAAgB,OAAqB;AACxF,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,CAAC,MAAO;AACZ,UAAM,SAAS,KAAK,QAAQ,IAAI,MAAM;AACtC,UAAM,UAAU,SAAS,MAAM,UAAU,IAAI,OAAO,OAAO,GAAG,UAAU,MAAM;AAC9E,QAAI,CAAC,QAAS;AACd,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,MAAM,MAAM,GAAG,QAAQ,QAAQ,MAAM,CAAC;AAC5E,QAAI,QAAQ,EAAG;AACf,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,qBAAqB,KAAK,MAAM,KAAK,CAAC,CAAC;AACtE,UAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,CAAC;AACjC,SAAK,aAAa,QAAQ;AAAA,MACzB,MAAM;AAAA,MACN,SAAS,QAAQ,QAAQ,MAAM,OAAO,GAAG;AAAA,MACzC,YAAY;AAAA,MACZ,WAAW,KAAK,IAAA;AAAA,IAAI,CACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,KAAK,WAAyB;AACrC,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,SAAK,QAAQ,OAAO,SAAS;AAC7B,QAAI,CAAC,MAAO;AACZ,kBAAc,MAAM,KAAK;AACzB,UAAM,YAAY,KAAK,IAAA;AACvB,UAAM,MAAM,MAAA;AAAA,EACb;AAAA,EAEQ,YAAY,WAAyB;AAC5C,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,MAAO,eAAc,MAAM,KAAK;AACpC,SAAK,SAAS,OAAO,SAAS;AAC9B,SAAK,QAAQ,OAAO,SAAS;AAAA,EAC9B;AAAA,EAEQ,UAAiD;AAAA,EAEjD,gBAAsB;AAC7B,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU,eAAe,UAAU,CAAC,YAAY,KAAK,gBAAgB,OAAO,CAAC;AAClF,SAAK,UAAU,YAAY,MAAM;AAChC,YAAM,MAAM,KAAK,IAAA;AACjB,iBAAW,CAAC,IAAI,KAAK,KAAK,KAAK,UAAU;AACxC,YAAI,MAAM,cAAc,QAAQ,MAAM,MAAM,YAAY,iBAAiB;AACxE,eAAK,YAAY,EAAE;AAAA,QACpB;AAAA,MACD;AACA,UAAI,KAAK,SAAS,SAAS,KAAK,KAAK,SAAS;AAC7C,sBAAc,KAAK,OAAO;AAC1B,aAAK,UAAU;AACf,aAAK,UAAA;AACL,aAAK,UAAU;AAAA,MAChB;AAAA,IACD,GAAG,QAAQ;AAAA,EACZ;AAAA;AAAA,EAGQ,QACP,OACA,QACW;AACX,YAAQ,OAAO,QAAA;AAAA,MACd,KAAK,SAAS;AACb,cAAM,YAAY;AAClB,cAAM,UAAoB,CAAA;AAC1B,mBAAW,QAAQ,OAAO,MAAO,SAAQ,KAAK,GAAG,MAAM,QAAQ,KAAK,IAAI,CAAC;AACzE,eAAO;AAAA,MACR;AAAA,MACA,KAAK;AAEJ,cAAM,UAAU,KAAK,YAAY,MAAM,SAAS;AAChD,eAAO,CAAA;AAAA,MACR;AACC,eAAO,CAAA;AAAA,IAAC;AAAA,EAEX;AAAA,EAEQ,KAAK,WAAyB;AACrC,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,UAAM,UAAU,KAAK,QAAQ,IAAI,SAAS;AAC1C,QAAI,CAAC,SAAS,CAAC,WAAW,QAAQ,SAAS,EAAG;AAE9C,QAAI,MAAM,UAAW;AACrB,QAAI,CAAC,MAAM,QAAQ;AAClB,UAAI,EAAE,MAAM,YAAY,EAAG;AAC3B,YAAM,OAAO,KAAK,OAAO,SAAS;AAClC,UAAI,CAAC,MAAM;AACV,cAAM,iBAAiB,KAAK,IAAI,MAAM,iBAAiB,GAAG,iBAAiB;AAC3E,cAAM,YAAY,MAAM;AACxB;AAAA,MACD;AACA,WAAK,KAAK,OAAO,OAAO,IAAI,EAAE,KAAK,MAAM;AACxC,YAAI,KAAK,SAAS,IAAI,SAAS,MAAM,MAAO,MAAK,OAAO,WAAW,KAAK;AAAA,MACzE,CAAC;AACD;AAAA,IACD;AAEA,SAAK,aAAa,WAAW,KAAK;AAElC,UAAM,SAAS,MAAM,OAAO,KAAA;AAC5B,QAAI,OAAO,WAAW,SAAS;AAC9B,WAAK,QAAQ,OAAO,MAAM;AAE1B,WAAK,QAAQ,OAAO,MAAM,OAAO,MAAM;AACvC,WAAK,OAAO,WAAW,KAAK;AAC5B;AAAA,IACD;AAEA,UAAM,aAAa,KAAK,QAAQ,OAAO,MAAM;AAC7C,QAAI,WAAW,SAAS,GAAG;AAC1B,WAAK,UAAU,WAAW,aAAa,MAAM,SAAS,YAAY,KAAK,IAAA,CAAK,CAAC;AAAA,IAC9E;AAEA,UAAM,YAAY,KAAK,cAAc,KAAK;AAC1C,QAAI,UAAU,SAAS,GAAG;AACzB,WAAK,UAAU,WAAW,EAAE,MAAM,aAAa,WAAW,WAAW,KAAK,IAAA,GAAO;AAAA,IAClF;AACA,SAAK,cAAc,WAAW,OAAO,SAAS;AAE9C,UAAM,UAAU,MAAM,QAAQ;AAC9B,UAAM,UAAU,KAAK,UAAU,OAAO;AACtC,QAAI,WAAW,YAAY,MAAM,aAAa;AAC7C,YAAM,cAAc;AACpB,WAAK,UAAU,WAAW,EAAE,MAAM,WAAW,SAAS,WAAW,KAAK,IAAA,GAAO;AAAA,IAC9E;AACA,UAAM,QAAQ,MAAM,QAAQ;AAC5B,QAAI,SAAS,UAAU,MAAM,WAAW;AACvC,YAAM,YAAY;AAClB,WAAK,UAAU,WAAW,EAAE,MAAM,SAAS,OAAO,WAAW,KAAK,IAAA,GAAO;AAAA,IAC1E;AAAA,EACD;AAAA;AAAA,EAGQ,OAAO,WAAmB,OAAqC;AACtE,UAAM,cAAc,KAAK,UAAU,MAAM,QAAQ,OAAO;AACxD,SAAK,UAAU,WAAW,KAAK,SAAS,KAAK,CAAC;AAC9C,eAAW,UAAU,KAAK,YAAY,SAAS,GAAG;AACjD,WAAK,aAAa,QAAQ,KAAK,SAAS,OAAO,MAAM,CAAC;AAAA,IACvD;AAAA,EACD;AAAA;AAAA,EAGQ,YAAY,WAA+B;AAClD,QAAI,KAAK,QAAQ,SAAS,UAAU,CAAA;AACpC,WAAO,CAAC,GAAI,KAAK,QAAQ,IAAI,SAAS,KAAK,CAAA,CAAG,EAAE,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,cACP,WACA,OACA,UACO;AACP,UAAM,UAAU,KAAK,QAAQ,IAAI,SAAS;AAC1C,eAAW,UAAU,KAAK,YAAY,SAAS,GAAG;AACjD,YAAM,SAAS,KAAK,QAAQ,IAAI,MAAM;AACtC,YAAM,MAAM,MAAM,UAAU,IAAI,OAAO,OAAO;AAE9C,UAAI,CAAC,OAAO,MAAM,MAAM,IAAI,MAAM,EAAG;AACrC,YAAM,WAAkC,CAAA;AACxC,YAAM,MAAM,KAAK,IAAA;AACjB,UAAI,CAAC,OAAO,SAAS,IAAI,OAAO;AAC/B,iBAAS,KAAK,KAAK,SAAS,OAAO,MAAM,CAAC;AAAA,MAC3C,OAAO;AACN,YAAI,IAAI,QAAQ,OAAO,GAAG;AACzB,mBAAS,KAAK,aAAa,IAAI,SAAS,IAAI,SAAS,GAAG,CAAC;AACzD,gBAAM,UAAU,IAAI,QAAQ;AAC5B,cAAI,kBAAkB,KAAK,EAAE,MAAM,WAAW,SAAS,WAAW,KAAK;AAAA,QACxE;AAEA,YAAI,SAAS,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,OAAO,GAAG;AACvD,gBAAM,WAAW,MAAM,QAAQ,eAAe,IAAI,OAAO,OAAO;AAChE,gBAAM,UAAU,gBAAgB,OAAO,SAAS,KAAK,UAAU,IAAI;AACnE,mBAAS,KAAK,EAAE,MAAM,aAAa,WAAW,CAAC,OAAO,GAAG,WAAW,KAAK;AAAA,QAC1E;AAAA,MACD;AACA,iBAAW,WAAW,UAAU;AAC/B,cAAM,SAAS,KAAK,aAAa,QAAQ,OAAO;AAChD,YAAI,WAAW,OAAQ;AACvB,YAAI,WAAW,gBAAgB;AAC9B,gBAAM,MAAM,IAAI,MAAM;AAAA,QACvB,OAAO;AACN,mBAAS,OAAO,MAAM;AACtB,eAAK,QAAQ,OAAO,MAAM;AAAA,QAC3B;AACA;AAAA,MACD;AAAA,IACD;AACA,SAAK,sBAAsB,KAAK;AAChC,QAAI,SAAS,SAAS,EAAG,MAAK,KAAK,SAAS;AAAA,EAC7C;AAAA;AAAA,EAGQ,sBAAsB,OAAqC;AAClE,eAAW,OAAO,MAAM,UAAU,OAAA,GAAU;AAC3C,UAAI,QAAQ,MAAA;AACZ,UAAI,QAAQ;AAAA,IACb;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,aAAa,WAAmB,OAAqC;AAC5E,QAAI,MAAM,MAAM,SAAS,EAAG;AAC5B,UAAM,UAAU,KAAK,QAAQ,IAAI,SAAS;AAC1C,UAAM,UAAU,KAAK,SAAS,KAAK;AACnC,UAAM,OAAO,KAAK,UAAU,OAAO;AACnC,eAAW,UAAU,CAAC,GAAG,MAAM,KAAK,GAAG;AACtC,UAAI,CAAC,SAAS,IAAI,MAAM,GAAG;AAC1B,cAAM,MAAM,OAAO,MAAM;AACzB;AAAA,MACD;AACA,YAAM,SAAS,KAAK,QAAQ,IAAI,MAAM,IACnC,KAAK,aAAa,QAAQ,KAAK,SAAS,OAAO,MAAM,CAAC,IACtD,KAAK,aAAa,QAAQ,SAAS,IAAI;AAC1C,UAAI,WAAW,eAAgB;AAC/B,YAAM,MAAM,OAAO,MAAM;AACzB,UAAI,WAAW,SAAU,SAAQ,OAAO,MAAM;AAAA,IAC/C;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,SAAS,OAA+B,QAAwC;AACvF,UAAM,SAAS,UAAU,KAAK,QAAQ,IAAI,MAAM;AAChD,QAAI,OAAQ,QAAO,KAAK,cAAc,OAAO,MAAM;AACnD,WAAO;AAAA,MACN,MAAM;AAAA,MACN,GAAG,OAAO,MAAM,OAAO;AAAA,MACvB,WAAW,CAAC,GAAG,MAAM,SAAS,EAAE;AAAA,QAC/B,CAAC,CAAC,IAAI,GAAG,MACR,IAAI,WACJ,gBAAgB,IAAI,KAAK,MAAM,QAAQ,eAAe,IAAI,EAAE,GAAG,MAAM,SAAS,IAAI,EAAE,CAAC;AAAA,MAAA;AAAA,MAEvF,WAAW,MAAM;AAAA,MACjB,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAEtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAc,OAA+B,QAA0C;AAC9F,UAAM,MAAM,MAAM,UAAU,IAAI,OAAO,OAAO;AAC9C,WAAO,QAAQ,QAAQ;AACvB,QAAI,CAAC,KAAK;AACT,aAAO;AAAA,QACN,MAAM;AAAA,QACN,SAAS,CAAA;AAAA,QACT,YAAY;AAAA,QACZ,WAAW,CAAA;AAAA,QACX,SAAS;AAAA,QACT,OAAO;AAAA,QACP,WAAW;AAAA,QACX,WAAW,KAAK,IAAA;AAAA,MAAI;AAAA,IAEtB;AACA,UAAM,UAAU;AAAA,MACf,OAAO;AAAA,MACP;AAAA,MACA,MAAM,QAAQ,eAAe,IAAI,OAAO,OAAO;AAAA,MAC/C;AAAA,IAAA;AAED,WAAO;AAAA,MACN,MAAM;AAAA,MACN,GAAG,OAAO,IAAI,OAAO;AAAA,MACrB,WAAW,CAAC,OAAO;AAAA,MACnB,WAAW;AAAA,MACX,WAAW,KAAK,IAAA;AAAA,IAAI;AAAA,EAEtB;AAAA;AAAA,EAGQ,UAAU,WAAmB,SAAoC;AACxE,UAAM,UAAU,KAAK,QAAQ,IAAI,SAAS;AAC1C,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,UAAM,OAAO,KAAK,UAAU,OAAO;AACnC,eAAW,UAAU,CAAC,GAAG,OAAO,GAAG;AAClC,UAAI,KAAK,QAAQ,IAAI,MAAM,EAAG;AAC9B,YAAM,SAAS,KAAK,aAAa,QAAQ,SAAS,IAAI;AAItD,UAAI,WAAW,eAAgB,QAAO,MAAM,IAAI,MAAM;AAAA,eAC7C,WAAW,SAAU,SAAQ,OAAO,MAAM;AAAA,IACpD;AACA,QAAI,QAAQ,SAAS,EAAG,MAAK,KAAK,SAAS;AAAA,EAC5C;AAAA,EAEQ,aAAa,QAAkB,SAA8B,MAA2B;AAC/F,WAAO,SAAS,QAAQ,SAAS,KAAK,QAAQ;AAAA,MAC7C,WAAW;AAAA;AAAA;AAAA,MAGX,aAAa,IAAI,OAAO;AAAA,MACxB;AAAA,IAAA,CACA;AAAA,EACF;AACD;AAeO,SAAS,gBAAgB,QAAgB,UAA6C;AAC5F,MAAI,WAAW,OAAQ,QAAO;AAC9B,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,SAAS,OAAO;AACnB,aAAS,MAAM,MAAM;AACrB,WAAO;AAAA,EACR;AAEA,MAAI;AACH,UAAM,MAAM,KAAK,MAAM,MAAM;AAC7B,YAAQ,IAAI,MAAA;AAAA,MACX,KAAK;AACJ,YAAI,SAAS,mBAAmB,OAAO,IAAI,YAAY,UAAU;AAChE,mBAAS,gBAAgB,IAAI,OAAO;AAAA,QACrC;AACA;AAAA,MACD,KAAK;AACJ,YAAI,SAAS,UAAU,OAAO,IAAI,SAAS,YAAY,OAAO,IAAI,SAAS,UAAU;AACpF,mBAAS,OAAO,IAAI,MAAM,IAAI,IAAI;AAAA,QACnC;AACA;AAAA,MACD,KAAK;AACJ,YAAI,SAAS,kBAAkB,OAAO,IAAI,WAAW,YAAY,OAAO,IAAI,UAAU,UAAU;AAC/F,mBAAS,eAAe,IAAI,QAAQ,IAAI,KAAK;AAAA,QAC9C;AACA;AAAA,IAAA;AAAA,EAEH,QAAQ;AAAA,EAER;AAEA,SAAO;AACR;AAeO,SAAS,YAAY,UAAkB,SAAS,IAAkB;AACxE,MAAI,aAAa,wBAAwB;AACxC,WAAO,IAAI,gBAAgB,MAAM,EAAE,IAAI,OAAO,IAAI,EAAE,MAAM,YAAY,OAAO,KAAA,IAAS,EAAE,MAAM,WAAA;AAAA,EAC/F;AAEA,QAAM,eAAe,SAAS,MAAM,mCAAmC;AACvE,MAAI,cAAc;AACjB,WAAO,EAAE,MAAM,WAAW,QAAQ,mBAAmB,aAAa,CAAC,CAAC,EAAA;AAAA,EACrE;AAEA,QAAM,aAAa,SAAS,MAAM,iEAAiE;AACnG,MAAI,YAAY;AACf,WAAO;AAAA,MACN,MAAM;AAAA,MACN,QAAQ,mBAAmB,WAAW,CAAC,CAAC;AAAA,MACxC,OAAO,mBAAmB,WAAW,CAAC,CAAC;AAAA,IAAA;AAAA,EAEzC;AAEA,QAAM,kBAAkB,SAAS,MAAM,gDAAgD;AACvF,MAAI,iBAAiB;AACpB,WAAO,EAAE,MAAM,cAAc,QAAQ,mBAAmB,gBAAgB,CAAC,CAAC,EAAA;AAAA,EAC3E;AAEA,QAAM,YAAY,SAAS,MAAM,oCAAoC;AACrE,MAAI,WAAW;AACd,WAAO,EAAE,MAAM,YAAY,QAAQ,mBAAmB,UAAU,CAAC,CAAC,EAAA;AAAA,EACnE;AAEA,SAAO;AACR;"}
1
+ {"version":3,"file":"ws-handlers.js","sources":["../../../../../src/db/projects-json.ts","../../../../../src/db/settings-json.ts","../../../../../src/db/visits-json.ts","../../../../../src/transcript/subagent.ts","../../../../../src/server/disk.ts","../../../../../src/tmux/geometry.ts","../../../../../src/tmux/login.ts","../../../../../src/tmux/resize.ts","../../../../../src/tmux/snapshot.ts","../../../../../src/server/plugins.ts","../../../../../src/transcript/past-sessions.ts","../../../../../src/server/ws-handlers.ts"],"sourcesContent":["/**\n * The projects the sidebar groups sessions under, kept on the server.\n *\n * A project is a directory a session has run in. The sidebar used to remember\n * them in the browser's localStorage, which meant every device kept its own\n * list, and \"Reset data\" — a button about that one browser's cache — took\n * every project without a live session with it. A directory belongs to the\n * machine it is on, so the machine remembers it: `~/.claude-mux/projects.json`\n * next to the sessions, shared by every browser that connects, and untouched\n * by anything the browser clears.\n *\n * The file is written atomically (tmp + rename), like a session file, so a\n * poll reading it mid-write sees the old list rather than half of the new.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { homedir } from \"os\";\nimport { getSessionsDir } from \"./sessions-json.js\";\n\n/** Overridable for tests; null means \"next to the sessions directory\". */\nlet projectsPath: string | null = null;\n\nexport function setProjectsPath(path: string | null): void {\n projectsPath = path;\n}\n\nfunction resolveProjectsPath(): string {\n return projectsPath ?? join(dirname(getSessionsDir()), \"projects.json\");\n}\n\ninterface ProjectsFile {\n v: 1;\n /** Absolute directories, in the order they were first seen. */\n projects: string[];\n}\n\nfunction readFile(): ProjectsFile {\n const path = resolveProjectsPath();\n if (!existsSync(path)) return { v: 1, projects: [] };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<ProjectsFile>;\n const projects = Array.isArray(parsed.projects)\n ? parsed.projects.filter((p): p is string => typeof p === \"string\" && p.length > 0)\n : [];\n return { v: 1, projects: [...new Set(projects)] };\n } catch {\n // A torn or hand-edited file must not take the sidebar down with it.\n return { v: 1, projects: [] };\n }\n}\n\nfunction writeFile(file: ProjectsFile): void {\n const path = resolveProjectsPath();\n mkdirSync(dirname(path), { recursive: true });\n const tmp = `${path}.${process.pid}.tmp`;\n writeFileSync(tmp, JSON.stringify(file, null, 2));\n renameSync(tmp, path);\n}\n\n/** Every remembered project, oldest first. */\nexport function getSavedProjects(): string[] {\n return readFile().projects;\n}\n\n/**\n * Directories that are nobody's project: the home directory, the root, the\n * places everything else lives under. A session opened in one of them is a\n * session without a project — it must not become a group of its own, and it\n * must never become the parent every real project nests under, which is what\n * happens the first time someone runs an agent in `~` by accident.\n */\nexport function isForbiddenRoot(cwd: string, home: string = homedir()): boolean {\n const path = cwd.replace(/\\/+$/, \"\") || \"/\";\n if (path === \"/\" || path === home) return true;\n if ([\"/tmp\", \"/home\", \"/Users\", \"/root\", \"/mnt\", \"/media\", \"/opt\", \"/var\", \"/srv\"].includes(path)) return true;\n // One level under a mount root is still a disk, not a project: /mnt/tudao.\n if (/^\\/(?:mnt|media|Volumes)\\/[^/]+$/.test(path)) return true;\n return false;\n}\n\n/** Whether `child` sits somewhere under `parent`. */\nfunction isUnder(child: string, parent: string): boolean {\n return child.startsWith(parent.endsWith(\"/\") ? parent : `${parent}/`);\n}\n\n/**\n * Remember these directories as projects. Returns whether anything new was\n * written, so a caller that runs every poll can tell a no-op from a change\n * worth telling clients about.\n *\n * Only roots are kept: a directory under a remembered project is that\n * project's subfolder, not a project — the sidebar shows its sessions inside\n * the parent's card with the relative path — and a directory that turns out\n * to be above a remembered one takes it over, so the file always holds the\n * shallowest cwd of each tree. A forbidden root is never remembered at all.\n */\nexport function saveProjects(cwds: Iterable<string>): boolean {\n const file = readFile();\n let changed = false;\n for (const raw of cwds) {\n const cwd = raw?.replace(/\\/+$/, \"\");\n if (!cwd || isForbiddenRoot(cwd)) continue;\n if (file.projects.some((p) => p === cwd || isUnder(cwd, p))) continue;\n const kept = file.projects.filter((p) => !isUnder(p, cwd));\n if (kept.length !== file.projects.length) changed = true;\n file.projects = [...kept, cwd];\n changed = true;\n }\n if (changed) writeFile(file);\n return changed;\n}\n\nexport function saveProject(cwd: string): boolean {\n return saveProjects([cwd]);\n}\n\n/** Forget a directory. Returns whether it was there. */\nexport function removeProject(cwd: string): boolean {\n const file = readFile();\n const next = file.projects.filter((p) => p !== cwd);\n if (next.length === file.projects.length) return false;\n writeFile({ ...file, projects: next });\n return true;\n}\n","/**\n * Settings that belong to the machine, not the browser: how this server\n * treats the sessions it creates. Kept in `~/.claude-mux/settings.json`\n * next to the sessions and projects, so every browser sees the same switch\n * and `claude-mux new-session` on the command line honours it too.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getSessionsDir } from \"./sessions-json.js\";\n\nexport interface Settings {\n /**\n * Run `/rc` in every session claude-mux itself creates, once its prompt is\n * up. Remote Control is what lets sessions on different machines see and\n * message each other (`ListAgents`, `SendMessage`) — but only for sessions\n * that have it on, which is easy to forget. Workers other tools spawn\n * straight into tmux are not claude-mux's to touch and are left alone.\n */\n autoRemoteControl: boolean;\n}\n\nexport const DEFAULT_SETTINGS: Settings = {\n autoRemoteControl: false,\n};\n\nlet settingsPath: string | null = null;\n\n/** Overridable for tests; null means \"next to the sessions directory\". */\nexport function setSettingsPath(path: string | null): void {\n settingsPath = path;\n}\n\nfunction resolveSettingsPath(): string {\n return settingsPath ?? join(dirname(getSessionsDir()), \"settings.json\");\n}\n\nexport function getSettings(): Settings {\n const path = resolveSettingsPath();\n if (!existsSync(path)) return { ...DEFAULT_SETTINGS };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf-8\")) as Partial<Settings>;\n return {\n autoRemoteControl:\n typeof parsed.autoRemoteControl === \"boolean\"\n ? parsed.autoRemoteControl\n : DEFAULT_SETTINGS.autoRemoteControl,\n };\n } catch {\n return { ...DEFAULT_SETTINGS };\n }\n}\n\n/** Change some settings; returns the whole set as it now stands. */\nexport function updateSettings(patch: Partial<Settings>): Settings {\n const next: Settings = { ...getSettings() };\n if (typeof patch.autoRemoteControl === \"boolean\") next.autoRemoteControl = patch.autoRemoteControl;\n const path = resolveSettingsPath();\n mkdirSync(dirname(path), { recursive: true });\n const tmp = `${path}.${process.pid}.tmp`;\n writeFileSync(tmp, JSON.stringify(next, null, 2));\n renameSync(tmp, path);\n return next;\n}\n\n/**\n * Whether a tmux session name is one claude-mux gave: `<project>-<agent>-<ms>`.\n * Anything else — `quita-b1`, a hand-made `main` — was started by someone\n * else, a person or an orchestrator, and is not for claude-mux to configure.\n */\nexport function isClaudeMuxSessionName(name: string): boolean {\n return /-(?:claude|gemini|copilot)-\\d{10,}$/.test(name);\n}\n","/**\n * When someone last looked at each session, kept in `~/.claude-mux/visits.json`.\n *\n * A turn that ends while nobody is watching is unread \"Done\" until somebody\n * opens the session, on any browser: the watermark lives on the server for\n * exactly that reason, so a phone that read it clears the desktop's badge.\n * The hook never touches this file — it stamps `turn_completed_at` on the\n * session, and the server compares the two.\n */\n\nimport { existsSync, readFileSync, statSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getSessionsDir } from \"./sessions-json.js\";\nimport { writeFileAtomic } from \"../utils/atomic-write.js\";\n\n/** Session id → last visit (epoch ms). */\nexport type Visits = Record<string, number>;\n\n/** An entry this old whose session file is gone is dropped on the next write. */\nconst KEEP_MS = 30 * 24 * 60 * 60 * 1000;\n\nlet visitsPath: string | null = null;\nlet cache: { mtimeMs: number; visits: Visits } | null = null;\n\n/** Overridable for tests; null means \"next to the sessions directory\". */\nexport function setVisitsPath(path: string | null): void {\n visitsPath = path;\n cache = null;\n}\n\nfunction resolveVisitsPath(): string {\n return visitsPath ?? join(dirname(getSessionsDir()), \"visits.json\");\n}\n\n/** Every recorded visit. Read on each session poll, so parsed only when the file changed. */\nexport function getVisits(): Visits {\n const path = resolveVisitsPath();\n try {\n const { mtimeMs } = statSync(path);\n if (cache?.mtimeMs === mtimeMs) return cache.visits;\n const parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n const visits: Visits = {};\n if (parsed && typeof parsed === \"object\") {\n for (const [id, at] of Object.entries(parsed)) {\n if (typeof at === \"number\" && Number.isFinite(at)) visits[id] = at;\n }\n }\n cache = { mtimeMs, visits };\n return visits;\n } catch {\n // Missing is empty. Unreadable (a hand edit, a cut-off write) keeps the\n // last good copy, so the next visit does not write every other\n // session's watermark away.\n return cache?.visits ?? {};\n }\n}\n\n/** Someone is looking at the session now. */\nexport function recordVisit(id: string, at: number = Date.now()): void {\n const now = Date.now();\n const next: Visits = {};\n for (const [other, when] of Object.entries(getVisits())) {\n // A session can live for months without being opened; only a gone one goes.\n if (now - when < KEEP_MS || existsSync(join(getSessionsDir(), `${other}.json`))) next[other] = when;\n }\n next[id] = at;\n const path = resolveVisitsPath();\n writeFileAtomic(path, JSON.stringify(next, null, 2));\n // Keep what was just written, so the next poll need not parse it back.\n cache = { mtimeMs: statSync(path).mtimeMs, visits: next };\n}\n\n/**\n * Put the session's last turn back to unread: the watermark rewinds to just\n * before the turn ended. A session with no finished turn has nothing to mark.\n */\nexport function markUnread(id: string, turnCompletedAt: number | null | undefined): boolean {\n if (!turnCompletedAt) return false;\n recordVisit(id, turnCompletedAt - 1);\n return true;\n}\n","/**\n * Shaping a subagent's transcript for display: what it ran, what it reported,\n * and whether it is still working. Lives beside the parser rather than in the\n * WebSocket layer — it is transcript logic, not transport.\n */\nimport type { SubagentMeta } from \"./tailer.js\";\nimport type { JsonlTailer } from \"./tailer.js\";\nimport type { TranscriptBuilder } from \"./parser.js\";\n\n/** What the manager holds per subagent; only these fields are read here. */\nexport interface SubagentSource {\n tailer: Pick<JsonlTailer, \"mtimeMs\">;\n builder: Pick<TranscriptBuilder, \"entries\">;\n meta: SubagentMeta;\n}\n\n/**\n * A subagent's work, condensed for the Task card that spawned it: what it ran\n * (summaries only — the parent card doesn't need every tool's full input) plus\n * the report it returned.\n */\nexport interface SubagentPayload {\n agentId: string;\n /** Parent Task tool_use id — the card this belongs under. */\n toolUseId: string | null;\n agentType: string | null;\n description: string | null;\n model: string | null;\n activity: { id: string; name: string; summary: string; ok: boolean | null }[];\n /** Older activity entries dropped from the list above. */\n trimmed: number;\n /** The subagent's final message: what it reported back. */\n report: string | null;\n /** The brief the parent wrote it (the Task prompt); full payloads only. */\n prompt: string | null;\n running: boolean;\n /**\n * Whether this carries the whole activity list and the report. A lean\n * payload has the tail of the activity only; the full one is sent when a\n * reader opens the card.\n */\n full: boolean;\n}\n\n/** Longest activity list sent per subagent; real agents reach 150+ tools. */\nexport const SUBAGENT_ACTIVITY_LIMIT = 120;\n/**\n * What a card that nobody has opened carries: enough to say what the agent\n * is doing now. A session with forty agents was sending forty lists of a\n * hundred calls in every snapshot, most of them inside closed cards.\n */\nexport const SUBAGENT_ACTIVITY_LEAN = 8;\n\n/**\n * Fallback liveness window, used only until the harness reports the agent as\n * finished (see TranscriptBuilder.finishedAgents). Covers the gap for agents\n * whose notification never arrives — the launching tool call returns\n * immediately for background agents, so writes are the only other signal.\n */\nconst SUBAGENT_LIVE_WINDOW_MS = 45_000;\n\n/** Condense a subagent's parsed transcript into what its Task card shows. */\nexport function subagentPayload(\n agentId: string,\n state: SubagentSource,\n finished: boolean,\n full = true\n): SubagentPayload {\n const limit = full ? SUBAGENT_ACTIVITY_LIMIT : SUBAGENT_ACTIVITY_LEAN;\n const activity: SubagentPayload['activity'] = [];\n let report: string | null = null;\n let prompt: string | null = null;\n for (const entry of state.builder.entries) {\n if (entry.kind === 'user' && prompt === null) prompt = entry.text;\n if (entry.kind === 'tool') {\n activity.push({\n id: entry.id,\n name: entry.name,\n summary: entry.summary,\n ok: entry.result ? entry.result.ok : null\n });\n } else if (entry.kind === 'text') {\n report = entry.text;\n }\n }\n const mtime = state.tailer.mtimeMs();\n const running =\n !finished && mtime !== null && Date.now() - mtime < SUBAGENT_LIVE_WINDOW_MS;\n const trimmed = Math.max(0, activity.length - limit);\n return {\n agentId,\n toolUseId: state.meta.toolUseId ?? null,\n agentType: state.meta.agentType ?? null,\n description: state.meta.description ?? null,\n model: state.meta.model ?? null,\n // Keep the newest calls — the tail is what \"what is it doing\" needs.\n activity: activity.slice(-limit),\n trimmed,\n report: full ? report : null,\n prompt: full ? prompt : null,\n running,\n full\n };\n}\n\n/**\n * Streams parsed session transcripts (from ~/.claude/projects JSONL files) to\n * clients, keyed by claude-mux session id. Mirrors TerminalWsManager's\n * lifecycle: first client for a session starts the tailer, last one stops it.\n */\n","import { statfs } from 'fs/promises';\nimport { homedir } from 'os';\n\n/** The block counts of `statfs` that `df` reads its \"Use%\" from. */\nexport interface BlockCounts {\n\tblocks: number;\n\tbfree: number;\n\tbavail: number;\n}\n\n/**\n * Share of a filesystem in use, as `df` reports it: the blocks reserved for\n * root count as neither used nor available, so a disk an ordinary user can no\n * longer write to reads 100%, and a fraction rounds up.\n */\nexport function usedPercent({ blocks, bfree, bavail }: BlockCounts): number {\n\tconst used = blocks - bfree;\n\tconst usable = used + bavail;\n\treturn usable > 0 ? Math.ceil((used / usable) * 100) : 0;\n}\n\n/**\n * Use of the filesystem holding the home directory, or null where it cannot be\n * read. Asynchronous, so a home on a network mount that stops answering never\n * holds up the server.\n */\nexport async function homeDiskPercent(path: string = homedir()): Promise<number | null> {\n\ttry {\n\t\treturn usedPercent(await statfs(path));\n\t} catch {\n\t\treturn null;\n\t}\n}\n","/**\n * The size a pane is held at while nobody is looking at it.\n *\n * Everything claude-mux reads off a pane — the prompt box, the dialog rows,\n * the queue — is read from the screen as Claude Code draws it, and Claude\n * Code draws to the width it has. A session started detached gets tmux's\n * default 80x24, and one left behind by a phone-sized terminal keeps that\n * terminal's width, so a dialog's rows wrap and a description lands on the\n * line the reader takes for the next row. Holding a detached window at one\n * generous size means the readers see the same layout every time.\n *\n * A window with a client attached is left alone: the client's terminal is the\n * right size for the person at it, and tmux resizes the window to it anyway.\n * When that client detaches, the next poll widens the window again.\n */\n\nimport { execFile } from \"child_process\";\nimport { promisify } from \"util\";\n\nconst execFileAsync = promisify(execFile);\n\n/** Wide enough that a model picker row, description and all, fits on one line. */\nexport const STANDARD_PANE_WIDTH = 200;\n\n/** Tall enough that a question with previews and a long prompt above it fits. */\nexport const STANDARD_PANE_HEIGHT = 50;\n\n/**\n * Below this a pane is worth widening: dialog rows start wrapping around the\n * width of a phone terminal, and 80 is what tmux hands a detached session.\n */\nexport const MIN_USEFUL_PANE_WIDTH = 160;\n\n/** The `new-session` arguments that start a detached session at the standard size. */\nexport function sizeArgsForNewSession(): string[] {\n return [\"-x\", String(STANDARD_PANE_WIDTH), \"-y\", String(STANDARD_PANE_HEIGHT)];\n}\n\n/**\n * Whether a pane should be widened: nobody is attached to it and it is\n * narrower than the readers want.\n */\nexport function wantsWidening(pane: { attached: number; width: number }): boolean {\n return pane.attached === 0 && pane.width < MIN_USEFUL_PANE_WIDTH;\n}\n\n/**\n * Widen a detached window to the standard size.\n *\n * `resize-window` switches the window to a manual size, which would make the\n * next terminal to attach see a clipped 200-column window rather than its\n * own. Setting `window-size` back to `latest` afterwards keeps the size that\n * was just applied — with no client there is nothing to follow — and lets\n * the next client take the window over as usual.\n *\n * @returns false when tmux refused, e.g. because the window is gone.\n */\nexport async function widenDetachedWindow(target: string): Promise<boolean> {\n try {\n await execFileAsync(\n \"tmux\",\n [\"resize-window\", \"-t\", target, \"-x\", String(STANDARD_PANE_WIDTH), \"-y\", String(STANDARD_PANE_HEIGHT)],\n { timeout: 1000 },\n );\n await execFileAsync(\"tmux\", [\"set-option\", \"-w\", \"-t\", target, \"window-size\", \"latest\"], {\n timeout: 1000,\n });\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * Claude Code's sign-in, as the pane draws it.\n *\n * `/login` never reaches the transcript: the link, the field the code goes\n * into and the verdict on it exist only on screen, and the link is printed\n * wider than the pane, so tmux wraps it over several lines. Reading them here\n * lets the transcript offer the link as a link and take the code in a field\n * of its own.\n */\n\nexport type LoginScreen =\n /** The code field is open: the link to sign in with, what is typed so far, and why the last code failed. */\n | { step: \"code\"; url: string | null; typed: string; error: string | null }\n /** The code was refused; Enter goes back to a fresh link and code field. */\n | { step: \"failed\"; error: string }\n /** The code was taken; Claude Code waits for Enter to carry on. */\n | { step: \"done\" }\n /** A turn failed for want of a sign-in, and nothing has happened since. */\n | { step: \"expired\" }\n | null;\n\n/** The field's own label, followed by whatever has been typed into it. */\nconst CODE_FIELD = /Paste code here if prompted >\\s?(.*)$/;\n\n/** The line above the link. */\nconst LINK_INTRO = /Browser didn't open\\?/;\n\n/** The link itself, which starts a line of its own. */\nconst LINK_START = /^\\s*(https:\\/\\/\\S+)$/;\n\n/** A wrapped piece of the link: a line of URL characters, flush left. */\nconst LINK_REST = /^[A-Za-z0-9%&=_.~+/:?#-]+$/;\n\nconst CODE_ERROR = /(Invalid code\\. Please make sure the full code was copied\\.?|OAuth error: .+)$/;\n\nconst SIGNED_IN = /Login successful\\. Press .*continue/i;\n\n/** The sign-in's own retry prompt, drawn under the error that ended it. */\nconst RETRY = /Press Enter to retry/i;\n\n/** What Claude Code answers a turn with when no sign-in is left. */\nconst EXPIRED = /Please run \\/login|run \\/login to restore|Not logged in · Please run \\/login/i;\n\n/** A message bullet: anything Claude Code printed into the conversation. */\nconst BULLET = /^\\s*●\\s/;\n\n/** How far up from the foot the sign-in screen may begin. */\nconst LOGIN_REACH = 40;\n\n/** Read the sign-in screen off a pane capture taken without colour. */\nexport function readLoginScreen(content: string): LoginScreen {\n if (!content) return null;\n const lines = content.split(\"\\n\");\n let last = lines.length - 1;\n while (last >= 0 && !lines[last].trim()) last--;\n if (last < 0) return null;\n const from = Math.max(0, last - LOGIN_REACH);\n\n for (let i = last; i >= from; i--) {\n if (SIGNED_IN.test(lines[i])) return { step: \"done\" };\n if (RETRY.test(lines[i])) {\n const above = lines.slice(Math.max(from, i - 3), i).map((l) => l.trim().match(CODE_ERROR)?.[1]).filter(Boolean);\n return { step: \"failed\", error: above.at(-1) ?? \"The sign-in failed.\" };\n }\n const field = lines[i].match(CODE_FIELD);\n if (!field) continue;\n let error: string | null = null;\n for (let j = i + 1; j <= last; j++) {\n const m = lines[j].trim().match(CODE_ERROR);\n if (m) error = m[1];\n }\n return { step: \"code\", url: readLink(lines, from, i), typed: field[1].trim(), error };\n }\n\n // The notice counts only while it is the last thing Claude Code said: any\n // message after it means a turn has run since.\n for (let i = last; i >= from; i--) {\n if (!BULLET.test(lines[i])) continue;\n return EXPIRED.test(lines[i]) ? { step: \"expired\" } : null;\n }\n return null;\n}\n\n/** The sign-in link between its introduction and the code field, joined back across the wrap. */\nfunction readLink(lines: string[], from: number, field: number): string | null {\n let start = -1;\n for (let i = field - 1; i >= from; i--) {\n if (LINK_START.test(lines[i])) start = i;\n if (LINK_INTRO.test(lines[i])) break;\n }\n if (start === -1) return null;\n let url = lines[start].trim();\n for (let i = start + 1; i < field; i++) {\n const piece = lines[i].replace(/\\s+$/, \"\");\n if (!LINK_REST.test(piece)) break;\n url += piece;\n }\n return url;\n}\n","import { execFileSync } from \"child_process\";\n\n/**\n * Resize a tmux window to the specified dimensions.\n * Uses resize-window instead of resize-pane for detached sessions.\n *\n * @param target - The tmux target in format \"session:window.pane\"\n * @param cols - Number of columns (clamped to 20-500)\n * @param rows - Number of rows (clamped to 5-200)\n */\nexport function resizeTmuxWindow(target: string, cols: number, rows: number): void {\n try {\n const safeCols = Math.max(20, Math.min(500, Math.floor(cols)));\n const safeRows = Math.max(5, Math.min(200, Math.floor(rows)));\n // Extract window target (session:window) from full target (session:window.pane)\n const windowTarget = target.replace(/\\.\\d+$/, \"\");\n execFileSync(\n \"tmux\",\n [\"resize-window\", \"-t\", windowTarget, \"-x\", String(safeCols), \"-y\", String(safeRows)],\n {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 2000,\n }\n );\n } catch {\n // Pane may not exist or tmux error - ignore\n }\n}\n","/**\n * Consistent pane snapshots for the terminal stream.\n *\n * The web terminal renders two regions:\n * - history: lines that scrolled off the top of the pane (immutable in tmux,\n * addressed by absolute index 0..history_size-1, 0 = oldest)\n * - screen: the visible pane (pane_height rows, redrawn constantly)\n *\n * `snapshotPane` reads `#{history_size}`, the last TAIL history lines and the\n * screen in ONE tmux invocation so the three are mutually consistent. The\n * manager diffs history_size between ticks and forwards only the new history\n * lines (taken from the tail) plus the screen when it changed.\n */\nimport { execFileSync } from \"child_process\";\n\nexport const HISTORY_TAIL = 64;\nconst SEP = \"@@CLAUDE_MUX_SEP@@\";\n\nexport interface PaneSnapshot {\n historySize: number;\n alt: boolean;\n cols: number;\n rows: number;\n /** Last min(HISTORY_TAIL, historySize) history lines, oldest first */\n tail: string[];\n /** Visible pane, exactly `rows` lines */\n screen: string[];\n}\n\nfunction run(args: string[]): string {\n return execFileSync(\"tmux\", args, {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n timeout: 2000,\n maxBuffer: 16 * 1024 * 1024,\n });\n}\n\n/** Split capture output into lines, dropping the single trailing newline tmux adds. */\nfunction splitLines(s: string): string[] {\n if (s === \"\") return [];\n const lines = s.split(\"\\n\");\n if (lines[lines.length - 1] === \"\") lines.pop();\n return lines;\n}\n\nexport function snapshotPane(target: string): PaneSnapshot | null {\n let out: string;\n try {\n out = run([\n \"display-message\", \"-p\", \"-t\", target,\n \"#{history_size} #{alternate_on} #{pane_width} #{pane_height}\",\n \";\",\n \"capture-pane\", \"-p\", \"-e\", \"-t\", target, \"-S\", `-${HISTORY_TAIL}`, \"-E\", \"-1\",\n \";\",\n \"display-message\", \"-p\", SEP,\n \";\",\n \"capture-pane\", \"-p\", \"-e\", \"-t\", target,\n ]);\n } catch {\n return null;\n }\n const nl = out.indexOf(\"\\n\");\n if (nl === -1) return null;\n const header = out.slice(0, nl).trim().split(/\\s+/);\n if (header.length < 4) return null;\n const historySize = Number(header[0]) || 0;\n const alt = header[1] === \"1\";\n const cols = Number(header[2]) || 0;\n const rows = Number(header[3]) || 0;\n\n const body = out.slice(nl + 1);\n const sepIdx = body.indexOf(SEP + \"\\n\");\n if (sepIdx === -1) return null;\n const tailRaw = body.slice(0, sepIdx);\n const screenRaw = body.slice(sepIdx + SEP.length + 1);\n\n let tail = splitLines(tailRaw);\n // When history_size < TAIL, tmux clamps -S and (for 0 history) the range\n // -S -64 -E -1 yields nothing useful. Trust history_size.\n if (historySize === 0) tail = [];\n else if (tail.length > historySize) tail = tail.slice(tail.length - historySize);\n\n return { historySize, alt, cols, rows, tail, screen: splitLines(screenRaw) };\n}\n\n/**\n * Fetch history lines [start, end) by absolute index.\n * tmux addresses history relative to the *current* history_size, which may have\n * moved since the caller last saw it. We read history_size in the same batch and,\n * if it drifted, retry with the fresh value (a few times) so the returned range\n * is exactly the one asked for. If it keeps drifting we return the range the\n * lines actually correspond to, labelled correctly.\n */\nexport function fetchHistoryRange(\n target: string,\n start: number,\n end: number,\n knownHistorySize: number,\n): { start: number; lines: string[]; historySize: number } | null {\n if (end <= start) return { start, lines: [], historySize: knownHistorySize };\n let known = knownHistorySize;\n let last: { start: number; lines: string[]; historySize: number } | null = null;\n for (let attempt = 0; attempt < 4; attempt++) {\n const relStart = start - known; // negative\n const relEnd = end - 1 - known; // negative, inclusive\n if (relEnd >= 0) {\n // Range (partly) past the end of history as we know it — cap to history.\n return { start, lines: [], historySize: known };\n }\n let out: string;\n try {\n out = run([\n \"display-message\", \"-p\", \"-t\", target, \"#{history_size}\",\n \";\",\n \"capture-pane\", \"-p\", \"-e\", \"-t\", target, \"-S\", String(relStart), \"-E\", String(relEnd),\n ]);\n } catch {\n return null;\n }\n const nl = out.indexOf(\"\\n\");\n if (nl === -1) return null;\n const historySize = Number(out.slice(0, nl).trim()) || 0;\n const lines = splitLines(out.slice(nl + 1));\n const drift = historySize - known;\n if (drift === 0) return { start, lines, historySize };\n // Offsets were interpreted against `historySize`, so lines begin at start+drift.\n last = { start: Math.max(0, start + drift), lines, historySize };\n known = historySize;\n }\n return last;\n}\n","/**\n * This machine's Claude Code plugins, for the fleet to keep in step\n * (`src/fleet-plugins.ts`): its marketplaces with the commit each is at and\n * the latest commit its source has, and its installed plugins with their\n * version. Read through the `claude plugin` CLI, never off its files, so the\n * report says what Claude Code itself would.\n *\n * Reading runs the CLI and asks GitHub, so it is never done on the poll's\n * time: `PluginReader.current()` answers with what is known and reads again in the\n * background once the last attempt is older than REFRESH_MS.\n * `startAlign()` runs \"Bring in line\" as a job in the background (updating\n * the marketplaces that are behind, then each install and enable, then a\n * fresh read) and answers at once: a marketplace update fetches a whole git\n * checkout and can take minutes, far past the 10 s Bun gives an idle request.\n * The page reads the job with `alignJob()` until it ends.\n *\n * Only user-scope plugins are reported; what a project turns on is its own.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { delimiter, join } from \"node:path\";\nimport { normaliseRemote } from \"../fleet-projects.js\";\nimport { isRepoSlug } from \"../github-ref.js\";\nimport type { AlignJob, AlignRequest, AlignStep, MarketplaceReport, PluginReport, PluginsReport } from \"../fleet-plugins.js\";\n\n/** How long a report stands before it is read again. */\nconst REFRESH_MS = 5 * 60_000;\n/** How long the latest commit of a marketplace's source stands. */\nconst LATEST_MS = 10 * 60_000;\nconst READ_TIMEOUT_MS = 30_000;\nconst STEP_TIMEOUT_MS = 90_000;\n/** A marketplace fetch, or an install that adds one: a large checkout far behind its remote takes minutes. */\nexport const FETCH_TIMEOUT_MS = 10 * 60_000;\n/** How much of a step's output a job keeps, in characters. */\nconst OUTPUT_TAIL = 8_000;\n\nexport interface RunResult {\n code: number;\n stdout: string;\n stderr: string;\n /** Stopped for running past its limit. */\n timedOut?: boolean;\n}\n\nexport type Runner = (cmd: string, args: string[], timeoutMs: number) => Promise<RunResult>;\n\ninterface PluginDeps {\n run?: Runner;\n now?: () => number;\n /** Reads a file's text; null when it is not there. */\n readText?: (path: string) => Promise<string | null>;\n}\n\n/**\n * `claude` is usually in `~/.local/bin`, which a service's PATH may leave\n * out, so that and Claude Code's own install folder are searched too.\n */\nconst RUN_PATH = [process.env.PATH ?? \"\", join(homedir(), \".local\", \"bin\"), join(homedir(), \".claude\", \"local\")]\n .filter(Boolean)\n .join(delimiter);\n\nconst defaultRun: Runner = (cmd, args, timeoutMs) =>\n new Promise((done) => {\n execFile(\n cmd,\n args,\n {\n timeout: timeoutMs,\n maxBuffer: 16 * 1024 * 1024,\n // git asking for a password or a host key would wait out the timeout.\n env: { ...process.env, PATH: RUN_PATH, GIT_TERMINAL_PROMPT: \"0\", GIT_SSH_COMMAND: \"ssh -o BatchMode=yes\" },\n },\n (err, stdout, stderr) => {\n const code = err ? (typeof err.code === \"number\" ? err.code : -1) : 0;\n // Killed for printing past maxBuffer is a failure of its own, not a timeout.\n const timedOut = !!err && err.killed === true && !!err.signal && err.code !== \"ERR_CHILD_PROCESS_STDIO_MAXBUFFER\";\n done({ code, stdout: String(stdout), stderr: String(stderr || (err && code === -1 && !timedOut ? err.message : \"\")), timedOut });\n },\n );\n });\n\nconst defaultReadText = (path: string) => readFile(path, \"utf8\").catch(() => null);\n\ninterface CliMarketplace {\n name?: unknown;\n source?: unknown;\n repo?: unknown;\n url?: unknown;\n installLocation?: unknown;\n}\n\ninterface CliPlugin {\n id?: unknown;\n version?: unknown;\n scope?: unknown;\n enabled?: unknown;\n}\n\nfunction parseJson<T>(text: string): T[] {\n try {\n const data = JSON.parse(text);\n return Array.isArray(data) ? data.filter((x) => x && typeof x === \"object\") : [];\n } catch {\n return [];\n }\n}\n\nconst SHA = /^[0-9a-f]{40}$/;\n\n/**\n * The commit a marketplace's checkout is at: its git HEAD, or the `.gcs-sha`\n * Claude Code writes beside one it downloaded as an archive.\n */\nasync function localCommit(location: string, run: Runner, readText: (path: string) => Promise<string | null>): Promise<string | null> {\n const head = await run(\"git\", [\"-C\", location, \"rev-parse\", \"HEAD\"], READ_TIMEOUT_MS);\n const sha = head.stdout.trim();\n if (head.code === 0 && SHA.test(sha)) return sha;\n const gcs = (await readText(join(location, \".gcs-sha\")))?.trim() ?? \"\";\n return SHA.test(gcs) ? gcs : null;\n}\n\n/** GitHub's own name for a repo (a renamed one answers under its new name) and its default branch's head. */\nasync function askGithub(repo: string, run: Runner): Promise<{ repo: string; latest: string } | null> {\n const res = await run(\"gh\", [\"api\", `repos/${repo}`, \"--jq\", \".full_name\"], READ_TIMEOUT_MS);\n const name = res.stdout.trim();\n if (res.code !== 0 || !isRepoSlug(name)) return null;\n const head = await run(\"gh\", [\"api\", `repos/${name}/commits/HEAD`, \"--jq\", \".sha\"], READ_TIMEOUT_MS);\n const sha = head.stdout.trim();\n return head.code === 0 && SHA.test(sha) ? { repo: name, latest: sha } : null;\n}\n\n/** A source's head through git itself, for a machine without `gh`. */\nasync function lsRemote(url: string, run: Runner): Promise<string | null> {\n const res = await run(\"git\", [\"ls-remote\", url, \"HEAD\"], READ_TIMEOUT_MS);\n const sha = res.stdout.split(/\\s/)[0];\n return res.code === 0 && SHA.test(sha) ? sha : null;\n}\n\ntype Source = { repo: string; latest: string | null };\n\nexport class PluginReader {\n private report: PluginsReport | null = null;\n private reading: Promise<PluginsReport | null> | null = null;\n private job: AlignJob | null = null;\n /** When the last read started, so a CLI that fails is not run again on every broadcast. */\n private attemptedAt = -Infinity;\n private sources = new Map<string, { at: number; source: Source }>();\n private readonly run: Runner;\n private readonly now: () => number;\n private readonly readText: (path: string) => Promise<string | null>;\n\n constructor(deps: PluginDeps = {}) {\n this.run = deps.run ?? defaultRun;\n this.now = deps.now ?? Date.now;\n this.readText = deps.readText ?? defaultReadText;\n }\n\n /** What is known now, starting a read in the background when the last one is stale. */\n current(): PluginsReport | null {\n if (this.now() - this.attemptedAt > REFRESH_MS && !this.reading) void this.read().catch((err) => console.warn(\"[plugins] read failed:\", err));\n return this.report;\n }\n\n /** Read the report now; a read already under way is joined. When the CLI cannot be run, the last report (null before any). */\n read(): Promise<PluginsReport | null> {\n if (!this.reading) this.attemptedAt = this.now();\n this.reading ??= this.readNow().finally(() => {\n this.reading = null;\n });\n return this.reading;\n }\n\n /** Read again after whatever read is under way, so the report reflects everything done before the call. */\n async refresh(): Promise<PluginsReport | null> {\n await this.reading;\n return this.read();\n }\n\n /**\n * Start \"Bring in line\" unless a run is under way, which is joined instead:\n * two runs at once would both rewrite Claude Code's plugin files. Answers\n * the job as it stands; `onDone` is called once it has ended and the report\n * has been read again.\n */\n startAlign(plan: AlignRequest, onDone: () => void = () => {}): { started: boolean; job: AlignJob } {\n if (this.job?.state === \"running\") return { started: false, job: snapshot(this.job) };\n const runs = alignRuns(plan);\n const job: AlignJob = { state: \"running\", startedAt: this.now(), finishedAt: null, steps: runs.map((r) => r.step) };\n this.job = job;\n void this.runJob(job, runs, onDone);\n return { started: true, job: snapshot(job) };\n }\n\n /** The last \"Bring in line\" run, null before any. */\n alignJob(): AlignJob | null {\n return this.job && snapshot(this.job);\n }\n\n /** Each step runs whatever the one before did; the report is read again at the end so the fleet sees the result. */\n private async runJob(job: AlignJob, runs: AlignRun[], onDone: () => void): Promise<void> {\n try {\n for (const { step, args, limit } of runs) {\n step.state = \"running\";\n step.startedAt = this.now();\n const res = await this.run(\"claude\", args, limit).catch(\n (err): RunResult => ({ code: -1, stdout: \"\", stderr: String(err) }),\n );\n step.finishedAt = this.now();\n // The page reads the job every second, through the relay for another machine.\n const output = `${res.stdout}${res.stderr}`.trim().slice(-OUTPUT_TAIL);\n if (res.timedOut) {\n step.state = \"timeout\";\n step.output = `${output}\\n(stopped after ${Math.round(limit / 60_000)} min without finishing)`.trimStart();\n } else {\n step.state = res.code === 0 ? \"ok\" : \"failed\";\n step.output = output;\n }\n }\n await this.refresh().catch((err) => console.warn(\"[plugins] read failed:\", err));\n } finally {\n job.state = \"done\";\n job.finishedAt = this.now();\n onDone();\n }\n }\n\n private async readNow(): Promise<PluginsReport | null> {\n const [mps, pls] = await Promise.all([\n this.run(\"claude\", [\"plugin\", \"marketplace\", \"list\", \"--json\"], READ_TIMEOUT_MS),\n this.run(\"claude\", [\"plugin\", \"list\", \"--json\"], READ_TIMEOUT_MS),\n ]);\n if (mps.code !== 0 || pls.code !== 0) {\n console.warn(\"[plugins] could not read:\", (mps.stderr || pls.stderr).trim().split(\"\\n\")[0]);\n return this.report;\n }\n const marketplaces = await Promise.all(parseJson<CliMarketplace>(mps.stdout).map((m) => this.marketplace(m)));\n const plugins: PluginReport[] = parseJson<CliPlugin>(pls.stdout)\n .filter((p) => typeof p.id === \"string\" && (p.scope === undefined || p.scope === \"user\"))\n .map((p) => ({\n id: p.id as string,\n version: typeof p.version === \"string\" ? p.version : null,\n enabled: p.enabled !== false,\n }))\n .sort((a, b) => a.id.localeCompare(b.id));\n const report: PluginsReport = {\n marketplaces: marketplaces.filter((m): m is MarketplaceReport => m !== null),\n plugins,\n readAt: this.now(),\n };\n this.report = report;\n return report;\n }\n\n private async marketplace(m: CliMarketplace): Promise<MarketplaceReport | null> {\n if (typeof m.name !== \"string\") return null;\n const location = typeof m.installLocation === \"string\" ? m.installLocation : null;\n // A GitHub repo added by URL is asked about as one added from GitHub.\n const github =\n m.source === \"github\" && typeof m.repo === \"string\"\n ? m.repo\n : typeof m.url === \"string\"\n ? /^github\\.com\\/([^/]+\\/[^/]+)$/.exec(normaliseRemote(m.url) ?? \"\")?.[1]\n : undefined;\n const written = github ?? (typeof m.url === \"string\" ? m.url : null);\n const [commit, source] = await Promise.all([\n location ? localCommit(location, this.run, this.readText) : null,\n this.source(github, written),\n ]);\n return { name: m.name, repo: source?.repo ?? written, commit, latest: source?.latest ?? null };\n }\n\n private async source(github: string | undefined, written: string | null): Promise<Source | null> {\n if (!written) return null;\n const cached = this.sources.get(written);\n if (cached && this.now() - cached.at < LATEST_MS) return cached.source;\n let source: Source | null = null;\n if (github) {\n source = (await askGithub(github, this.run)) ?? { repo: github, latest: await lsRemote(`https://github.com/${github}.git`, this.run) };\n } else {\n source = { repo: written, latest: await lsRemote(written, this.run) };\n }\n if (source) this.sources.set(written, { at: this.now(), source });\n return source;\n }\n}\n\n/** A step of a job with the `claude` arguments it runs and how long they may take. */\ninterface AlignRun {\n step: AlignStep;\n args: string[];\n limit: number;\n}\n\n/** The plan as the steps it runs, in order: marketplace updates, installs, enables. */\nfunction alignRuns(plan: AlignRequest): AlignRun[] {\n const run = (kind: AlignStep[\"kind\"], label: string, args: string[], limit: number): AlignRun => ({\n step: { kind, label, command: `claude ${args.join(\" \")}`, state: \"pending\", startedAt: null, finishedAt: null, output: \"\" },\n args,\n limit,\n });\n return [\n ...plan.update.map((name) => run(\"update\", `Updating ${name}`, [\"plugin\", \"marketplace\", \"update\", name], FETCH_TIMEOUT_MS)),\n ...plan.install.map((s) =>\n \"marketplace\" in s\n ? run(\"install\", `Installing ${s.name}`, [\"plugin\", \"install\", `${s.name}@${s.marketplace}`], STEP_TIMEOUT_MS)\n : // Adds the marketplace, which clones it.\n run(\"install\", `Installing ${s.name}`, [\"plugin\", \"install\", s.name, \"--marketplace\", s.repo], FETCH_TIMEOUT_MS),\n ),\n ...plan.enable.map((id) => run(\"enable\", `Enabling ${id}`, [\"plugin\", \"enable\", id, \"--scope\", \"user\"], STEP_TIMEOUT_MS)),\n ];\n}\n\nfunction snapshot(job: AlignJob): AlignJob {\n return { ...job, steps: job.steps.map((s) => ({ ...s })) };\n}\n\ntype GlobalWithPlugins = typeof globalThis & { __claudeMuxPlugins?: PluginReader };\n\n/** This process's one reader (the dev server loads modules more than once). */\nexport function pluginReader(): PluginReader {\n const g = globalThis as GlobalWithPlugins;\n return (g.__claudeMuxPlugins ??= new PluginReader());\n}\n","/**\n * Past sessions: the Claude Code conversations a machine still holds on disk\n * (GLOSSARY.md, \"Past session\"), read from their transcripts.\n *\n * A transcript is `~/.claude/projects/<launch dir>/<session id>.jsonl`; its\n * subagents sit below it in `<session id>/subagents/`, so only the files at\n * the top of each project directory are conversations. Each is read at its\n * two ends, never whole: the head gives where it ran and the first prompt,\n * the tail when it was last active, its latest folder and branch, and its\n * title (a `custom-title` set with /rename, else Claude Code's `ai-title`).\n * A title written mid-file is missed, and the first prompt stands in.\n *\n * Claude Code deletes transcripts after 30 days; what is on disk is the list.\n * The format is Claude Code's own and changes between versions, so every\n * field is optional and a file that cannot be read is left out.\n */\n\nimport { open, readdir, readFile, stat } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport { type PastSession } from \"../past-session.js\";\nimport { parseSlashCommand, unwrapPasted } from \"./parser.js\";\nimport { projectsRoot } from \"./tailer.js\";\n\n\n/** How much of each end of a transcript is read. */\nexport const END_BYTES = 64 * 1024;\n/** A prompt or title longer than this is cut; the list shows one line. */\nexport const MAX_LABEL = 160;\n\nconst SESSION_FILE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\.jsonl$/i;\n\n\ntype Entry = Record<string, unknown>;\n\n/** The whole JSON lines in `text`; with `cutStart`/`cutEnd` the first or last line is a fragment and is dropped. */\nfunction parseLines(text: string, cutStart: boolean, cutEnd: boolean): Entry[] {\n const lines = text.split(\"\\n\");\n if (cutStart) lines.shift();\n if (cutEnd) lines.pop();\n const out: Entry[] = [];\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const parsed: unknown = JSON.parse(line);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) out.push(parsed as Entry);\n } catch {\n // A torn or foreign line: skip it.\n }\n }\n return out;\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === \"string\" && value.trim() ? value : null;\n}\n\nfunction clip(text: string): string {\n const flat = text.replace(/\\s+/g, \" \").trim();\n return flat.length > MAX_LABEL ? `${flat.slice(0, MAX_LABEL - 1)}…` : flat;\n}\n\n/**\n * What a person typed in a user entry, or null for anything else: a tool\n * result, a meta line, a hook's or a command's output. A slash command reads\n * as typed (`/ak:maestro-worker 102`).\n */\nexport function promptText(entry: Entry): string | null {\n if (entry.type !== \"user\" || entry.isMeta === true || entry.isSidechain === true) return null;\n const message = entry.message as { content?: unknown } | undefined;\n let text: string | null = null;\n if (typeof message?.content === \"string\") text = message.content;\n else if (Array.isArray(message?.content)) {\n const block = message.content.find(\n (b): b is { type: \"text\"; text: string } => !!b && typeof b === \"object\" && b.type === \"text\" && typeof b.text === \"string\"\n );\n if (!block || message.content.some((b) => b?.type === \"tool_result\")) return null;\n text = block.text;\n }\n // A long paste is logged inside Claude Code's <pasted_content> wrapper.\n if (text) text = unwrapPasted(text);\n if (!text?.trim()) return null;\n const command = parseSlashCommand(text);\n if (command) return clip(command.args ? `${command.name} ${command.args}` : command.name);\n if (/^\\s*<(local-command|command-message|system-reminder|bash-|user-memory)/.test(text) || text.startsWith(\"Caveat:\")) {\n return null;\n }\n return clip(text);\n}\n\nfunction timestampOf(entry: Entry): number | null {\n const ts = typeof entry.timestamp === \"string\" ? Date.parse(entry.timestamp) : NaN;\n return Number.isFinite(ts) ? ts : null;\n}\n\n/**\n * Read one transcript's two ends into a past session, or null when it is not\n * one a person would reopen: a subagent's (sidechain), one run by the SDK or\n * `claude -p` rather than at the prompt, or one where nothing was ever asked.\n */\nexport async function readPastSession(path: string): Promise<PastSession | null> {\n let handle;\n try {\n handle = await open(path, \"r\");\n } catch {\n return null;\n }\n let head: string;\n let tail: string;\n let size: number;\n let mtimeMs: number;\n try {\n const info = await handle.stat();\n size = info.size;\n mtimeMs = info.mtimeMs;\n const headBuf = Buffer.alloc(Math.min(size, END_BYTES));\n await handle.read(headBuf, 0, headBuf.length, 0);\n head = headBuf.toString(\"utf8\");\n if (size > END_BYTES) {\n const tailLength = Math.min(size - END_BYTES, END_BYTES);\n const tailBuf = Buffer.alloc(tailLength);\n await handle.read(tailBuf, 0, tailLength, size - tailLength);\n tail = tailBuf.toString(\"utf8\");\n } else {\n tail = \"\";\n }\n } catch {\n return null;\n } finally {\n await handle.close();\n }\n\n const headEntries = parseLines(head, false, size > END_BYTES);\n // The tail's first line is whole only when it starts right after the head.\n const tailEntries = tail ? parseLines(tail, size > 2 * END_BYTES || !head.endsWith(\"\\n\"), false) : [];\n const all = [...headEntries, ...tailEntries];\n\n let cwd: string | null = null;\n let branch: string | null = null;\n let prompt: string | null = null;\n let customTitle: string | null = null;\n let aiTitle: string | null = null;\n let lastPrompt: string | null = null;\n let last: number | null = null;\n for (const entry of all) {\n // A subagent's line inside the conversation is not the conversation's.\n if (entry.isSidechain === true) continue;\n const entrypoint = str(entry.entrypoint);\n if (entrypoint?.startsWith(\"sdk\")) return null;\n cwd = str(entry.cwd) ?? cwd;\n branch = str(entry.gitBranch) ?? branch;\n prompt ??= promptText(entry);\n if (entry.type === \"custom-title\") customTitle = str(entry.customTitle) ?? customTitle;\n if (entry.type === \"ai-title\") aiTitle = str(entry.aiTitle) ?? aiTitle;\n if (entry.type === \"last-prompt\") lastPrompt = str(entry.lastPrompt) ?? lastPrompt;\n last = timestampOf(entry) ?? last;\n }\n prompt ??= lastPrompt ? clip(lastPrompt) : null;\n // /rename also leaves the title beside the transcript.\n customTitle = (await readTitleFile(path)) ?? customTitle;\n const title = customTitle ?? aiTitle;\n if (!prompt && !title) return null;\n return {\n id: basename(path, \".jsonl\"),\n cwd,\n branch,\n title: title ? clip(title) : null,\n prompt,\n lastActivity: last ?? mtimeMs,\n };\n}\n\nasync function readTitleFile(transcriptPath: string): Promise<string | null> {\n try {\n const raw = JSON.parse(await readFile(join(transcriptPath.replace(/\\.jsonl$/, \"\"), \"custom-title.json\"), \"utf8\"));\n return str(raw?.customTitle);\n } catch {\n return null;\n }\n}\n\n/** The conversation transcripts under `root`: the top of each project directory, never a subagent's. */\nexport async function listTranscripts(root = projectsRoot()): Promise<{ path: string; mtimeMs: number; size: number }[]> {\n let dirs: string[];\n try {\n dirs = await readdir(root);\n } catch {\n return [];\n }\n const out: { path: string; mtimeMs: number; size: number }[] = [];\n for (const dir of dirs) {\n let names: string[];\n try {\n names = await readdir(join(root, dir));\n } catch {\n continue;\n }\n for (const name of names) {\n if (!SESSION_FILE.test(name)) continue;\n const path = join(root, dir, name);\n try {\n const info = await stat(path);\n if (info.isFile()) out.push({ path, mtimeMs: info.mtimeMs, size: info.size });\n } catch {\n // Gone between the listing and the stat.\n }\n }\n }\n return out;\n}\n\n/**\n * Every past session on this machine, kept between reads: a transcript is\n * read again only when its size or time changes, so a listing after the\n * first costs a directory walk and a stat per file.\n */\nexport class PastSessionIndex {\n private cache = new Map<string, { mtimeMs: number; size: number; session: PastSession | null }>();\n\n constructor(private readonly root: () => string = projectsRoot) {}\n\n /** Every past session, newest first. A session id found twice (a transcript moved by /cd) keeps the newer. */\n async list(): Promise<PastSession[]> {\n const files = await listTranscripts(this.root());\n const seen = new Set<string>();\n for (const file of files) {\n seen.add(file.path);\n const known = this.cache.get(file.path);\n if (known && known.mtimeMs === file.mtimeMs && known.size === file.size) continue;\n this.cache.set(file.path, { mtimeMs: file.mtimeMs, size: file.size, session: await readPastSession(file.path) });\n }\n for (const path of this.cache.keys()) if (!seen.has(path)) this.cache.delete(path);\n const byId = new Map<string, PastSession>();\n for (const { session } of this.cache.values()) {\n if (!session) continue;\n const other = byId.get(session.id);\n if (!other || other.lastActivity < session.lastActivity) byId.set(session.id, session);\n }\n return [...byId.values()].sort((a, b) => b.lastActivity - a.lastActivity);\n }\n}\n\n/** The past sessions that are not live: any session this machine's broadcast lists is left out. */\nexport function withoutLive(past: readonly PastSession[], live: ReadonlySet<string>): PastSession[] {\n return past.filter((p) => !live.has(p.id));\n}\n","/**\n * Shared WebSocket handler logic for both dev (ws) and production (Bun) servers.\n * Abstracts the WebSocket-specific APIs behind a simple interface.\n *\n * Features:\n * - Max clients limit to prevent server overload\n * - Structured error logging\n * - Backpressure handling for slow clients\n */\n\nimport { execFileSync } from 'child_process';\nimport { existsSync, readFileSync } from 'fs';\nimport { getAllSessions, getSession, updateSession, readLinks, cleanupStaleSessions, sanitizeDisplayName, getVisits, type Session } from '../db/index.js';\nimport { type ContextUsage } from '../transcript/context.js';\nimport { TranscriptBuilder, type TranscriptEntry } from '../transcript/parser.js';\nimport { subagentPayload, type SubagentPayload } from '../transcript/subagent.js';\nimport { isDictated } from './dictation-marks.js';\nimport { homeDiskPercent } from './disk.js';\nimport { findTranscriptById, JsonlTailer, listSubagents, resolveTranscriptPath, type SubagentMeta } from '../transcript/tailer.js';\nimport { currentRemoteControlUrl, answerRemoteControlDialog, setRemoteControl } from './remote-control.js';\nimport { listAllPanes, type PaneInfo, capturePaneContentAsync, readActivity, readActivityLine, isPaneShowingIdlePrompt, detectRecentInterruption, readPromptBox, readQueuedMessages, readPromptOptions, readUpdateNotice, stripAnsi } from '../tmux/pane.js';\nimport type { PaneActivity, PromptChoice, UpdateNotice } from '../tmux/pane.js';\nimport { wantsWidening, widenDetachedWindow } from '../tmux/geometry.js';\nimport { readLoginScreen, type LoginScreen } from '../tmux/login.js';\nimport { readRewindScreen, type RewindScreen } from '../tmux/rewind.js';\nimport { upgradeStatus } from './restart-claude.js';\nimport { getSavedProjects, saveProjects } from '../db/projects-json.js';\nimport { peekContextPercent, peekTranscriptTitle } from '../transcript/context-peek.js';\n\n/**\n * What the session poll reads off the pane itself and rides the broadcast —\n * never written to the session JSON.\n *\n * Every field here also needs a line in `EnrichedSessionSchema`\n * (`src/types/ws-messages.ts`), or Zod strips it on the way into the browser\n * and the UI silently never sees it.\n */\ntype LivePaneFields = {\n\tpane_title: string | null;\n\tpane_alive: boolean;\n\tdraft_input: string | null;\n\tdraft_kind: 'typed' | 'suggestion' | null;\n\tpane_queue: string[];\n\tpane_choice: PromptChoice;\n\t/** Claude Code's footer notice about its own update; null when it shows none. */\n\tpane_update: UpdateNotice;\n\t/** Claude Code's sign-in screen, which the transcript never sees; null when none is drawn. */\n\tpane_login: LoginScreen;\n\tpane_rewind: RewindScreen;\n\t/** The upgrade the status line's button started, while it runs or after it ended without a restart. */\n\tupgrade: UpgradeStatus | null;\n\t/** The spinner line while a turn runs, split into its parts; null when none is drawn. */\n\tpane_activity: LiveActivity | null;\n\t/** Share of the context window in use as of the latest reply; null when unknown. */\n\tcontext_pct: number | null;\n\t/** The GitHub issue a maestro worker owns, as last read; null otherwise. */\n\tissue: IssueInfo | null;\n\t/** Every message claude-mux holds for the pane, next out first. */\n\tqueue: QueuedMessageInfo[];\n\t/** What the queue and steers handed the pane lately, so the transcript can label it. */\n\tdelivered: DeliveryInfo[];\n\t/** When anyone last had the session open, from `visits.json`; null when nobody has. */\n\tlast_visited_at: number | null;\n\t/** What the session has changed: git's count when it is in a repo, else its log's; null until known. */\n\tchanges: ChangesInfo | null;\n};\nimport { resizeTmuxWindow } from '../tmux/resize.js';\nimport { snapshotPane, fetchHistoryRange } from '../tmux/snapshot.js';\nimport { sessionWatcher } from './watcher.js';\nimport { getQueue, getDeliveries } from './message-queue.js';\nimport { getSettings, isClaudeMuxSessionName } from '../db/settings-json.js';\nimport type { ChangesInfo, DeliveryInfo, IssueInfo, QueuedMessageInfo, SessionsWsMessage, SystemStatsMessage, UpgradeStatus } from '../types/ws-messages.js';\nimport { inboxTickets, issueFor, watchSessionRepos } from './github.js';\nimport { projectReports } from './project-icons.js';\nimport { pluginReader } from './plugins.js';\nimport { PastSessionIndex, withoutLive } from '../transcript/past-sessions.js';\nimport type { PastSession } from '../past-session.js';\nimport type { PlainPane } from '../fleet-places.js';\nimport { peekGitSummary } from './git.js';\nimport { sessionChanges } from '../transcript/changes.js';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nexport interface WsConfig {\n\t/** Max clients for sessions endpoint (default: 50) */\n\tmaxSessionsClients?: number;\n\t/** Max clients per terminal target (default: 10) */\n\tmaxTerminalClientsPerTarget?: number;\n\t/** Max total terminal clients across all targets (default: 100) */\n\tmaxTerminalClientsTotal?: number;\n\t/** Max queued messages before dropping slow client (default: 100) */\n\tmaxQueuedMessages?: number;\n\t/** Enable debug logging (default: false) */\n\tdebug?: boolean;\n}\n\nconst DEFAULT_CONFIG: Required<WsConfig> = {\n\tmaxSessionsClients: 50,\n\tmaxTerminalClientsPerTarget: 10,\n\tmaxTerminalClientsTotal: 100,\n\tmaxQueuedMessages: 100,\n\tdebug: false\n};\n\n// ============================================================================\n// Logging\n// ============================================================================\n\ntype LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\ninterface LogEntry {\n\tlevel: LogLevel;\n\tcomponent: string;\n\tmessage: string;\n\tdata?: Record<string, unknown>;\n}\n\nfunction log(entry: LogEntry, config: Required<WsConfig>): void {\n\tif (entry.level === 'debug' && !config.debug) return;\n\n\tconst prefix = `[ws:${entry.component}]`;\n\tconst msg = entry.data\n\t\t? `${prefix} ${entry.message} ${JSON.stringify(entry.data)}`\n\t\t: `${prefix} ${entry.message}`;\n\n\tswitch (entry.level) {\n\t\tcase 'debug':\n\t\t\tconsole.debug(msg);\n\t\t\tbreak;\n\t\tcase 'info':\n\t\t\tconsole.info(msg);\n\t\t\tbreak;\n\t\tcase 'warn':\n\t\t\tconsole.warn(msg);\n\t\t\tbreak;\n\t\tcase 'error':\n\t\t\tconsole.error(msg);\n\t\t\tbreak;\n\t}\n}\n\n// ============================================================================\n// Generic WebSocket client interface\n// ============================================================================\n\nexport interface WsClient {\n\tsend: (data: string) => void;\n\tisOpen: () => boolean;\n\tclose: () => void;\n\t/** Optional: Get buffered amount for backpressure detection */\n\tgetBufferedAmount?: () => number;\n\t/** Optional: send without per-message deflate, for payloads that are compressed already. */\n\tsendUncompressed?: (data: string) => void;\n}\n\n// ============================================================================\n// Message types (sessions WS uses shared schemas from types/ws-messages.ts)\n// ============================================================================\n\nexport type { SessionsWsMessage, SystemStatsMessage } from '../types/ws-messages.js';\nexport type { SubagentPayload } from '../transcript/subagent.js';\n\n/** Visible pane changed (or initial). `historySize` is the absolute index right after the last history line. */\nexport interface ScreenMessage {\n\ttype: 'screen';\n\tlines: string[];\n\thistorySize: number;\n\talt: boolean;\n\tcols: number;\n\trows: number;\n\ttimestamp: number;\n}\n\n/** A contiguous block of history lines [start, start+lines.length). */\nexport interface HistoryMessage {\n\ttype: 'history';\n\tstart: number;\n\tlines: string[];\n\thistorySize: number;\n\ttimestamp: number;\n}\n\n/** History became invalid (pane reflowed on resize, cleared, alt-screen toggled). Client drops history and re-requests. */\nexport interface ResetMessage {\n\ttype: 'reset';\n\thistorySize: number;\n\talt: boolean;\n\tcols: number;\n\trows: number;\n\ttimestamp: number;\n}\n\nexport type TerminalMessage = ScreenMessage | HistoryMessage | ResetMessage;\n\nexport interface ResizeMessage {\n\ttype: 'resize';\n\tcols: number;\n\trows: number;\n}\n\n/** Client asks for `count` history lines ending right before absolute index `before`. */\ninterface HistoryRequestMessage {\n\ttype: 'history_request';\n\tbefore: number;\n\tcount: number;\n}\n\ninterface SubagentRequestMessage {\n\ttype: 'subagent_request';\n\tagentId: string;\n}\n\nconst DEFAULT_HISTORY_DEPTH = 200;\nconst MAX_HISTORY_DEPTH = 5000;\n/** Max lines served per history_request */\nconst MAX_HISTORY_REQUEST = 1000;\n\n// ============================================================================\n// Session Helpers\n// ============================================================================\n\n\nfunction deduplicateByTmuxTarget<T extends { tmux_target: string | null; last_update: number }>(\n\tsessions: T[]\n): T[] {\n\tconst byTarget = new Map<string, T>();\n\tconst noTarget: T[] = [];\n\tfor (const session of sessions) {\n\t\tif (!session.tmux_target) {\n\t\t\tnoTarget.push(session);\n\t\t\tcontinue;\n\t\t}\n\t\tconst existing = byTarget.get(session.tmux_target);\n\t\tif (!existing || session.last_update > existing.last_update) {\n\t\t\tbyTarget.set(session.tmux_target, session);\n\t\t}\n\t}\n\treturn [...byTarget.values(), ...noTarget];\n}\n\n/** Consecutive ticks a busy session's pane has looked idle (Esc edge case). */\nconst idleLookTicks = new Map<string, number>();\n\n/**\n * Capture each target's pane once per refresh and run all content-derived\n * checks (interruption, spinner, RC URL) off that single capture instead of\n * re-spawning `tmux capture-pane` three times per session per tick.\n */\n/**\n * The hook said the turn is paused on a background agent, and its tool events\n * keep the file fresh while it works. A count that has not been touched for\n * this long is a leftover — an agent that died, a file written by an older\n * hook — and the pane's ready prompt is believed over it, or the session\n * would sit busy forever and its queue never drain.\n */\nconst PAUSE_TRUST_MS = 10 * 60 * 1000;\n\nfunction pausedOnBackground(session: Session): boolean {\n\treturn (session.background_tasks ?? 0) > 0 && Date.now() - session.last_update < PAUSE_TRUST_MS;\n}\n\nasync function captureAndSyncSessions(): Promise<{\n\tcaptures: Map<string, string>;\n\t/** Same captures with ANSI intact, for reading the prompt box. */\n\trawCaptures: Map<string, string>;\n\tsessions: Session[];\n}> {\n\tconst sessions = getAllSessions();\n\tconst captures = new Map<string, string>();\n\tconst rawCaptures = new Map<string, string>();\n\n\t/** Persist and mirror onto the in-memory copy, so callers need no re-read. */\n\tconst apply = (session: Session, patch: Partial<Session>) => {\n\t\tupdateSession(session.id, patch);\n\t\tObject.assign(session, patch);\n\t};\n\n\tawait Promise.all(\n\t\tsessions.map(async (session) => {\n\t\t\tif (!session.tmux_target) return;\n\t\t\t// Captured with colour: the prompt box needs ANSI to tell text the\n\t\t\t// user typed from Claude Code's faint ghost text. Every other check\n\t\t\t// reads the stripped copy.\n\t\t\tconst raw = await capturePaneContentAsync(session.tmux_target, true);\n\t\t\tif (!raw) return;\n\t\t\tconst content = stripAnsi(raw);\n\t\t\tcaptures.set(session.tmux_target, content);\n\t\t\trawCaptures.set(session.tmux_target, raw);\n\n\t\t\tif (session.state !== 'idle' && detectRecentInterruption(content)) {\n\t\t\t\tapply(session, { state: 'idle', current_action: null, prompt_text: null });\n\t\t\t} else if (\n\t\t\t\tsession.state === 'busy' &&\n\t\t\t\t!pausedOnBackground(session) &&\n\t\t\t\tisPaneShowingIdlePrompt(content)\n\t\t\t) {\n\t\t\t\t// Esc during thinking leaves no \"Interrupted\" marker; the pane just\n\t\t\t\t// returns to the ready prompt. Debounce two ticks to avoid flapping\n\t\t\t\t// on transient frames between tools. A turn paused on a background\n\t\t\t\t// agent shows that same ready prompt on purpose; the hook said so.\n\t\t\t\tconst ticks = (idleLookTicks.get(session.id) ?? 0) + 1;\n\t\t\t\tif (ticks >= 2) {\n\t\t\t\t\tidleLookTicks.delete(session.id);\n\t\t\t\t\tapply(session, { state: 'idle', current_action: null, prompt_text: null });\n\t\t\t\t} else {\n\t\t\t\t\tidleLookTicks.set(session.id, ticks);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tidleLookTicks.delete(session.id);\n\t\t\t}\n\t\t\t// The hooks say idle but the pane is spinning: compaction, which fires\n\t\t\t// no hook. The spinner's own words are the action.\n\t\t\tif (session.state === 'idle') {\n\t\t\t\tconst activity = readActivityLine(content);\n\t\t\t\tif (activity) apply(session, { state: 'busy', current_action: activity });\n\t\t\t}\n\t\t})\n\t);\n\n\t// Drop debounce counters for sessions that vanished mid-count.\n\tif (idleLookTicks.size > 0) {\n\t\tconst live = new Set(sessions.map((s) => s.id));\n\t\tfor (const id of idleLookTicks.keys()) {\n\t\t\tif (!live.has(id)) idleLookTicks.delete(id);\n\t\t}\n\t}\n\n\treturn { captures, rawCaptures, sessions };\n}\n\n/**\n * The spinner as it rides the broadcast: the turn's start in place of its\n * elapsed count, which moves every second and would otherwise send the whole\n * sessions message to every client every tick. The browser counts up itself.\n */\ntype LiveActivity = Omit<PaneActivity, 'elapsed_s'> & { started_at: number | null };\n\n/** When each running turn started, as first worked out from the pane. */\nconst turnStarts = new Map<string, number>();\n\nfunction liveActivity(sessionId: string, activity: PaneActivity | null): LiveActivity | null {\n\tif (!activity) {\n\t\tturnStarts.delete(sessionId);\n\t\treturn null;\n\t}\n\tconst { elapsed_s, ...rest } = activity;\n\tif (elapsed_s === null) return { ...rest, started_at: turnStarts.get(sessionId) ?? null };\n\t// Whole seconds read at poll time put the start anywhere in a window of a\n\t// second or so; a reading inside the tolerance is the same turn.\n\tconst start = Date.now() - elapsed_s * 1000;\n\tconst known = turnStarts.get(sessionId);\n\tif (known !== undefined && Math.abs(start - known) <= 2000) return { ...rest, started_at: known };\n\tturnStarts.set(sessionId, start);\n\treturn { ...rest, started_at: start };\n}\n\n/**\n * Async version of getEnrichedSessions. Uses batched tmux calls\n * and concurrent interruption checks to avoid blocking the event loop.\n */\n/** Panes with a widen in flight, so a slow tmux is not asked twice. */\nconst wideningNow = new Set<string>();\n\n/** Sessions this process has already seen, so `/rc` goes only to new ones. */\nconst rcSeen = new Set<string>();\n/** False on the first poll, which only primes `rcSeen` with what already runs. */\nlet rcPrimed = false;\n\n/**\n * The transcript title each session was last seen to carry, so a name is\n * applied when Claude Code changes it and not on every tick that it differs.\n */\nconst transcriptTitleSeen = new Map<string, string>();\n\n/**\n * A `/rename` typed inside Claude Code goes through none of the hooks — slash\n * commands never reach UserPromptSubmit — so the only trace is the\n * `custom-title` record in the transcript. Mirror a change there into\n * `display_name`. The other direction (renamed from the dashboard) sends the\n * same `/rename` into the pane, so the record it produces already matches\n * and nothing is written twice.\n */\nfunction syncTranscriptTitle(s: Session): void {\n\tif ((s.agent ?? 'claude') !== 'claude') return;\n\tconst title = peekTranscriptTitle(s);\n\tif (title === undefined || transcriptTitleSeen.get(s.id) === title) return;\n\ttranscriptTitleSeen.set(s.id, title);\n\tconst name = sanitizeDisplayName(title);\n\tif (name === (s.display_name ?? null)) return;\n\tupdateSession(s.id, { display_name: name });\n\ts.display_name = name;\n}\n\n/**\n * The change count the sidebar shows: git's when the session is in a repo,\n * since git sees Bash edits too; the session's own log otherwise. Neither\n * waits: git is read in the background and answers on a later tick.\n */\nfunction liveChanges(s: Session): ChangesInfo | null {\n\tconst git = peekGitSummary(s.cwd);\n\tif (git) return { ...git, source: 'git' };\n\tif (git === undefined) return null;\n\tconst summary = sessionChanges(s)?.summary();\n\treturn summary ? { ...summary, source: 'session' } : null;\n}\n\nexport async function getEnrichedSessionsAsync(): Promise<(Session & LivePaneFields)[]> {\n\treturn (await readSessionsAndPanes()).sessions;\n}\n\n/**\n * The tmux panes no Claude session runs in (a shell, a dev server), which the\n * drawer places under their projects beside the sessions.\n */\nfunction plainPanes(panes: Map<string, PaneInfo>, sessions: Session[]): PlainPane[] {\n\tconst claude = new Set(sessions.map((s) => s.tmux_target));\n\tconst out: PlainPane[] = [];\n\tfor (const [target, pane] of panes) {\n\t\tif (claude.has(target)) continue;\n\t\tout.push({ target, session: target.split(':')[0], command: pane.command, cwd: pane.cwd, title: pane.title });\n\t}\n\treturn out;\n}\n\n/** This machine's sessions, enriched with what their panes show, and its plain panes, off one `list-panes`. */\nasync function readSessionsAndPanes(): Promise<{ sessions: (Session & LivePaneFields)[]; panes: PlainPane[] }> {\n\tconst [{ captures, rawCaptures, sessions }, allPanes] = await Promise.all([\n\t\tcaptureAndSyncSessions(),\n\t\tlistAllPanes()\n\t]);\n\n\tconst links = readLinks();\n\n\t// A pane nobody is attached to is held at the standard width, so the\n\t// screen the readers above parse is laid out the same way every time.\n\t// Fire-and-forget: the resize lands before the next tick's capture.\n\tfor (const s of sessions) {\n\t\tconst pane = s.tmux_target ? allPanes.get(s.tmux_target) : undefined;\n\t\tif (pane && wantsWidening(pane) && !wideningNow.has(s.tmux_target!)) {\n\t\t\tconst target = s.tmux_target!;\n\t\t\twideningNow.add(target);\n\t\t\tvoid widenDetachedWindow(target).finally(() => wideningNow.delete(target));\n\t\t}\n\t}\n\n\t// Remote Control for the sessions claude-mux itself made, when asked to.\n\t// A session is \"new\" when this process first sees it; everything alive at\n\t// startup is left as it is. Workers other tools start straight in tmux do\n\t// not carry a claude-mux name and are not touched.\n\tconst auto = getSettings().autoRemoteControl;\n\tfor (const s of sessions) {\n\t\tif (rcSeen.has(s.id)) continue;\n\t\trcSeen.add(s.id);\n\t\tif (!rcPrimed || !auto) continue;\n\t\tif (s.rc_url || !s.tmux_target || (s.agent ?? 'claude') !== 'claude') continue;\n\t\tif (!isClaudeMuxSessionName(s.tmux_target.split(':')[0])) continue;\n\t\tsetRemoteControl(s, true);\n\t}\n\trcPrimed = true;\n\n\tfor (const s of sessions) syncTranscriptTitle(s);\n\n\t// GitHub holds the other half of what a session is waiting on: the issue a\n\t// maestro worker owns, and the tickets in its repo that want a person.\n\twatchSessionRepos(sessions);\n\n\t// Remote Control: Claude Code's own record of the process says whether it\n\t// is on, the pane only where that record is silent. The dialog `/rc` draws\n\t// is answered here too, since nobody at the dashboard can see it.\n\tfor (const s of sessions) {\n\t\tif (!s.tmux_target) continue;\n\t\tconst content = captures.get(s.tmux_target);\n\t\tif (!content) continue;\n\t\tconst rcUrl = currentRemoteControlUrl(s, content);\n\t\tif (rcUrl !== (s.rc_url ?? null)) {\n\t\t\tupdateSession(s.id, { rc_url: rcUrl });\n\t\t\ts.rc_url = rcUrl;\n\t\t}\n\t\tvoid answerRemoteControlDialog(s.tmux_target, content, rcUrl);\n\t}\n\n\tconst visits = getVisits();\n\tconst enrichedSessions = sessions.map((s) => {\n\t\tconst paneTitle = s.tmux_target ? (allPanes.get(s.tmux_target)?.title ?? null) : null;\n\t\tconst raw = s.tmux_target ? rawCaptures.get(s.tmux_target) : undefined;\n\t\t// Stripped once per tick by the caller; the option reader needs no colour.\n\t\tconst stripped = s.tmux_target ? captures.get(s.tmux_target) : undefined;\n\t\t// Live only: what sits in the prompt box right now, so the transcript\n\t\t// view can show it without the terminal mirror.\n\t\tconst box = raw ? readPromptBox(raw) : null;\n\t\tconst enriched: Session & LivePaneFields = {\n\t\t\t...s,\n\t\t\tpane_title: paneTitle,\n\t\t\tpane_alive: s.tmux_target ? allPanes.has(s.tmux_target) : true,\n\t\t\tdraft_input: box?.text ?? null,\n\t\t\tdraft_kind: box?.kind ?? null,\n\t\t\t// Messages the user typed into the pane while it was busy, waiting\n\t\t\t// their turn in Claude Code's own queue.\n\t\t\tpane_queue: raw ? readQueuedMessages(raw) : [],\n\t\t\t// The dialog's own numbered rows, so the composer can offer them as\n\t\t\t// answers instead of making you drive arrows at a screen you can't see.\n\t\t\t// When the hooks report a dialog, any run at the foot is one. When\n\t\t\t// they don't — a local command's dialog such as `/model` leaves the\n\t\t\t// session idle, and the permission notification can trail the dialog\n\t\t\t// by a tick — only a dialog that names its own keys is believed.\n\t\t\tpane_choice: stripped\n\t\t\t\t? readPromptOptions(stripped, {\n\t\t\t\t\t\tdeclaredOnly: s.state !== 'waiting' && s.state !== 'permission'\n\t\t\t\t\t})\n\t\t\t\t: null,\n\t\t\t// Claude Code says so only in its footer, which the transcript never sees.\n\t\t\tpane_update: stripped ? readUpdateNotice(stripped) : null,\n\t\t\tpane_login: stripped ? readLoginScreen(stripped) : null,\n\t\t\tpane_rewind: stripped ? readRewindScreen(stripped) : null,\n\t\t\tupgrade: upgradeStatus(s.id),\n\t\t\t// The spinner says more than the hooks do: what runs, for how long,\n\t\t\t// how many tokens. Busy only, so a frame left over never outlives the turn.\n\t\t\tpane_activity: liveActivity(s.id, stripped && s.state === 'busy' ? readActivity(stripped) : null),\n\t\t\t// One stat per session per tick; the file is only read when it grew.\n\t\t\tcontext_pct: peekContextPercent(s),\n\t\t\tissue: issueFor(s.git_root, s.maestro_issue),\n\t\t\t// Whole, so the composer can draw and edit it without polling the queue route.\n\t\t\tqueue: s.tmux_target\n\t\t\t\t? getQueue(s.tmux_target).map(({ attempts: _attempts, ...info }) => info)\n\t\t\t\t: [],\n\t\t\tdelivered: s.tmux_target ? getDeliveries(s.tmux_target) : [],\n\t\t\tlast_visited_at: visits[s.id] ?? null,\n\t\t\tchanges: liveChanges(s),\n\t\t};\n\n\t\tif (s.tmux_target && links[s.tmux_target]) {\n\t\t\tconst mainTarget = links[s.tmux_target];\n\t\t\tconst mainSession = sessions.find((m) => m.tmux_target === mainTarget);\n\t\t\tif (mainSession) {\n\t\t\t\tenriched.linked_to = mainSession.id;\n\t\t\t}\n\t\t}\n\n\t\treturn enriched;\n\t});\n\n\treturn { sessions: deduplicateByTmuxTarget(enrichedSessions), panes: plainPanes(allPanes, sessions) };\n}\n\n// ============================================================================\n// Terminal Helpers\n// ============================================================================\n\nexport function capturePaneOutput(target: string, depth: number = DEFAULT_HISTORY_DEPTH): string | null {\n\ttry {\n\t\tconst lines = Math.max(1, Math.min(MAX_HISTORY_DEPTH, Math.floor(depth)));\n\t\treturn execFileSync('tmux', ['capture-pane', '-t', target, '-p', '-e', '-S', `-${lines}`], {\n\t\t\tencoding: 'utf-8',\n\t\t\tstdio: ['pipe', 'pipe', 'pipe'],\n\t\t\ttimeout: 2000\n\t\t});\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function resizePane(target: string, cols: number, rows: number): void {\n\tresizeTmuxWindow(target, cols, rows);\n}\n\n// ============================================================================\n// System Stats (CPU/RAM/Swap from /proc, disk from statfs, cached every 10s)\n// ============================================================================\n\nlet cachedSystemStats: { cpu: number; ram: number; swap: number; ramTotal: number; swapTotal: number; disk: number | null } = {\n\tcpu: 0,\n\tram: 0,\n\tswap: 0,\n\tramTotal: 0,\n\tswapTotal: 0,\n\tdisk: null\n};\nlet prevCpuIdle = 0;\nlet prevCpuTotal = 0;\nlet systemStatsTimer: ReturnType<typeof setInterval> | null = null;\nlet systemStatsRefCount = 0;\n\nfunction refreshSystemStats(): void {\n\tvoid homeDiskPercent().then((disk) => {\n\t\tcachedSystemStats.disk = disk;\n\t});\n\ttry {\n\t\tconst stat = readFileSync('/proc/stat', 'utf-8');\n\t\tconst meminfo = readFileSync('/proc/meminfo', 'utf-8');\n\n\t\t// CPU\n\t\tconst cpuLine = stat.split('\\n')[0];\n\t\tconst cpuParts = cpuLine.split(/\\s+/).slice(1).map(Number);\n\t\tconst idle = cpuParts[3] + (cpuParts[4] || 0);\n\t\tconst total = cpuParts.reduce((a, b) => a + b, 0);\n\t\tconst diffIdle = idle - prevCpuIdle;\n\t\tconst diffTotal = total - prevCpuTotal;\n\t\tconst cpuPercent = diffTotal > 0 ? Math.round((1 - diffIdle / diffTotal) * 100) : 0;\n\t\tprevCpuIdle = idle;\n\t\tprevCpuTotal = total;\n\n\t\t// Memory\n\t\tconst mem: Record<string, number> = {};\n\t\tfor (const line of meminfo.split('\\n')) {\n\t\t\tconst match = line.match(/^(\\w+):\\s+(\\d+)/);\n\t\t\tif (match) mem[match[1]] = parseInt(match[2], 10);\n\t\t}\n\t\tconst memTotal = mem['MemTotal'] || 1;\n\t\tconst memAvailable = mem['MemAvailable'] || 0;\n\t\tconst swapTotal = mem['SwapTotal'] || 0;\n\t\tconst swapFree = mem['SwapFree'] || 0;\n\n\t\tcachedSystemStats = {\n\t\t\tcpu: cpuPercent,\n\t\t\tram: Math.round(((memTotal - memAvailable) / memTotal) * 100),\n\t\t\tswap: swapTotal > 0 ? Math.round(((swapTotal - swapFree) / swapTotal) * 100) : 0,\n\t\t\tramTotal: Math.round(memTotal / 1024),\n\t\t\tswapTotal: Math.round(swapTotal / 1024),\n\t\t\tdisk: cachedSystemStats.disk\n\t\t};\n\t} catch {\n\t\t// /proc not available (non-Linux), keep last values\n\t}\n}\n\nfunction startSystemStats(): void {\n\tsystemStatsRefCount++;\n\tif (systemStatsRefCount === 1) {\n\t\trefreshSystemStats(); // initial read\n\t\tsystemStatsTimer = setInterval(refreshSystemStats, 10_000);\n\t}\n}\n\nfunction stopSystemStats(): void {\n\tsystemStatsRefCount--;\n\tif (systemStatsRefCount <= 0) {\n\t\tsystemStatsRefCount = 0;\n\t\tif (systemStatsTimer) {\n\t\t\tclearInterval(systemStatsTimer);\n\t\t\tsystemStatsTimer = null;\n\t\t}\n\t}\n}\n\n// ============================================================================\n// Sessions WebSocket Manager\n// ============================================================================\n\n/**\n * Where the fleet message comes from: the fleet state, which the web server\n * starts and hands over (`web/src/lib/server/fleet.ts`).\n */\nexport interface FleetSource {\n\tselfId: string;\n\tsnapshot(): unknown[];\n\tsubscribe(listener: () => void): () => void;\n\t/** Every other machine's past sessions, by machine id; they change seldom and go in a message of their own. */\n\tpast(): Record<string, unknown[]>;\n\tsubscribePast(listener: () => void): () => void;\n}\n\nconst FLEET_COALESCE_MS = 200;\n/** How often the transcripts on disk are listed again for the past sessions. */\nconst PAST_REFRESH_MS = 30_000;\n\n/**\n * Where the fleet preferences come from (`src/server/fleet-prefs.ts`). Unlike\n * the fleet, they go to every client: a page reads them, and a peer that\n * follows this machine merges them into its own. Anyone but a peer is sent\n * them without the keys the servers keep among themselves (`forPages()`).\n */\nexport interface PrefsSource {\n\tall(): unknown;\n\tforPages(): unknown;\n\tsubscribe(listener: () => void): () => void;\n}\n\nexport class SessionsWsManager {\n\tprivate clients = new Set<WsClient>();\n\t/** The clients that asked for the fleet too (`?fleet=1`): pages, not peers. */\n\tprivate fleetClients = new Set<WsClient>();\n\t/** Other machines' fleet states (`isPeerLink()`): the only clients sent the whole preference set. */\n\tprivate peerClients = new Set<WsClient>();\n\tprivate fleetSource: FleetSource | null = null;\n\tprivate stopFleet: (() => void) | null = null;\n\tprivate prefsSource: PrefsSource | null = null;\n\tprivate stopPrefs: (() => void) | null = null;\n\tprivate unsubscribe: (() => void) | null = null;\n\tprivate interruptCheckTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate systemStatsTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate lastHash = '';\n\tprivate lastStatsHash = '';\n\tprivate config: Required<WsConfig>;\n\tprivate droppedClients = 0;\n\tprivate refreshing = false;\n\tprivate refreshQueued = false;\n\t/** This machine's transcripts, listed in the background (`src/transcript/past-sessions.ts`). */\n\tprivate pastIndex = new PastSessionIndex();\n\t/** Every transcript on disk, live ones included; null until first listed. */\n\tprivate pastAll: PastSession[] | null = null;\n\t/** The sessions the last broadcast listed, which are not past. */\n\tprivate liveIds: Set<string> | null = null;\n\tprivate lastPastHash = '';\n\t/** What the last past check saw, so a tick that changed neither skips the filter and the hash. */\n\tprivate lastPastInput: { all: PastSession[]; live: string } | null = null;\n\tprivate pastTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate stopFleetPast: (() => void) | null = null;\n\n\tconstructor(config?: WsConfig) {\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\t/** Get current stats */\n\tgetStats(): { clients: number; droppedClients: number } {\n\t\treturn {\n\t\t\tclients: this.clients.size,\n\t\t\tdroppedClients: this.droppedClients\n\t\t};\n\t}\n\n\t/**\n\t * Add a client. Returns false if max clients reached.\n\t */\n\taddClient(client: WsClient, options: { fleet?: boolean; peer?: boolean } = {}): boolean {\n\t\tif (this.clients.size >= this.config.maxSessionsClients) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\tcomponent: 'sessions',\n\t\t\t\t\tmessage: 'Max clients reached, rejecting connection',\n\t\t\t\t\tdata: { current: this.clients.size, max: this.config.maxSessionsClients }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.clients.add(client);\n\t\tif (options.fleet) this.fleetClients.add(client);\n\t\tif (options.peer) this.peerClients.add(client);\n\t\tlog(\n\t\t\t{\n\t\t\t\tlevel: 'debug',\n\t\t\t\tcomponent: 'sessions',\n\t\t\t\tmessage: 'Client connected',\n\t\t\t\tdata: { total: this.clients.size }\n\t\t\t},\n\t\t\tthis.config\n\t\t);\n\n\t\tif (this.clients.size === 1 && !this.unsubscribe) {\n\t\t\tconsole.log('[ws:sessions] First client, subscribing to watcher');\n\t\t\tthis.unsubscribe = sessionWatcher.subscribe(() => this.refreshAndBroadcast());\n\t\t\t// Start interrupt check timer - runs independently of file changes\n\t\t\t// to catch interruptions triggered by web UI (Escape key).\n\t\t\t// Uses async getEnrichedSessionsAsync to avoid blocking the event loop.\n\t\t\tthis.interruptCheckTimer = setInterval(() => {\n\t\t\t\tthis.refreshAndBroadcast();\n\t\t\t}, 500);\n\t\t\t// Cleanup stale sessions (dead PIDs) periodically\n\t\t\ttry { cleanupStaleSessions(); } catch { /* best-effort */ }\n\t\t\tthis.cleanupTimer = setInterval(() => {\n\t\t\t\ttry { cleanupStaleSessions(); } catch { /* best-effort */ }\n\t\t\t}, 10_000);\n\t\t\t// Start system stats: refresh every 10s, broadcast separately\n\t\t\tstartSystemStats();\n\t\t\tthis.systemStatsTimer = setInterval(() => {\n\t\t\t\tthis.broadcastSystemStats();\n\t\t\t}, 10_000);\n\t\t\tvoid this.refreshPast();\n\t\t\tthis.pastTimer = setInterval(() => void this.refreshPast(), PAST_REFRESH_MS);\n\t\t} else {\n\t\t\tconsole.log('[ws:sessions] addClient: clients=', this.clients.size, 'hasUnsubscribe=', !!this.unsubscribe);\n\t\t}\n\t\t// Send initial state asynchronously (batched tmux call is more reliable for pane_alive)\n\t\tthis.createSessionsMessageAsync('connected').then((msg) => {\n\t\t\tif (client.isOpen()) {\n\t\t\t\tthis.sendToClient(client, msg);\n\t\t\t\tthis.sendToClient(client, this.createSystemStatsMessage());\n\t\t\t\tif (options.fleet && this.fleetSource) {\n\t\t\t\t\tthis.sendToClient(client, this.createFleetMessage(this.fleetSource));\n\t\t\t\t\tthis.sendToClient(client, this.createFleetPastMessage(this.fleetSource));\n\t\t\t\t}\n\t\t\t\tif (this.prefsSource) this.sendToClient(client, this.createPrefsMessage(this.prefsSource, !!options.peer));\n\t\t\t\tif (this.pastAll && this.liveIds) this.sendToClient(client, this.createPastMessage(this.pastAll, this.liveIds));\n\t\t\t}\n\t\t});\n\t\treturn true;\n\t}\n\n\t/** Start sending the fleet to the pages that ask for it, from `source` from now on. */\n\tsetFleetSource(source: FleetSource): void {\n\t\tthis.stopFleet?.();\n\t\tthis.stopFleetPast?.();\n\t\tthis.fleetSource = source;\n\t\tthis.stopFleet = source.subscribe(() => this.scheduleFleet());\n\t\tthis.stopFleetPast = source.subscribePast(() => {\n\t\t\tif (this.fleetClients.size > 0) this.broadcastTo(this.fleetClients, this.createFleetPastMessage(source));\n\t\t});\n\t}\n\n\tprivate createFleetPastMessage(source: FleetSource) {\n\t\treturn { type: 'fleet-past' as const, machines: source.past(), timestamp: Date.now() };\n\t}\n\n\t/**\n\t * This machine's past sessions: the transcripts on disk that no session in\n\t * the broadcast holds. Sent to every client, so a peer keeps them in its\n\t * fleet state and a page lists them, on connect and whenever they change.\n\t */\n\tprivate createPastMessage(all: PastSession[], live: ReadonlySet<string>) {\n\t\treturn { type: 'past' as const, sessions: withoutLive(all, live), timestamp: Date.now() };\n\t}\n\n\tprivate pastListing = false;\n\n\tprivate async refreshPast(): Promise<void> {\n\t\t// A first listing of a large disk can outlast the interval; never run two.\n\t\tif (this.pastListing) return;\n\t\tthis.pastListing = true;\n\t\ttry {\n\t\t\tthis.pastAll = await this.pastIndex.list();\n\t\t} catch (err) {\n\t\t\tconsole.error('[ws:sessions] listing past sessions failed:', err);\n\t\t\treturn;\n\t\t} finally {\n\t\t\tthis.pastListing = false;\n\t\t}\n\t\tthis.broadcastPastIfChanged();\n\t}\n\n\tprivate broadcastPastIfChanged(): void {\n\t\t// Not before the sessions are known, or a live one would pass for past.\n\t\tif (!this.pastAll || !this.liveIds || this.clients.size === 0) return;\n\t\tconst live = [...this.liveIds].sort().join('\\n');\n\t\tif (this.lastPastInput?.all === this.pastAll && this.lastPastInput.live === live) return;\n\t\tthis.lastPastInput = { all: this.pastAll, live };\n\t\tconst message = this.createPastMessage(this.pastAll, this.liveIds);\n\t\tconst hash = JSON.stringify(message.sessions);\n\t\tif (hash === this.lastPastHash) return;\n\t\tthis.lastPastHash = hash;\n\t\tthis.broadcastTo(this.clients, message);\n\t}\n\n\t/** Send the fleet preferences to every client, from `source` from now on, and again on each change. */\n\tsetPrefsSource(source: PrefsSource): void {\n\t\tthis.stopPrefs?.();\n\t\tthis.prefsSource = source;\n\t\tthis.stopPrefs = source.subscribe(() => this.broadcastPrefs());\n\t}\n\n\t/** The set as a peer, or anyone else, is sent it. */\n\tprivate createPrefsMessage(source: PrefsSource, peer: boolean) {\n\t\treturn { type: 'prefs' as const, prefs: peer ? source.all() : source.forPages(), timestamp: Date.now() };\n\t}\n\n\tprivate broadcastPrefs(): void {\n\t\tif (!this.prefsSource || this.clients.size === 0) return;\n\t\tconst others = new Set([...this.clients].filter((c) => !this.peerClients.has(c)));\n\t\tthis.broadcastTo(this.peerClients, this.createPrefsMessage(this.prefsSource, true));\n\t\tthis.broadcastTo(others, this.createPrefsMessage(this.prefsSource, false));\n\t}\n\n\tprivate fleetTimer: ReturnType<typeof setTimeout> | null = null;\n\n\t/** Peers change together (each polls every 500ms), so their changes go out as one message. */\n\tprivate scheduleFleet(): void {\n\t\tif (this.fleetTimer) return;\n\t\tthis.fleetTimer = setTimeout(() => {\n\t\t\tthis.fleetTimer = null;\n\t\t\tthis.broadcastFleet();\n\t\t}, FLEET_COALESCE_MS);\n\t}\n\n\tprivate createFleetMessage(source: FleetSource) {\n\t\treturn { type: 'fleet' as const, self: source.selfId, machines: source.snapshot(), timestamp: Date.now() };\n\t}\n\n\tprivate broadcastFleet(): void {\n\t\tif (!this.fleetSource || this.fleetClients.size === 0) return;\n\t\tthis.broadcastTo(this.fleetClients, this.createFleetMessage(this.fleetSource));\n\t}\n\n\t/** Send `message` to each of `clients`, dropping any that cannot take it; returns those dropped. */\n\tprivate broadcastTo(clients: Set<WsClient>, message: SessionsWsMessage | Record<string, unknown>): WsClient[] {\n\t\tconst data = JSON.stringify(message);\n\t\tconst drops = [...clients].filter((client) => !this.sendToClient(client, message, data));\n\t\tfor (const client of drops) this.dropClient(client);\n\t\treturn drops;\n\t}\n\n\t/**\n\t * A client whose send failed: out of both sets, counted, and closed, so a\n\t * page reconnects and a peer's fleet state sees it go rather than keep a\n\t * socket that answers pings and is sent nothing.\n\t */\n\tprivate dropClient(client: WsClient): void {\n\t\tthis.clients.delete(client);\n\t\tthis.fleetClients.delete(client);\n\t\tthis.peerClients.delete(client);\n\t\tthis.droppedClients++;\n\t\ttry {\n\t\t\tclient.close();\n\t\t} catch {\n\t\t\t// Already gone.\n\t\t}\n\t}\n\n\tremoveClient(client: WsClient): void {\n\t\tthis.fleetClients.delete(client);\n\t\tthis.peerClients.delete(client);\n\t\tconst had = this.clients.delete(client);\n\t\tif (had) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'debug',\n\t\t\t\t\tcomponent: 'sessions',\n\t\t\t\t\tmessage: 'Client disconnected',\n\t\t\t\t\tdata: { total: this.clients.size }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t}\n\n\t\tif (this.clients.size === 0 && this.unsubscribe) {\n\t\t\tthis.unsubscribe();\n\t\t\tthis.unsubscribe = null;\n\t\t\tthis.lastHash = '';\n\t\t\t// Stop interrupt check timer\n\t\t\tif (this.interruptCheckTimer) {\n\t\t\t\tclearInterval(this.interruptCheckTimer);\n\t\t\t\tthis.interruptCheckTimer = null;\n\t\t\t}\n\t\t\t// Stop cleanup timer\n\t\t\tif (this.cleanupTimer) {\n\t\t\t\tclearInterval(this.cleanupTimer);\n\t\t\t\tthis.cleanupTimer = null;\n\t\t\t}\n\t\t\t// Stop system stats broadcast + polling\n\t\t\tif (this.systemStatsTimer) {\n\t\t\t\tclearInterval(this.systemStatsTimer);\n\t\t\t\tthis.systemStatsTimer = null;\n\t\t\t}\n\t\t\tstopSystemStats();\n\t\t\tif (this.pastTimer) {\n\t\t\t\tclearInterval(this.pastTimer);\n\t\t\t\tthis.pastTimer = null;\n\t\t\t}\n\t\t\tthis.lastPastHash = '';\n\t\t\tthis.lastPastInput = null;\n\t\t}\n\t}\n\n\tprivate async createSessionsMessageAsync(type: 'sessions' | 'connected') {\n\t\tconst { sessions, panes } = await readSessionsAndPanes();\n\t\t// A closed pane's row is not live: its conversation can be resumed from the past list.\n\t\tthis.liveIds = new Set(sessions.filter((s) => s.pane_alive !== false).map((s) => s.id));\n\t\t// The server remembers every directory a session has run in, so the\n\t\t// sidebar's groups outlive their last session and are the same from\n\t\t// every browser. Writes only when a directory is new.\n\t\tsaveProjects(sessions.map((s) => s.cwd).filter((cwd): cwd is string => !!cwd));\n\t\tconst projects = getSavedProjects();\n\t\treturn {\n\t\t\ttype,\n\t\t\tsessions,\n\t\t\tcount: sessions.length,\n\t\t\t// Its panes that run no Claude session, for the drawer to place under their projects.\n\t\t\tpanes,\n\t\t\tprojects,\n\t\t\t// What the fleet matches one project across machines by, and its icon.\n\t\t\tprojectReports: projectReports(projects),\n\t\t\t// Its Claude Code marketplaces and plugins, for the fleet to keep in step.\n\t\t\tplugins: pluginReader().current(),\n\t\t\tsettings: getSettings(),\n\t\t\tinbox: inboxTickets(\n\t\t\t\tnew Set(\n\t\t\t\t\tsessions\n\t\t\t\t\t\t.filter((s) => s.pane_alive && s.maestro_issue && s.git_root)\n\t\t\t\t\t\t.map((s) => `${s.git_root}#${s.maestro_issue}`)\n\t\t\t\t)\n\t\t\t),\n\t\t\ttimestamp: Date.now()\n\t\t};\n\t}\n\n\tprivate createSystemStatsMessage(): SystemStatsMessage {\n\t\treturn { type: 'systemStats' as const, ...cachedSystemStats, timestamp: Date.now() };\n\t}\n\n\t/**\n\t * Public trigger for an immediate fresh broadcast. Used by API mutation\n\t * endpoints (rename/kill/restart) so the UI updates instantly instead of\n\t * waiting up to 500ms for the file watcher poll to detect mtime change.\n\t */\n\tbroadcastNow(): void {\n\t\tthis.refreshAndBroadcast();\n\t}\n\n\tprivate refreshAndBroadcast(): void {\n\t\t// If already refreshing, queue a follow-up refresh so we don't lose\n\t\t// watcher notifications that arrive while async tmux calls are in-flight.\n\t\tif (this.refreshing) {\n\t\t\tthis.refreshQueued = true;\n\t\t\treturn;\n\t\t}\n\t\tthis.refreshing = true;\n\n\t\tthis.createSessionsMessageAsync('sessions')\n\t\t\t.then((message) => {\n\t\t\t\t// (queue draining runs in its own server loop — see message-queue.ts ensureDrainLoop)\n\n\t\t\t\t// The inbox changes on GitHub's clock, not the sessions', so it counts\n\t\t\t\t// too, and so do the project reports and plugins, read in the background\n\t\t\t\t// (a plugins report is never changed once read, so its time stands for it).\n\t\t\t\t// A pane's title is only a tooltip and some programs animate it, so it alone sends nothing.\n\t\t\t\tconst panes = message.panes.map(({ title: _title, ...pane }) => pane);\n\t\t\t\tconst hash = JSON.stringify([message.sessions, panes, message.inbox, message.projectReports, message.plugins?.readAt]);\n\t\t\t\tif (hash === this.lastHash) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// A session that ended is past now, and one resumed is not.\n\t\t\t\tthis.broadcastPastIfChanged();\n\t\t\t\tthis.lastHash = hash;\n\n\t\t\t\tconst dropped = this.broadcastTo(this.clients, message).length;\n\t\t\t\tif (dropped > 0) {\n\t\t\t\t\tlog(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\t\t\tcomponent: 'sessions',\n\t\t\t\t\t\t\tmessage: 'Dropped slow client',\n\t\t\t\t\t\t\tdata: { dropped, total: this.clients.size }\n\t\t\t\t\t\t},\n\t\t\t\t\t\tthis.config\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.catch((err) => {\n\t\t\t\tlog(\n\t\t\t\t\t{\n\t\t\t\t\t\tlevel: 'error',\n\t\t\t\t\t\tcomponent: 'sessions',\n\t\t\t\t\t\tmessage: 'Refresh failed',\n\t\t\t\t\t\tdata: { error: String(err) }\n\t\t\t\t\t},\n\t\t\t\t\tthis.config\n\t\t\t\t);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.refreshing = false;\n\t\t\t\tif (this.refreshQueued) {\n\t\t\t\t\tthis.refreshQueued = false;\n\t\t\t\t\tthis.refreshAndBroadcast();\n\t\t\t\t}\n\t\t\t});\n\t}\n\n\tprivate broadcastSystemStats(): void {\n\t\tconst message = this.createSystemStatsMessage();\n\t\tconst hash = JSON.stringify(message);\n\t\tif (hash === this.lastStatsHash) return;\n\t\tthis.lastStatsHash = hash;\n\t\tthis.broadcastTo(this.clients, message);\n\t}\n\n\tprivate sendToClient(\n\t\tclient: WsClient,\n\t\tmessage: SessionsWsMessage | Record<string, unknown>,\n\t\tdata?: string\n\t): boolean {\n\t\t// The session list is a small, always-superseded payload: a client that\n\t\t// cannot keep up is dropped rather than waited for.\n\t\treturn (\n\t\t\tsendJson(client, message, this.config, {\n\t\t\t\tcomponent: 'sessions',\n\t\t\t\tmaxBuffered: 64 * 1024,\n\t\t\t\tdata\n\t\t\t}) === 'sent'\n\t\t);\n\t}\n}\n\n/**\n * 'sent' — handed to the socket. 'backpressure' — skipped, because the client\n * is still draining an earlier message. 'closed' — the socket is gone.\n */\ntype SendResult = 'sent' | 'backpressure' | 'closed';\n\n/**\n * Send one JSON message to a client. Every channel manager goes through this,\n * so the backpressure ceiling has one definition per channel and one place to\n * read; what a channel *does* about a slow client is its own call — the\n * session list and the terminal drop it, the transcript catches it up.\n */\nfunction sendJson(\n\tclient: WsClient,\n\tmessage: unknown,\n\tconfig: Required<WsConfig>,\n\topts: { component: string; maxBuffered: number; data?: string }\n): SendResult {\n\ttry {\n\t\tif (!client.isOpen()) return 'closed';\n\t\tif (client.getBufferedAmount) {\n\t\t\tconst buffered = client.getBufferedAmount();\n\t\t\tif (buffered > opts.maxBuffered) {\n\t\t\t\tlog(\n\t\t\t\t\t{\n\t\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\t\tcomponent: opts.component,\n\t\t\t\t\t\tmessage: 'Client backpressure detected',\n\t\t\t\t\t\tdata: { buffered }\n\t\t\t\t\t},\n\t\t\t\t\tconfig\n\t\t\t\t);\n\t\t\t\treturn 'backpressure';\n\t\t\t}\n\t\t}\n\t\tclient.send(opts.data ?? JSON.stringify(message));\n\t\treturn 'sent';\n\t} catch (err) {\n\t\tlog(\n\t\t\t{\n\t\t\t\tlevel: 'error',\n\t\t\t\tcomponent: opts.component,\n\t\t\t\tmessage: 'Failed to send to client',\n\t\t\t\tdata: { error: String(err) }\n\t\t\t},\n\t\t\tconfig\n\t\t);\n\t\treturn 'closed';\n\t}\n}\n\n// ============================================================================\n// Terminal WebSocket Manager\n// ============================================================================\n\ninterface TargetState {\n\thistorySize: number;\n\talt: boolean;\n\tcols: number;\n\trows: number;\n\tscreenKey: string;\n}\n\nexport class TerminalWsManager {\n\tprivate clients = new Map<string, Set<WsClient>>();\n\tprivate pollTimers = new Map<string, ReturnType<typeof setInterval>>();\n\tprivate states = new Map<string, TargetState>();\n\tprivate config: Required<WsConfig>;\n\tprivate totalClients = 0;\n\tprivate droppedClients = 0;\n\n\tconstructor(config?: WsConfig) {\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\t/** Get current stats */\n\tgetStats(): { totalClients: number; targets: number; droppedClients: number } {\n\t\treturn {\n\t\t\ttotalClients: this.totalClients,\n\t\t\ttargets: this.clients.size,\n\t\t\tdroppedClients: this.droppedClients\n\t\t};\n\t}\n\n\t/**\n\t * Add a client for a target. Returns false if max clients reached.\n\t */\n\taddClient(client: WsClient, target: string): boolean {\n\t\t// Check total limit\n\t\tif (this.totalClients >= this.config.maxTerminalClientsTotal) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\tcomponent: 'terminal',\n\t\t\t\t\tmessage: 'Max total clients reached',\n\t\t\t\t\tdata: { current: this.totalClients, max: this.config.maxTerminalClientsTotal }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check per-target limit\n\t\tconst targetClients = this.clients.get(target);\n\t\tif (targetClients && targetClients.size >= this.config.maxTerminalClientsPerTarget) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\tcomponent: 'terminal',\n\t\t\t\t\tmessage: 'Max clients per target reached',\n\t\t\t\t\tdata: { target, current: targetClients.size, max: this.config.maxTerminalClientsPerTarget }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!this.clients.has(target)) {\n\t\t\tthis.clients.set(target, new Set());\n\t\t}\n\t\tthis.clients.get(target)!.add(client);\n\t\tthis.totalClients++;\n\n\t\tlog(\n\t\t\t{\n\t\t\t\tlevel: 'debug',\n\t\t\t\tcomponent: 'terminal',\n\t\t\t\tmessage: 'Client connected',\n\t\t\t\tdata: { target, targetClients: this.clients.get(target)!.size, total: this.totalClients }\n\t\t\t},\n\t\t\tthis.config\n\t\t);\n\n\t\tif (this.clients.get(target)!.size === 1) {\n\t\t\tthis.startPolling(target);\n\t\t}\n\n\t\t// Initial state: screen + the last DEFAULT_HISTORY_DEPTH history lines.\n\t\tconst snap = snapshotPane(target);\n\t\tconst now = Date.now();\n\t\tif (snap) {\n\t\t\tthis.states.set(target, {\n\t\t\t\thistorySize: snap.historySize,\n\t\t\t\talt: snap.alt,\n\t\t\t\tcols: snap.cols,\n\t\t\t\trows: snap.rows,\n\t\t\t\tscreenKey: snap.screen.join('\\n')\n\t\t\t});\n\t\t\tthis.sendToClient(client, {\n\t\t\t\ttype: 'screen',\n\t\t\t\tlines: snap.screen,\n\t\t\t\thistorySize: snap.historySize,\n\t\t\t\talt: snap.alt,\n\t\t\t\tcols: snap.cols,\n\t\t\t\trows: snap.rows,\n\t\t\t\ttimestamp: now\n\t\t\t});\n\t\t\tif (!snap.alt && snap.historySize > 0) {\n\t\t\t\tthis.sendHistoryTail(client, target, snap.historySize, DEFAULT_HISTORY_DEPTH);\n\t\t\t}\n\t\t} else {\n\t\t\t// Pane gone / not capturable: send an empty screen so the UI renders\n\t\t\tthis.sendToClient(client, {\n\t\t\t\ttype: 'screen',\n\t\t\t\tlines: [],\n\t\t\t\thistorySize: 0,\n\t\t\t\talt: false,\n\t\t\t\tcols: 0,\n\t\t\t\trows: 0,\n\t\t\t\ttimestamp: now\n\t\t\t});\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Serve a client's request for `count` history lines before absolute index `before`.\n\t * Replies only to the requesting client (each viewer scrolls independently).\n\t */\n\trequestHistory(client: WsClient, target: string, before: number, count: number): void {\n\t\tconst state = this.states.get(target);\n\t\tif (!state || state.alt) return;\n\t\tconst n = Math.max(1, Math.min(MAX_HISTORY_REQUEST, Math.floor(count)));\n\t\tconst end = Math.max(0, Math.min(Math.floor(before), state.historySize));\n\t\tif (end === 0) return;\n\t\tthis.sendHistoryTail(client, target, end, n);\n\t}\n\n\tprivate sendHistoryTail(client: WsClient, target: string, end: number, count: number): void {\n\t\tconst state = this.states.get(target);\n\t\tconst known = state?.historySize ?? end;\n\t\tconst start = Math.max(0, end - count);\n\t\tconst res = fetchHistoryRange(target, start, end, known);\n\t\tif (!res) return;\n\t\tthis.sendToClient(client, {\n\t\t\ttype: 'history',\n\t\t\tstart: res.start,\n\t\t\tlines: res.lines,\n\t\t\thistorySize: res.historySize,\n\t\t\ttimestamp: Date.now()\n\t\t});\n\t}\n\n\tremoveClient(client: WsClient, target?: string): void {\n\t\tif (target) {\n\t\t\tconst targetClients = this.clients.get(target);\n\t\t\tif (targetClients && targetClients.delete(client)) {\n\t\t\t\tthis.totalClients--;\n\t\t\t\tlog(\n\t\t\t\t\t{\n\t\t\t\t\t\tlevel: 'debug',\n\t\t\t\t\t\tcomponent: 'terminal',\n\t\t\t\t\t\tmessage: 'Client disconnected',\n\t\t\t\t\t\tdata: { target, targetClients: targetClients.size, total: this.totalClients }\n\t\t\t\t\t},\n\t\t\t\t\tthis.config\n\t\t\t\t);\n\n\t\t\t\tif (targetClients.size === 0) {\n\t\t\t\t\tthis.stopPolling(target);\n\t\t\t\t\tthis.clients.delete(target);\n\t\t\t\t\tthis.states.delete(target);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Search all targets for the client\n\t\tfor (const [t, clients] of this.clients) {\n\t\t\tif (clients.delete(client)) {\n\t\t\t\tthis.totalClients--;\n\t\t\t\tif (clients.size === 0) {\n\t\t\t\t\tthis.stopPolling(t);\n\t\t\t\t\tthis.clients.delete(t);\n\t\t\t\t\tthis.states.delete(t);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate startPolling(target: string): void {\n\t\tif (this.pollTimers.has(target)) return;\n\t\tconst timer = setInterval(() => this.pollAndBroadcast(target), 200);\n\t\tthis.pollTimers.set(target, timer);\n\t}\n\n\tprivate stopPolling(target: string): void {\n\t\tconst timer = this.pollTimers.get(target);\n\t\tif (timer) {\n\t\t\tclearInterval(timer);\n\t\t\tthis.pollTimers.delete(target);\n\t\t}\n\t}\n\n\tprivate pollAndBroadcast(target: string): void {\n\t\tconst snap = snapshotPane(target);\n\t\tif (!snap) return; // pane gone; keep last state, clients will see pane_alive=false via sessions stream\n\t\tconst prev = this.states.get(target);\n\t\tconst now = Date.now();\n\t\tconst messages: TerminalMessage[] = [];\n\t\tconst screenKey = snap.screen.join('\\n');\n\n\t\tif (!prev) {\n\t\t\tthis.states.set(target, { historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, screenKey });\n\t\t\tmessages.push({ type: 'reset', historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t\tmessages.push({ type: 'screen', lines: snap.screen, historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t} else {\n\t\t\tconst reflowed =\n\t\t\t\tsnap.alt !== prev.alt ||\n\t\t\t\tsnap.cols !== prev.cols ||\n\t\t\t\tsnap.rows !== prev.rows ||\n\t\t\t\tsnap.historySize < prev.historySize;\n\t\t\tif (reflowed) {\n\t\t\t\t// Resize reflows history in tmux; `clear` drops it; alt-screen swaps buffers.\n\t\t\t\tmessages.push({ type: 'reset', historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t\t} else if (!snap.alt && snap.historySize > prev.historySize) {\n\t\t\t\tconst n = snap.historySize - prev.historySize;\n\t\t\t\tlet lines: string[] | null = null;\n\t\t\t\tlet start = prev.historySize;\n\t\t\t\tif (n <= snap.tail.length) {\n\t\t\t\t\tlines = snap.tail.slice(snap.tail.length - n);\n\t\t\t\t} else {\n\t\t\t\t\t// Burst larger than the tail we snapshot each tick — fetch the rest explicitly.\n\t\t\t\t\tconst res = fetchHistoryRange(target, prev.historySize, snap.historySize, snap.historySize);\n\t\t\t\t\tif (res) {\n\t\t\t\t\t\tlines = res.lines;\n\t\t\t\t\t\tstart = res.start;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (lines) {\n\t\t\t\t\tmessages.push({ type: 'history', start, lines, historySize: snap.historySize, timestamp: now });\n\t\t\t\t} else {\n\t\t\t\t\tmessages.push({ type: 'reset', historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (screenKey !== prev.screenKey || reflowed) {\n\t\t\t\tmessages.push({ type: 'screen', lines: snap.screen, historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, timestamp: now });\n\t\t\t}\n\t\t\tthis.states.set(target, { historySize: snap.historySize, alt: snap.alt, cols: snap.cols, rows: snap.rows, screenKey });\n\t\t}\n\n\t\tif (messages.length === 0) return;\n\t\tconst clients = this.clients.get(target);\n\t\tif (!clients) return;\n\t\tfor (const message of messages) {\n\t\t\tconst data = JSON.stringify(message);\n\t\t\tconst drops: WsClient[] = [];\n\t\t\tfor (const client of [...clients]) {\n\t\t\t\tif (!this.sendToClient(client, message, data)) drops.push(client);\n\t\t\t}\n\t\t\tfor (const client of drops) {\n\t\t\t\tclients.delete(client);\n\t\t\t\tthis.totalClients--;\n\t\t\t\tthis.droppedClients++;\n\t\t\t\tlog(\n\t\t\t\t\t{\n\t\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\t\tcomponent: 'terminal',\n\t\t\t\t\t\tmessage: 'Dropped slow client',\n\t\t\t\t\t\tdata: { target, total: this.totalClients }\n\t\t\t\t\t},\n\t\t\t\t\tthis.config\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (clients.size === 0) {\n\t\t\t\tthis.stopPolling(target);\n\t\t\t\tthis.clients.delete(target);\n\t\t\t\tthis.states.delete(target);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate sendToClient(client: WsClient, message: TerminalMessage, data?: string): boolean {\n\t\t// Terminal frames are small and always superseded by the next screen,\n\t\t// so a client that cannot keep up is dropped rather than waited for.\n\t\treturn (\n\t\t\tsendJson(client, message, this.config, {\n\t\t\t\tcomponent: 'terminal',\n\t\t\t\tmaxBuffered: 64 * 1024,\n\t\t\t\tdata\n\t\t\t}) === 'sent'\n\t\t);\n\t}\n}\n\n// ============================================================================\n// Transcript WebSocket Manager\n// ============================================================================\n\nexport type TranscriptWsMessage =\n\t| {\n\t\t\ttype: 'snapshot';\n\t\t\t/** The tail of the transcript — see SNAPSHOT_ENTRIES. */\n\t\t\tentries: TranscriptEntry[];\n\t\t\t/** Position of `entries[0]` in the whole transcript. */\n\t\t\tfirstIndex: number;\n\t\t\tsubagents: SubagentPayload[];\n\t\t\tcontext: ContextUsage | null;\n\t\t\t/** The model on the latest assistant line, as the API names it. */\n\t\t\tmodel: string | null;\n\t\t\tavailable: boolean;\n\t\t\ttimestamp: number;\n\t\t}\n\t/** An older slice, sent to one client that asked for it. */\n\t| { type: 'history'; entries: TranscriptEntry[]; firstIndex: number; timestamp: number }\n\t/**\n\t * Added or updated entries. `indices` runs parallel to `entries`: a client\n\t * holding only the tail uses it to tell a new entry from an update to one\n\t * it never received.\n\t */\n\t| { type: 'entries'; entries: TranscriptEntry[]; indices: number[]; timestamp: number }\n\t| { type: 'subagents'; subagents: SubagentPayload[]; timestamp: number }\n\t/**\n\t * Context-window usage, which only this channel carries: it is read off\n\t * the transcript, and transcripts are only tailed while a client watches\n\t * the session. A list-wide gauge would need the tailer hoisted out of this\n\t * manager into a registry both channels read — not a second reader.\n\t */\n\t| { type: 'context'; context: ContextUsage; timestamp: number }\n\t/** The model changed — `/model` mid-session — as seen on the next reply. */\n\t| { type: 'model'; model: string; timestamp: number };\n\ninterface SubagentState {\n\ttailer: JsonlTailer;\n\tbuilder: TranscriptBuilder;\n\tmeta: SubagentMeta;\n\t/** Payload as of the last sync, reused by snapshots. */\n\tpayload: SubagentPayload | null;\n\t/** Serialized payload last sent, to skip unchanged broadcasts. */\n\tlastSent: string;\n\t/** Entry ids added or updated since the last poll, for readers following this agent. */\n\tchanged: Set<string>;\n\t/** The file was replaced since the last poll: followers need a fresh snapshot. */\n\treset: boolean;\n}\n\n/**\n * A reader that opened one subagent as a window rather than its parent's\n * transcript. It rides the parent's session state — the same tailer that\n * feeds the Task cards — and is sent that agent's entries instead.\n */\ninterface AgentFollow {\n\tagentId: string;\n\t/** The agent's file had been found when this reader was last sent a snapshot. */\n\tfound: boolean;\n}\n\ninterface TranscriptSessionState {\n\tsessionId: string;\n\t/** Null until the session's JSONL has been located (see resolveIn). */\n\ttailer: JsonlTailer | null;\n\tbuilder: TranscriptBuilder;\n\ttimer: ReturnType<typeof setInterval>;\n\tavailable: boolean;\n\t/** Ticks until the next attempt to locate a still-unfound transcript. */\n\tresolveIn: number;\n\t/** Ticks to wait after the next miss, doubling up to RESOLVE_TICKS_MAX. */\n\tresolveBackoff: number;\n\tsubagents: Map<string, SubagentState>;\n\t/** Ticks until the next subagents directory scan. */\n\tdiscoverIn: number;\n\t/** Agents a reader has opened: their payloads go out in full from then on. */\n\texpanded: Set<string>;\n\t/**\n\t * The first read of a large transcript, in progress. It runs in slices\n\t * that yield between them, so the server keeps answering; the poll and\n\t * the snapshot wait on it.\n\t */\n\tattaching: Promise<void> | null;\n\t/** Set when the last client left; the state is kept warm until it ages out. */\n\tidleSince: number | null;\n\t/** Serialized context usage last sent, to skip unchanged broadcasts. */\n\tsentContext: string;\n\t/** Model id last sent, likewise. */\n\tsentModel: string | null;\n\t/** Clients that missed a broadcast to backpressure; each is resent a snapshot. */\n\tstale: Set<WsClient>;\n}\n\nconst TRANSCRIPT_POLL_MS = 500;\n/** Scan for newly spawned subagents every N polls (they appear rarely). */\nconst SUBAGENT_DISCOVER_TICKS = 4;\n/**\n * Re-attempt to locate a missing transcript after N polls, doubling up to\n * RESOLVE_TICKS_MAX. A session that has not yet answered its first prompt has\n * no file, and every attempt stats each project directory — worth doing\n * promptly at first and rarely once it is clear nothing is being written.\n * The backoff is only the fallback: a hook writing the session JSON is the\n * signal that Claude Code is at work (see sessionsChanged).\n */\nconst RESOLVE_TICKS = 4;\nconst RESOLVE_TICKS_MAX = 60;\n/**\n * Entries in the first snapshot. A long-running session holds thousands, and\n * shipping all of them costs megabytes on the wire and seconds of layout on a\n * phone for history the reader has to scroll back through anyway. The client\n * asks for older slices when the reader asks for them.\n */\nconst SNAPSHOT_ENTRIES = 120;\n/**\n * How long a session's parsed transcript is kept after its last reader\n * leaves. Switching between sessions is the common case, and a 46 MB\n * transcript with forty subagents costs over a second to parse cold, with\n * the server frozen for it; warm, coming back is one incremental read.\n */\nconst SESSION_KEEP_MS = 10 * 60 * 1000;\nconst SWEEP_MS = 60 * 1000;\n/** Bytes read per slice of a cold attach, between yields to the event loop. */\nconst ATTACH_SLICE_BYTES = 4 * 1024 * 1024;\n/** Ceiling on one history request, so a client cannot ask for the world. */\nconst MAX_HISTORY_ENTRIES = 500;\n\n/** The tail a snapshot carries of a builder: its last entries, where they start, and its context and model. */\nfunction tailOf(builder: TranscriptBuilder) {\n\tconst all = builder.entries;\n\tconst firstIndex = Math.max(0, all.length - SNAPSHOT_ENTRIES);\n\treturn {\n\t\tentries: firstIndex > 0 ? all.slice(firstIndex) : all,\n\t\tfirstIndex,\n\t\tcontext: builder.context,\n\t\tmodel: builder.model\n\t};\n}\n\n/** An `entries` delta for the given changed ids, each sent once with its index. */\nfunction entriesDelta(\n\tbuilder: TranscriptBuilder,\n\tids: Iterable<string>,\n\ttimestamp: number\n): TranscriptWsMessage {\n\tconst entries: TranscriptEntry[] = [];\n\tconst indices: number[] = [];\n\tfor (const id of new Set(ids)) {\n\t\tconst index = builder.indexOf(id);\n\t\tif (index === undefined) continue;\n\t\tentries.push(builder.entries[index]);\n\t\tindices.push(index);\n\t}\n\treturn { type: 'entries', entries, indices, timestamp };\n}\n\nexport class TranscriptWsManager {\n\tprivate clients = new Map<string, Set<WsClient>>();\n\tprivate sessions = new Map<string, TranscriptSessionState>();\n\t/** Clients reading one subagent rather than the whole session. */\n\tprivate follows = new Map<WsClient, AgentFollow>();\n\tprivate config: Required<WsConfig>;\n\tprivate unwatch: (() => void) | null = null;\n\n\tconstructor(config?: WsConfig) {\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\t/**\n\t * Attach a reader to a session's transcript, or with `agentId` to one of\n\t * its subagents: the same messages, carrying that agent's entries.\n\t */\n\taddClient(client: WsClient, sessionId: string, agentId?: string): boolean {\n\t\t// Agent windows and the session's own readers are capped apart, so a\n\t\t// handful of open agents cannot lock the session's transcript out.\n\t\tconst current = [...(this.clients.get(sessionId) ?? [])].filter(\n\t\t\t(c) => this.follows.has(c) === Boolean(agentId)\n\t\t).length;\n\t\tif (current >= this.config.maxTerminalClientsPerTarget) {\n\t\t\tlog(\n\t\t\t\t{\n\t\t\t\t\tlevel: 'warn',\n\t\t\t\t\tcomponent: 'transcript',\n\t\t\t\t\tmessage: 'Max clients per session reached',\n\t\t\t\t\tdata: { sessionId, agentId, current }\n\t\t\t\t},\n\t\t\t\tthis.config\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!this.clients.has(sessionId)) this.clients.set(sessionId, new Set());\n\t\tthis.clients.get(sessionId)!.add(client);\n\t\tif (agentId) this.follows.set(client, { agentId, found: false });\n\n\t\tlet state = this.sessions.get(sessionId);\n\t\tif (!state) {\n\t\t\tstate = this.startSession(sessionId);\n\t\t\tthis.sessions.set(sessionId, state);\n\t\t} else if (state.idleSince !== null) {\n\t\t\tthis.wake(sessionId, state);\n\t\t}\n\t\tconst send = () => {\n\t\t\tif (!agentId) state.sentContext = JSON.stringify(state.builder.context);\n\t\t\tthis.sendToClient(client, this.snapshot(state, client));\n\t\t};\n\t\t// A cold attach is still reading: the snapshot goes when it has caught up.\n\t\tif (state.attaching) void state.attaching.then(send);\n\t\telse send();\n\t\treturn true;\n\t}\n\n\t/** A parked session has a reader again: catch up on what was written meanwhile. */\n\tprivate wake(sessionId: string, state: TranscriptSessionState): void {\n\t\tstate.idleSince = null;\n\t\tstate.timer = setInterval(() => this.poll(sessionId), TRANSCRIPT_POLL_MS);\n\t\tif (!state.tailer || state.attaching) return;\n\t\tconst result = state.tailer.read();\n\t\tif (result.status === 'reset') {\n\t\t\tthis.consume(state, result);\n\t\t\tthis.consume(state, state.tailer.read());\n\t\t} else {\n\t\t\tthis.consume(state, result);\n\t\t}\n\t\tthis.syncSubagents(state);\n\t\t// Whoever woke it is sent a snapshot, which already holds these.\n\t\tthis.forgetSubagentChanges(state);\n\t}\n\n\tremoveClient(client: WsClient, sessionId?: string): void {\n\t\tthis.follows.delete(client);\n\t\tconst drop = (id: string, clients: Set<WsClient>) => {\n\t\t\tif (!clients.delete(client)) return false;\n\t\t\tif (clients.size === 0) this.park(id);\n\t\t\treturn true;\n\t\t};\n\t\tif (sessionId) {\n\t\t\tconst clients = this.clients.get(sessionId);\n\t\t\tif (clients) drop(sessionId, clients);\n\t\t\treturn;\n\t\t}\n\t\tfor (const [id, clients] of this.clients) {\n\t\t\tif (drop(id, clients)) break;\n\t\t}\n\t}\n\n\t/** A main-transcript builder that stamps voice-injected prompts (see dictation-marks). */\n\tprivate mainBuilder(sessionId: string): TranscriptBuilder {\n\t\treturn new TranscriptBuilder(false, (text, ts) => isDictated(sessionId, text, ts));\n\t}\n\n\tprivate startSession(sessionId: string): TranscriptSessionState {\n\t\tconst state: TranscriptSessionState = {\n\t\t\tsessionId,\n\t\t\ttailer: null,\n\t\t\tbuilder: this.mainBuilder(sessionId),\n\t\t\tavailable: false,\n\t\t\tresolveIn: RESOLVE_TICKS,\n\t\t\tresolveBackoff: RESOLVE_TICKS,\n\t\t\tsubagents: new Map(),\n\t\t\tdiscoverIn: 0,\n\t\t\texpanded: new Set(),\n\t\t\tattaching: null,\n\t\t\tidleSince: null,\n\t\t\tsentContext: '',\n\t\t\tsentModel: null,\n\t\t\tstale: new Set(),\n\t\t\ttimer: setInterval(() => this.poll(sessionId), TRANSCRIPT_POLL_MS)\n\t\t};\n\t\tconst path = this.locate(sessionId);\n\t\tif (path) this.attach(state, path);\n\t\tthis.ensureSweeper();\n\t\treturn state;\n\t}\n\n\t/**\n\t * Hooks wrote these sessions' JSON. Claude Code creates the transcript the\n\t * moment a prompt is submitted, and the UserPromptSubmit hook lands in the\n\t * same second, so a session still without a transcript whose recorded\n\t * path now exists attaches on its next poll rather than when the backoff,\n\t * by then up to 30s long, runs out. Checking that one path costs a stat;\n\t * a busy session writes its JSON on every tool call, and a full locate\n\t * on each of those would scan every project directory.\n\t */\n\tsessionsChanged(changed: Iterable<string>): void {\n\t\tfor (const id of changed) {\n\t\t\tconst state = this.sessions.get(id);\n\t\t\tif (!state || state.tailer) continue;\n\t\t\tconst recorded = getSession(id)?.transcript_path;\n\t\t\tif (recorded && existsSync(recorded)) state.resolveIn = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Point the state at a transcript file and read what is already in it, so\n\t * the next snapshot carries history rather than starting from empty. A\n\t * long transcript is read in slices with a yield between each, and its\n\t * subagents one per yield, so a 46 MB session does not hold the server\n\t * for the second it takes; `state.attaching` is what to wait on.\n\t */\n\tprivate attach(state: TranscriptSessionState, path: string): Promise<void> {\n\t\tconst tailer = new JsonlTailer(path);\n\t\tstate.tailer = tailer;\n\t\tconst yieldNow = () => new Promise<void>((resolve) => setImmediate(resolve));\n\t\tstate.attaching = (async () => {\n\t\t\tfor (;;) {\n\t\t\t\tconst result = tailer.read(ATTACH_SLICE_BYTES);\n\t\t\t\tthis.consume(state, result);\n\t\t\t\tif (result.status !== 'lines' || !result.more) break;\n\t\t\t\tawait yieldNow();\n\t\t\t}\n\t\t\tthis.discoverSubagents(state);\n\t\t\tfor (const [agentId, sub] of state.subagents) {\n\t\t\t\tthis.tailSubagent(state, agentId, sub);\n\t\t\t\tawait yieldNow();\n\t\t\t}\n\t\t\tthis.forgetSubagentChanges(state);\n\t\t})().finally(() => {\n\t\t\tif (state.tailer === tailer) state.attaching = null;\n\t\t});\n\t\treturn state.attaching;\n\t}\n\n\t/**\n\t * Where this session's JSONL lives, or null if it is not on disk yet. A\n\t * past session has no session JSON, and is found by its id alone.\n\t */\n\tprivate locate(sessionId: string): string | null {\n\t\tconst session = getSession(sessionId);\n\t\treturn session ? resolveTranscriptPath(session, sessionId) : findTranscriptById(sessionId);\n\t}\n\n\t/**\n\t * Pick up newly spawned subagents and tail the known ones. Returns the\n\t * payloads that changed since the last call.\n\t */\n\tprivate syncSubagents(state: TranscriptSessionState): SubagentPayload[] {\n\t\tif (!state.tailer) return [];\n\n\t\tif (state.discoverIn <= 0) {\n\t\t\tstate.discoverIn = SUBAGENT_DISCOVER_TICKS;\n\t\t\tthis.discoverSubagents(state);\n\t\t} else {\n\t\t\tstate.discoverIn--;\n\t\t}\n\n\t\tconst changed: SubagentPayload[] = [];\n\t\tfor (const [agentId, sub] of state.subagents) {\n\t\t\tconst payload = this.tailSubagent(state, agentId, sub);\n\t\t\tif (payload) changed.push(payload);\n\t\t}\n\t\treturn changed;\n\t}\n\n\t/** Register subagent files that appeared beside the transcript. */\n\tprivate discoverSubagents(state: TranscriptSessionState): void {\n\t\tif (!state.tailer) return;\n\t\tfor (const file of listSubagents(state.tailer.path, new Set(state.subagents.keys()))) {\n\t\t\tstate.subagents.set(file.agentId, {\n\t\t\t\ttailer: new JsonlTailer(file.path),\n\t\t\t\tbuilder: new TranscriptBuilder(true),\n\t\t\t\tmeta: file.meta,\n\t\t\t\tpayload: null,\n\t\t\t\tlastSent: '',\n\t\t\t\tchanged: new Set(),\n\t\t\t\treset: false\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Read one subagent's new lines and rebuild its payload — lean unless a\n\t * reader has opened its card. Returns the payload when it changed.\n\t */\n\tprivate tailSubagent(\n\t\tstate: TranscriptSessionState,\n\t\tagentId: string,\n\t\tsub: SubagentState\n\t): SubagentPayload | null {\n\t\tconst result = sub.tailer.read();\n\t\tif (result.status === 'reset') {\n\t\t\tsub.builder = new TranscriptBuilder(true);\n\t\t\tsub.changed.clear();\n\t\t\tsub.reset = true;\n\t\t} else if (result.status === 'lines') {\n\t\t\tfor (const line of result.lines) {\n\t\t\t\tfor (const id of sub.builder.feed(line)) sub.changed.add(id);\n\t\t\t}\n\t\t}\n\t\tconst payload = subagentPayload(\n\t\t\tagentId,\n\t\t\tsub,\n\t\t\tstate.builder.finishedAgents.has(agentId),\n\t\t\tstate.expanded.has(agentId)\n\t\t);\n\t\tsub.payload = payload;\n\t\tconst encoded = JSON.stringify(payload);\n\t\tif (encoded === sub.lastSent) return null;\n\t\tsub.lastSent = encoded;\n\t\treturn payload;\n\t}\n\n\t/**\n\t * A reader opened an agent's card: send everything it ran and reported,\n\t * and keep sending it in full while the session lives.\n\t */\n\trequestSubagent(client: WsClient, sessionId: string, agentId: string): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tconst sub = state?.subagents.get(agentId);\n\t\tif (!state || !sub) return;\n\t\tstate.expanded.add(agentId);\n\t\tconst payload = subagentPayload(agentId, sub, state.builder.finishedAgents.has(agentId), true);\n\t\tsub.payload = payload;\n\t\tsub.lastSent = JSON.stringify(payload);\n\t\tthis.sendToClient(client, { type: 'subagents', subagents: [payload], timestamp: Date.now() });\n\t}\n\n\t/**\n\t * Send one client the slice of `count` entries that ends just before\n\t * `before` — the index of the oldest entry it already holds.\n\t */\n\trequestHistory(client: WsClient, sessionId: string, before: number, count: number): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tif (!state) return;\n\t\tconst follow = this.follows.get(client);\n\t\tconst builder = follow ? state.subagents.get(follow.agentId)?.builder : state.builder;\n\t\tif (!builder) return;\n\t\tconst end = Math.max(0, Math.min(Math.floor(before), builder.entries.length));\n\t\tif (end === 0) return;\n\t\tconst n = Math.max(1, Math.min(MAX_HISTORY_ENTRIES, Math.floor(count)));\n\t\tconst start = Math.max(0, end - n);\n\t\tthis.sendToClient(client, {\n\t\t\ttype: 'history',\n\t\t\tentries: builder.entries.slice(start, end),\n\t\t\tfirstIndex: start,\n\t\t\ttimestamp: Date.now()\n\t\t});\n\t}\n\n\t/**\n\t * The last reader left. The parsed transcript stays, its poll stops, and\n\t * the sweeper drops it once nobody has come back for SESSION_KEEP_MS.\n\t */\n\tprivate park(sessionId: string): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tthis.clients.delete(sessionId);\n\t\tif (!state) return;\n\t\tclearInterval(state.timer);\n\t\tstate.idleSince = Date.now();\n\t\tstate.stale.clear();\n\t}\n\n\tprivate stopSession(sessionId: string): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tif (state) clearInterval(state.timer);\n\t\tthis.sessions.delete(sessionId);\n\t\tthis.clients.delete(sessionId);\n\t}\n\n\tprivate sweeper: ReturnType<typeof setInterval> | null = null;\n\n\tprivate ensureSweeper(): void {\n\t\tif (this.sweeper) return;\n\t\tthis.unwatch = sessionWatcher.subscribe((changed) => this.sessionsChanged(changed));\n\t\tthis.sweeper = setInterval(() => {\n\t\t\tconst now = Date.now();\n\t\t\tfor (const [id, state] of this.sessions) {\n\t\t\t\tif (state.idleSince !== null && now - state.idleSince > SESSION_KEEP_MS) {\n\t\t\t\t\tthis.stopSession(id);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.sessions.size === 0 && this.sweeper) {\n\t\t\t\tclearInterval(this.sweeper);\n\t\t\t\tthis.sweeper = null;\n\t\t\t\tthis.unwatch?.();\n\t\t\t\tthis.unwatch = null;\n\t\t\t}\n\t\t}, SWEEP_MS);\n\t}\n\n\t/** Apply a tail result to the builder; returns changed entry ids. */\n\tprivate consume(\n\t\tstate: TranscriptSessionState,\n\t\tresult: ReturnType<JsonlTailer['read']>\n\t): string[] {\n\t\tswitch (result.status) {\n\t\t\tcase 'lines': {\n\t\t\t\tstate.available = true;\n\t\t\t\tconst changed: string[] = [];\n\t\t\t\tfor (const line of result.lines) changed.push(...state.builder.feed(line));\n\t\t\t\treturn changed;\n\t\t\t}\n\t\t\tcase 'reset':\n\t\t\t\t// File replaced: rebuild on the next polls from offset 0.\n\t\t\t\tstate.builder = this.mainBuilder(state.sessionId);\n\t\t\t\treturn [];\n\t\t\tdefault:\n\t\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate poll(sessionId: string): void {\n\t\tconst state = this.sessions.get(sessionId);\n\t\tconst clients = this.clients.get(sessionId);\n\t\tif (!state || !clients || clients.size === 0) return;\n\n\t\tif (state.attaching) return;\n\t\tif (!state.tailer) {\n\t\t\tif (--state.resolveIn > 0) return;\n\t\t\tconst path = this.locate(sessionId);\n\t\t\tif (!path) {\n\t\t\t\tstate.resolveBackoff = Math.min(state.resolveBackoff * 2, RESOLVE_TICKS_MAX);\n\t\t\t\tstate.resolveIn = state.resolveBackoff;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvoid this.attach(state, path).then(() => {\n\t\t\t\tif (this.sessions.get(sessionId) === state) this.resend(sessionId, state);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tthis.recoverStale(sessionId, state);\n\n\t\tconst result = state.tailer.read();\n\t\tif (result.status === 'reset') {\n\t\t\tthis.consume(state, result);\n\t\t\t// Immediately re-read the fresh file and resend full snapshots.\n\t\t\tthis.consume(state, state.tailer.read());\n\t\t\tthis.resend(sessionId, state);\n\t\t\treturn;\n\t\t}\n\n\t\tconst changedIds = this.consume(state, result);\n\t\tif (changedIds.length > 0) {\n\t\t\tthis.broadcast(sessionId, entriesDelta(state.builder, changedIds, Date.now()));\n\t\t}\n\n\t\tconst subagents = this.syncSubagents(state);\n\t\tif (subagents.length > 0) {\n\t\t\tthis.broadcast(sessionId, { type: 'subagents', subagents, timestamp: Date.now() });\n\t\t}\n\t\tthis.feedFollowers(sessionId, state, subagents);\n\n\t\tconst context = state.builder.context;\n\t\tconst encoded = JSON.stringify(context);\n\t\tif (context && encoded !== state.sentContext) {\n\t\t\tstate.sentContext = encoded;\n\t\t\tthis.broadcast(sessionId, { type: 'context', context, timestamp: Date.now() });\n\t\t}\n\t\tconst model = state.builder.model;\n\t\tif (model && model !== state.sentModel) {\n\t\t\tstate.sentModel = model;\n\t\t\tthis.broadcast(sessionId, { type: 'model', model, timestamp: Date.now() });\n\t\t}\n\t}\n\n\t/** Re-broadcast the whole transcript, e.g. after a file reset or a re-attach. */\n\tprivate resend(sessionId: string, state: TranscriptSessionState): void {\n\t\tstate.sentContext = JSON.stringify(state.builder.context);\n\t\tthis.broadcast(sessionId, this.snapshot(state));\n\t\tfor (const client of this.followersOf(sessionId)) {\n\t\t\tthis.sendToClient(client, this.snapshot(state, client));\n\t\t}\n\t}\n\n\t/** The clients of a session that follow one of its agents. */\n\tprivate followersOf(sessionId: string): WsClient[] {\n\t\tif (this.follows.size === 0) return [];\n\t\treturn [...(this.clients.get(sessionId) ?? [])].filter((c) => this.follows.has(c));\n\t}\n\n\t/**\n\t * Send each agent's followers what changed in it this poll: its new and\n\t * updated entries, its payload (which carries running and the tool\n\t * count), and its context. A follower whose agent had not been written\n\t * when it attached is sent a snapshot the poll it turns up.\n\t */\n\tprivate feedFollowers(\n\t\tsessionId: string,\n\t\tstate: TranscriptSessionState,\n\t\tpayloads: SubagentPayload[]\n\t): void {\n\t\tconst clients = this.clients.get(sessionId);\n\t\tfor (const client of this.followersOf(sessionId)) {\n\t\t\tconst follow = this.follows.get(client)!;\n\t\t\tconst sub = state.subagents.get(follow.agentId);\n\t\t\t// A client still catching up is sent a whole snapshot by recoverStale.\n\t\t\tif (!sub || state.stale.has(client)) continue;\n\t\t\tconst messages: TranscriptWsMessage[] = [];\n\t\t\tconst now = Date.now();\n\t\t\tif (!follow.found || sub.reset) {\n\t\t\t\tmessages.push(this.snapshot(state, client));\n\t\t\t} else {\n\t\t\t\tif (sub.changed.size > 0) {\n\t\t\t\t\tmessages.push(entriesDelta(sub.builder, sub.changed, now));\n\t\t\t\t\tconst context = sub.builder.context;\n\t\t\t\t\tif (context) messages.push({ type: 'context', context, timestamp: now });\n\t\t\t\t}\n\t\t\t\t// The broadcast payloads are lean unless the card is open; a window wants it whole.\n\t\t\t\tif (payloads.some((p) => p.agentId === follow.agentId)) {\n\t\t\t\t\tconst finished = state.builder.finishedAgents.has(follow.agentId);\n\t\t\t\t\tconst payload = subagentPayload(follow.agentId, sub, finished, true);\n\t\t\t\t\tmessages.push({ type: 'subagents', subagents: [payload], timestamp: now });\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const message of messages) {\n\t\t\t\tconst result = this.sendToClient(client, message);\n\t\t\t\tif (result === 'sent') continue;\n\t\t\t\tif (result === 'backpressure') {\n\t\t\t\t\tstate.stale.add(client);\n\t\t\t\t} else {\n\t\t\t\t\tclients?.delete(client);\n\t\t\t\t\tthis.follows.delete(client);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tthis.forgetSubagentChanges(state);\n\t\tif (clients?.size === 0) this.park(sessionId);\n\t}\n\n\t/** Every follower has been sent what the agents' builders hold so far. */\n\tprivate forgetSubagentChanges(state: TranscriptSessionState): void {\n\t\tfor (const sub of state.subagents.values()) {\n\t\t\tsub.changed.clear();\n\t\t\tsub.reset = false;\n\t\t}\n\t}\n\n\t/**\n\t * Catch up the clients that missed a delta while their socket was backed\n\t * up. A snapshot costs the whole transcript tail, so it goes only to the\n\t * client that missed something, and only once its buffer has drained.\n\t */\n\tprivate recoverStale(sessionId: string, state: TranscriptSessionState): void {\n\t\tif (state.stale.size === 0) return;\n\t\tconst clients = this.clients.get(sessionId);\n\t\tconst message = this.snapshot(state);\n\t\tconst data = JSON.stringify(message);\n\t\tfor (const client of [...state.stale]) {\n\t\t\tif (!clients?.has(client)) {\n\t\t\t\tstate.stale.delete(client);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst result = this.follows.has(client)\n\t\t\t\t? this.sendToClient(client, this.snapshot(state, client))\n\t\t\t\t: this.sendToClient(client, message, data);\n\t\t\tif (result === 'backpressure') continue; // still draining; try next tick\n\t\t\tstate.stale.delete(client);\n\t\t\tif (result === 'closed') clients.delete(client);\n\t\t}\n\t}\n\n\t/**\n\t * Everything a freshly attached client needs. Context rides along here so\n\t * the client starts with a gauge instead of waiting for the next response;\n\t * callers mark it sent with `sentContext`.\n\t */\n\tprivate snapshot(state: TranscriptSessionState, client?: WsClient): TranscriptWsMessage {\n\t\tconst follow = client && this.follows.get(client);\n\t\tif (follow) return this.agentSnapshot(state, follow);\n\t\treturn {\n\t\t\ttype: 'snapshot',\n\t\t\t...tailOf(state.builder),\n\t\t\tsubagents: [...state.subagents].map(\n\t\t\t\t([id, sub]) =>\n\t\t\t\t\tsub.payload ??\n\t\t\t\t\tsubagentPayload(id, sub, state.builder.finishedAgents.has(id), state.expanded.has(id))\n\t\t\t),\n\t\t\tavailable: state.available,\n\t\t\ttimestamp: Date.now()\n\t\t};\n\t}\n\n\t/**\n\t * One agent's transcript, shaped like a session's: its entries' tail, its\n\t * own payload in full as the only subagent, and its context. An agent\n\t * whose file is not on disk (yet, or at all) is `available: false`.\n\t */\n\tprivate agentSnapshot(state: TranscriptSessionState, follow: AgentFollow): TranscriptWsMessage {\n\t\tconst sub = state.subagents.get(follow.agentId);\n\t\tfollow.found = sub !== undefined;\n\t\tif (!sub) {\n\t\t\treturn {\n\t\t\t\ttype: 'snapshot',\n\t\t\t\tentries: [],\n\t\t\t\tfirstIndex: 0,\n\t\t\t\tsubagents: [],\n\t\t\t\tcontext: null,\n\t\t\t\tmodel: null,\n\t\t\t\tavailable: false,\n\t\t\t\ttimestamp: Date.now()\n\t\t\t};\n\t\t}\n\t\tconst payload = subagentPayload(\n\t\t\tfollow.agentId,\n\t\t\tsub,\n\t\t\tstate.builder.finishedAgents.has(follow.agentId),\n\t\t\ttrue\n\t\t);\n\t\treturn {\n\t\t\ttype: 'snapshot',\n\t\t\t...tailOf(sub.builder),\n\t\t\tsubagents: [payload],\n\t\t\tavailable: true,\n\t\t\ttimestamp: Date.now()\n\t\t};\n\t}\n\n\t/** Send to the session's readers of the whole transcript; agent followers are fed apart. */\n\tprivate broadcast(sessionId: string, message: TranscriptWsMessage): void {\n\t\tconst clients = this.clients.get(sessionId);\n\t\tif (!clients) return;\n\t\tconst state = this.sessions.get(sessionId);\n\t\tconst data = JSON.stringify(message);\n\t\tfor (const client of [...clients]) {\n\t\t\tif (this.follows.has(client)) continue;\n\t\t\tconst result = this.sendToClient(client, message, data);\n\t\t\t// A skipped delta is a hole in that client's list, and deltas are\n\t\t\t// never replayed: it is sent a fresh snapshot once its socket\n\t\t\t// drains. Only a closed socket costs a client its place.\n\t\t\tif (result === 'backpressure') state?.stale.add(client);\n\t\t\telse if (result === 'closed') clients.delete(client);\n\t\t}\n\t\tif (clients.size === 0) this.park(sessionId);\n\t}\n\n\tprivate sendToClient(client: WsClient, message: TranscriptWsMessage, data?: string): SendResult {\n\t\treturn sendJson(client, message, this.config, {\n\t\t\tcomponent: 'transcript',\n\t\t\t// A snapshot of a long session is megabytes; the ceiling is here to\n\t\t\t// catch a socket that has stopped draining, not to size a message.\n\t\t\tmaxBuffered: 8 * 1024 * 1024,\n\t\t\tdata\n\t\t});\n\t}\n}\n\n// ============================================================================\n// Message Handling\n// ============================================================================\n\nexport interface WsMessageHandlers {\n\tresize?: (cols: number, rows: number) => void;\n\thistoryRequest?: (before: number, count: number) => void;\n\t/** Transcript: a reader opened an agent's card and wants all of it. */\n\tsubagentRequest?: (agentId: string) => void;\n\t/** Browser view: everything but the ping is the channel's own, handed over as it came. */\n\tinput?: (text: string) => void;\n}\n\nexport function handleWsMessage(msgStr: string, handlers?: WsMessageHandlers): 'pong' | null {\n\tif (msgStr === 'ping') return 'pong';\n\tif (!handlers) return null;\n\tif (handlers.input) {\n\t\thandlers.input(msgStr);\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst msg = JSON.parse(msgStr) as ResizeMessage | HistoryRequestMessage | SubagentRequestMessage;\n\t\tswitch (msg.type) {\n\t\t\tcase 'subagent_request':\n\t\t\t\tif (handlers.subagentRequest && typeof msg.agentId === 'string') {\n\t\t\t\t\thandlers.subagentRequest(msg.agentId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'resize':\n\t\t\t\tif (handlers.resize && typeof msg.cols === 'number' && typeof msg.rows === 'number') {\n\t\t\t\t\thandlers.resize(msg.cols, msg.rows);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'history_request':\n\t\t\t\tif (handlers.historyRequest && typeof msg.before === 'number' && typeof msg.count === 'number') {\n\t\t\t\t\thandlers.historyRequest(msg.before, msg.count);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t} catch {\n\t\t// malformed JSON\n\t}\n\n\treturn null;\n}\n\n// ============================================================================\n// URL Parsing\n// ============================================================================\n\nexport type WsPathResult =\n\t/** `fleet`: the page asked for every other machine as well (`?fleet=1`). */\n\t| { type: 'sessions'; fleet?: boolean }\n\t| { type: 'terminal'; target: string }\n\t| { type: 'transcript'; target: string; agent?: string }\n\t/** The Web pane's view of a remote browser tab; `target` is the tab's id. */\n\t| { type: 'browser'; target: string }\n\t| null;\n\nexport function parseWsPath(pathname: string, search = ''): WsPathResult {\n\tif (pathname === '/api/sessions/stream') {\n\t\treturn new URLSearchParams(search).has('fleet') ? { type: 'sessions', fleet: true } : { type: 'sessions' };\n\t}\n\n\tconst browserMatch = pathname.match(/^\\/api\\/browser\\/([^/]+)\\/stream$/);\n\tif (browserMatch) {\n\t\treturn { type: 'browser', target: decodeURIComponent(browserMatch[1]) };\n\t}\n\n\tconst agentMatch = pathname.match(/^\\/api\\/sessions\\/([^/]+)\\/agents\\/([^/]+)\\/transcript\\/stream$/);\n\tif (agentMatch) {\n\t\treturn {\n\t\t\ttype: 'transcript',\n\t\t\ttarget: decodeURIComponent(agentMatch[1]),\n\t\t\tagent: decodeURIComponent(agentMatch[2])\n\t\t};\n\t}\n\n\tconst transcriptMatch = pathname.match(/^\\/api\\/sessions\\/([^/]+)\\/transcript\\/stream$/);\n\tif (transcriptMatch) {\n\t\treturn { type: 'transcript', target: decodeURIComponent(transcriptMatch[1]) };\n\t}\n\n\tconst termMatch = pathname.match(/^\\/api\\/sessions\\/([^/]+)\\/stream$/);\n\tif (termMatch) {\n\t\treturn { type: 'terminal', target: decodeURIComponent(termMatch[1]) };\n\t}\n\n\treturn null;\n}\n\n"],"names":["join","homedir","execFile","readFile","run","stat"],"mappings":"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"}