termdock 1.4.190 → 1.4.191

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 +67 -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 +56 -1
  202. package/dist/server/cli.js +11 -4
  203. package/dist/server/routes/terminal.js +270 -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
@@ -5,13 +5,17 @@ export const COLLAB_HELP = `td collab — durable messages; no agent-specific ho
5
5
  status | capabilities
6
6
  rebind [--pane %3] (explicitly bind this peer to its current Agent; resumes queued delivery)
7
7
  send <session-id> <message> | reply <message-id> <message> | handoff <session-id> <message>
8
+ (send fan-out: comma-separated same-group ids, e.g. send a,b "任务"; every
9
+ recipient sees it as 群发 with the sibling list; waiting options apply to
10
+ single-recipient sends only)
8
11
  --group <id> --thread <id> --idempotency-key <key>
9
12
  --file <path> | --stdin (instead of inline body; -- ends option parsing)
10
13
  --wait-until queued|delivered|read --timeout 30s
11
14
  --expect-reply ack|result|any
12
15
  --response-kind ack|progress|result --metadata '<JSON object>'
13
- --task-envelope '<JSON: task_id,status,progress?,evidence?,blocker?>'
14
16
  --expires-at <ISO timestamp or epoch milliseconds>
17
+ reply: --task-envelope '<JSON: task_id,status,progress?,evidence?,blocker?>'
18
+ (status reporting happens only via reply; send/handoff dispatch carries no task state)
15
19
  message get <message-id> [--receipt-only]
16
20
  message read <message-id> (explicit consumption; not application ACK)
17
21
  message watch <message-id> [--wait-until delivered|read] [--timeout 30s]
@@ -22,14 +26,41 @@ export const COLLAB_HELP = `td collab — durable messages; no agent-specific ho
22
26
  cursor commit <token> --consumer <name> (commit after processing the page)
23
27
  add|remove <group-id> <session-id>
24
28
  spawn <group-id> <agent-slug> [--name <name>] [--cwd <path>] [--task <text>]
