@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
|
@@ -2062,6 +2062,7 @@ export async function runSupervisor(opts: {
|
|
|
2062
2062
|
log("INFO", `Supervisor started (PID: ${process.pid}, port: ${opts.port}, share: ${opts.share})`);
|
|
2063
2063
|
// Before any child is spawned: every child inherits these, the server included.
|
|
2064
2064
|
await logPriorityRaise("supervisor", (m) => log("WARN", m));
|
|
2065
|
+
(await import("./memory-diagnostics.ts")).startMemoryDiagnostics({ role: "supervisor" });
|
|
2065
2066
|
|
|
2066
2067
|
// ── Systemd self-heal: if the unit file is stale (e.g. still has
|
|
2067
2068
|
// Restart=on-failure), we were likely spawned by the old Bun.spawn()
|
|
@@ -2,6 +2,7 @@ import type { Subprocess, Terminal as BunTerminal } from "bun";
|
|
|
2
2
|
import { isUsableTerminalSize } from "../shared/terminal-size";
|
|
3
3
|
import { withoutAiChatMark } from "./ai-chat-env.ts";
|
|
4
4
|
import { createLogger } from "./logger.ts";
|
|
5
|
+
import { registerMemoryGauge } from "./memory-diagnostics.ts";
|
|
5
6
|
|
|
6
7
|
/** PTY lifecycle only — never what is typed into a terminal or what it prints. */
|
|
7
8
|
const log = createLogger("terminal");
|
|
@@ -397,3 +398,10 @@ export class TerminalService {
|
|
|
397
398
|
}
|
|
398
399
|
|
|
399
400
|
export const terminalService = new TerminalService();
|
|
401
|
+
|
|
402
|
+
registerMemoryGauge("terminal.sessions", () => terminalService["sessions"].size);
|
|
403
|
+
registerMemoryGauge("terminal.bufferedKB", () => {
|
|
404
|
+
let chars = 0;
|
|
405
|
+
for (const b of terminalService["outputBuffers"].values()) chars += b.length;
|
|
406
|
+
return Math.round(chars / 1024);
|
|
407
|
+
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type StripMode,
|
|
8
8
|
type TranscriptImageAudit,
|
|
9
9
|
} from "./transcript-images.ts";
|
|
10
|
+
import { readFileChunks } from "./file-chunks.ts";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* File-level image auditing and removal that never holds a transcript in memory.
|
|
@@ -50,19 +51,14 @@ async function forEachLine(
|
|
|
50
51
|
path: string,
|
|
51
52
|
onLine: (line: string, terminated: boolean) => void,
|
|
52
53
|
): Promise<void> {
|
|
53
|
-
const reader = Bun.file(path).stream().getReader();
|
|
54
54
|
const decoder = new TextDecoder();
|
|
55
55
|
let carry = "";
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for (const line of parts) onLine(line, true);
|
|
63
|
-
}
|
|
64
|
-
} finally {
|
|
65
|
-
reader.releaseLock();
|
|
56
|
+
// Not `Bun.file().stream()`, which keeps about the file's size in native memory per read
|
|
57
|
+
// (see `file-chunks.ts`) — and this runs over every transcript a cleanup visits.
|
|
58
|
+
for await (const value of readFileChunks(path)) {
|
|
59
|
+
const parts = (carry + decoder.decode(value, { stream: true })).split("\n");
|
|
60
|
+
carry = parts.pop() ?? "";
|
|
61
|
+
for (const line of parts) onLine(line, true);
|
|
66
62
|
}
|
|
67
63
|
if (carry) onLine(carry, false);
|
|
68
64
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The effort a Claude session actually runs at, which is not always the one picked in PPM.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code settles it in this order (measured against the bundled CLI's `get_settings`):
|
|
5
|
+
* `CLAUDE_CODE_EFFORT_LEVEL` beats the `--effort` PPM passes, whether it is in the environment
|
|
6
|
+
* or in the `env` block of a settings file, and a settings file beats the environment; a
|
|
7
|
+
* `maxEffortLevel` in the settings then clamps what is left; and a model without a level runs
|
|
8
|
+
* Extra and Max as High, or sends no effort at all. With
|
|
9
|
+
* `"env": { "CLAUDE_CODE_EFFORT_LEVEL": "max" }` in `~/.claude/settings.json`, every PPM chat
|
|
10
|
+
* ran at Max while the picker said High.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"] as const;
|
|
14
|
+
export type EffortLevel = (typeof EFFORT_LEVELS)[number];
|
|
15
|
+
|
|
16
|
+
export function isEffortLevel(value: unknown): value is EffortLevel {
|
|
17
|
+
return typeof value === "string" && (EFFORT_LEVELS as readonly string[]).includes(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function effortRank(level: EffortLevel): number {
|
|
21
|
+
return EFFORT_LEVELS.indexOf(level);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A settings file, as the CLI names its sources, or the environment PPM starts the CLI with. */
|
|
25
|
+
export type EffortSettingSource =
|
|
26
|
+
| "userSettings"
|
|
27
|
+
| "projectSettings"
|
|
28
|
+
| "localSettings"
|
|
29
|
+
| "flagSettings"
|
|
30
|
+
| "policySettings"
|
|
31
|
+
| "environment";
|
|
32
|
+
|
|
33
|
+
export interface EffortSetting<T> {
|
|
34
|
+
value: T;
|
|
35
|
+
source: EffortSettingSource;
|
|
36
|
+
/** The file it is written in, the way the user would look for it. */
|
|
37
|
+
file?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface EffortStatus {
|
|
41
|
+
/** `CLAUDE_CODE_EFFORT_LEVEL` as the CLI reads it. Absent when unset or a value the CLI ignores. */
|
|
42
|
+
env?: EffortSetting<string>;
|
|
43
|
+
/** The lowest `maxEffortLevel` the settings files set for the session's model. */
|
|
44
|
+
cap?: EffortSetting<EffortLevel>;
|
|
45
|
+
/**
|
|
46
|
+
* The running CLI's own answer: the effort it was started with and the one it sends. Only
|
|
47
|
+
* the CLI knows its organization's limits and what its model takes, so this outranks the rest.
|
|
48
|
+
*/
|
|
49
|
+
observed?: { requested: EffortLevel | null; applied: EffortLevel | null; model?: string };
|
|
50
|
+
/** Levels the model runs as themselves, [] when it takes none. Absent when the CLI did not say. */
|
|
51
|
+
supported?: EffortLevel[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type EffortEnvMeaning =
|
|
55
|
+
| { kind: "level"; level: EffortLevel }
|
|
56
|
+
/** `auto` or `unset`: no effort is sent and the model's default applies. */
|
|
57
|
+
| { kind: "auto" }
|
|
58
|
+
/** A number: a thinking budget, sent instead of an effort level. */
|
|
59
|
+
| { kind: "budget" };
|
|
60
|
+
|
|
61
|
+
/** What the CLI makes of `CLAUDE_CODE_EFFORT_LEVEL`, parsed as it parses it; null for a value it ignores. */
|
|
62
|
+
export function effortEnvMeaning(raw: string | null | undefined): EffortEnvMeaning | null {
|
|
63
|
+
if (!raw) return null;
|
|
64
|
+
const value = raw.toLowerCase();
|
|
65
|
+
if (value === "auto" || value === "unset") return { kind: "auto" };
|
|
66
|
+
const level = value === "med" ? "medium" : value;
|
|
67
|
+
if (isEffortLevel(level)) return { kind: "level", level };
|
|
68
|
+
return Number.isNaN(parseInt(value, 10)) ? null : { kind: "budget" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface EffortInEffect {
|
|
72
|
+
/** The level each request carries; null when none is sent, or when only the CLI can tell which. */
|
|
73
|
+
level: EffortLevel | null;
|
|
74
|
+
/** What decided it when that is not simply the pick. */
|
|
75
|
+
reason: "env" | "cap" | "model" | null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The effort a turn runs at given the level picked in PPM, and what overrides the pick if anything does. */
|
|
79
|
+
export function effortInEffect(pick: EffortLevel, status: EffortStatus | null | undefined): EffortInEffect {
|
|
80
|
+
if (!status) return { level: pick, reason: null };
|
|
81
|
+
const env = effortEnvMeaning(status.env?.value);
|
|
82
|
+
const { observed } = status;
|
|
83
|
+
if (env) {
|
|
84
|
+
// The variable outranks every pick, so the CLI's answer stays true whatever is picked since.
|
|
85
|
+
if (observed) return { level: observed.applied, reason: "env" };
|
|
86
|
+
return { level: envLevel(env, status), reason: "env" };
|
|
87
|
+
}
|
|
88
|
+
// A pick made after the CLI last answered is checked against the limits it reported.
|
|
89
|
+
const level = observed && observed.requested === pick ? observed.applied : withinLimits(pick, status);
|
|
90
|
+
if (level === pick) return { level, reason: null };
|
|
91
|
+
const capped = !!status.cap && effortRank(pick) > effortRank(status.cap.value);
|
|
92
|
+
return { level, reason: capped ? "cap" : "model" };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* What the CLI makes of the variable before it has said so itself. A settings cap changes it:
|
|
97
|
+
* a number is then read as High and `auto` sends the model's own default, each clamped to the cap,
|
|
98
|
+
* where without one neither sends an effort. That default differs by model (measured: Medium on
|
|
99
|
+
* Opus 5.5, High on Opus 4.5, 4.6 and Sonnet 4.6), so under any cap but Low, which leaves nothing
|
|
100
|
+
* lower, only the CLI's answer can say which level `auto` gives.
|
|
101
|
+
*/
|
|
102
|
+
function envLevel(env: EffortEnvMeaning, status: EffortStatus): EffortLevel | null {
|
|
103
|
+
if (env.kind === "level") return withinLimits(env.level, status);
|
|
104
|
+
if (!status.cap) return null;
|
|
105
|
+
if (env.kind === "budget") return withinLimits("high", status);
|
|
106
|
+
return status.cap.value === "low" ? withinLimits("low", status) : null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function withinLimits(level: EffortLevel, status: EffortStatus): EffortLevel | null {
|
|
110
|
+
if (status.supported?.length === 0) return null;
|
|
111
|
+
const capped = status.cap && effortRank(level) > effortRank(status.cap.value) ? status.cap.value : level;
|
|
112
|
+
// The CLI's own fallback for a model without Extra or Max.
|
|
113
|
+
const unsupported = status.supported && !status.supported.includes(capped);
|
|
114
|
+
return unsupported && (capped === "xhigh" || capped === "max") ? "high" : capped;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Where a setting lives, for a sentence that tells the user where to change it. */
|
|
118
|
+
export function effortSettingPlace(setting: EffortSetting<unknown>): string {
|
|
119
|
+
if (setting.file) return setting.file;
|
|
120
|
+
switch (setting.source) {
|
|
121
|
+
case "userSettings": return "~/.claude/settings.json";
|
|
122
|
+
case "projectSettings": return ".claude/settings.json";
|
|
123
|
+
case "localSettings": return ".claude/settings.local.json";
|
|
124
|
+
case "policySettings": return "managed settings";
|
|
125
|
+
case "flagSettings": return "the settings PPM passes to Claude Code";
|
|
126
|
+
case "environment": return "the environment PPM runs in";
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/types/api.ts
CHANGED
|
@@ -77,7 +77,9 @@ export type ChatWsServerMessage =
|
|
|
77
77
|
| { type: "error"; message: string }
|
|
78
78
|
| { type: "account_info"; accountId: string; accountLabel: string }
|
|
79
79
|
| { type: "phase_changed"; phase: SessionPhase; elapsed?: number }
|
|
80
|
-
| { type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: { requestId: string; tool: string; input: unknown } | null; sessionTitle: string | null; model?: string; effort?: string; thinking?: boolean; turnStop?: import("../shared/turn-stop").TurnStop | null }
|
|
80
|
+
| { type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: { requestId: string; tool: string; input: unknown } | null; sessionTitle: string | null; model?: string; effort?: string; thinking?: boolean; effortStatus?: import("../shared/effort-status").EffortStatus | null; turnStop?: import("../shared/turn-stop").TurnStop | null }
|
|
81
|
+
/** What the session's CLI said about its effort when the turn started. */
|
|
82
|
+
| { type: "effort_status"; status: import("../shared/effort-status").EffortStatus }
|
|
81
83
|
/** The turn that just ended was ended by an error. Sent just before its `done`. */
|
|
82
84
|
| { type: "turn_stop"; stop: import("../shared/turn-stop").TurnStop }
|
|
83
85
|
| { type: "turn_events"; events: unknown[]; streamSeq?: number; truncated?: boolean }
|
package/src/types/chat.ts
CHANGED
|
@@ -106,6 +106,14 @@ export interface AIProvider {
|
|
|
106
106
|
getUsage?(sessionId?: string, pickedAccountId?: string): Promise<UsageInfo>;
|
|
107
107
|
/** True when a live streaming subprocess exists for this session */
|
|
108
108
|
hasStreamingSession?(sessionId: string): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* The effort the session's live subprocess sends and what overrides the pick there; null
|
|
111
|
+
* when none is running or it cannot say. Only providers whose effort can be overridden
|
|
112
|
+
* outside PPM implement it.
|
|
113
|
+
*/
|
|
114
|
+
readEffortStatus?(sessionId: string): Promise<import("../shared/effort-status").EffortStatus | null>;
|
|
115
|
+
/** The same overrides, worked out from settings files for a session nothing has started yet. */
|
|
116
|
+
estimateEffortStatus?(projectPath: string | undefined, model?: string): import("../shared/effort-status").EffortStatus;
|
|
109
117
|
/** Prompt-cache lifetime for this session, in ms — how long holding its subprocess pays. */
|
|
110
118
|
promptCacheTtlMs?(sessionId: string): number;
|
|
111
119
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { ChevronUp, ChevronDown, ChevronsDown, ChevronsUpDown } from "@/lib/icons";
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
@@ -52,14 +52,18 @@ export function ChatScrollNav({ scrollElement, userIndices, scrollToBottom }: {
|
|
|
52
52
|
const rowTop = (el: HTMLElement, row: HTMLElement, elTop: number) =>
|
|
53
53
|
row.getBoundingClientRect().top - elTop + el.scrollTop;
|
|
54
54
|
|
|
55
|
-
const userRows = useCallback((el: HTMLElement) =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
const userRows = useCallback((el: HTMLElement) => {
|
|
56
|
+
const users = new Set(userIndices);
|
|
57
|
+
return Array.from(el.querySelectorAll<HTMLElement>("[data-msg-index]"))
|
|
58
|
+
.filter((r) => users.has(Number(r.dataset.msgIndex)));
|
|
59
|
+
}, [userIndices]);
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
// Followed only while the cluster is open, since nothing at rest shows it. A streaming answer
|
|
62
|
+
// scrolls the transcript on every flush, and each scroll measured every user row; a layout
|
|
63
|
+
// effect so opening shows the real position, not the last one.
|
|
64
|
+
useLayoutEffect(() => {
|
|
61
65
|
const el = scrollElement;
|
|
62
|
-
if (!el) return;
|
|
66
|
+
if (!el || !open) return;
|
|
63
67
|
let raf = 0;
|
|
64
68
|
const recompute = () => {
|
|
65
69
|
const top = el.scrollTop;
|
|
@@ -91,7 +95,7 @@ export function ChatScrollNav({ scrollElement, userIndices, scrollToBottom }: {
|
|
|
91
95
|
el.removeEventListener("scroll", schedule);
|
|
92
96
|
ro.disconnect();
|
|
93
97
|
};
|
|
94
|
-
}, [scrollElement, userRows]);
|
|
98
|
+
}, [scrollElement, userRows, open]);
|
|
95
99
|
|
|
96
100
|
// Auto-collapse. Restarts on every jump so a burst of navigation keeps it open.
|
|
97
101
|
useEffect(() => {
|
|
@@ -9,6 +9,7 @@ import { splitAttachmentMarkers } from "@/lib/attachment-marker-split";
|
|
|
9
9
|
import type { ChatAttemptEvent } from "@/lib/chat-attempt-lifecycle";
|
|
10
10
|
import { useChat } from "@/hooks/use-chat";
|
|
11
11
|
import { useChatPrewarm } from "@/hooks/use-chat-prewarm";
|
|
12
|
+
import { useEffortStatus } from "@/hooks/use-effort-status";
|
|
12
13
|
import { useUsage } from "@/hooks/use-usage";
|
|
13
14
|
import { useDesignSessionRedirect } from "@/hooks/use-design-session-redirect";
|
|
14
15
|
import { useTabStore } from "@/stores/tab-store";
|
|
@@ -26,9 +27,8 @@ import { AgentSessionProvider, normalizeProviderId } from "./agent-session-conte
|
|
|
26
27
|
import { McpSignInBar } from "@/components/mcp-auth/mcp-sign-in-bar";
|
|
27
28
|
import { SessionChangesBar } from "./session-changes-bar";
|
|
28
29
|
import { CONTINUE_AFTER_STOP, TurnStopBar } from "./turn-stop-bar";
|
|
29
|
-
import { SessionChangesContext,
|
|
30
|
+
import { SessionChangesContext, useSessionChangesValue } from "./session-changes-context";
|
|
30
31
|
import { useSessionFileChanges } from "@/hooks/use-session-file-changes";
|
|
31
|
-
import { openSessionReview } from "@/lib/session-file-changes-client";
|
|
32
32
|
import { useTeamActivityFeed } from "@/hooks/use-team-activity-feed";
|
|
33
33
|
import { MessageInput, type ChatAttachment, type MessagePriority } from "./message-input";
|
|
34
34
|
import { SlashCommandPicker, type SlashItem } from "./slash-command-picker";
|
|
@@ -458,6 +458,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
458
458
|
setModel,
|
|
459
459
|
effort,
|
|
460
460
|
setEffort,
|
|
461
|
+
effortStatus: sessionEffortStatus,
|
|
461
462
|
thinking,
|
|
462
463
|
setThinking,
|
|
463
464
|
turnSettings,
|
|
@@ -481,17 +482,19 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
481
482
|
refetchMessages();
|
|
482
483
|
}, [isConnected, reconnect, refetchMessages]);
|
|
483
484
|
|
|
485
|
+
const effortStatus = useEffortStatus({
|
|
486
|
+
projectName,
|
|
487
|
+
// Not while the provider is still a guess: the answer would be about another provider.
|
|
488
|
+
providerId: preparation.pending ? undefined : preparation.providerId ?? providerId,
|
|
489
|
+
model,
|
|
490
|
+
sessionStatus: sessionEffortStatus,
|
|
491
|
+
});
|
|
492
|
+
|
|
484
493
|
const sessionChanges = useSessionFileChanges({ projectName, sessionId, messages, isStreaming });
|
|
485
|
-
const openReview = useCallback((path?: string) => {
|
|
486
|
-
if (!sessionId) return;
|
|
487
|
-
openSessionReview({ projectName, sessionId, title: sessionTitle || undefined, providerId, paths: sessionChanges.paths, selectPath: path });
|
|
488
|
-
}, [projectName, sessionId, sessionTitle, providerId, sessionChanges.paths]);
|
|
489
494
|
// Each turn's change tray answers the same list the bar shows.
|
|
490
|
-
const
|
|
491
|
-
projectName
|
|
492
|
-
|
|
493
|
-
: null
|
|
494
|
-
), [projectName, sessionId, sessionChanges.files, sessionChanges.refresh, openReview]);
|
|
495
|
+
const { value: sessionChangesValue, openReview } = useSessionChangesValue({
|
|
496
|
+
projectName, sessionId, title: sessionTitle || undefined, providerId, changes: sessionChanges,
|
|
497
|
+
});
|
|
495
498
|
|
|
496
499
|
// `model`/`effort`/`thinking` are what change when a pick does; the picks sent are read
|
|
497
500
|
// through `turnSettings`, which leaves out whatever the user has not chosen.
|
|
@@ -1492,6 +1495,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
1492
1495
|
model={model}
|
|
1493
1496
|
onModelChange={!preparation?.pending && !firstSendPending ? setModel : undefined}
|
|
1494
1497
|
effort={effort}
|
|
1498
|
+
effortStatus={effortStatus}
|
|
1495
1499
|
onEffortChange={setEffort}
|
|
1496
1500
|
thinking={thinking}
|
|
1497
1501
|
onThinkingChange={setThinking}
|
|
@@ -3,16 +3,33 @@ import { Copy, Check } from "@/lib/icons";
|
|
|
3
3
|
import { cn } from "@/lib/utils";
|
|
4
4
|
import { formatRelativeDate } from "@/lib/format-date";
|
|
5
5
|
import { copyToClipboard } from "@/lib/clipboard";
|
|
6
|
+
import { useRelativeTimeTick } from "@/hooks/use-relative-time-tick";
|
|
7
|
+
|
|
8
|
+
// The fields `toLocaleString()` prints, in one formatter: a long chat draws hundreds of these.
|
|
9
|
+
let fullDateFormat: Intl.DateTimeFormat | undefined;
|
|
6
10
|
|
|
7
11
|
/** Full timestamp for the relative-time tooltip. */
|
|
8
12
|
function fullDate(iso: string): string {
|
|
9
13
|
try {
|
|
10
|
-
|
|
14
|
+
fullDateFormat ??= new Intl.DateTimeFormat(undefined, {
|
|
15
|
+
year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric",
|
|
16
|
+
});
|
|
17
|
+
return fullDateFormat.format(new Date(iso));
|
|
11
18
|
} catch {
|
|
12
19
|
return "";
|
|
13
20
|
}
|
|
14
21
|
}
|
|
15
22
|
|
|
23
|
+
/** The relative time, kept current on its own so the message around it is never redrawn for it. */
|
|
24
|
+
function RelativeTime({ timestamp }: { timestamp: string }) {
|
|
25
|
+
useRelativeTimeTick();
|
|
26
|
+
return (
|
|
27
|
+
<time title={fullDate(timestamp)} className="pr-1 tabular-nums">
|
|
28
|
+
{formatRelativeDate(timestamp)}
|
|
29
|
+
</time>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
16
33
|
/**
|
|
17
34
|
* Ghost-pill action button (icon + label) for the message action bar.
|
|
18
35
|
* 44px min touch target on mobile; tighter on desktop. Always visible.
|
|
@@ -84,9 +101,7 @@ export function MessageActionBar({
|
|
|
84
101
|
className,
|
|
85
102
|
)}
|
|
86
103
|
>
|
|
87
|
-
<
|
|
88
|
-
{formatRelativeDate(timestamp)}
|
|
89
|
-
</time>
|
|
104
|
+
<RelativeTime timestamp={timestamp} />
|
|
90
105
|
<CopyButton content={content} />
|
|
91
106
|
{children}
|
|
92
107
|
</div>
|
|
@@ -27,6 +27,7 @@ import { ReplyCard } from "./reply-card";
|
|
|
27
27
|
import type { ReplyReference } from "../../../shared/chat-reply";
|
|
28
28
|
import { PromptCacheChip } from "./prompt-cache-chip";
|
|
29
29
|
import type { PromptCacheState } from "../../../shared/prompt-cache-idle";
|
|
30
|
+
import type { EffortStatus } from "../../../shared/effort-status";
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Base64 payload for an image file, or undefined when it cannot be read.
|
|
@@ -144,6 +145,8 @@ interface MessageInputProps {
|
|
|
144
145
|
onModelChange?: (model: string) => void;
|
|
145
146
|
/** Current per-session effort (null = provider default) */
|
|
146
147
|
effort?: string | null;
|
|
148
|
+
/** What overrides the effort pick outside PPM (CLAUDE_CODE_EFFORT_LEVEL, a settings cap, the model) */
|
|
149
|
+
effortStatus?: EffortStatus | null;
|
|
147
150
|
/** Effort change handler */
|
|
148
151
|
onEffortChange?: (effort: string) => void;
|
|
149
152
|
/** Current per-session thinking on/off */
|
|
@@ -191,6 +194,7 @@ export const MessageInput = memo(function MessageInput({
|
|
|
191
194
|
model,
|
|
192
195
|
onModelChange,
|
|
193
196
|
effort,
|
|
197
|
+
effortStatus,
|
|
194
198
|
onEffortChange,
|
|
195
199
|
thinking,
|
|
196
200
|
onThinkingChange,
|
|
@@ -1048,6 +1052,7 @@ export const MessageInput = memo(function MessageInput({
|
|
|
1048
1052
|
<ModelThinkingSelector
|
|
1049
1053
|
model={model ?? null}
|
|
1050
1054
|
effort={effort ?? null}
|
|
1055
|
+
effortStatus={effortStatus}
|
|
1051
1056
|
thinking={thinking ?? false}
|
|
1052
1057
|
onModelChange={onModelChange}
|
|
1053
1058
|
onEffortChange={onEffortChange ?? (() => {})}
|
|
@@ -1152,6 +1157,7 @@ export const MessageInput = memo(function MessageInput({
|
|
|
1152
1157
|
<ModelThinkingSelector
|
|
1153
1158
|
model={model ?? null}
|
|
1154
1159
|
effort={effort ?? null}
|
|
1160
|
+
effortStatus={effortStatus}
|
|
1155
1161
|
thinking={thinking ?? false}
|
|
1156
1162
|
onModelChange={onModelChange}
|
|
1157
1163
|
onEffortChange={onEffortChange ?? (() => {})}
|
|
@@ -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
|
|