@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.
- package/.rdtest2-cf.out +0 -0
- package/CHANGELOG.md +18 -0
- package/CLAUDE.md +3 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +22 -4
- package/assets/skills/ppm/references/http-api.md +8 -1
- package/dist/web/assets/{adaptive-context-menu-DCZtgOL7.js → adaptive-context-menu-ChBZWLLq.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-D5CPBg06.js → ai-resource-editor-C8aAY8Po.js} +1 -1
- package/dist/web/assets/{ai-settings-section-DY-t1bK8.js → ai-settings-section-ERu1WiZc.js} +1 -1
- package/dist/web/assets/{angular-html-CTvM4ulF.js → angular-html-CXgHIQ2U.js} +1 -1
- package/dist/web/assets/{angular-ts-B_vSkjco.js → angular-ts-C8renMV9.js} +1 -1
- package/dist/web/assets/api-client-WpZ2FDHg.js +1 -0
- package/dist/web/assets/api-settings-BgSEE6et.js +1 -0
- package/dist/web/assets/{api-settings-B2iNXQ0c.js → api-settings-R59c66DE.js} +1 -1
- package/dist/web/assets/{api-themes-DrWnJbhs.js → api-themes-DKxrwJda.js} +1 -1
- package/dist/web/assets/{apl-DywzTry0.js → apl-DkGH8laI.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-CCalSKAt.js +1 -0
- package/dist/web/assets/{astro-1JaRgaSb.js → astro-tHUd8Zlg.js} +1 -1
- package/dist/web/assets/{audio-preview-DAuGvOiK.js → audio-preview-B_C6Dpvz.js} +1 -1
- package/dist/web/assets/{blade-CDeeJWBb.js → blade-BbKpjIYK.js} +1 -1
- package/dist/web/assets/{bsl-BfgoPVYR.js → bsl-DA0UYto0.js} +1 -1
- package/dist/web/assets/{button-tNy0ba3y.js → button-DBSpDPS0.js} +1 -1
- package/dist/web/assets/{cairo-C26gNwWW.js → cairo-79ayucbB.js} +1 -1
- package/dist/web/assets/chat-tab-BraiJsYG.js +15 -0
- package/dist/web/assets/{cobol-D92nBnON.js → cobol-DSS2EOYo.js} +1 -1
- package/dist/web/assets/code-editor-lhmLNhXu.js +10 -0
- package/dist/web/assets/{coffee-CIWH40R1.js → coffee-DvSxq2iD.js} +1 -1
- package/dist/web/assets/{conflict-editor-CDp0I3mD.js → conflict-editor-C4VCv51R.js} +1 -1
- package/dist/web/assets/{context-menu-BO1c9jKy.js → context-menu-BpwUny-o.js} +1 -1
- package/dist/web/assets/{cpp-DOrBqXCn.js → cpp-BhWZ-FSn.js} +1 -1
- package/dist/web/assets/{crystal-BNKqmHux.js → crystal-yP2OeF-f.js} +1 -1
- package/dist/web/assets/{csv-preview-D4svNDAq.js → csv-preview-D46W_icb.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-BqV0AxZg.js → data-grid-overlay-editor-DeGkPYtA.js} +1 -1
- package/dist/web/assets/{database-viewer-BCvv_Bwf.js → database-viewer-DFI4YtrH.js} +1 -1
- package/dist/web/assets/{dialog-DcLGDwaX.js → dialog-RIpeb87v.js} +1 -1
- package/dist/web/assets/{diff-viewer-D41fCFAi.js → diff-viewer-eaNOLcBL.js} +1 -1
- package/dist/web/assets/{docx-preview-DfaQn9v7.js → docx-preview-Cbj-pOK3.js} +1 -1
- package/dist/web/assets/{edge-Dbjp42Pt.js → edge-D3K9cd-p.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-BqcUuFCc.js → edit-diff-preview-DDqy3KLA.js} +1 -1
- package/dist/web/assets/{elixir-na3b1pYy.js → elixir-D4PBmsLp.js} +1 -1
- package/dist/web/assets/{elm-_Nalil1R.js → elm-DSpXjuaH.js} +1 -1
- package/dist/web/assets/{erb-CW_7dubf.js → erb-CaYZMTJW.js} +1 -1
- package/dist/web/assets/{erlang-DmZ7Zzb0.js → erlang-DX5BFqJj.js} +1 -1
- package/dist/web/assets/{esm-CwyBetO1.js → esm-Cv7HsFnk.js} +1 -1
- package/dist/web/assets/explorer-body-BxbSttfl.js +2 -0
- package/dist/web/assets/explorer-window-content-DUqQCC7A.js +1 -0
- package/dist/web/assets/{extension-store-DUaZINyC.js → extension-store-B1EClUAr.js} +1 -1
- package/dist/web/assets/{extension-webview-BW_mccb3.js → extension-webview-BaGvDk8v.js} +1 -1
- package/dist/web/assets/{file-store-yReVbR8q.js → file-store-m9crh6uq.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-DB2A8L4-.js → fortran-fixed-form-B0bAufUe.js} +1 -1
- package/dist/web/assets/{fsharp-BYHrti7z.js → fsharp-C2AL2b1M.js} +1 -1
- package/dist/web/assets/{gdresource-D5GBin40.js → gdresource-DRQsDMNI.js} +1 -1
- package/dist/web/assets/{git-commit-CUiOBoIL.js → git-commit-GtBdnrzk.js} +1 -1
- package/dist/web/assets/{git-log-panel-C1QDkmgl.js → git-log-panel-hwV1flSO.js} +1 -1
- package/dist/web/assets/{git-rebase-DOxGnpdw.js → git-rebase-h_0CGA6F.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DAUrJCqi.js +1 -0
- package/dist/web/assets/glide-data-grid-YsN-xm6x.js +138 -0
- package/dist/web/assets/{glimmer-js-Begy4UT7.js → glimmer-js-Bxwu93bS.js} +1 -1
- package/dist/web/assets/{glimmer-ts-rPApRY8N.js → glimmer-ts-CIUYdR4E.js} +1 -1
- package/dist/web/assets/{glsl-wrbPvrBL.js → glsl-CfxLI5jn.js} +1 -1
- package/dist/web/assets/{graphql-Cu4-NR__.js → graphql-CstqDPde.js} +1 -1
- package/dist/web/assets/group-chat-tab-B7mJO1gG.js +1 -0
- package/dist/web/assets/{hack-Ddo93XKD.js → hack-Bjqnssb5.js} +1 -1
- package/dist/web/assets/{haml-BFX_1i8j.js → haml-lVc-DfbM.js} +1 -1
- package/dist/web/assets/hand-x0ejq3fS.js +1 -0
- package/dist/web/assets/{handlebars-5gFKA4H5.js → handlebars-Yy6fPQI_.js} +1 -1
- package/dist/web/assets/{html-derivative-e5JLAFZF.js → html-derivative-CchzCY1z.js} +1 -1
- package/dist/web/assets/{html-C06I-zeO.js → html-lqtcy6-2.js} +1 -1
- package/dist/web/assets/{http-CrAAhSU7.js → http-ChclRrko.js} +1 -1
- package/dist/web/assets/{hurl-T1V3SX9T.js → hurl-CoOK7sXa.js} +1 -1
- package/dist/web/assets/{hxml-B056LLKN.js → hxml-fZSZ7Iyt.js} +1 -1
- package/dist/web/assets/{image-preview-DWyuCYT3.js → image-preview-ClAcqDlL.js} +1 -1
- package/dist/web/assets/index-CtJbMNNI.css +2 -0
- package/dist/web/assets/index-H_gScEOk.js +52 -0
- package/dist/web/assets/info-3K5VOQVL-Cx1ZfJbp.js +1 -0
- package/dist/web/assets/{input-DouJ9Zvt.js → input-DXPgUtxi.js} +1 -1
- package/dist/web/assets/{jinja-DGdtRh43.js → jinja-Y0w_KBdE.js} +1 -1
- package/dist/web/assets/{jison-Ah6QobPS.js → jison-BILj3F2e.js} +1 -1
- package/dist/web/assets/{julia-BJeGOK0K.js → julia-DRfOPDts.js} +1 -1
- package/dist/web/assets/{just-iw9oRs7j.js → just-n3U_oODO.js} +1 -1
- package/dist/web/assets/keybindings-store-BqctreaI.js +1 -0
- package/dist/web/assets/{keybindings-store-0Wqfa7Qp.js → keybindings-store-D-CHdq-G.js} +1 -1
- package/dist/web/assets/keyboard-DBBaoQSu.js +1 -0
- package/dist/web/assets/{latex-DHjj5hyN.js → latex-D6gztEOL.js} +1 -1
- package/dist/web/assets/{liquid-B-FFecDc.js → liquid-DPqCEVb9.js} +1 -1
- package/dist/web/assets/{lua-DDianJp_.js → lua-I40KPUm-.js} +1 -1
- package/dist/web/assets/markdown-renderer-BwOkiZC1.js +1 -0
- package/dist/web/assets/{markdown-renderer-BHiUhQN8.js → markdown-renderer-oJ9jPdQ2.js} +3 -3
- package/dist/web/assets/{marko-BW5SBVng.js → marko-DMjsThSW.js} +1 -1
- package/dist/web/assets/{mdc-B9gL9bjl.js → mdc-CN2alsbo.js} +1 -1
- package/dist/web/assets/{media-url-DUK-k0bB.js → media-url-COUvasjn.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-Cj1YBq-c.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-D1nM0Ik1.js +1 -0
- package/dist/web/assets/{nextflow-dTluY6qy.js → nextflow-CGa4JeZ6.js} +1 -1
- package/dist/web/assets/{nginx-DlwghGI-.js → nginx-CmqrIj84.js} +1 -1
- package/dist/web/assets/{nim-N9jddb5D.js → nim-CqhmTeMa.js} +1 -1
- package/dist/web/assets/notification-store-n-XEQ5pH.js +1 -0
- package/dist/web/assets/{number-overlay-editor-BVXM7aAs.js → number-overlay-editor-BEzJ1YeD.js} +1 -1
- package/dist/web/assets/{overview-panel-DCCDmtCQ.js → overview-panel-Bf9Gn8qV.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-Bqwiy7dz.js +1 -0
- package/dist/web/assets/{panel-store-B4rijFKN.js → panel-store-UWCH6B-_.js} +1 -1
- package/dist/web/assets/{pdf-preview-D7yLmAZW.js → pdf-preview-BMHM5cR2.js} +1 -1
- package/dist/web/assets/{perl-CkmpYKa_.js → perl-BOwpb6Tb.js} +1 -1
- package/dist/web/assets/{php-BwZ3KDPo.js → php-wT9fVxxu.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-BXSJ2God.js +1 -0
- package/dist/web/assets/{postgres-viewer--CRN1fvO.js → postgres-viewer-D8rfEY5b.js} +3 -3
- package/dist/web/assets/process-table-CsQ-hDJr.js +1 -0
- package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-CKAOr23O.js} +1 -1
- package/dist/web/assets/{pug-B3rBPqQo.js → pug-DdlNnj-p.js} +1 -1
- package/dist/web/assets/{qml-BfDbWFmg.js → qml-C5zBdydf.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-D6XrM7HH.js +1 -0
- package/dist/web/assets/{razor-B1ww1r38.js → razor-DqcJX4iq.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-CTiipn4k.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-Cf5B0dKo.js +1 -0
- package/dist/web/assets/remote-desktop-stats-overlay-vhJBIW7s.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-wCJUfI6G.js +1 -0
- package/dist/web/assets/{rst-C0MSq6m-.js → rst-DiMiMYUO.js} +1 -1
- package/dist/web/assets/{ruby-ClIZ1wb3.js → ruby-DK62iflA.js} +1 -1
- package/dist/web/assets/{run-in-terminal-4efbW2Xv.js → run-in-terminal-DJSL6nwZ.js} +1 -1
- package/dist/web/assets/{sas-DR0j3o4_.js → sas-6QPGJRa5.js} +1 -1
- package/dist/web/assets/{scroll-area-ZP8wcwTI.js → scroll-area-DXGXeiRz.js} +1 -1
- package/dist/web/assets/{scss-Vh3JLayR.js → scss-DmUqh48_.js} +1 -1
- package/dist/web/assets/{send-to-chat-BNrEpAyC.js → send-to-chat-BOony0sO.js} +1 -1
- package/dist/web/assets/settings-store-BpcY5aYE.js +2 -0
- package/dist/web/assets/settings-tab-BVqRO01j.js +1 -0
- package/dist/web/assets/settings-tab-FH581A_i.js +3 -0
- package/dist/web/assets/{shaderlab-0rHRnQ18.js → shaderlab-DopFR1mi.js} +1 -1
- package/dist/web/assets/{shellsession-Dxdw6D8c.js → shellsession-Bp_TGuXb.js} +1 -1
- package/dist/web/assets/{soy-BHlSNj1y.js → soy-DEB9KEFb.js} +1 -1
- package/dist/web/assets/{sparql-B2QaPpni.js → sparql-C8lEwO1p.js} +1 -1
- package/dist/web/assets/{sql-query-editor-R_iKjPP9.js → sql-query-editor-MMfk0Kbz.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-b2onA0SP.js → sqlite-viewer-vglzrgnZ.js} +1 -1
- package/dist/web/assets/{stata-CwIxjsEJ.js → stata-DVep4apN.js} +1 -1
- package/dist/web/assets/{surrealql-C6vo-rp-.js → surrealql-fSlJApOA.js} +1 -1
- package/dist/web/assets/{svelte-LLdJ6rlH.js → svelte-DezWBEDi.js} +1 -1
- package/dist/web/assets/{switch-DV-BDjyk.js → switch-DqL_ZjB_.js} +1 -1
- package/dist/web/assets/system-monitor-body-BYUfyF8f.js +2 -0
- package/dist/web/assets/system-monitor-tab-CKGXB8lH.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-BPif0azo.js → system-monitor-window-content-CiJ2yIsr.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BnAN6m4K.js → tab-host-window-content-DYlXR0cg.js} +1 -1
- package/dist/web/assets/{tab-store-tFnyPmm4.js → tab-store-D2thn4Vv.js} +1 -1
- package/dist/web/assets/team-member-sheet-Bnygu7Xw.js +2 -0
- package/dist/web/assets/{team-member-window-content-BSyU-5Uc.js → team-member-window-content-qrgFTC96.js} +1 -1
- package/dist/web/assets/{templ-CJGvhEP5.js → templ-D0zB42a9.js} +1 -1
- package/dist/web/assets/{terminal-tab-Ncor0Rfu.js → terminal-tab-D_-R5rGm.js} +3 -3
- package/dist/web/assets/{tex-psXWhc4t.js → tex-BJiPijcH.js} +1 -1
- package/dist/web/assets/{tool-cards-DdVP1wrD.js → tool-cards-BW7Xc20J.js} +4 -4
- package/dist/web/assets/treemap-KZPCXAKY-BobWhcHN.js +1 -0
- package/dist/web/assets/{ts-tags-D5Ar_ZCO.js → ts-tags-Dcr4u6y5.js} +1 -1
- package/dist/web/assets/{twig-vJgGC-qY.js → twig-B-SQU0bg.js} +1 -1
- package/dist/web/assets/{use-blob-url-BOVGWg9F.js → use-blob-url-BtPlqwLr.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BshnLXoY.js → use-monaco-theme-DQY1ZSaf.js} +1 -1
- package/dist/web/assets/{use-open-team-member-CY3wOdWL.js → use-open-team-member-C5LQ_TCM.js} +1 -1
- package/dist/web/assets/use-visual-viewport-pVxLfE5B.js +1 -0
- package/dist/web/assets/{use-websocket-B7xjNsyD.js → use-websocket-l39Odhlp.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-999ksD45.js → vendor-mermaid-CtDsSxbM.js} +1 -1
- package/dist/web/assets/{video-preview-7F72sRwf.js → video-preview-C5AhkIDl.js} +1 -1
- package/dist/web/assets/{vue-BWngFyav.js → vue-Cw10Sfbh.js} +1 -1
- package/dist/web/assets/{vue-html-BN5ZLu-H.js → vue-html-Cc5C_Z_w.js} +1 -1
- package/dist/web/assets/{vue-vine-BZHnO8yV.js → vue-vine-BuNMlnvu.js} +1 -1
- package/dist/web/assets/{window-pip-registry-BnYU83LE.js → window-pip-registry-C7oz3Dfy.js} +1 -1
- package/dist/web/assets/window-store-B5ZswdYg.js +1 -0
- package/dist/web/assets/{xml-DS2beZHp.js → xml-DqT1wkky.js} +1 -1
- package/dist/web/assets/{xsl-Ciiku1Bq.js → xsl-DLkge3A2.js} +1 -1
- package/dist/web/index.html +45 -43
- package/dist/web/sw.js +1 -1
- package/docs/architecture/ai-chat-and-providers.md +3 -0
- package/package.json +1 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- package/src/cli/commands/backup-cmd.ts +119 -0
- package/src/index.ts +3 -0
- package/src/providers/claude-agent-sdk.ts +29 -1
- package/src/server/index.ts +42 -15
- package/src/server/routes/accounts.ts +30 -2
- package/src/server/routes/chat.ts +8 -1
- package/src/server/routes/named-tunnel.ts +5 -2
- package/src/server/routes/remote-desktop.ts +64 -0
- package/src/server/routes/settings.ts +1 -0
- package/src/server/ws/chat.ts +126 -7
- package/src/server/ws/remote-desktop.ts +86 -0
- package/src/services/account.service.ts +106 -23
- package/src/services/db-backup/db-backup-paths.ts +66 -0
- package/src/services/db-backup/db-backup-restore.ts +105 -0
- package/src/services/db-backup/db-backup-retention.ts +96 -0
- package/src/services/db-backup/db-backup-sync.ts +113 -0
- package/src/services/db-backup/db-backup.service.ts +61 -0
- package/src/services/db.service.ts +29 -1
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/fs-credential-path-guard.ts +19 -1
- package/src/services/named-tunnel/cloudflared-login.service.ts +2 -2
- package/src/services/nested-subagent-spy.ts +206 -0
- package/src/services/remote-desktop/access-unit-assembler.ts +90 -0
- package/src/services/remote-desktop/avc1-codec-string.ts +18 -0
- package/src/services/remote-desktop/nal-splitter.ts +100 -0
- package/src/services/remote-desktop/remote-desktop-capture.ts +120 -0
- package/src/services/remote-desktop/remote-desktop-encoder-args.ts +42 -0
- package/src/services/remote-desktop/remote-desktop-flag.ts +10 -0
- package/src/services/remote-desktop/remote-desktop-input.ts +217 -0
- package/src/services/remote-desktop/remote-desktop-nonce.ts +38 -0
- package/src/services/remote-desktop/remote-desktop-session.ts +189 -0
- package/src/services/remote-desktop/remote-desktop-vk-map.ts +41 -0
- package/src/services/subagent-transcript-merger.ts +92 -75
- package/src/services/subprocess-retention.ts +29 -6
- package/src/services/supervisor.ts +58 -0
- package/src/services/team-member-activity/subagent-transcript-index.ts +56 -0
- package/src/services/windows-process-tree.ts +351 -351
- package/src/types/chat.ts +2 -0
- package/src/web/app.tsx +8 -0
- package/src/web/components/chat/account-dialogs.tsx +11 -4
- package/src/web/components/chat/question-card.tsx +9 -2
- package/src/web/components/chat/usage-badge.tsx +17 -3
- package/src/web/components/floating-window/window-content-registry.ts +2 -0
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/layout/mobile-drawer.tsx +38 -27
- package/src/web/components/layout/nav-section-rail.tsx +10 -2
- package/src/web/components/remote-desktop/open-remote-desktop.ts +26 -0
- package/src/web/components/remote-desktop/remote-desktop-coords.ts +98 -0
- package/src/web/components/remote-desktop/remote-desktop-frame-timestamp.ts +21 -0
- package/src/web/components/remote-desktop/remote-desktop-gesture-classifiers.ts +56 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +131 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-sheet.tsx +43 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +79 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +190 -0
- package/src/web/components/remote-desktop/remote-desktop-stats-overlay.tsx +54 -0
- package/src/web/components/remote-desktop/remote-desktop-stats.ts +37 -0
- package/src/web/components/remote-desktop/remote-desktop-touch-gesture-tracker.ts +195 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +72 -0
- package/src/web/components/remote-desktop/remote-desktop-ws-url.ts +22 -0
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +175 -0
- package/src/web/components/remote-desktop/use-remote-desktop-available.ts +29 -0
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +147 -0
- package/src/web/components/remote-desktop/use-remote-desktop-mobile-open-state.ts +18 -0
- package/src/web/components/remote-desktop/use-remote-desktop-touch.ts +224 -0
- package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +50 -0
- package/src/web/components/remote-desktop/use-remote-input-capture.ts +92 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +59 -59
- package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +2 -2
- package/src/web/hooks/use-notification-badge.ts +19 -11
- package/src/web/lib/feature-badges.ts +2 -0
- package/src/web/stores/settings-store.ts +13 -0
- package/src/web/stores/streaming-store.ts +21 -2
- package/dist/web/assets/api-client-BogqsFna.js +0 -1
- package/dist/web/assets/api-settings-DRF9oYkn.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-nw5-j_I_.js +0 -1
- package/dist/web/assets/chat-tab-IcnU3nqQ.js +0 -15
- package/dist/web/assets/code-editor-Dno5Srkm.js +0 -10
- package/dist/web/assets/explorer-body-DB1L30pa.js +0 -2
- package/dist/web/assets/explorer-window-content-Cwlt3C3I.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-C1xf87gN.js +0 -1
- package/dist/web/assets/glide-data-grid-Dz8lx6n2.js +0 -138
- package/dist/web/assets/group-chat-tab-DFwr-pig.js +0 -1
- package/dist/web/assets/index-DWpNR3nB.css +0 -2
- package/dist/web/assets/index-oUKV5nIA.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-C2z7FTDe.js +0 -1
- package/dist/web/assets/keybindings-store-d54pv2NK.js +0 -1
- package/dist/web/assets/markdown-renderer-CnE36_gd.js +0 -1
- package/dist/web/assets/mobile-bottom-sheet-CJ7ApOkI.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-BzjlS1Xn.js +0 -1
- package/dist/web/assets/notification-store-C7UBqxYP.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-DQUxN0wU.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BOr9ek4Q.js +0 -1
- package/dist/web/assets/process-table-CXC10GSy.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-DSErxZFb.js +0 -1
- package/dist/web/assets/settings-store-OLJE7dPH.js +0 -2
- package/dist/web/assets/settings-tab-B1rvHFc6.js +0 -3
- package/dist/web/assets/settings-tab-BUlmPJ2w.js +0 -1
- package/dist/web/assets/system-monitor-body-DPYQlBEt.js +0 -2
- package/dist/web/assets/system-monitor-tab-CPQdWv27.js +0 -1
- package/dist/web/assets/team-member-sheet-Bu2wG1sm.js +0 -2
- package/dist/web/assets/treemap-KZPCXAKY-C17p0LQw.js +0 -1
- package/dist/web/assets/window-store-CQ1lghvv.js +0 -1
- /package/dist/web/assets/{abap-CjzrhAS2.js → abap-CKgBNk5K.js} +0 -0
- /package/dist/web/assets/{actionscript-3-B59Kbl-9.js → actionscript-3-oARhAY6c.js} +0 -0
- /package/dist/web/assets/{ada-B7gItKAE.js → ada-2vi8ASy3.js} +0 -0
- /package/dist/web/assets/{andromeeda-Dy5zcy0Q.js → andromeeda-B3vMjPKd.js} +0 -0
- /package/dist/web/assets/{apache-B6T9jrc0.js → apache-DnIEr338.js} +0 -0
- /package/dist/web/assets/{apex-CTsIxDQw.js → apex-B7Qs5AY5.js} +0 -0
- /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-BkGKTjsq.js} +0 -0
- /package/dist/web/assets/{applescript-DuycKmmb.js → applescript-EbQNUCWb.js} +0 -0
- /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-Dd_cqGa-.js} +0 -0
- /package/dist/web/assets/{ara-CISL3599.js → ara-CtHdhFRw.js} +0 -0
- /package/dist/web/assets/{asciidoc-BXyE2Lpr.js → asciidoc-BH6qin_O.js} +0 -0
- /package/dist/web/assets/{asm-y258phtP.js → asm-D1XU1ACk.js} +0 -0
- /package/dist/web/assets/{aurora-x-Dq3Jc78M.js → aurora-x-BkhcxkuS.js} +0 -0
- /package/dist/web/assets/{awk-DxL6XSJv.js → awk-DijzB6o7.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DjoK6Evo.js → ayu-dark-tysG77WC.js} +0 -0
- /package/dist/web/assets/{ayu-light-CKKXBLMI.js → ayu-light-Dg0WMETC.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-DGXSrrXB.js → ayu-mirage--RsJCjC4.js} +0 -0
- /package/dist/web/assets/{ballerina-DZSXFmUf.js → ballerina-DT_5-l82.js} +0 -0
- /package/dist/web/assets/{bat-BNsZk6nY.js → bat-BRpDlkv0.js} +0 -0
- /package/dist/web/assets/{beancount-cPl4gJct.js → beancount--8Df-WGG.js} +0 -0
- /package/dist/web/assets/{berry-DGAodERk.js → berry-Bc9fo35J.js} +0 -0
- /package/dist/web/assets/{bibtex-DftVoRif.js → bibtex-CwyBpp9X.js} +0 -0
- /package/dist/web/assets/{bicep-Suioyyzh.js → bicep-DiyH2Bs7.js} +0 -0
- /package/dist/web/assets/{bird2-DjU24I2P.js → bird2-CHgK4a2A.js} +0 -0
- /package/dist/web/assets/{c-DFi8Y_nN.js → c-BM1qL6-f.js} +0 -0
- /package/dist/web/assets/{c3-1eSHnH5V.js → c3-D2oOjrP7.js} +0 -0
- /package/dist/web/assets/{cadence-DLLsFwQP.js → cadence-nS45wqF6.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-in727eFQ.js → can-open-in-ppm-DwP9KU8o.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DDj0JEiY.js → catppuccin-frappe-BpnJHiCj.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-DPyANHyR.js → catppuccin-latte-Bb95Wj3h.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-CO-h8VTT.js → catppuccin-macchiato-Btx8c17W.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BCmbN2IZ.js → catppuccin-mocha-B4DZOUGI.js} +0 -0
- /package/dist/web/assets/{clarity-C0pYsZjs.js → clarity-1ps69jU6.js} +0 -0
- /package/dist/web/assets/{clipboard-DBLGKMrH.js → clipboard-CrHX8AYw.js} +0 -0
- /package/dist/web/assets/{clojure-JFr6s8iZ.js → clojure-BklC0hKV.js} +0 -0
- /package/dist/web/assets/{cmake-EEcLEgVy.js → cmake-B573LYAQ.js} +0 -0
- /package/dist/web/assets/{codeowners-Deh3Lx0u.js → codeowners-BoYeX89M.js} +0 -0
- /package/dist/web/assets/{codeql-C317igT5.js → codeql-7Dhf5Udk.js} +0 -0
- /package/dist/web/assets/{common-lisp-DepbATZs.js → common-lisp-m1nxlthv.js} +0 -0
- /package/dist/web/assets/{coq-MxgxU5ya.js → coq-DNhGmd4a.js} +0 -0
- /package/dist/web/assets/{csharp-eEFXdsAq.js → csharp-P-nAurtS.js} +0 -0
- /package/dist/web/assets/{css-Db-syWW9.js → css-LYbSeh3-.js} +0 -0
- /package/dist/web/assets/{csv-DHeYcbaq.js → csv-DHZZZm7X.js} +0 -0
- /package/dist/web/assets/{csv-parser-_JWMLGl3.js → csv-parser-CY9Ggtg5.js} +0 -0
- /package/dist/web/assets/{cue-BmUNowrt.js → cue-D8yLUOle.js} +0 -0
- /package/dist/web/assets/{cypher-XjXHgCNS.js → cypher-9cbW8RYA.js} +0 -0
- /package/dist/web/assets/{d-BWJnZtig.js → d-ZihzhxqI.js} +0 -0
- /package/dist/web/assets/{dark-plus-Db7umas1.js → dark-plus-C8xIA1u3.js} +0 -0
- /package/dist/web/assets/{dart-DSI4HYh1.js → dart-CBm6jKc7.js} +0 -0
- /package/dist/web/assets/{data-grid-types-CfVHh9gv.js → data-grid-types-DkEQ-Na9.js} +0 -0
- /package/dist/web/assets/{dax-yw1JsAni.js → dax-B3cx6YeV.js} +0 -0
- /package/dist/web/assets/{desktop-C2QO4RvD.js → desktop-C03SpXth.js} +0 -0
- /package/dist/web/assets/{diff-Drt8aEyp.js → diff-D8LnnvFY.js} +0 -0
- /package/dist/web/assets/{dist-DNRvhVdz.js → dist-C4XoS_zo.js} +0 -0
- /package/dist/web/assets/{dist-VBxOHLmN.js → dist-Do3fljss.js} +0 -0
- /package/dist/web/assets/{docker-ByDKbmLl.js → docker-DwFiOQWW.js} +0 -0
- /package/dist/web/assets/{dotenv-BkfOLytR.js → dotenv-CVJ_vFSQ.js} +0 -0
- /package/dist/web/assets/{dracula-DAmrm-Un.js → dracula-DMw8UACY.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BUOEnKqR.js → dracula-soft-BnB3nDrA.js} +0 -0
- /package/dist/web/assets/{dream-maker-DQBIfSiO.js → dream-maker-2riXUQXo.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-Blh5kBD3.js → emacs-lisp-BQdYaiB5.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CEzhDxHq.js → everforest-dark-C2FPIWdj.js} +0 -0
- /package/dist/web/assets/{everforest-light-CPk_5Khm.js → everforest-light-DMOWf9sD.js} +0 -0
- /package/dist/web/assets/{fennel-B1MT_EBZ.js → fennel-JtSHp0vW.js} +0 -0
- /package/dist/web/assets/{fish-C_HGcewt.js → fish-CflDcRfe.js} +0 -0
- /package/dist/web/assets/{fluent-Dp1Iclmx.js → fluent-D4eu6Vac.js} +0 -0
- /package/dist/web/assets/{format-bytes-53WBtyi2.js → format-bytes-DR3228bu.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-B_-UA4XC.js → fortran-free-form-hO4RuC9Z.js} +0 -0
- /package/dist/web/assets/{gdscript-Bm_JpjS_.js → gdscript-D7n3a8VM.js} +0 -0
- /package/dist/web/assets/{gdshader-C_PNmfYU.js → gdshader-BhUmrlzG.js} +0 -0
- /package/dist/web/assets/{genie-B5rmR2TF.js → genie-VbyftqBp.js} +0 -0
- /package/dist/web/assets/{gherkin-BTJ34TJ9.js → gherkin-BZzhvUUb.js} +0 -0
- /package/dist/web/assets/{github-dark-DOkNVj9H.js → github-dark-C4q41woW.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DTsmiyVo.js → github-dark-default-CMNi7dQX.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CjbxAe9s.js → github-dark-dimmed-BRf_-LlY.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-r_M8F_k7.js → github-dark-high-contrast-xanKBsRI.js} +0 -0
- /package/dist/web/assets/{github-light-BzfxyMyb.js → github-light-BEQeNQCs.js} +0 -0
- /package/dist/web/assets/{github-light-default-BOqiBIy5.js → github-light-default-ceQrtdXg.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DQzBrDM4.js → github-light-high-contrast-doKbXzgW.js} +0 -0
- /package/dist/web/assets/{gleam-BWmuAtD8.js → gleam-Chzqf4fT.js} +0 -0
- /package/dist/web/assets/{gn-CNQPjiZW.js → gn-CCN_-6fp.js} +0 -0
- /package/dist/web/assets/{gnuplot-BlHjcPMe.js → gnuplot-CIplgwwP.js} +0 -0
- /package/dist/web/assets/{go-Bn8Ba9uz.js → go-Cjj-9dRs.js} +0 -0
- /package/dist/web/assets/{groovy-C1Y1_F4V.js → groovy-PqD4K7X1.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DueOsHav.js → gruvbox-dark-hard-BAbpUSF-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-DaQjD538.js → gruvbox-dark-medium-BWcWXDBB.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-CbbX2WOm.js → gruvbox-dark-soft-Dkx1okkk.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-E2M70ljZ.js → gruvbox-light-hard-WVsDK0MM.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-B_DDLY6h.js → gruvbox-light-medium-tiR9t7gr.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-2SZmyC1F.js → gruvbox-light-soft-HzHohaX6.js} +0 -0
- /package/dist/web/assets/{haskell-D8DeoXtG.js → haskell-B1eIZfr6.js} +0 -0
- /package/dist/web/assets/{haxe-BmZm7B0W.js → haxe-BcDdi5xr.js} +0 -0
- /package/dist/web/assets/{hcl-CKnBM3ng.js → hcl-Dq-HKVni.js} +0 -0
- /package/dist/web/assets/{hjson-Ct9t4-ci.js → hjson-Dr9D9WVU.js} +0 -0
- /package/dist/web/assets/{hlsl-BZSASBqS.js → hlsl-Bo8t2FuZ.js} +0 -0
- /package/dist/web/assets/{horizon-CzX1AZ08.js → horizon-DWG5IXc-.js} +0 -0
- /package/dist/web/assets/{horizon-bright-CCVNeRIP.js → horizon-bright-DFr9wTTx.js} +0 -0
- /package/dist/web/assets/{houston-DBlJN7xc.js → houston-NAVNhkup.js} +0 -0
- /package/dist/web/assets/{hy-Dyc6h6Xb.js → hy-DnyvUk3B.js} +0 -0
- /package/dist/web/assets/{image-gallery-DujlrjOC.js → image-gallery-DoFdNF9i.js} +0 -0
- /package/dist/web/assets/{imba-Bauru7s2.js → imba-CPbR5W_A.js} +0 -0
- /package/dist/web/assets/{ini-SjdwtaTp.js → ini-lc8_Lt0y.js} +0 -0
- /package/dist/web/assets/{java-D2r0ocpf.js → java-B0KCo2vK.js} +0 -0
- /package/dist/web/assets/{javascript-y-lof7iP.js → javascript-YqEbNRpd.js} +0 -0
- /package/dist/web/assets/{json-DO4LSx5A.js → json-Co0WtOlh.js} +0 -0
- /package/dist/web/assets/{json5-BEwWf-W3.js → json5-2Jhb0QUN.js} +0 -0
- /package/dist/web/assets/{jsonc-OBEFv7gP.js → jsonc-e72F11nU.js} +0 -0
- /package/dist/web/assets/{jsonl-DOyW4Mg3.js → jsonl-Bff8IPye.js} +0 -0
- /package/dist/web/assets/{jsonnet-BL5R2CpP.js → jsonnet-DRCkb-_H.js} +0 -0
- /package/dist/web/assets/{jssm-ksPusAyg.js → jssm-C6UiwVEm.js} +0 -0
- /package/dist/web/assets/{jsx-B6XEu6O-.js → jsx-u1aZlnfA.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-JBVe_ji_.js → kanagawa-dragon-De-RQFV3.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-ChprO68_.js → kanagawa-lotus-K8elE4QU.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-D_uB_R0Q.js → kanagawa-wave-BrStl_zx.js} +0 -0
- /package/dist/web/assets/{kdl-pf5B8N_m.js → kdl-CLCfMeZy.js} +0 -0
- /package/dist/web/assets/{kotlin-BdF0METC.js → kotlin-DEEC3pNh.js} +0 -0
- /package/dist/web/assets/{kusto-BneVP-OU.js → kusto-DTqRhCup.js} +0 -0
- /package/dist/web/assets/{laserwave-C4-UPIyG.js → laserwave-DsgpUSbA.js} +0 -0
- /package/dist/web/assets/{lean-BN6Jdymq.js → lean-Dl9MI0Y3.js} +0 -0
- /package/dist/web/assets/{less-Cx-fdlDC.js → less-Bg21rao6.js} +0 -0
- /package/dist/web/assets/{lib-CMuBa85c.js → lib-CXUwxw1M.js} +0 -0
- /package/dist/web/assets/{libesm-BB74PRsH.js → libesm-CVkncAQz.js} +0 -0
- /package/dist/web/assets/{light-plus-DFnnn_iL.js → light-plus-DGNbbK1V.js} +0 -0
- /package/dist/web/assets/{llvm-BGxtW0Nw.js → llvm-91_ZX3p-.js} +0 -0
- /package/dist/web/assets/{loader-circle-Dpqcv0eS.js → loader-circle-DouKgIXy.js} +0 -0
- /package/dist/web/assets/{lock-DldbT31x.js → lock-CNxUGpTk.js} +0 -0
- /package/dist/web/assets/{log-kQb4eJnj.js → log-DSXzbq8u.js} +0 -0
- /package/dist/web/assets/{logo-DqAzaq7s.js → logo-BQRNd4tc.js} +0 -0
- /package/dist/web/assets/{luau-Dc2cp-zS.js → luau-BE-TMpiZ.js} +0 -0
- /package/dist/web/assets/{make-DYg9dwaJ.js → make-ePYGKTpc.js} +0 -0
- /package/dist/web/assets/{markdown-O4EPpS3K.js → markdown-Cst3wgMW.js} +0 -0
- /package/dist/web/assets/{material-theme-ClP8yfQ2.js → material-theme-9XyvnNLb.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-LOK55hGu.js → material-theme-darker-H1VN4MTS.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-j72tzZqi.js → material-theme-lighter-DAreBbuF.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-C19LKClA.js → material-theme-ocean-BpTR-3II.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-DJ5tN2ax.js → material-theme-palenight-DUNzX711.js} +0 -0
- /package/dist/web/assets/{matlab-DQNZ_NU8.js → matlab-BpYNufBU.js} +0 -0
- /package/dist/web/assets/{mdx-CJayB7XK.js → mdx-BYjfe1DK.js} +0 -0
- /package/dist/web/assets/{mermaid-Cb0TRX92.js → mermaid-DPCcIW9B.js} +0 -0
- /package/dist/web/assets/{min-dark-CWM8Bo-m.js → min-dark-DnGOAGCo.js} +0 -0
- /package/dist/web/assets/{min-light-BBR1sjS2.js → min-light-CwV-o31y.js} +0 -0
- /package/dist/web/assets/{mipsasm-Jc7u7YSS.js → mipsasm-DdeCPC97.js} +0 -0
- /package/dist/web/assets/{mojo-CTB0cutd.js → mojo-BmxxZ3iM.js} +0 -0
- /package/dist/web/assets/{monitor-4vWfqzuN.js → monitor-C835ucWH.js} +0 -0
- /package/dist/web/assets/{monokai-CvM_YNKx.js → monokai-Cx6cQ-MA.js} +0 -0
- /package/dist/web/assets/{moonbit-CbFEfYYM.js → moonbit-D2-Gj5vj.js} +0 -0
- /package/dist/web/assets/{move-BT38p0kT.js → move-DSWGoQa8.js} +0 -0
- /package/dist/web/assets/{narrat-55QZ_kOG.js → narrat-D9TnZSSu.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-Bg8peyqg.js → nextflow-groovy-BbL-8PwD.js} +0 -0
- /package/dist/web/assets/{night-owl-DzS77iPK.js → night-owl-CZitKm5d.js} +0 -0
- /package/dist/web/assets/{night-owl-light-DER7fGnx.js → night-owl-light-DJVX1Pno.js} +0 -0
- /package/dist/web/assets/{nix-BSI8YesN.js → nix-R6hr3oln.js} +0 -0
- /package/dist/web/assets/{nord-9bn7iNWo.js → nord-ChGHya3l.js} +0 -0
- /package/dist/web/assets/{nushell-qDVZn_lw.js → nushell-QQWB-GWx.js} +0 -0
- /package/dist/web/assets/{objective-c-Blf-ATos.js → objective-c-C0pZ6y73.js} +0 -0
- /package/dist/web/assets/{objective-cpp-Bz8bAq47.js → objective-cpp-BdzVM31a.js} +0 -0
- /package/dist/web/assets/{ocaml-B9heAsc3.js → ocaml-D4KktNlS.js} +0 -0
- /package/dist/web/assets/{odin-BaFYnOwO.js → odin-Y-fypmGT.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-D6B_hSe-.js → one-dark-pro-BwecIxQL.js} +0 -0
- /package/dist/web/assets/{one-light-Ba-f0wyk.js → one-light-Cz7UVzLU.js} +0 -0
- /package/dist/web/assets/{openscad-UvYyL2MK.js → openscad-DtRe3qYD.js} +0 -0
- /package/dist/web/assets/{pascal-B4-qvEoG.js → pascal-Ck6I1fGG.js} +0 -0
- /package/dist/web/assets/{pkl-B146nqGx.js → pkl-DdFK94VW.js} +0 -0
- /package/dist/web/assets/{plastic-L38t-Id3.js → plastic-BQSAzZwR.js} +0 -0
- /package/dist/web/assets/{play-CpCAoQRZ.js → play-CJlWpVMw.js} +0 -0
- /package/dist/web/assets/{plsql-ItOG5Cca.js → plsql-DjXg_CP0.js} +0 -0
- /package/dist/web/assets/{po-imiCTaNX.js → po-Bw2SuM4r.js} +0 -0
- /package/dist/web/assets/{poimandres-Bu6sIba9.js → poimandres-YMK6c5VN.js} +0 -0
- /package/dist/web/assets/{polar-CTeGoFxV.js → polar-BMu17-O0.js} +0 -0
- /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DSGdlXrJ.js} +0 -0
- /package/dist/web/assets/{postcss-jvFe6GNE.js → postcss-DWib10RZ.js} +0 -0
- /package/dist/web/assets/{powerquery-D9Dw1hJp.js → powerquery-Dht4CK0b.js} +0 -0
- /package/dist/web/assets/{powershell-CZqvYQtg.js → powershell-DvrCg8ji.js} +0 -0
- /package/dist/web/assets/{prisma-B4D2nO_7.js → prisma-DbodYvCM.js} +0 -0
- /package/dist/web/assets/{prolog-CQbyUcCF.js → prolog-DZ-m6ysB.js} +0 -0
- /package/dist/web/assets/{proto-Cwpo0cW1.js → proto-HHpG3tGO.js} +0 -0
- /package/dist/web/assets/{puppet-CKxCwBod.js → puppet-BljHe_75.js} +0 -0
- /package/dist/web/assets/{purescript-B-MyXMMp.js → purescript-DzCiFR0B.js} +0 -0
- /package/dist/web/assets/{python-D6QbSR2S.js → python-B5BQ27cC.js} +0 -0
- /package/dist/web/assets/{qmldir-BpTSVcw_.js → qmldir-CYlcXrnn.js} +0 -0
- /package/dist/web/assets/{qss-DBc4gbpe.js → qss-Ce7T_TEg.js} +0 -0
- /package/dist/web/assets/{r-i9jMMi0s.js → r-Bku-2Jp_.js} +0 -0
- /package/dist/web/assets/{racket-BWNaxU7C.js → racket-DtfSL3WR.js} +0 -0
- /package/dist/web/assets/{raku-fqTZLn7L.js → raku-CnDLsBJr.js} +0 -0
- /package/dist/web/assets/{react-X4jUntEf.js → react-Dfio3PfK.js} +0 -0
- /package/dist/web/assets/{red-D-JEcuzA.js → red-vbqcE9b-.js} +0 -0
- /package/dist/web/assets/{refresh-cw-B9iv7csb.js → refresh-cw-84mz8aOE.js} +0 -0
- /package/dist/web/assets/{reg-BbirlU1S.js → reg-DIDLT8-A.js} +0 -0
- /package/dist/web/assets/{regexp-D8VTFrtg.js → regexp-CB4h-VPu.js} +0 -0
- /package/dist/web/assets/{rel-ByQGKiNM.js → rel-CiQEnWXa.js} +0 -0
- /package/dist/web/assets/{resolve-theme-Cqb9zWYc.js → resolve-theme-DPTQHAy1.js} +0 -0
- /package/dist/web/assets/{riscv-B5nqCfLE.js → riscv-R78iq5Rf.js} +0 -0
- /package/dist/web/assets/{ron-DOVh_iZR.js → ron-DhYENxc4.js} +0 -0
- /package/dist/web/assets/{rose-pine-jp51ZUzi.js → rose-pine-5wBeyjfJ.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-B7hkmmWy.js → rose-pine-dawn-FpKtPCuH.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Ff063yLj.js → rose-pine-moon-BwcIYc7C.js} +0 -0
- /package/dist/web/assets/{rosmsg-Cyk7Il8z.js → rosmsg-DWIGAYMl.js} +0 -0
- /package/dist/web/assets/{rotate-ccw-DnuPzJRE.js → rotate-ccw-CNTk2J7l.js} +0 -0
- /package/dist/web/assets/{rust-DRNM5epF.js → rust-DU_ofKgt.js} +0 -0
- /package/dist/web/assets/{sass-D_cB0-M1.js → sass-U9SUuYEy.js} +0 -0
- /package/dist/web/assets/{save-DxonD4y1.js → save-CtW9ojKk.js} +0 -0
- /package/dist/web/assets/{scala-Crl-1kKS.js → scala-op4gjA6j.js} +0 -0
- /package/dist/web/assets/{scheme-DCaWyEJ0.js → scheme-tEjkR8rL.js} +0 -0
- /package/dist/web/assets/{sdbl-BbXNqwtX.js → sdbl-C6SSRmZg.js} +0 -0
- /package/dist/web/assets/{send-B-t5Mz26.js → send-C_sLFt56.js} +0 -0
- /package/dist/web/assets/{shallow-4zAfs2KW.js → shallow-C1YoJQA-.js} +0 -0
- /package/dist/web/assets/{shellscript-BAgksBhW.js → shellscript-Bf2KiwQj.js} +0 -0
- /package/dist/web/assets/{shield-check-CdTiK1j2.js → shield-check-Du6HKYn9.js} +0 -0
- /package/dist/web/assets/{shield-off-DuetkXKM.js → shield-off-CWKb1MQL.js} +0 -0
- /package/dist/web/assets/{slack-dark-GB-ElUW_.js → slack-dark-oGgCey8r.js} +0 -0
- /package/dist/web/assets/{slack-ochin-tU6CFD3S.js → slack-ochin-CemlB8cE.js} +0 -0
- /package/dist/web/assets/{smalltalk-DSVGUbtL.js → smalltalk-CTE-6jrZ.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DZhi3PoS.js → snazzy-light-DoEIGnmz.js} +0 -0
- /package/dist/web/assets/{solarized-dark-D6AMCco7.js → solarized-dark-CStiJyBg.js} +0 -0
- /package/dist/web/assets/{solarized-light-DepuTWSE.js → solarized-light-CfuNycS9.js} +0 -0
- /package/dist/web/assets/{solidity-CsxN618l.js → solidity-DL6sO5rd.js} +0 -0
- /package/dist/web/assets/{splunk-CF1m0Kkv.js → splunk-yK4ZUFBB.js} +0 -0
- /package/dist/web/assets/{sql-DvyMDgrB.js → sql-Lw03eoN8.js} +0 -0
- /package/dist/web/assets/{ssh-config-a8sDo-Q8.js → ssh-config-DeRp25TR.js} +0 -0
- /package/dist/web/assets/{stylus-DCJ-6AjT.js → stylus-D8OMsPh-.js} +0 -0
- /package/dist/web/assets/{swift-c-ZFxyPP.js → swift-nsl9stPV.js} +0 -0
- /package/dist/web/assets/{synthwave-84-9NPgBcuv.js → synthwave-84-exAgsgpf.js} +0 -0
- /package/dist/web/assets/{system-verilog-DPD02IOo.js → system-verilog-DBWf00T6.js} +0 -0
- /package/dist/web/assets/{systemd-BUr5HRtU.js → systemd-nhu27Dw5.js} +0 -0
- /package/dist/web/assets/{table-FLU8ei8b.js → table-GAR4upb6.js} +0 -0
- /package/dist/web/assets/{talonscript-BNBQV5YJ.js → talonscript-CNOEcGDc.js} +0 -0
- /package/dist/web/assets/{tasl-BETjpSyu.js → tasl-CjYZenSH.js} +0 -0
- /package/dist/web/assets/{tcl-Clp_AZiC.js → tcl-BVA09RDz.js} +0 -0
- /package/dist/web/assets/{terraform-B0kZ0lim.js → terraform-C5EloH1q.js} +0 -0
- /package/dist/web/assets/{text-wrap-B05VGRa-.js → text-wrap-D71Jqz5U.js} +0 -0
- /package/dist/web/assets/{tokyo-night-oOJ0a7Dk.js → tokyo-night-D764DdhO.js} +0 -0
- /package/dist/web/assets/{toml-DOPCSumy.js → toml-CCDYblSB.js} +0 -0
- /package/dist/web/assets/{trash-2-CTXlTflF.js → trash-2-C08fhIg1.js} +0 -0
- /package/dist/web/assets/{triangle-alert-BRq0VAfd.js → triangle-alert-85BQgs3T.js} +0 -0
- /package/dist/web/assets/{tsv-D6qUGf84.js → tsv-BZ4sA05e.js} +0 -0
- /package/dist/web/assets/{tsx-B6qYhT6O.js → tsx-DPLBzAt5.js} +0 -0
- /package/dist/web/assets/{turtle-C0Rjzw73.js → turtle-CwGHCq40.js} +0 -0
- /package/dist/web/assets/{typescript-BiydShEu.js → typescript-CUH0vBAl.js} +0 -0
- /package/dist/web/assets/{typespec-DjmF6Aww.js → typespec-Bq31Vq4u.js} +0 -0
- /package/dist/web/assets/{typst-CPuscfcL.js → typst-MD9BR9W5.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-b94q8RvF.js → use-is-mobile-DUuki97q.js} +0 -0
- /package/dist/web/assets/{utils-C9OjCrou.js → utils-u2fbYPrY.js} +0 -0
- /package/dist/web/assets/{v-DE4vpLLQ.js → v-DZV7IxIT.js} +0 -0
- /package/dist/web/assets/{vala-CmUF2pS5.js → vala-urd1zi9S.js} +0 -0
- /package/dist/web/assets/{vb-BvcRoX4b.js → vb-DIZU3zbJ.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-K8kpLxdb.js → vendor-xterm-DCj0zgSC.js} +0 -0
- /package/dist/web/assets/{verilog-BGIzyH3R.js → verilog-Ndz_ciTA.js} +0 -0
- /package/dist/web/assets/{vesper-CG7IrHQQ.js → vesper-C86M7LJp.js} +0 -0
- /package/dist/web/assets/{vhdl-DMSumomw.js → vhdl-_klwDUjd.js} +0 -0
- /package/dist/web/assets/{viml-Drt7AEmU.js → viml-DzRXWoQh.js} +0 -0
- /package/dist/web/assets/{vitesse-black-DmsjfN0b.js → vitesse-black-TWZ20_3U.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-Dl55_WeJ.js → vitesse-dark-Dl7R6fy9.js} +0 -0
- /package/dist/web/assets/{vitesse-light-Crbv9aGg.js → vitesse-light-Cs0Dt2_n.js} +0 -0
- /package/dist/web/assets/{vyper-CB7psEJj.js → vyper-qnCVj8ia.js} +0 -0
- /package/dist/web/assets/{wasm-Dck8KPgP.js → wasm-Ckx9jBPe.js} +0 -0
- /package/dist/web/assets/{wasm-BY1tuM5m.js → wasm-Dy-SJ3W9.js} +0 -0
- /package/dist/web/assets/{wenyan-DhewQ68b.js → wenyan-DTEy2Lx-.js} +0 -0
- /package/dist/web/assets/{wgsl-DlmiP456.js → wgsl-DC7n63Sy.js} +0 -0
- /package/dist/web/assets/{wikitext-cQCnqNgE.js → wikitext-1zFoZiW6.js} +0 -0
- /package/dist/web/assets/{wit-S5MaCngB.js → wit-DtTfCRph.js} +0 -0
- /package/dist/web/assets/{wolfram-Bh8n_9zr.js → wolfram-B2Bgu1KL.js} +0 -0
- /package/dist/web/assets/{x-YDu4m2Iq.js → x-CeN0Qybc.js} +0 -0
- /package/dist/web/assets/{yaml-BoK_17l9.js → yaml-B-Jxk0IQ.js} +0 -0
- /package/dist/web/assets/{zenscript-DB3ZFwNJ.js → zenscript-BWRfvNER.js} +0 -0
- /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
|
|
12
|
-
*
|
|
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
|
|
24
|
-
* with "-"
|
|
25
|
-
*
|
|
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(/[
|
|
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
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
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
|
-
|
|
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>).
|
|
128
|
-
*
|
|
129
|
-
*
|
|
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
|
|
133
|
-
if (
|
|
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
|
|
139
|
-
if (!
|
|
140
|
-
const
|
|
141
|
-
|
|
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
|
|
17
|
-
*
|
|
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
|
-
*
|
|
20
|
-
*
|
|
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
|
-
|
|
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 =
|
|
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();
|