@tequila99/githuman 0.4.2 → 0.6.0

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 (390) hide show
  1. package/README.md +18 -2
  2. package/dist/cli/index.js +27 -2
  3. package/dist/cli/server-runtime.js +1 -0
  4. package/dist/server/app.js +22 -1
  5. package/dist/server/errors/git.js +2 -0
  6. package/dist/server/errors/terminal.js +4 -0
  7. package/dist/server/hooks/terminal/local-origin.js +25 -0
  8. package/dist/server/routes/agent.js +8 -2
  9. package/dist/server/routes/app-info.js +5 -2
  10. package/dist/server/routes/git.js +5 -2
  11. package/dist/server/routes/terminal.js +63 -0
  12. package/dist/server/services/agent/agent-session.js +2 -1
  13. package/dist/server/services/agent/context-builder.js +49 -2
  14. package/dist/server/services/agent/file-index.js +7 -4
  15. package/dist/server/services/git.service.js +44 -4
  16. package/dist/server/services/terminal/backend.js +216 -0
  17. package/dist/server/services/terminal/channel.js +200 -0
  18. package/dist/server/services/terminal/escape-tail.js +93 -0
  19. package/dist/server/services/terminal/processes.js +115 -0
  20. package/dist/server/services/terminal/prompt-tracker.js +15 -0
  21. package/dist/server/services/terminal/registry.js +167 -0
  22. package/dist/server/services/terminal/session.js +238 -0
  23. package/dist/server/services/terminal/socket.js +74 -0
  24. package/dist/server/services/terminal/tokens.js +32 -0
  25. package/dist/server/utils/sse.js +76 -5
  26. package/dist/shared/agents/file-search.js +19 -7
  27. package/dist/shared/agents/mention-paths.js +40 -0
  28. package/dist/shared/agents/schemas.js +13 -4
  29. package/dist/shared/app/schemas.js +5 -1
  30. package/dist/shared/git/schemas.js +4 -1
  31. package/dist/shared/terminal/constants.js +27 -0
  32. package/dist/shared/terminal/schemas.js +114 -0
  33. package/dist/shared/terminal/types.js +1 -0
  34. package/dist/shared/utils/guards.js +3 -0
  35. package/dist/web/assets/{AppPage-BYrCfRnC.js → AppPage-CiRB13sb.js} +2 -2
  36. package/dist/web/assets/CreateReviewFabButton-DTvX9Psc.js +1 -0
  37. package/dist/web/assets/ErrorNotFound-CYOZg8yN.js +1 -0
  38. package/dist/web/assets/IndexPage-CYFIGWy9.css +1 -0
  39. package/dist/web/assets/IndexPage-hI7bcJ6L.js +1 -0
  40. package/dist/web/assets/MainLayout-DCexR9_Y.css +1 -0
  41. package/dist/web/assets/MainLayout-baVxjyeL.js +11 -0
  42. package/dist/web/assets/PdfPreview-Cb0xawi0.css +1 -0
  43. package/dist/web/assets/PdfPreview-D3HwZ7pH.js +54 -0
  44. package/dist/web/assets/QBtn-CzrOO1zA.js +1 -0
  45. package/dist/web/assets/QScrollArea-Cu8zdp0y.js +1 -0
  46. package/dist/web/assets/QSelect-DQ_RRYIL.js +1 -0
  47. package/dist/web/assets/QSpace-CEuZSBS2.js +13 -0
  48. package/dist/web/assets/ReviewDetailPage-CayB2dtb.js +1 -0
  49. package/dist/web/assets/ReviewDetailPage-CkCilMNW.css +1 -0
  50. package/dist/web/assets/ReviewsPage-DWfEVjWO.js +1 -0
  51. package/dist/web/assets/StatusSwitcher--zNcE2-Y.js +1 -0
  52. package/dist/web/assets/StatusSwitcher-DTD5inU1.css +1 -0
  53. package/dist/web/assets/TerminalView-B11UHZv3.css +1 -0
  54. package/dist/web/assets/TerminalView-DvVbWk3f.js +19 -0
  55. package/dist/web/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  56. package/dist/web/assets/{abnfDiagram-O67JEVCF-dyvffE6R.js → abnfDiagram-O67JEVCF-C5SYdl8k.js} +1 -1
  57. package/dist/web/assets/agent-store-DX4gIEHa.js +1 -0
  58. package/dist/web/assets/app-info-store-Vy_Pq2yR.js +1 -0
  59. package/dist/web/assets/architecture-7GRP2DOG-Dpxe25D8.js +1 -0
  60. package/dist/web/assets/{architectureDiagram-NJMV4G6O-Dk-QthCI.js → architectureDiagram-NJMV4G6O-Bx8KGjS_.js} +1 -1
  61. package/dist/web/assets/{blockDiagram-BEXU5L5S-sF48dzNS.js → blockDiagram-BEXU5L5S-B4ybhPir.js} +1 -1
  62. package/dist/web/assets/{c4Diagram-YGBWAQC7-BWrux-0F.js → c4Diagram-YGBWAQC7-D2Fzm9Zx.js} +1 -1
  63. package/dist/web/assets/channel--0tdF_Sb.js +1 -0
  64. package/dist/web/assets/chunk-3FUC2YCW-DLRX-HPs.js +2 -0
  65. package/dist/web/assets/{chunk-5DYCD2WN-CpgXf7LJ.js → chunk-5DYCD2WN-6HcgVbfR.js} +1 -1
  66. package/dist/web/assets/{chunk-742MDFTN-BGzG-dZE.js → chunk-742MDFTN-CfTGkhlJ.js} +1 -1
  67. package/dist/web/assets/{chunk-7INBJB4K-DJGc-gqS.js → chunk-7INBJB4K-COfv2qWf.js} +1 -1
  68. package/dist/web/assets/{chunk-7M6MHVWA-CYIOaZel.js → chunk-7M6MHVWA-D0FxecF4.js} +5 -5
  69. package/dist/web/assets/{chunk-7PRAP22T-CJZNYL51.js → chunk-7PRAP22T-DFjSoEy0.js} +1 -1
  70. package/dist/web/assets/{chunk-GTNCS2PH-DOYGHmMk.js → chunk-GTNCS2PH-CGWGXDNO.js} +1 -1
  71. package/dist/web/assets/{chunk-GWA4HPMP-RIikrSw1.js → chunk-GWA4HPMP-J0EaOPjD.js} +1 -1
  72. package/dist/web/assets/{chunk-MBY4JIJT-ZSssyEJi.js → chunk-MBY4JIJT-PzEUkWiy.js} +1 -1
  73. package/dist/web/assets/{chunk-NETBCI7D-3tnHEq90.js → chunk-NETBCI7D-DHHOcLF0.js} +1 -1
  74. package/dist/web/assets/{chunk-O7XYJQB3-CDFTy-aQ.js → chunk-O7XYJQB3-BHUId2wW.js} +1 -1
  75. package/dist/web/assets/{chunk-UA2S7LBM-B8rY8Yj-.js → chunk-UA2S7LBM-DXQ1DAj6.js} +1 -1
  76. package/dist/web/assets/{chunk-WEXAMYUT-BdPY0e7q.js → chunk-WEXAMYUT-BMiibMha.js} +1 -1
  77. package/dist/web/assets/{chunk-Z7XXMR3K-Br_E149D.js → chunk-Z7XXMR3K-BcgKYeJS.js} +1 -1
  78. package/dist/web/assets/{chunk-ZIGJFQKS-DDjPFexL.js → chunk-ZIGJFQKS-D6psU74o.js} +1 -1
  79. package/dist/web/assets/{classDiagram-v2-NBCMYWYE-D9kneDxy.js → classDiagram-v2-NBCMYWYE-6IwOSgSE.js} +4 -4
  80. package/dist/web/assets/create-BSmXD9Zr.js +1 -0
  81. package/dist/web/assets/{cynefin-OW5HDTMX-Dot1PBqs.js → cynefin-OW5HDTMX-C77WtKve.js} +1 -1
  82. package/dist/web/assets/{cynefinDiagram-VND7K2PF-6uYi0u-j.js → cynefinDiagram-VND7K2PF-BvIp3Y9w.js} +1 -1
  83. package/dist/web/assets/{dagre-6A5THRUB-BDM5YkfJ.js → dagre-6A5THRUB-DN7jrLfY.js} +1 -1
  84. package/dist/web/assets/debounce-rZaFFwsz.js +1 -0
  85. package/dist/web/assets/{diagram-22UHCM2B-DxE0D8WB.js → diagram-22UHCM2B-DQUkKx9q.js} +8 -8
  86. package/dist/web/assets/{diagram-3UASUU5V-CuD1fcFQ.js → diagram-3UASUU5V-CTzVKsCf.js} +1 -1
  87. package/dist/web/assets/{diagram-ATOU4E4O-DX6enGgq.js → diagram-ATOU4E4O--Z3AVUjn.js} +1 -1
  88. package/dist/web/assets/{diagram-CDSNMT55-eLqOiVoC.js → diagram-CDSNMT55-BA-cRZ39.js} +1 -1
  89. package/dist/web/assets/{diagram-MLGK6HIB-DtIMj5y1.js → diagram-MLGK6HIB-Bx_zLrYg.js} +1 -1
  90. package/dist/web/assets/{diagram-MPIPVDR6-lYcwsjHR.js → diagram-MPIPVDR6-CWKuh-e-.js} +1 -1
  91. package/dist/web/assets/{ebnfDiagram-ZINNZB2B-CGmBWaKx.js → ebnfDiagram-ZINNZB2B-MyQLfQVI.js} +1 -1
  92. package/dist/web/assets/{elk-276RUBZZ-B1ntUD9z.js → elk-276RUBZZ-DBbqEAnd.js} +1 -1
  93. package/dist/web/assets/{erDiagram-OPXOYQCR-Dqzoldin.js → erDiagram-OPXOYQCR-CbRo_Rnk.js} +1 -1
  94. package/dist/web/assets/eventmodeling-NTZA5JFV-DMla2cPF.js +1 -0
  95. package/dist/web/assets/flowDiagram-KWPJA3E3-Bas61_mX.js +1 -0
  96. package/dist/web/assets/{ganttDiagram-FUAMR5RP-CF6kJAoY.js → ganttDiagram-FUAMR5RP-D1gJBc1W.js} +1 -1
  97. package/dist/web/assets/{gitGraph-4MIJSDKK-CUzcGxMa.js → gitGraph-4MIJSDKK-CIzz8UCs.js} +1 -1
  98. package/dist/web/assets/{gitGraphDiagram-X574FWY7-D-XNa6f2.js → gitGraphDiagram-X574FWY7-B5-RB5JZ.js} +1 -1
  99. package/dist/web/assets/i18n-B1_G449i.js +1 -0
  100. package/dist/web/assets/index-DEwYKvIG.js +2 -0
  101. package/dist/web/assets/index-Dma-RXBR.css +1 -0
  102. package/dist/web/assets/{info-A6RAGUB7-CH4SNmU8.js → info-A6RAGUB7-DF-JCGlS.js} +1 -1
  103. package/dist/web/assets/{infoDiagram-VRGFBTTK-35AKnD7M.js → infoDiagram-VRGFBTTK-z0Xepdo0.js} +1 -1
  104. package/dist/web/assets/init-app-DAsY3Joz.js +1 -0
  105. package/dist/web/assets/{is-BB7rJzdU.js → is-DssfAlMe.js} +1 -1
  106. package/dist/web/assets/{ishikawaDiagram-OU5B5YK6-D41YBWSb.js → ishikawaDiagram-OU5B5YK6-UqXiqAoe.js} +1 -1
  107. package/dist/web/assets/{journeyDiagram-ZHPQQLJL-CZF6Wwk8.js → journeyDiagram-ZHPQQLJL-CPGeWRg-.js} +1 -1
  108. package/dist/web/assets/{kanban-definition-PNTS6WVX-CVGaUle0.js → kanban-definition-PNTS6WVX-CMq9WuD5.js} +1 -1
  109. package/dist/web/assets/{line-CeH4KBRO.js → line-cZB9xcQk.js} +1 -1
  110. package/dist/web/assets/{mermaid-parser.core-YrU9LTTJ.js → mermaid-parser.core-D8h1T47v.js} +3 -3
  111. package/dist/web/assets/{mermaid.core-BkLEn5Vh.js → mermaid.core-CdpJws-B.js} +7 -7
  112. package/dist/web/assets/{mindmap-definition-NLK3R4M7-D22lxuXn.js → mindmap-definition-NLK3R4M7-B01KHb5Z.js} +1 -1
  113. package/dist/web/assets/{packet-AYTQ26CC-DMK0BdeB.js → packet-AYTQ26CC-CagxQ791.js} +1 -1
  114. package/dist/web/assets/pdf.worker-C2cQdohj.js +48 -0
  115. package/dist/web/assets/{pegDiagram-GJSIUBJH-B-e5e5cW.js → pegDiagram-GJSIUBJH-amhZRgGf.js} +1 -1
  116. package/dist/web/assets/{pie-WAS4IAKB-BYP5ND4b.js → pie-WAS4IAKB-zv3udP2i.js} +1 -1
  117. package/dist/web/assets/{pieDiagram-5QR66LMP-DrEY0oA_.js → pieDiagram-5QR66LMP-CjPzT-fa.js} +1 -1
  118. package/dist/web/assets/pinia-DCsnCHsH.js +1 -0
  119. package/dist/web/assets/pinia-DWj71cM9.js +1 -0
  120. package/dist/web/assets/{quadrantDiagram-O4NWA36T-D12UM_7Y.js → quadrantDiagram-O4NWA36T-7YpPBn0t.js} +1 -1
  121. package/dist/web/assets/{radar-RG4KPBEZ-50JpTuMu.js → radar-RG4KPBEZ-DZ-nSYpA.js} +1 -1
  122. package/dist/web/assets/{railroad-74A4TZTK-C-7jDcYE.js → railroad-74A4TZTK-CoylUO-u.js} +1 -1
  123. package/dist/web/assets/railroad-abnf-HS5TGJTU-C20Z1OCq.js +1 -0
  124. package/dist/web/assets/railroad-ebnf-LZEXJU2U-BltGHYDI.js +1 -0
  125. package/dist/web/assets/railroad-peg-WCYAUIDC-BT3QI-zw.js +1 -0
  126. package/dist/web/assets/{railroadDiagram-XR7U4H2S-q0-o99ts.js → railroadDiagram-XR7U4H2S-DlHZky0v.js} +1 -1
  127. package/dist/web/assets/{requirementDiagram-PLB6GJNP-DsO-Ejin.js → requirementDiagram-PLB6GJNP-Dn9whLhB.js} +1 -1
  128. package/dist/web/assets/runtime-core.esm-bundler-BKFI1w28.js +1 -0
  129. package/dist/web/assets/{safe-storage-DRouskD4.js → safe-storage-B3bJArYP.js} +1 -1
  130. package/dist/web/assets/{sankeyDiagram-IPEJSGJF-Ct9goD-6.js → sankeyDiagram-IPEJSGJF-jIf4UAnN.js} +1 -1
  131. package/dist/web/assets/{sequenceDiagram-PO4LG4MO-BMO_A2FU.js → sequenceDiagram-PO4LG4MO-CJahxeyg.js} +1 -1
  132. package/dist/web/assets/{stateDiagram-v2-GCMORJYK-BSzg0zQp.js → stateDiagram-v2-GCMORJYK-DArbI5q3.js} +2 -2
  133. package/dist/web/assets/{swimlanes-2SLR337P-CcmfFMci.js → swimlanes-2SLR337P-wyJPqm8c.js} +1 -1
  134. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-DgRybibH.js +8 -0
  135. package/dist/web/assets/theme-CFC7Mb_O.js +1 -0
  136. package/dist/web/assets/{timeline-definition-EJHVYXUP-0dcHdY2G.js → timeline-definition-EJHVYXUP-Bdh9ma4o.js} +1 -1
  137. package/dist/web/assets/{treeView-Q6P3EWNA-KynQIJ_Z.js → treeView-Q6P3EWNA-CEiXTwqS.js} +1 -1
  138. package/dist/web/assets/{treemap-WGGIJYW6-DPpYRzcH.js → treemap-WGGIJYW6-am0aIfU-.js} +1 -1
  139. package/dist/web/assets/use-app-theme-FYzHIigd.js +1 -0
  140. package/dist/web/assets/use-panel-width-e-yXva8i.js +1 -0
  141. package/dist/web/assets/use-server-events-znzHKZZx.js +1 -0
  142. package/dist/web/assets/use-virtual-scroll-CIwaJCUj.js +1 -0
  143. package/dist/web/assets/{usecaseDiagram-POWQR4AR-DJBtB_eN.js → usecaseDiagram-POWQR4AR-r3Qf5Bj-.js} +1 -1
  144. package/dist/web/assets/{vennDiagram-UO4OBE2U-BgllcrO_.js → vennDiagram-UO4OBE2U-eOWNPvLI.js} +1 -1
  145. package/dist/web/assets/{vue-i18n.runtime-D0pkhc7g.js → vue-i18n.runtime-BpRrzZrT.js} +2 -2
  146. package/dist/web/assets/{wardley-WFR3VGLG-D2Je-cZt.js → wardley-WFR3VGLG-BdipY77V.js} +1 -1
  147. package/dist/web/assets/{wardleyDiagram-VNRHLVJA-B7ktegci.js → wardleyDiagram-VNRHLVJA-sFpl0u9C.js} +1 -1
  148. package/dist/web/assets/{xychartDiagram-PMCCYNJV-DL7WkDZI.js → xychartDiagram-PMCCYNJV-CtLqW--F.js} +1 -1
  149. package/dist/web/index.html +7 -7
  150. package/dist/web/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  151. package/dist/web/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  152. package/dist/web/pdfjs/cmaps/78-H.bcmap +0 -0
  153. package/dist/web/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  154. package/dist/web/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  155. package/dist/web/pdfjs/cmaps/78-V.bcmap +0 -0
  156. package/dist/web/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  157. package/dist/web/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  158. package/dist/web/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  159. package/dist/web/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  160. package/dist/web/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  161. package/dist/web/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  162. package/dist/web/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  163. package/dist/web/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  164. package/dist/web/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  165. package/dist/web/pdfjs/cmaps/Add-H.bcmap +0 -0
  166. package/dist/web/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  167. package/dist/web/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  168. package/dist/web/pdfjs/cmaps/Add-V.bcmap +0 -0
  169. package/dist/web/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  170. package/dist/web/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  171. package/dist/web/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  172. package/dist/web/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  173. package/dist/web/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  174. package/dist/web/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  175. package/dist/web/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  176. package/dist/web/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  177. package/dist/web/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  178. package/dist/web/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  179. package/dist/web/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  180. package/dist/web/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  181. package/dist/web/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  182. package/dist/web/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  183. package/dist/web/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  184. package/dist/web/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  185. package/dist/web/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  186. package/dist/web/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  187. package/dist/web/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  188. package/dist/web/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  189. package/dist/web/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  190. package/dist/web/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  191. package/dist/web/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  192. package/dist/web/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  193. package/dist/web/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  194. package/dist/web/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  195. package/dist/web/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  196. package/dist/web/pdfjs/cmaps/B5-H.bcmap +0 -0
  197. package/dist/web/pdfjs/cmaps/B5-V.bcmap +0 -0
  198. package/dist/web/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  199. package/dist/web/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  200. package/dist/web/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  201. package/dist/web/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  202. package/dist/web/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  203. package/dist/web/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  204. package/dist/web/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  205. package/dist/web/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  206. package/dist/web/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  207. package/dist/web/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  208. package/dist/web/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  209. package/dist/web/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  210. package/dist/web/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  211. package/dist/web/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  212. package/dist/web/pdfjs/cmaps/EUC-H.bcmap +0 -0
  213. package/dist/web/pdfjs/cmaps/EUC-V.bcmap +0 -0
  214. package/dist/web/pdfjs/cmaps/Ext-H.bcmap +0 -0
  215. package/dist/web/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  216. package/dist/web/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  217. package/dist/web/pdfjs/cmaps/Ext-V.bcmap +0 -0
  218. package/dist/web/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  219. package/dist/web/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  220. package/dist/web/pdfjs/cmaps/GB-H.bcmap +4 -0
  221. package/dist/web/pdfjs/cmaps/GB-V.bcmap +0 -0
  222. package/dist/web/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  223. package/dist/web/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  224. package/dist/web/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  225. package/dist/web/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  226. package/dist/web/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  227. package/dist/web/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  228. package/dist/web/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  229. package/dist/web/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  230. package/dist/web/pdfjs/cmaps/GBT-H.bcmap +0 -0
  231. package/dist/web/pdfjs/cmaps/GBT-V.bcmap +0 -0
  232. package/dist/web/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  233. package/dist/web/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  234. package/dist/web/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  235. package/dist/web/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  236. package/dist/web/pdfjs/cmaps/H.bcmap +0 -0
  237. package/dist/web/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  238. package/dist/web/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  239. package/dist/web/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  240. package/dist/web/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  241. package/dist/web/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  242. package/dist/web/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  243. package/dist/web/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  244. package/dist/web/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  245. package/dist/web/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  246. package/dist/web/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  247. package/dist/web/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  248. package/dist/web/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  249. package/dist/web/pdfjs/cmaps/Hankaku.bcmap +0 -0
  250. package/dist/web/pdfjs/cmaps/Hiragana.bcmap +0 -0
  251. package/dist/web/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  252. package/dist/web/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  253. package/dist/web/pdfjs/cmaps/KSC-H.bcmap +0 -0
  254. package/dist/web/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  255. package/dist/web/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  256. package/dist/web/pdfjs/cmaps/KSC-V.bcmap +0 -0
  257. package/dist/web/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  258. package/dist/web/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  259. package/dist/web/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  260. package/dist/web/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  261. package/dist/web/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  262. package/dist/web/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  263. package/dist/web/pdfjs/cmaps/Katakana.bcmap +0 -0
  264. package/dist/web/pdfjs/cmaps/LICENSE +36 -0
  265. package/dist/web/pdfjs/cmaps/NWP-H.bcmap +0 -0
  266. package/dist/web/pdfjs/cmaps/NWP-V.bcmap +0 -0
  267. package/dist/web/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  268. package/dist/web/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  269. package/dist/web/pdfjs/cmaps/Roman.bcmap +0 -0
  270. package/dist/web/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  271. package/dist/web/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  272. package/dist/web/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  273. package/dist/web/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  274. package/dist/web/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  275. package/dist/web/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  276. package/dist/web/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  277. package/dist/web/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  278. package/dist/web/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  279. package/dist/web/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  280. package/dist/web/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  281. package/dist/web/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  282. package/dist/web/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  283. package/dist/web/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  284. package/dist/web/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  285. package/dist/web/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  286. package/dist/web/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  287. package/dist/web/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  288. package/dist/web/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  289. package/dist/web/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  290. package/dist/web/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  291. package/dist/web/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  292. package/dist/web/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  293. package/dist/web/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  294. package/dist/web/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  295. package/dist/web/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  296. package/dist/web/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  297. package/dist/web/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  298. package/dist/web/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  299. package/dist/web/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  300. package/dist/web/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  301. package/dist/web/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  302. package/dist/web/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  303. package/dist/web/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  304. package/dist/web/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  305. package/dist/web/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  306. package/dist/web/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  307. package/dist/web/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  308. package/dist/web/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  309. package/dist/web/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  310. package/dist/web/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  311. package/dist/web/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  312. package/dist/web/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  313. package/dist/web/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  314. package/dist/web/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  315. package/dist/web/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  316. package/dist/web/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  317. package/dist/web/pdfjs/cmaps/V.bcmap +0 -0
  318. package/dist/web/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  319. package/dist/web/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  320. package/dist/web/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  321. package/dist/web/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  322. package/dist/web/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  323. package/dist/web/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  324. package/dist/web/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  325. package/dist/web/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  326. package/dist/web/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  327. package/dist/web/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  328. package/dist/web/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  329. package/dist/web/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  330. package/dist/web/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
  331. package/dist/web/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  332. package/dist/web/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  333. package/dist/web/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  334. package/dist/web/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  335. package/dist/web/pdfjs/wasm/LICENSE_JBIG2 +196 -0
  336. package/dist/web/pdfjs/wasm/LICENSE_OPENJPEG +39 -0
  337. package/dist/web/pdfjs/wasm/LICENSE_PDFJS_JBIG2 +13 -0
  338. package/dist/web/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
  339. package/dist/web/pdfjs/wasm/LICENSE_PDFJS_QCMS +19 -0
  340. package/dist/web/pdfjs/wasm/LICENSE_QCMS +21 -0
  341. package/dist/web/pdfjs/wasm/jbig2.wasm +0 -0
  342. package/dist/web/pdfjs/wasm/jbig2_nowasm_fallback.js +15 -0
  343. package/dist/web/pdfjs/wasm/openjpeg.wasm +0 -0
  344. package/dist/web/pdfjs/wasm/openjpeg_nowasm_fallback.js +17 -0
  345. package/dist/web/pdfjs/wasm/qcms_bg.wasm +0 -0
  346. package/dist/web/pdfjs/wasm/quickjs-eval.js +16 -0
  347. package/dist/web/pdfjs/wasm/quickjs-eval.wasm +0 -0
  348. package/package.json +13 -3
  349. package/dist/web/assets/CreateReviewFabButton-UdzUa_ra.js +0 -1
  350. package/dist/web/assets/ErrorNotFound-BAP3n0aC.js +0 -1
  351. package/dist/web/assets/IndexPage-BtoeYBg2.js +0 -1
  352. package/dist/web/assets/IndexPage-D894ONbY.css +0 -1
  353. package/dist/web/assets/MainLayout-ChxXEt_Q.css +0 -1
  354. package/dist/web/assets/MainLayout-DjoItVv0.js +0 -7
  355. package/dist/web/assets/QBtn-BDbJU6_B.js +0 -1
  356. package/dist/web/assets/QMenu-Bh-SjGqz.js +0 -1
  357. package/dist/web/assets/QSelect-CZqAOLiG.js +0 -1
  358. package/dist/web/assets/ReviewDetailPage-CP6YqXPT.css +0 -1
  359. package/dist/web/assets/ReviewDetailPage-DvDoJ37c.js +0 -1
  360. package/dist/web/assets/ReviewsPage-CHUXLKKw.js +0 -1
  361. package/dist/web/assets/StatusSwitcher-Br_8-_CT.js +0 -2
  362. package/dist/web/assets/StatusSwitcher-DLU1h0rK.css +0 -1
  363. package/dist/web/assets/agent-icon-BUd-vCRS.css +0 -1
  364. package/dist/web/assets/agent-icon-Bc3evt8o.js +0 -17
  365. package/dist/web/assets/agent-store-FOng_RxK.js +0 -1
  366. package/dist/web/assets/app-info-store-xZiQfdgO.js +0 -1
  367. package/dist/web/assets/architecture-7GRP2DOG-JRGyT2Fi.js +0 -1
  368. package/dist/web/assets/channel-C9BIu6HY.js +0 -1
  369. package/dist/web/assets/chunk-3FUC2YCW-DM9oDQ0E.js +0 -2
  370. package/dist/web/assets/create-BkZH2Q0U.js +0 -1
  371. package/dist/web/assets/debounce-CDIE7CV6.js +0 -1
  372. package/dist/web/assets/eventmodeling-NTZA5JFV-DblBuyVi.js +0 -1
  373. package/dist/web/assets/export-file-Di_2jvjP.js +0 -1
  374. package/dist/web/assets/flowDiagram-KWPJA3E3-D5iLm5ip.js +0 -1
  375. package/dist/web/assets/i18n-DvBemXXT.js +0 -1
  376. package/dist/web/assets/index-Bn7ByP7x.js +0 -2
  377. package/dist/web/assets/index-CgeU6Wuu.css +0 -1
  378. package/dist/web/assets/init-app-C4PeiqQX.js +0 -1
  379. package/dist/web/assets/pinia-DB8WAJFa.js +0 -1
  380. package/dist/web/assets/pinia-DmTi2T44.js +0 -1
  381. package/dist/web/assets/railroad-abnf-HS5TGJTU-Bf3pkLrz.js +0 -1
  382. package/dist/web/assets/railroad-ebnf-LZEXJU2U-f10AkgiO.js +0 -1
  383. package/dist/web/assets/railroad-peg-WCYAUIDC-C4FjCCVf.js +0 -1
  384. package/dist/web/assets/runtime-core.esm-bundler-CUpq88z7.js +0 -1
  385. package/dist/web/assets/single-flight-r8B7stt1.js +0 -1
  386. package/dist/web/assets/swimlanesDiagram-TC7HE7FX-DLq4WqTk.js +0 -8
  387. package/dist/web/assets/theme-DQpF6lSZ.js +0 -1
  388. package/dist/web/assets/use-app-theme-B1Vh5WKT.js +0 -1
  389. package/dist/web/assets/use-panel-width-Cs_K2M_v.js +0 -1
  390. package/dist/web/assets/use-virtual-scroll-BVHTKnoC.js +0 -1
