termdock 1.4.231 → 1.4.232

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 (320) hide show
  1. package/.desktop-dist/main.js +9 -0
  2. package/dist/client/assets/{App-Bl-MnHlK.js → App-Di12Ej1O.js} +3 -3
  3. package/dist/client/assets/App-Di12Ej1O.js.br +0 -0
  4. package/dist/client/assets/App-Di12Ej1O.js.gz +0 -0
  5. package/dist/client/assets/{ChangeWalkthroughPanel-oVCcuJL3.js → ChangeWalkthroughPanel-pOBFJTxi.js} +1 -1
  6. package/dist/client/assets/ChangeWalkthroughPanel-pOBFJTxi.js.br +0 -0
  7. package/dist/client/assets/ChangeWalkthroughPanel-pOBFJTxi.js.gz +0 -0
  8. package/dist/client/assets/{DagPlayground-B33ZJSNC.js → DagPlayground-DWaLn2pa.js} +1 -1
  9. package/dist/client/assets/DagPlayground-DWaLn2pa.js.br +0 -0
  10. package/dist/client/assets/DagPlayground-DWaLn2pa.js.gz +0 -0
  11. package/dist/client/assets/{DiffLab-CZRP_SqH.js → DiffLab-DMvlScEP.js} +1 -1
  12. package/dist/client/assets/DiffLab-DMvlScEP.js.br +0 -0
  13. package/dist/client/assets/DiffLab-DMvlScEP.js.gz +0 -0
  14. package/dist/client/assets/{DiffReview-BheUk7E5.js → DiffReview-D1M9kVJy.js} +1 -1
  15. package/dist/client/assets/DiffReview-D1M9kVJy.js.br +0 -0
  16. package/dist/client/assets/DiffReview-D1M9kVJy.js.gz +0 -0
  17. package/dist/client/assets/{DiffReviewLab-DupHSbbd.js → DiffReviewLab-BVkhfOhb.js} +1 -1
  18. package/dist/client/assets/DiffReviewLab-BVkhfOhb.js.br +0 -0
  19. package/dist/client/assets/DiffReviewLab-BVkhfOhb.js.gz +0 -0
  20. package/dist/client/assets/{DiffViewer-Cd5EE8E9.js → DiffViewer-DN5Akyco.js} +1 -1
  21. package/dist/client/assets/DiffViewer-DN5Akyco.js.br +0 -0
  22. package/dist/client/assets/DiffViewer-DN5Akyco.js.gz +0 -0
  23. package/dist/client/assets/{ModelPreview-Fnuxnt8A.js → ModelPreview-D5Vl9PdY.js} +1 -1
  24. package/dist/client/assets/ModelPreview-D5Vl9PdY.js.br +0 -0
  25. package/dist/client/assets/ModelPreview-D5Vl9PdY.js.gz +0 -0
  26. package/dist/client/assets/{RightSidebar-DTTmdNhf.js → RightSidebar-CNICBrOL.js} +4 -4
  27. package/dist/client/assets/RightSidebar-CNICBrOL.js.br +0 -0
  28. package/dist/client/assets/RightSidebar-CNICBrOL.js.gz +0 -0
  29. package/dist/client/assets/{abnfDiagram-VCTEODGH-1oyW4gyC.js → abnfDiagram-VCTEODGH-Coq8shqQ.js} +1 -1
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-Coq8shqQ.js.br +0 -0
  31. package/dist/client/assets/abnfDiagram-VCTEODGH-Coq8shqQ.js.gz +0 -0
  32. package/dist/client/assets/app-BNPa5Xzm.js +35 -0
  33. package/dist/client/assets/app-BNPa5Xzm.js.br +0 -0
  34. package/dist/client/assets/app-BNPa5Xzm.js.gz +0 -0
  35. package/dist/client/assets/{arc-e9P469h4.js → arc-D9F88oM8.js} +1 -1
  36. package/dist/client/assets/arc-D9F88oM8.js.br +0 -0
  37. package/dist/client/assets/arc-D9F88oM8.js.gz +0 -0
  38. package/dist/client/assets/{architectureDiagram-5GKGNRK7-CloNCKLm.js → architectureDiagram-5GKGNRK7-Ci04e9H2.js} +1 -1
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-Ci04e9H2.js.br +0 -0
  40. package/dist/client/assets/architectureDiagram-5GKGNRK7-Ci04e9H2.js.gz +0 -0
  41. package/dist/client/assets/{blockDiagram-I7D4REHJ-D3wH4hOJ.js → blockDiagram-I7D4REHJ-B0V-2mCO.js} +1 -1
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-B0V-2mCO.js.br +0 -0
  43. package/dist/client/assets/blockDiagram-I7D4REHJ-B0V-2mCO.js.gz +0 -0
  44. package/dist/client/assets/{c4Diagram-7LVT6UL2-CX5tSHre.js → c4Diagram-7LVT6UL2-CgHFeNkR.js} +1 -1
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-CgHFeNkR.js.br +0 -0
  46. package/dist/client/assets/c4Diagram-7LVT6UL2-CgHFeNkR.js.gz +0 -0
  47. package/dist/client/assets/channel-Cf-_AhxJ.js +1 -0
  48. package/dist/client/assets/{chunk-2Q5K7J3B-Weq7o_yW.js → chunk-2Q5K7J3B-C1R3c0d8.js} +1 -1
  49. package/dist/client/assets/{chunk-5VM5RSS4-B1O3hmJE.js → chunk-5VM5RSS4-USD4Kst0.js} +1 -1
  50. package/dist/client/assets/{chunk-F27PBJKO-BRYx-Dtz.js → chunk-F27PBJKO-D-1deMoU.js} +1 -1
  51. package/dist/client/assets/chunk-F27PBJKO-D-1deMoU.js.br +0 -0
  52. package/dist/client/assets/chunk-F27PBJKO-D-1deMoU.js.gz +0 -0
  53. package/dist/client/assets/{chunk-IMKFNOWR-z6lXRY7P.js → chunk-IMKFNOWR-DyunJ4J2.js} +1 -1
  54. package/dist/client/assets/chunk-IMKFNOWR-DyunJ4J2.js.br +0 -0
  55. package/dist/client/assets/chunk-IMKFNOWR-DyunJ4J2.js.gz +0 -0
  56. package/dist/client/assets/{chunk-JWPE2WC7-DdDHAlPc.js → chunk-JWPE2WC7-ClM1w-Bz.js} +1 -1
  57. package/dist/client/assets/{chunk-POPQ4Y6H-BYbFuciT.js → chunk-POPQ4Y6H-Dmz_lI83.js} +1 -1
  58. package/dist/client/assets/{chunk-SVP7TREG-Bk-X0NaV.js → chunk-SVP7TREG-JSkpe0Ru.js} +1 -1
  59. package/dist/client/assets/chunk-SVP7TREG-JSkpe0Ru.js.br +0 -0
  60. package/dist/client/assets/chunk-SVP7TREG-JSkpe0Ru.js.gz +0 -0
  61. package/dist/client/assets/{chunk-TICWLB2K-BjUqsphB.js → chunk-TICWLB2K-DNRVmHct.js} +1 -1
  62. package/dist/client/assets/chunk-TICWLB2K-DNRVmHct.js.br +0 -0
  63. package/dist/client/assets/chunk-TICWLB2K-DNRVmHct.js.gz +0 -0
  64. package/dist/client/assets/{chunk-XXDRQBXY-DSXlNway.js → chunk-XXDRQBXY-C_nQBcA8.js} +1 -1
  65. package/dist/client/assets/classDiagram-ZZMXUADV-CCnYK6Cj.js +1 -0
  66. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CCnYK6Cj.js +1 -0
  67. package/dist/client/assets/{cose-bilkent-JH36ORCC-Bw8F9aUq.js → cose-bilkent-JH36ORCC-D2_1Vg5L.js} +1 -1
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-D2_1Vg5L.js.br +0 -0
  69. package/dist/client/assets/cose-bilkent-JH36ORCC-D2_1Vg5L.js.gz +0 -0
  70. package/dist/client/assets/{cynefin-OW5HDTMX-C5oB4ZTA.js → cynefin-OW5HDTMX-C_zLtwow.js} +1 -1
  71. package/dist/client/assets/cynefin-OW5HDTMX-C_zLtwow.js.br +0 -0
  72. package/dist/client/assets/{cynefin-OW5HDTMX-C5oB4ZTA.js.gz → cynefin-OW5HDTMX-C_zLtwow.js.gz} +0 -0
  73. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BkbwVdAD.js → cynefinDiagram-5FMLGOSQ-B0Wvfl0v.js} +1 -1
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B0Wvfl0v.js.br +0 -0
  75. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B0Wvfl0v.js.gz +0 -0
  76. package/dist/client/assets/{dagre-GXQ25YYZ-8Jt1so7H.js → dagre-GXQ25YYZ-D4dD95T3.js} +1 -1
  77. package/dist/client/assets/dagre-GXQ25YYZ-D4dD95T3.js.br +0 -0
  78. package/dist/client/assets/dagre-GXQ25YYZ-D4dD95T3.js.gz +0 -0
  79. package/dist/client/assets/{diagram-S7CK7UJ4-DyWUtoUO.js → diagram-S7CK7UJ4-2nnj1lep.js} +1 -1
  80. package/dist/client/assets/diagram-S7CK7UJ4-2nnj1lep.js.br +0 -0
  81. package/dist/client/assets/diagram-S7CK7UJ4-2nnj1lep.js.gz +0 -0
  82. package/dist/client/assets/{diagram-UQ7AKVKN-BBnqsz2l.js → diagram-UQ7AKVKN-C9N4zQ8r.js} +1 -1
  83. package/dist/client/assets/diagram-UQ7AKVKN-C9N4zQ8r.js.br +0 -0
  84. package/dist/client/assets/diagram-UQ7AKVKN-C9N4zQ8r.js.gz +0 -0
  85. package/dist/client/assets/{diagram-VSXAHHWV-DQ2JxOjW.js → diagram-VSXAHHWV-Cv7_lYe4.js} +1 -1
  86. package/dist/client/assets/diagram-VSXAHHWV-Cv7_lYe4.js.br +0 -0
  87. package/dist/client/assets/diagram-VSXAHHWV-Cv7_lYe4.js.gz +0 -0
  88. package/dist/client/assets/{diagram-VX7I27RA-Das3jLN9.js → diagram-VX7I27RA-BcgskUTT.js} +1 -1
  89. package/dist/client/assets/diagram-VX7I27RA-BcgskUTT.js.br +0 -0
  90. package/dist/client/assets/diagram-VX7I27RA-BcgskUTT.js.gz +0 -0
  91. package/dist/client/assets/{diagram-Z3DM3KII-0gbBMfrq.js → diagram-Z3DM3KII-v37kk7HI.js} +1 -1
  92. package/dist/client/assets/diagram-Z3DM3KII-v37kk7HI.js.br +0 -0
  93. package/dist/client/assets/diagram-Z3DM3KII-v37kk7HI.js.gz +0 -0
  94. package/dist/client/assets/{ebnfDiagram-PWID7BFC-DpAG2pFv.js → ebnfDiagram-PWID7BFC-CWRegGRp.js} +1 -1
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-CWRegGRp.js.br +0 -0
  96. package/dist/client/assets/ebnfDiagram-PWID7BFC-CWRegGRp.js.gz +0 -0
  97. package/dist/client/assets/{erDiagram-RLTQ6QDP-x4B0lUNv.js → erDiagram-RLTQ6QDP-C-a8Ptae.js} +1 -1
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-C-a8Ptae.js.br +0 -0
  99. package/dist/client/assets/erDiagram-RLTQ6QDP-C-a8Ptae.js.gz +0 -0
  100. package/dist/client/assets/{flowDiagram-HODETNUW-Dx34Jzbo.js → flowDiagram-HODETNUW-CZQBLCvO.js} +1 -1
  101. package/dist/client/assets/{flowDiagram-HODETNUW-Dx34Jzbo.js.br → flowDiagram-HODETNUW-CZQBLCvO.js.br} +0 -0
  102. package/dist/client/assets/flowDiagram-HODETNUW-CZQBLCvO.js.gz +0 -0
  103. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-C7fWuwM6.js → ganttDiagram-EL5Y4UJY-CDJSVMvA.js} +1 -1
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CDJSVMvA.js.br +0 -0
  105. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CDJSVMvA.js.gz +0 -0
  106. package/dist/client/assets/{gestureArbiter-CwYdj3ki.js → gestureArbiter-3yU_eQSh.js} +1 -1
  107. package/dist/client/assets/gestureArbiter-3yU_eQSh.js.br +0 -0
  108. package/dist/client/assets/{gestureArbiter-CwYdj3ki.js.gz → gestureArbiter-3yU_eQSh.js.gz} +0 -0
  109. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-B6QlpyGY.js → gitGraphDiagram-WWUBYQGX-BpKnzvx5.js} +1 -1
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BpKnzvx5.js.br +0 -0
  111. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BpKnzvx5.js.gz +0 -0
  112. package/dist/client/assets/{infoDiagram-27XIBGKW-D-cyvLtB.js → infoDiagram-27XIBGKW-CvIbo5Yc.js} +1 -1
  113. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Bji-6SyT.js → ishikawaDiagram-5VMMS53U-iTcusz9p.js} +1 -1
  114. package/dist/client/assets/ishikawaDiagram-5VMMS53U-iTcusz9p.js.br +0 -0
  115. package/dist/client/assets/ishikawaDiagram-5VMMS53U-iTcusz9p.js.gz +0 -0
  116. package/dist/client/assets/{journeyDiagram-3NMN7TZE-FTfWDJ8K.js → journeyDiagram-3NMN7TZE-AnHrPTVQ.js} +1 -1
  117. package/dist/client/assets/journeyDiagram-3NMN7TZE-AnHrPTVQ.js.br +0 -0
  118. package/dist/client/assets/journeyDiagram-3NMN7TZE-AnHrPTVQ.js.gz +0 -0
  119. package/dist/client/assets/{kanban-definition-UXKFOSKX-CvrvLpNJ.js → kanban-definition-UXKFOSKX-BSGQKNkn.js} +1 -1
  120. package/dist/client/assets/kanban-definition-UXKFOSKX-BSGQKNkn.js.br +0 -0
  121. package/dist/client/assets/kanban-definition-UXKFOSKX-BSGQKNkn.js.gz +0 -0
  122. package/dist/client/assets/{layout-D04TPU52.js → layout-DWfvuKxK.js} +1 -1
  123. package/dist/client/assets/layout-DWfvuKxK.js.br +0 -0
  124. package/dist/client/assets/layout-DWfvuKxK.js.gz +0 -0
  125. package/dist/client/assets/{linear-tmc9NdkO.js → linear-YGUC9oFd.js} +1 -1
  126. package/dist/client/assets/linear-YGUC9oFd.js.br +0 -0
  127. package/dist/client/assets/linear-YGUC9oFd.js.gz +0 -0
  128. package/dist/client/assets/{mermaid.core-BghOeJM9.js → mermaid.core-C2ofdbDu.js} +5 -5
  129. package/dist/client/assets/mermaid.core-C2ofdbDu.js.br +0 -0
  130. package/dist/client/assets/mermaid.core-C2ofdbDu.js.gz +0 -0
  131. package/dist/client/assets/{mindmap-definition-YA3MSWOX-BEhTGgsF.js → mindmap-definition-YA3MSWOX-DhpmPHTj.js} +1 -1
  132. package/dist/client/assets/mindmap-definition-YA3MSWOX-DhpmPHTj.js.br +0 -0
  133. package/dist/client/assets/mindmap-definition-YA3MSWOX-DhpmPHTj.js.gz +0 -0
  134. package/dist/client/assets/{passwordBootstrap-wvzKljMS.js → passwordBootstrap-VsBMR4A9.js} +1 -1
  135. package/dist/client/assets/passwordBootstrap-VsBMR4A9.js.br +0 -0
  136. package/dist/client/assets/passwordBootstrap-VsBMR4A9.js.gz +0 -0
  137. package/dist/client/assets/{pegDiagram-XKGWAZYB-BX1V7YDa.js → pegDiagram-XKGWAZYB-BRFYfMc1.js} +1 -1
  138. package/dist/client/assets/pegDiagram-XKGWAZYB-BRFYfMc1.js.br +0 -0
  139. package/dist/client/assets/pegDiagram-XKGWAZYB-BRFYfMc1.js.gz +0 -0
  140. package/dist/client/assets/{pieDiagram-E7YTZNPT-D5LMv0Qe.js → pieDiagram-E7YTZNPT-B3r-kmkG.js} +1 -1
  141. package/dist/client/assets/pieDiagram-E7YTZNPT-B3r-kmkG.js.br +0 -0
  142. package/dist/client/assets/pieDiagram-E7YTZNPT-B3r-kmkG.js.gz +0 -0
  143. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-4fzegqtV.js → quadrantDiagram-AXDQQJYC-DR1I_zXC.js} +1 -1
  144. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DR1I_zXC.js.br +0 -0
  145. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DR1I_zXC.js.gz +0 -0
  146. package/dist/client/assets/{railroadDiagram-O6MQD6OU-bgwdyvOY.js → railroadDiagram-O6MQD6OU-Bvfl-CvM.js} +1 -1
  147. package/dist/client/assets/railroadDiagram-O6MQD6OU-Bvfl-CvM.js.br +0 -0
  148. package/dist/client/assets/railroadDiagram-O6MQD6OU-Bvfl-CvM.js.gz +0 -0
  149. package/dist/client/assets/{referenceLongPress-Uw7_VECn.js → referenceLongPress-X-OUHgyO.js} +1 -1
  150. package/dist/client/assets/referenceLongPress-X-OUHgyO.js.br +0 -0
  151. package/dist/client/assets/referenceLongPress-X-OUHgyO.js.gz +0 -0
  152. package/dist/client/assets/{requirementDiagram-BXWQKSXE-CZhlA-YX.js → requirementDiagram-BXWQKSXE-C77Ip0mm.js} +1 -1
  153. package/dist/client/assets/requirementDiagram-BXWQKSXE-C77Ip0mm.js.br +0 -0
  154. package/dist/client/assets/requirementDiagram-BXWQKSXE-C77Ip0mm.js.gz +0 -0
  155. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-BSLsSYgS.js → sankeyDiagram-P5KCCOFB-C1irBgQp.js} +1 -1
  156. package/dist/client/assets/sankeyDiagram-P5KCCOFB-C1irBgQp.js.br +0 -0
  157. package/dist/client/assets/sankeyDiagram-P5KCCOFB-C1irBgQp.js.gz +0 -0
  158. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-D5q4HIce.js → sequenceDiagram-WJ2MYXX4-ziivSu_M.js} +1 -1
  159. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-ziivSu_M.js.br +0 -0
  160. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-ziivSu_M.js.gz +0 -0
  161. package/dist/client/assets/{sizeCapture-INFHLROL-C4i6fklz.js → sizeCapture-INFHLROL-Bd6W2W84.js} +1 -1
  162. package/dist/client/assets/sizeCapture-INFHLROL-Bd6W2W84.js.br +0 -0
  163. package/dist/client/assets/sizeCapture-INFHLROL-Bd6W2W84.js.gz +0 -0
  164. package/dist/client/assets/{sparkles-BKHm6SJH.js → sparkles-CnZrFoGY.js} +1 -1
  165. package/dist/client/assets/{stateDiagram-D77RDMKH-C7ooQSEd.js → stateDiagram-D77RDMKH-Dbp0hGDY.js} +1 -1
  166. package/dist/client/assets/stateDiagram-D77RDMKH-Dbp0hGDY.js.br +0 -0
  167. package/dist/client/assets/stateDiagram-D77RDMKH-Dbp0hGDY.js.gz +0 -0
  168. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DC2wSMDB.js +1 -0
  169. package/dist/client/assets/{swimlanes-42K2YHIH-BvYBqq7o.js → swimlanes-42K2YHIH-C2Dr-53Y.js} +1 -1
  170. package/dist/client/assets/swimlanes-42K2YHIH-C2Dr-53Y.js.br +0 -0
  171. package/dist/client/assets/swimlanes-42K2YHIH-C2Dr-53Y.js.gz +0 -0
  172. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-HYd7_s0l.js +8 -0
  173. package/dist/client/assets/{timeline-definition-24CTP7MA-Chgyw7xv.js → timeline-definition-24CTP7MA-D-bKj_yT.js} +1 -1
  174. package/dist/client/assets/timeline-definition-24CTP7MA-D-bKj_yT.js.br +0 -0
  175. package/dist/client/assets/timeline-definition-24CTP7MA-D-bKj_yT.js.gz +0 -0
  176. package/dist/client/assets/{triangle-alert-CcBfd16t.js → triangle-alert-D1EDVJ4z.js} +1 -1
  177. package/dist/client/assets/{vennDiagram-4TSXK5OY-CF1lMDLm.js → vennDiagram-4TSXK5OY-Dxo8DU3z.js} +1 -1
  178. package/dist/client/assets/vennDiagram-4TSXK5OY-Dxo8DU3z.js.br +0 -0
  179. package/dist/client/assets/vennDiagram-4TSXK5OY-Dxo8DU3z.js.gz +0 -0
  180. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-CxUB2Gwu.js → wardleyDiagram-VM6X3IG4-BZMkLNpR.js} +1 -1
  181. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BZMkLNpR.js.br +0 -0
  182. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BZMkLNpR.js.gz +0 -0
  183. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-D1FK5tqF.js → xychartDiagram-S5SC5T6Z-CbxLQ8Sa.js} +1 -1
  184. package/dist/client/assets/xychartDiagram-S5SC5T6Z-CbxLQ8Sa.js.br +0 -0
  185. package/dist/client/assets/xychartDiagram-S5SC5T6Z-CbxLQ8Sa.js.gz +0 -0
  186. package/dist/client/index.html +1 -1
  187. package/dist/client/index.html.br +0 -0
  188. package/dist/client/index.html.gz +0 -0
  189. package/dist/client/sw.js +1 -1
  190. package/dist/client/sw.js.br +0 -0
  191. package/dist/client/sw.js.gz +0 -0
  192. package/dist/server/agent/collaborationCli.js +113 -33
  193. package/dist/server/agent/collaborationPeerTransport.js +441 -0
  194. package/dist/server/agent/collaborationPrompt.js +10 -4
  195. package/dist/server/agent/collaborationRoutes.js +75 -15
  196. package/dist/server/agent/collaborationStore.js +108 -23
  197. package/dist/server/agent/collaborationText.js +5 -0
  198. package/dist/server/agent/collaborationTmuxDelivery.js +9 -6
  199. package/dist/server/entry.js +42 -2
  200. package/dist/server/federation/runtime.js +7 -2
  201. package/dist/server/routes/terminal.js +48 -20
  202. package/package.json +1 -1
  203. package/runtime-manifest.json +3 -3
  204. package/dist/client/assets/App-Bl-MnHlK.js.br +0 -0
  205. package/dist/client/assets/App-Bl-MnHlK.js.gz +0 -0
  206. package/dist/client/assets/ChangeWalkthroughPanel-oVCcuJL3.js.br +0 -0
  207. package/dist/client/assets/ChangeWalkthroughPanel-oVCcuJL3.js.gz +0 -0
  208. package/dist/client/assets/DagPlayground-B33ZJSNC.js.br +0 -0
  209. package/dist/client/assets/DagPlayground-B33ZJSNC.js.gz +0 -0
  210. package/dist/client/assets/DiffLab-CZRP_SqH.js.br +0 -0
  211. package/dist/client/assets/DiffLab-CZRP_SqH.js.gz +0 -0
  212. package/dist/client/assets/DiffReview-BheUk7E5.js.br +0 -0
  213. package/dist/client/assets/DiffReview-BheUk7E5.js.gz +0 -0
  214. package/dist/client/assets/DiffReviewLab-DupHSbbd.js.br +0 -0
  215. package/dist/client/assets/DiffReviewLab-DupHSbbd.js.gz +0 -0
  216. package/dist/client/assets/DiffViewer-Cd5EE8E9.js.br +0 -0
  217. package/dist/client/assets/DiffViewer-Cd5EE8E9.js.gz +0 -0
  218. package/dist/client/assets/ModelPreview-Fnuxnt8A.js.br +0 -0
  219. package/dist/client/assets/ModelPreview-Fnuxnt8A.js.gz +0 -0
  220. package/dist/client/assets/RightSidebar-DTTmdNhf.js.br +0 -0
  221. package/dist/client/assets/RightSidebar-DTTmdNhf.js.gz +0 -0
  222. package/dist/client/assets/abnfDiagram-VCTEODGH-1oyW4gyC.js.br +0 -0
  223. package/dist/client/assets/abnfDiagram-VCTEODGH-1oyW4gyC.js.gz +0 -0
  224. package/dist/client/assets/app-CGZYqW3s.js +0 -35
  225. package/dist/client/assets/app-CGZYqW3s.js.br +0 -0
  226. package/dist/client/assets/app-CGZYqW3s.js.gz +0 -0
  227. package/dist/client/assets/arc-e9P469h4.js.br +0 -0
  228. package/dist/client/assets/arc-e9P469h4.js.gz +0 -0
  229. package/dist/client/assets/architectureDiagram-5GKGNRK7-CloNCKLm.js.br +0 -0
  230. package/dist/client/assets/architectureDiagram-5GKGNRK7-CloNCKLm.js.gz +0 -0
  231. package/dist/client/assets/blockDiagram-I7D4REHJ-D3wH4hOJ.js.br +0 -0
  232. package/dist/client/assets/blockDiagram-I7D4REHJ-D3wH4hOJ.js.gz +0 -0
  233. package/dist/client/assets/c4Diagram-7LVT6UL2-CX5tSHre.js.br +0 -0
  234. package/dist/client/assets/c4Diagram-7LVT6UL2-CX5tSHre.js.gz +0 -0
  235. package/dist/client/assets/channel-4mkr49cg.js +0 -1
  236. package/dist/client/assets/chunk-F27PBJKO-BRYx-Dtz.js.br +0 -0
  237. package/dist/client/assets/chunk-F27PBJKO-BRYx-Dtz.js.gz +0 -0
  238. package/dist/client/assets/chunk-IMKFNOWR-z6lXRY7P.js.br +0 -0
  239. package/dist/client/assets/chunk-IMKFNOWR-z6lXRY7P.js.gz +0 -0
  240. package/dist/client/assets/chunk-SVP7TREG-Bk-X0NaV.js.br +0 -0
  241. package/dist/client/assets/chunk-SVP7TREG-Bk-X0NaV.js.gz +0 -0
  242. package/dist/client/assets/chunk-TICWLB2K-BjUqsphB.js.br +0 -0
  243. package/dist/client/assets/chunk-TICWLB2K-BjUqsphB.js.gz +0 -0
  244. package/dist/client/assets/classDiagram-ZZMXUADV-DxYPnGqp.js +0 -1
  245. package/dist/client/assets/classDiagram-v2-VYDZK3BY-DxYPnGqp.js +0 -1
  246. package/dist/client/assets/cose-bilkent-JH36ORCC-Bw8F9aUq.js.br +0 -0
  247. package/dist/client/assets/cose-bilkent-JH36ORCC-Bw8F9aUq.js.gz +0 -0
  248. package/dist/client/assets/cynefin-OW5HDTMX-C5oB4ZTA.js.br +0 -0
  249. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BkbwVdAD.js.br +0 -0
  250. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BkbwVdAD.js.gz +0 -0
  251. package/dist/client/assets/dagre-GXQ25YYZ-8Jt1so7H.js.br +0 -0
  252. package/dist/client/assets/dagre-GXQ25YYZ-8Jt1so7H.js.gz +0 -0
  253. package/dist/client/assets/diagram-S7CK7UJ4-DyWUtoUO.js.br +0 -0
  254. package/dist/client/assets/diagram-S7CK7UJ4-DyWUtoUO.js.gz +0 -0
  255. package/dist/client/assets/diagram-UQ7AKVKN-BBnqsz2l.js.br +0 -0
  256. package/dist/client/assets/diagram-UQ7AKVKN-BBnqsz2l.js.gz +0 -0
  257. package/dist/client/assets/diagram-VSXAHHWV-DQ2JxOjW.js.br +0 -0
  258. package/dist/client/assets/diagram-VSXAHHWV-DQ2JxOjW.js.gz +0 -0
  259. package/dist/client/assets/diagram-VX7I27RA-Das3jLN9.js.br +0 -0
  260. package/dist/client/assets/diagram-VX7I27RA-Das3jLN9.js.gz +0 -0
  261. package/dist/client/assets/diagram-Z3DM3KII-0gbBMfrq.js.br +0 -0
  262. package/dist/client/assets/diagram-Z3DM3KII-0gbBMfrq.js.gz +0 -0
  263. package/dist/client/assets/ebnfDiagram-PWID7BFC-DpAG2pFv.js.br +0 -0
  264. package/dist/client/assets/ebnfDiagram-PWID7BFC-DpAG2pFv.js.gz +0 -0
  265. package/dist/client/assets/erDiagram-RLTQ6QDP-x4B0lUNv.js.br +0 -0
  266. package/dist/client/assets/erDiagram-RLTQ6QDP-x4B0lUNv.js.gz +0 -0
  267. package/dist/client/assets/flowDiagram-HODETNUW-Dx34Jzbo.js.gz +0 -0
  268. package/dist/client/assets/ganttDiagram-EL5Y4UJY-C7fWuwM6.js.br +0 -0
  269. package/dist/client/assets/ganttDiagram-EL5Y4UJY-C7fWuwM6.js.gz +0 -0
  270. package/dist/client/assets/gestureArbiter-CwYdj3ki.js.br +0 -0
  271. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-B6QlpyGY.js.br +0 -0
  272. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-B6QlpyGY.js.gz +0 -0
  273. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Bji-6SyT.js.br +0 -0
  274. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Bji-6SyT.js.gz +0 -0
  275. package/dist/client/assets/journeyDiagram-3NMN7TZE-FTfWDJ8K.js.br +0 -0
  276. package/dist/client/assets/journeyDiagram-3NMN7TZE-FTfWDJ8K.js.gz +0 -0
  277. package/dist/client/assets/kanban-definition-UXKFOSKX-CvrvLpNJ.js.br +0 -0
  278. package/dist/client/assets/kanban-definition-UXKFOSKX-CvrvLpNJ.js.gz +0 -0
  279. package/dist/client/assets/layout-D04TPU52.js.br +0 -0
  280. package/dist/client/assets/layout-D04TPU52.js.gz +0 -0
  281. package/dist/client/assets/linear-tmc9NdkO.js.br +0 -0
  282. package/dist/client/assets/linear-tmc9NdkO.js.gz +0 -0
  283. package/dist/client/assets/mermaid.core-BghOeJM9.js.br +0 -0
  284. package/dist/client/assets/mermaid.core-BghOeJM9.js.gz +0 -0
  285. package/dist/client/assets/mindmap-definition-YA3MSWOX-BEhTGgsF.js.br +0 -0
  286. package/dist/client/assets/mindmap-definition-YA3MSWOX-BEhTGgsF.js.gz +0 -0
  287. package/dist/client/assets/passwordBootstrap-wvzKljMS.js.br +0 -0
  288. package/dist/client/assets/passwordBootstrap-wvzKljMS.js.gz +0 -0
  289. package/dist/client/assets/pegDiagram-XKGWAZYB-BX1V7YDa.js.br +0 -0
  290. package/dist/client/assets/pegDiagram-XKGWAZYB-BX1V7YDa.js.gz +0 -0
  291. package/dist/client/assets/pieDiagram-E7YTZNPT-D5LMv0Qe.js.br +0 -0
  292. package/dist/client/assets/pieDiagram-E7YTZNPT-D5LMv0Qe.js.gz +0 -0
  293. package/dist/client/assets/quadrantDiagram-AXDQQJYC-4fzegqtV.js.br +0 -0
  294. package/dist/client/assets/quadrantDiagram-AXDQQJYC-4fzegqtV.js.gz +0 -0
  295. package/dist/client/assets/railroadDiagram-O6MQD6OU-bgwdyvOY.js.br +0 -0
  296. package/dist/client/assets/railroadDiagram-O6MQD6OU-bgwdyvOY.js.gz +0 -0
  297. package/dist/client/assets/referenceLongPress-Uw7_VECn.js.br +0 -0
  298. package/dist/client/assets/referenceLongPress-Uw7_VECn.js.gz +0 -0
  299. package/dist/client/assets/requirementDiagram-BXWQKSXE-CZhlA-YX.js.br +0 -0
  300. package/dist/client/assets/requirementDiagram-BXWQKSXE-CZhlA-YX.js.gz +0 -0
  301. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BSLsSYgS.js.br +0 -0
  302. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BSLsSYgS.js.gz +0 -0
  303. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D5q4HIce.js.br +0 -0
  304. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D5q4HIce.js.gz +0 -0
  305. package/dist/client/assets/sizeCapture-INFHLROL-C4i6fklz.js.br +0 -0
  306. package/dist/client/assets/sizeCapture-INFHLROL-C4i6fklz.js.gz +0 -0
  307. package/dist/client/assets/stateDiagram-D77RDMKH-C7ooQSEd.js.br +0 -0
  308. package/dist/client/assets/stateDiagram-D77RDMKH-C7ooQSEd.js.gz +0 -0
  309. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CYPjXGda.js +0 -1
  310. package/dist/client/assets/swimlanes-42K2YHIH-BvYBqq7o.js.br +0 -0
  311. package/dist/client/assets/swimlanes-42K2YHIH-BvYBqq7o.js.gz +0 -0
  312. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DGury37v.js +0 -8
  313. package/dist/client/assets/timeline-definition-24CTP7MA-Chgyw7xv.js.br +0 -0
  314. package/dist/client/assets/timeline-definition-24CTP7MA-Chgyw7xv.js.gz +0 -0
  315. package/dist/client/assets/vennDiagram-4TSXK5OY-CF1lMDLm.js.br +0 -0
  316. package/dist/client/assets/vennDiagram-4TSXK5OY-CF1lMDLm.js.gz +0 -0
  317. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CxUB2Gwu.js.br +0 -0
  318. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CxUB2Gwu.js.gz +0 -0
  319. package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1FK5tqF.js.br +0 -0
  320. package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1FK5tqF.js.gz +0 -0
