@hienlh/ppm 0.18.7 → 0.18.9
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/.spike/s.err +63 -0
- package/.spike/tunnel.err +42 -0
- package/.spike/w.err +96 -0
- package/CHANGELOG.md +16 -0
- package/CLAUDE.md +1 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +5 -3
- package/dist/web/assets/{ai-resource-editor-DZQUyrsU.js → ai-resource-editor-DFCuNe-P.js} +1 -1
- package/dist/web/assets/{angular-html-jfMqUuRd.js → angular-html-Ctz0zN01.js} +1 -1
- package/dist/web/assets/{angular-ts-wytn9PMm.js → angular-ts-QXHpgB1C.js} +1 -1
- package/dist/web/assets/{api-settings-DlcoR5QT.js → api-settings-nblja3Rk.js} +1 -1
- package/dist/web/assets/{apl-i-ahKx6O.js → apl-b9KVk-O4.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-C7ra20CE.js +1 -0
- package/dist/web/assets/{astro-DVBCJfG6.js → astro-BlFEeGYr.js} +1 -1
- package/dist/web/assets/{audio-preview-GGksv6U8.js → audio-preview-CZLmhf__.js} +1 -1
- package/dist/web/assets/{blade-BHlL8SqU.js → blade-CGkj2MUI.js} +1 -1
- package/dist/web/assets/{bsl-D-UOfR1F.js → bsl-CVsbb6ap.js} +1 -1
- package/dist/web/assets/button-BA_fJOtX.js +1 -0
- package/dist/web/assets/{cairo-Bn6rucli.js → cairo-BbIHl2iE.js} +1 -1
- package/dist/web/assets/chat-tab-flMPLoKb.js +15 -0
- package/dist/web/assets/circle-hjUGru6R.js +1 -0
- package/dist/web/assets/circle-question-mark-CzR7YRc-.js +1 -0
- package/dist/web/assets/{cobol-Byn9FYKu.js → cobol-BWSGjmWb.js} +1 -1
- package/dist/web/assets/code-editor-C2VQbXzw.js +10 -0
- package/dist/web/assets/{coffee-DjBl4gm-.js → coffee-CWGNOxds.js} +1 -1
- package/dist/web/assets/{conflict-editor-Dg_JIcKq.js → conflict-editor-CdH_h0vD.js} +3 -3
- package/dist/web/assets/{context-menu-DkQdt6YA.js → context-menu-Cq0u3SK9.js} +1 -1
- package/dist/web/assets/{cpp-CKzic-3E.js → cpp-DHGV24J2.js} +1 -1
- package/dist/web/assets/{crystal-Bkg-Ly_g.js → crystal-Vlwf2UMP.js} +1 -1
- package/dist/web/assets/{csv-preview-DsJ_a2Op.js → csv-preview-CY_454vd.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-B1mTl3KS.js → data-grid-overlay-editor-D9il5DZS.js} +1 -1
- package/dist/web/assets/{database-viewer-CzjnrnMz.js → database-viewer-BCZOTrPT.js} +1 -1
- package/dist/web/assets/{diff-viewer-DUNkO-ll.js → diff-viewer-BwfMN-nW.js} +1 -1
- package/dist/web/assets/{docx-preview-DujpUDkE.js → docx-preview-D3Sdh_aT.js} +1 -1
- package/dist/web/assets/{edge-DVbZnd3Y.js → edge-Bhqk64zS.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-BivQwZMD.js → edit-diff-preview-gq6ALf2B.js} +1 -1
- package/dist/web/assets/{elixir-Dwi8Wu7W.js → elixir-D466WCFm.js} +1 -1
- package/dist/web/assets/{elm-DtIv-HXJ.js → elm-Bfs2qNPR.js} +1 -1
- package/dist/web/assets/{erb-D3HEDz1x.js → erb-WI7jUaB6.js} +1 -1
- package/dist/web/assets/{erlang-2MjTCzGz.js → erlang-BCBe8nCc.js} +1 -1
- package/dist/web/assets/{esm-CZ8B8Ba2.js → esm-CZs9WVDO.js} +1 -1
- package/dist/web/assets/{explorer-body-DSDJUQs7.js → explorer-body-BKOMM566.js} +2 -2
- package/dist/web/assets/{explorer-window-content-CWYBapkj.js → explorer-window-content-Bbzijk8t.js} +1 -1
- package/dist/web/assets/{extension-webview-DBTFznQp.js → extension-webview-CuOQrHIS.js} +1 -1
- package/dist/web/assets/format-bytes-Dk7B73B7.js +1 -0
- package/dist/web/assets/{fortran-fixed-form-BLyAtyPM.js → fortran-fixed-form-CXLIv8Ox.js} +1 -1
- package/dist/web/assets/{fsharp-Bxbn_gbr.js → fsharp-CZsYmiw9.js} +1 -1
- package/dist/web/assets/{gdresource-sFMY4D2Q.js → gdresource-BGRvoWfC.js} +1 -1
- package/dist/web/assets/{git-commit-BzB9Lio5.js → git-commit-DH6mY3Km.js} +1 -1
- package/dist/web/assets/{git-log-panel-D8ZQdoYl.js → git-log-panel-DcV7PhqW.js} +1 -1
- package/dist/web/assets/{git-rebase-CKSVd848.js → git-rebase-C5Gql6Ma.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-D4hYLNJU.js +1 -0
- package/dist/web/assets/{glide-data-grid-uIjdTT7c.js → glide-data-grid-Cehfgrzc.js} +7 -7
- package/dist/web/assets/{glimmer-js-BY8aExAJ.js → glimmer-js-1-6JMJlj.js} +1 -1
- package/dist/web/assets/{glimmer-ts-Bak54dBX.js → glimmer-ts-C_Njj0nK.js} +1 -1
- package/dist/web/assets/{glsl-nR2eaJQe.js → glsl-DbkxE1TN.js} +1 -1
- package/dist/web/assets/{graphql-CGPPFAU6.js → graphql-BK75fiXl.js} +1 -1
- package/dist/web/assets/group-chat-tab-BVPYxjz-.js +1 -0
- package/dist/web/assets/{hack-CXIMX-Ol.js → hack-DG151vit.js} +1 -1
- package/dist/web/assets/{haml-tPY-9Pfm.js → haml-DKeIPQWM.js} +1 -1
- package/dist/web/assets/{handlebars-BuW1LT7T.js → handlebars-398uCI13.js} +1 -1
- package/dist/web/assets/{html-bgVcwZ-t.js → html-NLWdst5q.js} +1 -1
- package/dist/web/assets/{html-derivative-D6-IxTgn.js → html-derivative-CJf_Le2z.js} +1 -1
- package/dist/web/assets/{http-CJ1zyB-h.js → http-G1K2FozA.js} +1 -1
- package/dist/web/assets/{hurl-CmoT7l3T.js → hurl-WDC6sSBz.js} +1 -1
- package/dist/web/assets/{hxml-CztOsL8o.js → hxml-Dc3jBXuf.js} +1 -1
- package/dist/web/assets/{image-preview-B0VXSpYu.js → image-preview-D-Ok10eZ.js} +1 -1
- package/dist/web/assets/index-CerSxGdf.js +52 -0
- package/dist/web/assets/index-DRA_OpWY.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-lRlrTlkb.js +1 -0
- package/dist/web/assets/{jinja-B1pq3jJq.js → jinja-CWsQj2y0.js} +1 -1
- package/dist/web/assets/{jison-D8B5pCwA.js → jison-CciJg2NW.js} +1 -1
- package/dist/web/assets/{julia-D0K3bwpU.js → julia-IOQNJAB7.js} +1 -1
- package/dist/web/assets/{just-B9Jlddu-.js → just-BGdHchwS.js} +1 -1
- package/dist/web/assets/{keybindings-store-FyolpNND.js → keybindings-store-aIY40k2o.js} +1 -1
- package/dist/web/assets/{latex-BPJSSE6j.js → latex-D6TOl-L2.js} +1 -1
- package/dist/web/assets/{liquid-lv10fo4U.js → liquid-CVSvu_Wn.js} +1 -1
- package/dist/web/assets/{lua-PNuaIBEF.js → lua-CcKiU6HW.js} +1 -1
- package/dist/web/assets/markdown-renderer-I89kyQhe.js +1 -0
- package/dist/web/assets/{markdown-renderer-BaBQ6Wm6.js → markdown-renderer-pVzBdkpl.js} +3 -3
- package/dist/web/assets/{marko-Du3frD6X.js → marko--UOicMGo.js} +1 -1
- package/dist/web/assets/{mdc-BeDVcFRl.js → mdc-D690aU2C.js} +1 -1
- package/dist/web/assets/media-url-CylYQld1.js +1 -0
- package/dist/web/assets/{mobile-explorer-sheet-Zxjl-e4c.js → mobile-explorer-sheet-5Uj9ZMM9.js} +1 -1
- package/dist/web/assets/{nextflow-DkcXP71s.js → nextflow-DR8PhpF8.js} +1 -1
- package/dist/web/assets/{nginx-C5n8N_bt.js → nginx-Qb9pUk1g.js} +1 -1
- package/dist/web/assets/{nim-CIhyfs-A.js → nim-DLa8R3NS.js} +1 -1
- package/dist/web/assets/{notification-store-DnGpxgkF.js → notification-store-DJ-OtAG3.js} +1 -1
- package/dist/web/assets/{number-overlay-editor-NgOsWJ7x.js → number-overlay-editor-D9SkFg1Y.js} +1 -1
- package/dist/web/assets/overview-panel-Cgi3UPY1.js +1 -0
- package/dist/web/assets/packet-RMMSAZCW-BIhk1mvp.js +1 -0
- package/dist/web/assets/{pdf-preview-xzAA4-kD.js → pdf-preview-Di5xHFyT.js} +1 -1
- package/dist/web/assets/{perl-Cf31sZYE.js → perl-AMNGIneF.js} +1 -1
- package/dist/web/assets/{php-Dzl_MDIW.js → php-CDsOOxXn.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-SLS6i76l.js +1 -0
- package/dist/web/assets/{postgres-viewer-Dkec6GyK.js → postgres-viewer-CR6VpTrX.js} +3 -3
- package/dist/web/assets/process-table-DL6shbzt.js +1 -0
- package/dist/web/assets/{pug-CxSYHrB3.js → pug-C2kdkhQ6.js} +1 -1
- package/dist/web/assets/{qml-DlEjGhwN.js → qml-DRDkUp75.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-JJ6O1Er_.js +1 -0
- package/dist/web/assets/{razor-KVt5uW14.js → razor-wY_6wUeC.js} +1 -1
- package/dist/web/assets/{rst-D5BwNTnw.js → rst-BRpofyNs.js} +1 -1
- package/dist/web/assets/{ruby-D5fejQwd.js → ruby-ieATBhWm.js} +1 -1
- package/dist/web/assets/{sas-Bj7B1CB2.js → sas-CoOOEbOO.js} +1 -1
- package/dist/web/assets/{scss-BB0CUoCL.js → scss-_nWV0bsb.js} +1 -1
- package/dist/web/assets/{settings-store-C9YT1Hir.js → settings-store-C20lqh0G.js} +2 -2
- package/dist/web/assets/settings-tab-jbiQwozX.js +1 -0
- package/dist/web/assets/{shaderlab-BdjEZUMc.js → shaderlab-XkqfIfO8.js} +1 -1
- package/dist/web/assets/{shellsession-Bp1OA1rR.js → shellsession-BKBIqTlM.js} +1 -1
- package/dist/web/assets/{soy-DlYz3wsC.js → soy-oVDnPzzh.js} +1 -1
- package/dist/web/assets/{sparql-CdfII5Jd.js → sparql-N2VclEsF.js} +1 -1
- package/dist/web/assets/{sql-query-editor-Bz1XrUqI.js → sql-query-editor-BPYsJDsn.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-D5-NZw6b.js → sqlite-viewer-Bq9ZJA2y.js} +1 -1
- package/dist/web/assets/{stata-BvUA6GUW.js → stata-BuuClVDg.js} +1 -1
- package/dist/web/assets/{surrealql--DLNbTyI.js → surrealql-DE5_63MS.js} +1 -1
- package/dist/web/assets/{svelte-CBfSsG4n.js → svelte--YWjlOrK.js} +1 -1
- package/dist/web/assets/system-monitor-body-CKEYWyN9.js +2 -0
- package/dist/web/assets/system-monitor-tab-WjhFFHyM.js +1 -0
- package/dist/web/assets/system-monitor-window-content-CMiISeoC.js +1 -0
- package/dist/web/assets/team-member-sheet-oY5EkH53.js +2 -0
- package/dist/web/assets/{team-member-window-content-DSpZ2zhL.js → team-member-window-content-yCBeI_5X.js} +1 -1
- package/dist/web/assets/{templ-Dv8zpKZW.js → templ-MlpvJx4a.js} +1 -1
- package/dist/web/assets/{terminal-tab-DXazqejW.js → terminal-tab-BxHtMR7h.js} +3 -3
- package/dist/web/assets/{tex-CsrOiqh1.js → tex-0loAoXJG.js} +1 -1
- package/dist/web/assets/{tool-cards-Bgp8iceS.js → tool-cards-BcGrk1zk.js} +5 -5
- package/dist/web/assets/treemap-KZPCXAKY-Be3KNmns.js +1 -0
- package/dist/web/assets/{ts-tags-3T98k4EJ.js → ts-tags-pt-Ug0hK.js} +1 -1
- package/dist/web/assets/{twig-u6QZ45FH.js → twig-DOePpLOL.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-CpF3A7JL.js → use-monaco-theme-CGucu3fb.js} +1 -1
- package/dist/web/assets/{use-open-team-member-CUvV02fA.js → use-open-team-member-DdrRXj-M.js} +1 -1
- package/dist/web/assets/{use-websocket-TnL2I1io.js → use-websocket-C-Rxv8JN.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-Bg4vE8QA.js → vendor-mermaid-DrwG0NYt.js} +1 -1
- package/dist/web/assets/video-preview-DYAbbmd0.js +1 -0
- package/dist/web/assets/{vue-CxIsxtwn.js → vue-D6f-rprj.js} +1 -1
- package/dist/web/assets/{vue-html-BGL5qnuH.js → vue-html-DEYRkS1V.js} +1 -1
- package/dist/web/assets/{vue-vine-DHPh_gMr.js → vue-vine-CxSG2ojf.js} +1 -1
- package/dist/web/assets/{xml-C-vM2m7J.js → xml-DhrxbATj.js} +1 -1
- package/dist/web/assets/{xsl-DvTXNwuO.js → xsl-CU0ZndUO.js} +1 -1
- package/dist/web/index.html +18 -17
- package/dist/web/sw.js +1 -1
- package/docs/system-architecture.md +2 -2
- package/package.json +1 -1
- package/src/server/helpers/media-route-handlers.ts +22 -3
- package/src/server/index.ts +2 -8
- package/src/server/routes/files.ts +5 -2
- package/src/server/routes/fs-browse.ts +5 -2
- package/src/server/routes/resources.ts +119 -73
- package/src/services/autostart-generator.ts +79 -7
- package/src/services/autostart-register.ts +15 -2
- package/src/services/fs-path-guard.service.ts +17 -1
- package/src/services/media-transcode/transcode-stream.ts +27 -5
- package/src/services/proc-table-linux.ts +4 -0
- package/src/services/redact-secrets.ts +33 -0
- package/src/services/system-metrics/cpu-memory-collector.ts +101 -0
- package/src/services/system-metrics/disk-net-collector-darwin.ts +75 -0
- package/src/services/system-metrics/disk-net-collector-linux.ts +88 -0
- package/src/services/system-metrics/disk-net-collector-windows.ts +57 -0
- package/src/services/system-metrics/gpu-collector-nvidia.ts +78 -0
- package/src/services/system-metrics/gpu-process-memory-nvidia.ts +66 -0
- package/src/services/system-metrics/gpu-process-usage-windows.ts +138 -0
- package/src/services/system-metrics/kill-executor.ts +85 -0
- package/src/services/system-metrics/kill-guard.ts +124 -0
- package/src/services/system-metrics/kill-identity-resolver.ts +72 -0
- package/src/services/system-metrics/kill-request-handler.ts +79 -0
- package/src/services/system-metrics/metrics-collector-lock.ts +36 -0
- package/src/services/system-metrics/metrics-poll-scheduler.ts +83 -0
- package/src/services/system-metrics/metrics-subscriber-registry.ts +85 -0
- package/src/services/system-metrics/powershell-session-bootstrap.ts +48 -0
- package/src/services/system-metrics/powershell-session.ts +182 -0
- package/src/services/system-metrics/ppm-process-ownership.ts +34 -0
- package/src/services/system-metrics/ppm-protected-pids.ts +84 -0
- package/src/services/system-metrics/process-collector-darwin.ts +141 -0
- package/src/services/system-metrics/process-collector-linux.ts +85 -0
- package/src/services/system-metrics/process-collector-types.ts +80 -0
- package/src/services/system-metrics/process-collector-windows-parse.ts +153 -0
- package/src/services/system-metrics/process-collector-windows.ts +136 -0
- package/src/services/system-metrics/process-cpu-delta.ts +61 -0
- package/src/services/system-metrics/process-grouping.ts +142 -0
- package/src/services/system-metrics/process-io-delta.ts +84 -0
- package/src/services/system-metrics/process-io-linux.ts +46 -0
- package/src/services/system-metrics/process-net-collector-darwin.ts +103 -0
- package/src/services/system-metrics/process-rows-builder.ts +111 -0
- package/src/services/system-metrics/rate-delta.ts +47 -0
- package/src/services/system-metrics/system-metrics-platform.ts +51 -0
- package/src/services/system-metrics/system-metrics-tick.ts +178 -0
- package/src/services/system-metrics/system-metrics.service.ts +212 -0
- package/src/types/system-metrics.ts +199 -0
- package/src/web/components/editor/video-player/video-player.tsx +54 -22
- package/src/web/components/floating-window/window-content-registry.ts +2 -0
- package/src/web/components/floating-window/window-persistence.ts +1 -1
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/system/build-kill-request.ts +9 -0
- package/src/web/components/system/chart-scale.ts +28 -0
- package/src/web/components/system/cpu-core-bars.tsx +33 -0
- package/src/web/components/system/kill-confirm-dialog.tsx +89 -0
- package/src/web/components/system/metric-chart-canvas.tsx +108 -0
- package/src/web/components/system/overview-cards/cpu-card.tsx +39 -0
- package/src/web/components/system/overview-cards/disk-card.tsx +41 -0
- package/src/web/components/system/overview-cards/gpu-card.tsx +33 -0
- package/src/web/components/system/overview-cards/mem-card.tsx +32 -0
- package/src/web/components/system/overview-cards/net-card.tsx +41 -0
- package/src/web/components/system/overview-panel.tsx +97 -0
- package/src/web/components/system/process-columns-grid.ts +93 -0
- package/src/web/components/system/process-group-row.tsx +71 -0
- package/src/web/components/system/process-row-format.ts +56 -0
- package/src/web/components/system/process-row.tsx +82 -0
- package/src/web/components/system/process-table-model.ts +127 -0
- package/src/web/components/system/process-table-sort.ts +90 -0
- package/src/web/components/system/process-table-toolbar.tsx +190 -0
- package/src/web/components/system/process-table-totals.ts +49 -0
- package/src/web/components/system/process-table.tsx +187 -0
- package/src/web/components/system/resolve-open-system-monitor-action.ts +29 -0
- package/src/web/components/system/resource-status-bar.tsx +17 -16
- package/src/web/components/system/sortable-header.tsx +65 -0
- package/src/web/components/system/sparkline-canvas.tsx +8 -9
- package/src/web/components/system/system-monitor-body.tsx +146 -0
- package/src/web/components/system/system-monitor-tab.tsx +2 -185
- package/src/web/components/system/system-monitor-window-content.tsx +18 -0
- package/src/web/components/system/use-open-system-monitor.ts +34 -0
- package/src/web/hooks/resource-monitor-lease.ts +55 -0
- package/src/web/hooks/use-element-width.ts +27 -0
- package/src/web/hooks/use-resource-monitor.ts +113 -35
- package/src/web/lib/format-bytes.ts +16 -0
- package/src/web/lib/media-url.ts +30 -4
- package/.release-run.sh +0 -5
- package/dist/web/assets/architecture-PBZL5I3N-Bs7wzj-W.js +0 -1
- package/dist/web/assets/arrow-right-DubGFkN8.js +0 -1
- package/dist/web/assets/button-B4MWPjWj.js +0 -1
- package/dist/web/assets/chat-tab-Xd2WMjdX.js +0 -15
- package/dist/web/assets/circle-question-mark-CENpx4oA.js +0 -1
- package/dist/web/assets/code-editor-DqXYJWAB.js +0 -10
- package/dist/web/assets/gitGraph-HDMCJU4V-D7i54kbM.js +0 -1
- package/dist/web/assets/group-chat-tab-B3sryFdA.js +0 -1
- package/dist/web/assets/index-DgbOF3xa.js +0 -52
- package/dist/web/assets/index-oxsyfDa0.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BV41AzR9.js +0 -1
- package/dist/web/assets/markdown-renderer-feiuhJ9n.js +0 -1
- package/dist/web/assets/media-url-CUpN5vI1.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-CaJoP44m.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-DSGZmNw2.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-Be4VUTs0.js +0 -1
- package/dist/web/assets/settings-tab-Bd8e6iqY.js +0 -1
- package/dist/web/assets/system-monitor-tab-A0jvt5Qw.js +0 -1
- package/dist/web/assets/team-member-sheet-5Oaqm8I_.js +0 -2
- package/dist/web/assets/treemap-KZPCXAKY-Cu2zgbxU.js +0 -1
- package/dist/web/assets/video-preview-CwtLPELG.js +0 -1
- package/src/services/resource-monitor-utils.ts +0 -141
- package/src/services/resource-monitor.service.ts +0 -147
- package/src/web/components/system/system-monitor-group-row.tsx +0 -153
- /package/dist/web/assets/{abap-C1K0-wzc.js → abap-MRbmsXOs.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CJhQVjLA.js → actionscript-3-BDDHKqa4.js} +0 -0
- /package/dist/web/assets/{ada-Bt8r9fME.js → ada-DpuV9KwV.js} +0 -0
- /package/dist/web/assets/{andromeeda-DF1I8qi3.js → andromeeda-D6qUCKJo.js} +0 -0
- /package/dist/web/assets/{apache-CnlleE6P.js → apache-CX1NU_Tl.js} +0 -0
- /package/dist/web/assets/{apex-j0C6Xmk6.js → apex-B4-aoQ3K.js} +0 -0
- /package/dist/web/assets/{api-settings-DfNRPr1q.js → api-settings-kQHV6pYZ.js} +0 -0
- /package/dist/web/assets/{api-themes-8WTH6KpV.js → api-themes-CrK0tkW_.js} +0 -0
- /package/dist/web/assets/{applescript-DO7AnO8U.js → applescript-Dm9NI6KR.js} +0 -0
- /package/dist/web/assets/{apply-theme-CNj1iA-v.js → apply-theme-BH_d4amk.js} +0 -0
- /package/dist/web/assets/{ara-i83AydZj.js → ara-BGN8GAYn.js} +0 -0
- /package/dist/web/assets/{asciidoc-DbBA6-sF.js → asciidoc-s_yVuR3t.js} +0 -0
- /package/dist/web/assets/{asm-B5S47Rgr.js → asm-BzQdSvTe.js} +0 -0
- /package/dist/web/assets/{aurora-x-De_IaSKw.js → aurora-x-BJTLETG_.js} +0 -0
- /package/dist/web/assets/{awk-CfyLHXqS.js → awk-BQEGWxvX.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DTLdZ1Si.js → ayu-dark-CpTORmLc.js} +0 -0
- /package/dist/web/assets/{ayu-light-DTOfkzhu.js → ayu-light-D8PVj-e6.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-DsAECCMv.js → ayu-mirage-DyElQHtm.js} +0 -0
- /package/dist/web/assets/{ballerina-CumGp9A7.js → ballerina-C82NkRtj.js} +0 -0
- /package/dist/web/assets/{bat-Ca2SBbYN.js → bat-0X7DtGI-.js} +0 -0
- /package/dist/web/assets/{beancount-CqyXBwwF.js → beancount-Xgvy6YRl.js} +0 -0
- /package/dist/web/assets/{berry-yl-kJr_H.js → berry-CCT2pZcQ.js} +0 -0
- /package/dist/web/assets/{bibtex-DoFFszjV.js → bibtex-CpXgZ9M_.js} +0 -0
- /package/dist/web/assets/{bicep-CWPi4nfK.js → bicep-fkeSA62l.js} +0 -0
- /package/dist/web/assets/{bird2-DXew-ut0.js → bird2-Co7SeAI8.js} +0 -0
- /package/dist/web/assets/{c-clbySDvd.js → c-BpmrTvJh.js} +0 -0
- /package/dist/web/assets/{c3-B65jvSM1.js → c3-BZ_Z0RrR.js} +0 -0
- /package/dist/web/assets/{cadence-Cm8tQTHN.js → cadence-B0GBC8OF.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-DXGOSUQc.js → can-open-in-ppm-omBzUBBq.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DLeGslh_.js → catppuccin-frappe-DIt_xR7m.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CR5GeHws.js → catppuccin-latte-Dw40AcqA.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-p-XgjxhP.js → catppuccin-macchiato-BJ7DfVEF.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-B8QOAZLl.js → catppuccin-mocha-NI2lfXzU.js} +0 -0
- /package/dist/web/assets/{clarity-DPulbvKH.js → clarity-F39L0xMJ.js} +0 -0
- /package/dist/web/assets/{clock-D493FU_D.js → clock-CvhF4DJP.js} +0 -0
- /package/dist/web/assets/{clojure-DFuWS20u.js → clojure-DloMkd8K.js} +0 -0
- /package/dist/web/assets/{cmake-DejCqcu4.js → cmake-CwerJun_.js} +0 -0
- /package/dist/web/assets/{code-CLF5x7KK.js → code-DDPoqVri.js} +0 -0
- /package/dist/web/assets/{codeowners-BwAyqWIH.js → codeowners-CQyVz4Qf.js} +0 -0
- /package/dist/web/assets/{codeql-DgSwFjqI.js → codeql-Bb229zuY.js} +0 -0
- /package/dist/web/assets/{common-lisp-CxIVJCtf.js → common-lisp-KMlIwq5I.js} +0 -0
- /package/dist/web/assets/{copy-BApI9v7Z.js → copy-DKn11Pye.js} +0 -0
- /package/dist/web/assets/{coq-DLSjSnmf.js → coq-DHX_3IIo.js} +0 -0
- /package/dist/web/assets/{csharp-C1HHzXBp.js → csharp-CEmCZv9Q.js} +0 -0
- /package/dist/web/assets/{css-VXsMY--y.js → css-CqZqBD05.js} +0 -0
- /package/dist/web/assets/{csv-BEbcJdHz.js → csv-l1g_V5MV.js} +0 -0
- /package/dist/web/assets/{csv-parser-BRtnJCfv.js → csv-parser-CnS0wqr6.js} +0 -0
- /package/dist/web/assets/{cue-BkDnFqUD.js → cue-BrxRmilg.js} +0 -0
- /package/dist/web/assets/{cypher-1cMqojSQ.js → cypher-rZkKwqZc.js} +0 -0
- /package/dist/web/assets/{d-BnYjRXtH.js → d-BALR-QuP.js} +0 -0
- /package/dist/web/assets/{dark-plus-XfbaH7kZ.js → dark-plus-Cj7rVwh-.js} +0 -0
- /package/dist/web/assets/{dart-Ca7s-8hK.js → dart-CWQcdkXk.js} +0 -0
- /package/dist/web/assets/{data-grid-types-BXwlFMzs.js → data-grid-types-CYvF-xK5.js} +0 -0
- /package/dist/web/assets/{database-B9okYLx6.js → database-BH0urvbs.js} +0 -0
- /package/dist/web/assets/{dax-CmuOw-mY.js → dax-BNHD-QBA.js} +0 -0
- /package/dist/web/assets/{desktop-C2gJ6Ovb.js → desktop-CN0IXPrP.js} +0 -0
- /package/dist/web/assets/{diff-BCEo1Ej7.js → diff-BtOXapES.js} +0 -0
- /package/dist/web/assets/{dist-CUPbqNnL.js → dist-DB1Oaqcc.js} +0 -0
- /package/dist/web/assets/{dist-DOlBnxFX.js → dist-Di6G_5jU.js} +0 -0
- /package/dist/web/assets/{docker-DN2jlyAh.js → docker-DNsAo_qO.js} +0 -0
- /package/dist/web/assets/{dotenv-NExMY2kS.js → dotenv-D6Pl6aOU.js} +0 -0
- /package/dist/web/assets/{download-D9UYwY0-.js → download-zCATy4x4.js} +0 -0
- /package/dist/web/assets/{dracula-B9lbE01y.js → dracula-C3m-w12D.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CMBBfKF_.js → dracula-soft-BYqvXm6Z.js} +0 -0
- /package/dist/web/assets/{dream-maker-DEyDwFsd.js → dream-maker-DZAPbPTi.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-D_qP2zS6.js → emacs-lisp-Ao5SW8ay.js} +0 -0
- /package/dist/web/assets/{everforest-dark-DbHa2U-p.js → everforest-dark-uC8QYotQ.js} +0 -0
- /package/dist/web/assets/{everforest-light-BISkJchP.js → everforest-light-CTgsVNMr.js} +0 -0
- /package/dist/web/assets/{fennel-6B22SfJR.js → fennel-WuBw6IR1.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-a8f3_w7a.js → file-exclamation-point-COAwgNGw.js} +0 -0
- /package/dist/web/assets/{fish-CubIIvJD.js → fish-Byy9dusu.js} +0 -0
- /package/dist/web/assets/{fluent-DF55zQ09.js → fluent-CDxb_sgw.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-DvOu8jAt.js → fortran-free-form-DKWSTamT.js} +0 -0
- /package/dist/web/assets/{gdscript-DCPxdLpq.js → gdscript-prYHMgtD.js} +0 -0
- /package/dist/web/assets/{gdshader-CTZ381FO.js → gdshader-D-RM-SQk.js} +0 -0
- /package/dist/web/assets/{genie-C_QDtvgU.js → genie-CowkJhej.js} +0 -0
- /package/dist/web/assets/{gherkin-BcvK42tF.js → gherkin-CLFpoVYJ.js} +0 -0
- /package/dist/web/assets/{github-dark-default-CKGVrl3Z.js → github-dark-default-BK_1Qgb8.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-C3dQZ_LC.js → github-dark-dimmed-Cv9lj6f-.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-DW78QukY.js → github-dark-high-contrast-CW5ZgIDh.js} +0 -0
- /package/dist/web/assets/{github-dark-C9afs_4w.js → github-dark-nW2F-RLd.js} +0 -0
- /package/dist/web/assets/{github-light-BnP2dznS.js → github-light-D4PqBHGZ.js} +0 -0
- /package/dist/web/assets/{github-light-default-C2fPZzPG.js → github-light-default-B-cHtJJV.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-BzIuUFR2.js → github-light-high-contrast-Dt0HJq50.js} +0 -0
- /package/dist/web/assets/{gleam-BpeKwEIO.js → gleam-DwSyXJWP.js} +0 -0
- /package/dist/web/assets/{gn-nUb_Mit0.js → gn-Cv5WEzHc.js} +0 -0
- /package/dist/web/assets/{gnuplot-D7I0v20p.js → gnuplot-D7bRdY_M.js} +0 -0
- /package/dist/web/assets/{go-D7Qqska7.js → go-DiRVJ22U.js} +0 -0
- /package/dist/web/assets/{groovy-DS-pne5-.js → groovy-BHAgLTeO.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-C0W01fsq.js → gruvbox-dark-hard-C8QZ3gzY.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Bqo6fQbg.js → gruvbox-dark-medium-Dp1eeid2.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-D-XSM_fu.js → gruvbox-dark-soft-Cwq0dWVj.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-IVAwMJRl.js → gruvbox-light-hard-CLQCq2-w.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-38ATiG7i.js → gruvbox-light-medium-APHsh6Mo.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DnZr3c_0.js → gruvbox-light-soft-CX-fYc64.js} +0 -0
- /package/dist/web/assets/{haskell-uQcywkDH.js → haskell-C4Tw-TDa.js} +0 -0
- /package/dist/web/assets/{haxe-c6bI4_GT.js → haxe-B7fSduTQ.js} +0 -0
- /package/dist/web/assets/{hcl-CQXTiEXq.js → hcl-Bi3ho6yC.js} +0 -0
- /package/dist/web/assets/{hjson-ChETf2bz.js → hjson-BXwae2M1.js} +0 -0
- /package/dist/web/assets/{hlsl-BMC4JGoE.js → hlsl-CRuQLBSa.js} +0 -0
- /package/dist/web/assets/{horizon-BusFlhLW.js → horizon-DtdQ6qjv.js} +0 -0
- /package/dist/web/assets/{horizon-bright-2oD0Mz7W.js → horizon-bright-BXSF_uKq.js} +0 -0
- /package/dist/web/assets/{houston-DdbbjqFg.js → houston-C6ASpUjT.js} +0 -0
- /package/dist/web/assets/{hy-D_SOuqw9.js → hy-BrWBHtHY.js} +0 -0
- /package/dist/web/assets/{image-gallery-CrM5fHKy.js → image-gallery-CGvlv8zR.js} +0 -0
- /package/dist/web/assets/{imba-BxLOHO5I.js → imba-C_JJXckG.js} +0 -0
- /package/dist/web/assets/{ini-ox-CFcg0.js → ini-DEE_vSHQ.js} +0 -0
- /package/dist/web/assets/{java-Bzo8yIal.js → java-CnmpcjcT.js} +0 -0
- /package/dist/web/assets/{javascript-DHuz9VXn.js → javascript-C-M7sa2e.js} +0 -0
- /package/dist/web/assets/{json-0_y80FD5.js → json-BjZ3dAk6.js} +0 -0
- /package/dist/web/assets/{json5-CEQ50pGM.js → json5-BQ2vpqjp.js} +0 -0
- /package/dist/web/assets/{jsonc-CfrLEAKA.js → jsonc-C5MjoWGx.js} +0 -0
- /package/dist/web/assets/{jsonl-fKbrANC2.js → jsonl-JvhrmQJI.js} +0 -0
- /package/dist/web/assets/{jsonnet-DKbkPaQ2.js → jsonnet-C5pdZVuo.js} +0 -0
- /package/dist/web/assets/{jssm-Dxxx1Hqu.js → jssm-Db17uybX.js} +0 -0
- /package/dist/web/assets/{jsx-DlJL-Oj3.js → jsx-tJp2Cyqu.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-CwN4SV1c.js → kanagawa-dragon-BculT_Y1.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-BHltayiE.js → kanagawa-lotus-yQgHUGwU.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-D-KXPjw6.js → kanagawa-wave-CyGoWN4d.js} +0 -0
- /package/dist/web/assets/{kdl-DvtoJdFo.js → kdl-Bn8R1REF.js} +0 -0
- /package/dist/web/assets/{kotlin-CTS4MLcx.js → kotlin-DWughK73.js} +0 -0
- /package/dist/web/assets/{kusto-HtSE_sKm.js → kusto--serPb0x.js} +0 -0
- /package/dist/web/assets/{laserwave-DaXZG9ge.js → laserwave-DfAPJ1tt.js} +0 -0
- /package/dist/web/assets/{lean-BYtGUfWx.js → lean-UAm1HOKl.js} +0 -0
- /package/dist/web/assets/{less-BoxmU_Mk.js → less-Xj6Ka0fF.js} +0 -0
- /package/dist/web/assets/{lib-B1clP4ai.js → lib-DgNcCtj3.js} +0 -0
- /package/dist/web/assets/{libesm-ah-mr-sh.js → libesm-CPh1Av0r.js} +0 -0
- /package/dist/web/assets/{light-plus-Bt5CyUuo.js → light-plus-BfS7gPcj.js} +0 -0
- /package/dist/web/assets/{llvm-CSdk9q9V.js → llvm-DvWiVwvU.js} +0 -0
- /package/dist/web/assets/{log-B-kp_3CL.js → log-Bo5z9bNz.js} +0 -0
- /package/dist/web/assets/{logo-DdIsXFDO.js → logo-CrIjcMZT.js} +0 -0
- /package/dist/web/assets/{luau-DUBpLlzQ.js → luau-C9TsYwNt.js} +0 -0
- /package/dist/web/assets/{make-B3JVjoW_.js → make-GYq4qVbx.js} +0 -0
- /package/dist/web/assets/{markdown-Seon0vHs.js → markdown-p5mNledS.js} +0 -0
- /package/dist/web/assets/{material-theme-Nq-ChX1P.js → material-theme-CWYOVxsP.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-CPV4ayXJ.js → material-theme-darker-BDGty7YH.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-0kyjYqEd.js → material-theme-lighter-JbgOgV3n.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-DGpAp5co.js → material-theme-ocean-EMOpLpa2.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-B0AUn0NG.js → material-theme-palenight-CY5hgm7h.js} +0 -0
- /package/dist/web/assets/{matlab-C2waytxs.js → matlab-C-He_NTH.js} +0 -0
- /package/dist/web/assets/{mdx-DAis6R6I.js → mdx-Cr2TR5fj.js} +0 -0
- /package/dist/web/assets/{mermaid-_rnT2YGD.js → mermaid-BYc0-B0U.js} +0 -0
- /package/dist/web/assets/{min-dark-BSSjchUB.js → min-dark-CMJwqJqB.js} +0 -0
- /package/dist/web/assets/{min-light-DomAFQsa.js → min-light-Cx6vFiEX.js} +0 -0
- /package/dist/web/assets/{mipsasm-bBgtvPyG.js → mipsasm-Bjer5HwK.js} +0 -0
- /package/dist/web/assets/{mobile-bottom-sheet-CS3XMwFw.js → mobile-bottom-sheet-BUOJgiaZ.js} +0 -0
- /package/dist/web/assets/{mojo-Cp9BayW0.js → mojo-t90PR-Y2.js} +0 -0
- /package/dist/web/assets/{monokai-CL_9gbmn.js → monokai-dci_PdCm.js} +0 -0
- /package/dist/web/assets/{moonbit-BS_fcr-P.js → moonbit-AAzI-Hd-.js} +0 -0
- /package/dist/web/assets/{move-C79AH45U.js → move-kwBhnhrG.js} +0 -0
- /package/dist/web/assets/{narrat-CJ5s3BDf.js → narrat-CXww2YQL.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-ZHa4JrPJ.js → nextflow-groovy-CJXsuXGB.js} +0 -0
- /package/dist/web/assets/{night-owl-Jkf9zFQK.js → night-owl-_GD204Wr.js} +0 -0
- /package/dist/web/assets/{night-owl-light-DRPeaG5J.js → night-owl-light-DNgkw3kl.js} +0 -0
- /package/dist/web/assets/{nix-D2Jet9DN.js → nix-BgiorKFd.js} +0 -0
- /package/dist/web/assets/{nord-CREVs22j.js → nord-CaBmcxFC.js} +0 -0
- /package/dist/web/assets/{nushell-CAiDi_mR.js → nushell-DUh4UGx1.js} +0 -0
- /package/dist/web/assets/{objective-c-CVkmJkC1.js → objective-c-Cl9KU86S.js} +0 -0
- /package/dist/web/assets/{objective-cpp-DIKDO1Ly.js → objective-cpp-DVkIA8A0.js} +0 -0
- /package/dist/web/assets/{ocaml-R9qtGQhs.js → ocaml-vvoHTak0.js} +0 -0
- /package/dist/web/assets/{odin-DTYNQqVr.js → odin-Cyjw8woZ.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-BSLWUJn5.js → one-dark-pro-B1cviILi.js} +0 -0
- /package/dist/web/assets/{one-light-DbkRXG3t.js → one-light-BPGUJeYI.js} +0 -0
- /package/dist/web/assets/{openscad-BlPcSnmT.js → openscad-BLMdTUU8.js} +0 -0
- /package/dist/web/assets/{pascal-B1Y46ZZB.js → pascal-i5niy3e7.js} +0 -0
- /package/dist/web/assets/{pkl-BhJSJbFv.js → pkl-CuOV7SsU.js} +0 -0
- /package/dist/web/assets/{plastic-BzxsCTpi.js → plastic-Bf_zE5KL.js} +0 -0
- /package/dist/web/assets/{plsql-DBba7dyP.js → plsql-cfqEtMD3.js} +0 -0
- /package/dist/web/assets/{plus-DmjC2iRB.js → plus-BM-Ty0fp.js} +0 -0
- /package/dist/web/assets/{po-BvgLlnNo.js → po-CFJcWgjr.js} +0 -0
- /package/dist/web/assets/{poimandres-DUaI_1JP.js → poimandres-G5YoaIFf.js} +0 -0
- /package/dist/web/assets/{polar-C7uILwJn.js → polar-rcjs3i9R.js} +0 -0
- /package/dist/web/assets/{postcss-BgJlpFdw.js → postcss-CBKY1FTZ.js} +0 -0
- /package/dist/web/assets/{powerquery-Dn1MIpZu.js → powerquery-Db7SXLbO.js} +0 -0
- /package/dist/web/assets/{powershell-Bo-DYzOS.js → powershell-LFy3MUTP.js} +0 -0
- /package/dist/web/assets/{prisma-CTLs5IdT.js → prisma-BE8GR1lH.js} +0 -0
- /package/dist/web/assets/{prolog-QRGun3eG.js → prolog-DOn5lR1e.js} +0 -0
- /package/dist/web/assets/{proto-B7PI7apn.js → proto-wr-TpAK_.js} +0 -0
- /package/dist/web/assets/{puppet-CBq0tLrP.js → puppet-D2Dkv62u.js} +0 -0
- /package/dist/web/assets/{purescript-DFewOQb2.js → purescript-DvgJgPdy.js} +0 -0
- /package/dist/web/assets/{python-iWuxKXvT.js → python-C7TlkHhK.js} +0 -0
- /package/dist/web/assets/{qmldir-cXiW3AsF.js → qmldir-CtZMwhcW.js} +0 -0
- /package/dist/web/assets/{qss-DASwAbBL.js → qss-dNZ2btnl.js} +0 -0
- /package/dist/web/assets/{r-TU2eaIPw.js → r-BlqnNkHS.js} +0 -0
- /package/dist/web/assets/{racket-SkRKqrLY.js → racket-CzRQrGc4.js} +0 -0
- /package/dist/web/assets/{raku-D31lthkZ.js → raku-BwutWhBF.js} +0 -0
- /package/dist/web/assets/{red-CTcQZevb.js → red-BMXMywmL.js} +0 -0
- /package/dist/web/assets/{reg-D8YAsTGf.js → reg-BqOKrIOw.js} +0 -0
- /package/dist/web/assets/{regexp-CD2uvAWx.js → regexp-Bero1BN2.js} +0 -0
- /package/dist/web/assets/{rel-CAaI-1fg.js → rel-BXKWIElA.js} +0 -0
- /package/dist/web/assets/{resolve-theme-CJvkL8hU.js → resolve-theme-IdhSR-gx.js} +0 -0
- /package/dist/web/assets/{riscv-DR-hJVIj.js → riscv-BbbBYJBo.js} +0 -0
- /package/dist/web/assets/{ron-CuUCJ0n4.js → ron-BtVTWW4b.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-Bqz2sBRY.js → rose-pine-dawn-D7lwhUei.js} +0 -0
- /package/dist/web/assets/{rose-pine-aFHxW48i.js → rose-pine-hvkvQzvU.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-wqyh0an4.js → rose-pine-moon-CoHDmsIw.js} +0 -0
- /package/dist/web/assets/{rosmsg-CI1ywimO.js → rosmsg-CdaZFgI8.js} +0 -0
- /package/dist/web/assets/{run-in-terminal-CGW6o37_.js → run-in-terminal-CyS5Itj-.js} +0 -0
- /package/dist/web/assets/{rust-p7Imk4G_.js → rust-Dy2qsphD.js} +0 -0
- /package/dist/web/assets/{sass-C8JfaqCV.js → sass-Bz7NVxDb.js} +0 -0
- /package/dist/web/assets/{save-DRPrDfTt.js → save-C4z1p51e.js} +0 -0
- /package/dist/web/assets/{scala-DNxCW1By.js → scala-sXSwsNyq.js} +0 -0
- /package/dist/web/assets/{scheme-BYuUPJoL.js → scheme-DS7TUbN7.js} +0 -0
- /package/dist/web/assets/{sdbl-DbPeJPUr.js → sdbl-iISFvIep.js} +0 -0
- /package/dist/web/assets/{send-to-chat-YXHdAyLG.js → send-to-chat-BcO39BDR.js} +0 -0
- /package/dist/web/assets/{shellscript-DMiHlR3q.js → shellscript-BkIl4ApC.js} +0 -0
- /package/dist/web/assets/{shield-check-8GObuUAn.js → shield-check-C5laObJv.js} +0 -0
- /package/dist/web/assets/{shield-off-egKAZEwR.js → shield-off-DULFtyLS.js} +0 -0
- /package/dist/web/assets/{slack-dark-C4xGcQXI.js → slack-dark-B1eg0_m2.js} +0 -0
- /package/dist/web/assets/{slack-ochin-BzgRt_wm.js → slack-ochin-mdkc1ldu.js} +0 -0
- /package/dist/web/assets/{smalltalk-LDeL9PYH.js → smalltalk-CORtd03t.js} +0 -0
- /package/dist/web/assets/{snazzy-light-CXE7a5j_.js → snazzy-light-Bc6Q2G89.js} +0 -0
- /package/dist/web/assets/{solarized-dark-DgUhY74x.js → solarized-dark-CF83zRXM.js} +0 -0
- /package/dist/web/assets/{solarized-light-DD9C-H-9.js → solarized-light-BoKQZaYU.js} +0 -0
- /package/dist/web/assets/{solidity-CAbDB4D4.js → solidity-DOgi1N4w.js} +0 -0
- /package/dist/web/assets/{sparkles-n1hzh65o.js → sparkles-Dqsf3hq6.js} +0 -0
- /package/dist/web/assets/{splunk-kQREPfIc.js → splunk-BXfw5ZLy.js} +0 -0
- /package/dist/web/assets/{sql-CBzeNAD8.js → sql-BcVs-eX4.js} +0 -0
- /package/dist/web/assets/{ssh-config-L1euxrsM.js → ssh-config-CLCtNkkd.js} +0 -0
- /package/dist/web/assets/{stylus-DvBnYniP.js → stylus-DdP992aO.js} +0 -0
- /package/dist/web/assets/{swift-DF5NDQp6.js → swift-BnGb6Lgc.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DbBhB_Hq.js → synthwave-84-cg9264l9.js} +0 -0
- /package/dist/web/assets/{system-verilog-D8s2ZW_k.js → system-verilog-GbHpszs3.js} +0 -0
- /package/dist/web/assets/{systemd-a5-XZJsG.js → systemd-Dz-bNsDQ.js} +0 -0
- /package/dist/web/assets/{table-BX2ZrJfM.js → table-CHmxuh6M.js} +0 -0
- /package/dist/web/assets/{talonscript-BLa0URTW.js → talonscript-CFIidoBA.js} +0 -0
- /package/dist/web/assets/{tasl-DIoV4XcC.js → tasl-Cu-TRTKx.js} +0 -0
- /package/dist/web/assets/{tcl-ztYAdXfQ.js → tcl-DwCHCB1K.js} +0 -0
- /package/dist/web/assets/{terraform-DvpCCiOZ.js → terraform-BzZw4T1z.js} +0 -0
- /package/dist/web/assets/{tokyo-night-BkHlYUv0.js → tokyo-night-RKsj2-tv.js} +0 -0
- /package/dist/web/assets/{toml-acIrxos6.js → toml-CirbOgDP.js} +0 -0
- /package/dist/web/assets/{tsv-BJB5EVCT.js → tsv-CraRcWdU.js} +0 -0
- /package/dist/web/assets/{tsx-B1MmXGT3.js → tsx-CjFg0lgw.js} +0 -0
- /package/dist/web/assets/{turtle-DFceTiqd.js → turtle-B-WBIOa6.js} +0 -0
- /package/dist/web/assets/{typescript-Dz9wU5Y1.js → typescript-MpLarYn1.js} +0 -0
- /package/dist/web/assets/{typespec-CPuYZijj.js → typespec-B7QlHMZ_.js} +0 -0
- /package/dist/web/assets/{typst-CfsCIYv8.js → typst-CcHSKFZj.js} +0 -0
- /package/dist/web/assets/{use-blob-url-BmlYYZ8L.js → use-blob-url-BKyVxOZw.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-B8FA4kQf.js → use-is-mobile-BXGVvczu.js} +0 -0
- /package/dist/web/assets/{v-CGo0jcXA.js → v-xTGhWZuD.js} +0 -0
- /package/dist/web/assets/{vala-AsSR0hrb.js → vala-DwebjH3H.js} +0 -0
- /package/dist/web/assets/{vb-C7tvbXv1.js → vb-BxJT2RAy.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-C1diXHJl.js → vendor-xterm-CU6zIYMB.js} +0 -0
- /package/dist/web/assets/{verilog--a33ND-p.js → verilog-CvV6RHPD.js} +0 -0
- /package/dist/web/assets/{vesper-B_Z0h4pn.js → vesper-CY3qd7E_.js} +0 -0
- /package/dist/web/assets/{vhdl-CWL5dZPN.js → vhdl-B9ZX-sUL.js} +0 -0
- /package/dist/web/assets/{viml-BglmT14Y.js → viml-Y42h_ExQ.js} +0 -0
- /package/dist/web/assets/{vitesse-black-RKFsSqQI.js → vitesse-black-DkXP6Sv-.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-D7zDWNmc.js → vitesse-dark-B9CkIB6r.js} +0 -0
- /package/dist/web/assets/{vitesse-light-MAp87W5b.js → vitesse-light-C7DHrJCK.js} +0 -0
- /package/dist/web/assets/{vyper-CgCBYg1G.js → vyper-Cnd9s5j-.js} +0 -0
- /package/dist/web/assets/{wasm-Bwl4txwp.js → wasm-DQE71XTB.js} +0 -0
- /package/dist/web/assets/{wasm-BOG7ZeTT.js → wasm-iWe7Fzjc.js} +0 -0
- /package/dist/web/assets/{wenyan-DpShPKXZ.js → wenyan-Bc5Pnm9l.js} +0 -0
- /package/dist/web/assets/{wgsl-BakcCblK.js → wgsl-D-YUAWkP.js} +0 -0
- /package/dist/web/assets/{wikitext-NroRWVAO.js → wikitext-DYQDUYf2.js} +0 -0
- /package/dist/web/assets/{wit-B5CM2pg6.js → wit-P6u7mfwy.js} +0 -0
- /package/dist/web/assets/{wolfram-7MTFBJpR.js → wolfram-6SXLUqao.js} +0 -0
- /package/dist/web/assets/{yaml-Bwr01G2K.js → yaml-CQiMLVcZ.js} +0 -0
- /package/dist/web/assets/{zenscript-Dj8GATvi.js → zenscript-DFMywo_P.js} +0 -0
- /package/dist/web/assets/{zig-LAE-fbS-.js → zig-CtAjaIiF.js} +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decides whether a process may be ended. Pure — the same function fills
|
|
3
|
+
* `ProcessInfo.protected` for the UI and gates the kill route, so the disabled
|
|
4
|
+
* button and the 403 cannot disagree.
|
|
5
|
+
*
|
|
6
|
+
* Names are compared lowercased and extension-free, which is why no literal
|
|
7
|
+
* below carries `.exe`: one list matches Windows, Linux and macOS.
|
|
8
|
+
*/
|
|
9
|
+
import type { MetricsPlatform } from "../../types/system-metrics.ts";
|
|
10
|
+
|
|
11
|
+
export interface KillGuardContext {
|
|
12
|
+
platform: MetricsPlatform;
|
|
13
|
+
/** Validated-live PPM infra pids: server, parent/supervisor, edge forwarder,
|
|
14
|
+
* every PPM-managed cloudflared. */
|
|
15
|
+
protectedPids: ReadonlySet<number>;
|
|
16
|
+
/** pid → ppid for this tick. Built once by the process collector. */
|
|
17
|
+
ppidOf: ReadonlyMap<number, number>;
|
|
18
|
+
/** pid → startedAt, so ancestor walks cannot follow a recycled parent. */
|
|
19
|
+
startedAtOf: ReadonlyMap<number, number>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface KillGuardVerdict {
|
|
23
|
+
allowed: boolean;
|
|
24
|
+
reason?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `taskkill /F` on the RPCSS/DcomLaunch `svchost` triggers CRITICAL_PROCESS_DIED;
|
|
28
|
+
* `dwm` blanks the Win11 desktop. Task Manager refuses all of these too.
|
|
29
|
+
* `explorer` is deliberately NOT here — it is killable, as in Task Manager. */
|
|
30
|
+
export const WINDOWS_CRITICAL_NAMES: ReadonlySet<string> = new Set([
|
|
31
|
+
"system", "registry", "memory compression", "secure system", "smss", "csrss",
|
|
32
|
+
"wininit", "winlogon", "services", "lsass", "lsaiso", "svchost", "dwm",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/** `sshd` is here because PPM is often driven from a phone against a remote
|
|
36
|
+
* box: ending it locks the user out of the machine they are managing. */
|
|
37
|
+
export const POSIX_CRITICAL_NAMES: ReadonlySet<string> = new Set([
|
|
38
|
+
"launchd", "kernel_task", "systemd", "init", "sshd",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A parent cannot have started after its child. Unknown (0) start times are
|
|
43
|
+
* accepted — a missing value must not make a real chain invisible.
|
|
44
|
+
*/
|
|
45
|
+
function plausibleLink(parentStarted: number | undefined, childStarted: number | undefined): boolean {
|
|
46
|
+
if (!parentStarted || !childStarted) return true;
|
|
47
|
+
return parentStarted <= childStarted;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** True when `target` is a strict ancestor of `pid` along plausible ppid links. */
|
|
51
|
+
function isAncestorOf(target: number, pid: number, ctx: KillGuardContext): boolean {
|
|
52
|
+
const visited = new Set<number>([pid]);
|
|
53
|
+
let cur = pid;
|
|
54
|
+
while (true) {
|
|
55
|
+
const parent = ctx.ppidOf.get(cur);
|
|
56
|
+
if (parent === undefined || parent < 0 || visited.has(parent)) return false;
|
|
57
|
+
if (!plausibleLink(ctx.startedAtOf.get(parent), ctx.startedAtOf.get(cur))) return false;
|
|
58
|
+
if (parent === target) return true;
|
|
59
|
+
visited.add(parent);
|
|
60
|
+
cur = parent;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Every descendant of `root` along plausible ppid links, cycle-safe. */
|
|
65
|
+
export function collectDescendants(root: number, ctx: Pick<KillGuardContext, "ppidOf" | "startedAtOf">): Set<number> {
|
|
66
|
+
const children = new Map<number, number[]>();
|
|
67
|
+
for (const [pid, ppid] of ctx.ppidOf) {
|
|
68
|
+
const list = children.get(ppid);
|
|
69
|
+
if (list) list.push(pid);
|
|
70
|
+
else children.set(ppid, [pid]);
|
|
71
|
+
}
|
|
72
|
+
const out = new Set<number>();
|
|
73
|
+
const queue = [root];
|
|
74
|
+
const visited = new Set<number>([root]);
|
|
75
|
+
while (queue.length > 0) {
|
|
76
|
+
const cur = queue.shift()!;
|
|
77
|
+
for (const child of children.get(cur) ?? []) {
|
|
78
|
+
if (visited.has(child)) continue;
|
|
79
|
+
if (!plausibleLink(ctx.startedAtOf.get(cur), ctx.startedAtOf.get(child))) continue;
|
|
80
|
+
visited.add(child);
|
|
81
|
+
out.add(child);
|
|
82
|
+
queue.push(child);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function checkKillAllowed(
|
|
89
|
+
target: { pid: number; name: string },
|
|
90
|
+
tree: boolean,
|
|
91
|
+
ctx: KillGuardContext,
|
|
92
|
+
): KillGuardVerdict {
|
|
93
|
+
const { pid } = target;
|
|
94
|
+
const name = target.name.toLowerCase();
|
|
95
|
+
const refuse = (reason: string): KillGuardVerdict => ({ allowed: false, reason });
|
|
96
|
+
|
|
97
|
+
if (!Number.isInteger(pid) || pid <= 0) return refuse("Invalid PID");
|
|
98
|
+
if (ctx.protectedPids.has(pid)) return refuse(`${target.name} is a PPM process and cannot be ended`);
|
|
99
|
+
|
|
100
|
+
if (ctx.platform === "win32") {
|
|
101
|
+
if (pid === 4) return refuse("Cannot kill an OS kernel process");
|
|
102
|
+
if (WINDOWS_CRITICAL_NAMES.has(name)) return refuse(`${target.name} is an OS-critical process`);
|
|
103
|
+
} else {
|
|
104
|
+
if (pid <= 1) return refuse("Cannot kill init");
|
|
105
|
+
if (POSIX_CRITICAL_NAMES.has(name)) return refuse(`${target.name} is an OS-critical process`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Ending an ancestor of the PPM server kills the server while its orphaned
|
|
109
|
+
// grandchildren keep the inheritable listening socket — the zombie-port state.
|
|
110
|
+
for (const protectedPid of ctx.protectedPids) {
|
|
111
|
+
if (isAncestorOf(pid, protectedPid, ctx)) {
|
|
112
|
+
return refuse(`Ending ${target.name} would also end the PPM server`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (tree) {
|
|
117
|
+
const descendants = collectDescendants(pid, ctx);
|
|
118
|
+
for (const protectedPid of ctx.protectedPids) {
|
|
119
|
+
if (descendants.has(protectedPid)) return refuse("Ending this process tree would also end PPM");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { allowed: true };
|
|
124
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live re-query for a kill request. A pid is not an identity: Windows recycles
|
|
3
|
+
* pids fast enough that the `node` the user clicked two seconds ago can be
|
|
4
|
+
* `lsass` by the time the POST lands. The route therefore never authorises
|
|
5
|
+
* against the snapshot — it re-collects the process table (one round trip, the
|
|
6
|
+
* same per-OS source the tick uses), compares the client's `startedAt` claim
|
|
7
|
+
* with the live one, and hands the guard the FRESH name plus fresh ppid maps.
|
|
8
|
+
*/
|
|
9
|
+
import type { ProcessCollector, RawProcessRow } from "./process-collector-types.ts";
|
|
10
|
+
|
|
11
|
+
export interface LiveProcessIdentity {
|
|
12
|
+
pid: number;
|
|
13
|
+
name: string;
|
|
14
|
+
startedAt: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** macOS `ps` start times are 1 s resolution and other sources may be coarser. */
|
|
18
|
+
export const IDENTITY_TOLERANCE_MS = 2000;
|
|
19
|
+
|
|
20
|
+
/** An unknown start time (0) on either side cannot prove a mismatch. */
|
|
21
|
+
export function identityMatches(claimedStartedAt: number, liveStartedAt: number): boolean {
|
|
22
|
+
if (!claimedStartedAt || !liveStartedAt) return true;
|
|
23
|
+
return Math.abs(claimedStartedAt - liveStartedAt) <= IDENTITY_TOLERANCE_MS;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface GuardMaps {
|
|
27
|
+
ppidOf: Map<number, number>;
|
|
28
|
+
startedAtOf: Map<number, number>;
|
|
29
|
+
byPid: Map<number, RawProcessRow>;
|
|
30
|
+
/** Rows that were refused because their pid was already present. */
|
|
31
|
+
warnings: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A pid appears once in any real process table. A second row for the same pid
|
|
36
|
+
* can only be a forged line that slipped through the transport, so the FIRST
|
|
37
|
+
* row wins and the duplicate is refused — a forgery must never be able to
|
|
38
|
+
* overwrite the name the kill guard is about to trust.
|
|
39
|
+
*/
|
|
40
|
+
export function buildGuardMaps(rows: readonly RawProcessRow[]): GuardMaps {
|
|
41
|
+
const ppidOf = new Map<number, number>();
|
|
42
|
+
const startedAtOf = new Map<number, number>();
|
|
43
|
+
const byPid = new Map<number, RawProcessRow>();
|
|
44
|
+
const warnings: string[] = [];
|
|
45
|
+
for (const r of rows) {
|
|
46
|
+
if (byPid.has(r.pid)) {
|
|
47
|
+
warnings.push(`Duplicate row for PID ${r.pid} ignored (name "${r.name}")`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
byPid.set(r.pid, r);
|
|
51
|
+
ppidOf.set(r.pid, r.ppid);
|
|
52
|
+
startedAtOf.set(r.pid, r.startedAt);
|
|
53
|
+
}
|
|
54
|
+
return { ppidOf, startedAtOf, byPid, warnings };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface LiveResolution {
|
|
58
|
+
live: LiveProcessIdentity | null;
|
|
59
|
+
maps: GuardMaps;
|
|
60
|
+
warnings: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function resolveLiveProcess(pid: number, collector: ProcessCollector): Promise<LiveResolution> {
|
|
64
|
+
const collection = await collector.collect();
|
|
65
|
+
const maps = buildGuardMaps(collection.rows);
|
|
66
|
+
const row = maps.byPid.get(pid);
|
|
67
|
+
return {
|
|
68
|
+
live: row ? { pid: row.pid, name: row.name, startedAt: row.startedAt } : null,
|
|
69
|
+
maps,
|
|
70
|
+
warnings: [...collection.warnings, ...maps.warnings],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kill decision chain, transport-free so it is testable without Hono:
|
|
3
|
+
* parse → live re-query → identity check → guard on the FRESH name → execute.
|
|
4
|
+
* Authorising against the snapshot would authorise against data up to one tick
|
|
5
|
+
* old, and Windows recycles pids fast enough for that to matter.
|
|
6
|
+
*/
|
|
7
|
+
import type { KillProcessRequest, KillProcessResult, MetricsPlatform } from "../../types/system-metrics.ts";
|
|
8
|
+
import { ok, err, type ApiResponse } from "../../types/api.ts";
|
|
9
|
+
import type { ProcessCollector } from "./process-collector-types.ts";
|
|
10
|
+
import { identityMatches, resolveLiveProcess } from "./kill-identity-resolver.ts";
|
|
11
|
+
import { checkKillAllowed } from "./kill-guard.ts";
|
|
12
|
+
import type { ProtectedPids } from "./ppm-protected-pids.ts";
|
|
13
|
+
|
|
14
|
+
export interface KillHandlerDeps {
|
|
15
|
+
platform: MetricsPlatform;
|
|
16
|
+
collector: ProcessCollector;
|
|
17
|
+
resolveProtected: (isAlive: (pid: number) => boolean, nameOf: (pid: number) => string | undefined) => ProtectedPids;
|
|
18
|
+
execute: (pid: number, tree: boolean) => Promise<KillProcessResult>;
|
|
19
|
+
/** Audit line: pid + name + result ONLY. `~/.ppm/ppm.log`'s tail is served
|
|
20
|
+
* unauthenticated by `/api/logs/recent`, so a command line must never land here. */
|
|
21
|
+
log: (line: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type KillStatus = 200 | 400 | 403 | 404 | 409 | 500;
|
|
25
|
+
|
|
26
|
+
export interface KillOutcome {
|
|
27
|
+
status: KillStatus;
|
|
28
|
+
body: ApiResponse<KillProcessResult>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function parseKillRequest(body: unknown): KillProcessRequest | null {
|
|
32
|
+
if (!body || typeof body !== "object") return null;
|
|
33
|
+
const b = body as Record<string, unknown>;
|
|
34
|
+
const pid = b.pid;
|
|
35
|
+
const startedAt = b.startedAt;
|
|
36
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return null;
|
|
37
|
+
if (typeof startedAt !== "number" || !Number.isFinite(startedAt) || startedAt < 0) return null;
|
|
38
|
+
if (b.tree !== undefined && typeof b.tree !== "boolean") return null;
|
|
39
|
+
return { pid, startedAt, tree: b.tree === true };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function handleKillRequest(body: unknown, deps: KillHandlerDeps): Promise<KillOutcome> {
|
|
43
|
+
const req = parseKillRequest(body);
|
|
44
|
+
if (!req) return { status: 400, body: err("Body must be {pid: positive integer, startedAt: epoch ms, tree?: boolean}") };
|
|
45
|
+
const tree = req.tree === true;
|
|
46
|
+
|
|
47
|
+
const { live, maps } = await resolveLiveProcess(req.pid, deps.collector);
|
|
48
|
+
if (!live) return { status: 404, body: err(`PID ${req.pid} is no longer running`) };
|
|
49
|
+
if (!identityMatches(req.startedAt, live.startedAt)) {
|
|
50
|
+
return { status: 409, body: err(`PID ${req.pid} was recycled — refresh and try again`) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const protectedPids = deps.resolveProtected(
|
|
54
|
+
(pid) => maps.byPid.has(pid),
|
|
55
|
+
(pid) => maps.byPid.get(pid)?.name.toLowerCase(),
|
|
56
|
+
);
|
|
57
|
+
const verdict = checkKillAllowed({ pid: live.pid, name: live.name }, tree, {
|
|
58
|
+
platform: deps.platform,
|
|
59
|
+
protectedPids: protectedPids.pids,
|
|
60
|
+
ppidOf: maps.ppidOf,
|
|
61
|
+
startedAtOf: maps.startedAtOf,
|
|
62
|
+
});
|
|
63
|
+
const prefix = `[SystemMetrics] kill pid=${live.pid} name=${live.name} tree=${tree}`;
|
|
64
|
+
if (!verdict.allowed) {
|
|
65
|
+
deps.log(`${prefix} → refused: ${verdict.reason}`);
|
|
66
|
+
return { status: 403, body: err(verdict.reason ?? "Refused") };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
deps.log(`${prefix} → allowed`);
|
|
70
|
+
try {
|
|
71
|
+
const result = await deps.execute(live.pid, tree);
|
|
72
|
+
deps.log(`${prefix} → done (${result.method}, ${result.killed.length} signalled)`);
|
|
73
|
+
return { status: 200, body: ok(result) };
|
|
74
|
+
} catch (e) {
|
|
75
|
+
const message = (e as Error)?.message ?? String(e);
|
|
76
|
+
deps.log(`${prefix} → failed: ${message}`);
|
|
77
|
+
return { status: 500, body: err(`Failed to end PID ${live.pid}: ${message}`) };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One holder at a time for the process collector. The PowerShell session takes
|
|
3
|
+
* a single request at a time, and a kill re-collects through the same collector
|
|
4
|
+
* the poll tick uses — so a kill waits for an in-flight tick, while a tick that
|
|
5
|
+
* arrives during a kill (or another tick) is simply skipped: metrics are lossy,
|
|
6
|
+
* a dropped poll is harmless, but two overlapping collections would either fail
|
|
7
|
+
* with "request already in flight" or double-consume the CPU delta baseline.
|
|
8
|
+
*/
|
|
9
|
+
export class CollectorLock {
|
|
10
|
+
private inflight: Promise<unknown> | null = null;
|
|
11
|
+
|
|
12
|
+
isHeld(): boolean {
|
|
13
|
+
return this.inflight !== null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Run now if free, otherwise skip and resolve to `null`. */
|
|
17
|
+
async tryRun<T>(work: () => Promise<T>): Promise<T | null> {
|
|
18
|
+
if (this.inflight) return null;
|
|
19
|
+
return this.hold(work());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Wait for the current holder (however it ends), then run. */
|
|
23
|
+
async runExclusive<T>(work: () => Promise<T>): Promise<T> {
|
|
24
|
+
while (this.inflight) await this.inflight.catch(() => {});
|
|
25
|
+
return this.hold(work());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private async hold<T>(p: Promise<T>): Promise<T> {
|
|
29
|
+
this.inflight = p;
|
|
30
|
+
try {
|
|
31
|
+
return await p;
|
|
32
|
+
} finally {
|
|
33
|
+
this.inflight = null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns live subscriber demand into exactly one poll timer, plus the delayed
|
|
3
|
+
* child teardown when full-tier demand disappears. Timers follow demand:
|
|
4
|
+
* ≥1 full subscriber → full cadence; else ≥1 light → light cadence; else none.
|
|
5
|
+
*/
|
|
6
|
+
import type { MetricsTier } from "../../types/system-metrics.ts";
|
|
7
|
+
|
|
8
|
+
export interface PollSchedulerOptions {
|
|
9
|
+
intervals: { full: number; light: number };
|
|
10
|
+
/** Grace before `onIdle` fires after the last full subscriber leaves, so a
|
|
11
|
+
* window reopen does not pay the PowerShell bootstrap again. */
|
|
12
|
+
idleTeardownMs: number;
|
|
13
|
+
onTick: () => void;
|
|
14
|
+
/** Full-tier demand has been absent for the whole grace period. */
|
|
15
|
+
onIdle: () => void;
|
|
16
|
+
/** Full-tier collection is about to start for the first time. */
|
|
17
|
+
onFullStart: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class PollScheduler {
|
|
21
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
22
|
+
private idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
23
|
+
private tier: MetricsTier | null = null;
|
|
24
|
+
|
|
25
|
+
constructor(private readonly opts: PollSchedulerOptions) {}
|
|
26
|
+
|
|
27
|
+
activeTier(): MetricsTier | null {
|
|
28
|
+
return this.tier;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Returns true when the active tier changed (so delta baselines may need a reset). */
|
|
32
|
+
reconcile(fullCount: number, lightCount: number): boolean {
|
|
33
|
+
const desired: MetricsTier | null = fullCount > 0 ? "full" : lightCount > 0 ? "light" : null;
|
|
34
|
+
if (desired === "full") this.clearIdle();
|
|
35
|
+
else if (this.tier === "full") this.armIdle();
|
|
36
|
+
if (desired === this.tier) return false;
|
|
37
|
+
|
|
38
|
+
if (this.timer) clearInterval(this.timer);
|
|
39
|
+
this.timer = null;
|
|
40
|
+
this.tier = desired;
|
|
41
|
+
if (desired === "full") this.opts.onFullStart();
|
|
42
|
+
if (desired) {
|
|
43
|
+
this.timer = setInterval(this.opts.onTick, this.opts.intervals[desired]);
|
|
44
|
+
this.opts.onTick();
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Re-arm the idle teardown after an out-of-band full collection (a kill). */
|
|
50
|
+
armIdle(): void {
|
|
51
|
+
this.clearIdle();
|
|
52
|
+
this.idleTimer = setTimeout(() => {
|
|
53
|
+
this.idleTimer = null;
|
|
54
|
+
if (this.tier !== "full") this.opts.onIdle();
|
|
55
|
+
}, this.opts.idleTeardownMs);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
stop(): void {
|
|
59
|
+
if (this.timer) clearInterval(this.timer);
|
|
60
|
+
this.timer = null;
|
|
61
|
+
this.tier = null;
|
|
62
|
+
this.clearIdle();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private clearIdle(): void {
|
|
66
|
+
if (this.idleTimer) clearTimeout(this.idleTimer);
|
|
67
|
+
this.idleTimer = null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const installedTeardowns = new WeakSet<() => void>();
|
|
72
|
+
|
|
73
|
+
/** A hard exit must not leave a PowerShell child holding the server's
|
|
74
|
+
* inheritable listening socket (the zombie-port state). The server's own
|
|
75
|
+
* SIGTERM/SIGINT handler calls `process.exit`, which fires `exit` — so this
|
|
76
|
+
* hook only tears down and never exits itself. Installed once per teardown fn. */
|
|
77
|
+
export function installProcessExitHooks(teardown: () => void): void {
|
|
78
|
+
if (installedTeardowns.has(teardown)) return;
|
|
79
|
+
installedTeardowns.add(teardown);
|
|
80
|
+
process.on("exit", teardown);
|
|
81
|
+
process.on("SIGTERM", teardown);
|
|
82
|
+
process.on("SIGINT", teardown);
|
|
83
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE subscriber leases. A proxy in front of PPM (Cloudflare Tunnel) may keep
|
|
3
|
+
* the origin request alive after the browser left, so `cancel()` cannot be
|
|
4
|
+
* trusted: each subscriber holds a lease the client renews by pinging, and the
|
|
5
|
+
* server reaps silent ones. Without this, a phone that closed its tab would keep
|
|
6
|
+
* the PowerShell child running forever and burn one of the five stream slots.
|
|
7
|
+
*/
|
|
8
|
+
import type { MetricsSnapshot, MetricsTier } from "../../types/system-metrics.ts";
|
|
9
|
+
|
|
10
|
+
export interface MetricsSubscriber {
|
|
11
|
+
sid: string;
|
|
12
|
+
tier: MetricsTier;
|
|
13
|
+
lastPingAt: number;
|
|
14
|
+
deliver: (snapshot: MetricsSnapshot) => void;
|
|
15
|
+
/** Called once when the server drops the subscriber (reap or explicit stop). */
|
|
16
|
+
close: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
20
|
+
|
|
21
|
+
/** Accept only a short opaque id so a hostile query cannot bloat the map. */
|
|
22
|
+
export function isValidSid(raw: string | undefined): raw is string {
|
|
23
|
+
return typeof raw === "string" && SID_PATTERN.test(raw);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class SubscriberRegistry {
|
|
27
|
+
private readonly subs = new Map<string, MetricsSubscriber>();
|
|
28
|
+
|
|
29
|
+
add(sub: MetricsSubscriber): void {
|
|
30
|
+
this.subs.set(sub.sid, sub);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
has(sid: string): boolean {
|
|
34
|
+
return this.subs.has(sid);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Remove and end the stream, so an explicit DELETE also closes the HTTP
|
|
38
|
+
* response a proxy might otherwise hold open. `close` must be idempotent. */
|
|
39
|
+
remove(sid: string): boolean {
|
|
40
|
+
const s = this.subs.get(sid);
|
|
41
|
+
if (!s) return false;
|
|
42
|
+
this.subs.delete(sid);
|
|
43
|
+
try { s.close(); } catch { /* stream already gone */ }
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ping(sid: string, now: number): boolean {
|
|
48
|
+
const s = this.subs.get(sid);
|
|
49
|
+
if (!s) return false;
|
|
50
|
+
s.lastPingAt = now;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Drop and close every lease silent for longer than `timeoutMs`. */
|
|
55
|
+
reap(now: number, timeoutMs: number): MetricsSubscriber[] {
|
|
56
|
+
const expired: MetricsSubscriber[] = [];
|
|
57
|
+
for (const s of this.subs.values()) {
|
|
58
|
+
if (now - s.lastPingAt > timeoutMs) expired.push(s);
|
|
59
|
+
}
|
|
60
|
+
for (const s of expired) {
|
|
61
|
+
this.subs.delete(s.sid);
|
|
62
|
+
try { s.close(); } catch { /* stream already gone */ }
|
|
63
|
+
}
|
|
64
|
+
return expired;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
count(tier?: MetricsTier): number {
|
|
68
|
+
if (!tier) return this.subs.size;
|
|
69
|
+
let n = 0;
|
|
70
|
+
for (const s of this.subs.values()) if (s.tier === tier) n++;
|
|
71
|
+
return n;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
list(tier: MetricsTier): MetricsSubscriber[] {
|
|
75
|
+
return [...this.subs.values()].filter((s) => s.tier === tier);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Close everything — server shutdown. */
|
|
79
|
+
clear(): void {
|
|
80
|
+
for (const s of this.subs.values()) {
|
|
81
|
+
try { s.close(); } catch { /* stream already gone */ }
|
|
82
|
+
}
|
|
83
|
+
this.subs.clear();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The PowerShell REPL bootstrap and the real spawner for the long-lived session.
|
|
3
|
+
*
|
|
4
|
+
* Why not `-Command -` with stdin scripts: PS 5.1 executes stdin line by line,
|
|
5
|
+
* so a multi-line construct makes the child exit 0 silently with no stderr.
|
|
6
|
+
* Instead this bootstrap IS the loop: one request = one line
|
|
7
|
+
* `<id> <base64(UTF-16LE script)>`, one reply = everything up to a literal
|
|
8
|
+
* `__END_<id>__` line. Per-request ids mean a hostile command line containing a
|
|
9
|
+
* sentinel can desync at most the one in-flight reply.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** One `-Command` string. `try{} catch{}` must stay one logical statement — a
|
|
13
|
+
* semicolon before `catch` is a parse error that kills the child at startup
|
|
14
|
+
* with `MissingCatchOrFinally`. */
|
|
15
|
+
export const POWERSHELL_BOOTSTRAP = [
|
|
16
|
+
"[Console]::OutputEncoding=[Text.Encoding]::UTF8",
|
|
17
|
+
"$ErrorActionPreference='Continue'",
|
|
18
|
+
"$in=[Console]::In",
|
|
19
|
+
"while($true){ $line=$in.ReadLine(); if($null -eq $line){break}; if($line -eq ''){continue}; " +
|
|
20
|
+
"$sp=$line.IndexOf(' '); $id=$line.Substring(0,$sp); " +
|
|
21
|
+
"$code=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($line.Substring($sp+1))); " +
|
|
22
|
+
"try { Invoke-Expression $code | Out-String -Width 1000000 -Stream | ForEach-Object { $_ } } catch { Write-Output ('__ERR__ ' + $_.Exception.Message) }; " +
|
|
23
|
+
"Write-Output ('__END_' + $id + '__') }",
|
|
24
|
+
].join("; ");
|
|
25
|
+
|
|
26
|
+
/** The minimal duplex surface the session needs, so tests can inject a fake. */
|
|
27
|
+
export interface PsChild {
|
|
28
|
+
pid: number;
|
|
29
|
+
stdin: { write(data: string): unknown; flush?(): unknown };
|
|
30
|
+
stdout: ReadableStream<Uint8Array>;
|
|
31
|
+
stderr?: ReadableStream<Uint8Array> | null;
|
|
32
|
+
exited: Promise<number>;
|
|
33
|
+
kill(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PsSpawner = () => PsChild;
|
|
37
|
+
|
|
38
|
+
/** `-NoProfile`: a user profile can redefine cmdlets and poison the parse.
|
|
39
|
+
* `-NonInteractive`: a prompt would wedge the pipe forever. */
|
|
40
|
+
export const defaultPsSpawner: PsSpawner = () =>
|
|
41
|
+
Bun.spawn(["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", POWERSHELL_BOOTSTRAP], {
|
|
42
|
+
stdin: "pipe", stdout: "pipe", stderr: "pipe", windowsHide: true,
|
|
43
|
+
}) as unknown as PsChild;
|
|
44
|
+
|
|
45
|
+
/** Encode one request line. The id is a small monotonic counter, never user input. */
|
|
46
|
+
export function encodeRequestLine(id: string, script: string): string {
|
|
47
|
+
return `${id} ${Buffer.from(script, "utf16le").toString("base64")}\n`;
|
|
48
|
+
}
|