termdock 1.4.173 → 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 (357) hide show
  1. package/.desktop-dist/main.js +204 -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-xuS6rtpv.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-BQGXUr8t.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-BfZzS1_d.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-Dn_lmPcC.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-DUybtKGZ.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-CLEmOHBp.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-O_XM5b9_.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-DlUEslcV.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-DCrP6W79.js → chunk-2Q5K7J3B-BsVAXGLa.js} +1 -1
  49. package/dist/client/assets/{chunk-5VM5RSS4-Zm6_pYQv.js → chunk-5VM5RSS4-BKtILsst.js} +1 -1
  50. package/dist/client/assets/{chunk-F27PBJKO-DkyD-X9z.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-UvWxXAkh.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-MVtVQeTr.js.gz +0 -0
  56. package/dist/client/assets/{chunk-JWPE2WC7-BjeBAFYM.js → chunk-JWPE2WC7-DliIIMe7.js} +1 -1
  57. package/dist/client/assets/{chunk-POPQ4Y6H-CnqrQdZC.js → chunk-POPQ4Y6H-XBSus3IC.js} +1 -1
  58. package/dist/client/assets/{chunk-SVP7TREG-C4a1s0v4.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-hnmjYAl_.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-xR4LOz28.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-CqY84fFp.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-Bd14zuaD.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-DSHcNXDs.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-CIGWQYeP.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-C9Fg7OLN.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-DoRhl7Fd.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-CI83g3iM.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-Dp2kP4PB.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-KtLMCM-V.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-D_zseu1m.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-DaWUl_Kz.js → erDiagram-RLTQ6QDP-DbjtZnJx.js} +1 -1
  98. package/dist/client/assets/{erDiagram-RLTQ6QDP-DaWUl_Kz.js.br → 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-BMoJ46zJ.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-lV_iVmoW.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-BVCT9Btd.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-CnKSUg74.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-BVhXOx1S.js → infoDiagram-27XIBGKW-BH4AynkM.js} +1 -1
  119. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Cyzfuoys.js → ishikawaDiagram-5VMMS53U-BQw90AKR.js} +1 -1
  120. package/dist/client/assets/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-CEIPDhQF.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-DISkQocf.js.gz +0 -0
  125. package/dist/client/assets/{kanban-definition-UXKFOSKX-C8OulEOF.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-YN93qRzb.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-D9T4dYh7.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-BdCvvCGV.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-CSpES3Jh.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-6f4vAA1Q.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-BC4Jd-I8.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-B0pnPaEN.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-B-oOamZT.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-DBSyxt0k.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-CcohzEZE.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-D7KS6rq_.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-CaRXtn4g.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-Dz836LqY.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-BK4KAY00.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-DWgxTDhO.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-BV1TIfOp.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-GYClC5F0.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-D1oKV5W0.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 +12 -3
  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 +311 -20
  210. package/dist/server/cli.js +28 -163
  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 +89 -79
  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-DKj7F0ex.js +0 -2
  240. package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js.br +0 -0
  241. package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js.gz +0 -0
  242. package/dist/client/assets/DagPlayground-D2Wui4jd.js +0 -1
  243. package/dist/client/assets/DagPlayground-D2Wui4jd.js.br +0 -0
  244. package/dist/client/assets/DagPlayground-D2Wui4jd.js.gz +0 -0
  245. package/dist/client/assets/DiffLab-xuS6rtpv.js.br +0 -0
  246. package/dist/client/assets/DiffLab-xuS6rtpv.js.gz +0 -0
  247. package/dist/client/assets/DiffReview-CAwpqM_w.js +0 -1
  248. package/dist/client/assets/DiffReview-CAwpqM_w.js.br +0 -0
  249. package/dist/client/assets/DiffReview-CAwpqM_w.js.gz +0 -0
  250. package/dist/client/assets/DiffReviewLab-BM_uGdyI.js +0 -2
  251. package/dist/client/assets/DiffReviewLab-BM_uGdyI.js.br +0 -0
  252. package/dist/client/assets/DiffReviewLab-BM_uGdyI.js.gz +0 -0
  253. package/dist/client/assets/DiffViewer-BQGXUr8t.js.br +0 -0
  254. package/dist/client/assets/DiffViewer-BQGXUr8t.js.gz +0 -0
  255. package/dist/client/assets/ModelPreview-BfZzS1_d.js.br +0 -0
  256. package/dist/client/assets/ModelPreview-BfZzS1_d.js.gz +0 -0
  257. package/dist/client/assets/RightSidebar-Cdqt3C7V.js +0 -55
  258. package/dist/client/assets/RightSidebar-Cdqt3C7V.js.br +0 -0
  259. package/dist/client/assets/RightSidebar-Cdqt3C7V.js.gz +0 -0
  260. package/dist/client/assets/abnfDiagram-VCTEODGH-Dn_lmPcC.js.br +0 -0
  261. package/dist/client/assets/abnfDiagram-VCTEODGH-Dn_lmPcC.js.gz +0 -0
  262. package/dist/client/assets/app-DDCkFsNe.js +0 -175
  263. package/dist/client/assets/app-DDCkFsNe.js.br +0 -0
  264. package/dist/client/assets/app-DDCkFsNe.js.gz +0 -0
  265. package/dist/client/assets/arc-DUybtKGZ.js.br +0 -0
  266. package/dist/client/assets/arc-DUybtKGZ.js.gz +0 -0
  267. package/dist/client/assets/architectureDiagram-5GKGNRK7-CLEmOHBp.js.br +0 -0
  268. package/dist/client/assets/architectureDiagram-5GKGNRK7-CLEmOHBp.js.gz +0 -0
  269. package/dist/client/assets/blockDiagram-I7D4REHJ-O_XM5b9_.js.br +0 -0
  270. package/dist/client/assets/blockDiagram-I7D4REHJ-O_XM5b9_.js.gz +0 -0
  271. package/dist/client/assets/c4Diagram-7LVT6UL2-DlUEslcV.js.br +0 -0
  272. package/dist/client/assets/c4Diagram-7LVT6UL2-DlUEslcV.js.gz +0 -0
  273. package/dist/client/assets/channel-Bk1VkYvd.js +0 -1
  274. package/dist/client/assets/chunk-F27PBJKO-DkyD-X9z.js.br +0 -0
  275. package/dist/client/assets/chunk-F27PBJKO-DkyD-X9z.js.gz +0 -0
  276. package/dist/client/assets/chunk-IMKFNOWR-UvWxXAkh.js.br +0 -0
  277. package/dist/client/assets/chunk-IMKFNOWR-UvWxXAkh.js.gz +0 -0
  278. package/dist/client/assets/chunk-SVP7TREG-C4a1s0v4.js.br +0 -0
  279. package/dist/client/assets/chunk-SVP7TREG-C4a1s0v4.js.gz +0 -0
  280. package/dist/client/assets/chunk-TICWLB2K-hnmjYAl_.js.br +0 -0
  281. package/dist/client/assets/chunk-TICWLB2K-hnmjYAl_.js.gz +0 -0
  282. package/dist/client/assets/classDiagram-ZZMXUADV-_tUz3Cxt.js +0 -1
  283. package/dist/client/assets/classDiagram-v2-VYDZK3BY-_tUz3Cxt.js +0 -1
  284. package/dist/client/assets/cose-bilkent-JH36ORCC-CqY84fFp.js.br +0 -0
  285. package/dist/client/assets/cose-bilkent-JH36ORCC-CqY84fFp.js.gz +0 -0
  286. package/dist/client/assets/cynefin-OW5HDTMX-Bd14zuaD.js.br +0 -0
  287. package/dist/client/assets/cynefin-OW5HDTMX-Bd14zuaD.js.gz +0 -0
  288. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DSHcNXDs.js.br +0 -0
  289. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DSHcNXDs.js.gz +0 -0
  290. package/dist/client/assets/dagre-GXQ25YYZ-CIGWQYeP.js.br +0 -0
  291. package/dist/client/assets/dagre-GXQ25YYZ-CIGWQYeP.js.gz +0 -0
  292. package/dist/client/assets/diagram-S7CK7UJ4-C9Fg7OLN.js.br +0 -0
  293. package/dist/client/assets/diagram-S7CK7UJ4-C9Fg7OLN.js.gz +0 -0
  294. package/dist/client/assets/diagram-UQ7AKVKN-DoRhl7Fd.js.br +0 -0
  295. package/dist/client/assets/diagram-UQ7AKVKN-DoRhl7Fd.js.gz +0 -0
  296. package/dist/client/assets/diagram-VSXAHHWV-CI83g3iM.js.br +0 -0
  297. package/dist/client/assets/diagram-VSXAHHWV-CI83g3iM.js.gz +0 -0
  298. package/dist/client/assets/diagram-VX7I27RA-Dp2kP4PB.js.br +0 -0
  299. package/dist/client/assets/diagram-VX7I27RA-Dp2kP4PB.js.gz +0 -0
  300. package/dist/client/assets/diagram-Z3DM3KII-KtLMCM-V.js.br +0 -0
  301. package/dist/client/assets/diagram-Z3DM3KII-KtLMCM-V.js.gz +0 -0
  302. package/dist/client/assets/ebnfDiagram-PWID7BFC-D_zseu1m.js.br +0 -0
  303. package/dist/client/assets/ebnfDiagram-PWID7BFC-D_zseu1m.js.gz +0 -0
  304. package/dist/client/assets/erDiagram-RLTQ6QDP-DaWUl_Kz.js.gz +0 -0
  305. package/dist/client/assets/flowDiagram-HODETNUW-BMoJ46zJ.js.br +0 -0
  306. package/dist/client/assets/flowDiagram-HODETNUW-BMoJ46zJ.js.gz +0 -0
  307. package/dist/client/assets/ganttDiagram-EL5Y4UJY-lV_iVmoW.js.br +0 -0
  308. package/dist/client/assets/ganttDiagram-EL5Y4UJY-lV_iVmoW.js.gz +0 -0
  309. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVCT9Btd.js.br +0 -0
  310. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVCT9Btd.js.gz +0 -0
  311. package/dist/client/assets/index-CnKSUg74.css.br +0 -0
  312. package/dist/client/assets/index-CnKSUg74.css.gz +0 -0
  313. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cyzfuoys.js.br +0 -0
  314. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cyzfuoys.js.gz +0 -0
  315. package/dist/client/assets/journeyDiagram-3NMN7TZE-CEIPDhQF.js.br +0 -0
  316. package/dist/client/assets/journeyDiagram-3NMN7TZE-CEIPDhQF.js.gz +0 -0
  317. package/dist/client/assets/kanban-definition-UXKFOSKX-C8OulEOF.js.br +0 -0
  318. package/dist/client/assets/kanban-definition-UXKFOSKX-C8OulEOF.js.gz +0 -0
  319. package/dist/client/assets/layout-YN93qRzb.js.br +0 -0
  320. package/dist/client/assets/layout-YN93qRzb.js.gz +0 -0
  321. package/dist/client/assets/linear-D9T4dYh7.js.br +0 -0
  322. package/dist/client/assets/linear-D9T4dYh7.js.gz +0 -0
  323. package/dist/client/assets/mermaid.core-BdCvvCGV.js.br +0 -0
  324. package/dist/client/assets/mermaid.core-BdCvvCGV.js.gz +0 -0
  325. package/dist/client/assets/mindmap-definition-YA3MSWOX-CSpES3Jh.js.br +0 -0
  326. package/dist/client/assets/mindmap-definition-YA3MSWOX-CSpES3Jh.js.gz +0 -0
  327. package/dist/client/assets/pegDiagram-XKGWAZYB-6f4vAA1Q.js.br +0 -0
  328. package/dist/client/assets/pegDiagram-XKGWAZYB-6f4vAA1Q.js.gz +0 -0
  329. package/dist/client/assets/pieDiagram-E7YTZNPT-BC4Jd-I8.js.br +0 -0
  330. package/dist/client/assets/pieDiagram-E7YTZNPT-BC4Jd-I8.js.gz +0 -0
  331. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0pnPaEN.js.br +0 -0
  332. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0pnPaEN.js.gz +0 -0
  333. package/dist/client/assets/railroadDiagram-O6MQD6OU-B-oOamZT.js.br +0 -0
  334. package/dist/client/assets/railroadDiagram-O6MQD6OU-B-oOamZT.js.gz +0 -0
  335. package/dist/client/assets/requirementDiagram-BXWQKSXE-DBSyxt0k.js.br +0 -0
  336. package/dist/client/assets/requirementDiagram-BXWQKSXE-DBSyxt0k.js.gz +0 -0
  337. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CcohzEZE.js.br +0 -0
  338. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CcohzEZE.js.gz +0 -0
  339. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D7KS6rq_.js.br +0 -0
  340. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D7KS6rq_.js.gz +0 -0
  341. package/dist/client/assets/sizeCapture-INFHLROL-CaRXtn4g.js.br +0 -0
  342. package/dist/client/assets/sizeCapture-INFHLROL-CaRXtn4g.js.gz +0 -0
  343. package/dist/client/assets/sparkles-CUdUt4qy.js +0 -1
  344. package/dist/client/assets/stateDiagram-D77RDMKH-Dz836LqY.js.br +0 -0
  345. package/dist/client/assets/stateDiagram-D77RDMKH-Dz836LqY.js.gz +0 -0
  346. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-5V5Y3wwn.js +0 -1
  347. package/dist/client/assets/swimlanes-42K2YHIH-BK4KAY00.js.br +0 -0
  348. package/dist/client/assets/swimlanes-42K2YHIH-BK4KAY00.js.gz +0 -0
  349. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Sj-7YK2P.js +0 -8
  350. package/dist/client/assets/timeline-definition-24CTP7MA-DWgxTDhO.js.br +0 -0
  351. package/dist/client/assets/timeline-definition-24CTP7MA-DWgxTDhO.js.gz +0 -0
  352. package/dist/client/assets/vennDiagram-4TSXK5OY-BV1TIfOp.js.br +0 -0
  353. package/dist/client/assets/vennDiagram-4TSXK5OY-BV1TIfOp.js.gz +0 -0
  354. package/dist/client/assets/wardleyDiagram-VM6X3IG4-GYClC5F0.js.br +0 -0
  355. package/dist/client/assets/wardleyDiagram-VM6X3IG4-GYClC5F0.js.gz +0 -0
  356. package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1oKV5W0.js.br +0 -0
  357. package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1oKV5W0.js.gz +0 -0
@@ -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,123 +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 success = command.action === 'reply' ? 'Reply sent.'
1413
- : command.action === 'handoff' ? 'Handoff sent.'
1414
- : command.action === 'add' ? 'Session added to collaboration group.'
1415
- : command.action === 'remove' ? 'Session removed from collaboration group.'
1416
- : command.action === 'spawn' ? 'Agent Session created and added to collaboration group.'
1417
- : 'Message sent.';
1418
- 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 });
1419
1284
  }