@@ -1,7 +1,14 @@
1
+ import { plainCollaborationSnapshot } from './collaborationText.js';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import crypto from 'node:crypto';
4
5
  import { COLLAB_LIMITS, CollaborationError, STATUS_RANK, canonicalShortId, drawCollaborationId, newCollaborationId, resolveIdPrefix, validateExtras } from './collaborationProtocol.js';
6
+ function newerRevision(a, b) {
7
+ return !b || a.updatedAt > b.updatedAt || (a.updatedAt === b.updatedAt && a.version > b.version);
8
+ }
9
+ function validRevision(value) {
10
+ return !!value && Number.isSafeInteger(value.updatedAt) && value.updatedAt >= 0 && typeof value.version === 'string' && /^[a-zA-Z0-9-]{1,80}$/.test(value.version);
11
+ }
5
12
  /** Characters that would corrupt a role line injected into the delivery shell
6
13
  * (`你的定位:...`) or smuggle terminal output. Unlike names, roles are plain
7
14
  * prose — 「」 and · are harmless there — so only control sequences are barred. */
@@ -21,11 +28,29 @@ function pruneRoles(group, sessionIds) {
21
28
  const roles = Object.fromEntries(Object.entries(group.roles).filter(([id]) => sessionIds.includes(id)));
22
29
  return Object.keys(roles).length ? { ...group, roles } : { ...group, roles: undefined };
23
30
  }
