@hienlh/ppm 0.21.0 → 0.22.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 (606) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CLAUDE.md +29 -4
  3. package/THIRD-PARTY-NOTICES.md +1 -1
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/http-api.md +1 -1
  6. package/bun.lock +16 -1
  7. package/dist/web/assets/accounts-settings-section-BVr9-FZJ.js +1 -0
  8. package/dist/web/assets/{ai-resource-editor-F09pf8Hd.js → ai-resource-editor-CdDpOwjh.js} +1 -1
  9. package/dist/web/assets/ai-settings-section-B1hQyjAd.js +1 -0
  10. package/dist/web/assets/ai-settings-section-X2UOfSaS.js +1 -0
  11. package/dist/web/assets/{angular-html-DkR4iCth.js → angular-html-G66FNlyn.js} +1 -1
  12. package/dist/web/assets/{angular-ts-DcyL4Chx.js → angular-ts-B-PnUMyt.js} +1 -1
  13. package/dist/web/assets/{api-client-bfB7cXRM.js → api-client-C1ZwRL4Y.js} +1 -1
  14. package/dist/web/assets/api-client-pQL53cDQ.js +1 -0
  15. package/dist/web/assets/{api-settings-BcNjZIvG.js → api-settings-_7Q-ULxp.js} +1 -1
  16. package/dist/web/assets/{api-themes-DBfyVUtX.js → api-themes-DRAERo4Q.js} +1 -1
  17. package/dist/web/assets/{apl-BVlrVtjt.js → apl-BmDqqwE8.js} +1 -1
  18. package/dist/web/assets/{appearance-settings-section-whgnq3ss.js → appearance-settings-section-DLoPK0LD.js} +1 -1
  19. package/dist/web/assets/architecture-PBZL5I3N-Dx6nBEKT.js +1 -0
  20. package/dist/web/assets/{astro-MjJlozLj.js → astro-BfwMNfxr.js} +1 -1
  21. package/dist/web/assets/{audio-preview-Bc54GCAV.js → audio-preview-DsYvgKqj.js} +1 -1
  22. package/dist/web/assets/{blade-DnJWi1fz.js → blade-BsFV4pnN.js} +1 -1
  23. package/dist/web/assets/branch-review-tab-G84Ojzae.js +1 -0
  24. package/dist/web/assets/{bsl-DCmJxSIz.js → bsl-Cw9Outng.js} +1 -1
  25. package/dist/web/assets/{button-VW4QGKCn.js → button-B9CTrsEf.js} +1 -1
  26. package/dist/web/assets/{cairo-C8dDenub.js → cairo-DOK5Wqnd.js} +1 -1
  27. package/dist/web/assets/chat-tab-DQJfjm8E.js +15 -0
  28. package/dist/web/assets/{cobol-DPx_nifl.js → cobol-DRaRy4FK.js} +1 -1
  29. package/dist/web/assets/code-editor-D04iUPsZ.js +7 -0
  30. package/dist/web/assets/{codex-daily-guard-CfybHv_c.js → codex-daily-guard-BSccLRGT.js} +1 -1
  31. package/dist/web/assets/{coffee-DvFzqhgN.js → coffee-DvXuBmEJ.js} +1 -1
  32. package/dist/web/assets/{conflict-editor-DenQUOT2.js → conflict-editor-C4ay676e.js} +2 -2
  33. package/dist/web/assets/context-menu-C6pIsf83.js +1 -0
  34. package/dist/web/assets/{cpp-BLJN0umi.js → cpp-zCmm8rKA.js} +1 -1
  35. package/dist/web/assets/{crystal-BXR-9fGw.js → crystal-CdQx6lB6.js} +1 -1
  36. package/dist/web/assets/{csv-preview-CClrJOuR.js → csv-preview-DbwEmG7o.js} +1 -1
  37. package/dist/web/assets/{data-grid-overlay-editor-D4fhcMfT.js → data-grid-overlay-editor-6xUCeLHh.js} +1 -1
  38. package/dist/web/assets/{database-viewer-DItjm8wB.js → database-viewer-S6LsALjX.js} +1 -1
  39. package/dist/web/assets/{dialog-DQTn55Mt.js → dialog-B84_DJQ8.js} +1 -1
  40. package/dist/web/assets/diff-viewer-3oBA7vgQ.js +4 -0
  41. package/dist/web/assets/{docx-preview-DYBkiUaE.js → docx-preview-CXuk-emm.js} +1 -1
  42. package/dist/web/assets/{edge-rWOlCmXT.js → edge-E8QLt7oi.js} +1 -1
  43. package/dist/web/assets/{edit-diff-preview-COtmo54G.js → edit-diff-preview-BGGGecVw.js} +1 -1
  44. package/dist/web/assets/{editor-language-service-DxkZ51E5.js → editor-language-service-DJmaKVY0.js} +1 -1
  45. package/dist/web/assets/{elixir-E7Y82okW.js → elixir-DPb_-72P.js} +1 -1
  46. package/dist/web/assets/{elm-DTPGbc-w.js → elm-BMzJY5b0.js} +1 -1
  47. package/dist/web/assets/{erb-DHqLHrE6.js → erb-AABQNtMK.js} +1 -1
  48. package/dist/web/assets/{erlang-Byg0nNiV.js → erlang-CEIKOkfP.js} +1 -1
  49. package/dist/web/assets/{esm-D_DmOM7n.js → esm-B9RVMpwV.js} +1 -1
  50. package/dist/web/assets/explorer-body-BQ0jyAGv.js +2 -0
  51. package/dist/web/assets/explorer-window-content-jdkWt34J.js +1 -0
  52. package/dist/web/assets/{extension-manager-section-BhwB6nqr.js → extension-manager-section-DG1guILi.js} +1 -1
  53. package/dist/web/assets/{extension-webview-DI4VL2tJ.js → extension-webview-BGSDUbdK.js} +1 -1
  54. package/dist/web/assets/file-icons-BXdvggK2.js +1 -0
  55. package/dist/web/assets/file-icons.generated-BtBErEwb.css +1 -0
  56. package/dist/web/assets/file-store-ojgMXgAw.js +1 -0
  57. package/dist/web/assets/files-settings-section-Gnyd7han.js +1 -0
  58. package/dist/web/assets/{format-file-meta-DOpzJJhS.js → format-file-meta-C77h04cZ.js} +1 -1
  59. package/dist/web/assets/{fortran-fixed-form-CY1n0Xf6.js → fortran-fixed-form-CrXvLHyi.js} +1 -1
  60. package/dist/web/assets/{fsharp-_vEDSCJz.js → fsharp-DCI4qAz_.js} +1 -1
  61. package/dist/web/assets/{gdresource-B6XT0wAC.js → gdresource-Vb2kO1MG.js} +1 -1
  62. package/dist/web/assets/{general-settings-section-BcxwiZ0X.js → general-settings-section-V6Khh15V.js} +1 -1
  63. package/dist/web/assets/{git-commit-Wk9ovq59.js → git-commit-BMI_OWn-.js} +1 -1
  64. package/dist/web/assets/git-log-panel-Bi513qjT.js +1 -0
  65. package/dist/web/assets/{git-rebase-DSarGPvK.js → git-rebase-D-d_2iqZ.js} +1 -1
  66. package/dist/web/assets/{git-repo-store-Dy69YCHN.js → git-repo-store-88Z8v-pF.js} +1 -1
  67. package/dist/web/assets/gitGraph-HDMCJU4V-C6qRruHI.js +1 -0
  68. package/dist/web/assets/{glide-data-grid-BvgAqSUr.js → glide-data-grid-DnQwYSmQ.js} +7 -7
  69. package/dist/web/assets/{glimmer-js-CkYI35_P.js → glimmer-js-BmNNiR0Z.js} +1 -1
  70. package/dist/web/assets/{glimmer-ts-BqmIZcds.js → glimmer-ts-DQE2Ufha.js} +1 -1
  71. package/dist/web/assets/{glsl-Be-yBCF5.js → glsl-BcIDsxRJ.js} +1 -1
  72. package/dist/web/assets/{graphql-a5DrENHY.js → graphql-DGKSLmGH.js} +1 -1
  73. package/dist/web/assets/group-chat-tab-DpczdwyV.js +1 -0
  74. package/dist/web/assets/{hack-BLAg20aD.js → hack-YiC8bEIb.js} +1 -1
  75. package/dist/web/assets/{haml-Dj6bGOQH.js → haml-DjblxAsz.js} +1 -1
  76. package/dist/web/assets/{handlebars-dmEa36Oh.js → handlebars-BQepOXaU.js} +1 -1
  77. package/dist/web/assets/{html-yacZYzSE.js → html-Cayj_2GP.js} +1 -1
  78. package/dist/web/assets/{html-derivative-lLi3-RNT.js → html-derivative-Weq79bL6.js} +1 -1
  79. package/dist/web/assets/{http-1KaxcE4e.js → http-BeLfdGdz.js} +1 -1
  80. package/dist/web/assets/{hurl-ST-dEEZR.js → hurl-D9rypCLK.js} +1 -1
  81. package/dist/web/assets/{hxml-CpDtuq__.js → hxml-clG8mmSH.js} +1 -1
  82. package/dist/web/assets/{image-preview-CJVxL0uH.js → image-preview-CzCc4_Do.js} +1 -1
  83. package/dist/web/assets/image-preview-geometry-9lgeLYB3.js +1 -0
  84. package/dist/web/assets/index-BH99TIE0.css +1 -0
  85. package/dist/web/assets/index-DU68xk06.js +52 -0
  86. package/dist/web/assets/info-3K5VOQVL-BOSpdwlz.js +1 -0
  87. package/dist/web/assets/{jinja-Cjw-evAn.js → jinja-BShBWfs5.js} +1 -1
  88. package/dist/web/assets/{jira-watcher-section-DIC1yIV3.js → jira-watcher-section-f2HUbGDY.js} +1 -1
  89. package/dist/web/assets/{jison-DfATMYoY.js → jison-dN2Nf_Dr.js} +1 -1
  90. package/dist/web/assets/{julia-DDCaxiqT.js → julia-5U0vR-uJ.js} +1 -1
  91. package/dist/web/assets/{just-CJL529u6.js → just-BMppVRy6.js} +1 -1
  92. package/dist/web/assets/keybindings-store-DCRsnKg5.js +1 -0
  93. package/dist/web/assets/{keyboard-shortcuts-section-BW03P3AF.js → keyboard-shortcuts-section-BpvVEw_Y.js} +1 -1
  94. package/dist/web/assets/{latex-Zc4EpLpW.js → latex-DSSOKjis.js} +1 -1
  95. package/dist/web/assets/{liquid-B_6KMNYS.js → liquid-C24dKoyI.js} +1 -1
  96. package/dist/web/assets/{lua-BtS2idK7.js → lua-DNOsnyhn.js} +1 -1
  97. package/dist/web/assets/markdown-renderer-Bcaf9_bA.js +1 -0
  98. package/dist/web/assets/markdown-renderer-DGJp8HC3.js +3 -0
  99. package/dist/web/assets/{marko-Wk6LBnck.js → marko-26MHO_Q9.js} +1 -1
  100. package/dist/web/assets/{mdc-cRaXBrzL.js → mdc-Dhw9mGrC.js} +1 -1
  101. package/dist/web/assets/{media-url-DL8OCGGg.js → media-url-yfqHpPz0.js} +1 -1
  102. package/dist/web/assets/{mobile-bottom-sheet-CoUauvLF.js → mobile-bottom-sheet-C3TniEP-.js} +1 -1
  103. package/dist/web/assets/mobile-explorer-sheet-Bol3XAO1.js +1 -0
  104. package/dist/web/assets/{nextflow-Bos2wvDV.js → nextflow-xH4oJTex.js} +1 -1
  105. package/dist/web/assets/{nginx-DMlOwzdr.js → nginx-CMz4w0M8.js} +1 -1
  106. package/dist/web/assets/{nim-D2dZQA88.js → nim-D8FnIYsx.js} +1 -1
  107. package/dist/web/assets/notification-store-BXt54VIn.js +1 -0
  108. package/dist/web/assets/{notifications-settings-section-DPUROMw-.js → notifications-settings-section-DqlPDfNX.js} +1 -1
  109. package/dist/web/assets/{number-overlay-editor-DZ2i_Bnb.js → number-overlay-editor-CRGwULhR.js} +1 -1
  110. package/dist/web/assets/{overview-panel-R146OIom.js → overview-panel-Di-3aV9p.js} +1 -1
  111. package/dist/web/assets/packet-RMMSAZCW-BGIE1QdU.js +1 -0
  112. package/dist/web/assets/panel-store-CboGJAwh.js +1 -0
  113. package/dist/web/assets/{pdf-preview-BWUPGLp6.js → pdf-preview-CmBI6E7d.js} +1 -1
  114. package/dist/web/assets/{perl-C5-Z6fPn.js → perl-D9iNWYwb.js} +1 -1
  115. package/dist/web/assets/{php-BXNNhkMH.js → php-Bw3Qo0Zv.js} +1 -1
  116. package/dist/web/assets/pie-UPGHQEXC-Dg-QTbXX.js +1 -0
  117. package/dist/web/assets/{postgres-viewer-CjOvaruz.js → postgres-viewer-8ZAqEALv.js} +3 -3
  118. package/dist/web/assets/{ppmbot-settings-section-DrvWh8l8.js → ppmbot-settings-section-DKJUA3Kq.js} +1 -1
  119. package/dist/web/assets/problems-panel-CFcoUx8h.js +1 -0
  120. package/dist/web/assets/{process-table-BRMS1MrX.js → process-table-EKr2dqG6.js} +1 -1
  121. package/dist/web/assets/project-framework-store-B70Q3dNM.js +1 -0
  122. package/dist/web/assets/project-store-B87hLRSk.js +1 -0
  123. package/dist/web/assets/{proxy-settings-section-Bv0Ny-Yy.js → proxy-settings-section-B7qRVl8w.js} +1 -1
  124. package/dist/web/assets/{pug-C1zf1yWy.js → pug-fT5JC6Vm.js} +1 -1
  125. package/dist/web/assets/{qml-OW58jGpD.js → qml-P_wEDB1w.js} +1 -1
  126. package/dist/web/assets/{query-audit-section-BcHJZcVC.js → query-audit-section-By6qyqdB.js} +1 -1
  127. package/dist/web/assets/radar-KQ55EAFF-DZ6IVu2s.js +1 -0
  128. package/dist/web/assets/{razor-DzjSaobM.js → razor-Drrt2_Ph.js} +1 -1
  129. package/dist/web/assets/remote-desktop-clipboard-notice-0WDYsyCA.js +1 -0
  130. package/dist/web/assets/remote-desktop-mobile-sheet-COYDg16H.js +2 -0
  131. package/dist/web/assets/remote-desktop-mobile-view-2DqSuS2U.js +1 -0
  132. package/dist/web/assets/{remote-desktop-readiness-gate-BTzss2wX.js → remote-desktop-readiness-gate-Dm_qMJMH.js} +1 -1
  133. package/dist/web/assets/remote-desktop-window-content-UodhsF7W.js +1 -0
  134. package/dist/web/assets/{rst-RfuihOUW.js → rst-Dr2mW4-h.js} +1 -1
  135. package/dist/web/assets/{ruby-CNO3pli1.js → ruby-C6EpUsTY.js} +1 -1
  136. package/dist/web/assets/{run-in-terminal-Cb_hVU8x.js → run-in-terminal-CdefEe_R.js} +1 -1
  137. package/dist/web/assets/{sas-DZY5BtXx.js → sas-D8ZEeP6v.js} +1 -1
  138. package/dist/web/assets/{schedules-settings-section-DO8xAOVj.js → schedules-settings-section-bG2lYfGv.js} +1 -1
  139. package/dist/web/assets/{scroll-area-LtNV8g5o.js → scroll-area-BzeiOd-B.js} +1 -1
  140. package/dist/web/assets/{scss--4g-REcN.js → scss-ITL7jQ1f.js} +1 -1
  141. package/dist/web/assets/{send-to-chat-Cnpru1M0.js → send-to-chat-F207yH3u.js} +1 -1
  142. package/dist/web/assets/{separator-Cds1yxy8.js → separator-TRMsJUYe.js} +1 -1
  143. package/dist/web/assets/settings-body-C06bFarv.js +2 -0
  144. package/dist/web/assets/settings-store-krC6bc79.js +2 -0
  145. package/dist/web/assets/settings-tab-34RSaASd.js +1 -0
  146. package/dist/web/assets/settings-window-content-Dr_2lmHH.js +1 -0
  147. package/dist/web/assets/{shaderlab-Cu4sBiVn.js → shaderlab-mW-IIlhk.js} +1 -1
  148. package/dist/web/assets/{shellsession-DWzJfEv7.js → shellsession-DhNl5STt.js} +1 -1
  149. package/dist/web/assets/{soy-BnqE-5Ff.js → soy-SdY0Qjtr.js} +1 -1
  150. package/dist/web/assets/{sparql-Ctg__NhZ.js → sparql-ftqZo2i-.js} +1 -1
  151. package/dist/web/assets/{sql-query-editor-D9k19T_M.js → sql-query-editor-BgRH0cHD.js} +1 -1
  152. package/dist/web/assets/{sqlite-viewer-DoV9hnWJ.js → sqlite-viewer-DUxGslpa.js} +1 -1
  153. package/dist/web/assets/{stata-Qk2h66XW.js → stata-Bj7Yjb4b.js} +1 -1
  154. package/dist/web/assets/{surrealql-8QEX7EfH.js → surrealql-CLhYGAfB.js} +1 -1
  155. package/dist/web/assets/{svelte-D_mubC5-.js → svelte-DTAlRHhK.js} +1 -1
  156. package/dist/web/assets/system-monitor-body-IZuLn7Qh.js +2 -0
  157. package/dist/web/assets/system-monitor-tab-DiSA490Z.js +1 -0
  158. package/dist/web/assets/{system-monitor-window-content-qXLcicFM.js → system-monitor-window-content-CEaFISgj.js} +1 -1
  159. package/dist/web/assets/{tab-host-window-content-7fPZNEni.js → tab-host-window-content-D0TN8Wrq.js} +1 -1
  160. package/dist/web/assets/{tab-store-DGJGnbwv.js → tab-store-OJa1GPCq.js} +1 -1
  161. package/dist/web/assets/team-member-sheet-DDPNcCjd.js +2 -0
  162. package/dist/web/assets/{team-member-window-content-BgvJFypM.js → team-member-window-content-DCiW_1-E.js} +1 -1
  163. package/dist/web/assets/{templ-C1jV0fsO.js → templ-BMfhACxb.js} +1 -1
  164. package/dist/web/assets/{terminal-tab-Cs0sGljx.js → terminal-tab-4ypP0NDY.js} +3 -3
  165. package/dist/web/assets/{tex-D_StFXy_.js → tex-BcssM-QA.js} +1 -1
  166. package/dist/web/assets/tool-cards-Dd6E7Q3k.js +9 -0
  167. package/dist/web/assets/treemap-KZPCXAKY-CE30wCXa.js +1 -0
  168. package/dist/web/assets/{ts-tags-B5m47rEm.js → ts-tags-CrR5IYI8.js} +1 -1
  169. package/dist/web/assets/{twig-C_br6GIl.js → twig-DchfUmyI.js} +1 -1
  170. package/dist/web/assets/{use-blob-url-C-SGi_Hb.js → use-blob-url-CPtCcY3i.js} +1 -1
  171. package/dist/web/assets/{use-inline-blame-B4trxGcR.js → use-inline-blame-vrfFHuaR.js} +1 -1
  172. package/dist/web/assets/{use-lsp-CV0vzILO.js → use-lsp-CsuZu49X.js} +1 -1
  173. package/dist/web/assets/{use-monaco-theme-DBqbWjWX.js → use-monaco-theme-BIVYJBIn.js} +1 -1
  174. package/dist/web/assets/{use-open-team-member-DENsjp0s.js → use-open-team-member-C3mrUzIC.js} +1 -1
  175. package/dist/web/assets/{use-websocket-LgXiX1qN.js → use-websocket-BMbufhX7.js} +1 -1
  176. package/dist/web/assets/{vendor-mermaid-Cbig5zA6.js → vendor-mermaid-BitiXJlL.js} +1 -1
  177. package/dist/web/assets/{vendor-ui-BBdkypKW.js → vendor-ui-DmZ-Mb-H.js} +1 -1
  178. package/dist/web/assets/video-preview-Byj8P_jA.js +1 -0
  179. package/dist/web/assets/{vue-DQwCnLdr.js → vue-1EL6Pw1h.js} +1 -1
  180. package/dist/web/assets/{vue-html-T_EoGpPJ.js → vue-html-BPQNwPC6.js} +1 -1
  181. package/dist/web/assets/{vue-vine-DmxLqX50.js → vue-vine-Bb_dsWLt.js} +1 -1
  182. package/dist/web/assets/{window-pip-registry-3l4hoxuD.js → window-pip-registry-yD6esSju.js} +1 -1
  183. package/dist/web/assets/ws-auth-CEYVQ8NH.js +1 -0
  184. package/dist/web/assets/{xml-Bs6Bw8is.js → xml-dxkLNA0K.js} +1 -1
  185. package/dist/web/assets/{xsl-f5ZNhRCc.js → xsl-Bw2adwrl.js} +1 -1
  186. package/dist/web/index.html +27 -26
  187. package/dist/web/sw.js +1 -1
  188. package/docs/architecture/plugins-and-tracing.md +285 -0
  189. package/docs/lessons-learned.md +31 -0
  190. package/docs/system-architecture.md +1 -0
  191. package/package.json +2 -1
  192. package/packages/ext-git-graph/src/compare-diff-base.test.ts +173 -0
  193. package/packages/ext-git-graph/src/compare-view.ts +22 -3
  194. package/packages/ext-git-graph/src/extension.ts +6 -0
  195. package/packages/ext-git-graph/src/types.ts +19 -0
  196. package/packages/ext-git-graph/src/webview-html.test.ts +134 -1
  197. package/packages/ext-git-graph/src/webview-html.ts +277 -27
  198. package/scripts/gen-file-icons.ts +493 -242
  199. package/src/providers/claude-agent-sdk.ts +42 -1
  200. package/src/providers/codex-app-server/codex-history.ts +77 -23
  201. package/src/server/index.ts +42 -7
  202. package/src/server/routes/chat.ts +15 -11
  203. package/src/server/routes/git.ts +83 -3
  204. package/src/server/routes/remote-desktop.ts +7 -2
  205. package/src/server/ws/global.ts +1 -1
  206. package/src/server/ws/remote-desktop.ts +12 -5
  207. package/src/services/binary-content.ts +25 -0
  208. package/src/services/chat-search.service.ts +1 -1
  209. package/src/services/chat.service.ts +9 -0
  210. package/src/services/event-loop-lag.ts +208 -0
  211. package/src/services/file-lines.ts +114 -0
  212. package/src/services/file-watcher/recreated-dir-poller.ts +108 -6
  213. package/src/services/file-watcher/watch-tree.ts +131 -10
  214. package/src/services/file-watcher.service.ts +73 -23
  215. package/src/services/git-branch-diff/branch-diff-parse.ts +151 -0
  216. package/src/services/git-branch-diff/branch-diff.service.ts +79 -0
  217. package/src/services/git-hunks/git-hunks.service.ts +26 -11
  218. package/src/services/git-hunks/unified-diff.ts +15 -1
  219. package/src/services/git.service.ts +74 -24
  220. package/src/services/jsonl-transcript-parser.ts +138 -6
  221. package/src/services/log-rotate.ts +142 -0
  222. package/src/services/media-transcode/ffmpeg-capabilities.ts +72 -2
  223. package/src/services/remote-desktop/remote-desktop-audio.ts +193 -0
  224. package/src/services/remote-desktop/remote-desktop-capture-input.ts +87 -14
  225. package/src/services/remote-desktop/remote-desktop-capture.ts +40 -7
  226. package/src/services/remote-desktop/remote-desktop-clipboard.ts +166 -0
  227. package/src/services/remote-desktop/remote-desktop-displays.ts +77 -1
  228. package/src/services/remote-desktop/remote-desktop-encoder-args.ts +25 -6
  229. package/src/services/remote-desktop/remote-desktop-evdev-key-map.ts +69 -0
  230. package/src/services/remote-desktop/remote-desktop-input-linux.ts +253 -0
  231. package/src/services/remote-desktop/remote-desktop-input-uinput.ts +242 -0
  232. package/src/services/remote-desktop/remote-desktop-input.ts +40 -8
  233. package/src/services/remote-desktop/remote-desktop-linux-session.ts +146 -0
  234. package/src/services/remote-desktop/remote-desktop-ogg-opus.ts +144 -0
  235. package/src/services/remote-desktop/remote-desktop-privacy.ts +163 -0
  236. package/src/services/remote-desktop/remote-desktop-quality.ts +138 -0
  237. package/src/services/remote-desktop/remote-desktop-requirements.ts +158 -5
  238. package/src/services/remote-desktop/remote-desktop-resolution.ts +302 -0
  239. package/src/services/remote-desktop/remote-desktop-session.ts +523 -21
  240. package/src/services/remote-desktop/remote-desktop-x11.ts +250 -0
  241. package/src/services/supervisor.ts +47 -4
  242. package/src/shared/remote-desktop-custom-quality.ts +117 -0
  243. package/src/shared/remote-desktop-quality.ts +127 -0
  244. package/src/types/chat.ts +4 -0
  245. package/src/types/git.ts +52 -0
  246. package/src/web/components/branch-review/branch-review-tab.tsx +360 -0
  247. package/src/web/components/branch-review/branch-review-tree-row.tsx +138 -0
  248. package/src/web/components/chat/attachment-chips.tsx +30 -6
  249. package/src/web/components/chat/chat-tab.tsx +12 -1
  250. package/src/web/components/chat/message-events.tsx +273 -0
  251. package/src/web/components/chat/message-list.tsx +107 -861
  252. package/src/web/components/chat/message-markdown.tsx +34 -0
  253. package/src/web/components/chat/message-media.tsx +174 -0
  254. package/src/web/components/chat/message-user-bubble.tsx +399 -0
  255. package/src/web/components/chat/provider-selector.tsx +30 -22
  256. package/src/web/components/chat/session-history-list.tsx +2 -2
  257. package/src/web/components/chat/session-list-panel.tsx +3 -2
  258. package/src/web/components/editor/binary-diff-view.tsx +212 -0
  259. package/src/web/components/editor/diff-viewer.tsx +76 -3
  260. package/src/web/components/explorer/file-tree.tsx +235 -134
  261. package/src/web/components/explorer/flatten-visible-tree.ts +35 -40
  262. package/src/web/components/explorer/tree-node.tsx +69 -69
  263. package/src/web/components/explorer/use-tree-row-dnd.ts +3 -4
  264. package/src/web/components/git/git-status-panel.tsx +25 -0
  265. package/src/web/components/layout/draggable-tab.tsx +4 -2
  266. package/src/web/components/layout/nav-section-rail.tsx +12 -3
  267. package/src/web/components/layout/status-bar.tsx +10 -6
  268. package/src/web/components/layout/tab-content.tsx +5 -0
  269. package/src/web/components/layout/tab-pool.tsx +1 -0
  270. package/src/web/components/layout/upgrade-button.tsx +5 -1
  271. package/src/web/components/remote-desktop/remote-desktop-clipboard-client.ts +67 -0
  272. package/src/web/components/remote-desktop/remote-desktop-clipboard-notice.tsx +127 -0
  273. package/src/web/components/remote-desktop/remote-desktop-codec-labels.ts +28 -0
  274. package/src/web/components/remote-desktop/remote-desktop-custom-quality-dialog.tsx +144 -0
  275. package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +13 -2
  276. package/src/web/components/remote-desktop/remote-desktop-mobile-settings.tsx +232 -0
  277. package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +21 -8
  278. package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +82 -3
  279. package/src/web/components/remote-desktop/remote-desktop-recording.ts +76 -0
  280. package/src/web/components/remote-desktop/remote-desktop-resolution-list.ts +100 -0
  281. package/src/web/components/remote-desktop/remote-desktop-toolbar.tsx +497 -0
  282. package/src/web/components/remote-desktop/remote-desktop-view-style.ts +145 -0
  283. package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +126 -35
  284. package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +11 -0
  285. package/src/web/components/remote-desktop/use-opus-audio-player.ts +148 -0
  286. package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +284 -1
  287. package/src/web/components/remote-desktop/use-remote-desktop-readiness.ts +75 -1
  288. package/src/web/components/remote-desktop/use-remote-desktop-recorder.ts +103 -0
  289. package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +19 -2
  290. package/src/web/components/remote-desktop/use-remote-input-capture.ts +39 -3
  291. package/src/web/components/settings/theme-picker.tsx +2 -1
  292. package/src/web/components/ui/tooltip.tsx +15 -2
  293. package/src/web/hooks/use-delayed-flag.ts +35 -0
  294. package/src/web/lib/branch-review-state.ts +175 -0
  295. package/src/web/lib/file-icon-name.ts +62 -0
  296. package/src/web/lib/file-icons.generated.ts +3287 -25
  297. package/src/web/lib/file-icons.tsx +71 -73
  298. package/src/web/lib/flatten-expansions.ts +18 -5
  299. package/src/web/lib/message-ordinals.ts +23 -0
  300. package/src/web/lib/provider-logos.tsx +143 -0
  301. package/src/web/lib/tab-type-icons.ts +11 -0
  302. package/src/web/stores/file-store.ts +73 -35
  303. package/src/web/stores/panel-utils.ts +5 -0
  304. package/src/web/stores/project-framework-store.ts +82 -0
  305. package/src/web/stores/project-store.ts +7 -1
  306. package/src/web/stores/settings-store.ts +128 -0
  307. package/src/web/stores/tab-store.ts +1 -0
  308. package/src/web/stores/window-panel-persistence.ts +1 -0
  309. package/src/web/styles/file-icons.generated.css +3282 -234
  310. package/src/web/styles/globals.css +26 -0
  311. package/dist/web/assets/accounts-settings-section-BuxD9zH3.js +0 -1
  312. package/dist/web/assets/ai-settings-section-Dd9tOUp0.js +0 -1
  313. package/dist/web/assets/ai-settings-section-FrU2X8Fu.js +0 -1
  314. package/dist/web/assets/api-client-BUCo1lms.js +0 -1
  315. package/dist/web/assets/architecture-PBZL5I3N-5K0wEkAn.js +0 -1
  316. package/dist/web/assets/chat-tab-Cr6_KSGO.js +0 -15
  317. package/dist/web/assets/code-editor-DfmlGamv.js +0 -7
  318. package/dist/web/assets/context-menu-DOODKZdF.js +0 -1
  319. package/dist/web/assets/diff-viewer-BpNj7pN9.js +0 -4
  320. package/dist/web/assets/explorer-body-D-5z464e.js +0 -2
  321. package/dist/web/assets/explorer-window-content-7-1wuRul.js +0 -1
  322. package/dist/web/assets/file-icons-EzPL0q-k.js +0 -1
  323. package/dist/web/assets/file-icons.generated-CfaJuvZu.css +0 -1
  324. package/dist/web/assets/file-store-DMABxOkV.js +0 -1
  325. package/dist/web/assets/files-settings-section-CZzABuKz.js +0 -1
  326. package/dist/web/assets/git-log-panel-CRN4JVOx.js +0 -1
  327. package/dist/web/assets/gitGraph-HDMCJU4V-DkVT2MAM.js +0 -1
  328. package/dist/web/assets/group-chat-tab-DFa5mAx5.js +0 -1
  329. package/dist/web/assets/index-D7DbXD3G.css +0 -1
  330. package/dist/web/assets/index-ZtfxZOyb.js +0 -52
  331. package/dist/web/assets/info-3K5VOQVL-BBsmKGzA.js +0 -1
  332. package/dist/web/assets/keybindings-store-tVfhPANS.js +0 -1
  333. package/dist/web/assets/markdown-renderer-CvL1pWeK.js +0 -3
  334. package/dist/web/assets/markdown-renderer-sJNNVhlS.js +0 -1
  335. package/dist/web/assets/mobile-explorer-sheet-CZ0HZ2E_.js +0 -1
  336. package/dist/web/assets/notification-store-BZbsPnW0.js +0 -1
  337. package/dist/web/assets/packet-RMMSAZCW-DwmYPOlx.js +0 -1
  338. package/dist/web/assets/panel-store-BVLBRn6M.js +0 -1
  339. package/dist/web/assets/pie-UPGHQEXC-D0oogIFi.js +0 -1
  340. package/dist/web/assets/problems-panel-B7BWVhdy.js +0 -1
  341. package/dist/web/assets/project-store-BhMbVln3.js +0 -1
  342. package/dist/web/assets/radar-KQ55EAFF-CQIJOQGa.js +0 -1
  343. package/dist/web/assets/remote-desktop-mobile-sheet-ByjaeZKX.js +0 -2
  344. package/dist/web/assets/remote-desktop-mobile-view-D13mtr9F.js +0 -1
  345. package/dist/web/assets/remote-desktop-window-content-CbqTCaC6.js +0 -1
  346. package/dist/web/assets/settings-body-Rnu-FcX-.js +0 -2
  347. package/dist/web/assets/settings-store-DZPgPtEA.js +0 -2
  348. package/dist/web/assets/settings-tab-C6PHxs0Z.js +0 -1
  349. package/dist/web/assets/settings-window-content-CQ4gT2qj.js +0 -1
  350. package/dist/web/assets/system-monitor-body-2vQ1kMxE.js +0 -2
  351. package/dist/web/assets/system-monitor-tab-krAcvwF7.js +0 -1
  352. package/dist/web/assets/team-member-sheet-2FnRmk1a.js +0 -2
  353. package/dist/web/assets/tool-cards-DU_S2o51.js +0 -9
  354. package/dist/web/assets/treemap-KZPCXAKY-BhEtjljr.js +0 -1
  355. package/dist/web/assets/use-remote-desktop-display-choice-BK_f787P.js +0 -1
  356. package/dist/web/assets/video-preview-BDNwumbW.js +0 -1
  357. package/dist/web/assets/ws-auth-DEAJiGCR.js +0 -1
  358. /package/dist/web/assets/{abap-tkptwpsK.js → abap-IN2yINdN.js} +0 -0
  359. /package/dist/web/assets/{actionscript-3-Di9TMGwg.js → actionscript-3-C_t772eR.js} +0 -0
  360. /package/dist/web/assets/{ada-CB9F079D.js → ada-DPme3emm.js} +0 -0
  361. /package/dist/web/assets/{andromeeda-Dcl8jpb7.js → andromeeda-DSV4i4mZ.js} +0 -0
  362. /package/dist/web/assets/{apache-D0Nk0qsH.js → apache-CWr8s8I8.js} +0 -0
  363. /package/dist/web/assets/{apex-mQIoWncD.js → apex-Z20JnDF5.js} +0 -0
  364. /package/dist/web/assets/{applescript-C6ljbcsI.js → applescript-BKNPu3ga.js} +0 -0
  365. /package/dist/web/assets/{apply-theme-BwIMNXyX.js → apply-theme-CbR31K6I.js} +0 -0
  366. /package/dist/web/assets/{ara-WFqRPGPe.js → ara-hWdY3O96.js} +0 -0
  367. /package/dist/web/assets/{asciidoc-bNd8w4nf.js → asciidoc-eynhq496.js} +0 -0
  368. /package/dist/web/assets/{asm-BQ1_F580.js → asm-Bu02Kq4P.js} +0 -0
  369. /package/dist/web/assets/{aurora-x-Ciqs_Dbu.js → aurora-x-BHBGXbIZ.js} +0 -0
  370. /package/dist/web/assets/{awk-GCR-6KZV.js → awk-B4ojezyv.js} +0 -0
  371. /package/dist/web/assets/{ayu-dark-DxJXuj99.js → ayu-dark-D0GQzFi9.js} +0 -0
  372. /package/dist/web/assets/{ayu-light-jvSyJWDV.js → ayu-light-BVYb5Yn8.js} +0 -0
  373. /package/dist/web/assets/{ayu-mirage-Cg3BPO6H.js → ayu-mirage-BGFW6oNb.js} +0 -0
  374. /package/dist/web/assets/{ballerina-BPz4CrX6.js → ballerina-B6Og3IKT.js} +0 -0
  375. /package/dist/web/assets/{bat-CSTs2hIG.js → bat-D8YFILfx.js} +0 -0
  376. /package/dist/web/assets/{beancount-DMateAZX.js → beancount-BjlbBBuX.js} +0 -0
  377. /package/dist/web/assets/{berry-9lXg0GRM.js → berry-Cz1ygeoj.js} +0 -0
  378. /package/dist/web/assets/{bibtex-DIbqmvFg.js → bibtex-CVkJceJh.js} +0 -0
  379. /package/dist/web/assets/{bicep-02WGsoJ8.js → bicep-DrhYI5GX.js} +0 -0
  380. /package/dist/web/assets/{bird2-BY0tbu3k.js → bird2-BV-KJtmc.js} +0 -0
  381. /package/dist/web/assets/{c-jMBX_9Je.js → c-CuLlvxbG.js} +0 -0
  382. /package/dist/web/assets/{c3-B8JVbNjW.js → c3-CI7-1CJ0.js} +0 -0
  383. /package/dist/web/assets/{cadence-DUIf1tvL.js → cadence-DjxhNP3Q.js} +0 -0
  384. /package/dist/web/assets/{can-open-in-ppm-DGEc0s4P.js → can-open-in-ppm-cv5--yHX.js} +0 -0
  385. /package/dist/web/assets/{catppuccin-frappe-FhUCVxex.js → catppuccin-frappe-CEozpJRh.js} +0 -0
  386. /package/dist/web/assets/{catppuccin-latte-CzfrtmgL.js → catppuccin-latte-C4sXQFMc.js} +0 -0
  387. /package/dist/web/assets/{catppuccin-macchiato-6xL0lOtj.js → catppuccin-macchiato-Ci-fDhXy.js} +0 -0
  388. /package/dist/web/assets/{catppuccin-mocha-D39-xS3P.js → catppuccin-mocha-CH7YXw8d.js} +0 -0
  389. /package/dist/web/assets/{clarity-D8TeGvoT.js → clarity-BWrJ7UmT.js} +0 -0
  390. /package/dist/web/assets/{clojure-Dx_acSX7.js → clojure-C94fh2hb.js} +0 -0
  391. /package/dist/web/assets/{cmake-CyeYPq8o.js → cmake-CtvCkMsG.js} +0 -0
  392. /package/dist/web/assets/{codeowners-DrTI0lmI.js → codeowners-CJnubqzH.js} +0 -0
  393. /package/dist/web/assets/{codeql-CPkFxVW-.js → codeql-BOoP5HY2.js} +0 -0
  394. /package/dist/web/assets/{common-lisp-B0k-3LTj.js → common-lisp-B9nlMFJd.js} +0 -0
  395. /package/dist/web/assets/{coq-uygpo7to.js → coq-CgzIQiSH.js} +0 -0
  396. /package/dist/web/assets/{csharp-DKUYGfSp.js → csharp-4xAIx3jw.js} +0 -0
  397. /package/dist/web/assets/{css-DoOUp7v4.js → css-D4YoLYGn.js} +0 -0
  398. /package/dist/web/assets/{csv-B3YE13gi.js → csv-C2v-ZakW.js} +0 -0
  399. /package/dist/web/assets/{csv-parser-DXePqmXe.js → csv-parser-_oDBUkwQ.js} +0 -0
  400. /package/dist/web/assets/{cue-DViO2wvn.js → cue-Bbznh_an.js} +0 -0
  401. /package/dist/web/assets/{cypher-4BMCIW5N.js → cypher-BKhVEARF.js} +0 -0
  402. /package/dist/web/assets/{d-BT_sTHas.js → d-D6IhWopl.js} +0 -0
  403. /package/dist/web/assets/{dark-plus-DVVXu-1N.js → dark-plus-xAWu790O.js} +0 -0
  404. /package/dist/web/assets/{dart-DHz6f6_6.js → dart-Bj6S9O1j.js} +0 -0
  405. /package/dist/web/assets/{data-grid-types-OgUxMLdQ.js → data-grid-types-C5bthITf.js} +0 -0
  406. /package/dist/web/assets/{dax-ChYCjweK.js → dax-CF2zvaqq.js} +0 -0
  407. /package/dist/web/assets/{desktop-D6Lt4p04.js → desktop-CnmyxMKE.js} +0 -0
  408. /package/dist/web/assets/{diff-D8TDe4hP.js → diff-Qi8WuJsI.js} +0 -0
  409. /package/dist/web/assets/{dist-DOTlFQJA.js → dist-Bjseolz4.js} +0 -0
  410. /package/dist/web/assets/{dist-CrBOCtCW.js → dist-Czq7BUMy.js} +0 -0
  411. /package/dist/web/assets/{docker-Bsp0YbPx.js → docker-vLbbNP5d.js} +0 -0
  412. /package/dist/web/assets/{dotenv-B_7giSY_.js → dotenv-DJMvvNzn.js} +0 -0
  413. /package/dist/web/assets/{dracula-TrQL2iB5.js → dracula-CtAh2rhB.js} +0 -0
  414. /package/dist/web/assets/{dracula-soft-n2XOlgh-.js → dracula-soft-CXtuAAf6.js} +0 -0
  415. /package/dist/web/assets/{dream-maker-CdIEdhpT.js → dream-maker-D5YQyK49.js} +0 -0
  416. /package/dist/web/assets/{editor-font-BDrdiGf1.js → editor-font-ClTC9gaD.js} +0 -0
  417. /package/dist/web/assets/{emacs-lisp-CSbixs6u.js → emacs-lisp-CGTjbiqF.js} +0 -0
  418. /package/dist/web/assets/{everforest-dark-BuZahZYp.js → everforest-dark-D59p7vAQ.js} +0 -0
  419. /package/dist/web/assets/{everforest-light-DPH53z5H.js → everforest-light-B3Qtud3V.js} +0 -0
  420. /package/dist/web/assets/{fennel-B1-K_hlJ.js → fennel-BCyHNUwf.js} +0 -0
  421. /package/dist/web/assets/{fish-Cy12Dbua.js → fish-DDDlLV2G.js} +0 -0
  422. /package/dist/web/assets/{fluent-B_82ys8E.js → fluent-By54R9tl.js} +0 -0
  423. /package/dist/web/assets/{format-bytes-D5sBgIqU.js → format-bytes-D1AQyUCt.js} +0 -0
  424. /package/dist/web/assets/{fortran-free-form-BRQdNzZF.js → fortran-free-form-tP8mRym_.js} +0 -0
  425. /package/dist/web/assets/{gdscript-XBDmLUm9.js → gdscript-nHC2z6Te.js} +0 -0
  426. /package/dist/web/assets/{gdshader-DhVfB3u4.js → gdshader-K-cSmPlW.js} +0 -0
  427. /package/dist/web/assets/{genie-X3K29Y49.js → genie-BAcRhvEe.js} +0 -0
  428. /package/dist/web/assets/{gherkin-DKIx-6cX.js → gherkin-CcifxMLb.js} +0 -0
  429. /package/dist/web/assets/{github-dark-Bf7zdp5s.js → github-dark-67KKlMdB.js} +0 -0
  430. /package/dist/web/assets/{github-dark-default-DqsBBV11.js → github-dark-default-tja8pidR.js} +0 -0
  431. /package/dist/web/assets/{github-dark-dimmed-X2gqRz6w.js → github-dark-dimmed-CtjhYQtx.js} +0 -0
  432. /package/dist/web/assets/{github-dark-high-contrast-Z7tvbGX4.js → github-dark-high-contrast-Bf-LPFgn.js} +0 -0
  433. /package/dist/web/assets/{github-light-BKxt-7zZ.js → github-light-CbJxE7n3.js} +0 -0
  434. /package/dist/web/assets/{github-light-default-B0liF4d4.js → github-light-default-DRQmdHxt.js} +0 -0
  435. /package/dist/web/assets/{github-light-high-contrast-F4encJwr.js → github-light-high-contrast-CJkDgTeM.js} +0 -0
  436. /package/dist/web/assets/{gleam-Pz2bHgBB.js → gleam-D4LCl2ri.js} +0 -0
  437. /package/dist/web/assets/{gn-CgmdNsPU.js → gn-COY8zZmC.js} +0 -0
  438. /package/dist/web/assets/{gnuplot-qLNF5yZ9.js → gnuplot-ROk1VitR.js} +0 -0
  439. /package/dist/web/assets/{go-CSDUNhE7.js → go-H30c3p0C.js} +0 -0
  440. /package/dist/web/assets/{groovy-B3NEnOHF.js → groovy-DBLCaelV.js} +0 -0
  441. /package/dist/web/assets/{gruvbox-dark-hard-DcoftiUX.js → gruvbox-dark-hard-DJy4irps.js} +0 -0
  442. /package/dist/web/assets/{gruvbox-dark-medium-D1wVSuD8.js → gruvbox-dark-medium-Dhfw1QTO.js} +0 -0
  443. /package/dist/web/assets/{gruvbox-dark-soft-DWpunymH.js → gruvbox-dark-soft-BncJU-8w.js} +0 -0
  444. /package/dist/web/assets/{gruvbox-light-hard-rHMvyUdS.js → gruvbox-light-hard-Cq12_YvM.js} +0 -0
  445. /package/dist/web/assets/{gruvbox-light-medium-3O2KiXO7.js → gruvbox-light-medium-DqVja5_v.js} +0 -0
  446. /package/dist/web/assets/{gruvbox-light-soft-lNlXJPwH.js → gruvbox-light-soft-IvwhveGF.js} +0 -0
  447. /package/dist/web/assets/{haskell-aaG3aC0h.js → haskell-DA1JV-MC.js} +0 -0
  448. /package/dist/web/assets/{haxe-CPcHhUDd.js → haxe-V9RTRc5E.js} +0 -0
  449. /package/dist/web/assets/{hcl-CMQNlR_4.js → hcl-hr1lyZ10.js} +0 -0
  450. /package/dist/web/assets/{hjson-DeuklPFj.js → hjson-D3vW3i7X.js} +0 -0
  451. /package/dist/web/assets/{hlsl-BI3hlsaM.js → hlsl-BB85gfJT.js} +0 -0
  452. /package/dist/web/assets/{horizon-C7-gOcjs.js → horizon-DU8OOnj8.js} +0 -0
  453. /package/dist/web/assets/{horizon-bright-Cxt9bRW7.js → horizon-bright-DNMUclW6.js} +0 -0
  454. /package/dist/web/assets/{houston-DbF2G7iD.js → houston-DAiZKf6T.js} +0 -0
  455. /package/dist/web/assets/{hy-DRG6Hs2I.js → hy-C6Zv0LMe.js} +0 -0
  456. /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-CGvlv8zR.js} +0 -0
  457. /package/dist/web/assets/{imba-CxKv_2TW.js → imba-Didnp6Y-.js} +0 -0
  458. /package/dist/web/assets/{ini-BiHsyCdO.js → ini-m0T7q4gQ.js} +0 -0
  459. /package/dist/web/assets/{input-D-dR8RI6.js → input-C7Q8FxeL.js} +0 -0
  460. /package/dist/web/assets/{java-D3RZzgDB.js → java-Buy07jGn.js} +0 -0
  461. /package/dist/web/assets/{javascript-4FmSqG-T.js → javascript-Cjq3CbRL.js} +0 -0
  462. /package/dist/web/assets/{json-CjB236_E.js → json-BSVu8IqD.js} +0 -0
  463. /package/dist/web/assets/{json5-bkfO-Rco.js → json5-NN_Vb7e6.js} +0 -0
  464. /package/dist/web/assets/{jsonc-BwaTeLyv.js → jsonc-BdyZhBUw.js} +0 -0
  465. /package/dist/web/assets/{jsonl-CfxNSwGd.js → jsonl-BsDSNvwi.js} +0 -0
  466. /package/dist/web/assets/{jsonnet-CQETCYrp.js → jsonnet-Gm963F-U.js} +0 -0
  467. /package/dist/web/assets/{jssm-D5o3Zzdz.js → jssm-Ds3bKWgJ.js} +0 -0
  468. /package/dist/web/assets/{jsx-BAMILPLo.js → jsx-BT5mW58X.js} +0 -0
  469. /package/dist/web/assets/{kanagawa-dragon-BhGVxu3R.js → kanagawa-dragon-BvNvf7xd.js} +0 -0
  470. /package/dist/web/assets/{kanagawa-lotus-FH4Y59ro.js → kanagawa-lotus-CuSiliSn.js} +0 -0
  471. /package/dist/web/assets/{kanagawa-wave-eilvP9MB.js → kanagawa-wave-C6g-d6jT.js} +0 -0
  472. /package/dist/web/assets/{kdl-BQ-yvXUK.js → kdl-Bx4kZef-.js} +0 -0
  473. /package/dist/web/assets/{kotlin-BuCx0-VT.js → kotlin-qvaVvCcm.js} +0 -0
  474. /package/dist/web/assets/{kusto-DAN_n_Cy.js → kusto-BPLyNCN6.js} +0 -0
  475. /package/dist/web/assets/{laserwave-Cq2BlxrT.js → laserwave-Sf8ZEhWP.js} +0 -0
  476. /package/dist/web/assets/{lean-Dhq7uXKw.js → lean-DGe6uqgk.js} +0 -0
  477. /package/dist/web/assets/{less-CxaMQb8j.js → less-CqM85x-9.js} +0 -0
  478. /package/dist/web/assets/{lib-BVwmtv2i.js → lib-CD6pOS4u.js} +0 -0
  479. /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-CPh1Av0r.js} +0 -0
  480. /package/dist/web/assets/{light-plus-B4sMAend.js → light-plus-DBtKwhFA.js} +0 -0
  481. /package/dist/web/assets/{llvm-DQ6-B_6Y.js → llvm-Dpbq3gky.js} +0 -0
  482. /package/dist/web/assets/{log-Bb_vqvu7.js → log-qKMcPm9S.js} +0 -0
  483. /package/dist/web/assets/{logo-BzfYv3A_.js → logo-DFo7i5KH.js} +0 -0
  484. /package/dist/web/assets/{luau-BnAeNR77.js → luau-DGlqWFyL.js} +0 -0
  485. /package/dist/web/assets/{make-B6IFuxpC.js → make-CixdvUQe.js} +0 -0
  486. /package/dist/web/assets/{markdown-Z0xi77Fn.js → markdown-CmRnbngE.js} +0 -0
  487. /package/dist/web/assets/{material-theme-1scy1SYn.js → material-theme-WDUbafB-.js} +0 -0
  488. /package/dist/web/assets/{material-theme-darker-G-JykXQP.js → material-theme-darker-u8y2xpOk.js} +0 -0
  489. /package/dist/web/assets/{material-theme-lighter-BtKkCMVq.js → material-theme-lighter-cyQZK3Tg.js} +0 -0
  490. /package/dist/web/assets/{material-theme-ocean-irrDC89L.js → material-theme-ocean-LBAVvw7k.js} +0 -0
  491. /package/dist/web/assets/{material-theme-palenight-Eq5_K4LM.js → material-theme-palenight-BJqhBxL0.js} +0 -0
  492. /package/dist/web/assets/{matlab-BE3oCXZC.js → matlab-B38sOJee.js} +0 -0
  493. /package/dist/web/assets/{mdx-DYiuFTGw.js → mdx-C8QrE_IW.js} +0 -0
  494. /package/dist/web/assets/{mermaid-DVlaEvtw.js → mermaid-BQ-eeU8A.js} +0 -0
  495. /package/dist/web/assets/{min-dark-DSkeTpQa.js → min-dark-D4gNa5tv.js} +0 -0
  496. /package/dist/web/assets/{min-light-CFv2Q0uF.js → min-light-Dfbhn_PL.js} +0 -0
  497. /package/dist/web/assets/{mipsasm-CiAFuZQ2.js → mipsasm-bLOhJkdm.js} +0 -0
  498. /package/dist/web/assets/{mojo-ARshcaSQ.js → mojo-B0BCOMQW.js} +0 -0
  499. /package/dist/web/assets/{monaco-dotenv-language-BDYnKcnr.js → monaco-dotenv-language-uB-wok6C.js} +0 -0
  500. /package/dist/web/assets/{monokai-CPTV-SOg.js → monokai-Bdw_07OM.js} +0 -0
  501. /package/dist/web/assets/{moonbit-D57B0dZr.js → moonbit-BFdGaeuk.js} +0 -0
  502. /package/dist/web/assets/{move-qgy3G_o9.js → move-CyXQFGPM.js} +0 -0
  503. /package/dist/web/assets/{narrat-CZ3rC-oc.js → narrat-9DvTD3_3.js} +0 -0
  504. /package/dist/web/assets/{nextflow-groovy-BxxIOcI4.js → nextflow-groovy-C4VY3kNY.js} +0 -0
  505. /package/dist/web/assets/{night-owl-B1ryGCda.js → night-owl-feYm66V0.js} +0 -0
  506. /package/dist/web/assets/{night-owl-light-BZvNnvMF.js → night-owl-light-DALqFjvx.js} +0 -0
  507. /package/dist/web/assets/{nix-BVBQi8yv.js → nix-C1kPA9Sw.js} +0 -0
  508. /package/dist/web/assets/{nord-3NXw613h.js → nord-1dvrj1Al.js} +0 -0
  509. /package/dist/web/assets/{nushell-BMOLRalb.js → nushell-DSQ_9Uqj.js} +0 -0
  510. /package/dist/web/assets/{objective-c-AxruEUgh.js → objective-c-Dhcn5ZK8.js} +0 -0
  511. /package/dist/web/assets/{objective-cpp-cFT0qRsD.js → objective-cpp-CGVYCSrp.js} +0 -0
  512. /package/dist/web/assets/{ocaml-_8JH6O8J.js → ocaml-rxXW21An.js} +0 -0
  513. /package/dist/web/assets/{odin-nwYvR0O6.js → odin-ClSJX2DE.js} +0 -0
  514. /package/dist/web/assets/{one-dark-pro-C1LaYJaH.js → one-dark-pro-3gbWDfFW.js} +0 -0
  515. /package/dist/web/assets/{one-light-B7rMc__c.js → one-light-C2q5ZZAw.js} +0 -0
  516. /package/dist/web/assets/{openscad-Cwes8bKO.js → openscad-eHXeafBl.js} +0 -0
  517. /package/dist/web/assets/{pascal-DMt-6tdZ.js → pascal-kKha3Txs.js} +0 -0
  518. /package/dist/web/assets/{pkl-QBTyYQmB.js → pkl-CTiPytg-.js} +0 -0
  519. /package/dist/web/assets/{plastic-BTSUp8Fk.js → plastic-D1Ygezi4.js} +0 -0
  520. /package/dist/web/assets/{plsql-DQv3IFXP.js → plsql-D_gbgZIr.js} +0 -0
  521. /package/dist/web/assets/{po-CNwZx4tW.js → po-BkhbDUnq.js} +0 -0
  522. /package/dist/web/assets/{poimandres-8Os7Qequ.js → poimandres-Do6NywrA.js} +0 -0
  523. /package/dist/web/assets/{polar-b4yK8oD8.js → polar-B3B23wca.js} +0 -0
  524. /package/dist/web/assets/{postcss-sFctwpxx.js → postcss-Dv_YBvWd.js} +0 -0
  525. /package/dist/web/assets/{powerquery-Cg_WrtuN.js → powerquery-fKJWI-Sh.js} +0 -0
  526. /package/dist/web/assets/{powershell-C7Ifz95h.js → powershell-B4Y7KIgx.js} +0 -0
  527. /package/dist/web/assets/{prisma-Z52HUJzQ.js → prisma-J--XdTXX.js} +0 -0
  528. /package/dist/web/assets/{prolog-DLqW-zR1.js → prolog-BBTg4Vb3.js} +0 -0
  529. /package/dist/web/assets/{proto-BOIzxCCJ.js → proto-DWlum3T8.js} +0 -0
  530. /package/dist/web/assets/{puppet-De7TjZIf.js → puppet-ZiUfc9JV.js} +0 -0
  531. /package/dist/web/assets/{purescript-R76ou1WT.js → purescript-CmT8SCHB.js} +0 -0
  532. /package/dist/web/assets/{python-Dprm0tNP.js → python-BURpj-fq.js} +0 -0
  533. /package/dist/web/assets/{qmldir-Clp_jpNw.js → qmldir-CpaiJywQ.js} +0 -0
  534. /package/dist/web/assets/{qss-eQPYYzSo.js → qss-C-zdz0i5.js} +0 -0
  535. /package/dist/web/assets/{r-CCcRHIf5.js → r-K56BeS-s.js} +0 -0
  536. /package/dist/web/assets/{racket-CzE472yp.js → racket-gkn6Z_Jw.js} +0 -0
  537. /package/dist/web/assets/{raku-DQwXrTSi.js → raku-C0ZBuKlx.js} +0 -0
  538. /package/dist/web/assets/{red-KCS6wyop.js → red-BWZ6x-3p.js} +0 -0
  539. /package/dist/web/assets/{reg-CcrAddUh.js → reg-Dt_EsyGR.js} +0 -0
  540. /package/dist/web/assets/{regexp-DDbud-Ri.js → regexp-CT6BLG2C.js} +0 -0
  541. /package/dist/web/assets/{rel-99GDgaHp.js → rel-MJ2LcyzJ.js} +0 -0
  542. /package/dist/web/assets/{resolve-theme-CW2niGV2.js → resolve-theme-B_ukrg_8.js} +0 -0
  543. /package/dist/web/assets/{riscv-Dm2eNv0R.js → riscv-BZYWzMAA.js} +0 -0
  544. /package/dist/web/assets/{ron-CzFt0ygH.js → ron-CLbwHWMn.js} +0 -0
  545. /package/dist/web/assets/{rose-pine-Ck6lCrCP.js → rose-pine-BPiI_daR.js} +0 -0
  546. /package/dist/web/assets/{rose-pine-dawn-vtALRJNj.js → rose-pine-dawn-BjkJc7dW.js} +0 -0
  547. /package/dist/web/assets/{rose-pine-moon-DPto8qXw.js → rose-pine-moon-BQSxkt7g.js} +0 -0
  548. /package/dist/web/assets/{rosmsg-NtS6429H.js → rosmsg-yt4151pJ.js} +0 -0
  549. /package/dist/web/assets/{rust-BjIXvF9h.js → rust-1dAcengL.js} +0 -0
  550. /package/dist/web/assets/{sass-CQ23wf5E.js → sass-DUgtQJsg.js} +0 -0
  551. /package/dist/web/assets/{scala-LfKLM143.js → scala-CxoGYpS0.js} +0 -0
  552. /package/dist/web/assets/{scheme-D3SLBXlF.js → scheme-B2K3u9e3.js} +0 -0
  553. /package/dist/web/assets/{sdbl-ODRd2rGN.js → sdbl-C0Ygk0n6.js} +0 -0
  554. /package/dist/web/assets/{shellscript-3KB1G_oA.js → shellscript-C2CvhqMk.js} +0 -0
  555. /package/dist/web/assets/{slack-dark-DTtyWmIy.js → slack-dark-DYdgzRGj.js} +0 -0
  556. /package/dist/web/assets/{slack-ochin-Bu0PApk3.js → slack-ochin-X-WD0WCg.js} +0 -0
  557. /package/dist/web/assets/{smalltalk-haMT-Sbe.js → smalltalk-Dp7g_zb9.js} +0 -0
  558. /package/dist/web/assets/{snazzy-light-DXJzefE9.js → snazzy-light-DWHWr-a6.js} +0 -0
  559. /package/dist/web/assets/{solarized-dark-B6LyYa18.js → solarized-dark-CxdLsjvb.js} +0 -0
  560. /package/dist/web/assets/{solarized-light-QLADebkv.js → solarized-light-Cz9rAvG5.js} +0 -0
  561. /package/dist/web/assets/{solidity-mzfq1aDc.js → solidity-D33gHHdn.js} +0 -0
  562. /package/dist/web/assets/{splunk-CyH4RWBp.js → splunk-BFbOroYw.js} +0 -0
  563. /package/dist/web/assets/{sql-CWVlT5D3.js → sql-ChpxvmdY.js} +0 -0
  564. /package/dist/web/assets/{ssh-config-DNvQJ1Qx.js → ssh-config-CUuSQ6JG.js} +0 -0
  565. /package/dist/web/assets/{streaming-store-uDu3PoN1.js → streaming-store-BYukdB56.js} +0 -0
  566. /package/dist/web/assets/{stylus-ByQQdtmf.js → stylus-DDihP0_W.js} +0 -0
  567. /package/dist/web/assets/{swift-D1cpY4ow.js → swift-XoalUn4x.js} +0 -0
  568. /package/dist/web/assets/{synthwave-84-BDF_-QiW.js → synthwave-84-DjkG0Bff.js} +0 -0
  569. /package/dist/web/assets/{system-verilog-BYrkMydC.js → system-verilog-f_pLKMYd.js} +0 -0
  570. /package/dist/web/assets/{systemd-CQm3zyK0.js → systemd-DjDcLfxp.js} +0 -0
  571. /package/dist/web/assets/{talonscript-DMW5CzE1.js → talonscript-BpUzYcDl.js} +0 -0
  572. /package/dist/web/assets/{tasl-BhFzMCr2.js → tasl-DP-cUbg8.js} +0 -0
  573. /package/dist/web/assets/{tcl-D7VEAF-9.js → tcl-BHoy-dfE.js} +0 -0
  574. /package/dist/web/assets/{terraform-DdT5A0Vy.js → terraform-vCVxN25B.js} +0 -0
  575. /package/dist/web/assets/{tokyo-night-WGLMM6Q9.js → tokyo-night-D0u4xZba.js} +0 -0
  576. /package/dist/web/assets/{toml-CZ7ipXHZ.js → toml-BS8D0__A.js} +0 -0
  577. /package/dist/web/assets/{tsv-B1zVvoTr.js → tsv-DCb-NQh3.js} +0 -0
  578. /package/dist/web/assets/{tsx-D4HwRUBq.js → tsx-DF8YeRgG.js} +0 -0
  579. /package/dist/web/assets/{turtle-B4r8KEJH.js → turtle-Bhx1GlIy.js} +0 -0
  580. /package/dist/web/assets/{typescript-BnHhxMtp.js → typescript-CAVD2Wb6.js} +0 -0
  581. /package/dist/web/assets/{typespec-DP2q46GT.js → typespec-D7mchdY_.js} +0 -0
  582. /package/dist/web/assets/{typst-DFsD_yaD.js → typst-CeXzq6Yw.js} +0 -0
  583. /package/dist/web/assets/{use-is-mobile-BQ9Bv5Ga.js → use-is-mobile-TkcwP1Gx.js} +0 -0
  584. /package/dist/web/assets/{use-visual-viewport-CbwnktSG.js → use-visual-viewport-Lp878uzs.js} +0 -0
  585. /package/dist/web/assets/{v-Bji8hSVT.js → v-DhzO54je.js} +0 -0
  586. /package/dist/web/assets/{vala-D3TIgGmU.js → vala-Dw_RAk3E.js} +0 -0
  587. /package/dist/web/assets/{vb-DvcxFS2q.js → vb-CiXdNh2Y.js} +0 -0
  588. /package/dist/web/assets/{vendor-xterm-BqPDsE9b.js → vendor-xterm-BHEY6Ctu.js} +0 -0
  589. /package/dist/web/assets/{verilog-Dy7cF35d.js → verilog-fITdxqwV.js} +0 -0
  590. /package/dist/web/assets/{vesper-BzjwUv9J.js → vesper-B1wm_At7.js} +0 -0
  591. /package/dist/web/assets/{vhdl-DuFnFNmW.js → vhdl-ebHjqKp9.js} +0 -0
  592. /package/dist/web/assets/{viml-yAPgKlbz.js → viml-Dgmnoxoo.js} +0 -0
  593. /package/dist/web/assets/{vitesse-black-oSSQdyIZ.js → vitesse-black-BZ5gMb7l.js} +0 -0
  594. /package/dist/web/assets/{vitesse-dark-BZxwPihB.js → vitesse-dark-Lij7kkOt.js} +0 -0
  595. /package/dist/web/assets/{vitesse-light-uIigycvt.js → vitesse-light-DW6Wq7VC.js} +0 -0
  596. /package/dist/web/assets/{vyper-CX3NAxm5.js → vyper-6FxDnVNg.js} +0 -0
  597. /package/dist/web/assets/{wasm-6mEOOvVa.js → wasm-BDxaNPCj.js} +0 -0
  598. /package/dist/web/assets/{wasm-D9td37qO.js → wasm-DKhAtzfe.js} +0 -0
  599. /package/dist/web/assets/{wenyan-lwNIHgk6.js → wenyan-C0k0DYG_.js} +0 -0
  600. /package/dist/web/assets/{wgsl-DQAzCfog.js → wgsl-Y2zagbdn.js} +0 -0
  601. /package/dist/web/assets/{wikitext-DTXmt5NE.js → wikitext-C4QGWzkY.js} +0 -0
  602. /package/dist/web/assets/{wit-Bdw_7WDH.js → wit-CQESzjHy.js} +0 -0
  603. /package/dist/web/assets/{wolfram-DqyHDZ07.js → wolfram-B-ia69Ia.js} +0 -0
  604. /package/dist/web/assets/{yaml-CPXhdByd.js → yaml-CEYYTQPm.js} +0 -0
  605. /package/dist/web/assets/{zenscript-6s_i5zek.js → zenscript-BggWXIeJ.js} +0 -0
  606. /package/dist/web/assets/{zig-B2htezJ_.js → zig-CbaUfBQe.js} +0 -0