1420
1285
  async function runInjectChangeAudit(source) {
1421
1286
  const raw = source === true
@@ -3458,7 +3323,7 @@ async function main() {
3458
3323
  }
3459
3324
  if (options.collab) {
3460
3325
  await runCollab(options.collab);
3461
- process.exit(0);
3326
+ return; // Preserve the receipt exit code and allow piped JSON output to drain.
3462
3327
  }
3463
3328
  if (options.pluginInit) {
3464
3329
  if (options.pluginInitJson)
@@ -1,7 +1,14 @@
1
+ import { createOpenAccessRouter } from './federation/openAccess.js';
2
+ import { createPasswordLoginRouter } from './federation/passwordLoginRoutes.js';
1
3
  import { apiAccessGate, isTrustedLocalRequest } from './utils/apiAccess.js';
4
+ import { isEncryptedRequest } from './federation/requestContext.js';
5
+ import { createFederationRuntime } from './federation/runtime.js';
6
+ import { RouteAccess } from './federation/routeAccess.js';
7
+ import { RouteInvitationStore } from './federation/routeInvitations.js';
8
+ import { attachRegisteredDirectTargets, verifyDirectTarget } from './federation/directRoutes.js';
9
+ import { secureChannelOriginAllowed } from './federation/originPolicy.js';
10
+ import { RelayRouter } from './federation/relay.js';
2
11
  import { assertPublicSecurity, securityHeaders } from './utils/publicSecurity.js';
3
- import { guardWebSocketSession } from './utils/webSocketSecurity.js';
4
- import { readTerminalHandshakeDimensions } from './utils/terminalHandshakeDimensions.js';
5
12
  import { apiCachePolicy } from './utils/apiCachePolicy.js';
6
13
  import 'dotenv/config';
7
14
  import express from 'express';
@@ -23,7 +30,7 @@ import { createOnboardingRouter } from './routes/onboarding.js';
23
30
  import { createLocalRouter } from './routes/local.js';
24
31
  import { csrfProtection } from './utils/csrfProtection.js';
25
32
  import { pathValidator } from './utils/pathValidator.js';
26
- import { isUpgradeRequestAuthenticated, requireAuth, renewSessionMiddleware } from './utils/authProtection.js';
33
+ import { requireAuth, renewSessionMiddleware } from './utils/authProtection.js';
27
34
  import { localAccessManager } from './utils/localAccess.js';
28
35
  import { isAllowedHost, isUpgradeOriginAllowed, validateHostMiddleware, validateOriginMiddleware, } from './utils/requestSecurity.js';
29
36
  import { getCookieSecurityOptions, setSecureCookieMode } from './utils/cookieSecurity.js';
@@ -37,22 +44,6 @@ import { getTermdockVersion, TERMDOCK_CAPABILITIES, TERMDOCK_PROTOCOL_VERSION, }
37
44
  import { PORT, DEFAULT_HOST } from './config.js';
38
45
  const CLIENT_STATE_COOKIE = 'termdock-client';
39
46
  export const DEFAULT_PORT = PORT.backend;
40
- /** Read one cookie from a raw Cookie header (upgrade requests bypass the
41
- * cookie-parser middleware). Values are percent-encoded. */
42
- function getCookieValue(cookieHeader, name) {
43
- if (!cookieHeader)
44
- return undefined;
45
- const pattern = new RegExp(`(?:^|;\\s*)${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}=([^;]*)`);
46
- const match = cookieHeader.match(pattern);
47
- if (!match)
48
- return undefined;
49
- try {
50
- return decodeURIComponent(match[1]);
51
- }
52
- catch {
53
- return match[1];
54
- }
55
- }
56
47
  const CLIENT_LOG_DEDUP_WINDOW_MS = 5_000;
57
48
  const CLIENT_LOG_RATE_WINDOW_MS = 10_000;
58
49
  const CLIENT_LOG_RATE_LIMIT = 120;
@@ -146,6 +137,17 @@ export function createApp(options = {}) {
146
137
  app.disable('x-powered-by');
147
138
  app.use(securityHeaders);
148
139
  app.use(validateHostMiddleware);
140
+ if (options.secureRequired)
141
+ app.use('/api/auth/open', createOpenAccessRouter(() => app.locals.passwordRuntime?.serviceId));
142
+ if (options.secureRequired)
143
+ app.use('/api/auth/password', createPasswordLoginRouter(() => app.locals.passwordRuntime));
144
+ app.use('/api', (req, res, next) => {
145
+ if (!options.secureRequired || isEncryptedRequest(req) || isTrustedLocalRequest(req, options.localApiToken))
146
+ return next();
147
+ if (req.method === 'GET' && ['/meta', '/auth/status'].includes(req.path))
148
+ return next();
149
+ res.status(426).json({ error: 'Pair this device to use an encrypted connection', code: 'E2EE_REQUIRED' });
150
+ });
149
151
  // Authenticate before allocating request bodies, including future API routes.
150
152
  app.use(cookieParser());
151
153
  app.use('/api', renewSessionMiddleware);
@@ -393,12 +395,78 @@ export function startServer(options = {}) {
393
395
  });
394
396
  runtimeMonitor.start();
395
397
  const app = createApp({
398
+ secureRequired: true,
396
399
  port: options.onboardingPort ?? port,
397
400
  httpsCaPath: options.httpsCaPath,
398
401
  localApiToken: options.localApiToken,
399
402
  runtimeMonitor,
400
403
  });
401
404
  const { server, scheme } = createServerForApp(app, options);
405
+ let entrySubjectAllowed = (_subjectId) => false;
406
+ let routeInvitations;
407
+ const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true);
408
+ const relayRouter = new RelayRouter({
409
+ authenticate: context => context,
410
+ allowRegister: (principal, serviceId) => routeAccess.allowRegister(principal, serviceId),
411
+ allowRoute: (principal, serviceId) => routeAccess.allowRoute(principal, serviceId),
412
+ });
413
+ const dynamicDirectTargets = new Map();
414
+ server.once('close', () => { for (const close of dynamicDirectTargets.values())
415
+ close(); });
416
+ try {
417
+ const detachDirectTargets = attachRegisteredDirectTargets(relayRouter, routeAccess.configuredDirectTargets(), path.join(homedir(), '.termdock', 'federation'));
418
+ server.once('close', detachDirectTargets);
419
+ }
420
+ catch {
421
+ console.error('Configured direct routes unavailable; target access remains closed.');
422
+ }
423
+ server.once('close', () => relayRouter.close());
424
+ const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
425
+ terminal: handleTerminalWebSocket, control: handleControlWebSocket,
426
+ }, {
427
+ listRouteAccess: () => routeInvitations?.list() || [],
428
+ grantRouteAccess: async (issuerId, targetServiceId, subjectId, url) => {
429
+ if (!routeInvitations)
430
+ throw new Error('ROUTE_NOT_AVAILABLE');
431
+ if (!routeAccess.hasConfiguredTarget(targetServiceId)) {
432
+ if (!url)
433
+ throw new Error('ROUTE_TARGET_NOT_CONFIGURED');
434
+ const target = { serviceId: targetServiceId, url };
435
+ await verifyDirectTarget(target);
436
+ const runtime = await federation;
437
+ if (!runtime.store.authorize({ subjectId: issuerId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed)
438
+ throw new Error('AUTHORIZATION_DENIED');
439
+ routeAccess.addDirectTarget(target);
440
+ if (!dynamicDirectTargets.has(targetServiceId))
441
+ dynamicDirectTargets.set(targetServiceId, attachRegisteredDirectTargets(relayRouter, [target]));
442
+ }
443
+ return routeInvitations.grant(issuerId, targetServiceId, subjectId);
444
+ },
445
+ revokeRouteAccess: id => routeInvitations?.revoke(id) || false,
446
+ issueRouteTicket: (subjectId, serviceId) => routeAccess.issueRouteTicket(subjectId, serviceId),
447
+ hasRouteGrant: (subjectId, serviceId) => routeInvitations?.allows(subjectId, serviceId) === true,
448
+ createRouteInvitation: (issuerId, serviceId) => {
449
+ if (!routeInvitations)
450
+ throw new Error('ROUTE_NOT_AVAILABLE');
451
+ return routeInvitations.create(issuerId, serviceId);
452
+ },
453
+ consumeRouteInvitation: (code, subjectId) => {
454
+ if (!routeInvitations)
455
+ throw new Error('ROUTE_NOT_AVAILABLE');
456
+ return routeInvitations.consume(code, subjectId);
457
+ },
458
+ });
459
+ void federation.then(runtime => {
460
+ app.locals.passwordRuntime = runtime;
461
+ entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
462
+ routeInvitations = new RouteInvitationStore({
463
+ filePath: path.join(homedir(), '.termdock', 'federation', 'route-invitations.json'), serviceId: runtime.serviceId,
464
+ issuerAllowed: subjectId => entrySubjectAllowed(subjectId),
465
+ targetAvailable: serviceId => routeAccess.hasConfiguredTarget(serviceId) && relayRouter.hasRoute(serviceId),
466
+ });
467
+ }).catch(() => console.error('Route invitations unavailable; uninitialized route grants remain denied.'));
468
+ void federation.catch(() => console.error('Encrypted access initialization failed; business access remains closed.'));
469
+ server.once('close', () => { void federation.then(runtime => runtime.close()).catch(() => { }); });
402
470
  server.headersTimeout = 10_000;
403
471
  server.requestTimeout = 120_000;
404
472
  server.keepAliveTimeout = 5_000;
@@ -464,14 +532,12 @@ export function startServer(options = {}) {
464
532
  zlibDeflateOptions: { level: 6, memLevel: 7 },
465
533
  },
466
534
  });
