@hienlh/ppm 0.18.14 → 0.18.15

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 (539) hide show
  1. package/.rdtest2-cf.out +0 -0
  2. package/CHANGELOG.md +18 -0
  3. package/CLAUDE.md +3 -0
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/cli-reference.md +22 -4
  6. package/assets/skills/ppm/references/http-api.md +8 -1
  7. package/dist/web/assets/{adaptive-context-menu-DCZtgOL7.js → adaptive-context-menu-ChBZWLLq.js} +1 -1
  8. package/dist/web/assets/{ai-resource-editor-D5CPBg06.js → ai-resource-editor-C8aAY8Po.js} +1 -1
  9. package/dist/web/assets/{ai-settings-section-DY-t1bK8.js → ai-settings-section-ERu1WiZc.js} +1 -1
  10. package/dist/web/assets/{angular-html-CTvM4ulF.js → angular-html-CXgHIQ2U.js} +1 -1
  11. package/dist/web/assets/{angular-ts-B_vSkjco.js → angular-ts-C8renMV9.js} +1 -1
  12. package/dist/web/assets/api-client-WpZ2FDHg.js +1 -0
  13. package/dist/web/assets/api-settings-BgSEE6et.js +1 -0
  14. package/dist/web/assets/{api-settings-B2iNXQ0c.js → api-settings-R59c66DE.js} +1 -1
  15. package/dist/web/assets/{api-themes-DrWnJbhs.js → api-themes-DKxrwJda.js} +1 -1
  16. package/dist/web/assets/{apl-DywzTry0.js → apl-DkGH8laI.js} +1 -1
  17. package/dist/web/assets/architecture-PBZL5I3N-CCalSKAt.js +1 -0
  18. package/dist/web/assets/{astro-1JaRgaSb.js → astro-tHUd8Zlg.js} +1 -1
  19. package/dist/web/assets/{audio-preview-DAuGvOiK.js → audio-preview-B_C6Dpvz.js} +1 -1
  20. package/dist/web/assets/{blade-CDeeJWBb.js → blade-BbKpjIYK.js} +1 -1
  21. package/dist/web/assets/{bsl-BfgoPVYR.js → bsl-DA0UYto0.js} +1 -1
  22. package/dist/web/assets/{button-tNy0ba3y.js → button-DBSpDPS0.js} +1 -1
  23. package/dist/web/assets/{cairo-C26gNwWW.js → cairo-79ayucbB.js} +1 -1
  24. package/dist/web/assets/chat-tab-BraiJsYG.js +15 -0
  25. package/dist/web/assets/{cobol-D92nBnON.js → cobol-DSS2EOYo.js} +1 -1
  26. package/dist/web/assets/code-editor-lhmLNhXu.js +10 -0
  27. package/dist/web/assets/{coffee-CIWH40R1.js → coffee-DvSxq2iD.js} +1 -1
  28. package/dist/web/assets/{conflict-editor-CDp0I3mD.js → conflict-editor-C4VCv51R.js} +1 -1
  29. package/dist/web/assets/{context-menu-BO1c9jKy.js → context-menu-BpwUny-o.js} +1 -1
  30. package/dist/web/assets/{cpp-DOrBqXCn.js → cpp-BhWZ-FSn.js} +1 -1
  31. package/dist/web/assets/{crystal-BNKqmHux.js → crystal-yP2OeF-f.js} +1 -1
  32. package/dist/web/assets/{csv-preview-D4svNDAq.js → csv-preview-D46W_icb.js} +1 -1
  33. package/dist/web/assets/{data-grid-overlay-editor-BqV0AxZg.js → data-grid-overlay-editor-DeGkPYtA.js} +1 -1
  34. package/dist/web/assets/{database-viewer-BCvv_Bwf.js → database-viewer-DFI4YtrH.js} +1 -1
  35. package/dist/web/assets/{dialog-DcLGDwaX.js → dialog-RIpeb87v.js} +1 -1
  36. package/dist/web/assets/{diff-viewer-D41fCFAi.js → diff-viewer-eaNOLcBL.js} +1 -1
  37. package/dist/web/assets/{docx-preview-DfaQn9v7.js → docx-preview-Cbj-pOK3.js} +1 -1
  38. package/dist/web/assets/{edge-Dbjp42Pt.js → edge-D3K9cd-p.js} +1 -1
  39. package/dist/web/assets/{edit-diff-preview-BqcUuFCc.js → edit-diff-preview-DDqy3KLA.js} +1 -1
  40. package/dist/web/assets/{elixir-na3b1pYy.js → elixir-D4PBmsLp.js} +1 -1
  41. package/dist/web/assets/{elm-_Nalil1R.js → elm-DSpXjuaH.js} +1 -1
  42. package/dist/web/assets/{erb-CW_7dubf.js → erb-CaYZMTJW.js} +1 -1
  43. package/dist/web/assets/{erlang-DmZ7Zzb0.js → erlang-DX5BFqJj.js} +1 -1
  44. package/dist/web/assets/{esm-CwyBetO1.js → esm-Cv7HsFnk.js} +1 -1
  45. package/dist/web/assets/explorer-body-BxbSttfl.js +2 -0
  46. package/dist/web/assets/explorer-window-content-DUqQCC7A.js +1 -0
  47. package/dist/web/assets/{extension-store-DUaZINyC.js → extension-store-B1EClUAr.js} +1 -1
  48. package/dist/web/assets/{extension-webview-BW_mccb3.js → extension-webview-BaGvDk8v.js} +1 -1
  49. package/dist/web/assets/{file-store-yReVbR8q.js → file-store-m9crh6uq.js} +1 -1
  50. package/dist/web/assets/{fortran-fixed-form-DB2A8L4-.js → fortran-fixed-form-B0bAufUe.js} +1 -1
  51. package/dist/web/assets/{fsharp-BYHrti7z.js → fsharp-C2AL2b1M.js} +1 -1
  52. package/dist/web/assets/{gdresource-D5GBin40.js → gdresource-DRQsDMNI.js} +1 -1
  53. package/dist/web/assets/{git-commit-CUiOBoIL.js → git-commit-GtBdnrzk.js} +1 -1
  54. package/dist/web/assets/{git-log-panel-C1QDkmgl.js → git-log-panel-hwV1flSO.js} +1 -1
  55. package/dist/web/assets/{git-rebase-DOxGnpdw.js → git-rebase-h_0CGA6F.js} +1 -1
  56. package/dist/web/assets/gitGraph-HDMCJU4V-DAUrJCqi.js +1 -0
  57. package/dist/web/assets/glide-data-grid-YsN-xm6x.js +138 -0
  58. package/dist/web/assets/{glimmer-js-Begy4UT7.js → glimmer-js-Bxwu93bS.js} +1 -1
  59. package/dist/web/assets/{glimmer-ts-rPApRY8N.js → glimmer-ts-CIUYdR4E.js} +1 -1
  60. package/dist/web/assets/{glsl-wrbPvrBL.js → glsl-CfxLI5jn.js} +1 -1
  61. package/dist/web/assets/{graphql-Cu4-NR__.js → graphql-CstqDPde.js} +1 -1
  62. package/dist/web/assets/group-chat-tab-B7mJO1gG.js +1 -0
  63. package/dist/web/assets/{hack-Ddo93XKD.js → hack-Bjqnssb5.js} +1 -1
  64. package/dist/web/assets/{haml-BFX_1i8j.js → haml-lVc-DfbM.js} +1 -1
  65. package/dist/web/assets/hand-x0ejq3fS.js +1 -0
  66. package/dist/web/assets/{handlebars-5gFKA4H5.js → handlebars-Yy6fPQI_.js} +1 -1
  67. package/dist/web/assets/{html-derivative-e5JLAFZF.js → html-derivative-CchzCY1z.js} +1 -1
  68. package/dist/web/assets/{html-C06I-zeO.js → html-lqtcy6-2.js} +1 -1
  69. package/dist/web/assets/{http-CrAAhSU7.js → http-ChclRrko.js} +1 -1
  70. package/dist/web/assets/{hurl-T1V3SX9T.js → hurl-CoOK7sXa.js} +1 -1
  71. package/dist/web/assets/{hxml-B056LLKN.js → hxml-fZSZ7Iyt.js} +1 -1
  72. package/dist/web/assets/{image-preview-DWyuCYT3.js → image-preview-ClAcqDlL.js} +1 -1
  73. package/dist/web/assets/index-CtJbMNNI.css +2 -0
  74. package/dist/web/assets/index-H_gScEOk.js +52 -0
  75. package/dist/web/assets/info-3K5VOQVL-Cx1ZfJbp.js +1 -0
  76. package/dist/web/assets/{input-DouJ9Zvt.js → input-DXPgUtxi.js} +1 -1
  77. package/dist/web/assets/{jinja-DGdtRh43.js → jinja-Y0w_KBdE.js} +1 -1
  78. package/dist/web/assets/{jison-Ah6QobPS.js → jison-BILj3F2e.js} +1 -1
  79. package/dist/web/assets/{julia-BJeGOK0K.js → julia-DRfOPDts.js} +1 -1
  80. package/dist/web/assets/{just-iw9oRs7j.js → just-n3U_oODO.js} +1 -1
  81. package/dist/web/assets/keybindings-store-BqctreaI.js +1 -0
  82. package/dist/web/assets/{keybindings-store-0Wqfa7Qp.js → keybindings-store-D-CHdq-G.js} +1 -1
  83. package/dist/web/assets/keyboard-DBBaoQSu.js +1 -0
  84. package/dist/web/assets/{latex-DHjj5hyN.js → latex-D6gztEOL.js} +1 -1
  85. package/dist/web/assets/{liquid-B-FFecDc.js → liquid-DPqCEVb9.js} +1 -1
  86. package/dist/web/assets/{lua-DDianJp_.js → lua-I40KPUm-.js} +1 -1
  87. package/dist/web/assets/markdown-renderer-BwOkiZC1.js +1 -0
  88. package/dist/web/assets/{markdown-renderer-BHiUhQN8.js → markdown-renderer-oJ9jPdQ2.js} +3 -3
  89. package/dist/web/assets/{marko-BW5SBVng.js → marko-DMjsThSW.js} +1 -1
  90. package/dist/web/assets/{mdc-B9gL9bjl.js → mdc-CN2alsbo.js} +1 -1
  91. package/dist/web/assets/{media-url-DUK-k0bB.js → media-url-COUvasjn.js} +1 -1
  92. package/dist/web/assets/mobile-bottom-sheet-Cj1YBq-c.js +1 -0
  93. package/dist/web/assets/mobile-explorer-sheet-D1nM0Ik1.js +1 -0
  94. package/dist/web/assets/{nextflow-dTluY6qy.js → nextflow-CGa4JeZ6.js} +1 -1
  95. package/dist/web/assets/{nginx-DlwghGI-.js → nginx-CmqrIj84.js} +1 -1
  96. package/dist/web/assets/{nim-N9jddb5D.js → nim-CqhmTeMa.js} +1 -1
  97. package/dist/web/assets/notification-store-n-XEQ5pH.js +1 -0
  98. package/dist/web/assets/{number-overlay-editor-BVXM7aAs.js → number-overlay-editor-BEzJ1YeD.js} +1 -1
  99. package/dist/web/assets/{overview-panel-DCCDmtCQ.js → overview-panel-Bf9Gn8qV.js} +1 -1
  100. package/dist/web/assets/packet-RMMSAZCW-Bqwiy7dz.js +1 -0
  101. package/dist/web/assets/{panel-store-B4rijFKN.js → panel-store-UWCH6B-_.js} +1 -1
  102. package/dist/web/assets/{pdf-preview-D7yLmAZW.js → pdf-preview-BMHM5cR2.js} +1 -1
  103. package/dist/web/assets/{perl-CkmpYKa_.js → perl-BOwpb6Tb.js} +1 -1
  104. package/dist/web/assets/{php-BwZ3KDPo.js → php-wT9fVxxu.js} +1 -1
  105. package/dist/web/assets/pie-UPGHQEXC-BXSJ2God.js +1 -0
  106. package/dist/web/assets/{postgres-viewer--CRN1fvO.js → postgres-viewer-D8rfEY5b.js} +3 -3
  107. package/dist/web/assets/process-table-CsQ-hDJr.js +1 -0
  108. package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-CKAOr23O.js} +1 -1
  109. package/dist/web/assets/{pug-B3rBPqQo.js → pug-DdlNnj-p.js} +1 -1
  110. package/dist/web/assets/{qml-BfDbWFmg.js → qml-C5zBdydf.js} +1 -1
  111. package/dist/web/assets/radar-KQ55EAFF-D6XrM7HH.js +1 -0
  112. package/dist/web/assets/{razor-B1ww1r38.js → razor-DqcJX4iq.js} +1 -1
  113. package/dist/web/assets/remote-desktop-mobile-sheet-CTiipn4k.js +2 -0
  114. package/dist/web/assets/remote-desktop-mobile-view-Cf5B0dKo.js +1 -0
  115. package/dist/web/assets/remote-desktop-stats-overlay-vhJBIW7s.js +1 -0
  116. package/dist/web/assets/remote-desktop-window-content-wCJUfI6G.js +1 -0
  117. package/dist/web/assets/{rst-C0MSq6m-.js → rst-DiMiMYUO.js} +1 -1
  118. package/dist/web/assets/{ruby-ClIZ1wb3.js → ruby-DK62iflA.js} +1 -1
  119. package/dist/web/assets/{run-in-terminal-4efbW2Xv.js → run-in-terminal-DJSL6nwZ.js} +1 -1
  120. package/dist/web/assets/{sas-DR0j3o4_.js → sas-6QPGJRa5.js} +1 -1
  121. package/dist/web/assets/{scroll-area-ZP8wcwTI.js → scroll-area-DXGXeiRz.js} +1 -1
  122. package/dist/web/assets/{scss-Vh3JLayR.js → scss-DmUqh48_.js} +1 -1
  123. package/dist/web/assets/{send-to-chat-BNrEpAyC.js → send-to-chat-BOony0sO.js} +1 -1
  124. package/dist/web/assets/settings-store-BpcY5aYE.js +2 -0
  125. package/dist/web/assets/settings-tab-BVqRO01j.js +1 -0
  126. package/dist/web/assets/settings-tab-FH581A_i.js +3 -0
  127. package/dist/web/assets/{shaderlab-0rHRnQ18.js → shaderlab-DopFR1mi.js} +1 -1
  128. package/dist/web/assets/{shellsession-Dxdw6D8c.js → shellsession-Bp_TGuXb.js} +1 -1
  129. package/dist/web/assets/{soy-BHlSNj1y.js → soy-DEB9KEFb.js} +1 -1
  130. package/dist/web/assets/{sparql-B2QaPpni.js → sparql-C8lEwO1p.js} +1 -1
  131. package/dist/web/assets/{sql-query-editor-R_iKjPP9.js → sql-query-editor-MMfk0Kbz.js} +1 -1
  132. package/dist/web/assets/{sqlite-viewer-b2onA0SP.js → sqlite-viewer-vglzrgnZ.js} +1 -1
  133. package/dist/web/assets/{stata-CwIxjsEJ.js → stata-DVep4apN.js} +1 -1
  134. package/dist/web/assets/{surrealql-C6vo-rp-.js → surrealql-fSlJApOA.js} +1 -1
  135. package/dist/web/assets/{svelte-LLdJ6rlH.js → svelte-DezWBEDi.js} +1 -1
  136. package/dist/web/assets/{switch-DV-BDjyk.js → switch-DqL_ZjB_.js} +1 -1
  137. package/dist/web/assets/system-monitor-body-BYUfyF8f.js +2 -0
  138. package/dist/web/assets/system-monitor-tab-CKGXB8lH.js +1 -0
  139. package/dist/web/assets/{system-monitor-window-content-BPif0azo.js → system-monitor-window-content-CiJ2yIsr.js} +1 -1
  140. package/dist/web/assets/{tab-host-window-content-BnAN6m4K.js → tab-host-window-content-DYlXR0cg.js} +1 -1
  141. package/dist/web/assets/{tab-store-tFnyPmm4.js → tab-store-D2thn4Vv.js} +1 -1
  142. package/dist/web/assets/team-member-sheet-Bnygu7Xw.js +2 -0
  143. package/dist/web/assets/{team-member-window-content-BSyU-5Uc.js → team-member-window-content-qrgFTC96.js} +1 -1
  144. package/dist/web/assets/{templ-CJGvhEP5.js → templ-D0zB42a9.js} +1 -1
  145. package/dist/web/assets/{terminal-tab-Ncor0Rfu.js → terminal-tab-D_-R5rGm.js} +3 -3
  146. package/dist/web/assets/{tex-psXWhc4t.js → tex-BJiPijcH.js} +1 -1
  147. package/dist/web/assets/{tool-cards-DdVP1wrD.js → tool-cards-BW7Xc20J.js} +4 -4
  148. package/dist/web/assets/treemap-KZPCXAKY-BobWhcHN.js +1 -0
  149. package/dist/web/assets/{ts-tags-D5Ar_ZCO.js → ts-tags-Dcr4u6y5.js} +1 -1
  150. package/dist/web/assets/{twig-vJgGC-qY.js → twig-B-SQU0bg.js} +1 -1
  151. package/dist/web/assets/{use-blob-url-BOVGWg9F.js → use-blob-url-BtPlqwLr.js} +1 -1
  152. package/dist/web/assets/{use-monaco-theme-BshnLXoY.js → use-monaco-theme-DQY1ZSaf.js} +1 -1
  153. package/dist/web/assets/{use-open-team-member-CY3wOdWL.js → use-open-team-member-C5LQ_TCM.js} +1 -1
  154. package/dist/web/assets/use-visual-viewport-pVxLfE5B.js +1 -0
  155. package/dist/web/assets/{use-websocket-B7xjNsyD.js → use-websocket-l39Odhlp.js} +1 -1
  156. package/dist/web/assets/{vendor-mermaid-999ksD45.js → vendor-mermaid-CtDsSxbM.js} +1 -1
  157. package/dist/web/assets/{video-preview-7F72sRwf.js → video-preview-C5AhkIDl.js} +1 -1
  158. package/dist/web/assets/{vue-BWngFyav.js → vue-Cw10Sfbh.js} +1 -1
  159. package/dist/web/assets/{vue-html-BN5ZLu-H.js → vue-html-Cc5C_Z_w.js} +1 -1
  160. package/dist/web/assets/{vue-vine-BZHnO8yV.js → vue-vine-BuNMlnvu.js} +1 -1
  161. package/dist/web/assets/{window-pip-registry-BnYU83LE.js → window-pip-registry-C7oz3Dfy.js} +1 -1
  162. package/dist/web/assets/window-store-B5ZswdYg.js +1 -0
  163. package/dist/web/assets/{xml-DS2beZHp.js → xml-DqT1wkky.js} +1 -1
  164. package/dist/web/assets/{xsl-Ciiku1Bq.js → xsl-DLkge3A2.js} +1 -1
  165. package/dist/web/index.html +45 -43
  166. package/dist/web/sw.js +1 -1
  167. package/docs/architecture/ai-chat-and-providers.md +3 -0
  168. package/package.json +1 -1
  169. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  170. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  171. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  172. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
  173. package/src/cli/commands/backup-cmd.ts +119 -0
  174. package/src/index.ts +3 -0
  175. package/src/providers/claude-agent-sdk.ts +29 -1
  176. package/src/server/index.ts +42 -15
  177. package/src/server/routes/accounts.ts +30 -2
  178. package/src/server/routes/chat.ts +8 -1
  179. package/src/server/routes/named-tunnel.ts +5 -2
  180. package/src/server/routes/remote-desktop.ts +64 -0
  181. package/src/server/routes/settings.ts +1 -0
  182. package/src/server/ws/chat.ts +126 -7
  183. package/src/server/ws/remote-desktop.ts +86 -0
  184. package/src/services/account.service.ts +106 -23
  185. package/src/services/db-backup/db-backup-paths.ts +66 -0
  186. package/src/services/db-backup/db-backup-restore.ts +105 -0
  187. package/src/services/db-backup/db-backup-retention.ts +96 -0
  188. package/src/services/db-backup/db-backup-sync.ts +113 -0
  189. package/src/services/db-backup/db-backup.service.ts +61 -0
  190. package/src/services/db.service.ts +29 -1
  191. package/src/services/edge-forwarder.ts +191 -191
  192. package/src/services/fs-credential-path-guard.ts +19 -1
  193. package/src/services/named-tunnel/cloudflared-login.service.ts +2 -2
  194. package/src/services/nested-subagent-spy.ts +206 -0
  195. package/src/services/remote-desktop/access-unit-assembler.ts +90 -0
  196. package/src/services/remote-desktop/avc1-codec-string.ts +18 -0
  197. package/src/services/remote-desktop/nal-splitter.ts +100 -0
  198. package/src/services/remote-desktop/remote-desktop-capture.ts +120 -0
  199. package/src/services/remote-desktop/remote-desktop-encoder-args.ts +42 -0
  200. package/src/services/remote-desktop/remote-desktop-flag.ts +10 -0
  201. package/src/services/remote-desktop/remote-desktop-input.ts +217 -0
  202. package/src/services/remote-desktop/remote-desktop-nonce.ts +38 -0
  203. package/src/services/remote-desktop/remote-desktop-session.ts +189 -0
  204. package/src/services/remote-desktop/remote-desktop-vk-map.ts +41 -0
  205. package/src/services/subagent-transcript-merger.ts +92 -75
  206. package/src/services/subprocess-retention.ts +29 -6
  207. package/src/services/supervisor.ts +58 -0
  208. package/src/services/team-member-activity/subagent-transcript-index.ts +56 -0
  209. package/src/services/windows-process-tree.ts +351 -351
  210. package/src/types/chat.ts +2 -0
  211. package/src/web/app.tsx +8 -0
  212. package/src/web/components/chat/account-dialogs.tsx +11 -4
  213. package/src/web/components/chat/question-card.tsx +9 -2
  214. package/src/web/components/chat/usage-badge.tsx +17 -3
  215. package/src/web/components/floating-window/window-content-registry.ts +2 -0
  216. package/src/web/components/floating-window/window-store-types.ts +1 -1
  217. package/src/web/components/layout/mobile-drawer.tsx +38 -27
  218. package/src/web/components/layout/nav-section-rail.tsx +10 -2
  219. package/src/web/components/remote-desktop/open-remote-desktop.ts +26 -0
  220. package/src/web/components/remote-desktop/remote-desktop-coords.ts +98 -0
  221. package/src/web/components/remote-desktop/remote-desktop-frame-timestamp.ts +21 -0
  222. package/src/web/components/remote-desktop/remote-desktop-gesture-classifiers.ts +56 -0
  223. package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +131 -0
  224. package/src/web/components/remote-desktop/remote-desktop-mobile-sheet.tsx +43 -0
  225. package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +79 -0
  226. package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +190 -0
  227. package/src/web/components/remote-desktop/remote-desktop-stats-overlay.tsx +54 -0
  228. package/src/web/components/remote-desktop/remote-desktop-stats.ts +37 -0
  229. package/src/web/components/remote-desktop/remote-desktop-touch-gesture-tracker.ts +195 -0
  230. package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +72 -0
  231. package/src/web/components/remote-desktop/remote-desktop-ws-url.ts +22 -0
  232. package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +175 -0
  233. package/src/web/components/remote-desktop/use-remote-desktop-available.ts +29 -0
  234. package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +147 -0
  235. package/src/web/components/remote-desktop/use-remote-desktop-mobile-open-state.ts +18 -0
  236. package/src/web/components/remote-desktop/use-remote-desktop-touch.ts +224 -0
  237. package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +50 -0
  238. package/src/web/components/remote-desktop/use-remote-input-capture.ts +92 -0
  239. package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +59 -59
  240. package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +2 -2
  241. package/src/web/hooks/use-notification-badge.ts +19 -11
  242. package/src/web/lib/feature-badges.ts +2 -0
  243. package/src/web/stores/settings-store.ts +13 -0
  244. package/src/web/stores/streaming-store.ts +21 -2
  245. package/dist/web/assets/api-client-BogqsFna.js +0 -1
  246. package/dist/web/assets/api-settings-DRF9oYkn.js +0 -1
  247. package/dist/web/assets/architecture-PBZL5I3N-nw5-j_I_.js +0 -1
  248. package/dist/web/assets/chat-tab-IcnU3nqQ.js +0 -15
  249. package/dist/web/assets/code-editor-Dno5Srkm.js +0 -10
  250. package/dist/web/assets/explorer-body-DB1L30pa.js +0 -2
  251. package/dist/web/assets/explorer-window-content-Cwlt3C3I.js +0 -1
  252. package/dist/web/assets/gitGraph-HDMCJU4V-C1xf87gN.js +0 -1
  253. package/dist/web/assets/glide-data-grid-Dz8lx6n2.js +0 -138
  254. package/dist/web/assets/group-chat-tab-DFwr-pig.js +0 -1
  255. package/dist/web/assets/index-DWpNR3nB.css +0 -2
  256. package/dist/web/assets/index-oUKV5nIA.js +0 -52
  257. package/dist/web/assets/info-3K5VOQVL-C2z7FTDe.js +0 -1
  258. package/dist/web/assets/keybindings-store-d54pv2NK.js +0 -1
  259. package/dist/web/assets/markdown-renderer-CnE36_gd.js +0 -1
  260. package/dist/web/assets/mobile-bottom-sheet-CJ7ApOkI.js +0 -1
  261. package/dist/web/assets/mobile-explorer-sheet-BzjlS1Xn.js +0 -1
  262. package/dist/web/assets/notification-store-C7UBqxYP.js +0 -1
  263. package/dist/web/assets/packet-RMMSAZCW-DQUxN0wU.js +0 -1
  264. package/dist/web/assets/pie-UPGHQEXC-BOr9ek4Q.js +0 -1
  265. package/dist/web/assets/process-table-CXC10GSy.js +0 -1
  266. package/dist/web/assets/radar-KQ55EAFF-DSErxZFb.js +0 -1
  267. package/dist/web/assets/settings-store-OLJE7dPH.js +0 -2
  268. package/dist/web/assets/settings-tab-B1rvHFc6.js +0 -3
  269. package/dist/web/assets/settings-tab-BUlmPJ2w.js +0 -1
  270. package/dist/web/assets/system-monitor-body-DPYQlBEt.js +0 -2
  271. package/dist/web/assets/system-monitor-tab-CPQdWv27.js +0 -1
  272. package/dist/web/assets/team-member-sheet-Bu2wG1sm.js +0 -2
  273. package/dist/web/assets/treemap-KZPCXAKY-C17p0LQw.js +0 -1
  274. package/dist/web/assets/window-store-CQ1lghvv.js +0 -1
  275. /package/dist/web/assets/{abap-CjzrhAS2.js → abap-CKgBNk5K.js} +0 -0
  276. /package/dist/web/assets/{actionscript-3-B59Kbl-9.js → actionscript-3-oARhAY6c.js} +0 -0
  277. /package/dist/web/assets/{ada-B7gItKAE.js → ada-2vi8ASy3.js} +0 -0
  278. /package/dist/web/assets/{andromeeda-Dy5zcy0Q.js → andromeeda-B3vMjPKd.js} +0 -0
  279. /package/dist/web/assets/{apache-B6T9jrc0.js → apache-DnIEr338.js} +0 -0
  280. /package/dist/web/assets/{apex-CTsIxDQw.js → apex-B7Qs5AY5.js} +0 -0
  281. /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-BkGKTjsq.js} +0 -0
  282. /package/dist/web/assets/{applescript-DuycKmmb.js → applescript-EbQNUCWb.js} +0 -0
  283. /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-Dd_cqGa-.js} +0 -0
  284. /package/dist/web/assets/{ara-CISL3599.js → ara-CtHdhFRw.js} +0 -0
  285. /package/dist/web/assets/{asciidoc-BXyE2Lpr.js → asciidoc-BH6qin_O.js} +0 -0
  286. /package/dist/web/assets/{asm-y258phtP.js → asm-D1XU1ACk.js} +0 -0
  287. /package/dist/web/assets/{aurora-x-Dq3Jc78M.js → aurora-x-BkhcxkuS.js} +0 -0
  288. /package/dist/web/assets/{awk-DxL6XSJv.js → awk-DijzB6o7.js} +0 -0
  289. /package/dist/web/assets/{ayu-dark-DjoK6Evo.js → ayu-dark-tysG77WC.js} +0 -0
  290. /package/dist/web/assets/{ayu-light-CKKXBLMI.js → ayu-light-Dg0WMETC.js} +0 -0
  291. /package/dist/web/assets/{ayu-mirage-DGXSrrXB.js → ayu-mirage--RsJCjC4.js} +0 -0
  292. /package/dist/web/assets/{ballerina-DZSXFmUf.js → ballerina-DT_5-l82.js} +0 -0
  293. /package/dist/web/assets/{bat-BNsZk6nY.js → bat-BRpDlkv0.js} +0 -0
  294. /package/dist/web/assets/{beancount-cPl4gJct.js → beancount--8Df-WGG.js} +0 -0
  295. /package/dist/web/assets/{berry-DGAodERk.js → berry-Bc9fo35J.js} +0 -0
  296. /package/dist/web/assets/{bibtex-DftVoRif.js → bibtex-CwyBpp9X.js} +0 -0
  297. /package/dist/web/assets/{bicep-Suioyyzh.js → bicep-DiyH2Bs7.js} +0 -0
  298. /package/dist/web/assets/{bird2-DjU24I2P.js → bird2-CHgK4a2A.js} +0 -0
  299. /package/dist/web/assets/{c-DFi8Y_nN.js → c-BM1qL6-f.js} +0 -0
  300. /package/dist/web/assets/{c3-1eSHnH5V.js → c3-D2oOjrP7.js} +0 -0
  301. /package/dist/web/assets/{cadence-DLLsFwQP.js → cadence-nS45wqF6.js} +0 -0
  302. /package/dist/web/assets/{can-open-in-ppm-in727eFQ.js → can-open-in-ppm-DwP9KU8o.js} +0 -0
  303. /package/dist/web/assets/{catppuccin-frappe-DDj0JEiY.js → catppuccin-frappe-BpnJHiCj.js} +0 -0
  304. /package/dist/web/assets/{catppuccin-latte-DPyANHyR.js → catppuccin-latte-Bb95Wj3h.js} +0 -0
  305. /package/dist/web/assets/{catppuccin-macchiato-CO-h8VTT.js → catppuccin-macchiato-Btx8c17W.js} +0 -0
  306. /package/dist/web/assets/{catppuccin-mocha-BCmbN2IZ.js → catppuccin-mocha-B4DZOUGI.js} +0 -0
  307. /package/dist/web/assets/{clarity-C0pYsZjs.js → clarity-1ps69jU6.js} +0 -0
  308. /package/dist/web/assets/{clipboard-DBLGKMrH.js → clipboard-CrHX8AYw.js} +0 -0
  309. /package/dist/web/assets/{clojure-JFr6s8iZ.js → clojure-BklC0hKV.js} +0 -0
  310. /package/dist/web/assets/{cmake-EEcLEgVy.js → cmake-B573LYAQ.js} +0 -0
  311. /package/dist/web/assets/{codeowners-Deh3Lx0u.js → codeowners-BoYeX89M.js} +0 -0
  312. /package/dist/web/assets/{codeql-C317igT5.js → codeql-7Dhf5Udk.js} +0 -0
  313. /package/dist/web/assets/{common-lisp-DepbATZs.js → common-lisp-m1nxlthv.js} +0 -0
  314. /package/dist/web/assets/{coq-MxgxU5ya.js → coq-DNhGmd4a.js} +0 -0
  315. /package/dist/web/assets/{csharp-eEFXdsAq.js → csharp-P-nAurtS.js} +0 -0
  316. /package/dist/web/assets/{css-Db-syWW9.js → css-LYbSeh3-.js} +0 -0
  317. /package/dist/web/assets/{csv-DHeYcbaq.js → csv-DHZZZm7X.js} +0 -0
  318. /package/dist/web/assets/{csv-parser-_JWMLGl3.js → csv-parser-CY9Ggtg5.js} +0 -0
  319. /package/dist/web/assets/{cue-BmUNowrt.js → cue-D8yLUOle.js} +0 -0
  320. /package/dist/web/assets/{cypher-XjXHgCNS.js → cypher-9cbW8RYA.js} +0 -0
  321. /package/dist/web/assets/{d-BWJnZtig.js → d-ZihzhxqI.js} +0 -0
  322. /package/dist/web/assets/{dark-plus-Db7umas1.js → dark-plus-C8xIA1u3.js} +0 -0
  323. /package/dist/web/assets/{dart-DSI4HYh1.js → dart-CBm6jKc7.js} +0 -0
  324. /package/dist/web/assets/{data-grid-types-CfVHh9gv.js → data-grid-types-DkEQ-Na9.js} +0 -0
  325. /package/dist/web/assets/{dax-yw1JsAni.js → dax-B3cx6YeV.js} +0 -0
  326. /package/dist/web/assets/{desktop-C2QO4RvD.js → desktop-C03SpXth.js} +0 -0
  327. /package/dist/web/assets/{diff-Drt8aEyp.js → diff-D8LnnvFY.js} +0 -0
  328. /package/dist/web/assets/{dist-DNRvhVdz.js → dist-C4XoS_zo.js} +0 -0
  329. /package/dist/web/assets/{dist-VBxOHLmN.js → dist-Do3fljss.js} +0 -0
  330. /package/dist/web/assets/{docker-ByDKbmLl.js → docker-DwFiOQWW.js} +0 -0
  331. /package/dist/web/assets/{dotenv-BkfOLytR.js → dotenv-CVJ_vFSQ.js} +0 -0
  332. /package/dist/web/assets/{dracula-DAmrm-Un.js → dracula-DMw8UACY.js} +0 -0
  333. /package/dist/web/assets/{dracula-soft-BUOEnKqR.js → dracula-soft-BnB3nDrA.js} +0 -0
  334. /package/dist/web/assets/{dream-maker-DQBIfSiO.js → dream-maker-2riXUQXo.js} +0 -0
  335. /package/dist/web/assets/{emacs-lisp-Blh5kBD3.js → emacs-lisp-BQdYaiB5.js} +0 -0
  336. /package/dist/web/assets/{everforest-dark-CEzhDxHq.js → everforest-dark-C2FPIWdj.js} +0 -0
  337. /package/dist/web/assets/{everforest-light-CPk_5Khm.js → everforest-light-DMOWf9sD.js} +0 -0
  338. /package/dist/web/assets/{fennel-B1MT_EBZ.js → fennel-JtSHp0vW.js} +0 -0
  339. /package/dist/web/assets/{fish-C_HGcewt.js → fish-CflDcRfe.js} +0 -0
  340. /package/dist/web/assets/{fluent-Dp1Iclmx.js → fluent-D4eu6Vac.js} +0 -0
  341. /package/dist/web/assets/{format-bytes-53WBtyi2.js → format-bytes-DR3228bu.js} +0 -0
  342. /package/dist/web/assets/{fortran-free-form-B_-UA4XC.js → fortran-free-form-hO4RuC9Z.js} +0 -0
  343. /package/dist/web/assets/{gdscript-Bm_JpjS_.js → gdscript-D7n3a8VM.js} +0 -0
  344. /package/dist/web/assets/{gdshader-C_PNmfYU.js → gdshader-BhUmrlzG.js} +0 -0
  345. /package/dist/web/assets/{genie-B5rmR2TF.js → genie-VbyftqBp.js} +0 -0
  346. /package/dist/web/assets/{gherkin-BTJ34TJ9.js → gherkin-BZzhvUUb.js} +0 -0
  347. /package/dist/web/assets/{github-dark-DOkNVj9H.js → github-dark-C4q41woW.js} +0 -0
  348. /package/dist/web/assets/{github-dark-default-DTsmiyVo.js → github-dark-default-CMNi7dQX.js} +0 -0
  349. /package/dist/web/assets/{github-dark-dimmed-CjbxAe9s.js → github-dark-dimmed-BRf_-LlY.js} +0 -0
  350. /package/dist/web/assets/{github-dark-high-contrast-r_M8F_k7.js → github-dark-high-contrast-xanKBsRI.js} +0 -0
  351. /package/dist/web/assets/{github-light-BzfxyMyb.js → github-light-BEQeNQCs.js} +0 -0
  352. /package/dist/web/assets/{github-light-default-BOqiBIy5.js → github-light-default-ceQrtdXg.js} +0 -0
  353. /package/dist/web/assets/{github-light-high-contrast-DQzBrDM4.js → github-light-high-contrast-doKbXzgW.js} +0 -0
  354. /package/dist/web/assets/{gleam-BWmuAtD8.js → gleam-Chzqf4fT.js} +0 -0
  355. /package/dist/web/assets/{gn-CNQPjiZW.js → gn-CCN_-6fp.js} +0 -0
  356. /package/dist/web/assets/{gnuplot-BlHjcPMe.js → gnuplot-CIplgwwP.js} +0 -0
  357. /package/dist/web/assets/{go-Bn8Ba9uz.js → go-Cjj-9dRs.js} +0 -0
  358. /package/dist/web/assets/{groovy-C1Y1_F4V.js → groovy-PqD4K7X1.js} +0 -0
  359. /package/dist/web/assets/{gruvbox-dark-hard-DueOsHav.js → gruvbox-dark-hard-BAbpUSF-.js} +0 -0
  360. /package/dist/web/assets/{gruvbox-dark-medium-DaQjD538.js → gruvbox-dark-medium-BWcWXDBB.js} +0 -0
  361. /package/dist/web/assets/{gruvbox-dark-soft-CbbX2WOm.js → gruvbox-dark-soft-Dkx1okkk.js} +0 -0
  362. /package/dist/web/assets/{gruvbox-light-hard-E2M70ljZ.js → gruvbox-light-hard-WVsDK0MM.js} +0 -0
  363. /package/dist/web/assets/{gruvbox-light-medium-B_DDLY6h.js → gruvbox-light-medium-tiR9t7gr.js} +0 -0
  364. /package/dist/web/assets/{gruvbox-light-soft-2SZmyC1F.js → gruvbox-light-soft-HzHohaX6.js} +0 -0
  365. /package/dist/web/assets/{haskell-D8DeoXtG.js → haskell-B1eIZfr6.js} +0 -0
  366. /package/dist/web/assets/{haxe-BmZm7B0W.js → haxe-BcDdi5xr.js} +0 -0
  367. /package/dist/web/assets/{hcl-CKnBM3ng.js → hcl-Dq-HKVni.js} +0 -0
  368. /package/dist/web/assets/{hjson-Ct9t4-ci.js → hjson-Dr9D9WVU.js} +0 -0
  369. /package/dist/web/assets/{hlsl-BZSASBqS.js → hlsl-Bo8t2FuZ.js} +0 -0
  370. /package/dist/web/assets/{horizon-CzX1AZ08.js → horizon-DWG5IXc-.js} +0 -0
  371. /package/dist/web/assets/{horizon-bright-CCVNeRIP.js → horizon-bright-DFr9wTTx.js} +0 -0
  372. /package/dist/web/assets/{houston-DBlJN7xc.js → houston-NAVNhkup.js} +0 -0
  373. /package/dist/web/assets/{hy-Dyc6h6Xb.js → hy-DnyvUk3B.js} +0 -0
  374. /package/dist/web/assets/{image-gallery-DujlrjOC.js → image-gallery-DoFdNF9i.js} +0 -0
  375. /package/dist/web/assets/{imba-Bauru7s2.js → imba-CPbR5W_A.js} +0 -0
  376. /package/dist/web/assets/{ini-SjdwtaTp.js → ini-lc8_Lt0y.js} +0 -0
  377. /package/dist/web/assets/{java-D2r0ocpf.js → java-B0KCo2vK.js} +0 -0
  378. /package/dist/web/assets/{javascript-y-lof7iP.js → javascript-YqEbNRpd.js} +0 -0
  379. /package/dist/web/assets/{json-DO4LSx5A.js → json-Co0WtOlh.js} +0 -0
  380. /package/dist/web/assets/{json5-BEwWf-W3.js → json5-2Jhb0QUN.js} +0 -0
  381. /package/dist/web/assets/{jsonc-OBEFv7gP.js → jsonc-e72F11nU.js} +0 -0
  382. /package/dist/web/assets/{jsonl-DOyW4Mg3.js → jsonl-Bff8IPye.js} +0 -0
  383. /package/dist/web/assets/{jsonnet-BL5R2CpP.js → jsonnet-DRCkb-_H.js} +0 -0
  384. /package/dist/web/assets/{jssm-ksPusAyg.js → jssm-C6UiwVEm.js} +0 -0
  385. /package/dist/web/assets/{jsx-B6XEu6O-.js → jsx-u1aZlnfA.js} +0 -0
  386. /package/dist/web/assets/{kanagawa-dragon-JBVe_ji_.js → kanagawa-dragon-De-RQFV3.js} +0 -0
  387. /package/dist/web/assets/{kanagawa-lotus-ChprO68_.js → kanagawa-lotus-K8elE4QU.js} +0 -0
  388. /package/dist/web/assets/{kanagawa-wave-D_uB_R0Q.js → kanagawa-wave-BrStl_zx.js} +0 -0
  389. /package/dist/web/assets/{kdl-pf5B8N_m.js → kdl-CLCfMeZy.js} +0 -0
  390. /package/dist/web/assets/{kotlin-BdF0METC.js → kotlin-DEEC3pNh.js} +0 -0
  391. /package/dist/web/assets/{kusto-BneVP-OU.js → kusto-DTqRhCup.js} +0 -0
  392. /package/dist/web/assets/{laserwave-C4-UPIyG.js → laserwave-DsgpUSbA.js} +0 -0
  393. /package/dist/web/assets/{lean-BN6Jdymq.js → lean-Dl9MI0Y3.js} +0 -0
  394. /package/dist/web/assets/{less-Cx-fdlDC.js → less-Bg21rao6.js} +0 -0
  395. /package/dist/web/assets/{lib-CMuBa85c.js → lib-CXUwxw1M.js} +0 -0
  396. /package/dist/web/assets/{libesm-BB74PRsH.js → libesm-CVkncAQz.js} +0 -0
  397. /package/dist/web/assets/{light-plus-DFnnn_iL.js → light-plus-DGNbbK1V.js} +0 -0
  398. /package/dist/web/assets/{llvm-BGxtW0Nw.js → llvm-91_ZX3p-.js} +0 -0
  399. /package/dist/web/assets/{loader-circle-Dpqcv0eS.js → loader-circle-DouKgIXy.js} +0 -0
  400. /package/dist/web/assets/{lock-DldbT31x.js → lock-CNxUGpTk.js} +0 -0
  401. /package/dist/web/assets/{log-kQb4eJnj.js → log-DSXzbq8u.js} +0 -0
  402. /package/dist/web/assets/{logo-DqAzaq7s.js → logo-BQRNd4tc.js} +0 -0
  403. /package/dist/web/assets/{luau-Dc2cp-zS.js → luau-BE-TMpiZ.js} +0 -0
  404. /package/dist/web/assets/{make-DYg9dwaJ.js → make-ePYGKTpc.js} +0 -0
  405. /package/dist/web/assets/{markdown-O4EPpS3K.js → markdown-Cst3wgMW.js} +0 -0
  406. /package/dist/web/assets/{material-theme-ClP8yfQ2.js → material-theme-9XyvnNLb.js} +0 -0
  407. /package/dist/web/assets/{material-theme-darker-LOK55hGu.js → material-theme-darker-H1VN4MTS.js} +0 -0
  408. /package/dist/web/assets/{material-theme-lighter-j72tzZqi.js → material-theme-lighter-DAreBbuF.js} +0 -0
  409. /package/dist/web/assets/{material-theme-ocean-C19LKClA.js → material-theme-ocean-BpTR-3II.js} +0 -0
  410. /package/dist/web/assets/{material-theme-palenight-DJ5tN2ax.js → material-theme-palenight-DUNzX711.js} +0 -0
  411. /package/dist/web/assets/{matlab-DQNZ_NU8.js → matlab-BpYNufBU.js} +0 -0
  412. /package/dist/web/assets/{mdx-CJayB7XK.js → mdx-BYjfe1DK.js} +0 -0
  413. /package/dist/web/assets/{mermaid-Cb0TRX92.js → mermaid-DPCcIW9B.js} +0 -0
  414. /package/dist/web/assets/{min-dark-CWM8Bo-m.js → min-dark-DnGOAGCo.js} +0 -0
  415. /package/dist/web/assets/{min-light-BBR1sjS2.js → min-light-CwV-o31y.js} +0 -0
  416. /package/dist/web/assets/{mipsasm-Jc7u7YSS.js → mipsasm-DdeCPC97.js} +0 -0
  417. /package/dist/web/assets/{mojo-CTB0cutd.js → mojo-BmxxZ3iM.js} +0 -0
  418. /package/dist/web/assets/{monitor-4vWfqzuN.js → monitor-C835ucWH.js} +0 -0
  419. /package/dist/web/assets/{monokai-CvM_YNKx.js → monokai-Cx6cQ-MA.js} +0 -0
  420. /package/dist/web/assets/{moonbit-CbFEfYYM.js → moonbit-D2-Gj5vj.js} +0 -0
  421. /package/dist/web/assets/{move-BT38p0kT.js → move-DSWGoQa8.js} +0 -0
  422. /package/dist/web/assets/{narrat-55QZ_kOG.js → narrat-D9TnZSSu.js} +0 -0
  423. /package/dist/web/assets/{nextflow-groovy-Bg8peyqg.js → nextflow-groovy-BbL-8PwD.js} +0 -0
  424. /package/dist/web/assets/{night-owl-DzS77iPK.js → night-owl-CZitKm5d.js} +0 -0
  425. /package/dist/web/assets/{night-owl-light-DER7fGnx.js → night-owl-light-DJVX1Pno.js} +0 -0
  426. /package/dist/web/assets/{nix-BSI8YesN.js → nix-R6hr3oln.js} +0 -0
  427. /package/dist/web/assets/{nord-9bn7iNWo.js → nord-ChGHya3l.js} +0 -0
  428. /package/dist/web/assets/{nushell-qDVZn_lw.js → nushell-QQWB-GWx.js} +0 -0
  429. /package/dist/web/assets/{objective-c-Blf-ATos.js → objective-c-C0pZ6y73.js} +0 -0
  430. /package/dist/web/assets/{objective-cpp-Bz8bAq47.js → objective-cpp-BdzVM31a.js} +0 -0
  431. /package/dist/web/assets/{ocaml-B9heAsc3.js → ocaml-D4KktNlS.js} +0 -0
  432. /package/dist/web/assets/{odin-BaFYnOwO.js → odin-Y-fypmGT.js} +0 -0
  433. /package/dist/web/assets/{one-dark-pro-D6B_hSe-.js → one-dark-pro-BwecIxQL.js} +0 -0
  434. /package/dist/web/assets/{one-light-Ba-f0wyk.js → one-light-Cz7UVzLU.js} +0 -0
  435. /package/dist/web/assets/{openscad-UvYyL2MK.js → openscad-DtRe3qYD.js} +0 -0
  436. /package/dist/web/assets/{pascal-B4-qvEoG.js → pascal-Ck6I1fGG.js} +0 -0
  437. /package/dist/web/assets/{pkl-B146nqGx.js → pkl-DdFK94VW.js} +0 -0
  438. /package/dist/web/assets/{plastic-L38t-Id3.js → plastic-BQSAzZwR.js} +0 -0
  439. /package/dist/web/assets/{play-CpCAoQRZ.js → play-CJlWpVMw.js} +0 -0
  440. /package/dist/web/assets/{plsql-ItOG5Cca.js → plsql-DjXg_CP0.js} +0 -0
  441. /package/dist/web/assets/{po-imiCTaNX.js → po-Bw2SuM4r.js} +0 -0
  442. /package/dist/web/assets/{poimandres-Bu6sIba9.js → poimandres-YMK6c5VN.js} +0 -0
  443. /package/dist/web/assets/{polar-CTeGoFxV.js → polar-BMu17-O0.js} +0 -0
  444. /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DSGdlXrJ.js} +0 -0
  445. /package/dist/web/assets/{postcss-jvFe6GNE.js → postcss-DWib10RZ.js} +0 -0
  446. /package/dist/web/assets/{powerquery-D9Dw1hJp.js → powerquery-Dht4CK0b.js} +0 -0
  447. /package/dist/web/assets/{powershell-CZqvYQtg.js → powershell-DvrCg8ji.js} +0 -0
  448. /package/dist/web/assets/{prisma-B4D2nO_7.js → prisma-DbodYvCM.js} +0 -0
  449. /package/dist/web/assets/{prolog-CQbyUcCF.js → prolog-DZ-m6ysB.js} +0 -0
  450. /package/dist/web/assets/{proto-Cwpo0cW1.js → proto-HHpG3tGO.js} +0 -0
  451. /package/dist/web/assets/{puppet-CKxCwBod.js → puppet-BljHe_75.js} +0 -0
  452. /package/dist/web/assets/{purescript-B-MyXMMp.js → purescript-DzCiFR0B.js} +0 -0
  453. /package/dist/web/assets/{python-D6QbSR2S.js → python-B5BQ27cC.js} +0 -0
  454. /package/dist/web/assets/{qmldir-BpTSVcw_.js → qmldir-CYlcXrnn.js} +0 -0
  455. /package/dist/web/assets/{qss-DBc4gbpe.js → qss-Ce7T_TEg.js} +0 -0
  456. /package/dist/web/assets/{r-i9jMMi0s.js → r-Bku-2Jp_.js} +0 -0
  457. /package/dist/web/assets/{racket-BWNaxU7C.js → racket-DtfSL3WR.js} +0 -0
  458. /package/dist/web/assets/{raku-fqTZLn7L.js → raku-CnDLsBJr.js} +0 -0
  459. /package/dist/web/assets/{react-X4jUntEf.js → react-Dfio3PfK.js} +0 -0
  460. /package/dist/web/assets/{red-D-JEcuzA.js → red-vbqcE9b-.js} +0 -0
  461. /package/dist/web/assets/{refresh-cw-B9iv7csb.js → refresh-cw-84mz8aOE.js} +0 -0
  462. /package/dist/web/assets/{reg-BbirlU1S.js → reg-DIDLT8-A.js} +0 -0
  463. /package/dist/web/assets/{regexp-D8VTFrtg.js → regexp-CB4h-VPu.js} +0 -0
  464. /package/dist/web/assets/{rel-ByQGKiNM.js → rel-CiQEnWXa.js} +0 -0
  465. /package/dist/web/assets/{resolve-theme-Cqb9zWYc.js → resolve-theme-DPTQHAy1.js} +0 -0
  466. /package/dist/web/assets/{riscv-B5nqCfLE.js → riscv-R78iq5Rf.js} +0 -0
  467. /package/dist/web/assets/{ron-DOVh_iZR.js → ron-DhYENxc4.js} +0 -0
  468. /package/dist/web/assets/{rose-pine-jp51ZUzi.js → rose-pine-5wBeyjfJ.js} +0 -0
  469. /package/dist/web/assets/{rose-pine-dawn-B7hkmmWy.js → rose-pine-dawn-FpKtPCuH.js} +0 -0
  470. /package/dist/web/assets/{rose-pine-moon-Ff063yLj.js → rose-pine-moon-BwcIYc7C.js} +0 -0
  471. /package/dist/web/assets/{rosmsg-Cyk7Il8z.js → rosmsg-DWIGAYMl.js} +0 -0
  472. /package/dist/web/assets/{rotate-ccw-DnuPzJRE.js → rotate-ccw-CNTk2J7l.js} +0 -0
  473. /package/dist/web/assets/{rust-DRNM5epF.js → rust-DU_ofKgt.js} +0 -0
  474. /package/dist/web/assets/{sass-D_cB0-M1.js → sass-U9SUuYEy.js} +0 -0
  475. /package/dist/web/assets/{save-DxonD4y1.js → save-CtW9ojKk.js} +0 -0
  476. /package/dist/web/assets/{scala-Crl-1kKS.js → scala-op4gjA6j.js} +0 -0
  477. /package/dist/web/assets/{scheme-DCaWyEJ0.js → scheme-tEjkR8rL.js} +0 -0
  478. /package/dist/web/assets/{sdbl-BbXNqwtX.js → sdbl-C6SSRmZg.js} +0 -0
  479. /package/dist/web/assets/{send-B-t5Mz26.js → send-C_sLFt56.js} +0 -0
  480. /package/dist/web/assets/{shallow-4zAfs2KW.js → shallow-C1YoJQA-.js} +0 -0
  481. /package/dist/web/assets/{shellscript-BAgksBhW.js → shellscript-Bf2KiwQj.js} +0 -0
  482. /package/dist/web/assets/{shield-check-CdTiK1j2.js → shield-check-Du6HKYn9.js} +0 -0
  483. /package/dist/web/assets/{shield-off-DuetkXKM.js → shield-off-CWKb1MQL.js} +0 -0
  484. /package/dist/web/assets/{slack-dark-GB-ElUW_.js → slack-dark-oGgCey8r.js} +0 -0
  485. /package/dist/web/assets/{slack-ochin-tU6CFD3S.js → slack-ochin-CemlB8cE.js} +0 -0
  486. /package/dist/web/assets/{smalltalk-DSVGUbtL.js → smalltalk-CTE-6jrZ.js} +0 -0
  487. /package/dist/web/assets/{snazzy-light-DZhi3PoS.js → snazzy-light-DoEIGnmz.js} +0 -0
  488. /package/dist/web/assets/{solarized-dark-D6AMCco7.js → solarized-dark-CStiJyBg.js} +0 -0
  489. /package/dist/web/assets/{solarized-light-DepuTWSE.js → solarized-light-CfuNycS9.js} +0 -0
  490. /package/dist/web/assets/{solidity-CsxN618l.js → solidity-DL6sO5rd.js} +0 -0
  491. /package/dist/web/assets/{splunk-CF1m0Kkv.js → splunk-yK4ZUFBB.js} +0 -0
  492. /package/dist/web/assets/{sql-DvyMDgrB.js → sql-Lw03eoN8.js} +0 -0
  493. /package/dist/web/assets/{ssh-config-a8sDo-Q8.js → ssh-config-DeRp25TR.js} +0 -0
  494. /package/dist/web/assets/{stylus-DCJ-6AjT.js → stylus-D8OMsPh-.js} +0 -0
  495. /package/dist/web/assets/{swift-c-ZFxyPP.js → swift-nsl9stPV.js} +0 -0
  496. /package/dist/web/assets/{synthwave-84-9NPgBcuv.js → synthwave-84-exAgsgpf.js} +0 -0
  497. /package/dist/web/assets/{system-verilog-DPD02IOo.js → system-verilog-DBWf00T6.js} +0 -0
  498. /package/dist/web/assets/{systemd-BUr5HRtU.js → systemd-nhu27Dw5.js} +0 -0
  499. /package/dist/web/assets/{table-FLU8ei8b.js → table-GAR4upb6.js} +0 -0
  500. /package/dist/web/assets/{talonscript-BNBQV5YJ.js → talonscript-CNOEcGDc.js} +0 -0
  501. /package/dist/web/assets/{tasl-BETjpSyu.js → tasl-CjYZenSH.js} +0 -0
  502. /package/dist/web/assets/{tcl-Clp_AZiC.js → tcl-BVA09RDz.js} +0 -0
  503. /package/dist/web/assets/{terraform-B0kZ0lim.js → terraform-C5EloH1q.js} +0 -0
  504. /package/dist/web/assets/{text-wrap-B05VGRa-.js → text-wrap-D71Jqz5U.js} +0 -0
  505. /package/dist/web/assets/{tokyo-night-oOJ0a7Dk.js → tokyo-night-D764DdhO.js} +0 -0
  506. /package/dist/web/assets/{toml-DOPCSumy.js → toml-CCDYblSB.js} +0 -0
  507. /package/dist/web/assets/{trash-2-CTXlTflF.js → trash-2-C08fhIg1.js} +0 -0
  508. /package/dist/web/assets/{triangle-alert-BRq0VAfd.js → triangle-alert-85BQgs3T.js} +0 -0
  509. /package/dist/web/assets/{tsv-D6qUGf84.js → tsv-BZ4sA05e.js} +0 -0
  510. /package/dist/web/assets/{tsx-B6qYhT6O.js → tsx-DPLBzAt5.js} +0 -0
  511. /package/dist/web/assets/{turtle-C0Rjzw73.js → turtle-CwGHCq40.js} +0 -0
  512. /package/dist/web/assets/{typescript-BiydShEu.js → typescript-CUH0vBAl.js} +0 -0
  513. /package/dist/web/assets/{typespec-DjmF6Aww.js → typespec-Bq31Vq4u.js} +0 -0
  514. /package/dist/web/assets/{typst-CPuscfcL.js → typst-MD9BR9W5.js} +0 -0
  515. /package/dist/web/assets/{use-is-mobile-b94q8RvF.js → use-is-mobile-DUuki97q.js} +0 -0
  516. /package/dist/web/assets/{utils-C9OjCrou.js → utils-u2fbYPrY.js} +0 -0
  517. /package/dist/web/assets/{v-DE4vpLLQ.js → v-DZV7IxIT.js} +0 -0
  518. /package/dist/web/assets/{vala-CmUF2pS5.js → vala-urd1zi9S.js} +0 -0
  519. /package/dist/web/assets/{vb-BvcRoX4b.js → vb-DIZU3zbJ.js} +0 -0
  520. /package/dist/web/assets/{vendor-xterm-K8kpLxdb.js → vendor-xterm-DCj0zgSC.js} +0 -0
  521. /package/dist/web/assets/{verilog-BGIzyH3R.js → verilog-Ndz_ciTA.js} +0 -0
  522. /package/dist/web/assets/{vesper-CG7IrHQQ.js → vesper-C86M7LJp.js} +0 -0
  523. /package/dist/web/assets/{vhdl-DMSumomw.js → vhdl-_klwDUjd.js} +0 -0
  524. /package/dist/web/assets/{viml-Drt7AEmU.js → viml-DzRXWoQh.js} +0 -0
  525. /package/dist/web/assets/{vitesse-black-DmsjfN0b.js → vitesse-black-TWZ20_3U.js} +0 -0
  526. /package/dist/web/assets/{vitesse-dark-Dl55_WeJ.js → vitesse-dark-Dl7R6fy9.js} +0 -0
  527. /package/dist/web/assets/{vitesse-light-Crbv9aGg.js → vitesse-light-Cs0Dt2_n.js} +0 -0
  528. /package/dist/web/assets/{vyper-CB7psEJj.js → vyper-qnCVj8ia.js} +0 -0
  529. /package/dist/web/assets/{wasm-Dck8KPgP.js → wasm-Ckx9jBPe.js} +0 -0
  530. /package/dist/web/assets/{wasm-BY1tuM5m.js → wasm-Dy-SJ3W9.js} +0 -0
  531. /package/dist/web/assets/{wenyan-DhewQ68b.js → wenyan-DTEy2Lx-.js} +0 -0
  532. /package/dist/web/assets/{wgsl-DlmiP456.js → wgsl-DC7n63Sy.js} +0 -0
  533. /package/dist/web/assets/{wikitext-cQCnqNgE.js → wikitext-1zFoZiW6.js} +0 -0
  534. /package/dist/web/assets/{wit-S5MaCngB.js → wit-DtTfCRph.js} +0 -0
  535. /package/dist/web/assets/{wolfram-Bh8n_9zr.js → wolfram-B2Bgu1KL.js} +0 -0
  536. /package/dist/web/assets/{x-YDu4m2Iq.js → x-CeN0Qybc.js} +0 -0
  537. /package/dist/web/assets/{yaml-BoK_17l9.js → yaml-B-Jxk0IQ.js} +0 -0
  538. /package/dist/web/assets/{zenscript-DB3ZFwNJ.js → zenscript-BWRfvNER.js} +0 -0
  539. /package/dist/web/assets/{zig-Bv1BWmfT.js → zig-CkwuvETm.js} +0 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Groups the raw NAL units from `NalSplitter` into WebCodecs-ready access units.