31
+ /** Public collaboration facts never expose legacy read guesses. */
32
+ export function terminalMessage(message, raw = false) {
33
+ const { readAt: _readAt, readSource: _readSource, ...body } = message;
34
+ const legacyConsumption = message.deliverySource === 'consumer_read';
35
+ return { ...body, status: legacyConsumption ? 'pending' : message.status === 'read' ? 'delivered' : message.status,
36
+ deliveredAt: legacyConsumption ? null : message.deliveredAt,
37
+ deliverySource: legacyConsumption ? undefined : message.deliverySource,
38
+ snapshot: message.snapshot ? (raw ? message.snapshot : plainCollaborationSnapshot(message.snapshot)) : null };
39
+ }
24
40
  const MAX_MESSAGES = 2_000;
25
41
  const MESSAGE_KINDS = new Set(['message', 'ask', 'reply', 'task', 'handoff', 'done']);
26
42
  export class CollaborationStore {
27
43
  filePath;
28
44
  drawId;
45
+ onMessageQueued;
46
+ peerActivity = new Map();
47
+ recordPeerActivity(sessionId, outputAt, observedAt) {
48
+ if (this.peerActivity.size >= 2000 && !this.peerActivity.has(sessionId))
49
+ this.peerActivity.delete(this.peerActivity.keys().next().value);
50
+ this.peerActivity.set(sessionId, { last_terminal_output_at: typeof outputAt === 'number' && Number.isFinite(outputAt) && outputAt > 0 ? outputAt : null,
51
+ activity_observed_at: typeof observedAt === 'number' && Number.isFinite(observedAt) ? observedAt : null,
52
+ last_peer_sync_at: Date.now(), activity_source: 'remote_terminal_output' });
53
+ }
29
54
  document = { version: 2, groups: [], messages: [] };
30
55
  persistedDocument = JSON.stringify(this.document);
31
56
  /** `drawId` is the random source behind newCollaborationId. It is a
@@ -128,9 +153,11 @@ export class CollaborationStore {
128
153
  else
129
154
  delete roles[input.sessionId];
130
155
  const updated = { ...group, roles: Object.keys(roles).length ? roles : undefined,
156
+ roleVersions: { ...group.roleVersions, [input.sessionId]: { version: crypto.randomUUID(), updatedAt: Math.max(Date.now(), (group.roleVersions?.[input.sessionId]?.updatedAt ?? 0) + 1) } },
131
157
  updatedAt: Math.max(Date.now(), group.updatedAt + 1) };
132
158
  this.document.groups = this.document.groups.map((candidate) => candidate.id === group.id ? updated : candidate);
133
159
  this.persist();
160
+ queueMicrotask(() => this.onMessageQueued?.());
134
161
  return updated;
135
162
  }
136
163
  remove(id) {
@@ -175,6 +202,55 @@ export class CollaborationStore {
175
202
  };
176
203
  this.persist();
177
204
  }
205
+ setRules(groupId, text, updatedBy, expectedVersion) {
206
+ const group = this.getGroup(groupId);
207
+ if (!group || group.deleted || !group.sessionIds.includes(updatedBy))
208
+ throw new CollaborationError('NOT_A_MEMBER', '只能修改当前协作组的约定', 403);
209
+ if (expectedVersion !== undefined && expectedVersion !== (group.instructions?.version ?? ''))
210
+ throw new CollaborationError('RULES_CHANGED', '群规已更新,请重新读取后再修改', 409);
211
+ if (typeof text === 'string')
212
+ text = text.replace(/\r\n?/g, '\n');
213
+ if (typeof text !== 'string' || Buffer.byteLength(text) > 8192 || /[\x00-\x08\x0b-\x1f\x7f]/.test(text))
214
+ throw new CollaborationError('INVALID_RULES', '群规最多 8192 UTF-8 字节,不允许终端控制字符');
215
+ const instructions = { text, updatedBy, version: crypto.randomUUID(), updatedAt: Math.max(Date.now(), (group.instructions?.updatedAt ?? 0) + 1) };
216
+ group.instructions = instructions;
217
+ group.updatedAt = Math.max(Date.now(), group.updatedAt + 1);
218
+ this.persist();
219
+ queueMicrotask(() => this.onMessageQueued?.());
220
+ return instructions;
221
+ }
222
+ context(groupId) {
223
+ const group = this.getGroup(groupId);
224
+ return group ? { instructions: group.instructions, roles: group.roles, roleVersions: group.roleVersions } : {};
225
+ }
226
+ mergeContext(groupId, context) {
227
+ const group = this.getGroup(groupId);
228
+ if (!group || group.deleted || !context || typeof context !== 'object')
229
+ return;
230
+ let changed = false;
231
+ const rules = context.instructions;
232
+ if (rules && validRevision(rules) && typeof rules.text === 'string' && Buffer.byteLength(rules.text) <= 8192
233
+ && !/[\x00-\x08\x0b-\x1f\x7f]/.test(rules.text) && typeof rules.updatedBy === 'string'
234
+ && group.sessionIds.includes(rules.updatedBy) && newerRevision(rules, group.instructions)) {
235
+ group.instructions = { ...rules };
236
+ changed = true;
237
+ }
238
+ for (const [id, revision] of Object.entries(context.roleVersions ?? {})) {
239
+ if (!group.sessionIds.includes(id) || !validRevision(revision) || !newerRevision(revision, group.roleVersions?.[id]))
240
+ continue;
241
+ const role = context.roles?.[id];
242
+ if (role !== undefined && typeof role !== 'string')
243
+ continue;
244
+ (group.roleVersions ??= {})[id] = revision;
245
+ if (role)
246
+ (group.roles ??= {})[id] = sanitizeCollaborationRole(role);
247
+ else if (group.roles)
248
+ delete group.roles[id];
249
+ changed = true;
250
+ }
251
+ if (changed)
252
+ this.persist();
253
+ }
178
254
  removeSession(sessionId) {
179
255
  const affected = this.document.groups.filter((group) => group.sessionIds.includes(sessionId));
180
256
  if (affected.length === 0)
@@ -241,7 +317,7 @@ export class CollaborationStore {
241
317
  toSessionId, kind: input.kind, content, threadId,
242
318
  fanOutIds: recipients.length > 1 ? recipients.filter((id) => id !== toSessionId) : undefined,
243
319
  replyTo: input.replyTo?.trim() || null, status: 'pending', createdAt: now,
244
- deliveredAt: null, readAt: null,
320
+ deliveredAt: null, readAt: null, instructions: group.instructions ? { ...group.instructions } : undefined,
245
321
  }));
246
322
  if (messages.some((message) => Buffer.byteLength(JSON.stringify(message)) > COLLAB_LIMITS.wire_bytes))
247
323
  throw new CollaborationError('MESSAGE_WIRE_TOO_LARGE', `Encoded message exceeds ${COLLAB_LIMITS.wire_bytes} JSON bytes`, 413);
@@ -249,6 +325,7 @@ export class CollaborationStore {
249
325
  if (key)
250
326
  (this.document.idempotency ??= {})[key] = { hash, ids: messages.map((message) => message.id), expiresAt: now + COLLAB_LIMITS.idempotency_retention_ms };
251
327
  this.persist();
328
+ queueMicrotask(() => this.onMessageQueued?.());
252
329
  return messages;
253
330
  }
254
331
  listMessages(groupId, limit = 200) {
@@ -332,7 +409,10 @@ export class CollaborationStore {
332
409
  const existing = this.getGroup(group.id);
333
410
  if (existing && (!existing.federated || existing.updatedAt > group.updatedAt))
334
411
  return;
335
- this.document.groups = [...this.document.groups.filter((item) => item.id !== group.id), group];
412
+ const priorContext = existing ? this.context(group.id) : undefined;
413
+ this.document.groups = [...this.document.groups.filter((item) => item.id !== group.id), { ...group }];
414
+ if (priorContext)
415
+ this.mergeContext(group.id, priorContext);
336
416
  if (group.deleted)
337
417
  this.document.messages = this.document.messages.filter((item) => item.groupId !== group.id);
338
418
  this.persist();
@@ -402,6 +482,11 @@ export class CollaborationStore {
402
482
  changed = true;
403
483
  }
404
484
  }
485
+ if (index >= 0 && rank[message.status] >= rank[this.document.messages[index].status]
486
+ && typeof message.snapshot === 'string' && message.snapshot && this.document.messages[index].snapshot !== message.snapshot) {
487
+ this.document.messages[index].snapshot = message.snapshot.slice(0, 16000);
488
+ changed = true;
489
+ }
405
490
  if (index >= 0 && rank[message.status] > rank[this.document.messages[index].status]) {
406
491
  const existing = this.document.messages[index];
407
492
  // A receipt may advance status, but never rewrite an existing message.
@@ -443,21 +528,26 @@ export class CollaborationStore {
443
528
  }
444
529
  diagnostic(id) { return this.document.transport?.[id] ?? null; }
445
530
  recordTransport(id, diagnostic) {
446
- if (!this.getMessage(id))
531
+ if (!this.getMessage(id) || (this.document.transport?.[id]?.checked_at ?? 0) > diagnostic.checked_at)
447
532
  return;
448
533
  (this.document.transport ??= {})[id] = diagnostic;
449
534
  this.persist();
450
535
  }
451
- receipt(id) {
536
+ receipt(id, raw = false) {
452
537
  const message = this.getMessage(id);
453
538
  if (!message)
454
539
  throw new CollaborationError('MESSAGE_NOT_FOUND', 'Message not found or no longer retained', 404);
455
540
  const replies = this.document.messages.filter((reply) => reply.replyTo === id && reply.fromSessionId === message.toSessionId && reply.toSessionId === message.fromSessionId);
456
541
  const diagnostic = this.diagnostic(id);
457
- return { message_id: id, thread_id: message.threadId, status: message.status, queued_at: message.createdAt,
458
- delivered_at: message.deliveredAt, read_at: message.readAt, expires_at: message.expiresAt ?? null,
459
- failure_reason: message.failureReason ?? null, delivery_semantics: message.deliverySource ?? (message.deliveredAt === null ? 'not_delivered' : 'legacy_or_unspecified'), read_semantics: message.readAt ? message.readSource ?? 'legacy_or_unspecified' : 'not_read',
460
- snapshot: message.snapshot ?? null,
542
+ const facts = terminalMessage(message, raw);
543
+ return { message_id: id, thread_id: message.threadId, status: facts.status, queued_at: message.createdAt,
544
+ delivered_at: facts.deliveredAt, expires_at: message.expiresAt ?? null,
545
+ failure_reason: message.failureReason ?? null, delivery_semantics: facts.deliverySource ?? (facts.deliveredAt === null ? 'not_delivered' : 'legacy_or_unspecified'),
546
+ snapshot: message.snapshot ? (raw ? message.snapshot : plainCollaborationSnapshot(message.snapshot)) : null,
547
+ delivery: { status: facts.status,
548
+ stage: facts.deliveredAt !== null ? 'terminal_written' : diagnostic?.remote_received_at ? 'remote_received' : 'queued',
549
+ remote_received_at: diagnostic?.remote_received_at ?? null, delivered_at: facts.deliveredAt, error: message.failureReason ?? diagnostic?.last_error ?? null },
550
+ reply: { status: replies.length ? 'received' : 'pending', ack_at: replies.find(reply => reply.responseKind === 'ack')?.createdAt ?? null, reply_ids: replies.map(reply => reply.id) },
461
551
  idempotency_key: message.idempotencyKey ?? null,
462
552
  ack_at: replies.find((reply) => reply.responseKind === 'ack')?.createdAt ?? null,
463
553
  reply_ids: replies.map((reply) => reply.id), result_ids: replies.filter((reply) => reply.responseKind === 'result').map((reply) => reply.id),
@@ -469,26 +559,21 @@ export class CollaborationStore {
469
559
  last_error: diagnostic?.last_error ?? null, transport_checked_at: diagnostic?.checked_at ?? null,
470
560
  fragments_sent: diagnostic?.fragments_sent ?? 0, fragments_total: diagnostic?.fragments_total ?? 0 };
471
561
  }
472
- sessionFacts(sessionId, online, turnState) {
562
+ sessionFacts(sessionId, online, _legacyTurnState) {
473
563
  const reports = this.document.messages.filter((message) => message.fromSessionId === sessionId);
474
564
  const tasks = new Map();
475
565
  for (const message of reports)
476
566
  if (message.task)
477
567
  tasks.set(message.task.task_id, { task_id: message.task.task_id, status: message.task.status, reported_at: message.createdAt, message_id: message.id });
478
- // Aggregated from the *latest* report of each task, never inferred: an
479
- // agent handed a task that has not yet replied is simply idle here —
480
- // dispatch in flight is the sender-side receipt's concern (pending), not a
481
- // fact about this session. blocked counts as active: the agent holds the
482
- // task open. A failed terminal state only surfaces once no task is still
483
- // in flight, since another in-progress task means the agent is busy.
484
- const latest = [...tasks.values()];
485
- const taskState = latest.some((task) => task.status === 'ack' || task.status === 'working' || task.status === 'blocked') ? 'active'
486
- : latest.some((task) => task.status === 'failed') ? 'failed'
487
- : 'idle';
488
- return { session_state: online ? 'online' : 'offline', turn_state: turnState === 'done' ? 'ended' : turnState ?? 'unknown',
489
- task_state: taskState, tasks: [...tasks.values()], last_heartbeat: null, last_tool_activity_at: null,
568
+ return { session_state: online ? 'online' : 'offline', tasks: [...tasks.values()],
490
569
  last_message_at: reports.length ? Math.max(...reports.map((message) => message.createdAt)) : null,
491
- state_source: 'optional_adapter', task_source: 'explicit_message' };
570
+ last_terminal_output_at: this.peerActivity.get(sessionId)?.last_terminal_output_at ?? null,
571
+ activity_observed_at: this.peerActivity.get(sessionId)?.activity_observed_at ?? null,
572
+ last_peer_sync_at: this.peerActivity.get(sessionId)?.last_peer_sync_at ?? null,
573
+ activity_source: this.peerActivity.get(sessionId)?.activity_source ?? 'unavailable',
574
+ output_idle_seconds: this.peerActivity.get(sessionId)?.last_terminal_output_at && this.peerActivity.get(sessionId)?.activity_observed_at
575
+ ? Math.max(0, Math.floor((this.peerActivity.get(sessionId).activity_observed_at - this.peerActivity.get(sessionId).last_terminal_output_at) / 1000)) : null,
576
+ task_source: 'explicit_message' };
492
577
  }
493
578
  /** Timeline of one task as reported by a session, oldest first. The lineage
494
579
  * starts at the recipient's first reply carrying that task_id — a bare
@@ -538,7 +623,7 @@ export class CollaborationStore {
538
623
  && (!options.thread || message.threadId === options.thread) && (!options.kind || message.kind === options.kind)
539
624
  && (!options.responseKind || message.responseKind === options.responseKind))
540
625
  .sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0));
541
- const messages = incremental || options.order === 'oldest' ? candidates.slice(0, limit) : candidates.slice().sort((a, b) => Number(a.status === 'read') - Number(b.status === 'read') || (b.sequence ?? 0) - (a.sequence ?? 0)).slice(0, limit);
626
+ const messages = incremental || options.order === 'oldest' ? candidates.slice(0, limit) : candidates.slice().sort((a, b) => (b.sequence ?? 0) - (a.sequence ?? 0)).slice(0, limit);
542
627
  const sequence = messages.length ? Math.max(...messages.map((message) => message.sequence ?? 0)) : after;
543
628
  const next_cursor = Buffer.from(JSON.stringify({ scope, sequence })).toString('base64url');
544
629
  return { messages, next_cursor, retention_gap: incremental && after < (this.document.prunedThrough?.[sessionId] ?? 0), has_more: incremental ? candidates.length > messages.length : false, consumer: options.consumer ?? null };
@@ -0,0 +1,5 @@
1
+ import { stripVTControlCharacters } from 'node:util';
2
+ /** Snapshot rendering only. Message bodies are never normalized. */
3
+ export function plainCollaborationSnapshot(text) {
4
+ return stripVTControlCharacters(text).replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '');
5
+ }
@@ -1,3 +1,4 @@
1
+ import { plainCollaborationSnapshot } from './collaborationText.js';
1
2
  import { normalizePromptForPaste } from './promptDelivery.js';