@@ -0,0 +1,167 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { MAX_TERMINAL_SESSIONS, TERMINAL_IDLE_MS, TERMINAL_ORPHAN_MS } from "../../../shared/terminal/constants.js";
3
+ import { TerminalLimitError, TerminalNotFoundError } from "../../errors/terminal.js";
4
+ import { createBackend } from "./backend.js";
5
+ import { processes } from "./processes.js";
6
+ import { TerminalSession } from "./session.js";
7
+ // Check the activity of every session this often.
8
+ const POLL_MS = 5000;
9
+ export class TerminalRegistry {
10
+ entries = new Map();
11
+ listeners = new Set();
12
+ closing = new Set();
13
+ poll;
14
+ creating = 0;
15
+ nextNumber = 1;
16
+ stopped = false;
17
+ checking = false;
18
+ options;
19
+ constructor(options) {
20
+ this.options = options;
21
+ this.poll = setInterval(() => {
22
+ void this.checkActivity();
23
+ }, options.pollMs ?? POLL_MS);
24
+ this.poll.unref();
25
+ }
26
+ get list() {
27
+ return [...this.entries.values()].map(entry => entry.session.info);
28
+ }
29
+ subscribe(listener) {
30
+ this.listeners.add(listener);
31
+ return () => this.listeners.delete(listener);
32
+ }
33
+ publish(message) {
34
+ for (const listener of this.listeners)
35
+ listener(message);
36
+ }
37
+ publishList() {
38
+ this.publish({ type: 'list', sessions: this.list });
39
+ }
40
+ now() {
41
+ return (this.options.now ?? Date.now)();
42
+ }
43
+ get(id) {
44
+ const entry = this.entries.get(id);
45
+ if (!entry)
46
+ throw new TerminalNotFoundError('Terminal session no longer exists.');
47
+ return entry.session;
48
+ }
49
+ async create(cols, rows) {
50
+ if (this.stopped)
51
+ throw new TerminalNotFoundError('Terminal server is stopping.');
52
+ if (this.entries.size + this.creating >= MAX_TERMINAL_SESSIONS)
53
+ throw new TerminalLimitError('The terminal limit has been reached.');
54
+ this.creating++;
55
+ try {
56
+ const backend = await (this.options.backend ?? createBackend)(this.options.repositoryPath, cols, rows);
57
+ if (this.stopped) {
58
+ await backend.kill();
59
+ throw new TerminalNotFoundError('Terminal server is stopping.');
60
+ }
61
+ const id = randomUUID();
62
+ const session = new TerminalSession(id, `Terminal ${this.nextNumber++}`, backend, cols, rows, message => this.publish(message), () => {
63
+ void this.close(id);
64
+ });
65
+ this.entries.set(id, {
66
+ session,
67
+ subscribers: new Set(),
68
+ orphanedSince: this.now(),
69
+ idleSince: null
70
+ });
71
+ this.options.onLifecycle?.({ type: 'created', id, mode: backend.mode });
72
+ this.publishList();
73
+ return session;
74
+ }
75
+ finally {
76
+ this.creating--;
77
+ }
78
+ }
79
+ attach(id, subscriber) {
80
+ const entry = this.entries.get(id);
81
+ if (!entry)
82
+ throw new TerminalNotFoundError('Terminal session no longer exists.');
83
+ entry.subscribers.add(subscriber);
84
+ entry.orphanedSince = null;
85
+ entry.idleSince = null;
86
+ }
87
+ detach(subscriber) {
88
+ for (const entry of this.entries.values()) {
89
+ if (!entry.subscribers.delete(subscriber) || entry.subscribers.size)
90
+ continue;
91
+ entry.orphanedSince = this.now();
92
+ entry.idleSince = entry.session.running === 'idle' ? this.now() : null;
93
+ }
94
+ }
95
+ async checkActivity() {
96
+ if (this.checking || this.stopped)
97
+ return;
98
+ this.checking = true;
99
+ try {
100
+ // One process list serves every session. A failed read leaves them unknown.
101
+ let rows;
102
+ if (this.entries.size > 0) {
103
+ try {
104
+ rows = await (this.options.listProcesses ?? processes)();
105
+ }
106
+ catch {
107
+ rows = undefined;
108
+ }
109
+ }
110
+ for (const [id, entry] of this.entries) {
111
+ const previous = entry.session.running;
112
+ entry.session.running =
113
+ rows === undefined
114
+ ? 'unknown'
115
+ : await entry.session.backend.activity(rows);
116
+ if (previous !== entry.session.running)
117
+ this.publishList();
118
+ if (entry.orphanedSince === null)
119
+ continue;
120
+ const now = this.now();
121
+ if (entry.session.running === 'idle') {
122
+ entry.idleSince ??= now;
123
+ }
124
+ else {
125
+ entry.idleSince = null;
126
+ }
127
+ if (now - entry.orphanedSince >= TERMINAL_ORPHAN_MS ||
128
+ (entry.idleSince !== null &&
129
+ now - entry.idleSince >= TERMINAL_IDLE_MS)) {
130
+ this.options.onLifecycle?.({ type: 'timeout', id });
131
+ await this.close(id);
132
+ }
133
+ }
134
+ }
135
+ finally {
136
+ this.checking = false;
137
+ }
138
+ }
139
+ async resize(id, cols, rows) {
140
+ await this.get(id).resize(cols, rows);
141
+ this.publishList();
142
+ }
143
+ async close(id) {
144
+ const entry = this.entries.get(id);
145
+ if (!entry)
146
+ return;
147
+ this.entries.delete(id);
148
+ this.options.onLifecycle?.({ type: 'closed', id });
149
+ this.publish({ type: 'exit', terminalId: id });
150
+ this.publishList();
151
+ const closing = entry.session.dispose();
152
+ this.closing.add(closing);
153
+ try {
154
+ await closing;
155
+ }
156
+ finally {
157
+ this.closing.delete(closing);
158
+ }
159
+ }
160
+ async dispose() {
161
+ this.stopped = true;
162
+ clearInterval(this.poll);
163
+ await Promise.all([...this.entries.keys()].map(id => this.close(id)));
164
+ await Promise.all(this.closing);
165
+ this.listeners.clear();
166
+ }
167
+ }
@@ -0,0 +1,238 @@
1
+ import headless from '@xterm/headless';
2
+ import { SerializeAddon } from '@xterm/addon-serialize';
3
+ import { TERMINAL_SCROLLBACK, TERMINAL_QUEUE_BYTES, TERMINAL_SNAPSHOT_BYTES, TERMINAL_FRAME_BYTES, TERMINAL_TITLE_LENGTH } from "../../../shared/terminal/constants.js";
4
+ import { EscapeTail } from "./escape-tail.js";
5
+ import { TerminalError } from "../../errors/terminal.js";
6
+ // Join output that arrives within one display frame (about 60 Hz) into one write.
7
+ const FLUSH_DELAY_MS = 16;
8
+ // One UTF-16 unit takes up to 3 UTF-8 bytes. A divisor of 4 keeps a part below the frame limit.
9
+ const FRAME_PART_UNITS = TERMINAL_FRAME_BYTES / 4;
10
+ // Close the session when the parser queue grows to twice the pause limit.
11
+ const QUEUE_CLOSE_BYTES = TERMINAL_QUEUE_BYTES * 2;
12
+ // Resume the backend only after the parser queue falls well below the pause limit.
13
+ const QUEUE_RESUME_BYTES = TERMINAL_QUEUE_BYTES / 4;
14
+ // A high surrogate starts a pair. A part must not end after it.
15
+ const HIGH_SURROGATE_FIRST = 0xd800;
16
+ const HIGH_SURROGATE_LAST = 0xdbff;
17
+ export class TerminalSession {
18
+ terminal;
19
+ serializer;
20
+ tail = new EscapeTail();
21
+ queue = Promise.resolve();
22
+ pendingBytes = 0;
23
+ staged = '';
24
+ flushTimer;
25
+ blocked = new Set();
26
+ sequence = 0;
27
+ stopped = false;
28
+ disposers = [];
29
+ title;
30
+ cols;
31
+ rows;
32
+ running = 'unknown';
33
+ id;
34
+ backend;
35
+ publish;
36
+ onExit;
37
+ constructor(id, title, backend, cols, rows, publish, onExit) {
38
+ this.id = id;
39
+ this.backend = backend;
40
+ this.publish = publish;
41
+ this.onExit = onExit;
42
+ this.title = title;
43
+ this.cols = cols;
44
+ this.rows = rows;
45
+ this.terminal = new headless.Terminal({
46
+ cols,
47
+ rows,
48
+ scrollback: TERMINAL_SCROLLBACK,
49
+ allowProposedApi: true
50
+ });
51
+ this.serializer = new SerializeAddon();
52
+ this.terminal.loadAddon(this.serializer);
53
+ this.disposers.push(backend.onData(data => this.output(data)), backend.onExit(onExit));
54
+ // Only the server answers terminal queries, even with multiple browsers.
55
+ this.disposers.push(() => this.terminal.dispose());
56
+ this.terminal.onData(data => {
57
+ if (!this.stopped && backend.mode === 'pty')
58
+ backend.write(data);
59
+ });
60
+ this.terminal.onTitleChange(shellTitle => {
61
+ this.title = shellTitle.slice(0, TERMINAL_TITLE_LENGTH);
62
+ publish({ type: 'title', terminalId: id, title: this.title });
63
+ });
64
+ }
65
+ get info() {
66
+ return {
67
+ id: this.id,
68
+ title: this.title,
69
+ cols: this.cols,
70
+ rows: this.rows,
71
+ mode: this.backend.mode,
72
+ running: this.running
73
+ };
74
+ }
75
+ output(data) {
76
+ if (this.stopped)
77
+ return;
78
+ this.staged += data;
79
+ if (Buffer.byteLength(this.staged) >= TERMINAL_FRAME_BYTES) {
80
+ this.flush();
81
+ }
82
+ else {
83
+ this.flushTimer ??= setTimeout(() => this.flush(), FLUSH_DELAY_MS);
84
+ }
85
+ }
86
+ flush() {
87
+ clearTimeout(this.flushTimer);
88
+ this.flushTimer = undefined;
89
+ const data = this.staged;
90
+ this.staged = '';
91
+ if (data)
92
+ this.enqueue(data);
93
+ }
94
+ enqueue(data) {
95
+ if (this.stopped)
96
+ return undefined;
97
+ // Bound each frame by UTF-8 bytes without splitting a surrogate pair.
98
+ for (let offset = 0; offset < data.length;) {
99
+ let end = Math.min(data.length, offset + FRAME_PART_UNITS);
100
+ if (end < data.length &&
101
+ data.charCodeAt(end - 1) >= HIGH_SURROGATE_FIRST &&
102
+ data.charCodeAt(end - 1) <= HIGH_SURROGATE_LAST)
103
+ end--;
104
+ const part = data.slice(offset, end);
105
+ offset = end;
106
+ const bytes = Buffer.byteLength(part);
107
+ this.pendingBytes += bytes;
108
+ if (this.pendingBytes > TERMINAL_QUEUE_BYTES)
109
+ this.updateFlow();
110
+ if (this.pendingBytes > QUEUE_CLOSE_BYTES) {
111
+ this.onExit();
112
+ return;
113
+ }
114
+ this.queue = this.queue
115
+ .then(async () => {
116
+ if (this.stopped)
117
+ return undefined;
118
+ this.tail.push(part);
119
+ await new Promise(resolve => this.terminal.write(part, resolve));
120
+ this.sequence++;
121
+ this.publish({
122
+ type: 'output',
123
+ terminalId: this.id,
124
+ data: part,
125
+ cols: this.cols,
126
+ rows: this.rows,
127
+ sequence: this.sequence
128
+ });
129
+ return undefined;
130
+ })
131
+ .catch(() => this.onExit())
132
+ .finally(() => {
133
+ this.pendingBytes -= bytes;
134
+ if (!this.stopped && this.pendingBytes < QUEUE_RESUME_BYTES)
135
+ this.updateFlow();
136
+ });
137
+ }
138
+ }
139
+ block(owner, blocked) {
140
+ if (blocked) {
141
+ this.blocked.add(owner);
142
+ }
143
+ else {
144
+ this.blocked.delete(owner);
145
+ }
146
+ this.updateFlow();
147
+ }
148
+ updateFlow() {
149
+ if (this.stopped)
150
+ return;
151
+ if (this.blocked.size || this.pendingBytes > TERMINAL_QUEUE_BYTES) {
152
+ this.backend.pause();
153
+ }
154
+ else {
155
+ this.backend.resume();
156
+ }
157
+ }
158
+ snapshot(send) {
159
+ this.flush();
160
+ this.queue = this.queue.then(() => {
161
+ if (this.stopped)
162
+ return undefined;
163
+ let scrollback = TERMINAL_SCROLLBACK;
164
+ let data = this.serializer.serialize({ scrollback }) + this.tail.tail;
165
+ while (Buffer.byteLength(data) > TERMINAL_SNAPSHOT_BYTES &&
166
+ scrollback > 0) {
167
+ scrollback = Math.floor(scrollback / 2);
168
+ data = this.serializer.serialize({ scrollback }) + this.tail.tail;
169
+ }
170
+ if (Buffer.byteLength(data) > TERMINAL_SNAPSHOT_BYTES)
171
+ throw new TerminalError('Terminal snapshot exceeds its limit.');
172
+ send({
173
+ type: 'snapshot',
174
+ terminalId: this.id,
175
+ data,
176
+ cols: this.cols,
177
+ rows: this.rows,
178
+ sequence: this.sequence
179
+ });
180
+ return undefined;
181
+ });
182
+ return this.queue;
183
+ }
184
+ write(data) {
185
+ if (!this.stopped)
186
+ this.backend.write(data);
187
+ }
188
+ line(data) {
189
+ if (this.backend.mode !== 'pipe')
190
+ throw new TerminalError('Line input requires limited mode.');
191
+ this.output(data.replace(/\n/g, '\r\n') + '\r\n');
192
+ this.backend.write(data + '\n');
193
+ }
194
+ signal(signal) {
195
+ if (signal === 'interrupt') {
196
+ this.backend.interrupt();
197
+ }
198
+ else {
199
+ this.onExit();
200
+ }
201
+ }
202
+ resize(cols, rows) {
203
+ this.flush();
204
+ this.queue = this.queue
205
+ .then(() => {
206
+ if (this.stopped || (cols === this.cols && rows === this.rows))
207
+ return undefined;
208
+ this.backend.resize(cols, rows);
209
+ this.terminal.resize(cols, rows);
210
+ this.cols = cols;
211
+ this.rows = rows;
212
+ // Order geometry changes with output so every viewer parses the same screen.
213
+ this.sequence++;
214
+ this.publish({
215
+ type: 'output',
216
+ terminalId: this.id,
217
+ data: '',
218
+ cols,
219
+ rows,
220
+ sequence: this.sequence
221
+ });
222
+ return undefined;
223
+ })
224
+ .catch(() => this.onExit());
225
+ return this.queue;
226
+ }
227
+ async dispose() {
228
+ if (this.stopped)
229
+ return undefined;
230
+ this.stopped = true;
231
+ clearTimeout(this.flushTimer);
232
+ this.staged = '';
233
+ await this.backend.kill();
234
+ await this.queue.catch(() => { });
235
+ for (const dispose of this.disposers)
236
+ dispose();
237
+ }
238
+ }
@@ -0,0 +1,74 @@
1
+ import { TERMINAL_SNAPSHOT_BYTES } from "../../../shared/terminal/constants.js";
2
+ import { TerminalChannel } from "./channel.js";
3
+ // Detect a dead peer: close the socket after one missed pong.
4
+ const HEARTBEAT_MS = 30_000;
5
+ function rawDataToText(data) {
6
+ if (Buffer.isBuffer(data))
7
+ return data.toString('utf8');
8
+ if (data instanceof ArrayBuffer)
9
+ return Buffer.from(data).toString('utf8');
10
+ return Buffer.concat(data).toString('utf8');
11
+ }
12
+ export function attachTerminalSocket(socket, registry, options = {}) {
13
+ let alive = true;
14
+ // oxlint-disable-next-line prefer-const -- close captures the channel while its constructor sends the first message
15
+ let channel;
16
+ const close = () => {
17
+ channel?.dispose();
18
+ socket.terminate();
19
+ };
20
+ const send = (message) => {
21
+ if (socket.readyState !== socket.OPEN)
22
+ return;
23
+ // A slow client must not make the server buffer without limit.
24
+ if (socket.bufferedAmount > TERMINAL_SNAPSHOT_BYTES) {
25
+ close();
26
+ return;
27
+ }
28
+ socket.send(JSON.stringify(message), error => {
29
+ if (error)
30
+ close();
31
+ });
32
+ };
33
+ channel = new TerminalChannel(registry, send, close);
34
+ socket.on('message', (data, binary) => {
35
+ // The protocol uses text frames only.
36
+ if (binary) {
37
+ close();
38
+ return;
39
+ }
40
+ try {
41
+ // The channel checks the shape of the message.
42
+ channel?.receive(JSON.parse(rawDataToText(data)));
43
+ }
44
+ catch {
45
+ close();
46
+ }
47
+ });
48
+ socket.on('pong', () => {
49
+ alive = true;
50
+ });
51
+ const heartbeat = setInterval(() => {
52
+ if (!alive) {
53
+ close();
54
+ return;
55
+ }
56
+ // The next pong must arrive before the next tick.
57
+ alive = false;
58
+ socket.ping();
59
+ }, options.heartbeatMs ?? HEARTBEAT_MS);
60
+ heartbeat.unref();
61
+ let ended = false;
62
+ // Both `close` and `error` events call this function, so it must run only once.
63
+ const cleanup = () => {
64
+ if (ended)
65
+ return;
66
+ ended = true;
67
+ clearInterval(heartbeat);
68
+ channel?.dispose();
69
+ options.onDisconnect?.();
70
+ };
71
+ socket.on('close', cleanup);
72
+ socket.on('error', cleanup);
73
+ return { close };
74
+ }
@@ -0,0 +1,32 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { TERMINAL_TOKEN_MS } from "../../../shared/terminal/constants.js";
3
+ // Bound token memory when a page repeatedly requests credentials.
4
+ const MAX_TOKENS = 256;
5
+ export class TerminalTokens {
6
+ tokens = new Map();
7
+ now;
8
+ constructor(now = Date.now) {
9
+ this.now = now;
10
+ }
11
+ issue(origin) {
12
+ this.prune();
13
+ if (this.tokens.size >= MAX_TOKENS)
14
+ this.tokens.delete(this.tokens.keys().next().value ?? '');
15
+ const token = randomBytes(32).toString('base64url');
16
+ this.tokens.set(token, { origin, expires: this.now() + TERMINAL_TOKEN_MS });
17
+ return token;
18
+ }
19
+ consume(token, origin) {
20
+ const info = this.tokens.get(token);
21
+ this.tokens.delete(token);
22
+ return !!info && info.expires > this.now() && info.origin === origin;
23
+ }
24
+ prune() {
25
+ for (const [token, info] of this.tokens)
26
+ if (info.expires <= this.now())
27
+ this.tokens.delete(token);
28
+ }
29
+ clear() {
30
+ this.tokens.clear();
31
+ }
32
+ }
@@ -1,11 +1,82 @@
1
+ // Each connection must wait for backpressure before sending its next message.
2
+ const SEND_QUEUES = new WeakMap();
1
3
  /**
2
- * Sends one SSE message and never fails. A client can leave at any time, and
3
- * `send` then rejects. Nothing awaits that promise, so Node would end the
4
- * process on the unhandled rejection.
4
+ * The maximum number of messages in the send queue of one connection. On a
5
+ * reconnect the agent stream replays every session buffer at once:
6
+ * MAX_AGENT_SESSIONS × MAX_BUFFERED_EVENTS, plus a `gap` and a `state` for
7
+ * each session. A lower limit closes the stream before its first send, and the
8
+ * client then reconnects in a loop. The limit is about twice that replay, so
9
+ * live events have room. This count bounds memory. There is no byte limit:
10
+ * most queued items point to events that the session buffers hold already (#85).
5
11
  */
