@hienlh/ppm 0.23.15 → 0.23.16
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 +7 -0
- package/CLAUDE.md +1 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{accounts-settings-section-B5YXy0vd.js → accounts-settings-section-DhzuFkDO.js} +1 -1
- package/dist/web/assets/accounts-settings-section-DhzuFkDO.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-DhzuFkDO.js.gz +0 -0
- package/dist/web/assets/{agent-session-sheet-Bj42PwCK.js → agent-session-sheet-CZAmOwSU.js} +2 -2
- package/dist/web/assets/agent-session-sheet-CZAmOwSU.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-CZAmOwSU.js.gz +0 -0
- package/dist/web/assets/{agent-session-window-content-LqL6fVPU.js → agent-session-window-content-CwokWtJq.js} +1 -1
- package/dist/web/assets/agent-session-window-content-CwokWtJq.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-CwokWtJq.js.gz +0 -0
- package/dist/web/assets/{ai-resource-editor-jVv2nvAG.js → ai-resource-editor-CT1JCaRQ.js} +1 -1
- package/dist/web/assets/ai-resource-editor-CT1JCaRQ.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-CT1JCaRQ.js.gz +0 -0
- package/dist/web/assets/{ai-settings-section-CN5BR1ZZ.js → ai-settings-section-CBlS1FZK.js} +1 -1
- package/dist/web/assets/{ai-settings-section-C9Q-v8fY.js → ai-settings-section-Ct9-lNqA.js} +1 -1
- package/dist/web/assets/ai-settings-section-Ct9-lNqA.js.br +0 -0
- package/dist/web/assets/ai-settings-section-Ct9-lNqA.js.gz +0 -0
- package/dist/web/assets/{android-tab-CwqiuEZP.js → android-tab-CUPQN2M4.js} +1 -1
- package/dist/web/assets/android-tab-CUPQN2M4.js.br +0 -0
- package/dist/web/assets/android-tab-CUPQN2M4.js.gz +0 -0
- package/dist/web/assets/{appearance-settings-section-D0JIfiRx.js → appearance-settings-section-DpYcNqXH.js} +1 -1
- package/dist/web/assets/appearance-settings-section-DpYcNqXH.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-DpYcNqXH.js.gz +0 -0
- package/dist/web/assets/{apps-panel-Cz_KJ37M.js → apps-panel-BqF_CkKy.js} +1 -1
- package/dist/web/assets/apps-panel-BqF_CkKy.js.br +0 -0
- package/dist/web/assets/apps-panel-BqF_CkKy.js.gz +0 -0
- package/dist/web/assets/{branch-review-tab-C3QNpsm7.js → branch-review-tab-BEZqFSlw.js} +1 -1
- package/dist/web/assets/branch-review-tab-BEZqFSlw.js.br +0 -0
- package/dist/web/assets/branch-review-tab-BEZqFSlw.js.gz +0 -0
- package/dist/web/assets/{chat-tab-D14CUl0J.js → chat-tab-N1B-Yv0P.js} +1 -1
- package/dist/web/assets/chat-tab-N1B-Yv0P.js.br +0 -0
- package/dist/web/assets/chat-tab-N1B-Yv0P.js.gz +0 -0
- package/dist/web/assets/chat-tab-kk-mAF05.js +16 -0
- package/dist/web/assets/chat-tab-kk-mAF05.js.br +0 -0
- package/dist/web/assets/chat-tab-kk-mAF05.js.gz +0 -0
- package/dist/web/assets/{code-editor-B7gbH4-x.js → code-editor-d1CXl2WL.js} +4 -4
- package/dist/web/assets/code-editor-d1CXl2WL.js.br +0 -0
- package/dist/web/assets/code-editor-d1CXl2WL.js.gz +0 -0
- package/dist/web/assets/{codex-reset-credit-button-DQDtyvnd.js → codex-reset-credit-button-CwSSBK6P.js} +1 -1
- package/dist/web/assets/codex-reset-credit-button-CwSSBK6P.js.br +0 -0
- package/dist/web/assets/{codex-reset-credit-button-DQDtyvnd.js.gz → codex-reset-credit-button-CwSSBK6P.js.gz} +0 -0
- package/dist/web/assets/{conflict-editor-ktOkrODA.js → conflict-editor-BucKz-uE.js} +1 -1
- package/dist/web/assets/conflict-editor-BucKz-uE.js.br +0 -0
- package/dist/web/assets/conflict-editor-BucKz-uE.js.gz +0 -0
- package/dist/web/assets/{connection-form-tab-CxvExR7T.js → connection-form-tab-DIc25Bko.js} +1 -1
- package/dist/web/assets/connection-form-tab-DIc25Bko.js.br +0 -0
- package/dist/web/assets/connection-form-tab-DIc25Bko.js.gz +0 -0
- package/dist/web/assets/{csv-preview-CBDMTEsF.js → csv-preview-CqDaGV5N.js} +1 -1
- package/dist/web/assets/csv-preview-CqDaGV5N.js.br +0 -0
- package/dist/web/assets/csv-preview-CqDaGV5N.js.gz +0 -0
- package/dist/web/assets/{db-tab-parts-BgVpKbi6.js → db-tab-parts-lp8ROGQB.js} +1 -1
- package/dist/web/assets/db-tab-parts-lp8ROGQB.js.br +0 -0
- package/dist/web/assets/db-tab-parts-lp8ROGQB.js.gz +0 -0
- package/dist/web/assets/{design-settings-section-DPGw61pY.js → design-settings-section-DxWEBH1L.js} +1 -1
- package/dist/web/assets/design-settings-section-DxWEBH1L.js.br +0 -0
- package/dist/web/assets/design-settings-section-DxWEBH1L.js.gz +0 -0
- package/dist/web/assets/{design-tab-BiCqHNl3.js → design-tab-Cl0CMAUx.js} +1 -1
- package/dist/web/assets/design-tab-Cl0CMAUx.js.br +0 -0
- package/dist/web/assets/design-tab-Cl0CMAUx.js.gz +0 -0
- package/dist/web/assets/{designs-sidebar-panel-pcJ2Rx5G.js → designs-sidebar-panel-aCr-EGfj.js} +1 -1
- package/dist/web/assets/designs-sidebar-panel-aCr-EGfj.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-aCr-EGfj.js.gz +0 -0
- package/dist/web/assets/{diff-viewer-CLAvyqVG.js → diff-viewer-XyJgWKwe.js} +1 -1
- package/dist/web/assets/diff-viewer-XyJgWKwe.js.br +0 -0
- package/dist/web/assets/diff-viewer-XyJgWKwe.js.gz +0 -0
- package/dist/web/assets/{docx-preview-XSkD2IL6.js → docx-preview-KJaj2YGN.js} +1 -1
- package/dist/web/assets/docx-preview-KJaj2YGN.js.br +0 -0
- package/dist/web/assets/docx-preview-KJaj2YGN.js.gz +0 -0
- package/dist/web/assets/{editor-dialog-cQvqVZAR.js → editor-dialog-9tW6c9mu.js} +1 -1
- package/dist/web/assets/editor-dialog-9tW6c9mu.js.br +0 -0
- package/dist/web/assets/editor-dialog-9tW6c9mu.js.gz +0 -0
- package/dist/web/assets/{editor-language-service-6-7mS6_9.js → editor-language-service-CkdO9ih8.js} +1 -1
- package/dist/web/assets/editor-language-service-CkdO9ih8.js.br +0 -0
- package/dist/web/assets/editor-language-service-CkdO9ih8.js.gz +0 -0
- package/dist/web/assets/{explorer-body-BlGDPmNa.js → explorer-body-B8AkNO5o.js} +1 -1
- package/dist/web/assets/explorer-body-B8AkNO5o.js.br +0 -0
- package/dist/web/assets/explorer-body-B8AkNO5o.js.gz +0 -0
- package/dist/web/assets/{explorer-window-content-BLWY8GJ_.js → explorer-window-content-D174CXKz.js} +1 -1
- package/dist/web/assets/explorer-window-content-D174CXKz.js.br +0 -0
- package/dist/web/assets/explorer-window-content-D174CXKz.js.gz +0 -0
- package/dist/web/assets/{extension-manager-section-NSEt5f4z.js → extension-manager-section-CQsqXsPF.js} +1 -1
- package/dist/web/assets/extension-manager-section-CQsqXsPF.js.br +0 -0
- package/dist/web/assets/extension-manager-section-CQsqXsPF.js.gz +0 -0
- package/dist/web/assets/{extension-webview-BkAEBWin.js → extension-webview-AfaxC7PL.js} +1 -1
- package/dist/web/assets/extension-webview-AfaxC7PL.js.br +0 -0
- package/dist/web/assets/extension-webview-AfaxC7PL.js.gz +0 -0
- package/dist/web/assets/{files-settings-section-Bq7liJ_r.js → files-settings-section-CC0aJryq.js} +1 -1
- package/dist/web/assets/files-settings-section-CC0aJryq.js.br +0 -0
- package/dist/web/assets/files-settings-section-CC0aJryq.js.gz +0 -0
- package/dist/web/assets/{general-settings-section-DvHROAMa.js → general-settings-section-DQTr20G_.js} +1 -1
- package/dist/web/assets/general-settings-section-DQTr20G_.js.br +0 -0
- package/dist/web/assets/general-settings-section-DQTr20G_.js.gz +0 -0
- package/dist/web/assets/{git-log-panel-Cu9kK48H.js → git-log-panel-CisEJPJz.js} +1 -1
- package/dist/web/assets/git-log-panel-CisEJPJz.js.br +0 -0
- package/dist/web/assets/git-log-panel-CisEJPJz.js.gz +0 -0
- package/dist/web/assets/{git-review-tab-B_JT649W.js → git-review-tab-DpWEy03w.js} +1 -1
- package/dist/web/assets/git-review-tab-DpWEy03w.js.br +0 -0
- package/dist/web/assets/git-review-tab-DpWEy03w.js.gz +0 -0
- package/dist/web/assets/{glide-data-grid-BlposOph.js → glide-data-grid-hOML-uXP.js} +1 -1
- package/dist/web/assets/glide-data-grid-hOML-uXP.js.br +0 -0
- package/dist/web/assets/glide-data-grid-hOML-uXP.js.gz +0 -0
- package/dist/web/assets/{grid-save-dialog-B0d0qhnN.js → grid-save-dialog-Bx61akzH.js} +1 -1
- package/dist/web/assets/grid-save-dialog-Bx61akzH.js.br +0 -0
- package/dist/web/assets/grid-save-dialog-Bx61akzH.js.gz +0 -0
- package/dist/web/assets/{group-chat-tab-jirUsGm-.js → group-chat-tab-eQdJHa8d.js} +1 -1
- package/dist/web/assets/group-chat-tab-eQdJHa8d.js.br +0 -0
- package/dist/web/assets/group-chat-tab-eQdJHa8d.js.gz +0 -0
- package/dist/web/assets/{impexp-tab-HJVcOTYJ.js → impexp-tab-BjIDxYTj.js} +1 -1
- package/dist/web/assets/impexp-tab-BjIDxYTj.js.br +0 -0
- package/dist/web/assets/impexp-tab-BjIDxYTj.js.gz +0 -0
- package/dist/web/assets/index-BACBJm4i.css +1 -0
- package/dist/web/assets/index-BACBJm4i.css.br +0 -0
- package/dist/web/assets/index-BACBJm4i.css.gz +0 -0
- package/dist/web/assets/{index-DLB3j_P0.js → index-COwBji1t.js} +5 -5
- package/dist/web/assets/index-COwBji1t.js.br +0 -0
- package/dist/web/assets/index-COwBji1t.js.gz +0 -0
- package/dist/web/assets/{jira-watcher-section-EQvdnETB.js → jira-watcher-section-dzpmNZA2.js} +1 -1
- package/dist/web/assets/{keybindings-store-CEy-Sdcc.js → keybindings-store-DRjk8512.js} +1 -1
- package/dist/web/assets/{keyboard-shortcuts-section-DN21gB-T.js → keyboard-shortcuts-section-BhyalK8A.js} +1 -1
- package/dist/web/assets/keyboard-shortcuts-section-BhyalK8A.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-BhyalK8A.js.gz +0 -0
- package/dist/web/assets/{language-servers-section-DOcZ72FJ.js → language-servers-section-BXLaicUY.js} +2 -2
- package/dist/web/assets/language-servers-section-BXLaicUY.js.br +0 -0
- package/dist/web/assets/language-servers-section-BXLaicUY.js.gz +0 -0
- package/dist/web/assets/{logs-app-C7n9GSCn.js → logs-app-BnjU6CRy.js} +1 -1
- package/dist/web/assets/logs-app-BnjU6CRy.js.br +0 -0
- package/dist/web/assets/{logs-app-C7n9GSCn.js.gz → logs-app-BnjU6CRy.js.gz} +0 -0
- package/dist/web/assets/{logs-tab-CSOiexvx.js → logs-tab-7uyD7gKW.js} +1 -1
- package/dist/web/assets/{logs-window-content-C4Qs6-0q.js → logs-window-content-B78a6oM2.js} +1 -1
- package/dist/web/assets/{lsp-client-DpYmPhXX.js → lsp-client-DOHnxEUE.js} +1 -1
- package/dist/web/assets/lsp-client-DOHnxEUE.js.br +0 -0
- package/dist/web/assets/lsp-client-DOHnxEUE.js.gz +0 -0
- package/dist/web/assets/{markdown-renderer-Df3r-CCb.js → markdown-renderer-B_hL6lRL.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CtEl6qAt.js → markdown-renderer-Dz0ZcxNo.js} +1 -1
- package/dist/web/assets/markdown-renderer-Dz0ZcxNo.js.br +0 -0
- package/dist/web/assets/markdown-renderer-Dz0ZcxNo.js.gz +0 -0
- package/dist/web/assets/{mobile-explorer-sheet-B-rcKEL9.js → mobile-explorer-sheet-C_3dq99D.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-C_3dq99D.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-C_3dq99D.js.gz +0 -0
- package/dist/web/assets/{new-design-dialog-DgztQK5R.js → new-design-dialog-CaubNbMT.js} +1 -1
- package/dist/web/assets/new-design-dialog-CaubNbMT.js.br +0 -0
- package/dist/web/assets/new-design-dialog-CaubNbMT.js.gz +0 -0
- package/dist/web/assets/{notification-store-B92dvP_u.js → notification-store-BTk3PZAj.js} +1 -1
- package/dist/web/assets/{notifications-settings-section-JiGvaCFx.js → notifications-settings-section-CJJ1l5cL.js} +1 -1
- package/dist/web/assets/notifications-settings-section-CJJ1l5cL.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-CJJ1l5cL.js.gz +0 -0
- package/dist/web/assets/{ppmbot-settings-section-XfrZ8VoC.js → ppmbot-settings-section-BFniAm3D.js} +1 -1
- package/dist/web/assets/ppmbot-settings-section-BFniAm3D.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-BFniAm3D.js.gz +0 -0
- package/dist/web/assets/{problems-panel-CDzwSJn6.js → problems-panel-CIxaCYJw.js} +1 -1
- package/dist/web/assets/problems-panel-CIxaCYJw.js.br +0 -0
- package/dist/web/assets/problems-panel-CIxaCYJw.js.gz +0 -0
- package/dist/web/assets/{process-table-C_B39GHm.js → process-table-l1fVcESX.js} +1 -1
- package/dist/web/assets/process-table-l1fVcESX.js.br +0 -0
- package/dist/web/assets/process-table-l1fVcESX.js.gz +0 -0
- package/dist/web/assets/{proxy-settings-section-CvaOvYIv.js → proxy-settings-section-Di5nBuy6.js} +1 -1
- package/dist/web/assets/proxy-settings-section-Di5nBuy6.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-Di5nBuy6.js.gz +0 -0
- package/dist/web/assets/{query-tab-B8aMpAbI.js → query-tab-hyKnRZr3.js} +1 -1
- package/dist/web/assets/query-tab-hyKnRZr3.js.br +0 -0
- package/dist/web/assets/query-tab-hyKnRZr3.js.gz +0 -0
- package/dist/web/assets/{remote-access-settings-section-Bv0eawR4.js → remote-access-settings-section-HpZAP0Al.js} +1 -1
- package/dist/web/assets/remote-access-settings-section-HpZAP0Al.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-HpZAP0Al.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-clipboard-notice-BpDakRY0.js → remote-desktop-clipboard-notice-DY7tWgjf.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-DY7tWgjf.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-DY7tWgjf.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-mobile-sheet-C5GQ3kzY.js → remote-desktop-mobile-sheet-CX9mDRle.js} +2 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-CX9mDRle.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-CX9mDRle.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-mobile-view-CZb5EMEK.js → remote-desktop-mobile-view-DNg0EpbV.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-view-DNg0EpbV.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DNg0EpbV.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-CipNtLX5.js → remote-desktop-readiness-gate-BTTLlVDc.js} +1 -1
- package/dist/web/assets/remote-desktop-readiness-gate-BTTLlVDc.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BTTLlVDc.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-window-content-B7OXXvAM.js → remote-desktop-window-content-CqxnhSu0.js} +1 -1
- package/dist/web/assets/remote-desktop-window-content-CqxnhSu0.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-CqxnhSu0.js.gz +0 -0
- package/dist/web/assets/{rename-object-dialog-B4W6S7Xg.js → rename-object-dialog-CnhMsNbT.js} +1 -1
- package/dist/web/assets/rename-object-dialog-CnhMsNbT.js.br +0 -0
- package/dist/web/assets/rename-object-dialog-CnhMsNbT.js.gz +0 -0
- package/dist/web/assets/{review-code-B4osj0k1.js → review-code-CunYhmcC.js} +1 -1
- package/dist/web/assets/review-code-CunYhmcC.js.br +0 -0
- package/dist/web/assets/review-code-CunYhmcC.js.gz +0 -0
- package/dist/web/assets/{save-as-dialog-DpXv_f1d.js → save-as-dialog-BtGSwTKZ.js} +1 -1
- package/dist/web/assets/save-as-dialog-BtGSwTKZ.js.br +0 -0
- package/dist/web/assets/save-as-dialog-BtGSwTKZ.js.gz +0 -0
- package/dist/web/assets/{schedules-settings-section-BbaPbc28.js → schedules-settings-section-CvlcMyEz.js} +1 -1
- package/dist/web/assets/schedules-settings-section-CvlcMyEz.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-CvlcMyEz.js.gz +0 -0
- package/dist/web/assets/{session-review-tab-Aq3FEooH.js → session-review-tab-C3HuIPD2.js} +1 -1
- package/dist/web/assets/session-review-tab-C3HuIPD2.js.br +0 -0
- package/dist/web/assets/session-review-tab-C3HuIPD2.js.gz +0 -0
- package/dist/web/assets/{settings-body-vXbOWchN.js → settings-body-DCHEAh2_.js} +1 -1
- package/dist/web/assets/settings-body-DCHEAh2_.js.br +0 -0
- package/dist/web/assets/settings-body-DCHEAh2_.js.gz +0 -0
- package/dist/web/assets/{settings-rows-DD7Gxmst.js → settings-rows-D8Y9o1ZD.js} +1 -1
- package/dist/web/assets/settings-rows-D8Y9o1ZD.js.br +0 -0
- package/dist/web/assets/settings-rows-D8Y9o1ZD.js.gz +0 -0
- package/dist/web/assets/settings-section-content-BXPg2H0N.js +1 -0
- package/dist/web/assets/{settings-section-content-DkUJFop6.js → settings-section-content-Dluhkh-u.js} +2 -2
- package/dist/web/assets/settings-section-content-Dluhkh-u.js.br +0 -0
- package/dist/web/assets/settings-section-content-Dluhkh-u.js.gz +0 -0
- package/dist/web/assets/{settings-tab-Cdv9_OIe.js → settings-tab-Cj1GkYi3.js} +1 -1
- package/dist/web/assets/{settings-window-content-DLa6Ph4u.js → settings-window-content-1fweMynB.js} +1 -1
- package/dist/web/assets/{slash-command-picker-lILYDfK3.js → slash-command-picker-BdKd_AiS.js} +1 -1
- package/dist/web/assets/slash-command-picker-BdKd_AiS.js.br +0 -0
- package/dist/web/assets/slash-command-picker-BdKd_AiS.js.gz +0 -0
- package/dist/web/assets/{sql-object-tab-Da42SMK5.js → sql-object-tab-DkdF_7Jz.js} +1 -1
- package/dist/web/assets/sql-object-tab-DkdF_7Jz.js.br +0 -0
- package/dist/web/assets/sql-object-tab-DkdF_7Jz.js.gz +0 -0
- package/dist/web/assets/{sqlite-viewer-V3SKNukY.js → sqlite-viewer-BoQMjJHj.js} +1 -1
- package/dist/web/assets/sqlite-viewer-BoQMjJHj.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-BoQMjJHj.js.gz +0 -0
- package/dist/web/assets/{structure-save-dialog-B635iyPI.js → structure-save-dialog-DGHxHJ4T.js} +1 -1
- package/dist/web/assets/structure-save-dialog-DGHxHJ4T.js.br +0 -0
- package/dist/web/assets/structure-save-dialog-DGHxHJ4T.js.gz +0 -0
- package/dist/web/assets/{structure-tab-Co9d8ktU.js → structure-tab-DuuX1e9c.js} +1 -1
- package/dist/web/assets/structure-tab-DuuX1e9c.js.br +0 -0
- package/dist/web/assets/structure-tab-DuuX1e9c.js.gz +0 -0
- package/dist/web/assets/{system-monitor-body-DC0j8RqP.js → system-monitor-body-CZVoh652.js} +2 -2
- package/dist/web/assets/system-monitor-body-CZVoh652.js.br +0 -0
- package/dist/web/assets/system-monitor-body-CZVoh652.js.gz +0 -0
- package/dist/web/assets/{system-monitor-tab-DA8BcdXo.js → system-monitor-tab-eGta1jpw.js} +1 -1
- package/dist/web/assets/{system-monitor-window-content-BiuLo8XW.js → system-monitor-window-content-BUG72ffC.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-Crvt1r5F.js → tab-host-window-content-BTYuY43x.js} +1 -1
- package/dist/web/assets/tab-host-window-content-BTYuY43x.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-BTYuY43x.js.gz +0 -0
- package/dist/web/assets/{table-tab-C2cYw7a0.js → table-tab-BsfgtqFE.js} +1 -1
- package/dist/web/assets/table-tab-BsfgtqFE.js.br +0 -0
- package/dist/web/assets/table-tab-BsfgtqFE.js.gz +0 -0
- package/dist/web/assets/{telegram-bot-token-field-BqNtOo5n.js → telegram-bot-token-field-CA8DEKRL.js} +1 -1
- package/dist/web/assets/telegram-bot-token-field-CA8DEKRL.js.br +0 -0
- package/dist/web/assets/telegram-bot-token-field-CA8DEKRL.js.gz +0 -0
- package/dist/web/assets/{terminal-tab-CrKxibGC.js → terminal-tab-CoT5KIfe.js} +1 -1
- package/dist/web/assets/terminal-tab-CoT5KIfe.js.br +0 -0
- package/dist/web/assets/terminal-tab-CoT5KIfe.js.gz +0 -0
- package/dist/web/assets/{tool-cards-BRto67jM.js → tool-cards-oZ6cptRr.js} +2 -2
- package/dist/web/assets/tool-cards-oZ6cptRr.js.br +0 -0
- package/dist/web/assets/tool-cards-oZ6cptRr.js.gz +0 -0
- package/dist/web/assets/{tools-settings-section-DEvZsrxq.js → tools-settings-section-CtcD-iTk.js} +1 -1
- package/dist/web/assets/tools-settings-section-CtcD-iTk.js.br +0 -0
- package/dist/web/assets/tools-settings-section-CtcD-iTk.js.gz +0 -0
- package/dist/web/assets/{use-inline-blame-CRWJU6Rq.js → use-inline-blame-BUuJj1Ru.js} +1 -1
- package/dist/web/assets/use-inline-blame-BUuJj1Ru.js.br +0 -0
- package/dist/web/assets/use-inline-blame-BUuJj1Ru.js.gz +0 -0
- package/dist/web/assets/{use-lsp-B-otjr-6.js → use-lsp-Bo4z4gLx.js} +1 -1
- package/dist/web/assets/use-lsp-Bo4z4gLx.js.br +0 -0
- package/dist/web/assets/use-lsp-Bo4z4gLx.js.gz +0 -0
- package/dist/web/assets/{use-monaco-theme-BiDXRJ4e.js → use-monaco-theme-1ANtPWqA.js} +1 -1
- package/dist/web/assets/{use-process-kill-BW2UW0mZ.js → use-process-kill-CvlXPal4.js} +1 -1
- package/dist/web/assets/use-process-kill-CvlXPal4.js.br +0 -0
- package/dist/web/assets/use-process-kill-CvlXPal4.js.gz +0 -0
- package/dist/web/assets/{use-sql-schema-info-D_Kc2FI7.js → use-sql-schema-info-DQzUqb7q.js} +1 -1
- package/dist/web/assets/use-sql-schema-info-DQzUqb7q.js.br +0 -0
- package/dist/web/assets/use-sql-schema-info-DQzUqb7q.js.gz +0 -0
- package/dist/web/assets/{use-websocket-n3Ud9vEA.js → use-websocket-QCAD3F5b.js} +1 -1
- package/dist/web/assets/{web-preview-tab-doNpegW3.js → web-preview-tab-Cp0LuL0T.js} +1 -1
- package/dist/web/assets/web-preview-tab-Cp0LuL0T.js.br +0 -0
- package/dist/web/assets/web-preview-tab-Cp0LuL0T.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/package.json +1 -1
- package/src/providers/claude-agent-sdk.ts +37 -7
- package/src/server/ws/chat-token-rotation.ts +150 -0
- package/src/server/ws/chat.ts +28 -0
- package/src/services/account.service.ts +14 -0
- package/src/web/components/chat/chat-reconnect-banner.tsx +54 -0
- package/src/web/components/chat/chat-tab.tsx +10 -13
- package/src/web/hooks/use-reconnect-indicator.ts +49 -0
- package/dist/web/assets/accounts-settings-section-B5YXy0vd.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-B5YXy0vd.js.gz +0 -0
- package/dist/web/assets/agent-session-sheet-Bj42PwCK.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-Bj42PwCK.js.gz +0 -0
- package/dist/web/assets/agent-session-window-content-LqL6fVPU.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-LqL6fVPU.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-jVv2nvAG.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-jVv2nvAG.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-C9Q-v8fY.js.br +0 -0
- package/dist/web/assets/ai-settings-section-C9Q-v8fY.js.gz +0 -0
- package/dist/web/assets/android-tab-CwqiuEZP.js.br +0 -0
- package/dist/web/assets/android-tab-CwqiuEZP.js.gz +0 -0
- package/dist/web/assets/appearance-settings-section-D0JIfiRx.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-D0JIfiRx.js.gz +0 -0
- package/dist/web/assets/apps-panel-Cz_KJ37M.js.br +0 -0
- package/dist/web/assets/apps-panel-Cz_KJ37M.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-C3QNpsm7.js.br +0 -0
- package/dist/web/assets/branch-review-tab-C3QNpsm7.js.gz +0 -0
- package/dist/web/assets/chat-tab-D14CUl0J.js.br +0 -0
- package/dist/web/assets/chat-tab-D14CUl0J.js.gz +0 -0
- package/dist/web/assets/chat-tab-D8iAVZKV.js +0 -16
- package/dist/web/assets/chat-tab-D8iAVZKV.js.br +0 -0
- package/dist/web/assets/chat-tab-D8iAVZKV.js.gz +0 -0
- package/dist/web/assets/code-editor-B7gbH4-x.js.br +0 -0
- package/dist/web/assets/code-editor-B7gbH4-x.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-DQDtyvnd.js.br +0 -0
- package/dist/web/assets/conflict-editor-ktOkrODA.js.br +0 -0
- package/dist/web/assets/conflict-editor-ktOkrODA.js.gz +0 -0
- package/dist/web/assets/connection-form-tab-CxvExR7T.js.br +0 -0
- package/dist/web/assets/connection-form-tab-CxvExR7T.js.gz +0 -0
- package/dist/web/assets/csv-preview-CBDMTEsF.js.br +0 -0
- package/dist/web/assets/csv-preview-CBDMTEsF.js.gz +0 -0
- package/dist/web/assets/db-tab-parts-BgVpKbi6.js.br +0 -0
- package/dist/web/assets/db-tab-parts-BgVpKbi6.js.gz +0 -0
- package/dist/web/assets/design-settings-section-DPGw61pY.js.br +0 -0
- package/dist/web/assets/design-settings-section-DPGw61pY.js.gz +0 -0
- package/dist/web/assets/design-tab-BiCqHNl3.js.br +0 -0
- package/dist/web/assets/design-tab-BiCqHNl3.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-pcJ2Rx5G.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-pcJ2Rx5G.js.gz +0 -0
- package/dist/web/assets/diff-viewer-CLAvyqVG.js.br +0 -0
- package/dist/web/assets/diff-viewer-CLAvyqVG.js.gz +0 -0
- package/dist/web/assets/docx-preview-XSkD2IL6.js.br +0 -0
- package/dist/web/assets/docx-preview-XSkD2IL6.js.gz +0 -0
- package/dist/web/assets/editor-dialog-cQvqVZAR.js.br +0 -0
- package/dist/web/assets/editor-dialog-cQvqVZAR.js.gz +0 -0
- package/dist/web/assets/editor-language-service-6-7mS6_9.js.br +0 -0
- package/dist/web/assets/editor-language-service-6-7mS6_9.js.gz +0 -0
- package/dist/web/assets/explorer-body-BlGDPmNa.js.br +0 -0
- package/dist/web/assets/explorer-body-BlGDPmNa.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-BLWY8GJ_.js.br +0 -0
- package/dist/web/assets/explorer-window-content-BLWY8GJ_.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-NSEt5f4z.js.br +0 -0
- package/dist/web/assets/extension-manager-section-NSEt5f4z.js.gz +0 -0
- package/dist/web/assets/extension-webview-BkAEBWin.js.br +0 -0
- package/dist/web/assets/extension-webview-BkAEBWin.js.gz +0 -0
- package/dist/web/assets/files-settings-section-Bq7liJ_r.js.br +0 -0
- package/dist/web/assets/files-settings-section-Bq7liJ_r.js.gz +0 -0
- package/dist/web/assets/general-settings-section-DvHROAMa.js.br +0 -0
- package/dist/web/assets/general-settings-section-DvHROAMa.js.gz +0 -0
- package/dist/web/assets/git-log-panel-Cu9kK48H.js.br +0 -0
- package/dist/web/assets/git-log-panel-Cu9kK48H.js.gz +0 -0
- package/dist/web/assets/git-review-tab-B_JT649W.js.br +0 -0
- package/dist/web/assets/git-review-tab-B_JT649W.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-BlposOph.js.br +0 -0
- package/dist/web/assets/glide-data-grid-BlposOph.js.gz +0 -0
- package/dist/web/assets/grid-save-dialog-B0d0qhnN.js.br +0 -0
- package/dist/web/assets/grid-save-dialog-B0d0qhnN.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-jirUsGm-.js.br +0 -0
- package/dist/web/assets/group-chat-tab-jirUsGm-.js.gz +0 -0
- package/dist/web/assets/impexp-tab-HJVcOTYJ.js.br +0 -0
- package/dist/web/assets/impexp-tab-HJVcOTYJ.js.gz +0 -0
- package/dist/web/assets/index-DLB3j_P0.js.br +0 -0
- package/dist/web/assets/index-DLB3j_P0.js.gz +0 -0
- package/dist/web/assets/index-M3DUzop8.css +0 -1
- package/dist/web/assets/index-M3DUzop8.css.br +0 -0
- package/dist/web/assets/index-M3DUzop8.css.gz +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-DN21gB-T.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-DN21gB-T.js.gz +0 -0
- package/dist/web/assets/language-servers-section-DOcZ72FJ.js.br +0 -0
- package/dist/web/assets/language-servers-section-DOcZ72FJ.js.gz +0 -0
- package/dist/web/assets/logs-app-C7n9GSCn.js.br +0 -0
- package/dist/web/assets/lsp-client-DpYmPhXX.js.br +0 -0
- package/dist/web/assets/lsp-client-DpYmPhXX.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-CtEl6qAt.js.br +0 -0
- package/dist/web/assets/markdown-renderer-CtEl6qAt.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-B-rcKEL9.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-B-rcKEL9.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-DgztQK5R.js.br +0 -0
- package/dist/web/assets/new-design-dialog-DgztQK5R.js.gz +0 -0
- package/dist/web/assets/notifications-settings-section-JiGvaCFx.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-JiGvaCFx.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-XfrZ8VoC.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-XfrZ8VoC.js.gz +0 -0
- package/dist/web/assets/problems-panel-CDzwSJn6.js.br +0 -0
- package/dist/web/assets/problems-panel-CDzwSJn6.js.gz +0 -0
- package/dist/web/assets/process-table-C_B39GHm.js.br +0 -0
- package/dist/web/assets/process-table-C_B39GHm.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-CvaOvYIv.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-CvaOvYIv.js.gz +0 -0
- package/dist/web/assets/query-tab-B8aMpAbI.js.br +0 -0
- package/dist/web/assets/query-tab-B8aMpAbI.js.gz +0 -0
- package/dist/web/assets/remote-access-settings-section-Bv0eawR4.js.br +0 -0
- package/dist/web/assets/remote-access-settings-section-Bv0eawR4.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-BpDakRY0.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-BpDakRY0.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-C5GQ3kzY.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-C5GQ3kzY.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-CZb5EMEK.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-CZb5EMEK.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CipNtLX5.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CipNtLX5.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-B7OXXvAM.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-B7OXXvAM.js.gz +0 -0
- package/dist/web/assets/rename-object-dialog-B4W6S7Xg.js.br +0 -0
- package/dist/web/assets/rename-object-dialog-B4W6S7Xg.js.gz +0 -0
- package/dist/web/assets/review-code-B4osj0k1.js.br +0 -0
- package/dist/web/assets/review-code-B4osj0k1.js.gz +0 -0
- package/dist/web/assets/save-as-dialog-DpXv_f1d.js.br +0 -5
- package/dist/web/assets/save-as-dialog-DpXv_f1d.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-BbaPbc28.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-BbaPbc28.js.gz +0 -0
- package/dist/web/assets/session-review-tab-Aq3FEooH.js.br +0 -0
- package/dist/web/assets/session-review-tab-Aq3FEooH.js.gz +0 -0
- package/dist/web/assets/settings-body-vXbOWchN.js.br +0 -0
- package/dist/web/assets/settings-body-vXbOWchN.js.gz +0 -0
- package/dist/web/assets/settings-rows-DD7Gxmst.js.br +0 -0
- package/dist/web/assets/settings-rows-DD7Gxmst.js.gz +0 -0
- package/dist/web/assets/settings-section-content-CVtvJ6ze.js +0 -1
- package/dist/web/assets/settings-section-content-DkUJFop6.js.br +0 -0
- package/dist/web/assets/settings-section-content-DkUJFop6.js.gz +0 -0
- package/dist/web/assets/slash-command-picker-lILYDfK3.js.br +0 -0
- package/dist/web/assets/slash-command-picker-lILYDfK3.js.gz +0 -0
- package/dist/web/assets/sql-object-tab-Da42SMK5.js.br +0 -0
- package/dist/web/assets/sql-object-tab-Da42SMK5.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-V3SKNukY.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-V3SKNukY.js.gz +0 -0
- package/dist/web/assets/structure-save-dialog-B635iyPI.js.br +0 -0
- package/dist/web/assets/structure-save-dialog-B635iyPI.js.gz +0 -0
- package/dist/web/assets/structure-tab-Co9d8ktU.js.br +0 -0
- package/dist/web/assets/structure-tab-Co9d8ktU.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-DC0j8RqP.js.br +0 -0
- package/dist/web/assets/system-monitor-body-DC0j8RqP.js.gz +0 -0
- package/dist/web/assets/tab-host-window-content-Crvt1r5F.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-Crvt1r5F.js.gz +0 -0
- package/dist/web/assets/table-tab-C2cYw7a0.js.br +0 -0
- package/dist/web/assets/table-tab-C2cYw7a0.js.gz +0 -0
- package/dist/web/assets/telegram-bot-token-field-BqNtOo5n.js.br +0 -0
- package/dist/web/assets/telegram-bot-token-field-BqNtOo5n.js.gz +0 -0
- package/dist/web/assets/terminal-tab-CrKxibGC.js.br +0 -0
- package/dist/web/assets/terminal-tab-CrKxibGC.js.gz +0 -0
- package/dist/web/assets/tool-cards-BRto67jM.js.br +0 -0
- package/dist/web/assets/tool-cards-BRto67jM.js.gz +0 -0
- package/dist/web/assets/tools-settings-section-DEvZsrxq.js.br +0 -0
- package/dist/web/assets/tools-settings-section-DEvZsrxq.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-CRWJU6Rq.js.br +0 -0
- package/dist/web/assets/use-inline-blame-CRWJU6Rq.js.gz +0 -0
- package/dist/web/assets/use-lsp-B-otjr-6.js.br +0 -0
- package/dist/web/assets/use-lsp-B-otjr-6.js.gz +0 -0
- package/dist/web/assets/use-process-kill-BW2UW0mZ.js.br +0 -0
- package/dist/web/assets/use-process-kill-BW2UW0mZ.js.gz +0 -0
- package/dist/web/assets/use-sql-schema-info-D_Kc2FI7.js.br +0 -0
- package/dist/web/assets/use-sql-schema-info-D_Kc2FI7.js.gz +0 -0
- package/dist/web/assets/web-preview-tab-doNpegW3.js.br +0 -0
- package/dist/web/assets/web-preview-tab-doNpegW3.js.gz +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renews Claude sign-ins while nothing is using them, and moves idle chats onto the new token.
|
|
3
|
+
*
|
|
4
|
+
* Every chat subprocess is started with its account's access token in
|
|
5
|
+
* `CLAUDE_CODE_OAUTH_TOKEN` and cannot renew it (`SubprocessCredential` in
|
|
6
|
+
* `claude-agent-sdk.ts` says why). Tokens live eight hours, and renewing one revokes the
|
|
7
|
+
* previous token, so a renewal used to land in the middle of whatever turn happened to be
|
|
8
|
+
* running: its next request got a 401, the provider rebuilt the subprocess, and every
|
|
9
|
+
* subagent still working inside it died with it.
|
|
10
|
+
*
|
|
11
|
+
* So a token is renewed early — inside `QUIET_REFRESH_WINDOW_S` of expiry — at a moment
|
|
12
|
+
* when no turn, approval or background work is running on that account. Idle subprocesses
|
|
13
|
+
* still holding the old token are then dropped, the same way a model switch drops them, and
|
|
14
|
+
* their next message resumes on the new one. A subprocess that is busy is left alone and
|
|
15
|
+
* dropped once its turn is over. Only an account kept busy for the whole window still meets
|
|
16
|
+
* the point-of-use renewal mid-turn, and the provider's 401 recovery covers that, as before.
|
|
17
|
+
*
|
|
18
|
+
* Accounts no subprocess is using are not touched: they are renewed where they are next
|
|
19
|
+
* used, which is the rule `account.service.ts` follows on purpose.
|
|
20
|
+
*/
|
|
21
|
+
import { accountService } from "../../services/account.service.ts";
|
|
22
|
+
import { createLogger } from "../../services/logger.ts";
|
|
23
|
+
import type { SubprocessCredential } from "../../providers/claude-agent-sdk.ts";
|
|
24
|
+
|
|
25
|
+
const log = createLogger("token-rotation");
|
|
26
|
+
|
|
27
|
+
/** Renew this close to expiry, as soon as nothing is running on the account. */
|
|
28
|
+
export const QUIET_REFRESH_WINDOW_S = 2 * 3600;
|
|
29
|
+
/** Inside this the point-of-use renewal owns the token (`PREFLIGHT_REFRESH_BUFFER_S`). */
|
|
30
|
+
const POINT_OF_USE_BUFFER_S = 300;
|
|
31
|
+
const SWEEP_INTERVAL_MS = 60_000;
|
|
32
|
+
|
|
33
|
+
export interface LiveSubprocess {
|
|
34
|
+
sessionId: string;
|
|
35
|
+
credential: SubprocessCredential;
|
|
36
|
+
/** A turn, an approval or background work is running in it — dropping it would lose work. */
|
|
37
|
+
busy: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface RenewalCandidate {
|
|
41
|
+
id: string;
|
|
42
|
+
expiresAt: number | null;
|
|
43
|
+
/** An OAuth account with a refresh token that the server has not rejected. */
|
|
44
|
+
renewable: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Accounts to renew now: in use by some subprocess, near expiry, and busy in none. */
|
|
48
|
+
export function accountsToRenewNow(candidates: RenewalCandidate[], live: LiveSubprocess[], nowS: number): string[] {
|
|
49
|
+
const inUse = new Set<string>();
|
|
50
|
+
const busy = new Set<string>();
|
|
51
|
+
for (const p of live) {
|
|
52
|
+
const id = p.credential.accountId;
|
|
53
|
+
if (!id || !p.credential.oauthToken) continue;
|
|
54
|
+
inUse.add(id);
|
|
55
|
+
if (p.busy) busy.add(id);
|
|
56
|
+
}
|
|
57
|
+
return candidates
|
|
58
|
+
.filter((c) => {
|
|
59
|
+
if (!c.renewable || c.expiresAt == null || !inUse.has(c.id) || busy.has(c.id)) return false;
|
|
60
|
+
const left = c.expiresAt - nowS;
|
|
61
|
+
return left <= QUIET_REFRESH_WINDOW_S && left > POINT_OF_USE_BUFFER_S;
|
|
62
|
+
})
|
|
63
|
+
.map((c) => c.id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Idle subprocesses still holding a token their account no longer has. */
|
|
67
|
+
export function staleIdleSessions(live: LiveSubprocess[], currentToken: (accountId: string) => string | null): string[] {
|
|
68
|
+
return live
|
|
69
|
+
.filter((p) => {
|
|
70
|
+
const id = p.credential.accountId;
|
|
71
|
+
if (p.busy || !id || !p.credential.oauthToken) return false;
|
|
72
|
+
const current = currentToken(id);
|
|
73
|
+
return current !== null && current !== p.credential.oauthToken;
|
|
74
|
+
})
|
|
75
|
+
.map((p) => p.sessionId);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface TokenRotationDeps {
|
|
79
|
+
/** Every live Claude subprocess and whether work is running in it right now. */
|
|
80
|
+
liveSubprocesses: () => LiveSubprocess[];
|
|
81
|
+
/** Restart one idle subprocess; its next message resumes on the account's current token. */
|
|
82
|
+
dropIdle: (sessionId: string) => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function currentOAuthToken(accountId: string): string | null {
|
|
86
|
+
const token = accountService.getWithTokens(accountId)?.accessToken;
|
|
87
|
+
return token && token.startsWith("sk-ant-oat") ? token : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Drop every idle subprocess whose token has been replaced. Safe to call at any time. */
|
|
91
|
+
export function dropStaleIdle(deps: TokenRotationDeps): void {
|
|
92
|
+
for (const sessionId of staleIdleSessions(deps.liveSubprocesses(), currentOAuthToken)) {
|
|
93
|
+
log.info(`session=${sessionId} restarting idle subprocess: its account's token was renewed`);
|
|
94
|
+
deps.dropIdle(sessionId);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One pass: renew what is due and quiet, then move idle subprocesses onto new tokens. */
|
|
99
|
+
export async function sweepTokenRotation(deps: TokenRotationDeps, nowS = Math.floor(Date.now() / 1000)): Promise<void> {
|
|
100
|
+
const candidates: RenewalCandidate[] = accountService.list().map((a) => {
|
|
101
|
+
const acc = accountService.getWithTokens(a.id);
|
|
102
|
+
return {
|
|
103
|
+
id: a.id,
|
|
104
|
+
expiresAt: acc?.expiresAt ?? null,
|
|
105
|
+
renewable: !!acc && acc.accessToken.startsWith("sk-ant-oat") && acc.refreshToken !== "" && !acc.reauthRequired,
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
for (const id of accountsToRenewNow(candidates, deps.liveSubprocesses(), nowS)) {
|
|
109
|
+
const left = (candidates.find((c) => c.id === id)?.expiresAt ?? nowS) - nowS;
|
|
110
|
+
log.info(`Renewing ${id} early while no turn runs on it (token expires in ${left}s)`);
|
|
111
|
+
try {
|
|
112
|
+
await accountService.refreshAccessToken(id, false, false, QUIET_REFRESH_WINDOW_S);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// The point-of-use renewal tries again before the token is needed.
|
|
115
|
+
log.warn(`Early renewal failed for ${id}: ${(e as Error).message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
dropStaleIdle(deps);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Survive Bun --hot reloads: one timer and one listener, whichever module copy registered last.
|
|
122
|
+
const HOT_KEY = "__PPM_TOKEN_ROTATION__" as const;
|
|
123
|
+
const hot = ((globalThis as any)[HOT_KEY] ??= { stop: null, deps: null }) as {
|
|
124
|
+
stop: (() => void) | null;
|
|
125
|
+
deps: TokenRotationDeps | null;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Run a sweep now with the registered chat layer, rather than waiting for the timer. */
|
|
129
|
+
export async function runTokenRotationSweep(): Promise<void> {
|
|
130
|
+
if (hot.deps) await sweepTokenRotation(hot.deps);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function registerTokenRotation(deps: TokenRotationDeps): () => void {
|
|
134
|
+
hot.stop?.();
|
|
135
|
+
hot.deps = deps;
|
|
136
|
+
let sweeping = false;
|
|
137
|
+
const sweep = () => {
|
|
138
|
+
if (sweeping) return;
|
|
139
|
+
sweeping = true;
|
|
140
|
+
sweepTokenRotation(deps)
|
|
141
|
+
.catch((e) => log.warn(`token rotation sweep failed: ${(e as Error).message}`))
|
|
142
|
+
.finally(() => { sweeping = false; });
|
|
143
|
+
};
|
|
144
|
+
const timer = setInterval(sweep, SWEEP_INTERVAL_MS);
|
|
145
|
+
(timer as { unref?: () => void }).unref?.();
|
|
146
|
+
// Any renewal — this sweep's, a turn's pre-flight, the usage poller's — strands idle subprocesses.
|
|
147
|
+
const unlisten = accountService.onTokenRotated(() => dropStaleIdle(deps));
|
|
148
|
+
hot.stop = () => { clearInterval(timer); unlisten(); };
|
|
149
|
+
return hot.stop;
|
|
150
|
+
}
|
package/src/server/ws/chat.ts
CHANGED
|
@@ -33,6 +33,8 @@ import { parseTabOpenResult, type TabOpenRequest } from "../../shared/tab-open-p
|
|
|
33
33
|
import { readLastTurnStop } from "../../services/session-trace/turn-stop-reader.ts";
|
|
34
34
|
import { describeTurnStop, type TurnStop } from "../../shared/turn-stop.ts";
|
|
35
35
|
import { createLogger } from "../../services/logger.ts";
|
|
36
|
+
import { dropStaleIdle, registerTokenRotation, type TokenRotationDeps } from "./chat-token-rotation.ts";
|
|
37
|
+
import type { SubprocessCredential } from "../../providers/claude-agent-sdk.ts";
|
|
36
38
|
|
|
37
39
|
const log = createLogger("chat");
|
|
38
40
|
const bgShellLog = createLogger("bg-shell");
|
|
@@ -436,6 +438,28 @@ registerMcpSignInSync({
|
|
|
436
438
|
),
|
|
437
439
|
});
|
|
438
440
|
|
|
441
|
+
const tokenRotationDeps: TokenRotationDeps = {
|
|
442
|
+
liveSubprocesses: () => {
|
|
443
|
+
const provider = providerRegistry.get("claude") as {
|
|
444
|
+
liveSubprocessCredentials?: () => Array<{ sessionId: string; credential: SubprocessCredential }>;
|
|
445
|
+
} | undefined;
|
|
446
|
+
return (provider?.liveSubprocessCredentials?.() ?? []).map(({ sessionId, credential }) => {
|
|
447
|
+
const entry = activeSessions.get(sessionId);
|
|
448
|
+
// The consumer loop outlives each turn, so a live subprocess between turns sits at
|
|
449
|
+
// `idle` with `isStreamingActive` still set. A subprocess with no entry here belongs to
|
|
450
|
+
// something else (a scheduled run) and is never ours to judge idle.
|
|
451
|
+
const busy = !entry || entry.phase !== "idle" || !!entry.pendingApprovalEvent || hasBackgroundWork(sessionId);
|
|
452
|
+
return { sessionId, credential, busy };
|
|
453
|
+
});
|
|
454
|
+
},
|
|
455
|
+
dropIdle: (sessionId) => dropIdleSubprocess(
|
|
456
|
+
sessionId,
|
|
457
|
+
"token_rotated",
|
|
458
|
+
"Subprocess restarted: its account's sign-in token was renewed, the next message resumes on the new one",
|
|
459
|
+
),
|
|
460
|
+
};
|
|
461
|
+
registerTokenRotation(tokenRotationDeps);
|
|
462
|
+
|
|
439
463
|
/**
|
|
440
464
|
* Sessions with a turn in flight, optionally narrowed to one project.
|
|
441
465
|
*
|
|
@@ -1253,6 +1277,10 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
1253
1277
|
broadcast(sessionId, { type: "compact_status", status: "done" });
|
|
1254
1278
|
}
|
|
1255
1279
|
setPhase(sessionId, "idle");
|
|
1280
|
+
// A renewal that landed while this turn ran left its subprocess holding a revoked
|
|
1281
|
+
// token; restart it now rather than let the next message open with a 401. Deferred so
|
|
1282
|
+
// the drop does not close the query from inside the loop that is reading it.
|
|
1283
|
+
setTimeout(() => dropStaleIdle(tokenRotationDeps), 0);
|
|
1256
1284
|
// Reset heartbeat tracking for next turn
|
|
1257
1285
|
firstEventReceived = false;
|
|
1258
1286
|
startTime = Date.now();
|
|
@@ -166,6 +166,17 @@ class AccountService {
|
|
|
166
166
|
private pendingStates = new Map<string, { verifier: string; createdAt: number }>();
|
|
167
167
|
/** Per-account mutex: dedup concurrent refresh calls so only one OAuth request fires at a time. */
|
|
168
168
|
private pendingRefreshes = new Map<string, Promise<void>>();
|
|
169
|
+
private tokenListeners = new Set<(accountId: string) => void>();
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Hear about every replaced access token. Renewing one revokes the previous token, so a
|
|
173
|
+
* Claude subprocess still holding it fails its next request — see
|
|
174
|
+
* `src/server/ws/chat-token-rotation.ts`, which moves idle chats onto the new one.
|
|
175
|
+
*/
|
|
176
|
+
onTokenRotated(listener: (accountId: string) => void): () => void {
|
|
177
|
+
this.tokenListeners.add(listener);
|
|
178
|
+
return () => { this.tokenListeners.delete(listener); };
|
|
179
|
+
}
|
|
169
180
|
|
|
170
181
|
private toAccount(row: AccountRow): Account {
|
|
171
182
|
let profileData: OAuthProfileData | null = null;
|
|
@@ -518,6 +529,9 @@ class AccountService {
|
|
|
518
529
|
reauth_required: 0,
|
|
519
530
|
...(parked ? {} : { status: "active" as const }),
|
|
520
531
|
});
|
|
532
|
+
for (const listener of this.tokenListeners) {
|
|
533
|
+
try { listener(id); } catch (e) { log.warn(`token rotation listener failed for ${id}: ${(e as Error).message}`); }
|
|
534
|
+
}
|
|
521
535
|
}
|
|
522
536
|
|
|
523
537
|
/**
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Loader2, WifiOff } from "@/lib/icons";
|
|
2
|
+
import { useReconnectIndicator } from "@/hooks/use-reconnect-indicator";
|
|
3
|
+
|
|
4
|
+
interface ChatReconnectBannerProps {
|
|
5
|
+
isReconnecting: boolean;
|
|
6
|
+
onRetry: () => void;
|
|
7
|
+
/** Overrides for tests; production uses the hook's defaults. */
|
|
8
|
+
showDelayMs?: number;
|
|
9
|
+
lostAfterMs?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A small pill floating over the top of the chat while the socket reconnects.
|
|
14
|
+
*
|
|
15
|
+
* It deliberately covers nothing else: the messages on screen stay readable and
|
|
16
|
+
* scrollable, and anything sent meanwhile is queued and goes out on reconnect.
|
|
17
|
+
*/
|
|
18
|
+
export function ChatReconnectBanner({ isReconnecting, onRetry, showDelayMs, lostAfterMs }: ChatReconnectBannerProps) {
|
|
19
|
+
const { stage, restart } = useReconnectIndicator(isReconnecting, showDelayMs, lostAfterMs);
|
|
20
|
+
if (stage === "hidden") return null;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="pointer-events-none absolute inset-x-0 top-2 z-40 flex justify-center px-4">
|
|
24
|
+
<div
|
|
25
|
+
role="status"
|
|
26
|
+
aria-live="polite"
|
|
27
|
+
data-reconnect-stage={stage}
|
|
28
|
+
className="pointer-events-auto flex items-center gap-2 rounded-full border border-border bg-surface/95 px-3 py-1 text-xs text-text-secondary shadow-sm backdrop-blur-sm"
|
|
29
|
+
>
|
|
30
|
+
{stage === "reconnecting" ? (
|
|
31
|
+
<>
|
|
32
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
33
|
+
<span>Reconnecting…</span>
|
|
34
|
+
</>
|
|
35
|
+
) : (
|
|
36
|
+
<>
|
|
37
|
+
<WifiOff className="size-3.5 text-error" />
|
|
38
|
+
<span>Connection lost</span>
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={() => {
|
|
42
|
+
restart();
|
|
43
|
+
onRetry();
|
|
44
|
+
}}
|
|
45
|
+
className="-my-1 min-h-11 rounded-full px-2 font-medium text-primary hover:underline active:opacity-70 md:min-h-0 md:py-1"
|
|
46
|
+
>
|
|
47
|
+
Retry
|
|
48
|
+
</button>
|
|
49
|
+
</>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -19,6 +19,7 @@ import { useChatAccountClaim } from "@/hooks/use-chat-account-claim";
|
|
|
19
19
|
import { startPrepare, getPrepare, isPrepared, forgetPrepare } from "@/lib/new-chat-prepare-client";
|
|
20
20
|
import { MessageList } from "./message-list";
|
|
21
21
|
import { BackgroundCommandBar } from "./background-command-bar";
|
|
22
|
+
import { ChatReconnectBanner } from "./chat-reconnect-banner";
|
|
22
23
|
import { RunningAgentsBar } from "./running-agents-bar";
|
|
23
24
|
import { AgentSessionProvider, normalizeProviderId } from "./agent-session-context";
|
|
24
25
|
import { McpSignInBar } from "@/components/mcp-auth/mcp-sign-in-bar";
|
|
@@ -473,6 +474,12 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
473
474
|
killBackgroundShell,
|
|
474
475
|
} = useChat(sessionId, providerId, projectName, handleSessionMigrated, observeAttempt, replyTransportOptions);
|
|
475
476
|
|
|
477
|
+
// Shared by the history bar's reload button and the reconnect pill's Retry.
|
|
478
|
+
const reloadChat = useCallback(() => {
|
|
479
|
+
if (!isConnected) reconnect();
|
|
480
|
+
refetchMessages();
|
|
481
|
+
}, [isConnected, reconnect, refetchMessages]);
|
|
482
|
+
|
|
476
483
|
const sessionChanges = useSessionFileChanges({ projectName, sessionId, messages, isStreaming });
|
|
477
484
|
const openReview = useCallback((path?: string) => {
|
|
478
485
|
if (!sessionId) return;
|
|
@@ -1283,15 +1290,8 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
1283
1290
|
</div>
|
|
1284
1291
|
)}
|
|
1285
1292
|
|
|
1286
|
-
{/* Reconnect overlay */}
|
|
1287
|
-
{isReconnecting
|
|
1288
|
-
<div className="absolute inset-0 z-50 flex items-center justify-center bg-background/60 backdrop-blur-sm">
|
|
1289
|
-
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
1290
|
-
<Loader2 className="size-4 animate-spin" />
|
|
1291
|
-
<span>Reconnecting...</span>
|
|
1292
|
-
</div>
|
|
1293
|
-
</div>
|
|
1294
|
-
)}
|
|
1293
|
+
{/* Reconnect status — a pill, not an overlay: the chat stays readable and usable */}
|
|
1294
|
+
<ChatReconnectBanner isReconnecting={isReconnecting} onRetry={reloadChat} />
|
|
1295
1295
|
|
|
1296
1296
|
{/* Edit-fork overlay — covers the chat while the new version forks + connects */}
|
|
1297
1297
|
{editForking && (
|
|
@@ -1399,10 +1399,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
|
|
|
1399
1399
|
onSelectSession={handleSelectSession}
|
|
1400
1400
|
historyFilter={historyFilter}
|
|
1401
1401
|
isConnected={isConnected}
|
|
1402
|
-
onReload={
|
|
1403
|
-
if (!isConnected) reconnect();
|
|
1404
|
-
refetchMessages();
|
|
1405
|
-
}}
|
|
1402
|
+
onReload={reloadChat}
|
|
1406
1403
|
teamActivity={teamActivity}
|
|
1407
1404
|
teamMessages={teamMessages}
|
|
1408
1405
|
onTeamOpen={markTeamRead}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/** What the chat shows while its socket is being re-established. */
|
|
4
|
+
export type ReconnectStage = "hidden" | "reconnecting" | "lost";
|
|
5
|
+
|
|
6
|
+
/** A reconnect that lands within this window is never shown — returning to the app should not flash. */
|
|
7
|
+
export const RECONNECT_SHOW_DELAY_MS = 800;
|
|
8
|
+
/** Past this the connection is reported lost and a retry is offered. */
|
|
9
|
+
export const RECONNECT_LOST_AFTER_MS = 8_000;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Turns a raw `isReconnecting` flag into what is worth telling the user.
|
|
13
|
+
*
|
|
14
|
+
* A phone browser closes the socket of every page it sends to the background, so a
|
|
15
|
+
* reconnect follows almost every return to PPM and usually finishes in well under a
|
|
16
|
+
* second. Both timers restart whenever the page becomes visible again: timers frozen
|
|
17
|
+
* while the page was hidden fire as soon as it resumes, which would otherwise report
|
|
18
|
+
* a connection that started reconnecting a moment ago as lost.
|
|
19
|
+
*/
|
|
20
|
+
export function useReconnectIndicator(
|
|
21
|
+
isReconnecting: boolean,
|
|
22
|
+
showDelayMs = RECONNECT_SHOW_DELAY_MS,
|
|
23
|
+
lostAfterMs = RECONNECT_LOST_AFTER_MS,
|
|
24
|
+
): { stage: ReconnectStage; restart: () => void } {
|
|
25
|
+
const [stage, setStage] = useState<ReconnectStage>("hidden");
|
|
26
|
+
const [epoch, setEpoch] = useState(0);
|
|
27
|
+
const restart = useCallback(() => setEpoch((n) => n + 1), []);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const onVisibility = () => {
|
|
31
|
+
if (document.visibilityState === "visible") restart();
|
|
32
|
+
};
|
|
33
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
34
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
35
|
+
}, [restart]);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setStage("hidden");
|
|
39
|
+
if (!isReconnecting) return;
|
|
40
|
+
const show = setTimeout(() => setStage("reconnecting"), showDelayMs);
|
|
41
|
+
const lost = setTimeout(() => setStage("lost"), lostAfterMs);
|
|
42
|
+
return () => {
|
|
43
|
+
clearTimeout(show);
|
|
44
|
+
clearTimeout(lost);
|
|
45
|
+
};
|
|
46
|
+
}, [isReconnecting, showDelayMs, lostAfterMs, epoch]);
|
|
47
|
+
|
|
48
|
+
return { stage, restart };
|
|
49
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|