467
- const WS_PATH_RE = /^\/api\/terminal\/([^/]+)\/ws$/;
468
- const CONTROL_WS_PATH = '/api/control/ws';
469
535
  server.on('upgrade', (request, socket, head) => {
470
536
  if (wss.clients.size >= 256) {
471
537
  socket.end('HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\n\r\n');
472
538
  return;
473
539
  }
474
- if (!isAllowedHost(request.headers.host) || !isUpgradeOriginAllowed(request.headers.origin, request.headers.host)) {
540
+ if (!isAllowedHost(request.headers.host)) {
475
541
  socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
476
542
  socket.destroy();
477
543
  return;
@@ -485,41 +551,38 @@ export function startServer(options = {}) {
485
551
  return;
486
552
  }
487
553
  const pathname = url.pathname;
488
- // Reject the upgrade before the WebSocket handshake completes if auth
489
- // is enabled and the client cookie is missing/expired. We respond with
490
- // a real HTTP 401 so the browser surfaces a useful error. Applied to
491
- // both per-terminal and control paths.
492
- const cookieHeader = request.headers.cookie;
493
- if (!isUpgradeRequestAuthenticated(typeof cookieHeader === 'string' ? cookieHeader : undefined)) {
494
- socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
495
- socket.destroy();
554
+ if (pathname === '/api/federation/relay') {
555
+ // Native relay peers have no Origin. Authenticate explicitly before upgrade.
556
+ if (scheme !== 'https' || (request.headers.origin && !isUpgradeOriginAllowed(request.headers.origin, request.headers.host))) {
557
+ socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
558
+ return;
559
+ }
560
+ const principal = routeAccess.authenticate(request.headers.authorization, url.searchParams.get('routeToken'));
561
+ if (!principal) {
562
+ socket.end('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
563
+ return;
564
+ }
565
+ wss.handleUpgrade(request, socket, head, ws => {
566
+ void relayRouter.attach(ws, principal).then(attached => { if (attached)
567
+ ws.send(JSON.stringify({ type: 'ready' })); });
568
+ });
496
569
  return;
497
570
  }
498
- // Control WS: server-pushed global client-state events. One per browser.
499
- if (pathname === CONTROL_WS_PATH) {
500
- const clientId = crypto.randomUUID();
571
+ if (pathname === '/api/federation/secure') {
572
+ // This exception applies only to the cookie-independent Noise handshake.
573
+ // Target pinning and device authorization remain mandatory inside it.
574
+ if (!secureChannelOriginAllowed(request.headers.origin, () => isUpgradeOriginAllowed(request.headers.origin, request.headers.host))) {
575
+ socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
576
+ return;
577
+ }
501
578
  wss.handleUpgrade(request, socket, head, (ws) => {
502
- handleControlWebSocket(ws, clientId);
503
- guardWebSocketSession(ws, cookieHeader);
579
+ void federation.then(runtime => runtime.accept(ws, { allowOpenAccess: !!request.headers.origin && isUpgradeOriginAllowed(request.headers.origin, request.headers.host) })).catch(() => ws.close(1011, 'Encrypted access unavailable'));
504
580
  });
505
581
  return;
506
582
  }
507
- // Per-terminal WS: bidirectional I/O for a single terminal session.
508
- const match = pathname.match(WS_PATH_RE);
509
- if (!match) {
510
- socket.destroy();
511
- return;
512
- }
513
- const sessionId = match[1];
514
- const clientId = crypto.randomUUID();
515
- const pushClientId = getCookieValue(cookieHeader, CLIENT_STATE_COOKIE);
516
- // 短线重连时客户端会带上 ?since=<lastSeq>,让服务端只补发增量。
517
- const sinceParam = url.searchParams.get('since');
518
- const sinceSeq = sinceParam ? Math.max(0, Number.parseInt(sinceParam, 10) || 0) : 0;
519
- wss.handleUpgrade(request, socket, head, (ws) => {
520
- handleTerminalWebSocket(ws, sessionId, clientId, { sinceSeq, pushClientId, streamEpoch: url.searchParams.get('epoch') ?? undefined, flowControl: url.searchParams.get('flow') === '2', independentTmux: url.searchParams.get('transport') === 'tmux-client', outputActive: url.searchParams.get('active') !== '0' }, readTerminalHandshakeDimensions(url.searchParams));
521
- guardWebSocketSession(ws, cookieHeader);
522
- });
583
+ // Business WebSockets have no plaintext fallback after pairing migration.
584
+ socket.end('HTTP/1.1 426 Upgrade Required\r\nConnection: close\r\n\r\n');
585
+ return;
523
586
  });
524
587
  server.listen(port, host, () => {
525
588
  const displayHost = host === '0.0.0.0' ? 'localhost' : host;
@@ -0,0 +1,122 @@
1
+ const METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);
2
+ export function canonicalApiPath(path) {
3
+ if (typeof path !== 'string' || !path.startsWith('/api/') || /[\\\x00-\x20\x7f#]/.test(path))
4
+ return null;
5
+ const pathname = path.split('?')[0];
6
+ if (pathname.includes('//'))
7
+ return null;
8
+ try {
9
+ const segments = pathname.split('/').map((segment, index) => {
10
+ if (index === 0 || (segment === '' && index === pathname.split('/').length - 1))
11
+ return segment;
12
+ const value = decodeURIComponent(segment);
13
+ // Decode exactly once. Never allow an encoded separator, traversal segment,
14
+ // control byte, invalid UTF-8 or a second escape layer to change routing.
15
+ if (!value || value === '.' || value === '..' || /[\\/\x00-\x1f\x7f]/.test(value) || /%[a-f0-9]{2}/i.test(value))
16
+ throw new Error('Ambiguous API path');
17
+ return value;
18
+ });
19
+ const canonical = segments.join('/');
20
+ if (canonical.endsWith('/') && !canonical.startsWith('/api/terminal/fs/preview/'))
21
+ return null;
22
+ return canonical;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ const pathOnly = canonicalApiPath;
29
+ /** Restricts a full-service tunnel to business APIs, excluding authentication,
30
+ * local-only operations and recursive federation/bootstrap endpoints. */
31
+ export function isBusinessApiPath(method, path) {
32
+ if (!METHODS.has(method))
33
+ return false;
34
+ const pathname = pathOnly(path);
35
+ if (!pathname)
36
+ return false;
37
+ return !/^\/api\/(?:auth|local|federation|onboarding|csrf)(?:\/|$)/i.test(pathname);
38
+ }
39
+ const fileReads = new Set(['list', 'watch', 'search', 'read', 'git-blob', 'eda-preview', 'eda-inspect', 'blob', 'video', 'download', 'diff', 'diff-files', 'untracked-files', 'git-context', 'git-bundle', 'change-audit', 'branch-diff', 'commit-diff', 'git-recent-commits', 'branch-audit', 'git-action/status', 'local-open-availability']);
40
+ const filePosts = new Set(['git-action', 'apply-hunk', 'upload', 'open-in-file-browser']);
41
+ const settings = new Set(['settings', 'toolbar-presets', 'program-detection', 'context-draft']);
42
+ const serviceReads = new Set(['update', 'auto-title/catalog', 'agent-hooks', 'agent-launchers', 'agent-plugins', 'quota', 'tmux/status', 'tmux/sessions', 'agent-resume-history']);
43
+ const reservedSessionIds = new Set(['fs', 'settings', 'client-state', 'session-inventory', 'operations', 'tmux', 'create', 'force-kill', 'serialize-state', 'agent-resume-history', 'toolbar-presets', 'context-draft', 'program-detection', 'update', 'quota', 'agent-hooks', 'agent-launchers', 'agent-plugins', 'agent-plugin-icon', 'auto-title', 'directory-suggestions']);
44
+ export function resolveAccessRequest(method, path) {
45
+ if (!isBusinessApiPath(method, path))
46
+ return null;
47
+ const pathname = pathOnly(path);
48
+ const read = method === 'GET' || method === 'HEAD';
49
+ if (/^\/api\/notifications\/(status|subscribe|preferences|unsubscribe)$/.test(pathname)) {
50
+ if (read && pathname.endsWith('/status'))
51
+ return { action: 'service.view' };
52
+ if (method === 'POST' && !pathname.endsWith('/status'))
53
+ return { action: 'service.configure' };
54
+ return null;
55
+ }
56
+ const prefix = '/api/terminal/';
57
+ if (!pathname.startsWith(prefix))
58
+ return null;
59
+ const route = pathname.slice(prefix.length);
60
+ if (route.startsWith('fs/')) {
61
+ const op = route.slice(3);
62
+ if (read && (fileReads.has(op) || /^preview\/.+/.test(op)))
63
+ return { action: 'file.read' };
64
+ if ((method === 'POST' && filePosts.has(op)) || (method === 'DELETE' && ['file', 'change-audit', 'branch-audit'].includes(op)) || (method === 'GET' && op === 'cancel-slot'))
65
+ return { action: 'file.write' };
66
+ return null;
67
+ }
68
+ if (read && route === 'session-inventory')
69
+ return { action: 'service.view', metadata: 'session-inventory' };
70
+ if (read && route === 'client-state')
71
+ return { action: 'service.view', metadata: 'global-client-state' };
72
+ if (settings.has(route)) {
73
+ if (read)
74
+ return { action: 'service.view' };
75
+ if (method === 'PUT' || (method === 'DELETE' && route === 'program-detection'))
76
+ return { action: 'service.configure' };
77
+ return null;
78
+ }
79
+ if (read && serviceReads.has(route))
80
+ return { action: 'service.view' };
81
+ if (read && route === 'directory-suggestions')
82
+ return { action: 'file.read' };
83
+ if (method === 'POST' && route === 'create')
84
+ return { action: 'service.create-session' };
85
+ const session = /^([^/]+)(?:\/(health|attach|stream|input|resize|tmux|restart|agent-resume))?$/.exec(route);
86
+ if (session && !reservedSessionIds.has(session[1].toLowerCase())) {
87
+ const sessionId = session[1];
88
+ const op = session[2];
89
+ if (read && ['health', 'attach', 'stream', 'agent-resume'].includes(op))
90
+ return { action: 'session.view', sessionId };
91
+ if (method === 'POST' && op === 'input')
92
+ return { action: 'session.input', sessionId };
93
+ if (method === 'POST' && op === 'resize')
94
+ return { action: 'session.resize', sessionId };
95
+ if (method === 'DELETE' && !op)
96
+ return { action: 'session.terminate', sessionId };
97
+ // tmux switch-session can reach other backends; restart/agent-resume accept
98
+ // launch configuration. Their unrestricted bodies require service authority.
99
+ if (method === 'POST' && ['tmux', 'restart', 'agent-resume'].includes(op))
100
+ return { action: 'service.configure' };
101
+ }
102
+ // Complex operations manipulate global state or accept additional resource IDs.
103
+ // An unregistered operation intentionally receives no custom-grant mapping.
104
+ return null;
105
+ }
106
+ /** WS control mapping; caller must bind sessionId to the opened channel. */
107
+ export function terminalMessageAction(type) {
108
+ switch (type) {
109
+ case 'input':
110
+ case 'focus':
111
+ case 'agent-review-ack': return 'session.input';
112
+ case 'resize': return 'session.resize';
113
+ case 'tmux': return 'service.configure';
114
+ case 'ping':
115
+ case 'viewing':
116
+ case 'output-subscription':
117
+ case 'output-ack': return 'session.view';
118
+ // Legacy flow-control can pause the shared PTY; grant only to controllers.
119
+ case 'flow-control': return 'session.input';
120
+ default: return null;
121
+ }
122
+ }