termdock 1.4.190 → 1.4.192

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 (326) hide show
  1. package/dist/client/assets/{App-BkcaQXGe.js → App-BbIl0DAa.js} +37 -37
  2. package/dist/client/assets/App-BbIl0DAa.js.br +0 -0
  3. package/dist/client/assets/App-BbIl0DAa.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-BkTM7zQ0.js → ChangeWalkthroughPanel-CT7mLy_e.js} +1 -1
  5. package/dist/client/assets/ChangeWalkthroughPanel-CT7mLy_e.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-CT7mLy_e.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-7UxLIhH5.js → DagPlayground-Bsf4bv7e.js} +1 -1
  8. package/dist/client/assets/DagPlayground-Bsf4bv7e.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-Bsf4bv7e.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-Cn2bPbbB.js → DiffLab-BjiZcUSn.js} +1 -1
  11. package/dist/client/assets/DiffLab-BjiZcUSn.js.br +0 -0
  12. package/dist/client/assets/DiffLab-BjiZcUSn.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-Bax7DQmN.js → DiffReview-DmkAyOh4.js} +1 -1
  14. package/dist/client/assets/DiffReview-DmkAyOh4.js.br +0 -0
  15. package/dist/client/assets/DiffReview-DmkAyOh4.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-TG4kjagX.js → DiffReviewLab-wXYUmdna.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-wXYUmdna.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-wXYUmdna.js.gz +0 -0
  19. package/dist/client/assets/{DiffViewer-3MezBn_a.js → DiffViewer-BlwwMnsB.js} +1 -1
  20. package/dist/client/assets/DiffViewer-BlwwMnsB.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-BlwwMnsB.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-BV_0H-Ls.js → ModelPreview-DGIaQAcq.js} +1 -1
  23. package/dist/client/assets/ModelPreview-DGIaQAcq.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-DGIaQAcq.js.gz +0 -0
  25. package/dist/client/assets/{RightSidebar-BHlwv3gW.js → RightSidebar-PgJ0XzEh.js} +47 -47
  26. package/dist/client/assets/RightSidebar-PgJ0XzEh.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-PgJ0XzEh.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-DOk0otQq.js → abnfDiagram-VCTEODGH-B_sCIKms.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-B_sCIKms.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-B_sCIKms.js.gz +0 -0
  31. package/dist/client/assets/app-5zC_J9mq.js +34 -0
  32. package/dist/client/assets/app-5zC_J9mq.js.br +0 -0
  33. package/dist/client/assets/app-5zC_J9mq.js.gz +0 -0
  34. package/dist/client/assets/{arc-CfeOYtSi.js → arc-Ps2GTfJ3.js} +1 -1
  35. package/dist/client/assets/arc-Ps2GTfJ3.js.br +0 -0
  36. package/dist/client/assets/arc-Ps2GTfJ3.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-Dys1Wr-I.js → architectureDiagram-5GKGNRK7-DxKfqPAR.js} +1 -1
  38. package/dist/client/assets/architectureDiagram-5GKGNRK7-DxKfqPAR.js.br +0 -0
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-DxKfqPAR.js.gz +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-D8DbjMtS.js → blockDiagram-I7D4REHJ-BNL8tkC0.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-BNL8tkC0.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-BNL8tkC0.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-DJcMUQXb.js → c4Diagram-7LVT6UL2-hyEavwo6.js} +1 -1
  44. package/dist/client/assets/c4Diagram-7LVT6UL2-hyEavwo6.js.br +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-hyEavwo6.js.gz +0 -0
  46. package/dist/client/assets/channel-Bkfq4dnz.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-BPmsHIzM.js → chunk-2Q5K7J3B-DN_HrxPz.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-BTxB35XY.js → chunk-5VM5RSS4-BofAFEcC.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-BiUpuqFN.js → chunk-F27PBJKO-CLNw1JDM.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-CLNw1JDM.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-CLNw1JDM.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-Bvca4yZA.js → chunk-IMKFNOWR-DsVDl8p7.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-DsVDl8p7.js.br +0 -0
  54. package/dist/client/assets/chunk-IMKFNOWR-DsVDl8p7.js.gz +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-CxCNQSVG.js → chunk-JWPE2WC7-CdfzOrPC.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-C9E5B9o7.js → chunk-POPQ4Y6H-MXmTC-sG.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-D4Qm28xz.js → chunk-SVP7TREG-DfwfQAY6.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-DfwfQAY6.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-DfwfQAY6.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-CS0AERD9.js → chunk-TICWLB2K-N3OPIvmU.js} +1 -1
  61. package/dist/client/assets/chunk-TICWLB2K-N3OPIvmU.js.br +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-N3OPIvmU.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-C1vCDgbp.js → chunk-XXDRQBXY-IurIojt5.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-Dahb2o7K.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-Dahb2o7K.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-D0-yuoIn.js → cose-bilkent-JH36ORCC-DHbDHTvp.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-DHbDHTvp.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-DHbDHTvp.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-C9ZqMdi9.js → cynefin-OW5HDTMX-DyTW2Iyi.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-DyTW2Iyi.js.br +0 -0
  71. package/dist/client/assets/cynefin-OW5HDTMX-DyTW2Iyi.js.gz +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Drvx9Dke.js → cynefinDiagram-5FMLGOSQ-DEan3pcN.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DEan3pcN.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DEan3pcN.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-mUXNFR_H.js → dagre-GXQ25YYZ-Rnw2v1uV.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-Rnw2v1uV.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-Rnw2v1uV.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-CKJgF0VS.js → diagram-S7CK7UJ4-CmBa29S9.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-CmBa29S9.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-CmBa29S9.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-Cp9NE2Gc.js → diagram-UQ7AKVKN-Cqzmpane.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-Cqzmpane.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-Cqzmpane.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-mnyDXn4l.js → diagram-VSXAHHWV-CrHTQozH.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-CrHTQozH.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-CrHTQozH.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-6lyjUw6O.js → diagram-VX7I27RA-C7nQgan9.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-C7nQgan9.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-C7nQgan9.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-3K_Dhn1t.js → diagram-Z3DM3KII-93X66n3v.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-93X66n3v.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-93X66n3v.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-B2mHkMsE.js → ebnfDiagram-PWID7BFC-LfScrrH8.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-LfScrrH8.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-LfScrrH8.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-Brfc7YEf.js → erDiagram-RLTQ6QDP-DAWKsrIB.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-DAWKsrIB.js.br +0 -0
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-DAWKsrIB.js.gz +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-UGHsvQMc.js → flowDiagram-HODETNUW-CcdXfpU7.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-CcdXfpU7.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-CcdXfpU7.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CC30lZuL.js → ganttDiagram-EL5Y4UJY-DPLNbHSY.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DPLNbHSY.js.br +0 -0
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DPLNbHSY.js.gz +0 -0
  105. package/dist/client/assets/{gestureArbiter-DIYXjBYY.js → gestureArbiter-B5PxTluh.js} +1 -1
  106. package/dist/client/assets/gestureArbiter-B5PxTluh.js.br +0 -0
  107. package/dist/client/assets/gestureArbiter-B5PxTluh.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CiwHG61R.js → gitGraphDiagram-WWUBYQGX-P741cwUf.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-P741cwUf.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-P741cwUf.js.gz +0 -0
  111. package/dist/client/assets/{index-TEmneMvF.css → index-oKp6kSMR.css} +1 -1
  112. package/dist/client/assets/index-oKp6kSMR.css.br +0 -0
  113. package/dist/client/assets/{index-TEmneMvF.css.gz → index-oKp6kSMR.css.gz} +0 -0
  114. package/dist/client/assets/{infoDiagram-27XIBGKW-CcAeYa6X.js → infoDiagram-27XIBGKW-xrl-VW74.js} +1 -1
  115. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-COFtLTBs.js → ishikawaDiagram-5VMMS53U-BJmiVWrB.js} +1 -1
  116. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BJmiVWrB.js.br +0 -0
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BJmiVWrB.js.gz +0 -0
  118. package/dist/client/assets/{journeyDiagram-3NMN7TZE-D0iXlEDw.js → journeyDiagram-3NMN7TZE-DZX8uYK2.js} +1 -1
  119. package/dist/client/assets/journeyDiagram-3NMN7TZE-DZX8uYK2.js.br +0 -0
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-DZX8uYK2.js.gz +0 -0
  121. package/dist/client/assets/{kanban-definition-UXKFOSKX-Dr2EVP3G.js → kanban-definition-UXKFOSKX-1SmdXf0S.js} +1 -1
  122. package/dist/client/assets/kanban-definition-UXKFOSKX-1SmdXf0S.js.br +0 -0
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-1SmdXf0S.js.gz +0 -0
  124. package/dist/client/assets/{layout-CCzGp_3m.js → layout-De4QxmMN.js} +1 -1
  125. package/dist/client/assets/layout-De4QxmMN.js.br +0 -0
  126. package/dist/client/assets/layout-De4QxmMN.js.gz +0 -0
  127. package/dist/client/assets/{linear-B-AlDkuD.js → linear-Dc93ZnhP.js} +1 -1
  128. package/dist/client/assets/linear-Dc93ZnhP.js.br +0 -0
  129. package/dist/client/assets/linear-Dc93ZnhP.js.gz +0 -0
  130. package/dist/client/assets/{mermaid.core-C8GTgQvN.js → mermaid.core-Ny4ifEul.js} +5 -5
  131. package/dist/client/assets/mermaid.core-Ny4ifEul.js.br +0 -0
  132. package/dist/client/assets/mermaid.core-Ny4ifEul.js.gz +0 -0
  133. package/dist/client/assets/{mindmap-definition-YA3MSWOX-B1_AIM7r.js → mindmap-definition-YA3MSWOX-CE75rUrK.js} +1 -1
  134. package/dist/client/assets/mindmap-definition-YA3MSWOX-CE75rUrK.js.br +0 -0
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-CE75rUrK.js.gz +0 -0
  136. package/dist/client/assets/{passwordBootstrap-DUJCjLgA.js → passwordBootstrap-BuXV9qsA.js} +1 -1
  137. package/dist/client/assets/passwordBootstrap-BuXV9qsA.js.br +0 -0
  138. package/dist/client/assets/passwordBootstrap-BuXV9qsA.js.gz +0 -0
  139. package/dist/client/assets/{pegDiagram-XKGWAZYB-BGri_6xk.js → pegDiagram-XKGWAZYB-CR8E9wWF.js} +1 -1
  140. package/dist/client/assets/pegDiagram-XKGWAZYB-CR8E9wWF.js.br +0 -0
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-CR8E9wWF.js.gz +0 -0
  142. package/dist/client/assets/{pieDiagram-E7YTZNPT-DyJUIUtO.js → pieDiagram-E7YTZNPT-CKZz2-Iz.js} +1 -1
  143. package/dist/client/assets/pieDiagram-E7YTZNPT-CKZz2-Iz.js.br +0 -0
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-CKZz2-Iz.js.gz +0 -0
  145. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-ChYx832r.js → quadrantDiagram-AXDQQJYC-w0wqPGg1.js} +1 -1
  146. package/dist/client/assets/quadrantDiagram-AXDQQJYC-w0wqPGg1.js.br +0 -0
  147. package/dist/client/assets/quadrantDiagram-AXDQQJYC-w0wqPGg1.js.gz +0 -0
  148. package/dist/client/assets/{railroadDiagram-O6MQD6OU-Ll7CEGCv.js → railroadDiagram-O6MQD6OU-CWYxj4Iu.js} +1 -1
  149. package/dist/client/assets/railroadDiagram-O6MQD6OU-CWYxj4Iu.js.br +0 -0
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-CWYxj4Iu.js.gz +0 -0
  151. package/dist/client/assets/{referenceLongPress-DhODL_ZR.js → referenceLongPress-xQTJmyUv.js} +1 -1
  152. package/dist/client/assets/referenceLongPress-xQTJmyUv.js.br +0 -0
  153. package/dist/client/assets/referenceLongPress-xQTJmyUv.js.gz +0 -0
  154. package/dist/client/assets/{requirementDiagram-BXWQKSXE-WAFfi6wC.js → requirementDiagram-BXWQKSXE-DzcUlIZo.js} +1 -1
  155. package/dist/client/assets/requirementDiagram-BXWQKSXE-DzcUlIZo.js.br +0 -0
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-DzcUlIZo.js.gz +0 -0
  157. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-Bmk9JzLH.js → sankeyDiagram-P5KCCOFB-B1KFzqqb.js} +1 -1
  158. package/dist/client/assets/sankeyDiagram-P5KCCOFB-B1KFzqqb.js.br +0 -0
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-B1KFzqqb.js.gz +0 -0
  160. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CQLp4wz7.js → sequenceDiagram-WJ2MYXX4-Bph5Zy95.js} +1 -1
  161. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Bph5Zy95.js.br +0 -0
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Bph5Zy95.js.gz +0 -0
  163. package/dist/client/assets/{sizeCapture-INFHLROL-7biER85h.js → sizeCapture-INFHLROL-B22qdSe2.js} +1 -1
  164. package/dist/client/assets/sizeCapture-INFHLROL-B22qdSe2.js.br +0 -0
  165. package/dist/client/assets/sizeCapture-INFHLROL-B22qdSe2.js.gz +0 -0
  166. package/dist/client/assets/{sparkles-Bdif6oju.js → sparkles-iFGazcNw.js} +1 -1
  167. package/dist/client/assets/{stateDiagram-D77RDMKH-Cy9r_YvI.js → stateDiagram-D77RDMKH-qt4ZIeIU.js} +1 -1
  168. package/dist/client/assets/stateDiagram-D77RDMKH-qt4ZIeIU.js.br +0 -0
  169. package/dist/client/assets/stateDiagram-D77RDMKH-qt4ZIeIU.js.gz +0 -0
  170. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-xKezo5Hj.js +1 -0
  171. package/dist/client/assets/{swimlanes-42K2YHIH-DeAGlPA-.js → swimlanes-42K2YHIH-BLCfdR2d.js} +1 -1
  172. package/dist/client/assets/swimlanes-42K2YHIH-BLCfdR2d.js.br +0 -0
  173. package/dist/client/assets/swimlanes-42K2YHIH-BLCfdR2d.js.gz +0 -0
  174. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CNA032xr.js +8 -0
  175. package/dist/client/assets/{timeline-definition-24CTP7MA-BRAobv6g.js → timeline-definition-24CTP7MA-B1KrZ6Jd.js} +1 -1
  176. package/dist/client/assets/timeline-definition-24CTP7MA-B1KrZ6Jd.js.br +0 -0
  177. package/dist/client/assets/timeline-definition-24CTP7MA-B1KrZ6Jd.js.gz +0 -0
  178. package/dist/client/assets/{triangle-alert-Bllwcw-n.js → triangle-alert-BNMREFNU.js} +1 -1
  179. package/dist/client/assets/{vennDiagram-4TSXK5OY-B4uz7tHn.js → vennDiagram-4TSXK5OY-DWNDoCmv.js} +1 -1
  180. package/dist/client/assets/vennDiagram-4TSXK5OY-DWNDoCmv.js.br +0 -0
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-DWNDoCmv.js.gz +0 -0
  182. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-uRNjWdqL.js → wardleyDiagram-VM6X3IG4-kdVZqb5Z.js} +1 -1
  183. package/dist/client/assets/wardleyDiagram-VM6X3IG4-kdVZqb5Z.js.br +0 -0
  184. package/dist/client/assets/wardleyDiagram-VM6X3IG4-kdVZqb5Z.js.gz +0 -0
  185. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DCT4HGtL.js → xychartDiagram-S5SC5T6Z-C1U9Cr8F.js} +1 -1
  186. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C1U9Cr8F.js.br +0 -0
  187. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C1U9Cr8F.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.js +1 -1
  192. package/dist/client/sw.js.br +0 -0
  193. package/dist/client/sw.js.gz +0 -0
  194. package/dist/server/agent/collaborationCli.js +224 -11
  195. package/dist/server/agent/collaborationDeliveryWorker.js +94 -1
  196. package/dist/server/agent/collaborationGroupRoutes.js +25 -0
  197. package/dist/server/agent/collaborationPrompt.js +97 -34
  198. package/dist/server/agent/collaborationRoutes.js +119 -9
  199. package/dist/server/agent/collaborationSpawn.js +16 -0
  200. package/dist/server/agent/collaborationStore.js +86 -12
  201. package/dist/server/agent/collaborationTmuxDelivery.js +86 -1
  202. package/dist/server/cli.js +11 -4
  203. package/dist/server/routes/terminal.js +271 -13
  204. package/package.json +1 -1
  205. package/runtime-manifest.json +3 -3
  206. package/dist/client/assets/App-BkcaQXGe.js.br +0 -0
  207. package/dist/client/assets/App-BkcaQXGe.js.gz +0 -0
  208. package/dist/client/assets/ChangeWalkthroughPanel-BkTM7zQ0.js.br +0 -0
  209. package/dist/client/assets/ChangeWalkthroughPanel-BkTM7zQ0.js.gz +0 -0
  210. package/dist/client/assets/DagPlayground-7UxLIhH5.js.br +0 -0
  211. package/dist/client/assets/DagPlayground-7UxLIhH5.js.gz +0 -0
  212. package/dist/client/assets/DiffLab-Cn2bPbbB.js.br +0 -0
  213. package/dist/client/assets/DiffLab-Cn2bPbbB.js.gz +0 -0
  214. package/dist/client/assets/DiffReview-Bax7DQmN.js.br +0 -0
  215. package/dist/client/assets/DiffReview-Bax7DQmN.js.gz +0 -0
  216. package/dist/client/assets/DiffReviewLab-TG4kjagX.js.br +0 -0
  217. package/dist/client/assets/DiffReviewLab-TG4kjagX.js.gz +0 -0
  218. package/dist/client/assets/DiffViewer-3MezBn_a.js.br +0 -0
  219. package/dist/client/assets/DiffViewer-3MezBn_a.js.gz +0 -0
  220. package/dist/client/assets/ModelPreview-BV_0H-Ls.js.br +0 -0
  221. package/dist/client/assets/ModelPreview-BV_0H-Ls.js.gz +0 -0
  222. package/dist/client/assets/RightSidebar-BHlwv3gW.js.br +0 -0
  223. package/dist/client/assets/RightSidebar-BHlwv3gW.js.gz +0 -0
  224. package/dist/client/assets/abnfDiagram-VCTEODGH-DOk0otQq.js.br +0 -0
  225. package/dist/client/assets/abnfDiagram-VCTEODGH-DOk0otQq.js.gz +0 -0
  226. package/dist/client/assets/app-B0az5Yz6.js +0 -34
  227. package/dist/client/assets/app-B0az5Yz6.js.br +0 -0
  228. package/dist/client/assets/app-B0az5Yz6.js.gz +0 -0
  229. package/dist/client/assets/arc-CfeOYtSi.js.br +0 -0
  230. package/dist/client/assets/arc-CfeOYtSi.js.gz +0 -0
  231. package/dist/client/assets/architectureDiagram-5GKGNRK7-Dys1Wr-I.js.br +0 -0
  232. package/dist/client/assets/architectureDiagram-5GKGNRK7-Dys1Wr-I.js.gz +0 -0
  233. package/dist/client/assets/blockDiagram-I7D4REHJ-D8DbjMtS.js.br +0 -0
  234. package/dist/client/assets/blockDiagram-I7D4REHJ-D8DbjMtS.js.gz +0 -0
  235. package/dist/client/assets/c4Diagram-7LVT6UL2-DJcMUQXb.js.br +0 -0
  236. package/dist/client/assets/c4Diagram-7LVT6UL2-DJcMUQXb.js.gz +0 -0
  237. package/dist/client/assets/channel-SwcBL8jW.js +0 -1
  238. package/dist/client/assets/chunk-F27PBJKO-BiUpuqFN.js.br +0 -0
  239. package/dist/client/assets/chunk-F27PBJKO-BiUpuqFN.js.gz +0 -0
  240. package/dist/client/assets/chunk-IMKFNOWR-Bvca4yZA.js.br +0 -0
  241. package/dist/client/assets/chunk-IMKFNOWR-Bvca4yZA.js.gz +0 -0
  242. package/dist/client/assets/chunk-SVP7TREG-D4Qm28xz.js.br +0 -0
  243. package/dist/client/assets/chunk-SVP7TREG-D4Qm28xz.js.gz +0 -0
  244. package/dist/client/assets/chunk-TICWLB2K-CS0AERD9.js.br +0 -0
  245. package/dist/client/assets/chunk-TICWLB2K-CS0AERD9.js.gz +0 -0
  246. package/dist/client/assets/classDiagram-ZZMXUADV-BOSTHjwM.js +0 -1
  247. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BOSTHjwM.js +0 -1
  248. package/dist/client/assets/cose-bilkent-JH36ORCC-D0-yuoIn.js.br +0 -0
  249. package/dist/client/assets/cose-bilkent-JH36ORCC-D0-yuoIn.js.gz +0 -0
  250. package/dist/client/assets/cynefin-OW5HDTMX-C9ZqMdi9.js.br +0 -0
  251. package/dist/client/assets/cynefin-OW5HDTMX-C9ZqMdi9.js.gz +0 -0
  252. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Drvx9Dke.js.br +0 -0
  253. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Drvx9Dke.js.gz +0 -0
  254. package/dist/client/assets/dagre-GXQ25YYZ-mUXNFR_H.js.br +0 -0
  255. package/dist/client/assets/dagre-GXQ25YYZ-mUXNFR_H.js.gz +0 -0
  256. package/dist/client/assets/diagram-S7CK7UJ4-CKJgF0VS.js.br +0 -0
  257. package/dist/client/assets/diagram-S7CK7UJ4-CKJgF0VS.js.gz +0 -0
  258. package/dist/client/assets/diagram-UQ7AKVKN-Cp9NE2Gc.js.br +0 -0
  259. package/dist/client/assets/diagram-UQ7AKVKN-Cp9NE2Gc.js.gz +0 -0
  260. package/dist/client/assets/diagram-VSXAHHWV-mnyDXn4l.js.br +0 -0
  261. package/dist/client/assets/diagram-VSXAHHWV-mnyDXn4l.js.gz +0 -0
  262. package/dist/client/assets/diagram-VX7I27RA-6lyjUw6O.js.br +0 -0
  263. package/dist/client/assets/diagram-VX7I27RA-6lyjUw6O.js.gz +0 -0
  264. package/dist/client/assets/diagram-Z3DM3KII-3K_Dhn1t.js.br +0 -0
  265. package/dist/client/assets/diagram-Z3DM3KII-3K_Dhn1t.js.gz +0 -0
  266. package/dist/client/assets/ebnfDiagram-PWID7BFC-B2mHkMsE.js.br +0 -0
  267. package/dist/client/assets/ebnfDiagram-PWID7BFC-B2mHkMsE.js.gz +0 -0
  268. package/dist/client/assets/erDiagram-RLTQ6QDP-Brfc7YEf.js.br +0 -0
  269. package/dist/client/assets/erDiagram-RLTQ6QDP-Brfc7YEf.js.gz +0 -0
  270. package/dist/client/assets/flowDiagram-HODETNUW-UGHsvQMc.js.br +0 -0
  271. package/dist/client/assets/flowDiagram-HODETNUW-UGHsvQMc.js.gz +0 -0
  272. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CC30lZuL.js.br +0 -0
  273. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CC30lZuL.js.gz +0 -0
  274. package/dist/client/assets/gestureArbiter-DIYXjBYY.js.br +0 -0
  275. package/dist/client/assets/gestureArbiter-DIYXjBYY.js.gz +0 -0
  276. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CiwHG61R.js.br +0 -0
  277. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CiwHG61R.js.gz +0 -0
  278. package/dist/client/assets/index-TEmneMvF.css.br +0 -0
  279. package/dist/client/assets/ishikawaDiagram-5VMMS53U-COFtLTBs.js.br +0 -0
  280. package/dist/client/assets/ishikawaDiagram-5VMMS53U-COFtLTBs.js.gz +0 -0
  281. package/dist/client/assets/journeyDiagram-3NMN7TZE-D0iXlEDw.js.br +0 -0
  282. package/dist/client/assets/journeyDiagram-3NMN7TZE-D0iXlEDw.js.gz +0 -0
  283. package/dist/client/assets/kanban-definition-UXKFOSKX-Dr2EVP3G.js.br +0 -0
  284. package/dist/client/assets/kanban-definition-UXKFOSKX-Dr2EVP3G.js.gz +0 -0
  285. package/dist/client/assets/layout-CCzGp_3m.js.br +0 -0
  286. package/dist/client/assets/layout-CCzGp_3m.js.gz +0 -0
  287. package/dist/client/assets/linear-B-AlDkuD.js.br +0 -0
  288. package/dist/client/assets/linear-B-AlDkuD.js.gz +0 -0
  289. package/dist/client/assets/mermaid.core-C8GTgQvN.js.br +0 -0
  290. package/dist/client/assets/mermaid.core-C8GTgQvN.js.gz +0 -0
  291. package/dist/client/assets/mindmap-definition-YA3MSWOX-B1_AIM7r.js.br +0 -0
  292. package/dist/client/assets/mindmap-definition-YA3MSWOX-B1_AIM7r.js.gz +0 -0
  293. package/dist/client/assets/passwordBootstrap-DUJCjLgA.js.br +0 -0
  294. package/dist/client/assets/passwordBootstrap-DUJCjLgA.js.gz +0 -0
  295. package/dist/client/assets/pegDiagram-XKGWAZYB-BGri_6xk.js.br +0 -0
  296. package/dist/client/assets/pegDiagram-XKGWAZYB-BGri_6xk.js.gz +0 -0
  297. package/dist/client/assets/pieDiagram-E7YTZNPT-DyJUIUtO.js.br +0 -0
  298. package/dist/client/assets/pieDiagram-E7YTZNPT-DyJUIUtO.js.gz +0 -0
  299. package/dist/client/assets/quadrantDiagram-AXDQQJYC-ChYx832r.js.br +0 -0
  300. package/dist/client/assets/quadrantDiagram-AXDQQJYC-ChYx832r.js.gz +0 -0
  301. package/dist/client/assets/railroadDiagram-O6MQD6OU-Ll7CEGCv.js.br +0 -0
  302. package/dist/client/assets/railroadDiagram-O6MQD6OU-Ll7CEGCv.js.gz +0 -0
  303. package/dist/client/assets/referenceLongPress-DhODL_ZR.js.br +0 -0
  304. package/dist/client/assets/referenceLongPress-DhODL_ZR.js.gz +0 -0
  305. package/dist/client/assets/requirementDiagram-BXWQKSXE-WAFfi6wC.js.br +0 -0
  306. package/dist/client/assets/requirementDiagram-BXWQKSXE-WAFfi6wC.js.gz +0 -0
  307. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bmk9JzLH.js.br +0 -0
  308. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bmk9JzLH.js.gz +0 -0
  309. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CQLp4wz7.js.br +0 -0
  310. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CQLp4wz7.js.gz +0 -0
  311. package/dist/client/assets/sizeCapture-INFHLROL-7biER85h.js.br +0 -0
  312. package/dist/client/assets/sizeCapture-INFHLROL-7biER85h.js.gz +0 -0
  313. package/dist/client/assets/stateDiagram-D77RDMKH-Cy9r_YvI.js.br +0 -0
  314. package/dist/client/assets/stateDiagram-D77RDMKH-Cy9r_YvI.js.gz +0 -0
  315. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DOly_YL3.js +0 -1
  316. package/dist/client/assets/swimlanes-42K2YHIH-DeAGlPA-.js.br +0 -0
  317. package/dist/client/assets/swimlanes-42K2YHIH-DeAGlPA-.js.gz +0 -0
  318. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DxYgGhLH.js +0 -8
  319. package/dist/client/assets/timeline-definition-24CTP7MA-BRAobv6g.js.br +0 -0
  320. package/dist/client/assets/timeline-definition-24CTP7MA-BRAobv6g.js.gz +0 -0
  321. package/dist/client/assets/vennDiagram-4TSXK5OY-B4uz7tHn.js.br +0 -0
  322. package/dist/client/assets/vennDiagram-4TSXK5OY-B4uz7tHn.js.gz +0 -0
  323. package/dist/client/assets/wardleyDiagram-VM6X3IG4-uRNjWdqL.js.br +0 -0
  324. package/dist/client/assets/wardleyDiagram-VM6X3IG4-uRNjWdqL.js.gz +0 -0
  325. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DCT4HGtL.js.br +0 -0
  326. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DCT4HGtL.js.gz +0 -0