2
3
  const TMUX_KEY_NAMES = {
3
4
  enter: 'Enter', escape: 'Escape', space: 'Space', left: 'Left', right: 'Right', up: 'Up', down: 'Down',
@@ -120,10 +121,10 @@ export async function sendTmuxPaneKey(run, pane, key) {
120
121
  await run(['send-keys', '-t', pane.paneId, TMUX_KEY_NAMES[key]]);
121
122
  }
122
123
  /** Current pane screen text (viewport), identity-checked. */
123
- export async function captureTmuxPaneText(run, pane) {
124
+ export async function captureTmuxPaneText(run, pane, rawOutput = false) {
124
125
  await assertSamePane(run, pane);
125
- const raw = await run(['capture-pane', '-p', '-J', '-t', pane.paneId]);
126
- return raw.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '').replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '');
126
+ const raw = await run(['capture-pane', '-p', '-J', ...(rawOutput ? ['-e'] : []), '-t', pane.paneId]);
127
+ return rawOutput ? raw : plainCollaborationSnapshot(raw);
127
128
  }
128
129
  /** Dismiss an interactive approval dialog on the member pane by pressing
129
130
  * Enter (the highlighted option). Refuses unless the screen actually shows
@@ -153,8 +154,10 @@ export async function recoverStuckPaste(run, pane, baseline) {
153
154
  }
154
155
  /** History-inclusive pane text (scrollback + viewport) used to confirm a
155
156
  * delivery was actually rendered by the agent, not wiped by a boot clear. */
