@hienlh/ppm 0.21.0 → 0.22.1
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/CHANGELOG.md +50 -0
- package/CLAUDE.md +29 -4
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/bun.lock +16 -1
- package/dist/web/assets/accounts-settings-section-D3BylchP.js +1 -0
- package/dist/web/assets/{ai-resource-editor-F09pf8Hd.js → ai-resource-editor-BLPcnhTM.js} +1 -1
- package/dist/web/assets/ai-settings-section-BtR7rF-T.js +1 -0
- package/dist/web/assets/ai-settings-section-r9JctHNM.js +1 -0
- package/dist/web/assets/{angular-html-DkR4iCth.js → angular-html-G66FNlyn.js} +1 -1
- package/dist/web/assets/{angular-ts-DcyL4Chx.js → angular-ts-B-PnUMyt.js} +1 -1
- package/dist/web/assets/{api-client-bfB7cXRM.js → api-client-C1ZwRL4Y.js} +1 -1
- package/dist/web/assets/api-client-pQL53cDQ.js +1 -0
- package/dist/web/assets/{api-settings-BcNjZIvG.js → api-settings-_7Q-ULxp.js} +1 -1
- package/dist/web/assets/{api-themes-DBfyVUtX.js → api-themes-DRAERo4Q.js} +1 -1
- package/dist/web/assets/{apl-BVlrVtjt.js → apl-BmDqqwE8.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-whgnq3ss.js → appearance-settings-section-CiMdk3sb.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-Dx6nBEKT.js +1 -0
- package/dist/web/assets/{astro-MjJlozLj.js → astro-BfwMNfxr.js} +1 -1
- package/dist/web/assets/{audio-preview-Bc54GCAV.js → audio-preview-DsYvgKqj.js} +1 -1
- package/dist/web/assets/{blade-DnJWi1fz.js → blade-BsFV4pnN.js} +1 -1
- package/dist/web/assets/branch-review-tab-B1pFjlKM.js +1 -0
- package/dist/web/assets/{bsl-DCmJxSIz.js → bsl-Cw9Outng.js} +1 -1
- package/dist/web/assets/{button-VW4QGKCn.js → button-B9CTrsEf.js} +1 -1
- package/dist/web/assets/{cairo-C8dDenub.js → cairo-DOK5Wqnd.js} +1 -1
- package/dist/web/assets/chat-tab-DZagX06m.js +15 -0
- package/dist/web/assets/{cobol-DPx_nifl.js → cobol-DRaRy4FK.js} +1 -1
- package/dist/web/assets/code-editor-BJhOEsnZ.js +7 -0
- package/dist/web/assets/{codex-daily-guard-CfybHv_c.js → codex-daily-guard-CaCQJ4r_.js} +1 -1
- package/dist/web/assets/{coffee-DvFzqhgN.js → coffee-DvXuBmEJ.js} +1 -1
- package/dist/web/assets/{conflict-editor-DenQUOT2.js → conflict-editor-B7u_p49h.js} +3 -3
- package/dist/web/assets/context-menu-C6pIsf83.js +1 -0
- package/dist/web/assets/{cpp-BLJN0umi.js → cpp-zCmm8rKA.js} +1 -1
- package/dist/web/assets/{crystal-BXR-9fGw.js → crystal-CdQx6lB6.js} +1 -1
- package/dist/web/assets/{csv-preview-CClrJOuR.js → csv-preview-CttjJU1e.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-D4fhcMfT.js → data-grid-overlay-editor-6xUCeLHh.js} +1 -1
- package/dist/web/assets/{database-viewer-DItjm8wB.js → database-viewer-Ca4HxIfx.js} +1 -1
- package/dist/web/assets/{dialog-DQTn55Mt.js → dialog-B84_DJQ8.js} +1 -1
- package/dist/web/assets/diff-viewer-9-XjLIwa.js +4 -0
- package/dist/web/assets/{docx-preview-DYBkiUaE.js → docx-preview-CXuk-emm.js} +1 -1
- package/dist/web/assets/{edge-rWOlCmXT.js → edge-E8QLt7oi.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-COtmo54G.js → edit-diff-preview-BGGGecVw.js} +1 -1
- package/dist/web/assets/{editor-language-service-DxkZ51E5.js → editor-language-service-D0DuEOs_.js} +1 -1
- package/dist/web/assets/{elixir-E7Y82okW.js → elixir-DPb_-72P.js} +1 -1
- package/dist/web/assets/{elm-DTPGbc-w.js → elm-BMzJY5b0.js} +1 -1
- package/dist/web/assets/{erb-DHqLHrE6.js → erb-AABQNtMK.js} +1 -1
- package/dist/web/assets/{erlang-Byg0nNiV.js → erlang-CEIKOkfP.js} +1 -1
- package/dist/web/assets/{esm-D_DmOM7n.js → esm-B9RVMpwV.js} +1 -1
- package/dist/web/assets/explorer-body-VdCBqcZu.js +2 -0
- package/dist/web/assets/explorer-window-content-BIsbl7Th.js +1 -0
- package/dist/web/assets/extension-manager-section-HkY7geS5.js +1 -0
- package/dist/web/assets/{extension-webview-DI4VL2tJ.js → extension-webview-DZG8YHX9.js} +1 -1
- package/dist/web/assets/file-icons-BXdvggK2.js +1 -0
- package/dist/web/assets/file-icons.generated-BtBErEwb.css +1 -0
- package/dist/web/assets/file-store-ojgMXgAw.js +1 -0
- package/dist/web/assets/files-settings-section-qnRtkX05.js +1 -0
- package/dist/web/assets/{format-file-meta-DOpzJJhS.js → format-file-meta-C77h04cZ.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CY1n0Xf6.js → fortran-fixed-form-CrXvLHyi.js} +1 -1
- package/dist/web/assets/{fsharp-_vEDSCJz.js → fsharp-DCI4qAz_.js} +1 -1
- package/dist/web/assets/{gdresource-B6XT0wAC.js → gdresource-Vb2kO1MG.js} +1 -1
- package/dist/web/assets/{general-settings-section-BcxwiZ0X.js → general-settings-section-BAFSZmCE.js} +1 -1
- package/dist/web/assets/{git-commit-Wk9ovq59.js → git-commit-BMI_OWn-.js} +1 -1
- package/dist/web/assets/git-log-panel-2sGpfEla.js +1 -0
- package/dist/web/assets/{git-rebase-DSarGPvK.js → git-rebase-D-d_2iqZ.js} +1 -1
- package/dist/web/assets/{git-repo-store-Dy69YCHN.js → git-repo-store-88Z8v-pF.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-C6qRruHI.js +1 -0
- package/dist/web/assets/{glide-data-grid-BvgAqSUr.js → glide-data-grid-f0hyITiF.js} +7 -7
- package/dist/web/assets/{glimmer-js-CkYI35_P.js → glimmer-js-BmNNiR0Z.js} +1 -1
- package/dist/web/assets/{glimmer-ts-BqmIZcds.js → glimmer-ts-DQE2Ufha.js} +1 -1
- package/dist/web/assets/{glsl-Be-yBCF5.js → glsl-BcIDsxRJ.js} +1 -1
- package/dist/web/assets/{graphql-a5DrENHY.js → graphql-DGKSLmGH.js} +1 -1
- package/dist/web/assets/group-chat-tab-Ck6sk7yY.js +1 -0
- package/dist/web/assets/{hack-BLAg20aD.js → hack-YiC8bEIb.js} +1 -1
- package/dist/web/assets/{haml-Dj6bGOQH.js → haml-DjblxAsz.js} +1 -1
- package/dist/web/assets/{handlebars-dmEa36Oh.js → handlebars-BQepOXaU.js} +1 -1
- package/dist/web/assets/{html-yacZYzSE.js → html-Cayj_2GP.js} +1 -1
- package/dist/web/assets/{html-derivative-lLi3-RNT.js → html-derivative-Weq79bL6.js} +1 -1
- package/dist/web/assets/{http-1KaxcE4e.js → http-BeLfdGdz.js} +1 -1
- package/dist/web/assets/{hurl-ST-dEEZR.js → hurl-D9rypCLK.js} +1 -1
- package/dist/web/assets/{hxml-CpDtuq__.js → hxml-clG8mmSH.js} +1 -1
- package/dist/web/assets/{image-preview-CJVxL0uH.js → image-preview-CzCc4_Do.js} +1 -1
- package/dist/web/assets/image-preview-geometry-9lgeLYB3.js +1 -0
- package/dist/web/assets/index-BH99TIE0.css +1 -0
- package/dist/web/assets/index-CEZVxM4G.js +52 -0
- package/dist/web/assets/info-3K5VOQVL-BOSpdwlz.js +1 -0
- package/dist/web/assets/{jinja-Cjw-evAn.js → jinja-BShBWfs5.js} +1 -1
- package/dist/web/assets/{jira-watcher-section-DIC1yIV3.js → jira-watcher-section-CeLmhvu1.js} +1 -1
- package/dist/web/assets/{jison-DfATMYoY.js → jison-dN2Nf_Dr.js} +1 -1
- package/dist/web/assets/{julia-DDCaxiqT.js → julia-5U0vR-uJ.js} +1 -1
- package/dist/web/assets/{just-CJL529u6.js → just-BMppVRy6.js} +1 -1
- package/dist/web/assets/keybindings-store-C_3ChOEf.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-BW03P3AF.js → keyboard-shortcuts-section-PVz6kuh2.js} +1 -1
- package/dist/web/assets/{latex-Zc4EpLpW.js → latex-DSSOKjis.js} +1 -1
- package/dist/web/assets/{liquid-B_6KMNYS.js → liquid-C24dKoyI.js} +1 -1
- package/dist/web/assets/{lua-BtS2idK7.js → lua-DNOsnyhn.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CvL1pWeK.js → markdown-renderer-C140Ijk0.js} +3 -3
- package/dist/web/assets/markdown-renderer-Dz7-DYUg.js +1 -0
- package/dist/web/assets/{marko-Wk6LBnck.js → marko-26MHO_Q9.js} +1 -1
- package/dist/web/assets/{mdc-cRaXBrzL.js → mdc-Dhw9mGrC.js} +1 -1
- package/dist/web/assets/{media-url-DL8OCGGg.js → media-url-yfqHpPz0.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-CoUauvLF.js → mobile-bottom-sheet-C3TniEP-.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CUG_owdf.js +1 -0
- package/dist/web/assets/{nextflow-Bos2wvDV.js → nextflow-xH4oJTex.js} +1 -1
- package/dist/web/assets/{nginx-DMlOwzdr.js → nginx-CMz4w0M8.js} +1 -1
- package/dist/web/assets/{nim-D2dZQA88.js → nim-D8FnIYsx.js} +1 -1
- package/dist/web/assets/notification-store-6vib1ISy.js +1 -0
- package/dist/web/assets/{notifications-settings-section-DPUROMw-.js → notifications-settings-section-DqlPDfNX.js} +1 -1
- package/dist/web/assets/{number-overlay-editor-DZ2i_Bnb.js → number-overlay-editor-CRGwULhR.js} +1 -1
- package/dist/web/assets/{overview-panel-R146OIom.js → overview-panel-Di-3aV9p.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-BGIE1QdU.js +1 -0
- package/dist/web/assets/panel-store-CboGJAwh.js +1 -0
- package/dist/web/assets/{pdf-preview-BWUPGLp6.js → pdf-preview-CmBI6E7d.js} +1 -1
- package/dist/web/assets/{perl-C5-Z6fPn.js → perl-D9iNWYwb.js} +1 -1
- package/dist/web/assets/{php-BXNNhkMH.js → php-Bw3Qo0Zv.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-Dg-QTbXX.js +1 -0
- package/dist/web/assets/{postgres-viewer-CjOvaruz.js → postgres-viewer-8ZAqEALv.js} +3 -3
- package/dist/web/assets/{ppmbot-settings-section-DrvWh8l8.js → ppmbot-settings-section-Cji-Mil-.js} +1 -1
- package/dist/web/assets/problems-panel-a6_hrWvl.js +1 -0
- package/dist/web/assets/{process-table-BRMS1MrX.js → process-table-CiIqlOIK.js} +1 -1
- package/dist/web/assets/project-framework-store-B70Q3dNM.js +1 -0
- package/dist/web/assets/project-store-B87hLRSk.js +1 -0
- package/dist/web/assets/{proxy-settings-section-Bv0Ny-Yy.js → proxy-settings-section-BjI4IFgp.js} +1 -1
- package/dist/web/assets/{pug-C1zf1yWy.js → pug-fT5JC6Vm.js} +1 -1
- package/dist/web/assets/{qml-OW58jGpD.js → qml-P_wEDB1w.js} +1 -1
- package/dist/web/assets/{query-audit-section-BcHJZcVC.js → query-audit-section-By6qyqdB.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DZ6IVu2s.js +1 -0
- package/dist/web/assets/{razor-DzjSaobM.js → razor-Drrt2_Ph.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-eXYvwgx7.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-Doh-fci-.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-Dt8Akc3p.js +1 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-BTzss2wX.js → remote-desktop-readiness-gate-t83fqbID.js} +1 -1
- package/dist/web/assets/remote-desktop-window-content-CguhZMmx.js +1 -0
- package/dist/web/assets/{rst-RfuihOUW.js → rst-Dr2mW4-h.js} +1 -1
- package/dist/web/assets/{ruby-CNO3pli1.js → ruby-C6EpUsTY.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Cb_hVU8x.js → run-in-terminal-CdefEe_R.js} +1 -1
- package/dist/web/assets/{sas-DZY5BtXx.js → sas-D8ZEeP6v.js} +1 -1
- package/dist/web/assets/{schedules-settings-section-DO8xAOVj.js → schedules-settings-section-BVkpW5ms.js} +1 -1
- package/dist/web/assets/{scroll-area-LtNV8g5o.js → scroll-area-BzeiOd-B.js} +1 -1
- package/dist/web/assets/{scss--4g-REcN.js → scss-ITL7jQ1f.js} +1 -1
- package/dist/web/assets/{send-to-chat-Cnpru1M0.js → send-to-chat-F207yH3u.js} +1 -1
- package/dist/web/assets/{separator-Cds1yxy8.js → separator-TRMsJUYe.js} +1 -1
- package/dist/web/assets/settings-body-CoIxYAh6.js +2 -0
- package/dist/web/assets/settings-store-krC6bc79.js +2 -0
- package/dist/web/assets/settings-tab-7DKVUTbM.js +1 -0
- package/dist/web/assets/settings-window-content-3_39TeX-.js +1 -0
- package/dist/web/assets/{shaderlab-Cu4sBiVn.js → shaderlab-mW-IIlhk.js} +1 -1
- package/dist/web/assets/{shellsession-DWzJfEv7.js → shellsession-DhNl5STt.js} +1 -1
- package/dist/web/assets/{soy-BnqE-5Ff.js → soy-SdY0Qjtr.js} +1 -1
- package/dist/web/assets/{sparql-Ctg__NhZ.js → sparql-ftqZo2i-.js} +1 -1
- package/dist/web/assets/{sql-query-editor-D9k19T_M.js → sql-query-editor-CQB7Yp7U.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DoV9hnWJ.js → sqlite-viewer-Bd7rjr1D.js} +1 -1
- package/dist/web/assets/{stata-Qk2h66XW.js → stata-Bj7Yjb4b.js} +1 -1
- package/dist/web/assets/{surrealql-8QEX7EfH.js → surrealql-CLhYGAfB.js} +1 -1
- package/dist/web/assets/{svelte-D_mubC5-.js → svelte-DTAlRHhK.js} +1 -1
- package/dist/web/assets/system-monitor-body-DsRXeMyU.js +2 -0
- package/dist/web/assets/system-monitor-tab-DdSZgux3.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-qXLcicFM.js → system-monitor-window-content-ClPo4-El.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-7fPZNEni.js → tab-host-window-content-d2TAD4PU.js} +1 -1
- package/dist/web/assets/{tab-store-DGJGnbwv.js → tab-store-OJa1GPCq.js} +1 -1
- package/dist/web/assets/team-member-sheet-7d2Qp-ie.js +2 -0
- package/dist/web/assets/{team-member-window-content-BgvJFypM.js → team-member-window-content-Bctg2gmg.js} +1 -1
- package/dist/web/assets/{templ-C1jV0fsO.js → templ-BMfhACxb.js} +1 -1
- package/dist/web/assets/{terminal-tab-Cs0sGljx.js → terminal-tab-0HQic25V.js} +3 -3
- package/dist/web/assets/{tex-D_StFXy_.js → tex-BcssM-QA.js} +1 -1
- package/dist/web/assets/tool-cards-CmLLRACd.js +9 -0
- package/dist/web/assets/treemap-KZPCXAKY-CE30wCXa.js +1 -0
- package/dist/web/assets/{ts-tags-B5m47rEm.js → ts-tags-CrR5IYI8.js} +1 -1
- package/dist/web/assets/{twig-C_br6GIl.js → twig-DchfUmyI.js} +1 -1
- package/dist/web/assets/{use-blob-url-C-SGi_Hb.js → use-blob-url-CPtCcY3i.js} +1 -1
- package/dist/web/assets/{use-inline-blame-B4trxGcR.js → use-inline-blame-sDMjcz1-.js} +3 -3
- package/dist/web/assets/{use-lsp-CV0vzILO.js → use-lsp-CBgID-EU.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DBqbWjWX.js → use-monaco-theme-gvhJ_jZh.js} +1 -1
- package/dist/web/assets/{use-open-team-member-DENsjp0s.js → use-open-team-member-C3mrUzIC.js} +1 -1
- package/dist/web/assets/{use-websocket-LgXiX1qN.js → use-websocket-C9wlRliB.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-Cbig5zA6.js → vendor-mermaid-BitiXJlL.js} +1 -1
- package/dist/web/assets/{vendor-ui-BBdkypKW.js → vendor-ui-DmZ-Mb-H.js} +1 -1
- package/dist/web/assets/video-preview-CSDK5j6H.js +1 -0
- package/dist/web/assets/{vue-DQwCnLdr.js → vue-1EL6Pw1h.js} +1 -1
- package/dist/web/assets/{vue-html-T_EoGpPJ.js → vue-html-BPQNwPC6.js} +1 -1
- package/dist/web/assets/{vue-vine-DmxLqX50.js → vue-vine-Bb_dsWLt.js} +1 -1
- package/dist/web/assets/{window-pip-registry-3l4hoxuD.js → window-pip-registry-yD6esSju.js} +1 -1
- package/dist/web/assets/ws-auth-CEYVQ8NH.js +1 -0
- package/dist/web/assets/{xml-Bs6Bw8is.js → xml-dxkLNA0K.js} +1 -1
- package/dist/web/assets/{xsl-f5ZNhRCc.js → xsl-Bw2adwrl.js} +1 -1
- package/dist/web/index.html +27 -26
- package/dist/web/sw.js +1 -1
- package/docs/architecture/ai-chat-and-providers.md +9 -0
- package/docs/architecture/plugins-and-tracing.md +285 -0
- package/docs/lessons-learned.md +31 -0
- package/docs/system-architecture.md +1 -0
- package/package.json +2 -1
- package/packages/ext-git-graph/src/compare-diff-base.test.ts +173 -0
- package/packages/ext-git-graph/src/compare-view.ts +22 -3
- package/packages/ext-git-graph/src/extension.ts +6 -0
- package/packages/ext-git-graph/src/types.ts +19 -0
- package/packages/ext-git-graph/src/webview-html.test.ts +159 -1
- package/packages/ext-git-graph/src/webview-html.ts +344 -26
- package/scripts/gen-file-icons.ts +493 -242
- package/src/providers/claude-agent-sdk.ts +42 -1
- package/src/providers/codex-app-server/codex-history.ts +77 -23
- package/src/server/index.ts +42 -7
- package/src/server/routes/chat.ts +15 -11
- package/src/server/routes/git.ts +83 -3
- package/src/server/routes/remote-desktop.ts +7 -2
- package/src/server/ws/global.ts +1 -1
- package/src/server/ws/remote-desktop.ts +12 -5
- package/src/services/binary-content.ts +25 -0
- package/src/services/chat-search.service.ts +1 -1
- package/src/services/chat.service.ts +9 -0
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/event-loop-lag.ts +208 -0
- package/src/services/file-lines.ts +114 -0
- package/src/services/file-watcher/recreated-dir-poller.ts +108 -6
- package/src/services/file-watcher/watch-tree.ts +131 -10
- package/src/services/file-watcher.service.ts +73 -23
- package/src/services/git-branch-diff/branch-diff-parse.ts +151 -0
- package/src/services/git-branch-diff/branch-diff.service.ts +79 -0
- package/src/services/git-hunks/git-hunks.service.ts +26 -11
- package/src/services/git-hunks/unified-diff.ts +15 -1
- package/src/services/git.service.ts +74 -24
- package/src/services/jsonl-transcript-parser.ts +138 -6
- package/src/services/log-rotate.ts +142 -0
- package/src/services/media-transcode/ffmpeg-capabilities.ts +72 -2
- package/src/services/remote-desktop/remote-desktop-audio.ts +193 -0
- package/src/services/remote-desktop/remote-desktop-capture-input.ts +87 -14
- package/src/services/remote-desktop/remote-desktop-capture.ts +40 -7
- package/src/services/remote-desktop/remote-desktop-clipboard.ts +166 -0
- package/src/services/remote-desktop/remote-desktop-displays.ts +77 -1
- package/src/services/remote-desktop/remote-desktop-encoder-args.ts +25 -6
- package/src/services/remote-desktop/remote-desktop-evdev-key-map.ts +69 -0
- package/src/services/remote-desktop/remote-desktop-input-linux.ts +253 -0
- package/src/services/remote-desktop/remote-desktop-input-uinput.ts +242 -0
- package/src/services/remote-desktop/remote-desktop-input.ts +40 -8
- package/src/services/remote-desktop/remote-desktop-linux-session.ts +146 -0
- package/src/services/remote-desktop/remote-desktop-ogg-opus.ts +144 -0
- package/src/services/remote-desktop/remote-desktop-privacy.ts +163 -0
- package/src/services/remote-desktop/remote-desktop-quality.ts +138 -0
- package/src/services/remote-desktop/remote-desktop-requirements.ts +158 -5
- package/src/services/remote-desktop/remote-desktop-resolution.ts +302 -0
- package/src/services/remote-desktop/remote-desktop-session.ts +523 -21
- package/src/services/remote-desktop/remote-desktop-x11.ts +250 -0
- package/src/services/supervisor.ts +47 -4
- package/src/services/windows-process-tree.ts +351 -351
- package/src/shared/codex-daily-guard.ts +12 -5
- package/src/shared/remote-desktop-custom-quality.ts +117 -0
- package/src/shared/remote-desktop-quality.ts +127 -0
- package/src/types/chat.ts +4 -0
- package/src/types/git.ts +52 -0
- package/src/web/components/branch-review/branch-review-tab.tsx +360 -0
- package/src/web/components/branch-review/branch-review-tree-row.tsx +138 -0
- package/src/web/components/chat/attachment-chips.tsx +30 -6
- package/src/web/components/chat/chat-tab.tsx +12 -1
- package/src/web/components/chat/message-events.tsx +273 -0
- package/src/web/components/chat/message-list.tsx +107 -861
- package/src/web/components/chat/message-markdown.tsx +34 -0
- package/src/web/components/chat/message-media.tsx +174 -0
- package/src/web/components/chat/message-user-bubble.tsx +399 -0
- package/src/web/components/chat/provider-selector.tsx +30 -22
- package/src/web/components/chat/session-history-list.tsx +2 -2
- package/src/web/components/chat/session-list-panel.tsx +3 -2
- package/src/web/components/editor/binary-diff-view.tsx +212 -0
- package/src/web/components/editor/diff-viewer.tsx +76 -3
- package/src/web/components/explorer/file-tree.tsx +235 -134
- package/src/web/components/explorer/flatten-visible-tree.ts +35 -40
- package/src/web/components/explorer/tree-node.tsx +69 -69
- package/src/web/components/explorer/use-tree-row-dnd.ts +3 -4
- package/src/web/components/extensions/extension-tree-view.tsx +4 -11
- package/src/web/components/git/git-status-panel.tsx +25 -0
- package/src/web/components/layout/command-palette.tsx +2 -18
- package/src/web/components/layout/draggable-tab.tsx +4 -2
- package/src/web/components/layout/nav-section-rail.tsx +12 -3
- package/src/web/components/layout/status-bar.tsx +10 -6
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/tab-pool.tsx +1 -0
- package/src/web/components/layout/upgrade-button.tsx +5 -1
- package/src/web/components/remote-desktop/remote-desktop-clipboard-client.ts +67 -0
- package/src/web/components/remote-desktop/remote-desktop-clipboard-notice.tsx +127 -0
- package/src/web/components/remote-desktop/remote-desktop-codec-labels.ts +28 -0
- package/src/web/components/remote-desktop/remote-desktop-custom-quality-dialog.tsx +144 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +13 -2
- package/src/web/components/remote-desktop/remote-desktop-mobile-settings.tsx +232 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +21 -8
- package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +82 -3
- package/src/web/components/remote-desktop/remote-desktop-recording.ts +76 -0
- package/src/web/components/remote-desktop/remote-desktop-resolution-list.ts +100 -0
- package/src/web/components/remote-desktop/remote-desktop-toolbar.tsx +497 -0
- package/src/web/components/remote-desktop/remote-desktop-view-style.ts +145 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +126 -35
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +11 -0
- package/src/web/components/remote-desktop/use-opus-audio-player.ts +148 -0
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +284 -1
- package/src/web/components/remote-desktop/use-remote-desktop-readiness.ts +75 -1
- package/src/web/components/remote-desktop/use-remote-desktop-recorder.ts +103 -0
- package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +19 -2
- package/src/web/components/remote-desktop/use-remote-input-capture.ts +39 -3
- package/src/web/components/settings/accounts/account-card.tsx +2 -2
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +4 -4
- package/src/web/components/settings/extension-manager-section.tsx +8 -1
- package/src/web/components/settings/theme-picker.tsx +2 -1
- package/src/web/components/ui/tooltip.tsx +15 -2
- package/src/web/lib/branch-review-state.ts +175 -0
- package/src/web/lib/extension-icons.ts +70 -0
- package/src/web/lib/file-icon-name.ts +62 -0
- package/src/web/lib/file-icons.generated.ts +3287 -25
- package/src/web/lib/file-icons.tsx +71 -73
- package/src/web/lib/flatten-expansions.ts +18 -5
- package/src/web/lib/message-ordinals.ts +23 -0
- package/src/web/lib/provider-logos.tsx +143 -0
- package/src/web/lib/tab-type-icons.ts +25 -0
- package/src/web/stores/file-store.ts +73 -35
- package/src/web/stores/panel-utils.ts +5 -0
- package/src/web/stores/project-framework-store.ts +82 -0
- package/src/web/stores/project-store.ts +7 -1
- package/src/web/stores/settings-store.ts +128 -0
- package/src/web/stores/tab-store.ts +1 -0
- package/src/web/stores/window-panel-persistence.ts +1 -0
- package/src/web/styles/file-icons.generated.css +3282 -234
- package/src/web/styles/globals.css +26 -0
- package/dist/web/assets/accounts-settings-section-BuxD9zH3.js +0 -1
- package/dist/web/assets/ai-settings-section-Dd9tOUp0.js +0 -1
- package/dist/web/assets/ai-settings-section-FrU2X8Fu.js +0 -1
- package/dist/web/assets/api-client-BUCo1lms.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-5K0wEkAn.js +0 -1
- package/dist/web/assets/chat-tab-Cr6_KSGO.js +0 -15
- package/dist/web/assets/code-editor-DfmlGamv.js +0 -7
- package/dist/web/assets/context-menu-DOODKZdF.js +0 -1
- package/dist/web/assets/diff-viewer-BpNj7pN9.js +0 -4
- package/dist/web/assets/explorer-body-D-5z464e.js +0 -2
- package/dist/web/assets/explorer-window-content-7-1wuRul.js +0 -1
- package/dist/web/assets/extension-manager-section-BhwB6nqr.js +0 -1
- package/dist/web/assets/file-icons-EzPL0q-k.js +0 -1
- package/dist/web/assets/file-icons.generated-CfaJuvZu.css +0 -1
- package/dist/web/assets/file-store-DMABxOkV.js +0 -1
- package/dist/web/assets/files-settings-section-CZzABuKz.js +0 -1
- package/dist/web/assets/git-log-panel-CRN4JVOx.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DkVT2MAM.js +0 -1
- package/dist/web/assets/group-chat-tab-DFa5mAx5.js +0 -1
- package/dist/web/assets/index-D7DbXD3G.css +0 -1
- package/dist/web/assets/index-ZtfxZOyb.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-BBsmKGzA.js +0 -1
- package/dist/web/assets/keybindings-store-tVfhPANS.js +0 -1
- package/dist/web/assets/markdown-renderer-sJNNVhlS.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-CZ0HZ2E_.js +0 -1
- package/dist/web/assets/notification-store-BZbsPnW0.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-DwmYPOlx.js +0 -1
- package/dist/web/assets/panel-store-BVLBRn6M.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-D0oogIFi.js +0 -1
- package/dist/web/assets/problems-panel-B7BWVhdy.js +0 -1
- package/dist/web/assets/project-store-BhMbVln3.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-CQIJOQGa.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-ByjaeZKX.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-view-D13mtr9F.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-CbqTCaC6.js +0 -1
- package/dist/web/assets/settings-body-Rnu-FcX-.js +0 -2
- package/dist/web/assets/settings-store-DZPgPtEA.js +0 -2
- package/dist/web/assets/settings-tab-C6PHxs0Z.js +0 -1
- package/dist/web/assets/settings-window-content-CQ4gT2qj.js +0 -1
- package/dist/web/assets/system-monitor-body-2vQ1kMxE.js +0 -2
- package/dist/web/assets/system-monitor-tab-krAcvwF7.js +0 -1
- package/dist/web/assets/team-member-sheet-2FnRmk1a.js +0 -2
- package/dist/web/assets/tool-cards-DU_S2o51.js +0 -9
- package/dist/web/assets/treemap-KZPCXAKY-BhEtjljr.js +0 -1
- package/dist/web/assets/use-remote-desktop-display-choice-BK_f787P.js +0 -1
- package/dist/web/assets/video-preview-BDNwumbW.js +0 -1
- package/dist/web/assets/ws-auth-DEAJiGCR.js +0 -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/dist/web/assets/{abap-tkptwpsK.js → abap-IN2yINdN.js} +0 -0
- /package/dist/web/assets/{actionscript-3-Di9TMGwg.js → actionscript-3-C_t772eR.js} +0 -0
- /package/dist/web/assets/{ada-CB9F079D.js → ada-DPme3emm.js} +0 -0
- /package/dist/web/assets/{andromeeda-Dcl8jpb7.js → andromeeda-DSV4i4mZ.js} +0 -0
- /package/dist/web/assets/{apache-D0Nk0qsH.js → apache-CWr8s8I8.js} +0 -0
- /package/dist/web/assets/{apex-mQIoWncD.js → apex-Z20JnDF5.js} +0 -0
- /package/dist/web/assets/{applescript-C6ljbcsI.js → applescript-BKNPu3ga.js} +0 -0
- /package/dist/web/assets/{apply-theme-BwIMNXyX.js → apply-theme-CbR31K6I.js} +0 -0
- /package/dist/web/assets/{ara-WFqRPGPe.js → ara-hWdY3O96.js} +0 -0
- /package/dist/web/assets/{asciidoc-bNd8w4nf.js → asciidoc-eynhq496.js} +0 -0
- /package/dist/web/assets/{asm-BQ1_F580.js → asm-Bu02Kq4P.js} +0 -0
- /package/dist/web/assets/{aurora-x-Ciqs_Dbu.js → aurora-x-BHBGXbIZ.js} +0 -0
- /package/dist/web/assets/{awk-GCR-6KZV.js → awk-B4ojezyv.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DxJXuj99.js → ayu-dark-D0GQzFi9.js} +0 -0
- /package/dist/web/assets/{ayu-light-jvSyJWDV.js → ayu-light-BVYb5Yn8.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-Cg3BPO6H.js → ayu-mirage-BGFW6oNb.js} +0 -0
- /package/dist/web/assets/{ballerina-BPz4CrX6.js → ballerina-B6Og3IKT.js} +0 -0
- /package/dist/web/assets/{bat-CSTs2hIG.js → bat-D8YFILfx.js} +0 -0
- /package/dist/web/assets/{beancount-DMateAZX.js → beancount-BjlbBBuX.js} +0 -0
- /package/dist/web/assets/{berry-9lXg0GRM.js → berry-Cz1ygeoj.js} +0 -0
- /package/dist/web/assets/{bibtex-DIbqmvFg.js → bibtex-CVkJceJh.js} +0 -0
- /package/dist/web/assets/{bicep-02WGsoJ8.js → bicep-DrhYI5GX.js} +0 -0
- /package/dist/web/assets/{bird2-BY0tbu3k.js → bird2-BV-KJtmc.js} +0 -0
- /package/dist/web/assets/{c-jMBX_9Je.js → c-CuLlvxbG.js} +0 -0
- /package/dist/web/assets/{c3-B8JVbNjW.js → c3-CI7-1CJ0.js} +0 -0
- /package/dist/web/assets/{cadence-DUIf1tvL.js → cadence-DjxhNP3Q.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-DGEc0s4P.js → can-open-in-ppm-cv5--yHX.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-FhUCVxex.js → catppuccin-frappe-CEozpJRh.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CzfrtmgL.js → catppuccin-latte-C4sXQFMc.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-6xL0lOtj.js → catppuccin-macchiato-Ci-fDhXy.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-D39-xS3P.js → catppuccin-mocha-CH7YXw8d.js} +0 -0
- /package/dist/web/assets/{clarity-D8TeGvoT.js → clarity-BWrJ7UmT.js} +0 -0
- /package/dist/web/assets/{clojure-Dx_acSX7.js → clojure-C94fh2hb.js} +0 -0
- /package/dist/web/assets/{cmake-CyeYPq8o.js → cmake-CtvCkMsG.js} +0 -0
- /package/dist/web/assets/{codeowners-DrTI0lmI.js → codeowners-CJnubqzH.js} +0 -0
- /package/dist/web/assets/{codeql-CPkFxVW-.js → codeql-BOoP5HY2.js} +0 -0
- /package/dist/web/assets/{common-lisp-B0k-3LTj.js → common-lisp-B9nlMFJd.js} +0 -0
- /package/dist/web/assets/{coq-uygpo7to.js → coq-CgzIQiSH.js} +0 -0
- /package/dist/web/assets/{csharp-DKUYGfSp.js → csharp-4xAIx3jw.js} +0 -0
- /package/dist/web/assets/{css-DoOUp7v4.js → css-D4YoLYGn.js} +0 -0
- /package/dist/web/assets/{csv-B3YE13gi.js → csv-C2v-ZakW.js} +0 -0
- /package/dist/web/assets/{csv-parser-DXePqmXe.js → csv-parser-_oDBUkwQ.js} +0 -0
- /package/dist/web/assets/{cue-DViO2wvn.js → cue-Bbznh_an.js} +0 -0
- /package/dist/web/assets/{cypher-4BMCIW5N.js → cypher-BKhVEARF.js} +0 -0
- /package/dist/web/assets/{d-BT_sTHas.js → d-D6IhWopl.js} +0 -0
- /package/dist/web/assets/{dark-plus-DVVXu-1N.js → dark-plus-xAWu790O.js} +0 -0
- /package/dist/web/assets/{dart-DHz6f6_6.js → dart-Bj6S9O1j.js} +0 -0
- /package/dist/web/assets/{data-grid-types-OgUxMLdQ.js → data-grid-types-C5bthITf.js} +0 -0
- /package/dist/web/assets/{dax-ChYCjweK.js → dax-CF2zvaqq.js} +0 -0
- /package/dist/web/assets/{desktop-D6Lt4p04.js → desktop-CnmyxMKE.js} +0 -0
- /package/dist/web/assets/{diff-D8TDe4hP.js → diff-Qi8WuJsI.js} +0 -0
- /package/dist/web/assets/{dist-DOTlFQJA.js → dist-Bjseolz4.js} +0 -0
- /package/dist/web/assets/{dist-CrBOCtCW.js → dist-Czq7BUMy.js} +0 -0
- /package/dist/web/assets/{docker-Bsp0YbPx.js → docker-vLbbNP5d.js} +0 -0
- /package/dist/web/assets/{dotenv-B_7giSY_.js → dotenv-DJMvvNzn.js} +0 -0
- /package/dist/web/assets/{dracula-TrQL2iB5.js → dracula-CtAh2rhB.js} +0 -0
- /package/dist/web/assets/{dracula-soft-n2XOlgh-.js → dracula-soft-CXtuAAf6.js} +0 -0
- /package/dist/web/assets/{dream-maker-CdIEdhpT.js → dream-maker-D5YQyK49.js} +0 -0
- /package/dist/web/assets/{editor-font-BDrdiGf1.js → editor-font-ClTC9gaD.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CSbixs6u.js → emacs-lisp-CGTjbiqF.js} +0 -0
- /package/dist/web/assets/{everforest-dark-BuZahZYp.js → everforest-dark-D59p7vAQ.js} +0 -0
- /package/dist/web/assets/{everforest-light-DPH53z5H.js → everforest-light-B3Qtud3V.js} +0 -0
- /package/dist/web/assets/{fennel-B1-K_hlJ.js → fennel-BCyHNUwf.js} +0 -0
- /package/dist/web/assets/{fish-Cy12Dbua.js → fish-DDDlLV2G.js} +0 -0
- /package/dist/web/assets/{fluent-B_82ys8E.js → fluent-By54R9tl.js} +0 -0
- /package/dist/web/assets/{format-bytes-D5sBgIqU.js → format-bytes-D1AQyUCt.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BRQdNzZF.js → fortran-free-form-tP8mRym_.js} +0 -0
- /package/dist/web/assets/{gdscript-XBDmLUm9.js → gdscript-nHC2z6Te.js} +0 -0
- /package/dist/web/assets/{gdshader-DhVfB3u4.js → gdshader-K-cSmPlW.js} +0 -0
- /package/dist/web/assets/{genie-X3K29Y49.js → genie-BAcRhvEe.js} +0 -0
- /package/dist/web/assets/{gherkin-DKIx-6cX.js → gherkin-CcifxMLb.js} +0 -0
- /package/dist/web/assets/{github-dark-Bf7zdp5s.js → github-dark-67KKlMdB.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DqsBBV11.js → github-dark-default-tja8pidR.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-X2gqRz6w.js → github-dark-dimmed-CtjhYQtx.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-Z7tvbGX4.js → github-dark-high-contrast-Bf-LPFgn.js} +0 -0
- /package/dist/web/assets/{github-light-BKxt-7zZ.js → github-light-CbJxE7n3.js} +0 -0
- /package/dist/web/assets/{github-light-default-B0liF4d4.js → github-light-default-DRQmdHxt.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-F4encJwr.js → github-light-high-contrast-CJkDgTeM.js} +0 -0
- /package/dist/web/assets/{gleam-Pz2bHgBB.js → gleam-D4LCl2ri.js} +0 -0
- /package/dist/web/assets/{gn-CgmdNsPU.js → gn-COY8zZmC.js} +0 -0
- /package/dist/web/assets/{gnuplot-qLNF5yZ9.js → gnuplot-ROk1VitR.js} +0 -0
- /package/dist/web/assets/{go-CSDUNhE7.js → go-H30c3p0C.js} +0 -0
- /package/dist/web/assets/{groovy-B3NEnOHF.js → groovy-DBLCaelV.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DcoftiUX.js → gruvbox-dark-hard-DJy4irps.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-D1wVSuD8.js → gruvbox-dark-medium-Dhfw1QTO.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DWpunymH.js → gruvbox-dark-soft-BncJU-8w.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-rHMvyUdS.js → gruvbox-light-hard-Cq12_YvM.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-3O2KiXO7.js → gruvbox-light-medium-DqVja5_v.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-lNlXJPwH.js → gruvbox-light-soft-IvwhveGF.js} +0 -0
- /package/dist/web/assets/{haskell-aaG3aC0h.js → haskell-DA1JV-MC.js} +0 -0
- /package/dist/web/assets/{haxe-CPcHhUDd.js → haxe-V9RTRc5E.js} +0 -0
- /package/dist/web/assets/{hcl-CMQNlR_4.js → hcl-hr1lyZ10.js} +0 -0
- /package/dist/web/assets/{hjson-DeuklPFj.js → hjson-D3vW3i7X.js} +0 -0
- /package/dist/web/assets/{hlsl-BI3hlsaM.js → hlsl-BB85gfJT.js} +0 -0
- /package/dist/web/assets/{horizon-C7-gOcjs.js → horizon-DU8OOnj8.js} +0 -0
- /package/dist/web/assets/{horizon-bright-Cxt9bRW7.js → horizon-bright-DNMUclW6.js} +0 -0
- /package/dist/web/assets/{houston-DbF2G7iD.js → houston-DAiZKf6T.js} +0 -0
- /package/dist/web/assets/{hy-DRG6Hs2I.js → hy-C6Zv0LMe.js} +0 -0
- /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-CGvlv8zR.js} +0 -0
- /package/dist/web/assets/{imba-CxKv_2TW.js → imba-Didnp6Y-.js} +0 -0
- /package/dist/web/assets/{ini-BiHsyCdO.js → ini-m0T7q4gQ.js} +0 -0
- /package/dist/web/assets/{input-D-dR8RI6.js → input-C7Q8FxeL.js} +0 -0
- /package/dist/web/assets/{java-D3RZzgDB.js → java-Buy07jGn.js} +0 -0
- /package/dist/web/assets/{javascript-4FmSqG-T.js → javascript-Cjq3CbRL.js} +0 -0
- /package/dist/web/assets/{json-CjB236_E.js → json-BSVu8IqD.js} +0 -0
- /package/dist/web/assets/{json5-bkfO-Rco.js → json5-NN_Vb7e6.js} +0 -0
- /package/dist/web/assets/{jsonc-BwaTeLyv.js → jsonc-BdyZhBUw.js} +0 -0
- /package/dist/web/assets/{jsonl-CfxNSwGd.js → jsonl-BsDSNvwi.js} +0 -0
- /package/dist/web/assets/{jsonnet-CQETCYrp.js → jsonnet-Gm963F-U.js} +0 -0
- /package/dist/web/assets/{jssm-D5o3Zzdz.js → jssm-Ds3bKWgJ.js} +0 -0
- /package/dist/web/assets/{jsx-BAMILPLo.js → jsx-BT5mW58X.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BhGVxu3R.js → kanagawa-dragon-BvNvf7xd.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-FH4Y59ro.js → kanagawa-lotus-CuSiliSn.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-eilvP9MB.js → kanagawa-wave-C6g-d6jT.js} +0 -0
- /package/dist/web/assets/{kdl-BQ-yvXUK.js → kdl-Bx4kZef-.js} +0 -0
- /package/dist/web/assets/{kotlin-BuCx0-VT.js → kotlin-qvaVvCcm.js} +0 -0
- /package/dist/web/assets/{kusto-DAN_n_Cy.js → kusto-BPLyNCN6.js} +0 -0
- /package/dist/web/assets/{laserwave-Cq2BlxrT.js → laserwave-Sf8ZEhWP.js} +0 -0
- /package/dist/web/assets/{lean-Dhq7uXKw.js → lean-DGe6uqgk.js} +0 -0
- /package/dist/web/assets/{less-CxaMQb8j.js → less-CqM85x-9.js} +0 -0
- /package/dist/web/assets/{lib-BVwmtv2i.js → lib-CD6pOS4u.js} +0 -0
- /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-CPh1Av0r.js} +0 -0
- /package/dist/web/assets/{light-plus-B4sMAend.js → light-plus-DBtKwhFA.js} +0 -0
- /package/dist/web/assets/{llvm-DQ6-B_6Y.js → llvm-Dpbq3gky.js} +0 -0
- /package/dist/web/assets/{log-Bb_vqvu7.js → log-qKMcPm9S.js} +0 -0
- /package/dist/web/assets/{logo-BzfYv3A_.js → logo-DFo7i5KH.js} +0 -0
- /package/dist/web/assets/{luau-BnAeNR77.js → luau-DGlqWFyL.js} +0 -0
- /package/dist/web/assets/{make-B6IFuxpC.js → make-CixdvUQe.js} +0 -0
- /package/dist/web/assets/{markdown-Z0xi77Fn.js → markdown-CmRnbngE.js} +0 -0
- /package/dist/web/assets/{material-theme-1scy1SYn.js → material-theme-WDUbafB-.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-G-JykXQP.js → material-theme-darker-u8y2xpOk.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BtKkCMVq.js → material-theme-lighter-cyQZK3Tg.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-irrDC89L.js → material-theme-ocean-LBAVvw7k.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-Eq5_K4LM.js → material-theme-palenight-BJqhBxL0.js} +0 -0
- /package/dist/web/assets/{matlab-BE3oCXZC.js → matlab-B38sOJee.js} +0 -0
- /package/dist/web/assets/{mdx-DYiuFTGw.js → mdx-C8QrE_IW.js} +0 -0
- /package/dist/web/assets/{mermaid-DVlaEvtw.js → mermaid-BQ-eeU8A.js} +0 -0
- /package/dist/web/assets/{min-dark-DSkeTpQa.js → min-dark-D4gNa5tv.js} +0 -0
- /package/dist/web/assets/{min-light-CFv2Q0uF.js → min-light-Dfbhn_PL.js} +0 -0
- /package/dist/web/assets/{mipsasm-CiAFuZQ2.js → mipsasm-bLOhJkdm.js} +0 -0
- /package/dist/web/assets/{mojo-ARshcaSQ.js → mojo-B0BCOMQW.js} +0 -0
- /package/dist/web/assets/{monaco-dotenv-language-BDYnKcnr.js → monaco-dotenv-language-uB-wok6C.js} +0 -0
- /package/dist/web/assets/{monokai-CPTV-SOg.js → monokai-Bdw_07OM.js} +0 -0
- /package/dist/web/assets/{moonbit-D57B0dZr.js → moonbit-BFdGaeuk.js} +0 -0
- /package/dist/web/assets/{move-qgy3G_o9.js → move-CyXQFGPM.js} +0 -0
- /package/dist/web/assets/{narrat-CZ3rC-oc.js → narrat-9DvTD3_3.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-BxxIOcI4.js → nextflow-groovy-C4VY3kNY.js} +0 -0
- /package/dist/web/assets/{night-owl-B1ryGCda.js → night-owl-feYm66V0.js} +0 -0
- /package/dist/web/assets/{night-owl-light-BZvNnvMF.js → night-owl-light-DALqFjvx.js} +0 -0
- /package/dist/web/assets/{nix-BVBQi8yv.js → nix-C1kPA9Sw.js} +0 -0
- /package/dist/web/assets/{nord-3NXw613h.js → nord-1dvrj1Al.js} +0 -0
- /package/dist/web/assets/{nushell-BMOLRalb.js → nushell-DSQ_9Uqj.js} +0 -0
- /package/dist/web/assets/{objective-c-AxruEUgh.js → objective-c-Dhcn5ZK8.js} +0 -0
- /package/dist/web/assets/{objective-cpp-cFT0qRsD.js → objective-cpp-CGVYCSrp.js} +0 -0
- /package/dist/web/assets/{ocaml-_8JH6O8J.js → ocaml-rxXW21An.js} +0 -0
- /package/dist/web/assets/{odin-nwYvR0O6.js → odin-ClSJX2DE.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-C1LaYJaH.js → one-dark-pro-3gbWDfFW.js} +0 -0
- /package/dist/web/assets/{one-light-B7rMc__c.js → one-light-C2q5ZZAw.js} +0 -0
- /package/dist/web/assets/{openscad-Cwes8bKO.js → openscad-eHXeafBl.js} +0 -0
- /package/dist/web/assets/{pascal-DMt-6tdZ.js → pascal-kKha3Txs.js} +0 -0
- /package/dist/web/assets/{pkl-QBTyYQmB.js → pkl-CTiPytg-.js} +0 -0
- /package/dist/web/assets/{plastic-BTSUp8Fk.js → plastic-D1Ygezi4.js} +0 -0
- /package/dist/web/assets/{plsql-DQv3IFXP.js → plsql-D_gbgZIr.js} +0 -0
- /package/dist/web/assets/{po-CNwZx4tW.js → po-BkhbDUnq.js} +0 -0
- /package/dist/web/assets/{poimandres-8Os7Qequ.js → poimandres-Do6NywrA.js} +0 -0
- /package/dist/web/assets/{polar-b4yK8oD8.js → polar-B3B23wca.js} +0 -0
- /package/dist/web/assets/{postcss-sFctwpxx.js → postcss-Dv_YBvWd.js} +0 -0
- /package/dist/web/assets/{powerquery-Cg_WrtuN.js → powerquery-fKJWI-Sh.js} +0 -0
- /package/dist/web/assets/{powershell-C7Ifz95h.js → powershell-B4Y7KIgx.js} +0 -0
- /package/dist/web/assets/{prisma-Z52HUJzQ.js → prisma-J--XdTXX.js} +0 -0
- /package/dist/web/assets/{prolog-DLqW-zR1.js → prolog-BBTg4Vb3.js} +0 -0
- /package/dist/web/assets/{proto-BOIzxCCJ.js → proto-DWlum3T8.js} +0 -0
- /package/dist/web/assets/{puppet-De7TjZIf.js → puppet-ZiUfc9JV.js} +0 -0
- /package/dist/web/assets/{purescript-R76ou1WT.js → purescript-CmT8SCHB.js} +0 -0
- /package/dist/web/assets/{python-Dprm0tNP.js → python-BURpj-fq.js} +0 -0
- /package/dist/web/assets/{qmldir-Clp_jpNw.js → qmldir-CpaiJywQ.js} +0 -0
- /package/dist/web/assets/{qss-eQPYYzSo.js → qss-C-zdz0i5.js} +0 -0
- /package/dist/web/assets/{r-CCcRHIf5.js → r-K56BeS-s.js} +0 -0
- /package/dist/web/assets/{racket-CzE472yp.js → racket-gkn6Z_Jw.js} +0 -0
- /package/dist/web/assets/{raku-DQwXrTSi.js → raku-C0ZBuKlx.js} +0 -0
- /package/dist/web/assets/{red-KCS6wyop.js → red-BWZ6x-3p.js} +0 -0
- /package/dist/web/assets/{reg-CcrAddUh.js → reg-Dt_EsyGR.js} +0 -0
- /package/dist/web/assets/{regexp-DDbud-Ri.js → regexp-CT6BLG2C.js} +0 -0
- /package/dist/web/assets/{rel-99GDgaHp.js → rel-MJ2LcyzJ.js} +0 -0
- /package/dist/web/assets/{resolve-theme-CW2niGV2.js → resolve-theme-B_ukrg_8.js} +0 -0
- /package/dist/web/assets/{riscv-Dm2eNv0R.js → riscv-BZYWzMAA.js} +0 -0
- /package/dist/web/assets/{ron-CzFt0ygH.js → ron-CLbwHWMn.js} +0 -0
- /package/dist/web/assets/{rose-pine-Ck6lCrCP.js → rose-pine-BPiI_daR.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-vtALRJNj.js → rose-pine-dawn-BjkJc7dW.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-DPto8qXw.js → rose-pine-moon-BQSxkt7g.js} +0 -0
- /package/dist/web/assets/{rosmsg-NtS6429H.js → rosmsg-yt4151pJ.js} +0 -0
- /package/dist/web/assets/{rust-BjIXvF9h.js → rust-1dAcengL.js} +0 -0
- /package/dist/web/assets/{sass-CQ23wf5E.js → sass-DUgtQJsg.js} +0 -0
- /package/dist/web/assets/{scala-LfKLM143.js → scala-CxoGYpS0.js} +0 -0
- /package/dist/web/assets/{scheme-D3SLBXlF.js → scheme-B2K3u9e3.js} +0 -0
- /package/dist/web/assets/{sdbl-ODRd2rGN.js → sdbl-C0Ygk0n6.js} +0 -0
- /package/dist/web/assets/{shellscript-3KB1G_oA.js → shellscript-C2CvhqMk.js} +0 -0
- /package/dist/web/assets/{slack-dark-DTtyWmIy.js → slack-dark-DYdgzRGj.js} +0 -0
- /package/dist/web/assets/{slack-ochin-Bu0PApk3.js → slack-ochin-X-WD0WCg.js} +0 -0
- /package/dist/web/assets/{smalltalk-haMT-Sbe.js → smalltalk-Dp7g_zb9.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DXJzefE9.js → snazzy-light-DWHWr-a6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-B6LyYa18.js → solarized-dark-CxdLsjvb.js} +0 -0
- /package/dist/web/assets/{solarized-light-QLADebkv.js → solarized-light-Cz9rAvG5.js} +0 -0
- /package/dist/web/assets/{solidity-mzfq1aDc.js → solidity-D33gHHdn.js} +0 -0
- /package/dist/web/assets/{splunk-CyH4RWBp.js → splunk-BFbOroYw.js} +0 -0
- /package/dist/web/assets/{sql-CWVlT5D3.js → sql-ChpxvmdY.js} +0 -0
- /package/dist/web/assets/{ssh-config-DNvQJ1Qx.js → ssh-config-CUuSQ6JG.js} +0 -0
- /package/dist/web/assets/{streaming-store-uDu3PoN1.js → streaming-store-BYukdB56.js} +0 -0
- /package/dist/web/assets/{stylus-ByQQdtmf.js → stylus-DDihP0_W.js} +0 -0
- /package/dist/web/assets/{swift-D1cpY4ow.js → swift-XoalUn4x.js} +0 -0
- /package/dist/web/assets/{synthwave-84-BDF_-QiW.js → synthwave-84-DjkG0Bff.js} +0 -0
- /package/dist/web/assets/{system-verilog-BYrkMydC.js → system-verilog-f_pLKMYd.js} +0 -0
- /package/dist/web/assets/{systemd-CQm3zyK0.js → systemd-DjDcLfxp.js} +0 -0
- /package/dist/web/assets/{talonscript-DMW5CzE1.js → talonscript-BpUzYcDl.js} +0 -0
- /package/dist/web/assets/{tasl-BhFzMCr2.js → tasl-DP-cUbg8.js} +0 -0
- /package/dist/web/assets/{tcl-D7VEAF-9.js → tcl-BHoy-dfE.js} +0 -0
- /package/dist/web/assets/{terraform-DdT5A0Vy.js → terraform-vCVxN25B.js} +0 -0
- /package/dist/web/assets/{tokyo-night-WGLMM6Q9.js → tokyo-night-D0u4xZba.js} +0 -0
- /package/dist/web/assets/{toml-CZ7ipXHZ.js → toml-BS8D0__A.js} +0 -0
- /package/dist/web/assets/{tsv-B1zVvoTr.js → tsv-DCb-NQh3.js} +0 -0
- /package/dist/web/assets/{tsx-D4HwRUBq.js → tsx-DF8YeRgG.js} +0 -0
- /package/dist/web/assets/{turtle-B4r8KEJH.js → turtle-Bhx1GlIy.js} +0 -0
- /package/dist/web/assets/{typescript-BnHhxMtp.js → typescript-CAVD2Wb6.js} +0 -0
- /package/dist/web/assets/{typespec-DP2q46GT.js → typespec-D7mchdY_.js} +0 -0
- /package/dist/web/assets/{typst-DFsD_yaD.js → typst-CeXzq6Yw.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-BQ9Bv5Ga.js → use-is-mobile-TkcwP1Gx.js} +0 -0
- /package/dist/web/assets/{use-visual-viewport-CbwnktSG.js → use-visual-viewport-Lp878uzs.js} +0 -0
- /package/dist/web/assets/{v-Bji8hSVT.js → v-DhzO54je.js} +0 -0
- /package/dist/web/assets/{vala-D3TIgGmU.js → vala-Dw_RAk3E.js} +0 -0
- /package/dist/web/assets/{vb-DvcxFS2q.js → vb-CiXdNh2Y.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-BqPDsE9b.js → vendor-xterm-BHEY6Ctu.js} +0 -0
- /package/dist/web/assets/{verilog-Dy7cF35d.js → verilog-fITdxqwV.js} +0 -0
- /package/dist/web/assets/{vesper-BzjwUv9J.js → vesper-B1wm_At7.js} +0 -0
- /package/dist/web/assets/{vhdl-DuFnFNmW.js → vhdl-ebHjqKp9.js} +0 -0
- /package/dist/web/assets/{viml-yAPgKlbz.js → viml-Dgmnoxoo.js} +0 -0
- /package/dist/web/assets/{vitesse-black-oSSQdyIZ.js → vitesse-black-BZ5gMb7l.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BZxwPihB.js → vitesse-dark-Lij7kkOt.js} +0 -0
- /package/dist/web/assets/{vitesse-light-uIigycvt.js → vitesse-light-DW6Wq7VC.js} +0 -0
- /package/dist/web/assets/{vyper-CX3NAxm5.js → vyper-6FxDnVNg.js} +0 -0
- /package/dist/web/assets/{wasm-6mEOOvVa.js → wasm-BDxaNPCj.js} +0 -0
- /package/dist/web/assets/{wasm-D9td37qO.js → wasm-DKhAtzfe.js} +0 -0
- /package/dist/web/assets/{wenyan-lwNIHgk6.js → wenyan-C0k0DYG_.js} +0 -0
- /package/dist/web/assets/{wgsl-DQAzCfog.js → wgsl-Y2zagbdn.js} +0 -0
- /package/dist/web/assets/{wikitext-DTXmt5NE.js → wikitext-C4QGWzkY.js} +0 -0
- /package/dist/web/assets/{wit-Bdw_7WDH.js → wit-CQESzjHy.js} +0 -0
- /package/dist/web/assets/{wolfram-DqyHDZ07.js → wolfram-B-ia69Ia.js} +0 -0
- /package/dist/web/assets/{yaml-CPXhdByd.js → yaml-CEYYTQPm.js} +0 -0
- /package/dist/web/assets/{zenscript-6s_i5zek.js → zenscript-BggWXIeJ.js} +0 -0
- /package/dist/web/assets/{zig-B2htezJ_.js → zig-CbaUfBQe.js} +0 -0
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Edge forwarder — owns the public port and pipes raw TCP to the server's
|
|
3
|
-
* current loopback port.
|
|
4
|
-
*
|
|
5
|
-
* ─── CRITICAL INVARIANT: THIS PROCESS MUST NEVER SPAWN A CHILD ───
|
|
6
|
-
*
|
|
7
|
-
* On Windows a child spawned with fd stdio inherits every inheritable handle,
|
|
8
|
-
* including a listening socket. When such a descendant is orphaned it keeps the
|
|
9
|
-
* socket open, so the port stays in LISTEN under a dead PID and can never be
|
|
10
|
-
* rebound — a "zombie port". The server hits this constantly because it spawns
|
|
11
|
-
* chat/tool/MCP subprocesses; those inherit its listener and wedge its port.
|
|
12
|
-
*
|
|
13
|
-
* The edge exists precisely because it spawns nothing, so its socket cannot be
|
|
14
|
-
* inherited and its port cannot zombie. cloudflared can therefore stay pinned
|
|
15
|
-
* to it forever and the public URL stops rotating. Adding any child-process
|
|
16
|
-
* call to this file reintroduces the exact bug the edge was built to remove.
|
|
17
|
-
*
|
|
18
|
-
* Raw TCP, never HTTP: WebSocket upgrades and SSE streams pass through
|
|
19
|
-
* untouched, and forwarded requests keep the headers cloudflared set.
|
|
20
|
-
*/
|
|
21
|
-
import net from "node:net";
|
|
22
|
-
import { resolveTargetPort, _resetTargetCache } from "./edge-target-resolver.ts";
|
|
23
|
-
|
|
24
|
-
/** Delay between attempts while waiting for the server to come back. */
|
|
25
|
-
const CONNECT_RETRY_MS = 250;
|
|
26
|
-
/**
|
|
27
|
-
* How long a client connection waits for a reachable server before giving up.
|
|
28
|
-
* During an upgrade the server is briefly absent; refusing instantly would
|
|
29
|
-
* surface as a broken page rather than a slow one.
|
|
30
|
-
*/
|
|
31
|
-
const CONNECT_WINDOW_MS = 5000;
|
|
32
|
-
|
|
33
|
-
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
34
|
-
|
|
35
|
-
/** Resolve once the socket is connected; reject on the first connect error. */
|
|
36
|
-
function connectUpstream(port: number): Promise<net.Socket> {
|
|
37
|
-
return new Promise((resolvePromise, reject) => {
|
|
38
|
-
const upstream = net.connect({
|
|
39
|
-
port,
|
|
40
|
-
host: "127.0.0.1", // never taken from status.json — loopback only
|
|
41
|
-
allowHalfOpen: true,
|
|
42
|
-
});
|
|
43
|
-
const onError = (err: Error) => {
|
|
44
|
-
upstream.destroy();
|
|
45
|
-
reject(err);
|
|
46
|
-
};
|
|
47
|
-
upstream.once("error", onError);
|
|
48
|
-
upstream.once("connect", () => {
|
|
49
|
-
upstream.removeListener("error", onError);
|
|
50
|
-
resolvePromise(upstream);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Largest amount of client data held while waiting for the server. Bounds the
|
|
57
|
-
* memory a slow upload can pin during an upgrade window.
|
|
58
|
-
*/
|
|
59
|
-
const MAX_PENDING_BYTES = 8 * 1024 * 1024;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Pipe one client connection to the server.
|
|
63
|
-
*
|
|
64
|
-
* Resolving the upstream is async, so the first bytes usually arrive before
|
|
65
|
-
* there is anywhere to send them. They must be captured by a `data` listener
|
|
66
|
-
* attached synchronously on this very tick and replayed once the upstream
|
|
67
|
-
* exists.
|
|
68
|
-
*
|
|
69
|
-
* `socket.pause()` is NOT a substitute — verified against Bun 1.3.13: bytes
|
|
70
|
-
* that arrive while paused are lost, and a later `pipe()`/`resume()` does not
|
|
71
|
-
* bring them back. A socket with no `data` listener drops them outright too.
|
|
72
|
-
* Both variants silently swallowed the first request in testing.
|
|
73
|
-
*/
|
|
74
|
-
async function handleConnection(
|
|
75
|
-
client: net.Socket,
|
|
76
|
-
connectWindowMs: number,
|
|
77
|
-
): Promise<void> {
|
|
78
|
-
const pending: Buffer[] = [];
|
|
79
|
-
let pendingBytes = 0;
|
|
80
|
-
let clientEnded = false;
|
|
81
|
-
const onEarlyData = (chunk: Buffer) => {
|
|
82
|
-
pendingBytes += chunk.length;
|
|
83
|
-
if (pendingBytes > MAX_PENDING_BYTES) {
|
|
84
|
-
client.destroy();
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
pending.push(chunk);
|
|
88
|
-
};
|
|
89
|
-
client.on("data", onEarlyData);
|
|
90
|
-
client.once("end", () => { clientEnded = true; });
|
|
91
|
-
|
|
92
|
-
let upstream: net.Socket | null = null;
|
|
93
|
-
let clientGone = false;
|
|
94
|
-
|
|
95
|
-
// A socket error with no listener is an unhandled 'error' event, which would
|
|
96
|
-
// take the whole edge process — and the public port — down with it.
|
|
97
|
-
const teardown = () => {
|
|
98
|
-
upstream?.destroy();
|
|
99
|
-
client.destroy();
|
|
100
|
-
};
|
|
101
|
-
client.on("error", teardown);
|
|
102
|
-
client.once("close", () => { clientGone = true; });
|
|
103
|
-
|
|
104
|
-
const deadline = Date.now() + connectWindowMs;
|
|
105
|
-
while (!clientGone && Date.now() < deadline) {
|
|
106
|
-
const port = resolveTargetPort();
|
|
107
|
-
if (port !== null) {
|
|
108
|
-
try {
|
|
109
|
-
upstream = await connectUpstream(port);
|
|
110
|
-
break;
|
|
111
|
-
} catch {
|
|
112
|
-
// Server not up yet (or moved mid-restart) — re-resolve and retry.
|
|
113
|
-
_resetTargetCache();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
await sleep(CONNECT_RETRY_MS);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (clientGone) return;
|
|
120
|
-
if (!upstream) {
|
|
121
|
-
client.destroy();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
upstream.on("error", teardown);
|
|
126
|
-
|
|
127
|
-
// Replay what arrived during the connect, then hand both directions to the
|
|
128
|
-
// stream machinery. Removing the listener and piping happen in the same tick,
|
|
129
|
-
// so no chunk can slip through the gap.
|
|
130
|
-
client.off("data", onEarlyData);
|
|
131
|
-
for (const chunk of pending) upstream.write(chunk);
|
|
132
|
-
pending.length = 0;
|
|
133
|
-
|
|
134
|
-
client.pipe(upstream);
|
|
135
|
-
upstream.pipe(client);
|
|
136
|
-
|
|
137
|
-
// The client may have half-closed while we were still connecting.
|
|
138
|
-
if (clientEnded) upstream.end();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export interface EdgeForwarderOptions {
|
|
142
|
-
publicPort: number;
|
|
143
|
-
host?: string;
|
|
144
|
-
/** How long a client waits for a reachable server. Defaults to 5s. */
|
|
145
|
-
connectWindowMs?: number;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Bind the public port and start forwarding. Resolves once listening so the
|
|
150
|
-
* caller can fail fast when the port is unavailable.
|
|
151
|
-
*/
|
|
152
|
-
export function startEdgeForwarder(
|
|
153
|
-
opts: EdgeForwarderOptions,
|
|
154
|
-
): Promise<net.Server> {
|
|
155
|
-
const host = opts.host ?? "0.0.0.0";
|
|
156
|
-
const connectWindowMs = opts.connectWindowMs ?? CONNECT_WINDOW_MS;
|
|
157
|
-
return new Promise((resolvePromise, reject) => {
|
|
158
|
-
// allowHalfOpen keeps the other direction alive when one side sends FIN —
|
|
159
|
-
// long-lived chat WS and SSE streams depend on it.
|
|
160
|
-
const server = net.createServer({ allowHalfOpen: true }, (client) => {
|
|
161
|
-
void handleConnection(client, connectWindowMs);
|
|
162
|
-
});
|
|
163
|
-
server.once("error", reject);
|
|
164
|
-
server.listen(opts.publicPort, host, () => {
|
|
165
|
-
server.removeListener("error", reject);
|
|
166
|
-
resolvePromise(server);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ─── Process entry ─────────────────────────────────────────────────────
|
|
172
|
-
// Mirrors the `__serve__` guard in src/server/index.ts: the supervisor
|
|
173
|
-
// re-invokes the binary as `<bin> __edge__ <publicPort> <host>`.
|
|
174
|
-
if (process.argv.includes("__edge__")) {
|
|
175
|
-
const idx = process.argv.indexOf("__edge__");
|
|
176
|
-
const publicPort = parseInt(process.argv[idx + 1] ?? "", 10);
|
|
177
|
-
const host = process.argv[idx + 2] ?? "0.0.0.0";
|
|
178
|
-
|
|
179
|
-
if (!Number.isInteger(publicPort) || publicPort <= 0 || publicPort > 65535) {
|
|
180
|
-
process.stderr.write(`[edge] Invalid public port: ${process.argv[idx + 1]}\n`);
|
|
181
|
-
process.exit(2);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
await startEdgeForwarder({ publicPort, host });
|
|
186
|
-
process.stderr.write(`[edge] Listening on ${host}:${publicPort}\n`);
|
|
187
|
-
} catch (err) {
|
|
188
|
-
process.stderr.write(`[edge] Failed to bind ${host}:${publicPort}: ${err}\n`);
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Edge forwarder — owns the public port and pipes raw TCP to the server's
|
|
3
|
+
* current loopback port.
|
|
4
|
+
*
|
|
5
|
+
* ─── CRITICAL INVARIANT: THIS PROCESS MUST NEVER SPAWN A CHILD ───
|
|
6
|
+
*
|
|
7
|
+
* On Windows a child spawned with fd stdio inherits every inheritable handle,
|
|
8
|
+
* including a listening socket. When such a descendant is orphaned it keeps the
|
|
9
|
+
* socket open, so the port stays in LISTEN under a dead PID and can never be
|
|
10
|
+
* rebound — a "zombie port". The server hits this constantly because it spawns
|
|
11
|
+
* chat/tool/MCP subprocesses; those inherit its listener and wedge its port.
|
|
12
|
+
*
|
|
13
|
+
* The edge exists precisely because it spawns nothing, so its socket cannot be
|
|
14
|
+
* inherited and its port cannot zombie. cloudflared can therefore stay pinned
|
|
15
|
+
* to it forever and the public URL stops rotating. Adding any child-process
|
|
16
|
+
* call to this file reintroduces the exact bug the edge was built to remove.
|
|
17
|
+
*
|
|
18
|
+
* Raw TCP, never HTTP: WebSocket upgrades and SSE streams pass through
|
|
19
|
+
* untouched, and forwarded requests keep the headers cloudflared set.
|
|
20
|
+
*/
|
|
21
|
+
import net from "node:net";
|
|
22
|
+
import { resolveTargetPort, _resetTargetCache } from "./edge-target-resolver.ts";
|
|
23
|
+
|
|
24
|
+
/** Delay between attempts while waiting for the server to come back. */
|
|
25
|
+
const CONNECT_RETRY_MS = 250;
|
|
26
|
+
/**
|
|
27
|
+
* How long a client connection waits for a reachable server before giving up.
|
|
28
|
+
* During an upgrade the server is briefly absent; refusing instantly would
|
|
29
|
+
* surface as a broken page rather than a slow one.
|
|
30
|
+
*/
|
|
31
|
+
const CONNECT_WINDOW_MS = 5000;
|
|
32
|
+
|
|
33
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
34
|
+
|
|
35
|
+
/** Resolve once the socket is connected; reject on the first connect error. */
|
|
36
|
+
function connectUpstream(port: number): Promise<net.Socket> {
|
|
37
|
+
return new Promise((resolvePromise, reject) => {
|
|
38
|
+
const upstream = net.connect({
|
|
39
|
+
port,
|
|
40
|
+
host: "127.0.0.1", // never taken from status.json — loopback only
|
|
41
|
+
allowHalfOpen: true,
|
|
42
|
+
});
|
|
43
|
+
const onError = (err: Error) => {
|
|
44
|
+
upstream.destroy();
|
|
45
|
+
reject(err);
|
|
46
|
+
};
|
|
47
|
+
upstream.once("error", onError);
|
|
48
|
+
upstream.once("connect", () => {
|
|
49
|
+
upstream.removeListener("error", onError);
|
|
50
|
+
resolvePromise(upstream);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Largest amount of client data held while waiting for the server. Bounds the
|
|
57
|
+
* memory a slow upload can pin during an upgrade window.
|
|
58
|
+
*/
|
|
59
|
+
const MAX_PENDING_BYTES = 8 * 1024 * 1024;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Pipe one client connection to the server.
|
|
63
|
+
*
|
|
64
|
+
* Resolving the upstream is async, so the first bytes usually arrive before
|
|
65
|
+
* there is anywhere to send them. They must be captured by a `data` listener
|
|
66
|
+
* attached synchronously on this very tick and replayed once the upstream
|
|
67
|
+
* exists.
|
|
68
|
+
*
|
|
69
|
+
* `socket.pause()` is NOT a substitute — verified against Bun 1.3.13: bytes
|
|
70
|
+
* that arrive while paused are lost, and a later `pipe()`/`resume()` does not
|
|
71
|
+
* bring them back. A socket with no `data` listener drops them outright too.
|
|
72
|
+
* Both variants silently swallowed the first request in testing.
|
|
73
|
+
*/
|
|
74
|
+
async function handleConnection(
|
|
75
|
+
client: net.Socket,
|
|
76
|
+
connectWindowMs: number,
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
const pending: Buffer[] = [];
|
|
79
|
+
let pendingBytes = 0;
|
|
80
|
+
let clientEnded = false;
|
|
81
|
+
const onEarlyData = (chunk: Buffer) => {
|
|
82
|
+
pendingBytes += chunk.length;
|
|
83
|
+
if (pendingBytes > MAX_PENDING_BYTES) {
|
|
84
|
+
client.destroy();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
pending.push(chunk);
|
|
88
|
+
};
|
|
89
|
+
client.on("data", onEarlyData);
|
|
90
|
+
client.once("end", () => { clientEnded = true; });
|
|
91
|
+
|
|
92
|
+
let upstream: net.Socket | null = null;
|
|
93
|
+
let clientGone = false;
|
|
94
|
+
|
|
95
|
+
// A socket error with no listener is an unhandled 'error' event, which would
|
|
96
|
+
// take the whole edge process — and the public port — down with it.
|
|
97
|
+
const teardown = () => {
|
|
98
|
+
upstream?.destroy();
|
|
99
|
+
client.destroy();
|
|
100
|
+
};
|
|
101
|
+
client.on("error", teardown);
|
|
102
|
+
client.once("close", () => { clientGone = true; });
|
|
103
|
+
|
|
104
|
+
const deadline = Date.now() + connectWindowMs;
|
|
105
|
+
while (!clientGone && Date.now() < deadline) {
|
|
106
|
+
const port = resolveTargetPort();
|
|
107
|
+
if (port !== null) {
|
|
108
|
+
try {
|
|
109
|
+
upstream = await connectUpstream(port);
|
|
110
|
+
break;
|
|
111
|
+
} catch {
|
|
112
|
+
// Server not up yet (or moved mid-restart) — re-resolve and retry.
|
|
113
|
+
_resetTargetCache();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
await sleep(CONNECT_RETRY_MS);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (clientGone) return;
|
|
120
|
+
if (!upstream) {
|
|
121
|
+
client.destroy();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
upstream.on("error", teardown);
|
|
126
|
+
|
|
127
|
+
// Replay what arrived during the connect, then hand both directions to the
|
|
128
|
+
// stream machinery. Removing the listener and piping happen in the same tick,
|
|
129
|
+
// so no chunk can slip through the gap.
|
|
130
|
+
client.off("data", onEarlyData);
|
|
131
|
+
for (const chunk of pending) upstream.write(chunk);
|
|
132
|
+
pending.length = 0;
|
|
133
|
+
|
|
134
|
+
client.pipe(upstream);
|
|
135
|
+
upstream.pipe(client);
|
|
136
|
+
|
|
137
|
+
// The client may have half-closed while we were still connecting.
|
|
138
|
+
if (clientEnded) upstream.end();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface EdgeForwarderOptions {
|
|
142
|
+
publicPort: number;
|
|
143
|
+
host?: string;
|
|
144
|
+
/** How long a client waits for a reachable server. Defaults to 5s. */
|
|
145
|
+
connectWindowMs?: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Bind the public port and start forwarding. Resolves once listening so the
|
|
150
|
+
* caller can fail fast when the port is unavailable.
|
|
151
|
+
*/
|
|
152
|
+
export function startEdgeForwarder(
|
|
153
|
+
opts: EdgeForwarderOptions,
|
|
154
|
+
): Promise<net.Server> {
|
|
155
|
+
const host = opts.host ?? "0.0.0.0";
|
|
156
|
+
const connectWindowMs = opts.connectWindowMs ?? CONNECT_WINDOW_MS;
|
|
157
|
+
return new Promise((resolvePromise, reject) => {
|
|
158
|
+
// allowHalfOpen keeps the other direction alive when one side sends FIN —
|
|
159
|
+
// long-lived chat WS and SSE streams depend on it.
|
|
160
|
+
const server = net.createServer({ allowHalfOpen: true }, (client) => {
|
|
161
|
+
void handleConnection(client, connectWindowMs);
|
|
162
|
+
});
|
|
163
|
+
server.once("error", reject);
|
|
164
|
+
server.listen(opts.publicPort, host, () => {
|
|
165
|
+
server.removeListener("error", reject);
|
|
166
|
+
resolvePromise(server);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── Process entry ─────────────────────────────────────────────────────
|
|
172
|
+
// Mirrors the `__serve__` guard in src/server/index.ts: the supervisor
|
|
173
|
+
// re-invokes the binary as `<bin> __edge__ <publicPort> <host>`.
|
|
174
|
+
if (process.argv.includes("__edge__")) {
|
|
175
|
+
const idx = process.argv.indexOf("__edge__");
|
|
176
|
+
const publicPort = parseInt(process.argv[idx + 1] ?? "", 10);
|
|
177
|
+
const host = process.argv[idx + 2] ?? "0.0.0.0";
|
|
178
|
+
|
|
179
|
+
if (!Number.isInteger(publicPort) || publicPort <= 0 || publicPort > 65535) {
|
|
180
|
+
process.stderr.write(`[edge] Invalid public port: ${process.argv[idx + 1]}\n`);
|
|
181
|
+
process.exit(2);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await startEdgeForwarder({ publicPort, host });
|
|
186
|
+
process.stderr.write(`[edge] Listening on ${host}:${publicPort}\n`);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
process.stderr.write(`[edge] Failed to bind ${host}:${publicPort}: ${err}\n`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event-loop lag, and — the part that decides what to do about it — whether the
|
|
3
|
+
* time was ours.
|
|
4
|
+
*
|
|
5
|
+
* PPM serves every HTTP request, every WebSocket frame and all of the chat work
|
|
6
|
+
* from one Bun process with one event loop, so anything synchronous holds up
|
|
7
|
+
* everything else for exactly as long as it runs. That is measurable from
|
|
8
|
+
* outside (`/api/health`, which returns a literal, has been seen taking 18.9s on
|
|
9
|
+
* a busy instance against 0.12-0.65ms on an idle one of the same binary), but
|
|
10
|
+
* from outside it names no culprit.
|
|
11
|
+
*
|
|
12
|
+
* A late tick means one of two things, and they are fixed in different places:
|
|
13
|
+
*
|
|
14
|
+
* - **self** — the loop was busy with our own synchronous work, or with GC.
|
|
15
|
+
* Worth hunting: a blocking call, a whole-file read, a synchronous DB write.
|
|
16
|
+
* - **starved** — the OS gave the CPU to something else. PPM shares a machine,
|
|
17
|
+
* and when a chat session spawns them a *cgroup*, with compilers, browsers
|
|
18
|
+
* and one `claude` process per session. Nothing in this repository fixes
|
|
19
|
+
* that; moving work to another thread least of all, since the CPU was never
|
|
20
|
+
* ours to move it to.
|
|
21
|
+
*
|
|
22
|
+
* Sampling `process.cpuUsage()` across the same gap tells them apart: time we
|
|
23
|
+
* burned is ours, time we did not is somebody else's. Without that, "the server
|
|
24
|
+
* stalled" is a fact with two opposite conclusions.
|
|
25
|
+
*
|
|
26
|
+
* The monitor costs one timer and two counter reads per tick. It holds its
|
|
27
|
+
* samples in memory and writes nothing to disk — `~/.ppm/ppm.log` is already
|
|
28
|
+
* 261 MB with 54% of its lines duplicated, and a diagnostic that makes the
|
|
29
|
+
* problem it diagnoses worse is not one.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** How often the monitor checks in. */
|
|
33
|
+
export const TICK_MS = 250;
|
|
34
|
+
|
|
35
|
+
/** A tick at least this late is recorded. One whole interval of slip. */
|
|
36
|
+
export const REPORT_THRESHOLD_MS = 250;
|
|
37
|
+
|
|
38
|
+
/** Samples kept. At one stall per tick this is ~50s of solid stalling. */
|
|
39
|
+
export const MAX_SAMPLES = 240;
|
|
40
|
+
|
|
41
|
+
/** CPU/wall at or above this during the gap: the loop was busy with our work. */
|
|
42
|
+
export const SELF_RATIO = 0.7;
|
|
43
|
+
|
|
44
|
+
/** CPU/wall at or below this: the process was not running for most of the gap. */
|
|
45
|
+
export const STARVED_RATIO = 0.25;
|
|
46
|
+
|
|
47
|
+
export type LagCause = "self" | "starved" | "mixed";
|
|
48
|
+
|
|
49
|
+
export interface LagSample {
|
|
50
|
+
/** Epoch ms at which the late tick finally ran. */
|
|
51
|
+
at: number;
|
|
52
|
+
/** How much later than scheduled it ran. */
|
|
53
|
+
lagMs: number;
|
|
54
|
+
/** Process CPU (user + system) consumed across the same gap. */
|
|
55
|
+
cpuMs: number;
|
|
56
|
+
/** Resident set at that moment, to spot a stall that is really GC pressure. */
|
|
57
|
+
rssMb: number;
|
|
58
|
+
cause: LagCause;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LagReport {
|
|
62
|
+
/** Whether the monitor is running. */
|
|
63
|
+
running: boolean;
|
|
64
|
+
/** Ticks observed since it started. */
|
|
65
|
+
ticks: number;
|
|
66
|
+
/** Ticks that slipped past the threshold. */
|
|
67
|
+
stalls: number;
|
|
68
|
+
/** Total slip across those, in ms. */
|
|
69
|
+
blockedMs: number;
|
|
70
|
+
/** The single worst slip seen, in ms. */
|
|
71
|
+
worstMs: number;
|
|
72
|
+
/** Wall time the monitor has been running, in ms. */
|
|
73
|
+
uptimeMs: number;
|
|
74
|
+
/** Share of wall time the loop was unavailable, 0-1. */
|
|
75
|
+
blockedFraction: number;
|
|
76
|
+
/** How the stalls broke down. */
|
|
77
|
+
byCause: Record<LagCause, number>;
|
|
78
|
+
/** Most recent stalls, newest last. */
|
|
79
|
+
samples: LagSample[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Which of the two stories the numbers tell.
|
|
84
|
+
*
|
|
85
|
+
* The ratio can exceed 1: `cpuUsage` counts every thread, and Bun runs file
|
|
86
|
+
* system work on a pool, so a gap spent in parallel IO bills more CPU than wall
|
|
87
|
+
* time passed. That is still our own work, which is what the answer says.
|
|
88
|
+
*/
|
|
89
|
+
export function classifyLag(lagMs: number, cpuMs: number): LagCause {
|
|
90
|
+
if (lagMs <= 0) return "self";
|
|
91
|
+
const ratio = cpuMs / lagMs;
|
|
92
|
+
if (ratio >= SELF_RATIO) return "self";
|
|
93
|
+
if (ratio <= STARVED_RATIO) return "starved";
|
|
94
|
+
return "mixed";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Fold one observation into a sample, or return null when the tick was on time.
|
|
99
|
+
*
|
|
100
|
+
* Split out from the timer so the arithmetic — which is the whole of the
|
|
101
|
+
* measurement — can be tested without waiting for real stalls to happen.
|
|
102
|
+
*/
|
|
103
|
+
export function sampleFromTick(args: {
|
|
104
|
+
now: number;
|
|
105
|
+
elapsedMs: number;
|
|
106
|
+
cpuMicros: number;
|
|
107
|
+
rssBytes: number;
|
|
108
|
+
thresholdMs?: number;
|
|
109
|
+
}): LagSample | null {
|
|
110
|
+
const lagMs = args.elapsedMs - TICK_MS;
|
|
111
|
+
if (lagMs < (args.thresholdMs ?? REPORT_THRESHOLD_MS)) return null;
|
|
112
|
+
const cpuMs = args.cpuMicros / 1000;
|
|
113
|
+
return {
|
|
114
|
+
at: args.now,
|
|
115
|
+
lagMs: Math.round(lagMs),
|
|
116
|
+
cpuMs: Math.round(cpuMs),
|
|
117
|
+
rssMb: Math.round(args.rssBytes / 1048576),
|
|
118
|
+
cause: classifyLag(lagMs, cpuMs),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Monitor ───────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
125
|
+
let samples: LagSample[] = [];
|
|
126
|
+
let startedAt = 0;
|
|
127
|
+
let lastAt = 0;
|
|
128
|
+
let lastCpu: { user: number; system: number } | null = null;
|
|
129
|
+
let ticks = 0;
|
|
130
|
+
let stalls = 0;
|
|
131
|
+
let blockedMs = 0;
|
|
132
|
+
let worstMs = 0;
|
|
133
|
+
|
|
134
|
+
function tick(): void {
|
|
135
|
+
const now = Date.now();
|
|
136
|
+
const cpu = process.cpuUsage();
|
|
137
|
+
const elapsedMs = now - lastAt;
|
|
138
|
+
const cpuMicros = lastCpu
|
|
139
|
+
? cpu.user - lastCpu.user + (cpu.system - lastCpu.system)
|
|
140
|
+
: 0;
|
|
141
|
+
lastAt = now;
|
|
142
|
+
lastCpu = cpu;
|
|
143
|
+
ticks++;
|
|
144
|
+
|
|
145
|
+
const sample = sampleFromTick({
|
|
146
|
+
now,
|
|
147
|
+
elapsedMs,
|
|
148
|
+
cpuMicros,
|
|
149
|
+
rssBytes: process.memoryUsage.rss(),
|
|
150
|
+
});
|
|
151
|
+
if (!sample) return;
|
|
152
|
+
|
|
153
|
+
stalls++;
|
|
154
|
+
blockedMs += sample.lagMs;
|
|
155
|
+
if (sample.lagMs > worstMs) worstMs = sample.lagMs;
|
|
156
|
+
samples.push(sample);
|
|
157
|
+
if (samples.length > MAX_SAMPLES) samples.shift();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Idempotent: calling it twice does not start a second timer. */
|
|
161
|
+
export function startLagMonitor(): void {
|
|
162
|
+
if (timer) return;
|
|
163
|
+
startedAt = Date.now();
|
|
164
|
+
lastAt = startedAt;
|
|
165
|
+
lastCpu = process.cpuUsage();
|
|
166
|
+
ticks = 0;
|
|
167
|
+
stalls = 0;
|
|
168
|
+
blockedMs = 0;
|
|
169
|
+
worstMs = 0;
|
|
170
|
+
samples = [];
|
|
171
|
+
timer = setInterval(tick, TICK_MS);
|
|
172
|
+
// The monitor must never be the reason the process stays alive.
|
|
173
|
+
(timer as unknown as { unref?: () => void }).unref?.();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function stopLagMonitor(): void {
|
|
177
|
+
if (!timer) return;
|
|
178
|
+
clearInterval(timer);
|
|
179
|
+
timer = null;
|
|
180
|
+
lastCpu = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function lagReport(): LagReport {
|
|
184
|
+
const uptimeMs = startedAt ? Date.now() - startedAt : 0;
|
|
185
|
+
const byCause: Record<LagCause, number> = { self: 0, starved: 0, mixed: 0 };
|
|
186
|
+
for (const s of samples) byCause[s.cause]++;
|
|
187
|
+
return {
|
|
188
|
+
running: timer !== null,
|
|
189
|
+
ticks,
|
|
190
|
+
stalls,
|
|
191
|
+
blockedMs,
|
|
192
|
+
worstMs,
|
|
193
|
+
uptimeMs,
|
|
194
|
+
blockedFraction: uptimeMs > 0 ? blockedMs / uptimeMs : 0,
|
|
195
|
+
byCause,
|
|
196
|
+
samples: [...samples],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Test seam: drop every counter and sample without touching the timer. */
|
|
201
|
+
export function resetLagMonitorForTest(): void {
|
|
202
|
+
samples = [];
|
|
203
|
+
ticks = 0;
|
|
204
|
+
stalls = 0;
|
|
205
|
+
blockedMs = 0;
|
|
206
|
+
worstMs = 0;
|
|
207
|
+
startedAt = 0;
|
|
208
|
+
}
|