29
+ role list <group-id>
30
+ role set <group-id> <session-id> <role…> (trailing words join as the role)
31
+ role unset <group-id> <session-id>
32
+ (roles are shared within the group; members set each other's, your own
33
+ rides the delivery shell header)
34
+ rename <session-id> <name…> (rename a member of any shared group;
35
+ trailing words join as the new name; roster and shells show it at once)
36
+ drive <session-id> approve|enter|escape|space|left|right|up|down|capture
37
+ drive <session-id> run <command…>
38
+ (drive the terminal of a member session you share a group with — terminal
39
+ operations are shell operations: approve dismisses an interactive approval
40
+ dialog and refuses unless one is actually showing; named keys inject one
41
+ key; capture reads the current screen back; run submits one line and
42
+ returns the screen. Cannot target your own session. Treat every drive as
43
+ strong control: the member's shell executes what you send.)
44
+ cleanup <session-id>… 移除协作会话并终止其 tmux/进程(仅限与你同组的会话;
45
+ 不能清理当前会话自身,也不能通过清理解散你所在的组)
46
+ 风险操作:默认只打印清理计划并拒绝执行(exit 1)——这是不可恢复的删除。
47
+ 必须先向用户(人类)说明将清理的会话与影响并获得其明确同意,
48
+ 才能以 --confirm 重跑执行;每次执行都需要当场的人类授权。
25
49
  --json (default) | --jsonl | --text
26
50
  Exit codes: 0 requested condition met; 1 invalid request/network error;
51
+ cleanup without --confirm prints the plan and refuses (also 1);
27
52
  2 wait timeout (message may still deliver); 3 failed/expired.
28
53
  Message limit: ${COLLAB_LIMITS.message_bytes} UTF-8 bytes; metadata: ${COLLAB_LIMITS.metadata_bytes} bytes.
29
54
  Idempotency retention: 7 days. read/ACK/result never imply each other.
30
55
  Inbox defaults to unread first, newest 50; cursor/consumer mode reads oldest unseen first.
31
56
  Reading never advances a consumer or marks messages read automatically.`;
32
- const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'unread', 'follow', 'stdin', 'receipt-only', 'help']);
57
+ /** One roster row: prefer the member's human name, keep the full session id
58
+ * reachable for role set/unset targeting, mark unset members explicitly. */
59
+ function roleLine(member, role) {
60
+ const label = member.name ? `${member.name} (${member.sessionId})` : member.sessionId;
61
+ return role ? `- ${label}:${role}` : `- ${label}(未设置)`;
62
+ }
63
+ const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'unread', 'follow', 'stdin', 'receipt-only', 'confirm', 'help']);
33
64
  const VALUE_OPTIONS = new Set(['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']);
34
65
  export function parseCollaborationCommand(argv) {
35
66
  const options = {};
@@ -61,7 +92,7 @@ export function parseCollaborationCommand(argv) {
61
92
  positional.push(value);
62
93
  }
63
94
  const action = (options.help ? 'help' : positional.shift() ?? 'status');
64
- if (!['status', 'inbox', 'send', 'handoff', 'reply', 'add', 'remove', 'spawn', 'message', 'cursor', 'rebind', 'capabilities', 'help'].includes(action))
95
+ if (!['status', 'inbox', 'send', 'handoff', 'reply', 'add', 'remove', 'spawn', 'message', 'cursor', 'rebind', 'role', 'rename', 'cleanup', 'drive', 'capabilities', 'help'].includes(action))
65
96
  throw new Error('Unknown collaboration command; see td collab --help');
66
97
  if (options.pane && !/^%\d+$/.test(String(options.pane)))
67
98
  throw new Error('pane must be a tmux pane id such as %3');
@@ -106,17 +137,63 @@ export function parseCollaborationCommand(argv) {
106
137
  command.cwd = options.cwd;
107
138
  command.task = options.task;
108
139
  }
140
+ else if (action === 'role') {
141
+ command.operation = positional.shift();
142
+ command.groupId = positional.shift();
143
+ if (command.operation === 'list') {
144
+ if (!command.groupId || positional.length)
145
+ throw new Error('Usage: td collab role list <group-id>');
146
+ }
147
+ else if (command.operation === 'set') {
148
+ command.sessionId = positional.shift();
149
+ command.role = positional.join(' ');
150
+ if (!command.groupId || !command.sessionId || !command.role?.trim())
151
+ throw new Error('Usage: td collab role set <group-id> <session-id> <role…>');
152
+ }
153
+ else if (command.operation === 'unset') {
154
+ command.sessionId = positional.shift();
155
+ if (!command.groupId || !command.sessionId || positional.length)
156
+ throw new Error('Usage: td collab role unset <group-id> <session-id>');
157
+ }
158
+ else
159
+ throw new Error('Usage: td collab role list|set|unset (see td collab --help)');
160
+ }
161
+ else if (action === 'rename') {
162
+ command.sessionId = positional.shift();
163
+ command.name = positional.join(' ');
164
+ if (!command.sessionId || !command.name?.trim())
165
+ throw new Error('Usage: td collab rename <session-id> <name…>');
166
+ }
167
+ else if (action === 'cleanup') {
168
+ command.sessionIds = [...positional];
169
+ if (command.sessionIds.length === 0)
170
+ throw new Error('Usage: td collab cleanup <session-id> [<session-id>…]');
171
+ }
172
+ else if (action === 'drive') {
173
+ command.sessionId = positional.shift();
174
+ command.operation = positional.shift();
175
+ if (!command.sessionId || !command.operation)
176
+ throw new Error('Usage: td collab drive <session-id> approve|enter|escape|space|left|right|up|down|capture|run <command…>');
177
+ if (command.operation === 'run') {
178
+ command.message = positional.join(' ');
179
+ if (!command.message?.trim())
180
+ throw new Error('Usage: td collab drive <session-id> run <command…>');
181
+ }
182
+ else if (positional.length || !['approve', 'enter', 'escape', 'space', 'left', 'right', 'up', 'down', 'capture'].includes(command.operation)) {
183
+ throw new Error(`Unknown drive action ${command.operation ?? ''}; use approve|enter|escape|space|left|right|up|down|capture|run`);
184
+ }
185
+ }
109
186
  else if (positional.length && action !== 'help')
110
187
  throw new Error(`Unexpected arguments for ${action}`);
111
188
  const allowed = new Set(['json', 'jsonl', 'text', 'help']);
112
189
  const byAction = {
113
190
  status: [], capabilities: [], rebind: ['pane'], help: [...BOOLEAN_OPTIONS, ...VALUE_OPTIONS],
114
- send: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at', 'kind'],
115
- handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
191
+ send: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at', 'kind'],
192
+ handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at'],
116
193
  reply: ['idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
117
194
  inbox: ['unread', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'group', 'thread', 'kind', 'response-kind', 'follow', 'timeout'],
118
195
  message: ['receipt-only', 'follow', 'wait-until', 'timeout', 'expect-reply'], cursor: ['consumer'],
119
- add: [], remove: [], spawn: ['name', 'cwd', 'task'],
196
+ add: [], remove: [], spawn: ['name', 'cwd', 'task'], role: [], rename: [], cleanup: ['confirm'], drive: [],
120
197
  };
121
198
  for (const option of byAction[action])
122
199
  allowed.add(option);
@@ -160,12 +237,28 @@ export async function executeCollaborationCommand(command, context, io) {
160
237
  const output = (value) => {
161
238
  if (o.text) {
162
239
  if (Array.isArray(value.messages))
163
- for (const message of value.messages)
164
- io.write(`[${message.responseKind ?? message.kind}] ${message.id} from ${message.fromSessionId ?? 'user'}\n${message.content}`);
240
+ for (const message of value.messages) {
241
+ const fanIds = Array.isArray(message.fanOutIds) ? message.fanOutIds : [];
242
+ const names = value.names ?? {};
243
+ io.write(`[${message.responseKind ?? message.kind}] ${message.id} from ${message.fromSessionId ?? 'user'}${fanIds.length ? ' · 群发' : ''}`);
244
+ // A fan-out dispatch names its sibling recipients so a broadcast is
245
+ // never read as a one-to-one assignment; unknown ids stay raw.
246
+ if (fanIds.length)
247
+ io.write(`同时发给了:${fanIds.map((id) => names[id] ?? id).join('、')}`);
248
+ io.write(message.content);
249
+ }
165
250
  else if (value.message)
166
251
  io.write(`[${value.status}] ${value.message_id}\n${value.message.content}`);
167
- else if (value.message_id)
252
+ else if (value.message_id) {
168
253
  io.write(`${value.status} ${value.message_id} thread=${value.thread_id}${value.code ? ` ${value.code}` : ''}${value.failure_reason ? ` ${value.failure_reason}` : ''}`);
254
+ // A timeout after the wait stage itself was reached means delivery
255
+ // (or reading) completed — only the expected reply/result is late.
256
+ if (value.code === 'WAIT_TIMEOUT' && value.stage_reached && value.expect_reply) {
257
+ io.write(`\n投递已完成;等待${value.expect_reply === 'result' ? '结果' : '回复'}超时(expect-reply=${value.expect_reply})`);
258
+ }
259
+ if (value.snapshot)
260
+ io.write(`\n${value.snapshot}`);
261
+ }
169
262
  else
170
263
  io.write(JSON.stringify(value, null, 2));
171
264
  if (value.next_cursor)
@@ -184,6 +277,24 @@ export async function executeCollaborationCommand(command, context, io) {
184
277
  try {
185
278
  if (command.action === 'help') {
186
279
  io.write(COLLAB_HELP);
280
+ // Append the caller's own groups with every member role — help doubles
281
+ // as the newcomer's one-shot introduction to the current roster. The
282
+ // snapshot requires a managed session, so it degrades to bare help
283
+ // outside one (a plain CLI read still prints the full surface).
284
+ try {
285
+ const mine = await request('GET', '/role');
286
+ const groups = mine.groups ?? [];
287
+ if (groups.length) {
288
+ io.write('\n本会话所在协作组的成员定位:');
289
+ for (const group of groups) {
290
+ io.write(`组「${group.name ?? ''}」(${group.sessionIds.length} 个成员)`);
291
+ const members = new Map((group.members ?? []).map((member) => [member.sessionId, member.name ?? null]));
292
+ for (const id of group.sessionIds)
293
+ io.write(roleLine({ sessionId: id, name: members.get(id) ?? null }, group.roles?.[id]));
294
+ }
295
+ }
296
+ }
297
+ catch { /* help stays available without a server or session */ }
187
298
  return 0;
188
299
  }
189
300
  if (command.action === 'capabilities' || command.action === 'status') {
@@ -252,13 +363,114 @@ export async function executeCollaborationCommand(command, context, io) {
252
363
  idempotencyKey = String(o['idempotency-key'] ?? randomUUID());
253
364
  const extras = { group_id: o.group, thread_id: o.thread, idempotency_key: idempotencyKey, response_kind: o['response-kind'],
254
365
  metadata: o.metadata ? JSON.parse(String(o.metadata)) : undefined, task: o['task-envelope'] ? JSON.parse(String(o['task-envelope'])) : undefined, expires_at: expiresAt };
366
+ // A comma-separated target list fans one dispatch out to several
367
+ // recipients of a shared group; every edge carries the sibling list so
368
+ // recipients see the 群发 framing. Reply stays one-to-one by nature.
369
+ const targets = command.target.split(',').map((id) => id.trim()).filter(Boolean);
370
+ const fanOut = command.action !== 'reply' && targets.length > 1;
371
+ if (command.action !== 'reply' && !targets.length)
372
+ throw new Error(`${command.action} requires a target session id`);
373
+ if (fanOut && (o['wait-until'] || o['expect-reply']))
374
+ throw new Error('--wait-until/--expect-reply need a single recipient; fan-out confirms queued delivery only');
255
375
  deadline = now() + duration(String(o.timeout ?? '30s'));
256
376
  receipt = await request('POST', command.action === 'reply' ? '/reply' : '/send', command.action === 'reply'
257
- ? { ...extras, messageId: command.target, content } : { ...extras, targetSessionId: command.target, message: content, kind: command.action === 'handoff' ? 'handoff' : o.kind ?? 'message' });
377
+ ? { ...extras, messageId: command.target, content }
378
+ : { ...extras, ...(fanOut ? { toSessionIds: targets } : { targetSessionId: targets[0] }), message: content, kind: command.action === 'handoff' ? 'handoff' : o.kind ?? 'message' });
258
379
  // CLI receipts stay small even when the message is a large evidence package.
259
380
  delete receipt.messages;
260
381
  receipt.idempotency_key = idempotencyKey;
261
382
  }
383
+ else if (command.action === 'role') {
384
+ if (command.operation === 'list') {
385
+ const body = await request('GET', `/role?group=${encodeURIComponent(command.groupId)}`);
386
+ if (o.text) {
387
+ const group = body.group;
388
+ io.write(`定位表(${group.name ? `组「${group.name}」· ` : ''}${group.sessionIds.length} 个成员):`);
389
+ const members = new Map((group.members ?? []).map((member) => [member.sessionId, member.name ?? null]));
390
+ for (const id of group.sessionIds)
391
+ io.write(roleLine({ sessionId: id, name: members.get(id) ?? null }, group.roles?.[id]));
392
+ }
393
+ else
394
+ output(body);
395
+ return 0;
396
+ }
397
+ const body = await request('POST', '/role', { group_id: command.groupId, session_id: command.sessionId,
398
+ role: command.operation === 'unset' ? null : command.role });
399
+ if (o.text) {
400
+ io.write(command.operation === 'set'
401
+ ? `定位已设置:${command.sessionId} = ${body.group.roles?.[command.sessionId] ?? ''}`
402
+ : `定位已清除:${command.sessionId}`);
403
+ }
404
+ else
405
+ output(body);
406
+ return 0;
407
+ }
408
+ else if (command.action === 'rename') {
409
+ const body = await request('POST', '/name', { session_id: command.sessionId, name: command.name });
410
+ if (o.text)
411
+ io.write(`已改名:${command.sessionId} = ${body.name ?? command.name}`);
412
+ else
413
+ output(body);
414
+ return 0;
415
+ }
416
+ else if (command.action === 'drive') {
417
+ const body = await request('POST', '/drive', { session: command.sessionId, action: command.operation, text: command.message });
418
+ const result = body;
419
+ if (o.text) {
420
+ if (command.operation === 'run') {
421
+ io.write(`已向 ${command.sessionId} 发送一行并提交:${command.message}`);
422
+ if (result.snapshot)
423
+ io.write(`--- ${command.sessionId} 当前屏幕 ---\n${result.snapshot}`);
424
+ }
425
+ else if (command.operation === 'capture') {
426
+ if (result.snapshot)
427
+ io.write(result.snapshot);
428
+ else
429
+ io.write('(无快照)');
430
+ }
431
+ else if (command.operation === 'approve') {
432
+ io.write(result.approved ? `已批准 ${command.sessionId} 的审批对话框` : '目标面板当前没有显示审批对话框;未发送任何按键');
433
+ }
434
+ else
435
+ io.write(`已向 ${command.sessionId} 发送 ${command.operation}`);
436
+ }
437
+ else
438
+ output(body);
439
+ return 0;
440
+ }
441
+ else if (command.action === 'cleanup') {
442
+ const plan = await request('POST', '/cleanup', { sessionIds: command.sessionIds, confirmed: Boolean(o.confirm) });
443
+ const targets = (plan.plan?.targets ?? []);
444
+ const groups = (plan.plan?.groups ?? []);
445
+ if (plan.ok !== true) {
446
+ if (o.text) {
447
+ io.write('清理计划(未执行 —— 需要人类授权)');
448
+ for (const target of targets)
449
+ io.write(`- ${target.name ?? target.sessionId}(${target.sessionId})${target.mode === 'tmux' && target.tmuxSessionName ? ` · tmux ${target.tmuxSessionName}` : target.mode ? ` · ${target.mode}` : ''}`);
450
+ if (groups.length) {
451
+ io.write('协作组影响:');
452
+ for (const group of groups)
453
+ io.write(group.dissolves
454
+ ? `- 组「${group.name ?? group.id}」将被解散(组内消息一并清除)`
455
+ : `- 组「${group.name ?? group.id}」:${group.sizeBefore} 个成员 → ${group.sizeAfter} 个成员`);
456
+ }
457
+ io.write('⚠ 这是不可恢复的风险操作:将移除上述会话记录并终止其 tmux/进程。');
458
+ io.write('执行前必须先向用户(人类)说明以上内容并获得明确同意,再以 --confirm 重跑本命令;每次执行都需要当场的人类授权。');
459
+ }
460
+ else
461
+ output(plan);
462
+ return 1;
463
+ }
464
+ const removed = (plan.removed ?? []);
465
+ if (o.text) {
466
+ io.write(`已清理 ${removed.length} 个会话:`);
467
+ for (const entry of removed)
468
+ io.write(`- ${entry.name ?? entry.sessionId}(${entry.sessionId}):${entry.tmuxSessionName ? (entry.alreadyGone ? 'tmux 已不存在,记录已移除' : `tmux ${entry.tmuxSessionName} 已终止`) : entry.killed ? '进程已终止,记录已移除' : '记录已移除'}`);
469
+ }
470
+ else
471
+ output(plan);
472
+ return 0;
473
+ }
262
474
  else {
263
475
  const body = command.action === 'spawn' ? { groupId: command.groupId, agentSlug: command.agentSlug, name: command.name, cwd: command.cwd, task: command.task }
264
476
  : { groupId: command.groupId, targetSessionId: command.sessionId, action: command.action };
@@ -284,7 +496,8 @@ export async function executeCollaborationCommand(command, context, io) {
284
496
  }
285
497
  }
286
498
  if (now() >= deadline) {
287
- output({ ...receipt, ok: false, code: 'WAIT_TIMEOUT', wait_until: stage, expect_reply: o['expect-reply'] ?? null, delivery_continues: true });
499
+ output({ ...receipt, ok: false, code: 'WAIT_TIMEOUT', wait_until: stage, stage_reached: waitSatisfied(receipt, stage, undefined),
500
+ expect_reply: o['expect-reply'] ?? null, delivery_continues: true });
288
501
  return 2;
289
502
  }
290
503
  await sleep(Math.min(500, deadline - now()));
@@ -4,6 +4,11 @@ export class CollaborationDeliveryWorker {
4
4
  states = new Map();
5
5
  failures = new Map();
6
6
  submitted = new Set();
7
+ /** Sessions whose first delivery was confirmed consumed (or settled) in
8
+ * this process. Only first deliveries pass the confirm gate — a write to
9
+ * an agent that has been interacting is consumed by definition of pty
10
+ * semantics, and every delivery after the first rides that assumption. */
11
+ confirmedSessions = new Set();
7
12
  timer = null;
8
13
  ticking = null;
9
14
  constructor(options) {
@@ -111,10 +116,11 @@ export class CollaborationDeliveryWorker {
111
116
  return;
112
117
  }
113
118
  const message = pending[0];
119
+ const attempts = store.diagnostic(message.id)?.attempt_count ?? 0;
114
120
  if (!this.submitted.has(message.id)) {
115
121
  store.recordTransport(message.id, {
116
122
  relay_online: null, peer_reachable: true,
117
- attempt_count: (store.diagnostic(message.id)?.attempt_count ?? 0) + 1,
123
+ attempt_count: attempts + 1,
118
124
  next_retry_at: Date.now() + 2_000, last_error: 'DELIVERY_IN_PROGRESS', checked_at: Date.now(),
119
125
  });
120
126
  try {
@@ -125,13 +131,73 @@ export class CollaborationDeliveryWorker {
125
131
  return;
126
132
  }
127
133
  this.submitted.add(message.id);
134
+ // Capture the recipient terminal before marking delivered, so a sender
135
+ // waiting on delivered always finds the snapshot present. The capture is
136
+ // strictly best-effort: a failure must never block delivery completion.
137
+ if (route.capture) {
138
+ try {
139
+ const snapshot = await route.capture();
140
+ if (snapshot)
141
+ store.setSnapshot(message.id, snapshot);
142
+ }
143
+ catch { /* best-effort */ }
144
+ }
145
+ }
146
+ // First-delivery confirm gate: the route becoming ready only proves the
147
+ // agent process is up, not that its TUI is reading — a write inside the
148
+ // boot window can be wiped by the startup clear while the message is
149
+ // already marked delivered. When the route can read terminal history,
150
+ // hold the first delivery until our message actually appears there (the
151
+ // agent rendered it), dismissing any approval dialog blocking the agent
152
+ // on the way. Unconfirmed writes are re-attempted up to the configured
153
+ // bound; at-least-once transport is preserved throughout.
154
+ const confirmMs = this.options.firstDeliveryConfirmMs ?? 1_500;
155
+ if (route.confirm && confirmMs > 0 && !this.confirmedSessions.has(id)
156
+ && attempts + 1 < (this.options.maxUnconfirmedWrites ?? 3)) {
157
+ if (!(await this.confirmConsumed(pending, route, confirmMs))) {
158
+ this.submitted.delete(message.id);
159
+ store.recordTransport(message.id, {
160
+ relay_online: null, peer_reachable: true,
161
+ attempt_count: attempts + 1,
162
+ next_retry_at: Date.now() + 4_000, last_error: 'AGENT_CONSUME_UNCONFIRMED', checked_at: Date.now(),
163
+ });
164
+ this.failures.delete(id);
165
+ return;
166
+ }
128
167
  }
129
168
  // If persistence fails after writing, the in-process guard avoids a
130
169
  // second write on retry. Across a crash, transport is at-least-once.
131
170
  this.complete(id, message);
132
171
  }
172
+ /** Wait out the confirm window, then look for the written messages in the
173
+ * recipient's terminal history. While the agent is blocked on an approval
174
+ * dialog (its first Bash call needs permission) dismiss it once per cycle
175
+ * — the delivery itself told the agent to run td collab, and the pane
176
+ * must show an actual dialog for the key to be sent. */
177
+ async confirmConsumed(messages, route, delayMs) {
178
+ for (let attempt = 0; attempt < 3; attempt += 1) {
179
+ await new Promise((resolve) => setTimeout(resolve, attempt === 0 ? delayMs : Math.min(1_200, delayMs)));
180
+ let content = null;
181
+ try {
182
+ content = await route.confirm();
183
+ }
184
+ catch {
185
+ content = null;
186
+ }
187
+ if (content && messages.some((message) => content.includes(message.id)))
188
+ return true;
189
+ if (content && route.approve) {
190
+ try {
191
+ await route.approve();
192
+ }
193
+ catch { /* a failed dismiss must not settle delivery */ }
194
+ }
195
+ }
196
+ return false;
197
+ }
133
198
  complete(id, message) {
134
199
  const { store } = this.options;
200
+ this.confirmedSessions.add(id);
135
201
  store.markDelivered([message.id]);
136
202
  this.submitted.delete(message.id);
137
203
  store.recordTransport(message.id, {
@@ -1,5 +1,6 @@
1
1
  import { Router } from 'express';
2
2
  import { CollaborationError } from './collaborationProtocol.js';
3
+ import { COLLAB_NAME_FORBIDDEN } from './collaborationPrompt.js';
3
4
  /** The current service owns group membership, including retained offline members. */
4
5
  export function collaborationGroupRoutes(options) {
5
6
  const router = Router();
@@ -15,6 +16,9 @@ export function collaborationGroupRoutes(options) {
15
16
  || sessionIds.some((value) => typeof value !== 'string' || !value.trim())) {
16
17
  throw new CollaborationError('INVALID_GROUP', '名称和成员列表无效', 400);
17
18
  }
19
+ if (COLLAB_NAME_FORBIDDEN.test(name.trim())) {
20
+ throw new CollaborationError('INVALID_GROUP', '名称不能包含「【】」、间隔号「·」、换行或控制字符', 400);
21
+ }
18
22
  const existing = id ? options.store.getGroup(id) : null;
19
23
  if (id && (!existing || existing.deleted))
20
24
  throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
@@ -52,6 +56,27 @@ export function collaborationGroupRoutes(options) {
52
56
  error: error instanceof Error ? error.message : '成员移动失败' });
53
57
  }
54
58
  });
59
+ router.post('/collaboration-groups/:groupId/role', (req, res) => {
60
+ try {
61
+ const existing = options.store.getGroup(req.params.groupId);
62
+ if (!existing || existing.deleted)
63
+ throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
64
+ const { sessionId, role } = req.body ?? {};
65
+ if (typeof sessionId !== 'string' || !existing.sessionIds.includes(sessionId)) {
66
+ throw new CollaborationError('NOT_A_MEMBER', '该会话不在协作组中,无法设置定位', 400);
67
+ }
68
+ if (role === undefined || (role !== null && typeof role !== 'string')) {
69
+ throw new CollaborationError('INVALID_ROLE', '定位必须是文本;清除请传 null', 400);
70
+ }
71
+ const updated = options.store.setRole({ groupId: existing.id, sessionId, role: role });
72
+ res.json({ group: { ...updated, roles: updated.roles ?? {} } });
73
+ }
74
+ catch (error) {
75
+ res.status(error instanceof CollaborationError ? error.httpStatus : 400)
76
+ .json({ code: error instanceof CollaborationError ? error.code : 'GROUP_ROLE_FAILED',
77
+ error: error instanceof Error ? error.message : '定位设置失败' });
78
+ }
79
+ });
55
80
  router.post('/collaboration-groups/:groupId/promote', (req, res) => {
56
81
  try {
57
82
  const { group, expectedUpdatedAt } = req.body ?? {};
@@ -1,54 +1,117 @@
1
- const MESSAGE_LABELS = {
2
- message: '消息',
3
- ask: '问题',
4
- reply: '回复',
5
- task: '任务',
6
- handoff: '交接',
7
- done: '完成',
8
- };
1
+ import { sanitizeCollaborationRole } from './collaborationStore.js';
2
+ const SHELL_RULE = ''.repeat(30);
3
+ /** Body larger than this (in UTF-8 bytes) is replaced by a retrieval pointer
4
+ * instead of being injected into the terminal verbatim. */
5
+ const MAX_INLINE_BODY_BYTES = 8_192;
6
+ /**
7
+ * Characters that would corrupt the delivery shell header `「X」群(N 个成员)`
8
+ * and the per-message `来自:X · kind` line: the quote brackets and the middle
9
+ * dot, which the render layer uses as structure, plus control sequences
10
+ * (C0 controls + DEL) that could inject terminal output. Legacy and federated
11
+ * names may carry them, so the render layer neutralizes them defensively;
12
+ * user-entered names are rejected at creation instead.
13
+ */
14
+ export const COLLAB_NAME_FORBIDDEN = /[【】「」·\x00-\x1f\x7f]/;
15
+ /** Render-safe name for shell header / per-message source lines: reserved
16
+ * bracket and delimiter characters become spaces (they would otherwise split
17
+ * the `「X」群` and `来自:X · kind` lines), control characters are
18
+ * neutralized, whitespace collapses, and the display length is capped. */
19
+ export function sanitizeCollaborationName(name, max = 40) {
20
+ const cleaned = name
21
+ .replace(/[【】「」]/g, ' ')
22
+ .replace(/·/g, ' ')
23
+ .replace(/[\x00-\x1f\x7f]/g, ' ')
24
+ .replace(/\s+/g, ' ')
25
+ .trim();
26
+ return cleaned.length > max ? `${cleaned.slice(0, max - 1)}…` : cleaned;
27
+ }
28
+ /** A single delivered message: `来自:X · kind` over a fenced body, with the
29
+ * reply route for agent-sourced messages kept outside the fence. A fan-out
30
+ * dispatch (message.fanOutIds present) is flagged `· 群发` and names the
31
+ * sibling recipients on their own line, so a broadcast is never mistaken for
32
+ * a one-to-one assignment — raw ids fall back to the sanitized id itself. */
33
+ function formatCollaborationMessage(message, source, fannedNames, fence) {
34
+ const lines = [`来自:${source} · ${message.kind}${fannedNames.length ? ' · 群发' : ''}`];
35
+ if (fannedNames.length)
36
+ lines.push(`同时发给了:${fannedNames.join('、')}`);
37
+ lines.push('', fence, message.content, fence);
38
+ if (message.task)
39
+ lines.push('', `任务上报:${JSON.stringify(message.task)}`);
40
+ if (message.fromSessionId)
41
+ lines.push('', `回复:td collab reply ${message.id} "回复内容" --text`);
42
+ return lines.join('\n');
43
+ }
9
44
  export function formatCollaborationDelivery(input) {
10
45
  const groupsById = new Map(input.groups.map((group) => [group.id, group]));
11
46
  const sessionsById = new Map(input.sessions.map((session) => [session.sessionId, session]));
12
- const messageBlocks = input.messages.map((message) => {
47
+ const blocks = [];
48
+ for (const message of input.messages) {
13
49
  const sourceSession = message.fromSessionId ? sessionsById.get(message.fromSessionId) : null;
14
50
  const source = message.fromSessionId
15
- ? sourceSession?.name ?? message.fromSessionId
51
+ ? sanitizeCollaborationName(sourceSession?.name ?? message.fromSessionId)
16
52
  : '用户';
17
- const group = groupsById.get(message.groupId)?.name ?? '协作组';
18
- const body = Buffer.byteLength(message.content) > 8_192
19
- ? `大消息已完整保存(${Buffer.byteLength(message.content)} 字节)。使用 td collab message get ${message.id} --json 获取正文;不要把这条提示当作任务正文。`
53
+ const bytes = Buffer.byteLength(message.content);
54
+ const body = bytes > MAX_INLINE_BODY_BYTES
55
+ ? `大消息已完整保存(${bytes} 字节)。使用 td collab message get ${message.id} --json 获取正文;不要把这条提示当作消息正文。`
20
56
  : message.content;
21
57
  const fence = '`'.repeat(Math.max(3, ...Array.from(body.matchAll(/`+/g), (match) => match[0].length + 1)));
22
- const plainBody = input.messages.length === 1 && !/[\r\n`]/.test(body) && body.length <= 240;
23
- return [
24
- `【${group} · ${source}${['task', 'handoff', 'done'].includes(message.kind) ? ` · ${MESSAGE_LABELS[message.kind]}` : ''}】`,
25
- ...(plainBody ? [body] : [fence, body, fence]),
26
- ...(message.task ? [`任务上报:${JSON.stringify(message.task)}`] : []),
27
- message.fromSessionId
28
- ? `回复:\`td collab reply ${message.id} "回复内容" --text\``
29
- : '用户消息:直接在当前会话处理,无需回复收件箱。',
30
- ].join('\n');
31
- });
58
+ const fannedNames = (message.fanOutIds ?? [])
59
+ .map((sessionId) => sanitizeCollaborationName(sessionsById.get(sessionId)?.name ?? sessionId));
60
+ blocks.push(formatCollaborationMessage({ ...message, content: body }, source, fannedNames, fence));
61
+ }
62
+ // The shell header names the group only when every block belongs to one;
63
+ // a mixed batch drops the header entirely (no single-group claim to make).
64
+ // The member count rides along as the at-a-glance size of that group.
65
+ const groupIds = [...new Set(input.messages.map((message) => message.groupId))];
66
+ const singleGroup = groupIds.length === 1 ? groupsById.get(groupIds[0]) : null;
67
+ const shellHeader = singleGroup
68
+ ? `「${sanitizeCollaborationName(singleGroup.name ?? '协作组')}」群(${singleGroup.sessionIds.length} 个成员)`
69
+ : '';
32
70
  const peerIds = Array.from(new Set(input.groups.flatMap((group) => group.sessionIds)))
