@hienlh/ppm 0.23.21 → 0.23.23
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 +31 -0
- package/CLAUDE.md +6 -3
- package/README.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 +2 -1
- package/dist/web/assets/{accounts-settings-section-DD88DRdx.js → accounts-settings-section-Cg-LncY6.js} +1 -1
- package/dist/web/assets/accounts-settings-section-Cg-LncY6.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-Cg-LncY6.js.gz +0 -0
- package/dist/web/assets/{agent-session-sheet-C0PhhLKR.js → agent-session-sheet-DMdV8GOu.js} +2 -2
- package/dist/web/assets/agent-session-sheet-DMdV8GOu.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-DMdV8GOu.js.gz +0 -0
- package/dist/web/assets/{agent-session-window-content-D0XZgh4U.js → agent-session-window-content-CYg58J-V.js} +1 -1
- package/dist/web/assets/agent-session-window-content-CYg58J-V.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-CYg58J-V.js.gz +0 -0
- package/dist/web/assets/{ai-resource-editor-CpfQB6A7.js → ai-resource-editor-BvEnn0Z_.js} +1 -1
- package/dist/web/assets/ai-resource-editor-BvEnn0Z_.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-BvEnn0Z_.js.gz +0 -0
- package/dist/web/assets/{ai-settings-section-DqE67XNC.js → ai-settings-section-Bo-nf2rY.js} +1 -1
- package/dist/web/assets/ai-settings-section-DAqiu_11.js +1 -0
- package/dist/web/assets/ai-settings-section-DAqiu_11.js.br +0 -0
- package/dist/web/assets/ai-settings-section-DAqiu_11.js.gz +0 -0
- package/dist/web/assets/{android-tab-4Z4AgHz1.js → android-tab-C-wmlFc0.js} +1 -1
- package/dist/web/assets/android-tab-C-wmlFc0.js.br +0 -0
- package/dist/web/assets/android-tab-C-wmlFc0.js.gz +0 -0
- package/dist/web/assets/{appearance-settings-section-DjQjiJF7.js → appearance-settings-section-CYqX6BGh.js} +1 -1
- package/dist/web/assets/appearance-settings-section-CYqX6BGh.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-CYqX6BGh.js.gz +0 -0
- package/dist/web/assets/{apps-panel-_lxY9xbc.js → apps-panel-D1VTBxgC.js} +1 -1
- package/dist/web/assets/apps-panel-D1VTBxgC.js.br +0 -0
- package/dist/web/assets/apps-panel-D1VTBxgC.js.gz +0 -0
- package/dist/web/assets/{branch-review-tab-BzrhnFSs.js → branch-review-tab-BG6vxSsC.js} +1 -1
- package/dist/web/assets/branch-review-tab-BG6vxSsC.js.br +0 -0
- package/dist/web/assets/branch-review-tab-BG6vxSsC.js.gz +0 -0
- package/dist/web/assets/chat-tab-BAwGLGyR.js +19 -0
- package/dist/web/assets/chat-tab-BAwGLGyR.js.br +0 -0
- package/dist/web/assets/chat-tab-BAwGLGyR.js.gz +0 -0
- package/dist/web/assets/{chat-tab-BKHepkYi.js → chat-tab-BJ0Mi2sd.js} +1 -1
- package/dist/web/assets/chat-tab-BJ0Mi2sd.js.br +0 -0
- package/dist/web/assets/chat-tab-BJ0Mi2sd.js.gz +0 -0
- package/dist/web/assets/{code-editor-COWmNXO4.js → code-editor-DuwbwTpM.js} +4 -4
- package/dist/web/assets/code-editor-DuwbwTpM.js.br +0 -0
- package/dist/web/assets/code-editor-DuwbwTpM.js.gz +0 -0
- package/dist/web/assets/{codex-reset-credit-button-BHt4yJet.js → codex-reset-credit-button-tIwfnf2G.js} +1 -1
- package/dist/web/assets/codex-reset-credit-button-tIwfnf2G.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-tIwfnf2G.js.gz +0 -0
- package/dist/web/assets/{conflict-editor-tj-1gzKr.js → conflict-editor-D2n-hQqs.js} +1 -1
- package/dist/web/assets/conflict-editor-D2n-hQqs.js.br +0 -0
- package/dist/web/assets/conflict-editor-D2n-hQqs.js.gz +0 -0
- package/dist/web/assets/{connection-form-tab-BV2QX8Cf.js → connection-form-tab-DYqPNq_-.js} +1 -1
- package/dist/web/assets/connection-form-tab-DYqPNq_-.js.br +0 -0
- package/dist/web/assets/connection-form-tab-DYqPNq_-.js.gz +0 -0
- package/dist/web/assets/{csv-preview-xhE2esgz.js → csv-preview-Xh3Z6BS5.js} +1 -1
- package/dist/web/assets/csv-preview-Xh3Z6BS5.js.br +0 -0
- package/dist/web/assets/csv-preview-Xh3Z6BS5.js.gz +0 -0
- package/dist/web/assets/{db-tab-parts-C3MsjOID.js → db-tab-parts-Dqnb3GCP.js} +1 -1
- package/dist/web/assets/db-tab-parts-Dqnb3GCP.js.br +0 -0
- package/dist/web/assets/db-tab-parts-Dqnb3GCP.js.gz +0 -0
- package/dist/web/assets/{design-settings-section-C3LxYhIw.js → design-settings-section-DNNWgxF9.js} +1 -1
- package/dist/web/assets/design-settings-section-DNNWgxF9.js.br +0 -0
- package/dist/web/assets/design-settings-section-DNNWgxF9.js.gz +0 -0
- package/dist/web/assets/{design-tab-DYcvjdfb.js → design-tab-BoAdSpZZ.js} +1 -1
- package/dist/web/assets/design-tab-BoAdSpZZ.js.br +0 -0
- package/dist/web/assets/design-tab-BoAdSpZZ.js.gz +0 -0
- package/dist/web/assets/{designs-sidebar-panel-Dpr4PPFT.js → designs-sidebar-panel-BKw7HFAz.js} +1 -1
- package/dist/web/assets/designs-sidebar-panel-BKw7HFAz.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-BKw7HFAz.js.gz +0 -0
- package/dist/web/assets/{diff-viewer-Fl_4jqr6.js → diff-viewer-CRmfCAZX.js} +1 -1
- package/dist/web/assets/diff-viewer-CRmfCAZX.js.br +0 -0
- package/dist/web/assets/diff-viewer-CRmfCAZX.js.gz +0 -0
- package/dist/web/assets/{docx-preview-bVE9shPX.js → docx-preview-zIrtL09u.js} +1 -1
- package/dist/web/assets/docx-preview-zIrtL09u.js.br +0 -0
- package/dist/web/assets/docx-preview-zIrtL09u.js.gz +0 -0
- package/dist/web/assets/{editor-dialog-eEOYWdbr.js → editor-dialog-jMYvXVla.js} +1 -1
- package/dist/web/assets/editor-dialog-jMYvXVla.js.br +0 -0
- package/dist/web/assets/editor-dialog-jMYvXVla.js.gz +0 -0
- package/dist/web/assets/{editor-language-service-D1_Ggq4s.js → editor-language-service-Yb-61Bro.js} +1 -1
- package/dist/web/assets/editor-language-service-Yb-61Bro.js.br +0 -0
- package/dist/web/assets/editor-language-service-Yb-61Bro.js.gz +0 -0
- package/dist/web/assets/{explorer-body-C0UOcBPJ.js → explorer-body-C6qKehmd.js} +1 -1
- package/dist/web/assets/explorer-body-C6qKehmd.js.br +0 -0
- package/dist/web/assets/explorer-body-C6qKehmd.js.gz +0 -0
- package/dist/web/assets/{explorer-window-content-Dxee7SuJ.js → explorer-window-content-B9P8grrY.js} +1 -1
- package/dist/web/assets/explorer-window-content-B9P8grrY.js.br +0 -0
- package/dist/web/assets/explorer-window-content-B9P8grrY.js.gz +0 -0
- package/dist/web/assets/{extension-manager-section-D8krnoj8.js → extension-manager-section-C45v1QtG.js} +1 -1
- package/dist/web/assets/extension-manager-section-C45v1QtG.js.br +0 -0
- package/dist/web/assets/extension-manager-section-C45v1QtG.js.gz +0 -0
- package/dist/web/assets/{extension-webview-CtIdV69T.js → extension-webview-DIxM-xkH.js} +1 -1
- package/dist/web/assets/extension-webview-DIxM-xkH.js.br +0 -0
- package/dist/web/assets/extension-webview-DIxM-xkH.js.gz +0 -0
- package/dist/web/assets/{files-settings-section-C8vtarR8.js → files-settings-section-DeVlzKXd.js} +1 -1
- package/dist/web/assets/files-settings-section-DeVlzKXd.js.br +0 -0
- package/dist/web/assets/files-settings-section-DeVlzKXd.js.gz +0 -0
- package/dist/web/assets/format-date-CkyHzZLA.js +1 -0
- package/dist/web/assets/{general-settings-section-Chu8Ut8h.js → general-settings-section-CLVDIEym.js} +1 -1
- package/dist/web/assets/general-settings-section-CLVDIEym.js.br +0 -0
- package/dist/web/assets/general-settings-section-CLVDIEym.js.gz +0 -0
- package/dist/web/assets/{git-log-panel-DG09p6k5.js → git-log-panel-D3Q9NRll.js} +1 -1
- package/dist/web/assets/git-log-panel-D3Q9NRll.js.br +0 -0
- package/dist/web/assets/git-log-panel-D3Q9NRll.js.gz +0 -0
- package/dist/web/assets/{git-review-tab-CSy8DjSC.js → git-review-tab-ORgiYbva.js} +1 -1
- package/dist/web/assets/git-review-tab-ORgiYbva.js.br +0 -0
- package/dist/web/assets/git-review-tab-ORgiYbva.js.gz +0 -0
- package/dist/web/assets/{glide-data-grid-BN1y4BT0.js → glide-data-grid-CtWl1BTk.js} +1 -1
- package/dist/web/assets/glide-data-grid-CtWl1BTk.js.br +0 -0
- package/dist/web/assets/glide-data-grid-CtWl1BTk.js.gz +0 -0
- package/dist/web/assets/{grid-save-dialog-CfSuWw2t.js → grid-save-dialog-eV25Mqpg.js} +1 -1
- package/dist/web/assets/grid-save-dialog-eV25Mqpg.js.br +0 -0
- package/dist/web/assets/grid-save-dialog-eV25Mqpg.js.gz +0 -0
- package/dist/web/assets/{group-chat-tab-B04QMp7A.js → group-chat-tab-CYmbYXN2.js} +1 -1
- package/dist/web/assets/group-chat-tab-CYmbYXN2.js.br +0 -0
- package/dist/web/assets/group-chat-tab-CYmbYXN2.js.gz +0 -0
- package/dist/web/assets/{impexp-tab-BRNouquR.js → impexp-tab-D2OKxpFq.js} +1 -1
- package/dist/web/assets/impexp-tab-D2OKxpFq.js.br +0 -0
- package/dist/web/assets/impexp-tab-D2OKxpFq.js.gz +0 -0
- package/dist/web/assets/{index-BpgSIM5B.js → index-D8QDpdgs.js} +7 -7
- package/dist/web/assets/index-D8QDpdgs.js.br +0 -0
- package/dist/web/assets/index-D8QDpdgs.js.gz +0 -0
- package/dist/web/assets/{jira-watcher-section-CJeS5r6D.js → jira-watcher-section-B3OaMlim.js} +1 -1
- package/dist/web/assets/{keybindings-store-Dvxq-vZD.js → keybindings-store-C_aRhBu4.js} +1 -1
- package/dist/web/assets/{keyboard-shortcuts-section-BKDyZHhK.js → keyboard-shortcuts-section-DXRyKrSy.js} +1 -1
- package/dist/web/assets/keyboard-shortcuts-section-DXRyKrSy.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-DXRyKrSy.js.gz +0 -0
- package/dist/web/assets/{language-servers-section-CbbkezhA.js → language-servers-section-BTs9iuZc.js} +2 -2
- package/dist/web/assets/language-servers-section-BTs9iuZc.js.br +0 -0
- package/dist/web/assets/language-servers-section-BTs9iuZc.js.gz +0 -0
- package/dist/web/assets/{logs-app-Blr1-v4q.js → logs-app-BlhVTOYN.js} +1 -1
- package/dist/web/assets/logs-app-BlhVTOYN.js.br +0 -0
- package/dist/web/assets/logs-app-BlhVTOYN.js.gz +0 -0
- package/dist/web/assets/{logs-tab-CGPtn2tU.js → logs-tab-kqNARbz0.js} +1 -1
- package/dist/web/assets/{logs-window-content-DAczcNmh.js → logs-window-content-UUCJJCqG.js} +1 -1
- package/dist/web/assets/{lsp-client-Ds3U2O52.js → lsp-client-BTlK_8lb.js} +1 -1
- package/dist/web/assets/lsp-client-BTlK_8lb.js.br +0 -0
- package/dist/web/assets/lsp-client-BTlK_8lb.js.gz +0 -0
- package/dist/web/assets/{markdown-renderer-DbJVwfUH.js → markdown-renderer-ClGxDien.js} +1 -1
- package/dist/web/assets/{markdown-renderer-DzCPadpB.js → markdown-renderer-DpsToFFB.js} +1 -1
- package/dist/web/assets/markdown-renderer-DpsToFFB.js.br +0 -0
- package/dist/web/assets/markdown-renderer-DpsToFFB.js.gz +0 -0
- package/dist/web/assets/{mobile-explorer-sheet-BClCMXb8.js → mobile-explorer-sheet-CybJbScN.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CybJbScN.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-CybJbScN.js.gz +0 -0
- package/dist/web/assets/{new-design-dialog-HRP9H11v.js → new-design-dialog-9WTgcJoj.js} +1 -1
- package/dist/web/assets/new-design-dialog-9WTgcJoj.js.br +0 -0
- package/dist/web/assets/new-design-dialog-9WTgcJoj.js.gz +0 -0
- package/dist/web/assets/{notification-store-BomzYNQj.js → notification-store-CPVq5ec5.js} +1 -1
- package/dist/web/assets/notifications-settings-section-xrYII-pL.js +1 -0
- package/dist/web/assets/notifications-settings-section-xrYII-pL.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-xrYII-pL.js.gz +0 -0
- package/dist/web/assets/{ppmbot-settings-section-BRowIIqc.js → ppmbot-settings-section-Bc46iDP9.js} +1 -1
- package/dist/web/assets/ppmbot-settings-section-Bc46iDP9.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-Bc46iDP9.js.gz +0 -0
- package/dist/web/assets/{problems-panel-DdB10Ybd.js → problems-panel-D49-Xkzd.js} +1 -1
- package/dist/web/assets/problems-panel-D49-Xkzd.js.br +0 -0
- package/dist/web/assets/problems-panel-D49-Xkzd.js.gz +0 -0
- package/dist/web/assets/{process-table-CzLaWPe0.js → process-table-NxYTZ0n9.js} +1 -1
- package/dist/web/assets/process-table-NxYTZ0n9.js.br +0 -0
- package/dist/web/assets/process-table-NxYTZ0n9.js.gz +0 -0
- package/dist/web/assets/{proxy-settings-section-BCq7R5DA.js → proxy-settings-section-Exx7cQc4.js} +1 -1
- package/dist/web/assets/proxy-settings-section-Exx7cQc4.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-Exx7cQc4.js.gz +0 -0
- package/dist/web/assets/{query-tab-DGvj_h6l.js → query-tab-BWtJPuxw.js} +1 -1
- package/dist/web/assets/query-tab-BWtJPuxw.js.br +0 -0
- package/dist/web/assets/{query-tab-DGvj_h6l.js.gz → query-tab-BWtJPuxw.js.gz} +0 -0
- package/dist/web/assets/{remote-access-settings-section-BrqOoOyk.js → remote-access-settings-section-C4K__DNW.js} +1 -1
- package/dist/web/assets/remote-access-settings-section-C4K__DNW.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-C4K__DNW.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-clipboard-notice-5r6UbdlN.js → remote-desktop-clipboard-notice-DQyysBwL.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-DQyysBwL.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-DQyysBwL.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-mobile-sheet-B9Q4auqe.js → remote-desktop-mobile-sheet-DZtEFmgf.js} +2 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-DZtEFmgf.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DZtEFmgf.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-mobile-view-CL-jrkA8.js → remote-desktop-mobile-view-VW7HdPUX.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-view-VW7HdPUX.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-VW7HdPUX.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-DXEf2Jnz.js → remote-desktop-readiness-gate-B3tuP9Kt.js} +1 -1
- package/dist/web/assets/remote-desktop-readiness-gate-B3tuP9Kt.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-B3tuP9Kt.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-window-content-20P6-gKB.js → remote-desktop-window-content-n0NoDCaE.js} +1 -1
- package/dist/web/assets/remote-desktop-window-content-n0NoDCaE.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-n0NoDCaE.js.gz +0 -0
- package/dist/web/assets/{rename-object-dialog-Bs7pUsUP.js → rename-object-dialog-rLTW4A55.js} +1 -1
- package/dist/web/assets/rename-object-dialog-rLTW4A55.js.br +0 -0
- package/dist/web/assets/rename-object-dialog-rLTW4A55.js.gz +0 -0
- package/dist/web/assets/{review-code-Bjo4sHN9.js → review-code-DNfsaNO1.js} +1 -1
- package/dist/web/assets/review-code-DNfsaNO1.js.br +0 -0
- package/dist/web/assets/review-code-DNfsaNO1.js.gz +0 -0
- package/dist/web/assets/{save-as-dialog-BHx0Wp2B.js → save-as-dialog-BSz8WrNZ.js} +1 -1
- package/dist/web/assets/save-as-dialog-BSz8WrNZ.js.br +0 -0
- package/dist/web/assets/save-as-dialog-BSz8WrNZ.js.gz +0 -0
- package/dist/web/assets/{schedules-settings-section-C6ATYDPK.js → schedules-settings-section-DqUZ-H2K.js} +1 -1
- package/dist/web/assets/schedules-settings-section-DqUZ-H2K.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-DqUZ-H2K.js.gz +0 -0
- package/dist/web/assets/{session-review-tab-BPMUyNTx.js → session-review-tab-DKtrFb9y.js} +1 -1
- package/dist/web/assets/session-review-tab-DKtrFb9y.js.br +0 -0
- package/dist/web/assets/session-review-tab-DKtrFb9y.js.gz +0 -0
- package/dist/web/assets/{settings-body-Dy5D6Vu7.js → settings-body-C42xA9Dw.js} +1 -1
- package/dist/web/assets/settings-body-C42xA9Dw.js.br +0 -0
- package/dist/web/assets/settings-body-C42xA9Dw.js.gz +0 -0
- package/dist/web/assets/{settings-rows-BRoYE2To.js → settings-rows-C7ZatT9O.js} +1 -1
- package/dist/web/assets/settings-rows-C7ZatT9O.js.br +0 -0
- package/dist/web/assets/settings-rows-C7ZatT9O.js.gz +0 -0
- package/dist/web/assets/{settings-section-content-essUViKu.js → settings-section-content-B_iXJpcK.js} +2 -2
- package/dist/web/assets/settings-section-content-B_iXJpcK.js.br +0 -0
- package/dist/web/assets/settings-section-content-B_iXJpcK.js.gz +0 -0
- package/dist/web/assets/settings-section-content-rxfQEQ66.js +1 -0
- package/dist/web/assets/{settings-tab-Di1DUP1j.js → settings-tab-PRw6OpEb.js} +1 -1
- package/dist/web/assets/{settings-window-content-DYvYFPWy.js → settings-window-content-WITQsGfW.js} +1 -1
- package/dist/web/assets/{slash-command-picker-8da30mYP.js → slash-command-picker-DmDs8nOj.js} +1 -1
- package/dist/web/assets/slash-command-picker-DmDs8nOj.js.br +0 -0
- package/dist/web/assets/slash-command-picker-DmDs8nOj.js.gz +0 -0
- package/dist/web/assets/{sql-object-tab-DRIHVXuR.js → sql-object-tab-D6P4hzkb.js} +1 -1
- package/dist/web/assets/sql-object-tab-D6P4hzkb.js.br +0 -0
- package/dist/web/assets/sql-object-tab-D6P4hzkb.js.gz +0 -0
- package/dist/web/assets/{sqlite-viewer-G-Jl2yEr.js → sqlite-viewer-mUitnzHB.js} +1 -1
- package/dist/web/assets/sqlite-viewer-mUitnzHB.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-mUitnzHB.js.gz +0 -0
- package/dist/web/assets/{structure-save-dialog-Cwikz0we.js → structure-save-dialog-CmbNlmBY.js} +1 -1
- package/dist/web/assets/structure-save-dialog-CmbNlmBY.js.br +0 -0
- package/dist/web/assets/structure-save-dialog-CmbNlmBY.js.gz +0 -0
- package/dist/web/assets/{structure-tab-CWEgdZPm.js → structure-tab-DCPNuFTe.js} +1 -1
- package/dist/web/assets/structure-tab-DCPNuFTe.js.br +0 -0
- package/dist/web/assets/structure-tab-DCPNuFTe.js.gz +0 -0
- package/dist/web/assets/{system-monitor-body-BDQm9-wb.js → system-monitor-body-BhxyqSqu.js} +2 -2
- package/dist/web/assets/system-monitor-body-BhxyqSqu.js.br +0 -0
- package/dist/web/assets/system-monitor-body-BhxyqSqu.js.gz +0 -0
- package/dist/web/assets/{system-monitor-tab-BouN21Yh.js → system-monitor-tab-B_miogQI.js} +1 -1
- package/dist/web/assets/{system-monitor-window-content-BRyWBSOb.js → system-monitor-window-content-8IogR6tR.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-CZ55-bTz.js → tab-host-window-content-BWHQjgWZ.js} +1 -1
- package/dist/web/assets/tab-host-window-content-BWHQjgWZ.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-BWHQjgWZ.js.gz +0 -0
- package/dist/web/assets/{table-tab-Bmn4UdBV.js → table-tab-E3YMueu_.js} +1 -1
- package/dist/web/assets/table-tab-E3YMueu_.js.br +0 -0
- package/dist/web/assets/table-tab-E3YMueu_.js.gz +0 -0
- package/dist/web/assets/{telegram-bot-token-field-fdHwRf39.js → telegram-bot-token-field-CwW24m62.js} +1 -1
- package/dist/web/assets/telegram-bot-token-field-CwW24m62.js.br +0 -0
- package/dist/web/assets/telegram-bot-token-field-CwW24m62.js.gz +0 -0
- package/dist/web/assets/{terminal-tab-D-qlZXLK.js → terminal-tab-BAe5cWEL.js} +1 -1
- package/dist/web/assets/terminal-tab-BAe5cWEL.js.br +0 -0
- package/dist/web/assets/terminal-tab-BAe5cWEL.js.gz +0 -0
- package/dist/web/assets/{tool-cards-BdXbQ9Fv.js → tool-cards-DrCiSH2r.js} +2 -2
- package/dist/web/assets/tool-cards-DrCiSH2r.js.br +0 -0
- package/dist/web/assets/tool-cards-DrCiSH2r.js.gz +0 -0
- package/dist/web/assets/{tools-settings-section-CcPaspbR.js → tools-settings-section-S-vBr2af.js} +1 -1
- package/dist/web/assets/tools-settings-section-S-vBr2af.js.br +0 -0
- package/dist/web/assets/tools-settings-section-S-vBr2af.js.gz +0 -0
- package/dist/web/assets/{use-inline-blame-Ck5XoSS3.js → use-inline-blame-BzVaaCCt.js} +1 -1
- package/dist/web/assets/use-inline-blame-BzVaaCCt.js.br +0 -0
- package/dist/web/assets/{use-inline-blame-Ck5XoSS3.js.gz → use-inline-blame-BzVaaCCt.js.gz} +0 -0
- package/dist/web/assets/{use-lsp-CGSqfFBk.js → use-lsp-BwDL9Fqk.js} +1 -1
- package/dist/web/assets/use-lsp-BwDL9Fqk.js.br +0 -0
- package/dist/web/assets/use-lsp-BwDL9Fqk.js.gz +0 -0
- package/dist/web/assets/{use-monaco-theme-BW05eZS4.js → use-monaco-theme-DljSc3Xo.js} +1 -1
- package/dist/web/assets/{use-process-kill-DJ_f7rCO.js → use-process-kill-YDcDqDtG.js} +1 -1
- package/dist/web/assets/use-process-kill-YDcDqDtG.js.br +0 -0
- package/dist/web/assets/use-process-kill-YDcDqDtG.js.gz +0 -0
- package/dist/web/assets/{use-sql-schema-info-wt320fVR.js → use-sql-schema-info-cAI0vn7d.js} +1 -1
- package/dist/web/assets/use-sql-schema-info-cAI0vn7d.js.br +0 -0
- package/dist/web/assets/use-sql-schema-info-cAI0vn7d.js.gz +0 -0
- package/dist/web/assets/{use-websocket-CTwe74Cb.js → use-websocket-Djo_ruX_.js} +1 -1
- package/dist/web/assets/{web-preview-tab-D4XayUEK.js → web-preview-tab-DXy0hRef.js} +1 -1
- package/dist/web/assets/web-preview-tab-DXy0hRef.js.br +0 -0
- package/dist/web/assets/web-preview-tab-DXy0hRef.js.gz +0 -0
- package/dist/web/index.html +2 -2
- package/dist/web/index.html.br +0 -0
- package/dist/web/index.html.gz +0 -0
- package/dist/web/sw.js +1 -1
- package/dist/web/sw.js.br +0 -0
- package/dist/web/sw.js.gz +0 -0
- package/docs/architecture/ai-chat-and-providers.md +29 -0
- package/docs/deployment-guide.md +39 -0
- package/docs/lessons-learned.md +14 -4
- package/package.json +1 -1
- package/scripts/mem-diag-report.ts +31 -0
- package/src/providers/claude-agent-sdk-query-options.ts +13 -2
- package/src/providers/claude-agent-sdk.ts +58 -18
- package/src/providers/claude-approval-queue.ts +75 -0
- package/src/providers/claude-effort-status.ts +211 -0
- package/src/providers/registry.ts +10 -1
- package/src/server/index.ts +7 -6
- package/src/server/middleware/access-log.ts +3 -0
- package/src/server/middleware/cors-headers.ts +44 -0
- package/src/server/routes/chat.ts +21 -1
- package/src/server/routes/database-impexp.ts +1 -1
- package/src/server/routes/design-preview.ts +2 -2
- package/src/server/routes/settings.ts +16 -0
- package/src/server/ws/chat-effort-status.ts +73 -0
- package/src/server/ws/chat.ts +33 -3
- package/src/services/compact-segment.ts +14 -19
- package/src/services/database/grid-export-tickets.ts +6 -2
- package/src/services/database/impexp/readers/file-text.ts +6 -3
- package/src/services/edge-forwarder.ts +12 -0
- package/src/services/file-chunks.ts +39 -0
- package/src/services/file-lines.ts +10 -17
- package/src/services/lsp/lsp-manager.ts +3 -0
- package/src/services/memory-diagnostics-native.ts +110 -0
- package/src/services/memory-diagnostics-report.ts +200 -0
- package/src/services/memory-diagnostics-requests.ts +95 -0
- package/src/services/memory-diagnostics-spawns.ts +242 -0
- package/src/services/memory-diagnostics.ts +121 -23
- package/src/services/memory-watchdog.ts +185 -0
- package/src/services/nested-subagent-spy.ts +3 -0
- package/src/services/process-thread-snapshot.ts +12 -2
- package/src/services/process-tree-memory.ts +161 -0
- package/src/services/read-lines.ts +16 -27
- package/src/services/supervisor.ts +1 -0
- package/src/services/terminal.service.ts +8 -0
- package/src/services/transcript-images-file.ts +7 -11
- package/src/shared/effort-status.ts +128 -0
- package/src/types/api.ts +3 -1
- package/src/types/chat.ts +8 -0
- package/src/web/components/chat/chat-scroll-nav.tsx +12 -8
- package/src/web/components/chat/chat-tab.tsx +15 -11
- package/src/web/components/chat/message-action-bar.tsx +19 -4
- package/src/web/components/chat/message-input.tsx +6 -0
- package/src/web/components/chat/message-list.tsx +55 -21
- package/src/web/components/chat/message-user-bubble.tsx +2 -2
- package/src/web/components/chat/model-thinking-selector-helpers.ts +79 -0
- package/src/web/components/chat/model-thinking-selector.tsx +36 -10
- package/src/web/components/chat/session-changes-context.tsx +29 -1
- package/src/web/components/layout/reparenting-tab.tsx +53 -14
- package/src/web/components/settings/ai-settings-section.tsx +32 -0
- package/src/web/components/settings/notifications-settings-section.tsx +14 -1
- package/src/web/hooks/use-chat.ts +16 -0
- package/src/web/hooks/use-effort-status.ts +46 -0
- package/src/web/hooks/use-relative-time-tick.ts +35 -0
- package/src/web/hooks/use-session-file-changes.ts +5 -1
- package/src/web/hooks/use-tab-overflow.ts +26 -13
- package/src/web/lib/format-date.ts +6 -1
- package/src/web/lib/web-push-client.ts +35 -5
- package/src/web/lib/web-push-support.ts +57 -4
- package/dist/web/assets/accounts-settings-section-DD88DRdx.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-DD88DRdx.js.gz +0 -0
- package/dist/web/assets/agent-session-sheet-C0PhhLKR.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-C0PhhLKR.js.gz +0 -0
- package/dist/web/assets/agent-session-window-content-D0XZgh4U.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-D0XZgh4U.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-CpfQB6A7.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-CpfQB6A7.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-CQWBwWcM.js +0 -1
- package/dist/web/assets/ai-settings-section-CQWBwWcM.js.br +0 -0
- package/dist/web/assets/ai-settings-section-CQWBwWcM.js.gz +0 -0
- package/dist/web/assets/android-tab-4Z4AgHz1.js.br +0 -0
- package/dist/web/assets/android-tab-4Z4AgHz1.js.gz +0 -0
- package/dist/web/assets/appearance-settings-section-DjQjiJF7.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-DjQjiJF7.js.gz +0 -0
- package/dist/web/assets/apps-panel-_lxY9xbc.js.br +0 -0
- package/dist/web/assets/apps-panel-_lxY9xbc.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-BzrhnFSs.js.br +0 -0
- package/dist/web/assets/branch-review-tab-BzrhnFSs.js.gz +0 -0
- package/dist/web/assets/chat-tab-BKHepkYi.js.br +0 -0
- package/dist/web/assets/chat-tab-BKHepkYi.js.gz +0 -0
- package/dist/web/assets/chat-tab-Cv8gYzcA.js +0 -19
- package/dist/web/assets/chat-tab-Cv8gYzcA.js.br +0 -0
- package/dist/web/assets/chat-tab-Cv8gYzcA.js.gz +0 -0
- package/dist/web/assets/code-editor-COWmNXO4.js.br +0 -0
- package/dist/web/assets/code-editor-COWmNXO4.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-BHt4yJet.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-BHt4yJet.js.gz +0 -0
- package/dist/web/assets/conflict-editor-tj-1gzKr.js.br +0 -0
- package/dist/web/assets/conflict-editor-tj-1gzKr.js.gz +0 -0
- package/dist/web/assets/connection-form-tab-BV2QX8Cf.js.br +0 -0
- package/dist/web/assets/connection-form-tab-BV2QX8Cf.js.gz +0 -0
- package/dist/web/assets/csv-preview-xhE2esgz.js.br +0 -0
- package/dist/web/assets/csv-preview-xhE2esgz.js.gz +0 -0
- package/dist/web/assets/db-tab-parts-C3MsjOID.js.br +0 -0
- package/dist/web/assets/db-tab-parts-C3MsjOID.js.gz +0 -0
- package/dist/web/assets/design-settings-section-C3LxYhIw.js.br +0 -0
- package/dist/web/assets/design-settings-section-C3LxYhIw.js.gz +0 -0
- package/dist/web/assets/design-tab-DYcvjdfb.js.br +0 -0
- package/dist/web/assets/design-tab-DYcvjdfb.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-Dpr4PPFT.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-Dpr4PPFT.js.gz +0 -0
- package/dist/web/assets/diff-viewer-Fl_4jqr6.js.br +0 -0
- package/dist/web/assets/diff-viewer-Fl_4jqr6.js.gz +0 -0
- package/dist/web/assets/docx-preview-bVE9shPX.js.br +0 -0
- package/dist/web/assets/docx-preview-bVE9shPX.js.gz +0 -0
- package/dist/web/assets/editor-dialog-eEOYWdbr.js.br +0 -0
- package/dist/web/assets/editor-dialog-eEOYWdbr.js.gz +0 -0
- package/dist/web/assets/editor-language-service-D1_Ggq4s.js.br +0 -0
- package/dist/web/assets/editor-language-service-D1_Ggq4s.js.gz +0 -0
- package/dist/web/assets/explorer-body-C0UOcBPJ.js.br +0 -0
- package/dist/web/assets/explorer-body-C0UOcBPJ.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-Dxee7SuJ.js.br +0 -0
- package/dist/web/assets/explorer-window-content-Dxee7SuJ.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-D8krnoj8.js.br +0 -0
- package/dist/web/assets/extension-manager-section-D8krnoj8.js.gz +0 -0
- package/dist/web/assets/extension-webview-CtIdV69T.js.br +0 -0
- package/dist/web/assets/extension-webview-CtIdV69T.js.gz +0 -0
- package/dist/web/assets/files-settings-section-C8vtarR8.js.br +0 -0
- package/dist/web/assets/files-settings-section-C8vtarR8.js.gz +0 -0
- package/dist/web/assets/format-date-CSHaU2wN.js +0 -1
- package/dist/web/assets/general-settings-section-Chu8Ut8h.js.br +0 -0
- package/dist/web/assets/general-settings-section-Chu8Ut8h.js.gz +0 -0
- package/dist/web/assets/git-log-panel-DG09p6k5.js.br +0 -0
- package/dist/web/assets/git-log-panel-DG09p6k5.js.gz +0 -0
- package/dist/web/assets/git-review-tab-CSy8DjSC.js.br +0 -0
- package/dist/web/assets/git-review-tab-CSy8DjSC.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-BN1y4BT0.js.br +0 -0
- package/dist/web/assets/glide-data-grid-BN1y4BT0.js.gz +0 -0
- package/dist/web/assets/grid-save-dialog-CfSuWw2t.js.br +0 -0
- package/dist/web/assets/grid-save-dialog-CfSuWw2t.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-B04QMp7A.js.br +0 -0
- package/dist/web/assets/group-chat-tab-B04QMp7A.js.gz +0 -0
- package/dist/web/assets/impexp-tab-BRNouquR.js.br +0 -0
- package/dist/web/assets/impexp-tab-BRNouquR.js.gz +0 -0
- package/dist/web/assets/index-BpgSIM5B.js.br +0 -0
- package/dist/web/assets/index-BpgSIM5B.js.gz +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-BKDyZHhK.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-BKDyZHhK.js.gz +0 -0
- package/dist/web/assets/language-servers-section-CbbkezhA.js.br +0 -0
- package/dist/web/assets/language-servers-section-CbbkezhA.js.gz +0 -0
- package/dist/web/assets/logs-app-Blr1-v4q.js.br +0 -0
- package/dist/web/assets/logs-app-Blr1-v4q.js.gz +0 -0
- package/dist/web/assets/lsp-client-Ds3U2O52.js.br +0 -0
- package/dist/web/assets/lsp-client-Ds3U2O52.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-DzCPadpB.js.br +0 -0
- package/dist/web/assets/markdown-renderer-DzCPadpB.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-BClCMXb8.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-BClCMXb8.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-HRP9H11v.js.br +0 -0
- package/dist/web/assets/new-design-dialog-HRP9H11v.js.gz +0 -0
- package/dist/web/assets/notifications-settings-section-CYTTQaNr.js +0 -1
- package/dist/web/assets/notifications-settings-section-CYTTQaNr.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-CYTTQaNr.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-BRowIIqc.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-BRowIIqc.js.gz +0 -0
- package/dist/web/assets/problems-panel-DdB10Ybd.js.br +0 -0
- package/dist/web/assets/problems-panel-DdB10Ybd.js.gz +0 -0
- package/dist/web/assets/process-table-CzLaWPe0.js.br +0 -0
- package/dist/web/assets/process-table-CzLaWPe0.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-BCq7R5DA.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-BCq7R5DA.js.gz +0 -0
- package/dist/web/assets/query-tab-DGvj_h6l.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-BrqOoOyk.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-BrqOoOyk.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-5r6UbdlN.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-5r6UbdlN.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-B9Q4auqe.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-B9Q4auqe.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-CL-jrkA8.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-CL-jrkA8.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-DXEf2Jnz.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-DXEf2Jnz.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-20P6-gKB.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-20P6-gKB.js.gz +0 -0
- package/dist/web/assets/rename-object-dialog-Bs7pUsUP.js.br +0 -0
- package/dist/web/assets/rename-object-dialog-Bs7pUsUP.js.gz +0 -0
- package/dist/web/assets/review-code-Bjo4sHN9.js.br +0 -0
- package/dist/web/assets/review-code-Bjo4sHN9.js.gz +0 -0
- package/dist/web/assets/save-as-dialog-BHx0Wp2B.js.br +0 -0
- package/dist/web/assets/save-as-dialog-BHx0Wp2B.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-C6ATYDPK.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-C6ATYDPK.js.gz +0 -0
- package/dist/web/assets/session-review-tab-BPMUyNTx.js.br +0 -0
- package/dist/web/assets/session-review-tab-BPMUyNTx.js.gz +0 -0
- package/dist/web/assets/settings-body-Dy5D6Vu7.js.br +0 -0
- package/dist/web/assets/settings-body-Dy5D6Vu7.js.gz +0 -0
- package/dist/web/assets/settings-rows-BRoYE2To.js.br +0 -0
- package/dist/web/assets/settings-rows-BRoYE2To.js.gz +0 -0
- package/dist/web/assets/settings-section-content-C4DX7TYv.js +0 -1
- package/dist/web/assets/settings-section-content-essUViKu.js.br +0 -0
- package/dist/web/assets/settings-section-content-essUViKu.js.gz +0 -0
- package/dist/web/assets/slash-command-picker-8da30mYP.js.br +0 -0
- package/dist/web/assets/slash-command-picker-8da30mYP.js.gz +0 -0
- package/dist/web/assets/sql-object-tab-DRIHVXuR.js.br +0 -0
- package/dist/web/assets/sql-object-tab-DRIHVXuR.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-G-Jl2yEr.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-G-Jl2yEr.js.gz +0 -0
- package/dist/web/assets/structure-save-dialog-Cwikz0we.js.br +0 -0
- package/dist/web/assets/structure-save-dialog-Cwikz0we.js.gz +0 -0
- package/dist/web/assets/structure-tab-CWEgdZPm.js.br +0 -0
- package/dist/web/assets/structure-tab-CWEgdZPm.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-BDQm9-wb.js.br +0 -0
- package/dist/web/assets/system-monitor-body-BDQm9-wb.js.gz +0 -0
- package/dist/web/assets/tab-host-window-content-CZ55-bTz.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-CZ55-bTz.js.gz +0 -0
- package/dist/web/assets/table-tab-Bmn4UdBV.js.br +0 -0
- package/dist/web/assets/table-tab-Bmn4UdBV.js.gz +0 -0
- package/dist/web/assets/telegram-bot-token-field-fdHwRf39.js.br +0 -0
- package/dist/web/assets/telegram-bot-token-field-fdHwRf39.js.gz +0 -0
- package/dist/web/assets/terminal-tab-D-qlZXLK.js.br +0 -0
- package/dist/web/assets/terminal-tab-D-qlZXLK.js.gz +0 -0
- package/dist/web/assets/tool-cards-BdXbQ9Fv.js.br +0 -0
- package/dist/web/assets/tool-cards-BdXbQ9Fv.js.gz +0 -0
- package/dist/web/assets/tools-settings-section-CcPaspbR.js.br +0 -0
- package/dist/web/assets/tools-settings-section-CcPaspbR.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-Ck5XoSS3.js.br +0 -0
- package/dist/web/assets/use-lsp-CGSqfFBk.js.br +0 -0
- package/dist/web/assets/use-lsp-CGSqfFBk.js.gz +0 -0
- package/dist/web/assets/use-process-kill-DJ_f7rCO.js.br +0 -0
- package/dist/web/assets/use-process-kill-DJ_f7rCO.js.gz +0 -0
- package/dist/web/assets/use-sql-schema-info-wt320fVR.js.br +0 -0
- package/dist/web/assets/use-sql-schema-info-wt320fVR.js.gz +0 -0
- package/dist/web/assets/web-preview-tab-D4XayUEK.js.br +0 -0
- package/dist/web/assets/web-preview-tab-D4XayUEK.js.gz +0 -0
|
@@ -51,8 +51,8 @@ export function createDesignPreviewRoutes(now: () => number = Date.now) {
|
|
|
51
51
|
const api = new Hono();
|
|
52
52
|
const content = new Hono();
|
|
53
53
|
|
|
54
|
-
//
|
|
55
|
-
//
|
|
54
|
+
// Every response here must say `null`, error pages included; asserted after the handler so
|
|
55
|
+
// it holds whatever an inner layer set. The app's CORS layer leaves an existing value alone.
|
|
56
56
|
content.use("*", async (c, next) => {
|
|
57
57
|
await next();
|
|
58
58
|
c.res.headers.set("Access-Control-Allow-Origin", "null");
|
|
@@ -314,6 +314,22 @@ settingsRoutes.get("/ai/providers/:id/models", async (c) => {
|
|
|
314
314
|
}
|
|
315
315
|
});
|
|
316
316
|
|
|
317
|
+
/**
|
|
318
|
+
* GET /settings/ai/providers/:id/effort-status — what overrides the default effort outside PPM for
|
|
319
|
+
* every project: the user settings and the environment PPM runs in. A project's own settings can
|
|
320
|
+
* add more, which the chat's picker reports. `status` is null for a provider nothing overrides.
|
|
321
|
+
*/
|
|
322
|
+
settingsRoutes.get("/ai/providers/:id/effort-status", (c) => {
|
|
323
|
+
const id = c.req.param("id");
|
|
324
|
+
const provider = providerRegistry.get(id);
|
|
325
|
+
if (!provider) return c.json(err(`Provider "${id}" not found`), 404);
|
|
326
|
+
try {
|
|
327
|
+
return c.json(ok({ status: provider.estimateEffortStatus?.(undefined, c.req.query("model") || undefined) ?? null }));
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return c.json(err((e as Error).message), 500);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
317
333
|
// ── Keybindings ──────────────────────────────────────────────────────
|
|
318
334
|
|
|
319
335
|
const KEYBINDINGS_KEY = "keybindings";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The effort a chat's turns really run at, kept beside the pick.
|
|
3
|
+
*
|
|
4
|
+
* PPM starts the CLI with the picked `--effort`, but `CLAUDE_CODE_EFFORT_LEVEL` (in the
|
|
5
|
+
* environment or a settings file's `env`), a `maxEffortLevel` cap, an organization limit and the
|
|
6
|
+
* model itself all outrank it there. So the session's CLI is asked at the start of every turn —
|
|
7
|
+
* a settings file edited since the last one shows up there too — and its answer goes to the
|
|
8
|
+
* clients. Before any turn has asked, what the settings files say stands in.
|
|
9
|
+
*/
|
|
10
|
+
import { effortInEffect, effortSettingPlace, type EffortStatus } from "../../shared/effort-status.ts";
|
|
11
|
+
|
|
12
|
+
export interface EffortStatusSession {
|
|
13
|
+
providerId: string;
|
|
14
|
+
projectPath?: string;
|
|
15
|
+
/** The CLI's last answer for this session. */
|
|
16
|
+
effortStatus?: EffortStatus;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface EffortStatusProvider {
|
|
20
|
+
readEffortStatus?(sessionId: string): Promise<EffortStatus | null>;
|
|
21
|
+
estimateEffortStatus?(projectPath: string | undefined, model?: string): EffortStatus;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** What a greeting carries: the CLI's answer, else the settings files; null for a provider nothing overrides. */
|
|
25
|
+
export function sessionEffortStatus(
|
|
26
|
+
provider: EffortStatusProvider | undefined,
|
|
27
|
+
session: EffortStatusSession,
|
|
28
|
+
model: string | undefined,
|
|
29
|
+
): EffortStatus | null {
|
|
30
|
+
return session.effortStatus ?? provider?.estimateEffortStatus?.(session.projectPath, model) ?? null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The session log's line for a turn whose effort is not the one it was started with. */
|
|
34
|
+
export function effortOverrideLogLine(status: EffortStatus): string | null {
|
|
35
|
+
const { observed } = status;
|
|
36
|
+
if (!observed?.requested) return null;
|
|
37
|
+
const { level, reason } = effortInEffect(observed.requested, status);
|
|
38
|
+
if (!reason) return null;
|
|
39
|
+
const by = reason === "env" && status.env
|
|
40
|
+
? `CLAUDE_CODE_EFFORT_LEVEL=${status.env.value} in ${effortSettingPlace(status.env)}`
|
|
41
|
+
: reason === "cap" && status.cap
|
|
42
|
+
? `maxEffortLevel ${status.cap.value} in ${effortSettingPlace(status.cap)}`
|
|
43
|
+
: `what ${observed.model ?? "the model"} allows`;
|
|
44
|
+
return `Effort ${observed.requested} was picked; Claude Code sends ${level ?? "no effort"} (${by})`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Ask the session's CLI at a turn's start, and tell the clients when its answer changed. Never
|
|
49
|
+
* rejects: it runs unawaited beside the turn, where a rejection would reach the server's
|
|
50
|
+
* fatal-error handler for what is only a label.
|
|
51
|
+
*/
|
|
52
|
+
export async function refreshEffortStatus(p: {
|
|
53
|
+
sessionId: string;
|
|
54
|
+
session: EffortStatusSession;
|
|
55
|
+
provider: EffortStatusProvider | undefined;
|
|
56
|
+
/** False once the session's entry was replaced while the CLI was answering. */
|
|
57
|
+
isCurrent: () => boolean;
|
|
58
|
+
broadcast: (event: unknown) => void;
|
|
59
|
+
log: (line: string) => void;
|
|
60
|
+
}): Promise<void> {
|
|
61
|
+
try {
|
|
62
|
+
const status = await p.provider?.readEffortStatus?.(p.sessionId);
|
|
63
|
+
if (!status || !p.isCurrent()) return;
|
|
64
|
+
const before = p.session.effortStatus;
|
|
65
|
+
p.session.effortStatus = status;
|
|
66
|
+
if (before && JSON.stringify(before) === JSON.stringify(status)) return;
|
|
67
|
+
p.broadcast({ type: "effort_status", status });
|
|
68
|
+
const line = effortOverrideLogLine(status);
|
|
69
|
+
if (line) p.log(line);
|
|
70
|
+
} catch {
|
|
71
|
+
// The status stays as it was; the next turn asks again.
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/server/ws/chat.ts
CHANGED
|
@@ -24,6 +24,8 @@ import { isAsyncAgentLaunchAck, isTerminalAgentStatus } from "../../shared/backg
|
|
|
24
24
|
import { cacheReleaseDelayMs, selectWarmIdleEvictions } from "../../services/subprocess-retention.ts";
|
|
25
25
|
import { needsAuthServerNames } from "../../services/mcp-oauth/mcp-oauth-redirect.ts";
|
|
26
26
|
import { mcpStatusEvent, registerMcpSignInSync } from "./chat-mcp-sign-in-sync.ts";
|
|
27
|
+
import { refreshEffortStatus, sessionEffortStatus } from "./chat-effort-status.ts";
|
|
28
|
+
import type { EffortStatus } from "../../shared/effort-status.ts";
|
|
27
29
|
import { claudeTranscriptExists } from "../../services/claude-transcript-exists.ts";
|
|
28
30
|
import { registerMemoryGauge } from "../../services/memory-diagnostics.ts";
|
|
29
31
|
import { setTabOpenDelivery, tabOpenBroker } from "../../services/tab-tools-mcp/tab-open-broker.ts";
|
|
@@ -114,6 +116,11 @@ function resolveSessionEffort(sessionId: string): string | undefined {
|
|
|
114
116
|
return getSessionEffort(sessionId) ?? sessionProviderConfig(sessionId)?.effort;
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
/** What overrides the effort shown in session_state, if anything does. */
|
|
120
|
+
function resolveEffortStatus(sessionId: string, entry: SessionEntry): EffortStatus | null {
|
|
121
|
+
return sessionEffortStatus(providerRegistry.get(entry.providerId), entry, resolveSessionModel(sessionId));
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
/** Whether thinking is effectively ON: per-session override wins, else provider config, else SDK default. */
|
|
118
125
|
function resolveSessionThinkingEnabled(sessionId: string): boolean {
|
|
119
126
|
return isThinkingEnabled(
|
|
@@ -196,6 +203,8 @@ interface SessionEntry {
|
|
|
196
203
|
compactStatus?: "compacting" | null;
|
|
197
204
|
/** MCP servers the subprocess reported as `needs-auth` at init — drives the chat's sign-in bar */
|
|
198
205
|
mcpNeedsAuth?: string[];
|
|
206
|
+
/** What the subprocess said at the last turn's start about the effort it sends — see chat-effort-status.ts */
|
|
207
|
+
effortStatus?: EffortStatus;
|
|
199
208
|
/** toolUseIds of Bash/Agent calls launched with run_in_background — their spy outlives the tool_result */
|
|
200
209
|
backgroundToolUseIds?: Set<string>;
|
|
201
210
|
/** Nested-agent children buffered into turnEvents this turn (see MAX_NESTED_TURN_EVENTS) */
|
|
@@ -957,9 +966,20 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
957
966
|
// System events → transition connecting → thinking, forward compact events
|
|
958
967
|
if (evType === "system") {
|
|
959
968
|
const sub = (ev as any).subtype;
|
|
960
|
-
if (sub === "init"
|
|
961
|
-
|
|
962
|
-
|
|
969
|
+
if (sub === "init") {
|
|
970
|
+
if (Array.isArray(ev.mcpServers)) {
|
|
971
|
+
entry.mcpNeedsAuth = needsAuthServerNames(ev.mcpServers);
|
|
972
|
+
broadcast(sessionId, mcpStatusEvent(entry.mcpNeedsAuth));
|
|
973
|
+
}
|
|
974
|
+
// Not awaited: the turn goes on while the CLI answers.
|
|
975
|
+
void refreshEffortStatus({
|
|
976
|
+
sessionId,
|
|
977
|
+
session: entry,
|
|
978
|
+
provider: providerRegistry.get(entry.providerId),
|
|
979
|
+
isCurrent: () => activeSessions.get(sessionId) === entry,
|
|
980
|
+
broadcast: (event) => broadcast(sessionId, event),
|
|
981
|
+
log: (line) => logSessionEvent(sessionId, "INFO", line),
|
|
982
|
+
});
|
|
963
983
|
} else if (sub === "compacting") {
|
|
964
984
|
entry.compactStatus = "compacting";
|
|
965
985
|
log.debug(`session=${sessionId} compact_status=compacting (persisted on entry)`);
|
|
@@ -1196,6 +1216,9 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
1196
1216
|
const stopped = turnStop ? describeTurnStop(turnStop) : null;
|
|
1197
1217
|
|
|
1198
1218
|
const finalText = entry.finalText;
|
|
1219
|
+
// The consumer outlives the turn (Claude pushes a follow-up into the same stream), so
|
|
1220
|
+
// the next turn's answer must not start with this one's.
|
|
1221
|
+
entry.finalText = undefined;
|
|
1199
1222
|
import("../../services/notification.service.ts").then(({ notificationService }) => {
|
|
1200
1223
|
const project = entry.projectName || "Project";
|
|
1201
1224
|
const session = chatService.getSession(sessionId);
|
|
@@ -1385,6 +1408,7 @@ export const chatWebSocket = {
|
|
|
1385
1408
|
mcpNeedsAuth: existing.mcpNeedsAuth ?? [],
|
|
1386
1409
|
model: resolveSessionModel(sessionId),
|
|
1387
1410
|
effort: resolveSessionEffort(sessionId),
|
|
1411
|
+
effortStatus: resolveEffortStatus(sessionId, existing),
|
|
1388
1412
|
thinking: resolveSessionThinkingEnabled(sessionId),
|
|
1389
1413
|
promptCache: promptCacheSnapshot(sessionId, existing),
|
|
1390
1414
|
turnStop: existing.phase === "idle" ? lastTurnStop(sessionId) : null,
|
|
@@ -1554,6 +1578,7 @@ export const chatWebSocket = {
|
|
|
1554
1578
|
mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
|
|
1555
1579
|
model: resolveSessionModel(sessionId),
|
|
1556
1580
|
effort: resolveSessionEffort(sessionId),
|
|
1581
|
+
effortStatus: resolveEffortStatus(sessionId, entry),
|
|
1557
1582
|
thinking: resolveSessionThinkingEnabled(sessionId),
|
|
1558
1583
|
promptCache: promptCacheSnapshot(sessionId, entry),
|
|
1559
1584
|
turnStop: entry.phase === "idle" ? lastTurnStop(sessionId) : null,
|
|
@@ -1776,6 +1801,8 @@ export const chatWebSocket = {
|
|
|
1776
1801
|
}
|
|
1777
1802
|
entry.model = parsed.model;
|
|
1778
1803
|
setSessionModel(sessionId, parsed.model);
|
|
1804
|
+
// Its levels and limits were the old model's; the settings files stand in until the next turn asks.
|
|
1805
|
+
entry.effortStatus = undefined;
|
|
1779
1806
|
const provider = providerRegistry.get(providerId);
|
|
1780
1807
|
const hasLiveStream = provider?.hasStreamingSession?.(sessionId) ?? false;
|
|
1781
1808
|
// Only abort when idle between turns — never interrupt an active turn.
|
|
@@ -1795,6 +1822,7 @@ export const chatWebSocket = {
|
|
|
1795
1822
|
mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
|
|
1796
1823
|
model: resolveSessionModel(sessionId),
|
|
1797
1824
|
effort: resolveSessionEffort(sessionId),
|
|
1825
|
+
effortStatus: resolveEffortStatus(sessionId, entry),
|
|
1798
1826
|
thinking: resolveSessionThinkingEnabled(sessionId),
|
|
1799
1827
|
}));
|
|
1800
1828
|
} else if (parsed.type === "set_effort") {
|
|
@@ -1822,6 +1850,7 @@ export const chatWebSocket = {
|
|
|
1822
1850
|
mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
|
|
1823
1851
|
model: resolveSessionModel(sessionId),
|
|
1824
1852
|
effort: resolveSessionEffort(sessionId),
|
|
1853
|
+
effortStatus: resolveEffortStatus(sessionId, entry),
|
|
1825
1854
|
thinking: resolveSessionThinkingEnabled(sessionId),
|
|
1826
1855
|
}));
|
|
1827
1856
|
} else if (parsed.type === "set_thinking") {
|
|
@@ -1843,6 +1872,7 @@ export const chatWebSocket = {
|
|
|
1843
1872
|
mcpNeedsAuth: entry.mcpNeedsAuth ?? [],
|
|
1844
1873
|
model: resolveSessionModel(sessionId),
|
|
1845
1874
|
effort: resolveSessionEffort(sessionId),
|
|
1875
|
+
effortStatus: resolveEffortStatus(sessionId, entry),
|
|
1846
1876
|
thinking: resolveSessionThinkingEnabled(sessionId),
|
|
1847
1877
|
}));
|
|
1848
1878
|
} else if (parsed.type === "cancel") {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { statSync } from "node:fs";
|
|
20
20
|
import type { ChatMessage } from "../types/chat.ts";
|
|
21
21
|
import { parseJsonlTranscript } from "./jsonl-transcript-parser.ts";
|
|
22
|
+
import { readFileChunks } from "./file-chunks.ts";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* How many bytes one expand may parse.
|
|
@@ -90,26 +91,20 @@ export async function compactSegmentWindow(filePath: string, beforeUuid?: string
|
|
|
90
91
|
// instead would copy a long record once per chunk it spans.
|
|
91
92
|
const pending: Uint8Array[] = [];
|
|
92
93
|
let at = 0; // file offset of the first pending byte
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
pending.push(value);
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
const lines = Buffer.concat([...pending, value.subarray(0, lastNewline + 1)]);
|
|
104
|
-
const stop = scan(lines, at);
|
|
105
|
-
if (stop !== undefined) return window(stop);
|
|
106
|
-
at += lines.length;
|
|
107
|
-
pending.length = 0;
|
|
108
|
-
pending.push(value.subarray(lastNewline + 1));
|
|
94
|
+
// Not `Bun.file().stream()`, which keeps about the file's size in native memory per read
|
|
95
|
+
// (see `file-chunks.ts`). Returning mid-loop closes the file.
|
|
96
|
+
for await (const value of readFileChunks(filePath)) {
|
|
97
|
+
const lastNewline = value.lastIndexOf(NEWLINE);
|
|
98
|
+
if (lastNewline === -1) {
|
|
99
|
+
pending.push(value);
|
|
100
|
+
continue;
|
|
109
101
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
const lines = Buffer.concat([...pending, value.subarray(0, lastNewline + 1)]);
|
|
103
|
+
const stop = scan(lines, at);
|
|
104
|
+
if (stop !== undefined) return window(stop);
|
|
105
|
+
at += lines.length;
|
|
106
|
+
pending.length = 0;
|
|
107
|
+
pending.push(value.subarray(lastNewline + 1));
|
|
113
108
|
}
|
|
114
109
|
// A last line with no newline yet is still a record the full parse would read.
|
|
115
110
|
const stop = scan(Buffer.concat(pending), at);
|
|
@@ -15,8 +15,12 @@ import { GRID_EXPORT_TICKET_TTL_MS } from "../../shared/db-grid-export.ts";
|
|
|
15
15
|
export interface ExportDownload {
|
|
16
16
|
fileName: string;
|
|
17
17
|
contentType: string;
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The file, read from the database as it is sent — or, for a file already on disk, the file
|
|
20
|
+
* itself: a `Bun.file` body is sent by Bun directly, where `Bun.file().stream()` keeps about the
|
|
21
|
+
* file's size in native memory per download (see `file-chunks.ts`).
|
|
22
|
+
*/
|
|
23
|
+
open(): ReadableStream<Uint8Array> | Blob;
|
|
20
24
|
/** Lets go of the rows and the connection, when the download never starts. */
|
|
21
25
|
abandon(): void;
|
|
22
26
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* order mark, or UTF-16 with one — what PPM's "CSV for Excel" writes. Bytes that are not text in
|
|
4
4
|
* that encoding fail the read: turned into "�" they would be imported as a different value.
|
|
5
5
|
*/
|
|
6
|
+
import { readFileChunks } from "../../../file-chunks.ts";
|
|
6
7
|
|
|
7
8
|
/** The encoding the first bytes name, and how many of them the mark takes. */
|
|
8
9
|
function encodingOf(head: Uint8Array): { encoding: "utf-8" | "utf-16le" | "utf-16be"; skip: number } {
|
|
@@ -30,7 +31,9 @@ function notText(encoding: string): FileTextError {
|
|
|
30
31
|
|
|
31
32
|
/** The text of the file at `path`, in pieces as it is read. Stop ends it between two pieces. */
|
|
32
33
|
export async function* fileText(path: string, signal?: AbortSignal): AsyncGenerator<string> {
|
|
33
|
-
|
|
34
|
+
// Not `Bun.file().stream()`, which keeps about the file's size in native memory per read
|
|
35
|
+
// (see `file-chunks.ts`) — an import file can be hundreds of MB.
|
|
36
|
+
const chunks = readFileChunks(path);
|
|
34
37
|
let decoder: TextDecoder | null = null;
|
|
35
38
|
let encoding = "utf-8";
|
|
36
39
|
let head: Uint8Array = new Uint8Array(0);
|
|
@@ -55,7 +58,7 @@ export async function* fileText(path: string, signal?: AbortSignal): AsyncGenera
|
|
|
55
58
|
sliceStart = performance.now();
|
|
56
59
|
}
|
|
57
60
|
signal?.throwIfAborted();
|
|
58
|
-
const { done, value } = await
|
|
61
|
+
const { done, value } = await chunks.next();
|
|
59
62
|
if (done) break;
|
|
60
63
|
let bytes: Uint8Array = value;
|
|
61
64
|
if (!decoder) {
|
|
@@ -75,6 +78,6 @@ export async function* fileText(path: string, signal?: AbortSignal): AsyncGenera
|
|
|
75
78
|
if (rest) yield rest;
|
|
76
79
|
} finally {
|
|
77
80
|
// Read to its end or not, the file is closed.
|
|
78
|
-
await
|
|
81
|
+
await chunks.return(undefined).catch(() => {});
|
|
79
82
|
}
|
|
80
83
|
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import net from "node:net";
|
|
22
22
|
import { resolveTargetPort, _resetTargetCache } from "./edge-target-resolver.ts";
|
|
23
23
|
import { createLogger, installFileLogSink } from "./logger.ts";
|
|
24
|
+
import { registerMemoryGauge, startMemoryDiagnostics } from "./memory-diagnostics.ts";
|
|
24
25
|
|
|
25
26
|
const log = createLogger("edge");
|
|
26
27
|
|
|
@@ -42,6 +43,12 @@ const CONNECT_WINDOW_MS = 5000;
|
|
|
42
43
|
|
|
43
44
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
44
45
|
|
|
46
|
+
/** Client connections open right now, and ever: the memory watchdog's view of the edge's load. */
|
|
47
|
+
let openConnections = 0;
|
|
48
|
+
let totalConnections = 0;
|
|
49
|
+
registerMemoryGauge("edge.openConnections", () => openConnections);
|
|
50
|
+
registerMemoryGauge("edge.totalConnections", () => totalConnections);
|
|
51
|
+
|
|
45
52
|
/** At most one warning a minute about connections dropped for want of a server. */
|
|
46
53
|
const DROP_WARN_INTERVAL_MS = 60_000;
|
|
47
54
|
let droppedSinceWarning = 0;
|
|
@@ -201,6 +208,9 @@ export function startEdgeForwarder(
|
|
|
201
208
|
// allowHalfOpen keeps the other direction alive when one side sends FIN —
|
|
202
209
|
// long-lived chat WS and SSE streams depend on it.
|
|
203
210
|
const server = net.createServer({ allowHalfOpen: true }, (client) => {
|
|
211
|
+
openConnections++;
|
|
212
|
+
totalConnections++;
|
|
213
|
+
client.once("close", () => { openConnections--; });
|
|
204
214
|
void handleConnection(client, connectWindowMs);
|
|
205
215
|
});
|
|
206
216
|
server.once("error", reject);
|
|
@@ -218,6 +228,8 @@ if (process.argv.includes("__edge__")) {
|
|
|
218
228
|
// Lines go to ppm.log with a timestamp and level like every other process's. The
|
|
219
229
|
// supervisor hands the log over as stderr, so the stderr echo is skipped (see logger.ts).
|
|
220
230
|
installFileLogSink({ echo: "stderr" });
|
|
231
|
+
// Reads memory through the OS only: it spawns nothing, so the invariant above holds.
|
|
232
|
+
startMemoryDiagnostics({ role: "edge" });
|
|
221
233
|
const idx = process.argv.indexOf("__edge__");
|
|
222
234
|
const publicPort = parseInt(process.argv[idx + 1] ?? "", 10);
|
|
223
235
|
const host = process.argv[idx + 2] ?? "0.0.0.0";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A file's bytes, a piece at a time, through `node:fs/promises` — never `Bun.file().stream()`.
|
|
5
|
+
*
|
|
6
|
+
* On Bun 1.3.10 (Windows), every full read through `Bun.file(path).stream()` keeps roughly the
|
|
7
|
+
* file's size in native memory that is never handed back: a 30 MB transcript read 40 times took
|
|
8
|
+
* a process from 377 MB to 1,949 MB private, 38 MB a read, with the JS heap back at 18 MB. A
|
|
9
|
+
* chat tab opening reads the whole transcript this way (`readCompactions`), so a server whose
|
|
10
|
+
* tabs reconnected all day reached 25 GB committed and the machine ran out of memory. The same
|
|
11
|
+
* 40 reads through a `FileHandle` stayed flat (0.1 MB a read); so did `createReadStream` and
|
|
12
|
+
* `readSync`. Measured as private bytes (commit charge), which RSS and the heap both hide.
|
|
13
|
+
*
|
|
14
|
+
* Each chunk is a fresh buffer, so a caller may keep it after asking for the next one. Breaking
|
|
15
|
+
* out of the loop closes the file (the generator's `finally`), so an early stop leaks no handle.
|
|
16
|
+
*
|
|
17
|
+
* `fromByte` is inclusive, `toByte` exclusive; both are absolute offsets.
|
|
18
|
+
*/
|
|
19
|
+
export async function* readFileChunks(
|
|
20
|
+
filePath: string,
|
|
21
|
+
opts: { fromByte?: number; toByte?: number; chunkBytes?: number } = {},
|
|
22
|
+
): AsyncGenerator<Uint8Array> {
|
|
23
|
+
const chunkBytes = opts.chunkBytes ?? 512 * 1024;
|
|
24
|
+
let position = Math.max(0, opts.fromByte ?? 0);
|
|
25
|
+
const end = opts.toByte ?? Infinity;
|
|
26
|
+
const handle = await open(filePath, "r");
|
|
27
|
+
try {
|
|
28
|
+
while (position < end) {
|
|
29
|
+
const want = Math.min(chunkBytes, end - position);
|
|
30
|
+
const buf = new Uint8Array(want);
|
|
31
|
+
const { bytesRead } = await handle.read(buf, 0, want, position);
|
|
32
|
+
if (bytesRead === 0) break;
|
|
33
|
+
position += bytesRead;
|
|
34
|
+
yield bytesRead === want ? buf : buf.subarray(0, bytesRead);
|
|
35
|
+
}
|
|
36
|
+
} finally {
|
|
37
|
+
await handle.close().catch(() => {});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* Neither cost was bounded: both grow with the file, and both were paid on the
|
|
16
16
|
* one thread that also serves every other request.
|
|
17
17
|
*/
|
|
18
|
+
import { readFileChunks } from "./file-chunks.ts";
|
|
18
19
|
|
|
19
20
|
/** How much of the end of a file is read to find the last few lines. */
|
|
20
21
|
export const DEFAULT_TAIL_BYTES = 64 * 1024;
|
|
@@ -89,24 +90,16 @@ export async function countLines(filePath: string): Promise<number> {
|
|
|
89
90
|
let count = 0;
|
|
90
91
|
let lastByte = -1;
|
|
91
92
|
let sinceYield = 0;
|
|
92
|
-
// `
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (value.length > 0) lastByte = value[value.length - 1]!;
|
|
102
|
-
sinceYield += value.length;
|
|
103
|
-
if (sinceYield >= YIELD_EVERY_BYTES) {
|
|
104
|
-
sinceYield = 0;
|
|
105
|
-
await new Promise<void>((r) => setTimeout(r, 0));
|
|
106
|
-
}
|
|
93
|
+
// Not `Bun.file().stream()`, which keeps about the file's size in native
|
|
94
|
+
// memory per read and never returns it (see `file-chunks.ts`).
|
|
95
|
+
for await (const value of readFileChunks(filePath)) {
|
|
96
|
+
for (let i = 0; i < value.length; i++) if (value[i] === 0x0a) count++;
|
|
97
|
+
if (value.length > 0) lastByte = value[value.length - 1]!;
|
|
98
|
+
sinceYield += value.length;
|
|
99
|
+
if (sinceYield >= YIELD_EVERY_BYTES) {
|
|
100
|
+
sinceYield = 0;
|
|
101
|
+
await new Promise<void>((r) => setTimeout(r, 0));
|
|
107
102
|
}
|
|
108
|
-
} finally {
|
|
109
|
-
reader.releaseLock();
|
|
110
103
|
}
|
|
111
104
|
if (lastByte === -1) return 0; // empty file
|
|
112
105
|
if (lastByte !== 0x0a) count++; // last line carries no newline
|
|
@@ -20,6 +20,7 @@ import { resolveBunPath } from "../autostart-generator.ts";
|
|
|
20
20
|
import { createLogger } from "../logger.ts";
|
|
21
21
|
import { canInstall, lspInstallDir, rustupServerPath } from "./lsp-install.ts";
|
|
22
22
|
import { LspSession, type LspSessionState } from "./lsp-session.ts";
|
|
23
|
+
import { registerMemoryGauge } from "../memory-diagnostics.ts";
|
|
23
24
|
import {
|
|
24
25
|
LANGUAGE_SERVERS,
|
|
25
26
|
ancestorDirs,
|
|
@@ -595,3 +596,5 @@ export class LspManager {
|
|
|
595
596
|
}
|
|
596
597
|
|
|
597
598
|
export const lspManager = new LspManager();
|
|
599
|
+
|
|
600
|
+
registerMemoryGauge("lsp.servers", () => lspManager["entries"].size);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What the OS says this process holds, beyond what `process.memoryUsage()` reports.
|
|
5
|
+
*
|
|
6
|
+
* `rss` is only what is resident right now. A leak of *committed* memory — an allocator
|
|
7
|
+
* segment reserved and never handed back, a pipe buffer per child — can sit at 30 GB of
|
|
8
|
+
* private bytes while RSS reads 1–2 GB, because Windows trims the working set and pages
|
|
9
|
+
* the rest out. That is exactly how the server's leak looked on 20/10/2026: mem-diag said
|
|
10
|
+
* 1 GB, Windows' low-memory detector named the same pid at 32 GB, and every new child
|
|
11
|
+
* process died of memory exhaustion. Private bytes are the number that runs the machine
|
|
12
|
+
* out of commit, so that is the one recorded here.
|
|
13
|
+
*
|
|
14
|
+
* Every field is `undefined` when this platform cannot measure it, never `0`.
|
|
15
|
+
*/
|
|
16
|
+
export interface NativeMemory {
|
|
17
|
+
/** Windows `PrivateUsage` (commit charge); Linux `RssAnon + VmSwap`. */
|
|
18
|
+
privateMB?: number;
|
|
19
|
+
/** Windows `PeakWorkingSetSize`; Linux `VmHWM`. */
|
|
20
|
+
peakRssMB?: number;
|
|
21
|
+
/** Linux `VmSize` — address space, mostly reservations. */
|
|
22
|
+
virtualMB?: number;
|
|
23
|
+
/** Windows kernel handles; Linux open file descriptors. Climbs with leaked pipes and children. */
|
|
24
|
+
handles?: number;
|
|
25
|
+
/** Linux only: `Threads` in `/proc/self/status`. */
|
|
26
|
+
threads?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const MB = 1024 * 1024;
|
|
30
|
+
|
|
31
|
+
type Win32 = {
|
|
32
|
+
K32GetProcessMemoryInfo: (process: bigint, counters: number, size: number) => number;
|
|
33
|
+
GetProcessHandleCount: (process: bigint, count: number) => number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** `(HANDLE)-1`, the pseudo handle for the calling process. A bigint, so it crosses FFI unrounded. */
|
|
37
|
+
const CURRENT_PROCESS = -1n;
|
|
38
|
+
/** `sizeof(PROCESS_MEMORY_COUNTERS_EX)` on 64-bit Windows: two DWORDs then nine SIZE_Ts. */
|
|
39
|
+
const COUNTERS_SIZE = 80;
|
|
40
|
+
const OFFSET_PEAK_WORKING_SET = 8;
|
|
41
|
+
const OFFSET_PRIVATE_USAGE = 72;
|
|
42
|
+
|
|
43
|
+
let win32: Win32 | null | undefined;
|
|
44
|
+
|
|
45
|
+
async function loadWin32(): Promise<Win32 | null> {
|
|
46
|
+
if (win32 !== undefined) return win32;
|
|
47
|
+
try {
|
|
48
|
+
const { dlopen, FFIType } = await import("bun:ffi");
|
|
49
|
+
win32 = dlopen("kernel32.dll", {
|
|
50
|
+
K32GetProcessMemoryInfo: { args: [FFIType.i64, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
|
|
51
|
+
GetProcessHandleCount: { args: [FFIType.i64, FFIType.ptr], returns: FFIType.i32 },
|
|
52
|
+
}).symbols as unknown as Win32;
|
|
53
|
+
} catch {
|
|
54
|
+
win32 = null;
|
|
55
|
+
}
|
|
56
|
+
return win32;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function readWindows(): Promise<NativeMemory> {
|
|
60
|
+
const lib = await loadWin32();
|
|
61
|
+
if (!lib) return {};
|
|
62
|
+
const { ptr } = await import("bun:ffi");
|
|
63
|
+
const out: NativeMemory = {};
|
|
64
|
+
|
|
65
|
+
const counters = new Uint8Array(COUNTERS_SIZE);
|
|
66
|
+
const view = new DataView(counters.buffer);
|
|
67
|
+
view.setUint32(0, COUNTERS_SIZE, true);
|
|
68
|
+
if (lib.K32GetProcessMemoryInfo(CURRENT_PROCESS, ptr(counters), COUNTERS_SIZE) !== 0) {
|
|
69
|
+
out.privateMB = Math.round(Number(view.getBigUint64(OFFSET_PRIVATE_USAGE, true)) / MB);
|
|
70
|
+
out.peakRssMB = Math.round(Number(view.getBigUint64(OFFSET_PEAK_WORKING_SET, true)) / MB);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const handleCount = new Uint32Array(1);
|
|
74
|
+
if (lib.GetProcessHandleCount(CURRENT_PROCESS, ptr(handleCount)) !== 0) out.handles = handleCount[0];
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Parse the `kB` lines of `/proc/self/status` that matter here. Exported for tests. */
|
|
79
|
+
export function parseProcStatus(text: string): NativeMemory {
|
|
80
|
+
const kb = (key: string): number | undefined => {
|
|
81
|
+
const m = text.match(new RegExp(`^${key}:\\s+(\\d+)\\s+kB`, "m"));
|
|
82
|
+
return m ? Number(m[1]) : undefined;
|
|
83
|
+
};
|
|
84
|
+
const anon = kb("RssAnon");
|
|
85
|
+
const swap = kb("VmSwap");
|
|
86
|
+
const hwm = kb("VmHWM");
|
|
87
|
+
const size = kb("VmSize");
|
|
88
|
+
const threads = text.match(/^Threads:\s+(\d+)/m);
|
|
89
|
+
return {
|
|
90
|
+
privateMB: anon === undefined ? undefined : Math.round((anon + (swap ?? 0)) / 1024),
|
|
91
|
+
peakRssMB: hwm === undefined ? undefined : Math.round(hwm / 1024),
|
|
92
|
+
virtualMB: size === undefined ? undefined : Math.round(size / 1024),
|
|
93
|
+
threads: threads ? Number(threads[1]) : undefined,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readLinux(): NativeMemory {
|
|
98
|
+
const out = parseProcStatus(readFileSync("/proc/self/status", "utf8"));
|
|
99
|
+
try { out.handles = readdirSync("/proc/self/fd").length; } catch { /* fd count is optional */ }
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** One reading of this process's native memory. Never throws; an unreadable field is left out. */
|
|
104
|
+
export async function readNativeMemory(platform: NodeJS.Platform = process.platform): Promise<NativeMemory> {
|
|
105
|
+
try {
|
|
106
|
+
if (platform === "win32") return await readWindows();
|
|
107
|
+
if (platform === "linux") return readLinux();
|
|
108
|
+
} catch { /* fall through: no native reading this tick */ }
|
|
109
|
+
return {};
|
|
110
|
+
}
|