termdock 1.4.174 → 1.4.175

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 (355) hide show
  1. package/.desktop-dist/main.js +154 -7
  2. package/dist/client/assets/App-BXYZyI_k.js +150 -0
  3. package/dist/client/assets/App-BXYZyI_k.js.br +0 -0
  4. package/dist/client/assets/App-BXYZyI_k.js.gz +0 -0
  5. package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js +2 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js.br +0 -0
  7. package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js.gz +0 -0
  8. package/dist/client/assets/DagPlayground-ZIqLZoMo.js +1 -0
  9. package/dist/client/assets/DagPlayground-ZIqLZoMo.js.br +0 -0
  10. package/dist/client/assets/DagPlayground-ZIqLZoMo.js.gz +0 -0
  11. package/dist/client/assets/{DiffLab-CdbPsjKS.js → DiffLab-CSMf8So2.js} +2 -2
  12. package/dist/client/assets/DiffLab-CSMf8So2.js.br +0 -0
  13. package/dist/client/assets/DiffLab-CSMf8So2.js.gz +0 -0
  14. package/dist/client/assets/DiffReview-CE5lW1zQ.js +1 -0
  15. package/dist/client/assets/DiffReview-CE5lW1zQ.js.br +0 -0
  16. package/dist/client/assets/DiffReview-CE5lW1zQ.js.gz +0 -0
  17. package/dist/client/assets/DiffReviewLab-CpUW75fZ.js +2 -0
  18. package/dist/client/assets/DiffReviewLab-CpUW75fZ.js.br +0 -0
  19. package/dist/client/assets/DiffReviewLab-CpUW75fZ.js.gz +0 -0
  20. package/dist/client/assets/{DiffViewer-hdxcM9yF.js → DiffViewer-BomvZ4kq.js} +5 -5
  21. package/dist/client/assets/DiffViewer-BomvZ4kq.js.br +0 -0
  22. package/dist/client/assets/DiffViewer-BomvZ4kq.js.gz +0 -0
  23. package/dist/client/assets/{ModelPreview-BZ454pgM.js → ModelPreview-BWdZFyhh.js} +3 -3
  24. package/dist/client/assets/ModelPreview-BWdZFyhh.js.br +0 -0
  25. package/dist/client/assets/ModelPreview-BWdZFyhh.js.gz +0 -0
  26. package/dist/client/assets/RightSidebar-CYVwFbOc.js +79 -0
  27. package/dist/client/assets/RightSidebar-CYVwFbOc.js.br +0 -0
  28. package/dist/client/assets/RightSidebar-CYVwFbOc.js.gz +0 -0
  29. package/dist/client/assets/{abnfDiagram-VCTEODGH-C185Omck.js → abnfDiagram-VCTEODGH-BIPsLStE.js} +1 -1
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-BIPsLStE.js.br +0 -0
  31. package/dist/client/assets/abnfDiagram-VCTEODGH-BIPsLStE.js.gz +0 -0
  32. package/dist/client/assets/app-D6KzR8B3.js +34 -0
  33. package/dist/client/assets/app-D6KzR8B3.js.br +0 -0
  34. package/dist/client/assets/app-D6KzR8B3.js.gz +0 -0
  35. package/dist/client/assets/{arc-DNt-DTCl.js → arc-DzeT6hBj.js} +1 -1
  36. package/dist/client/assets/arc-DzeT6hBj.js.br +0 -0
  37. package/dist/client/assets/arc-DzeT6hBj.js.gz +0 -0
  38. package/dist/client/assets/{architectureDiagram-5GKGNRK7-C5Nwm_Qz.js → architectureDiagram-5GKGNRK7-B1qsxd_5.js} +1 -1
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-B1qsxd_5.js.br +0 -0
  40. package/dist/client/assets/architectureDiagram-5GKGNRK7-B1qsxd_5.js.gz +0 -0
  41. package/dist/client/assets/{blockDiagram-I7D4REHJ-C5aR_7V9.js → blockDiagram-I7D4REHJ-NLsa29Jp.js} +1 -1
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-NLsa29Jp.js.br +0 -0
  43. package/dist/client/assets/blockDiagram-I7D4REHJ-NLsa29Jp.js.gz +0 -0
  44. package/dist/client/assets/{c4Diagram-7LVT6UL2-DjV6ZNwd.js → c4Diagram-7LVT6UL2-DBAUvou3.js} +1 -1
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-DBAUvou3.js.br +0 -0
  46. package/dist/client/assets/c4Diagram-7LVT6UL2-DBAUvou3.js.gz +0 -0
  47. package/dist/client/assets/channel-BhwBAWhq.js +1 -0
  48. package/dist/client/assets/{chunk-2Q5K7J3B-Ct7nd4cL.js → chunk-2Q5K7J3B-BsVAXGLa.js} +1 -1
  49. package/dist/client/assets/{chunk-5VM5RSS4-CXem4UYP.js → chunk-5VM5RSS4-BKtILsst.js} +1 -1
  50. package/dist/client/assets/{chunk-F27PBJKO-DyMNEawE.js → chunk-F27PBJKO-Dn9zcQZG.js} +1 -1
  51. package/dist/client/assets/chunk-F27PBJKO-Dn9zcQZG.js.br +0 -0
  52. package/dist/client/assets/chunk-F27PBJKO-Dn9zcQZG.js.gz +0 -0
  53. package/dist/client/assets/{chunk-IMKFNOWR-BsW7iIrh.js → chunk-IMKFNOWR-MVtVQeTr.js} +1 -1
  54. package/dist/client/assets/chunk-IMKFNOWR-MVtVQeTr.js.br +0 -0
  55. package/dist/client/assets/{chunk-IMKFNOWR-BsW7iIrh.js.gz → chunk-IMKFNOWR-MVtVQeTr.js.gz} +0 -0
  56. package/dist/client/assets/{chunk-JWPE2WC7-EO8E1q1J.js → chunk-JWPE2WC7-DliIIMe7.js} +1 -1
  57. package/dist/client/assets/{chunk-POPQ4Y6H-0One8T9x.js → chunk-POPQ4Y6H-XBSus3IC.js} +1 -1
  58. package/dist/client/assets/{chunk-SVP7TREG-CDu41dkj.js → chunk-SVP7TREG-wleSQcJR.js} +1 -1
  59. package/dist/client/assets/chunk-SVP7TREG-wleSQcJR.js.br +0 -0
  60. package/dist/client/assets/chunk-SVP7TREG-wleSQcJR.js.gz +0 -0
  61. package/dist/client/assets/{chunk-TICWLB2K-BACjgX9v.js → chunk-TICWLB2K-Dq6LdswN.js} +1 -1
  62. package/dist/client/assets/chunk-TICWLB2K-Dq6LdswN.js.br +0 -0
  63. package/dist/client/assets/chunk-TICWLB2K-Dq6LdswN.js.gz +0 -0
  64. package/dist/client/assets/{chunk-XXDRQBXY-BJWowF6E.js → chunk-XXDRQBXY-xafyKlM-.js} +1 -1
  65. package/dist/client/assets/classDiagram-ZZMXUADV-slQB0ns2.js +1 -0
  66. package/dist/client/assets/classDiagram-v2-VYDZK3BY-slQB0ns2.js +1 -0
  67. package/dist/client/assets/{cose-bilkent-JH36ORCC-WTwHOCdm.js → cose-bilkent-JH36ORCC-BR7Ohnm_.js} +1 -1
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-BR7Ohnm_.js.br +0 -0
  69. package/dist/client/assets/cose-bilkent-JH36ORCC-BR7Ohnm_.js.gz +0 -0
  70. package/dist/client/assets/{cynefin-OW5HDTMX-D4y8TkaO.js → cynefin-OW5HDTMX-CmPjQgER.js} +1 -1
  71. package/dist/client/assets/cynefin-OW5HDTMX-CmPjQgER.js.br +0 -0
  72. package/dist/client/assets/cynefin-OW5HDTMX-CmPjQgER.js.gz +0 -0
  73. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BzN8Ggk_.js → cynefinDiagram-5FMLGOSQ-CFyR9RvM.js} +1 -1
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CFyR9RvM.js.br +0 -0
  75. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CFyR9RvM.js.gz +0 -0
  76. package/dist/client/assets/{dagre-GXQ25YYZ-CwMYtt5E.js → dagre-GXQ25YYZ-D_RVMRO1.js} +1 -1
  77. package/dist/client/assets/dagre-GXQ25YYZ-D_RVMRO1.js.br +0 -0
  78. package/dist/client/assets/dagre-GXQ25YYZ-D_RVMRO1.js.gz +0 -0
  79. package/dist/client/assets/{diagram-S7CK7UJ4-BnTeHiAb.js → diagram-S7CK7UJ4-6fr-ZoPj.js} +1 -1
  80. package/dist/client/assets/diagram-S7CK7UJ4-6fr-ZoPj.js.br +0 -0
  81. package/dist/client/assets/diagram-S7CK7UJ4-6fr-ZoPj.js.gz +0 -0
  82. package/dist/client/assets/{diagram-UQ7AKVKN-BNlh251g.js → diagram-UQ7AKVKN-CgwiUtz0.js} +1 -1
  83. package/dist/client/assets/diagram-UQ7AKVKN-CgwiUtz0.js.br +0 -0
  84. package/dist/client/assets/diagram-UQ7AKVKN-CgwiUtz0.js.gz +0 -0
  85. package/dist/client/assets/{diagram-VSXAHHWV-B5FftXkQ.js → diagram-VSXAHHWV-CLxzTB9k.js} +1 -1
  86. package/dist/client/assets/diagram-VSXAHHWV-CLxzTB9k.js.br +0 -0
  87. package/dist/client/assets/diagram-VSXAHHWV-CLxzTB9k.js.gz +0 -0
  88. package/dist/client/assets/{diagram-VX7I27RA-C51ZNXR2.js → diagram-VX7I27RA-DjxDAvdZ.js} +1 -1
  89. package/dist/client/assets/diagram-VX7I27RA-DjxDAvdZ.js.br +0 -0
  90. package/dist/client/assets/diagram-VX7I27RA-DjxDAvdZ.js.gz +0 -0
  91. package/dist/client/assets/{diagram-Z3DM3KII-CXoZH2qf.js → diagram-Z3DM3KII-CDPOjiz4.js} +1 -1
  92. package/dist/client/assets/diagram-Z3DM3KII-CDPOjiz4.js.br +0 -0
  93. package/dist/client/assets/diagram-Z3DM3KII-CDPOjiz4.js.gz +0 -0
  94. package/dist/client/assets/{ebnfDiagram-PWID7BFC-C7YFLlx4.js → ebnfDiagram-PWID7BFC-BLbwyF1O.js} +1 -1
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLbwyF1O.js.br +0 -0
  96. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLbwyF1O.js.gz +0 -0
  97. package/dist/client/assets/{erDiagram-RLTQ6QDP-BNQlS7-g.js → erDiagram-RLTQ6QDP-DbjtZnJx.js} +1 -1
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-DbjtZnJx.js.br +0 -0
  99. package/dist/client/assets/erDiagram-RLTQ6QDP-DbjtZnJx.js.gz +0 -0
  100. package/dist/client/assets/{flowDiagram-HODETNUW-DIQXZ06I.js → flowDiagram-HODETNUW-BirJInAN.js} +1 -1
  101. package/dist/client/assets/flowDiagram-HODETNUW-BirJInAN.js.br +0 -0
  102. package/dist/client/assets/flowDiagram-HODETNUW-BirJInAN.js.gz +0 -0
  103. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BaJQfOqD.js → ganttDiagram-EL5Y4UJY-d6Q9vSbv.js} +1 -1
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-d6Q9vSbv.js.br +0 -0
  105. package/dist/client/assets/ganttDiagram-EL5Y4UJY-d6Q9vSbv.js.gz +0 -0
  106. package/dist/client/assets/gestureArbiter-C2J0D0kV.css +1 -0
  107. package/dist/client/assets/gestureArbiter-C2J0D0kV.css.br +0 -0
  108. package/dist/client/assets/gestureArbiter-C2J0D0kV.css.gz +0 -0
  109. package/dist/client/assets/gestureArbiter-D1SNS_gK.js +1 -0
  110. package/dist/client/assets/gestureArbiter-D1SNS_gK.js.br +0 -0
  111. package/dist/client/assets/gestureArbiter-D1SNS_gK.js.gz +0 -0
  112. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-tQ4E6pwN.js → gitGraphDiagram-WWUBYQGX-BWBoIeS6.js} +1 -1
  113. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BWBoIeS6.js.br +0 -0
  114. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BWBoIeS6.js.gz +0 -0
  115. package/dist/client/assets/{index-DlxiaPv1.css → index-DUI8KRf6.css} +1 -1
  116. package/dist/client/assets/index-DUI8KRf6.css.br +0 -0
  117. package/dist/client/assets/index-DUI8KRf6.css.gz +0 -0
  118. package/dist/client/assets/{infoDiagram-27XIBGKW-DTOUeQrN.js → infoDiagram-27XIBGKW-BH4AynkM.js} +1 -1
  119. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-8sBi-gla.js → ishikawaDiagram-5VMMS53U-BQw90AKR.js} +1 -1
  120. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-8sBi-gla.js.br → ishikawaDiagram-5VMMS53U-BQw90AKR.js.br} +0 -0
  121. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BQw90AKR.js.gz +0 -0
  122. package/dist/client/assets/{journeyDiagram-3NMN7TZE-ZwikziWh.js → journeyDiagram-3NMN7TZE-DISkQocf.js} +1 -1
  123. package/dist/client/assets/journeyDiagram-3NMN7TZE-DISkQocf.js.br +0 -0
  124. package/dist/client/assets/{journeyDiagram-3NMN7TZE-ZwikziWh.js.gz → journeyDiagram-3NMN7TZE-DISkQocf.js.gz} +0 -0
  125. package/dist/client/assets/{kanban-definition-UXKFOSKX-wjuw2jQ4.js → kanban-definition-UXKFOSKX-Qg2LyEVy.js} +1 -1
  126. package/dist/client/assets/kanban-definition-UXKFOSKX-Qg2LyEVy.js.br +0 -0
  127. package/dist/client/assets/kanban-definition-UXKFOSKX-Qg2LyEVy.js.gz +0 -0
  128. package/dist/client/assets/{layout-nASMIj-E.js → layout-Cd7-4_Vr.js} +1 -1
  129. package/dist/client/assets/layout-Cd7-4_Vr.js.br +0 -0
  130. package/dist/client/assets/layout-Cd7-4_Vr.js.gz +0 -0
  131. package/dist/client/assets/{linear-pGaQeqW5.js → linear-DFTi2fTn.js} +1 -1
  132. package/dist/client/assets/linear-DFTi2fTn.js.br +0 -0
  133. package/dist/client/assets/linear-DFTi2fTn.js.gz +0 -0
  134. package/dist/client/assets/{mermaid.core-CKEBWbQz.js → mermaid.core-2v726lUU.js} +5 -5
  135. package/dist/client/assets/mermaid.core-2v726lUU.js.br +0 -0
  136. package/dist/client/assets/mermaid.core-2v726lUU.js.gz +0 -0
  137. package/dist/client/assets/{mindmap-definition-YA3MSWOX-CBjYcnwd.js → mindmap-definition-YA3MSWOX-_FpwteCy.js} +4 -4
  138. package/dist/client/assets/mindmap-definition-YA3MSWOX-_FpwteCy.js.br +0 -0
  139. package/dist/client/assets/mindmap-definition-YA3MSWOX-_FpwteCy.js.gz +0 -0
  140. package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js +2 -0
  141. package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js.br +0 -0
  142. package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js.gz +0 -0
  143. package/dist/client/assets/{pegDiagram-XKGWAZYB-DJqvclIp.js → pegDiagram-XKGWAZYB-DLFf-HmZ.js} +1 -1
  144. package/dist/client/assets/pegDiagram-XKGWAZYB-DLFf-HmZ.js.br +0 -0
  145. package/dist/client/assets/pegDiagram-XKGWAZYB-DLFf-HmZ.js.gz +0 -0
  146. package/dist/client/assets/{pieDiagram-E7YTZNPT-DLV4WPwc.js → pieDiagram-E7YTZNPT-DqTnfXHG.js} +1 -1
  147. package/dist/client/assets/pieDiagram-E7YTZNPT-DqTnfXHG.js.br +0 -0
  148. package/dist/client/assets/pieDiagram-E7YTZNPT-DqTnfXHG.js.gz +0 -0
  149. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BN7Zpsb9.js → quadrantDiagram-AXDQQJYC-B_oBH52Q.js} +1 -1
  150. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B_oBH52Q.js.br +0 -0
  151. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B_oBH52Q.js.gz +0 -0
  152. package/dist/client/assets/{railroadDiagram-O6MQD6OU-DfHX6MUm.js → railroadDiagram-O6MQD6OU-HxFQ7Pce.js} +1 -1
  153. package/dist/client/assets/railroadDiagram-O6MQD6OU-HxFQ7Pce.js.br +0 -0
  154. package/dist/client/assets/railroadDiagram-O6MQD6OU-HxFQ7Pce.js.gz +0 -0
  155. package/dist/client/assets/referenceLongPress-B3NGIjvv.js +1 -0
  156. package/dist/client/assets/referenceLongPress-B3NGIjvv.js.br +0 -0
  157. package/dist/client/assets/referenceLongPress-B3NGIjvv.js.gz +0 -0
  158. package/dist/client/assets/{requirementDiagram-BXWQKSXE-tQVSFg_k.js → requirementDiagram-BXWQKSXE-NZdfaZFk.js} +1 -1
  159. package/dist/client/assets/requirementDiagram-BXWQKSXE-NZdfaZFk.js.br +0 -0
  160. package/dist/client/assets/requirementDiagram-BXWQKSXE-NZdfaZFk.js.gz +0 -0
  161. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-C7JW1-Wh.js → sankeyDiagram-P5KCCOFB-BgCctv6X.js} +1 -1
  162. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BgCctv6X.js.br +0 -0
  163. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BgCctv6X.js.gz +0 -0
  164. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BVeDiFDI.js → sequenceDiagram-WJ2MYXX4-Br42Za1R.js} +1 -1
  165. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Br42Za1R.js.br +0 -0
  166. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Br42Za1R.js.gz +0 -0
  167. package/dist/client/assets/{sizeCapture-INFHLROL-CBRaCpeP.js → sizeCapture-INFHLROL-FT4z4S1b.js} +1 -1
  168. package/dist/client/assets/sizeCapture-INFHLROL-FT4z4S1b.js.br +0 -0
  169. package/dist/client/assets/sizeCapture-INFHLROL-FT4z4S1b.js.gz +0 -0
  170. package/dist/client/assets/sparkles-BaGaevME.js +1 -0
  171. package/dist/client/assets/{stateDiagram-D77RDMKH-CiLpGRuf.js → stateDiagram-D77RDMKH-DD0t9f84.js} +1 -1
  172. package/dist/client/assets/stateDiagram-D77RDMKH-DD0t9f84.js.br +0 -0
  173. package/dist/client/assets/stateDiagram-D77RDMKH-DD0t9f84.js.gz +0 -0
  174. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BhvVVB56.js +1 -0
  175. package/dist/client/assets/{swimlanes-42K2YHIH-BkzVtkVY.js → swimlanes-42K2YHIH-DcC8pGLx.js} +1 -1
  176. package/dist/client/assets/swimlanes-42K2YHIH-DcC8pGLx.js.br +0 -0
  177. package/dist/client/assets/swimlanes-42K2YHIH-DcC8pGLx.js.gz +0 -0
  178. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DSC5lz0i.js +8 -0
  179. package/dist/client/assets/{timeline-definition-24CTP7MA-DKyfGITP.js → timeline-definition-24CTP7MA-DnwO_Pxd.js} +1 -1
  180. package/dist/client/assets/timeline-definition-24CTP7MA-DnwO_Pxd.js.br +0 -0
  181. package/dist/client/assets/timeline-definition-24CTP7MA-DnwO_Pxd.js.gz +0 -0
  182. package/dist/client/assets/triangle-alert-BCQO9Ap9.js +1 -0
  183. package/dist/client/assets/{vennDiagram-4TSXK5OY-CRgkkrgx.js → vennDiagram-4TSXK5OY-IVNSrZDM.js} +1 -1
  184. package/dist/client/assets/vennDiagram-4TSXK5OY-IVNSrZDM.js.br +0 -0
  185. package/dist/client/assets/vennDiagram-4TSXK5OY-IVNSrZDM.js.gz +0 -0
  186. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-Cr7yS4CZ.js → wardleyDiagram-VM6X3IG4-BnGetaQL.js} +1 -1
  187. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BnGetaQL.js.br +0 -0
  188. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BnGetaQL.js.gz +0 -0
  189. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DkPYl6E0.js → xychartDiagram-S5SC5T6Z-C01eBN-W.js} +1 -1
  190. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C01eBN-W.js.br +0 -0
  191. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C01eBN-W.js.gz +0 -0
  192. package/dist/client/index.html +2 -2
  193. package/dist/client/index.html.br +0 -0
  194. package/dist/client/index.html.gz +0 -0
  195. package/dist/client/preview-shell.html +1 -0
  196. package/dist/client/preview-shell.js +20 -0
  197. package/dist/client/preview-shell.js.br +0 -0
  198. package/dist/client/preview-shell.js.gz +0 -0
  199. package/dist/client/sw-secure-api.js +132 -0
  200. package/dist/client/sw-secure-api.js.br +0 -0
  201. package/dist/client/sw-secure-api.js.gz +0 -0
  202. package/dist/client/sw.js +1 -1
  203. package/dist/client/sw.js.br +0 -0
  204. package/dist/client/sw.js.gz +0 -0
  205. package/dist/server/agent/collaborationCli.js +298 -0
  206. package/dist/server/agent/collaborationPrompt.js +10 -2
  207. package/dist/server/agent/collaborationProtocol.js +50 -0
  208. package/dist/server/agent/collaborationRoutes.js +86 -0
  209. package/dist/server/agent/collaborationStore.js +249 -21
  210. package/dist/server/cli.js +28 -168
  211. package/dist/server/entry.js +114 -51
  212. package/dist/server/federation/accessPolicy.js +122 -0
  213. package/dist/server/federation/authorization.js +226 -0
  214. package/dist/server/federation/bootstrapCors.js +23 -0
  215. package/dist/server/federation/cli.js +185 -0
  216. package/dist/server/federation/deviceNames.js +29 -0
  217. package/dist/server/federation/directRoutes.js +72 -0
  218. package/dist/server/federation/invitations.js +123 -0
  219. package/dist/server/federation/openAccess.js +25 -0
  220. package/dist/server/federation/originPolicy.js +19 -0
  221. package/dist/server/federation/packets.js +116 -0
  222. package/dist/server/federation/passwordBootstrap.js +86 -0
  223. package/dist/server/federation/passwordLoginRoutes.js +98 -0
  224. package/dist/server/federation/relay.js +346 -0
  225. package/dist/server/federation/requestContext.js +4 -0
  226. package/dist/server/federation/routeAccess.js +124 -0
  227. package/dist/server/federation/routeInvitations.js +160 -0
  228. package/dist/server/federation/runtime.js +537 -0
  229. package/dist/server/federation/secureProtocol.js +223 -0
  230. package/dist/server/federation/socketDuplex.js +42 -0
  231. package/dist/server/federation/streamLimits.js +5 -0
  232. package/dist/server/routes/filesystem.js +2 -1
  233. package/dist/server/routes/terminal.js +40 -72
  234. package/dist/server/utils/authProtection.js +13 -0
  235. package/dist/server/utils/csrfProtection.js +3 -0
  236. package/dist/server/utils/publicSecurity.js +8 -3
  237. package/package.json +10 -3
  238. package/runtime-manifest.json +4 -4
  239. package/dist/client/assets/ChangeWalkthroughPanel-hj8I8Cwy.js +0 -2
  240. package/dist/client/assets/ChangeWalkthroughPanel-hj8I8Cwy.js.br +0 -0
  241. package/dist/client/assets/ChangeWalkthroughPanel-hj8I8Cwy.js.gz +0 -0
  242. package/dist/client/assets/DagPlayground-BRoB8JX5.js +0 -1
  243. package/dist/client/assets/DagPlayground-BRoB8JX5.js.br +0 -0
  244. package/dist/client/assets/DagPlayground-BRoB8JX5.js.gz +0 -0
  245. package/dist/client/assets/DiffLab-CdbPsjKS.js.br +0 -0
  246. package/dist/client/assets/DiffLab-CdbPsjKS.js.gz +0 -0
  247. package/dist/client/assets/DiffReview-MQXjrRsW.js +0 -1
  248. package/dist/client/assets/DiffReview-MQXjrRsW.js.br +0 -0
  249. package/dist/client/assets/DiffReview-MQXjrRsW.js.gz +0 -0
  250. package/dist/client/assets/DiffReviewLab-C47CNvDM.js +0 -2
  251. package/dist/client/assets/DiffReviewLab-C47CNvDM.js.br +0 -0
  252. package/dist/client/assets/DiffReviewLab-C47CNvDM.js.gz +0 -0
  253. package/dist/client/assets/DiffViewer-hdxcM9yF.js.br +0 -0
  254. package/dist/client/assets/DiffViewer-hdxcM9yF.js.gz +0 -0
  255. package/dist/client/assets/ModelPreview-BZ454pgM.js.br +0 -0
  256. package/dist/client/assets/ModelPreview-BZ454pgM.js.gz +0 -0
  257. package/dist/client/assets/RightSidebar-i0Oq68YI.js +0 -55
  258. package/dist/client/assets/RightSidebar-i0Oq68YI.js.br +0 -0
  259. package/dist/client/assets/RightSidebar-i0Oq68YI.js.gz +0 -0
  260. package/dist/client/assets/abnfDiagram-VCTEODGH-C185Omck.js.br +0 -0
  261. package/dist/client/assets/abnfDiagram-VCTEODGH-C185Omck.js.gz +0 -0
  262. package/dist/client/assets/app-DAp8rVMc.js +0 -175
  263. package/dist/client/assets/app-DAp8rVMc.js.br +0 -0
  264. package/dist/client/assets/app-DAp8rVMc.js.gz +0 -0
  265. package/dist/client/assets/arc-DNt-DTCl.js.br +0 -0
  266. package/dist/client/assets/arc-DNt-DTCl.js.gz +0 -0
  267. package/dist/client/assets/architectureDiagram-5GKGNRK7-C5Nwm_Qz.js.br +0 -0
  268. package/dist/client/assets/architectureDiagram-5GKGNRK7-C5Nwm_Qz.js.gz +0 -0
  269. package/dist/client/assets/blockDiagram-I7D4REHJ-C5aR_7V9.js.br +0 -0
  270. package/dist/client/assets/blockDiagram-I7D4REHJ-C5aR_7V9.js.gz +0 -0
  271. package/dist/client/assets/c4Diagram-7LVT6UL2-DjV6ZNwd.js.br +0 -0
  272. package/dist/client/assets/c4Diagram-7LVT6UL2-DjV6ZNwd.js.gz +0 -0
  273. package/dist/client/assets/channel-uhLrATdy.js +0 -1
  274. package/dist/client/assets/chunk-F27PBJKO-DyMNEawE.js.br +0 -0
  275. package/dist/client/assets/chunk-F27PBJKO-DyMNEawE.js.gz +0 -0
  276. package/dist/client/assets/chunk-IMKFNOWR-BsW7iIrh.js.br +0 -0
  277. package/dist/client/assets/chunk-SVP7TREG-CDu41dkj.js.br +0 -0
  278. package/dist/client/assets/chunk-SVP7TREG-CDu41dkj.js.gz +0 -0
  279. package/dist/client/assets/chunk-TICWLB2K-BACjgX9v.js.br +0 -0
  280. package/dist/client/assets/chunk-TICWLB2K-BACjgX9v.js.gz +0 -0
  281. package/dist/client/assets/classDiagram-ZZMXUADV-CnxFfzm9.js +0 -1
  282. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CnxFfzm9.js +0 -1
  283. package/dist/client/assets/cose-bilkent-JH36ORCC-WTwHOCdm.js.br +0 -0
  284. package/dist/client/assets/cose-bilkent-JH36ORCC-WTwHOCdm.js.gz +0 -0
  285. package/dist/client/assets/cynefin-OW5HDTMX-D4y8TkaO.js.br +0 -0
  286. package/dist/client/assets/cynefin-OW5HDTMX-D4y8TkaO.js.gz +0 -0
  287. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BzN8Ggk_.js.br +0 -0
  288. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BzN8Ggk_.js.gz +0 -0
  289. package/dist/client/assets/dagre-GXQ25YYZ-CwMYtt5E.js.br +0 -0
  290. package/dist/client/assets/dagre-GXQ25YYZ-CwMYtt5E.js.gz +0 -0
  291. package/dist/client/assets/diagram-S7CK7UJ4-BnTeHiAb.js.br +0 -0
  292. package/dist/client/assets/diagram-S7CK7UJ4-BnTeHiAb.js.gz +0 -0
  293. package/dist/client/assets/diagram-UQ7AKVKN-BNlh251g.js.br +0 -0
  294. package/dist/client/assets/diagram-UQ7AKVKN-BNlh251g.js.gz +0 -0
  295. package/dist/client/assets/diagram-VSXAHHWV-B5FftXkQ.js.br +0 -0
  296. package/dist/client/assets/diagram-VSXAHHWV-B5FftXkQ.js.gz +0 -0
  297. package/dist/client/assets/diagram-VX7I27RA-C51ZNXR2.js.br +0 -0
  298. package/dist/client/assets/diagram-VX7I27RA-C51ZNXR2.js.gz +0 -0
  299. package/dist/client/assets/diagram-Z3DM3KII-CXoZH2qf.js.br +0 -0
  300. package/dist/client/assets/diagram-Z3DM3KII-CXoZH2qf.js.gz +0 -0
  301. package/dist/client/assets/ebnfDiagram-PWID7BFC-C7YFLlx4.js.br +0 -0
  302. package/dist/client/assets/ebnfDiagram-PWID7BFC-C7YFLlx4.js.gz +0 -0
  303. package/dist/client/assets/erDiagram-RLTQ6QDP-BNQlS7-g.js.br +0 -0
  304. package/dist/client/assets/erDiagram-RLTQ6QDP-BNQlS7-g.js.gz +0 -0
  305. package/dist/client/assets/flowDiagram-HODETNUW-DIQXZ06I.js.br +0 -0
  306. package/dist/client/assets/flowDiagram-HODETNUW-DIQXZ06I.js.gz +0 -0
  307. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BaJQfOqD.js.br +0 -0
  308. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BaJQfOqD.js.gz +0 -0
  309. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tQ4E6pwN.js.br +0 -0
  310. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tQ4E6pwN.js.gz +0 -0
  311. package/dist/client/assets/index-DlxiaPv1.css.br +0 -0
  312. package/dist/client/assets/index-DlxiaPv1.css.gz +0 -0
  313. package/dist/client/assets/ishikawaDiagram-5VMMS53U-8sBi-gla.js.gz +0 -0
  314. package/dist/client/assets/journeyDiagram-3NMN7TZE-ZwikziWh.js.br +0 -0
  315. package/dist/client/assets/kanban-definition-UXKFOSKX-wjuw2jQ4.js.br +0 -0
  316. package/dist/client/assets/kanban-definition-UXKFOSKX-wjuw2jQ4.js.gz +0 -0
  317. package/dist/client/assets/layout-nASMIj-E.js.br +0 -0
  318. package/dist/client/assets/layout-nASMIj-E.js.gz +0 -0
  319. package/dist/client/assets/linear-pGaQeqW5.js.br +0 -0
  320. package/dist/client/assets/linear-pGaQeqW5.js.gz +0 -0
  321. package/dist/client/assets/mermaid.core-CKEBWbQz.js.br +0 -0
  322. package/dist/client/assets/mermaid.core-CKEBWbQz.js.gz +0 -0
  323. package/dist/client/assets/mindmap-definition-YA3MSWOX-CBjYcnwd.js.br +0 -0
  324. package/dist/client/assets/mindmap-definition-YA3MSWOX-CBjYcnwd.js.gz +0 -0
  325. package/dist/client/assets/pegDiagram-XKGWAZYB-DJqvclIp.js.br +0 -0
  326. package/dist/client/assets/pegDiagram-XKGWAZYB-DJqvclIp.js.gz +0 -0
  327. package/dist/client/assets/pieDiagram-E7YTZNPT-DLV4WPwc.js.br +0 -0
  328. package/dist/client/assets/pieDiagram-E7YTZNPT-DLV4WPwc.js.gz +0 -0
  329. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BN7Zpsb9.js.br +0 -0
  330. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BN7Zpsb9.js.gz +0 -0
  331. package/dist/client/assets/railroadDiagram-O6MQD6OU-DfHX6MUm.js.br +0 -0
  332. package/dist/client/assets/railroadDiagram-O6MQD6OU-DfHX6MUm.js.gz +0 -0
  333. package/dist/client/assets/requirementDiagram-BXWQKSXE-tQVSFg_k.js.br +0 -0
  334. package/dist/client/assets/requirementDiagram-BXWQKSXE-tQVSFg_k.js.gz +0 -0
  335. package/dist/client/assets/sankeyDiagram-P5KCCOFB-C7JW1-Wh.js.br +0 -0
  336. package/dist/client/assets/sankeyDiagram-P5KCCOFB-C7JW1-Wh.js.gz +0 -0
  337. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BVeDiFDI.js.br +0 -0
  338. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BVeDiFDI.js.gz +0 -0
  339. package/dist/client/assets/sizeCapture-INFHLROL-CBRaCpeP.js.br +0 -0
  340. package/dist/client/assets/sizeCapture-INFHLROL-CBRaCpeP.js.gz +0 -0
  341. package/dist/client/assets/sparkles-D0cwIvB0.js +0 -1
  342. package/dist/client/assets/stateDiagram-D77RDMKH-CiLpGRuf.js.br +0 -0
  343. package/dist/client/assets/stateDiagram-D77RDMKH-CiLpGRuf.js.gz +0 -0
  344. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BRbS0rv2.js +0 -1
  345. package/dist/client/assets/swimlanes-42K2YHIH-BkzVtkVY.js.br +0 -0
  346. package/dist/client/assets/swimlanes-42K2YHIH-BkzVtkVY.js.gz +0 -0
  347. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CeWFezkt.js +0 -8
  348. package/dist/client/assets/timeline-definition-24CTP7MA-DKyfGITP.js.br +0 -0
  349. package/dist/client/assets/timeline-definition-24CTP7MA-DKyfGITP.js.gz +0 -0
  350. package/dist/client/assets/vennDiagram-4TSXK5OY-CRgkkrgx.js.br +0 -0
  351. package/dist/client/assets/vennDiagram-4TSXK5OY-CRgkkrgx.js.gz +0 -0
  352. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cr7yS4CZ.js.br +0 -0
  353. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cr7yS4CZ.js.gz +0 -0
  354. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DkPYl6E0.js.br +0 -0
  355. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DkPYl6E0.js.gz +0 -0