@@ -1,6 +1,13 @@
1
1
  import { Router } from 'express';
2
2
  import { COLLAB_LIMITS, CollaborationError, extrasFromBody } from './collaborationProtocol.js';
3
- export function collaborationRoutes({ store, resolveSession, deliver, rebind }) {
3
+ /** Member names arrive agent-authored over a CLI; control characters would
4
+ * corrupt terminal shells, tmux options and persisted records. Fold them to
5
+ * spaces, collapse runs, and cap the length (display sanitization for the
6
+ * delivery shell stays in the presentation layer). */
7
+ export function cleanMemberName(value) {
8
+ return value.replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 80);
9
+ }
10
+ export function collaborationRoutes({ store, resolveSession, deliver, rebind, resolveNames, renameSession }) {
4
11
  const router = Router();
5
12
  const run = (handler) => async (req, res) => {
6
13
  try {
@@ -34,6 +41,27 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind })
34
41
  const receipts = ids.map((id) => store.receipt(id));
35
42
  res.json({ ok: true, ...receipts[0], messages, receipts });
36
43
  };
44
+ /** When the recipient is served by this server's own delivery worker, give
45
+ * the write (and its post-write snapshot capture) a short window to finish
46
+ * before the receipt leaves the send/reply call — the sender then sees the
47
+ * recipient's screen right away. Un-waitable targets (relayed/federated,
48
+ * offline, or a mock deliver) return immediately with the message queued. */
49
+ const awaitDelivery = async (id, outcome) => {
50
+ if (!outcome?.waitable)
51
+ return;
52
+ const deadline = Date.now() + 2_000;
53
+ for (;;) {
54
+ const receipt = store.receipt(id);
55
+ const settled = receipt.status !== 'pending'
56
+ || receipt.snapshot != null
57
+ || (receipt.attempt_count > 0
58
+ && receipt.last_error !== 'DELIVERY_IN_PROGRESS'
59
+ && (receipt.next_retry_at ?? 0) > Date.now());
60
+ if (settled || Date.now() >= deadline)
61
+ return;
62
+ await new Promise((resolve) => setTimeout(resolve, 100));
63
+ }
64
+ };
37
65
  router.get('/capabilities', run((_req, res) => {
38
66
  res.json({ protocol_version: 2, limits: COLLAB_LIMITS,
39
67
  routing: { background_recovery: true, explicit_rebind: Boolean(rebind), fixed_tmux_pane: true },
@@ -44,20 +72,37 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind })
44
72
  timeout: 'stops waiting, does not cancel delivery',
45
73
  retry: 'same message id may be submitted again after a crash or uncertain transport result; consumers deduplicate by message id' } });