@@ -0,0 +1,250 @@
1
+ /**
2
+ * One shared Xlib connection for the X11 capture/input paths, over `bun:ffi` — no helper
3
+ * binary, matching `remote-desktop-input-win32.ts` (user32) and `-darwin.ts` (CoreGraphics).
4
+ *
5
+ * Two things here exist to keep a desktop problem from becoming a PPM problem:
6
+ *
7
+ * 1. **Xlib's default I/O error handler calls `exit()`.** If the X server goes away — a
8
+ * logout, a GPU driver reset, `systemctl restart sddm` — libX11 prints `XIO: fatal IO
9
+ * error` and terminates the *calling process*, which here is the whole PPM server: every
10
+ * project, terminal and chat session dies because someone logged out of the desktop. Both
11
+ * handlers are therefore replaced (`XSetIOErrorHandler` returns instead of falling through,
12
+ * and libX11 ≥ 1.7's `XSetIOErrorExitHandler` cancels the exit that otherwise happens even
13
+ * when the first one returns). Protocol errors (`BadWindow` from a monitor unplugged
14
+ * mid-enumeration) go to a no-op `XSetErrorHandler` rather than the default's stderr spew.
15
+ * The callbacks are held in module scope: a GC'd `JSCallback` would leave Xlib calling a
16
+ * freed trampoline, which is a segfault rather than the crash it was installed to prevent.
17
+ *
18
+ * On libX11 **1.6** that protection is only half there, and the code says so rather than
19
+ * assuming otherwise: the third handler does not exist before 1.7 (2021), so an I/O error
20
+ * still reaches the `exit()` the second handler returns into. It is loaded from its own
21
+ * dlopen table for that reason — `bun:ffi`'s `dlopen` throws on the *first* missing symbol
22
+ * and hands back none of the others, so one optional function in the main table would take
23
+ * capture, input, privacy mode and monitor enumeration down together on every host older
24
+ * than that (Ubuntu 20.04 ships 1.6.9).
25
+ *
26
+ * 2. **A connection that took an I/O error is not reusable.** There is no safe call left on it,
27
+ * not even `XCloseDisplay`, so it is dropped un-closed (a few KB, once per X server
28
+ * lifetime) and the next caller opens a fresh one.
29
+ *
30
+ * libXtst and libXrandr are optional: a host can have Xlib but no XTEST extension (input goes
31
+ * view-only) or no libXrandr (one whole-screen display instead of per-monitor). Neither is a
32
+ * reason to fail capture, so both load into `null` rather than throwing.
33
+ */
34
+ import { resetLinuxSession, type LinuxSession } from "./remote-desktop-linux-session.ts";
35
+
36
+ type Ffi = typeof import("bun:ffi");
37
+ /** Xlib pointer/handle as bun:ffi hands it back. */
38
+ type Ptr = number | bigint;
39
+
40
+ const X11_SYMBOLS = {
41
+ XOpenDisplay: { args: ["cstring"], returns: "ptr" },
42
+ XDefaultRootWindow: { args: ["ptr"], returns: "u64" },
43
+ XDefaultScreen: { args: ["ptr"], returns: "i32" },
44
+ XDisplayWidth: { args: ["ptr", "i32"], returns: "i32" },
45
+ XDisplayHeight: { args: ["ptr", "i32"], returns: "i32" },
46
+ XFlush: { args: ["ptr"], returns: "i32" },
47
+ XSync: { args: ["ptr", "i32"], returns: "i32" },
48
+ XGetAtomName: { args: ["ptr", "u64"], returns: "ptr" },
49
+ XFree: { args: ["ptr"], returns: "i32" },
50
+ XStringToKeysym: { args: ["cstring"], returns: "u64" },
51
+ XKeysymToKeycode: { args: ["ptr", "u64"], returns: "u8" },
52
+ XDisplayKeycodes: { args: ["ptr", "ptr", "ptr"], returns: "i32" },
53
+ XGetKeyboardMapping: { args: ["ptr", "u8", "i32", "ptr"], returns: "ptr" },
54
+ XChangeKeyboardMapping: { args: ["ptr", "i32", "i32", "ptr", "i32"], returns: "i32" },
55
+ XQueryPointer: { args: ["ptr", "u64", "ptr", "ptr", "ptr", "ptr", "ptr", "ptr", "ptr"], returns: "i32" },
56
+ // Privacy mode: an X grab with `owner_events = False` routes every local key and button to
57
+ // the grabbing client, which simply drops them. Paired with `XTestGrabControl` below, which
58
+ // is what keeps the *remote* session's injected events working through that grab.
59
+ XGrabKeyboard: { args: ["ptr", "u64", "i32", "i32", "i32", "u64"], returns: "i32" },
60
+ XGrabPointer: { args: ["ptr", "u64", "i32", "u32", "i32", "i32", "u64", "u64", "u64"], returns: "i32" },
61
+ XUngrabKeyboard: { args: ["ptr", "u64"], returns: "i32" },
62
+ XUngrabPointer: { args: ["ptr", "u64"], returns: "i32" },
63
+ XSetErrorHandler: { args: ["ptr"], returns: "ptr" },
64
+ XSetIOErrorHandler: { args: ["ptr"], returns: "ptr" },
65
+ } as const;
66
+
67
+ /** libX11 ≥ 1.7 only, which is why it is a table of its own rather than one more line above.
68
+ * Exported so a test can assert the split still holds: moving this key back into
69
+ * `X11_SYMBOLS` is a one-line change that costs every pre-1.7 host its input. */
70
+ export const X11_EXIT_SYMBOLS = {
71
+ XSetIOErrorExitHandler: { args: ["ptr", "ptr", "ptr"], returns: "void" },
72
+ } as const;
73
+
74
+ const XTST_SYMBOLS = {
75
+ XTestQueryExtension: { args: ["ptr", "ptr", "ptr", "ptr", "ptr"], returns: "i32" },
76
+ XTestFakeMotionEvent: { args: ["ptr", "i32", "i32", "i32", "u64"], returns: "i32" },
77
+ XTestFakeButtonEvent: { args: ["ptr", "u32", "i32", "u64"], returns: "i32" },
78
+ XTestFakeKeyEvent: { args: ["ptr", "u32", "i32", "u64"], returns: "i32" },
79
+ /** Makes this client's XTEST events *impervious to grabs* — the one call that lets privacy
80
+ * mode hold a keyboard/pointer grab without also blocking the remote session's own input. */
81
+ XTestGrabControl: { args: ["ptr", "i32"], returns: "i32" },
82
+ } as const;
83
+
84
+ const XRANDR_SYMBOLS = {
85
+ XRRGetMonitors: { args: ["ptr", "u64", "i32", "ptr"], returns: "ptr" },
86
+ XRRFreeMonitors: { args: ["ptr"], returns: "void" },
87
+ // Resolution switching. `…Current` rather than `XRRGetScreenResources`: the latter re-polls
88
+ // every output over DDC, which takes tens of milliseconds and can wake a sleeping monitor.
89
+ XRRGetScreenResourcesCurrent: { args: ["ptr", "u64"], returns: "ptr" },
90
+ XRRFreeScreenResources: { args: ["ptr"], returns: "void" },
91
+ XRRGetOutputInfo: { args: ["ptr", "ptr", "u64"], returns: "ptr" },
92
+ XRRFreeOutputInfo: { args: ["ptr"], returns: "void" },
93
+ XRRGetCrtcInfo: { args: ["ptr", "ptr", "u64"], returns: "ptr" },
94
+ XRRFreeCrtcInfo: { args: ["ptr"], returns: "void" },
95
+ XRRSetCrtcConfig: {
96
+ args: ["ptr", "ptr", "u64", "u64", "i32", "i32", "u64", "u16", "ptr", "i32"],
97
+ returns: "i32",
98
+ },
99
+ XRRSetScreenSize: { args: ["ptr", "u64", "i32", "i32", "i32", "i32"], returns: "void" },
100
+ } as const;
101
+
102
+ /** DPMS lives in libXext, not libX11. Used to turn the host's monitor off for privacy mode
103
+ * while leaving the framebuffer — and therefore the capture — alone. */
104
+ const DPMS_SYMBOLS = {
105
+ DPMSQueryExtension: { args: ["ptr", "ptr", "ptr"], returns: "i32" },
106
+ DPMSCapable: { args: ["ptr"], returns: "i32" },
107
+ DPMSEnable: { args: ["ptr"], returns: "i32" },
108
+ DPMSForceLevel: { args: ["ptr", "u16"], returns: "i32" },
109
+ DPMSInfo: { args: ["ptr", "ptr", "ptr"], returns: "i32" },
110
+ } as const;
111
+
112
+ /* eslint-disable @typescript-eslint/no-explicit-any -- dlopen's symbol types are per-call */
113
+ type X11Lib = { [K in keyof typeof X11_SYMBOLS]: any };
114
+ type X11ExitLib = { [K in keyof typeof X11_EXIT_SYMBOLS]: any };
115
+ type XtstLib = { [K in keyof typeof XTST_SYMBOLS]: any };
116
+ type XrandrLib = { [K in keyof typeof XRANDR_SYMBOLS]: any };
117
+ type DpmsLib = { [K in keyof typeof DPMS_SYMBOLS]: any };
118
+ /* eslint-enable @typescript-eslint/no-explicit-any */
119
+
120
+ export interface X11Connection {
121
+ ffi: Ffi;
122
+ x11: X11Lib;
123
+ /** null when the host has no libXtst — capture works, input does not. */
124
+ xtst: XtstLib | null;
125
+ /** null when the host has no libXrandr — one whole-screen display instead of per-monitor. */
126
+ xrandr: XrandrLib | null;
127
+ /** null when the host has no libXext, or its X server has no DPMS extension — privacy mode
128
+ * then blocks local input without blanking the monitor. */
129
+ dpms: DpmsLib | null;
130
+ dpy: Ptr;
131
+ root: bigint;
132
+ screen: number;
133
+ /** XTEST is present AND usable on this connection. */
134
+ hasXTest: boolean;
135
+ /** Which display this was opened against, so a later session change cannot reuse it. */
136
+ display: string;
137
+ }
138
+
139
+ let ffiModule: Ffi | null = null;
140
+ let connection: X11Connection | null = null;
141
+ /** Kept forever: Xlib holds raw pointers to these trampolines for the process lifetime. Named
142
+ * rather than an array, because the one read outside `installErrorHandlers` used to be
143
+ * `liveCallbacks[2]` — a fourth handler would have handed the wrong trampoline to Xlib, with
144
+ * a wrong signature, and nothing to say so until it was called. */
145
+ let errorCallbacks: { onError: Trampoline; onIoError: Trampoline; onIoExit: Trampoline } | null = null;
146
+ let connectionLost = false;
147
+
148
+ /** One `JSCallback`, whose `.ptr` is the address Xlib keeps for the process lifetime. */
149
+ type Trampoline = InstanceType<Ffi["JSCallback"]>;
150
+
151
+ function tryDlopen<T>(ffi: Ffi, names: string[], symbols: object): T | null {
152
+ for (const name of names) {
153
+ try { return ffi.dlopen(name, symbols as never).symbols as T; } catch { /* try the next soname */ }
154
+ }
155
+ return null;
156
+ }
157
+
158
+ /** Replace the three handlers that would otherwise print to stderr or kill the process. */
159
+ function installErrorHandlers(ffi: Ffi, x11: X11Lib): void {
160
+ if (errorCallbacks) return;
161
+ const { JSCallback } = ffi;
162
+
163
+ // Protocol error (BadWindow/BadMatch): return 0, the same as the default handler minus stderr.
164
+ const onError = new JSCallback(() => 0, { args: ["ptr", "ptr"], returns: "i32" });
165
+ // I/O error: server gone. Mark dead and return — libX11 would otherwise fall through to exit.
166
+ const onIoError = new JSCallback(() => { connectionLost = true; return 0; }, { args: ["ptr"], returns: "i32" });
167
+ // The exit that happens anyway once the handler above returns. Cancelled by doing nothing.
168
+ const onIoExit = new JSCallback(() => { connectionLost = true; }, { args: ["ptr", "ptr"], returns: "void" });
169
+
170
+ x11.XSetErrorHandler(onError.ptr);
171
+ x11.XSetIOErrorHandler(onIoError.ptr);
172
+ // Written last, and this is the whole of the "installed" flag: the old boolean was set
173
+ // *before* the two calls above, so anything that read it in between was told the process was
174
+ // protected while the default handlers were still in place.
175
+ errorCallbacks = { onError, onIoError, onIoExit };
176
+ }
177
+
178
+ /** The shared connection for `session`, or null when Xlib is missing or the server refuses us
179
+ * (no `$DISPLAY` server running, an auth cookie we cannot read). */
180
+ export async function getX11(session: LinuxSession): Promise<X11Connection | null> {
181
+ if (session.kind !== "x11") return null;
182
+ if (connectionLost) { connection = null; connectionLost = false; }
183
+ // Matched on the display, not merely on there being a connection. The session is re-probed
184
+ // periodically now, so it can name a different display on a still-live server — a second
185
+ // seat, a nested Xephyr, `:0` becoming `:1`. Handing back the old `Display*` then sends
186
+ // every keystroke and click into the display nobody is looking at, silently and for good:
187
+ // nothing errors, because the old server is still there.
188
+ // Dropped rather than closed, the way `resetX11` and the module header already decide: Xlib's
189
+ // I/O error handler can `exit()` the server process, so `XCloseDisplay` is never called here.
190
+ if (connection && connection.display !== session.display) connection = null;
191
+ if (connection) return connection;
192
+
193
+ if (!ffiModule) ffiModule = await import("bun:ffi");
194
+ const ffi = ffiModule;
195
+ const x11 = tryDlopen<X11Lib>(ffi, ["libX11.so.6", "libX11.so"], X11_SYMBOLS);
196
+ if (!x11) return null;
197
+ // Same library, second `dlopen`: null here means libX11 1.6, not a missing libX11.
198
+ const x11Exit = tryDlopen<X11ExitLib>(ffi, ["libX11.so.6", "libX11.so"], X11_EXIT_SYMBOLS);
199
+ installErrorHandlers(ffi, x11);
200
+
201
+ const dpy = x11.XOpenDisplay(Buffer.from(`${session.display}\0`));
202
+ if (!dpy) return null;
203
+
204
+ const xtst = tryDlopen<XtstLib>(ffi, ["libXtst.so.6", "libXtst.so"], XTST_SYMBOLS);
205
+ const xrandr = tryDlopen<XrandrLib>(ffi, ["libXrandr.so.2", "libXrandr.so"], XRANDR_SYMBOLS);
206
+ const xext = tryDlopen<DpmsLib>(ffi, ["libXext.so.6", "libXext.so"], DPMS_SYMBOLS);
207
+
208
+ let hasXTest = false;
209
+ if (xtst) {
210
+ const out = new Int32Array(1);
211
+ hasXTest = xtst.XTestQueryExtension(dpy, ffi.ptr(out), ffi.ptr(out), ffi.ptr(out), ffi.ptr(out)) !== 0;
212
+ }
213
+
214
+ // The exit handler needs the Display, so it is installed after the connection exists — and
215
+ // only where the symbol resolved. The guard was on whether a *callback* existed, which is
216
+ // always true and says nothing about whether libX11 has the function to pass it to.
217
+ if (x11Exit && errorCallbacks) x11Exit.XSetIOErrorExitHandler(dpy, errorCallbacks.onIoExit.ptr, null);
218
+
219
+ // libXext being present is not enough: a server can be built without the DPMS extension, and
220
+ // `DPMSForceLevel` against one of those is a no-op that returns success.
221
+ let dpms: DpmsLib | null = null;
222
+ if (xext) {
223
+ const out = new Int32Array(2);
224
+ const present = xext.DPMSQueryExtension(dpy, ffi.ptr(out), ffi.ptr(out.subarray(1))) !== 0;
225
+ if (present && xext.DPMSCapable(dpy) !== 0) dpms = xext;
226
+ }
227
+
228
+ connection = {
229
+ ffi, x11, xtst, xrandr, dpms, dpy,
230
+ root: BigInt(x11.XDefaultRootWindow(dpy)),
231
+ screen: x11.XDefaultScreen(dpy),
232
+ hasXTest,
233
+ display: session.display,
234
+ };
235
+ return connection;
236
+ }
237
+
238
+ /** `bun:ffi` types every address as a branded `Pointer`, but walking a C array means doing
239
+ * arithmetic on a plain number. One cast, here, instead of at every struct field. */
240
+ export function asPointer(address: number): import("bun:ffi").Pointer {
241
+ return address as unknown as import("bun:ffi").Pointer;
242
+ }
243
+
244
+ /** Drop the cached connection (tests, and after a session change) and, with it, the memoized
245
+ * session it was opened against. */
246
+ export function resetX11(): void {
247
+ connection = null;
248
+ connectionLost = false;
249
+ resetLinuxSession();
250
+ }
@@ -11,6 +11,7 @@ import {
11
11
  unlinkSync, statSync,
12
12
  } from "node:fs";