@@ -1,11 +1,13 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import crypto from 'node:crypto';
4
+ import { COLLAB_LIMITS, CollaborationError, STATUS_RANK, validateExtras } from './collaborationProtocol.js';
4
5
  const MAX_MESSAGES = 2_000;
5
6
  const MESSAGE_KINDS = new Set(['message', 'ask', 'reply', 'task', 'handoff', 'done']);
6
7
  export class CollaborationStore {
7
8
  filePath;
8
9
  document = { version: 2, groups: [], messages: [] };
10
+ persistedDocument = JSON.stringify(this.document);
9
11
  constructor(filePath) {
10
12
  this.filePath = filePath;
11
13
  try {
@@ -14,10 +16,21 @@ export class CollaborationStore {
14
16
  this.document.groups = parsed.groups.filter((group) => group && typeof group.id === 'string' && Array.isArray(group.sessionIds));
15
17
  }
16
18
  if (Array.isArray(parsed.messages)) {
17
- this.document.messages = parsed.messages.filter((message) => message && typeof message.id === 'string' && typeof message.toSessionId === 'string' && MESSAGE_KINDS.has(message.kind)).slice(-MAX_MESSAGES);
19
+ this.document.messages = parsed.messages.filter((message) => message && typeof message.id === 'string' && typeof message.toSessionId === 'string' && MESSAGE_KINDS.has(message.kind));
20
+ for (const message of this.document.messages) {
21
+ message.sequence ??= (this.document.sequence ?? 0) + 1;
22
+ this.document.sequence = Math.max(this.document.sequence ?? 0, message.sequence);
23
+ }
18
24
  }
25
+ this.document.sequence = Math.max(parsed.sequence ?? 0, this.document.sequence ?? 0);
26
+ this.document.idempotency = parsed.idempotency ?? {};
27
+ this.document.consumers = parsed.consumers ?? {};
28
+ this.document.transport = parsed.transport ?? {};
29
+ this.document.fragments = parsed.fragments ?? {};
30
+ this.document.prunedThrough = parsed.prunedThrough ?? {};
19
31
  }
20
32
  catch { /* first run */ }
33
+ this.persistedDocument = JSON.stringify(this.document);
21
34
  }
22
35
  list() {
23
36
  return this.document.groups.filter((group) => !group.deleted).sort((a, b) => b.updatedAt - a.updatedAt);
@@ -92,21 +105,41 @@ export class CollaborationStore {
92
105
  throw new Error('协作组不存在');
93
106
  if (!MESSAGE_KINDS.has(input.kind))
94
107
  throw new Error('消息类型无效');
95
- const content = input.content.trim();
96
- if (!content)
108
+ const extras = validateExtras(input);
109
+ const content = input.content;
110
+ if (Buffer.byteLength(content) > COLLAB_LIMITS.message_bytes)
111
+ throw new CollaborationError('MESSAGE_TOO_LARGE', `Message exceeds ${COLLAB_LIMITS.message_bytes} UTF-8 bytes`, 413);
112
+ if (!content.trim())
97
113
  throw new Error('消息不能为空');
98
114
  const recipients = Array.from(new Set(input.toSessionIds)).filter((id) => group.sessionIds.includes(id) && id !== input.fromSessionId);
99
115
  if (recipients.length === 0)
100
116
  throw new Error('没有有效的接收会话');
101
117
  const now = Date.now();
118
+ const key = input.idempotencyKey ? JSON.stringify([input.groupId, input.fromSessionId, input.idempotencyKey]) : null;
119
+ const hash = crypto.createHash('sha256').update(stableJson([recipients.slice().sort(), input.kind, content, input.threadId ?? null, input.replyTo ?? null, extras.responseKind, extras.metadata, extras.task, extras.expiresAt])).digest('hex');
120
+ const previous = key ? this.document.idempotency?.[key] : null;
121
+ if (previous && previous.expiresAt > now) {
122
+ if (previous.hash !== hash)
123
+ throw new CollaborationError('IDEMPOTENCY_CONFLICT', 'This key was already used with a different payload', 409);
124
+ const originals = previous.ids.map((id) => this.getMessage(id));
125
+ if (originals.some((message) => !message))
126
+ throw new CollaborationError('IDEMPOTENCY_RECORD_GONE', 'Original message removed; this key cannot be reused yet', 409);
127
+ return originals;
128
+ }
102
129
  const threadId = input.threadId?.trim() || crypto.randomUUID();
103
130
  const messages = recipients.map((toSessionId) => ({
131
+ idempotencyKey: input.idempotencyKey, responseKind: extras.responseKind, metadata: extras.metadata, task: extras.task, expiresAt: extras.expiresAt,
132
+ sequence: this.nextSequence(),
104
133
  id: crypto.randomUUID(), groupId: group.id, fromSessionId: input.fromSessionId,
105
134
  toSessionId, kind: input.kind, content, threadId,
106
135
  replyTo: input.replyTo?.trim() || null, status: 'pending', createdAt: now,
107
136
  deliveredAt: null, readAt: null,
108
137
  }));
109
- this.document.messages = [...this.document.messages, ...messages].slice(-MAX_MESSAGES);
138
+ if (messages.some((message) => Buffer.byteLength(JSON.stringify(message)) > COLLAB_LIMITS.wire_bytes))
139
+ throw new CollaborationError('MESSAGE_WIRE_TOO_LARGE', `Encoded message exceeds ${COLLAB_LIMITS.wire_bytes} JSON bytes`, 413);
140
+ this.document.messages.push(...messages);
141
+ if (key)
142
+ (this.document.idempotency ??= {})[key] = { hash, ids: messages.map((message) => message.id), expiresAt: now + COLLAB_LIMITS.idempotency_retention_ms };
110
143
  this.persist();
111
144
  return messages;
112
145
  }
@@ -114,12 +147,18 @@ export class CollaborationStore {
114
147
  return this.document.messages.filter((message) => message.groupId === groupId).slice(-Math.max(1, Math.min(limit, 500)));
115
148
  }
116
149
  getMessage(id) {
150
+ this.expire();
117
151
  return this.document.messages.find((message) => message.id === id) ?? null;
118
152
  }
119
153
  inbox(sessionId, options = {}) {
120
- return this.document.messages
121
- .filter((message) => message.toSessionId === sessionId && (!options.pendingOnly || message.status === 'pending'))
122
- .slice(-Math.max(1, Math.min(options.limit ?? 50, 200)));
154
+ this.expire();
155
+ const matches = this.document.messages.filter((message) => message.toSessionId === sessionId && (!options.pendingOnly || message.status === 'pending'));
156
+ const limit = Math.max(1, Math.min(options.limit ?? 50, 200));
157
+ return options.pendingOnly ? matches.slice(0, limit) : matches.slice(-limit);
158
+ }
159
+ pendingCount(sessionId) {
160
+ this.expire();
161
+ return this.document.messages.filter((message) => message.toSessionId === sessionId && message.status === 'pending').length;
123
162
  }
124
163
  markDelivered(messageIds) {
125
164
  return this.updateStatus(messageIds, 'delivered');
@@ -129,14 +168,15 @@ export class CollaborationStore {
129
168
  }
130
169
  updateStatus(messageIds, status) {
131
170
  const ids = new Set(messageIds);
171
+ this.expire();
132
172
  const now = Date.now();
133
173
  const changed = [];
134
174
  this.document.messages = this.document.messages.map((message) => {
135
- if (!ids.has(message.id))
175
+ if (!ids.has(message.id) || message.status === 'expired' || message.status === 'failed' || message.status === 'read')
136
176
  return message;
137
177
  const updated = status === 'read'
138
- ? { ...message, status, deliveredAt: message.deliveredAt ?? now, readAt: now }
139
- : message.status === 'pending' ? { ...message, status, deliveredAt: now } : message;
178
+ ? { ...message, status, deliveredAt: message.deliveredAt ?? now, readAt: now, readSource: 'explicit', deliverySource: message.deliverySource ?? (message.deliveredAt === null ? 'consumer_read' : 'legacy_or_unspecified') }
179
+ : message.status === 'pending' ? { ...message, status, deliveredAt: now, deliverySource: 'pty_written' } : message;
140
180
  changed.push(updated);
141
181
  return updated;
142
182
  });
@@ -145,9 +185,11 @@ export class CollaborationStore {
145
185
  return changed;
146
186
  }
147
187
  federationSnapshot() {
188
+ this.expire();
148
189
  const groups = this.document.groups.filter((group) => group.federated);
149
190
  const ids = new Set(groups.filter((group) => !group.deleted).map((group) => group.id));
150
- return { groups, messages: this.document.messages.filter((message) => ids.has(message.groupId)) };
191
+ const messages = this.document.messages.filter((message) => ids.has(message.groupId));
192
+ return { groups, messages, transportDiagnostics: Object.fromEntries(messages.flatMap((message) => this.document.transport?.[message.id] ? [[message.id, this.document.transport[message.id]]] : [])) };
151
193
  }
152
194
  mergeFederatedGroup(group) {
153
195
  if (!group.federated || typeof group.id !== 'string' || !group.id.startsWith('cross-') || typeof group.name !== 'string' || !group.name.trim()
@@ -169,7 +211,7 @@ export class CollaborationStore {
169
211
  }
170
212
  mergeFederatedMessages(messages) {
171
213
  let changed = false;
172
- const rank = { pending: 0, delivered: 1, read: 2 };
214
+ const rank = STATUS_RANK;
173
215
  for (const message of messages) {
174
216
  if (!message || typeof message !== 'object')
175
217
  continue;
@@ -178,31 +220,217 @@ export class CollaborationStore {
178
220
  || (message.fromSessionId !== null && !group.sessionIds.includes(message.fromSessionId))
179
221
  || typeof message.id !== 'string' || typeof message.threadId !== 'string' || !Number.isFinite(message.createdAt)
180
222
  || typeof message.content !== 'string'
181
- || message.content.length > 20_000 || !MESSAGE_KINDS.has(message.kind)
182
- || !Object.hasOwn(rank, message.status))
223
+ || Buffer.byteLength(message.content) > COLLAB_LIMITS.message_bytes || !MESSAGE_KINDS.has(message.kind)
224
+ || !Object.hasOwn(rank, message.status) || Buffer.byteLength(JSON.stringify(message)) > COLLAB_LIMITS.wire_bytes)
183
225
  continue;
226
+ try {
227
+ validateExtras(message);
228
+ }
229
+ catch {
230
+ continue;
231
+ }
184
232
  const index = this.document.messages.findIndex((item) => item.id === message.id);
185
233
  if (index < 0) {
186
- this.document.messages.push(message);
234
+ this.document.messages.push({ ...message, sequence: this.nextSequence() });
187
235
  changed = true;
188
236
  }
189
237
  else if (rank[message.status] > rank[this.document.messages[index].status]) {
190
238
  const existing = this.document.messages[index];
191
239
  // A receipt may advance status, but never rewrite an existing message.
192
240
  this.document.messages[index] = { ...existing, status: message.status,
193
- deliveredAt: message.deliveredAt, readAt: message.readAt };
241
+ deliveredAt: message.deliveredAt, readAt: message.readAt, failureReason: message.failureReason, readSource: message.readSource, deliverySource: message.deliverySource };
194
242
  changed = true;
195
243
  }
196
244
  }
197
- if (changed) {
198
- this.document.messages = this.document.messages.slice(-MAX_MESSAGES);
245
+ if (changed)
246
+ this.persist();
247
+ }
248
+ nextSequence() { return this.document.sequence = (this.document.sequence ?? 0) + 1; }
249
+ expire() {
250
+ let changed = false;
251
+ for (const message of this.document.messages)
252
+ if (message.status === 'pending' && message.expiresAt && message.expiresAt <= Date.now()) {
253
+ message.status = 'expired';
254
+ message.failureReason = 'MESSAGE_EXPIRED';
255
+ changed = true;
256
+ }
257
+ if (changed)
258
+ this.persist();
259
+ }
260
+ fail(id, reason) {
261
+ const message = this.getMessage(id);
262
+ if (message?.status === 'pending') {
263
+ message.status = 'failed';
264
+ message.failureReason = reason;
265
+ this.persist();
266
+ }
267
+ }
268
+ diagnostic(id) { return this.document.transport?.[id] ?? null; }
269
+ recordTransport(id, diagnostic) {
270
+ if (!this.getMessage(id))
271
+ return;
272
+ (this.document.transport ??= {})[id] = diagnostic;
273
+ this.persist();
274
+ }
275
+ receipt(id) {
276
+ const message = this.getMessage(id);
277
+ if (!message)
278
+ throw new CollaborationError('MESSAGE_NOT_FOUND', 'Message not found or no longer retained', 404);
279
+ const replies = this.document.messages.filter((reply) => reply.replyTo === id && reply.fromSessionId === message.toSessionId && reply.toSessionId === message.fromSessionId);
280
+ const diagnostic = this.diagnostic(id);
281
+ return { message_id: id, thread_id: message.threadId, status: message.status, queued_at: message.createdAt,
282
+ delivered_at: message.deliveredAt, read_at: message.readAt, expires_at: message.expiresAt ?? null,
283
+ failure_reason: message.failureReason ?? null, delivery_semantics: message.deliverySource ?? (message.deliveredAt === null ? 'not_delivered' : 'legacy_or_unspecified'), read_semantics: message.readAt ? message.readSource ?? 'legacy_or_unspecified' : 'not_read',
284
+ idempotency_key: message.idempotencyKey ?? null,
285
+ ack_at: replies.find((reply) => reply.responseKind === 'ack')?.createdAt ?? null,
286
+ reply_ids: replies.map((reply) => reply.id), result_ids: replies.filter((reply) => reply.responseKind === 'result').map((reply) => reply.id),
287
+ response_kind: message.responseKind ?? null, task: message.task ?? null,
288
+ size_bytes: Buffer.byteLength(message.content),
289
+ relay_online: diagnostic && Date.now() - diagnostic.checked_at < 15_000 ? diagnostic.relay_online : null,
290
+ peer_reachable: diagnostic && Date.now() - diagnostic.checked_at < 15_000 ? diagnostic.peer_reachable : null,
291
+ attempt_count: diagnostic?.attempt_count ?? 0, next_retry_at: diagnostic?.next_retry_at ?? null,
292
+ last_error: diagnostic?.last_error ?? null, transport_checked_at: diagnostic?.checked_at ?? null,
293
+ fragments_sent: diagnostic?.fragments_sent ?? 0, fragments_total: diagnostic?.fragments_total ?? 0 };
294
+ }
295
+ sessionFacts(sessionId, online, turnState) {
296
+ const reports = this.document.messages.filter((message) => message.fromSessionId === sessionId);
297
+ const tasks = new Map();
298
+ for (const message of reports)
299
+ if (message.task)
300
+ tasks.set(message.task.task_id, { task_id: message.task.task_id, status: message.task.status, reported_at: message.createdAt, message_id: message.id });
301
+ return { session_state: online ? 'online' : 'offline', turn_state: turnState === 'done' ? 'ended' : turnState ?? 'unknown',
302
+ task_state: 'unknown', tasks: [...tasks.values()], last_heartbeat: null, last_tool_activity_at: null,
303
+ last_message_at: reports.length ? Math.max(...reports.map((message) => message.createdAt)) : null,
304
+ state_source: 'optional_adapter', task_source: 'explicit_message' };
305
+ }
306
+ page(sessionId, options = {}) {
307
+ this.expire();
308
+ if (options.responseKind && !['ack', 'progress', 'result'].includes(options.responseKind))
309
+ throw new CollaborationError('INVALID_RESPONSE_KIND', 'response_kind must be ack, progress or result');
310
+ if (options.kind && !MESSAGE_KINDS.has(options.kind))
311
+ throw new CollaborationError('INVALID_KIND', 'Unknown message kind');
312
+ const limit = options.limit ?? COLLAB_LIMITS.default_page_size;
313
+ if (!Number.isInteger(limit) || limit < 1 || limit > COLLAB_LIMITS.max_page_size)
314
+ throw new CollaborationError('INVALID_LIMIT', 'limit must be 1–200');
315
+ if (options.consumer !== undefined && (!options.consumer || options.consumer.length > 128))
316
+ throw new CollaborationError('INVALID_CONSUMER', 'consumer must contain 1–128 characters');
317
+ if (options.cursor && options.afterId)
318
+ throw new CollaborationError('INVALID_CURSOR', 'Choose cursor or after-id');
319
+ const scope = JSON.stringify([sessionId, options.group ?? null, options.thread ?? null, options.from ?? null, options.kind ?? null, options.responseKind ?? null, options.unread ?? false, options.since ?? null]);
320
+ const consumerKey = JSON.stringify([scope, options.consumer]);
321
+ let after = options.consumer ? this.document.consumers?.[consumerKey] ?? 0 : 0;
322
+ if (options.cursor) {
323
+ try {
324
+ const decoded = JSON.parse(Buffer.from(options.cursor, 'base64url').toString());
325
+ if (decoded.scope !== scope || !Number.isSafeInteger(decoded.sequence) || decoded.sequence < 0)
326
+ throw new Error();
327
+ after = decoded.sequence;
328
+ }
329
+ catch {
330
+ throw new CollaborationError('INVALID_CURSOR', 'Cursor is invalid or belongs to different filters');
331
+ }
332
+ }
333
+ if (options.afterId) {
334
+ const anchor = this.getMessage(options.afterId);
335
+ if (!anchor || anchor.toSessionId !== sessionId)
336
+ throw new CollaborationError('CURSOR_GONE', 'after-id is unknown or no longer retained; resync explicitly', 410);
337
+ after = anchor.sequence ?? 0;
338
+ }
339
+ const incremental = Boolean(options.cursor || options.afterId || options.consumer || options.since !== undefined);
340
+ const candidates = this.document.messages.filter((message) => message.toSessionId === sessionId && (message.sequence ?? 0) > after
341
+ && (!options.unread || message.status !== 'read') && (options.since === undefined || message.createdAt > options.since)
342
+ && (!options.from || message.fromSessionId === options.from) && (!options.group || message.groupId === options.group)
343
+ && (!options.thread || message.threadId === options.thread) && (!options.kind || message.kind === options.kind)
344
+ && (!options.responseKind || message.responseKind === options.responseKind))
345
+ .sort((a, b) => (a.sequence ?? 0) - (b.sequence ?? 0));
346
+ const messages = incremental || options.order === 'oldest' ? candidates.slice(0, limit) : candidates.slice().sort((a, b) => Number(a.status === 'read') - Number(b.status === 'read') || (b.sequence ?? 0) - (a.sequence ?? 0)).slice(0, limit);
347
+ const sequence = messages.length ? Math.max(...messages.map((message) => message.sequence ?? 0)) : after;
348
+ const next_cursor = Buffer.from(JSON.stringify({ scope, sequence })).toString('base64url');
349
+ return { messages, next_cursor, retention_gap: incremental && after < (this.document.prunedThrough?.[sessionId] ?? 0), has_more: incremental ? candidates.length > messages.length : false, consumer: options.consumer ?? null };
350
+ }
351
+ commitCursor(sessionId, cursor, consumer) {
352
+ if (!consumer || consumer.length > 128)
353
+ throw new CollaborationError('INVALID_CONSUMER', 'consumer must contain 1–128 characters');
354
+ try {
355
+ const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString());
356
+ if (JSON.parse(decoded.scope)[0] !== sessionId || !Number.isSafeInteger(decoded.sequence) || decoded.sequence < 0 || decoded.sequence > (this.document.sequence ?? 0))
357
+ throw new Error();
358
+ const key = JSON.stringify([decoded.scope, consumer]);
359
+ (this.document.consumers ??= {})[key] = Math.max(this.document.consumers?.[key] ?? 0, decoded.sequence);
199
360
  this.persist();
200
361
  }
362
+ catch {
363
+ throw new CollaborationError('INVALID_CURSOR', 'Invalid consumption cursor');
364
+ }
365
+ }
366
+ acceptFragment(fragment) {
367
+ const group = this.getGroup(fragment.group_id);
368
+ if (!group?.federated || group.deleted || typeof fragment.message_id !== 'string' || !/^[a-zA-Z0-9_-]{1,128}$/.test(fragment.message_id) || ['__proto__', 'constructor', 'prototype'].includes(fragment.message_id) || !Number.isInteger(fragment.total) || fragment.total < 1 || fragment.total > 64
369
+ || !Number.isInteger(fragment.index) || fragment.index < 0 || fragment.index >= fragment.total || !/^[a-f0-9]{64}$/.test(fragment.sha256)
370
+ || typeof fragment.data !== 'string' || !/^[A-Za-z0-9+/]*={0,2}$/.test(fragment.data) || Buffer.from(fragment.data, 'base64').length > COLLAB_LIMITS.fragment_bytes)
371
+ throw new CollaborationError('INVALID_FRAGMENT', 'Invalid message fragment');
372
+ const existing = this.getMessage(fragment.message_id);
373
+ if (existing) {
374
+ if (existing.groupId !== fragment.group_id)
375
+ throw new CollaborationError('FRAGMENT_CONFLICT', 'Message belongs to another group', 409);
376
+ return { message_id: existing.id, received: fragment.total, total: fragment.total, complete: true };
377
+ }
378
+ const fragments = this.document.fragments ??= {};
379
+ for (const [id, state] of Object.entries(fragments))
380
+ if (state.createdAt + COLLAB_LIMITS.fragment_ttl_ms < Date.now())
381
+ delete fragments[id];
382
+ if (!fragments[fragment.message_id] && Object.keys(fragments).length >= 32)
383
+ throw new CollaborationError('FRAGMENT_CAPACITY', 'Too many incomplete messages', 429);
384
+ const state = fragments[fragment.message_id] ??= { groupId: fragment.group_id, total: fragment.total, sha256: fragment.sha256, chunks: {}, createdAt: Date.now() };
385
+ if (state.groupId !== fragment.group_id || state.total !== fragment.total || state.sha256 !== fragment.sha256 || (state.chunks[fragment.index] && state.chunks[fragment.index] !== fragment.data))
386
+ throw new CollaborationError('FRAGMENT_CONFLICT', 'Conflicting fragment', 409);
387
+ state.chunks[fragment.index] = fragment.data;
388
+ const received = Object.keys(state.chunks).length;
389
+ if (received === state.total) {
390
+ const bytes = Buffer.concat(Array.from({ length: state.total }, (_, index) => Buffer.from(state.chunks[index], 'base64')));
391
+ if (crypto.createHash('sha256').update(bytes).digest('hex') !== state.sha256)
392
+ throw new CollaborationError('FRAGMENT_CHECKSUM', 'Message checksum mismatch');
393
+ const message = JSON.parse(bytes.toString());
394
+ if (message.id !== fragment.message_id || message.groupId !== fragment.group_id)
395
+ throw new CollaborationError('FRAGMENT_IDENTITY', 'Message identity mismatch');
396
+ this.mergeFederatedMessages([message]);
397
+ if (!this.getMessage(message.id))
398
+ throw new CollaborationError('INVALID_MESSAGE', 'Reassembled message rejected');
399
+ delete fragments[fragment.message_id];
400
+ }
401
+ this.persist();
402
+ return { message_id: fragment.message_id, received, total: state.total, complete: received === state.total };
201
403
  }
202
404
  persist() {
203
- fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
405
+ const now = Date.now();
406
+ for (const [key, record] of Object.entries(this.document.idempotency ?? {}))
407
+ if (record.expiresAt <= now)
408
+ delete this.document.idempotency[key];
409
+ const protectedIds = new Set(Object.values(this.document.idempotency ?? {}).flatMap((record) => record.ids));
410
+ const history = this.document.messages.filter((message) => message.status !== 'pending' && !protectedIds.has(message.id)).slice(-MAX_MESSAGES);
411
+ const retained = new Set(history.map((message) => message.id));
412
+ this.document.messages = this.document.messages.filter((message) => {
413
+ if (message.status === 'pending' || protectedIds.has(message.id) || retained.has(message.id))
414
+ return true;
415
+ (this.document.prunedThrough ??= {})[message.toSessionId] = Math.max(this.document.prunedThrough?.[message.toSessionId] ?? 0, message.sequence ?? 0);
416
+ delete this.document.transport?.[message.id];
417
+ return false;
418
+ });
204
419
  const temporaryPath = `${this.filePath}.${process.pid}.tmp`;
205
- fs.writeFileSync(temporaryPath, JSON.stringify(this.document, null, 2), { mode: 0o600 });
206
- fs.renameSync(temporaryPath, this.filePath);
420
+ try {
421
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
422
+ const serialized = JSON.stringify(this.document);
423
+ fs.writeFileSync(temporaryPath, serialized, { mode: 0o600 });
424
+ fs.renameSync(temporaryPath, this.filePath);
425
+ this.persistedDocument = serialized;
426
+ }
427
+ catch (error) {
428
+ this.document = JSON.parse(this.persistedDocument);
429
+ throw error;
430
+ }
207
431
  }
208
432
  }
433
+ function stableJson(value) {
434
+ return JSON.stringify(value, (_key, entry) => entry && typeof entry === 'object' && !Array.isArray(entry)
435
+ ? Object.fromEntries(Object.entries(entry).sort(([a], [b]) => a.localeCompare(b))) : entry);
436
+ }
@@ -10,6 +10,12 @@ import { resolve } from 'path';
10
10
  // dotenv 不会覆盖已存在的环境变量,因此系统环境变量和命令行参数优先级最高
11
11
  loadDotenv({ path: resolve(os.homedir(), '.termdock', '.env'), quiet: true });
12
12
  loadDotenv({ path: resolve(process.cwd(), '.env'), quiet: true });
13
+ // Federation commands run before normal server/terminal CLI initialization.
14
+ if (process.argv.slice(2).some(arg => arg === '--federation-pairing' || arg === '--federation-relay')) {
15
+ const { runFederationCli } = await import('./federation/cli.js');
16
+ process.exit(await runFederationCli(process.argv.slice(2)));
17
+ }
18
+ import { parseCollaborationCommand, executeCollaborationCommand, COLLAB_HELP } from './agent/collaborationCli.js';
13
19
  import fs from 'fs';
14
20
  import http from 'http';
15
21
  import https from 'https';
@@ -183,7 +189,7 @@ Short commands:
183
189
  Emit one lifecycle/status event from an Agent hook.
184
190
  pi Same as agent-plugin
185
191
  collab status Show this Session's collaboration groups and peers
186
- collab inbox Read and acknowledge collaboration messages
192
+ collab inbox Read messages without acknowledging (see collab --help)
187
193
  collab send <session-id> <message>
188
194
  Send a durable message to a related Session
189
195
  collab handoff <session-id> <summary>
@@ -678,53 +684,11 @@ function parseArgs(argv) {
678
684
  }
679
685
  }
680
686
  else if (command === 'collab') {
681
- const action = (next || 'status');
682
- const json = argv.includes('--json');
683
- if (action === 'status' || action === 'inbox') {
684
- collab = { action, json };
685
- }
686
- else if (action === 'send' || action === 'handoff' || action === 'reply') {
687
- const target = argv[2];
688
- const message = argv.slice(3).filter((part) => part !== '--json').join(' ').trim();
689
- if (!target || !message) {
690
- console.error(`${ICON.err} ${c.red(`Usage: td collab ${action} <${action === 'reply' ? 'message-id' : 'session-id'}> <message>`)}`);
691
- process.exit(1);
692
- }
693
- collab = { action, target, message, json };
694
- }
695
- else if (action === 'add' || action === 'remove') {
696
- const groupId = argv[2];
697
- const sessionId = argv[3];
698
- if (!groupId || !sessionId || groupId.startsWith('-') || sessionId.startsWith('-')) {
699
- console.error(`${ICON.err} ${c.red(`Usage: td collab ${action} <group-id> <session-id>`)}`);
700
- process.exit(1);
701
- }
702
- collab = { action, groupId, sessionId, json };
703
- }
704
- else if (action === 'spawn') {
705
- const groupId = argv[2];
706
- const agentSlug = argv[3];
707
- const optionValue = (flag) => {
708
- const index = argv.indexOf(flag);
709
- const value = index >= 0 ? argv[index + 1] : undefined;
710
- return value && !value.startsWith('--') ? value : undefined;
711
- };
712
- if (!groupId || !agentSlug || groupId.startsWith('-') || agentSlug.startsWith('-')) {
713
- console.error(`${ICON.err} ${c.red('Usage: td collab spawn <group-id> <agent-slug> [--name <name>] [--cwd <path>] [--task <text>]')}`);
714
- process.exit(1);
715
- }
716
- collab = {
717
- action,
718
- groupId,
719
- agentSlug,
720
- name: optionValue('--name'),
721
- cwd: optionValue('--cwd'),
722
- task: optionValue('--task'),
723
- json,
724
- };
687
+ try {
688
+ collab = parseCollaborationCommand(argv.slice(1));
725
689
  }
726
- else {
727
- console.error(`${ICON.err} ${c.red('Usage: td collab <status|inbox|send|handoff|reply|add|remove|spawn>')}`);
690
+ catch (error) {
691
+ console.error(JSON.stringify({ ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) }));
728
692
  process.exit(1);
729
693
  }
730
694
  argv = [];
@@ -1219,7 +1183,7 @@ async function readStdinText() {
1219
1183
  process.stdin.on('error', reject);
1220
1184
  });
1221
1185
  }
1222
- async function postLocalJson(baseUrl, token, endpoint, payload) {
1186
+ async function postLocalJson(baseUrl, token, endpoint, payload, timeoutMs = 30_000) {
1223
1187
  const url = new URL(endpoint, baseUrl);
1224
1188
  const body = JSON.stringify(payload);
1225
1189
  const isHttps = url.protocol === 'https:';
@@ -1244,12 +1208,14 @@ async function postLocalJson(baseUrl, token, endpoint, payload) {
1244
1208
  res.on('data', (chunk) => { responseBody += chunk; });
1245
1209
  res.on('end', () => resolve({ statusCode: res.statusCode ?? 0, body: responseBody }));
1246
1210
  });
1211
+ const timer = setTimeout(() => req.destroy(new Error('Local collaboration request timed out')), timeoutMs);
1212
+ req.on('close', () => clearTimeout(timer));
1247
1213
  req.on('error', reject);
1248
1214
  req.write(body);
1249
1215
  req.end();
1250
1216
  });
1251
1217
  }