46
74
  }));
47
- router.post('/send', run((req, res, sessionId) => {
75
+ router.post('/send', run(async (req, res, sessionId) => {
48
76
  const target = typeof req.body.targetSessionId === 'string' ? req.body.targetSessionId.trim() : '';
49
- const groups = store.groupsForSession(sessionId).filter((group) => group.sessionIds.includes(target) && (!req.body.group_id || group.id === req.body.group_id));
77
+ // A fan-out send lists every recipient in toSessionIds; the store persists
78
+ // each edge with its sibling fanOutIds so recipients can tell a broadcast
79
+ // from a one-to-one assignment. The single-recipient shape stays intact.
80
+ const requestedTargets = Array.isArray(req.body.toSessionIds)
81
+ ? [...new Set(req.body.toSessionIds.filter((id) => typeof id === 'string' && id.trim().length > 0))]
82
+ : [];
83
+ const targets = requestedTargets.length > 0 ? requestedTargets : (target ? [target] : []);
84
+ if (!targets.length)
85
+ throw new CollaborationError('NO_TARGET', 'send requires a targetSessionId (or toSessionIds for a fan-out)', 400);
86
+ const groups = store.groupsForSession(sessionId)
87
+ .filter((group) => targets.every((candidate) => group.sessionIds.includes(candidate)) && (!req.body.group_id || group.id === req.body.group_id));
50
88
  if (!groups.length)
51
- throw new CollaborationError('GROUP_NOT_FOUND', 'Sender and recipient must share the specified group');
89
+ throw new CollaborationError('GROUP_NOT_FOUND', 'Sender and recipients must share the specified group');
52
90
  if (groups.length > 1 && !req.body.group_id)
53
91
  throw new CollaborationError('AMBIGUOUS_GROUP', 'Multiple shared groups; specify --group');
54
- const messages = store.send({ ...extrasFromBody(req.body), groupId: groups[0].id, fromSessionId: sessionId, toSessionIds: [target],
92
+ if (req.body.task)
93
+ throw new CollaborationError('DISPATCH_CARRIES_TASK', 'Dispatch carries no task state; recipients report task status through replies', 400);
94
+ const messages = store.send({ ...extrasFromBody(req.body), groupId: groups[0].id, fromSessionId: sessionId, toSessionIds: targets,
55
95
  kind: (req.body.kind ?? 'message'), content: typeof req.body.message === 'string' ? req.body.message : '',
56
96
  threadId: typeof req.body.thread_id === 'string' ? req.body.thread_id : undefined });
57
- deliver(target);
97
+ // Wake and await every local recipient edge in parallel; relayed or
98
+ // federated ids answer immediately with the message queued, so fan-out
99
+ // never stalls on a peer.
100
+ await Promise.all(messages.map(async (message) => {
101
+ await awaitDelivery(message.id, (await deliver(message.toSessionId)) ?? null);
102
+ }));
58
103
  sent(res, messages.map((message) => message.id));
59
104
  }));
60
- router.post('/reply', run((req, res, sessionId) => {
105
+ router.post('/reply', run(async (req, res, sessionId) => {
61
106
  const original = ownMessage(String(req.body.messageId ?? ''), sessionId, true);
62
107
  if (!original.fromSessionId)
63
108
  throw new CollaborationError('NO_REPLY_TARGET', 'User messages have no agent reply target');
@@ -66,7 +111,7 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind })
66
111
  replyTo: original.id, threadId: original.threadId });
67
112
  // Reply is explicit consumption; only mark after successful validation/persistence.
68
113
  store.markRead([original.id]);
69
- deliver(original.fromSessionId);
114
+ await awaitDelivery(messages[0].id, (await deliver(original.fromSessionId)) ?? null);
70
115
  sent(res, messages.map((message) => message.id));
71
116
  }));
72
117
  router.get('/message/:id', run((req, res, sessionId) => {
@@ -87,11 +132,76 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind })
87
132
  const page = store.page(sessionId, { unread: string('unread') === 'true', since, afterId: string('after_id'), cursor: string('cursor'),
88
133
  consumer: string('consumer'), limit: string('limit') ? Number(string('limit')) : undefined,
89
134
  from: string('from'), group: string('group'), thread: string('thread'), kind: string('kind'), responseKind: string('response_kind'), order: string('order') });
90
- res.json({ ok: true, ...page });
135
+ // Fan-out edges carry sibling recipients; the names map lets the CLI text
136
+ // renderer show 同时发给了:… as people instead of bare session ids. The
137
+ // fallback to the raw id stays server-side agnostic (federated/offline).
138
+ const nameIds = [...new Set(page.messages.flatMap((message) => [message.fromSessionId, ...(message.fanOutIds ?? [])].filter((id) => typeof id === 'string' && id.length > 0)))];
139
+ res.json({ ok: true, ...page, ...(nameIds.length ? { names: resolveNames?.(nameIds) ?? {} } : {}) });
91
140
  }));
92
141
  router.post('/cursor/commit', run((req, res, sessionId) => {
93
142
  store.commitCursor(sessionId, String(req.body.cursor ?? ''), String(req.body.consumer ?? ''));
94
143
  res.json({ ok: true, consumer: req.body.consumer, cursor: req.body.cursor });
95
144
  }));
145
+ const groupOf = (id, sessionId) => {
146
+ const group = store.getGroup(id);
147
+ if (!group || group.deleted)
148
+ throw new CollaborationError('GROUP_NOT_FOUND', 'Collaboration group not found', 404);
149
+ if (!group.sessionIds.includes(sessionId))
150
+ throw new CollaborationError('NOT_A_MEMBER', 'Session is not a member of this group', 403);
151
+ return group;
152
+ };
153
+ // Member names ride along so role lists read as people, not bare ids; the
154
+ // caller falls back to the session id when a name is unknown (federated or
155
+ // offline members have no local record).
156
+ const groupView = (group) => {
157
+ const names = resolveNames?.(group.sessionIds) ?? {};
158
+ return {
159
+ id: group.id, name: group.name, sessionIds: group.sessionIds,
160
+ members: group.sessionIds.map((sessionId) => ({ sessionId, name: names[sessionId] ?? null })),
161
+ roles: group.roles ?? {},
162
+ };
163
+ };
164
+ router.get('/role', run((req, res, sessionId) => {
165
+ const groupId = String(req.query.group ?? '');
166
+ if (groupId) {
167
+ res.json({ ok: true, group: groupView(groupOf(groupId, sessionId)) });
168
+ return;
169
+ }
170
+ // Without a group this lists the caller's own groups with full role
171
+ // snapshots — what `td collab --help` renders so a newcomer sees every
172
+ // member's role in one step instead of hunting the group id first.
173
+ res.json({ ok: true, groups: store.groupsForSession(sessionId)
174
+ .filter((group) => !group.deleted)
175
+ .map(groupView) });
176
+ }));
177
+ router.post('/role', run((req, res, sessionId) => {
178
+ const group = groupOf(String(req.body.group_id ?? ''), sessionId);
179
+ const role = req.body.role;
180
+ if (typeof req.body.session_id !== 'string' || !group.sessionIds.includes(req.body.session_id)) {
181
+ throw new CollaborationError('NOT_A_MEMBER', 'Role target must be a member of this group', 400);
182
+ }
183
+ if (role === undefined || (role !== null && typeof role !== 'string')) {
184
+ throw new CollaborationError('INVALID_ROLE', 'role must be text or null', 400);
185
+ }
186
+ const updated = store.setRole({ groupId: group.id, sessionId: req.body.session_id, role: role });
187
+ res.json({ ok: true, group: { ...updated, roles: updated.roles ?? {} } });
188
+ }));
189
+ router.post('/name', run(async (req, res, sessionId) => {
190
+ if (!renameSession)
191
+ throw new CollaborationError('RENAME_UNAVAILABLE', 'Member renaming is not available', 503);
192
+ const target = typeof req.body.session_id === 'string' ? req.body.session_id.trim() : '';
193
+ if (!store.groupsForSession(sessionId).some((group) => !group.deleted && group.sessionIds.includes(target))) {
194
+ throw new CollaborationError('NOT_A_MEMBER', 'Rename target must share a collaboration group with the caller', 403);
195
+ }
196
+ const name = typeof req.body.name === 'string' ? cleanMemberName(req.body.name) : '';
197
+ if (!name)
198
+ throw new CollaborationError('INVALID_NAME', 'name must be non-empty text up to 80 characters', 400);
199
+ const result = await renameSession(target, name);
200
+ if (!result.ok) {
201
+ const missing = result.code === 'SESSION_NOT_FOUND';
202
+ throw new CollaborationError(missing ? 'SESSION_NOT_FOUND' : 'RENAME_FAILED', result.error ?? 'Unable to rename the member', missing ? 404 : 400);
203
+ }
204
+ res.json({ ok: true, sessionId: target, name });
205
+ }));
96
206
  return router;
97
207
  }
@@ -7,3 +7,19 @@ export function resolveCollaborationSpawnMode(input) {
7
7
  ?? asSpawnMode(input.fallbackMode)
8
8
  ?? 'shell';
9
9
  }
10
+ /**
11
+ * Shell command line that boots a collaboration member agent. Spawned agents
12
+ * have no human at the keyboard, yet their delivery shells instruct them to
13
+ * run `td collab …` — without pre-authorization every such call stops at the
14
+ * agent's permission dialog and the collaboration loop stalls. Agents whose
15
+ * CLI flags are known get a scoped allowlist (collab surface only; the server
16
+ * guards every collab action anyway). Unsupported agents launch plain — the
17
+ * delivery confirm gate can dismiss their dialogs instead.
18
+ */
19
+ export function buildCollaborationSpawnCommand(input) {
20
+ const { slug, command } = input;
21
+ if (slug === 'claude') {
22
+ return `${command} --allowedTools "Bash(td collab *)"`;
23
+ }
24
+ return command;
25
+ }
@@ -2,6 +2,25 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import crypto from 'node:crypto';
4
4
  import { COLLAB_LIMITS, CollaborationError, STATUS_RANK, validateExtras } from './collaborationProtocol.js';
5
+ /** Characters that would corrupt a role line injected into the delivery shell
6
+ * (`你的定位:...`) or smuggle terminal output. Unlike names, roles are plain
7
+ * prose — 「」 and · are harmless there — so only control sequences are barred. */
8
+ export const COLLAB_ROLE_FORBIDDEN = /[\x00-\x1f\x7f]/;
9
+ export const COLLAB_ROLE_MAX_CHARS = 200;
10
+ /** Data/render-safe role: control characters collapse to spaces, whitespace
11
+ * folds, and the display length is capped with an ellipsis. Applied on write
12
+ * and again defensively at render time (federated peers may carry junk). */
13
+ export function sanitizeCollaborationRole(role, max = COLLAB_ROLE_MAX_CHARS) {
14
+ const cleaned = role.replace(/[\x00-\x1f\x7f]/g, ' ').replace(/\s+/g, ' ').trim();
15
+ return cleaned.length > max ? `${cleaned.slice(0, max - 1)}…` : cleaned;
16
+ }
17
+ /** Drop roles whose member has left the group; survivors keep theirs. */
18
+ function pruneRoles(group, sessionIds) {
19
+ if (!group.roles)
20
+ return group;
21
+ const roles = Object.fromEntries(Object.entries(group.roles).filter(([id]) => sessionIds.includes(id)));
22
+ return Object.keys(roles).length ? { ...group, roles } : { ...group, roles: undefined };
23
+ }
5
24
  const MAX_MESSAGES = 2_000;
6
25
  const MESSAGE_KINDS = new Set(['message', 'ask', 'reply', 'task', 'handoff', 'done']);
7
26
  export class CollaborationStore {
@@ -41,21 +60,44 @@ export class CollaborationStore {
41
60
  save(input) {
42
61
  const now = Date.now();
43
62
  const existing = input.id ? this.document.groups.find((group) => group.id === input.id) : null;
44
- const group = {
63
+ const sessionIds = Array.from(new Set(input.sessionIds.map((id) => id.trim()).filter(Boolean)));
64
+ const group = pruneRoles({
45
65
  ...existing,
46
- ...(existing?.remoteSessions ? { remoteSessions: existing.remoteSessions.filter((session) => input.sessionIds.includes(session.sessionId)) } : {}),
66
+ ...(existing?.remoteSessions ? { remoteSessions: existing.remoteSessions.filter((session) => sessionIds.includes(session.sessionId)) } : {}),
47
67
  id: existing?.id ?? crypto.randomUUID(),
48
68
  name: input.name.trim(),
49
- sessionIds: Array.from(new Set(input.sessionIds.map((id) => id.trim()).filter(Boolean))),
69
+ sessionIds,
50
70
  createdAt: existing?.createdAt ?? now,
51
71
  updatedAt: Math.max(now, (existing?.updatedAt ?? 0) + 1),
52
- };
72
+ }, sessionIds);
53
73
  this.document.groups = existing
54
74
  ? this.document.groups.map((candidate) => candidate.id === group.id ? group : candidate)
55
75
  : [...this.document.groups, group];
56
76
  this.persist();
57
77
  return group;
58
78
  }
79
+ /** Set or clear a member's role (定位). The target may be any group member —
80
+ * oneself, a peer, or a federated remote session; peers can set each other's
81
+ * roles. Roles ride the group document into federation and are dropped
82
+ * together with the member on removal. */
83
+ setRole(input) {
84
+ const group = this.getGroup(input.groupId);
85
+ if (!group || group.deleted)
86
+ throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
87
+ if (!group.sessionIds.includes(input.sessionId))
88
+ throw new CollaborationError('NOT_A_MEMBER', '该会话不在协作组中,无法设置定位', 400);
89
+ const role = input.role === null ? null : sanitizeCollaborationRole(input.role);
90
+ const roles = { ...(group.roles ?? {}) };
91
+ if (role)
92
+ roles[input.sessionId] = role;
93
+ else
94
+ delete roles[input.sessionId];
95
+ const updated = { ...group, roles: Object.keys(roles).length ? roles : undefined,
96
+ updatedAt: Math.max(Date.now(), group.updatedAt + 1) };
97
+ this.document.groups = this.document.groups.map((candidate) => candidate.id === group.id ? updated : candidate);
98
+ this.persist();
99
+ return updated;
100
+ }
59
101
  remove(id) {
60
102
  const before = this.document.groups.length;
61
103
  const existing = this.getGroup(id);
@@ -88,9 +130,9 @@ export class CollaborationStore {
88
130
  if (group.id === source.id) {
89
131
  if (dissolved)
90
132
  return group.federated ? [{ ...group, deleted: true, updatedAt: Math.max(now, group.updatedAt + 1) }] : [];
91
- return [{ ...group, sessionIds: sourceIds,
133
+ return [pruneRoles({ ...group, sessionIds: sourceIds,
92
134
  ...(group.remoteSessions ? { remoteSessions: group.remoteSessions.filter((session) => sourceIds.includes(session.sessionId)) } : {}),
93
- updatedAt: Math.max(now, group.updatedAt + 1) }];
135
+ updatedAt: Math.max(now, group.updatedAt + 1) }, sourceIds)];
94
136
  }
95
137
  return group.id === target.id ? [{ ...group, sessionIds: [...new Set([...group.sessionIds, input.sessionId])], updatedAt: Math.max(now, group.updatedAt + 1) }] : [group];
96
138
  }),
@@ -109,7 +151,8 @@ export class CollaborationStore {
109
151
  return [group];
110
152
  if (dissolvedIds.has(group.id))
111
153
  return group.federated ? [{ ...group, deleted: true, updatedAt: Math.max(now, group.updatedAt + 1) }] : [];
112
- return [{ ...group, sessionIds: group.sessionIds.filter((id) => id !== sessionId), updatedAt: Math.max(now, group.updatedAt + 1) }];
154
+ const remaining = group.sessionIds.filter((id) => id !== sessionId);
155
+ return [pruneRoles({ ...group, sessionIds: remaining, updatedAt: Math.max(now, group.updatedAt + 1) }, remaining)];
113
156
  });
114
157
  this.document.messages = this.document.messages.filter((message) => !dissolvedIds.has(message.groupId)
115
158
  && message.fromSessionId !== sessionId
@@ -160,6 +203,7 @@ export class CollaborationStore {
160
203
  sequence: this.nextSequence(),
161
204
  id: crypto.randomUUID(), groupId: group.id, fromSessionId: input.fromSessionId,
162
205
  toSessionId, kind: input.kind, content, threadId,
206
+ fanOutIds: recipients.length > 1 ? recipients.filter((id) => id !== toSessionId) : undefined,
163
207
  replyTo: input.replyTo?.trim() || null, status: 'pending', createdAt: now,
164
208
  deliveredAt: null, readAt: null,
165
209
  }));
@@ -195,10 +239,14 @@ export class CollaborationStore {
195
239
  markDelivered(messageIds) {
196
240
  return this.updateStatus(messageIds, 'delivered');
197
241
  }
198
- markRead(messageIds) {
199
- return this.updateStatus(messageIds, 'read');
242
+ /**
243
+ * @param source 'explicit' — a consumer acknowledged the message (CLI/UI).
244
+ * Idempotent; never implies an application-level ACK.
245
+ */
246
+ markRead(messageIds, source = 'explicit') {
247
+ return this.updateStatus(messageIds, 'read', source);
200
248
  }
201
- updateStatus(messageIds, status) {
249
+ updateStatus(messageIds, status, readSource = 'explicit') {
202
250
  const ids = new Set(messageIds);
203
251
  this.expire();
204
252
  const now = Date.now();
@@ -207,7 +255,7 @@ export class CollaborationStore {
207
255
  if (!ids.has(message.id) || message.status === 'expired' || message.status === 'failed' || message.status === 'read')
208
256
  return message;
209
257
  const updated = status === 'read'
210
- ? { ...message, status, deliveredAt: message.deliveredAt ?? now, readAt: now, readSource: 'explicit', deliverySource: message.deliverySource ?? (message.deliveredAt === null ? 'consumer_read' : 'legacy_or_unspecified') }
258
+ ? { ...message, status, deliveredAt: message.deliveredAt ?? now, readAt: now, readSource, deliverySource: message.deliverySource ?? (message.deliveredAt === null ? 'consumer_read' : 'legacy_or_unspecified') }
211
259
  : message.status === 'pending' ? { ...message, status, deliveredAt: now, deliverySource: 'pty_written' } : message;
212
260
  changed.push(updated);
213
261
  return updated;
@@ -321,6 +369,13 @@ export class CollaborationStore {
321
369
  this.persist();
322
370
  }
323
371
  }
372
+ setSnapshot(id, snapshot) {
373
+ const message = this.getMessage(id);
374
+ if (!message)
375
+ return;
376
+ message.snapshot = snapshot;
377
+ this.persist();
378
+ }
324
379
  diagnostic(id) { return this.document.transport?.[id] ?? null; }
325
380
  recordTransport(id, diagnostic) {
326
381
  if (!this.getMessage(id))
@@ -337,6 +392,7 @@ export class CollaborationStore {
337
392
  return { message_id: id, thread_id: message.threadId, status: message.status, queued_at: message.createdAt,
338
393
  delivered_at: message.deliveredAt, read_at: message.readAt, expires_at: message.expiresAt ?? null,
339
394
  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',
395
+ snapshot: message.snapshot ?? null,
340
396
  idempotency_key: message.idempotencyKey ?? null,
341
397
  ack_at: replies.find((reply) => reply.responseKind === 'ack')?.createdAt ?? null,
342
398
  reply_ids: replies.map((reply) => reply.id), result_ids: replies.filter((reply) => reply.responseKind === 'result').map((reply) => reply.id),
@@ -354,11 +410,29 @@ export class CollaborationStore {
354
410
  for (const message of reports)
355
411
  if (message.task)
356
412
  tasks.set(message.task.task_id, { task_id: message.task.task_id, status: message.task.status, reported_at: message.createdAt, message_id: message.id });
413
+ // Aggregated from the *latest* report of each task, never inferred: an
414
+ // agent handed a task that has not yet replied is simply idle here —
415
+ // dispatch in flight is the sender-side receipt's concern (pending), not a
416
+ // fact about this session. blocked counts as active: the agent holds the
417
+ // task open. A failed terminal state only surfaces once no task is still
418
+ // in flight, since another in-progress task means the agent is busy.
419
+ const latest = [...tasks.values()];
420
+ const taskState = latest.some((task) => task.status === 'ack' || task.status === 'working' || task.status === 'blocked') ? 'active'
421
+ : latest.some((task) => task.status === 'failed') ? 'failed'
422
+ : 'idle';
357
423
  return { session_state: online ? 'online' : 'offline', turn_state: turnState === 'done' ? 'ended' : turnState ?? 'unknown',
358
- task_state: 'unknown', tasks: [...tasks.values()], last_heartbeat: null, last_tool_activity_at: null,
424
+ task_state: taskState, tasks: [...tasks.values()], last_heartbeat: null, last_tool_activity_at: null,
359
425
  last_message_at: reports.length ? Math.max(...reports.map((message) => message.createdAt)) : null,
360
426
  state_source: 'optional_adapter', task_source: 'explicit_message' };
361
427
  }
428
+ /** Timeline of one task as reported by a session, oldest first. The lineage
429
+ * starts at the recipient's first reply carrying that task_id — a bare
430
+ * dispatch carries no envelope. Messages without a task simply never
431
+ * match. */
432
+ byTask(sessionId, taskId) {
433
+ this.expire();
434
+ return this.document.messages.filter((message) => message.fromSessionId === sessionId && message.task?.task_id === taskId);
435
+ }
362
436
  page(sessionId, options = {}) {
363
437
  this.expire();
364
438
  if (options.responseKind && !['ack', 'progress', 'result'].includes(options.responseKind))
@@ -1,10 +1,95 @@
1
1
  import { buildBracketedSubmitBytes } from './promptDelivery.js';
2
- export async function writeCollaborationTmuxPane(run, pane, prompt) {
2
+ const TMUX_KEY_NAMES = {
3
+ enter: 'Enter', escape: 'Escape', space: 'Space', left: 'Left', right: 'Right', up: 'Up', down: 'Down',
4
+ };
5
+ /** Screen text that means an interactive approval/confirm dialog is showing.
6
+ * Driving approve sends Enter, which activates the highlighted option — the
7
+ * number of tools that render such prompts is finite, but the phrasing is
8
+ * not, so match generously and never press keys without this evidence. */
9
+ const APPROVAL_DIALOG_PATTERNS = [
10
+ /requires approval/i,
11
+ /needs? approval/i,
12
+ /do you want to proceed/i,
13
+ /do you want to (continue|run this)/i,
14
+ /is this ok/i,
15
+ /\b1\.\s*yes\b/i,
16
+ /需要批准|需要授权|等待批准/i,
17
+ /是否(允许|继续|执行|批准)/,
18
+ /\b1\.\s*是\b/,
19
+ /允许.*执行|确认.*执行/i,
20
+ ];
21
+ /** Agent TUIs collapse pasted input in the input box to a marker line like
22
+ * "[Pasted text #3 +42 lines]". While the paste sits unsubmitted the marker
23
+ * stays on screen; a submitted paste clears the box and only the transcript
24
+ * holds the content. Session-unique #N lets us diff screen captures. */
25
+ const PASTE_MARKER_PATTERN = /\[Pasted text #(\d+)/g;
26
+ export function detectApprovalDialog(content) {
27
+ return APPROVAL_DIALOG_PATTERNS.some((pattern) => pattern.test(content));
28
+ }
29
+ /** Paste marker sequence numbers visible in a screen capture. */
30
+ export function extractPasteMarkerNumbers(content) {
31
+ return new Set([...content.matchAll(PASTE_MARKER_PATTERN)].map((match) => Number(match[1])));
32
+ }
33
+ /** True when `after` shows a paste marker that was not in `before`. Diffing
34
+ * prevents a recovery Enter from submitting a stale paste that predates this
35
+ * delivery (or someone else's draft): we only ever submit what we just put
36
+ * there, at most once per delivery. */
37
+ export function hasNewPasteMarker(after, before) {
38
+ const beforeNumbers = extractPasteMarkerNumbers(before);
39
+ return [...extractPasteMarkerNumbers(after)].some((number) => !beforeNumbers.has(number));
40
+ }
41
+ async function assertSamePane(run, pane) {
3
42
  const identity = (await run(['display-message', '-p', '-t', pane.paneId,
4
43
  '#{pid}:#{session_id}:#{pane_id}:#{pane_pid}'])).trim();
5
44
  if (identity !== `${pane.serverPid}:${pane.sessionId}:${pane.paneId}:${pane.panePid}`)
6
45
  throw new Error('TMUX_PANE_CHANGED');
46
+ }
47
+ export async function writeCollaborationTmuxPane(run, pane, prompt) {
48
+ await assertSamePane(run, pane);
7
49
  // A fixed pane target is independent of the current window, keyboard focus
8
50
  // and browser presence. One invocation preserves the bracketed submit bytes.
9
51
  await run(['send-keys', '-t', pane.paneId, '-l', '--', buildBracketedSubmitBytes(prompt)]);
10
52
  }
53
+ /** Inject a named key into a member pane after proving the pane identity
54
+ * unchanged. Named keys only — no freeform keystroke passthrough. */
55
+ export async function sendTmuxPaneKey(run, pane, key) {
56
+ await assertSamePane(run, pane);
57
+ await run(['send-keys', '-t', pane.paneId, TMUX_KEY_NAMES[key]]);
58
+ }
59
+ /** Current pane screen text (viewport), identity-checked. */
60
+ export async function captureTmuxPaneText(run, pane) {
61
+ await assertSamePane(run, pane);
62
+ const raw = await run(['capture-pane', '-p', '-J', '-t', pane.paneId]);
63
+ return raw.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '').replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '');
64
+ }
65
+ /** Dismiss an interactive approval dialog on the member pane by pressing
66
+ * Enter (the highlighted option). Refuses unless the screen actually shows
67
+ * an approval prompt: driving a dialog that isn't there would only inject
68
+ * a stray keystroke into the agent's work. Returns false when no dialog. */
69
+ export async function approveCollaborationDialog(run, pane) {
70
+ const content = await captureTmuxPaneText(run, pane);
71
+ if (!detectApprovalDialog(content))
72
+ return false;
73
+ await sendTmuxPaneKey(run, pane, 'enter');
74
+ return true;
75
+ }
76
+ /** Submit a paste that arrived while the agent was finishing its previous
77
+ * turn and is still sitting unsubmitted in the input box. Only acts on
78
+ * differential evidence — a paste marker that appeared after the write —
79
+ * never on pre-existing stale markers, and returns whether Enter was sent.
80
+ * Committing the whole input buffer is safe: each delivery is written once,
81
+ * so each marker's content enters the transcript exactly once. */
82
+ export async function recoverStuckPaste(run, pane, baseline) {
83
+ const viewport = await captureTmuxPaneText(run, pane);
84
+ if (!hasNewPasteMarker(viewport, baseline))
85
+ return false;
86
+ await sendTmuxPaneKey(run, pane, 'enter');
87
+ return true;
88
+ }
89
+ /** History-inclusive pane text (scrollback + viewport) used to confirm a
90
+ * delivery was actually rendered by the agent, not wiped by a boot clear. */
91
+ export async function captureTmuxPaneHistory(run, pane, lines = 800) {
92
+ await assertSamePane(run, pane);
93
+ const raw = await run(['capture-pane', '-p', '-J', '-S', `-${lines}`, '-t', pane.paneId]);
94
+ return raw.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '').replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '');
95
+ }
@@ -1244,12 +1244,13 @@ async function getLocalJson(baseUrl, token, endpoint, timeoutMs = 30_000) {
1244
1244
  });
1245
1245
  }
1246
1246
  async function runCollab(command) {
1247
- if (command.action === 'help') {
1248
- console.log(COLLAB_HELP);
1249
- return;
1250
- }
1251
1247
  const runningState = getRunningState();
1252
1248
  if (!runningState?.localApiToken) {
1249
+ // Help is a plain read: it must stay available without a running server.
1250
+ if (command.action === 'help') {
1251
+ console.log(COLLAB_HELP);
1252
+ return;
1253
+ }
1253
1254
  console.error(JSON.stringify({ ok: false, code: 'SERVICE_UNAVAILABLE', error: 'Termdock is not running or its local API token is unavailable.' }));
1254
1255
  process.exit(1);
1255
1256
  }
@@ -1275,6 +1276,12 @@ async function runCollab(command) {
1275
1276
  }
1276
1277
  }
1277
1278
  if (!backendSessionId && !tmuxSessionName) {
1279
+ // Bare help stays reachable from a plain shell; the roster snapshot in
1280
+ // help needs a session identity and only appears when one is available.
1281
+ if (command.action === 'help') {
1282
+ console.log(COLLAB_HELP);
1283
+ return;
1284
+ }
1278
1285
  console.error(JSON.stringify({ ok: false, code: 'SESSION_NOT_FOUND', error: 'td collab must run inside a Termdock-managed Session.' }));
1279
1286
  process.exit(1);
1280
1287
  }