termdock 1.4.241 → 1.4.243

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 (321) hide show
  1. package/dist/client/assets/App-DxuaHe8h.js +155 -0
  2. package/dist/client/assets/App-DxuaHe8h.js.br +0 -0
  3. package/dist/client/assets/App-DxuaHe8h.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-C8tldKLn.js → ChangeWalkthroughPanel-BOKLP0Aa.js} +1 -1
  5. package/dist/client/assets/ChangeWalkthroughPanel-BOKLP0Aa.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-BOKLP0Aa.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-_8xFwzoy.js → DagPlayground-D-y62Yjj.js} +1 -1
  8. package/dist/client/assets/DagPlayground-D-y62Yjj.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-D-y62Yjj.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-Bm27LDoY.js → DiffLab-BmPowG45.js} +1 -1
  11. package/dist/client/assets/DiffLab-BmPowG45.js.br +0 -0
  12. package/dist/client/assets/DiffLab-BmPowG45.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-B_gl5KEk.js → DiffReview-CGVgXgpW.js} +1 -1
  14. package/dist/client/assets/DiffReview-CGVgXgpW.js.br +0 -0
  15. package/dist/client/assets/DiffReview-CGVgXgpW.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-Dgu955Xn.js → DiffReviewLab-CuzOT2xb.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-CuzOT2xb.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-CuzOT2xb.js.gz +0 -0
  19. package/dist/client/assets/{DiffViewer-3tHakIDI.js → DiffViewer-BDHnNu2-.js} +1 -1
  20. package/dist/client/assets/DiffViewer-BDHnNu2-.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-BDHnNu2-.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-D8rlVB76.js → ModelPreview-WPo18n63.js} +1 -1
  23. package/dist/client/assets/ModelPreview-WPo18n63.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-WPo18n63.js.gz +0 -0
  25. package/dist/client/assets/{RightSidebar-Cd6fVuZ6.js → RightSidebar-CC7yX38D.js} +4 -4
  26. package/dist/client/assets/RightSidebar-CC7yX38D.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-CC7yX38D.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-Dt72PWoy.js → abnfDiagram-VCTEODGH-Cg90MbDX.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-Cg90MbDX.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-Cg90MbDX.js.gz +0 -0
  31. package/dist/client/assets/{app-DuOQ5oY3.js → app-BiDthfAY.js} +4 -4
  32. package/dist/client/assets/app-BiDthfAY.js.br +0 -0
  33. package/dist/client/assets/app-BiDthfAY.js.gz +0 -0
  34. package/dist/client/assets/{arc-CKvN_JkE.js → arc-D5m1C5_F.js} +1 -1
  35. package/dist/client/assets/arc-D5m1C5_F.js.br +0 -0
  36. package/dist/client/assets/arc-D5m1C5_F.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-CX7Gjex9.js → architectureDiagram-5GKGNRK7-CibYfpyp.js} +1 -1
  38. package/dist/client/assets/architectureDiagram-5GKGNRK7-CibYfpyp.js.br +0 -0
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-CibYfpyp.js.gz +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-BMaBqkw8.js → blockDiagram-I7D4REHJ-rk7OY5q4.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-rk7OY5q4.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-rk7OY5q4.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-Bm7U73rl.js → c4Diagram-7LVT6UL2-dOK22mdg.js} +1 -1
  44. package/dist/client/assets/c4Diagram-7LVT6UL2-dOK22mdg.js.br +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-dOK22mdg.js.gz +0 -0
  46. package/dist/client/assets/channel-Ck5sO4Js.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-B1NuLwpJ.js → chunk-2Q5K7J3B-BrhYtaF9.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-CjfAUVix.js → chunk-5VM5RSS4-CnqKUXPl.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-KLmJjuc2.js → chunk-F27PBJKO-Bdj77Kyj.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-Bdj77Kyj.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-Bdj77Kyj.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-D1b50_xa.js → chunk-IMKFNOWR-B8Gtv6AG.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-B8Gtv6AG.js.br +0 -0
  54. package/dist/client/assets/chunk-IMKFNOWR-B8Gtv6AG.js.gz +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-CrjqhBX6.js → chunk-JWPE2WC7-CM2Goygi.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-Izugq_I1.js → chunk-POPQ4Y6H-C3BQ1qmP.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-sL_467mK.js → chunk-SVP7TREG-CjL_qH1j.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-CjL_qH1j.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-CjL_qH1j.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-BQWSilD2.js → chunk-TICWLB2K-B-ca5DIp.js} +1 -1
  61. package/dist/client/assets/chunk-TICWLB2K-B-ca5DIp.js.br +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-B-ca5DIp.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-C10F0akw.js → chunk-XXDRQBXY-CbVj2dIw.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-9G7iCqG9.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-9G7iCqG9.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-CrPAUlRG.js → cose-bilkent-JH36ORCC-3DGxUNO0.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-3DGxUNO0.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-3DGxUNO0.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-Bx2REbom.js → cynefin-OW5HDTMX-CGuEp5qx.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-CGuEp5qx.js.br +0 -0
  71. package/dist/client/assets/cynefin-OW5HDTMX-CGuEp5qx.js.gz +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-DIdCmQka.js → cynefinDiagram-5FMLGOSQ-C72W7yEn.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-C72W7yEn.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-C72W7yEn.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-6T64o8VI.js → dagre-GXQ25YYZ-BKQwdkXC.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-BKQwdkXC.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-BKQwdkXC.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-BenkfLAl.js → diagram-S7CK7UJ4-BLMlGHfz.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-BLMlGHfz.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-BLMlGHfz.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-BHobFcQF.js → diagram-UQ7AKVKN-veBe9JyG.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-veBe9JyG.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-veBe9JyG.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-dhk5gov4.js → diagram-VSXAHHWV-B-C7VeWe.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-B-C7VeWe.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-B-C7VeWe.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-98uipYMt.js → diagram-VX7I27RA-CvmZ0BA1.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-CvmZ0BA1.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-CvmZ0BA1.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-Bi8PFZ27.js → diagram-Z3DM3KII-BJ2L5PMj.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-BJ2L5PMj.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-BJ2L5PMj.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-7NoB0jrU.js → ebnfDiagram-PWID7BFC-BUY4xoiJ.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-BUY4xoiJ.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-BUY4xoiJ.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-C7Z9m9Nf.js → erDiagram-RLTQ6QDP-Cz4eMcO7.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-Cz4eMcO7.js.br +0 -0
  98. package/dist/client/assets/{erDiagram-RLTQ6QDP-C7Z9m9Nf.js.gz → erDiagram-RLTQ6QDP-Cz4eMcO7.js.gz} +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-BY1yTBdY.js → flowDiagram-HODETNUW-CfUF4E27.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-CfUF4E27.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-CfUF4E27.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BExP528m.js → ganttDiagram-EL5Y4UJY-DMlqyly8.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DMlqyly8.js.br +0 -0
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DMlqyly8.js.gz +0 -0
  105. package/dist/client/assets/{gestureArbiter-BX1L2PZl.js → gestureArbiter-Ct7GE8Vn.js} +1 -1
  106. package/dist/client/assets/gestureArbiter-Ct7GE8Vn.js.br +0 -0
  107. package/dist/client/assets/gestureArbiter-Ct7GE8Vn.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-Dgw8DcAZ.js → gitGraphDiagram-WWUBYQGX-WxfCDRbn.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-WxfCDRbn.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-WxfCDRbn.js.gz +0 -0
  111. package/dist/client/assets/{index-BzglvLj5.css → index-fjWWhVQp.css} +1 -1
  112. package/dist/client/assets/index-fjWWhVQp.css.br +0 -0
  113. package/dist/client/assets/index-fjWWhVQp.css.gz +0 -0
  114. package/dist/client/assets/{infoDiagram-27XIBGKW-BtyGlLCl.js → infoDiagram-27XIBGKW-BWDhPWlk.js} +1 -1
  115. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-DHor3FxJ.js → ishikawaDiagram-5VMMS53U-DrqtXpGk.js} +1 -1
  116. package/dist/client/assets/ishikawaDiagram-5VMMS53U-DrqtXpGk.js.br +0 -0
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-DrqtXpGk.js.gz +0 -0
  118. package/dist/client/assets/{journeyDiagram-3NMN7TZE-zIpSOGqw.js → journeyDiagram-3NMN7TZE-BUJu4ksN.js} +1 -1
  119. package/dist/client/assets/journeyDiagram-3NMN7TZE-BUJu4ksN.js.br +0 -0
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-BUJu4ksN.js.gz +0 -0
  121. package/dist/client/assets/{kanban-definition-UXKFOSKX-Db-J_3Ke.js → kanban-definition-UXKFOSKX-DkwvppsF.js} +1 -1
  122. package/dist/client/assets/kanban-definition-UXKFOSKX-DkwvppsF.js.br +0 -0
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-DkwvppsF.js.gz +0 -0
  124. package/dist/client/assets/{layout-fhC50xhx.js → layout-CXXsOUxv.js} +1 -1
  125. package/dist/client/assets/{layout-fhC50xhx.js.br → layout-CXXsOUxv.js.br} +0 -0
  126. package/dist/client/assets/layout-CXXsOUxv.js.gz +0 -0
  127. package/dist/client/assets/{linear-cRh-kitO.js → linear-D58UH8pU.js} +1 -1
  128. package/dist/client/assets/{linear-cRh-kitO.js.br → linear-D58UH8pU.js.br} +0 -0
  129. package/dist/client/assets/linear-D58UH8pU.js.gz +0 -0
  130. package/dist/client/assets/{mermaid.core-BJ-BnV2O.js → mermaid.core-DbPHytFc.js} +5 -5
  131. package/dist/client/assets/mermaid.core-DbPHytFc.js.br +0 -0
  132. package/dist/client/assets/mermaid.core-DbPHytFc.js.gz +0 -0
  133. package/dist/client/assets/{mindmap-definition-YA3MSWOX-CASwnewq.js → mindmap-definition-YA3MSWOX-Cw9Or2bt.js} +1 -1
  134. package/dist/client/assets/mindmap-definition-YA3MSWOX-Cw9Or2bt.js.br +0 -0
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-Cw9Or2bt.js.gz +0 -0
  136. package/dist/client/assets/{passwordBootstrap-BUCqUzVV.js → passwordBootstrap-D8_QAGU4.js} +1 -1
  137. package/dist/client/assets/passwordBootstrap-D8_QAGU4.js.br +0 -0
  138. package/dist/client/assets/passwordBootstrap-D8_QAGU4.js.gz +0 -0
  139. package/dist/client/assets/{pegDiagram-XKGWAZYB-DvxIsNki.js → pegDiagram-XKGWAZYB-D2BZC108.js} +1 -1
  140. package/dist/client/assets/pegDiagram-XKGWAZYB-D2BZC108.js.br +0 -0
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-D2BZC108.js.gz +0 -0
  142. package/dist/client/assets/{pieDiagram-E7YTZNPT-Cd5KTCKr.js → pieDiagram-E7YTZNPT-DWG3V8rh.js} +1 -1
  143. package/dist/client/assets/pieDiagram-E7YTZNPT-DWG3V8rh.js.br +0 -0
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-DWG3V8rh.js.gz +0 -0
  145. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B2Dzl9r4.js → quadrantDiagram-AXDQQJYC-BUG1FEHA.js} +1 -1
  146. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BUG1FEHA.js.br +0 -0
  147. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BUG1FEHA.js.gz +0 -0
  148. package/dist/client/assets/{railroadDiagram-O6MQD6OU-0k2P5vxC.js → railroadDiagram-O6MQD6OU-HNQAFV2N.js} +1 -1
  149. package/dist/client/assets/railroadDiagram-O6MQD6OU-HNQAFV2N.js.br +0 -0
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-HNQAFV2N.js.gz +0 -0
  151. package/dist/client/assets/{referenceLongPress-DiAiVNRD.js → referenceLongPress-DjPrgH7e.js} +1 -1
  152. package/dist/client/assets/referenceLongPress-DjPrgH7e.js.br +0 -0
  153. package/dist/client/assets/referenceLongPress-DjPrgH7e.js.gz +0 -0
  154. package/dist/client/assets/{requirementDiagram-BXWQKSXE-q7DHcUvb.js → requirementDiagram-BXWQKSXE-Q467WyDg.js} +1 -1
  155. package/dist/client/assets/requirementDiagram-BXWQKSXE-Q467WyDg.js.br +0 -0
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-Q467WyDg.js.gz +0 -0
  157. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-Bl1JWXJq.js → sankeyDiagram-P5KCCOFB-BRhQkSIm.js} +1 -1
  158. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BRhQkSIm.js.br +0 -0
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BRhQkSIm.js.gz +0 -0
  160. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BrEcjCaW.js → sequenceDiagram-WJ2MYXX4-Bwm7jKPw.js} +1 -1
  161. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Bwm7jKPw.js.br +0 -0
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Bwm7jKPw.js.gz +0 -0
  163. package/dist/client/assets/{sizeCapture-INFHLROL-JIXpO6YT.js → sizeCapture-INFHLROL-BzppmTFt.js} +1 -1
  164. package/dist/client/assets/sizeCapture-INFHLROL-BzppmTFt.js.br +0 -0
  165. package/dist/client/assets/sizeCapture-INFHLROL-BzppmTFt.js.gz +0 -0
  166. package/dist/client/assets/{sparkles-Cr5Ep9Lu.js → sparkles-Cx3WfNBe.js} +1 -1
  167. package/dist/client/assets/{stateDiagram-D77RDMKH-uz_XpHV7.js → stateDiagram-D77RDMKH-B0vmOYRa.js} +1 -1
  168. package/dist/client/assets/stateDiagram-D77RDMKH-B0vmOYRa.js.br +0 -0
  169. package/dist/client/assets/stateDiagram-D77RDMKH-B0vmOYRa.js.gz +0 -0
  170. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CTEByrn1.js +1 -0
  171. package/dist/client/assets/{swimlanes-42K2YHIH-BYE9uO28.js → swimlanes-42K2YHIH-ClVJUm6S.js} +1 -1
  172. package/dist/client/assets/swimlanes-42K2YHIH-ClVJUm6S.js.br +0 -0
  173. package/dist/client/assets/swimlanes-42K2YHIH-ClVJUm6S.js.gz +0 -0
  174. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Dqn5Nbl_.js +8 -0
  175. package/dist/client/assets/{timeline-definition-24CTP7MA-46Pz3ht4.js → timeline-definition-24CTP7MA-C55BHZGa.js} +1 -1
  176. package/dist/client/assets/timeline-definition-24CTP7MA-C55BHZGa.js.br +0 -0
  177. package/dist/client/assets/timeline-definition-24CTP7MA-C55BHZGa.js.gz +0 -0
  178. package/dist/client/assets/{triangle-alert-CD2hoUd0.js → triangle-alert-fkNWBAOB.js} +1 -1
  179. package/dist/client/assets/{vennDiagram-4TSXK5OY-K0fm406P.js → vennDiagram-4TSXK5OY-Dn01JiD_.js} +1 -1
  180. package/dist/client/assets/vennDiagram-4TSXK5OY-Dn01JiD_.js.br +0 -0
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-Dn01JiD_.js.gz +0 -0
  182. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-Defom9Go.js → wardleyDiagram-VM6X3IG4-CGkIhHWg.js} +1 -1
  183. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CGkIhHWg.js.br +0 -0
  184. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CGkIhHWg.js.gz +0 -0
  185. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DjylOW-i.js → xychartDiagram-S5SC5T6Z-BAaOu-kq.js} +1 -1
  186. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BAaOu-kq.js.br +0 -0
  187. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BAaOu-kq.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 +125 -86
  195. package/dist/server/agent/collaborationGroupRoutes.js +17 -3
  196. package/dist/server/agent/collaborationPrompt.js +63 -72
  197. package/dist/server/agent/collaborationProtocol.js +6 -1
  198. package/dist/server/agent/collaborationService.js +1 -1
  199. package/dist/server/agent/collaborationStore.js +2 -2
  200. package/dist/server/routes/terminal.js +8 -2
  201. package/package.json +1 -1
  202. package/runtime-manifest.json +3 -3
  203. package/dist/client/assets/App-Dk1zwrS6.js +0 -155
  204. package/dist/client/assets/App-Dk1zwrS6.js.br +0 -0
  205. package/dist/client/assets/App-Dk1zwrS6.js.gz +0 -0
  206. package/dist/client/assets/ChangeWalkthroughPanel-C8tldKLn.js.br +0 -0
  207. package/dist/client/assets/ChangeWalkthroughPanel-C8tldKLn.js.gz +0 -0
  208. package/dist/client/assets/DagPlayground-_8xFwzoy.js.br +0 -0
  209. package/dist/client/assets/DagPlayground-_8xFwzoy.js.gz +0 -0
  210. package/dist/client/assets/DiffLab-Bm27LDoY.js.br +0 -0
  211. package/dist/client/assets/DiffLab-Bm27LDoY.js.gz +0 -0
  212. package/dist/client/assets/DiffReview-B_gl5KEk.js.br +0 -0
  213. package/dist/client/assets/DiffReview-B_gl5KEk.js.gz +0 -0
  214. package/dist/client/assets/DiffReviewLab-Dgu955Xn.js.br +0 -0
  215. package/dist/client/assets/DiffReviewLab-Dgu955Xn.js.gz +0 -0
  216. package/dist/client/assets/DiffViewer-3tHakIDI.js.br +0 -0
  217. package/dist/client/assets/DiffViewer-3tHakIDI.js.gz +0 -0
  218. package/dist/client/assets/ModelPreview-D8rlVB76.js.br +0 -0
  219. package/dist/client/assets/ModelPreview-D8rlVB76.js.gz +0 -0
  220. package/dist/client/assets/RightSidebar-Cd6fVuZ6.js.br +0 -0
  221. package/dist/client/assets/RightSidebar-Cd6fVuZ6.js.gz +0 -0
  222. package/dist/client/assets/abnfDiagram-VCTEODGH-Dt72PWoy.js.br +0 -0
  223. package/dist/client/assets/abnfDiagram-VCTEODGH-Dt72PWoy.js.gz +0 -0
  224. package/dist/client/assets/app-DuOQ5oY3.js.br +0 -0
  225. package/dist/client/assets/app-DuOQ5oY3.js.gz +0 -0
  226. package/dist/client/assets/arc-CKvN_JkE.js.br +0 -0
  227. package/dist/client/assets/arc-CKvN_JkE.js.gz +0 -0
  228. package/dist/client/assets/architectureDiagram-5GKGNRK7-CX7Gjex9.js.br +0 -0
  229. package/dist/client/assets/architectureDiagram-5GKGNRK7-CX7Gjex9.js.gz +0 -0
  230. package/dist/client/assets/blockDiagram-I7D4REHJ-BMaBqkw8.js.br +0 -0
  231. package/dist/client/assets/blockDiagram-I7D4REHJ-BMaBqkw8.js.gz +0 -0
  232. package/dist/client/assets/c4Diagram-7LVT6UL2-Bm7U73rl.js.br +0 -0
  233. package/dist/client/assets/c4Diagram-7LVT6UL2-Bm7U73rl.js.gz +0 -0
  234. package/dist/client/assets/channel-BukD64h5.js +0 -1
  235. package/dist/client/assets/chunk-F27PBJKO-KLmJjuc2.js.br +0 -0
  236. package/dist/client/assets/chunk-F27PBJKO-KLmJjuc2.js.gz +0 -0
  237. package/dist/client/assets/chunk-IMKFNOWR-D1b50_xa.js.br +0 -0
  238. package/dist/client/assets/chunk-IMKFNOWR-D1b50_xa.js.gz +0 -0
  239. package/dist/client/assets/chunk-SVP7TREG-sL_467mK.js.br +0 -0
  240. package/dist/client/assets/chunk-SVP7TREG-sL_467mK.js.gz +0 -0
  241. package/dist/client/assets/chunk-TICWLB2K-BQWSilD2.js.br +0 -0
  242. package/dist/client/assets/chunk-TICWLB2K-BQWSilD2.js.gz +0 -0
  243. package/dist/client/assets/classDiagram-ZZMXUADV-DA86AYYB.js +0 -1
  244. package/dist/client/assets/classDiagram-v2-VYDZK3BY-DA86AYYB.js +0 -1
  245. package/dist/client/assets/cose-bilkent-JH36ORCC-CrPAUlRG.js.br +0 -0
  246. package/dist/client/assets/cose-bilkent-JH36ORCC-CrPAUlRG.js.gz +0 -0
  247. package/dist/client/assets/cynefin-OW5HDTMX-Bx2REbom.js.br +0 -0
  248. package/dist/client/assets/cynefin-OW5HDTMX-Bx2REbom.js.gz +0 -0
  249. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DIdCmQka.js.br +0 -0
  250. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DIdCmQka.js.gz +0 -0
  251. package/dist/client/assets/dagre-GXQ25YYZ-6T64o8VI.js.br +0 -0
  252. package/dist/client/assets/dagre-GXQ25YYZ-6T64o8VI.js.gz +0 -0
  253. package/dist/client/assets/diagram-S7CK7UJ4-BenkfLAl.js.br +0 -0
  254. package/dist/client/assets/diagram-S7CK7UJ4-BenkfLAl.js.gz +0 -0
  255. package/dist/client/assets/diagram-UQ7AKVKN-BHobFcQF.js.br +0 -0
  256. package/dist/client/assets/diagram-UQ7AKVKN-BHobFcQF.js.gz +0 -0
  257. package/dist/client/assets/diagram-VSXAHHWV-dhk5gov4.js.br +0 -0
  258. package/dist/client/assets/diagram-VSXAHHWV-dhk5gov4.js.gz +0 -0
  259. package/dist/client/assets/diagram-VX7I27RA-98uipYMt.js.br +0 -0
  260. package/dist/client/assets/diagram-VX7I27RA-98uipYMt.js.gz +0 -0
  261. package/dist/client/assets/diagram-Z3DM3KII-Bi8PFZ27.js.br +0 -0
  262. package/dist/client/assets/diagram-Z3DM3KII-Bi8PFZ27.js.gz +0 -0
  263. package/dist/client/assets/ebnfDiagram-PWID7BFC-7NoB0jrU.js.br +0 -0
  264. package/dist/client/assets/ebnfDiagram-PWID7BFC-7NoB0jrU.js.gz +0 -0
  265. package/dist/client/assets/erDiagram-RLTQ6QDP-C7Z9m9Nf.js.br +0 -0
  266. package/dist/client/assets/flowDiagram-HODETNUW-BY1yTBdY.js.br +0 -0
  267. package/dist/client/assets/flowDiagram-HODETNUW-BY1yTBdY.js.gz +0 -0
  268. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BExP528m.js.br +0 -0
  269. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BExP528m.js.gz +0 -0
  270. package/dist/client/assets/gestureArbiter-BX1L2PZl.js.br +0 -0
  271. package/dist/client/assets/gestureArbiter-BX1L2PZl.js.gz +0 -0
  272. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Dgw8DcAZ.js.br +0 -0
  273. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Dgw8DcAZ.js.gz +0 -0
  274. package/dist/client/assets/index-BzglvLj5.css.br +0 -0
  275. package/dist/client/assets/index-BzglvLj5.css.gz +0 -0
  276. package/dist/client/assets/ishikawaDiagram-5VMMS53U-DHor3FxJ.js.br +0 -0
  277. package/dist/client/assets/ishikawaDiagram-5VMMS53U-DHor3FxJ.js.gz +0 -0
  278. package/dist/client/assets/journeyDiagram-3NMN7TZE-zIpSOGqw.js.br +0 -0
  279. package/dist/client/assets/journeyDiagram-3NMN7TZE-zIpSOGqw.js.gz +0 -0
  280. package/dist/client/assets/kanban-definition-UXKFOSKX-Db-J_3Ke.js.br +0 -0
  281. package/dist/client/assets/kanban-definition-UXKFOSKX-Db-J_3Ke.js.gz +0 -0
  282. package/dist/client/assets/layout-fhC50xhx.js.gz +0 -0
  283. package/dist/client/assets/linear-cRh-kitO.js.gz +0 -0
  284. package/dist/client/assets/mermaid.core-BJ-BnV2O.js.br +0 -0
  285. package/dist/client/assets/mermaid.core-BJ-BnV2O.js.gz +0 -0
  286. package/dist/client/assets/mindmap-definition-YA3MSWOX-CASwnewq.js.br +0 -0
  287. package/dist/client/assets/mindmap-definition-YA3MSWOX-CASwnewq.js.gz +0 -0
  288. package/dist/client/assets/passwordBootstrap-BUCqUzVV.js.br +0 -0
  289. package/dist/client/assets/passwordBootstrap-BUCqUzVV.js.gz +0 -0
  290. package/dist/client/assets/pegDiagram-XKGWAZYB-DvxIsNki.js.br +0 -0
  291. package/dist/client/assets/pegDiagram-XKGWAZYB-DvxIsNki.js.gz +0 -0
  292. package/dist/client/assets/pieDiagram-E7YTZNPT-Cd5KTCKr.js.br +0 -0
  293. package/dist/client/assets/pieDiagram-E7YTZNPT-Cd5KTCKr.js.gz +0 -0
  294. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B2Dzl9r4.js.br +0 -0
  295. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B2Dzl9r4.js.gz +0 -0
  296. package/dist/client/assets/railroadDiagram-O6MQD6OU-0k2P5vxC.js.br +0 -0
  297. package/dist/client/assets/railroadDiagram-O6MQD6OU-0k2P5vxC.js.gz +0 -0
  298. package/dist/client/assets/referenceLongPress-DiAiVNRD.js.br +0 -0
  299. package/dist/client/assets/referenceLongPress-DiAiVNRD.js.gz +0 -0
  300. package/dist/client/assets/requirementDiagram-BXWQKSXE-q7DHcUvb.js.br +0 -0
  301. package/dist/client/assets/requirementDiagram-BXWQKSXE-q7DHcUvb.js.gz +0 -0
  302. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bl1JWXJq.js.br +0 -0
  303. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bl1JWXJq.js.gz +0 -0
  304. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BrEcjCaW.js.br +0 -0
  305. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BrEcjCaW.js.gz +0 -0
  306. package/dist/client/assets/sizeCapture-INFHLROL-JIXpO6YT.js.br +0 -0
  307. package/dist/client/assets/sizeCapture-INFHLROL-JIXpO6YT.js.gz +0 -0
  308. package/dist/client/assets/stateDiagram-D77RDMKH-uz_XpHV7.js.br +0 -0
  309. package/dist/client/assets/stateDiagram-D77RDMKH-uz_XpHV7.js.gz +0 -0
  310. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DLePl8jh.js +0 -1
  311. package/dist/client/assets/swimlanes-42K2YHIH-BYE9uO28.js.br +0 -0
  312. package/dist/client/assets/swimlanes-42K2YHIH-BYE9uO28.js.gz +0 -0
  313. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-4NCunUpJ.js +0 -8
  314. package/dist/client/assets/timeline-definition-24CTP7MA-46Pz3ht4.js.br +0 -0
  315. package/dist/client/assets/timeline-definition-24CTP7MA-46Pz3ht4.js.gz +0 -0
  316. package/dist/client/assets/vennDiagram-4TSXK5OY-K0fm406P.js.br +0 -0
  317. package/dist/client/assets/vennDiagram-4TSXK5OY-K0fm406P.js.gz +0 -0
  318. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Defom9Go.js.br +0 -0
  319. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Defom9Go.js.gz +0 -0
  320. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DjylOW-i.js.br +0 -0
  321. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DjylOW-i.js.gz +0 -0