156
- export async function captureTmuxPaneHistory(run, pane, lines = 800) {
157
+ export async function captureTmuxPaneHistory(run, pane, lines = 800, rawOutput = false) {
157
158
  await assertSamePane(run, pane);
158
- const raw = await run(['capture-pane', '-p', '-J', '-S', `-${lines}`, '-t', pane.paneId]);
159
- return raw.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '').replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '');
159
+ if (!Number.isInteger(lines) || lines < 1 || lines > 10000)
160
+ throw new Error('INVALID_HISTORY_LINES');
161
+ const raw = await run(['capture-pane', '-p', '-J', ...(rawOutput ? ['-e'] : []), '-S', `-${lines}`, '-t', pane.paneId]);
162
+ return rawOutput ? raw : plainCollaborationSnapshot(raw);
160
163
  }
@@ -1,3 +1,6 @@
1
+ import { X509Certificate } from 'node:crypto';
2
+ import { CollaborationPeerTransport, connectCollaborationRpc } from './agent/collaborationPeerTransport.js';
3
+ import { collaborationStore, deliverPeerCollaboration, collaborationLocalActivity } from './routes/terminal.js';
1
4
  import { setPushTargetPeerId } from './notifications/pushService.js';
2
5
  import { desktopDirectTargets } from './federation/desktopTargets.js';