3
+ *
4
+ * `VideoDecoder` decodes one access unit (one frame) per `EncodedVideoChunk`, not one
5
+ * NAL — feeding SPS/PPS/slice as three separate chunks throws or corrupts decode. libx264
6
+ * (no B-frames, `-bf 0`) emits at most one slice NAL per frame, optionally preceded by
7
+ * SPS/PPS on a keyframe, so an AU boundary is simply "the next VCL NAL after we already
8
+ * buffered one" — everything non-VCL (SPS/PPS/AUD/SEI) that arrived first attaches to the
9
+ * AU it precedes.
10
+ */
11
+ import { NalSplitter, nalType, isVclNal, NAL_TYPE_IDR, NAL_TYPE_SPS, NAL_TYPE_PPS } from "./nal-splitter.ts";
12
+
13
+ export interface AccessUnit {
14
+ /** Annex-B encoded bytes (start codes + NAL payloads) — one `EncodedVideoChunk` worth. */
15
+ bytes: Uint8Array;
16
+ isKey: boolean;
17
+ }
18
+
19
+ const START_CODE = Uint8Array.of(0, 0, 0, 1);
20
+
21
+ function encodeAnnexB(nals: Uint8Array[]): Uint8Array {
22
+ let total = 0;
23
+ for (const n of nals) total += START_CODE.length + n.length;
24
+ const out = new Uint8Array(total);
25
+ let offset = 0;
26
+ for (const n of nals) {
27
+ out.set(START_CODE, offset);
28
+ offset += START_CODE.length;
29
+ out.set(n, offset);
30
+ offset += n.length;
31
+ }
32
+ return out;
33
+ }
34
+
35
+ export class AccessUnitAssembler {
36
+ private readonly splitter = new NalSplitter();
37
+ private pending: Uint8Array[] = [];
38
+ private pendingHasVcl = false;
39
+ private sps: Uint8Array | null = null;
40
+ private pps: Uint8Array | null = null;
41
+
42
+ /** Feed a raw ffmpeg stdout chunk; returns zero or more completed access units. */
43
+ push(chunk: Uint8Array): AccessUnit[] {
44
+ const aus: AccessUnit[] = [];
45
+ for (const nal of this.splitter.push(chunk)) {
46
+ const type = nalType(nal);
47
+ if (type === NAL_TYPE_SPS) this.sps = nal;
48
+ else if (type === NAL_TYPE_PPS) this.pps = nal;
49
+
50
+ const vcl = isVclNal(nal);
51
+ if (vcl && this.pendingHasVcl) {
52
+ const au = this.flush();
53
+ if (au) aus.push(au);
54
+ }
55
+ this.pending.push(nal);
56
+ if (vcl) this.pendingHasVcl = true;
57
+ }
58
+ return aus;
59
+ }
60
+
61
+ /** SPS bytes seen so far (NAL header byte included) — used to derive the avc1 codec
62
+ * string. Null until the encoder has emitted its first keyframe. */
63
+ cachedSps(): Uint8Array | null {
64
+ return this.sps;
65
+ }
66
+
67
+ private flush(): AccessUnit | null {
68
+ if (this.pending.length === 0) {
69
+ this.pendingHasVcl = false;
70
+ return null;
71
+ }
72
+ let nals = this.pending;
73
+ const isKey = nals.some((n) => nalType(n) === NAL_TYPE_IDR);
74
+ if (isKey) {
75
+ // A late-joining client (or a client that missed the very first AU) needs SPS+PPS
76
+ // concatenated into the same key chunk it decodes — three separate chunks is not
77
+ // decodable Annex-B input for WebCodecs. Normally libx264 already includes them; this
78
+ // is a defensive backfill from the cache, not the common path.
79
+ const hasSps = nals.some((n) => nalType(n) === NAL_TYPE_SPS);
80
+ const hasPps = nals.some((n) => nalType(n) === NAL_TYPE_PPS);
81
+ const prefix: Uint8Array[] = [];
82
+ if (!hasSps && this.sps) prefix.push(this.sps);
83
+ if (!hasPps && this.pps) prefix.push(this.pps);
84
+ if (prefix.length > 0) nals = [...prefix, ...nals];
85
+ }
86
+ this.pending = [];
87
+ this.pendingHasVcl = false;
88
+ return { bytes: encodeAnnexB(nals), isKey };
89
+ }
90
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Derive the WebCodecs `avc1.PPCCLL` codec string from a real SPS NAL rather than
3
+ * guessing. `encoderArgs()` in `ffmpeg-capabilities.ts` sets no `-profile`/`-level`, so the
4
+ * actual encoded profile/level is encoder-default and unknown until the bitstream exists —
5
+ * a hardcoded guess can mismatch and make `VideoDecoder.configure()` throw or refuse.
6
+ *
7
+ * SPS layout (after the 1-byte NAL header at index 0): profile_idc, then the
8
+ * constraint-flags/reserved byte, then level_idc — always the first three bytes of the
9
+ * RBSP, unaffected by emulation-prevention bytes (those only appear deeper in the SPS).
10
+ */
11
+ export function avc1CodecString(sps: Uint8Array): string | null {
12
+ if (sps.length < 4) return null;
13
+ const profileIdc = sps[1]!;
14
+ const constraintFlags = sps[2]!;
15
+ const levelIdc = sps[3]!;
16
+ const hex = (n: number) => n.toString(16).padStart(2, "0");
17
+ return `avc1.${hex(profileIdc)}${hex(constraintFlags)}${hex(levelIdc)}`;
18
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Incremental Annex-B start-code splitter.
3
+ *
4
+ * ffmpeg's `-f h264` stdout is a raw Annex-B byte stream: NAL units separated by
5
+ * `00 00 01` or `00 00 00 01` start codes, with no framing that respects our read
6
+ * chunk boundaries. A NAL can straddle two `stdout` reads, so this holds the tail
7
+ * of the last incomplete NAL across calls to `push()` rather than assuming a full
8
+ * NAL always arrives in one chunk.
9
+ */
10
+
11
+ /** NAL unit types relevant to access-unit assembly (ITU-T H.264 Table 7-1). */
12
+ export const NAL_TYPE_SLICE_NON_IDR = 1;
13
+ export const NAL_TYPE_IDR = 5;
14
+ export const NAL_TYPE_SPS = 7;
15
+ export const NAL_TYPE_PPS = 8;
16
+
17
+ /** Low 5 bits of the NAL header byte (the unit's first byte) is `nal_unit_type`. */
18
+ export function nalType(nal: Uint8Array): number {
19
+ return (nal[0] ?? 0) & 0x1f;
20
+ }
21
+
22
+ /** A slice NAL starts a new access unit; SPS/PPS/AUD/SEI attach to whichever AU follows them. */
23
+ export function isVclNal(nal: Uint8Array): boolean {
24
+ const t = nalType(nal);
25
+ return t === NAL_TYPE_SLICE_NON_IDR || t === NAL_TYPE_IDR;
26
+ }
27
+
28
+ /** `Bun.spawn` stdout chunks type as `Uint8Array<ArrayBufferLike>` (could in principle back
29
+ * onto a SharedArrayBuffer), while `new Uint8Array(n)` types as the narrower
30
+ * `Uint8Array<ArrayBuffer>` — pin everything here to the wider type so a plain chunk and our
31
+ * own freshly-allocated buffers can always mix without a type error. */
32
+ export type Bytes = Uint8Array<ArrayBufferLike>;
33
+
34
+ function concat(a: Bytes, b: Bytes): Bytes {
35
+ if (a.length === 0) return b;
36
+ if (b.length === 0) return a;
37
+ const out = new Uint8Array(a.length + b.length);
38
+ out.set(a, 0);
39
+ out.set(b, a.length);
40
+ return out;
41
+ }
42
+
43
+ /** Index of the next `00 00 01` marker at or after `from`, or -1. This always finds the
44
+ * *last* 3 bytes of a start code — a 4-byte `00 00 00 01` form has its 3-byte suffix match
45
+ * here too (at index+1), and H.264 emulation prevention guarantees `00 00 01`/`00 00 00`
46
+ * never occurs inside real RBSP payload data, so there is no false-positive risk scanning
47
+ * straight through NAL bodies. */
48
+ function findStartCode(buf: Bytes, from: number): number {
49
+ for (let i = from; i + 2 < buf.length; i++) {
50
+ if (buf[i] === 0 && buf[i + 1] === 0 && buf[i + 2] === 1) return i;
51
+ }
52
+ return -1;
53
+ }
54
+
55
+ /** Bytes to skip past a marker found by `findStartCode` to reach the payload — always 3
56
+ * (the marker itself), regardless of how many extra leading zero bytes preceded it. */
57
+ const MARKER_LEN = 3;
58
+
59
+ /** Guard against an unbounded buffer if ffmpeg ever emits a byte stream with no start
60
+ * code at all (would indicate a broken pipe/format, not a real Annex-B stream). */
61
+ const MAX_BUFFER_BYTES = 8 * 1024 * 1024;
62
+
63
+ export class NalSplitter {
64
+ private buffer: Bytes = new Uint8Array(0);
65
+
66
+ /** Feed a raw stdout chunk; returns zero or more complete NAL payloads (start code
67
+ * stripped). Incomplete trailing bytes are retained for the next call. */
68
+ push(chunk: Bytes): Bytes[] {
69
+ this.buffer = concat(this.buffer, chunk);
70
+ const nals: Bytes[] = [];
71
+
72
+ let start = findStartCode(this.buffer, 0);
73
+ if (start === -1) {
74
+ if (this.buffer.length > MAX_BUFFER_BYTES) this.buffer = new Uint8Array(0);
75
+ return nals;
76
+ }
77
+ // Drop any garbage before the first start code (should not happen with a clean
78
+ // ffmpeg pipe, but a partial NAL from before this splitter attached must not corrupt
79
+ // the first parsed unit).
80
+ for (;;) {
81
+ const nextStart = findStartCode(this.buffer, start + MARKER_LEN);
82
+ if (nextStart === -1) {
83
+ this.buffer = this.buffer.slice(start);
84
+ break;
85
+ }
86
+ // `nextStart` only marks the *last* 3 bytes of the following start code — a 4-byte
87
+ // `00 00 00 01` form leaves its leading zero sitting right before `nextStart`, which
88
+ // would otherwise get appended to this NAL's payload as a spurious trailing byte.
89
+ // Trim any such zero bytes back to the real payload end (standard Annex-B handling of
90
+ // `leading_zero_8bits`/`trailing_zero_8bits`).
91
+ let payloadEnd = nextStart;
92
+ const payloadStart = start + MARKER_LEN;
93
+ while (payloadEnd > payloadStart && this.buffer[payloadEnd - 1] === 0) payloadEnd--;
94
+ const payload = this.buffer.slice(payloadStart, payloadEnd);
95
+ if (payload.length > 0) nals.push(payload);
96
+ start = nextStart;
97
+ }
98
+ return nals;
99
+ }
100
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Spawn ffmpeg (`gdigrab` → H.264 Annex-B on `pipe:1`) and turn its stdout into access units.
3
+ *
4
+ * Teardown is `proc.kill()` only — never `reader.cancel()`, never `for await (chunk of
5
+ * proc.stdout) { ... break }`. Both of those cancel the underlying stream reader, which
6
+ * segfaults Bun 1.3.x on Windows the instant a consumer goes away (see
7
+ * `transcode-stream.ts:130-148` and `tests/integration/transcode-stream-client-disconnect.test.ts`
8
+ * — that guard targets a `Response` body; the WS rewrite here re-creates the same trap one
9
+ * layer down if the pump loop is written as `for await`). `stop()` only calls `proc.kill()`;
10
+ * the in-flight `reader.read()` then resolves with `done: true` and the pump loop exits on
11
+ * its own — it is never cancelled from outside.
12
+ */
13
+ import { getFfmpegCapabilities } from "../media-transcode/ffmpeg-capabilities.ts";
14
+ import { captureEncoderArgs, CAPTURE_FRAMERATE } from "./remote-desktop-encoder-args.ts";
15
+ import { AccessUnitAssembler, type AccessUnit } from "./access-unit-assembler.ts";
16
+
17
+ export class CaptureUnavailableError extends Error {
18
+ constructor(msg = "ffmpeg is not installed (gdigrab capture requires it)") {
19
+ super(msg);
20
+ this.name = "CaptureUnavailableError";
21
+ }
22
+ }
23
+
24
+ /** Build the gdigrab argv; pure so it can be asserted on without spawning a process.
25
+ * `-fflags nobuffer -flags low_delay` + `-flush_packets 1` stop ffmpeg from holding frames in
26
+ * its demux/mux buffers before emitting — on a fast/LAN transport that buffering is a big slice
27
+ * of the felt lag. `encoder` selects the H.264 encoder args (hardware NVENC/QSV/AMF when the
28
+ * capability probe found one, else libx264). */
29
+ export function buildCaptureArgs(ffmpeg: string, encoder: string = "libx264"): string[] {
30
+ return [
31
+ ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
32
+ "-fflags", "nobuffer", "-flags", "low_delay",
33
+ "-f", "gdigrab", "-framerate", String(CAPTURE_FRAMERATE), "-i", "desktop",
34
+ "-vf", "scale=-2:720",
35
+ ...captureEncoderArgs(encoder),
36
+ "-flush_packets", "1",
37
+ "-f", "h264", "pipe:1",
38
+ ];
39
+ }
40
+
41
+ export interface CaptureHandle {
42
+ /** SPS bytes cached from the stream (for the avc1 codec string), null before the encoder's
43
+ * first keyframe has been parsed. */
44
+ cachedSps(): Uint8Array | null;
45
+ /** Kill ffmpeg. Idempotent, safe to call from multiple teardown paths. */
46
+ stop(): void;
47
+ /** True once `stop()` has run or the process exited on its own. */
48
+ isStopped(): boolean;
49
+ }
50
+
51
+ export interface StartCaptureOptions {
52
+ onAccessUnit: (au: AccessUnit) => void;
53
+ /** Called once the process exits, whether via `stop()` or on its own (crash/killed
54
+ * externally) — lets the session registry clean up without polling. `reason` is a short
55
+ * stderr tail, present only when ffmpeg died on its own (e.g. gdigrab access denied on a
56
+ * disconnected session) so the caller can surface *why* to the client instead of a bare
57
+ * disconnect. */
58
+ onExit?: (code: number | null, reason?: string) => void;
59
+ }
60
+
61
+ /** Start gdigrab capture. Rejects immediately if ffmpeg is not on PATH. */
62
+ export async function startCapture(opts: StartCaptureOptions): Promise<CaptureHandle> {
63
+ const caps = await getFfmpegCapabilities();
64
+ if (!caps.ffmpeg) throw new CaptureUnavailableError();
65
+
66
+ const proc = Bun.spawn(buildCaptureArgs(caps.ffmpeg, caps.encoder ?? "libx264"), {
67
+ stdout: "pipe",
68
+ stderr: "pipe",
69
+ stdin: "ignore",
70
+ });
71
+
72
+ let stopped = false;
73
+ const stop = () => {
74
+ if (stopped) return;
75
+ stopped = true;
76
+ if (!proc.killed) proc.kill();
77
+ };
78
+
79
+ // Drain stderr so ffmpeg never blocks on a full pipe; keep a short tail for diagnostics.
80
+ const stderrTail = new Response(proc.stderr).text().catch(() => "");
81
+ proc.exited.then(async (code) => {
82
+ // Read `stopped` BEFORE overwriting it: this is the only reliable "did WE ask ffmpeg to
83
+ // die" signal. `proc.killed` is NOT reliable for that — Bun sets it `true` even when
84
+ // ffmpeg exits on its own with a nonzero code (verified: gdigrab "access denied" on a
85
+ // disconnected session exits with `killed: true` despite `stop()` never having been
86
+ // called), which previously suppressed this warning — and any client-facing error —
87
+ // for exactly the crash this exists to report.
88
+ const diedOnItsOwn = !stopped;
89
+ stopped = true;
90
+ let reason: string | undefined;
91
+ if (diedOnItsOwn && code !== 0 && code !== null) {
92
+ reason = (await stderrTail).trim().split("\n").slice(-3).join(" | ");
93
+ console.warn(`[remote-desktop] ffmpeg exited ${code}: ${reason}`);
94
+ }
95
+ opts.onExit?.(code, reason);
96
+ });
97
+
98
+ const assembler = new AccessUnitAssembler();
99
+
100
+ // Manual pull loop — the only safe way to drain this stdout (see file header). Runs
101
+ // detached from the caller; it terminates itself once `proc.kill()` resolves the pending
102
+ // read with `done: true`, never via an external cancel.
103
+ const reader = proc.stdout.getReader();
104
+ (async () => {
105
+ for (;;) {
106
+ const { done, value } = await reader.read();
107
+ if (done) return;
108
+ if (!value) continue;
109
+ for (const au of assembler.push(value)) opts.onAccessUnit(au);
110
+ }
111
+ })().catch((e) => {
112
+ console.error(`[remote-desktop] capture pump failed: ${(e as Error).message}`);
113
+ });
114
+
115
+ return {
116
+ cachedSps: () => assembler.cachedSps(),
117
+ stop,
118
+ isStopped: () => stopped,
119
+ };
120
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Capture-tuned libx264 args — deliberately NOT `encoderArgs()` from `ffmpeg-capabilities.ts`.
3
+ * That table is tuned for "watch a file" (nvenc VBR, libx264 `-crf 23 -maxrate 8M -bufsize
4
+ * 16M`), which fights a real-time capture: large bufsize/VBR inflates latency spikes exactly
5
+ * where `-tune zerolatency` is trying to remove them.
6
+ *
7
+ * libx264 is forced (never the hardware picks from `ffmpeg-capabilities.ts`) so profile/level
8
+ * behavior is deterministic across machines — the actual codec string is still derived from
9
+ * the real SPS bytes at runtime (`avc1-codec-string.ts`), this just keeps that derivation
10
+ * predictable during development.
11
+ */
12
+
13
+ /** 30fps + a 1s keyframe interval (30 frames): smoother than 15fps and bounds how long a
14
+ * dropped/late-joining client waits to resync (see WS backpressure handling). */
15
+ export const CAPTURE_FRAMERATE = 30;
16
+ export const CAPTURE_GOP_FRAMES = 30;
17
+
18
+ /** ffmpeg output args for the H.264 encode step (goes after `-i desktop -vf ...`).
19
+ * Prefers hardware NVENC (verified to work even under an RDP session on this class of GPU) —
20
+ * hardware encode adds ~5–15ms vs 30–100ms for libx264, the dominant slice of the felt lag on
21
+ * a fast/LAN transport. Falls back to a low-latency libx264 profile when no hardware H.264
22
+ * encoder is available. The avc1 codec string is derived from the real SPS at runtime, so the
23
+ * encoder's actual profile/level is handled regardless of which branch runs. */
24
+ export function captureEncoderArgs(encoder: string = "libx264"): string[] {
25
+ const common = ["-b:v", "6M", "-maxrate", "6M", "-g", String(CAPTURE_GOP_FRAMES), "-bf", "0", "-pix_fmt", "yuv420p"];
26
+ switch (encoder) {
27
+ case "h264_nvenc":
28
+ // p2 + `-tune ll` (low latency), CBR, no frame reordering, minimal output delay/VBV.
29
+ return ["-c:v", "h264_nvenc", "-preset", "p2", "-tune", "ll", "-rc", "cbr",
30
+ "-zerolatency", "1", "-delay", "0", "-bufsize", "512k", ...common];
31
+ case "h264_qsv":
32
+ return ["-c:v", "h264_qsv", "-preset", "veryfast", "-low_power", "1", "-bufsize", "512k", ...common];
33
+ case "h264_amf":
34
+ return ["-c:v", "h264_amf", "-quality", "speed", "-rc", "cbr", "-bufsize", "512k", ...common];
35
+ default:
36
+ // libx264 low-latency. `-tune zerolatency` enables sliced-threads (multi-slice NALs);
37
+ // access-unit-assembler.ts assumes one slice/frame, so force it back with sliced-threads=0.
38
+ return ["-c:v", "libx264", "-preset", "veryfast", "-tune", "zerolatency",
39
+ "-profile:v", "main", "-level", "4.0", "-x264-params", "sliced-threads=0:slices=1",
40
+ "-bufsize", "1M", ...common];
41
+ }
42
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Feature flag gate. A live video+input channel to the host must never be reachable just
3
+ * because the code shipped — default OFF until phase-07 hardening (host-approval prompt,
4
+ * signed input path) lands. Every entry point (REST routes, WS upgrade) checks this
5
+ * independently rather than trusting a single call site.
6
+ */
7
+ export function isRemoteDesktopEnabled(): boolean {
8
+ const v = process.env.REMOTE_DESKTOP_ENABLED;
9
+ return v === "1" || v === "true";
10
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Mouse/keyboard injection via Bun FFI straight into `user32.dll` `SendInput` — no separate
3
+ * helper process (drops the Rust-binary build step + unsigned-exe/SmartScreen risk the phase
4
+ * plan flagged for a bundled injector; phase 02's SYSTEM-session helper is a different
5
+ * process/token anyway, so nothing here is meant to be reused as-is).
6
+ *
7
+ * Normal desktop only, current interactive session, no SYSTEM/UAC/lock-screen support.
8
+ *
9
+ * `bun:ffi` is only ever `import()`-ed and `dlopen`-ed lazily from inside `loadUser32()`, so
10
+ * merely importing this module (routing, types) on Linux/macOS test runners never touches
11
+ * `user32.dll`.
12
+ */
13
+ import { codeToVk, MODIFIER_VK_CODES } from "./remote-desktop-vk-map.ts";
14
+
15
+ export class RemoteInputUnavailableError extends Error {
16
+ constructor(msg = "Remote input injection is only available on Windows") {
17
+ super(msg);
18
+ this.name = "RemoteInputUnavailableError";
19
+ }
20
+ }
21
+
22
+ const INPUT_MOUSE = 0;
23
+ const INPUT_KEYBOARD = 1;
24
+ const MOUSEEVENTF_MOVE = 0x0001;
25
+ const MOUSEEVENTF_LEFTDOWN = 0x0002;
26
+ const MOUSEEVENTF_LEFTUP = 0x0004;
27
+ const MOUSEEVENTF_RIGHTDOWN = 0x0008;
28
+ const MOUSEEVENTF_RIGHTUP = 0x0010;
29
+ const MOUSEEVENTF_ABSOLUTE = 0x8000;
30
+ const MOUSEEVENTF_VIRTUALDESK = 0x4000;
31
+ const MOUSEEVENTF_WHEEL = 0x0800;
32
+ /** One notch of a real mouse wheel — `mouseData` for `MOUSEEVENTF_WHEEL` is a signed multiple
33
+ * of this constant, not a raw pixel delta. */
34
+ const WHEEL_DELTA = 120;
35
+ const KEYEVENTF_KEYUP = 0x0002;
36
+ /** `((DPI_AWARENESS_CONTEXT)-4)` — PER_MONITOR_AWARE_V2. Not a real pointer; the x64 ABI
37
+ * passes it in the same register/slot as a pointer arg would, bit pattern only. */
38
+ const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4n;
39
+ /** Each `INPUT` struct is 40 bytes on x64 (8-byte `type` slot + 32-byte union). */
40
+ const INPUT_STRUCT_SIZE = 40;
41
+ /** Desktop access rights for input injection. `SendInput` silently no-ops when the calling
42
+ * thread is not attached to the *input* desktop; attaching needs the READ/WRITE/SWITCH rights
43
+ * plus JOURNALPLAYBACK so the desktop accepts injected input. These MUST be the real DESKTOP_*
44
+ * bit values — an invalid mask (e.g. an out-of-range bit) makes `OpenInputDesktop` return NULL
45
+ * and the attach silently fails, leaving SendInput pointed at the wrong desktop. */
46
+ const DESKTOP_READOBJECTS = 0x0001;
47
+ const DESKTOP_JOURNALPLAYBACK = 0x0020;
48
+ const DESKTOP_WRITEOBJECTS = 0x0080;
49
+ const DESKTOP_SWITCHDESKTOP = 0x0100;
50
+ const INPUT_DESKTOP_ACCESS =
51
+ DESKTOP_READOBJECTS | DESKTOP_JOURNALPLAYBACK | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP; // 0x1A1
52
+
53
+ /** WINSTA_ALL_ACCESS — enough to open the interactive window station and make it this process's
54
+ * station so its input desktop becomes reachable. */
55
+ const WINSTA_ALL_ACCESS = 0x37f;
56
+
57
+ type FfiModule = typeof import("bun:ffi");
58
+ type User32Symbols = {
59
+ SendInput: (count: number, ptr: number | bigint, size: number) => number;
60
+ SetProcessDpiAwarenessContext: (ctx: bigint) => boolean;
61
+ OpenInputDesktop: (flags: number, inherit: boolean, access: number) => number | bigint;
62
+ SetThreadDesktop: (hdesk: number | bigint) => boolean;
63
+ CloseDesktop: (hdesk: number | bigint) => boolean;
64
+ OpenWindowStationW: (name: number | bigint, inherit: boolean, access: number) => number | bigint;
65
+ SetProcessWindowStation: (hwinsta: number | bigint) => boolean;
66
+ };
67
+
68
+ let ffiModule: FfiModule | null = null;
69
+ let user32: User32Symbols | null = null;
70
+ let dpiAwarenessAttempted = false;
71
+
72
+ async function loadUser32(): Promise<{ ffi: FfiModule; lib: User32Symbols }> {
73
+ if (process.platform !== "win32") throw new RemoteInputUnavailableError();
74
+ if (!ffiModule) ffiModule = await import("bun:ffi");
75
+ const { dlopen, FFIType } = ffiModule;
76
+ if (!user32) {
77
+ user32 = dlopen("user32.dll", {
78
+ SendInput: { args: [FFIType.u32, FFIType.ptr, FFIType.i32], returns: FFIType.u32 },
79
+ SetProcessDpiAwarenessContext: { args: [FFIType.i64], returns: FFIType.bool },
80
+ OpenInputDesktop: { args: [FFIType.u32, FFIType.bool, FFIType.u32], returns: FFIType.ptr },
81
+ SetThreadDesktop: { args: [FFIType.ptr], returns: FFIType.bool },
82
+ CloseDesktop: { args: [FFIType.ptr], returns: FFIType.bool },
83
+ OpenWindowStationW: { args: [FFIType.ptr, FFIType.bool, FFIType.u32], returns: FFIType.ptr },
84
+ SetProcessWindowStation: { args: [FFIType.ptr], returns: FFIType.bool },
85
+ }).symbols as unknown as User32Symbols;
86
+ }
87
+ if (!dpiAwarenessAttempted) {
88
+ dpiAwarenessAttempted = true;
89
+ // A second call to this in the same process returns E_ACCESSDENIED — swallow it, it just
90
+ // means something else (or a previous session) already set it.
91
+ try { user32.SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); } catch { /* already set */ }
92
+ }
93
+ return { ffi: ffiModule, lib: user32 };
94
+ }
95
+
96
+ function writeMouseInput(dv: DataView, offset: number, xAbs: number, yAbs: number, flags: number, mouseData = 0): void {
97
+ dv.setUint32(offset, INPUT_MOUSE, true);
98
+ dv.setInt32(offset + 8, xAbs, true);
99
+ dv.setInt32(offset + 12, yAbs, true);
100
+ dv.setInt32(offset + 16, mouseData, true); // wheel delta for MOUSEEVENTF_WHEEL, unused otherwise
101
+ dv.setUint32(offset + 20, flags >>> 0, true);
102
+ dv.setUint32(offset + 24, 0, true); // time: 0 lets the system supply it
103
+ }
104
+
105
+ function writeKeyInput(dv: DataView, offset: number, vk: number, keyUp: boolean): void {
106
+ dv.setUint32(offset, INPUT_KEYBOARD, true);
107
+ dv.setUint16(offset + 8, vk, true);
108
+ dv.setUint16(offset + 10, 0, true); // wScan — unused, we inject by VK not scan code
109
+ dv.setUint32(offset + 12, keyUp ? KEYEVENTF_KEYUP : 0, true);
110
+ dv.setUint32(offset + 16, 0, true); // time
111
+ }
112
+
113
+ /** Handle of the desktop this thread is currently attached to, so we can close the previous one
114
+ * when the active input desktop changes (lock/unlock/UAC switches it). One handle in flight. */
115
+ let attachedDesktop: number | bigint | null = null;
116
+
117
+ /** Attach the calling thread to the *current* input desktop before injecting. Re-run every send
118
+ * because the input desktop changes on lock/unlock; `CreateProcessAsUser`/process start only
119
+ * fixes the initial desktop, not later switches. Best-effort: on failure we still try SendInput
120
+ * (it just no-ops), and log once rather than per-event spam. */
121
+ /** Attach the PROCESS to the interactive window station ("winsta0") once, so its input desktop
122
+ * is reachable. Without this, a PPM process launched from a non-interactive station (a service,
123
+ * or some spawners) can't OpenInputDesktop at all — SendInput then goes nowhere. No-op when the
124
+ * process is already on winsta0 (the normal case for PPM started inside the user's session). */
125
+ let stationEnsured = false;
126
+ function ensureInteractiveStation(ffi: FfiModule, lib: User32Symbols): void {
127
+ if (stationEnsured) return;
128
+ stationEnsured = true;
129
+ const name = "winsta0";
130
+ const wide = new Uint16Array(name.length + 1);
131
+ for (let i = 0; i < name.length; i++) wide[i] = name.charCodeAt(i);
132
+ const hwinsta = lib.OpenWindowStationW(ffi.ptr(wide), false, WINSTA_ALL_ACCESS);
133
+ if (!hwinsta) return; // already on it, or no access — OpenInputDesktop below will tell us
134
+ lib.SetProcessWindowStation(hwinsta);
135
+ }
136
+
137
+ function attachToInputDesktop(ffi: FfiModule, lib: User32Symbols): void {
138
+ ensureInteractiveStation(ffi, lib);
139
+ const hdesk = lib.OpenInputDesktop(0, false, INPUT_DESKTOP_ACCESS);
140
+ if (!hdesk) return; // couldn't open the input desktop (e.g. secure/lock desktop); leave thread as-is
141
+ const ok = lib.SetThreadDesktop(hdesk);
142
+ if (ok) {
143
+ if (attachedDesktop && attachedDesktop !== hdesk) {
144
+ try { lib.CloseDesktop(attachedDesktop); } catch { /* ignore */ }
145
+ }
146
+ attachedDesktop = hdesk;
147
+ } else {
148
+ // SetThreadDesktop fails if this thread owns windows/hooks — shouldn't for the server thread.
149
+ try { lib.CloseDesktop(hdesk); } catch { /* ignore */ }
150
+ }
151
+ }
152
+
153
+ async function sendRaw(buf: Uint8Array, count: number): Promise<void> {
154
+ const { ffi, lib } = await loadUser32();
155
+ attachToInputDesktop(ffi, lib);
156
+ const sent = lib.SendInput(count, ffi.ptr(buf), INPUT_STRUCT_SIZE);
157
+ if (sent !== count) console.warn(`[remote-desktop] SendInput accepted ${sent}/${count} events`);
158
+ }
159
+
160
+ /** Move the cursor and, if this is a click, press/release the button — one absolute move
161
+ * per call so the button acts at the position the client actually clicked. `xFrac`/`yFrac`
162
+ * are 0..1 fractions of the capture (client already dropped devicePixelRatio; only the
163
+ * canvas-relative fraction is meaningful for host coordinates). */
164
+ export async function injectPointer(
165
+ xFrac: number,
166
+ yFrac: number,
167
+ button: "left" | "right" | null,
168
+ down: boolean | null,
169
+ ): Promise<void> {
170
+ const xAbs = Math.round(Math.min(Math.max(xFrac, 0), 1) * 65535);
171
+ const yAbs = Math.round(Math.min(Math.max(yFrac, 0), 1) * 65535);
172
+ let flags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
173
+ if (button === "left") flags |= down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
174
+ else if (button === "right") flags |= down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
175
+
176
+ const buf = new Uint8Array(INPUT_STRUCT_SIZE);
177
+ writeMouseInput(new DataView(buf.buffer), 0, xAbs, yAbs, flags);
178
+ await sendRaw(buf, 1);
179
+ }
180
+
181
+ /** Scroll the wheel. `deltaY` is in `WHEEL_DELTA` (120) units — positive rotates the wheel
182
+ * forward (scrolls up/away from the user), negative rotates backward (scrolls down). The
183
+ * client accumulates raw two-finger-drag pixels into this unit before sending, so this layer
184
+ * stays a dumb relay: round + clamp to a safe i16 range so a runaway client value can't wrap
185
+ * into a huge or negative-looking mouseData field. */
186
+ export async function injectWheel(deltaY: number): Promise<void> {
187
+ const clamped = Math.max(-32768, Math.min(32767, Math.round(deltaY)));
188
+ if (clamped === 0) return;
189
+ const buf = new Uint8Array(INPUT_STRUCT_SIZE);
190
+ writeMouseInput(new DataView(buf.buffer), 0, 0, 0, MOUSEEVENTF_WHEEL, clamped);
191
+ await sendRaw(buf, 1);
192
+ }
193
+
194
+ /** Send a keydown/keyup by physical key code. Returns false for an unmapped code (caller
195
+ * should just drop it rather than injecting a wrong key). */
196
+ export async function injectKey(code: string, down: boolean): Promise<boolean> {
197
+ const vk = codeToVk(code);
198
+ if (vk === null) return false;
199
+ const buf = new Uint8Array(INPUT_STRUCT_SIZE);
200
+ writeKeyInput(new DataView(buf.buffer), 0, vk, !down);
201
+ await sendRaw(buf, 1);
202
+ return true;
203
+ }
204
+
205
+ /** Force keyup for every modifier VK, regardless of tracked state — the backstop for a lost
206
+ * keyup (blur, WS drop) leaving Shift/Ctrl/Alt/Win logically held on the host. */
207
+ export async function releaseAllModifiers(): Promise<void> {
208
+ if (process.platform !== "win32") return;
209
+ const buf = new Uint8Array(INPUT_STRUCT_SIZE * MODIFIER_VK_CODES.length);
210
+ const dv = new DataView(buf.buffer);
211
+ MODIFIER_VK_CODES.forEach((vk, i) => writeKeyInput(dv, i * INPUT_STRUCT_SIZE, vk, true));
212
+ try { await sendRaw(buf, MODIFIER_VK_CODES.length); } catch { /* best-effort */ }
213
+ }
214
+
215
+ export function isInputAvailable(): boolean {
216
+ return process.platform === "win32";
217
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Single-use, short-TTL connection nonce — mirrors `download-token.service.ts`'s
3
+ * consume-once pattern.
4
+ *
5
+ * The PPM static bearer token already travels as `?token=` on every WS upgrade
6
+ * (`isWsUpgradeAuthorized`, `src/server/index.ts`) and proves nothing beyond "holds the one
7
+ * reusable app token" — it is not a real per-session grant. `POST /api/remote-desktop/session`
8
+ * mints one of these after re-checking `auth.enabled`; the client then presents it via the WS
9
+ * subprotocol header (never the query string) and it is consumed exactly once, at upgrade
10
+ * time, in `src/server/index.ts`.
11
+ */
12
+ import { randomUUIDv7 } from "bun";
13
+
14
+ const TTL_MS = 30_000;
15
+ const nonces = new Map<string, number>();
16
+
17
+ /** Mint a nonce; caller must have already verified auth is enabled + a valid request. */
18
+ export function mintRemoteDesktopNonce(): string {
19
+ const token = randomUUIDv7();
20
+ nonces.set(token, Date.now());
21
+ cleanup();
22
+ return token;
23
+ }
24
+
25
+ /** Validate + consume. Returns false for missing/expired/already-used nonces. */
26
+ export function consumeRemoteDesktopNonce(token: string): boolean {
27
+ const mintedAt = nonces.get(token);
28
+ if (mintedAt === undefined) return false;
29
+ nonces.delete(token);
30
+ return Date.now() - mintedAt <= TTL_MS;
31
+ }
32
+
33
+ function cleanup(): void {
34
+ const now = Date.now();
35
+ for (const [token, mintedAt] of nonces) {
36
+ if (now - mintedAt > TTL_MS) nonces.delete(token);
37
+ }
38
+ }