@@ -2,76 +2,85 @@ import fs from 'node:fs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { COLLAB_LIMITS, CollaborationError, canonicalShortId } from './collaborationProtocol.js';
4
4
  export const COLLAB_HELP = `td collab — durable messages; no agent-specific hooks required
5
- (except --session, wherever an id is taken, the short id shown in deliveries and --text
6
- output works too; a shorter unique prefix down to 4 characters also resolves —
7
- an ambiguous prefix is refused, so use more characters or the full id)
5
+ 我想…
6
+ 回复刚收到的消息 用投递「来自」行里的 id: td collab reply <id> "内容" --text
7
+ 看有谁、我在哪些组 td collab status --text
8
+ 主动找人 → td collab send <成员id> "消息内容" --text
9
+ 看对方在干什么 → td collab capture <成员id> --text(只读巡屏,不发送按键)
10
+ 取大消息全文 → td collab message get <id> --text
11
+ 改我的显示名 → td collab rename <我的id> <新名字>
12
+ 设角色定位 → td collab role set <组id> <成员id> <定位>
13
+ 查/改群规 → td collab rules get|set <组id>
14
+ 消息发出去没动静 → td collab message get <id>(投递状态与重试原因)
15
+ Output: --json (default) | --jsonl | --text (human-readable lines; prefer it
16
+ when reading results yourself. Commands without a dedicated --text form
17
+ still print JSON)
8
18
  Source identity (all collab commands): --session <full-Termdock-session-id>
9
19
  or TERMDOCK_COLLAB_SESSION_ID. CLI option wins; explicit identity bypasses
10
20
  environment/tmux detection. Use your own full member ID from status output,
11
21
  not a tmux name, backend ID or Claude session ID. Unknown IDs are refused.
12
- This selects a source in the trusted local-user context; it is not proof
13
- of identity or isolation between processes running as the same OS user.
14
22
  Example: td collab --session <your-full-session-id> status
15
- Rebind identity and location separately:
16
- td collab --session <your-full-session-id> rebind --pane %3
17
- Without explicit identity, the backend environment / tmux pane is detected.
23
+ Ids: wherever an id is taken, the short id shown in deliveries and --text
24
+ output works too; a shorter unique prefix down to 4 characters also
25
+ resolves an ambiguous prefix is refused, so use more characters.
18
26
  Scheduled self-reminders: td automation create --name 'Review progress' --every 30 --self --prompt 'Review group progress and continue'
19
27
  Scheduling help: td automation --help
20
- status | capabilities
21
- transport list (server-owned remote session directory)
22
- transport invite --origin https://this-service:9834
23
- Create a 10-minute invitation granting collaboration directory/group access.
24
- Transfer its JSON privately to the other machine; no browser is required.
25
- transport accept --origin https://this-service:9834 --file invitation.json
26
- Pair both services once; subsequent discovery, groups and delivery are automatic.
27
- group save --file group.json
28
- JSON: {name, sessionIds, id?, expectedUpdatedAt?}; same service API as the UI.
29
- transport info
30
- Print this service's public identity and CA fingerprint (no private keys).
31
- transport register <group-id> --file <nodes.json>
32
- One-time server peer registration; nodes.json is an array of public
33
- {serviceId, origin, caFingerprint256?} entries, including this service.
34
- Obtain pins from each administrator's transport info over a trusted channel.
35
- Registered servers deliver without an open browser or Desktop client.
36
- rebind [--pane %3] (explicitly bind this peer to its current Agent; resumes queued delivery)
37
- send <session-id> <message> | reply <message-id> <message> | handoff <session-id> <message>
38
- (send fan-out: comma-separated same-group ids, e.g. send a,b "任务"; every
39
- recipient sees it as 群发 with the sibling list; waiting options apply to
40
- single-recipient sends only)
41
- --group <id> --thread <id> --idempotency-key <key>
42
- --file <path> | --stdin (instead of inline body; -- ends option parsing)
43
- --wait-until queued|delivered --timeout 30s
44
- --expect-reply ack|result|any
45
- --response-kind ack|progress|result --metadata '<JSON object>'
46
- --expires-at <ISO timestamp or epoch milliseconds>
47
- reply: --task-envelope '<JSON: task_id,status,progress?,evidence?,blocker?>'
28
+
29
+ ── 常用 ──
30
+ status (who is in my groups, their names, roles, observed activity)
31
+ send <session-id> <message> (deliver to one member; comma-separated same-group
32
+ ids fan out, e.g. send a,b "任务" recipients see it as 群发)
33
+ reply <message-id> <message> (answer a message you received; one-to-one)
34
+ handoff <session-id> <message> (like send, framed as a task hand-off)
35
+ shared options: --group <id> --thread <id> --idempotency-key <key>
36
+ --file <path> | --stdin (instead of inline body; -- ends option parsing)
37
+ --wait-until queued|delivered --timeout 30s --expect-reply ack|result|any
38
+ --response-kind ack|progress|result --metadata '<JSON object>'
39
+ --expires-at <ISO timestamp or epoch milliseconds>
40
+ reply also: --task-envelope '<JSON: task_id,status,progress?,evidence?,blocker?>'
48
41
  (status reporting happens only via reply; send/handoff dispatch carries no task state)
49
- message get <message-id> [--receipt-only] [--raw]
42
+ message get <id> [--receipt-only] [--raw] [--no-rules] [--text]
43
+ (delivery status, retries and the full body of large messages)
50
44
  message confirm-shell <message-id> (sender confirms delivery into a shell; continues the same message)
51
45
  message watch <message-id> [--wait-until delivered] [--timeout 30s]
52
- inbox [--since <ISO or epoch-ms>] [--after-id <id>]
53
- [--cursor <token>] [--consumer <name>] [--limit 1..200]
54
- [--from <id>] [--group <id>] [--thread <id>] [--kind <kind>]
55
- [--response-kind ack|progress|result] [--follow] [--timeout 30s]
56
- cursor commit <token> --consumer <name> (commit after processing the page)
46
+ capture <session-id> [--lines 1..10000] [--raw] [--text]
47
+ (查看伙伴正在做什么:读取同组、本机 tmux 成员的当前屏幕,不发送按键、不打断对方。
48
+ 不是完整聊天历史或完成凭证;用 status --text 查成员 ID;不支持远端成员,远端请 send 询问进展。)
49
+ ── 群与规则 ──
50
+ group save --file group.json
51
+ JSON: {name, sessionIds, id?, expectedUpdatedAt?}; same service API as the UI.
57
52
  add|remove <group-id> <session-id>
58
53
  spawn <group-id> <agent-slug> [--name <name>] [--cwd <path>] [--task <text>]
59
- rules get <group-id> [--text]
60
- rules set <group-id> <text> | --file <path> | --stdin [--if-version <version>]
61
- rules clear <group-id> [--if-version <version>]
62
- Shared group guidance, up to 8192 UTF-8 bytes; message envelopes retain its version.
63
- traits list|set|unset (alias of role; long-lived capabilities/preferences)
64
- role list <group-id>
65
- role set <group-id> <session-id> <role…> (trailing words join as the role)
66
- role unset <group-id> <session-id>
67
- (roles are shared within the group; members set each other's, your own
68
- rides the delivery shell header)
54
+ rules get <group-id> [--text] | rules set <group-id> <text> | --file <path> | --stdin [--if-version <version>]
55
+ | rules clear <group-id> [--if-version <version>]
56
+ (shared group guidance, up to 8192 UTF-8 bytes; message envelopes retain
57
+ its version. if-version rejects concurrent overwrites with GROUP_CHANGED)
69
58
  rename <session-id> <name…> (rename a member of any shared group;
70
59
  trailing words join as the new name; roster and shells show it at once)
71
- capture <session-id> [--lines 1..10000] [--raw] [--text]
72
- (查看伙伴正在做什么:读取同组、本机 tmux 成员的当前屏幕,不发送按键、不打断对方。
73
- 需要了解进展、排查卡住或决定是否跟进时先 capture;不是完整聊天历史或完成凭证。
74
- status --text 查成员 ID;不支持远端成员,远端请 send 询问进展。)
60
+ role list <group-id> | role set <group-id> <session-id> <role…> | role unset <group-id> <session-id>
61
+ (roles are shared within the group; members set each other's, your own
62
+ rides the delivery shell header. traits is an alias of role)
63
+ ── 收件箱与游标 ──
64
+ inbox [--since <ISO or epoch-ms>] [--after-id <id>] [--cursor <token>]
65
+ [--consumer <name>] [--limit 1..200] [--from <id>] [--group <id>]
66
+ [--thread <id>] [--kind <kind>] [--response-kind ack|progress|result]
67
+ [--follow] [--timeout 30s]
68
+ (defaults to newest 50; cursor/consumer mode reads oldest unseen first.
69
+ Reading never advances a consumer. Read receipts are not supported)
70
+ cursor commit <token> --consumer <name> (commit after processing the page)
71
+ ── 传输与运维 ──
72
+ transport list (server-owned remote session directory) | transport info (this service's public identity and CA fingerprint, no private keys)
73
+ transport invite --origin https://this-service:9834
74
+ (create a 10-minute invitation granting collaboration directory/group
75
+ access; transfer its JSON privately — no browser required)
76
+ transport accept --origin https://this-service:9834 --file invitation.json
77
+ (pair both services once; discovery, groups and delivery become automatic)
78
+ transport register <group-id> --file <nodes.json>
79
+ (one-time server peer registration; nodes.json is an array of public
80
+ {serviceId, origin, caFingerprint256?} entries, including this service.
81
+ Obtain pins from each administrator's transport info over a trusted
82
+ channel. Registered servers deliver without an open browser or Desktop client)
83
+ rebind [--pane %3] (explicitly bind this peer to its current Agent; resumes queued delivery)
75
84
  drive <session-id> approve|enter|escape|space|left|right|up|down|capture
76
85
  drive <session-id> run <command…>
77
86
  (drive the terminal of a member session you share a group with — terminal
@@ -79,30 +88,30 @@ export const COLLAB_HELP = `td collab — durable messages; no agent-specific ho
79
88
  dialog and refuses unless one is actually showing; named keys inject one
