@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
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
// for the SDK value "xhigh"; the literal "extra" would crash the CLI subprocess,
|
|
7
7
|
// so it must NEVER appear as a value here.
|
|
8
8
|
|
|
9
|
+
import {
|
|
10
|
+
effortEnvMeaning, effortInEffect, effortSettingPlace, isEffortLevel,
|
|
11
|
+
type EffortInEffect, type EffortLevel, type EffortStatus,
|
|
12
|
+
} from "../../../shared/effort-status";
|
|
13
|
+
import { modelDisplayName } from "@/lib/model-display-name";
|
|
14
|
+
|
|
9
15
|
export interface EffortOption {
|
|
10
16
|
label: string;
|
|
11
17
|
value: "low" | "medium" | "high" | "xhigh" | "max";
|
|
@@ -31,3 +37,76 @@ export function chipLabel(modelDisplay: string, effortValue: string | null | und
|
|
|
31
37
|
const eff = effortLabel(effortValue);
|
|
32
38
|
return eff ? `${modelDisplay} · ${eff}` : modelDisplay;
|
|
33
39
|
}
|
|
40
|
+
|
|
41
|
+
export const DEFAULT_EFFORT = EFFORT_OPTIONS.find((o) => o.default)!.value;
|
|
42
|
+
|
|
43
|
+
/** The picked effort beside the one the turns run at, and what overrides the pick if anything does. */
|
|
44
|
+
export function shownEffort(
|
|
45
|
+
effort: string | null | undefined,
|
|
46
|
+
status: EffortStatus | null | undefined,
|
|
47
|
+
): EffortInEffect & { pick: EffortLevel } {
|
|
48
|
+
const pick = isEffortLevel(effort) ? effort : DEFAULT_EFFORT;
|
|
49
|
+
return { pick, ...effortInEffect(pick, status) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Notice text; the names a user would type or search for (a variable, a file) are marked as code. */
|
|
53
|
+
export type NoticePart = string | { code: string };
|
|
54
|
+
|
|
55
|
+
export interface EffortNotice {
|
|
56
|
+
title: string;
|
|
57
|
+
detail: NoticePart[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function noticeText(parts: NoticePart[]): string {
|
|
61
|
+
return parts.map((part) => (typeof part === "string" ? part : part.code)).join("");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const USE_PPM_PICK = "Remove it there to use the effort picked in PPM.";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Why the effort the turns run at is not the one picked, naming the setting and where it is, so
|
|
68
|
+
* the user can change it. Null when the pick is what runs.
|
|
69
|
+
*/
|
|
70
|
+
export function effortNotice(
|
|
71
|
+
shown: EffortInEffect & { pick: EffortLevel },
|
|
72
|
+
status: EffortStatus | null | undefined,
|
|
73
|
+
modelDisplay: string,
|
|
74
|
+
): EffortNotice | null {
|
|
75
|
+
if (!shown.reason || !status) return null;
|
|
76
|
+
const observedModel = status.observed?.model;
|
|
77
|
+
const model = observedModel ? modelDisplayName(observedModel.replace(/\[[^\]]*\]$/, "")) : modelDisplay;
|
|
78
|
+
const level = shown.level ? effortLabel(shown.level) : null;
|
|
79
|
+
|
|
80
|
+
const meaning = effortEnvMeaning(status.env?.value);
|
|
81
|
+
if (shown.reason === "env" && status.env && meaning) {
|
|
82
|
+
const setting: NoticePart[] = [{ code: `CLAUDE_CODE_EFFORT_LEVEL=${status.env.value}` }, " in ", { code: effortSettingPlace(status.env) }];
|
|
83
|
+
if (meaning.kind === "auto") {
|
|
84
|
+
// With a cap in place the CLI sends the model's default, clamped, rather than nothing, and
|
|
85
|
+
// until it answers that default is not known here, so the sentence names only the cap.
|
|
86
|
+
const what = status.cap
|
|
87
|
+
? `Claude Code sends ${model}'s default effort, capped at ${effortLabel(status.cap.value)}.`
|
|
88
|
+
: level
|
|
89
|
+
? `Claude Code sends ${model}'s default, ${level}.`
|
|
90
|
+
: `no effort is sent, so ${model}'s default applies.`;
|
|
91
|
+
return { title: "Effort is left to the model", detail: [...setting, ` overrides the effort picked here: ${what} `, USE_PPM_PICK] };
|
|
92
|
+
}
|
|
93
|
+
if (meaning.kind === "budget") {
|
|
94
|
+
// Under a cap the CLI reads the number as High, clamped, instead of as a thinking budget.
|
|
95
|
+
if (level) {
|
|
96
|
+
return { title: `Effort is set to ${level} outside PPM`, detail: [...setting, ` overrides the effort picked here; with a maxEffortLevel set, Claude Code runs it as ${level}. `, USE_PPM_PICK] };
|
|
97
|
+
}
|
|
98
|
+
return { title: "A thinking budget replaces the effort", detail: [...setting, " sets a thinking budget instead of an effort level, so the effort picked here is not sent. ", USE_PPM_PICK] };
|
|
99
|
+
}
|
|
100
|
+
const runs = shown.level === meaning.level ? "" : level ? ` ${model} runs it as ${level}.` : ` ${model} takes no effort level, so none is sent.`;
|
|
101
|
+
return { title: `Effort is set to ${effortLabel(meaning.level)} outside PPM`, detail: [...setting, ` overrides the effort picked here.${runs} `, USE_PPM_PICK] };
|
|
102
|
+
}
|
|
103
|
+
if (shown.reason === "cap" && status.cap) {
|
|
104
|
+
const runs = shown.level === status.cap.value || !level ? "" : ` ${model} runs it as ${level}.`;
|
|
105
|
+
return {
|
|
106
|
+
title: `Effort is capped at ${effortLabel(status.cap.value)}`,
|
|
107
|
+
detail: [{ code: `maxEffortLevel: "${status.cap.value}"` }, " in ", { code: effortSettingPlace(status.cap) }, ` lowers any effort picked above it.${runs}`],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (!level) return { title: `${model} takes no effort level`, detail: [`Claude Code sends no effort with ${model}, so the effort picked here has no effect.`] };
|
|
111
|
+
return { title: `Runs at ${level}`, detail: [`${effortLabel(shown.pick)} isn't available for ${model}, so Claude Code runs it at ${level}.`] };
|
|
112
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, type ReactNode } from "react";
|
|
2
|
-
import { Check, Sparkles, Brain } from "@/lib/icons";
|
|
2
|
+
import { Check, Sparkles, Brain, TriangleAlert } from "@/lib/icons";
|
|
3
3
|
import { api, projectUrl } from "@/lib/api-client";
|
|
4
4
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
5
5
|
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
6
6
|
import { modelDisplayName } from "@/lib/model-display-name";
|
|
7
|
-
import {
|
|
7
|
+
import type { EffortStatus } from "../../../shared/effort-status";
|
|
8
|
+
import { EFFORT_OPTIONS, chipLabel, effortLabel, effortNotice, noticeText, shownEffort, type EffortNotice } from "./model-thinking-selector-helpers";
|
|
8
9
|
|
|
9
10
|
interface ModelOption {
|
|
10
11
|
value: string;
|
|
@@ -18,6 +19,8 @@ const MODEL_CACHE_TTL = 5 * 60 * 1000;
|
|
|
18
19
|
interface ModelThinkingSelectorProps {
|
|
19
20
|
model: string | null;
|
|
20
21
|
effort: string | null;
|
|
22
|
+
/** What overrides the effort pick outside PPM, so the chip shows the effort the turns run at. */
|
|
23
|
+
effortStatus?: EffortStatus | null;
|
|
21
24
|
thinking: boolean;
|
|
22
25
|
onModelChange: (model: string) => void;
|
|
23
26
|
onEffortChange: (effort: string) => void;
|
|
@@ -33,8 +36,6 @@ function shortLabel(label: string): string {
|
|
|
33
36
|
return label.replace(/^Claude\s+/i, "");
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
const DEFAULT_EFFORT = EFFORT_OPTIONS.find((o) => o.default)!.value;
|
|
37
|
-
|
|
38
39
|
/**
|
|
39
40
|
* Unified model + thinking picker — one chip, one popup. Mirrors Anthropic's native
|
|
40
41
|
* picker: model list, effort levels (Extra→SDK "xhigh"), and a thinking on/off toggle.
|
|
@@ -43,6 +44,7 @@ const DEFAULT_EFFORT = EFFORT_OPTIONS.find((o) => o.default)!.value;
|
|
|
43
44
|
export function ModelThinkingSelector({
|
|
44
45
|
model,
|
|
45
46
|
effort,
|
|
47
|
+
effortStatus,
|
|
46
48
|
thinking,
|
|
47
49
|
onModelChange,
|
|
48
50
|
onEffortChange,
|
|
@@ -109,9 +111,11 @@ export function ModelThinkingSelector({
|
|
|
109
111
|
|
|
110
112
|
const current = models.find((m) => m.value === model);
|
|
111
113
|
const modelDisplay = current ? shortLabel(current.label) : model ? modelDisplayName(model) : "Model";
|
|
112
|
-
const
|
|
114
|
+
const shown = shownEffort(effort, effortStatus);
|
|
115
|
+
const notice = effortNotice(shown, effortStatus, modelDisplay);
|
|
113
116
|
const showModelList = models.length > 0 || Boolean(modelStatus);
|
|
114
|
-
|
|
117
|
+
// The effort the turns run at, which an override outside PPM can make differ from the pick.
|
|
118
|
+
const chipText = chipLabel(modelDisplay, shown.level);
|
|
115
119
|
|
|
116
120
|
const pick = (fn: () => void) => {
|
|
117
121
|
fn();
|
|
@@ -119,6 +123,8 @@ export function ModelThinkingSelector({
|
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
const content = (
|
|
126
|
+
<>
|
|
127
|
+
{notice && <EffortNoticeBanner notice={notice} />}
|
|
122
128
|
<div className="flex divide-x divide-border">
|
|
123
129
|
{/* Left column: model list */}
|
|
124
130
|
{showModelList && (
|
|
@@ -137,12 +143,13 @@ export function ModelThinkingSelector({
|
|
|
137
143
|
<div className="flex-1 min-w-0 py-1">
|
|
138
144
|
<SectionLabel>Effort</SectionLabel>
|
|
139
145
|
{EFFORT_OPTIONS.map((o) => (
|
|
140
|
-
<OptionRow key={o.value} active={o.value ===
|
|
146
|
+
<OptionRow key={o.value} active={o.value === shown.pick} onClick={() => pick(() => onEffortChange(o.value))}>
|
|
141
147
|
<span className="flex-1 truncate">
|
|
142
148
|
{o.label}
|
|
143
149
|
{o.default && <span className="ml-2 text-xs text-text-subtle">Default</span>}
|
|
150
|
+
{o.value === shown.level && shown.level !== shown.pick && <span className="ml-2 text-xs text-warning">In effect</span>}
|
|
144
151
|
</span>
|
|
145
|
-
{o.value ===
|
|
152
|
+
{o.value === shown.pick && <Check className="size-4 shrink-0 text-primary" />}
|
|
146
153
|
</OptionRow>
|
|
147
154
|
))}
|
|
148
155
|
<Separator />
|
|
@@ -161,6 +168,7 @@ export function ModelThinkingSelector({
|
|
|
161
168
|
</button>
|
|
162
169
|
</div>
|
|
163
170
|
</div>
|
|
171
|
+
</>
|
|
164
172
|
);
|
|
165
173
|
|
|
166
174
|
return (
|
|
@@ -174,11 +182,12 @@ export function ModelThinkingSelector({
|
|
|
174
182
|
if (!disabled) setOpen((v) => !v);
|
|
175
183
|
}}
|
|
176
184
|
className="inline-flex max-w-full items-center gap-1.5 px-[9px] py-1 rounded-full text-[11.5px] text-primary bg-accent-wash border border-accent-wash-border hover:brightness-110 transition-[filter] disabled:opacity-50 disabled:cursor-default"
|
|
177
|
-
aria-label={`Model ${modelDisplay}, effort ${
|
|
178
|
-
title={disabled ? "Can't change while running" : chipText}
|
|
185
|
+
aria-label={`Model ${modelDisplay}, effort ${shown.level ? effortLabel(shown.level) : "none"}${notice ? ` (${notice.title})` : ""}${thinking ? ", thinking on" : ""}`}
|
|
186
|
+
title={disabled ? "Can't change while running" : notice ? `${chipText}\n${notice.title}: ${noticeText(notice.detail)}` : chipText}
|
|
179
187
|
>
|
|
180
188
|
<Sparkles className="h-3.5 w-3.5 shrink-0" />
|
|
181
189
|
<span className="min-w-0 max-w-[140px] truncate">{chipText}</span>
|
|
190
|
+
{notice && <TriangleAlert className="h-3 w-3 shrink-0 text-warning" />}
|
|
182
191
|
{thinking && <Brain className="h-3 w-3 shrink-0" />}
|
|
183
192
|
</button>
|
|
184
193
|
|
|
@@ -204,6 +213,23 @@ export function ModelThinkingSelector({
|
|
|
204
213
|
);
|
|
205
214
|
}
|
|
206
215
|
|
|
216
|
+
/** Above both columns, so it reads at full width in the phone's sheet too. */
|
|
217
|
+
function EffortNoticeBanner({ notice }: { notice: EffortNotice }) {
|
|
218
|
+
return (
|
|
219
|
+
<div role="note" className="flex gap-2 border-b border-border bg-warning/10 px-3 py-2.5 text-sm leading-relaxed text-text-secondary md:text-xs">
|
|
220
|
+
<TriangleAlert className="mt-0.5 size-4 shrink-0 text-warning" />
|
|
221
|
+
<div className="min-w-0">
|
|
222
|
+
<div className="font-medium text-text-primary">{notice.title}</div>
|
|
223
|
+
<p className="m-0 break-words">
|
|
224
|
+
{notice.detail.map((part, i) => typeof part === "string"
|
|
225
|
+
? part
|
|
226
|
+
: <code key={i} className="rounded bg-surface-elevated px-1 font-mono text-[0.92em]">{part.code}</code>)}
|
|
227
|
+
</p>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
207
233
|
function SectionLabel({ children }: { children: ReactNode }) {
|
|
208
234
|
return <div className="px-3 py-1.5 text-xs font-medium text-text-secondary">{children}</div>;
|
|
209
235
|
}
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* that answer them too: each turn's change tray keeps, reverts and reopens the blocks its edits
|
|
4
4
|
* wrote, and has to see the same list the bar does — one request for the whole chat.
|
|
5
5
|
*/
|
|
6
|
-
import { createContext, useContext } from "react";
|
|
6
|
+
import { createContext, useCallback, useContext, useMemo, useRef } from "react";
|
|
7
7
|
import type { SessionFileChange } from "../../../shared/session-file-changes";
|
|
8
|
+
import { openSessionReview } from "@/lib/session-file-changes-client";
|
|
8
9
|
|
|
9
10
|
export interface SessionChangesValue {
|
|
10
11
|
projectName: string;
|
|
@@ -19,3 +20,30 @@ export interface SessionChangesValue {
|
|
|
19
20
|
export const SessionChangesContext = createContext<SessionChangesValue | null>(null);
|
|
20
21
|
|
|
21
22
|
export const useSessionChanges = () => useContext(SessionChangesContext);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The value the chat provides, and the Review opener its changes bar uses.
|
|
26
|
+
*
|
|
27
|
+
* Every turn's action bar reads this context, so the value has to stay the same object while
|
|
28
|
+
* the agent streams: `paths` is a new array on every flush, which is why it is read only when
|
|
29
|
+
* the Review tab opens.
|
|
30
|
+
*/
|
|
31
|
+
export function useSessionChangesValue({ projectName, sessionId, title, providerId, changes }: {
|
|
32
|
+
projectName: string;
|
|
33
|
+
sessionId: string | null;
|
|
34
|
+
title?: string;
|
|
35
|
+
providerId: string;
|
|
36
|
+
changes: { files: SessionFileChange[]; paths: string[]; refresh: () => void };
|
|
37
|
+
}): { value: SessionChangesValue | null; openReview: (path?: string) => void } {
|
|
38
|
+
const pathsRef = useRef(changes.paths);
|
|
39
|
+
pathsRef.current = changes.paths;
|
|
40
|
+
const openReview = useCallback((path?: string) => {
|
|
41
|
+
if (!sessionId) return;
|
|
42
|
+
openSessionReview({ projectName, sessionId, title, providerId, paths: pathsRef.current, selectPath: path });
|
|
43
|
+
}, [projectName, sessionId, title, providerId]);
|
|
44
|
+
const { files, refresh } = changes;
|
|
45
|
+
const value = useMemo((): SessionChangesValue | null => (
|
|
46
|
+
projectName && sessionId ? { projectName, sessionId, files, refresh, openReview } : null
|
|
47
|
+
), [projectName, sessionId, files, refresh, openReview]);
|
|
48
|
+
return { value, openReview };
|
|
49
|
+
}
|
|
@@ -53,6 +53,32 @@ export function ReparentingTab({
|
|
|
53
53
|
const panelIdRef = useRef(panelId);
|
|
54
54
|
panelIdRef.current = panelId;
|
|
55
55
|
|
|
56
|
+
// Where each scroller in the tab last stood, as its own scroll events said. A move resets
|
|
57
|
+
// every offset, and on a project switch the old panel unmounts first, taking the wrapper off
|
|
58
|
+
// the page before the move below runs — too late to read them then. A chat came back at its
|
|
59
|
+
// top, and its history loader took that for the reader scrolling up and fetched another page
|
|
60
|
+
// on every switch back.
|
|
61
|
+
// Held weakly: a scroller React removed would keep its whole tree alive, and a chat replaces
|
|
62
|
+
// its transcript's on every session switch.
|
|
63
|
+
const scrolledRef = useRef(new WeakMap<Element, { top: number; left: number }>());
|
|
64
|
+
/** The keys of `scrolledRef`, which a WeakMap cannot list. */
|
|
65
|
+
const scrollersRef = useRef(new Set<WeakRef<Element>>());
|
|
66
|
+
/** Moved while hidden, when there was no layout to put the offsets back into. */
|
|
67
|
+
const restorePendingRef = useRef(false);
|
|
68
|
+
const restoreScroll = () => {
|
|
69
|
+
for (const ref of scrollersRef.current) {
|
|
70
|
+
const el = ref.deref();
|
|
71
|
+
const at = el && scrolledRef.current.get(el);
|
|
72
|
+
if (!el || !at || !wrapper.contains(el)) {
|
|
73
|
+
scrollersRef.current.delete(ref);
|
|
74
|
+
if (el) scrolledRef.current.delete(el);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
el.scrollTop = at.top;
|
|
78
|
+
el.scrollLeft = at.left;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
56
82
|
// Mount the wrapper and own its removal. The listener is native and capturing because
|
|
57
83
|
// the tab content is DOM-reparented out of the panel's fiber subtree: the panel never
|
|
58
84
|
// sees clicks on the content body, so focus is set here, where the panel is known.
|
|
@@ -65,17 +91,39 @@ export function ReparentingTab({
|
|
|
65
91
|
if (pid === DOCK_PANEL_ID || isWindowPanelId(pid)) return;
|
|
66
92
|
usePanelStore.getState().setFocusedPanel(pid);
|
|
67
93
|
};
|
|
94
|
+
const scrolled = scrolledRef.current;
|
|
95
|
+
const scrollers = scrollersRef.current;
|
|
96
|
+
const onScroll = (e: Event) => {
|
|
97
|
+
// Parked out of sight a scroller has no size, and whatever moves it there is not the reader.
|
|
98
|
+
if (wrapper.parentElement === hiddenContainer) return;
|
|
99
|
+
const el = e.target as Element;
|
|
100
|
+
if (el.nodeType !== Node.ELEMENT_NODE) return;
|
|
101
|
+
if (!scrolled.has(el)) {
|
|
102
|
+
for (const ref of scrollers) if (!ref.deref()) scrollers.delete(ref);
|
|
103
|
+
scrollers.add(new WeakRef(el));
|
|
104
|
+
}
|
|
105
|
+
scrolled.set(el, { top: el.scrollTop, left: el.scrollLeft });
|
|
106
|
+
};
|
|
68
107
|
wrapper.addEventListener("pointerdown", onMouseDown, true);
|
|
69
108
|
wrapper.addEventListener("focusin", onMouseDown, true);
|
|
109
|
+
wrapper.addEventListener("scroll", onScroll, true);
|
|
70
110
|
return () => {
|
|
71
111
|
wrapper.removeEventListener("pointerdown", onMouseDown, true);
|
|
72
112
|
wrapper.removeEventListener("focusin", onMouseDown, true);
|
|
113
|
+
wrapper.removeEventListener("scroll", onScroll, true);
|
|
73
114
|
wrapper.remove();
|
|
74
115
|
};
|
|
75
116
|
}, [wrapper, hiddenContainer]);
|
|
76
117
|
|
|
77
118
|
useLayoutEffect(() => {
|
|
78
119
|
wrapper.style.display = isActive ? "" : "none";
|
|
120
|
+
// Only for a tab moved while hidden: a plain tab switch keeps its offsets, and writing them
|
|
121
|
+
// back forces a layout of the whole tab in the middle of the commit.
|
|
122
|
+
if (isActive && restorePendingRef.current) {
|
|
123
|
+
restorePendingRef.current = false;
|
|
124
|
+
restoreScroll();
|
|
125
|
+
}
|
|
126
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
79
127
|
}, [wrapper, isActive]);
|
|
80
128
|
|
|
81
129
|
// Imperatively move the wrapper into the correct panel slot.
|
|
@@ -96,21 +144,12 @@ export function ReparentingTab({
|
|
|
96
144
|
|
|
97
145
|
if (wrapper.parentElement === slot) return;
|
|
98
146
|
|
|
99
|
-
// Save scroll positions — appendChild resets them during the DOM move
|
|
100
|
-
const scrollables: { el: Element; top: number; left: number }[] = [];
|
|
101
|
-
wrapper.querySelectorAll("*").forEach((el) => {
|
|
102
|
-
if (el.scrollTop || el.scrollLeft) {
|
|
103
|
-
scrollables.push({ el, top: el.scrollTop, left: el.scrollLeft });
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
147
|
slot.appendChild(wrapper);
|
|
108
|
-
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
148
|
+
// Before paint, from what the scroll events recorded: walking every element of the tab
|
|
149
|
+
// to read offsets cost a whole transcript's worth of reads per move, and read zeros
|
|
150
|
+
// whenever the wrapper had already left the page.
|
|
151
|
+
restorePendingRef.current = !isActive;
|
|
152
|
+
if (isActive) restoreScroll();
|
|
114
153
|
});
|
|
115
154
|
|
|
116
155
|
// Portal target for the tab's own dropdowns, tooltips and sheets. It belongs here and
|
|
@@ -17,6 +17,9 @@ import { ProviderBadge } from "@/components/chat/provider-selector";
|
|
|
17
17
|
import { openSettings } from "./open-settings";
|
|
18
18
|
import { CodexContextSettings } from "./codex-context-settings";
|
|
19
19
|
import { McpSignInList } from "@/components/mcp-auth/mcp-sign-in-list";
|
|
20
|
+
import { effortNotice, noticeText, shownEffort } from "@/components/chat/model-thinking-selector-helpers";
|
|
21
|
+
import { modelDisplayName } from "@/lib/model-display-name";
|
|
22
|
+
import type { EffortStatus } from "../../../shared/effort-status";
|
|
20
23
|
import type { ModelOption } from "../../../types/chat";
|
|
21
24
|
import type { ProviderProbeStatus } from "../../../providers/provider-probe";
|
|
22
25
|
|
|
@@ -88,6 +91,21 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
|
|
|
88
91
|
.finally(() => setModelsLoading(false));
|
|
89
92
|
}, [activeTab, activeRegistered]);
|
|
90
93
|
|
|
94
|
+
// `CLAUDE_CODE_EFFORT_LEVEL` or a cap in the user's Claude Code settings makes the default
|
|
95
|
+
// below inert for every project, which the select alone would not show.
|
|
96
|
+
const [effortStatus, setEffortStatus] = useState<EffortStatus | null>(null);
|
|
97
|
+
const defaultModel = settings?.providers[activeTab]?.model;
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
setEffortStatus(null);
|
|
100
|
+
if (!activeTab) return;
|
|
101
|
+
let active = true;
|
|
102
|
+
const query = defaultModel ? `?model=${encodeURIComponent(defaultModel)}` : "";
|
|
103
|
+
api.get<{ status: EffortStatus | null }>(`/api/settings/ai/providers/${activeTab}/effort-status${query}`)
|
|
104
|
+
.then(({ status }) => { if (active) setEffortStatus(status); })
|
|
105
|
+
.catch(() => {});
|
|
106
|
+
return () => { active = false; };
|
|
107
|
+
}, [activeTab, defaultModel]);
|
|
108
|
+
|
|
91
109
|
const retryProbe = async () => {
|
|
92
110
|
setProbing(true);
|
|
93
111
|
try {
|
|
@@ -108,6 +126,11 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
|
|
|
108
126
|
|
|
109
127
|
const config = settings?.providers[activeTab];
|
|
110
128
|
const isSdkProvider = config?.type === "agent-sdk" || (!config?.type && activeTab === "claude");
|
|
129
|
+
const effortOverride = effortNotice(
|
|
130
|
+
shownEffort(config?.effort, effortStatus),
|
|
131
|
+
effortStatus,
|
|
132
|
+
config?.model ? modelDisplayName(config.model) : "the model",
|
|
133
|
+
);
|
|
111
134
|
|
|
112
135
|
const handleSettingsSave = async (patch: Partial<AISettings>) => {
|
|
113
136
|
if (!settings) return false;
|
|
@@ -359,6 +382,15 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
|
|
|
359
382
|
))}
|
|
360
383
|
</SelectContent>
|
|
361
384
|
</Select>
|
|
385
|
+
{effortOverride && (
|
|
386
|
+
<div role="note" className="flex items-start gap-2 rounded-md border border-warning/30 bg-warning/5 px-2.5 py-2">
|
|
387
|
+
<AlertTriangle className="size-3.5 text-warning shrink-0 mt-0.5" />
|
|
388
|
+
<div className="min-w-0 flex-1 space-y-1">
|
|
389
|
+
<p className={`${labelSize} text-text-secondary`}>{effortOverride.title}</p>
|
|
390
|
+
<p className="text-[11px] text-muted-foreground break-words">{noticeText(effortOverride.detail)}</p>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
362
394
|
</div>
|
|
363
395
|
|
|
364
396
|
<div className={fieldGap}>
|
|
@@ -144,6 +144,8 @@ function PushSection() {
|
|
|
144
144
|
const [subscription, setSubscription] = useState<PushSubscription | null>(null);
|
|
145
145
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
146
146
|
const [busy, setBusy] = useState(false);
|
|
147
|
+
const [enabling, setEnabling] = useState(false);
|
|
148
|
+
const [enableError, setEnableError] = useState<string | null>(null);
|
|
147
149
|
const [testing, setTesting] = useState<string | null>(null);
|
|
148
150
|
const support = pushSupport();
|
|
149
151
|
|
|
@@ -174,6 +176,8 @@ function PushSection() {
|
|
|
174
176
|
const toggle = async (on: boolean) => {
|
|
175
177
|
if (!status) return;
|
|
176
178
|
setBusy(true);
|
|
179
|
+
setEnabling(on);
|
|
180
|
+
setEnableError(null);
|
|
177
181
|
try {
|
|
178
182
|
if (on) {
|
|
179
183
|
await enablePush(status.publicKey);
|
|
@@ -182,9 +186,12 @@ function PushSection() {
|
|
|
182
186
|
await disablePush();
|
|
183
187
|
}
|
|
184
188
|
} catch (e) {
|
|
185
|
-
|
|
189
|
+
// Kept on screen rather than in a toast: the fix is usually a browser setting to go and change.
|
|
190
|
+
if (on) setEnableError(errorText(e));
|
|
191
|
+
else toast.error("Could not turn push off", { description: errorText(e) });
|
|
186
192
|
} finally {
|
|
187
193
|
setBusy(false);
|
|
194
|
+
setEnabling(false);
|
|
188
195
|
await load();
|
|
189
196
|
}
|
|
190
197
|
};
|
|
@@ -231,6 +238,12 @@ function PushSection() {
|
|
|
231
238
|
/>
|
|
232
239
|
</div>
|
|
233
240
|
{support.state !== "available" && <p className="text-xs leading-relaxed text-warning">{support.message}</p>}
|
|
241
|
+
{enabling && (
|
|
242
|
+
<p className="text-xs leading-relaxed text-muted-foreground">
|
|
243
|
+
Waiting for the browser's push service. Just after the browser starts, this can take up to a minute.
|
|
244
|
+
</p>
|
|
245
|
+
)}
|
|
246
|
+
{enableError && <p className="break-words text-xs leading-relaxed text-error">{enableError}</p>}
|
|
234
247
|
{loadError && <p className="text-xs text-error">{loadError}</p>}
|
|
235
248
|
|
|
236
249
|
{status && status.devices.length > 0 && (
|
|
@@ -13,6 +13,7 @@ import type { ChatMessage, ChatEvent } from "../../types/chat";
|
|
|
13
13
|
import type { BackgroundAgentStatus } from "../../shared/background-agent-status";
|
|
14
14
|
import type { PromptCacheState } from "../../shared/prompt-cache-idle";
|
|
15
15
|
import type { TurnStop } from "../../shared/turn-stop";
|
|
16
|
+
import type { EffortStatus } from "../../shared/effort-status";
|
|
16
17
|
import { decodeReply, encodeReply, type ReplyReference } from "../../shared/chat-reply";
|
|
17
18
|
import { prefixTokens } from "../../shared/turn-usage";
|
|
18
19
|
import { dbExecuteApprovalInput } from "../../shared/db-ai-tools";
|
|
@@ -131,6 +132,11 @@ interface UseChatReturn {
|
|
|
131
132
|
effort: string | null;
|
|
132
133
|
/** Switch the per-session effort (low|medium|high|xhigh|max) */
|
|
133
134
|
setEffort: (effort: string) => void;
|
|
135
|
+
/**
|
|
136
|
+
* What overrides the effort pick outside PPM, as the server last said: undefined until it
|
|
137
|
+
* has, null when nothing can for this session's provider.
|
|
138
|
+
*/
|
|
139
|
+
effortStatus: EffortStatus | null | undefined;
|
|
134
140
|
/** Whether per-session thinking is on */
|
|
135
141
|
thinking: boolean;
|
|
136
142
|
/** Toggle per-session thinking on/off */
|
|
@@ -251,6 +257,7 @@ export function useChat(
|
|
|
251
257
|
const pendingModelRef = useRef<string | null>(null);
|
|
252
258
|
const [effort, setEffortState] = useState<string | null>(null);
|
|
253
259
|
const effortRef = useRef<string | null>(null);
|
|
260
|
+
const [effortStatus, setEffortStatus] = useState<EffortStatus | null | undefined>(undefined);
|
|
254
261
|
// Optimistic until session_state lands; the SDK default is adaptive thinking, so ON
|
|
255
262
|
// is the honest starting guess.
|
|
256
263
|
const [thinking, setThinkingState] = useState<boolean>(true);
|
|
@@ -1021,6 +1028,11 @@ export function useChat(
|
|
|
1021
1028
|
return;
|
|
1022
1029
|
}
|
|
1023
1030
|
|
|
1031
|
+
if ((data as any).type === "effort_status") {
|
|
1032
|
+
setEffortStatus(((data as any).status as EffortStatus | undefined) ?? null);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1024
1036
|
// Handle compact status events
|
|
1025
1037
|
if ((data as any).type === "compact_status") {
|
|
1026
1038
|
const status = (data as any).status;
|
|
@@ -1096,6 +1108,8 @@ export function useChat(
|
|
|
1096
1108
|
setEffortState(state.effort);
|
|
1097
1109
|
effortRef.current = state.effort;
|
|
1098
1110
|
}
|
|
1111
|
+
// Only the greetings that carry the effort carry this; the others leave it as it was.
|
|
1112
|
+
if ("effortStatus" in state) setEffortStatus((state.effortStatus as EffortStatus | null) ?? null);
|
|
1099
1113
|
// Display only. Writing this back into the ref would promote a server-derived
|
|
1100
1114
|
// default into an explicit user choice that rides along on every message and
|
|
1101
1115
|
// pins the session — the path that silently disabled thinking.
|
|
@@ -1289,6 +1303,7 @@ export function useChat(
|
|
|
1289
1303
|
// Another session's subprocess saw another MCP state; its session_state brings its own.
|
|
1290
1304
|
setMcpNeedsAuth([]);
|
|
1291
1305
|
setTurnStop(null);
|
|
1306
|
+
setEffortStatus(undefined);
|
|
1292
1307
|
// Clear ephemeral pre-compact expansions on session change
|
|
1293
1308
|
setExpansions(new Map());
|
|
1294
1309
|
// Drop the previous session's version groups. Keeping them would let the
|
|
@@ -1819,6 +1834,7 @@ export function useChat(
|
|
|
1819
1834
|
setModel,
|
|
1820
1835
|
effort,
|
|
1821
1836
|
setEffort,
|
|
1837
|
+
effortStatus,
|
|
1822
1838
|
thinking,
|
|
1823
1839
|
setThinking,
|
|
1824
1840
|
turnSettings,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { api, projectUrl } from "@/lib/api-client";
|
|
3
|
+
import type { EffortStatus } from "../../shared/effort-status";
|
|
4
|
+
|
|
5
|
+
/** Short-lived: it is read from settings files the user may be editing right now. */
|
|
6
|
+
const CACHE_MS = 30_000;
|
|
7
|
+
const cache = new Map<string, { status: EffortStatus | null; at: number }>();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* What overrides the effort pick outside PPM. A session's own status comes over its socket once
|
|
11
|
+
* the server has sent it; a chat with no session yet — the new-chat composer — asks what the
|
|
12
|
+
* project's settings files say, so the chip is right before the first message too.
|
|
13
|
+
*/
|
|
14
|
+
export function useEffortStatus(p: {
|
|
15
|
+
projectName?: string;
|
|
16
|
+
providerId?: string;
|
|
17
|
+
model?: string | null;
|
|
18
|
+
sessionStatus: EffortStatus | null | undefined;
|
|
19
|
+
}): EffortStatus | null {
|
|
20
|
+
const { projectName, providerId, model, sessionStatus } = p;
|
|
21
|
+
const key = JSON.stringify([projectName ?? null, providerId ?? null, model ?? null]);
|
|
22
|
+
const wanted = sessionStatus === undefined && !!projectName && !!providerId;
|
|
23
|
+
const [fetched, setFetched] = useState<{ key: string; status: EffortStatus | null } | null>(null);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!wanted) return;
|
|
27
|
+
const hit = cache.get(key);
|
|
28
|
+
if (hit && Date.now() - hit.at < CACHE_MS) {
|
|
29
|
+
setFetched({ key, status: hit.status });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let active = true;
|
|
33
|
+
const query = model ? `?model=${encodeURIComponent(model)}` : "";
|
|
34
|
+
api.get<{ status: EffortStatus | null }>(`${projectUrl(projectName!)}/chat/providers/${providerId}/effort-status${query}`)
|
|
35
|
+
.then(({ status }) => {
|
|
36
|
+
cache.set(key, { status, at: Date.now() });
|
|
37
|
+
if (active) setFetched({ key, status });
|
|
38
|
+
})
|
|
39
|
+
// Nothing to say then; the chip shows the pick as it always did.
|
|
40
|
+
.catch(() => {});
|
|
41
|
+
return () => { active = false; };
|
|
42
|
+
}, [wanted, key, projectName, providerId, model]);
|
|
43
|
+
|
|
44
|
+
if (sessionStatus !== undefined) return sessionStatus;
|
|
45
|
+
return fetched?.key === key ? fetched.status : cache.get(key)?.status ?? null;
|
|
46
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Re-renders the caller every half minute, for labels such as "5m ago".
|
|
5
|
+
*
|
|
6
|
+
* The chat's labels used to stay current only because the whole transcript was redrawn on
|
|
7
|
+
* every render of the chat; now that it is not, they ask for the time themselves. One timer for
|
|
8
|
+
* the page, running only while a label is mounted.
|
|
9
|
+
*/
|
|
10
|
+
export const RELATIVE_TIME_TICK_MS = 30_000;
|
|
11
|
+
|
|
12
|
+
let tick = 0;
|
|
13
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
14
|
+
const listeners = new Set<() => void>();
|
|
15
|
+
|
|
16
|
+
function subscribe(listener: () => void): () => void {
|
|
17
|
+
listeners.add(listener);
|
|
18
|
+
timer ??= setInterval(() => {
|
|
19
|
+
tick++;
|
|
20
|
+
for (const notify of listeners) notify();
|
|
21
|
+
}, RELATIVE_TIME_TICK_MS);
|
|
22
|
+
return () => {
|
|
23
|
+
listeners.delete(listener);
|
|
24
|
+
if (listeners.size === 0 && timer !== undefined) {
|
|
25
|
+
clearInterval(timer);
|
|
26
|
+
timer = undefined;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const snapshot = () => tick;
|
|
32
|
+
|
|
33
|
+
export function useRelativeTimeTick(): number {
|
|
34
|
+
return useSyncExternalStore(subscribe, snapshot, snapshot);
|
|
35
|
+
}
|
|
@@ -35,6 +35,10 @@ import {
|
|
|
35
35
|
import type { ChatMessage } from "../../types/chat";
|
|
36
36
|
import { SESSION_FILE_CHANGES_PUSH, type SessionFileChange, type SessionFileChangesPushEvent } from "../../shared/session-file-changes";
|
|
37
37
|
|
|
38
|
+
// One array for "not asked yet", which is most chats: a fresh `[]` per render reached every
|
|
39
|
+
// turn's action bar through the context and redrew them all on each render of the chat.
|
|
40
|
+
const NO_FILES: SessionFileChange[] = [];
|
|
41
|
+
|
|
38
42
|
export function useSessionFileChanges({ projectName, sessionId, messages, isStreaming }: {
|
|
39
43
|
projectName: string;
|
|
40
44
|
sessionId: string | null;
|
|
@@ -173,5 +177,5 @@ export function useSessionFileChanges({ projectName, sessionId, messages, isStre
|
|
|
173
177
|
|
|
174
178
|
// A list fetched for the previous session is never shown under this one.
|
|
175
179
|
const unloadedCount = Math.max(paths.length, count.key === key ? count.n : 0);
|
|
176
|
-
return { files: loaded ? state.files :
|
|
180
|
+
return { files: loaded ? state.files : NO_FILES, paths, unloadedCount, loaded, loading: loadingKey === key && key !== "", refresh, setReviewed };
|
|
177
181
|
}
|