13
13
  import { getPpmDir } from "./ppm-dir.ts";
14
+ import { stdioIsLogFile, consumeStdioIsLogEnv, rotateIfOversized, MAX_LOG_BYTES, STDIO_IS_LOG_ENV } from "./log-rotate.ts";
14
15
  import { isCompiledBinary } from "./autostart-generator.ts";
15
16
  import { cleanupStaleBinaryUpgradeArtifacts } from "./binary-upgrade-swap.ts";
16
17
  import {
@@ -60,6 +61,7 @@ const DB_BACKUP_STALE_WARN_MS = 21_600_000; // 6h — snapshots stopped happenin
60
61
  const SELF_REPLACE_TIMEOUT_MS = 30_000; // 30s to wait for new supervisor
61
62
  const EDGE_PROBE_INTERVAL_MS = 10_000; // the public port is dark while the edge is down — check often
62
63
  const SERVER_PORT_MIRROR_TIMEOUT_MS = 30_000; // how long to wait for the server to publish its port
64
+ const LOG_ROTATE_INTERVAL_MS = 60_000; // how often ppm.log is checked against its cap
63
65
 
64
66
  const logFile = () => resolve(getPpmDir(), "ppm.log");
65
67
  const restartingFlag = () => resolve(getPpmDir(), ".restarting");
@@ -154,12 +156,51 @@ let cloudConnected = false; // tracks whether we've initiated a cloud WS connect
154
156
  let originalArgv: string[] = [];
155
157
 
156
158
  // ─── Logging ───────────────────────────────────────────────────────────
159
+
160
+ /**
161
+ * Whether this process's own stderr already lands in ppm.log.
162
+ *
163
+ * Normally it does not — under systemd stderr is the journal, and the
164
+ * supervisor's lines belong there as well as in the log. But the replacement
165
+ * supervisor of a self-upgrade is spawned with `stdio: ["ignore", newLogFd,
166
+ * newLogFd]`, and from then on every line would be written to the file twice.
167
+ * Resolved once, lazily: fd 2 does not change underneath a running process.
168
+ */
169
+ let stderrIsLogFile: boolean | null = null;
170
+
157
171
  function log(level: string, msg: string) {
158
172
  const ts = new Date().toISOString();
159
173
  const line = `[${ts}] [${level}] [supervisor] ${msg}\n`;
160
174
  try { appendFileSync(logFile(), line); } catch {}
161
- // Always write supervisor logs to stderr so journalctl captures them
162
- try { process.stderr.write(line); } catch {}
175
+ if (stderrIsLogFile === null) {
176
+ stderrIsLogFile = stdioIsLogFile(2, logFile());
177
+ consumeStdioIsLogEnv();
178
+ }
179
+ // Write supervisor logs to stderr so journalctl captures them — unless
180
+ // stderr is the log file itself, where that is the same line again.
181
+ if (!stderrIsLogFile) { try { process.stderr.write(line); } catch {} }
182
+ }
183
+
184
+ let logRotateTimer: ReturnType<typeof setInterval> | null = null;
185
+
186
+ /**
187
+ * Keep ppm.log under its cap. Only the supervisor does this.
188
+ *
189
+ * It is the long-lived owner of the descriptor every child was handed, and two
190
+ * processes truncating one file would race. Deliberately *not* run once at
191
+ * startup: the first rotation of a log that has been left to grow copies the
192
+ * whole of it, and doing that during boot adds to the one stall this work is
193
+ * trying to remove. The first tick is a minute away and nothing is worse for
194
+ * waiting.
195
+ */
196
+ function startLogRotation() {
197
+ if (logRotateTimer) return;
198
+ logRotateTimer = setInterval(() => {
199
+ if (rotateIfOversized(logFile())) {
200
+ log("INFO", `Rotated ppm.log at cap ${Math.round(MAX_LOG_BYTES / 1048576)} MB`);
201
+ }
202
+ }, LOG_ROTATE_INTERVAL_MS);
203
+ logRotateTimer.unref?.();
163
204
  }
164
205
 
165
206
  // ─── Backoff calc ──────────────────────────────────────────────────────
@@ -622,7 +663,8 @@ export async function spawnServer(
622
663
  serverChild = await withProbeSpawnGate(() => Bun.spawn({
623
664
  cmd,
624
665
  stdio: ["ignore", logFd, logFd],
625
- env: process.env,
666
+ // The child cannot work out on Windows that fd 1 is already the log, so it is told.
667
+ env: { ...process.env, [STDIO_IS_LOG_ENV]: "1" },
626
668
  // No visible console window. Critical on Windows after an upgrade: the new
627
669
  // supervisor is spawned consoleless (detached), so without this its console
628
670
  // children — and the Claude SDK grandchildren they spawn — pop blank windows.
@@ -1509,7 +1551,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
1509
1551
  const proc = await withProbeSpawnGate(() => nodeSpawn(cmd[0]!, cmd.slice(1), {
1510
1552
  detached: true,
1511
1553
  stdio: ["ignore", newLogFd, newLogFd] as any,
1512
- env: process.env as NodeJS.ProcessEnv,
1554
+ env: { ...process.env, [STDIO_IS_LOG_ENV]: "1" } as NodeJS.ProcessEnv,
1513
1555
  windowsHide: true,
1514
1556
  }));
1515
1557
  const killNewChild = () => { try { if (proc.pid) process.kill(proc.pid); } catch {} };
@@ -1873,6 +1915,7 @@ export async function runSupervisor(opts: {
1873
1915
  originalArgv = [...process.argv];
1874
1916
 
1875
1917
  const logFd = openSync(logFile(), "a");
1918
+ startLogRotation();
1876
1919
  log("INFO", `Supervisor started (PID: ${process.pid}, port: ${opts.port}, share: ${opts.share})`);
1877
1920
 
1878
1921
  // ── Systemd self-heal: if the unit file is stale (e.g. still has
@@ -0,0 +1,117 @@
1
+ /**
2
+ * RustDesk's "Custom image quality": a bitrate *percentage* and an fps, with the real bitrate
3
+ * derived from the capture's own resolution. Ported rather than invented, because two parts of
4
+ * it are counter-intuitive enough that a reasonable guess is wrong:
5
+ *
6
+ * 1. **The percentage is not the ratio.** `video_qos.rs:444` reads it back as
7
+ * `((q >> 8 & 0xFFF) * 2) / 100.0`, so the ratio is `percent × 2 / 100` — the dialog's
8
+ * default **50% means ratio 1.0**, i.e. exactly `base_bitrate`. Reading 50% as ratio 0.5
9
+ * (the obvious reading) makes every custom setting half as sharp as the user asked for,
10
+ * and nothing reports it: the picture is simply dimmer than the number promises.
11
+ * 2. **`base_bitrate` is a nearest-preset lookup, not a formula.** `codec.rs:929` finds the
12
+ * table row whose *pixel count* is closest to the source's, then scales that row's bitrate
13
+ * by the pixel ratio. A plain bits-per-pixel constant does not reproduce it, because the
14
+ * table is deliberately non-linear (720p→1000, 1080p→2073, 4K→5000: 4× the pixels of
15
+ * 1080p for 2.4× the bits).
16
+ *
17
+ * The fixed rungs use the same machinery in RustDesk (`Quality::ratio()` → Best 1.5,
18
+ * Balanced 0.67, Low 0.5); PPM keeps its own absolute-bitrate ladder for those, so only the
19
+ * custom path needs this.
20
+ */
21
+
22
+ /** `RESOLUTION_PRESETS` from `libs/scrap/src/common/codec.rs`, kbps. */
23
+ const RESOLUTION_BITRATE_PRESETS: readonly (readonly [number, number, number])[] = [
24
+ [640, 480, 400],
25
+ [800, 600, 500],
26
+ [1024, 768, 800],
27
+ [1280, 720, 1000],
28
+ [1366, 768, 1100],
29
+ [1440, 900, 1300],
30
+ [1600, 900, 1500],
31
+ [1920, 1080, 2073],
32
+ [2048, 1080, 2200],
33
+ [2560, 1440, 3000],
34
+ [3440, 1440, 4000],
35
+ [3840, 2160, 5000],
36
+ [7680, 4320, 12000],
37
+ ];
38
+
39
+ /** `kMinQuality` / `kDefaultQuality` / `kMaxQuality` / `kMaxMoreQuality` from `consts.dart`. */
40
+ export const CUSTOM_QUALITY_MIN_PERCENT = 10;
41
+ export const CUSTOM_QUALITY_DEFAULT_PERCENT = 50;
42
+ export const CUSTOM_QUALITY_MAX_PERCENT = 100;
43
+ /** The "More" checkbox raises the ceiling this far — 2000% is ratio 40, which is `BR_MAX`. */
44
+ export const CUSTOM_QUALITY_MAX_MORE_PERCENT = 2000;
45
+
46
+ /** `kMinFps` / `kDefaultFps` / `kMaxFps`. */
47
+ export const CUSTOM_FPS_MIN = 5;
48
+ export const CUSTOM_FPS_DEFAULT = 30;
49
+ export const CUSTOM_FPS_MAX = 120;
50
+
51
+ /** `BR_MIN` / `BR_MAX` from `video_qos.rs`. */
52
+ const BR_MIN = 0.2;
53
+ const BR_MAX = 40.0;
54
+
55
+ /** Nearest-pixel-count preset, scaled by the pixel ratio. See note 2. */
56
+ export function baseBitrateKbps(width: number, height: number): number {
57
+ const pixels = Math.max(1, Math.round(width) * Math.round(height));
58
+ let best = RESOLUTION_BITRATE_PRESETS[7]!; // 1080p, the table's own fallback
59
+ let bestDistance = Infinity;
60
+ for (const preset of RESOLUTION_BITRATE_PRESETS) {
61
+ const distance = Math.abs(preset[0] * preset[1] - pixels);
62
+ if (distance < bestDistance) { bestDistance = distance; best = preset; }
63
+ }
64
+ const presetPixels = best[0] * best[1];
65
+ // Floored at 1: a 0x0 capture (before the first frame) otherwise scales the smallest preset
66
+ // down to `round(400 / 307200)` = **0**, and ffmpeg refuses a zero bitrate outright rather
67
+ // than picking something sensible.
68
+ return Math.max(1, Math.round(best[2] * (pixels / presetPixels)));
69
+ }
70
+
71
+ /** Percent → bitrate ratio. `percent × 2 / 100`, clamped. See note 1. */
72
+ export function customQualityRatio(percent: number): number {
73
+ if (!Number.isFinite(percent)) return (CUSTOM_QUALITY_DEFAULT_PERCENT * 2) / 100;
74
+ return Math.min(Math.max((percent * 2) / 100, BR_MIN), BR_MAX);
75
+ }
76
+
77
+ /** Clamp any bitrate ratio to RustDesk's own bounds. Named rungs already sit inside them; the
78
+ * congestion scaling in `remote-desktop-quality.ts` is what can push one out. */
79
+ export function clampRatio(ratio: number): number {
80
+ if (!Number.isFinite(ratio)) return BR_MIN;
81
+ return Math.min(Math.max(ratio, BR_MIN), BR_MAX);
82
+ }
83
+
84
+ /** The bitrate a ratio really encodes at, for this capture size. Every rung goes through here —
85
+ * a named one, a congestion-scaled one and the custom dialog's percentage alike, which is what
86
+ * makes "the chosen quality is a ceiling on the ratio" a statement about one number. */
87
+ export function ratioBitrateKbps(ratio: number, width: number, height: number): number {
88
+ // Floored at 1: the table's smallest row makes `baseBitrateKbps(0, 0)` round to 0, and ffmpeg
89
+ // refuses a zero bitrate outright.
90
+ return Math.max(1, Math.round(baseBitrateKbps(width, height) * clampRatio(ratio)));
91
+ }
92
+
93
+ /** An ffmpeg bitrate string (`-b:v`/`-maxrate`) for a ratio. */
94
+ export function ratioBitrateArg(ratio: number, width: number, height: number): string {
95
+ return `${ratioBitrateKbps(ratio, width, height)}k`;
96
+ }
97
+
98
+ /** The bitrate a custom setting really encodes at, for this capture size. */
99
+ export function customBitrateKbps(percent: number, width: number, height: number): number {
100
+ return ratioBitrateKbps(customQualityRatio(percent), width, height);
101
+ }
102
+
103
+ /** An ffmpeg bitrate string (`-b:v`/`-maxrate`) for a custom setting. */
104
+ export function customBitrateArg(percent: number, width: number, height: number): string {
105
+ return `${customBitrateKbps(percent, width, height)}k`;
106
+ }
107
+
108
+ export function clampCustomQualityPercent(value: unknown, allowMore = false): number {
109
+ const max = allowMore ? CUSTOM_QUALITY_MAX_MORE_PERCENT : CUSTOM_QUALITY_MAX_PERCENT;
110
+ if (typeof value !== "number" || !Number.isFinite(value)) return CUSTOM_QUALITY_DEFAULT_PERCENT;
111
+ return Math.min(Math.max(Math.round(value), CUSTOM_QUALITY_MIN_PERCENT), max);
112
+ }
113
+
114
+ export function clampCustomFps(value: unknown): number {
115
+ if (typeof value !== "number" || !Number.isFinite(value)) return CUSTOM_FPS_DEFAULT;
116
+ return Math.min(Math.max(Math.round(value), CUSTOM_FPS_MIN), CUSTOM_FPS_MAX);
117
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The remote-desktop quality ladder — shared, because both ends need the same table: the
3
+ * server turns a rung into ffmpeg args, and the client renders the picker and shows which rung
4
+ * is active. One definition so a label or a rung cannot drift between them.
5
+ *
6
+ * This is RustDesk's own set, ported rather than invented: `toolbarImageQuality` in
7
+ * `flutter/lib/common/widgets/toolbar.dart` builds exactly four radio items — Good image
8
+ * quality, Balanced, Optimize reaction time, Custom — and `Quality` in
9
+ * `libs/scrap/src/common/codec.rs` is the matching enum with the three ratios below.
10
+ *
11
+ * Two things follow from that, and both are the opposite of what this file used to do:
12
+ *
13
+ * 1. **A rung never changes the resolution.** It sets a bitrate ratio, and the stream stays at
14
+ * the host's native size. The old ladder capped height per rung (720p on `balanced`), which
15
+ * on a 3440x1440 host threw away 4.8x the pixels — and measured, it bought almost nothing:
16
+ * 720p used 1.17 Mbit/s against 1.26 Mbit/s for native at the same cap, because a desktop is
17
+ * mostly static and H.264 spends bits on *change*, not on area. Neither came close to the
18
+ * cap it was given, so the downscale was pure loss.
19
+ * 2. **There is no `auto` rung.** RustDesk has no such menu item because the adaptation is
20
+ * always running: the chosen quality is the *maximum* ratio (`video_qos.rs`: "user set image
21
+ * quality => update to the maximum ratio of the latest quality") and congestion lowers the
22
+ * effective ratio underneath it. A pin that switched adaptation *off* is what made a chosen
23
+ * rung flicker forever on a link that could not carry it.
24
+ *
25
+ * The *policy* built on top of this (how far the ratio may fall, and when it moves) is server
26
+ * side only, in `src/services/remote-desktop/remote-desktop-quality.ts`.
27
+ */
28
+
29
+ export type QualityPresetId = "best" | "balanced" | "low";
30
+
31
+ /** One entry of the Image quality menu: a label and the bitrate ratio it asks for. Deliberately
32
+ * carries no resolution and no bitrate — the real bitrate depends on the capture size, so it
33
+ * can only be resolved once the display is known (`resolveQuality`). */
34
+ export interface QualityRung {
35
+ id: QualityPresetId;
36
+ label: string;
37
+ /** RustDesk's `BR_BEST` / `BR_BALANCED` / `BR_SPEED` from `codec.rs`, unchanged. */
38
+ ratio: number;
39
+ }
40
+
41
+ /** What the capture layer needs: a frame rate and an ffmpeg bitrate string. A rung plus a
42
+ * capture size resolves to one of these; so does the custom dialog's percentage. */
43
+ export interface QualityPreset {
44
+ fps: number;
45
+ /** ffmpeg bitrate string, used for both `-b:v` and `-maxrate`. */
46
+ bitrate: string;
47
+ }
48
+
49
+ export const QUALITY_PRESETS: Record<QualityPresetId, QualityRung> = {
50
+ best: { id: "best", label: "Good image quality", ratio: 1.5 },
51
+ balanced: { id: "balanced", label: "Balanced", ratio: 0.67 },
52
+ low: { id: "low", label: "Optimize reaction time", ratio: 0.5 },
53
+ };
54
+
55
+ /* "Optimize reaction time" is RustDesk's own name for the 0.5 rung and it is now the right one,
56
+ * where on the old ladder it would have been backwards. There, the low rungs downscaled and the
57
+ * *high* rungs were the low-latency ones — measured 1440p60 `gap p50=16.6ms` against 720p30
58
+ * `p50=33.3ms`. With resolution out of the picture the only thing a lower rung changes is how
59
+ * many bytes have to cross the link per frame, so less really does mean sooner. */
60
+
61
+ /** RustDesk's menu order, which is best-first — not the worst-to-best order a ladder walks. */
62
+ export const QUALITY_PRESET_ORDER: readonly QualityPresetId[] = ["best", "balanced", "low"];
63
+
64
+ /** `Quality::default()` in `codec.rs` is `Balanced`. */
65
+ export const DEFAULT_PRESET_ID: QualityPresetId = "balanced";
66
+
67
+ /** `pub const FPS: u32 = 30` in `video_qos.rs` — what a named rung streams at. Only the custom
68
+ * dialog sets a frame rate directly; RustDesk otherwise adapts it, never exposes it. */
69
+ export const DEFAULT_FPS = 30;
70
+
71
+ /** Coerce whatever came over the wire into a real preset id, or null for garbage.
72
+ * `Object.hasOwn`, never `in`: `in` walks the prototype chain, so `"toString"` off the wire
73
+ * would pass as a preset id and `QUALITY_PRESETS["toString"]` would hand the capture pipeline
74
+ * a *function* where it expected a rung — nonsense ffmpeg args from one client string. */
75
+ export function parsePresetId(value: unknown): QualityPresetId | null {
76
+ return typeof value === "string" && Object.hasOwn(QUALITY_PRESETS, value)
77
+ ? value as QualityPresetId
78
+ : null;
79
+ }
80
+
81
+ /** What the client can ask for. `custom` only means something together with the two numbers
82
+ * from its dialog, which is why it is a separate arm rather than a fourth rung. */
83
+ export type QualityChoice = QualityPresetId | "custom";
84
+
85
+ /** A custom rung: RustDesk's bitrate percentage + fps, at the host's native resolution — the
86
+ * same resolution every named rung uses (see `remote-desktop-custom-quality.ts`). */
87
+ export interface CustomQuality {
88
+ /** Bitrate percentage; the real bitrate comes from the capture size. */
89
+ percent: number;
90
+ fps: number;
91
+ }
92
+
93
+ /** Coerce a stored/untrusted value into a `QualityChoice`, falling back to the default rung.
94
+ * Separate from `parsePresetId` because the persisted device pref may legitimately be
95
+ * `"custom"`, and because localStorage is user-writable — a rung read back from it is
96
+ * untrusted input exactly like one off the wire, with the same `Object.hasOwn` hazard behind
97
+ * it. A pref left over from the old five-rung ladder (`"tiny"`, `"max"`, `"auto"`) parses as
98
+ * garbage and lands on the default, which is what should happen to a rung that no longer
99
+ * exists. */
100
+ export function parseQualityChoice(value: unknown): QualityChoice {
101
+ if (value === "custom") return value;
102
+ return parsePresetId(value) ?? DEFAULT_PRESET_ID;
103
+ }
104
+
105
+ /** Next value for the mobile toolbar's one-tap-per-hop button, in menu order. A dropdown in the
106
+ * thumb zone is worse than a button that cycles.
107
+ *
108
+ * `custom` is deliberately not in the cycle: it only means something together with the two
109
+ * numbers from its dialog, so a one-tap button that landed on it would change the stream to
110
+ * whatever was last typed there — surprising, and with no visible cause. */
111
+ export function nextQualityChoice(current: QualityChoice): QualityChoice {
112
+ if (current === "custom") return QUALITY_PRESET_ORDER[0]!;
113
+ const at = QUALITY_PRESET_ORDER.indexOf(current);
114
+ return QUALITY_PRESET_ORDER[(at + 1) % QUALITY_PRESET_ORDER.length]!;
115
+ }
116
+
117
+ /** Compact label for a toolbar button, where the full label does not fit. RustDesk has no such
118
+ * button, so these are the shortest thing that still distinguishes the four. */
119
+ const SHORT_LABELS: Record<QualityPresetId, string> = {
120
+ best: "Good",
121
+ balanced: "Balanced",
122
+ low: "Reaction",
123
+ };
124
+
125
+ export function shortQualityLabel(choice: QualityChoice): string {
126
+ return choice === "custom" ? "Custom" : SHORT_LABELS[choice];
127
+ }
package/src/types/chat.ts CHANGED
@@ -50,6 +50,10 @@ export interface AIProvider {
50
50
  onToolApproval?: (callback: ToolApprovalHandler) => void;
51
51
  abortQuery?(sessionId: string, source?: string): void;
52
52
  getMessages?(sessionId: string): Promise<ChatMessage[]>;
53
+ /** Every message in the transcript, including the segments before each compaction.
54
+ * `getMessages` answers with the resumable *conversation*; this answers with the
55
+ * whole history. Only the search index asks for it — see `indexSession`. */
56
+ getFullMessages?(sessionId: string): Promise<ChatMessage[]>;
53
57
  listSessionsByDir?(dir: string, opts?: { limit?: number; offset?: number }): Promise<SessionInfo[]>;
54
58
  ensureProjectPath?(sessionId: string, path: string): void;
55
59
  setForkSource?(sessionId: string, sourceSessionId: string): void;
package/src/types/git.ts CHANGED
@@ -59,6 +59,24 @@ export interface GitDiffFile {
59
59
  content: string;
60
60
  }
61
61
 
62
+ /**
63
+ * Both sides of a single-file diff, as whole files.
64
+ *
65
+ * `binary` is decided from the bytes, and when it is true both sides come back
66
+ * **empty**: decoded, a PNG is megabytes of U+FFFD that no one can read and that
67
+ * JSON has to escape. The viewer asks again with `text=1` ("Open Anyway") when
68
+ * the user wants them anyway. A null size means the file does not exist on that
69
+ * side, which is how the binary view tells an added or deleted file from a
70
+ * changed one.
71
+ */
72
+ export interface FileFullDiff {
73
+ original: string;
74
+ modified: string;
75
+ binary: boolean;
76
+ originalSize: number | null;
77
+ modifiedSize: number | null;
78
+ }
79
+
62
80
  export interface GitWorktree {
63
81
  /** Absolute path to the worktree directory */
64
82
  path: string;
@@ -79,3 +97,37 @@ export interface GitWorktree {
79
97
  /** True if this worktree can be pruned (directory missing/stale) */
80
98
  prunable: boolean;
81
99
  }
100
+
101
+ /**
102
+ * A whole branch's changes against a base, as one list.
103
+ *
104
+ * `mergeBase` is the commit every per-file diff has to be opened against — in
105
+ * three-dot mode it is where the two refs diverged, not `base` itself.
106
+ */
107
+ export interface BranchDiff {
108
+ base: string;
109
+ head: string;
110
+ mode: "three-dot" | "two-dot";
111
+ mergeBase: string;
112
+ /**
113
+ * `head` resolved to a commit, which is what a file must be opened against.
114
+ * `head` itself is a ref name and moves: a commit landing between the list
115
+ * fetch and a file being opened would show the new tip beside counts and a
116
+ * blob id describing the old one.
117
+ */
118
+ headCommit: string;
119
+ /** Files omitted from `files` because the list hit its cap; 0 when complete. */
120
+ omitted: number;
121
+ files: BranchDiffFile[];
122
+ }
123
+
124
+ export interface BranchDiffFile {
125
+ path: string;
126
+ oldPath?: string;
127
+ status: "A" | "M" | "D" | "R" | "C" | "T";
128
+ additions: number;
129
+ deletions: number;
130
+ binary: boolean;
131
+ /** Head-side blob id; what a "reviewed" flag is remembered against. */
132
+ blob: string;
133
+ }