@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
|
@@ -15,9 +15,20 @@ import {
|
|
|
15
15
|
} from "../services/tab-tools-mcp/tab-tools-mcp-tool.ts";
|
|
16
16
|
import { TAB_TOOLS } from "../shared/ppm-tools.ts";
|
|
17
17
|
import { CLAUDE_DB_TOOLS_MCP_SERVER, DB_TOOLS, DB_TOOLS_TIMEOUT_MS, type DbToolsMcpAccess } from "../services/db-ai-tools/db-ai-tools-tool.ts";
|
|
18
|
+
import { EFFORT_LEVELS, type EffortLevel } from "../shared/effort-status.ts";
|
|
18
19
|
|
|
19
|
-
export const VALID_EFFORT_VALUES =
|
|
20
|
-
export type EffortValue =
|
|
20
|
+
export const VALID_EFFORT_VALUES = EFFORT_LEVELS;
|
|
21
|
+
export type EffortValue = EffortLevel;
|
|
22
|
+
|
|
23
|
+
export type ClaudeSettingSource = "user" | "project" | "local";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The settings files a chat's CLI loads. `local` stays out: a project's
|
|
27
|
+
* `.claude/settings.local.json` can deny tools and set `env` (an `ANTHROPIC_API_KEY` there moves
|
|
28
|
+
* the chat onto another account), and the empty `settings` PPM passes cancels neither. The
|
|
29
|
+
* project's own `.claude/settings.json` is loaded and can do the same; only the local file is out.
|
|
30
|
+
*/
|
|
31
|
+
export const CLAUDE_SETTING_SOURCES: readonly ClaudeSettingSource[] = ["user", "project"];
|
|
21
32
|
|
|
22
33
|
/**
|
|
23
34
|
* Thinking is a tri-state carried as a nullable integer through config, the DB and
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getSessionInfo as sdkGetSessionInfo,
|
|
8
8
|
getSessionMessages,
|
|
9
9
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
10
|
-
import { allowedToolsFor, buildModelQueryOptions, buildSystemPromptOption, buildToolHooks, CLAUDE_DB_TOOLS, CLAUDE_TAB_TOOLS, dbToolsMcpServers, designMcpServers, fileWriteTarget, preToolUseDecision, READ_ONLY_TOOLS, shellHookCall, tabToolsMcpServers, withSessionTokenMasked } from "./claude-agent-sdk-query-options.ts";
|
|
10
|
+
import { allowedToolsFor, buildModelQueryOptions, CLAUDE_SETTING_SOURCES, buildSystemPromptOption, buildToolHooks, CLAUDE_DB_TOOLS, CLAUDE_TAB_TOOLS, dbToolsMcpServers, designMcpServers, fileWriteTarget, preToolUseDecision, READ_ONLY_TOOLS, shellHookCall, tabToolsMcpServers, withSessionTokenMasked } from "./claude-agent-sdk-query-options.ts";
|
|
11
11
|
import { localServerBaseUrl } from "../services/server-listen-address.ts";
|
|
12
12
|
import { TAB_TOOLS_MCP_PATH, tabToolsMcpAccessFor } from "../services/tab-tools-mcp/tab-tools-mcp-tokens.ts";
|
|
13
13
|
import { DB_TOOLS_MCP_PATH, dbToolsMcpAccessFor } from "../services/db-ai-tools/db-ai-tools-tokens.ts";
|
|
@@ -19,6 +19,10 @@ import { captureBaseline } from "../services/session-file-baselines/session-file
|
|
|
19
19
|
import { observeFile } from "../services/session-file-baselines/session-file-history.ts";
|
|
20
20
|
import { beginShellCommand, endShellCommand, noteFileToolWrite } from "../services/session-file-baselines/shell-change-tracker.ts";
|
|
21
21
|
import { WarmSpares, spawnFingerprint } from "./claude-warm-spare.ts";
|
|
22
|
+
import { APPROVAL_WAKE, ApprovalQueue } from "./claude-approval-queue.ts";
|
|
23
|
+
import { EFFORT_ENV_VAR, effortStatusFromCli, estimateEffortStatus, userSettingsFile, type EffortSettingsQuery } from "./claude-effort-status.ts";
|
|
24
|
+
import type { EffortStatus } from "../shared/effort-status.ts";
|
|
25
|
+
import { withTimeout } from "../services/mcp-oauth/mcp-control-query.ts";
|
|
22
26
|
import { CLAUDE_DESIGN_CHECK_TOOL } from "../services/design/mcp/design-mcp-tool.ts";
|
|
23
27
|
import { designToolDecision } from "../services/design/design-tool-policy.ts";
|
|
24
28
|
import { CLAUDE_MODELS } from "../types/claude-models.ts";
|
|
@@ -62,6 +66,8 @@ const usageLog = createLogger("usage");
|
|
|
62
66
|
const mcpControlLog = createLogger("mcp-control");
|
|
63
67
|
|
|
64
68
|
const CLAUDE_PROJECTS_DIR = resolve(homedir(), ".claude/projects");
|
|
69
|
+
/** A running CLI answers `get_settings` in a few ms; this only bounds one that never does. */
|
|
70
|
+
const EFFORT_READ_TIMEOUT_MS = 5_000;
|
|
65
71
|
|
|
66
72
|
/**
|
|
67
73
|
* Resolve the Claude CLI the SDK should spawn.
|
|
@@ -283,6 +289,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
283
289
|
* and this is the only place that knows what made it do so.
|
|
284
290
|
*/
|
|
285
291
|
private teardownReasons = new Map<string, string>();
|
|
292
|
+
/** The `--effort` each session's CLI was started with, to set beside what it reports sending. */
|
|
293
|
+
private startedEffort = new Map<string, string | null>();
|
|
286
294
|
/** The API's last rate-limit status per account and limit, so only a change is logged. */
|
|
287
295
|
private rateLimitStatus = new Map<string, string>();
|
|
288
296
|
/** CLIs started for chats that do not exist yet — see `prewarm`. */
|
|
@@ -738,6 +746,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
738
746
|
}
|
|
739
747
|
}
|
|
740
748
|
this.forkSources.delete(sessionId);
|
|
749
|
+
this.startedEffort.delete(sessionId);
|
|
741
750
|
|
|
742
751
|
// Best-effort: delete JSONL from ~/.claude/projects/
|
|
743
752
|
try {
|
|
@@ -933,7 +942,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
933
942
|
...(p.forkSession && { forkSession: true }),
|
|
934
943
|
cwd: p.cwd,
|
|
935
944
|
systemPrompt: p.systemPrompt,
|
|
936
|
-
settingSources: [
|
|
945
|
+
settingSources: [...CLAUDE_SETTING_SOURCES],
|
|
937
946
|
env: p.env,
|
|
938
947
|
settings: { permissions: { allow: [], deny: [] } },
|
|
939
948
|
allowedTools: p.allowedTools,
|
|
@@ -1177,12 +1186,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1177
1186
|
const dbToolsMcp = opts?.designSession ? undefined : opts?.dbToolsMcp;
|
|
1178
1187
|
const allowedTools = allowedToolsFor({ isBypass, agentTeams: providerConfig.agent_teams, designPolicy, designCheckTool });
|
|
1179
1188
|
|
|
1180
|
-
/**
|
|
1181
|
-
|
|
1182
|
-
* PreToolUse hook pushes events here; the main loop yields them.
|
|
1183
|
-
*/
|
|
1184
|
-
const approvalEvents: ChatEvent[] = [];
|
|
1185
|
-
let approvalNotify: (() => void) | undefined;
|
|
1189
|
+
/** Approval requests from canUseTool and the PreToolUse hook, handed out by the main loop. */
|
|
1190
|
+
const approvals = new ApprovalQueue<ChatEvent>();
|
|
1186
1191
|
|
|
1187
1192
|
/**
|
|
1188
1193
|
* Helper: send approval request to FE and wait for response.
|
|
@@ -1193,8 +1198,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1193
1198
|
const promise = new Promise<{ approved: boolean; data?: unknown }>((resolve) => {
|
|
1194
1199
|
this.pendingApprovals.set(requestId, { resolve, sessionId });
|
|
1195
1200
|
});
|
|
1196
|
-
|
|
1197
|
-
approvalNotify?.();
|
|
1201
|
+
approvals.push({ type: "approval_request", requestId, tool: toolName, input });
|
|
1198
1202
|
return promise;
|
|
1199
1203
|
};
|
|
1200
1204
|
|
|
@@ -1460,6 +1464,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1460
1464
|
providerConfig,
|
|
1461
1465
|
stderr: stderrCallback,
|
|
1462
1466
|
});
|
|
1467
|
+
// Every retry below restarts the CLI with these same options.
|
|
1468
|
+
this.startedEffort.set(sessionId, (queryOptions.effort as string | undefined) ?? null);
|
|
1463
1469
|
|
|
1464
1470
|
// Crash retry: if subprocess exits with non-zero code before producing events,
|
|
1465
1471
|
// clean up and retry once with a fresh query before surfacing the error.
|
|
@@ -1610,7 +1616,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1610
1616
|
assistantContent = "";
|
|
1611
1617
|
pendingToolCount = 0;
|
|
1612
1618
|
let sdkEventCount = 0;
|
|
1613
|
-
for await (const msg of eventSource) {
|
|
1619
|
+
for await (const msg of approvals.alongside(eventSource)) {
|
|
1620
|
+
// An approval goes out now: the CLI sends nothing more until it is answered.
|
|
1621
|
+
if (msg === APPROVAL_WAKE) {
|
|
1622
|
+
yield* approvals.drain();
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1614
1625
|
sdkEventCount++;
|
|
1615
1626
|
hadAnyEvents = true;
|
|
1616
1627
|
if (sdkEventCount === 1) {
|
|
@@ -1640,9 +1651,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1640
1651
|
const parentId = (msg as any).parent_tool_use_id as string | undefined;
|
|
1641
1652
|
|
|
1642
1653
|
// Yield any queued approval events
|
|
1643
|
-
|
|
1644
|
-
yield approvalEvents.shift()!;
|
|
1645
|
-
}
|
|
1654
|
+
yield* approvals.drain();
|
|
1646
1655
|
|
|
1647
1656
|
// Log system events for debugging SDK lifecycle: the subtype only, never the message.
|
|
1648
1657
|
// thinking_tokens and status=requesting arrive at token rate, so not even at DEBUG.
|
|
@@ -2430,9 +2439,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
2430
2439
|
}
|
|
2431
2440
|
|
|
2432
2441
|
// Yield remaining approval events
|
|
2433
|
-
|
|
2434
|
-
yield approvalEvents.shift()!;
|
|
2435
|
-
}
|
|
2442
|
+
yield* approvals.drain();
|
|
2436
2443
|
|
|
2437
2444
|
if (!hadAnyEvents) {
|
|
2438
2445
|
yield { type: "error", message: "Claude did not respond. Check that 'claude' CLI works in your terminal." };
|
|
@@ -2533,7 +2540,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
2533
2540
|
...(needsNodeInterpreter(process.platform, cliExecutablePath) && { executable: "node" }),
|
|
2534
2541
|
...(cliExecutablePath && { pathToClaudeCodeExecutable: cliExecutablePath }),
|
|
2535
2542
|
cwd,
|
|
2536
|
-
settingSources: [
|
|
2543
|
+
settingSources: [...CLAUDE_SETTING_SOURCES],
|
|
2537
2544
|
// No account: MCP OAuth tokens belong to the CLI's own credential store, the same
|
|
2538
2545
|
// one an interactive `claude` writes, and no model call is ever made here.
|
|
2539
2546
|
env: this.buildQueryEnv(cwd, null),
|
|
@@ -2578,6 +2585,39 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
2578
2585
|
}
|
|
2579
2586
|
}
|
|
2580
2587
|
|
|
2588
|
+
/**
|
|
2589
|
+
* The effort this session's CLI sends and what overrides the pick, asked of the CLI itself:
|
|
2590
|
+
* `CLAUDE_CODE_EFFORT_LEVEL`, a settings cap, an organization limit and the model's own levels
|
|
2591
|
+
* all count there, while the `--effort` PPM passed is only one input to it. Null when the
|
|
2592
|
+
* session has no live subprocess or the CLI does not answer.
|
|
2593
|
+
*/
|
|
2594
|
+
async readEffortStatus(sessionId: string): Promise<EffortStatus | null> {
|
|
2595
|
+
const ss = this.streamingSessions.get(sessionId);
|
|
2596
|
+
if (!ss) return null;
|
|
2597
|
+
const cli = ss.query as unknown as EffortSettingsQuery;
|
|
2598
|
+
try {
|
|
2599
|
+
const [answer, init] = await Promise.all([
|
|
2600
|
+
withTimeout(cli.getSettings(), EFFORT_READ_TIMEOUT_MS, "Claude did not report its settings"),
|
|
2601
|
+
withTimeout(cli.initializationResult(), EFFORT_READ_TIMEOUT_MS, "Claude did not report its models").catch(() => undefined),
|
|
2602
|
+
]);
|
|
2603
|
+
return effortStatusFromCli({
|
|
2604
|
+
answer,
|
|
2605
|
+
models: init?.models,
|
|
2606
|
+
requested: this.startedEffort.get(sessionId),
|
|
2607
|
+
processEnv: process.env[EFFORT_ENV_VAR],
|
|
2608
|
+
userFile: userSettingsFile(process.env).shown,
|
|
2609
|
+
});
|
|
2610
|
+
} catch (e) {
|
|
2611
|
+
log.debug(`session=${sessionId} effort status unavailable: ${(e as Error).message}`);
|
|
2612
|
+
return null;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/** What the settings files say about effort for a chat in `projectPath` no CLI has answered for yet. */
|
|
2617
|
+
estimateEffortStatus(projectPath: string | undefined, model?: string): EffortStatus {
|
|
2618
|
+
return estimateEffortStatus({ projectPath, model, env: process.env });
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2581
2621
|
/** Abort and fully teardown the streaming session — user must resume to continue */
|
|
2582
2622
|
abortQuery(sessionId: string, source = "unknown"): void {
|
|
2583
2623
|
const ss = this.streamingSessions.get(sessionId);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** What `alongside` yields instead of a message when only the queue has something to hand out. */
|
|
2
|
+
export const APPROVAL_WAKE = Symbol("approval-wake");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Approval requests raised from the CLI's callbacks (`canUseTool`, the PreToolUse hook), for the
|
|
6
|
+
* turn's loop over the CLI's messages to hand out.
|
|
7
|
+
*
|
|
8
|
+
* The CLI asks through a control request and then says nothing until it is answered, so a queue
|
|
9
|
+
* looked at only when the next message arrives can wait forever: an AskUserQuestion stayed behind
|
|
10
|
+
* a thinking spinner for 17 minutes, until the user cancelled. Whether the turn's last message
|
|
11
|
+
* landed before or after the callback ran decided it, which is why it looked intermittent.
|
|
12
|
+
*/
|
|
13
|
+
export class ApprovalQueue<E> {
|
|
14
|
+
private readonly queued: E[] = [];
|
|
15
|
+
private wake: (() => void) | undefined;
|
|
16
|
+
|
|
17
|
+
push(event: E): void {
|
|
18
|
+
this.queued.push(event);
|
|
19
|
+
this.wake?.();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
*drain(): Generator<E> {
|
|
23
|
+
while (this.queued.length > 0) yield this.queued.shift()!;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `source`'s items, plus `APPROVAL_WAKE` whenever something is queued and `source` has nothing
|
|
28
|
+
* to give. The caller must drain the queue on a wake, or it is woken again at once.
|
|
29
|
+
*/
|
|
30
|
+
async *alongside<T>(source: AsyncIterable<T>): AsyncGenerator<T | typeof APPROVAL_WAKE> {
|
|
31
|
+
const it = source[Symbol.asyncIterator]();
|
|
32
|
+
// Kept across wakes: a second next() would hand this item to a promise nobody reads.
|
|
33
|
+
let next: Promise<IteratorResult<T>> | undefined;
|
|
34
|
+
// Closed on an early exit, as `for await` closes it: not once it has ended or thrown.
|
|
35
|
+
let open = true;
|
|
36
|
+
try {
|
|
37
|
+
for (;;) {
|
|
38
|
+
if (this.queued.length > 0) {
|
|
39
|
+
yield APPROVAL_WAKE;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const pending = (next ??= it.next());
|
|
43
|
+
let got: IteratorResult<T> | typeof APPROVAL_WAKE;
|
|
44
|
+
try {
|
|
45
|
+
got = await new Promise<IteratorResult<T> | typeof APPROVAL_WAKE>((resolve, reject) => {
|
|
46
|
+
this.wake = () => resolve(APPROVAL_WAKE);
|
|
47
|
+
// next() may have run a callback that queued something before there was a wake to call.
|
|
48
|
+
if (this.queued.length > 0) this.wake();
|
|
49
|
+
pending.then(resolve, reject);
|
|
50
|
+
});
|
|
51
|
+
} catch (e) {
|
|
52
|
+
open = false;
|
|
53
|
+
throw e;
|
|
54
|
+
} finally {
|
|
55
|
+
this.wake = undefined;
|
|
56
|
+
}
|
|
57
|
+
if (got === APPROVAL_WAKE) continue;
|
|
58
|
+
next = undefined;
|
|
59
|
+
if (got.done) {
|
|
60
|
+
open = false;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
yield got.value;
|
|
64
|
+
}
|
|
65
|
+
} finally {
|
|
66
|
+
if (open && next) {
|
|
67
|
+
// Not awaited: an async generator's return() waits behind the next() still out, which
|
|
68
|
+
// the CLI answers only after the approval it is waiting on.
|
|
69
|
+
Promise.resolve().then(() => it.return?.()).catch(() => {});
|
|
70
|
+
} else if (open) {
|
|
71
|
+
await it.return?.();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What decides a Claude session's effort besides the pick, worked out from the same settings
|
|
3
|
+
* the CLI reads. The live answer comes from the running CLI (`readEffortStatus` in the
|
|
4
|
+
* provider); until a turn has started one, `estimateEffortStatus` reads the settings files
|
|
5
|
+
* the CLI would, which is enough for the common overrides and is replaced by the CLI's own
|
|
6
|
+
* answer as soon as there is one.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import * as nodePath from "node:path";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import {
|
|
13
|
+
effortEnvMeaning, effortRank, isEffortLevel,
|
|
14
|
+
type EffortLevel, type EffortSetting, type EffortSettingSource, type EffortStatus,
|
|
15
|
+
} from "../shared/effort-status.ts";
|
|
16
|
+
import { CLAUDE_SETTING_SOURCES, type ClaudeSettingSource } from "./claude-agent-sdk-query-options.ts";
|
|
17
|
+
|
|
18
|
+
export const EFFORT_ENV_VAR = "CLAUDE_CODE_EFFORT_LEVEL";
|
|
19
|
+
|
|
20
|
+
/** One entry of `get_settings`'s `sources`, which the CLI orders from lowest priority to highest. */
|
|
21
|
+
export interface CliSettingsSource {
|
|
22
|
+
source: string;
|
|
23
|
+
settings: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The part of the `get_settings` answer read here. `getSettings()` is on the SDK's `Query`
|
|
28
|
+
* object but left out of `sdk.d.ts`, which is why this is typed here; a test asserts the SDK
|
|
29
|
+
* still has it, so an upgrade that renames it fails loudly instead of the status going quiet.
|
|
30
|
+
*/
|
|
31
|
+
export interface CliSettingsAnswer {
|
|
32
|
+
sources?: Array<CliSettingsSource | null>;
|
|
33
|
+
/** What the session sends after env overrides, caps and model downgrades. */
|
|
34
|
+
applied?: { model?: string; effort?: string | null };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A model as the CLI's `initialize` answer lists it. */
|
|
38
|
+
export interface CliModelInfo {
|
|
39
|
+
value: string;
|
|
40
|
+
resolvedModel?: string;
|
|
41
|
+
supportedEffortLevels?: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface EffortSettingsQuery {
|
|
45
|
+
getSettings(): Promise<CliSettingsAnswer>;
|
|
46
|
+
initializationResult(): Promise<{ models?: CliModelInfo[] }>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type PathApi = Pick<typeof nodePath, "join" | "relative" | "isAbsolute" | "sep">;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Where the CLI keeps its user settings; `~/` stands in for the home folder in what is shown.
|
|
53
|
+
* Decided with `relative` rather than a `${home}/` prefix, which never matches a Windows path.
|
|
54
|
+
*/
|
|
55
|
+
export function userSettingsFile(
|
|
56
|
+
env: Record<string, string | undefined>,
|
|
57
|
+
home = homedir(),
|
|
58
|
+
path: PathApi = nodePath,
|
|
59
|
+
): { path: string; shown: string } {
|
|
60
|
+
const file = path.join(env.CLAUDE_CONFIG_DIR || path.join(home, ".claude"), "settings.json");
|
|
61
|
+
const rel = path.relative(home, file);
|
|
62
|
+
const inHome = rel !== "" && rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
|
|
63
|
+
return { path: file, shown: inHome ? `~/${rel.split(path.sep).join("/")}` : file };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const FILE_OF: Partial<Record<EffortSettingSource, string>> = {
|
|
67
|
+
projectSettings: ".claude/settings.json",
|
|
68
|
+
localSettings: ".claude/settings.local.json",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function place(source: string, userFile: string): Pick<EffortSetting<unknown>, "source" | "file"> {
|
|
72
|
+
const known = source as EffortSettingSource;
|
|
73
|
+
const file = source === "userSettings" ? userFile : FILE_OF[known];
|
|
74
|
+
return { source: known, ...(file && { file }) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `modelSettings` keys name a model the way its dated, `[1m]`, Bedrock and Vertex spellings all match. */
|
|
78
|
+
function modelKey(model: string): string {
|
|
79
|
+
return model.toLowerCase()
|
|
80
|
+
.replace(/\[[^\]]*\]$/, "")
|
|
81
|
+
.replace(/@.*$/, "")
|
|
82
|
+
.replace(/-v\d+(?::\d+)?$/, "")
|
|
83
|
+
.replace(/^(?:[a-z-]+\.)?anthropic\./, "")
|
|
84
|
+
.replace(/-\d{8}$/, "");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A source's own cap for the model: a matching `modelSettings` entry replaces its `maxEffortLevel`. */
|
|
88
|
+
function sourceCap(settings: Record<string, unknown>, model: string | undefined): unknown {
|
|
89
|
+
const perModel = settings.modelSettings;
|
|
90
|
+
if (model && perModel && typeof perModel === "object") {
|
|
91
|
+
const key = modelKey(model);
|
|
92
|
+
let lowest: EffortLevel | undefined;
|
|
93
|
+
for (const [name, entry] of Object.entries(perModel as Record<string, unknown>)) {
|
|
94
|
+
const level = (entry as { maxEffortLevel?: unknown } | null)?.maxEffortLevel;
|
|
95
|
+
if (modelKey(name) !== key || !isEffortLevel(level)) continue;
|
|
96
|
+
if (!lowest || effortRank(level) < effortRank(lowest)) lowest = level;
|
|
97
|
+
}
|
|
98
|
+
if (lowest) return lowest;
|
|
99
|
+
}
|
|
100
|
+
return settings.maxEffortLevel;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The overrides the settings and the environment hold: the `CLAUDE_CODE_EFFORT_LEVEL` that wins
|
|
105
|
+
* (the highest-priority settings file that sets it, else the environment, which every settings
|
|
106
|
+
* file outranks) and the lowest cap (across files the lowest wins, and "max" caps nothing).
|
|
107
|
+
*/
|
|
108
|
+
export function effortOverrides(p: {
|
|
109
|
+
sources: ReadonlyArray<CliSettingsSource | null | undefined>;
|
|
110
|
+
processEnv?: string;
|
|
111
|
+
model?: string;
|
|
112
|
+
userFile: string;
|
|
113
|
+
}): Pick<EffortStatus, "env" | "cap"> {
|
|
114
|
+
const out: Pick<EffortStatus, "env" | "cap"> = {};
|
|
115
|
+
const sources = p.sources.filter((s): s is CliSettingsSource => !!s?.settings && typeof s.settings === "object");
|
|
116
|
+
|
|
117
|
+
let env: EffortSetting<string> | undefined;
|
|
118
|
+
for (let i = sources.length - 1; i >= 0 && !env; i--) {
|
|
119
|
+
const value = (sources[i]!.settings.env as Record<string, unknown> | undefined)?.[EFFORT_ENV_VAR];
|
|
120
|
+
if (typeof value === "string") env = { value, ...place(sources[i]!.source, p.userFile) };
|
|
121
|
+
}
|
|
122
|
+
env ??= p.processEnv !== undefined ? { value: p.processEnv, source: "environment" } : undefined;
|
|
123
|
+
// A value the CLI ignores overrides nothing — but it still hides a lower one, as it does there.
|
|
124
|
+
if (env && effortEnvMeaning(env.value)) out.env = env;
|
|
125
|
+
|
|
126
|
+
for (const s of sources) {
|
|
127
|
+
const level = sourceCap(s.settings, p.model);
|
|
128
|
+
if (!isEffortLevel(level) || level === "max") continue;
|
|
129
|
+
if (!out.cap || effortRank(level) < effortRank(out.cap.value)) out.cap = { value: level, ...place(s.source, p.userFile) };
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function levels(values: unknown): EffortLevel[] {
|
|
135
|
+
return Array.isArray(values) ? values.filter(isEffortLevel) : [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The model's levels, from the `initialize` answer; undefined when it lists no such model. */
|
|
139
|
+
export function supportedLevels(models: CliModelInfo[] | undefined, model: string | undefined): EffortLevel[] | undefined {
|
|
140
|
+
if (!models || !model) return undefined;
|
|
141
|
+
const bare = (name: string) => name.toLowerCase().replace(/\[[^\]]*\]$/, "");
|
|
142
|
+
const hit = models.find((m) => [m.value, m.resolvedModel].some((name) => name && bare(name) === bare(model)));
|
|
143
|
+
return hit ? levels(hit.supportedEffortLevels) : undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The running CLI's answer, as a status: its overrides plus what it said it sends. */
|
|
147
|
+
export function effortStatusFromCli(p: {
|
|
148
|
+
answer: CliSettingsAnswer;
|
|
149
|
+
models?: CliModelInfo[];
|
|
150
|
+
requested: string | null | undefined;
|
|
151
|
+
processEnv?: string;
|
|
152
|
+
userFile: string;
|
|
153
|
+
}): EffortStatus {
|
|
154
|
+
const model = typeof p.answer.applied?.model === "string" ? p.answer.applied.model : undefined;
|
|
155
|
+
const applied = p.answer.applied?.effort;
|
|
156
|
+
const supported = supportedLevels(p.models, model);
|
|
157
|
+
return {
|
|
158
|
+
...effortOverrides({ sources: p.answer.sources ?? [], processEnv: p.processEnv, model, userFile: p.userFile }),
|
|
159
|
+
observed: {
|
|
160
|
+
requested: isEffortLevel(p.requested) ? p.requested : null,
|
|
161
|
+
applied: isEffortLevel(applied) ? applied : null,
|
|
162
|
+
...(model && { model }),
|
|
163
|
+
},
|
|
164
|
+
...(supported && { supported }),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The settings files a chat in `projectPath` reads, lowest priority first: those of the sources
|
|
170
|
+
* its CLI is started with, so the estimate never counts a file the chat does not load. A file
|
|
171
|
+
* that does not parse is skipped, as the CLI skips it.
|
|
172
|
+
*/
|
|
173
|
+
export function readSettingsFiles(
|
|
174
|
+
projectPath: string | undefined,
|
|
175
|
+
userPath: string,
|
|
176
|
+
sources: readonly ClaudeSettingSource[] = CLAUDE_SETTING_SOURCES,
|
|
177
|
+
): CliSettingsSource[] {
|
|
178
|
+
const fileOf: Record<ClaudeSettingSource, [EffortSettingSource, string] | null> = {
|
|
179
|
+
user: ["userSettings", userPath],
|
|
180
|
+
project: projectPath ? ["projectSettings", join(projectPath, ".claude", "settings.json")] : null,
|
|
181
|
+
local: projectPath ? ["localSettings", join(projectPath, ".claude", "settings.local.json")] : null,
|
|
182
|
+
};
|
|
183
|
+
const files = sources.map((source) => fileOf[source]).filter((f): f is [EffortSettingSource, string] => !!f);
|
|
184
|
+
const out: CliSettingsSource[] = [];
|
|
185
|
+
for (const [source, file] of files) {
|
|
186
|
+
let settings: unknown;
|
|
187
|
+
try { settings = JSON.parse(readFileSync(file, "utf8")); } catch { continue; }
|
|
188
|
+
if (settings && typeof settings === "object" && !Array.isArray(settings)) out.push({ source, settings: settings as Record<string, unknown> });
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The overrides a chat in `projectPath` will meet, from the files alone — for a session no CLI
|
|
195
|
+
* has answered for yet. Managed settings and organization limits are left to the CLI's answer.
|
|
196
|
+
*/
|
|
197
|
+
export function estimateEffortStatus(p: {
|
|
198
|
+
projectPath?: string;
|
|
199
|
+
model?: string;
|
|
200
|
+
env?: Record<string, string | undefined>;
|
|
201
|
+
home?: string;
|
|
202
|
+
}): EffortStatus {
|
|
203
|
+
const env = p.env ?? process.env;
|
|
204
|
+
const user = userSettingsFile(env, p.home);
|
|
205
|
+
return effortOverrides({
|
|
206
|
+
sources: readSettingsFiles(p.projectPath, user.path),
|
|
207
|
+
processEnv: env[EFFORT_ENV_VAR],
|
|
208
|
+
model: p.model,
|
|
209
|
+
userFile: user.shown,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
@@ -5,6 +5,7 @@ import { configService } from "../services/config.service.ts";
|
|
|
5
5
|
import { nextProbeDelayMs, type ProviderProbeStatus } from "./provider-probe.ts";
|
|
6
6
|
import { CODEX_DEFAULT_MODEL } from "../types/config.ts";
|
|
7
7
|
import { createLogger } from "../services/logger.ts";
|
|
8
|
+
import { registerMemoryGauge } from "../services/memory-diagnostics.ts";
|
|
8
9
|
|
|
9
10
|
const log = createLogger("registry");
|
|
10
11
|
|
|
@@ -53,7 +54,13 @@ class ProviderRegistry {
|
|
|
53
54
|
export const providerRegistry = new ProviderRegistry();
|
|
54
55
|
|
|
55
56
|
// SDK providers registered synchronously (no binary check needed)
|
|
56
|
-
|
|
57
|
+
const claudeProvider = new ClaudeAgentSdkProvider();
|
|
58
|
+
providerRegistry.register(claudeProvider);
|
|
59
|
+
// Sizes for the opt-in memory monitor (PPM_MEM_DIAG). Bracket access reaches the private
|
|
60
|
+
// maps without widening the class; registering costs nothing while the monitor is off.
|
|
61
|
+
registerMemoryGauge("claude.streamingSessions", () => claudeProvider["streamingSessions"].size);
|
|
62
|
+
registerMemoryGauge("claude.activeQueries", () => claudeProvider["activeQueries"].size);
|
|
63
|
+
registerMemoryGauge("claude.activeSessions", () => claudeProvider["activeSessions"].size);
|
|
57
64
|
providerRegistry.register(new MockProvider()); // testing only
|
|
58
65
|
|
|
59
66
|
/**
|
|
@@ -176,6 +183,8 @@ async function probeCodexProvider(): Promise<ProviderProbeStatus> {
|
|
|
176
183
|
const result = await codex.probe();
|
|
177
184
|
if (result.ok) {
|
|
178
185
|
providerRegistry.register(codex);
|
|
186
|
+
registerMemoryGauge("codex.sessions", () => codex["sessions"].size);
|
|
187
|
+
registerMemoryGauge("codex.live", () => codex["live"].size);
|
|
179
188
|
ensureCodexConfigEntry();
|
|
180
189
|
log.info("Codex provider registered (@openai/codex found)");
|
|
181
190
|
status = { id: "codex", registered: true, attempts, lastProbeAt };
|
package/src/server/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { cors } from "hono/cors";
|
|
3
2
|
import { writeFileSync } from "node:fs";
|
|
4
3
|
import { SERVER_PORT_FILE } from "../services/edge-target-resolver.ts";
|
|
5
4
|
import { configService } from "../services/config.service.ts";
|
|
@@ -7,6 +6,7 @@ import { VERSION } from "../version.ts";
|
|
|
7
6
|
import { authMiddleware } from "./middleware/auth.ts";
|
|
8
7
|
import { gzipJson } from "./middleware/gzip-json.ts";
|
|
9
8
|
import { accessLog } from "./middleware/access-log.ts";
|
|
9
|
+
import { corsHeaders } from "./middleware/cors-headers.ts";
|
|
10
10
|
import { createLogger } from "../services/logger.ts";
|
|
11
11
|
import { projectRoutes } from "./routes/projects.ts";
|
|
12
12
|
import { settingsRoutes } from "./routes/settings.ts";
|
|
@@ -167,14 +167,15 @@ app.onError((error, c) => {
|
|
|
167
167
|
return c.text("Internal Server Error", 500);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
// Design share links: the public reviewer surface. Ahead of
|
|
171
|
-
//
|
|
172
|
-
//
|
|
170
|
+
// Design share links: the public reviewer surface. Ahead of the CORS layer, of `/api` auth
|
|
171
|
+
// (a share code is never the PPM token) and of the static fallback (its catch-all keeps every
|
|
172
|
+
// `/s` path off the app shell).
|
|
173
173
|
import { designShareGuestRoutes } from "./routes/design-share-guest.ts";
|
|
174
174
|
app.route("/s", designShareGuestRoutes);
|
|
175
175
|
|
|
176
|
-
// CORS for dev
|
|
177
|
-
|
|
176
|
+
// CORS for dev. Not Hono's `cors()`: it writes headers before the handler, which makes Hono
|
|
177
|
+
// rebuild every response from its body and turns a `Bun.file` body into a leaking stream.
|
|
178
|
+
app.use("*", corsHeaders);
|
|
178
179
|
|
|
179
180
|
// Public endpoints (before auth)
|
|
180
181
|
// Per-boot id so a named-tunnel health probe can tell "my connector is dark"
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { MiddlewareHandler } from "hono";
|
|
15
15
|
import { createLogger, type LogLevel } from "../../services/logger.ts";
|
|
16
|
+
import { requestTracker } from "../../services/memory-diagnostics-requests.ts";
|
|
16
17
|
|
|
17
18
|
const log = createLogger("http");
|
|
18
19
|
|
|
@@ -119,6 +120,8 @@ export const accessLog: MiddlewareHandler = async (c, next) => {
|
|
|
119
120
|
const ms = Math.round(performance.now() - started);
|
|
120
121
|
const url = new URL(c.req.url);
|
|
121
122
|
const status = c.res.status;
|
|
123
|
+
// Before the level check: the memory monitor counts every request, logged or not.
|
|
124
|
+
requestTracker.record(c.req.method, url.pathname, c.res.headers.get("content-length"));
|
|
122
125
|
const level = accessLogLevel(c.req.method, url.pathname, status, ms);
|
|
123
126
|
if (!log.isEnabled(level)) return;
|
|
124
127
|
const line = `${c.req.method} ${describeRequestTarget(url)} ${status} ${ms}ms${ms >= SLOW_REQUEST_MS ? " (slow)" : ""}`;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from "hono";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The app-wide CORS headers, written onto the handler's own response AFTER it is made.
|
|
5
|
+
*
|
|
6
|
+
* Hono's `cors()` sets its headers on `c.res` before `next()`, which makes Hono build a
|
|
7
|
+
* placeholder response; when the handler then returns its own, the `c.res` setter rebuilds it
|
|
8
|
+
* as `new Response(res.body, res)` to merge the two. Reading `.body` turns a `Bun.file(...)`
|
|
9
|
+
* body into Bun's file stream — the one that keeps about the file's size in native memory per
|
|
10
|
+
* response and never returns it (see `services/file-chunks.ts`). Measured on Bun 1.3.10
|
|
11
|
+
* (Windows), a 23 MB file: 2.9 MB per response bare, 30.2 MB behind `cors()`, and the
|
|
12
|
+
* response lost its `Content-Length` (sent chunked). Every static asset, chat image and
|
|
13
|
+
* download went through that.
|
|
14
|
+
*
|
|
15
|
+
* Same answer as `cors()` with its defaults (`origin: "*"`), with one difference: a route that
|
|
16
|
+
* sets `Access-Control-Allow-Origin` itself keeps it (`cors()` overwrote it, which is why the
|
|
17
|
+
* design preview had to re-assert `null`).
|
|
18
|
+
*/
|
|
19
|
+
const ALLOW_METHODS = "GET,HEAD,PUT,POST,DELETE,PATCH";
|
|
20
|
+
|
|
21
|
+
export const corsHeaders: MiddlewareHandler = async (c, next) => {
|
|
22
|
+
if (c.req.method === "OPTIONS") {
|
|
23
|
+
// A preflight never reaches a handler, so answering it here rebuilds nothing.
|
|
24
|
+
const headers = new Headers({ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": ALLOW_METHODS });
|
|
25
|
+
const requested = c.req.header("Access-Control-Request-Headers");
|
|
26
|
+
if (requested) {
|
|
27
|
+
headers.set("Access-Control-Allow-Headers", requested.split(/\s*,\s*/).join(","));
|
|
28
|
+
headers.append("Vary", "Access-Control-Request-Headers");
|
|
29
|
+
}
|
|
30
|
+
return new Response(null, { status: 204, statusText: "No Content", headers });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await next();
|
|
34
|
+
if (c.res.headers.has("Access-Control-Allow-Origin")) return;
|
|
35
|
+
try {
|
|
36
|
+
c.res.headers.set("Access-Control-Allow-Origin", "*");
|
|
37
|
+
} catch {
|
|
38
|
+
// A response whose headers are immutable (one passed through from `fetch`) is rebuilt;
|
|
39
|
+
// its body is a stream already, so there is no file to turn into one.
|
|
40
|
+
const res = new Response(c.res.body, c.res);
|
|
41
|
+
res.headers.set("Access-Control-Allow-Origin", "*");
|
|
42
|
+
c.res = res;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
@@ -166,6 +166,23 @@ chatRoutes.get("/providers/:providerId/models", async (c) => {
|
|
|
166
166
|
}
|
|
167
167
|
});
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* GET /chat/providers/:providerId/effort-status?model= — what overrides the effort picked for a
|
|
171
|
+
* new chat in this project (`CLAUDE_CODE_EFFORT_LEVEL`, a `maxEffortLevel` cap), from the settings
|
|
172
|
+
* files; a session's own socket carries the CLI's answer once a turn has run. `status` is null
|
|
173
|
+
* for a provider nothing overrides.
|
|
174
|
+
*/
|
|
175
|
+
chatRoutes.get("/providers/:providerId/effort-status", (c) => {
|
|
176
|
+
const provider = providerRegistry.get(c.req.param("providerId"));
|
|
177
|
+
if (!provider) return c.json(err(`Provider "${c.req.param("providerId")}" not found`), 404);
|
|
178
|
+
try {
|
|
179
|
+
const status = provider.estimateEffortStatus?.(c.get("projectPath"), c.req.query("model") || undefined) ?? null;
|
|
180
|
+
return c.json(ok({ status }));
|
|
181
|
+
} catch (e) {
|
|
182
|
+
return c.json(err((e as Error).message), 500);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
169
186
|
/** GET /chat/sessions — list chat sessions filtered by project from context */
|
|
170
187
|
chatRoutes.get("/sessions", async (c) => {
|
|
171
188
|
try {
|
|
@@ -1277,7 +1294,10 @@ chatRoutes.get("/uploads/:filename", async (c) => {
|
|
|
1277
1294
|
if (c.req.header("if-none-match") === etag) {
|
|
1278
1295
|
return new Response(null, { status: 304, headers: { ETag: etag } });
|
|
1279
1296
|
}
|
|
1280
|
-
|
|
1297
|
+
// The file itself, not `file.stream()`: that stream keeps about the file's size in native
|
|
1298
|
+
// memory per response and never returns it (see `file-chunks.ts`), and chat images are
|
|
1299
|
+
// served every time a tab opens without them cached.
|
|
1300
|
+
return new Response(file, {
|
|
1281
1301
|
headers: {
|
|
1282
1302
|
"Content-Type": file.type || "application/octet-stream",
|
|
1283
1303
|
"Cache-Control": "public, max-age=31536000, immutable",
|
|
@@ -310,7 +310,7 @@ impExpJobRoutes.post("/jobs/:id/download", async (c) => {
|
|
|
310
310
|
const ticket = issueExportTicket({
|
|
311
311
|
fileName: file.name,
|
|
312
312
|
contentType: file.format === "zip" ? "application/zip" : CONTENT_TYPES[file.format],
|
|
313
|
-
open: () => Bun.file(file.path)
|
|
313
|
+
open: () => Bun.file(file.path),
|
|
314
314
|
abandon: () => {},
|
|
315
315
|
});
|
|
316
316
|
return c.json(ok<GridExportTicket>({ ticket, fileName: file.name }));
|