@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,189 @@
1
+ /**
2
+ * Owns one remote-desktop connection end to end: capture lifecycle, WS backpressure,
3
+ * heartbeat/TTL teardown, and forwarding input to the injector. "One active session per
4
+ * host" — a new connection evicts (and awaits) any previous one before starting its own
5
+ * capture so two `gdigrab` processes never contend for the same display.
6
+ */
7
+ import { startCapture, type CaptureHandle } from "./remote-desktop-capture.ts";
8
+ import { avc1CodecString } from "./avc1-codec-string.ts";
9
+ import type { AccessUnit } from "./access-unit-assembler.ts";
10
+ import { injectPointer, injectKey, injectWheel, releaseAllModifiers, isInputAvailable } from "./remote-desktop-input.ts";
11
+
12
+ /** Minimal socket surface this module needs — matches Bun's `ServerWebSocket` shape closely
13
+ * enough to be faked in a unit test without a real connection. */
14
+ export interface RemoteDesktopSocket {
15
+ send(data: string | Uint8Array): number;
16
+ getBufferedAmount?(): number;
17
+ close(code?: number, reason?: string): void;
18
+ }
19
+
20
+ /** App-level heartbeat, independent of Bun's 960s socket `idleTimeout` — a dead tunnel or a
21
+ * sleeping laptop must not leave ffmpeg + input access live for minutes unattended. */
22
+ const HEARTBEAT_INTERVAL_MS = 5_000;
23
+ // 30s (6 missed 5s pings): tolerant of brief network jitter and of a client whose ping timer
24
+ // is throttled while its tab is briefly backgrounded, without leaving an unattended session
25
+ // (ffmpeg + input access) live for long. The client also re-pings the moment its tab is visible.
26
+ const HEARTBEAT_TIMEOUT_MS = 30_000;
27
+ /** Above this many buffered bytes, drop delta AUs until the next keyframe rather than
28
+ * queueing forever — a slow WAN/tunnel link must degrade to "waits ~2s for a keyframe",
29
+ * never to unbounded memory growth or an ever-growing latency queue. */
30
+ const BACKPRESSURE_THRESHOLD_BYTES = 512 * 1024;
31
+
32
+ export class RemoteDesktopSession {
33
+ private capture: CaptureHandle | null = null;
34
+ private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
35
+ private lastPingAt = Date.now();
36
+ private droppingUntilKey = false;
37
+ private sentConfig = false;
38
+ private closed = false;
39
+ private readonly heldKeyCodes = new Set<string>();
40
+ private exitResolve: (() => void) | null = null;
41
+ /** Resolves once the underlying ffmpeg process has actually exited (not merely asked to). */
42
+ readonly exited: Promise<void>;
43
+
44
+ constructor(private readonly ws: RemoteDesktopSocket) {
45
+ this.exited = new Promise((resolve) => { this.exitResolve = resolve; });
46
+ }
47
+
48
+ async start(): Promise<void> {
49
+ this.capture = await startCapture({
50
+ onAccessUnit: (au) => this.handleAccessUnit(au),
51
+ // `reason` is only set when ffmpeg died on its own (crash, access denied, etc) — tell
52
+ // the client *why* before closing instead of leaving it to guess from a bare
53
+ // disconnect (this is exactly what happens today for e.g. gdigrab failing against a
54
+ // disconnected Windows session: "Failed to capture image (error 5)").
55
+ onExit: (_code, reason) => {
56
+ this.exitResolve?.();
57
+ if (!this.closed) {
58
+ if (reason) {
59
+ try { this.ws.send(JSON.stringify({ type: "error", message: `Capture failed: ${reason}` })); } catch { /* closing anyway */ }
60
+ }
61
+ this.close();
62
+ }
63
+ },
64
+ });
65
+ this.heartbeatTimer = setInterval(() => this.checkHeartbeat(), HEARTBEAT_INTERVAL_MS);
66
+ }
67
+
68
+ noteClientAlive(): void {
69
+ this.lastPingAt = Date.now();
70
+ }
71
+
72
+ async handleClientMessage(raw: string): Promise<void> {
73
+ let msg: Record<string, unknown>;
74
+ try { msg = JSON.parse(raw); } catch { return; }
75
+ if (msg.type === "ping") { this.noteClientAlive(); return; }
76
+ if (msg.type === "stop") { this.close(); return; }
77
+ if (!isInputAvailable()) return; // part 1b unsupported on this OS — silently ignore
78
+
79
+ if (msg.type === "pointer") {
80
+ const { xFrac, yFrac, button, down } = msg as { xFrac?: unknown; yFrac?: unknown; button?: unknown; down?: unknown };
81
+ if (typeof xFrac === "number" && typeof yFrac === "number") {
82
+ const btn = button === "left" || button === "right" ? button : null;
83
+ await injectPointer(xFrac, yFrac, btn, typeof down === "boolean" ? down : null);
84
+ }
85
+ return;
86
+ }
87
+ if (msg.type === "wheel") {
88
+ const { dy } = msg as { dy?: unknown };
89
+ if (typeof dy === "number" && Number.isFinite(dy)) await injectWheel(dy);
90
+ return;
91
+ }
92
+ if (msg.type === "key") {
93
+ const { code, down } = msg as { code?: unknown; down?: unknown };
94
+ if (typeof code === "string" && typeof down === "boolean") {
95
+ if (down) this.heldKeyCodes.add(code); else this.heldKeyCodes.delete(code);
96
+ await injectKey(code, down);
97
+ }
98
+ return;
99
+ }
100
+ if (msg.type === "releaseAll") {
101
+ await this.releaseHeldKeys();
102
+ }
103
+ }
104
+
105
+ /** Force a keyup for every key this session has tracked as held, then run the modifier
106
+ * backstop too — `heldKeyCodes` covers whatever the client actually pressed (letters,
107
+ * digits, etc, not just modifiers); `releaseAllModifiers()` is a second backstop for a
108
+ * modifier that raced a disconnect before its keydown was ever tracked. Previously this
109
+ * only ran the modifier backstop and *cleared* `heldKeyCodes` without releasing them,
110
+ * so a held non-modifier key (e.g. a letter) stayed logically down on the host. */
111
+ private async releaseHeldKeys(): Promise<void> {
112
+ const codes = [...this.heldKeyCodes];
113
+ this.heldKeyCodes.clear();
114
+ await Promise.all(codes.map((code) => injectKey(code, false).catch(() => {})));
115
+ await releaseAllModifiers();
116
+ }
117
+
118
+ close(): void {
119
+ if (this.closed) return;
120
+ this.closed = true;
121
+ if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
122
+ this.capture?.stop();
123
+ activeSessions.delete(this);
124
+ if (this.heldKeyCodes.size > 0) {
125
+ this.releaseHeldKeys().catch(() => {});
126
+ }
127
+ try { this.ws.close(); } catch { /* already closing */ }
128
+ }
129
+
130
+ private checkHeartbeat(): void {
131
+ if (Date.now() - this.lastPingAt > HEARTBEAT_TIMEOUT_MS) {
132
+ console.warn("[remote-desktop] heartbeat timeout — tearing down session");
133
+ this.close();
134
+ }
135
+ }
136
+
137
+ private handleAccessUnit(au: AccessUnit): void {
138
+ if (this.closed) return;
139
+ if (!this.sentConfig) {
140
+ const sps = this.capture?.cachedSps();
141
+ if (!sps) return; // nothing decodable yet — wait for the encoder's first SPS
142
+ const codec = avc1CodecString(sps);
143
+ if (!codec) return;
144
+ this.ws.send(JSON.stringify({ type: "config", codec }));
145
+ this.sentConfig = true;
146
+ }
147
+
148
+ const buffered = this.ws.getBufferedAmount?.() ?? 0;
149
+ if (buffered > BACKPRESSURE_THRESHOLD_BYTES && !au.isKey) {
150
+ this.droppingUntilKey = true;
151
+ return;
152
+ }
153
+ if (this.droppingUntilKey && !au.isKey) return;
154
+ if (au.isKey) this.droppingUntilKey = false;
155
+
156
+ const framed = new Uint8Array(1 + au.bytes.length);
157
+ framed[0] = au.isKey ? 1 : 0;
158
+ framed.set(au.bytes, 1);
159
+ this.ws.send(framed);
160
+ }
161
+ }
162
+
163
+ const activeSessions = new Set<RemoteDesktopSession>();
164
+
165
+ /** Evict any previous session (awaiting its ffmpeg exit, capped so a wedged process can't
166
+ * hang a reconnect) before starting the new one. */
167
+ export async function createRemoteDesktopSession(ws: RemoteDesktopSocket): Promise<RemoteDesktopSession> {
168
+ for (const existing of [...activeSessions]) {
169
+ existing.close();
170
+ await Promise.race([existing.exited, Bun.sleep(2000)]);
171
+ }
172
+ const session = new RemoteDesktopSession(ws);
173
+ await session.start();
174
+ activeSessions.add(session);
175
+ return session;
176
+ }
177
+
178
+ /** Process-exit sweep so ffmpeg never outlives a server crash/exit — a clean WS close or an
179
+ * idle heartbeat timeout is handled by the session itself; this covers everything else
180
+ * (SIGINT/SIGTERM, uncaught crash unwind). */
181
+ let sweepRegistered = false;
182
+ export function registerRemoteDesktopExitSweep(): void {
183
+ if (sweepRegistered) return;
184
+ sweepRegistered = true;
185
+ const sweep = () => { for (const s of [...activeSessions]) s.close(); };
186
+ process.on("exit", sweep);
187
+ process.on("SIGINT", sweep);
188
+ process.on("SIGTERM", sweep);
189
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `KeyboardEvent.code` (physical key, layout-independent) → Win32 virtual-key code.
3
+ * Only `code` is accepted from the client — `key` depends on the browser's active layout
4
+ * and would map wrong the moment host and client disagree on layout.
5
+ */
6
+ const CODE_TO_VK: Record<string, number> = {
7
+ Backspace: 0x08, Tab: 0x09, Enter: 0x0d, ShiftLeft: 0xa0, ShiftRight: 0xa1,
8
+ ControlLeft: 0xa2, ControlRight: 0xa3, AltLeft: 0xa4, AltRight: 0xa5,
9
+ Pause: 0x13, CapsLock: 0x14, Escape: 0x1b, Space: 0x20,
10
+ PageUp: 0x21, PageDown: 0x22, End: 0x23, Home: 0x24,
11
+ ArrowLeft: 0x25, ArrowUp: 0x26, ArrowRight: 0x27, ArrowDown: 0x28,
12
+ Insert: 0x2d, Delete: 0x2e,
13
+ Digit0: 0x30, Digit1: 0x31, Digit2: 0x32, Digit3: 0x33, Digit4: 0x34,
14
+ Digit5: 0x35, Digit6: 0x36, Digit7: 0x37, Digit8: 0x38, Digit9: 0x39,
15
+ KeyA: 0x41, KeyB: 0x42, KeyC: 0x43, KeyD: 0x44, KeyE: 0x45, KeyF: 0x46,
16
+ KeyG: 0x47, KeyH: 0x48, KeyI: 0x49, KeyJ: 0x4a, KeyK: 0x4b, KeyL: 0x4c,
17
+ KeyM: 0x4d, KeyN: 0x4e, KeyO: 0x4f, KeyP: 0x50, KeyQ: 0x51, KeyR: 0x52,
18
+ KeyS: 0x53, KeyT: 0x54, KeyU: 0x55, KeyV: 0x56, KeyW: 0x57, KeyX: 0x58,
19
+ KeyY: 0x59, KeyZ: 0x5a,
20
+ MetaLeft: 0x5b, MetaRight: 0x5c, ContextMenu: 0x5d,
21
+ Numpad0: 0x60, Numpad1: 0x61, Numpad2: 0x62, Numpad3: 0x63, Numpad4: 0x64,
22
+ Numpad5: 0x65, Numpad6: 0x66, Numpad7: 0x67, Numpad8: 0x68, Numpad9: 0x69,
23
+ NumpadMultiply: 0x6a, NumpadAdd: 0x6b, NumpadSubtract: 0x6d,
24
+ NumpadDecimal: 0x6e, NumpadDivide: 0x6f,
25
+ F1: 0x70, F2: 0x71, F3: 0x72, F4: 0x73, F5: 0x74, F6: 0x75,
26
+ F7: 0x76, F8: 0x77, F9: 0x78, F10: 0x79, F11: 0x7a, F12: 0x7b,
27
+ NumLock: 0x90, ScrollLock: 0x91,
28
+ Semicolon: 0xba, Equal: 0xbb, Comma: 0xbc, Minus: 0xbd, Period: 0xbe,
29
+ Slash: 0xbf, Backquote: 0xc0, BracketLeft: 0xdb, Backslash: 0xdc,
30
+ BracketRight: 0xdd, Quote: 0xde,
31
+ };
32
+
33
+ /** All modifier VK codes — used to force-release stuck modifiers on teardown (a lost keyup
34
+ * otherwise leaves e.g. Shift logically held on the host after the WS drops). */
35
+ export const MODIFIER_VK_CODES = [0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0x5b, 0x5c] as const;
36
+
37
+ /** Resolve a `KeyboardEvent.code` to its VK; null for anything unmapped (IME composition,
38
+ * unusual layouts) rather than guessing and injecting the wrong key. */
39
+ export function codeToVk(code: string): number | null {
40
+ return CODE_TO_VK[code] ?? null;
41
+ }
@@ -5,11 +5,13 @@
5
5
  * session JSONL (no isSidechain lines, no parent_tool_use_id). Instead each
6
6
  * agent gets its own transcript at:
7
7
  * ~/.claude/projects/<slug>/<sessionId>/subagents/agent-<id>.jsonl
8
- * with a sibling agent-<id>.meta.json carrying the spawning Agent tool_use id.
8
+ * with a sibling agent-<id>.meta.json carrying the spawning Agent tool_use id
9
+ * (session-spawned agents) or the spawning agent's id (nested agents).
9
10
  *
10
11
  * Without merging these, a reloaded session shows bare Agent cards and the
11
- * live-streamed children are lost. This module reads the meta files, parses
12
- * each agent transcript, and attaches its events as the card's children.
12
+ * live-streamed children are lost. This module groups the transcripts under
13
+ * their session-level card, parses each one, and attaches the events every
14
+ * nesting level flattened into one time-ordered list — as the card's children.
13
15
  */
14
16
 
15
17
  import { existsSync, readdirSync, readFileSync } from "node:fs";
@@ -17,18 +19,20 @@ import { homedir } from "node:os";
17
19
  import { join } from "node:path";
18
20
  import type { ChatEvent } from "../types/chat.ts";
19
21
  import { parseSessionMessage } from "./jsonl-transcript-parser.ts";
22
+ import { groupSubagentsByCard } from "./team-member-activity/subagent-transcript-index.ts";
20
23
 
21
24
  /**
22
25
  * Locate the per-session directory (…/projects/<slug>/<sessionId>) that holds
23
- * subagents/. The SDK encodes the cwd by replacing separators + drive colon
24
- * with "-"; when that drifts (drive-letter case), fall back to scanning
25
- * project dirs for the session's main JSONL.
26
+ * subagents/. The SDK encodes the cwd by replacing separators, drive colon
27
+ * and dots with "-" (`…\ppm\.claude\worktrees\x` `…-ppm--claude-worktrees-x`);
28
+ * when that drifts (drive-letter case), fall back to scanning project dirs for
29
+ * the session's main JSONL.
26
30
  */
27
31
  export function resolveSessionDir(sessionId: string, projectPath: string | null | undefined): string | null {
28
32
  const home = homedir();
29
33
  const projectsRoot = join(home, ".claude", "projects");
30
34
  if (projectPath) {
31
- const encoded = projectPath.replace(/[/\\:]/g, "-");
35
+ const encoded = projectPath.replace(/[/\\:.]/g, "-");
32
36
  const dir = join(projectsRoot, encoded);
33
37
  if (existsSync(join(dir, `${sessionId}.jsonl`))) return join(dir, sessionId);
34
38
  }
@@ -53,92 +57,105 @@ interface MessageLike {
53
57
  const MAX_CHILDREN_PER_AGENT = 2000;
54
58
  const MAX_CHILD_OUTPUT_CHARS = 50_000;
55
59
 
56
- /** Map of Agent-card toolUseId agent transcript path, from subagents/*.meta.json */
57
- function indexAgentTranscripts(subagentsDir: string): Map<string, string> {
58
- const map = new Map<string, string>();
59
- let entries: string[];
60
- try {
61
- entries = readdirSync(subagentsDir);
62
- } catch {
63
- return map;
64
- }
65
- for (const name of entries) {
66
- if (!name.endsWith(".meta.json")) continue;
67
- try {
68
- const meta = JSON.parse(readFileSync(join(subagentsDir, name), "utf8"));
69
- const toolUseId = meta?.toolUseId as string | undefined;
70
- if (!toolUseId) continue;
71
- const transcript = join(subagentsDir, name.replace(/\.meta\.json$/, ".jsonl"));
72
- if (existsSync(transcript)) map.set(toolUseId, transcript);
73
- } catch {
74
- /* unreadable meta — skip this agent */
75
- }
76
- }
77
- return map;
60
+ /** One child event plus the transcript record time it came from (epoch ms). */
61
+ export interface TimedChatEvent {
62
+ ts: number;
63
+ ev: ChatEvent;
78
64
  }
79
65
 
80
- /** Parse one agent transcript into a flat children event list (stream order). */
81
- export function parseAgentTranscript(filePath: string): ChatEvent[] {
82
- const children: ChatEvent[] = [];
66
+ /**
67
+ * Incremental parser for one agent transcript. Feed it JSONL lines in file
68
+ * order (whole file at once, or a live tail's new lines) and it returns the
69
+ * child events those lines carry, keeping the skip/limit state across calls.
70
+ */
71
+ export function createAgentTranscriptLineParser() {
72
+ let isFirstUser = true;
73
+ let emitted = 0;
74
+ let lastTs = 0;
75
+ return {
76
+ feed(line: string): TimedChatEvent[] {
77
+ const out: TimedChatEvent[] = [];
78
+ const trimmed = line.trim();
79
+ if (!trimmed || emitted >= MAX_CHILDREN_PER_AGENT) return out;
80
+ let entry: any;
81
+ try {
82
+ entry = JSON.parse(trimmed);
83
+ } catch {
84
+ return out;
85
+ }
86
+ if (entry.type !== "user" && entry.type !== "assistant") return out;
87
+ if (!entry.message) return out;
88
+ // The first user record is the agent's spawn prompt — the live stream
89
+ // never showed it as a child, so skip it to match the streaming view.
90
+ if (entry.type === "user" && isFirstUser) {
91
+ isFirstUser = false;
92
+ const content = entry.message?.content;
93
+ const hasToolResult = Array.isArray(content) && content.some((b: any) => b?.type === "tool_result");
94
+ if (!hasToolResult) return out;
95
+ }
96
+ // Records without a timestamp inherit the previous one so file order holds.
97
+ const parsedTs = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : NaN;
98
+ if (!Number.isNaN(parsedTs)) lastTs = parsedTs;
99
+ const parsed = parseSessionMessage(entry);
100
+ for (const ev of parsed.events ?? []) {
101
+ if (emitted >= MAX_CHILDREN_PER_AGENT) break;
102
+ // Keep single events from ballooning the history payload (agent files
103
+ // can carry multi-MB tool outputs the live stream also showed in full,
104
+ // but 24 agents × full outputs breaks mobile reloads).
105
+ if (ev.type === "tool_result" && typeof ev.output === "string" && ev.output.length > MAX_CHILD_OUTPUT_CHARS) {
106
+ ev.output = ev.output.slice(0, MAX_CHILD_OUTPUT_CHARS) + "\n… [truncated]";
107
+ }
108
+ out.push({ ts: lastTs, ev });
109
+ emitted++;
110
+ }
111
+ return out;
112
+ },
113
+ };
114
+ }
115
+
116
+ /** Parse one whole agent transcript into timed child events (file order). */
117
+ export function parseAgentTranscriptTimed(filePath: string): TimedChatEvent[] {
83
118
  let text: string;
84
119
  try {
85
120
  text = readFileSync(filePath, "utf8");
86
121
  } catch {
87
- return children;
88
- }
89
- let isFirstUser = true;
90
- for (const line of text.split("\n")) {
91
- const trimmed = line.trim();
92
- if (!trimmed) continue;
93
- let entry: any;
94
- try {
95
- entry = JSON.parse(trimmed);
96
- } catch {
97
- continue;
98
- }
99
- if (entry.type !== "user" && entry.type !== "assistant") continue;
100
- if (!entry.message) continue;
101
- if (children.length >= MAX_CHILDREN_PER_AGENT) break;
102
- // The first user record is the agent's spawn prompt — the live stream
103
- // never showed it as a child, so skip it to match the streaming view.
104
- if (entry.type === "user" && isFirstUser) {
105
- isFirstUser = false;
106
- const content = entry.message?.content;
107
- const hasToolResult = Array.isArray(content) && content.some((b: any) => b?.type === "tool_result");
108
- if (!hasToolResult) continue;
109
- }
110
- const parsed = parseSessionMessage(entry);
111
- for (const ev of parsed.events ?? []) {
112
- // Keep single events from ballooning the history payload (agent files
113
- // can carry multi-MB tool outputs the live stream also showed in full,
114
- // but 24 agents × full outputs breaks mobile reloads).
115
- if (ev.type === "tool_result" && typeof ev.output === "string" && ev.output.length > MAX_CHILD_OUTPUT_CHARS) {
116
- ev.output = ev.output.slice(0, MAX_CHILD_OUTPUT_CHARS) + "\n… [truncated]";
117
- }
118
- children.push(ev);
119
- }
122
+ return [];
120
123
  }
121
- return children;
124
+ const parser = createAgentTranscriptLineParser();
125
+ const out: TimedChatEvent[] = [];
126
+ for (const line of text.split("\n")) out.push(...parser.feed(line));
127
+ return out;
128
+ }
129
+
130
+ /** Parse one agent transcript into a flat children event list (stream order). */
131
+ export function parseAgentTranscript(filePath: string): ChatEvent[] {
132
+ return parseAgentTranscriptTimed(filePath).map((t) => t.ev);
122
133
  }
123
134
 
124
135
  /**
125
136
  * Attach subagent transcript events as children of their Agent/Task cards.
126
137
  * `sessionDir` is the per-session directory next to the main JSONL
127
- * (…/projects/<slug>/<sessionId>). Existing children are replaced — the disk
128
- * transcript is the complete record, while live-collected children may be a
129
- * partial overlap. Mutates messages in-place.
138
+ * (…/projects/<slug>/<sessionId>). A card's group — the agent it spawned plus
139
+ * any agents that one spawned in turn is merged into one list ordered by
140
+ * record time, so a nested worker's steps read where they happened rather
141
+ * than after the parent agent's conclusion. Existing children are replaced —
142
+ * the disk transcripts are the complete record, while live-collected children
143
+ * may be a partial overlap. Mutates messages in-place.
130
144
  */
131
145
  export function mergeSubagentChildren(sessionDir: string, messages: MessageLike[]): void {
132
- const index = indexAgentTranscripts(join(sessionDir, "subagents"));
133
- if (index.size === 0) return;
146
+ const groups = groupSubagentsByCard(join(sessionDir, "subagents"));
147
+ if (groups.size === 0) return;
134
148
 
135
149
  for (const msg of messages) {
136
150
  for (const ev of msg.events ?? []) {
137
151
  if (ev.type !== "tool_use" || (ev.tool !== "Agent" && ev.tool !== "Task") || !ev.toolUseId) continue;
138
- const transcript = index.get(ev.toolUseId);
139
- if (!transcript) continue;
140
- const children = parseAgentTranscript(transcript);
141
- if (children.length > 0) ev.children = children;
152
+ const group = groups.get(ev.toolUseId);
153
+ if (!group) continue;
154
+ const timed: TimedChatEvent[] = [];
155
+ for (const agent of group) timed.push(...parseAgentTranscriptTimed(agent.transcriptPath));
156
+ // Stable sort: same-timestamp events keep transcript order.
157
+ timed.sort((a, b) => a.ts - b.ts);
158
+ if (timed.length > 0) ev.children = timed.map((t) => t.ev);
142
159
  }
143
160
  }
144
161
  }
@@ -13,13 +13,36 @@
13
13
  */
14
14
 
15
15
  /**
16
- * Anthropic's default prompt-cache lifetime. A prefix not re-sent within this window has to
17
- * be written to the cache again, so a subprocess older than this is worth nothing.
16
+ * Anthropic's prompt-cache lifetime, per auth mode. From the SDK's own typings: "1 hour on a
17
+ * Claude subscription within its usage limits, 5 minutes on an API key, Bedrock, Vertex or
18
+ * Foundry."
18
19
  *
19
- * Named here rather than inlined because the retention window is *derived* from it — the two
20
- * were previously equal by coincidence, which read as if the teardown delay were arbitrary.
20
+ * Both are named here because the retention window is *derived* from whichever applies — the
21
+ * delay was previously equal to the TTL by coincidence, which read as arbitrary.
22
+ *
23
+ * Which one a session gets is not a constant: PPM supports settings `api_key` and a custom
24
+ * `base_url` as first-class options, and "within its usage limits" excludes exactly the
25
+ * accounts the rotation exists for. Holding a subprocess for an hour on an API-key install
26
+ * guards a cache that died at minute five, at ~350MB a session. The provider decides; these
27
+ * are the two values it picks between.
28
+ */
29
+ export const SUBSCRIPTION_PROMPT_CACHE_TTL_MS = 60 * 60_000;
30
+ export const API_KEY_PROMPT_CACHE_TTL_MS = 5 * 60_000;
31
+
32
+ /** The longer of the two — what a timer that must outlast any cache window has to clear. */
33
+ export const PROMPT_CACHE_TTL_MS = SUBSCRIPTION_PROMPT_CACHE_TTL_MS;
34
+
35
+ /**
36
+ * What a provider that does not state a window gets.
37
+ *
38
+ * The short one, deliberately. `promptCacheTtlMs` is optional on the provider interface, so
39
+ * "no opinion" and "an hour" would otherwise be the same answer — and the providers without
40
+ * an opinion are exactly the ones with no Anthropic prompt cache to protect. Codex reaches
41
+ * this path today: it implements `hasStreamingSession` but not `promptCacheTtlMs`, so an
42
+ * hour here would hold ~350MB per abandoned session guarding a cache it never had. A
43
+ * provider that benefits from the long window says so.
21
44
  */
22
- export const PROMPT_CACHE_TTL_MS = 5 * 60_000;
45
+ const UNDECLARED_PROMPT_CACHE_TTL_MS = API_KEY_PROMPT_CACHE_TTL_MS;
23
46
 
24
47
  /**
25
48
  * Milliseconds until a session's prompt cache lapses.
@@ -31,7 +54,7 @@ export const PROMPT_CACHE_TTL_MS = 5 * 60_000;
31
54
  export function cacheReleaseDelayMs(
32
55
  lastTurnEndedAt: number | undefined,
33
56
  now: number,
34
- ttlMs: number = PROMPT_CACHE_TTL_MS,
57
+ ttlMs: number = UNDECLARED_PROMPT_CACHE_TTL_MS,
35
58
  ): number {
36
59
  // No completed turn means nothing has been cached, so there is nothing to protect.
37
60
  if (lastTurnEndedAt == null) return 0;
@@ -52,6 +52,9 @@ const TUNNEL_URL_REGEX = /https:\/\/(?!api\.)[a-z0-9-]+\.trycloudflare\.com/;
52
52
  const NAMED_TUNNEL_READY_REGEX = /Registered tunnel connection/;
53
53
  const UPGRADE_CHECK_INTERVAL_MS = 900_000; // 15min
54
54
  const UPGRADE_SKIP_INITIAL_MS = 300_000; // 5min delay before first check
55
+ const DB_BACKUP_INTERVAL_MS = 3_600_000; // 1h — the recovery-point target
56
+ const DB_BACKUP_START_DELAY_MS = 30_000; // let the server settle before the first snapshot
57
+ const DB_BACKUP_STALE_WARN_MS = 21_600_000; // 6h — snapshots stopped happening
55
58
  const SELF_REPLACE_TIMEOUT_MS = 30_000; // 30s to wait for new supervisor
56
59
  const EDGE_PROBE_INTERVAL_MS = 10_000; // the public port is dark while the edge is down — check often
57
60
  const SERVER_PORT_MIRROR_TIMEOUT_MS = 30_000; // how long to wait for the server to publish its port
@@ -127,6 +130,7 @@ let healthTimer: ReturnType<typeof setInterval> | null = null;
127
130
  let tunnelProbeTimer: ReturnType<typeof setInterval> | null = null;
128
131
  let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
129
132
  let upgradeCheckTimer: ReturnType<typeof setInterval> | null = null;
133
+ let dbBackupTimer: ReturnType<typeof setInterval> | null = null;
130
134
  let upgradeDelayTimer: ReturnType<typeof setTimeout> | null = null;
131
135
  let cloudMonitorTimer: ReturnType<typeof setInterval> | null = null;
132
136
  let descendantSnapshotTimer: ReturnType<typeof setInterval> | null = null; // win32 only
@@ -936,6 +940,41 @@ export async function spawnTunnel(port: number, generation: number = ++tunnelGen
936
940
  return retryTunnelAfterFailure(generation);
937
941
  }
938
942
 
943
+ // ─── Config-database snapshots ─────────────────────────────────────────
944
+ let dbBackupDelayTimer: ReturnType<typeof setTimeout> | null = null;
945
+
946
+ /**
947
+ * Take one verified snapshot of the config database, then report whether
948
+ * snapshots are keeping up.
949
+ *
950
+ * Failures are logged and swallowed: a backup problem must never take the
951
+ * server down. The staleness warning exists because a silently-failing backup
952
+ * is worse than no backup — it is the state where recovery is believed to be
953
+ * available and is not.
954
+ */
955
+ async function runDbBackupTick(reason: "hourly" | "start"): Promise<void> {
956
+ if (shuttingDown) return;
957
+ try {
958
+ const { backupDb } = await import("./db-backup/db-backup.service.ts");
959
+ const result = await backupDb(reason);
960
+ const mb = (result.bytes / 1_048_576).toFixed(1);
961
+ const prunedNote = result.pruned.length ? `, pruned ${result.pruned.length}` : "";
962
+ log("INFO", `DB snapshot (${reason}): ${result.path} ${mb}MB in ${result.ms}ms${prunedNote}`);
963
+ } catch (e: any) {
964
+ log("ERROR", `DB snapshot (${reason}) failed: ${e?.message ?? e}`);
965
+ }
966
+
967
+ try {
968
+ const { newestBackupAgeMs } = await import("./db-backup/db-backup.service.ts");
969
+ const age = await newestBackupAgeMs();
970
+ if (age === null) {
971
+ log("WARN", "DB snapshot: no backups exist yet — the database is currently unrecoverable");
972
+ } else if (age > DB_BACKUP_STALE_WARN_MS) {
973
+ log("WARN", `DB snapshot: newest backup is ${Math.round(age / 3_600_000)}h old — recovery point is degraded`);
974
+ }
975
+ } catch { /* staleness reporting is advisory only */ }
976
+ }
977
+
939
978
  // ─── Health checks ─────────────────────────────────────────────────────
940
979
  function startServerHealthCheck() {
941
980
  healthTimer = setInterval(async () => {
@@ -1314,6 +1353,8 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
1314
1353
  if (heartbeatTimer) clearInterval(heartbeatTimer);
1315
1354
  if (upgradeCheckTimer) clearInterval(upgradeCheckTimer);
1316
1355
  if (upgradeDelayTimer) clearTimeout(upgradeDelayTimer);
1356
+ if (dbBackupTimer) clearInterval(dbBackupTimer);
1357
+ if (dbBackupDelayTimer) clearTimeout(dbBackupDelayTimer);
1317
1358
  if (cloudMonitorTimer) clearInterval(cloudMonitorTimer);
1318
1359
  // Disconnect Cloud WS so new supervisor can reconnect cleanly
1319
1360
  try { const { disconnect } = await import("./cloud-ws.service.ts"); disconnect(); } catch {}
@@ -1398,6 +1439,8 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
1398
1439
  if (heartbeatTimer) clearInterval(heartbeatTimer);
1399
1440
  if (upgradeCheckTimer) clearInterval(upgradeCheckTimer);
1400
1441
  if (upgradeDelayTimer) clearTimeout(upgradeDelayTimer);
1442
+ if (dbBackupTimer) clearInterval(dbBackupTimer);
1443
+ if (dbBackupDelayTimer) clearTimeout(dbBackupDelayTimer);
1401
1444
  process.exit(0);
1402
1445
  }
1403
1446
  } catch {}
@@ -1680,6 +1723,8 @@ export function shutdown() {
1680
1723
  if (heartbeatTimer) clearInterval(heartbeatTimer);
1681
1724
  if (upgradeCheckTimer) clearInterval(upgradeCheckTimer);
1682
1725
  if (upgradeDelayTimer) clearTimeout(upgradeDelayTimer);
1726
+ if (dbBackupTimer) clearInterval(dbBackupTimer);
1727
+ if (dbBackupDelayTimer) clearTimeout(dbBackupDelayTimer);
1683
1728
  if (cloudMonitorTimer) clearInterval(cloudMonitorTimer);
1684
1729
  if (descendantSnapshotTimer) clearInterval(descendantSnapshotTimer);
1685
1730
 
@@ -1962,6 +2007,19 @@ export async function runSupervisor(opts: {
1962
2007
  }
1963
2008
  }, RESUME_TICK_MS);
1964
2009
 
2010
+ // Snapshot the config database on a schedule. The supervisor is the only
2011
+ // always-on process, so this is the one place a periodic snapshot survives
2012
+ // both server restarts and crash-backoff windows.
2013
+ //
2014
+ // The start-up snapshot is not redundant with the hourly one: a machine that
2015
+ // is only powered on for short sessions, or a supervisor that sat paused
2016
+ // after exhausting its restart budget, would otherwise take no snapshot at
2017
+ // all for as long as that lasted.
2018
+ dbBackupDelayTimer = setTimeout(() => {
2019
+ void runDbBackupTick("start");
2020
+ dbBackupTimer = setInterval(() => void runDbBackupTick("hourly"), DB_BACKUP_INTERVAL_MS);
2021
+ }, DB_BACKUP_START_DELAY_MS);
2022
+
1965
2023
  // Start upgrade check timer (5min initial delay, then every 15min)
1966
2024
  upgradeDelayTimer = setTimeout(() => {
1967
2025
  checkAvailableVersion();