80
89
  key; capture reads the current screen back; run submits one line and
81
90
  returns the screen. Works on plain shell members too (no agent needed) for
82
- run/capture; key actions (approve/enter/…) require an agent pane, and are
83
- refused while the user has the pane scrolled into copy-mode. Cannot target
84
- your own session. Treat every drive as strong control: the member's shell
85
- executes what you send.)
91
+ run/capture; key actions require an agent pane and are refused while the
92
+ user has the pane scrolled into copy-mode. Cannot target your own session.
93
+ Treat every drive as strong control: the member's shell executes what you send)
86
94
  cleanup <session-id>… 移除协作会话并终止其 tmux/进程(仅限与你同组的会话;
87
95
  不能清理当前会话自身,也不能通过清理解散你所在的组)
88
96
  风险操作:默认只打印清理计划并拒绝执行(exit 1)——这是不可恢复的删除。
89
97
  必须先向用户(人类)说明将清理的会话与影响并获得其明确同意,
90
98
  才能以 --confirm 重跑执行;每次执行都需要当场的人类授权。
91
- --json (default) | --jsonl | --text
99
+ help (this text; when a managed session is available it also lists the
100
+ groups you are in with every member's id, name and role)
92
101
  Exit codes: 0 requested condition met; 1 invalid request/network error;
93
102
  cleanup without --confirm prints the plan and refuses (also 1);
94
103
  2 wait timeout (message may still deliver); 3 failed/expired.
95
104
  Message limit: ${COLLAB_LIMITS.message_bytes} UTF-8 bytes; metadata: ${COLLAB_LIMITS.metadata_bytes} bytes.
96
105
  Idempotency retention: 7 days. Terminal delivery, ACK and result never imply each other.
97
- Inbox defaults to newest 50; cursor/consumer mode reads oldest unseen first.
98
- Reading never advances a consumer. Read receipts are not supported.`;
106
+ Delivery semantics: delivered = written to the terminal never proof of
107
+ reading or task completion; a timeout stops waiting, it does not cancel delivery.`;
99
108
  /** One roster row: prefer the member's human name, keep the full session id
100
109
  * reachable for role set/unset targeting, mark unset members explicitly. */
101
110
  function roleLine(member, role) {
102
111
  const label = member.name ? `${member.name} (${member.sessionId})` : member.sessionId;
103
112
  return role ? `- ${label}:${role}` : `- ${label}(未设置)`;
104
113
  }
105
- const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'follow', 'stdin', 'receipt-only', 'confirm', 'raw', 'help']);
114
+ const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'follow', 'stdin', 'receipt-only', 'confirm', 'raw', 'help', 'no-rules']);
106
115
  const VALUE_OPTIONS = new Set(['session', 'group', 'thread', 'idempotency-key', 'file', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'kind', 'name', 'cwd', 'task', 'pane', 'lines', 'if-version', 'origin']);
