@hienlh/ppm 0.23.22 → 0.23.24
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 +17 -0
- package/CLAUDE.md +3 -1
- package/README.md +1 -1
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +2 -1
- package/dist/web/assets/{accounts-settings-section-CiNmIj02.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-DRR36PDA.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-DMhy16BM.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-BG8boPw7.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-De5dYWlC.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-lHDxD93G.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-DO-ZyoMj.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-CH-0CHYY.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-CdGtvekz.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-DMqWnFIo.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-BCv8ONNS.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-CGMbIHak.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-CGMbIHak.js.gz → codex-reset-credit-button-tIwfnf2G.js.gz} +0 -0
- package/dist/web/assets/{conflict-editor-3WmzqBlW.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-BoWucvAe.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-BCgGsy7F.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-o6QMHBbA.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-seo6SdSk.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-B-f_b2LM.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-CVT_TQP7.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-y9uBp_8u.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-Dn_x3Inm.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-BwNXSajD.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-XA_TnPLN.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-DukR7zM9.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-BjTZ44S3.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-C4WD1BPQ.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-CV2ujflc.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-hS5ixQ8o.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-BnKYY0AA.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-JiWfR5xv.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-DgvcY__Q.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-r1MKxDjz.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-Fa0yA-Ep.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-B2Q8yqSX.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-B8Ht4l32.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-BtAn_krv.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--ibKZfBs.js → jira-watcher-section-B3OaMlim.js} +1 -1
- package/dist/web/assets/{keybindings-store-Cwxl4tHv.js → keybindings-store-C_aRhBu4.js} +1 -1
- package/dist/web/assets/{keyboard-shortcuts-section-aoiLpWwf.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-BkGAezo6.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-DYgPtmjm.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-BbDosK63.js → logs-tab-kqNARbz0.js} +1 -1
- package/dist/web/assets/{logs-window-content-CEXAyv-f.js → logs-window-content-UUCJJCqG.js} +1 -1
- package/dist/web/assets/{lsp-client-CIcphNmU.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-BNzQeoAO.js → markdown-renderer-ClGxDien.js} +1 -1
- package/dist/web/assets/{markdown-renderer-dk1w3goH.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-D4lPZQZc.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-CIq7xspW.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-Rv36noYX.js → notification-store-CPVq5ec5.js} +1 -1
- package/dist/web/assets/{notifications-settings-section-CoWMtXCC.js → notifications-settings-section-xrYII-pL.js} +1 -1
- 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-Ct0UwA6T.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-D-HT-0PE.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-CXN0s4Pq.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-e6WWzP8n.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-C7C67t8F.js → query-tab-BWtJPuxw.js} +1 -1
- package/dist/web/assets/query-tab-BWtJPuxw.js.br +0 -0
- package/dist/web/assets/query-tab-BWtJPuxw.js.gz +0 -0
- package/dist/web/assets/{remote-access-settings-section-gGoATCF9.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-D5KjvMZx.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-CY_DsGXZ.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-DdpI-RF8.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-DdpI-RF8.js.gz → remote-desktop-mobile-view-VW7HdPUX.js.gz} +0 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-WIyCCAv5.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-BMORZLZt.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-pIgWe_AL.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-oTNcTYlI.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-CnPLGTaL.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-CREqHG_i.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-nFVJYYJH.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-DNzH0wOQ.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-D02_4sDY.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-DWopMfmq.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-Cg0v5kO7.js → settings-tab-PRw6OpEb.js} +1 -1
- package/dist/web/assets/{settings-window-content-CqhwxJJn.js → settings-window-content-WITQsGfW.js} +1 -1
- package/dist/web/assets/{slash-command-picker-Bcizxvbh.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-b1Z2zVBm.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-DhPv28tq.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-sGUpqs8D.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-Bo2OmDOc.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-C475Et9u.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-Cm52yoW8.js → system-monitor-tab-B_miogQI.js} +1 -1
- package/dist/web/assets/{system-monitor-window-content-YYsGHxZF.js → system-monitor-window-content-8IogR6tR.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BKI6QoDa.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-D-nKWMJR.js → table-tab-E3YMueu_.js} +1 -1
- package/dist/web/assets/table-tab-E3YMueu_.js.br +0 -0
- package/dist/web/assets/{table-tab-D-nKWMJR.js.gz → table-tab-E3YMueu_.js.gz} +0 -0
- package/dist/web/assets/{telegram-bot-token-field-BeNS2X2k.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-BbP753Ab.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-Dh8Ekn2W.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-DGZRIULE.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-BxwM-Wkj.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-BzVaaCCt.js.gz +0 -0
- package/dist/web/assets/{use-lsp-BqvwRKmM.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-DU0EpDHH.js → use-monaco-theme-DljSc3Xo.js} +1 -1
- package/dist/web/assets/{use-process-kill-CVN_c4KF.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-C0wBmR6-.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-K0OUXbbY.js → use-websocket-Djo_ruX_.js} +1 -1
- package/dist/web/assets/{web-preview-tab-DOgLv8Gt.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/lessons-learned.md +14 -4
- package/package.json +1 -1
- package/src/providers/claude-agent-sdk-query-options.ts +13 -2
- package/src/providers/claude-agent-sdk.ts +112 -30
- package/src/providers/claude-approval-queue.ts +75 -0
- package/src/providers/claude-effort-status.ts +211 -0
- package/src/server/routes/chat.ts +17 -0
- package/src/server/routes/settings.ts +16 -0
- package/src/server/ws/chat-effort-status.ts +73 -0
- package/src/server/ws/chat.ts +30 -3
- 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/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/dist/web/assets/accounts-settings-section-CiNmIj02.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-CiNmIj02.js.gz +0 -0
- package/dist/web/assets/agent-session-sheet-DRR36PDA.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-DRR36PDA.js.gz +0 -0
- package/dist/web/assets/agent-session-window-content-DMhy16BM.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-DMhy16BM.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-BG8boPw7.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-BG8boPw7.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-DMGM1hYx.js +0 -1
- package/dist/web/assets/ai-settings-section-DMGM1hYx.js.br +0 -0
- package/dist/web/assets/ai-settings-section-DMGM1hYx.js.gz +0 -0
- package/dist/web/assets/android-tab-lHDxD93G.js.br +0 -0
- package/dist/web/assets/android-tab-lHDxD93G.js.gz +0 -0
- package/dist/web/assets/appearance-settings-section-DO-ZyoMj.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-DO-ZyoMj.js.gz +0 -0
- package/dist/web/assets/apps-panel-CH-0CHYY.js.br +0 -0
- package/dist/web/assets/apps-panel-CH-0CHYY.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-CdGtvekz.js.br +0 -0
- package/dist/web/assets/branch-review-tab-CdGtvekz.js.gz +0 -0
- package/dist/web/assets/chat-tab-DMqWnFIo.js.br +0 -0
- package/dist/web/assets/chat-tab-DMqWnFIo.js.gz +0 -0
- package/dist/web/assets/chat-tab-DzWpwPFb.js +0 -19
- package/dist/web/assets/chat-tab-DzWpwPFb.js.br +0 -0
- package/dist/web/assets/chat-tab-DzWpwPFb.js.gz +0 -0
- package/dist/web/assets/code-editor-BCv8ONNS.js.br +0 -0
- package/dist/web/assets/code-editor-BCv8ONNS.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-CGMbIHak.js.br +0 -0
- package/dist/web/assets/conflict-editor-3WmzqBlW.js.br +0 -0
- package/dist/web/assets/conflict-editor-3WmzqBlW.js.gz +0 -0
- package/dist/web/assets/connection-form-tab-BoWucvAe.js.br +0 -0
- package/dist/web/assets/connection-form-tab-BoWucvAe.js.gz +0 -0
- package/dist/web/assets/csv-preview-BCgGsy7F.js.br +0 -0
- package/dist/web/assets/csv-preview-BCgGsy7F.js.gz +0 -0
- package/dist/web/assets/db-tab-parts-o6QMHBbA.js.br +0 -0
- package/dist/web/assets/db-tab-parts-o6QMHBbA.js.gz +0 -0
- package/dist/web/assets/design-settings-section-seo6SdSk.js.br +0 -0
- package/dist/web/assets/design-settings-section-seo6SdSk.js.gz +0 -0
- package/dist/web/assets/design-tab-B-f_b2LM.js.br +0 -0
- package/dist/web/assets/design-tab-B-f_b2LM.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-CVT_TQP7.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-CVT_TQP7.js.gz +0 -0
- package/dist/web/assets/diff-viewer-y9uBp_8u.js.br +0 -0
- package/dist/web/assets/diff-viewer-y9uBp_8u.js.gz +0 -0
- package/dist/web/assets/docx-preview-Dn_x3Inm.js.br +0 -0
- package/dist/web/assets/docx-preview-Dn_x3Inm.js.gz +0 -0
- package/dist/web/assets/editor-dialog-BwNXSajD.js.br +0 -0
- package/dist/web/assets/editor-dialog-BwNXSajD.js.gz +0 -0
- package/dist/web/assets/editor-language-service-XA_TnPLN.js.br +0 -0
- package/dist/web/assets/editor-language-service-XA_TnPLN.js.gz +0 -0
- package/dist/web/assets/explorer-body-DukR7zM9.js.br +0 -0
- package/dist/web/assets/explorer-body-DukR7zM9.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-BjTZ44S3.js.br +0 -0
- package/dist/web/assets/explorer-window-content-BjTZ44S3.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-C4WD1BPQ.js.br +0 -0
- package/dist/web/assets/extension-manager-section-C4WD1BPQ.js.gz +0 -0
- package/dist/web/assets/extension-webview-CV2ujflc.js.br +0 -0
- package/dist/web/assets/extension-webview-CV2ujflc.js.gz +0 -0
- package/dist/web/assets/files-settings-section-hS5ixQ8o.js.br +0 -0
- package/dist/web/assets/files-settings-section-hS5ixQ8o.js.gz +0 -0
- package/dist/web/assets/format-date-CSHaU2wN.js +0 -1
- package/dist/web/assets/general-settings-section-BnKYY0AA.js.br +0 -0
- package/dist/web/assets/general-settings-section-BnKYY0AA.js.gz +0 -0
- package/dist/web/assets/git-log-panel-JiWfR5xv.js.br +0 -0
- package/dist/web/assets/git-log-panel-JiWfR5xv.js.gz +0 -0
- package/dist/web/assets/git-review-tab-DgvcY__Q.js.br +0 -0
- package/dist/web/assets/git-review-tab-DgvcY__Q.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-r1MKxDjz.js.br +0 -0
- package/dist/web/assets/glide-data-grid-r1MKxDjz.js.gz +0 -0
- package/dist/web/assets/grid-save-dialog-Fa0yA-Ep.js.br +0 -0
- package/dist/web/assets/grid-save-dialog-Fa0yA-Ep.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-B2Q8yqSX.js.br +0 -0
- package/dist/web/assets/group-chat-tab-B2Q8yqSX.js.gz +0 -0
- package/dist/web/assets/impexp-tab-B8Ht4l32.js.br +0 -0
- package/dist/web/assets/impexp-tab-B8Ht4l32.js.gz +0 -0
- package/dist/web/assets/index-BtAn_krv.js.br +0 -0
- package/dist/web/assets/index-BtAn_krv.js.gz +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-aoiLpWwf.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-aoiLpWwf.js.gz +0 -0
- package/dist/web/assets/language-servers-section-BkGAezo6.js.br +0 -0
- package/dist/web/assets/language-servers-section-BkGAezo6.js.gz +0 -0
- package/dist/web/assets/logs-app-DYgPtmjm.js.br +0 -0
- package/dist/web/assets/logs-app-DYgPtmjm.js.gz +0 -0
- package/dist/web/assets/lsp-client-CIcphNmU.js.br +0 -0
- package/dist/web/assets/lsp-client-CIcphNmU.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-dk1w3goH.js.br +0 -0
- package/dist/web/assets/markdown-renderer-dk1w3goH.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-D4lPZQZc.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-D4lPZQZc.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-CIq7xspW.js.br +0 -0
- package/dist/web/assets/new-design-dialog-CIq7xspW.js.gz +0 -0
- package/dist/web/assets/notifications-settings-section-CoWMtXCC.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-CoWMtXCC.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-Ct0UwA6T.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-Ct0UwA6T.js.gz +0 -0
- package/dist/web/assets/problems-panel-D-HT-0PE.js.br +0 -0
- package/dist/web/assets/problems-panel-D-HT-0PE.js.gz +0 -0
- package/dist/web/assets/process-table-CXN0s4Pq.js.br +0 -0
- package/dist/web/assets/process-table-CXN0s4Pq.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-e6WWzP8n.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-e6WWzP8n.js.gz +0 -0
- package/dist/web/assets/query-tab-C7C67t8F.js.br +0 -0
- package/dist/web/assets/query-tab-C7C67t8F.js.gz +0 -0
- package/dist/web/assets/remote-access-settings-section-gGoATCF9.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-gGoATCF9.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-D5KjvMZx.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-D5KjvMZx.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-CY_DsGXZ.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-CY_DsGXZ.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DdpI-RF8.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-WIyCCAv5.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-WIyCCAv5.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-BMORZLZt.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-BMORZLZt.js.gz +0 -0
- package/dist/web/assets/rename-object-dialog-pIgWe_AL.js.br +0 -0
- package/dist/web/assets/rename-object-dialog-pIgWe_AL.js.gz +0 -0
- package/dist/web/assets/review-code-oTNcTYlI.js.br +0 -0
- package/dist/web/assets/review-code-oTNcTYlI.js.gz +0 -0
- package/dist/web/assets/save-as-dialog-CnPLGTaL.js.br +0 -0
- package/dist/web/assets/save-as-dialog-CnPLGTaL.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-CREqHG_i.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-CREqHG_i.js.gz +0 -0
- package/dist/web/assets/session-review-tab-nFVJYYJH.js.br +0 -0
- package/dist/web/assets/session-review-tab-nFVJYYJH.js.gz +0 -0
- package/dist/web/assets/settings-body-DNzH0wOQ.js.br +0 -0
- package/dist/web/assets/settings-body-DNzH0wOQ.js.gz +0 -0
- package/dist/web/assets/settings-rows-D02_4sDY.js.br +0 -0
- package/dist/web/assets/settings-rows-D02_4sDY.js.gz +0 -0
- package/dist/web/assets/settings-section-content-BYW_9mHv.js +0 -1
- package/dist/web/assets/settings-section-content-DWopMfmq.js.br +0 -0
- package/dist/web/assets/settings-section-content-DWopMfmq.js.gz +0 -0
- package/dist/web/assets/slash-command-picker-Bcizxvbh.js.br +0 -0
- package/dist/web/assets/slash-command-picker-Bcizxvbh.js.gz +0 -0
- package/dist/web/assets/sql-object-tab-b1Z2zVBm.js.br +0 -0
- package/dist/web/assets/sql-object-tab-b1Z2zVBm.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-DhPv28tq.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-DhPv28tq.js.gz +0 -0
- package/dist/web/assets/structure-save-dialog-sGUpqs8D.js.br +0 -0
- package/dist/web/assets/structure-save-dialog-sGUpqs8D.js.gz +0 -0
- package/dist/web/assets/structure-tab-Bo2OmDOc.js.br +0 -0
- package/dist/web/assets/structure-tab-Bo2OmDOc.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-C475Et9u.js.br +0 -0
- package/dist/web/assets/system-monitor-body-C475Et9u.js.gz +0 -0
- package/dist/web/assets/tab-host-window-content-BKI6QoDa.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-BKI6QoDa.js.gz +0 -0
- package/dist/web/assets/table-tab-D-nKWMJR.js.br +0 -0
- package/dist/web/assets/telegram-bot-token-field-BeNS2X2k.js.br +0 -0
- package/dist/web/assets/telegram-bot-token-field-BeNS2X2k.js.gz +0 -0
- package/dist/web/assets/terminal-tab-BbP753Ab.js.br +0 -0
- package/dist/web/assets/terminal-tab-BbP753Ab.js.gz +0 -0
- package/dist/web/assets/tool-cards-Dh8Ekn2W.js.br +0 -0
- package/dist/web/assets/tool-cards-Dh8Ekn2W.js.gz +0 -0
- package/dist/web/assets/tools-settings-section-DGZRIULE.js.br +0 -0
- package/dist/web/assets/tools-settings-section-DGZRIULE.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-BxwM-Wkj.js.br +0 -0
- package/dist/web/assets/use-inline-blame-BxwM-Wkj.js.gz +0 -0
- package/dist/web/assets/use-lsp-BqvwRKmM.js.br +0 -0
- package/dist/web/assets/use-lsp-BqvwRKmM.js.gz +0 -0
- package/dist/web/assets/use-process-kill-CVN_c4KF.js.br +0 -0
- package/dist/web/assets/use-process-kill-CVN_c4KF.js.gz +0 -0
- package/dist/web/assets/use-sql-schema-info-C0wBmR6-.js.br +0 -0
- package/dist/web/assets/use-sql-schema-info-C0wBmR6-.js.gz +0 -0
- package/dist/web/assets/web-preview-tab-DOgLv8Gt.js.br +0 -0
- package/dist/web/assets/web-preview-tab-DOgLv8Gt.js.gz +0 -0
|
@@ -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.
|
|
@@ -221,6 +227,14 @@ interface StreamingSession {
|
|
|
221
227
|
lastUserContent: string;
|
|
222
228
|
/** Latest user message images — updated on follow-ups for accurate retry */
|
|
223
229
|
lastUserImages?: Array<{ data: string; mediaType: string }>;
|
|
230
|
+
/**
|
|
231
|
+
* A user message has been pushed whose turn has not finished.
|
|
232
|
+
*
|
|
233
|
+
* A crash retry may only re-send a prompt while this is true. Without it the retry re-sent
|
|
234
|
+
* whatever content it could find into a session that was simply idle, and an instruction the
|
|
235
|
+
* user gave hours earlier ran a second time as a real turn.
|
|
236
|
+
*/
|
|
237
|
+
awaitingReply?: boolean;
|
|
224
238
|
/** The sign-in this subprocess was started with — see `SubprocessCredential`. */
|
|
225
239
|
credential: SubprocessCredential;
|
|
226
240
|
}
|
|
@@ -283,6 +297,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
283
297
|
* and this is the only place that knows what made it do so.
|
|
284
298
|
*/
|
|
285
299
|
private teardownReasons = new Map<string, string>();
|
|
300
|
+
/** The `--effort` each session's CLI was started with, to set beside what it reports sending. */
|
|
301
|
+
private startedEffort = new Map<string, string | null>();
|
|
286
302
|
/** The API's last rate-limit status per account and limit, so only a change is logged. */
|
|
287
303
|
private rateLimitStatus = new Map<string, string>();
|
|
288
304
|
/** CLIs started for chats that do not exist yet — see `prewarm`. */
|
|
@@ -738,6 +754,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
738
754
|
}
|
|
739
755
|
}
|
|
740
756
|
this.forkSources.delete(sessionId);
|
|
757
|
+
this.startedEffort.delete(sessionId);
|
|
741
758
|
|
|
742
759
|
// Best-effort: delete JSONL from ~/.claude/projects/
|
|
743
760
|
try {
|
|
@@ -829,6 +846,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
829
846
|
// Track latest message for retry paths (fixes stale firstMsg bug)
|
|
830
847
|
ss.lastUserContent = modelInput;
|
|
831
848
|
ss.lastUserImages = opts?.images;
|
|
849
|
+
ss.awaitingReply = true;
|
|
832
850
|
log.debug(`pushMessage: session=${sessionId} priority=${opts?.priority ?? 'next'}`);
|
|
833
851
|
}
|
|
834
852
|
|
|
@@ -933,7 +951,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
933
951
|
...(p.forkSession && { forkSession: true }),
|
|
934
952
|
cwd: p.cwd,
|
|
935
953
|
systemPrompt: p.systemPrompt,
|
|
936
|
-
settingSources: [
|
|
954
|
+
settingSources: [...CLAUDE_SETTING_SOURCES],
|
|
937
955
|
env: p.env,
|
|
938
956
|
settings: { permissions: { allow: [], deny: [] } },
|
|
939
957
|
allowedTools: p.allowedTools,
|
|
@@ -1177,12 +1195,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1177
1195
|
const dbToolsMcp = opts?.designSession ? undefined : opts?.dbToolsMcp;
|
|
1178
1196
|
const allowedTools = allowedToolsFor({ isBypass, agentTeams: providerConfig.agent_teams, designPolicy, designCheckTool });
|
|
1179
1197
|
|
|
1180
|
-
/**
|
|
1181
|
-
|
|
1182
|
-
* PreToolUse hook pushes events here; the main loop yields them.
|
|
1183
|
-
*/
|
|
1184
|
-
const approvalEvents: ChatEvent[] = [];
|
|
1185
|
-
let approvalNotify: (() => void) | undefined;
|
|
1198
|
+
/** Approval requests from canUseTool and the PreToolUse hook, handed out by the main loop. */
|
|
1199
|
+
const approvals = new ApprovalQueue<ChatEvent>();
|
|
1186
1200
|
|
|
1187
1201
|
/**
|
|
1188
1202
|
* Helper: send approval request to FE and wait for response.
|
|
@@ -1193,8 +1207,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1193
1207
|
const promise = new Promise<{ approved: boolean; data?: unknown }>((resolve) => {
|
|
1194
1208
|
this.pendingApprovals.set(requestId, { resolve, sessionId });
|
|
1195
1209
|
});
|
|
1196
|
-
|
|
1197
|
-
approvalNotify?.();
|
|
1210
|
+
approvals.push({ type: "approval_request", requestId, tool: toolName, input });
|
|
1198
1211
|
return promise;
|
|
1199
1212
|
};
|
|
1200
1213
|
|
|
@@ -1460,12 +1473,32 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1460
1473
|
providerConfig,
|
|
1461
1474
|
stderr: stderrCallback,
|
|
1462
1475
|
});
|
|
1476
|
+
// Every retry below restarts the CLI with these same options.
|
|
1477
|
+
this.startedEffort.set(sessionId, (queryOptions.effort as string | undefined) ?? null);
|
|
1463
1478
|
|
|
1464
1479
|
// Crash retry: if subprocess exits with non-zero code before producing events,
|
|
1465
1480
|
// clean up and retry once with a fresh query before surfacing the error.
|
|
1466
1481
|
const MAX_CRASH_RETRIES = 1;
|
|
1467
1482
|
let crashRetryCount = 0;
|
|
1468
1483
|
|
|
1484
|
+
/**
|
|
1485
|
+
* What the crashed subprocess was doing, read off its session entry before that entry is
|
|
1486
|
+
* deleted. Undefined until a crash. `awaiting` false means no turn was in flight, and a
|
|
1487
|
+
* retry must then push nothing at all: the session is only being rebuilt so the next real
|
|
1488
|
+
* message has somewhere to go.
|
|
1489
|
+
*/
|
|
1490
|
+
let crashed: { content: string; images?: Array<{ data: string; mediaType: string }>; awaiting: boolean } | undefined;
|
|
1491
|
+
|
|
1492
|
+
// Once the turn has produced output (top-level assistant text/tool_use), the user
|
|
1493
|
+
// message + any tool_results are persisted to JSONL. Mid-turn retries must resume and
|
|
1494
|
+
// continue — NOT re-push the original message, which restarts the turn and re-displays
|
|
1495
|
+
// an already-answered AskUserQuestion.
|
|
1496
|
+
//
|
|
1497
|
+
// Declared outside `crashRetryLoop` on purpose: inside it, a crash re-entered the loop
|
|
1498
|
+
// body and cleared the very flag that keeps the retry from re-sending the prompt. It is
|
|
1499
|
+
// reset at the end of each turn instead.
|
|
1500
|
+
let turnProgressed = false;
|
|
1501
|
+
|
|
1469
1502
|
crashRetryLoop: for (;;) {
|
|
1470
1503
|
try {
|
|
1471
1504
|
// Streaming input: create message channel and persistent query.
|
|
@@ -1479,11 +1512,6 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1479
1512
|
session_id: sessionId,
|
|
1480
1513
|
};
|
|
1481
1514
|
|
|
1482
|
-
// Once the turn has produced output (top-level assistant text/tool_use), the user
|
|
1483
|
-
// message + any tool_results are persisted to JSONL. Mid-turn retries must resume and
|
|
1484
|
-
// continue — NOT re-push the original message, which restarts the turn and re-displays
|
|
1485
|
-
// an already-answered AskUserQuestion. Persists across retryLoop iterations.
|
|
1486
|
-
let turnProgressed = false;
|
|
1487
1515
|
|
|
1488
1516
|
// Build a retry message for token-refresh / transient-error retries.
|
|
1489
1517
|
// Pre-turn (nothing persisted yet): re-push the LATEST user content so the SDK has a
|
|
@@ -1494,8 +1522,10 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1494
1522
|
// Also returns the raw content/images for re-populating the new streaming session.
|
|
1495
1523
|
const buildRetryMsg = () => {
|
|
1496
1524
|
const ss = this.streamingSessions.get(sessionId);
|
|
1497
|
-
|
|
1498
|
-
|
|
1525
|
+
// `crashed` is the only source once the entry is gone: `?? modelInput` would otherwise
|
|
1526
|
+
// reach past every follow-up to the message that opened the stream, hours earlier.
|
|
1527
|
+
const content = ss?.lastUserContent ?? crashed?.content ?? modelInput;
|
|
1528
|
+
const images = ss?.lastUserImages ?? crashed?.images;
|
|
1499
1529
|
const retryContent = turnProgressed ? "Continue from where you left off." : content;
|
|
1500
1530
|
return {
|
|
1501
1531
|
msg: {
|
|
@@ -1516,9 +1546,14 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1516
1546
|
: undefined;
|
|
1517
1547
|
const channel = spare ? undefined : createMessageChannel();
|
|
1518
1548
|
const initialCtrl = spare?.controller ?? channel!.controller;
|
|
1519
|
-
// On crash retry, use buildRetryMsg to get the latest user message (not the stale firstMsg)
|
|
1549
|
+
// On crash retry, use buildRetryMsg to get the latest user message (not the stale firstMsg).
|
|
1550
|
+
// A crash with no turn in flight pushes NOTHING: the subprocess is replaced so the next real
|
|
1551
|
+
// message has somewhere to go, and re-sending a finished instruction runs it a second time.
|
|
1520
1552
|
const initRetry = crashRetryCount > 0 ? buildRetryMsg() : null;
|
|
1521
|
-
|
|
1553
|
+
const resendOnCrash = crashRetryCount > 0 && crashed?.awaiting === true;
|
|
1554
|
+
if (crashRetryCount === 0) initialCtrl.push(firstMsg);
|
|
1555
|
+
else if (resendOnCrash && initRetry) initialCtrl.push(initRetry.msg);
|
|
1556
|
+
else log.info(`session=${sessionId} crash retry rebuilt the subprocess with no turn to resume — nothing re-sent`);
|
|
1522
1557
|
const initContent = initRetry?.lastUserContent ?? modelInput;
|
|
1523
1558
|
const initImages = initRetry?.lastUserImages ?? opts?.images;
|
|
1524
1559
|
|
|
@@ -1533,7 +1568,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1533
1568
|
// The query's one start line: a warm CLI and the MCP servers belong to it.
|
|
1534
1569
|
log.info(`query: session=${sessionId} isFirst=${isFirstMessage} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform} accountMode=${!!account} permissionMode=${permissionMode} isBypass=${isBypass}`
|
|
1535
1570
|
+ ` cli=${spare ? `warm ageMs=${spare.ageMs}` : "cold"}${crashRetryCount > 0 ? ` crashRetry=${crashRetryCount}` : ""} mcp=${Object.keys(mcpServers).join(",") || "none"}`);
|
|
1536
|
-
this.streamingSessions.set(sessionId, { meta, query: initialQuery, controller: initialCtrl, lastUserContent: initContent, lastUserImages: initImages, credential: queryCredential });
|
|
1571
|
+
this.streamingSessions.set(sessionId, { meta, query: initialQuery, controller: initialCtrl, lastUserContent: initContent, lastUserImages: initImages, awaitingReply: crashRetryCount === 0 || resendOnCrash, credential: queryCredential });
|
|
1537
1572
|
this.activeQueries.set(sessionId, initialQuery);
|
|
1538
1573
|
let eventSource: AsyncIterable<any> = initialQuery;
|
|
1539
1574
|
log.debug(`session=${sessionId} query() created, waiting for first SDK event...`);
|
|
@@ -1610,7 +1645,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1610
1645
|
assistantContent = "";
|
|
1611
1646
|
pendingToolCount = 0;
|
|
1612
1647
|
let sdkEventCount = 0;
|
|
1613
|
-
for await (const msg of eventSource) {
|
|
1648
|
+
for await (const msg of approvals.alongside(eventSource)) {
|
|
1649
|
+
// An approval goes out now: the CLI sends nothing more until it is answered.
|
|
1650
|
+
if (msg === APPROVAL_WAKE) {
|
|
1651
|
+
yield* approvals.drain();
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1614
1654
|
sdkEventCount++;
|
|
1615
1655
|
hadAnyEvents = true;
|
|
1616
1656
|
if (sdkEventCount === 1) {
|
|
@@ -1640,9 +1680,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
1640
1680
|
const parentId = (msg as any).parent_tool_use_id as string | undefined;
|
|
1641
1681
|
|
|
1642
1682
|
// Yield any queued approval events
|
|
1643
|
-
|
|
1644
|
-
yield approvalEvents.shift()!;
|
|
1645
|
-
}
|
|
1683
|
+
yield* approvals.drain();
|
|
1646
1684
|
|
|
1647
1685
|
// Log system events for debugging SDK lifecycle: the subtype only, never the message.
|
|
1648
1686
|
// thinking_tokens and status=requesting arrive at token rate, so not even at DEBUG.
|
|
@@ -2395,6 +2433,12 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
2395
2433
|
}
|
|
2396
2434
|
}
|
|
2397
2435
|
|
|
2436
|
+
// The turn is over: a crash from here on has nothing to resume, and must not re-send
|
|
2437
|
+
// this instruction. `turnProgressed` goes with it, so the next turn starts clean.
|
|
2438
|
+
const finished = this.streamingSessions.get(sessionId);
|
|
2439
|
+
if (finished) finished.awaitingReply = false;
|
|
2440
|
+
turnProgressed = false;
|
|
2441
|
+
|
|
2398
2442
|
// Streaming input: yield done for this turn, then continue for next turn
|
|
2399
2443
|
yieldedDone = true;
|
|
2400
2444
|
yield {
|
|
@@ -2430,9 +2474,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
2430
2474
|
}
|
|
2431
2475
|
|
|
2432
2476
|
// Yield remaining approval events
|
|
2433
|
-
|
|
2434
|
-
yield approvalEvents.shift()!;
|
|
2435
|
-
}
|
|
2477
|
+
yield* approvals.drain();
|
|
2436
2478
|
|
|
2437
2479
|
if (!hadAnyEvents) {
|
|
2438
2480
|
yield { type: "error", message: "Claude did not respond. Check that 'claude' CLI works in your terminal." };
|
|
@@ -2444,10 +2486,17 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
2444
2486
|
const crashMsg = (crashErr as Error).message ?? String(crashErr);
|
|
2445
2487
|
const stderrInfo = stderrBuffer.trim() ? ` stderr: ${stderrBuffer.trim().slice(-500)}` : "";
|
|
2446
2488
|
|
|
2447
|
-
// Clean up crashed subprocess before retry or error
|
|
2489
|
+
// Clean up crashed subprocess before retry or error. Read what the turn was doing FIRST:
|
|
2490
|
+
// the retry below needs it, and deleting the entry is what used to send it back to the
|
|
2491
|
+
// message that opened the stream.
|
|
2448
2492
|
this.activeQueries.delete(sessionId);
|
|
2449
2493
|
const ss = this.streamingSessions.get(sessionId);
|
|
2450
|
-
if (ss) {
|
|
2494
|
+
if (ss) {
|
|
2495
|
+
crashed = { content: ss.lastUserContent, images: ss.lastUserImages, awaiting: ss.awaitingReply === true };
|
|
2496
|
+
ss.controller.done(); ss.query.close(); this.streamingSessions.delete(sessionId);
|
|
2497
|
+
} else {
|
|
2498
|
+
crashed = { content: modelInput, images: opts?.images, awaiting: false };
|
|
2499
|
+
}
|
|
2451
2500
|
log.debug(`session=${sessionId} streaming session ended`);
|
|
2452
2501
|
|
|
2453
2502
|
// Each case below logs itself: a cancel or a closed socket is not a failure.
|
|
@@ -2533,7 +2582,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
2533
2582
|
...(needsNodeInterpreter(process.platform, cliExecutablePath) && { executable: "node" }),
|
|
2534
2583
|
...(cliExecutablePath && { pathToClaudeCodeExecutable: cliExecutablePath }),
|
|
2535
2584
|
cwd,
|
|
2536
|
-
settingSources: [
|
|
2585
|
+
settingSources: [...CLAUDE_SETTING_SOURCES],
|
|
2537
2586
|
// No account: MCP OAuth tokens belong to the CLI's own credential store, the same
|
|
2538
2587
|
// one an interactive `claude` writes, and no model call is ever made here.
|
|
2539
2588
|
env: this.buildQueryEnv(cwd, null),
|
|
@@ -2578,6 +2627,39 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
2578
2627
|
}
|
|
2579
2628
|
}
|
|
2580
2629
|
|
|
2630
|
+
/**
|
|
2631
|
+
* The effort this session's CLI sends and what overrides the pick, asked of the CLI itself:
|
|
2632
|
+
* `CLAUDE_CODE_EFFORT_LEVEL`, a settings cap, an organization limit and the model's own levels
|
|
2633
|
+
* all count there, while the `--effort` PPM passed is only one input to it. Null when the
|
|
2634
|
+
* session has no live subprocess or the CLI does not answer.
|
|
2635
|
+
*/
|
|
2636
|
+
async readEffortStatus(sessionId: string): Promise<EffortStatus | null> {
|
|
2637
|
+
const ss = this.streamingSessions.get(sessionId);
|
|
2638
|
+
if (!ss) return null;
|
|
2639
|
+
const cli = ss.query as unknown as EffortSettingsQuery;
|
|
2640
|
+
try {
|
|
2641
|
+
const [answer, init] = await Promise.all([
|
|
2642
|
+
withTimeout(cli.getSettings(), EFFORT_READ_TIMEOUT_MS, "Claude did not report its settings"),
|
|
2643
|
+
withTimeout(cli.initializationResult(), EFFORT_READ_TIMEOUT_MS, "Claude did not report its models").catch(() => undefined),
|
|
2644
|
+
]);
|
|
2645
|
+
return effortStatusFromCli({
|
|
2646
|
+
answer,
|
|
2647
|
+
models: init?.models,
|
|
2648
|
+
requested: this.startedEffort.get(sessionId),
|
|
2649
|
+
processEnv: process.env[EFFORT_ENV_VAR],
|
|
2650
|
+
userFile: userSettingsFile(process.env).shown,
|
|
2651
|
+
});
|
|
2652
|
+
} catch (e) {
|
|
2653
|
+
log.debug(`session=${sessionId} effort status unavailable: ${(e as Error).message}`);
|
|
2654
|
+
return null;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
/** What the settings files say about effort for a chat in `projectPath` no CLI has answered for yet. */
|
|
2659
|
+
estimateEffortStatus(projectPath: string | undefined, model?: string): EffortStatus {
|
|
2660
|
+
return estimateEffortStatus({ projectPath, model, env: process.env });
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2581
2663
|
/** Abort and fully teardown the streaming session — user must resume to continue */
|
|
2582
2664
|
abortQuery(sessionId: string, source = "unknown"): void {
|
|
2583
2665
|
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
|
+
}
|
|
@@ -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 {
|
|
@@ -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";
|