33
71
  .filter((sessionId) => sessionId !== input.targetSessionId);
34
72
  // Keep routing IDs once, only for peers who are not already directly replyable.
35
73
  const sourceIds = new Set(input.messages.map((message) => message.fromSessionId));
36
74
  const peers = peerIds.filter((id) => !sourceIds.has(id)).map((sessionId) => {
37
75
  const session = sessionsById.get(sessionId);
38
- return `- ${session?.name ?? '离线会话'}:\`td collab send ${sessionId} "消息内容" --text\``;
76
+ return `- ${session ? sanitizeCollaborationName(session.name) : '离线会话'}:\`td collab send ${sessionId} "消息内容" --text\``;
39
77
  });
40
78
  const unreachable = peerIds.filter((id) => sessionsById.get(id)?.status === 'service-unreachable');
41
- const instructions = [
42
- ...(peers.length ? ['联系其他成员:', ...peers] : []),
43
- '更多操作:`td collab --help`。',
79
+ // Education (peer roster examples) decays once the session knows the group;
80
+ // the `--help` pointer stays permanently as the one-line entrance to the
81
+ // full command surface — everything else can be looked up from there.
82
+ const routingHelp = input.showRoutingHelp === false ? [] : (peers.length ? ['联系其他成员:', ...peers] : []);
83
+ const dynamicNotices = [
44
84
  ...(peerIds.some((id) => id.startsWith('remote:'))
45
85
  ? ['跨服务通信使用 td collab;转发客户端须保持运行,网页或 PWA 暂停后需返回前台继续转发。'] : []),
46
- ...unreachable.map((id) => `注意:${sessionsById.get(id)?.name ?? id} 服务不可达,消息无法送达;仅可排队等待重连。`),
86
+ ...unreachable.map((id) => {
87
+ const session = sessionsById.get(id);
88
+ return `注意:${session ? sanitizeCollaborationName(session.name) : id} 服务不可达,消息无法送达;仅可排队等待重连。`;
89
+ }),
47
90
  ];
48
- return [
49
- ...(input.messages.length > 1 ? [`[Termdock 协作 · ${input.messages.length} 条]`, ''] : []),
50
- messageBlocks.join('\n\n'),
51
- '',
52
- ...instructions,
53
- ].join('\n');
91
+ const notes = [...routingHelp, ...dynamicNotices, '更多操作:`td collab --help`。'];
92
+ // Notes (education + dynamic notices) live inside the shell, after the last
93
+ // message, so the closing rule still marks the end of the delivered block.
94
+ if (!blocks.length)
95
+ return notes.join('\n');
96
+ // Self-identity rides the shell under the header so the recipient always
97
+ // knows the name peers see (a rename lands on the next delivery — no
98
+ // separate notification needed) and, in a single-group delivery, its role.
99
+ // The name needs no group claim and survives mixed batches; the role (定位)
100
+ // only rides when the batch maps to exactly one group.
101
+ const ownName = sanitizeCollaborationName(sessionsById.get(input.targetSessionId)?.name ?? '');
102
+ const ownRole = singleGroup
103
+ ? sanitizeCollaborationRole(singleGroup.roles?.[input.targetSessionId] ?? '')
104
+ : '';
105
+ const lines = [SHELL_RULE];
106
+ if (shellHeader)
107
+ lines.push(shellHeader);
108
+ if (ownName)
109
+ lines.push(`你的名字:${ownName}`);
110
+ if (ownRole)
111
+ lines.push(`你的定位:${ownRole}`);
112
+ // No blank line between the identity block and the first message, nor
113
+ // between the last message and the notes: the shell reads as one compact
114
+ // block, and only message-to-message boundaries keep a blank line.
115
+ lines.push(blocks.join('\n\n'), ...notes, SHELL_RULE);
116
+ return lines.join('\n');
54
117
  }