termdock 1.4.178 → 1.4.179

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 (333) hide show
  1. package/dist/client/assets/App-DXq9kwY5.js +150 -0
  2. package/dist/client/assets/App-DXq9kwY5.js.br +0 -0
  3. package/dist/client/assets/App-DXq9kwY5.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-BNWqAnqc.js → ChangeWalkthroughPanel-DWUBYx6Q.js} +2 -2
  5. package/dist/client/assets/ChangeWalkthroughPanel-DWUBYx6Q.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-DWUBYx6Q.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-IlTGwr-I.js → DagPlayground-DyczrcHM.js} +1 -1
  8. package/dist/client/assets/DagPlayground-DyczrcHM.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-DyczrcHM.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-CwhNpJvo.js → DiffLab-D5wb-H7y.js} +1 -1
  11. package/dist/client/assets/DiffLab-D5wb-H7y.js.br +0 -0
  12. package/dist/client/assets/DiffLab-D5wb-H7y.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-DyhINv0F.js → DiffReview-CVIz-B5T.js} +1 -1
  14. package/dist/client/assets/DiffReview-CVIz-B5T.js.br +0 -0
  15. package/dist/client/assets/DiffReview-CVIz-B5T.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-D9Kt5VwB.js → DiffReviewLab-99hTNRaj.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-99hTNRaj.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-99hTNRaj.js.gz +0 -0
  19. package/dist/client/assets/{DiffViewer-3zz2PI-8.js → DiffViewer-BPor2_7o.js} +1 -1
  20. package/dist/client/assets/DiffViewer-BPor2_7o.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-BPor2_7o.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-CN8xsiGC.js → ModelPreview-DpImj4vY.js} +1 -1
  23. package/dist/client/assets/ModelPreview-DpImj4vY.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-DpImj4vY.js.gz +0 -0
  25. package/dist/client/assets/RightSidebar-BW8SSsoA.js +79 -0
  26. package/dist/client/assets/RightSidebar-BW8SSsoA.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-BW8SSsoA.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-DkdoJ2ZH.js → abnfDiagram-VCTEODGH-CNWmFVMh.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-CNWmFVMh.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-CNWmFVMh.js.gz +0 -0
  31. package/dist/client/assets/app-Pl7tV8Be.js +34 -0
  32. package/dist/client/assets/app-Pl7tV8Be.js.br +0 -0
  33. package/dist/client/assets/app-Pl7tV8Be.js.gz +0 -0
  34. package/dist/client/assets/{arc-ctzz5PcK.js → arc-Z8mcHjh7.js} +1 -1
  35. package/dist/client/assets/arc-Z8mcHjh7.js.br +0 -0
  36. package/dist/client/assets/arc-Z8mcHjh7.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-DNLcYfGQ.js → architectureDiagram-5GKGNRK7-DuxbAeFP.js} +1 -1
  38. package/dist/client/assets/architectureDiagram-5GKGNRK7-DuxbAeFP.js.br +0 -0
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-DuxbAeFP.js.gz +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-Coye1ou5.js → blockDiagram-I7D4REHJ-SxAFw0An.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-SxAFw0An.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-SxAFw0An.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-DTa8jyPp.js → c4Diagram-7LVT6UL2-CcRdiWo7.js} +1 -1
  44. package/dist/client/assets/{c4Diagram-7LVT6UL2-DTa8jyPp.js.br → c4Diagram-7LVT6UL2-CcRdiWo7.js.br} +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-CcRdiWo7.js.gz +0 -0
  46. package/dist/client/assets/channel-nXn0ntBk.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-BpDyjNfs.js → chunk-2Q5K7J3B-DJ_qzvZZ.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-DgwdqXKl.js → chunk-5VM5RSS4-l3H0C2ci.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-DWf2gZ96.js → chunk-F27PBJKO-B7xASv8n.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-B7xASv8n.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-B7xASv8n.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-l42o_E2c.js → chunk-IMKFNOWR-Wg5gR7sZ.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-Wg5gR7sZ.js.br +0 -0
  54. package/dist/client/assets/chunk-IMKFNOWR-Wg5gR7sZ.js.gz +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-8CkEf05l.js → chunk-JWPE2WC7-BlXUS79m.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-BKKOfbdg.js → chunk-POPQ4Y6H-CU38G8hb.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-RORrf2aM.js → chunk-SVP7TREG-D-yZlXqX.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-D-yZlXqX.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-D-yZlXqX.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-hPqafEtC.js → chunk-TICWLB2K-DrOYmcBB.js} +1 -1
  61. package/dist/client/assets/{chunk-TICWLB2K-hPqafEtC.js.br → chunk-TICWLB2K-DrOYmcBB.js.br} +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-DrOYmcBB.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-CrvjJwms.js → chunk-XXDRQBXY-BbsHtVFa.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-DxqN38m7.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-DxqN38m7.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-CwRSiQLa.js → cose-bilkent-JH36ORCC-_wKUNL8T.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-_wKUNL8T.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-_wKUNL8T.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-CXnppZ8I.js → cynefin-OW5HDTMX-CPr--Wnc.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-CPr--Wnc.js.br +0 -0
  71. package/dist/client/assets/cynefin-OW5HDTMX-CPr--Wnc.js.gz +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BSDrHSIA.js → cynefinDiagram-5FMLGOSQ-5ygpPOWT.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-5ygpPOWT.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-5ygpPOWT.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-d-5j4vkq.js → dagre-GXQ25YYZ-Cvx65nHO.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-Cvx65nHO.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-Cvx65nHO.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-DizMBpGW.js → diagram-S7CK7UJ4-Dw5cp_BA.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-Dw5cp_BA.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-Dw5cp_BA.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-CNQK26rH.js → diagram-UQ7AKVKN-D8BZ5Tp3.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-D8BZ5Tp3.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-D8BZ5Tp3.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-Dua-2_46.js → diagram-VSXAHHWV-BxJD7ZWH.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-BxJD7ZWH.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-BxJD7ZWH.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-CIq-4729.js → diagram-VX7I27RA-CvzlU0bI.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-CvzlU0bI.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-CvzlU0bI.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-BukQ0u-x.js → diagram-Z3DM3KII-Be4HdFlL.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-Be4HdFlL.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-Be4HdFlL.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-D2zRkRBI.js → ebnfDiagram-PWID7BFC-C_wrrPjF.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-C_wrrPjF.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-C_wrrPjF.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-DbX6A3ls.js → erDiagram-RLTQ6QDP-IHyLgV6M.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-IHyLgV6M.js.br +0 -0
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-IHyLgV6M.js.gz +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-YECDWA2G.js → flowDiagram-HODETNUW-CpfW88Uc.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-CpfW88Uc.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-CpfW88Uc.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-D3EJdUov.js → ganttDiagram-EL5Y4UJY-CsbQ6KLb.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CsbQ6KLb.js.br +0 -0
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CsbQ6KLb.js.gz +0 -0
  105. package/dist/client/assets/{gestureArbiter-D3IZpZS7.js → gestureArbiter-CJQ8K48V.js} +1 -1
  106. package/dist/client/assets/gestureArbiter-CJQ8K48V.js.br +0 -0
  107. package/dist/client/assets/gestureArbiter-CJQ8K48V.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-tr_HKoQK.js → gitGraphDiagram-WWUBYQGX-DJZqSGtH.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DJZqSGtH.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DJZqSGtH.js.gz +0 -0
  111. package/dist/client/assets/{index-C4Ny0NDQ.css → index-PWu1VEN_.css} +1 -1
  112. package/dist/client/assets/index-PWu1VEN_.css.br +0 -0
  113. package/dist/client/assets/index-PWu1VEN_.css.gz +0 -0
  114. package/dist/client/assets/{infoDiagram-27XIBGKW-xBV63YYg.js → infoDiagram-27XIBGKW-D6sSgR2Y.js} +1 -1
  115. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BrYSZu1R.js → ishikawaDiagram-5VMMS53U-BJJSUXep.js} +1 -1
  116. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BJJSUXep.js.br +0 -0
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BJJSUXep.js.gz +0 -0
  118. package/dist/client/assets/{journeyDiagram-3NMN7TZE-C9Ft--aA.js → journeyDiagram-3NMN7TZE-ou1XS2DP.js} +1 -1
  119. package/dist/client/assets/journeyDiagram-3NMN7TZE-ou1XS2DP.js.br +0 -0
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-ou1XS2DP.js.gz +0 -0
  121. package/dist/client/assets/{kanban-definition-UXKFOSKX-FCxqSu11.js → kanban-definition-UXKFOSKX-BFQGYQzd.js} +1 -1
  122. package/dist/client/assets/kanban-definition-UXKFOSKX-BFQGYQzd.js.br +0 -0
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-BFQGYQzd.js.gz +0 -0
  124. package/dist/client/assets/{layout-BqjIyPqO.js → layout-zUU3xcam.js} +1 -1
  125. package/dist/client/assets/layout-zUU3xcam.js.br +0 -0
  126. package/dist/client/assets/layout-zUU3xcam.js.gz +0 -0
  127. package/dist/client/assets/{linear-CEAGQX5N.js → linear-CYwYQI2I.js} +1 -1
  128. package/dist/client/assets/linear-CYwYQI2I.js.br +0 -0
  129. package/dist/client/assets/linear-CYwYQI2I.js.gz +0 -0
  130. package/dist/client/assets/{mermaid.core-DvAY78t_.js → mermaid.core-91Llsw5j.js} +5 -5
  131. package/dist/client/assets/mermaid.core-91Llsw5j.js.br +0 -0
  132. package/dist/client/assets/mermaid.core-91Llsw5j.js.gz +0 -0
  133. package/dist/client/assets/{mindmap-definition-YA3MSWOX-D7_qIQ9H.js → mindmap-definition-YA3MSWOX-RWG074lb.js} +1 -1
  134. package/dist/client/assets/mindmap-definition-YA3MSWOX-RWG074lb.js.br +0 -0
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-RWG074lb.js.gz +0 -0
  136. package/dist/client/assets/{passwordBootstrap-Dksfdvk3.js → passwordBootstrap-DRknuNUN.js} +1 -1
  137. package/dist/client/assets/passwordBootstrap-DRknuNUN.js.br +0 -0
  138. package/dist/client/assets/passwordBootstrap-DRknuNUN.js.gz +0 -0
  139. package/dist/client/assets/{pegDiagram-XKGWAZYB-DeTIBKGC.js → pegDiagram-XKGWAZYB-Dyh2rzGS.js} +1 -1
  140. package/dist/client/assets/pegDiagram-XKGWAZYB-Dyh2rzGS.js.br +0 -0
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-Dyh2rzGS.js.gz +0 -0
  142. package/dist/client/assets/{pieDiagram-E7YTZNPT-z5IcoVwx.js → pieDiagram-E7YTZNPT-CL3UcUfh.js} +1 -1
  143. package/dist/client/assets/pieDiagram-E7YTZNPT-CL3UcUfh.js.br +0 -0
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-CL3UcUfh.js.gz +0 -0
  145. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B-zCZvES.js → quadrantDiagram-AXDQQJYC-DOkejMel.js} +1 -1
  146. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DOkejMel.js.br +0 -0
  147. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DOkejMel.js.gz +0 -0
  148. package/dist/client/assets/{railroadDiagram-O6MQD6OU-DbpeB7P2.js → railroadDiagram-O6MQD6OU-BC780Uh3.js} +1 -1
  149. package/dist/client/assets/railroadDiagram-O6MQD6OU-BC780Uh3.js.br +0 -0
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-BC780Uh3.js.gz +0 -0
  151. package/dist/client/assets/{referenceLongPress-p56g9xFb.js → referenceLongPress-Dlc0425_.js} +1 -1
  152. package/dist/client/assets/referenceLongPress-Dlc0425_.js.br +0 -0
  153. package/dist/client/assets/referenceLongPress-Dlc0425_.js.gz +0 -0
  154. package/dist/client/assets/{requirementDiagram-BXWQKSXE-UhJYMJ_U.js → requirementDiagram-BXWQKSXE-CwCGr0qR.js} +1 -1
  155. package/dist/client/assets/requirementDiagram-BXWQKSXE-CwCGr0qR.js.br +0 -0
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-CwCGr0qR.js.gz +0 -0
  157. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-xKsuFR17.js → sankeyDiagram-P5KCCOFB-CYDYBwJQ.js} +1 -1
  158. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CYDYBwJQ.js.br +0 -0
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CYDYBwJQ.js.gz +0 -0
  160. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DrjBl65C.js → sequenceDiagram-WJ2MYXX4-DhHsZi3K.js} +1 -1
  161. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DhHsZi3K.js.br +0 -0
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DhHsZi3K.js.gz +0 -0
  163. package/dist/client/assets/{sizeCapture-INFHLROL-BIlGJ9tm.js → sizeCapture-INFHLROL-4oUXWvYA.js} +1 -1
  164. package/dist/client/assets/sizeCapture-INFHLROL-4oUXWvYA.js.br +0 -0
  165. package/dist/client/assets/sizeCapture-INFHLROL-4oUXWvYA.js.gz +0 -0
  166. package/dist/client/assets/{sparkles-B-uEi0Pa.js → sparkles-gqiuBkfP.js} +1 -1
  167. package/dist/client/assets/{stateDiagram-D77RDMKH-C3k9XvA6.js → stateDiagram-D77RDMKH-7hUELZGo.js} +1 -1
  168. package/dist/client/assets/stateDiagram-D77RDMKH-7hUELZGo.js.br +0 -0
  169. package/dist/client/assets/stateDiagram-D77RDMKH-7hUELZGo.js.gz +0 -0
  170. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-MO84Jqt6.js +1 -0
  171. package/dist/client/assets/{swimlanes-42K2YHIH-BN-x1rXp.js → swimlanes-42K2YHIH-E1qoordP.js} +1 -1
  172. package/dist/client/assets/swimlanes-42K2YHIH-E1qoordP.js.br +0 -0
  173. package/dist/client/assets/swimlanes-42K2YHIH-E1qoordP.js.gz +0 -0
  174. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DnSXMnZQ.js +8 -0
  175. package/dist/client/assets/{timeline-definition-24CTP7MA-Dcedjtic.js → timeline-definition-24CTP7MA-CTYyCnUW.js} +1 -1
  176. package/dist/client/assets/timeline-definition-24CTP7MA-CTYyCnUW.js.br +0 -0
  177. package/dist/client/assets/timeline-definition-24CTP7MA-CTYyCnUW.js.gz +0 -0
  178. package/dist/client/assets/{triangle-alert-BSNAwyll.js → triangle-alert-DrBuMUPw.js} +1 -1
  179. package/dist/client/assets/{vennDiagram-4TSXK5OY-DAIk-5Rt.js → vennDiagram-4TSXK5OY-DVWbhWsS.js} +1 -1
  180. package/dist/client/assets/vennDiagram-4TSXK5OY-DVWbhWsS.js.br +0 -0
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-DVWbhWsS.js.gz +0 -0
  182. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-3iXOTpAg.js → wardleyDiagram-VM6X3IG4-BFAwOxhc.js} +1 -1
  183. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BFAwOxhc.js.br +0 -0
  184. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BFAwOxhc.js.gz +0 -0
  185. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-OD1a_9Qg.js → xychartDiagram-S5SC5T6Z-DYXOiBiX.js} +1 -1
  186. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DYXOiBiX.js.br +0 -0
  187. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DYXOiBiX.js.gz +0 -0
  188. package/dist/client/index.html +2 -2
  189. package/dist/client/index.html.br +0 -0
  190. package/dist/client/index.html.gz +0 -0
  191. package/dist/client/sw-notifications.js +5 -45
  192. package/dist/client/sw-notifications.js.br +0 -0
  193. package/dist/client/sw-notifications.js.gz +0 -0
  194. package/dist/client/sw.js +1 -1
  195. package/dist/client/sw.js.br +0 -0
  196. package/dist/client/sw.js.gz +0 -0
  197. package/dist/server/agent/collaborationCli.js +10 -3
  198. package/dist/server/agent/collaborationDeliveryWorker.js +154 -0
  199. package/dist/server/agent/collaborationRoutes.js +15 -4
  200. package/dist/server/agent/collaborationRouting.js +94 -0
  201. package/dist/server/agent/collaborationStore.js +6 -1
  202. package/dist/server/agent/collaborationTmuxDelivery.js +10 -0
  203. package/dist/server/agent/sessionBindingRecovery.js +59 -0
  204. package/dist/server/entry.js +30 -13
  205. package/dist/server/federation/desktopTargets.js +64 -0
  206. package/dist/server/federation/directRoutes.js +27 -19
  207. package/dist/server/federation/routeAccess.js +14 -7
  208. package/dist/server/federation/trustedCa.js +55 -0
  209. package/dist/server/routes/terminal.js +242 -67
  210. package/package.json +1 -1
  211. package/runtime-manifest.json +3 -3
  212. package/dist/client/assets/App-CAEtVL8Z.js +0 -150
  213. package/dist/client/assets/App-CAEtVL8Z.js.br +0 -0
  214. package/dist/client/assets/App-CAEtVL8Z.js.gz +0 -0
  215. package/dist/client/assets/ChangeWalkthroughPanel-BNWqAnqc.js.br +0 -0
  216. package/dist/client/assets/ChangeWalkthroughPanel-BNWqAnqc.js.gz +0 -0
  217. package/dist/client/assets/DagPlayground-IlTGwr-I.js.br +0 -0
  218. package/dist/client/assets/DagPlayground-IlTGwr-I.js.gz +0 -0
  219. package/dist/client/assets/DiffLab-CwhNpJvo.js.br +0 -0
  220. package/dist/client/assets/DiffLab-CwhNpJvo.js.gz +0 -0
  221. package/dist/client/assets/DiffReview-DyhINv0F.js.br +0 -0
  222. package/dist/client/assets/DiffReview-DyhINv0F.js.gz +0 -0
  223. package/dist/client/assets/DiffReviewLab-D9Kt5VwB.js.br +0 -0
  224. package/dist/client/assets/DiffReviewLab-D9Kt5VwB.js.gz +0 -0
  225. package/dist/client/assets/DiffViewer-3zz2PI-8.js.br +0 -0
  226. package/dist/client/assets/DiffViewer-3zz2PI-8.js.gz +0 -0
  227. package/dist/client/assets/ModelPreview-CN8xsiGC.js.br +0 -0
  228. package/dist/client/assets/ModelPreview-CN8xsiGC.js.gz +0 -0
  229. package/dist/client/assets/RightSidebar-DQk1cwKH.js +0 -79
  230. package/dist/client/assets/RightSidebar-DQk1cwKH.js.br +0 -0
  231. package/dist/client/assets/RightSidebar-DQk1cwKH.js.gz +0 -0
  232. package/dist/client/assets/abnfDiagram-VCTEODGH-DkdoJ2ZH.js.br +0 -0
  233. package/dist/client/assets/abnfDiagram-VCTEODGH-DkdoJ2ZH.js.gz +0 -0
  234. package/dist/client/assets/app-B42CoF1r.js +0 -34
  235. package/dist/client/assets/app-B42CoF1r.js.br +0 -0
  236. package/dist/client/assets/app-B42CoF1r.js.gz +0 -0
  237. package/dist/client/assets/arc-ctzz5PcK.js.br +0 -0
  238. package/dist/client/assets/arc-ctzz5PcK.js.gz +0 -0
  239. package/dist/client/assets/architectureDiagram-5GKGNRK7-DNLcYfGQ.js.br +0 -0
  240. package/dist/client/assets/architectureDiagram-5GKGNRK7-DNLcYfGQ.js.gz +0 -0
  241. package/dist/client/assets/blockDiagram-I7D4REHJ-Coye1ou5.js.br +0 -0
  242. package/dist/client/assets/blockDiagram-I7D4REHJ-Coye1ou5.js.gz +0 -0
  243. package/dist/client/assets/c4Diagram-7LVT6UL2-DTa8jyPp.js.gz +0 -0
  244. package/dist/client/assets/channel-CzvSDv06.js +0 -1
  245. package/dist/client/assets/chunk-F27PBJKO-DWf2gZ96.js.br +0 -0
  246. package/dist/client/assets/chunk-F27PBJKO-DWf2gZ96.js.gz +0 -0
  247. package/dist/client/assets/chunk-IMKFNOWR-l42o_E2c.js.br +0 -0
  248. package/dist/client/assets/chunk-IMKFNOWR-l42o_E2c.js.gz +0 -0
  249. package/dist/client/assets/chunk-SVP7TREG-RORrf2aM.js.br +0 -0
  250. package/dist/client/assets/chunk-SVP7TREG-RORrf2aM.js.gz +0 -0
  251. package/dist/client/assets/chunk-TICWLB2K-hPqafEtC.js.gz +0 -0
  252. package/dist/client/assets/classDiagram-ZZMXUADV-CDknJfE0.js +0 -1
  253. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CDknJfE0.js +0 -1
  254. package/dist/client/assets/cose-bilkent-JH36ORCC-CwRSiQLa.js.br +0 -0
  255. package/dist/client/assets/cose-bilkent-JH36ORCC-CwRSiQLa.js.gz +0 -0
  256. package/dist/client/assets/cynefin-OW5HDTMX-CXnppZ8I.js.br +0 -0
  257. package/dist/client/assets/cynefin-OW5HDTMX-CXnppZ8I.js.gz +0 -0
  258. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BSDrHSIA.js.br +0 -0
  259. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BSDrHSIA.js.gz +0 -0
  260. package/dist/client/assets/dagre-GXQ25YYZ-d-5j4vkq.js.br +0 -0
  261. package/dist/client/assets/dagre-GXQ25YYZ-d-5j4vkq.js.gz +0 -0
  262. package/dist/client/assets/diagram-S7CK7UJ4-DizMBpGW.js.br +0 -0
  263. package/dist/client/assets/diagram-S7CK7UJ4-DizMBpGW.js.gz +0 -0
  264. package/dist/client/assets/diagram-UQ7AKVKN-CNQK26rH.js.br +0 -0
  265. package/dist/client/assets/diagram-UQ7AKVKN-CNQK26rH.js.gz +0 -0
  266. package/dist/client/assets/diagram-VSXAHHWV-Dua-2_46.js.br +0 -0
  267. package/dist/client/assets/diagram-VSXAHHWV-Dua-2_46.js.gz +0 -0
  268. package/dist/client/assets/diagram-VX7I27RA-CIq-4729.js.br +0 -0
  269. package/dist/client/assets/diagram-VX7I27RA-CIq-4729.js.gz +0 -0
  270. package/dist/client/assets/diagram-Z3DM3KII-BukQ0u-x.js.br +0 -0
  271. package/dist/client/assets/diagram-Z3DM3KII-BukQ0u-x.js.gz +0 -0
  272. package/dist/client/assets/ebnfDiagram-PWID7BFC-D2zRkRBI.js.br +0 -0
  273. package/dist/client/assets/ebnfDiagram-PWID7BFC-D2zRkRBI.js.gz +0 -0
  274. package/dist/client/assets/erDiagram-RLTQ6QDP-DbX6A3ls.js.br +0 -0
  275. package/dist/client/assets/erDiagram-RLTQ6QDP-DbX6A3ls.js.gz +0 -0
  276. package/dist/client/assets/flowDiagram-HODETNUW-YECDWA2G.js.br +0 -0
  277. package/dist/client/assets/flowDiagram-HODETNUW-YECDWA2G.js.gz +0 -0
  278. package/dist/client/assets/ganttDiagram-EL5Y4UJY-D3EJdUov.js.br +0 -0
  279. package/dist/client/assets/ganttDiagram-EL5Y4UJY-D3EJdUov.js.gz +0 -0
  280. package/dist/client/assets/gestureArbiter-D3IZpZS7.js.br +0 -0
  281. package/dist/client/assets/gestureArbiter-D3IZpZS7.js.gz +0 -0
  282. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tr_HKoQK.js.br +0 -0
  283. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tr_HKoQK.js.gz +0 -0
  284. package/dist/client/assets/index-C4Ny0NDQ.css.br +0 -0
  285. package/dist/client/assets/index-C4Ny0NDQ.css.gz +0 -0
  286. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BrYSZu1R.js.br +0 -0
  287. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BrYSZu1R.js.gz +0 -0
  288. package/dist/client/assets/journeyDiagram-3NMN7TZE-C9Ft--aA.js.br +0 -0
  289. package/dist/client/assets/journeyDiagram-3NMN7TZE-C9Ft--aA.js.gz +0 -0
  290. package/dist/client/assets/kanban-definition-UXKFOSKX-FCxqSu11.js.br +0 -0
  291. package/dist/client/assets/kanban-definition-UXKFOSKX-FCxqSu11.js.gz +0 -0
  292. package/dist/client/assets/layout-BqjIyPqO.js.br +0 -0
  293. package/dist/client/assets/layout-BqjIyPqO.js.gz +0 -0
  294. package/dist/client/assets/linear-CEAGQX5N.js.br +0 -0
  295. package/dist/client/assets/linear-CEAGQX5N.js.gz +0 -0
  296. package/dist/client/assets/mermaid.core-DvAY78t_.js.br +0 -0
  297. package/dist/client/assets/mermaid.core-DvAY78t_.js.gz +0 -0
  298. package/dist/client/assets/mindmap-definition-YA3MSWOX-D7_qIQ9H.js.br +0 -0
  299. package/dist/client/assets/mindmap-definition-YA3MSWOX-D7_qIQ9H.js.gz +0 -0
  300. package/dist/client/assets/passwordBootstrap-Dksfdvk3.js.br +0 -0
  301. package/dist/client/assets/passwordBootstrap-Dksfdvk3.js.gz +0 -0
  302. package/dist/client/assets/pegDiagram-XKGWAZYB-DeTIBKGC.js.br +0 -0
  303. package/dist/client/assets/pegDiagram-XKGWAZYB-DeTIBKGC.js.gz +0 -0
  304. package/dist/client/assets/pieDiagram-E7YTZNPT-z5IcoVwx.js.br +0 -0
  305. package/dist/client/assets/pieDiagram-E7YTZNPT-z5IcoVwx.js.gz +0 -0
  306. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B-zCZvES.js.br +0 -0
  307. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B-zCZvES.js.gz +0 -0
  308. package/dist/client/assets/railroadDiagram-O6MQD6OU-DbpeB7P2.js.br +0 -0
  309. package/dist/client/assets/railroadDiagram-O6MQD6OU-DbpeB7P2.js.gz +0 -0
  310. package/dist/client/assets/referenceLongPress-p56g9xFb.js.br +0 -0
  311. package/dist/client/assets/referenceLongPress-p56g9xFb.js.gz +0 -0
  312. package/dist/client/assets/requirementDiagram-BXWQKSXE-UhJYMJ_U.js.br +0 -0
  313. package/dist/client/assets/requirementDiagram-BXWQKSXE-UhJYMJ_U.js.gz +0 -0
  314. package/dist/client/assets/sankeyDiagram-P5KCCOFB-xKsuFR17.js.br +0 -0
  315. package/dist/client/assets/sankeyDiagram-P5KCCOFB-xKsuFR17.js.gz +0 -0
  316. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DrjBl65C.js.br +0 -0
  317. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DrjBl65C.js.gz +0 -0
  318. package/dist/client/assets/sizeCapture-INFHLROL-BIlGJ9tm.js.br +0 -0
  319. package/dist/client/assets/sizeCapture-INFHLROL-BIlGJ9tm.js.gz +0 -0
  320. package/dist/client/assets/stateDiagram-D77RDMKH-C3k9XvA6.js.br +0 -0
  321. package/dist/client/assets/stateDiagram-D77RDMKH-C3k9XvA6.js.gz +0 -0
  322. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B7GnCu2o.js +0 -1
  323. package/dist/client/assets/swimlanes-42K2YHIH-BN-x1rXp.js.br +0 -0
  324. package/dist/client/assets/swimlanes-42K2YHIH-BN-x1rXp.js.gz +0 -0
  325. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BH_gm34Y.js +0 -8
  326. package/dist/client/assets/timeline-definition-24CTP7MA-Dcedjtic.js.br +0 -0
  327. package/dist/client/assets/timeline-definition-24CTP7MA-Dcedjtic.js.gz +0 -0
  328. package/dist/client/assets/vennDiagram-4TSXK5OY-DAIk-5Rt.js.br +0 -0
  329. package/dist/client/assets/vennDiagram-4TSXK5OY-DAIk-5Rt.js.gz +0 -0
  330. package/dist/client/assets/wardleyDiagram-VM6X3IG4-3iXOTpAg.js.br +0 -0
  331. package/dist/client/assets/wardleyDiagram-VM6X3IG4-3iXOTpAg.js.gz +0 -0
  332. package/dist/client/assets/xychartDiagram-S5SC5T6Z-OD1a_9Qg.js.br +0 -0
  333. package/dist/client/assets/xychartDiagram-S5SC5T6Z-OD1a_9Qg.js.gz +0 -0