1252
- async function getLocalJson(baseUrl, token, endpoint) {
1218
+ async function getLocalJson(baseUrl, token, endpoint, timeoutMs = 30_000) {
1253
1219
  const url = new URL(endpoint, baseUrl);
1254
1220
  const isHttps = url.protocol === 'https:';
1255
1221
  const requestImpl = isHttps ? https.request : http.request;
@@ -1271,14 +1237,20 @@ async function getLocalJson(baseUrl, token, endpoint) {
1271
1237
  res.on('data', (chunk) => { responseBody += chunk; });
1272
1238
  res.on('end', () => resolve({ statusCode: res.statusCode ?? 0, body: responseBody }));
1273
1239
  });
1240
+ const timer = setTimeout(() => req.destroy(new Error('Local collaboration request timed out')), timeoutMs);
1241
+ req.on('close', () => clearTimeout(timer));
1274
1242
  req.on('error', reject);
1275
1243
  req.end();
1276
1244
  });
1277
1245
  }
1278
1246
  async function runCollab(command) {
1247
+ if (command.action === 'help') {
1248
+ console.log(COLLAB_HELP);
1249
+ return;
1250
+ }
1279
1251
  const runningState = getRunningState();
1280
1252
  if (!runningState?.localApiToken) {
1281
- console.error(`${ICON.err} ${c.red('Termdock is not running or its local API token is unavailable.')}`);
1253
+ console.error(JSON.stringify({ ok: false, code: 'SERVICE_UNAVAILABLE', error: 'Termdock is not running or its local API token is unavailable.' }));
1282
1254
  process.exit(1);
1283
1255
  }
1284
1256
  const backendSessionId = process.env.TERMDOCK_BACKEND_SESSION_ID?.trim() || null;
@@ -1299,128 +1271,16 @@ async function runCollab(command) {
1299
1271
  }
1300
1272
  }
