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":"message-queue.js","sources":["../../../../../src/server/dictation-marks.ts","../../../../../src/server/message-queue.ts"],"sourcesContent":["/**\n * Remember which injected prompts arrived by voice.\n *\n * The voice endpoint pastes transcribed text into the pane, after which it is\n * indistinguishable from typing — the JSONL user line it becomes carries no\n * origin. So the endpoint records what it injected here, and the transcript\n * manager asks on each new user/queued entry whether its text matches a\n * recent dictation. Matching is by normalized text within a time window: if\n * the user edits the paste before submitting, the text no longer matches and\n * the prompt correctly reads as typed.\n *\n * Marks are mirrored to ~/.claude-mux/dictation.json so a server restart\n * (which re-parses transcripts from scratch) re-marks old entries the same\n * way. Kept in globalThis to survive Vite's dual module loading.\n */\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { CLAUDE_MUX_DIR } from \"../utils/paths.js\";\nimport { writeFileAtomic } from \"../utils/atomic-write.js\";\n\ninterface DictationMark {\n /** Normalized injected text. */\n text: string;\n /** When it was injected (ms). */\n at: number;\n}\n\n/** How far apart the injection and the JSONL user line may sit. Generous:\n * a dictation can sit in the prompt box a while before the user submits it. */\nconst MATCH_WINDOW_MS = 15 * 60_000;\n\n/** Per session; old marks only matter until the entry they match is written,\n * but they must survive restarts, so cap by count rather than age. */\nconst MAX_MARKS_PER_SESSION = 100;\n\n/** Sessions kept in the file, most recently dictated-into first, so the\n * per-utterance rewrite cannot grow with every session ever spoken to. */\nconst MAX_SESSIONS = 50;\n\n// Read at call time, not module load, so tests can point it at a temp file.\nfunction marksPath(): string {\n return process.env.CLAUDE_MUX_DICTATION_PATH ?? join(CLAUDE_MUX_DIR, \"dictation.json\");\n}\n\ninterface MarksGlobalState {\n marks: Map<string, DictationMark[]>;\n loaded: boolean;\n}\n\nfunction state(): MarksGlobalState {\n const g = globalThis as typeof globalThis & { __claudeMuxDictationMarks?: MarksGlobalState };\n return (g.__claudeMuxDictationMarks ??= { marks: new Map(), loaded: false });\n}\n\nfunction normalize(text: string): string {\n return text.trim().replace(/\\s+/g, \" \");\n}\n\nfunction load(): Map<string, DictationMark[]> {\n const s = state();\n if (!s.loaded) {\n s.loaded = true;\n try {\n if (existsSync(marksPath())) {\n const raw = JSON.parse(readFileSync(marksPath(), \"utf8\")) as Record<string, DictationMark[]>;\n for (const [id, list] of Object.entries(raw)) {\n if (Array.isArray(list)) s.marks.set(id, list);\n }\n }\n } catch (err) {\n console.error(\"[dictation] failed to read marks file:\", err);\n }\n }\n return s.marks;\n}\n\nfunction save(marks: Map<string, DictationMark[]>): void {\n try {\n writeFileAtomic(marksPath(), JSON.stringify(Object.fromEntries(marks)));\n } catch (err) {\n console.error(\"[dictation] failed to write marks file:\", err);\n }\n}\n\n/** Record that `text` was just injected into `sessionId`'s pane by voice. */\nexport function recordDictation(sessionId: string, text: string): void {\n const normalized = normalize(text);\n if (!normalized) return;\n const marks = load();\n const list = marks.get(sessionId) ?? [];\n list.push({ text: normalized, at: Date.now() });\n marks.set(sessionId, list.slice(-MAX_MARKS_PER_SESSION));\n if (marks.size > MAX_SESSIONS) {\n // Marks are appended in time order, so a list's last entry is its newest.\n const stalest = [...marks.entries()]\n .sort((a, b) => (b[1].at(-1)?.at ?? 0) - (a[1].at(-1)?.at ?? 0))\n .slice(MAX_SESSIONS);\n for (const [id] of stalest) marks.delete(id);\n }\n save(marks);\n}\n\n/** Whether a transcript entry with this text and timestamp was dictated. */\nexport function isDictated(sessionId: string, text: string, ts: number): boolean {\n const list = load().get(sessionId);\n if (!list || list.length === 0) return false;\n const normalized = normalize(text);\n if (!normalized) return false;\n return list.some((m) => m.text === normalized && Math.abs(ts - m.at) <= MATCH_WINDOW_MS);\n}\n\n/** Test hook: drop all in-memory state so the next call re-reads the file. */\nexport function resetDictationMarksForTest(): void {\n const s = state();\n s.marks = new Map();\n s.loaded = false;\n}\n","/**\n * Message queue for sessions.\n * Messages are queued per tmux target and auto-sent when the session goes idle.\n *\n * The queue is kept in memory and mirrored to ~/.claude-mux/queue.json, so a\n * server restart does not silently swallow messages that were waiting for a\n * busy pane. Only one server owns the file at a time (see the ownership lock\n * below): a second instance, such as the dev server running alongside the\n * systemd one, keeps its own in-memory queue and leaves the file alone, so\n * neither restores — and then re-sends — what the other is already holding.\n */\n\nimport { execFileSync, execSync } from 'child_process';\nimport { randomUUID } from 'crypto';\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { getAllSessions } from '../db/index.js';\nimport { CLAUDE_MUX_DIR } from '../utils/paths.js';\nimport { writeFileAtomic } from '../utils/atomic-write.js';\nimport { isPidAlive } from '../utils/pid.js';\nimport { capturePaneContentAsync, readPaneHold, readPromptBox } from '../tmux/pane.js';\nimport { composePromptWithAttachments } from './attachments.js';\nimport { recordDictation } from './dictation-marks.js';\nimport type { DeliveryInfo, QueuedMessageInfo } from '../types/ws-messages.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Who put the message in the queue. `user` is something a human typed and is\n * waiting to see delivered; `control` is claude-mux talking to the agent on its\n * own behalf (today: mirroring a dashboard rename as `/rename`). The UI needs\n * the difference so it can stop reporting its own bookkeeping as your backlog.\n */\nexport type QueuedMessageKind = QueuedMessageInfo['kind'];\n\nexport interface QueuedMessage extends QueuedMessageInfo {\n\t/** Failed send attempts, so a message aimed at a dead pane gives up. */\n\tattempts?: number;\n}\n\n/** What the pane is given for a queued message: its attachments folded into the text. */\nfunction promptOf(message: QueuedMessageInfo): string {\n\treturn composePromptWithAttachments(message.text, message.attachments ?? []);\n}\n\ninterface SessionLike {\n\ttmux_target: string | null;\n\tstate: string;\n}\n\n// ============================================================================\n// Timing\n// ============================================================================\n\nconst IDLE_GRACE_MS = 1000;\nconst RESEND_FALLBACK_MS = 10_000;\nconst DRAIN_INTERVAL_MS = 500;\n/** How long a message may wait for its pane before it is assumed stale. */\nconst QUEUE_TTL_MS = 24 * 60 * 60 * 1000;\n/** How long a target may be absent from the session list before its queue dies. */\nconst ORPHAN_GRACE_MS = 60_000;\n/** Send attempts before an undeliverable message is dropped. */\nconst MAX_SEND_ATTEMPTS = 3;\n\n\n// ============================================================================\n// Shared tmux text injection helper\n// ============================================================================\n\n/**\n * Send text to a tmux pane using load-buffer → paste-buffer → send-keys Enter.\n * Extracted from the send API route to be reused by both the API and the queue drain.\n */\nexport function sendTextToPane(target: string, text: string, opts: { appendEnter?: boolean } = {}): void {\n\tconst { appendEnter = true } = opts;\n\texecSync(`tmux load-buffer -b claude-mux-input -`, {\n\t\tinput: text,\n\t\tstdio: ['pipe', 'ignore', 'ignore']\n\t});\n\texecFileSync('tmux', ['paste-buffer', '-b', 'claude-mux-input', '-t', target], {\n\t\tstdio: 'ignore'\n\t});\n\texecFileSync('tmux', ['delete-buffer', '-b', 'claude-mux-input'], { stdio: 'ignore' });\n\tif (appendEnter) {\n\t\texecFileSync('tmux', ['send-keys', '-t', target, 'Enter'], { stdio: 'ignore' });\n\t}\n}\n\n/**\n * What keeps a pane from taking a message right now, read off the pane itself;\n * null when nothing does, or when the pane cannot be read (the send that\n * follows then fails on its own account, and is counted there).\n */\nfunction paneHold(target: string): ReturnType<typeof readPaneHold> {\n\ttry {\n\t\tconst raw: unknown = execFileSync('tmux', ['capture-pane', '-p', '-e', '-t', target], {\n\t\t\tencoding: 'utf-8',\n\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t\ttimeout: 1000\n\t\t});\n\t\treturn typeof raw === 'string' ? readPaneHold(raw) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction pressEnter(target: string): void {\n\ttry {\n\t\texecFileSync('tmux', ['send-keys', '-t', target, 'Enter'], { stdio: 'ignore' });\n\t} catch {\n\t\t/* the pane is gone; the next send finds that out */\n\t}\n}\n\n/** How long Claude Code gets to take a pasted line before the box is read. */\nconst SUBMIT_SETTLE_MS = 600;\n\n/**\n * Whether Claude Code took the line just sent. A message it accepted has left\n * the box: either the turn started, or, while busy, it went to Claude Code's\n * own queue and the box shows the hint for that. Text still sitting there\n * typed means the Enter was lost, which happens when the paste and the key\n * land inside the same input burst; one more Enter is sent, and the box read\n * again. Resolves false when the text is still there after that.\n */\nexport async function confirmSubmitted(target: string): Promise<boolean> {\n\tfor (let attempt = 0; attempt < 2; attempt++) {\n\t\tawait new Promise((r) => setTimeout(r, SUBMIT_SETTLE_MS));\n\t\tconst raw = await capturePaneContentAsync(target, true);\n\t\tconst box = raw === null ? null : readPromptBox(raw);\n\t\tif (!box || box.kind !== 'typed' || !box.text.trim()) return true;\n\t\tif (attempt === 0) execFileSync('tmux', ['send-keys', '-t', target, 'Enter'], { stdio: 'ignore' });\n\t}\n\treturn false;\n}\n\n/** How a message handed to the pane fared (`submitToPane`). */\nexport type SubmitResult = 'taken' | 'in-box' | 'occupied';\n\n/** Two texts alike but for how the box wrapped and indented them. */\nfunction sameTyped(a: string, b: string): boolean {\n\treturn a.replace(/\\s+/g, '') === b.replace(/\\s+/g, '');\n}\n\n/**\n * Paste `text` into the pane and submit it, never on top of text already\n * typed there. The paste would join the two into one message, and the usual\n * way text comes to be there is an earlier send of this same message whose\n * Enter Claude Code lost: pasting again on a retry sent it twice. So a box\n * already holding the text is submitted as it stands, and one holding\n * anything else is left alone (`occupied`) for the person to send or clear.\n * `in-box` means the text was handed over but still sits typed after the\n * retry Enter (`confirmSubmitted`).\n */\nexport async function submitToPane(target: string, text: string): Promise<SubmitResult> {\n\tconst raw = await capturePaneContentAsync(target, true);\n\tconst box = raw === null ? null : readPromptBox(raw);\n\tif (box?.kind === 'typed' && box.text.trim()) {\n\t\tif (!sameTyped(box.text, text)) return 'occupied';\n\t\tpressEnter(target);\n\t} else {\n\t\tsendTextToPane(target, text);\n\t}\n\treturn (await confirmSubmitted(target)) ? 'taken' : 'in-box';\n}\n\n/**\n * Claude Code's \"send now\" chord. While a turn runs, it hands every message\n * waiting in Claude Code's own queue to the model at once, moving a running\n * foreground command to the background rather than killing it. While idle, it\n * submits whatever sits typed in the box.\n */\nexport function sendNowInPane(target: string): void {\n\texecFileSync('tmux', ['send-keys', '-t', target, 'C-x', 'C-s'], { stdio: 'ignore' });\n}\n\n/** How hard a steer pushes: `steer` leaves the reading to Claude Code, `now` makes it read at once. */\nexport type SteerMode = 'steer' | 'now';\n\n/**\n * Remember that what a pane was just handed had been spoken, so the transcript\n * badges the turn it becomes. Done on delivery, with the text as pasted: the\n * mark is matched against the prompt Claude Code logs, within a few minutes of\n * it, and a message can sit in the queue far longer than that.\n */\nexport function markDictated(target: string, text: string): void {\n\tconst session = getAllSessions().find((s) => s.tmux_target === target || s.id === target);\n\tif (session) recordDictation(session.id, text);\n}\n\n/**\n * Deliver text into the running turn. A busy pane takes the pasted line into\n * Claude Code's own queue, where Claude reads it when it next stops for input;\n * in `now` mode the chord then pushes that queue into the turn at once. Any\n * other pane just takes it as a normal send. Answers how the handover went,\n * as `submitToPane` does.\n */\nexport async function steerIntoPane(\n\ttarget: string,\n\ttext: string,\n\tbusy: boolean,\n\topts: { mode?: SteerMode; dictated?: boolean } = {}\n): Promise<SubmitResult> {\n\tconst result = await submitToPane(target, text);\n\tif (result !== 'taken') return result;\n\tif (busy && opts.mode === 'now') sendNowInPane(target);\n\trecordDelivery(target, text, 'steer');\n\tif (opts.dictated) markDictated(target, text);\n\treturn 'taken';\n}\n\n// ============================================================================\n// Queue storage — shared via globalThis to survive Vite's dual module loading\n// (Vite plugin uses native import, API routes use SSR module loader)\n// ============================================================================\n\ninterface QueueGlobalState {\n\tqueues: Map<string, QueuedMessage[]>;\n\tpendingDrain: Map<string, number>;\n\t/** target → timestamp of last auto-send; blocks re-send until session leaves idle */\n\trecentlySent: Map<string, number>;\n\t/** target → first tick at which no session claimed it, for orphan expiry */\n\tmissingSince: Map<string, number>;\n\t/** target → what the queue and steer handed the pane lately, newest last */\n\tdeliveries: Map<string, DeliveryInfo[]>;\n\tdrainTimer: ReturnType<typeof setInterval> | null;\n\t/** Whether this process owns ~/.claude-mux/queue.json and may write to it. */\n\tpersist: boolean;\n}\n\nconst GLOBAL_KEY = '__claude_mux_message_queue__';\n\n/** Overridable so tests never touch the real ~/.claude-mux/queue.json. */\nconst QUEUE_PATH = process.env.CLAUDE_MUX_QUEUE_PATH ?? join(CLAUDE_MUX_DIR, 'queue.json');\n\ninterface PersistedQueueFile {\n\tv: number;\n\towner_pid: number;\n\tqueues: Record<string, QueuedMessage[]>;\n}\n\n/**\n * Read the persisted queue and decide whether this process may own it.\n *\n * Ownership is the pid recorded in the file: if that process is still running\n * and is not us, another server is already draining those messages, so we start\n * empty and never write — two servers pasting the same queued text into one\n * pane is worse than losing the restore.\n */\nfunction loadPersisted(): { queues: Map<string, QueuedMessage[]>; persist: boolean } {\n\tconst empty = new Map<string, QueuedMessage[]>();\n\tif (!existsSync(QUEUE_PATH)) return { queues: empty, persist: true };\n\ttry {\n\t\tconst file = JSON.parse(readFileSync(QUEUE_PATH, 'utf-8')) as PersistedQueueFile;\n\t\tif (file.owner_pid && file.owner_pid !== process.pid && isPidAlive(file.owner_pid)) {\n\t\t\tconsole.warn(\n\t\t\t\t`[queue] ${QUEUE_PATH} is owned by live pid ${file.owner_pid}; running without persistence`\n\t\t\t);\n\t\t\treturn { queues: empty, persist: false };\n\t\t}\n\t\tconst queues = new Map<string, QueuedMessage[]>();\n\t\tfor (const [target, messages] of Object.entries(file.queues ?? {})) {\n\t\t\t// Expiry belongs to pruneQueues, which runs on the first drain tick.\n\t\t\tconst restored = (messages ?? [])\n\t\t\t\t.filter((m) => m && typeof m.text === 'string')\n\t\t\t\t.map((m) => ({\n\t\t\t\t\t...m,\n\t\t\t\t\t// Files written before items carried an id get one on the way in.\n\t\t\t\t\tid: typeof m.id === 'string' ? m.id : randomUUID(),\n\t\t\t\t\tkind: m.kind === 'control' ? 'control' : ('user' as QueuedMessageKind)\n\t\t\t\t}));\n\t\t\tif (restored.length > 0) queues.set(target, restored);\n\t\t}\n\t\tif (queues.size > 0) {\n\t\t\tconsole.log(`[queue] Restored ${queues.size} queue(s) from ${QUEUE_PATH}`);\n\t\t}\n\t\treturn { queues, persist: true };\n\t} catch (err) {\n\t\tconsole.error('[queue] Failed to read persisted queue:', err);\n\t\treturn { queues: empty, persist: true };\n\t}\n}\n\n/** Mirror the in-memory queues to disk. No-op when another server owns the file. */\nfunction persistQueues(): void {\n\tif (!globalState.persist) return;\n\ttry {\n\t\tconst queues: Record<string, QueuedMessage[]> = {};\n\t\tfor (const [target, queue] of globalState.queues) {\n\t\t\tif (queue.length > 0) queues[target] = queue;\n\t\t}\n\t\tconst file: PersistedQueueFile = { v: 1, owner_pid: process.pid, queues };\n\t\twriteFileAtomic(QUEUE_PATH, JSON.stringify(file, null, 2));\n\t} catch (err) {\n\t\tconsole.error('[queue] Failed to persist queue:', err);\n\t}\n}\n\nfunction getGlobalState(): QueueGlobalState {\n\tconst g = globalThis as Record<string, unknown>;\n\tif (!g[GLOBAL_KEY]) {\n\t\tconst restored = loadPersisted();\n\t\tg[GLOBAL_KEY] = {\n\t\t\tqueues: restored.queues,\n\t\t\tpendingDrain: new Map<string, number>(),\n\t\t\trecentlySent: new Map<string, number>(),\n\t\t\tmissingSince: new Map<string, number>(),\n\t\t\tdeliveries: new Map<string, DeliveryInfo[]>(),\n\t\t\tdrainTimer: null,\n\t\t\tpersist: restored.persist\n\t\t};\n\t}\n\tconst state = g[GLOBAL_KEY] as Partial<QueueGlobalState>;\n\t// Backfill fields added after an older module instance created the state (dev HMR)\n\tstate.recentlySent ??= new Map<string, number>();\n\tstate.missingSince ??= new Map<string, number>();\n\tstate.deliveries ??= new Map<string, DeliveryInfo[]>();\n\tstate.drainTimer ??= null;\n\tstate.persist ??= true;\n\treturn state as QueueGlobalState;\n}\n\nconst globalState = getGlobalState();\nconst { queues, pendingDrain, recentlySent, missingSince, deliveries } = globalState;\n\n// ============================================================================\n// Delivery log\n// ============================================================================\n\n/** How many deliveries a pane remembers, and for how long. */\nconst DELIVERY_LOG_SIZE = 20;\nconst DELIVERY_LOG_TTL_MS = 60 * 60 * 1000;\n\n/**\n * Remember that the queue or a steer handed this text to the pane, so the\n * transcript can label the user turn it becomes. In memory only: the label is\n * a courtesy, and a restart forgetting it costs nothing.\n */\nfunction recordDelivery(target: string, text: string, via: DeliveryInfo['via']): void {\n\tconst now = Date.now();\n\tconst log = (deliveries.get(target) ?? []).filter((d) => now - d.at < DELIVERY_LOG_TTL_MS);\n\tlog.push({ text, via, at: now });\n\tdeliveries.set(target, log.slice(-DELIVERY_LOG_SIZE));\n}\n\nexport function getDeliveries(target: string): DeliveryInfo[] {\n\treturn deliveries.get(target) ?? [];\n}\n\n// ============================================================================\n// Queue operations\n// ============================================================================\n\nfunction queueFor(target: string): QueuedMessage[] {\n\tlet queue = queues.get(target);\n\tif (!queue) queues.set(target, (queue = []));\n\treturn queue;\n}\n\nexport function enqueue(\n\ttarget: string,\n\ttext: string,\n\tkind: QueuedMessageKind = 'user',\n\tattachments: string[] = [],\n\tdictated = false\n): QueuedMessage[] {\n\tconst queue = queueFor(target);\n\tconst message: QueuedMessage = { id: randomUUID(), text, queuedAt: Date.now(), kind };\n\tif (attachments.length > 0) message.attachments = attachments;\n\tif (dictated) message.dictated = true;\n\tqueue.push(message);\n\tmissingSince.delete(target);\n\tpersistQueues();\n\tensureDrainLoop();\n\treturn queue;\n}\n\nexport function dequeue(target: string): QueuedMessage | undefined {\n\tconst queue = queues.get(target);\n\tif (!queue || queue.length === 0) return undefined;\n\tconst message = queue.shift();\n\tif (queue.length === 0) queues.delete(target);\n\tpersistQueues();\n\treturn message;\n}\n\nexport function getQueue(target: string): QueuedMessage[] {\n\treturn queues.get(target) ?? [];\n}\n\n/** Drop one item by id; a no-op once it has left the queue. */\nexport function removeQueueItem(target: string, id: string): QueuedMessage[] {\n\tconst index = queues.get(target)?.findIndex((m) => m.id === id) ?? -1;\n\treturn removeFromQueue(target, index);\n}\n\n/**\n * Move one item, named by id, to a slot. The id keeps a drain that sent the\n * head in the meantime from moving its neighbour instead.\n */\nexport function moveQueueItem(target: string, id: string, toIndex: number): QueuedMessage[] {\n\tconst queue = queues.get(target);\n\tconst fromIndex = queue?.findIndex((m) => m.id === id) ?? -1;\n\tif (!queue || fromIndex === -1) return queue ?? [];\n\treturn reorderQueue(target, fromIndex, Math.min(Math.max(toIndex, 0), queue.length - 1));\n}\n\nexport function removeFromQueue(target: string, index: number): QueuedMessage[] {\n\tconst queue = queues.get(target);\n\tif (!queue || index < 0 || index >= queue.length) return queue ?? [];\n\tqueue.splice(index, 1);\n\tif (queue.length === 0) queues.delete(target);\n\tpersistQueues();\n\treturn queue ?? [];\n}\n\n/**\n * Replace one item's text in place; its position and timestamp stay. The item\n * is named by id rather than position, because the drain may have sent the\n * head between the client's view of the queue and this call. False when it\n * has already left the queue.\n */\nexport function editQueueItem(target: string, id: string, text: string): boolean {\n\tconst item = queues.get(target)?.find((m) => m.id === id);\n\tif (!item) return false;\n\titem.text = text;\n\tpersistQueues();\n\treturn true;\n}\n\n/**\n * How promoting a queued item went: `sent` into the turn, `missing` because it\n * had already left the queue, `in-box` when Claude Code left the pasted\n * text sitting in its input box, or `occupied` when other text was typed there\n * and the item went back to its place in the queue.\n */\nexport type PromoteResult = 'sent' | 'missing' | 'in-box' | 'occupied';\n\n/**\n * Take one item out of the queue and steer it into the pane. Taken out first,\n * so the drain loop cannot send it while the steer is in flight. It goes back\n * only when the paste itself failed: text Claude Code left in its box is\n * already in the pane, and queueing it again would deliver it twice.\n */\nexport async function promoteToSteer(\n\ttarget: string,\n\tid: string,\n\tbusy: boolean,\n\tmode: SteerMode = 'steer'\n): Promise<PromoteResult> {\n\tconst queue = queues.get(target);\n\tconst index = queue?.findIndex((m) => m.id === id) ?? -1;\n\tif (!queue || index === -1) return 'missing';\n\tconst [message] = queue.splice(index, 1);\n\tif (queue.length === 0) queues.delete(target);\n\tpersistQueues();\n\tconst putBack = () => {\n\t\tconst restored = queueFor(target);\n\t\trestored.splice(Math.min(index, restored.length), 0, message);\n\t\tpersistQueues();\n\t\tensureDrainLoop();\n\t};\n\ttry {\n\t\tconst opts = { mode, dictated: message.dictated };\n\t\tconst result = await steerIntoPane(target, promptOf(message), busy, opts);\n\t\tif (result === 'occupied') putBack();\n\t\treturn result === 'taken' ? 'sent' : result;\n\t} catch (err) {\n\t\tputBack();\n\t\tthrow err;\n\t}\n}\n\nexport function reorderQueue(target: string, fromIndex: number, toIndex: number): QueuedMessage[] {\n\tconst queue = queues.get(target);\n\tif (!queue) return [];\n\tif (fromIndex < 0 || fromIndex >= queue.length) return queue;\n\tif (toIndex < 0 || toIndex >= queue.length) return queue;\n\tconst [item] = queue.splice(fromIndex, 1);\n\tqueue.splice(toIndex, 0, item);\n\tpersistQueues();\n\treturn queue;\n}\n\nexport function clearQueue(target: string): void {\n\tqueues.delete(target);\n\tpendingDrain.delete(target);\n\tmissingSince.delete(target);\n\tpersistQueues();\n}\n\n/** Whether anything at all is queued — the drain loop's own start/stop guard. */\nexport function hasQueuedMessages(): boolean {\n\treturn queues.size > 0;\n}\n\n// ============================================================================\n// Auto-drain logic\n// ============================================================================\n\n/**\n * Forget queues that can no longer be delivered: messages older than the TTL,\n * and queues whose tmux target no longer belongs to any session. Without this a\n * queue aimed at a closed pane keeps the drain loop spinning forever.\n */\nfunction pruneQueues(knownTargets: Set<string>, now: number): void {\n\tlet changed = false;\n\tconst drop = (target: string) => {\n\t\tqueues.delete(target);\n\t\tpendingDrain.delete(target);\n\t\tmissingSince.delete(target);\n\t\tdeliveries.delete(target);\n\t\tchanged = true;\n\t};\n\n\tfor (const [target, queue] of queues) {\n\t\t// Messages are appended in time order (and a retry goes back to the\n\t\t// front), so the head is the oldest: no scan unless it has expired.\n\t\tif (queue.length === 0 || now - queue[0].queuedAt >= QUEUE_TTL_MS) {\n\t\t\tconst fresh = queue.filter((m) => now - m.queuedAt < QUEUE_TTL_MS);\n\t\t\tif (fresh.length < queue.length) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[queue] Dropped ${queue.length - fresh.length} message(s) for ${target}: older than ${QUEUE_TTL_MS}ms`\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (fresh.length === 0) {\n\t\t\t\tdrop(target);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tqueues.set(target, fresh);\n\t\t\tchanged = true;\n\t\t}\n\n\t\tif (knownTargets.has(target)) {\n\t\t\tmissingSince.delete(target);\n\t\t\tcontinue;\n\t\t}\n\t\t// The session file is gone (pane closed, session ended). Give it a grace\n\t\t// period — a session list read can come back short mid-write.\n\t\tconst since = missingSince.get(target) ?? now;\n\t\tmissingSince.set(target, since);\n\t\tif (now - since >= ORPHAN_GRACE_MS) {\n\t\t\tconsole.warn(`[queue] Dropped ${queue.length} message(s) for ${target}: session gone`);\n\t\t\tdrop(target);\n\t\t}\n\t}\n\n\tif (changed) persistQueues();\n}\n\n/**\n * Drain queued messages for sessions sitting idle.\n * Called every ~500ms from the sessions WS manager refresh loop.\n *\n * Logic: while session is idle and has a queue, arm a 1s timer; if still idle\n * when it elapses, dequeue and send the front message. Leaving idle cancels\n * the timer. The 1s grace lets Claude settle after finishing a task and\n * covers the case where a message is enqueued while already idle.\n */\nexport function drainQueues(sessions: SessionLike[]): void {\n\tconst now = Date.now();\n\n\tconst knownTargets = new Set<string>();\n\tfor (const session of sessions) {\n\t\tif (session.tmux_target) knownTargets.add(session.tmux_target);\n\t}\n\tpruneQueues(knownTargets, now);\n\n\tfor (const session of sessions) {\n\t\tif (!session.tmux_target) continue;\n\t\tconst target = session.tmux_target;\n\n\t\tconst queue = queues.get(target);\n\t\tif (!queue || queue.length === 0) {\n\t\t\tpendingDrain.delete(target);\n\t\t\t// Nothing left to hold back, and a send remembered past this point\n\t\t\t// would make a draft typed later look like our own unsubmitted message.\n\t\t\trecentlySent.delete(target);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (session.state !== 'idle') {\n\t\t\t// Session picked up work (hook flipped state) — clear send guard\n\t\t\tpendingDrain.delete(target);\n\t\t\trecentlySent.delete(target);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// After an auto-send, wait for the session to leave idle (UserPromptSubmit\n\t\t// hook) before sending the next message, so two queued messages don't get\n\t\t// pasted into the same prompt. Falls back after 10s in case the hook never fires.\n\t\tconst sentAt = recentlySent.get(target);\n\t\tif (sentAt !== undefined && now - sentAt < RESEND_FALLBACK_MS) continue;\n\n\t\tconst pendingTime = pendingDrain.get(target);\n\t\tif (pendingTime === undefined) {\n\t\t\tpendingDrain.set(target, now);\n\t\t\tcontinue;\n\t\t}\n\t\tif (now - pendingTime < IDLE_GRACE_MS) continue;\n\n\t\t// The hooks say idle, but only the pane knows whether a message pasted\n\t\t// now starts a turn. Compaction fires no hook, so a compacting session\n\t\t// reads idle: Claude Code parked each message sent into it, no prompt hook\n\t\t// answered, the fallback above let the next one through ten seconds\n\t\t// later, and the whole queue then ran as one prompt.\n\t\tconst hold = paneHold(target);\n\t\tif (hold) {\n\t\t\t// Our own last message, still in the box: its Enter was folded into\n\t\t\t// the paste. One more submits it; anything else typed there is a\n\t\t\t// person's draft, and waits for them.\n\t\t\tif (hold === 'typed' && sentAt !== undefined) pressEnter(target);\n\t\t\tpendingDrain.set(target, now);\n\t\t\tif (sentAt !== undefined) recentlySent.set(target, now);\n\t\t\tcontinue;\n\t\t}\n\n\t\tpendingDrain.delete(target);\n\t\t// Peek rather than dequeue: a message only leaves the queue once tmux has\n\t\t// taken it, so a failed send costs one attempt instead of a remove and a\n\t\t// re-insert (and two writes of the persisted file).\n\t\tconst message = queue[0];\n\t\ttry {\n\t\t\tconst prompt = promptOf(message);\n\t\t\tsendTextToPane(target, prompt);\n\t\t\tdequeue(target);\n\t\t\trecordDelivery(target, prompt, 'queue');\n\t\t\tif (message.dictated) markDictated(target, prompt);\n\t\t\trecentlySent.set(target, now);\n\t\t\tconsole.log(`[queue] Auto-sent queued message to ${target}`);\n\t\t} catch (err) {\n\t\t\t// tmux refused the paste — usually the pane is gone. Retry a couple of\n\t\t\t// times, then drop it: an undeliverable message must not keep the drain\n\t\t\t// loop, and its error log, running forever.\n\t\t\tmessage.attempts = (message.attempts ?? 0) + 1;\n\t\t\tif (message.attempts >= MAX_SEND_ATTEMPTS) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[queue] Giving up on message for ${target} after ${message.attempts} attempts:`,\n\t\t\t\t\terr\n\t\t\t\t);\n\t\t\t\tdequeue(target);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconsole.error(\n\t\t\t\t`[queue] Failed to send queued message to ${target} (attempt ${message.attempts}):`,\n\t\t\t\terr\n\t\t\t);\n\t\t\tpersistQueues();\n\t\t}\n\t}\n}\n\n/**\n * Server-owned drain loop. Runs while any queue is non-empty, independent of\n * whether a dashboard client is connected (the WS refresh loop only runs with\n * clients, which previously left queues stuck once the phone closed the app).\n * State comes straight from the session JSON files written by hooks.\n */\nexport function ensureDrainLoop(): void {\n\tif (globalState.drainTimer) return;\n\tglobalState.drainTimer = setInterval(() => {\n\t\tif (!hasQueuedMessages()) {\n\t\t\tif (globalState.drainTimer) clearInterval(globalState.drainTimer);\n\t\t\tglobalState.drainTimer = null;\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tdrainQueues(getAllSessions());\n\t\t} catch (err) {\n\t\t\tconsole.error('[queue] drain loop error:', err);\n\t\t}\n\t}, DRAIN_INTERVAL_MS);\n\t// Don't keep the process alive just for this timer\n\t(globalState.drainTimer as { unref?: () => void }).unref?.();\n}\n\n// Resume draining if queues survived a module reload (dev HMR keeps globalThis)\n// or a server restart (the persisted file above).\nif (hasQueuedMessages()) ensureDrainLoop();\n"],"names":["queues","state"],"mappings":"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"}
1
+ {"version":3,"file":"message-queue.js","sources":["../../../../../src/server/dictation-marks.ts","../../../../../src/server/message-queue.ts"],"sourcesContent":["/**\n * Remember which injected prompts arrived by voice.\n *\n * The voice endpoint pastes transcribed text into the pane, after which it is\n * indistinguishable from typing — the JSONL user line it becomes carries no\n * origin. So the endpoint records what it injected here, and the transcript\n * manager asks on each new user/queued entry whether its text matches a\n * recent dictation. Matching is by normalized text within a time window: if\n * the user edits the paste before submitting, the text no longer matches and\n * the prompt correctly reads as typed.\n *\n * Marks are mirrored to ~/.claude-mux/dictation.json so a server restart\n * (which re-parses transcripts from scratch) re-marks old entries the same\n * way. Kept in globalThis to survive Vite's dual module loading.\n */\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { CLAUDE_MUX_DIR } from \"../utils/paths.js\";\nimport { writeFileAtomic } from \"../utils/atomic-write.js\";\n\ninterface DictationMark {\n /** Normalized injected text. */\n text: string;\n /** When it was injected (ms). */\n at: number;\n}\n\n/** How far apart the injection and the JSONL user line may sit. Generous:\n * a dictation can sit in the prompt box a while before the user submits it. */\nconst MATCH_WINDOW_MS = 15 * 60_000;\n\n/** Per session; old marks only matter until the entry they match is written,\n * but they must survive restarts, so cap by count rather than age. */\nconst MAX_MARKS_PER_SESSION = 100;\n\n/** Sessions kept in the file, most recently dictated-into first, so the\n * per-utterance rewrite cannot grow with every session ever spoken to. */\nconst MAX_SESSIONS = 50;\n\n// Read at call time, not module load, so tests can point it at a temp file.\nfunction marksPath(): string {\n return process.env.CLAUDE_MUX_DICTATION_PATH ?? join(CLAUDE_MUX_DIR, \"dictation.json\");\n}\n\ninterface MarksGlobalState {\n marks: Map<string, DictationMark[]>;\n loaded: boolean;\n}\n\nfunction state(): MarksGlobalState {\n const g = globalThis as typeof globalThis & { __claudeMuxDictationMarks?: MarksGlobalState };\n return (g.__claudeMuxDictationMarks ??= { marks: new Map(), loaded: false });\n}\n\nfunction normalize(text: string): string {\n return text.trim().replace(/\\s+/g, \" \");\n}\n\nfunction load(): Map<string, DictationMark[]> {\n const s = state();\n if (!s.loaded) {\n s.loaded = true;\n try {\n if (existsSync(marksPath())) {\n const raw = JSON.parse(readFileSync(marksPath(), \"utf8\")) as Record<string, DictationMark[]>;\n for (const [id, list] of Object.entries(raw)) {\n if (Array.isArray(list)) s.marks.set(id, list);\n }\n }\n } catch (err) {\n console.error(\"[dictation] failed to read marks file:\", err);\n }\n }\n return s.marks;\n}\n\nfunction save(marks: Map<string, DictationMark[]>): void {\n try {\n writeFileAtomic(marksPath(), JSON.stringify(Object.fromEntries(marks)));\n } catch (err) {\n console.error(\"[dictation] failed to write marks file:\", err);\n }\n}\n\n/** Record that `text` was just injected into `sessionId`'s pane by voice. */\nexport function recordDictation(sessionId: string, text: string): void {\n const normalized = normalize(text);\n if (!normalized) return;\n const marks = load();\n const list = marks.get(sessionId) ?? [];\n list.push({ text: normalized, at: Date.now() });\n marks.set(sessionId, list.slice(-MAX_MARKS_PER_SESSION));\n if (marks.size > MAX_SESSIONS) {\n // Marks are appended in time order, so a list's last entry is its newest.\n const stalest = [...marks.entries()]\n .sort((a, b) => (b[1].at(-1)?.at ?? 0) - (a[1].at(-1)?.at ?? 0))\n .slice(MAX_SESSIONS);\n for (const [id] of stalest) marks.delete(id);\n }\n save(marks);\n}\n\n/** Whether a transcript entry with this text and timestamp was dictated. */\nexport function isDictated(sessionId: string, text: string, ts: number): boolean {\n const list = load().get(sessionId);\n if (!list || list.length === 0) return false;\n const normalized = normalize(text);\n if (!normalized) return false;\n const near = list.filter((m) => Math.abs(ts - m.at) <= MATCH_WINDOW_MS);\n if (near.some((m) => m.text === normalized)) return true;\n return isMadeOf(normalized, near.map((m) => m.text));\n}\n\n/**\n * Whether `text` is nothing but dictations one after another. Claude Code\n * runs the messages it queued while busy as one prompt, joined by line\n * breaks, so two things said during a turn come back as a single entry.\n */\nfunction isMadeOf(text: string, parts: string[]): boolean {\n if (parts.length < 2) return false;\n // Longest first, so a dictation that starts with a shorter one is tried whole.\n const sorted = [...new Set(parts)].sort((a, b) => b.length - a.length);\n const reach = (rest: string): boolean => {\n if (rest === \"\") return true;\n return sorted.some(\n (part) =>\n rest.startsWith(part) &&\n (rest.length === part.length || rest[part.length] === \" \") &&\n reach(rest.slice(part.length).trimStart()),\n );\n };\n return reach(text);\n}\n\n/** Test hook: drop all in-memory state so the next call re-reads the file. */\nexport function resetDictationMarksForTest(): void {\n const s = state();\n s.marks = new Map();\n s.loaded = false;\n}\n","/**\n * Message queue for sessions.\n * Messages are queued per tmux target and auto-sent when the session goes idle.\n *\n * The queue is kept in memory and mirrored to ~/.claude-mux/queue.json, so a\n * server restart does not silently swallow messages that were waiting for a\n * busy pane. Only one server owns the file at a time (see the ownership lock\n * below): a second instance, such as the dev server running alongside the\n * systemd one, keeps its own in-memory queue and leaves the file alone, so\n * neither restores — and then re-sends — what the other is already holding.\n */\n\nimport { execFileSync, execSync } from 'child_process';\nimport { randomUUID } from 'crypto';\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { getAllSessions } from '../db/index.js';\nimport { CLAUDE_MUX_DIR } from '../utils/paths.js';\nimport { writeFileAtomic } from '../utils/atomic-write.js';\nimport { isPidAlive } from '../utils/pid.js';\nimport { capturePaneContentAsync, readPaneHold, readPromptBox } from '../tmux/pane.js';\nimport { composePromptWithAttachments } from './attachments.js';\nimport { recordDictation } from './dictation-marks.js';\nimport type { DeliveryInfo, QueuedMessageInfo } from '../types/ws-messages.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Who put the message in the queue. `user` is something a human typed and is\n * waiting to see delivered; `control` is claude-mux talking to the agent on its\n * own behalf (today: mirroring a dashboard rename as `/rename`). The UI needs\n * the difference so it can stop reporting its own bookkeeping as your backlog.\n */\nexport type QueuedMessageKind = QueuedMessageInfo['kind'];\n\nexport interface QueuedMessage extends QueuedMessageInfo {\n\t/** Failed send attempts, so a message aimed at a dead pane gives up. */\n\tattempts?: number;\n}\n\n/** What the pane is given for a queued message: its attachments folded into the text. */\nfunction promptOf(message: QueuedMessageInfo): string {\n\treturn composePromptWithAttachments(message.text, message.attachments ?? []);\n}\n\ninterface SessionLike {\n\ttmux_target: string | null;\n\tstate: string;\n}\n\n// ============================================================================\n// Timing\n// ============================================================================\n\nconst IDLE_GRACE_MS = 1000;\nconst RESEND_FALLBACK_MS = 10_000;\nconst DRAIN_INTERVAL_MS = 500;\n/** How long a message may wait for its pane before it is assumed stale. */\nconst QUEUE_TTL_MS = 24 * 60 * 60 * 1000;\n/** How long a target may be absent from the session list before its queue dies. */\nconst ORPHAN_GRACE_MS = 60_000;\n/** Send attempts before an undeliverable message is dropped. */\nconst MAX_SEND_ATTEMPTS = 3;\n\n\n// ============================================================================\n// Shared tmux text injection helper\n// ============================================================================\n\n/**\n * Send text to a tmux pane using load-buffer → paste-buffer → send-keys Enter.\n * Extracted from the send API route to be reused by both the API and the queue drain.\n */\nexport function sendTextToPane(target: string, text: string, opts: { appendEnter?: boolean } = {}): void {\n\tconst { appendEnter = true } = opts;\n\texecSync(`tmux load-buffer -b claude-mux-input -`, {\n\t\tinput: text,\n\t\tstdio: ['pipe', 'ignore', 'ignore']\n\t});\n\texecFileSync('tmux', ['paste-buffer', '-b', 'claude-mux-input', '-t', target], {\n\t\tstdio: 'ignore'\n\t});\n\texecFileSync('tmux', ['delete-buffer', '-b', 'claude-mux-input'], { stdio: 'ignore' });\n\tif (appendEnter) {\n\t\texecFileSync('tmux', ['send-keys', '-t', target, 'Enter'], { stdio: 'ignore' });\n\t}\n}\n\n/**\n * What keeps a pane from taking a message right now, read off the pane itself;\n * null when nothing does, or when the pane cannot be read (the send that\n * follows then fails on its own account, and is counted there).\n */\nfunction paneHold(target: string): ReturnType<typeof readPaneHold> {\n\ttry {\n\t\tconst raw: unknown = execFileSync('tmux', ['capture-pane', '-p', '-e', '-t', target], {\n\t\t\tencoding: 'utf-8',\n\t\t\tstdio: ['ignore', 'pipe', 'ignore'],\n\t\t\ttimeout: 1000\n\t\t});\n\t\treturn typeof raw === 'string' ? readPaneHold(raw) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction pressEnter(target: string): void {\n\ttry {\n\t\texecFileSync('tmux', ['send-keys', '-t', target, 'Enter'], { stdio: 'ignore' });\n\t} catch {\n\t\t/* the pane is gone; the next send finds that out */\n\t}\n}\n\n/** How long Claude Code gets to take a pasted line before the box is read. */\nconst SUBMIT_SETTLE_MS = 600;\n\n/**\n * Whether Claude Code took the line just sent. A message it accepted has left\n * the box: either the turn started, or, while busy, it went to Claude Code's\n * own queue and the box shows the hint for that. Text still sitting there\n * typed means the Enter was lost, which happens when the paste and the key\n * land inside the same input burst; one more Enter is sent, and the box read\n * again. Resolves false when the text is still there after that.\n */\nexport async function confirmSubmitted(target: string): Promise<boolean> {\n\tfor (let attempt = 0; attempt < 2; attempt++) {\n\t\tawait new Promise((r) => setTimeout(r, SUBMIT_SETTLE_MS));\n\t\tconst raw = await capturePaneContentAsync(target, true);\n\t\tconst box = raw === null ? null : readPromptBox(raw);\n\t\tif (!box || box.kind !== 'typed' || !box.text.trim()) return true;\n\t\tif (attempt === 0) execFileSync('tmux', ['send-keys', '-t', target, 'Enter'], { stdio: 'ignore' });\n\t}\n\treturn false;\n}\n\n/** How a message handed to the pane fared (`submitToPane`). */\nexport type SubmitResult = 'taken' | 'in-box' | 'occupied';\n\n/** Two texts alike but for how the box wrapped and indented them. */\nfunction sameTyped(a: string, b: string): boolean {\n\treturn a.replace(/\\s+/g, '') === b.replace(/\\s+/g, '');\n}\n\n/**\n * Paste `text` into the pane and submit it, never on top of text already\n * typed there. The paste would join the two into one message, and the usual\n * way text comes to be there is an earlier send of this same message whose\n * Enter Claude Code lost: pasting again on a retry sent it twice. So a box\n * already holding the text is submitted as it stands, and one holding\n * anything else is left alone (`occupied`) for the person to send or clear.\n * `in-box` means the text was handed over but still sits typed after the\n * retry Enter (`confirmSubmitted`).\n */\nexport async function submitToPane(target: string, text: string): Promise<SubmitResult> {\n\tconst raw = await capturePaneContentAsync(target, true);\n\tconst box = raw === null ? null : readPromptBox(raw);\n\tif (box?.kind === 'typed' && box.text.trim()) {\n\t\tif (!sameTyped(box.text, text)) return 'occupied';\n\t\tpressEnter(target);\n\t} else {\n\t\tsendTextToPane(target, text);\n\t}\n\treturn (await confirmSubmitted(target)) ? 'taken' : 'in-box';\n}\n\n/**\n * Claude Code's \"send now\" chord. While a turn runs, it hands every message\n * waiting in Claude Code's own queue to the model at once, moving a running\n * foreground command to the background rather than killing it. While idle, it\n * submits whatever sits typed in the box.\n */\nexport function sendNowInPane(target: string): void {\n\texecFileSync('tmux', ['send-keys', '-t', target, 'C-x', 'C-s'], { stdio: 'ignore' });\n}\n\n/** How hard a steer pushes: `steer` leaves the reading to Claude Code, `now` makes it read at once. */\nexport type SteerMode = 'steer' | 'now';\n\n/**\n * Remember that what a pane was just handed had been spoken, so the transcript\n * badges the turn it becomes. Done on delivery, with the text as pasted: the\n * mark is matched against the prompt Claude Code logs, within a few minutes of\n * it, and a message can sit in the queue far longer than that.\n */\nexport function markDictated(target: string, text: string): void {\n\tconst session = getAllSessions().find((s) => s.tmux_target === target || s.id === target);\n\tif (session) recordDictation(session.id, text);\n}\n\n/**\n * Deliver text into the running turn. A busy pane takes the pasted line into\n * Claude Code's own queue, where Claude reads it when it next stops for input;\n * in `now` mode the chord then pushes that queue into the turn at once. Any\n * other pane just takes it as a normal send. Answers how the handover went,\n * as `submitToPane` does.\n */\nexport async function steerIntoPane(\n\ttarget: string,\n\ttext: string,\n\tbusy: boolean,\n\topts: { mode?: SteerMode; dictated?: boolean } = {}\n): Promise<SubmitResult> {\n\tconst result = await submitToPane(target, text);\n\tif (result !== 'taken') return result;\n\tif (busy && opts.mode === 'now') sendNowInPane(target);\n\trecordDelivery(target, text, 'steer');\n\tif (opts.dictated) markDictated(target, text);\n\treturn 'taken';\n}\n\n// ============================================================================\n// Queue storage — shared via globalThis to survive Vite's dual module loading\n// (Vite plugin uses native import, API routes use SSR module loader)\n// ============================================================================\n\ninterface QueueGlobalState {\n\tqueues: Map<string, QueuedMessage[]>;\n\tpendingDrain: Map<string, number>;\n\t/** target → timestamp of last auto-send; blocks re-send until session leaves idle */\n\trecentlySent: Map<string, number>;\n\t/** target → first tick at which no session claimed it, for orphan expiry */\n\tmissingSince: Map<string, number>;\n\t/** target → what the queue and steer handed the pane lately, newest last */\n\tdeliveries: Map<string, DeliveryInfo[]>;\n\tdrainTimer: ReturnType<typeof setInterval> | null;\n\t/** Whether this process owns ~/.claude-mux/queue.json and may write to it. */\n\tpersist: boolean;\n}\n\nconst GLOBAL_KEY = '__claude_mux_message_queue__';\n\n/** Overridable so tests never touch the real ~/.claude-mux/queue.json. */\nconst QUEUE_PATH = process.env.CLAUDE_MUX_QUEUE_PATH ?? join(CLAUDE_MUX_DIR, 'queue.json');\n\ninterface PersistedQueueFile {\n\tv: number;\n\towner_pid: number;\n\tqueues: Record<string, QueuedMessage[]>;\n}\n\n/**\n * Read the persisted queue and decide whether this process may own it.\n *\n * Ownership is the pid recorded in the file: if that process is still running\n * and is not us, another server is already draining those messages, so we start\n * empty and never write — two servers pasting the same queued text into one\n * pane is worse than losing the restore.\n */\nfunction loadPersisted(): { queues: Map<string, QueuedMessage[]>; persist: boolean } {\n\tconst empty = new Map<string, QueuedMessage[]>();\n\tif (!existsSync(QUEUE_PATH)) return { queues: empty, persist: true };\n\ttry {\n\t\tconst file = JSON.parse(readFileSync(QUEUE_PATH, 'utf-8')) as PersistedQueueFile;\n\t\tif (file.owner_pid && file.owner_pid !== process.pid && isPidAlive(file.owner_pid)) {\n\t\t\tconsole.warn(\n\t\t\t\t`[queue] ${QUEUE_PATH} is owned by live pid ${file.owner_pid}; running without persistence`\n\t\t\t);\n\t\t\treturn { queues: empty, persist: false };\n\t\t}\n\t\tconst queues = new Map<string, QueuedMessage[]>();\n\t\tfor (const [target, messages] of Object.entries(file.queues ?? {})) {\n\t\t\t// Expiry belongs to pruneQueues, which runs on the first drain tick.\n\t\t\tconst restored = (messages ?? [])\n\t\t\t\t.filter((m) => m && typeof m.text === 'string')\n\t\t\t\t.map((m) => ({\n\t\t\t\t\t...m,\n\t\t\t\t\t// Files written before items carried an id get one on the way in.\n\t\t\t\t\tid: typeof m.id === 'string' ? m.id : randomUUID(),\n\t\t\t\t\tkind: m.kind === 'control' ? 'control' : ('user' as QueuedMessageKind)\n\t\t\t\t}));\n\t\t\tif (restored.length > 0) queues.set(target, restored);\n\t\t}\n\t\tif (queues.size > 0) {\n\t\t\tconsole.log(`[queue] Restored ${queues.size} queue(s) from ${QUEUE_PATH}`);\n\t\t}\n\t\treturn { queues, persist: true };\n\t} catch (err) {\n\t\tconsole.error('[queue] Failed to read persisted queue:', err);\n\t\treturn { queues: empty, persist: true };\n\t}\n}\n\n/** Mirror the in-memory queues to disk. No-op when another server owns the file. */\nfunction persistQueues(): void {\n\tif (!globalState.persist) return;\n\ttry {\n\t\tconst queues: Record<string, QueuedMessage[]> = {};\n\t\tfor (const [target, queue] of globalState.queues) {\n\t\t\tif (queue.length > 0) queues[target] = queue;\n\t\t}\n\t\tconst file: PersistedQueueFile = { v: 1, owner_pid: process.pid, queues };\n\t\twriteFileAtomic(QUEUE_PATH, JSON.stringify(file, null, 2));\n\t} catch (err) {\n\t\tconsole.error('[queue] Failed to persist queue:', err);\n\t}\n}\n\nfunction getGlobalState(): QueueGlobalState {\n\tconst g = globalThis as Record<string, unknown>;\n\tif (!g[GLOBAL_KEY]) {\n\t\tconst restored = loadPersisted();\n\t\tg[GLOBAL_KEY] = {\n\t\t\tqueues: restored.queues,\n\t\t\tpendingDrain: new Map<string, number>(),\n\t\t\trecentlySent: new Map<string, number>(),\n\t\t\tmissingSince: new Map<string, number>(),\n\t\t\tdeliveries: new Map<string, DeliveryInfo[]>(),\n\t\t\tdrainTimer: null,\n\t\t\tpersist: restored.persist\n\t\t};\n\t}\n\tconst state = g[GLOBAL_KEY] as Partial<QueueGlobalState>;\n\t// Backfill fields added after an older module instance created the state (dev HMR)\n\tstate.recentlySent ??= new Map<string, number>();\n\tstate.missingSince ??= new Map<string, number>();\n\tstate.deliveries ??= new Map<string, DeliveryInfo[]>();\n\tstate.drainTimer ??= null;\n\tstate.persist ??= true;\n\treturn state as QueueGlobalState;\n}\n\nconst globalState = getGlobalState();\nconst { queues, pendingDrain, recentlySent, missingSince, deliveries } = globalState;\n\n// ============================================================================\n// Delivery log\n// ============================================================================\n\n/** How many deliveries a pane remembers, and for how long. */\nconst DELIVERY_LOG_SIZE = 20;\nconst DELIVERY_LOG_TTL_MS = 60 * 60 * 1000;\n\n/**\n * Remember that the queue or a steer handed this text to the pane, so the\n * transcript can label the user turn it becomes. In memory only: the label is\n * a courtesy, and a restart forgetting it costs nothing.\n */\nfunction recordDelivery(target: string, text: string, via: DeliveryInfo['via']): void {\n\tconst now = Date.now();\n\tconst log = (deliveries.get(target) ?? []).filter((d) => now - d.at < DELIVERY_LOG_TTL_MS);\n\tlog.push({ text, via, at: now });\n\tdeliveries.set(target, log.slice(-DELIVERY_LOG_SIZE));\n}\n\nexport function getDeliveries(target: string): DeliveryInfo[] {\n\treturn deliveries.get(target) ?? [];\n}\n\n// ============================================================================\n// Queue operations\n// ============================================================================\n\nfunction queueFor(target: string): QueuedMessage[] {\n\tlet queue = queues.get(target);\n\tif (!queue) queues.set(target, (queue = []));\n\treturn queue;\n}\n\nexport function enqueue(\n\ttarget: string,\n\ttext: string,\n\tkind: QueuedMessageKind = 'user',\n\tattachments: string[] = [],\n\tdictated = false\n): QueuedMessage[] {\n\tconst queue = queueFor(target);\n\tconst message: QueuedMessage = { id: randomUUID(), text, queuedAt: Date.now(), kind };\n\tif (attachments.length > 0) message.attachments = attachments;\n\tif (dictated) message.dictated = true;\n\tqueue.push(message);\n\tmissingSince.delete(target);\n\tpersistQueues();\n\tensureDrainLoop();\n\treturn queue;\n}\n\nexport function dequeue(target: string): QueuedMessage | undefined {\n\tconst queue = queues.get(target);\n\tif (!queue || queue.length === 0) return undefined;\n\tconst message = queue.shift();\n\tif (queue.length === 0) queues.delete(target);\n\tpersistQueues();\n\treturn message;\n}\n\nexport function getQueue(target: string): QueuedMessage[] {\n\treturn queues.get(target) ?? [];\n}\n\n/** Drop one item by id; a no-op once it has left the queue. */\nexport function removeQueueItem(target: string, id: string): QueuedMessage[] {\n\tconst index = queues.get(target)?.findIndex((m) => m.id === id) ?? -1;\n\treturn removeFromQueue(target, index);\n}\n\n/**\n * Move one item, named by id, to a slot. The id keeps a drain that sent the\n * head in the meantime from moving its neighbour instead.\n */\nexport function moveQueueItem(target: string, id: string, toIndex: number): QueuedMessage[] {\n\tconst queue = queues.get(target);\n\tconst fromIndex = queue?.findIndex((m) => m.id === id) ?? -1;\n\tif (!queue || fromIndex === -1) return queue ?? [];\n\treturn reorderQueue(target, fromIndex, Math.min(Math.max(toIndex, 0), queue.length - 1));\n}\n\nexport function removeFromQueue(target: string, index: number): QueuedMessage[] {\n\tconst queue = queues.get(target);\n\tif (!queue || index < 0 || index >= queue.length) return queue ?? [];\n\tqueue.splice(index, 1);\n\tif (queue.length === 0) queues.delete(target);\n\tpersistQueues();\n\treturn queue ?? [];\n}\n\n/**\n * Replace one item's text in place; its position and timestamp stay. The item\n * is named by id rather than position, because the drain may have sent the\n * head between the client's view of the queue and this call. False when it\n * has already left the queue.\n */\nexport function editQueueItem(target: string, id: string, text: string): boolean {\n\tconst item = queues.get(target)?.find((m) => m.id === id);\n\tif (!item) return false;\n\titem.text = text;\n\tpersistQueues();\n\treturn true;\n}\n\n/**\n * How promoting a queued item went: `sent` into the turn, `missing` because it\n * had already left the queue, `in-box` when Claude Code left the pasted\n * text sitting in its input box, or `occupied` when other text was typed there\n * and the item went back to its place in the queue.\n */\nexport type PromoteResult = 'sent' | 'missing' | 'in-box' | 'occupied';\n\n/**\n * Take one item out of the queue and steer it into the pane. Taken out first,\n * so the drain loop cannot send it while the steer is in flight. It goes back\n * only when the paste itself failed: text Claude Code left in its box is\n * already in the pane, and queueing it again would deliver it twice.\n */\nexport async function promoteToSteer(\n\ttarget: string,\n\tid: string,\n\tbusy: boolean,\n\tmode: SteerMode = 'steer'\n): Promise<PromoteResult> {\n\tconst queue = queues.get(target);\n\tconst index = queue?.findIndex((m) => m.id === id) ?? -1;\n\tif (!queue || index === -1) return 'missing';\n\tconst [message] = queue.splice(index, 1);\n\tif (queue.length === 0) queues.delete(target);\n\tpersistQueues();\n\tconst putBack = () => {\n\t\tconst restored = queueFor(target);\n\t\trestored.splice(Math.min(index, restored.length), 0, message);\n\t\tpersistQueues();\n\t\tensureDrainLoop();\n\t};\n\ttry {\n\t\tconst opts = { mode, dictated: message.dictated };\n\t\tconst result = await steerIntoPane(target, promptOf(message), busy, opts);\n\t\tif (result === 'occupied') putBack();\n\t\treturn result === 'taken' ? 'sent' : result;\n\t} catch (err) {\n\t\tputBack();\n\t\tthrow err;\n\t}\n}\n\nexport function reorderQueue(target: string, fromIndex: number, toIndex: number): QueuedMessage[] {\n\tconst queue = queues.get(target);\n\tif (!queue) return [];\n\tif (fromIndex < 0 || fromIndex >= queue.length) return queue;\n\tif (toIndex < 0 || toIndex >= queue.length) return queue;\n\tconst [item] = queue.splice(fromIndex, 1);\n\tqueue.splice(toIndex, 0, item);\n\tpersistQueues();\n\treturn queue;\n}\n\nexport function clearQueue(target: string): void {\n\tqueues.delete(target);\n\tpendingDrain.delete(target);\n\tmissingSince.delete(target);\n\tpersistQueues();\n}\n\n/** Whether anything at all is queued — the drain loop's own start/stop guard. */\nexport function hasQueuedMessages(): boolean {\n\treturn queues.size > 0;\n}\n\n// ============================================================================\n// Auto-drain logic\n// ============================================================================\n\n/**\n * Forget queues that can no longer be delivered: messages older than the TTL,\n * and queues whose tmux target no longer belongs to any session. Without this a\n * queue aimed at a closed pane keeps the drain loop spinning forever.\n */\nfunction pruneQueues(knownTargets: Set<string>, now: number): void {\n\tlet changed = false;\n\tconst drop = (target: string) => {\n\t\tqueues.delete(target);\n\t\tpendingDrain.delete(target);\n\t\tmissingSince.delete(target);\n\t\tdeliveries.delete(target);\n\t\tchanged = true;\n\t};\n\n\tfor (const [target, queue] of queues) {\n\t\t// Messages are appended in time order (and a retry goes back to the\n\t\t// front), so the head is the oldest: no scan unless it has expired.\n\t\tif (queue.length === 0 || now - queue[0].queuedAt >= QUEUE_TTL_MS) {\n\t\t\tconst fresh = queue.filter((m) => now - m.queuedAt < QUEUE_TTL_MS);\n\t\t\tif (fresh.length < queue.length) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[queue] Dropped ${queue.length - fresh.length} message(s) for ${target}: older than ${QUEUE_TTL_MS}ms`\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (fresh.length === 0) {\n\t\t\t\tdrop(target);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tqueues.set(target, fresh);\n\t\t\tchanged = true;\n\t\t}\n\n\t\tif (knownTargets.has(target)) {\n\t\t\tmissingSince.delete(target);\n\t\t\tcontinue;\n\t\t}\n\t\t// The session file is gone (pane closed, session ended). Give it a grace\n\t\t// period — a session list read can come back short mid-write.\n\t\tconst since = missingSince.get(target) ?? now;\n\t\tmissingSince.set(target, since);\n\t\tif (now - since >= ORPHAN_GRACE_MS) {\n\t\t\tconsole.warn(`[queue] Dropped ${queue.length} message(s) for ${target}: session gone`);\n\t\t\tdrop(target);\n\t\t}\n\t}\n\n\tif (changed) persistQueues();\n}\n\n/**\n * Drain queued messages for sessions sitting idle.\n * Called every ~500ms from the sessions WS manager refresh loop.\n *\n * Logic: while session is idle and has a queue, arm a 1s timer; if still idle\n * when it elapses, dequeue and send the front message. Leaving idle cancels\n * the timer. The 1s grace lets Claude settle after finishing a task and\n * covers the case where a message is enqueued while already idle.\n */\nexport function drainQueues(sessions: SessionLike[]): void {\n\tconst now = Date.now();\n\n\tconst knownTargets = new Set<string>();\n\tfor (const session of sessions) {\n\t\tif (session.tmux_target) knownTargets.add(session.tmux_target);\n\t}\n\tpruneQueues(knownTargets, now);\n\n\tfor (const session of sessions) {\n\t\tif (!session.tmux_target) continue;\n\t\tconst target = session.tmux_target;\n\n\t\tconst queue = queues.get(target);\n\t\tif (!queue || queue.length === 0) {\n\t\t\tpendingDrain.delete(target);\n\t\t\t// Nothing left to hold back, and a send remembered past this point\n\t\t\t// would make a draft typed later look like our own unsubmitted message.\n\t\t\trecentlySent.delete(target);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (session.state !== 'idle') {\n\t\t\t// Session picked up work (hook flipped state) — clear send guard\n\t\t\tpendingDrain.delete(target);\n\t\t\trecentlySent.delete(target);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// After an auto-send, wait for the session to leave idle (UserPromptSubmit\n\t\t// hook) before sending the next message, so two queued messages don't get\n\t\t// pasted into the same prompt. Falls back after 10s in case the hook never fires.\n\t\tconst sentAt = recentlySent.get(target);\n\t\tif (sentAt !== undefined && now - sentAt < RESEND_FALLBACK_MS) continue;\n\n\t\tconst pendingTime = pendingDrain.get(target);\n\t\tif (pendingTime === undefined) {\n\t\t\tpendingDrain.set(target, now);\n\t\t\tcontinue;\n\t\t}\n\t\tif (now - pendingTime < IDLE_GRACE_MS) continue;\n\n\t\t// The hooks say idle, but only the pane knows whether a message pasted\n\t\t// now starts a turn. Compaction fires no hook, so a compacting session\n\t\t// reads idle: Claude Code parked each message sent into it, no prompt hook\n\t\t// answered, the fallback above let the next one through ten seconds\n\t\t// later, and the whole queue then ran as one prompt.\n\t\tconst hold = paneHold(target);\n\t\tif (hold) {\n\t\t\t// Our own last message, still in the box: its Enter was folded into\n\t\t\t// the paste. One more submits it; anything else typed there is a\n\t\t\t// person's draft, and waits for them.\n\t\t\tif (hold === 'typed' && sentAt !== undefined) pressEnter(target);\n\t\t\tpendingDrain.set(target, now);\n\t\t\tif (sentAt !== undefined) recentlySent.set(target, now);\n\t\t\tcontinue;\n\t\t}\n\n\t\tpendingDrain.delete(target);\n\t\t// Peek rather than dequeue: a message only leaves the queue once tmux has\n\t\t// taken it, so a failed send costs one attempt instead of a remove and a\n\t\t// re-insert (and two writes of the persisted file).\n\t\tconst message = queue[0];\n\t\ttry {\n\t\t\tconst prompt = promptOf(message);\n\t\t\tsendTextToPane(target, prompt);\n\t\t\tdequeue(target);\n\t\t\trecordDelivery(target, prompt, 'queue');\n\t\t\tif (message.dictated) markDictated(target, prompt);\n\t\t\trecentlySent.set(target, now);\n\t\t\tconsole.log(`[queue] Auto-sent queued message to ${target}`);\n\t\t} catch (err) {\n\t\t\t// tmux refused the paste — usually the pane is gone. Retry a couple of\n\t\t\t// times, then drop it: an undeliverable message must not keep the drain\n\t\t\t// loop, and its error log, running forever.\n\t\t\tmessage.attempts = (message.attempts ?? 0) + 1;\n\t\t\tif (message.attempts >= MAX_SEND_ATTEMPTS) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[queue] Giving up on message for ${target} after ${message.attempts} attempts:`,\n\t\t\t\t\terr\n\t\t\t\t);\n\t\t\t\tdequeue(target);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconsole.error(\n\t\t\t\t`[queue] Failed to send queued message to ${target} (attempt ${message.attempts}):`,\n\t\t\t\terr\n\t\t\t);\n\t\t\tpersistQueues();\n\t\t}\n\t}\n}\n\n/**\n * Server-owned drain loop. Runs while any queue is non-empty, independent of\n * whether a dashboard client is connected (the WS refresh loop only runs with\n * clients, which previously left queues stuck once the phone closed the app).\n * State comes straight from the session JSON files written by hooks.\n */\nexport function ensureDrainLoop(): void {\n\tif (globalState.drainTimer) return;\n\tglobalState.drainTimer = setInterval(() => {\n\t\tif (!hasQueuedMessages()) {\n\t\t\tif (globalState.drainTimer) clearInterval(globalState.drainTimer);\n\t\t\tglobalState.drainTimer = null;\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tdrainQueues(getAllSessions());\n\t\t} catch (err) {\n\t\t\tconsole.error('[queue] drain loop error:', err);\n\t\t}\n\t}, DRAIN_INTERVAL_MS);\n\t// Don't keep the process alive just for this timer\n\t(globalState.drainTimer as { unref?: () => void }).unref?.();\n}\n\n// Resume draining if queues survived a module reload (dev HMR keeps globalThis)\n// or a server restart (the persisted file above).\nif (hasQueuedMessages()) ensureDrainLoop();\n"],"names":["queues","state"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const VERSION = "0.30.13";
1
+ const VERSION = "0.30.14";
2
2
  export {
3
3
  VERSION as V
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sources":["../../../../../src/utils/version.ts"],"sourcesContent":["/**\n * Central version constant for claude-watch.\n * Keep in sync with package.json version.\n */\nexport const VERSION = \"0.30.13\";\n"],"names":[],"mappings":"AAIO,MAAM,UAAU;"}
1
+ {"version":3,"file":"version.js","sources":["../../../../../src/utils/version.ts"],"sourcesContent":["/**\n * Central version constant for claude-watch.\n * Keep in sync with package.json version.\n */\nexport const VERSION = \"0.30.14\";\n"],"names":[],"mappings":"AAIO,MAAM,UAAU;"}
@@ -3,7 +3,7 @@ import { b as getSessionsDir, c as cleanupStaleSessions, g as getSession, r as r
3
3
  import { dirname, join } from "path";
4
4
  import { homedir } from "os";
5
5
  import { w as writeFileAtomic } from "./atomic-write.js";
6
- import { p as parseSlashCommand, s as sessionWatcher, T as TranscriptBuilder, a as peekGitSummary, b as sessionChanges } from "./git.js";
6
+ import { u as unwrapPasted, p as parseSlashCommand, s as sessionWatcher, T as TranscriptBuilder, a as peekGitSummary, b as sessionChanges } from "./git.js";
7
7
  import { i as isDictated, g as getDeliveries, a as getQueue } from "./message-queue.js";
8
8
  import { statfs } from "fs/promises";
9
9
  import { p as projectsRoot, J as JsonlTailer, r as resolveTranscriptPath, f as findTranscriptById, l as listSubagents } from "./tailer.js";
@@ -648,6 +648,7 @@ function promptText(entry) {
648
648
  if (!block || message.content.some((b) => b?.type === "tool_result")) return null;
649
649
  text = block.text;
650
650
  }
651
+ if (text) text = unwrapPasted(text);
651
652
  if (!text?.trim()) return null;
652
653
  const command = parseSlashCommand(text);
653
654
  if (command) return clip(command.args ? `${command.name} ${command.args}` : command.name);