@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.
Files changed (443) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CLAUDE.md +1 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +1 -1
  5. package/dist/web/assets/{accounts-settings-section-B5YXy0vd.js → accounts-settings-section-DhzuFkDO.js} +1 -1
  6. package/dist/web/assets/accounts-settings-section-DhzuFkDO.js.br +0 -0
  7. package/dist/web/assets/accounts-settings-section-DhzuFkDO.js.gz +0 -0
  8. package/dist/web/assets/{agent-session-sheet-Bj42PwCK.js → agent-session-sheet-CZAmOwSU.js} +2 -2
  9. package/dist/web/assets/agent-session-sheet-CZAmOwSU.js.br +0 -0
  10. package/dist/web/assets/agent-session-sheet-CZAmOwSU.js.gz +0 -0
  11. package/dist/web/assets/{agent-session-window-content-LqL6fVPU.js → agent-session-window-content-CwokWtJq.js} +1 -1
  12. package/dist/web/assets/agent-session-window-content-CwokWtJq.js.br +0 -0
  13. package/dist/web/assets/agent-session-window-content-CwokWtJq.js.gz +0 -0
  14. package/dist/web/assets/{ai-resource-editor-jVv2nvAG.js → ai-resource-editor-CT1JCaRQ.js} +1 -1
  15. package/dist/web/assets/ai-resource-editor-CT1JCaRQ.js.br +0 -0
  16. package/dist/web/assets/ai-resource-editor-CT1JCaRQ.js.gz +0 -0
  17. package/dist/web/assets/{ai-settings-section-CN5BR1ZZ.js → ai-settings-section-CBlS1FZK.js} +1 -1
  18. package/dist/web/assets/{ai-settings-section-C9Q-v8fY.js → ai-settings-section-Ct9-lNqA.js} +1 -1
  19. package/dist/web/assets/ai-settings-section-Ct9-lNqA.js.br +0 -0
  20. package/dist/web/assets/ai-settings-section-Ct9-lNqA.js.gz +0 -0
  21. package/dist/web/assets/{android-tab-CwqiuEZP.js → android-tab-CUPQN2M4.js} +1 -1
  22. package/dist/web/assets/android-tab-CUPQN2M4.js.br +0 -0
  23. package/dist/web/assets/android-tab-CUPQN2M4.js.gz +0 -0
  24. package/dist/web/assets/{appearance-settings-section-D0JIfiRx.js → appearance-settings-section-DpYcNqXH.js} +1 -1
  25. package/dist/web/assets/appearance-settings-section-DpYcNqXH.js.br +0 -0
  26. package/dist/web/assets/appearance-settings-section-DpYcNqXH.js.gz +0 -0
  27. package/dist/web/assets/{apps-panel-Cz_KJ37M.js → apps-panel-BqF_CkKy.js} +1 -1
  28. package/dist/web/assets/apps-panel-BqF_CkKy.js.br +0 -0
  29. package/dist/web/assets/apps-panel-BqF_CkKy.js.gz +0 -0
  30. package/dist/web/assets/{branch-review-tab-C3QNpsm7.js → branch-review-tab-BEZqFSlw.js} +1 -1
  31. package/dist/web/assets/branch-review-tab-BEZqFSlw.js.br +0 -0
  32. package/dist/web/assets/branch-review-tab-BEZqFSlw.js.gz +0 -0
  33. package/dist/web/assets/{chat-tab-D14CUl0J.js → chat-tab-N1B-Yv0P.js} +1 -1
  34. package/dist/web/assets/chat-tab-N1B-Yv0P.js.br +0 -0
  35. package/dist/web/assets/chat-tab-N1B-Yv0P.js.gz +0 -0
  36. package/dist/web/assets/chat-tab-kk-mAF05.js +16 -0
  37. package/dist/web/assets/chat-tab-kk-mAF05.js.br +0 -0
  38. package/dist/web/assets/chat-tab-kk-mAF05.js.gz +0 -0
  39. package/dist/web/assets/{code-editor-B7gbH4-x.js → code-editor-d1CXl2WL.js} +4 -4
  40. package/dist/web/assets/code-editor-d1CXl2WL.js.br +0 -0
  41. package/dist/web/assets/code-editor-d1CXl2WL.js.gz +0 -0
  42. package/dist/web/assets/{codex-reset-credit-button-DQDtyvnd.js → codex-reset-credit-button-CwSSBK6P.js} +1 -1
  43. package/dist/web/assets/codex-reset-credit-button-CwSSBK6P.js.br +0 -0
  44. package/dist/web/assets/{codex-reset-credit-button-DQDtyvnd.js.gz → codex-reset-credit-button-CwSSBK6P.js.gz} +0 -0
  45. package/dist/web/assets/{conflict-editor-ktOkrODA.js → conflict-editor-BucKz-uE.js} +1 -1
  46. package/dist/web/assets/conflict-editor-BucKz-uE.js.br +0 -0
  47. package/dist/web/assets/conflict-editor-BucKz-uE.js.gz +0 -0
  48. package/dist/web/assets/{connection-form-tab-CxvExR7T.js → connection-form-tab-DIc25Bko.js} +1 -1
  49. package/dist/web/assets/connection-form-tab-DIc25Bko.js.br +0 -0
  50. package/dist/web/assets/connection-form-tab-DIc25Bko.js.gz +0 -0
  51. package/dist/web/assets/{csv-preview-CBDMTEsF.js → csv-preview-CqDaGV5N.js} +1 -1
  52. package/dist/web/assets/csv-preview-CqDaGV5N.js.br +0 -0
  53. package/dist/web/assets/csv-preview-CqDaGV5N.js.gz +0 -0
  54. package/dist/web/assets/{db-tab-parts-BgVpKbi6.js → db-tab-parts-lp8ROGQB.js} +1 -1
  55. package/dist/web/assets/db-tab-parts-lp8ROGQB.js.br +0 -0
  56. package/dist/web/assets/db-tab-parts-lp8ROGQB.js.gz +0 -0
  57. package/dist/web/assets/{design-settings-section-DPGw61pY.js → design-settings-section-DxWEBH1L.js} +1 -1
  58. package/dist/web/assets/design-settings-section-DxWEBH1L.js.br +0 -0
  59. package/dist/web/assets/design-settings-section-DxWEBH1L.js.gz +0 -0
  60. package/dist/web/assets/{design-tab-BiCqHNl3.js → design-tab-Cl0CMAUx.js} +1 -1
  61. package/dist/web/assets/design-tab-Cl0CMAUx.js.br +0 -0
  62. package/dist/web/assets/design-tab-Cl0CMAUx.js.gz +0 -0
  63. package/dist/web/assets/{designs-sidebar-panel-pcJ2Rx5G.js → designs-sidebar-panel-aCr-EGfj.js} +1 -1
  64. package/dist/web/assets/designs-sidebar-panel-aCr-EGfj.js.br +0 -0
  65. package/dist/web/assets/designs-sidebar-panel-aCr-EGfj.js.gz +0 -0
  66. package/dist/web/assets/{diff-viewer-CLAvyqVG.js → diff-viewer-XyJgWKwe.js} +1 -1
  67. package/dist/web/assets/diff-viewer-XyJgWKwe.js.br +0 -0
  68. package/dist/web/assets/diff-viewer-XyJgWKwe.js.gz +0 -0
  69. package/dist/web/assets/{docx-preview-XSkD2IL6.js → docx-preview-KJaj2YGN.js} +1 -1
  70. package/dist/web/assets/docx-preview-KJaj2YGN.js.br +0 -0
  71. package/dist/web/assets/docx-preview-KJaj2YGN.js.gz +0 -0
  72. package/dist/web/assets/{editor-dialog-cQvqVZAR.js → editor-dialog-9tW6c9mu.js} +1 -1
  73. package/dist/web/assets/editor-dialog-9tW6c9mu.js.br +0 -0
  74. package/dist/web/assets/editor-dialog-9tW6c9mu.js.gz +0 -0
  75. package/dist/web/assets/{editor-language-service-6-7mS6_9.js → editor-language-service-CkdO9ih8.js} +1 -1
  76. package/dist/web/assets/editor-language-service-CkdO9ih8.js.br +0 -0
  77. package/dist/web/assets/editor-language-service-CkdO9ih8.js.gz +0 -0
  78. package/dist/web/assets/{explorer-body-BlGDPmNa.js → explorer-body-B8AkNO5o.js} +1 -1
  79. package/dist/web/assets/explorer-body-B8AkNO5o.js.br +0 -0
  80. package/dist/web/assets/explorer-body-B8AkNO5o.js.gz +0 -0
  81. package/dist/web/assets/{explorer-window-content-BLWY8GJ_.js → explorer-window-content-D174CXKz.js} +1 -1
  82. package/dist/web/assets/explorer-window-content-D174CXKz.js.br +0 -0
  83. package/dist/web/assets/explorer-window-content-D174CXKz.js.gz +0 -0
  84. package/dist/web/assets/{extension-manager-section-NSEt5f4z.js → extension-manager-section-CQsqXsPF.js} +1 -1
  85. package/dist/web/assets/extension-manager-section-CQsqXsPF.js.br +0 -0
  86. package/dist/web/assets/extension-manager-section-CQsqXsPF.js.gz +0 -0
  87. package/dist/web/assets/{extension-webview-BkAEBWin.js → extension-webview-AfaxC7PL.js} +1 -1
  88. package/dist/web/assets/extension-webview-AfaxC7PL.js.br +0 -0
  89. package/dist/web/assets/extension-webview-AfaxC7PL.js.gz +0 -0
  90. package/dist/web/assets/{files-settings-section-Bq7liJ_r.js → files-settings-section-CC0aJryq.js} +1 -1
  91. package/dist/web/assets/files-settings-section-CC0aJryq.js.br +0 -0
  92. package/dist/web/assets/files-settings-section-CC0aJryq.js.gz +0 -0
  93. package/dist/web/assets/{general-settings-section-DvHROAMa.js → general-settings-section-DQTr20G_.js} +1 -1
  94. package/dist/web/assets/general-settings-section-DQTr20G_.js.br +0 -0
  95. package/dist/web/assets/general-settings-section-DQTr20G_.js.gz +0 -0
  96. package/dist/web/assets/{git-log-panel-Cu9kK48H.js → git-log-panel-CisEJPJz.js} +1 -1
  97. package/dist/web/assets/git-log-panel-CisEJPJz.js.br +0 -0
  98. package/dist/web/assets/git-log-panel-CisEJPJz.js.gz +0 -0
  99. package/dist/web/assets/{git-review-tab-B_JT649W.js → git-review-tab-DpWEy03w.js} +1 -1
  100. package/dist/web/assets/git-review-tab-DpWEy03w.js.br +0 -0
  101. package/dist/web/assets/git-review-tab-DpWEy03w.js.gz +0 -0
  102. package/dist/web/assets/{glide-data-grid-BlposOph.js → glide-data-grid-hOML-uXP.js} +1 -1
  103. package/dist/web/assets/glide-data-grid-hOML-uXP.js.br +0 -0
  104. package/dist/web/assets/glide-data-grid-hOML-uXP.js.gz +0 -0
  105. package/dist/web/assets/{grid-save-dialog-B0d0qhnN.js → grid-save-dialog-Bx61akzH.js} +1 -1
  106. package/dist/web/assets/grid-save-dialog-Bx61akzH.js.br +0 -0
  107. package/dist/web/assets/grid-save-dialog-Bx61akzH.js.gz +0 -0
  108. package/dist/web/assets/{group-chat-tab-jirUsGm-.js → group-chat-tab-eQdJHa8d.js} +1 -1
  109. package/dist/web/assets/group-chat-tab-eQdJHa8d.js.br +0 -0
  110. package/dist/web/assets/group-chat-tab-eQdJHa8d.js.gz +0 -0
  111. package/dist/web/assets/{impexp-tab-HJVcOTYJ.js → impexp-tab-BjIDxYTj.js} +1 -1
  112. package/dist/web/assets/impexp-tab-BjIDxYTj.js.br +0 -0
  113. package/dist/web/assets/impexp-tab-BjIDxYTj.js.gz +0 -0
  114. package/dist/web/assets/index-BACBJm4i.css +1 -0
  115. package/dist/web/assets/index-BACBJm4i.css.br +0 -0
  116. package/dist/web/assets/index-BACBJm4i.css.gz +0 -0
  117. package/dist/web/assets/{index-DLB3j_P0.js → index-COwBji1t.js} +5 -5
  118. package/dist/web/assets/index-COwBji1t.js.br +0 -0
  119. package/dist/web/assets/index-COwBji1t.js.gz +0 -0
  120. package/dist/web/assets/{jira-watcher-section-EQvdnETB.js → jira-watcher-section-dzpmNZA2.js} +1 -1
  121. package/dist/web/assets/{keybindings-store-CEy-Sdcc.js → keybindings-store-DRjk8512.js} +1 -1
  122. package/dist/web/assets/{keyboard-shortcuts-section-DN21gB-T.js → keyboard-shortcuts-section-BhyalK8A.js} +1 -1
  123. package/dist/web/assets/keyboard-shortcuts-section-BhyalK8A.js.br +0 -0
  124. package/dist/web/assets/keyboard-shortcuts-section-BhyalK8A.js.gz +0 -0
  125. package/dist/web/assets/{language-servers-section-DOcZ72FJ.js → language-servers-section-BXLaicUY.js} +2 -2
  126. package/dist/web/assets/language-servers-section-BXLaicUY.js.br +0 -0
  127. package/dist/web/assets/language-servers-section-BXLaicUY.js.gz +0 -0
  128. package/dist/web/assets/{logs-app-C7n9GSCn.js → logs-app-BnjU6CRy.js} +1 -1
  129. package/dist/web/assets/logs-app-BnjU6CRy.js.br +0 -0
  130. package/dist/web/assets/{logs-app-C7n9GSCn.js.gz → logs-app-BnjU6CRy.js.gz} +0 -0
  131. package/dist/web/assets/{logs-tab-CSOiexvx.js → logs-tab-7uyD7gKW.js} +1 -1
  132. package/dist/web/assets/{logs-window-content-C4Qs6-0q.js → logs-window-content-B78a6oM2.js} +1 -1
  133. package/dist/web/assets/{lsp-client-DpYmPhXX.js → lsp-client-DOHnxEUE.js} +1 -1
  134. package/dist/web/assets/lsp-client-DOHnxEUE.js.br +0 -0
  135. package/dist/web/assets/lsp-client-DOHnxEUE.js.gz +0 -0
  136. package/dist/web/assets/{markdown-renderer-Df3r-CCb.js → markdown-renderer-B_hL6lRL.js} +1 -1
  137. package/dist/web/assets/{markdown-renderer-CtEl6qAt.js → markdown-renderer-Dz0ZcxNo.js} +1 -1
  138. package/dist/web/assets/markdown-renderer-Dz0ZcxNo.js.br +0 -0
  139. package/dist/web/assets/markdown-renderer-Dz0ZcxNo.js.gz +0 -0
  140. package/dist/web/assets/{mobile-explorer-sheet-B-rcKEL9.js → mobile-explorer-sheet-C_3dq99D.js} +1 -1
  141. package/dist/web/assets/mobile-explorer-sheet-C_3dq99D.js.br +0 -0
  142. package/dist/web/assets/mobile-explorer-sheet-C_3dq99D.js.gz +0 -0
  143. package/dist/web/assets/{new-design-dialog-DgztQK5R.js → new-design-dialog-CaubNbMT.js} +1 -1
  144. package/dist/web/assets/new-design-dialog-CaubNbMT.js.br +0 -0
  145. package/dist/web/assets/new-design-dialog-CaubNbMT.js.gz +0 -0
  146. package/dist/web/assets/{notification-store-B92dvP_u.js → notification-store-BTk3PZAj.js} +1 -1
  147. package/dist/web/assets/{notifications-settings-section-JiGvaCFx.js → notifications-settings-section-CJJ1l5cL.js} +1 -1
  148. package/dist/web/assets/notifications-settings-section-CJJ1l5cL.js.br +0 -0
  149. package/dist/web/assets/notifications-settings-section-CJJ1l5cL.js.gz +0 -0
  150. package/dist/web/assets/{ppmbot-settings-section-XfrZ8VoC.js → ppmbot-settings-section-BFniAm3D.js} +1 -1
  151. package/dist/web/assets/ppmbot-settings-section-BFniAm3D.js.br +0 -0
  152. package/dist/web/assets/ppmbot-settings-section-BFniAm3D.js.gz +0 -0
  153. package/dist/web/assets/{problems-panel-CDzwSJn6.js → problems-panel-CIxaCYJw.js} +1 -1
  154. package/dist/web/assets/problems-panel-CIxaCYJw.js.br +0 -0
  155. package/dist/web/assets/problems-panel-CIxaCYJw.js.gz +0 -0
  156. package/dist/web/assets/{process-table-C_B39GHm.js → process-table-l1fVcESX.js} +1 -1
  157. package/dist/web/assets/process-table-l1fVcESX.js.br +0 -0
  158. package/dist/web/assets/process-table-l1fVcESX.js.gz +0 -0
  159. package/dist/web/assets/{proxy-settings-section-CvaOvYIv.js → proxy-settings-section-Di5nBuy6.js} +1 -1
  160. package/dist/web/assets/proxy-settings-section-Di5nBuy6.js.br +0 -0
  161. package/dist/web/assets/proxy-settings-section-Di5nBuy6.js.gz +0 -0
  162. package/dist/web/assets/{query-tab-B8aMpAbI.js → query-tab-hyKnRZr3.js} +1 -1
  163. package/dist/web/assets/query-tab-hyKnRZr3.js.br +0 -0
  164. package/dist/web/assets/query-tab-hyKnRZr3.js.gz +0 -0
  165. package/dist/web/assets/{remote-access-settings-section-Bv0eawR4.js → remote-access-settings-section-HpZAP0Al.js} +1 -1
  166. package/dist/web/assets/remote-access-settings-section-HpZAP0Al.js.br +0 -0
  167. package/dist/web/assets/remote-access-settings-section-HpZAP0Al.js.gz +0 -0
  168. package/dist/web/assets/{remote-desktop-clipboard-notice-BpDakRY0.js → remote-desktop-clipboard-notice-DY7tWgjf.js} +1 -1
  169. package/dist/web/assets/remote-desktop-clipboard-notice-DY7tWgjf.js.br +0 -0
  170. package/dist/web/assets/remote-desktop-clipboard-notice-DY7tWgjf.js.gz +0 -0
  171. package/dist/web/assets/{remote-desktop-mobile-sheet-C5GQ3kzY.js → remote-desktop-mobile-sheet-CX9mDRle.js} +2 -2
  172. package/dist/web/assets/remote-desktop-mobile-sheet-CX9mDRle.js.br +0 -0
  173. package/dist/web/assets/remote-desktop-mobile-sheet-CX9mDRle.js.gz +0 -0
  174. package/dist/web/assets/{remote-desktop-mobile-view-CZb5EMEK.js → remote-desktop-mobile-view-DNg0EpbV.js} +1 -1
  175. package/dist/web/assets/remote-desktop-mobile-view-DNg0EpbV.js.br +0 -0
  176. package/dist/web/assets/remote-desktop-mobile-view-DNg0EpbV.js.gz +0 -0
  177. package/dist/web/assets/{remote-desktop-readiness-gate-CipNtLX5.js → remote-desktop-readiness-gate-BTTLlVDc.js} +1 -1
  178. package/dist/web/assets/remote-desktop-readiness-gate-BTTLlVDc.js.br +0 -0
  179. package/dist/web/assets/remote-desktop-readiness-gate-BTTLlVDc.js.gz +0 -0
  180. package/dist/web/assets/{remote-desktop-window-content-B7OXXvAM.js → remote-desktop-window-content-CqxnhSu0.js} +1 -1
  181. package/dist/web/assets/remote-desktop-window-content-CqxnhSu0.js.br +0 -0
  182. package/dist/web/assets/remote-desktop-window-content-CqxnhSu0.js.gz +0 -0
  183. package/dist/web/assets/{rename-object-dialog-B4W6S7Xg.js → rename-object-dialog-CnhMsNbT.js} +1 -1
  184. package/dist/web/assets/rename-object-dialog-CnhMsNbT.js.br +0 -0
  185. package/dist/web/assets/rename-object-dialog-CnhMsNbT.js.gz +0 -0
  186. package/dist/web/assets/{review-code-B4osj0k1.js → review-code-CunYhmcC.js} +1 -1
  187. package/dist/web/assets/review-code-CunYhmcC.js.br +0 -0
  188. package/dist/web/assets/review-code-CunYhmcC.js.gz +0 -0
  189. package/dist/web/assets/{save-as-dialog-DpXv_f1d.js → save-as-dialog-BtGSwTKZ.js} +1 -1
  190. package/dist/web/assets/save-as-dialog-BtGSwTKZ.js.br +0 -0
  191. package/dist/web/assets/save-as-dialog-BtGSwTKZ.js.gz +0 -0
  192. package/dist/web/assets/{schedules-settings-section-BbaPbc28.js → schedules-settings-section-CvlcMyEz.js} +1 -1
  193. package/dist/web/assets/schedules-settings-section-CvlcMyEz.js.br +0 -0
  194. package/dist/web/assets/schedules-settings-section-CvlcMyEz.js.gz +0 -0
  195. package/dist/web/assets/{session-review-tab-Aq3FEooH.js → session-review-tab-C3HuIPD2.js} +1 -1
  196. package/dist/web/assets/session-review-tab-C3HuIPD2.js.br +0 -0
  197. package/dist/web/assets/session-review-tab-C3HuIPD2.js.gz +0 -0
  198. package/dist/web/assets/{settings-body-vXbOWchN.js → settings-body-DCHEAh2_.js} +1 -1
  199. package/dist/web/assets/settings-body-DCHEAh2_.js.br +0 -0
  200. package/dist/web/assets/settings-body-DCHEAh2_.js.gz +0 -0
  201. package/dist/web/assets/{settings-rows-DD7Gxmst.js → settings-rows-D8Y9o1ZD.js} +1 -1
  202. package/dist/web/assets/settings-rows-D8Y9o1ZD.js.br +0 -0
  203. package/dist/web/assets/settings-rows-D8Y9o1ZD.js.gz +0 -0
  204. package/dist/web/assets/settings-section-content-BXPg2H0N.js +1 -0
  205. package/dist/web/assets/{settings-section-content-DkUJFop6.js → settings-section-content-Dluhkh-u.js} +2 -2
  206. package/dist/web/assets/settings-section-content-Dluhkh-u.js.br +0 -0
  207. package/dist/web/assets/settings-section-content-Dluhkh-u.js.gz +0 -0
  208. package/dist/web/assets/{settings-tab-Cdv9_OIe.js → settings-tab-Cj1GkYi3.js} +1 -1
  209. package/dist/web/assets/{settings-window-content-DLa6Ph4u.js → settings-window-content-1fweMynB.js} +1 -1
  210. package/dist/web/assets/{slash-command-picker-lILYDfK3.js → slash-command-picker-BdKd_AiS.js} +1 -1
  211. package/dist/web/assets/slash-command-picker-BdKd_AiS.js.br +0 -0
  212. package/dist/web/assets/slash-command-picker-BdKd_AiS.js.gz +0 -0
  213. package/dist/web/assets/{sql-object-tab-Da42SMK5.js → sql-object-tab-DkdF_7Jz.js} +1 -1
  214. package/dist/web/assets/sql-object-tab-DkdF_7Jz.js.br +0 -0
  215. package/dist/web/assets/sql-object-tab-DkdF_7Jz.js.gz +0 -0
  216. package/dist/web/assets/{sqlite-viewer-V3SKNukY.js → sqlite-viewer-BoQMjJHj.js} +1 -1
  217. package/dist/web/assets/sqlite-viewer-BoQMjJHj.js.br +0 -0
  218. package/dist/web/assets/sqlite-viewer-BoQMjJHj.js.gz +0 -0
  219. package/dist/web/assets/{structure-save-dialog-B635iyPI.js → structure-save-dialog-DGHxHJ4T.js} +1 -1
  220. package/dist/web/assets/structure-save-dialog-DGHxHJ4T.js.br +0 -0
  221. package/dist/web/assets/structure-save-dialog-DGHxHJ4T.js.gz +0 -0
  222. package/dist/web/assets/{structure-tab-Co9d8ktU.js → structure-tab-DuuX1e9c.js} +1 -1
  223. package/dist/web/assets/structure-tab-DuuX1e9c.js.br +0 -0
  224. package/dist/web/assets/structure-tab-DuuX1e9c.js.gz +0 -0
  225. package/dist/web/assets/{system-monitor-body-DC0j8RqP.js → system-monitor-body-CZVoh652.js} +2 -2
  226. package/dist/web/assets/system-monitor-body-CZVoh652.js.br +0 -0
  227. package/dist/web/assets/system-monitor-body-CZVoh652.js.gz +0 -0
  228. package/dist/web/assets/{system-monitor-tab-DA8BcdXo.js → system-monitor-tab-eGta1jpw.js} +1 -1
  229. package/dist/web/assets/{system-monitor-window-content-BiuLo8XW.js → system-monitor-window-content-BUG72ffC.js} +1 -1
  230. package/dist/web/assets/{tab-host-window-content-Crvt1r5F.js → tab-host-window-content-BTYuY43x.js} +1 -1
  231. package/dist/web/assets/tab-host-window-content-BTYuY43x.js.br +0 -0
  232. package/dist/web/assets/tab-host-window-content-BTYuY43x.js.gz +0 -0
  233. package/dist/web/assets/{table-tab-C2cYw7a0.js → table-tab-BsfgtqFE.js} +1 -1
  234. package/dist/web/assets/table-tab-BsfgtqFE.js.br +0 -0
  235. package/dist/web/assets/table-tab-BsfgtqFE.js.gz +0 -0
  236. package/dist/web/assets/{telegram-bot-token-field-BqNtOo5n.js → telegram-bot-token-field-CA8DEKRL.js} +1 -1
  237. package/dist/web/assets/telegram-bot-token-field-CA8DEKRL.js.br +0 -0
  238. package/dist/web/assets/telegram-bot-token-field-CA8DEKRL.js.gz +0 -0
  239. package/dist/web/assets/{terminal-tab-CrKxibGC.js → terminal-tab-CoT5KIfe.js} +1 -1
  240. package/dist/web/assets/terminal-tab-CoT5KIfe.js.br +0 -0
  241. package/dist/web/assets/terminal-tab-CoT5KIfe.js.gz +0 -0
  242. package/dist/web/assets/{tool-cards-BRto67jM.js → tool-cards-oZ6cptRr.js} +2 -2
  243. package/dist/web/assets/tool-cards-oZ6cptRr.js.br +0 -0
  244. package/dist/web/assets/tool-cards-oZ6cptRr.js.gz +0 -0
  245. package/dist/web/assets/{tools-settings-section-DEvZsrxq.js → tools-settings-section-CtcD-iTk.js} +1 -1
  246. package/dist/web/assets/tools-settings-section-CtcD-iTk.js.br +0 -0
  247. package/dist/web/assets/tools-settings-section-CtcD-iTk.js.gz +0 -0
  248. package/dist/web/assets/{use-inline-blame-CRWJU6Rq.js → use-inline-blame-BUuJj1Ru.js} +1 -1
  249. package/dist/web/assets/use-inline-blame-BUuJj1Ru.js.br +0 -0
  250. package/dist/web/assets/use-inline-blame-BUuJj1Ru.js.gz +0 -0
  251. package/dist/web/assets/{use-lsp-B-otjr-6.js → use-lsp-Bo4z4gLx.js} +1 -1
  252. package/dist/web/assets/use-lsp-Bo4z4gLx.js.br +0 -0
  253. package/dist/web/assets/use-lsp-Bo4z4gLx.js.gz +0 -0
  254. package/dist/web/assets/{use-monaco-theme-BiDXRJ4e.js → use-monaco-theme-1ANtPWqA.js} +1 -1
  255. package/dist/web/assets/{use-process-kill-BW2UW0mZ.js → use-process-kill-CvlXPal4.js} +1 -1
  256. package/dist/web/assets/use-process-kill-CvlXPal4.js.br +0 -0
  257. package/dist/web/assets/use-process-kill-CvlXPal4.js.gz +0 -0
  258. package/dist/web/assets/{use-sql-schema-info-D_Kc2FI7.js → use-sql-schema-info-DQzUqb7q.js} +1 -1
  259. package/dist/web/assets/use-sql-schema-info-DQzUqb7q.js.br +0 -0
  260. package/dist/web/assets/use-sql-schema-info-DQzUqb7q.js.gz +0 -0
  261. package/dist/web/assets/{use-websocket-n3Ud9vEA.js → use-websocket-QCAD3F5b.js} +1 -1
  262. package/dist/web/assets/{web-preview-tab-doNpegW3.js → web-preview-tab-Cp0LuL0T.js} +1 -1
  263. package/dist/web/assets/web-preview-tab-Cp0LuL0T.js.br +0 -0
  264. package/dist/web/assets/web-preview-tab-Cp0LuL0T.js.gz +0 -0
  265. package/dist/web/index.html +2 -2
  266. package/dist/web/index.html.br +0 -0
  267. package/dist/web/index.html.gz +0 -0
  268. package/dist/web/sw.js +1 -1
  269. package/dist/web/sw.js.br +0 -0
  270. package/dist/web/sw.js.gz +0 -0
  271. package/package.json +1 -1
  272. package/src/providers/claude-agent-sdk.ts +37 -7
  273. package/src/server/ws/chat-token-rotation.ts +150 -0
  274. package/src/server/ws/chat.ts +28 -0
  275. package/src/services/account.service.ts +14 -0
  276. package/src/web/components/chat/chat-reconnect-banner.tsx +54 -0
  277. package/src/web/components/chat/chat-tab.tsx +10 -13
  278. package/src/web/hooks/use-reconnect-indicator.ts +49 -0
  279. package/dist/web/assets/accounts-settings-section-B5YXy0vd.js.br +0 -0
  280. package/dist/web/assets/accounts-settings-section-B5YXy0vd.js.gz +0 -0
  281. package/dist/web/assets/agent-session-sheet-Bj42PwCK.js.br +0 -0
  282. package/dist/web/assets/agent-session-sheet-Bj42PwCK.js.gz +0 -0
  283. package/dist/web/assets/agent-session-window-content-LqL6fVPU.js.br +0 -0
  284. package/dist/web/assets/agent-session-window-content-LqL6fVPU.js.gz +0 -0
  285. package/dist/web/assets/ai-resource-editor-jVv2nvAG.js.br +0 -0
  286. package/dist/web/assets/ai-resource-editor-jVv2nvAG.js.gz +0 -0
  287. package/dist/web/assets/ai-settings-section-C9Q-v8fY.js.br +0 -0
  288. package/dist/web/assets/ai-settings-section-C9Q-v8fY.js.gz +0 -0
  289. package/dist/web/assets/android-tab-CwqiuEZP.js.br +0 -0
  290. package/dist/web/assets/android-tab-CwqiuEZP.js.gz +0 -0
  291. package/dist/web/assets/appearance-settings-section-D0JIfiRx.js.br +0 -0
  292. package/dist/web/assets/appearance-settings-section-D0JIfiRx.js.gz +0 -0
  293. package/dist/web/assets/apps-panel-Cz_KJ37M.js.br +0 -0
  294. package/dist/web/assets/apps-panel-Cz_KJ37M.js.gz +0 -0
  295. package/dist/web/assets/branch-review-tab-C3QNpsm7.js.br +0 -0
  296. package/dist/web/assets/branch-review-tab-C3QNpsm7.js.gz +0 -0
  297. package/dist/web/assets/chat-tab-D14CUl0J.js.br +0 -0
  298. package/dist/web/assets/chat-tab-D14CUl0J.js.gz +0 -0
  299. package/dist/web/assets/chat-tab-D8iAVZKV.js +0 -16
  300. package/dist/web/assets/chat-tab-D8iAVZKV.js.br +0 -0
  301. package/dist/web/assets/chat-tab-D8iAVZKV.js.gz +0 -0
  302. package/dist/web/assets/code-editor-B7gbH4-x.js.br +0 -0
  303. package/dist/web/assets/code-editor-B7gbH4-x.js.gz +0 -0
  304. package/dist/web/assets/codex-reset-credit-button-DQDtyvnd.js.br +0 -0
  305. package/dist/web/assets/conflict-editor-ktOkrODA.js.br +0 -0
  306. package/dist/web/assets/conflict-editor-ktOkrODA.js.gz +0 -0
  307. package/dist/web/assets/connection-form-tab-CxvExR7T.js.br +0 -0
  308. package/dist/web/assets/connection-form-tab-CxvExR7T.js.gz +0 -0
  309. package/dist/web/assets/csv-preview-CBDMTEsF.js.br +0 -0
  310. package/dist/web/assets/csv-preview-CBDMTEsF.js.gz +0 -0
  311. package/dist/web/assets/db-tab-parts-BgVpKbi6.js.br +0 -0
  312. package/dist/web/assets/db-tab-parts-BgVpKbi6.js.gz +0 -0
  313. package/dist/web/assets/design-settings-section-DPGw61pY.js.br +0 -0
  314. package/dist/web/assets/design-settings-section-DPGw61pY.js.gz +0 -0
  315. package/dist/web/assets/design-tab-BiCqHNl3.js.br +0 -0
  316. package/dist/web/assets/design-tab-BiCqHNl3.js.gz +0 -0
  317. package/dist/web/assets/designs-sidebar-panel-pcJ2Rx5G.js.br +0 -0
  318. package/dist/web/assets/designs-sidebar-panel-pcJ2Rx5G.js.gz +0 -0
  319. package/dist/web/assets/diff-viewer-CLAvyqVG.js.br +0 -0
  320. package/dist/web/assets/diff-viewer-CLAvyqVG.js.gz +0 -0
  321. package/dist/web/assets/docx-preview-XSkD2IL6.js.br +0 -0
  322. package/dist/web/assets/docx-preview-XSkD2IL6.js.gz +0 -0
  323. package/dist/web/assets/editor-dialog-cQvqVZAR.js.br +0 -0
  324. package/dist/web/assets/editor-dialog-cQvqVZAR.js.gz +0 -0
  325. package/dist/web/assets/editor-language-service-6-7mS6_9.js.br +0 -0
  326. package/dist/web/assets/editor-language-service-6-7mS6_9.js.gz +0 -0
  327. package/dist/web/assets/explorer-body-BlGDPmNa.js.br +0 -0
  328. package/dist/web/assets/explorer-body-BlGDPmNa.js.gz +0 -0
  329. package/dist/web/assets/explorer-window-content-BLWY8GJ_.js.br +0 -0
  330. package/dist/web/assets/explorer-window-content-BLWY8GJ_.js.gz +0 -0
  331. package/dist/web/assets/extension-manager-section-NSEt5f4z.js.br +0 -0
  332. package/dist/web/assets/extension-manager-section-NSEt5f4z.js.gz +0 -0
  333. package/dist/web/assets/extension-webview-BkAEBWin.js.br +0 -0
  334. package/dist/web/assets/extension-webview-BkAEBWin.js.gz +0 -0
  335. package/dist/web/assets/files-settings-section-Bq7liJ_r.js.br +0 -0
  336. package/dist/web/assets/files-settings-section-Bq7liJ_r.js.gz +0 -0
  337. package/dist/web/assets/general-settings-section-DvHROAMa.js.br +0 -0
  338. package/dist/web/assets/general-settings-section-DvHROAMa.js.gz +0 -0
  339. package/dist/web/assets/git-log-panel-Cu9kK48H.js.br +0 -0
  340. package/dist/web/assets/git-log-panel-Cu9kK48H.js.gz +0 -0
  341. package/dist/web/assets/git-review-tab-B_JT649W.js.br +0 -0
  342. package/dist/web/assets/git-review-tab-B_JT649W.js.gz +0 -0
  343. package/dist/web/assets/glide-data-grid-BlposOph.js.br +0 -0
  344. package/dist/web/assets/glide-data-grid-BlposOph.js.gz +0 -0
  345. package/dist/web/assets/grid-save-dialog-B0d0qhnN.js.br +0 -0
  346. package/dist/web/assets/grid-save-dialog-B0d0qhnN.js.gz +0 -0
  347. package/dist/web/assets/group-chat-tab-jirUsGm-.js.br +0 -0
  348. package/dist/web/assets/group-chat-tab-jirUsGm-.js.gz +0 -0
  349. package/dist/web/assets/impexp-tab-HJVcOTYJ.js.br +0 -0
  350. package/dist/web/assets/impexp-tab-HJVcOTYJ.js.gz +0 -0
  351. package/dist/web/assets/index-DLB3j_P0.js.br +0 -0
  352. package/dist/web/assets/index-DLB3j_P0.js.gz +0 -0
  353. package/dist/web/assets/index-M3DUzop8.css +0 -1
  354. package/dist/web/assets/index-M3DUzop8.css.br +0 -0
  355. package/dist/web/assets/index-M3DUzop8.css.gz +0 -0
  356. package/dist/web/assets/keyboard-shortcuts-section-DN21gB-T.js.br +0 -0
  357. package/dist/web/assets/keyboard-shortcuts-section-DN21gB-T.js.gz +0 -0
  358. package/dist/web/assets/language-servers-section-DOcZ72FJ.js.br +0 -0
  359. package/dist/web/assets/language-servers-section-DOcZ72FJ.js.gz +0 -0
  360. package/dist/web/assets/logs-app-C7n9GSCn.js.br +0 -0
  361. package/dist/web/assets/lsp-client-DpYmPhXX.js.br +0 -0
  362. package/dist/web/assets/lsp-client-DpYmPhXX.js.gz +0 -0
  363. package/dist/web/assets/markdown-renderer-CtEl6qAt.js.br +0 -0
  364. package/dist/web/assets/markdown-renderer-CtEl6qAt.js.gz +0 -0
  365. package/dist/web/assets/mobile-explorer-sheet-B-rcKEL9.js.br +0 -0
  366. package/dist/web/assets/mobile-explorer-sheet-B-rcKEL9.js.gz +0 -0
  367. package/dist/web/assets/new-design-dialog-DgztQK5R.js.br +0 -0
  368. package/dist/web/assets/new-design-dialog-DgztQK5R.js.gz +0 -0
  369. package/dist/web/assets/notifications-settings-section-JiGvaCFx.js.br +0 -0
  370. package/dist/web/assets/notifications-settings-section-JiGvaCFx.js.gz +0 -0
  371. package/dist/web/assets/ppmbot-settings-section-XfrZ8VoC.js.br +0 -0
  372. package/dist/web/assets/ppmbot-settings-section-XfrZ8VoC.js.gz +0 -0
  373. package/dist/web/assets/problems-panel-CDzwSJn6.js.br +0 -0
  374. package/dist/web/assets/problems-panel-CDzwSJn6.js.gz +0 -0
  375. package/dist/web/assets/process-table-C_B39GHm.js.br +0 -0
  376. package/dist/web/assets/process-table-C_B39GHm.js.gz +0 -0
  377. package/dist/web/assets/proxy-settings-section-CvaOvYIv.js.br +0 -0
  378. package/dist/web/assets/proxy-settings-section-CvaOvYIv.js.gz +0 -0
  379. package/dist/web/assets/query-tab-B8aMpAbI.js.br +0 -0
  380. package/dist/web/assets/query-tab-B8aMpAbI.js.gz +0 -0
  381. package/dist/web/assets/remote-access-settings-section-Bv0eawR4.js.br +0 -0
  382. package/dist/web/assets/remote-access-settings-section-Bv0eawR4.js.gz +0 -0
  383. package/dist/web/assets/remote-desktop-clipboard-notice-BpDakRY0.js.br +0 -0
  384. package/dist/web/assets/remote-desktop-clipboard-notice-BpDakRY0.js.gz +0 -0
  385. package/dist/web/assets/remote-desktop-mobile-sheet-C5GQ3kzY.js.br +0 -0
  386. package/dist/web/assets/remote-desktop-mobile-sheet-C5GQ3kzY.js.gz +0 -0
  387. package/dist/web/assets/remote-desktop-mobile-view-CZb5EMEK.js.br +0 -0
  388. package/dist/web/assets/remote-desktop-mobile-view-CZb5EMEK.js.gz +0 -0
  389. package/dist/web/assets/remote-desktop-readiness-gate-CipNtLX5.js.br +0 -0
  390. package/dist/web/assets/remote-desktop-readiness-gate-CipNtLX5.js.gz +0 -0
  391. package/dist/web/assets/remote-desktop-window-content-B7OXXvAM.js.br +0 -0
  392. package/dist/web/assets/remote-desktop-window-content-B7OXXvAM.js.gz +0 -0
  393. package/dist/web/assets/rename-object-dialog-B4W6S7Xg.js.br +0 -0
  394. package/dist/web/assets/rename-object-dialog-B4W6S7Xg.js.gz +0 -0
  395. package/dist/web/assets/review-code-B4osj0k1.js.br +0 -0
  396. package/dist/web/assets/review-code-B4osj0k1.js.gz +0 -0
  397. package/dist/web/assets/save-as-dialog-DpXv_f1d.js.br +0 -5
  398. package/dist/web/assets/save-as-dialog-DpXv_f1d.js.gz +0 -0
  399. package/dist/web/assets/schedules-settings-section-BbaPbc28.js.br +0 -0
  400. package/dist/web/assets/schedules-settings-section-BbaPbc28.js.gz +0 -0
  401. package/dist/web/assets/session-review-tab-Aq3FEooH.js.br +0 -0
  402. package/dist/web/assets/session-review-tab-Aq3FEooH.js.gz +0 -0
  403. package/dist/web/assets/settings-body-vXbOWchN.js.br +0 -0
  404. package/dist/web/assets/settings-body-vXbOWchN.js.gz +0 -0
  405. package/dist/web/assets/settings-rows-DD7Gxmst.js.br +0 -0
  406. package/dist/web/assets/settings-rows-DD7Gxmst.js.gz +0 -0
  407. package/dist/web/assets/settings-section-content-CVtvJ6ze.js +0 -1
  408. package/dist/web/assets/settings-section-content-DkUJFop6.js.br +0 -0
  409. package/dist/web/assets/settings-section-content-DkUJFop6.js.gz +0 -0
  410. package/dist/web/assets/slash-command-picker-lILYDfK3.js.br +0 -0
  411. package/dist/web/assets/slash-command-picker-lILYDfK3.js.gz +0 -0
  412. package/dist/web/assets/sql-object-tab-Da42SMK5.js.br +0 -0
  413. package/dist/web/assets/sql-object-tab-Da42SMK5.js.gz +0 -0
  414. package/dist/web/assets/sqlite-viewer-V3SKNukY.js.br +0 -0
  415. package/dist/web/assets/sqlite-viewer-V3SKNukY.js.gz +0 -0
  416. package/dist/web/assets/structure-save-dialog-B635iyPI.js.br +0 -0
  417. package/dist/web/assets/structure-save-dialog-B635iyPI.js.gz +0 -0
  418. package/dist/web/assets/structure-tab-Co9d8ktU.js.br +0 -0
  419. package/dist/web/assets/structure-tab-Co9d8ktU.js.gz +0 -0
  420. package/dist/web/assets/system-monitor-body-DC0j8RqP.js.br +0 -0
  421. package/dist/web/assets/system-monitor-body-DC0j8RqP.js.gz +0 -0
  422. package/dist/web/assets/tab-host-window-content-Crvt1r5F.js.br +0 -0
  423. package/dist/web/assets/tab-host-window-content-Crvt1r5F.js.gz +0 -0
  424. package/dist/web/assets/table-tab-C2cYw7a0.js.br +0 -0
  425. package/dist/web/assets/table-tab-C2cYw7a0.js.gz +0 -0
  426. package/dist/web/assets/telegram-bot-token-field-BqNtOo5n.js.br +0 -0
  427. package/dist/web/assets/telegram-bot-token-field-BqNtOo5n.js.gz +0 -0
  428. package/dist/web/assets/terminal-tab-CrKxibGC.js.br +0 -0
  429. package/dist/web/assets/terminal-tab-CrKxibGC.js.gz +0 -0
  430. package/dist/web/assets/tool-cards-BRto67jM.js.br +0 -0
  431. package/dist/web/assets/tool-cards-BRto67jM.js.gz +0 -0
  432. package/dist/web/assets/tools-settings-section-DEvZsrxq.js.br +0 -0
  433. package/dist/web/assets/tools-settings-section-DEvZsrxq.js.gz +0 -0
  434. package/dist/web/assets/use-inline-blame-CRWJU6Rq.js.br +0 -0
  435. package/dist/web/assets/use-inline-blame-CRWJU6Rq.js.gz +0 -0
  436. package/dist/web/assets/use-lsp-B-otjr-6.js.br +0 -0
  437. package/dist/web/assets/use-lsp-B-otjr-6.js.gz +0 -0
  438. package/dist/web/assets/use-process-kill-BW2UW0mZ.js.br +0 -0
  439. package/dist/web/assets/use-process-kill-BW2UW0mZ.js.gz +0 -0
  440. package/dist/web/assets/use-sql-schema-info-D_Kc2FI7.js.br +0 -0
  441. package/dist/web/assets/use-sql-schema-info-D_Kc2FI7.js.gz +0 -0
  442. package/dist/web/assets/web-preview-tab-doNpegW3.js.br +0 -0
  443. 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
+ }
@@ -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
+ }