@@ -0,0 +1,154 @@
1
+ export class CollaborationDeliveryWorker {
2
+ options;
3
+ running = new Map();
4
+ states = new Map();
5
+ failures = new Map();
6
+ submitted = new Set();
7
+ timer = null;
8
+ ticking = null;
9
+ constructor(options) {
10
+ this.options = options;
11
+ }
12
+ start() {
13
+ if (this.timer)
14
+ return;
15
+ this.timer = setInterval(() => { void this.tick(); }, 2_000);
16
+ this.timer.unref?.();
17
+ void this.tick();
18
+ }
19
+ stop() {
20
+ if (this.timer)
21
+ clearInterval(this.timer);
22
+ this.timer = null;
23
+ }
24
+ state(id) {
25
+ const state = this.states.get(id);
26
+ return state && Date.now() - state.checkedAt < 15_000 ? state
27
+ : { state: 'recovering', reason: 'ROUTE_NOT_CHECKED', checkedAt: state?.checkedAt ?? null };
28
+ }
29
+ wake(id) {
30
+ void this.run(id).catch(this.options.onError);
31
+ }
32
+ tick() {
33
+ if (this.ticking)
34
+ return this.ticking;
35
+ this.ticking = this.tickOnce().finally(() => { this.ticking = null; });
36
+ return this.ticking;
37
+ }
38
+ async tickOnce() {
39
+ try {
40
+ const ids = [...new Set([...this.options.peers(), ...this.options.store.pendingRecipients()])].filter(this.options.isLocal);
41
+ // Bound process probes/attachments while keeping a broken peer from
42
+ // serially blocking the entire queue.
43
+ let cursor = 0;
44
+ await Promise.all(Array.from({ length: Math.min(4, ids.length) }, async () => {
45
+ while (cursor < ids.length)
46
+ await this.run(ids[cursor++]).catch(this.options.onError);
47
+ }));
48
+ }
49
+ catch (error) {
50
+ this.options.onError(error);
51
+ }
52
+ }
53
+ run(id) {
54
+ const running = this.running.get(id);
55
+ if (running)
56
+ return running;
57
+ if (!this.options.isLocal(id))
58
+ return Promise.resolve();
59
+ const operation = Promise.resolve().then(() => this.deliver(id)).finally(() => {
60
+ if (this.running.get(id) === operation)
61
+ this.running.delete(id);
62
+ });
63
+ this.running.set(id, operation);
64
+ return operation;
65
+ }
66
+ reconfigure(id, configure) {
67
+ const previous = this.running.get(id) ?? Promise.resolve();
68
+ const operation = previous.catch(() => undefined).then(async () => {
69
+ await configure();
70
+ this.failures.delete(id);
71
+ this.states.delete(id);
72
+ for (const message of this.options.store.inbox(id, { pendingOnly: true, limit: 200 })) {
73
+ const diagnostic = this.options.store.diagnostic(message.id);
74
+ if (diagnostic)
75
+ this.options.store.recordTransport(message.id, { ...diagnostic, next_retry_at: null, last_error: null });
76
+ }
77
+ }).finally(() => {
78
+ if (this.running.get(id) === operation)
79
+ this.running.delete(id);
80
+ });
81
+ this.running.set(id, operation);
82
+ return operation;
83
+ }
84
+ async deliver(id) {
85
+ const { store } = this.options;
86
+ const first = store.inbox(id, { pendingOnly: true, limit: 1 })[0];
87
+ if (!first && Date.now() - (this.states.get(id)?.checkedAt ?? 0) < 5_000)
88
+ return;
89
+ if (first && this.submitted.has(first.id)) {
90
+ this.complete(id, first);
91
+ return;
92
+ }
93
+ if (first && (store.diagnostic(first.id)?.next_retry_at ?? 0) > Date.now())
94
+ return;
95
+ this.states.set(id, { state: 'recovering', reason: null, checkedAt: Date.now() });
96
+ let route;
97
+ try {
98
+ route = await this.options.resolve(id);
99
+ }
100
+ catch (error) {
101
+ route = { state: 'unavailable', reason: `ROUTE_RECOVERY_FAILED: ${error instanceof Error ? error.message : String(error)}` };
102
+ }
103
+ this.states.set(id, { state: route.state, reason: route.reason ?? null, checkedAt: Date.now() });
104
+ // Re-read after asynchronous recovery: a consumer may have read the
105
+ // message, or its TTL may have elapsed while tmux was being inspected.
106
+ const pending = store.inbox(id, { pendingOnly: true, limit: 1 });
107
+ if (!pending.length)
108
+ return;
109
+ if (route.state !== 'ready' || !route.write) {
110
+ this.retry(id, pending, route.reason ?? route.state, false);
111
+ return;
112
+ }
113
+ const message = pending[0];
114
+ if (!this.submitted.has(message.id)) {
115
+ store.recordTransport(message.id, {
116
+ relay_online: null, peer_reachable: true,
117
+ attempt_count: (store.diagnostic(message.id)?.attempt_count ?? 0) + 1,
118
+ next_retry_at: Date.now() + 2_000, last_error: 'DELIVERY_IN_PROGRESS', checked_at: Date.now(),
119
+ });
120
+ try {
121
+ await route.write(pending);
122
+ }
123
+ catch (error) {
124
+ this.retry(id, pending, `TERMINAL_WRITE_FAILED: ${error instanceof Error ? error.message : String(error)}`, true);
125
+ return;
126
+ }
127
+ this.submitted.add(message.id);
128
+ }
129
+ // If persistence fails after writing, the in-process guard avoids a
130
+ // second write on retry. Across a crash, transport is at-least-once.
131
+ this.complete(id, message);
132
+ }
133
+ complete(id, message) {
134
+ const { store } = this.options;
135
+ store.markDelivered([message.id]);
136
+ this.submitted.delete(message.id);
137
+ store.recordTransport(message.id, {
138
+ relay_online: null, peer_reachable: true, attempt_count: store.diagnostic(message.id)?.attempt_count ?? 1,
139
+ next_retry_at: null, last_error: null, checked_at: Date.now(),
140
+ });
141
+ this.failures.delete(id);
142
+ }
143
+ retry(id, messages, reason, reachable) {
144
+ const failures = (this.failures.get(id) ?? 0) + 1;
145
+ this.failures.set(id, failures);
146
+ const next = Date.now() + Math.min(30_000, 2_000 * 2 ** Math.min(failures - 1, 4));
147
+ for (const message of messages)
148
+ this.options.store.recordTransport(message.id, {
149
+ relay_online: null, peer_reachable: reachable,
150
+ attempt_count: this.options.store.diagnostic(message.id)?.attempt_count ?? 0,
151
+ next_retry_at: next, last_error: reason, checked_at: Date.now(),
152
+ });
153
+ }
154
+ }
@@ -1,19 +1,28 @@
1
1
  import { Router } from 'express';