107
116
  export function parseCollaborationCommand(argv) {
108
117
  const options = {};
@@ -271,7 +280,7 @@ export function parseCollaborationCommand(argv) {
271
280
  handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at'],
272
281
  reply: ['idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
273
282
  inbox: ['raw', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'group', 'thread', 'kind', 'response-kind', 'follow', 'timeout'],
274
- message: ['raw', 'receipt-only', 'follow', 'wait-until', 'timeout', 'expect-reply'], cursor: ['consumer'],
283
+ message: ['raw', 'receipt-only', 'no-rules', 'follow', 'wait-until', 'timeout', 'expect-reply'], cursor: ['consumer'],
275
284
  add: [], remove: [], spawn: ['name', 'cwd', 'task'], role: [], rename: [], cleanup: ['confirm'], drive: ['lines', 'raw'],
276
285
  };
277
286
  for (const option of byAction[action])
@@ -329,8 +338,10 @@ export async function executeCollaborationCommand(command, context, io) {
329
338
  io.write(message.content);
330
339
  }
331
340
  else if (value.message) {
332
- if (value.message.instructions?.text)
333
- io.write(`协作约定(版本 ${value.message.instructions.version}):\n${value.message.instructions.text}`);
341
+ // One line instead of the full rules text — the CLI is stateless so it
342
+ // cannot diff versions across calls; --no-rules silences it entirely.
343
+ if (!o['no-rules'] && value.message.instructions?.text)
344
+ io.write(`消息附群规版本 ${value.message.instructions.version};td collab rules get <组id> --text 查看全文`);
334
345
  io.write(`[${value.status}] ${canonicalShortId(String(value.message_id ?? ''))}\n${value.message.content}`);
335
346
  }
336
347
  else if (value.message_id) {
@@ -340,8 +351,12 @@ export async function executeCollaborationCommand(command, context, io) {
340
351
  if (value.code === 'WAIT_TIMEOUT' && value.stage_reached && value.expect_reply) {
341
352
  io.write(`\n投递已完成;等待${value.expect_reply === 'result' ? '结果' : '回复'}超时(expect-reply=${value.expect_reply})`);
342
353
  }
354
+ // The screen rides the receipt as the delivery credential: the service
355
+ // observes terminal writes, never reading, so this capture is the only
356
+ // evidence the message actually landed. Kept visible in --text, but
357
+ // fenced and labelled so it cannot be mistaken for delivery state.
343
358
  if (value.snapshot)
344
- io.write(`\n${value.snapshot}`);
359
+ io.write(`\n对方终端当前屏幕(凭证:确认你的消息已写入;不代表已读或任务完成):\n───\n${value.snapshot}\n───`);
345
360
  }
346
361
  else
347
362
  io.write(JSON.stringify(value, null, 2));
@@ -382,7 +397,12 @@ export async function executeCollaborationCommand(command, context, io) {
382
397
  if (command.action === 'group') {
383
398
  if (fs.statSync(String(o.file)).size > 64 * 1024)
384
399
  throw new Error('Group input exceeds 64 KiB');
385
- output(await request('POST', '/group', { input: JSON.parse(fs.readFileSync(String(o.file), 'utf8')) }));
400
+ const saved = await request('POST', '/group', { input: JSON.parse(fs.readFileSync(String(o.file), 'utf8')) });
401
+ if (o.text && saved.group) {
402
+ io.write(`已保存协作组「${saved.group.name ?? saved.group.id ?? ''}」(${saved.group.sessionIds?.length ?? 0} 个成员,updatedAt=${saved.group.updatedAt ?? '未知'})`);
403
+ }
404
+ else
405
+ output(saved);
386
406
  return 0;
387
407
  }
388
408
  if (command.action === 'transport') {
@@ -420,6 +440,7 @@ export async function executeCollaborationCommand(command, context, io) {
420
440
  for (const id of group.sessionIds)
421
441
  io.write(roleLine({ sessionId: id, name: members.get(id) ?? null }, group.roles?.[id]));
422
442
  }
443
+ io.write('给成员发消息: td collab send <成员id> "消息内容" --text(成员 id 见上表)');
423
444
  }
424
445
  }
425
446
  catch { /* help stays available without a server or session */ }
@@ -427,31 +448,41 @@ export async function executeCollaborationCommand(command, context, io) {
427
448
  }
428
449
  if (command.action === 'capabilities' || command.action === 'status') {
429
450
  const result = await request('GET', command.action === 'status' ? '/peers' : '/capabilities');
430
- output(result);
431
451
  if (command.action === 'status' && o.text) {
432
- for (const group of result.groups ?? []) {
433
- io.write(`组「${group.name}」成员定位:`);
434
- for (const id of group.sessionIds ?? []) {
435
- const member = [...(result.peers ?? []), result.source].find(item => item?.sessionId === id);
436
- io.write(roleLine({ sessionId: id, name: member?.name }, group.roles?.[id]));
452
+ // Human-readable status: no JSON dump, one line per fact.
453
+ const source = result.source;
454
+ if (source?.sessionId)
455
+ io.write(`我:${source.name ?? ''}(${source.sessionId})`);
456
+ for (const peer of result.peers ?? []) {
457
+ const facts = peer;
458
+ const line = `- ${facts.name || facts.sessionId}(${facts.sessionId})${facts.status ? ` · ${facts.status}` : ''}`;
459
+ io.write(line);
460
+ if (facts.last_terminal_output_at && facts.activity_observed_at) {
461
+ io.write(` 观测时距终端输出 ${facts.output_idle_seconds ?? Math.max(0, Math.floor((facts.activity_observed_at - facts.last_terminal_output_at) / 1000))} 秒(不代表任务进度)`);
437
462
  }
438
463
  }
439
- for (const peer of result.peers ?? [])
440
- if (peer.last_terminal_output_at && peer.activity_observed_at) {
441
- io.write(`${peer.name || peer.sessionId}:观测时距终端输出 ${peer.output_idle_seconds ?? Math.max(0, Math.floor((peer.activity_observed_at - peer.last_terminal_output_at) / 1000))} 秒(不代表任务进度)`);
464
+ for (const group of result.groups ?? []) {
465
+ const view = group;
466
+ io.write(`组「${view.name}」成员定位:`);
467
+ const peersAndSource = [...(result.peers ?? []), result.source];
468
+ for (const id of view.sessionIds ?? []) {
469
+ const member = peersAndSource.find(item => item?.sessionId === id);
470
+ io.write(roleLine({ sessionId: id, name: member?.name }, view.roles?.[id]));
442
471
  }
443
- }
444
- if (command.action === 'status' && o.text && Array.isArray(result.peers) && result.peers.length) {
472
+ }
445
473
  io.write('查看伙伴当前屏幕(只读,仅本机 tmux):');
446
- for (const peer of result.peers) {
474
+ for (const peer of result.peers ?? []) {
447
475
  const id = peer.sessionId;
448
- if (typeof id !== 'string' || id === result.source?.sessionId)
476
+ const name = peer.name;
477
+ if (typeof id !== 'string' || id === source?.sessionId)
449
478
  continue;
450
- io.write(`- ${peer.name || id}:${id.startsWith('remote:')
479
+ io.write(`- ${name || id}:${id.startsWith('remote:')
451
480
  ? `远端成员,请用 td collab send ${id} "当前进展?" --text`
452
481
  : `td collab capture ${id} --text`}`);
453
482
  }
483
+ return 0;
454
484
  }
485
+ output(result);
455
486
  return 0;
456
487
  }
457
488
  if (command.action === 'rebind') {
@@ -551,7 +582,7 @@ export async function executeCollaborationCommand(command, context, io) {
551
582
  role: command.operation === 'unset' ? null : command.role });
552
583
  if (o.text) {
553
584
  io.write(command.operation === 'set'
554
- ? `定位已设置:${command.sessionId} = ${body.group.roles?.[command.sessionId] ?? ''}`
585
+ ? `定位已设置:${command.sessionId} = ${body.group.roles?.[command.sessionId] ?? ''}\n(改成员显示名: td collab rename ${command.sessionId} <新名字>)`
555
586
  : `定位已清除:${command.sessionId}`);
556
587
  }
557
588
  else
@@ -666,8 +697,16 @@ export async function executeCollaborationCommand(command, context, io) {
666
697
  output({ ...receipt, ok: false, code: 'WAIT_TIMEOUT', delivery_continues: true, last_error: error instanceof Error ? error.message : String(error) });
667
698
  return 2;
668
699
  }
669
- output({ ...(receipt ?? {}), ok: false, code: error instanceof CollaborationError ? error.code : 'COLLABORATION_ERROR',
670
- error: error instanceof Error ? error.message : String(error), ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}), ...(receipt ? { delivery_continues: true } : {}) });
700
+ const code = error instanceof CollaborationError ? error.code : 'COLLABORATION_ERROR';
701
+ const message = error instanceof Error ? error.message : String(error);
702
+ if (o.text) {
703
+ // Single-line errors in text mode; the enqueued-message hint keeps a
704
+ // mid-wait failure from reading as a lost message.
705
+ io.write(`${code}: ${message}${receipt?.message_id ? `(消息 ${canonicalShortId(String(receipt.message_id))} 已入队,投递继续)` : ''}`);
706
+ return 1;
707
+ }
708
+ output({ ...(receipt ?? {}), ok: false, code,
709
+ error: message, ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}), ...(receipt ? { delivery_continues: true } : {}) });
671
710
  return 1;
672
711
  }
673
712
  }
@@ -10,7 +10,17 @@ export function collaborationGroupRoutes(options) {
10
10
  });
11
11
  router.post('/collaboration-groups', async (req, res) => {
12
12
  try {
13
- const { id, name, sessionIds, expectedUpdatedAt } = req.body ?? {};
13
+ const body = req.body ?? {};
14
+ const { id, name, sessionIds, expectedUpdatedAt } = body;
15
+ // Unknown fields used to be dropped silently while the save still
16
+ // returned ok — a caller submitting `members` to rename someone got a
17
+ // success and no rename. Reject them and route the common case (member
18
+ // renames) to the command that actually does it.
19
+ const unknownKeys = Object.keys(body).filter((key) => !['id', 'name', 'sessionIds', 'expectedUpdatedAt'].includes(key));
20
+ if (unknownKeys.length) {
21
+ const hint = unknownKeys.includes('members') ? ';要改成员显示名请用 td collab rename <session-id> <新名字>' : '';
22
+ throw new CollaborationError('INVALID_GROUP', `含未知字段 ${unknownKeys.join('、')}(合法字段:name, sessionIds, id, expectedUpdatedAt)${hint}`, 400);
23
+ }
14
24
  if ((id !== undefined && (typeof id !== 'string' || !id))
15
25
  || typeof name !== 'string' || !name.trim() || !Array.isArray(sessionIds)
16
26
  || sessionIds.some((value) => typeof value !== 'string' || !value.trim())) {
@@ -23,7 +33,7 @@ export function collaborationGroupRoutes(options) {
23
33
  if (id && (!existing || existing.deleted))
24
34
  throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
25
35
  if (expectedUpdatedAt !== undefined && (!Number.isFinite(expectedUpdatedAt) || expectedUpdatedAt !== existing?.updatedAt)) {
26
- throw new CollaborationError('GROUP_CHANGED', '协作组已被修改,请重新打开成员管理后再保存', 409);
36
+ throw new CollaborationError('GROUP_CHANGED', `协作组已被修改(当前 updatedAt=${existing?.updatedAt})。请先确认最新成员列表,再以 expectedUpdatedAt=${existing?.updatedAt} 重新提交`, 409, { currentUpdatedAt: existing?.updatedAt });
27
37
  }
28
38
  const ids = [...new Set(sessionIds)];
29
39
  if (ids.length < 2)
@@ -41,6 +51,7 @@ export function collaborationGroupRoutes(options) {
41
51
  catch (error) {
42
52
  res.status(error instanceof CollaborationError ? error.httpStatus : 500)
43
53
  .json({ code: error instanceof CollaborationError ? error.code : 'GROUP_SAVE_FAILED',
54
+ ...(error instanceof CollaborationError && error.details ? error.details : {}),
44
55
  error: error instanceof Error ? error.message : '协作组保存失败' });
45
56
  }
46
57
  });
@@ -57,6 +68,7 @@ export function collaborationGroupRoutes(options) {
57
68
  catch (error) {
58
69
  res.status(error instanceof CollaborationError ? error.httpStatus : 500)
59
70
  .json({ code: error instanceof CollaborationError ? error.code : 'GROUP_MOVE_FAILED',
71
+ ...(error instanceof CollaborationError && error.details ? error.details : {}),
60
72
  error: error instanceof Error ? error.message : '成员移动失败' });
61
73
  }
62
74
  });
@@ -97,13 +109,15 @@ export function collaborationGroupRoutes(options) {
97
109
  catch (error) {
98
110
  res.status(error instanceof CollaborationError ? error.httpStatus : 400)
99
111
  .json({ code: error instanceof CollaborationError ? error.code : 'GROUP_PROMOTION_FAILED',
112
+ ...(error instanceof CollaborationError && error.details ? error.details : {}),
100
113
  error: error instanceof Error ? error.message : '跨服务组转换失败' });
101
114
  }
102
115
  });
103
116
  router.delete('/collaboration-groups/:groupId', (req, res) => {
104
117
  const existing = options.store.getGroup(req.params.groupId);
105
118
  if (req.query.expectedUpdatedAt !== undefined && Number(req.query.expectedUpdatedAt) !== existing?.updatedAt) {
106
- return res.status(409).json({ code: 'GROUP_CHANGED', error: '协作组已变化,请刷新后重新操作' });
119
+ return res.status(409).json({ code: 'GROUP_CHANGED', currentUpdatedAt: existing?.updatedAt,
120
+ error: `协作组已变化(当前 updatedAt=${existing?.updatedAt}),请刷新后重新操作` });
107
121
  }
108
122
  if (!options.store.remove(req.params.groupId))
109
123
  return res.status(404).json({ error: '协作组不存在' });
@@ -1,6 +1,6 @@
1
1
  import { canonicalShortId } from './collaborationProtocol.js';
2
2
  import { sanitizeCollaborationRole } from './collaborationStore.js';
3
- const SHELL_RULE = '─'.repeat(30);
3
+ const SHELL_RULE = '─'.repeat(3);
4
4
  /** Body larger than this (in UTF-8 bytes) is replaced by a retrieval pointer
5
5
  * instead of being injected into the terminal verbatim. */
6
6
  const MAX_INLINE_BODY_BYTES = 4_096;
@@ -61,41 +61,22 @@ export function collaborationMessageAnchorTokens(messages) {
61
61
  return [message.id, counts.get(short) === 1 ? short : message.id];
62
62
  }));
63
63
  }
64
- /** The one line of a delivered block that carries the message's id, chosen by
65
- * what the recipient can actually do with it: an agent-sourced message gets the
66
- * reply command, a user message gets the read-back command (`td collab reply`
67
- * refuses user messages NO_REPLY_TARGET so naming it there would be a dead
68
- * command). Both routes accept the short form (the CLI resolves id prefixes),
69
- * so whichever token collaborationMessageAnchorTokens picks for this delivery is what
70
- * the recipient can paste back. Every message must carry its token regardless
71
- * of source: the invariant is pinned by the "carries its id" test in
72
- * collaborationPrompt.test.ts. */
73
- export function collaborationMessageAnchorLine(message, token = canonicalShortId(message.id)) {
74
- return message.fromSessionId
75
- ? `回复:td collab reply ${token} "内容" --text`
76
- : `详情:td collab message get ${token} --json`;
77
- }
78
- /** A single delivered message: `来自:X · kind` over a fenced body, with the
79
- * anchor line (reply route / read-back route) kept outside the fence. A fan-out
80
- * dispatch (message.fanOutIds present) is flagged `· 群发` and names the
81
- * sibling recipients on their own line, so a broadcast is never mistaken for
82
- * a one-to-one assignment — raw ids fall back to the sanitized id itself. */
83
- function formatCollaborationMessage(message, source, fannedNames, fence, token, now) {
84
- const lines = [`来自:${source}${message.kind === 'message' ? '' : ` · ${message.kind}`}${fannedNames.length ? ' · 群发' : ''}`];
64
+ /** A single delivered message: `来自:X · kind · id` over a fenced body. The
65
+ * message id rides the source line it is the one place a recipient can
66
+ * look up how to reply (`td collab --help`, always present below) without a
67
+ * per-message command template. A fan-out dispatch (message.fanOutIds
68
+ * present) is flagged 群发` and names the sibling recipients on their own
69
+ * line, so a broadcast is never mistaken for a one-to-one assignment — raw
70
+ * ids fall back to the sanitized id itself. The id must stay in every block:
71
+ * the delivery-confirm gate searches the terminal text for this exact token. */
72
+ function formatCollaborationMessage(message, source, fannedNames, fence, token) {
73
+ const kindSuffix = message.kind === 'message' ? '' : ` · ${message.kind}`;
74
+ const lines = [`来自:${source}${kindSuffix}${fannedNames.length ? ' · 群发' : ''} · ${token}`];
85
75
  if (fannedNames.length)
86
76
  lines.push(`同时发给了:${fannedNames.join('、')}`);
87
- const rules = message.instructions;
88
- if (rules?.text) {
89
- const age = collaborationRulesAge(rules.updatedAt, now);
90
- const inline = Array.from(rules.text).length <= 200 && rules.text.split(/\r\n?|\n/).length <= 4;
91
- lines.push(inline
92
- ? `群规[${age}]:${rules.text}`
93
- : `查看群规[${age}]:td collab rules get ${message.groupId} --text`);
94
- }
95
77
  lines.push('', fence, message.content, fence);
96
78
  if (message.task)
97
79
  lines.push('', `任务上报:${JSON.stringify(message.task)}`);
98
- lines.push('', collaborationMessageAnchorLine(message, token));
99
80
  return lines.join('\n');
100
81
  }
101
82
  export function formatCollaborationDelivery(input) {
@@ -117,10 +98,7 @@ export function formatCollaborationDelivery(input) {
117
98
  const fence = '`'.repeat(Math.max(3, ...Array.from(body.matchAll(/`+/g), (match) => match[0].length + 1)));
118
99
  const fannedNames = (message.fanOutIds ?? [])
119
100
  .map((sessionId) => sanitizeCollaborationName(sessionsById.get(sessionId)?.name ?? sessionId));
120
- // Show current guidance alongside the command that retrieves current rules.
121
- // The stored message snapshot remains available through message get.
122
- const instructions = groupsById.get(message.groupId)?.instructions ?? message.instructions;
123
- blocks.push(formatCollaborationMessage({ ...message, content: body, instructions }, source, fannedNames, fence, token, now));
101
+ blocks.push(formatCollaborationMessage({ ...message, content: body }, source, fannedNames, fence, token));
124
102
  }
125
103
  // The shell header names the group only when every block belongs to one;
126
104
  // a mixed batch drops the header entirely (no single-group claim to make).
@@ -128,53 +106,66 @@ export function formatCollaborationDelivery(input) {
128
106
  const groupIds = [...new Set(input.messages.map((message) => message.groupId))];
129
107
  const singleGroup = groupIds.length === 1 ? groupsById.get(groupIds[0]) : null;
130
108
  const shellHeader = singleGroup
131
- ? `「${sanitizeCollaborationName(singleGroup.name ?? '协作组')}」群 · ${singleGroup.sessionIds.length} 人`
109
+ ? `「${sanitizeCollaborationName(singleGroup.name ?? '协作组')}」td群 · ${singleGroup.sessionIds.length} 人`
132
110
  : '';
111
+ // One-time education block: static guidance that only rides the first
112
+ // delivery (and again whenever the roster or the group rules change).
113
+ // Current group guidance is preferred over the message's stored snapshot —
114
+ // the snapshot stays reachable through message get.
115
+ const educated = input.showRoutingHelp !== false;
116
+ const education = [];
117
+ if (educated) {
118
+ const rules = singleGroup?.instructions ?? input.messages.find((message) => message.instructions)?.instructions;
119
+ if (rules?.text) {
120
+ const age = collaborationRulesAge(rules.updatedAt, now);
121
+ const inline = Array.from(rules.text).length <= 200 && rules.text.split(/\r\n?|\n/).length <= 4;
122
+ education.push(inline
123
+ ? `群规[${age}]:${rules.text}`
124
+ : `查看群规[${age}]:td collab rules get ${singleGroup?.id ?? input.messages[0].groupId} --text`);
125
+ }
126
+ const peerIds = Array.from(new Set(input.groups.flatMap((group) => group.sessionIds)))
127
+ .filter((sessionId) => sessionId !== input.targetSessionId);
128
+ // Keep routing IDs once, only for peers who are not already directly replyable.
129
+ const sourceIds = new Set(input.messages.map((message) => message.fromSessionId));
130
+ const peers = peerIds.filter((id) => !sourceIds.has(id)).map((sessionId) => {
131
+ const session = sessionsById.get(sessionId);
132
+ return `- ${session ? sanitizeCollaborationName(session.name) : '离线会话'}:\`td collab send ${sessionId} "消息内容" --text\``;
133
+ });
134
+ if (peers.length)
135
+ education.push('联系其他成员:', ...peers);
136
+ const captureHelp = '查看屏幕:`td collab capture <会话ID> --text`(只读,不打断对方;仅同组本机 tmux,远端用 send 询问)。ID:`td collab status --text`。快照不代表完成,勿循环轮询。';
137
+ education.push(captureHelp);
138
+ if (peerIds.some((id) => id.startsWith('remote:'))) {
139
+ education.push('跨服务通信使用 td collab;已登记节点由服务后台直接投递,使用 message get 查看送达回执与重试原因。');
140
+ }
141
+ }
142
+ // Dynamic notices report current conditions and are never gated:
143
+ // an unreachable peer changes what a reply can actually do, every delivery.
133
144
  const peerIds = Array.from(new Set(input.groups.flatMap((group) => group.sessionIds)))
134
145
  .filter((sessionId) => sessionId !== input.targetSessionId);
135
- // Keep routing IDs once, only for peers who are not already directly replyable.
136
- const sourceIds = new Set(input.messages.map((message) => message.fromSessionId));
137
- const peers = peerIds.filter((id) => !sourceIds.has(id)).map((sessionId) => {
138
- const session = sessionsById.get(sessionId);
139
- return `- ${session ? sanitizeCollaborationName(session.name) : '离线会话'}:\`td collab send ${sessionId} "消息内容" --text\``;
140
- });
141
146
  const unreachable = peerIds.filter((id) => sessionsById.get(id)?.status === 'service-unreachable');
142
- // Static command help is shown once per roster; delivery conditions remain visible.
143
- const routingHelp = input.showRoutingHelp === false ? [] : (peers.length ? ['联系其他成员:', ...peers] : []);
144
- const dynamicNotices = [
145
- ...(peerIds.some((id) => id.startsWith('remote:'))
146
- ? ['跨服务通信使用 td collab;已登记节点由服务后台直接投递,使用 message get 查看送达回执与重试原因。'] : []),
147
- ...unreachable.map((id) => {
148
- const session = sessionsById.get(id);
149
- return `注意:${session ? sanitizeCollaborationName(session.name) : id} 服务不可达,消息无法送达;仅可排队等待重连。`;
150
- }),
151
- ];
152
- const captureHelp = '查看屏幕:`td collab capture <会话ID> --text`(只读,不打断对方;仅同组本机 tmux,远端用 send 询问)。ID:`td collab status --text`。快照不代表完成,勿循环轮询。';
153
- const notes = [...routingHelp, ...dynamicNotices,
154
- ...(input.showRoutingHelp === false ? [] : [captureHelp]), '帮助:td collab --help'];
155
- // Notes (education + dynamic notices) live inside the shell, after the last
156
- // message, so the closing rule still marks the end of the delivered block.
147
+ const dynamicNotices = unreachable.map((id) => {
148
+ const session = sessionsById.get(id);
149
+ return `注意:${session ? sanitizeCollaborationName(session.name) : id} 服务不可达,消息无法送达;仅可排队等待重连。`;
150
+ });
151
+ const notes = [...education, ...dynamicNotices, '帮助:td collab --help'];
157
152
  if (!blocks.length)
158
153
  return notes.join('\n');
159
- // Self-identity rides the shell under the header so the recipient always
160
- // knows the name peers see (a rename lands on the next delivery — no
161
- // separate notification needed) and, in a single-group delivery, its role.
162
- // The name needs no group claim and survives mixed batches; the role (定位)
163
- // only rides when the batch maps to exactly one group.
154
+ // Recipient identity rides the shell under the header so the recipient
155
+ // always knows the name peers see (a rename lands on the next delivery — no
156
+ // separate notification needed) and, in a single-group delivery, its role
157
+ // (定位). The name needs no group claim and survives mixed batches; the
158
+ // role only rides when the batch maps to exactly one group.
164
159
  const ownName = sanitizeCollaborationName(sessionsById.get(input.targetSessionId)?.name ?? '');
165
160
  const ownRole = singleGroup
166
161
  ? sanitizeCollaborationRole(singleGroup.roles?.[input.targetSessionId] ?? '')
167
162
  : '';
168
- const lines = [SHELL_RULE];
169
- if (shellHeader)
170
- lines.push(shellHeader);
171
- if (ownName)
172
- lines.push(`你:${ownName}`);
173
- if (ownRole)
174
- lines.push(`你的定位:${ownRole}`);
175
- // No blank line between the identity block and the first message, nor
176
- // between the last message and the notes: the shell reads as one compact
177
- // block, and only message-to-message boundaries keep a blank line.
163
+ const contextLines = [shellHeader, ownName ? `收件人:${ownName}${ownRole ? ` · 定位:${ownRole}` : ''}` : ''].filter(Boolean);
164
+ const lines = [SHELL_RULE, ...contextLines];
165
+ // A blank line separates the who/where context block from the message
166
+ // stream: the shell reads as context, then one compact block per message.
167
+ if (contextLines.length)
168
+ lines.push('');
178
169
  lines.push(blocks.join('\n\n'), ...notes, SHELL_RULE);
179
170
  return lines.join('\n');
180
171
  }
@@ -13,10 +13,15 @@ export const COLLAB_LIMITS = {
13
13
  export class CollaborationError extends Error {
14
14
  code;
15
15
  httpStatus;
16
- constructor(code, message, httpStatus = 400) {
16
+ details;
17
+ /** Structured fields merged into the JSON error body (e.g. the group's
18
+ * current updatedAt on GROUP_CHANGED) so callers can retry programmatically
19
+ * without scraping the sentence. */
20
+ constructor(code, message, httpStatus = 400, details) {
17
21
  super(message);
18
22
  this.code = code;
19
23
  this.httpStatus = httpStatus;
24
+ this.details = details;
20
25
  }
21
26
  }
22
27
  /** A delivered terminal shows a short id, because a 36-character one wraps in
@@ -274,7 +274,7 @@ export class CollaborationService {
274
274
  if (input.id && (!existing || existing.deleted))
275
275
  throw new Error('GROUP_NOT_FOUND');
276
276
  if (existing && input.expectedUpdatedAt !== existing.updatedAt)
277
- throw new CollaborationError('GROUP_CHANGED', '协作组已变化,请刷新后重试', 409);
277
+ throw new CollaborationError('GROUP_CHANGED', `协作组已变化(当前 updatedAt=${existing.updatedAt}),请刷新后重试`, 409, { currentUpdatedAt: existing.updatedAt });
278
278
  if (typeof input.name !== 'string' || !input.name.trim() || COLLAB_NAME_FORBIDDEN.test(input.name) || !Array.isArray(input.sessionIds) || input.sessionIds.some(id => typeof id !== 'string') || new Set(input.sessionIds).size < 2)
279
279
  throw new Error('INVALID_GROUP');
280
280
  if (!input.sessionIds.some(id => address(id)) && !existing?.federated) {
@@ -182,7 +182,7 @@ export class CollaborationStore {
182
182
  throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
183
183
  if (source.id === target.id || !source.sessionIds.includes(input.sessionId)
184
184
  || source.updatedAt !== input.expectedSourceUpdatedAt || target.updatedAt !== input.expectedTargetUpdatedAt) {
185
- throw new CollaborationError('GROUP_CHANGED', '协作组已变化,请刷新后重新移动成员', 409);
185
+ throw new CollaborationError('GROUP_CHANGED', `协作组已变化(当前 updatedAt:源组=${source.updatedAt},目标组=${target.updatedAt}),请刷新后重新移动成员`, 409, { currentSourceUpdatedAt: source.updatedAt, currentTargetUpdatedAt: target.updatedAt });
186
186
  }
187
187
  const sourceIds = source.sessionIds.filter((id) => id !== input.sessionId);
188
188
  const dissolved = sourceIds.length < 2;
@@ -448,7 +448,7 @@ export class CollaborationStore {
448
448
  if (!existing || existing.deleted)
449
449
  throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
450
450
  if (existing.federated || existing.updatedAt !== expectedUpdatedAt || this.getGroup(group.id)) {
451
- throw new CollaborationError('GROUP_CHANGED', '协作组已被修改,请重新打开成员管理后再保存', 409);
451
+ throw new CollaborationError('GROUP_CHANGED', `协作组已被修改(当前 updatedAt=${existing.updatedAt}),请刷新后再保存`, 409, { currentUpdatedAt: existing.updatedAt });
452
452
  }
453
453
  const promoted = { ...group, createdAt: existing.createdAt, updatedAt: Math.max(Date.now(), existing.updatedAt + 1, group.updatedAt) };
454
454
  const idempotency = { ...this.document.idempotency };