12
+ export const MAX_QUEUED_MESSAGES = 50_000;
13
+ /**
14
+ * A send that has not finished after this time means that the client stopped
15
+ * reading. The check runs when a new message arrives, so it closes a dead
16
+ * connection before the count limit does. A slow client that still reads
17
+ * finishes each send sooner. The
18
+ * check uses `Date.now()`: a jump of the system clock can make a false stall,
19
+ * and the client then only reconnects.
20
+ */
21
+ export const STALL_TIMEOUT_MS = 30_000;
22
+ /**
23
+ * Closes a connection that does not read, and drops its queue. Dropping only
24
+ * some messages would hide the loss from the client. The client reconnects by
25
+ * itself: the agent stream resumes from `Last-Event-ID` or gets `gap`, and
26
+ * `/api/events` refetches after the reconnect. Nothing is logged: the Fastify
27
+ * logger is off, and the reconnect is the normal recovery (#85).
28
+ */
29
+ function dropConnection(sse, queue) {
30
+ if (queue.closed)
31
+ return;
32
+ queue.closed = true;
33
+ queue.messages.length = 0;
34
+ try {
35
+ sse.close();
36
+ }
37
+ catch {
38
+ // The connection is already gone.
39
+ }
40
+ }
41
+ async function flush(sse, queue) {
42
+ queue.sending = true;
43
+ try {
44
+ while (!queue.closed && sse.isConnected && queue.messages.length) {
45
+ queue.sendStartedAt = Date.now();
46
+ await sse.send(queue.messages.shift());
47
+ }
48
+ }
49
+ catch {
50
+ // A disconnected client must not cause an unhandled rejection.
51
+ }
52
+ finally {
53
+ queue.messages.length = 0;
54
+ queue.sending = false;
55
+ }
56
+ }
57
+ /** Queue messages in order without concurrent writes to a slow client. */
6
58
  export function safeSend(sse, message) {
7
- if (!sse.isConnected) {
59
+ if (!sse.isConnected)
60
+ return;
61
+ let queue = SEND_QUEUES.get(sse);
62
+ if (!queue) {
63
+ queue = { messages: [], sending: false, closed: false, sendStartedAt: 0 };
64
+ SEND_QUEUES.set(sse, queue);
65
+ const connectionQueue = queue;
66
+ sse.onClose?.(() => {
67
+ connectionQueue.closed = true;
68
+ connectionQueue.messages.length = 0;
69
+ });
70
+ }
71
+ if (queue.closed)
72
+ return;
73
+ // The queue grows only here, so the limits are checked only here.
74
+ if (queue.messages.length >= MAX_QUEUED_MESSAGES ||
75
+ (queue.sending && Date.now() - queue.sendStartedAt > STALL_TIMEOUT_MS)) {
76
+ dropConnection(sse, queue);
8
77
  return;
9
78
  }
10
- sse.send(message).catch(() => { });
79
+ queue.messages.push(message);
80
+ if (!queue.sending)
81
+ void flush(sse, queue);
11
82
  }
@@ -1,3 +1,4 @@
1
+ import { isDirectoryPath, withoutTrailingSlash } from "./mention-paths.js";
1
2
  function isSubsequence(needle, haystack) {
2
3
  let at = 0;
3
4
  for (const char of haystack) {
@@ -13,7 +14,16 @@ function isSubsequence(needle, haystack) {
13
14
  /** Lower is better; undefined = no match. */
14
15
  function score(path, query) {
15
16
  const lower = path.toLowerCase();
16
- const base = lower.slice(lower.lastIndexOf('/') + 1);
17
+ // A query that ends with `/` names a directory: `web/` or `src/web/` puts
18
+ // `src/web/` first, above the files in it.
19
+ if (query.endsWith('/') && isDirectoryPath(lower) && lower.endsWith(query)) {
20
+ const before = lower.length - query.length;
21
+ if (before === 0 || lower[before - 1] === '/')
22
+ return 0;
23
+ }
24
+ // The name of a directory is taken without the `/`: with it, the name is empty.
25
+ const name = withoutTrailingSlash(lower);
26
+ const base = name.slice(name.lastIndexOf('/') + 1);
17
27
  if (base === query)
18
28
  return 0;
19
29
  if (base.startsWith(query))
@@ -27,21 +37,23 @@ function score(path, query) {
27
37
  return undefined;
28
38
  }
29
39
  /**
30
- * Orders `files` by how well they match `query`: file name first (exact,
31
- * prefix, substring), then anywhere in the path, then as scattered letters.
32
- * Ties go to the shorter path. An empty query matches everything.
40
+ * Orders `paths` by how well they match `query`: name first (exact, prefix,
41
+ * substring), then anywhere in the path, then as scattered letters. Ties go
42
+ * to a file before a directory, then to the shorter path. An empty query
43
+ * matches everything, so files lead the list and short directories do not.
33
44
  */
34
- export function rankFiles(files, query) {
45
+ export function rankPaths(paths, query) {
35
46
  const needle = query.trim().toLowerCase();
36
47
  const scored = [];
37
- for (const path of files) {
48
+ for (const path of paths) {
38
49
  const rank = needle === '' ? 0 : score(path, needle);
39
50
  if (rank !== undefined) {
40
- scored.push({ path, rank });
51
+ scored.push({ path, rank, directory: isDirectoryPath(path) ? 1 : 0 });
41
52
  }
42
53
  }
43
54
  return scored
44
55
  .sort((a, b) => a.rank - b.rank ||
56
+ a.directory - b.directory ||
45
57
  a.path.length - b.path.length ||
46
58
  a.path.localeCompare(b.path))
47
59
  .map(entry => entry.path);
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A mentioned path that ends with `/` names a directory, as in agent CLIs.
3
+ * The context item keeps the path without that `/` (#80).
4
+ */
5
+ export function isDirectoryPath(path) {
6
+ return path.endsWith('/');
7
+ }
8
+ /** The path without trailing `/`: the form that a `directory` context item holds. */
9
+ export function withoutTrailingSlash(path) {
10
+ return path.replace(/\/+$/, '');
11
+ }
12
+ /**
13
+ * The files and every directory that holds one of them, a directory as `a/b/`.
14
+ * `git ls-files --others` already gives an untracked nested repository as
15
+ * `dir/`, so a path with a trailing `/` is a directory too.
16
+ */
17
+ export function withDirectories(paths) {
18
+ const directories = new Set();
19
+ for (const path of paths) {
20
+ const parts = withoutTrailingSlash(path).split('/');
21
+ const last = isDirectoryPath(path) ? parts.length : parts.length - 1;
22
+ for (let end = 1; end <= last; end++) {
23
+ directories.add(`${parts.slice(0, end).join('/')}/`);
24
+ }
25
+ }
26
+ directories.delete('/');
27
+ return [...paths.filter(path => !isDirectoryPath(path)), ...directories];
28
+ }
29
+ /** Name, parent path and kind of a mentioned path, for chips and the popup. */
30
+ export function mentionParts(path) {
31
+ const directory = isDirectoryPath(path);
32
+ const bare = withoutTrailingSlash(path);
33
+ const at = bare.lastIndexOf('/');
34
+ const name = at === -1 ? bare : bare.slice(at + 1);
35
+ return {
36
+ name: directory ? `${name}/` : name,
37
+ parent: at === -1 ? '' : bare.slice(0, at),
38
+ directory
39
+ };
40
+ }