2
2
  import { COLLAB_LIMITS, CollaborationError, extrasFromBody } from './collaborationProtocol.js';
3
- export function collaborationRoutes({ store, resolveSession, deliver }) {
3
+ export function collaborationRoutes({ store, resolveSession, deliver, rebind }) {
4
4
  const router = Router();
5
- const run = (handler) => (req, res) => {
5
+ const run = (handler) => async (req, res) => {
6
6
  try {
7
7
  const sessionId = resolveSession((req.method === 'GET' ? req.query : req.body) ?? {});
8
8
  if (!sessionId)
9
9
  throw new CollaborationError('SESSION_NOT_FOUND', 'Run inside a Termdock managed session', 404);
10
- handler(req, res, sessionId);
10
+ await handler(req, res, sessionId);
11
11
  }
12
12
  catch (error) {
13
13
  res.status(error instanceof CollaborationError ? error.httpStatus : 400).json({ ok: false,
14
14
  code: error instanceof CollaborationError ? error.code : 'COLLABORATION_ERROR', error: error instanceof Error ? error.message : String(error) });
15
15
  }
16
16
  };
17
+ router.post('/route/rebind', run(async (req, res, sessionId) => {
18
+ if (!rebind)
19
+ throw new CollaborationError('ROUTE_REBIND_UNAVAILABLE', 'Route rebinding is not available');
20
+ const pane = req.body.pane;
21
+ if (pane !== undefined && pane !== null && (typeof pane !== 'string' || !/^%\d+$/.test(pane))) {
22
+ throw new CollaborationError('INVALID_PANE', 'pane must be a tmux pane id such as %3');
23
+ }
24
+ res.json({ ok: true, route: await rebind(sessionId, pane ?? null) });
25
+ }));
17
26
  const ownMessage = (id, sessionId, recipientOnly = false) => {
18
27
  const message = store.getMessage(id);
19
28
  if (!message || (message.toSessionId !== sessionId && (recipientOnly || message.fromSessionId !== sessionId)))
@@ -27,11 +36,13 @@ export function collaborationRoutes({ store, resolveSession, deliver }) {
27
36
  };
28
37
  router.get('/capabilities', run((_req, res) => {
29
38
  res.json({ protocol_version: 2, limits: COLLAB_LIMITS,
39
+ routing: { background_recovery: true, explicit_rebind: Boolean(rebind), fixed_tmux_pane: true },
30
40
  statuses: ['pending', 'delivered', 'read', 'failed', 'expired'], queued_status: 'pending',
31
41
  semantics: { delivered: 'written to terminal; no application acknowledgement implied', read: 'explicit consumer acknowledgement',
32
42
  ack: 'explicit response_kind=ack', result: 'explicit response_kind=result; inspect task.status and evidence',
33
43
  done: 'adapter reports turn ended, never proof of task completion', heartbeat: 'null unless explicitly observed',
34
- timeout: 'stops waiting, does not cancel delivery' } });
44
+ timeout: 'stops waiting, does not cancel delivery',
45
+ retry: 'same message id may be submitted again after a crash or uncertain transport result; consumers deduplicate by message id' } });
35
46
  }));
36
47
  router.post('/send', run((req, res, sessionId) => {
37
48
  const target = typeof req.body.targetSessionId === 'string' ? req.body.targetSessionId.trim() : '';
@@ -0,0 +1,94 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export class CollaborationRoutingStore {
4
+ file;
5
+ bindings = new Map();
6
+ loadError = null;
7
+ constructor(file) {
8
+ this.file = file;
9
+ try {
10
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'));
11
+ if (data.version !== 1 || !Array.isArray(data.bindings))
12
+ throw new Error('Invalid collaboration routing store');
13
+ for (const binding of data.bindings) {
14
+ if (!binding || typeof binding.sessionId !== 'string' || !['shell', 'tmux'].includes(binding.mode)
15
+ || ![binding.backendSessionId, binding.tmuxSessionName, binding.agentSlug, binding.nativeSessionId].every((value) => value === null || typeof value === 'string')
16
+ || (binding.pane !== null && (!binding.pane || !Number.isInteger(binding.pane.serverPid) || !Number.isInteger(binding.pane.panePid)
17
+ || !/^%\d+$/.test(binding.pane.paneId) || !/^\$\d+$/.test(binding.pane.sessionId) || typeof binding.pane.agentSlug !== 'string'))) {
18
+ throw new Error('Invalid collaboration binding');
19
+ }
20
+ this.bindings.set(binding.sessionId, binding);
21
+ }
22
+ }
23
+ catch (error) {
24
+ if (error.code !== 'ENOENT') {
25
+ this.bindings.clear();
26
+ this.loadError = error;
27
+ }
28
+ }
29
+ }
30
+ get(id) {
31
+ const binding = this.bindings.get(id);
32
+ return binding ? structuredClone(binding) : null;
33
+ }
34
+ assertReadable() {
35
+ if (this.loadError)
36
+ throw new Error('COLLABORATION_ROUTING_STORE_UNREADABLE', { cause: this.loadError });
37
+ }
38
+ ownerOfBackend(id) {
39
+ return [...this.bindings.values()].find((binding) => binding.backendSessionId === id)?.sessionId ?? null;
40
+ }
41
+ bind(binding) {
42
+ this.assertReadable();
43
+ const owner = binding.backendSessionId ? this.ownerOfBackend(binding.backendSessionId) : null;
44
+ if (owner && owner !== binding.sessionId)
45
+ throw new Error('BACKEND_ALREADY_BOUND');
46
+ if (JSON.stringify(this.bindings.get(binding.sessionId)) === JSON.stringify(binding))
47
+ return;
48
+ const next = new Map(this.bindings);
49
+ next.set(binding.sessionId, structuredClone(binding));
50
+ this.persist(next);
51
+ }
52
+ remove(id) {
53
+ if (!this.bindings.has(id))
54
+ return;
55
+ const next = new Map(this.bindings);
56
+ next.delete(id);
57
+ this.persist(next);
58
+ }
59
+ persist(next) {
60
+ this.assertReadable();
61
+ fs.mkdirSync(path.dirname(this.file), { recursive: true });
62
+ const temporary = `${this.file}.${process.pid}.tmp`;
63
+ fs.writeFileSync(temporary, JSON.stringify({ version: 1, bindings: [...next.values()] }), { mode: 0o600 });
64
+ fs.renameSync(temporary, this.file);
65
+ this.bindings = next;
66
+ }
67
+ }
68
+ // Once pinned, changing the active pane cannot change the recipient. A reused
69
+ // tmux name/pane after a server restart must not inherit an old peer's inbox.
70
+ export function selectCollaborationPane(binding, panes) {
71
+ if (binding.pane) {
72
+ const previous = binding.pane;
73
+ const pane = panes.find((candidate) => candidate.serverPid === previous.serverPid
74
+ && candidate.sessionId === previous.sessionId && candidate.paneId === previous.paneId && candidate.panePid === previous.panePid);
75
+ if (!pane)
76
+ return { state: 'identity-mismatch', reason: 'TMUX_PANE_CHANGED' };
77
+ if (!pane.agentSlug)
78
+ return { state: 'agent-exited', reason: 'AGENT_NOT_RUNNING' };
79
+ if (pane.agentSlug !== previous.agentSlug || (previous.nativeSessionId && pane.nativeSessionId && previous.nativeSessionId !== pane.nativeSessionId)) {
80
+ return { state: 'identity-mismatch', reason: 'AGENT_IDENTITY_CHANGED' };
81
+ }
82
+ return { state: 'ready', pane };
83
+ }
84
+ const candidates = panes.filter((pane) => pane.agentSlug && (!binding.agentSlug || binding.agentSlug === pane.agentSlug)
85
+ && (!binding.nativeSessionId || !pane.nativeSessionId || binding.nativeSessionId === pane.nativeSessionId));
86
+ const exact = binding.nativeSessionId ? candidates.filter((pane) => pane.nativeSessionId === binding.nativeSessionId) : [];
87
+ const matches = exact.length ? exact : candidates;
88
+ if (matches.length === 1)
89
+ return { state: 'ready', pane: matches[0] };
90
+ if (!matches.length && panes.some((pane) => pane.agentSlug))
91
+ return { state: 'identity-mismatch', reason: 'AGENT_IDENTITY_CHANGED' };
92
+ return matches.length > 1 ? { state: 'ambiguous', reason: 'MULTIPLE_AGENT_PANES' }
93
+ : { state: 'agent-exited', reason: 'AGENT_NOT_RUNNING_OR_IDENTITY_CHANGED' };
94
+ }
@@ -160,6 +160,10 @@ export class CollaborationStore {
160
160
  this.expire();
161
161
  return this.document.messages.filter((message) => message.toSessionId === sessionId && message.status === 'pending').length;
162
162
  }
163
+ pendingRecipients() {
164
+ this.expire();
165
+ return [...new Set(this.document.messages.filter((message) => message.status === 'pending').map((message) => message.toSessionId))];
166
+ }
163
167
  markDelivered(messageIds) {
164
168
  return this.updateStatus(messageIds, 'delivered');
165
169
  }
@@ -249,7 +253,8 @@ export class CollaborationStore {
249
253
  expire() {
250
254
  let changed = false;
251
255
  for (const message of this.document.messages)
252
- if (message.status === 'pending' && message.expiresAt && message.expiresAt <= Date.now()) {
256
+ if (message.status === 'pending' && message.expiresAt && message.expiresAt <= Date.now()
257
+ && this.document.transport?.[message.id]?.last_error !== 'DELIVERY_IN_PROGRESS') {
253
258
  message.status = 'expired';
254
259
  message.failureReason = 'MESSAGE_EXPIRED';
255
260
  changed = true;
@@ -0,0 +1,10 @@
1
+ import { buildBracketedSubmitBytes } from './promptDelivery.js';
2
+ export async function writeCollaborationTmuxPane(run, pane, prompt) {
3
+ const identity = (await run(['display-message', '-p', '-t', pane.paneId,
4
+ '#{pid}:#{session_id}:#{pane_id}:#{pane_pid}'])).trim();
5
+ if (identity !== `${pane.serverPid}:${pane.sessionId}:${pane.paneId}:${pane.panePid}`)
6
+ throw new Error('TMUX_PANE_CHANGED');
7
+ // A fixed pane target is independent of the current window, keyboard focus
8
+ // and browser presence. One invocation preserves the bracketed submit bytes.
9
+ await run(['send-keys', '-t', pane.paneId, '-l', '--', buildBracketedSubmitBytes(prompt)]);
10
+ }
@@ -0,0 +1,59 @@
1
+ export function resolveCollaborationBackend(record, records, backends, routing) {
2
+ routing.assertReadable();
3
+ const binding = routing.get(record.sessionId);
4
+ const target = binding ? { ...record, mode: binding.mode, tmuxSessionName: binding.tmuxSessionName,
5
+ backendSessionId: binding.backendSessionId,
6
+ agentResume: binding.agentSlug ? { slug: binding.agentSlug, sessionId: binding.nativeSessionId } : record.agentResume } : record;
7
+ const existing = target.backendSessionId ? backends.get(target.backendSessionId) : null;
8
+ const recovered = existing && existing.mode === target.mode && existing.tmuxSessionName === target.tmuxSessionName
9
+ ? [target.backendSessionId, existing]
10
+ : recoverSessionBinding({ ...target, backendSessionId: null }, records, backends, binding);
11
+ if (!recovered)
12
+ return null;
13
+ const [id, backend] = recovered;
14
+ if (backend.mode !== 'shell' && backend.mode !== 'tmux')
15
+ return null;
16
+ const owner = routing.ownerOfBackend(id);
17
+ if (owner && owner !== record.sessionId)
18
+ return null;
19
+ if (records.some((other) => other.sessionId !== record.sessionId && other.backendSessionId === id))
20
+ return null;
21
+ routing.bind({ sessionId: record.sessionId, backendSessionId: id, mode: backend.mode,
22
+ tmuxSessionName: backend.tmuxSessionName,
23
+ agentSlug: binding?.agentSlug ?? backend.agent?.slug ?? record.agentResume?.slug ?? null,
24
+ nativeSessionId: binding?.nativeSessionId ?? backend.agentSession?.sessionId ?? record.agentResume?.sessionId ?? null,
25
+ pane: binding?.pane ?? null });
26
+ return recovered;
27
+ }
28
+ // Match live runtime evidence, never the search index or a title/cwd heuristic.
29
+ export function recoverSessionBinding(record, records, backends, binding) {
30
+ if (binding && binding.sessionId !== record.sessionId)
31
+ return null;
32
+ if (record.backendSessionId && backends.has(record.backendSessionId))
33
+ return null;
34
+ const nativeSessionId = binding?.nativeSessionId ?? record.agentResume?.sessionId;
35
+ const agentSlug = binding?.agentSlug ?? record.agentResume?.slug;
36
+ const candidates = [...backends].filter(([id, backend]) => {
37
+ if (records.some((other) => other.sessionId !== record.sessionId && other.backendSessionId === id))
38
+ return false;
39
+ if (backend.mode !== record.mode)
40
+ return false;
41
+ if (record.tmuxSessionName && backend.tmuxSessionName !== record.tmuxSessionName)
42
+ return false;
43
+ const nativeId = backend.agentSession?.sessionId;
44
+ const slug = backend.agent?.slug;
45
+ if (agentSlug && slug && agentSlug !== slug)
46
+ return false;
47
+ if (nativeSessionId && nativeId && nativeSessionId !== nativeId)
48
+ return false;
49
+ if (record.agentResume && slug && record.agentResume.slug !== slug)
50
+ return false;
51
+ if (record.agentResume?.sessionId && nativeId && record.agentResume.sessionId !== nativeId)
52
+ return false;
53
+ const tmuxMatch = record.mode === 'tmux' && Boolean(record.tmuxSessionName)
54
+ && backend.tmuxSessionName === record.tmuxSessionName;
55
+ const nativeMatch = Boolean(nativeId) && nativeSessionId === nativeId && agentSlug === slug;
56
+ return tmuxMatch || nativeMatch;
57
+ });
58
+ return candidates.length === 1 ? candidates[0] : null;
59
+ }
@@ -1,3 +1,4 @@
1
+ import { desktopDirectTargets } from './federation/desktopTargets.js';
1
2
  import { createOpenAccessRouter } from './federation/openAccess.js';
2
3
  import { createPasswordLoginRouter } from './federation/passwordLoginRoutes.js';
3
4
  import { apiAccessGate, isTrustedLocalRequest } from './utils/apiAccess.js';
@@ -404,27 +405,42 @@ export function startServer(options = {}) {
404
405
  const { server, scheme } = createServerForApp(app, options);
405
406
  let entrySubjectAllowed = (_subjectId) => false;
406
407
  let routeInvitations;
407
- const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true);
408
+ let federationServiceId = '';
409
+ const desktopTargets = () => desktopDirectTargets(path.join(homedir(), '.termdock', 'desktop.json'), federationServiceId);
410
+ const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true, desktopTargets);
408
411
  const relayRouter = new RelayRouter({
409
412
  authenticate: context => context,
410
413
  allowRegister: (principal, serviceId) => routeAccess.allowRegister(principal, serviceId),
411
414
  allowRoute: (principal, serviceId) => routeAccess.allowRoute(principal, serviceId),
412
415
  });
413
416
  const dynamicDirectTargets = new Map();
414
- server.once('close', () => { for (const close of dynamicDirectTargets.values())
415
- close(); });
416
- try {
417
- const detachDirectTargets = attachRegisteredDirectTargets(relayRouter, routeAccess.configuredDirectTargets(), path.join(homedir(), '.termdock', 'federation'));
418
- server.once('close', detachDirectTargets);
419
- }
420
- catch {
421
- console.error('Configured direct routes unavailable; target access remains closed.');
422
- }
417
+ const refreshDirectTargets = () => {
418
+ const desired = new Map(routeAccess.configuredDirectTargets().filter(target => target.serviceId !== federationServiceId).map(target => [target.serviceId, target]));
419
+ for (const [id, registered] of dynamicDirectTargets) {
420
+ const target = desired.get(id);
421
+ if (!target || registered.signature !== JSON.stringify([target.url, target.caPath, target.caFingerprint256])) {
422
+ registered.close();
423
+ dynamicDirectTargets.delete(id);
424
+ }
425
+ }
426
+ for (const [id, target] of desired)
427
+ if (!dynamicDirectTargets.has(id)) {
428
+ try {
429
+ dynamicDirectTargets.set(id, { signature: JSON.stringify([target.url, target.caPath, target.caFingerprint256]), close: attachRegisteredDirectTargets(relayRouter, [target], path.join(homedir(), '.termdock', 'federation')) });
430
+ }
431
+ catch { /* A malformed or unavailable local route remains unusable. */ }
432
+ }
433
+ };
434
+ refreshDirectTargets();
435
+ const desktopRoutesTimer = setInterval(refreshDirectTargets, 2000);
436
+ desktopRoutesTimer.unref();
437
+ server.once('close', () => { clearInterval(desktopRoutesTimer); for (const target of dynamicDirectTargets.values())
438
+ target.close(); });
423
439
  server.once('close', () => relayRouter.close());
424
440
  const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
425
441
  terminal: handleTerminalWebSocket, control: handleControlWebSocket,
426
442
  }, {
427
- listRouteTargets: () => routeAccess.configuredTargets().map(target => ({ ...target, available: relayRouter.hasRoute(target.serviceId) })),
443
+ listRouteTargets: () => { refreshDirectTargets(); return routeAccess.configuredTargets().map(target => ({ ...target, available: relayRouter.hasRoute(target.serviceId) })); },
428
444
  listRouteAccess: () => routeInvitations?.list() || [],
429
445
  grantRouteAccess: async (issuerId, targetServiceId, subjectId, url) => {
430
446
  if (!routeInvitations)
@@ -438,8 +454,7 @@ export function startServer(options = {}) {
438
454
  if (!runtime.store.authorize({ subjectId: issuerId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed)
439
455
  throw new Error('AUTHORIZATION_DENIED');
440
456
  routeAccess.addDirectTarget(target);
441
- if (!dynamicDirectTargets.has(targetServiceId))
442
- dynamicDirectTargets.set(targetServiceId, attachRegisteredDirectTargets(relayRouter, [target]));
457
+ refreshDirectTargets();
443
458
  }
444
459
  return routeInvitations.grant(issuerId, targetServiceId, subjectId);
445
460
  },
@@ -458,6 +473,8 @@ export function startServer(options = {}) {
458
473
  },
459
474
  });
460
475
  void federation.then(runtime => {
476
+ federationServiceId = runtime.serviceId;
477
+ refreshDirectTargets();
461
478
  app.locals.passwordRuntime = runtime;
462
479
  entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
463
480
  routeInvitations = new RouteInvitationStore({
@@ -0,0 +1,64 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ const peer = (value) => typeof value === 'string' && /^[a-zA-Z0-9_.:-]{1,160}$/.test(value);
3
+ const fingerprint = (value) => typeof value === 'string' && /^(?:[\da-f]{2}:){31}[\da-f]{2}$/i.test(value);
4
+ function origin(value) {
5
+ if (typeof value !== 'string' || value.length > 2048)
6
+ return;
7
+ try {
8
+ const url = new URL(value);
9
+ if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '/')
10
+ return;
11
+ return url.origin;
12
+ }
13
+ catch {
14
+ return;
15
+ }
16
+ }
17
+ /** Read only this OS user's Desktop directory. The local service must itself
18
+ * have been pinned there before its administrator can discover other entries.
19
+ * This supplies addresses, never the Desktop client's device authorization.
20
+ */
21
+ export function desktopDirectTargets(file, localServiceId) {
22
+ if (!localServiceId)
23
+ return [];
24
+ try {
25
+ if (statSync(file).size > 1024 * 1024)
26
+ return [];
27
+ const data = JSON.parse(readFileSync(file, 'utf8'));
28
+ if (data?.version !== 1 || !Array.isArray(data.connections) || data.connections.length > 256)
29
+ return [];
30
+ const removed = new Set(Array.isArray(data.removedServiceKeys) ? data.removedServiceKeys : []);
31
+ const connections = data.connections.filter((item) => item && ![item.id, item.targetPeerId, item.serviceOrigin || item.url].some(key => removed.has(key)));
32
+ if (!connections.some((item) => item.targetPeerId === localServiceId))
33
+ return [];
34
+ const trusts = new Map();
35
+ if (Array.isArray(data.trustedCertificateAuthorities))
36
+ for (const trust of data.trustedCertificateAuthorities.slice(0, 256)) {
37
+ const url = origin(trust?.origin);
38
+ if (url && fingerprint(trust.fingerprint256))
39
+ trusts.set(url, trust.fingerprint256.toUpperCase());
40
+ }
41
+ const targets = new Map();
42
+ for (const item of connections) {
43
+ if (!peer(item.targetPeerId) || item.targetPeerId === localServiceId)
44
+ continue;
45
+ const url = origin(item.serviceOrigin || item.url);
46
+ if (!url)
47
+ continue;
48
+ // An entry-only bookmark does not supply a direct address for its target.
49
+ if ((!item.serviceOrigin && item.entryServiceId && item.entryServiceId !== item.targetPeerId)
50
+ || (Array.isArray(item.routes) && item.routes.some((route) => route && route.targetPeerId !== item.targetPeerId && origin(route.url) === url)))
51
+ continue;
52
+ const caFingerprint256 = trusts.get(url);
53
+ targets.set(item.targetPeerId, { serviceId: item.targetPeerId, url,
54
+ ...(typeof item.label === 'string' && item.label.trim() ? { label: item.label.trim().slice(0, 120) } : {}),
55
+ ...(caFingerprint256 ? { caFingerprint256 } : {}) });
56
+ if (targets.size >= 64)
57
+ break;
58
+ }
59
+ return [...targets.values()];
60
+ }
61
+ catch {
62
+ return [];
63
+ }
64
+ }
@@ -1,11 +1,14 @@
1
+ import { readPinnedCertificateAuthority } from './trustedCa.js';
1
2
  import { createIdentity, secureConnection } from './secureProtocol.js';
2
3
  import { socketDuplex } from './socketDuplex.js';
3
4
  import { readFileSync } from 'node:fs';
4
5
  import { resolve } from 'node:path';
5
6
  import WebSocket from 'ws';
6
7
  function endpoint(config) {
7
- if (!config || typeof config !== 'object' || Object.keys(config).some(key => !['serviceId', 'url', 'caPath'].includes(key)) || !/^[a-zA-Z0-9_.:-]{1,160}$/.test(config.serviceId) || typeof config.url !== 'string' || (config.caPath !== undefined && typeof config.caPath !== 'string'))
8
+ if (!config || typeof config !== 'object' || Object.keys(config).some(key => !['serviceId', 'url', 'caPath', 'caFingerprint256', 'label'].includes(key)) || !/^[a-zA-Z0-9_.:-]{1,160}$/.test(config.serviceId) || typeof config.url !== 'string' || (config.caPath !== undefined && typeof config.caPath !== 'string'))
8
9
  throw new Error('Invalid direct target configuration');
10
+ if ((config.caFingerprint256 !== undefined && (typeof config.caFingerprint256 !== 'string' || !/^(?:[\da-f]{2}:){31}[\da-f]{2}$/i.test(config.caFingerprint256))) || (config.caPath && config.caFingerprint256) || (config.label !== undefined && (typeof config.label !== 'string' || config.label.length > 120)))
11
+ throw new Error('Invalid direct target metadata');
9
12
  const url = new URL(config.url);
10
13
  if (!['https:', 'wss:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || !['/', '/api/federation/secure'].includes(url.pathname))
11
14
  throw new Error('Direct targets require a verified HTTPS/WSS origin');
@@ -24,7 +27,7 @@ export function attachRegisteredDirectTargets(router, configs, baseDir = process
24
27
  if (ids.has(config.serviceId))
25
28
  throw new Error('Duplicate direct target');
26
29
  ids.add(config.serviceId);
27
- return { serviceId: config.serviceId, url, ca: config.caPath ? readFileSync(resolve(baseDir, config.caPath)) : undefined };
30
+ return { serviceId: config.serviceId, url, caFingerprint256: config.caFingerprint256, ca: config.caPath ? readFileSync(resolve(baseDir, config.caPath)) : undefined };
28
31
  });
29
32
  let stopped = false;
30
33
  const sockets = new Set();
@@ -34,22 +37,25 @@ export function attachRegisteredDirectTargets(router, configs, baseDir = process
34
37
  ws.terminate(); };
35
38
  try {
36
39
  for (const target of targets)
37
- cleanups.push(router.registerDirect(target.serviceId, () => new Promise((accept, reject) => {
38
- if (stopped) {
39
- reject(new Error('Direct target stopped'));
40
- return;
41
- }
42
- const ws = new WebSocket(target.url, { ca: target.ca, rejectUnauthorized: true, followRedirects: false, handshakeTimeout: 10000, maxPayload: 256 * 1024 });
43
- sockets.add(ws);
44
- ws.once('close', () => sockets.delete(ws));
45
- const failed = () => { reject(new Error('Direct target unavailable')); ws.terminate(); };
46
- ws.once('error', failed);
47
- ws.once('close', failed);
48
- ws.once('open', () => { ws.off('error', failed); ws.off('close', failed); ws.on('error', () => ws.terminate()); if (stopped)
49
- failed();
50
- else
51
- accept(ws); });
52
- })));
40
+ cleanups.push(router.registerDirect(target.serviceId, async () => {
41
+ const ca = target.caFingerprint256 ? await readPinnedCertificateAuthority(target.url.replace(/^wss:/, 'https:'), target.caFingerprint256) : target.ca;
42
+ return new Promise((accept, reject) => {
43
+ if (stopped) {
44
+ reject(new Error('Direct target stopped'));
45
+ return;
46
+ }
47
+ const ws = new WebSocket(target.url, { ca, rejectUnauthorized: true, followRedirects: false, handshakeTimeout: 10000, maxPayload: 256 * 1024 });
48
+ sockets.add(ws);
49
+ ws.once('close', () => sockets.delete(ws));
50
+ const failed = () => { reject(new Error('Direct target unavailable')); ws.terminate(); };
51
+ ws.once('error', failed);
52
+ ws.once('close', failed);
53
+ ws.once('open', () => { ws.off('error', failed); ws.off('close', failed); ws.on('error', () => ws.terminate()); if (stopped)
54
+ failed();
55
+ else
56
+ accept(ws); });
57
+ });
58
+ }));
53
59
  }
54
60
  catch (error) {
55
61
  cleanup();
@@ -60,7 +66,9 @@ export function attachRegisteredDirectTargets(router, configs, baseDir = process
60
66
  /** An administrator enabling a new backup first proves the target's pinned
61
67
  * identity; HTTPS validation remains enabled throughout. */
62
68
  export async function verifyDirectTarget(config) {
63
- const ws = new WebSocket(endpoint(config), { rejectUnauthorized: true, followRedirects: false, handshakeTimeout: 5000, maxPayload: 256 * 1024 });
69
+ const url = endpoint(config);
70
+ const ca = config.caFingerprint256 ? await readPinnedCertificateAuthority(url.replace(/^wss:/, 'https:'), config.caFingerprint256) : config.caPath ? readFileSync(config.caPath) : undefined;
71
+ const ws = new WebSocket(url, { ca, rejectUnauthorized: true, followRedirects: false, handshakeTimeout: 5000, maxPayload: 256 * 1024 });
64
72
  const duplex = socketDuplex(ws);
65
73
  try {
66
74
  await new Promise((resolve, reject) => { ws.once('open', resolve); ws.once('error', reject); ws.once('close', () => reject(new Error('Target closed'))); });