@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
@@ -27,7 +27,7 @@ import { SUBSCRIPTION_PROMPT_CACHE_TTL_MS, API_KEY_PROMPT_CACHE_TTL_MS } from ".
27
27
  import { buildTurnUsage, formatTurnUsageLog } from "../shared/turn-usage.ts";
28
28
  import { accountSelector } from "../services/account-selector.service.ts";
29
29
  import { accountService, type AccountWithTokens } from "../services/account.service.ts";
30
- import { parseSessionMessage, nestChildEventsAcrossMessages } from "../services/jsonl-transcript-parser.ts";
30
+ import { parseSessionMessage, nestChildEventsAcrossMessages, parseJsonlTranscript, fullParseWindow } from "../services/jsonl-transcript-parser.ts";
31
31
  import { applyBackgroundAgentStatus } from "../shared/background-agent-status.ts";
32
32
  import { mergeSubagentChildren, resolveSessionDir } from "../services/subagent-transcript-merger.ts";
33
33
  import { stringifyToolResultContent } from "../shared/tool-result-content.ts";
@@ -2133,6 +2133,47 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2133
2133
  }
2134
2134
  }
2135
2135
 
2136
+ /**
2137
+ * Every message in the transcript, compacted-away segments included.
2138
+ *
2139
+ * `getSessionMessages` walks back from the newest message through
2140
+ * `parentUuid`, and the `compact_boundary` record Claude Code writes when it
2141
+ * compacts carries `parentUuid: null` — so the walk stops there and
2142
+ * `getMessages` answers with the last segment only. That is right for the
2143
+ * chat view, which shows the compact summary with a "Load previous
2144
+ * conversation" button beside it. The search index has no such affordance:
2145
+ * asking it the same question indexed 179 of one session's 1084 messages and
2146
+ * left the oldest 18 hours of it unfindable by any query. Reading the file
2147
+ * linearly ignores the `parentUuid` chain and costs less than the SDK call
2148
+ * (40ms for a 6.9MB transcript).
2149
+ */
2150
+ async getFullMessages(sessionId: string, opts?: { maxBytes?: number }): Promise<ChatMessage[]> {
2151
+ const transcriptDir = resolveSessionDir(sessionId, getSessionProjectPath(sessionId));
2152
+ if (transcriptDir) {
2153
+ const file = `${transcriptDir}.jsonl`;
2154
+ // Nothing else bounds this path. `validateJsonlPath` guards the route
2155
+ // that serves one chat's pre-compact scroll; the search indexer arrives
2156
+ // here instead, unattended and once per stale session — and a bumped
2157
+ // INDEXER_VERSION makes that every session on disk. Parsing costs about
2158
+ // 4.3× the file in transient allocator growth (measured: 88.6MB → +355MB
2159
+ // RSS, which neither a forced GC nor dropping the array gives back), so
2160
+ // past the bound the newest `FULL_PARSE_MAX_BYTES` are read and the rest
2161
+ // of the history is left out of the index rather than out of memory.
2162
+ // `maxBytes` is a parameter only so a test can assert the window against a
2163
+ // one-byte-over fixture: a real 128MB transcript is a 12s read even when
2164
+ // the point is that most of it goes unread.
2165
+ const fromByte = fullParseWindow(file, opts?.maxBytes);
2166
+ if (fromByte > 0) {
2167
+ console.warn(`[sdk] getFullMessages: ${sessionId} transcript is over the full-parse bound — indexing from byte ${fromByte} on, older segments stay out of the index`);
2168
+ }
2169
+ try {
2170
+ const fromFile = await parseJsonlTranscript(file, undefined, { fromByte });
2171
+ if (fromFile.length > 0) return fromFile;
2172
+ } catch { /* unreadable or malformed — fall back to the conversation */ }
2173
+ }
2174
+ return this.getMessages(sessionId);
2175
+ }
2176
+
2136
2177
  async getMessages(sessionId: string): Promise<ChatMessage[]> {
2137
2178
  try {
2138
2179
  const messages = await getSessionMessages(sessionId);
@@ -97,7 +97,8 @@ function fnOutputToToolResult(p: Record<string, unknown>): ChatEvent {
97
97
  export function parseRolloutJsonl(
98
98
  text: string,
99
99
  opts?: {
100
- preCompact?: boolean;
100
+ /** Which compaction boundary to stop at, counting from the start of the file. */
101
+ preCompactIndex?: number;
101
102
  /** Reads a spawned thread's transcript so its card can nest it. */
102
103
  loadSubagent?: (threadId: string) => SubagentTranscript | null;
103
104
  },
@@ -105,6 +106,7 @@ export function parseRolloutJsonl(
105
106
  const messages: ChatMessage[] = [];
106
107
  let i = 0;
107
108
  let pendingEvents: ChatEvent[] = [];
109
+ let compactionsSeen = 0;
108
110
  // Kept so the completion can answer the card the spawn opened, with the
109
111
  // report the child ended on.
110
112
  const subagentTranscripts = new Map<string, SubagentTranscript | null>();
@@ -184,8 +186,19 @@ export function parseRolloutJsonl(
184
186
  else if (typeof p.type === "string" && p.type.endsWith("_call_output")) pendingEvents.push(fnOutputToToolResult(p));
185
187
  else if (typeof p.type === "string" && p.type.endsWith("_call")) pendingEvents.push(genericCallToToolUse(p));
186
188
  } else if (rec.type === "compacted") {
187
- // Pre-compact mode: everything accumulated so far IS the pre-compact history.
188
- if (opts?.preCompact) { if (pendingEvents.length) flushAssistant("", ts); break; }
189
+ if (opts?.preCompactIndex) {
190
+ // One segment per request, the way the Claude parser's `oneSegment` does.
191
+ // The requested boundary ends the walk; every boundary before it closes a
192
+ // stretch nobody asked for, so what was collected is dropped rather than
193
+ // prepended — the card at the head of the segment is what leads back to it.
194
+ if (++compactionsSeen === opts.preCompactIndex) {
195
+ if (pendingEvents.length) flushAssistant("", ts);
196
+ break;
197
+ }
198
+ pendingEvents = [];
199
+ messages.length = 0;
200
+ continue;
201
+ }
189
202
  // In-place compaction: `replacement_history` REPLACES everything before this
190
203
  // point. Reset to the post-compact base; turns after it append normally.
191
204
  pendingEvents = [];
@@ -374,16 +387,42 @@ function subagentLoader(
374
387
  };
375
388
  }
376
389
 
377
- /** The compaction summary text if this rollout was compacted, else null. */
378
- function compactionSummary(text: string): string | null {
390
+ /**
391
+ * Every compaction's summary text, in file order `[]` when never compacted.
392
+ *
393
+ * All of them rather than the first, because a thread can be compacted more than
394
+ * once and each boundary has its own summary. The *last* is what the post-compact
395
+ * view's card shows, since that is the compaction which produced the view being
396
+ * rendered; reading the first showed a twice-compacted thread the summary of work
397
+ * two segments ago. The earlier ones head the segments the "load more" walk opens.
398
+ */
399
+ function compactionSummaries(text: string): string[] {
400
+ const out: string[] = [];
379
401
  for (const line of completeLines(text)) {
380
402
  const rec = parseLine(line);
381
- if (rec?.type === "compacted") {
382
- const msg = (rec.payload as Record<string, unknown> | undefined)?.message;
383
- return typeof msg === "string" ? msg : "";
384
- }
403
+ if (rec?.type !== "compacted") continue;
404
+ const msg = (rec.payload as Record<string, unknown> | undefined)?.message;
405
+ out.push(typeof msg === "string" ? msg : "");
385
406
  }
386
- return null;
407
+ return out;
408
+ }
409
+
410
+ /**
411
+ * The "earlier conversation was compacted" card that heads a compacted view.
412
+ *
413
+ * `#<index>` in the id is load-bearing, not decoration: the client sends the card's
414
+ * id back as `before`, and on a thread compacted more than once it is the only thing
415
+ * saying *which* boundary to walk back from. `#` rather than another `-` because a
416
+ * thread id is a uuid whose last group can be all digits, and a trailing `-12` would
417
+ * then be indistinguishable from an index.
418
+ */
419
+ function compactCard(summary: string, threadId: string, index: number, file: string): ChatMessage {
420
+ return {
421
+ id: `codex-compact-${threadId}#${index}`,
422
+ role: "assistant",
423
+ content: `${summary || "_Earlier conversation was compacted to save context._"}\n\nread the full transcript at: ${file}`,
424
+ timestamp: new Date().toISOString(),
425
+ };
387
426
  }
388
427
 
389
428
  /**
@@ -400,14 +439,9 @@ export function getRolloutMessages(sessionsDir: string, threadId: string, reques
400
439
  const msgs = parseRolloutJsonl(text, {
401
440
  loadSubagent: subagentLoader(sessionsDir, requestedCwd, new Set([threadId]), SUBAGENT_DEPTH),
402
441
  });
403
- const summary = compactionSummary(text);
404
- if (summary !== null) {
405
- msgs.unshift({
406
- id: `codex-compact-${threadId}`,
407
- role: "assistant",
408
- content: `${summary || "_Earlier conversation was compacted to save context._"}\n\nread the full transcript at: ${file}`,
409
- timestamp: new Date().toISOString(),
410
- });
442
+ const summaries = compactionSummaries(text);
443
+ if (summaries.length) {
444
+ msgs.unshift(compactCard(summaries[summaries.length - 1]!, threadId, summaries.length, file));
411
445
  }
412
446
  return msgs;
413
447
  } catch { return []; }
@@ -422,17 +456,37 @@ export function isCodexRolloutPath(p: string): boolean {
422
456
  }
423
457
 
424
458
  /**
425
- * Pre-compact slice for the "load more" feature. Jails to ~/.codex/sessions,
426
- * fail-closed on cwd, returns the messages BEFORE the compaction boundary.
459
+ * One pre-compact segment for the "load more" feature. Jails to ~/.codex/sessions,
460
+ * fail-closed on cwd.
461
+ *
462
+ * One segment per request rather than everything before the first boundary, which is
463
+ * what this used to return. A thread compacted twice has three stretches, and the old
464
+ * answer handed the newest card the *oldest* stretch while leaving the middle one
465
+ * reachable from nothing at all — not a short answer, a lost one. `before` names the
466
+ * card that was clicked, and the segment it opens is itself headed by the previous
467
+ * boundary's card, so the walk continues the way the Claude path's does.
427
468
  */
428
- export function getCodexPreCompactMessages(file: string, requestedCwd?: string): ChatMessage[] {
469
+ export function getCodexPreCompactMessages(file: string, requestedCwd?: string, beforeId?: string): ChatMessage[] {
429
470
  if (!isCodexRolloutPath(file)) throw new Error("Access denied: not a codex rollout");
430
471
  const resolved = resolve(file);
431
472
  let text: string;
432
473
  try { text = readFileSync(resolved, "utf-8"); } catch { throw new Error("File not found"); }
474
+ // Read once and reused below. `readSessionMeta` opens and reads the whole rollout again, and
475
+ // this path is already reading a file that reaches tens of megabytes on the very sessions
476
+ // the feature exists for — asking for it twice per click was a third of the cost for nothing.
477
+ const meta = readSessionMeta(resolved);
433
478
  if (requestedCwd != null) {
434
- const meta = readSessionMeta(resolved);
435
479
  if (!meta?.cwd || normPath(meta.cwd) !== normPath(requestedCwd)) return []; // fail-closed
436
480
  }
437
- return parseRolloutJsonl(text, { preCompact: true });
481
+ const summaries = compactionSummaries(text);
482
+ if (!summaries.length) return [];
483
+ // A client holding a card from before the index existed sends the bare id. The newest
484
+ // boundary is the one that card came from, and on a thread compacted once — which is
485
+ // every thread that path was ever correct for — the two agree.
486
+ const asked = Number(beforeId?.match(/#(\d+)$/)?.[1]);
487
+ const index = asked >= 1 && asked <= summaries.length ? asked : summaries.length;
488
+ const segment = parseRolloutJsonl(text, { preCompactIndex: index });
489
+ if (index < 2) return segment;
490
+ const threadId = meta?.id ?? threadIdFromName(resolved) ?? "";
491
+ return [compactCard(summaries[index - 2]!, threadId, index - 1, resolved), ...segment];
438
492
  }
@@ -45,6 +45,7 @@ async function setupLogFile() {
45
45
  const { resolve } = await import("node:path");
46
46
  const { appendFileSync, mkdirSync, existsSync } = await import("node:fs");
47
47
  const { getPpmDir } = await import("../services/ppm-dir.ts");
48
+ const { stdioIsLogFile, consumeStdioIsLogEnv } = await import("../services/log-rotate.ts");
48
49
 
49
50
  const ppmDir = getPpmDir();
50
51
  if (!existsSync(ppmDir)) mkdirSync(ppmDir, { recursive: true });
@@ -70,9 +71,26 @@ async function setupLogFile() {
70
71
  try { appendFileSync(logPath, `[${ts}] [${level}] ${redact(msg)}\n`); } catch {}
71
72
  };
72
73
 
73
- console.log = (...args: unknown[]) => { origLog(...args); writeLog("INFO", args); };
74
- console.error = (...args: unknown[]) => { origError(...args); writeLog("ERROR", args); };
75
- console.warn = (...args: unknown[]) => { origWarn(...args); writeLog("WARN", args); };
74
+ // The supervisor spawns this process with `stdio: ["ignore", logFd, logFd]`
75
+ // where logFd is ppm.log itself, so console output already lands in the log
76
+ // through fd 1 with no timestamp, no level, and crucially *unredacted*,
77
+ // because `redact()` above only ever ran on the appended copy. That is how
78
+ // ppm.log came to hold 1,395,012 lines of which only 532,792 carried the
79
+ // prefix: the other 862,220 were the same events arriving raw.
80
+ //
81
+ // Where the console already reaches the file, the original call is dropped
82
+ // and only the formatted, redacted line is written — one copy per event, and
83
+ // the safe one. Run from a terminal (`bun dev:server`) stdout is a tty, both
84
+ // checks are false, and the terminal keeps its output exactly as before.
85
+ const stdoutIsLogFile = stdioIsLogFile(1, logPath);
86
+ const stderrIsLogFile = stdioIsLogFile(2, logPath);
87
+ // Read once, then dropped: the terminals, SDK children and `ppm` invocations this server
88
+ // spawns inherit its environment, and none of their stdouts is the log file.
89
+ consumeStdioIsLogEnv();
90
+
91
+ console.log = (...args: unknown[]) => { if (!stdoutIsLogFile) origLog(...args); writeLog("INFO", args); };
92
+ console.error = (...args: unknown[]) => { if (!stderrIsLogFile) origError(...args); writeLog("ERROR", args); };
93
+ console.warn = (...args: unknown[]) => { if (!stderrIsLogFile) origWarn(...args); writeLog("WARN", args); };
76
94
 
77
95
  // Capture uncaught errors — count-based exit for supervisor restart
78
96
  let exceptionCount = 0;
@@ -131,16 +149,21 @@ app.get("/api/info", (c) => c.json(ok({
131
149
  tunnel_active: !!tunnelService.getTunnelUrl(),
132
150
  })));
133
151
 
134
- // Public: recent logs for bug reports (last 30 lines)
152
+ // Public: recent logs for bug reports (last 30 lines).
153
+ //
154
+ // Reads a bounded tail, never the file. This route sits *before*
155
+ // authMiddleware, and `readFileSync` + `split("\n")` on the 276 MB ppm.log this
156
+ // was found on cost 356 ms of blocked event loop and a 644 MB resident spike —
157
+ // per call, unauthenticated, and growing with the log.
135
158
  app.get("/api/logs/recent", async (c) => {
136
159
  const { resolve } = await import("node:path");
137
- const { existsSync, readFileSync } = await import("node:fs");
160
+ const { existsSync } = await import("node:fs");
138
161
  const { getPpmDir } = await import("../services/ppm-dir.ts");
139
162
  const { redactSecrets } = await import("../services/redact-secrets.ts");
163
+ const { tailLines } = await import("../services/file-lines.ts");
140
164
  const logFile = resolve(getPpmDir(), "ppm.log");
141
165
  if (!existsSync(logFile)) return c.json(ok({ logs: "" }));
142
- const content = readFileSync(logFile, "utf-8");
143
- const lines = content.split("\n").slice(-30).join("\n").trim();
166
+ const lines = await tailLines(logFile, 30);
144
167
  // Double-redact in case old logs have unredacted content
145
168
  return c.json(ok({ logs: redactSecrets(lines) }));
146
169
  });
@@ -179,6 +202,15 @@ app.route("/api/system", resourceRoutes);
179
202
  import { hostInfoRoutes } from "./routes/host-info.ts";
180
203
  app.route("/api/system", hostInfoRoutes);
181
204
 
205
+ // Event-loop lag. One process serves every request and all of the chat work, so
206
+ // a stall here is a stall everywhere; the report says whether the time was spent
207
+ // on our own synchronous work or off the CPU entirely, which is what decides
208
+ // whether the answer is in this repository at all.
209
+ app.get("/api/system/event-loop", async (c) => {
210
+ const { lagReport } = await import("../services/event-loop-lag.ts");
211
+ return c.json(ok(lagReport()));
212
+ });
213
+
182
214
  // Remote desktop (video capture + input) — on by default, opt-out via REMOTE_DESKTOP_ENABLED=0, see remote-desktop-flag.ts
183
215
  import { remoteDesktopRoutes } from "./routes/remote-desktop.ts";
184
216
  app.route("/api/remote-desktop", remoteDesktopRoutes);
@@ -970,6 +1002,9 @@ if (process.argv.includes("__serve__")) {
970
1002
  // Start background usage limit polling (every 5 min)
971
1003
  import("../services/claude-usage.service.ts").then(({ startUsagePolling }) => startUsagePolling()).catch(() => {});
972
1004
 
1005
+ // Watch how long the loop is unavailable for, and whose fault that is
1006
+ import("../services/event-loop-lag.ts").then(({ startLagMonitor }) => startLagMonitor()).catch(() => {});
1007
+
973
1008
  // Discover + activate enabled extensions
974
1009
  import("../services/extension.service.ts").then(({ extensionService }) => extensionService.startup()).catch((e) => {
975
1010
  console.error("[ExtService] Startup error:", e);
@@ -1,7 +1,8 @@
1
1
  import { Hono } from "hono";
2
2
  import { resolve, join, basename } from "node:path";
3
- import { existsSync, readdirSync, statSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { existsSync, readdirSync, statSync, writeFileSync } from "node:fs";
4
4
  import { unlink } from "node:fs/promises";
5
+ import { countLines } from "../../services/file-lines.ts";
5
6
  import { ensureUploadsDir, resolveUploadPath } from "../../services/chat-upload-storage.service.ts";
6
7
  import { chatService } from "../../services/chat.service.ts";
7
8
  import { draftService } from "../../services/draft.service.ts";
@@ -818,24 +819,25 @@ function resolveSessionJsonlPath(sessionId: string): { jsonlPath: string; jsonlD
818
819
  }
819
820
 
820
821
  /** GET /chat/sessions/:id/debug — session debug info (IDs, JSONL path) */
821
- chatRoutes.get("/sessions/:id/debug", (c) => {
822
+ chatRoutes.get("/sessions/:id/debug", async (c) => {
822
823
  const sessionId = c.req.param("id");
823
824
  const { jsonlPath, jsonlDir, projectPath, exists } = resolveSessionJsonlPath(sessionId);
824
825
  // Transcript weight: file size + record count (1 JSONL line = 1 event record).
825
826
  // Line count skipped above 64MB so the debug button stays snappy on huge files.
827
+ //
828
+ // `countLines` rather than `readFileSync(path, "utf8")` plus a `charCodeAt`
829
+ // walk. Not for speed — both are 53 ms on a 35 MB transcript — but because
830
+ // the old one held the loop for every one of those milliseconds, and this one
831
+ // hands it back nine times. The yields inside it are explicit `setTimeout`s
832
+ // and have to be: awaiting the stream alone resolves as microtasks and blocks
833
+ // just as hard. See `file-lines.ts`.
826
834
  let jsonlSizeBytes: number | null = null;
827
835
  let jsonlLines: number | null = null;
828
836
  if (exists && jsonlPath) {
829
837
  try {
830
838
  const st = statSync(jsonlPath);
831
839
  jsonlSizeBytes = st.size;
832
- if (st.size <= 64 * 1024 * 1024) {
833
- const text = readFileSync(jsonlPath, "utf8");
834
- let n = 0;
835
- for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) n++;
836
- if (text.length > 0 && text.charCodeAt(text.length - 1) !== 10) n++;
837
- jsonlLines = n;
838
- }
840
+ if (st.size <= 64 * 1024 * 1024) jsonlLines = await countLines(jsonlPath);
839
841
  } catch { /* stat/read failure — omit weight fields */ }
840
842
  }
841
843
  // PPM session ID == SDK session ID (canonical — see claude-agent-sdk.ts:728).
@@ -1000,11 +1002,13 @@ chatRoutes.get("/pre-compact-messages", async (c) => {
1000
1002
  // Codex rollouts live under ~/.codex/sessions (different format + jail than Claude JSONL).
1001
1003
  const { isCodexRolloutPath, getCodexPreCompactMessages } = await import("../../providers/codex-app-server/codex-history.ts");
1002
1004
  if (isCodexRolloutPath(jsonlPath)) {
1003
- const messages = getCodexPreCompactMessages(jsonlPath, c.get("projectPath"));
1005
+ const messages = getCodexPreCompactMessages(jsonlPath, c.get("projectPath"), beforeUuid);
1004
1006
  return c.json(ok(messages));
1005
1007
  }
1006
1008
  const validated = validateJsonlPath(jsonlPath);
1007
- const messages = await parseJsonlTranscript(validated, beforeUuid);
1009
+ // One compaction segment per request: the client walks further back by
1010
+ // expanding the summary that arrives at the head of each one.
1011
+ const messages = await parseJsonlTranscript(validated, beforeUuid, { oneSegment: true });
1008
1012
  return c.json(ok(messages));
1009
1013
  } catch (e) {
1010
1014
  const message = e instanceof Error ? e.message : "Unknown error";
@@ -3,6 +3,7 @@ import { resolve } from "node:path";
3
3
  import { gitService } from "../../services/git.service.ts";
4
4
  import { gitHunksService, type HunkRequest, type HunkScope } from "../../services/git-hunks/git-hunks.service.ts";
5
5
  import { assertSafeRev, gitBlameService } from "../../services/git-blame/git-blame.service.ts";
6
+ import { branchDiff } from "../../services/git-branch-diff/branch-diff.service.ts";
6
7
  import { discoverGitRepos, isGitRepo } from "../../services/git-repos/git-repo-discovery.ts";
7
8
  import { isInsideDir, realPathOrSelfSync } from "../../services/fs-ops/fs-real-path.ts";
8
9
  import { ok, err } from "../../types/api.ts";
@@ -128,6 +129,32 @@ gitRoutes.get("/diff-stat", async (c) => {
128
129
  }
129
130
  });
130
131
 
132
+ /**
133
+ * GET /git/branch-diff?base=&head=&mode=three-dot|two-dot
134
+ *
135
+ * Every file a branch changed, in one answer, plus the commit those changes
136
+ * were measured against. The Branch Review tab opens each file's diff at
137
+ * `mergeBase`, so the list and the viewer can never disagree about the base.
138
+ *
139
+ * A bad ref is a 400, not a 500: `base` and `head` come straight from a picker,
140
+ * and a branch deleted since it was rendered is an ordinary thing to ask about.
141
+ */
142
+ gitRoutes.get("/branch-diff", async (c) => {
143
+ const projectPath = c.get("projectPath");
144
+ const mode = c.req.query("mode") === "two-dot" ? "two-dot" : "three-dot";
145
+ try {
146
+ const result = await branchDiff(
147
+ projectPath,
148
+ c.req.query("base"),
149
+ c.req.query("head"),
150
+ mode,
151
+ );
152
+ return c.json(ok(result));
153
+ } catch (e) {
154
+ return c.json(err((e as Error).message), 400);
155
+ }
156
+ });
157
+
131
158
  /** GET /git/file-diff?file=&ref= */
132
159
  gitRoutes.get("/file-diff", async (c) => {
133
160
  try {
@@ -144,9 +171,11 @@ gitRoutes.get("/file-diff", async (c) => {
144
171
  }
145
172
  });
146
173
 
147
- /** GET /git/file-full-diff?file=&ref=
174
+ /** GET /git/file-full-diff?file=&ref=&ref2=&text=1
148
175
  * Returns full file contents (VSCode-style) for both sides:
149
- * { original: <ref version>, modified: <working tree> } */
176
+ * { original: <ref version>, modified: <working tree> }
177
+ * A binary file answers `binary: true` with both sides empty; `text=1` is the
178
+ * viewer's "Open Anyway" and asks for the decoded bytes regardless. */
150
179
  gitRoutes.get("/file-full-diff", async (c) => {
151
180
  try {
152
181
  const projectPath = c.get("projectPath");
@@ -156,13 +185,64 @@ gitRoutes.get("/file-full-diff", async (c) => {
156
185
  const ref2 = c.req.query("ref2") || undefined;
157
186
  const bad = invalidRev(ref, ref2);
158
187
  if (bad) return c.json(err(bad), 400);
159
- const result = await gitService.fileFullDiff(projectPath, file, ref, ref2);
188
+ const result = await gitService.fileFullDiff(projectPath, file, ref, ref2, {
189
+ text: c.req.query("text") === "1",
190
+ });
160
191
  return c.json(ok(result));
161
192
  } catch (e) {
162
193
  return c.json(err((e as Error).message), 500);
163
194
  }
164
195
  });
165
196
 
197
+ /**
198
+ * The content types `/git/file-blob` will name. Everything outside this list is
199
+ * served as `application/octet-stream`: a blob URL inherits *this* origin, so
200
+ * answering with the repository's own `text/html` — or `image/svg+xml`, which
201
+ * carries script — would let a committed file run code inside the app.
202
+ */
203
+ const BLOB_IMAGE_TYPES: Record<string, string> = {
204
+ png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif",
205
+ webp: "image/webp", bmp: "image/bmp", ico: "image/x-icon", avif: "image/avif",
206
+ };
207
+
208
+ /**
209
+ * GET /git/file-blob?file=&ref=HEAD — the file's bytes at a revision.
210
+ *
211
+ * What the binary diff view draws its left-hand pane from: `/files/raw` serves
212
+ * the working tree, and nothing else reaches the version a commit holds. The
213
+ * path needs no traversal check of its own — git resolves `ref:path` inside the
214
+ * repository and refuses anything above it ("is outside repository").
215
+ */
216
+ gitRoutes.get("/file-blob", async (c) => {
217
+ try {
218
+ const projectPath = c.get("projectPath");
219
+ const file = c.req.query("file");
220
+ if (!file) return c.json(err("Missing query: file"), 400);
221
+ const ref = c.req.query("ref") || "HEAD";
222
+ // The same guard as every sibling route, and it is not decoration here: `ref` reaches
223
+ // `git show` as its own argv word, so `?ref=--output=<path>` is an option rather than a
224
+ // revision. Measured on a scratch repository, `git show --output=<victim> HEAD:a.txt`
225
+ // exits 0 and leaves the victim at zero bytes — arbitrary file destruction, on a server
226
+ // that is routinely reachable through a public tunnel URL.
227
+ const bad = invalidRev(ref);
228
+ if (bad) return c.json(err(bad), 400);
229
+ const bytes = await gitService.fileBlob(projectPath, file, ref);
230
+ if (!bytes) return c.json(err("File does not exist at that revision"), 404);
231
+ const ext = file.split(".").pop()?.toLowerCase() ?? "";
232
+ // Copied into a plain Uint8Array because a Buffer is typed over
233
+ // ArrayBufferLike, which BodyInit does not accept.
234
+ return new Response(new Uint8Array(bytes), {
235
+ headers: {
236
+ "Content-Type": BLOB_IMAGE_TYPES[ext] ?? "application/octet-stream",
237
+ "Content-Length": String(bytes.length),
238
+ "X-Content-Type-Options": "nosniff",
239
+ },
240
+ });
241
+ } catch (e) {
242
+ return c.json(err((e as Error).message), 500);
243
+ }
244
+ });
245
+
166
246
  /** GET /git/graph?max=200&skip=0 */
167
247
  gitRoutes.get("/graph", async (c) => {
168
248
  try {
@@ -10,7 +10,7 @@
10
10
  import { Hono, type Context } from "hono";
11
11
  import { ok, err } from "../../types/api.ts";
12
12
  import { configService } from "../../services/config.service.ts";
13
- import { getFfmpegCapabilities } from "../../services/media-transcode/ffmpeg-capabilities.ts";
13
+ import { getFfmpegCapabilities, workingEncoders } from "../../services/media-transcode/ffmpeg-capabilities.ts";
14
14
  import { isRemoteDesktopEnabled } from "../../services/remote-desktop/remote-desktop-flag.ts";
15
15
  import { mintRemoteDesktopNonce } from "../../services/remote-desktop/remote-desktop-nonce.ts";
16
16
  import { listDisplays } from "../../services/remote-desktop/remote-desktop-displays.ts";
@@ -51,9 +51,14 @@ function assertSessionAllowed(c: Context): Response | null {
51
51
 
52
52
  remoteDesktopRoutes.get("/capabilities", async (c) => {
53
53
  if (!isRemoteDesktopEnabled()) return c.json(err("remote desktop is disabled"), 404);
54
- const [caps, readiness, displays] = await Promise.all([getFfmpegCapabilities(), remoteDesktopReadiness(), listDisplays()]);
54
+ const [caps, readiness, displays, encoders] = await Promise.all([
55
+ getFfmpegCapabilities(), remoteDesktopReadiness(), listDisplays(), workingEncoders(),
56
+ ]);
55
57
  return c.json(ok({
56
58
  displays,
59
+ // Every encoder that really encodes here, preference order, for the codec picker. The
60
+ // first is what a session uses unless the client names another.
61
+ encoders,
57
62
  // Flat booleans kept for existing clients/tests; `readiness` is the source of truth.
58
63
  ffmpegAvailable: !!caps.ffmpeg,
59
64
  videoAvailable: readiness.videoReady,
@@ -58,7 +58,7 @@ function setWatch(ws: GlobalWsSocket, projectName: string): void {
58
58
  const project = configService.get("projects").find((p) => p.name === projectName);
59
59
  if (!project) return;
60
60
 
61
- startWatching(projectName, project.path);
61
+ void startWatching(projectName, project.path);
62
62
  watchedProject.set(ws, projectName);
63
63
  }
64
64
 
@@ -6,9 +6,11 @@
6
6
  * token already travels as `?token=` on every WS URL (`isWsUpgradeAuthorized`,
7
7
  * `src/server/index.ts`) and proves nothing beyond "holds the one reusable app token", so a
8
8
  * single-use short-TTL nonce (`POST /api/remote-desktop/session`) must be presented as the
9
- * client's *first* message — `{type:"auth", nonce, displayId?}` — before capture or input
10
- * starts. `displayId` picks one of `/capabilities`' `displays`; absent/unknown = primary. Kept out
11
- * of the query string (unlike the coarse token) so it never lands in proxy/tunnel access logs.
9
+ * client's *first* message — `{type:"auth", nonce, displayId?, cursor?, codec?}` — before
10
+ * capture or input starts. `displayId` picks one of `/capabilities`' `displays`; absent/unknown = primary.
11
+ * `cursor` and `codec` carry the client's saved cursor and encoder prefs, because ffmpeg takes
12
+ * both at startup and applying them afterwards would respawn it on every connect. Kept out of
13
+ * the query string (unlike the coarse token) so they never land in proxy/tunnel access logs.
12
14
  *
13
15
  * Both the feature flag and `auth.enabled` are re-checked here even though
14
16
  * `src/server/index.ts` already gated the upgrade on them — this handler must never depend on
@@ -58,8 +60,13 @@ async function authenticateFirstMessage(ws: RemoteDesktopWs, text: string): Prom
58
60
  getBufferedAmount: ws.getBufferedAmount ? () => ws.getBufferedAmount!() : undefined,
59
61
  close: (code, reason) => ws.close(code, reason),
60
62
  };
61
- const displayId = typeof parsed.displayId === "string" ? parsed.displayId : undefined;
62
- ws.data.session = await createRemoteDesktopSession(socket, displayId);
63
+ ws.data.session = await createRemoteDesktopSession(socket, {
64
+ displayId: typeof parsed.displayId === "string" ? parsed.displayId : undefined,
65
+ // Only an explicit `false` hides it: an older client sends no flag at all and must keep
66
+ // getting the pointer it has always had.
67
+ showCursor: parsed.cursor !== false,
68
+ encoder: typeof parsed.codec === "string" ? parsed.codec : undefined,
69
+ });
63
70
  } catch (e) {
64
71
  console.error(`[remote-desktop] failed to start capture: ${(e as Error).message}`);
65
72
  ws.send(JSON.stringify({ type: "error", message: (e as Error).message }));
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Is this file content binary?
3
+ *
4
+ * The answer has to be taken from *bytes*. `git show` hands simple-git a string
5
+ * decoded as UTF-8, and a PNG survives that as megabytes of U+FFFD — which is
6
+ * exactly what the diff editor used to render, line-numbered and highlighted,
7
+ * for every image in a commit.
8
+ */
9
+
10
+ /** git's own window (`buffer_is_binary`, FIRST_FEW_BYTES in xdiff-interface.c). */
11
+ export const BINARY_SNIFF_BYTES = 8000;
12
+
13
+ /**
14
+ * True when `bytes` looks binary — a NUL inside the first 8000 bytes.
15
+ *
16
+ * The same rule git applies when it prints "Binary files differ" instead of a
17
+ * diff, and the same shape as `file.service`'s check for whether to answer
18
+ * base64, so the diff view and the editor agree about any given file. A side
19
+ * that does not exist (`null`) is not binary: an added file has no old version,
20
+ * and that alone must not send the pair down the binary path.
21
+ */
22
+ export function isBinaryContent(bytes: Uint8Array | null | undefined): boolean {
23
+ if (!bytes) return false;
24
+ return bytes.subarray(0, BINARY_SNIFF_BYTES).includes(0);
25
+ }
@@ -188,7 +188,7 @@ export async function indexSession(
188
188
  projectPath: string,
189
189
  jsonlMtime = 0,
190
190
  ): Promise<void> {
191
- const messages = await chatService.getMessages(providerId, sessionId);
191
+ const messages = await chatService.getFullMessages(providerId, sessionId);
192
192
  indexMessages(sessionId, projectPath, messages, jsonlMtime);
193
193
  }
194
194
 
@@ -201,6 +201,15 @@ class ChatService {
201
201
  ? { ...message, content: stripSharedContext(message.content) }
202
202
  : message);
203
203
  }
204
+
205
+ /** Whole transcript rather than the resumable conversation, for the search index.
206
+ * Falls back to the conversation for a provider that draws no distinction. */
207
+ async getFullMessages(providerId: string, sessionId: string): Promise<ChatMessage[]> {
208
+ const provider = providerRegistry.get(providerId);
209
+ if (!provider) return [];
210
+ if (provider.getFullMessages) return await provider.getFullMessages(sessionId);
211
+ return await provider.getMessages?.(sessionId) ?? [];
212
+ }
204
213
  }
205
214
 
206
215
  export const chatService = new ChatService();