@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { decodeReply, type ReplyReference } from "../../../shared/chat-reply";
|
|
2
2
|
import { createReplyReference, resolveReplyMessage } from "./message-reply-reference";
|
|
3
3
|
import { useEffect, useRef, useState, useMemo, useCallback, useLayoutEffect, memo } from "react";
|
|
4
4
|
import { userMessageOrdinals } from "@/lib/message-ordinals";
|
|
@@ -205,8 +205,12 @@ export function MessageList({
|
|
|
205
205
|
// Mirror the lib's scroll-element ref into state so effects/nav re-run when the
|
|
206
206
|
// scroll container mounts late (it appears only after the loading screen).
|
|
207
207
|
const [scrollEl, setScrollEl] = useState<HTMLDivElement | null>(null);
|
|
208
|
+
// Read by the callbacks every bubble holds: one built on the state above changes when
|
|
209
|
+
// the container mounts, which drew the whole transcript twice on every open.
|
|
210
|
+
const scrollElRef = useRef<HTMLDivElement | null>(null);
|
|
208
211
|
const setScrollRef = useCallback((el: HTMLDivElement | null) => {
|
|
209
212
|
scrollRef(el);
|
|
213
|
+
scrollElRef.current = el;
|
|
210
214
|
setScrollEl(el);
|
|
211
215
|
}, [scrollRef]);
|
|
212
216
|
|
|
@@ -238,10 +242,11 @@ export function MessageList({
|
|
|
238
242
|
// cleanup clears the pending flash, so a second jump can't leave a stale highlight.
|
|
239
243
|
const flashCleanupRef = useRef<(() => void) | null>(null);
|
|
240
244
|
const handleJumpToEdit = useCallback((editRef: string) => {
|
|
241
|
-
|
|
245
|
+
const el = scrollElRef.current;
|
|
246
|
+
if (!el) return;
|
|
242
247
|
flashCleanupRef.current?.();
|
|
243
|
-
flashCleanupRef.current = jumpToEdit(
|
|
244
|
-
}, [
|
|
248
|
+
flashCleanupRef.current = jumpToEdit(el, editRef);
|
|
249
|
+
}, []);
|
|
245
250
|
useEffect(() => () => flashCleanupRef.current?.(), []);
|
|
246
251
|
|
|
247
252
|
// The Review tab names the turn that wrote each block from what this chat shows.
|
|
@@ -338,26 +343,31 @@ export function MessageList({
|
|
|
338
343
|
const reply = createReplyReference(message, sessionId, providerId);
|
|
339
344
|
if (reply) onReply?.(reply);
|
|
340
345
|
}, [sessionId, providerId, onReply]);
|
|
341
|
-
const replyAvailable = useCallback((reply: ReplyReference) => !!resolveReplyMessage(filtered, reply, sessionId, providerId), [filtered, sessionId, providerId]);
|
|
342
346
|
const replyFlashCleanup = useRef<(() => void) | null>(null);
|
|
343
347
|
useEffect(() => () => replyFlashCleanup.current?.(), []);
|
|
344
348
|
/** Scroll to the message at `index` of `filtered` and flash it; false when it is not drawn. */
|
|
345
349
|
const showMessageAt = useCallback((index: number) => {
|
|
346
|
-
const
|
|
347
|
-
|
|
350
|
+
const el = scrollElRef.current;
|
|
351
|
+
const target = el?.querySelector<HTMLElement>(`[data-msg-index="${index}"]`);
|
|
352
|
+
if (!el || !target) return false;
|
|
348
353
|
stopScroll();
|
|
349
354
|
replyFlashCleanup.current?.();
|
|
350
|
-
|
|
355
|
+
el.scrollTo({ top: el.scrollTop + target.getBoundingClientRect().top - el.getBoundingClientRect().top - 24, behavior: "smooth" });
|
|
351
356
|
target.classList.add("bg-primary/10", "ring-1", "ring-primary/40");
|
|
352
357
|
const clear = () => target.classList.remove("bg-primary/10", "ring-1", "ring-primary/40");
|
|
353
358
|
const timer = setTimeout(clear, 1400);
|
|
354
359
|
replyFlashCleanup.current = () => { clearTimeout(timer); clear(); };
|
|
355
360
|
return true;
|
|
356
|
-
}, [
|
|
361
|
+
}, [stopScroll]);
|
|
362
|
+
// Every bubble holds this callback and `filtered` is new on every streamed flush, so
|
|
363
|
+
// depending on it re-rendered the whole transcript ten times a second. Only a click reads it.
|
|
364
|
+
const filteredRef = useRef(filtered);
|
|
365
|
+
useLayoutEffect(() => { filteredRef.current = filtered; }, [filtered]);
|
|
357
366
|
const handleJumpToReply = useCallback((reply: ReplyReference) => {
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
367
|
+
const list = filteredRef.current;
|
|
368
|
+
const message = resolveReplyMessage(list, reply, sessionId, providerId);
|
|
369
|
+
if (message) showMessageAt(list.indexOf(message));
|
|
370
|
+
}, [sessionId, providerId, showMessageAt]);
|
|
361
371
|
|
|
362
372
|
// "Show in chat" from the Review tab: the call's card, or the prompt that asked for it when
|
|
363
373
|
// the card is not drawn (a sub-agent's call). Waits for the tab to be shown and its history to
|
|
@@ -482,14 +492,32 @@ export function MessageList({
|
|
|
482
492
|
const el = scrollEl;
|
|
483
493
|
const sentinel = topSentinelRef.current;
|
|
484
494
|
if (!el || !sentinel || !hasMore || compactLoadError) return;
|
|
495
|
+
// A transcript parked out of sight (its panel unmounted on a project switch) has no height,
|
|
496
|
+
// so its top always reads as in range; loading then grew the chat by a page on every switch
|
|
497
|
+
// back. What the observer says there is set aside and asked again once there is a height:
|
|
498
|
+
// it reports changes only, so a short transcript whose top stayed in range heard nothing.
|
|
499
|
+
let parked = false;
|
|
485
500
|
const io = new IntersectionObserver(
|
|
486
|
-
(entries) => {
|
|
501
|
+
(entries) => {
|
|
502
|
+
parked = el.clientHeight === 0;
|
|
503
|
+
if (!parked && entries.some((e) => e.isIntersecting)) loadMore();
|
|
504
|
+
},
|
|
487
505
|
// Start the fetch while the top is still a screenful away, so the next
|
|
488
506
|
// segment is usually there before the user reaches the end of this one.
|
|
489
507
|
{ root: el, rootMargin: "400px 0px 0px 0px" },
|
|
490
508
|
);
|
|
491
509
|
io.observe(sentinel);
|
|
492
|
-
|
|
510
|
+
const ro = new ResizeObserver(() => {
|
|
511
|
+
if (!parked || el.clientHeight === 0) return;
|
|
512
|
+
parked = false;
|
|
513
|
+
io.unobserve(sentinel);
|
|
514
|
+
io.observe(sentinel);
|
|
515
|
+
});
|
|
516
|
+
ro.observe(el);
|
|
517
|
+
return () => {
|
|
518
|
+
io.disconnect();
|
|
519
|
+
ro.disconnect();
|
|
520
|
+
};
|
|
493
521
|
}, [scrollEl, hasMore, compactLoadError, loadMore]);
|
|
494
522
|
|
|
495
523
|
if (messagesLoading && (!keepStaleWhileLoading || messages.length === 0)) {
|
|
@@ -576,6 +604,10 @@ export function MessageList({
|
|
|
576
604
|
}
|
|
577
605
|
turnCopyText = parts.join("\n\n");
|
|
578
606
|
}
|
|
607
|
+
// A boolean rather than a lookup the bubble calls while rendering: it has to change
|
|
608
|
+
// when older history brings the quoted message in, and not on every streamed flush.
|
|
609
|
+
const replyTo = msg.role === "user" ? decodeReply(msg.content).replyTo : null;
|
|
610
|
+
const replyAvailable = replyTo ? !!resolveReplyMessage(filtered, replyTo, sessionId, providerId) : undefined;
|
|
579
611
|
return (
|
|
580
612
|
<div
|
|
581
613
|
key={msg.id}
|
|
@@ -589,11 +621,10 @@ export function MessageList({
|
|
|
589
621
|
<RenderErrorBoundary fallbackContent={msg.content}>
|
|
590
622
|
<MessageBubble
|
|
591
623
|
message={msg}
|
|
592
|
-
onReply={onReply && sessionId && providerId
|
|
593
|
-
?
|
|
594
|
-
? turnReplyMessage ? () => handleReply(turnReplyMessage!) : undefined
|
|
595
|
-
: handleReply
|
|
624
|
+
onReply={onReply && sessionId && providerId && (msg.role !== "assistant" || turnReplyMessage)
|
|
625
|
+
? handleReply
|
|
596
626
|
: undefined}
|
|
627
|
+
replyTarget={turnReplyMessage}
|
|
597
628
|
onJumpToReply={handleJumpToReply}
|
|
598
629
|
replyAvailable={replyAvailable}
|
|
599
630
|
isStreaming={isStreaming && msg.id.startsWith("streaming-")}
|
|
@@ -651,10 +682,13 @@ function assistantMessageText(msg: ChatMessage): string {
|
|
|
651
682
|
: msg.content;
|
|
652
683
|
}
|
|
653
684
|
|
|
654
|
-
const MessageBubble = memo(function MessageBubble({ message, isStreaming, isLastAssistantInTurn, turnCopyText, turnChanges, turn, onJumpToEdit, projectName, onFork, onEdit, isEditing, onDismiss, prevMsgId, sessionId, providerId, versionGroup, onNavigateVersion, versionNavDisabled, bashPartialOutput, onReply, onJumpToReply, replyAvailable }: {
|
|
685
|
+
const MessageBubble = memo(function MessageBubble({ message, isStreaming, isLastAssistantInTurn, turnCopyText, turnChanges, turn, onJumpToEdit, projectName, onFork, onEdit, isEditing, onDismiss, prevMsgId, sessionId, providerId, versionGroup, onNavigateVersion, versionNavDisabled, bashPartialOutput, onReply, replyTarget, onJumpToReply, replyAvailable }: {
|
|
655
686
|
onReply?: (message: ChatMessage) => void;
|
|
687
|
+
/** Assistant only: the message of this turn that Reply quotes, which may be an earlier one. */
|
|
688
|
+
replyTarget?: ChatMessage;
|
|
656
689
|
onJumpToReply?: (reply: ReplyReference) => void;
|
|
657
|
-
|
|
690
|
+
/** Whether the message this one quotes is in the list; undefined when it quotes none. */
|
|
691
|
+
replyAvailable?: boolean;
|
|
658
692
|
message: ChatMessage; isStreaming: boolean; isLastAssistantInTurn?: boolean; turnCopyText?: string; projectName?: string;
|
|
659
693
|
/** Files this turn changed — drives the action-bar change pill. */
|
|
660
694
|
turnChanges?: TurnFileChange[];
|
|
@@ -734,7 +768,7 @@ const MessageBubble = memo(function MessageBubble({ message, isStreaming, isLast
|
|
|
734
768
|
{/* Action bar: only on the last assistant message of the turn, after streaming ends */}
|
|
735
769
|
{!isStreaming && isLastAssistantInTurn && (
|
|
736
770
|
<TurnChangeRollup
|
|
737
|
-
onReply={onReply ? () => onReply(
|
|
771
|
+
onReply={onReply && replyTarget ? () => onReply(replyTarget) : undefined}
|
|
738
772
|
timestamp={message.timestamp}
|
|
739
773
|
content={turnCopyText ?? assistantMessageText(message)}
|
|
740
774
|
changes={turnChanges}
|
|
@@ -37,7 +37,7 @@ export function UserBubble({ content, messageId, timestamp, projectName, onFork,
|
|
|
37
37
|
onEdit?: () => void;
|
|
38
38
|
onReply?: () => void;
|
|
39
39
|
onJumpToReply?: (reply: ReplyReference) => void;
|
|
40
|
-
replyAvailable?:
|
|
40
|
+
replyAvailable?: boolean;
|
|
41
41
|
isEditing?: boolean;
|
|
42
42
|
sessionId?: string;
|
|
43
43
|
providerId?: string;
|
|
@@ -80,7 +80,7 @@ export function UserBubble({ content, messageId, timestamp, projectName, onFork,
|
|
|
80
80
|
: "max-w-[80%] bg-accent-wash border-accent-wash-border text-text",
|
|
81
81
|
isEditing && "ring-2 ring-primary/60 border-primary/40",
|
|
82
82
|
)}>
|
|
83
|
-
{replyTo && <ReplyCard reply={replyTo} onJump={replyAvailable
|
|
83
|
+
{replyTo && <ReplyCard reply={replyTo} onJump={replyAvailable ? () => onJumpToReply?.(replyTo) : undefined} unavailable={!replyAvailable} />}
|
|
84
84
|
{/* System tags as badges */}
|
|
85
85
|
{tags.length > 0 && <SystemTagBadges tags={tags} />}
|
|
86
86
|
|
|
@@ -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}>
|
|
@@ -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,
|