1301
1273
  if (!backendSessionId && !tmuxSessionName) {
1302
- console.error(`${ICON.err} ${c.red('td collab must run inside a Termdock-managed Session.')}`);
1274
+ console.error(JSON.stringify({ ok: false, code: 'SESSION_NOT_FOUND', error: 'td collab must run inside a Termdock-managed Session.' }));
1303
1275
  process.exit(1);
1304
1276
  }
1305
1277
  const context = backendSessionId ? { backendSessionId } : { tmuxSessionName };
1306
- const contextQuery = new URLSearchParams(Object.entries(context).filter((entry) => typeof entry[1] === 'string')).toString();
1307
1278
  const baseUrl = runningState.localUrl
1308
1279
  ?? `${runningState.scheme ?? 'http'}://${runningState.host === '0.0.0.0' ? 'localhost' : runningState.host}:${runningState.port}`;
1309
- let response;
1310
- if (command.action === 'status') {
1311
- response = await getLocalJson(baseUrl, runningState.localApiToken, `/api/terminal/operations/orchestration/peers?${contextQuery}`);
1312
- }
1313
- else if (command.action === 'inbox') {
1314
- response = await getLocalJson(baseUrl, runningState.localApiToken, `/api/terminal/operations/orchestration/inbox?${contextQuery}&markRead=true`);
1315
- }
1316
- else if (command.action === 'reply') {
1317
- response = await postLocalJson(baseUrl, runningState.localApiToken, '/api/terminal/operations/orchestration/reply', {
1318
- ...context,
1319
- messageId: command.target,
1320
- content: command.message,
1321
- });
1322
- }
1323
- else if (command.action === 'add' || command.action === 'remove') {
1324
- response = await postLocalJson(baseUrl, runningState.localApiToken, '/api/terminal/operations/orchestration/members', {
1325
- ...context,
1326
- groupId: command.groupId,
1327
- targetSessionId: command.sessionId,
1328
- action: command.action,
1329
- });
1330
- }
1331
- else if (command.action === 'spawn') {
1332
- response = await postLocalJson(baseUrl, runningState.localApiToken, '/api/terminal/operations/orchestration/spawn', {
1333
- ...context,
1334
- groupId: command.groupId,
1335
- agentSlug: command.agentSlug,
1336
- name: command.name,
1337
- cwd: command.cwd,
1338
- task: command.task,
1339
- });
1340
- }
1341
- else {
1342
- response = await postLocalJson(baseUrl, runningState.localApiToken, '/api/terminal/operations/orchestration/send', {
1343
- ...context,
1344
- targetSessionId: command.target,
1345
- message: command.message,
1346
- kind: command.action === 'handoff' ? 'handoff' : 'message',
1347
- });
1348
- }
1349
- let body = {};
1350
- try {
1351
- body = JSON.parse(response.body || '{}');
1352
- }
1353
- catch { /* handled below */ }
1354
- if (response.statusCode < 200 || response.statusCode >= 300) {
1355
- console.error(`${ICON.err} ${c.red(typeof body.error === 'string' ? body.error : 'Collaboration request failed')}`);
1356
- process.exit(1);
1357
- }
1358
- if (command.json) {
1359
- console.log(JSON.stringify(body, null, 2));
1360
- return;
1361
- }
1362
- if (command.action === 'status') {
1363
- const groups = Array.isArray(body.groups) ? body.groups : [];
1364
- const peers = Array.isArray(body.peers) ? body.peers : [];
1365
- const sessions = Array.isArray(body.sessions) ? body.sessions : [];
1366
- const agents = Array.isArray(body.agents) ? body.agents : [];
1367
- console.log(`${ICON.ok} ${c.green(`Collaboration: ${groups.length} group(s), ${peers.length} peer(s)`)}`);
1368
- for (const group of groups) {
1369
- console.log(` ${c.green(group.name ?? 'Group')} ${c.dim(`[${group.id ?? ''}]`)}`);
1370
- if (group.sessionIds?.length)
1371
- console.log(` ${c.dim(`${group.sessionIds.length} member(s)`)}`);
1372
- }
1373
- if (peers.length > 0)
1374
- console.log(`\n${c.dim('Current group peers:')}`);
1375
- for (const peer of peers) {
1376
- console.log(` ${c.cyan(peer.name ?? peer.sessionId ?? 'Session')} ${c.dim(`[${peer.sessionId ?? ''}]`)}`);
1377
- console.log(` ${peer.status ?? 'offline'} · ${peer.capability ?? ''}`);
1378
- if (peer.currentTask)
1379
- console.log(` ${c.dim(peer.currentTask)}`);
1380
- }
1381
- for (const group of groups) {
1382
- const memberIds = new Set(group.sessionIds ?? []);
1383
- const candidates = sessions.filter((session) => session.sessionId && !memberIds.has(session.sessionId));
1384
- if (candidates.length > 0) {
1385
- console.log(`\n${c.dim(`Sessions available to add to ${group.name ?? 'group'} [${group.id ?? ''}]:`)}`);
1386
- for (const session of candidates) {
1387
- console.log(` ${session.name ?? 'Session'} ${c.dim(`[${session.sessionId ?? ''}]`)} · ${session.status ?? 'offline'}`);
1388
- if (session.cwd)
1389
- console.log(` ${c.dim(session.cwd)}`);
1390
- }
1391
- }
1392
- }
1393
- if (agents.length > 0) {
1394
- console.log(`\n${c.dim('Agent types available to spawn:')}`);
1395
- for (const agent of agents)
1396
- console.log(` ${agent.displayName ?? agent.slug ?? 'Agent'} ${c.dim(`[${agent.slug ?? ''}]`)}`);
1397
- }
1398
- return;
1399
- }
1400
- if (command.action === 'inbox') {
1401
- const messages = Array.isArray(body.messages) ? body.messages : [];
1402
- if (messages.length === 0) {
1403
- console.log(`${ICON.ok} ${c.dim('Collaboration inbox is empty.')}`);
1404
- return;
1405
- }
1406
- for (const message of messages) {
1407
- console.log(`${c.cyan(message.kind ?? 'message')} ${c.dim(`#${message.id ?? ''}`)} from ${message.fromSessionId ?? 'user'}`);
1408
- console.log(` ${message.content ?? ''}`);
1409
- }
1410
- return;
1411
- }
1412
- const delivery = body.delivery;
1413
- if (delivery?.reason) {
1414
- console.log(`${delivery.serviceUnavailable ? '服务不可达' : '等待投递'}: ${delivery.reason}`);
1415
- return;
1416
- }
1417
- const success = command.action === 'reply' ? 'Reply sent.'
1418
- : command.action === 'handoff' ? 'Handoff sent.'
1419
- : command.action === 'add' ? 'Session added to collaboration group.'
1420
- : command.action === 'remove' ? 'Session removed from collaboration group.'
1421
- : command.action === 'spawn' ? 'Agent Session created and added to collaboration group.'
1422
- : 'Message sent.';
1423
- console.log(`${ICON.ok} ${c.green(success)}`);
1280
+ process.exitCode = await executeCollaborationCommand(command, Object.fromEntries(Object.entries(context).filter((entry) => typeof entry[1] === 'string')), { request: (method, endpoint, body, timeout) => method === 'GET'
1281
+ ? getLocalJson(baseUrl, runningState.localApiToken, endpoint, timeout)
1282
+ : postLocalJson(baseUrl, runningState.localApiToken, endpoint, body, timeout),
1283
+ write: (line) => console.log(line), stdin: readStdinText });
1424
1284
  }
1425
1285
  async function runInjectChangeAudit(source) {
1426
1286
  const raw = source === true
@@ -3463,7 +3323,7 @@ async function main() {
3463
3323
  }
3464
3324
  if (options.collab) {
3465
3325
  await runCollab(options.collab);
3466
- process.exit(0);
3326
+ return; // Preserve the receipt exit code and allow piped JSON output to drain.
3467
3327
  }
3468
3328
  if (options.pluginInit) {
3469
3329
  if (options.pluginInitJson)