3
6
  import { createOpenAccessRouter } from './federation/openAccess.js';
@@ -404,11 +407,12 @@ export function startServer(options = {}) {
404
407
  runtimeMonitor,
405
408
  });
406
409
  const { server, scheme } = createServerForApp(app, options);
410
+ let collaborationTransport;
407
411
  let entrySubjectAllowed = (_subjectId) => false;
408
412
  let routeInvitations;
409
413
  let federationServiceId = '';
410
414
  const desktopTargets = () => desktopDirectTargets(path.join(homedir(), '.termdock', 'desktop.json'), federationServiceId);
411
- const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true, desktopTargets);
415
+ const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true || collaborationTransport?.canRoute(subjectId, targetServiceId) === true, desktopTargets);
412
416
  const relayRouter = new RelayRouter({
413
417
  authenticate: context => context,
414
418
  allowRegister: (principal, serviceId) => routeAccess.allowRegister(principal, serviceId),
@@ -441,6 +445,11 @@ export function startServer(options = {}) {
441
445
  const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
442
446
  terminal: handleTerminalWebSocket, control: handleControlWebSocket,
443
447
  }, {
448
+ collaborationExchange: (subjectId, packet) => {
449
+ if (!collaborationTransport)
450
+ throw new Error('COLLABORATION_UNAVAILABLE');
451
+ return collaborationTransport.receive(subjectId, packet);
452
+ },
444
453
  listRouteTargets: () => { refreshDirectTargets(); return routeAccess.configuredTargets().map(target => ({ ...target, available: relayRouter.hasRoute(target.serviceId) })); },
445
454
  listRouteAccess: () => routeInvitations?.list() || [],
446
455
  grantRouteAccess: async (issuerId, targetServiceId, subjectId, url) => {
@@ -461,7 +470,7 @@ export function startServer(options = {}) {
461
470
  },
462
471
  revokeRouteAccess: id => routeInvitations?.revoke(id) || false,
463
472
  issueRouteTicket: (subjectId, serviceId) => routeAccess.issueRouteTicket(subjectId, serviceId),
464
- hasRouteGrant: (subjectId, serviceId) => routeInvitations?.allows(subjectId, serviceId) === true,
473
+ hasRouteGrant: (subjectId, serviceId) => routeInvitations?.allows(subjectId, serviceId) === true || collaborationTransport?.canRoute(subjectId, serviceId) === true,
465
474
  createRouteInvitation: (issuerId, serviceId) => {
466
475
  if (!routeInvitations)
467
476
  throw new Error('ROUTE_NOT_AVAILABLE');
@@ -478,6 +487,37 @@ export function startServer(options = {}) {
478
487
  setPushTargetPeerId(runtime.serviceId);
479
488
  refreshDirectTargets();
480
489
  app.locals.passwordRuntime = runtime;
490
+ collaborationTransport = new CollaborationPeerTransport({
491
+ file: path.join(homedir(), '.termdock', 'federation', 'collaboration-peers.json'), serviceId: runtime.serviceId,
492
+ store: collaborationStore, deliver: deliverPeerCollaboration, activity: collaborationLocalActivity,
493
+ connect: async (peer, via) => {
494
+ if (!via)
495
+ return connectCollaborationRpc(runtime.identity, peer);
496
+ const entry = await connectCollaborationRpc(runtime.identity, via);
497
+ try {
498
+ const ticket = await entry.request({ type: 'route-ticket', serviceId: peer.serviceId });
499
+ if (typeof ticket.routeToken !== 'string')
500
+ throw new Error('ROUTE_UNAVAILABLE');
501
+ const url = new URL('/api/federation/relay', via.origin.replace(/^https:/, 'wss:'));
502
+ url.searchParams.set('routeToken', ticket.routeToken);
503
+ // TLS authenticates the entry; Noise pins the final destination.
504
+ return await connectCollaborationRpc(runtime.identity, { ...via, serviceId: peer.serviceId }, url.href);
505
+ }
506
+ finally {
507
+ entry.close();
508
+ }
509
+ },
510
+ });
511
+ app.locals.collaborationTransport = collaborationTransport;
512
+ let caFingerprint256;
513
+ if (options.httpsCaPath)
514
+ try {
515
+ caFingerprint256 = new X509Certificate(fs.readFileSync(options.httpsCaPath)).fingerprint256;
516
+ }
517
+ catch { /* System TLS trust remains required. */ }
518
+ app.locals.collaborationNode = { serviceId: runtime.serviceId, ...(caFingerprint256 ? { caFingerprint256 } : {}) };
519
+ collaborationTransport.start();
520
+ server.once('close', () => collaborationTransport?.close());
481
521
  entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
482
522
  routeInvitations = new RouteInvitationStore({
483
523
  filePath: path.join(homedir(), '.termdock', 'federation', 'route-invitations.json'), serviceId: runtime.serviceId,
@@ -251,7 +251,12 @@ export async function createFederationRuntime(app, directory, handlers, options
251
251
  try {
252
252
  if (packet.id.length > 128)
253
253
  throw new Error('INVALID_ID');
254
- if (packet.type === 'password-parameters') {
254
+ if (packet.type === 'collaboration-exchange') {
255
+ if (!options.collaborationExchange)
256
+ throw new Error('COLLABORATION_UNAVAILABLE');
257
+ send({ type: 'result', id: packet.id, ...options.collaborationExchange(subjectId, packet) });
258
+ }
259
+ else if (packet.type === 'password-parameters') {
255
260
  send({ type: 'result', id: packet.id, ...passwordBootstrap.parameters() });
256
261
  }
257
262
  else if (packet.type === 'password-start') {
@@ -544,6 +549,6 @@ export async function createFederationRuntime(app, directory, handlers, options
544
549
  }
545
550
  }
546
551
  }
547
- return { serviceId, store, accept, close() { for (const channel of channels)
552
+ return { serviceId, identity, store, accept, close() { for (const channel of channels)
548
553
  channel.close(); internal.close(); } };
549
554
  }
@@ -1,3 +1,4 @@
1
+ import { assertPeerRegistrationAuthority } from '../agent/collaborationPeerTransport.js';
1
2
  import { progressRoutes } from '../notifications/progressRoutes.js';
2
3
  import { ensureNodePty } from '../utils/ensureNodePty.js';
3
4
  import { PtySpawnBackoff, PtySpawnDeferredError } from '../utils/ptySpawnBackoff.js';
@@ -68,7 +69,7 @@ const router = express.Router();
68
69
  const execFileAsync = promisify(execFile);
69
70
  const TERMDOCK_DIR = `${os.homedir()}/.termdock`;
70
71
  const automationStore = new AutomationStore(`${TERMDOCK_DIR}/automations.json`);
71
- const collaborationStore = new CollaborationStore(`${TERMDOCK_DIR}/collaboration-groups.json`);
72
+ export const collaborationStore = new CollaborationStore(`${TERMDOCK_DIR}/collaboration-groups.json`);
72
73
  const collaborationRouting = new CollaborationRoutingStore(`${TERMDOCK_DIR}/collaboration-routing.json`);
73
74
  const collaborationDeliveryWorker = new CollaborationDeliveryWorker({
74
75
  store: collaborationStore,
@@ -1338,12 +1339,12 @@ function orchestrationSessionSnapshot(record) {
1338
1339
  const route = collaborationDeliveryWorker.state(record.sessionId);
1339
1340
  const slug = binding?.pane?.agentSlug ?? binding?.agentSlug ?? record.agentResume?.slug;
1340
1341
  const agent = slug ? agentBySlug(slug) : backend?.agent;
1341
- const sameNativeSession = !binding?.pane || Boolean(binding.pane.nativeSessionId
1342
- && binding.pane.nativeSessionId === backend?.agentSession?.sessionId && binding.pane.agentSlug === backend?.agent?.slug);
1343
- const turnState = sameNativeSession ? backend?.agentSession?.status : undefined;
1344
- const latestPrompt = sameNativeSession ? backend?.autoTitlePromptPayloads.at(-1)?.trim() ?? '' : '';
1345
1342
  return {
1346
- ...collaborationStore.sessionFacts(record.sessionId, route.state === 'ready', turnState),
1343
+ ...collaborationStore.sessionFacts(record.sessionId, route.state === 'ready'),
1344
+ last_terminal_output_at: backend?.lastOutputAt || null,
1345
+ activity_observed_at: backend ? Date.now() : null,
1346
+ activity_source: backend ? 'terminal_output' : 'unavailable',
1347
+ output_idle_seconds: backend?.lastOutputAt ? Math.max(0, Math.floor((Date.now() - backend.lastOutputAt) / 1000)) : null,
1347
1348
  route_state: route.state,
1348
1349
  route_error: route.reason,
1349
1350
  route_checked_at: route.checkedAt,
@@ -1353,11 +1354,10 @@ function orchestrationSessionSnapshot(record) {
1353
1354
  name: record.name,
1354
1355
  cwd: backend?.cwd ?? record.cwd ?? '',
1355
1356
  agent: agent ? { slug: agent.slug, displayName: agent.displayName } : null,
1356
- status: route.state === 'ready' ? turnState ?? 'ready' : route.state,
1357
+ status: route.state,
1357
1358
  capability: agent
1358
1359
  ? [agent.displayName, ...(agent.capabilities ?? []), backend?.activeProgram?.command || record.activeProgram || 'Agent 会话'].join(' · ')
1359
1360
  : (backend?.activeProgram?.command || record.activeProgram || 'Shell 终端'),
1360
- currentTask: latestPrompt || record.name,
1361
1361
  updatedAt: backend?.lastActivity ?? record.lastActivity,
1362
1362
  };
1363
1363
  }
@@ -1438,16 +1438,20 @@ function resolveFrontendSessionId(input) {
1438
1438
  }
1439
1439
  function collaborationRemoteSessions() {
1440
1440
  return Array.from(new Map(collaborationStore.list().flatMap((group) => group.remoteSessions ?? [])
1441
- .map((session) => [session.sessionId, { ...session, ...collaborationStore.sessionFacts(session.sessionId, session.serviceConnected === true && Date.now() - (session.serviceCheckedAt ?? 0) <= 15_000, session.status), status: session.serviceConnected !== true || Date.now() - (session.serviceCheckedAt ?? 0) > 15_000 ? 'service-unreachable' : session.status, name: `${session.name} · ${session.serviceLabel}` }])).values());
1441
+ .map((session) => {
1442
+ const facts = collaborationStore.sessionFacts(session.sessionId, false);
1443
+ const { currentTask: _currentTask, ...terminalSession } = session;
1444
+ const checkedAt = Math.max(session.serviceCheckedAt ?? 0, facts.last_peer_sync_at ?? 0);
1445
+ const online = Date.now() - checkedAt <= 15_000 && (session.serviceConnected === true || !!facts.last_peer_sync_at && Date.now() - facts.last_peer_sync_at <= 15_000);
1446
+ return [session.sessionId, { ...terminalSession, ...facts, session_state: online ? 'online' : 'offline',
1447
+ serviceConnected: online, serviceCheckedAt: checkedAt, status: online ? 'service-reachable' : 'service-unreachable', name: `${session.name} · ${session.serviceLabel}` }];
1448
+ })).values());
1442
1449
  }
1443
1450
  function tryDeliverCollaborationInbox(frontendSessionId) {
1444
1451
  const remote = collaborationRemoteSessions().find((session) => session.sessionId === frontendSessionId);
1445
1452
  if (remote) {
1446
- const unavailable = remote.serviceConnected !== true || Date.now() - (remote.serviceCheckedAt ?? 0) > 15_000;
1447
- return { delivered: [], pending: collaborationStore.pendingCount(frontendSessionId),
1448
- serviceUnavailable: unavailable, reason: unavailable
1449
- ? `服务 ${remote.serviceLabel} 不可达:消息尚未送达,仅保存在待发送队列;请勿等待对方已收到的回复,服务重连且转发客户端恢复运行后重试投递。`
1450
- : '跨服务消息尚未确认送达,正在等待在线客户端转发。' };
1453
+ return { delivered: [], pending: collaborationStore.pendingCount(frontendSessionId), waitable: true,
1454
+ reason: '跨服务消息已入队,服务后台正在投递;使用 message get 查看送达回执或注册、重试原因。' };
1451
1455
  }
1452
1456
  collaborationDeliveryWorker.wake(frontendSessionId);
1453
1457
  const route = collaborationDeliveryWorker.state(frontendSessionId);
@@ -1608,7 +1612,7 @@ async function resolveCollaborationRoute(frontendSessionId) {
1608
1612
  }
1609
1613
  if (!backend)
1610
1614
  return { state: 'detached', reason: 'TMUX_BACKEND_UNAVAILABLE' };
1611
- return { state: 'ready', isShell: pane.isShell, capture: async () => (await captureTmuxPane(pinned.paneId)).content, write: async (messages) => {
1615
+ return { state: 'ready', isShell: pane.isShell, capture: async () => captureTmuxPaneText(runTmux, pinned), write: async (messages) => {
1612
1616
  if (!globalSessionState.sessions.some((candidate) => candidate.sessionId === frontendSessionId))
1613
1617
  throw new Error('SESSION_REMOVED');
1614
1618
  await writeCollaborationTmuxPane(runTmux, pinned, formatLocalCollaborationMessages(frontendSessionId, messages), runTmuxStdin);
@@ -4805,7 +4809,7 @@ async function spawnTerminalSessionUnlocked(req, input) {
4805
4809
  cols,
4806
4810
  rows,
4807
4811
  lastActivity: Date.now(),
4808
- lastOutputAt: Date.now(),
4812
+ lastOutputAt: 0,
4809
4813
  clients: new Map(),
4810
4814
  createdAt: Date.now(),
4811
4815
  hasWrittenData: false,
@@ -4894,7 +4898,7 @@ async function adoptPtyHostSessions() {
4894
4898
  cols: meta.cols,
4895
4899
  rows: meta.rows,
4896
4900
  lastActivity: Date.now(),
4897
- lastOutputAt: Date.now(),
4901
+ lastOutputAt: 0,
4898
4902
  clients: new Map(),
4899
4903
  createdAt: meta.startedAt,
4900
4904
  hasWrittenData: true,
@@ -5474,10 +5478,30 @@ router.delete('/operations/automations/:automationId', (req, res) => {
5474
5478
  });
5475
5479
  // Authenticated desktop connections exchange only collaboration data; no peer credentials
5476
5480
  // or arbitrary remote URLs are accepted by the server.
5477
- router.get('/operations/collaboration-federation', (_req, res) => {
5478
- res.json({ protocolVersion: 2, limits: COLLAB_LIMITS, ...collaborationStore.federationSnapshot(),
5481
+ router.get('/operations/collaboration-federation', (req, res) => {
5482
+ res.json({ protocolVersion: 2, serverTransport: req.app.locals.collaborationNode, limits: COLLAB_LIMITS, ...collaborationStore.federationSnapshot(),
5479
5483
  sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot) });
5480
5484
  });
5485
+ router.post('/operations/collaboration-peers', (req, res) => {
5486
+ try {
5487
+ assertPeerRegistrationAuthority(req);
5488
+ if (!req.app.locals.collaborationTransport)
5489
+ return res.status(503).json({ error: 'COLLABORATION_UNAVAILABLE' });
5490
+ req.app.locals.collaborationTransport.configure(req.body.groupId, req.body.localOrigin, req.body.nodes);
5491
+ res.json({ ok: true });
5492
+ }
5493
+ catch (error) {
5494
+ res.status(400).json({ error: getErrorMessage(error) });
5495
+ }
5496
+ });
5497
+ export function collaborationLocalActivity() {
5498
+ return globalSessionState.sessions.map(record => {
5499
+ const backendId = collaborationRouting.get(record.sessionId)?.backendSessionId ?? record.backendSessionId;
5500
+ const backend = backendId ? terminalSessions.get(backendId) : undefined;
5501
+ return { sessionId: record.sessionId, last_terminal_output_at: backend?.lastOutputAt || null, activity_observed_at: backend ? Date.now() : null };
5502
+ });
5503
+ }
5504
+ export function deliverPeerCollaboration(sessionId) { void tryDeliverCollaborationInbox(sessionId); }
5481
5505
  router.post('/operations/collaboration-federation', (req, res) => {
5482
5506
  try {
5483
5507
  const group = req.body?.group;
@@ -5832,7 +5856,11 @@ router.post('/operations/orchestration/drive', async (req, res) => {
5832
5856
  return res.json({ ok: true, action, sessionId: target, approved: true });
5833
5857
  }
5834
5858
  if (action === 'capture') {
5835
- const snapshot = await captureTmuxPaneText(runTmux, pane);
5859
+ const lines = req.body.lines;
5860
+ if (lines !== undefined && (!Number.isInteger(lines) || lines < 1 || lines > 10000))
5861
+ return res.status(400).json({ error: 'lines 必须为 1..10000 的历史行数' });
5862
+ const snapshot = lines === undefined ? await captureTmuxPaneText(runTmux, pane, req.body.raw === true)
5863
+ : await captureTmuxPaneHistory(runTmux, pane, lines, req.body.raw === true);
5836
5864
  return res.json({ ok: true, action, sessionId: target, snapshot });
5837
5865
  }
5838
5866
  if (action === 'run') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termdock",
3
- "version": "1.4.231",
3
+ "version": "1.4.232",
4
4
  "description": "A complete web-based terminal application with modern UI",
5
5
  "type": "module",
6
6
  "main": ".desktop-dist/main.js",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "packageName": "termdock",
4
- "version": "1.4.231",
4
+ "version": "1.4.232",
5
5
  "runtimeProtocolVersion": 1,
6
6
  "minimumDesktopVersion": "1.4.46",
7
7
  "nodeMajor": 22,
8
8
  "dependencyHash": "sha256-K4KyYJuVoQDpZsBM8ZX1Rw3MddOxAzVdp/cPm0iGZz0=",
9
- "serverBundleHash": "sha256-DO8IZjFbTWRMSTgEwFWqztjBTjT2Wyex9+RzTXfIPLE=",
10
- "clientBundleHash": "sha256-m4yxqgotYCKT1VQ6akPDlFWQugeT/FT15LHALETA930=",
9
+ "serverBundleHash": "sha256-GHB6P5NPYFEdDR/sPm0FHVSEBdpzcJ8Jghr5X2uikWU=",
10
+ "clientBundleHash": "sha256-AWAHyBvSOOJ9eViAjt+iC2NSH0Uex5K8eEGrkeFVEFI=",
11
11
  "entrypoint": "dist/server/cli.js",
12
12
  "clientEntrypoint": "dist/client/index.html"
13
13
  }