@hienlh/ppm 0.23.15 → 0.23.17

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 (455) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/CLAUDE.md +1 -0
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/cli-reference.md +4 -4
  5. package/assets/skills/ppm/references/http-api.md +1 -1
  6. package/dist/web/assets/{accounts-settings-section-B5YXy0vd.js → accounts-settings-section-BqsI4Ixj.js} +1 -1
  7. package/dist/web/assets/accounts-settings-section-BqsI4Ixj.js.br +0 -0
  8. package/dist/web/assets/accounts-settings-section-BqsI4Ixj.js.gz +0 -0
  9. package/dist/web/assets/{agent-session-sheet-Bj42PwCK.js → agent-session-sheet-BmoNB8BG.js} +2 -2
  10. package/dist/web/assets/agent-session-sheet-BmoNB8BG.js.br +0 -0
  11. package/dist/web/assets/agent-session-sheet-BmoNB8BG.js.gz +0 -0
  12. package/dist/web/assets/{agent-session-window-content-LqL6fVPU.js → agent-session-window-content-rlCrYBYv.js} +1 -1
  13. package/dist/web/assets/agent-session-window-content-rlCrYBYv.js.br +0 -0
  14. package/dist/web/assets/agent-session-window-content-rlCrYBYv.js.gz +0 -0
  15. package/dist/web/assets/{ai-resource-editor-jVv2nvAG.js → ai-resource-editor-CUOR9Xtf.js} +1 -1
  16. package/dist/web/assets/ai-resource-editor-CUOR9Xtf.js.br +0 -0
  17. package/dist/web/assets/ai-resource-editor-CUOR9Xtf.js.gz +0 -0
  18. package/dist/web/assets/{ai-settings-section-C9Q-v8fY.js → ai-settings-section-CNdbcCaI.js} +1 -1
  19. package/dist/web/assets/ai-settings-section-CNdbcCaI.js.br +0 -0
  20. package/dist/web/assets/ai-settings-section-CNdbcCaI.js.gz +0 -0
  21. package/dist/web/assets/{ai-settings-section-CN5BR1ZZ.js → ai-settings-section-aXXTb0dN.js} +1 -1
  22. package/dist/web/assets/{android-tab-CwqiuEZP.js → android-tab-BsuTMKJw.js} +1 -1
  23. package/dist/web/assets/android-tab-BsuTMKJw.js.br +0 -0
  24. package/dist/web/assets/android-tab-BsuTMKJw.js.gz +0 -0
  25. package/dist/web/assets/{appearance-settings-section-D0JIfiRx.js → appearance-settings-section-Dk6d_fTo.js} +1 -1
  26. package/dist/web/assets/appearance-settings-section-Dk6d_fTo.js.br +0 -0
  27. package/dist/web/assets/appearance-settings-section-Dk6d_fTo.js.gz +0 -0
  28. package/dist/web/assets/{apps-panel-Cz_KJ37M.js → apps-panel-C20ys4Cy.js} +1 -1
  29. package/dist/web/assets/apps-panel-C20ys4Cy.js.br +0 -0
  30. package/dist/web/assets/apps-panel-C20ys4Cy.js.gz +0 -0
  31. package/dist/web/assets/{branch-review-tab-C3QNpsm7.js → branch-review-tab-CcvkQMRA.js} +1 -1
  32. package/dist/web/assets/branch-review-tab-CcvkQMRA.js.br +0 -0
  33. package/dist/web/assets/branch-review-tab-CcvkQMRA.js.gz +0 -0
  34. package/dist/web/assets/{chat-tab-D14CUl0J.js → chat-tab-BAh37UwG.js} +1 -1
  35. package/dist/web/assets/chat-tab-BAh37UwG.js.br +0 -0
  36. package/dist/web/assets/chat-tab-BAh37UwG.js.gz +0 -0
  37. package/dist/web/assets/chat-tab-D-cl4yMo.js +19 -0
  38. package/dist/web/assets/chat-tab-D-cl4yMo.js.br +0 -0
  39. package/dist/web/assets/chat-tab-D-cl4yMo.js.gz +0 -0
  40. package/dist/web/assets/{code-editor-B7gbH4-x.js → code-editor-DIGsPNPS.js} +4 -4
  41. package/dist/web/assets/code-editor-DIGsPNPS.js.br +0 -0
  42. package/dist/web/assets/{code-editor-B7gbH4-x.js.gz → code-editor-DIGsPNPS.js.gz} +0 -0
  43. package/dist/web/assets/{codex-reset-credit-button-DQDtyvnd.js → codex-reset-credit-button-C0a2svAw.js} +1 -1
  44. package/dist/web/assets/codex-reset-credit-button-C0a2svAw.js.br +0 -0
  45. package/dist/web/assets/codex-reset-credit-button-C0a2svAw.js.gz +0 -0
  46. package/dist/web/assets/{conflict-editor-ktOkrODA.js → conflict-editor-zORSNMk6.js} +1 -1
  47. package/dist/web/assets/conflict-editor-zORSNMk6.js.br +0 -0
  48. package/dist/web/assets/conflict-editor-zORSNMk6.js.gz +0 -0
  49. package/dist/web/assets/{connection-form-tab-CxvExR7T.js → connection-form-tab-BkE7wACA.js} +1 -1
  50. package/dist/web/assets/connection-form-tab-BkE7wACA.js.br +0 -0
  51. package/dist/web/assets/connection-form-tab-BkE7wACA.js.gz +0 -0
  52. package/dist/web/assets/{csv-preview-CBDMTEsF.js → csv-preview-CDUY0I94.js} +1 -1
  53. package/dist/web/assets/csv-preview-CDUY0I94.js.br +0 -0
  54. package/dist/web/assets/csv-preview-CDUY0I94.js.gz +0 -0
  55. package/dist/web/assets/{db-tab-parts-BgVpKbi6.js → db-tab-parts-tgVIgcs5.js} +1 -1
  56. package/dist/web/assets/db-tab-parts-tgVIgcs5.js.br +0 -0
  57. package/dist/web/assets/db-tab-parts-tgVIgcs5.js.gz +0 -0
  58. package/dist/web/assets/{design-settings-section-DPGw61pY.js → design-settings-section-CqFPwhis.js} +1 -1
  59. package/dist/web/assets/design-settings-section-CqFPwhis.js.br +0 -0
  60. package/dist/web/assets/design-settings-section-CqFPwhis.js.gz +0 -0
  61. package/dist/web/assets/{design-tab-BiCqHNl3.js → design-tab-CVTzge3p.js} +1 -1
  62. package/dist/web/assets/design-tab-CVTzge3p.js.br +0 -0
  63. package/dist/web/assets/design-tab-CVTzge3p.js.gz +0 -0
  64. package/dist/web/assets/{designs-sidebar-panel-pcJ2Rx5G.js → designs-sidebar-panel-DKTSE1LS.js} +1 -1
  65. package/dist/web/assets/designs-sidebar-panel-DKTSE1LS.js.br +0 -0
  66. package/dist/web/assets/designs-sidebar-panel-DKTSE1LS.js.gz +0 -0
  67. package/dist/web/assets/{diff-viewer-CLAvyqVG.js → diff-viewer-3VDHcDag.js} +1 -1
  68. package/dist/web/assets/diff-viewer-3VDHcDag.js.br +0 -0
  69. package/dist/web/assets/diff-viewer-3VDHcDag.js.gz +0 -0
  70. package/dist/web/assets/{docx-preview-XSkD2IL6.js → docx-preview-FgE_WU6t.js} +1 -1
  71. package/dist/web/assets/docx-preview-FgE_WU6t.js.br +0 -0
  72. package/dist/web/assets/docx-preview-FgE_WU6t.js.gz +0 -0
  73. package/dist/web/assets/{editor-dialog-cQvqVZAR.js → editor-dialog-BhK77FSu.js} +1 -1
  74. package/dist/web/assets/editor-dialog-BhK77FSu.js.br +0 -0
  75. package/dist/web/assets/editor-dialog-BhK77FSu.js.gz +0 -0
  76. package/dist/web/assets/{editor-language-service-6-7mS6_9.js → editor-language-service-BIN7M57r.js} +1 -1
  77. package/dist/web/assets/editor-language-service-BIN7M57r.js.br +4 -0
  78. package/dist/web/assets/editor-language-service-BIN7M57r.js.gz +0 -0
  79. package/dist/web/assets/{explorer-body-BlGDPmNa.js → explorer-body-DNsWiVO3.js} +1 -1
  80. package/dist/web/assets/explorer-body-DNsWiVO3.js.br +0 -0
  81. package/dist/web/assets/explorer-body-DNsWiVO3.js.gz +0 -0
  82. package/dist/web/assets/{explorer-window-content-BLWY8GJ_.js → explorer-window-content-DnOJYhRX.js} +1 -1
  83. package/dist/web/assets/explorer-window-content-DnOJYhRX.js.br +0 -0
  84. package/dist/web/assets/explorer-window-content-DnOJYhRX.js.gz +0 -0
  85. package/dist/web/assets/{extension-manager-section-NSEt5f4z.js → extension-manager-section-CJtRpUUs.js} +1 -1
  86. package/dist/web/assets/extension-manager-section-CJtRpUUs.js.br +0 -0
  87. package/dist/web/assets/extension-manager-section-CJtRpUUs.js.gz +0 -0
  88. package/dist/web/assets/{extension-webview-BkAEBWin.js → extension-webview-Bza7cPL1.js} +1 -1
  89. package/dist/web/assets/extension-webview-Bza7cPL1.js.br +0 -0
  90. package/dist/web/assets/extension-webview-Bza7cPL1.js.gz +0 -0
  91. package/dist/web/assets/{files-settings-section-Bq7liJ_r.js → files-settings-section-8rWMj1Oi.js} +1 -1
  92. package/dist/web/assets/files-settings-section-8rWMj1Oi.js.br +0 -0
  93. package/dist/web/assets/files-settings-section-8rWMj1Oi.js.gz +0 -0
  94. package/dist/web/assets/{general-settings-section-DvHROAMa.js → general-settings-section-joWQEgIn.js} +1 -1
  95. package/dist/web/assets/general-settings-section-joWQEgIn.js.br +0 -0
  96. package/dist/web/assets/general-settings-section-joWQEgIn.js.gz +0 -0
  97. package/dist/web/assets/{git-log-panel-Cu9kK48H.js → git-log-panel-BxYYWEiU.js} +1 -1
  98. package/dist/web/assets/git-log-panel-BxYYWEiU.js.br +0 -0
  99. package/dist/web/assets/git-log-panel-BxYYWEiU.js.gz +0 -0
  100. package/dist/web/assets/{git-review-tab-B_JT649W.js → git-review-tab-Btnvgk1e.js} +1 -1
  101. package/dist/web/assets/git-review-tab-Btnvgk1e.js.br +0 -0
  102. package/dist/web/assets/git-review-tab-Btnvgk1e.js.gz +0 -0
  103. package/dist/web/assets/{glide-data-grid-BlposOph.js → glide-data-grid-lCwaM1Gy.js} +1 -1
  104. package/dist/web/assets/glide-data-grid-lCwaM1Gy.js.br +0 -0
  105. package/dist/web/assets/glide-data-grid-lCwaM1Gy.js.gz +0 -0
  106. package/dist/web/assets/{grid-save-dialog-B0d0qhnN.js → grid-save-dialog-DoEEQ1vM.js} +1 -1
  107. package/dist/web/assets/grid-save-dialog-DoEEQ1vM.js.br +0 -0
  108. package/dist/web/assets/grid-save-dialog-DoEEQ1vM.js.gz +0 -0
  109. package/dist/web/assets/{group-chat-tab-jirUsGm-.js → group-chat-tab-B7Dwx9uL.js} +1 -1
  110. package/dist/web/assets/group-chat-tab-B7Dwx9uL.js.br +0 -0
  111. package/dist/web/assets/group-chat-tab-B7Dwx9uL.js.gz +0 -0
  112. package/dist/web/assets/{impexp-tab-HJVcOTYJ.js → impexp-tab-BtEDbQSn.js} +1 -1
  113. package/dist/web/assets/impexp-tab-BtEDbQSn.js.br +0 -0
  114. package/dist/web/assets/impexp-tab-BtEDbQSn.js.gz +0 -0
  115. package/dist/web/assets/index-BACBJm4i.css +1 -0
  116. package/dist/web/assets/index-BACBJm4i.css.br +0 -0
  117. package/dist/web/assets/index-BACBJm4i.css.gz +0 -0
  118. package/dist/web/assets/{index-DLB3j_P0.js → index-CPBujJc_.js} +5 -5
  119. package/dist/web/assets/index-CPBujJc_.js.br +0 -0
  120. package/dist/web/assets/index-CPBujJc_.js.gz +0 -0
  121. package/dist/web/assets/{jira-watcher-section-EQvdnETB.js → jira-watcher-section-mnly-maX.js} +1 -1
  122. package/dist/web/assets/{keybindings-store-CEy-Sdcc.js → keybindings-store-BSPTMRiv.js} +1 -1
  123. package/dist/web/assets/{keyboard-shortcuts-section-DN21gB-T.js → keyboard-shortcuts-section-DULhEWPg.js} +1 -1
  124. package/dist/web/assets/keyboard-shortcuts-section-DULhEWPg.js.br +0 -0
  125. package/dist/web/assets/keyboard-shortcuts-section-DULhEWPg.js.gz +0 -0
  126. package/dist/web/assets/{language-servers-section-DOcZ72FJ.js → language-servers-section-CwBJsJ31.js} +2 -2
  127. package/dist/web/assets/language-servers-section-CwBJsJ31.js.br +0 -0
  128. package/dist/web/assets/language-servers-section-CwBJsJ31.js.gz +0 -0
  129. package/dist/web/assets/{logs-app-C7n9GSCn.js → logs-app-DatfI2Up.js} +1 -1
  130. package/dist/web/assets/logs-app-DatfI2Up.js.br +0 -0
  131. package/dist/web/assets/logs-app-DatfI2Up.js.gz +0 -0
  132. package/dist/web/assets/{logs-tab-CSOiexvx.js → logs-tab-an_9ujcz.js} +1 -1
  133. package/dist/web/assets/{logs-window-content-C4Qs6-0q.js → logs-window-content-Usr3JTmE.js} +1 -1
  134. package/dist/web/assets/{lsp-client-DpYmPhXX.js → lsp-client-tCCcXWsR.js} +1 -1
  135. package/dist/web/assets/lsp-client-tCCcXWsR.js.br +0 -0
  136. package/dist/web/assets/lsp-client-tCCcXWsR.js.gz +0 -0
  137. package/dist/web/assets/{markdown-renderer-CtEl6qAt.js → markdown-renderer-D0YUMQLE.js} +1 -1
  138. package/dist/web/assets/markdown-renderer-D0YUMQLE.js.br +0 -0
  139. package/dist/web/assets/markdown-renderer-D0YUMQLE.js.gz +0 -0
  140. package/dist/web/assets/{markdown-renderer-Df3r-CCb.js → markdown-renderer-DdzxNveP.js} +1 -1
  141. package/dist/web/assets/{mobile-explorer-sheet-B-rcKEL9.js → mobile-explorer-sheet-Bo27IRye.js} +1 -1
  142. package/dist/web/assets/mobile-explorer-sheet-Bo27IRye.js.br +0 -0
  143. package/dist/web/assets/mobile-explorer-sheet-Bo27IRye.js.gz +0 -0
  144. package/dist/web/assets/{new-design-dialog-DgztQK5R.js → new-design-dialog-DIOCWZW1.js} +1 -1
  145. package/dist/web/assets/new-design-dialog-DIOCWZW1.js.br +0 -0
  146. package/dist/web/assets/new-design-dialog-DIOCWZW1.js.gz +0 -0
  147. package/dist/web/assets/{notification-store-B92dvP_u.js → notification-store-CRPOp-6B.js} +1 -1
  148. package/dist/web/assets/{notifications-settings-section-JiGvaCFx.js → notifications-settings-section-DSSCTkfX.js} +1 -1
  149. package/dist/web/assets/notifications-settings-section-DSSCTkfX.js.br +0 -0
  150. package/dist/web/assets/notifications-settings-section-DSSCTkfX.js.gz +0 -0
  151. package/dist/web/assets/{ppmbot-settings-section-XfrZ8VoC.js → ppmbot-settings-section-BIYSlZu5.js} +1 -1
  152. package/dist/web/assets/ppmbot-settings-section-BIYSlZu5.js.br +0 -0
  153. package/dist/web/assets/ppmbot-settings-section-BIYSlZu5.js.gz +0 -0
  154. package/dist/web/assets/{problems-panel-CDzwSJn6.js → problems-panel-CH-00nA7.js} +1 -1
  155. package/dist/web/assets/problems-panel-CH-00nA7.js.br +0 -0
  156. package/dist/web/assets/problems-panel-CH-00nA7.js.gz +0 -0
  157. package/dist/web/assets/{process-table-C_B39GHm.js → process-table-DQO0g5do.js} +1 -1
  158. package/dist/web/assets/process-table-DQO0g5do.js.br +0 -0
  159. package/dist/web/assets/process-table-DQO0g5do.js.gz +0 -0
  160. package/dist/web/assets/{proxy-settings-section-CvaOvYIv.js → proxy-settings-section-Buo3hvQ4.js} +1 -1
  161. package/dist/web/assets/proxy-settings-section-Buo3hvQ4.js.br +0 -0
  162. package/dist/web/assets/proxy-settings-section-Buo3hvQ4.js.gz +0 -0
  163. package/dist/web/assets/{query-tab-B8aMpAbI.js → query-tab-CU0QVZpj.js} +1 -1
  164. package/dist/web/assets/query-tab-CU0QVZpj.js.br +0 -0
  165. package/dist/web/assets/query-tab-CU0QVZpj.js.gz +0 -0
  166. package/dist/web/assets/{remote-access-settings-section-Bv0eawR4.js → remote-access-settings-section-vlHMJPdY.js} +1 -1
  167. package/dist/web/assets/remote-access-settings-section-vlHMJPdY.js.br +0 -0
  168. package/dist/web/assets/remote-access-settings-section-vlHMJPdY.js.gz +0 -0
  169. package/dist/web/assets/{remote-desktop-clipboard-notice-BpDakRY0.js → remote-desktop-clipboard-notice-DNqX7d1L.js} +1 -1
  170. package/dist/web/assets/remote-desktop-clipboard-notice-DNqX7d1L.js.br +0 -0
  171. package/dist/web/assets/remote-desktop-clipboard-notice-DNqX7d1L.js.gz +0 -0
  172. package/dist/web/assets/{remote-desktop-mobile-sheet-C5GQ3kzY.js → remote-desktop-mobile-sheet-Dz7tctpc.js} +2 -2
  173. package/dist/web/assets/remote-desktop-mobile-sheet-Dz7tctpc.js.br +0 -0
  174. package/dist/web/assets/remote-desktop-mobile-sheet-Dz7tctpc.js.gz +0 -0
  175. package/dist/web/assets/{remote-desktop-mobile-view-CZb5EMEK.js → remote-desktop-mobile-view-BqhQdShH.js} +1 -1
  176. package/dist/web/assets/remote-desktop-mobile-view-BqhQdShH.js.br +0 -0
  177. package/dist/web/assets/remote-desktop-mobile-view-BqhQdShH.js.gz +0 -0
  178. package/dist/web/assets/{remote-desktop-readiness-gate-CipNtLX5.js → remote-desktop-readiness-gate-rnTq4ZQS.js} +1 -1
  179. package/dist/web/assets/remote-desktop-readiness-gate-rnTq4ZQS.js.br +0 -0
  180. package/dist/web/assets/remote-desktop-readiness-gate-rnTq4ZQS.js.gz +0 -0
  181. package/dist/web/assets/{remote-desktop-window-content-B7OXXvAM.js → remote-desktop-window-content-TqjHt0_5.js} +1 -1
  182. package/dist/web/assets/remote-desktop-window-content-TqjHt0_5.js.br +0 -0
  183. package/dist/web/assets/remote-desktop-window-content-TqjHt0_5.js.gz +0 -0
  184. package/dist/web/assets/{rename-object-dialog-B4W6S7Xg.js → rename-object-dialog-D0YpY40E.js} +1 -1
  185. package/dist/web/assets/rename-object-dialog-D0YpY40E.js.br +0 -0
  186. package/dist/web/assets/rename-object-dialog-D0YpY40E.js.gz +0 -0
  187. package/dist/web/assets/{review-code-B4osj0k1.js → review-code-DsF9921h.js} +1 -1
  188. package/dist/web/assets/review-code-DsF9921h.js.br +0 -0
  189. package/dist/web/assets/{review-code-B4osj0k1.js.gz → review-code-DsF9921h.js.gz} +0 -0
  190. package/dist/web/assets/{save-as-dialog-DpXv_f1d.js → save-as-dialog-niU7kVMJ.js} +1 -1
  191. package/dist/web/assets/save-as-dialog-niU7kVMJ.js.br +0 -0
  192. package/dist/web/assets/save-as-dialog-niU7kVMJ.js.gz +0 -0
  193. package/dist/web/assets/{schedules-settings-section-BbaPbc28.js → schedules-settings-section-Heuv2Kj4.js} +1 -1
  194. package/dist/web/assets/schedules-settings-section-Heuv2Kj4.js.br +0 -0
  195. package/dist/web/assets/schedules-settings-section-Heuv2Kj4.js.gz +0 -0
  196. package/dist/web/assets/{session-review-tab-Aq3FEooH.js → session-review-tab-Dob0FLvA.js} +1 -1
  197. package/dist/web/assets/session-review-tab-Dob0FLvA.js.br +0 -0
  198. package/dist/web/assets/session-review-tab-Dob0FLvA.js.gz +0 -0
  199. package/dist/web/assets/{settings-body-vXbOWchN.js → settings-body-5t6SQbLx.js} +1 -1
  200. package/dist/web/assets/settings-body-5t6SQbLx.js.br +0 -0
  201. package/dist/web/assets/settings-body-5t6SQbLx.js.gz +0 -0
  202. package/dist/web/assets/{settings-rows-DD7Gxmst.js → settings-rows-B0w15zot.js} +1 -1
  203. package/dist/web/assets/settings-rows-B0w15zot.js.br +0 -0
  204. package/dist/web/assets/settings-rows-B0w15zot.js.gz +0 -0
  205. package/dist/web/assets/{settings-section-content-DkUJFop6.js → settings-section-content-D41IJ62x.js} +2 -2
  206. package/dist/web/assets/settings-section-content-D41IJ62x.js.br +0 -0
  207. package/dist/web/assets/settings-section-content-D41IJ62x.js.gz +0 -0
  208. package/dist/web/assets/settings-section-content-DuVO6erO.js +1 -0
  209. package/dist/web/assets/{settings-tab-Cdv9_OIe.js → settings-tab-DGU5FVbO.js} +1 -1
  210. package/dist/web/assets/{settings-window-content-DLa6Ph4u.js → settings-window-content-Da5PY5MZ.js} +1 -1
  211. package/dist/web/assets/{slash-command-picker-lILYDfK3.js → slash-command-picker-DpZsPuNf.js} +1 -1
  212. package/dist/web/assets/slash-command-picker-DpZsPuNf.js.br +0 -0
  213. package/dist/web/assets/slash-command-picker-DpZsPuNf.js.gz +0 -0
  214. package/dist/web/assets/{sql-object-tab-Da42SMK5.js → sql-object-tab-BTS8aHGO.js} +1 -1
  215. package/dist/web/assets/sql-object-tab-BTS8aHGO.js.br +0 -0
  216. package/dist/web/assets/sql-object-tab-BTS8aHGO.js.gz +0 -0
  217. package/dist/web/assets/{sqlite-viewer-V3SKNukY.js → sqlite-viewer-CVo5eqWj.js} +1 -1
  218. package/dist/web/assets/sqlite-viewer-CVo5eqWj.js.br +0 -0
  219. package/dist/web/assets/sqlite-viewer-CVo5eqWj.js.gz +0 -0
  220. package/dist/web/assets/{structure-save-dialog-B635iyPI.js → structure-save-dialog-B9Q-Rhgj.js} +1 -1
  221. package/dist/web/assets/structure-save-dialog-B9Q-Rhgj.js.br +0 -0
  222. package/dist/web/assets/structure-save-dialog-B9Q-Rhgj.js.gz +0 -0
  223. package/dist/web/assets/{structure-tab-Co9d8ktU.js → structure-tab-_cQn0KG7.js} +1 -1
  224. package/dist/web/assets/structure-tab-_cQn0KG7.js.br +0 -0
  225. package/dist/web/assets/structure-tab-_cQn0KG7.js.gz +0 -0
  226. package/dist/web/assets/{system-monitor-body-DC0j8RqP.js → system-monitor-body-YS3i1PUs.js} +2 -2
  227. package/dist/web/assets/system-monitor-body-YS3i1PUs.js.br +0 -0
  228. package/dist/web/assets/system-monitor-body-YS3i1PUs.js.gz +0 -0
  229. package/dist/web/assets/{system-monitor-tab-DA8BcdXo.js → system-monitor-tab-D63xmd8U.js} +1 -1
  230. package/dist/web/assets/{system-monitor-window-content-BiuLo8XW.js → system-monitor-window-content-DkCsK8Ed.js} +1 -1
  231. package/dist/web/assets/{tab-host-window-content-Crvt1r5F.js → tab-host-window-content-CrASOMX8.js} +1 -1
  232. package/dist/web/assets/tab-host-window-content-CrASOMX8.js.br +0 -0
  233. package/dist/web/assets/tab-host-window-content-CrASOMX8.js.gz +0 -0
  234. package/dist/web/assets/{table-tab-C2cYw7a0.js → table-tab-JlfqyPZR.js} +1 -1
  235. package/dist/web/assets/table-tab-JlfqyPZR.js.br +0 -0
  236. package/dist/web/assets/table-tab-JlfqyPZR.js.gz +0 -0
  237. package/dist/web/assets/{telegram-bot-token-field-BqNtOo5n.js → telegram-bot-token-field-ctRa2529.js} +1 -1
  238. package/dist/web/assets/telegram-bot-token-field-ctRa2529.js.br +0 -0
  239. package/dist/web/assets/telegram-bot-token-field-ctRa2529.js.gz +0 -0
  240. package/dist/web/assets/{terminal-tab-CrKxibGC.js → terminal-tab-BsXBBru0.js} +1 -1
  241. package/dist/web/assets/terminal-tab-BsXBBru0.js.br +0 -0
  242. package/dist/web/assets/terminal-tab-BsXBBru0.js.gz +0 -0
  243. package/dist/web/assets/{tool-cards-BRto67jM.js → tool-cards-BDzK03U4.js} +3 -3
  244. package/dist/web/assets/tool-cards-BDzK03U4.js.br +0 -0
  245. package/dist/web/assets/tool-cards-BDzK03U4.js.gz +0 -0
  246. package/dist/web/assets/{tools-settings-section-DEvZsrxq.js → tools-settings-section-DCG74pbd.js} +1 -1
  247. package/dist/web/assets/tools-settings-section-DCG74pbd.js.br +0 -0
  248. package/dist/web/assets/tools-settings-section-DCG74pbd.js.gz +0 -0
  249. package/dist/web/assets/{use-inline-blame-CRWJU6Rq.js → use-inline-blame-BOn_m8f-.js} +1 -1
  250. package/dist/web/assets/use-inline-blame-BOn_m8f-.js.br +0 -0
  251. package/dist/web/assets/use-inline-blame-BOn_m8f-.js.gz +0 -0
  252. package/dist/web/assets/{use-lsp-B-otjr-6.js → use-lsp-D144ftsf.js} +1 -1
  253. package/dist/web/assets/use-lsp-D144ftsf.js.br +0 -0
  254. package/dist/web/assets/use-lsp-D144ftsf.js.gz +0 -0
  255. package/dist/web/assets/{use-monaco-theme-BiDXRJ4e.js → use-monaco-theme-ArU1f9T6.js} +1 -1
  256. package/dist/web/assets/{use-process-kill-BW2UW0mZ.js → use-process-kill-BdmXHMFE.js} +1 -1
  257. package/dist/web/assets/use-process-kill-BdmXHMFE.js.br +0 -0
  258. package/dist/web/assets/use-process-kill-BdmXHMFE.js.gz +0 -0
  259. package/dist/web/assets/{use-sql-schema-info-D_Kc2FI7.js → use-sql-schema-info-BQXSMw6p.js} +1 -1
  260. package/dist/web/assets/use-sql-schema-info-BQXSMw6p.js.br +0 -0
  261. package/dist/web/assets/use-sql-schema-info-BQXSMw6p.js.gz +0 -0
  262. package/dist/web/assets/{use-websocket-n3Ud9vEA.js → use-websocket-rWdrMWa8.js} +1 -1
  263. package/dist/web/assets/{web-preview-tab-doNpegW3.js → web-preview-tab-B2OSrnl9.js} +1 -1
  264. package/dist/web/assets/web-preview-tab-B2OSrnl9.js.br +0 -0
  265. package/dist/web/assets/web-preview-tab-B2OSrnl9.js.gz +0 -0
  266. package/dist/web/index.html +2 -2
  267. package/dist/web/index.html.br +0 -0
  268. package/dist/web/index.html.gz +0 -0
  269. package/dist/web/sw.js +1 -1
  270. package/dist/web/sw.js.br +0 -0
  271. package/dist/web/sw.js.gz +0 -0
  272. package/docs/architecture/ai-chat-and-providers.md +22 -0
  273. package/package.json +1 -1
  274. package/src/providers/claude-agent-sdk.ts +37 -7
  275. package/src/providers/codex-app-server/codex-event-mapper.ts +3 -0
  276. package/src/providers/codex-app-server/codex-provider.ts +3 -21
  277. package/src/providers/codex-app-server/codex-rollout-items.ts +3 -0
  278. package/src/providers/codex-app-server/codex-thread-params.ts +1 -0
  279. package/src/providers/codex-app-server/codex-user-input.ts +41 -0
  280. package/src/server/ws/chat-token-rotation.ts +150 -0
  281. package/src/server/ws/chat.ts +28 -0
  282. package/src/services/account.service.ts +14 -0
  283. package/src/shared/chat-questions.ts +19 -0
  284. package/src/web/components/chat/async-question-card.tsx +24 -0
  285. package/src/web/components/chat/chat-reconnect-banner.tsx +54 -0
  286. package/src/web/components/chat/chat-tab.tsx +13 -15
  287. package/src/web/components/chat/message-events.tsx +6 -0
  288. package/src/web/components/chat/question-card.tsx +8 -10
  289. package/src/web/components/chat/tool-cards.tsx +4 -4
  290. package/src/web/hooks/use-reconnect-indicator.ts +49 -0
  291. package/dist/web/assets/accounts-settings-section-B5YXy0vd.js.br +0 -0
  292. package/dist/web/assets/accounts-settings-section-B5YXy0vd.js.gz +0 -0
  293. package/dist/web/assets/agent-session-sheet-Bj42PwCK.js.br +0 -0
  294. package/dist/web/assets/agent-session-sheet-Bj42PwCK.js.gz +0 -0
  295. package/dist/web/assets/agent-session-window-content-LqL6fVPU.js.br +0 -0
  296. package/dist/web/assets/agent-session-window-content-LqL6fVPU.js.gz +0 -0
  297. package/dist/web/assets/ai-resource-editor-jVv2nvAG.js.br +0 -0
  298. package/dist/web/assets/ai-resource-editor-jVv2nvAG.js.gz +0 -0
  299. package/dist/web/assets/ai-settings-section-C9Q-v8fY.js.br +0 -0
  300. package/dist/web/assets/ai-settings-section-C9Q-v8fY.js.gz +0 -0
  301. package/dist/web/assets/android-tab-CwqiuEZP.js.br +0 -0
  302. package/dist/web/assets/android-tab-CwqiuEZP.js.gz +0 -0
  303. package/dist/web/assets/appearance-settings-section-D0JIfiRx.js.br +0 -0
  304. package/dist/web/assets/appearance-settings-section-D0JIfiRx.js.gz +0 -0
  305. package/dist/web/assets/apps-panel-Cz_KJ37M.js.br +0 -0
  306. package/dist/web/assets/apps-panel-Cz_KJ37M.js.gz +0 -0
  307. package/dist/web/assets/branch-review-tab-C3QNpsm7.js.br +0 -0
  308. package/dist/web/assets/branch-review-tab-C3QNpsm7.js.gz +0 -0
  309. package/dist/web/assets/chat-tab-D14CUl0J.js.br +0 -0
  310. package/dist/web/assets/chat-tab-D14CUl0J.js.gz +0 -0
  311. package/dist/web/assets/chat-tab-D8iAVZKV.js +0 -16
  312. package/dist/web/assets/chat-tab-D8iAVZKV.js.br +0 -0
  313. package/dist/web/assets/chat-tab-D8iAVZKV.js.gz +0 -0
  314. package/dist/web/assets/code-editor-B7gbH4-x.js.br +0 -0
  315. package/dist/web/assets/codex-reset-credit-button-DQDtyvnd.js.br +0 -0
  316. package/dist/web/assets/codex-reset-credit-button-DQDtyvnd.js.gz +0 -0
  317. package/dist/web/assets/conflict-editor-ktOkrODA.js.br +0 -0
  318. package/dist/web/assets/conflict-editor-ktOkrODA.js.gz +0 -0
  319. package/dist/web/assets/connection-form-tab-CxvExR7T.js.br +0 -0
  320. package/dist/web/assets/connection-form-tab-CxvExR7T.js.gz +0 -0
  321. package/dist/web/assets/csv-preview-CBDMTEsF.js.br +0 -0
  322. package/dist/web/assets/csv-preview-CBDMTEsF.js.gz +0 -0
  323. package/dist/web/assets/db-tab-parts-BgVpKbi6.js.br +0 -0
  324. package/dist/web/assets/db-tab-parts-BgVpKbi6.js.gz +0 -0
  325. package/dist/web/assets/design-settings-section-DPGw61pY.js.br +0 -0
  326. package/dist/web/assets/design-settings-section-DPGw61pY.js.gz +0 -0
  327. package/dist/web/assets/design-tab-BiCqHNl3.js.br +0 -0
  328. package/dist/web/assets/design-tab-BiCqHNl3.js.gz +0 -0
  329. package/dist/web/assets/designs-sidebar-panel-pcJ2Rx5G.js.br +0 -0
  330. package/dist/web/assets/designs-sidebar-panel-pcJ2Rx5G.js.gz +0 -0
  331. package/dist/web/assets/diff-viewer-CLAvyqVG.js.br +0 -0
  332. package/dist/web/assets/diff-viewer-CLAvyqVG.js.gz +0 -0
  333. package/dist/web/assets/docx-preview-XSkD2IL6.js.br +0 -0
  334. package/dist/web/assets/docx-preview-XSkD2IL6.js.gz +0 -0
  335. package/dist/web/assets/editor-dialog-cQvqVZAR.js.br +0 -0
  336. package/dist/web/assets/editor-dialog-cQvqVZAR.js.gz +0 -0
  337. package/dist/web/assets/editor-language-service-6-7mS6_9.js.br +0 -0
  338. package/dist/web/assets/editor-language-service-6-7mS6_9.js.gz +0 -0
  339. package/dist/web/assets/explorer-body-BlGDPmNa.js.br +0 -0
  340. package/dist/web/assets/explorer-body-BlGDPmNa.js.gz +0 -0
  341. package/dist/web/assets/explorer-window-content-BLWY8GJ_.js.br +0 -0
  342. package/dist/web/assets/explorer-window-content-BLWY8GJ_.js.gz +0 -0
  343. package/dist/web/assets/extension-manager-section-NSEt5f4z.js.br +0 -0
  344. package/dist/web/assets/extension-manager-section-NSEt5f4z.js.gz +0 -0
  345. package/dist/web/assets/extension-webview-BkAEBWin.js.br +0 -0
  346. package/dist/web/assets/extension-webview-BkAEBWin.js.gz +0 -0
  347. package/dist/web/assets/files-settings-section-Bq7liJ_r.js.br +0 -0
  348. package/dist/web/assets/files-settings-section-Bq7liJ_r.js.gz +0 -0
  349. package/dist/web/assets/general-settings-section-DvHROAMa.js.br +0 -0
  350. package/dist/web/assets/general-settings-section-DvHROAMa.js.gz +0 -0
  351. package/dist/web/assets/git-log-panel-Cu9kK48H.js.br +0 -0
  352. package/dist/web/assets/git-log-panel-Cu9kK48H.js.gz +0 -0
  353. package/dist/web/assets/git-review-tab-B_JT649W.js.br +0 -0
  354. package/dist/web/assets/git-review-tab-B_JT649W.js.gz +0 -0
  355. package/dist/web/assets/glide-data-grid-BlposOph.js.br +0 -0
  356. package/dist/web/assets/glide-data-grid-BlposOph.js.gz +0 -0
  357. package/dist/web/assets/grid-save-dialog-B0d0qhnN.js.br +0 -0
  358. package/dist/web/assets/grid-save-dialog-B0d0qhnN.js.gz +0 -0
  359. package/dist/web/assets/group-chat-tab-jirUsGm-.js.br +0 -0
  360. package/dist/web/assets/group-chat-tab-jirUsGm-.js.gz +0 -0
  361. package/dist/web/assets/impexp-tab-HJVcOTYJ.js.br +0 -0
  362. package/dist/web/assets/impexp-tab-HJVcOTYJ.js.gz +0 -0
  363. package/dist/web/assets/index-DLB3j_P0.js.br +0 -0
  364. package/dist/web/assets/index-DLB3j_P0.js.gz +0 -0
  365. package/dist/web/assets/index-M3DUzop8.css +0 -1
  366. package/dist/web/assets/index-M3DUzop8.css.br +0 -0
  367. package/dist/web/assets/index-M3DUzop8.css.gz +0 -0
  368. package/dist/web/assets/keyboard-shortcuts-section-DN21gB-T.js.br +0 -0
  369. package/dist/web/assets/keyboard-shortcuts-section-DN21gB-T.js.gz +0 -0
  370. package/dist/web/assets/language-servers-section-DOcZ72FJ.js.br +0 -0
  371. package/dist/web/assets/language-servers-section-DOcZ72FJ.js.gz +0 -0
  372. package/dist/web/assets/logs-app-C7n9GSCn.js.br +0 -0
  373. package/dist/web/assets/logs-app-C7n9GSCn.js.gz +0 -0
  374. package/dist/web/assets/lsp-client-DpYmPhXX.js.br +0 -0
  375. package/dist/web/assets/lsp-client-DpYmPhXX.js.gz +0 -0
  376. package/dist/web/assets/markdown-renderer-CtEl6qAt.js.br +0 -0
  377. package/dist/web/assets/markdown-renderer-CtEl6qAt.js.gz +0 -0
  378. package/dist/web/assets/mobile-explorer-sheet-B-rcKEL9.js.br +0 -0
  379. package/dist/web/assets/mobile-explorer-sheet-B-rcKEL9.js.gz +0 -0
  380. package/dist/web/assets/new-design-dialog-DgztQK5R.js.br +0 -0
  381. package/dist/web/assets/new-design-dialog-DgztQK5R.js.gz +0 -0
  382. package/dist/web/assets/notifications-settings-section-JiGvaCFx.js.br +0 -0
  383. package/dist/web/assets/notifications-settings-section-JiGvaCFx.js.gz +0 -0
  384. package/dist/web/assets/ppmbot-settings-section-XfrZ8VoC.js.br +0 -0
  385. package/dist/web/assets/ppmbot-settings-section-XfrZ8VoC.js.gz +0 -0
  386. package/dist/web/assets/problems-panel-CDzwSJn6.js.br +0 -0
  387. package/dist/web/assets/problems-panel-CDzwSJn6.js.gz +0 -0
  388. package/dist/web/assets/process-table-C_B39GHm.js.br +0 -0
  389. package/dist/web/assets/process-table-C_B39GHm.js.gz +0 -0
  390. package/dist/web/assets/proxy-settings-section-CvaOvYIv.js.br +0 -0
  391. package/dist/web/assets/proxy-settings-section-CvaOvYIv.js.gz +0 -0
  392. package/dist/web/assets/query-tab-B8aMpAbI.js.br +0 -0
  393. package/dist/web/assets/query-tab-B8aMpAbI.js.gz +0 -0
  394. package/dist/web/assets/remote-access-settings-section-Bv0eawR4.js.br +0 -0
  395. package/dist/web/assets/remote-access-settings-section-Bv0eawR4.js.gz +0 -0
  396. package/dist/web/assets/remote-desktop-clipboard-notice-BpDakRY0.js.br +0 -0
  397. package/dist/web/assets/remote-desktop-clipboard-notice-BpDakRY0.js.gz +0 -0
  398. package/dist/web/assets/remote-desktop-mobile-sheet-C5GQ3kzY.js.br +0 -0
  399. package/dist/web/assets/remote-desktop-mobile-sheet-C5GQ3kzY.js.gz +0 -0
  400. package/dist/web/assets/remote-desktop-mobile-view-CZb5EMEK.js.br +0 -0
  401. package/dist/web/assets/remote-desktop-mobile-view-CZb5EMEK.js.gz +0 -0
  402. package/dist/web/assets/remote-desktop-readiness-gate-CipNtLX5.js.br +0 -0
  403. package/dist/web/assets/remote-desktop-readiness-gate-CipNtLX5.js.gz +0 -0
  404. package/dist/web/assets/remote-desktop-window-content-B7OXXvAM.js.br +0 -0
  405. package/dist/web/assets/remote-desktop-window-content-B7OXXvAM.js.gz +0 -0
  406. package/dist/web/assets/rename-object-dialog-B4W6S7Xg.js.br +0 -0
  407. package/dist/web/assets/rename-object-dialog-B4W6S7Xg.js.gz +0 -0
  408. package/dist/web/assets/review-code-B4osj0k1.js.br +0 -0
  409. package/dist/web/assets/save-as-dialog-DpXv_f1d.js.br +0 -5
  410. package/dist/web/assets/save-as-dialog-DpXv_f1d.js.gz +0 -0
  411. package/dist/web/assets/schedules-settings-section-BbaPbc28.js.br +0 -0
  412. package/dist/web/assets/schedules-settings-section-BbaPbc28.js.gz +0 -0
  413. package/dist/web/assets/session-review-tab-Aq3FEooH.js.br +0 -0
  414. package/dist/web/assets/session-review-tab-Aq3FEooH.js.gz +0 -0
  415. package/dist/web/assets/settings-body-vXbOWchN.js.br +0 -0
  416. package/dist/web/assets/settings-body-vXbOWchN.js.gz +0 -0
  417. package/dist/web/assets/settings-rows-DD7Gxmst.js.br +0 -0
  418. package/dist/web/assets/settings-rows-DD7Gxmst.js.gz +0 -0
  419. package/dist/web/assets/settings-section-content-CVtvJ6ze.js +0 -1
  420. package/dist/web/assets/settings-section-content-DkUJFop6.js.br +0 -0
  421. package/dist/web/assets/settings-section-content-DkUJFop6.js.gz +0 -0
  422. package/dist/web/assets/slash-command-picker-lILYDfK3.js.br +0 -0
  423. package/dist/web/assets/slash-command-picker-lILYDfK3.js.gz +0 -0
  424. package/dist/web/assets/sql-object-tab-Da42SMK5.js.br +0 -0
  425. package/dist/web/assets/sql-object-tab-Da42SMK5.js.gz +0 -0
  426. package/dist/web/assets/sqlite-viewer-V3SKNukY.js.br +0 -0
  427. package/dist/web/assets/sqlite-viewer-V3SKNukY.js.gz +0 -0
  428. package/dist/web/assets/structure-save-dialog-B635iyPI.js.br +0 -0
  429. package/dist/web/assets/structure-save-dialog-B635iyPI.js.gz +0 -0
  430. package/dist/web/assets/structure-tab-Co9d8ktU.js.br +0 -0
  431. package/dist/web/assets/structure-tab-Co9d8ktU.js.gz +0 -0
  432. package/dist/web/assets/system-monitor-body-DC0j8RqP.js.br +0 -0
  433. package/dist/web/assets/system-monitor-body-DC0j8RqP.js.gz +0 -0
  434. package/dist/web/assets/tab-host-window-content-Crvt1r5F.js.br +0 -0
  435. package/dist/web/assets/tab-host-window-content-Crvt1r5F.js.gz +0 -0
  436. package/dist/web/assets/table-tab-C2cYw7a0.js.br +0 -0
  437. package/dist/web/assets/table-tab-C2cYw7a0.js.gz +0 -0
  438. package/dist/web/assets/telegram-bot-token-field-BqNtOo5n.js.br +0 -0
  439. package/dist/web/assets/telegram-bot-token-field-BqNtOo5n.js.gz +0 -0
  440. package/dist/web/assets/terminal-tab-CrKxibGC.js.br +0 -0
  441. package/dist/web/assets/terminal-tab-CrKxibGC.js.gz +0 -0
  442. package/dist/web/assets/tool-cards-BRto67jM.js.br +0 -0
  443. package/dist/web/assets/tool-cards-BRto67jM.js.gz +0 -0
  444. package/dist/web/assets/tools-settings-section-DEvZsrxq.js.br +0 -0
  445. package/dist/web/assets/tools-settings-section-DEvZsrxq.js.gz +0 -0
  446. package/dist/web/assets/use-inline-blame-CRWJU6Rq.js.br +0 -0
  447. package/dist/web/assets/use-inline-blame-CRWJU6Rq.js.gz +0 -0
  448. package/dist/web/assets/use-lsp-B-otjr-6.js.br +0 -0
  449. package/dist/web/assets/use-lsp-B-otjr-6.js.gz +0 -0
  450. package/dist/web/assets/use-process-kill-BW2UW0mZ.js.br +0 -0
  451. package/dist/web/assets/use-process-kill-BW2UW0mZ.js.gz +0 -0
  452. package/dist/web/assets/use-sql-schema-info-D_Kc2FI7.js.br +0 -0
  453. package/dist/web/assets/use-sql-schema-info-D_Kc2FI7.js.gz +0 -0
  454. package/dist/web/assets/web-preview-tab-doNpegW3.js.br +0 -0
  455. package/dist/web/assets/web-preview-tab-doNpegW3.js.gz +0 -0
@@ -221,6 +221,30 @@ interface StreamingSession {
221
221
  lastUserContent: string;
222
222
  /** Latest user message images — updated on follow-ups for accurate retry */
223
223
  lastUserImages?: Array<{ data: string; mediaType: string }>;
224
+ /** The sign-in this subprocess was started with — see `SubprocessCredential`. */
225
+ credential: SubprocessCredential;
226
+ }
227
+
228
+ /**
229
+ * The OAuth token a CLI subprocess holds, fixed for its whole life.
230
+ *
231
+ * The CLI cannot renew a token handed to it in `CLAUDE_CODE_OAUTH_TOKEN`: it carries no
232
+ * refresh token, and the CLI's own 401 recovery deliberately keeps a user-supplied token
233
+ * rather than adopting one from disk. Its only other renewal paths — the SDK's
234
+ * `getOAuthToken` callback and the rotated-token file — are enabled for Anthropic's own
235
+ * clients alone (the callback is gated on `CLAUDE_CODE_ENTRYPOINT` being Desktop, Cowork or
236
+ * VS Code). So when PPM renews an account's token, which revokes the previous one, every
237
+ * subprocess still holding it fails its next request. Recording the token per subprocess is
238
+ * what lets the chat layer renew while an account is quiet and restart idle subprocesses
239
+ * onto the new token between turns (`src/server/ws/chat-token-rotation.ts`).
240
+ */
241
+ export interface SubprocessCredential {
242
+ accountId: string | null;
243
+ oauthToken: string;
244
+ }
245
+
246
+ function credentialOf(env: Record<string, string | undefined>, accountId: string | undefined): SubprocessCredential {
247
+ return { accountId: accountId ?? null, oauthToken: env.CLAUDE_CODE_OAUTH_TOKEN ?? "" };
224
248
  }
225
249
 
226
250
  /**
@@ -825,6 +849,11 @@ export class ClaudeAgentSdkProvider implements AIProvider {
825
849
  return this.streamingSessions.has(sessionId);
826
850
  }
827
851
 
852
+ /** Every session with a live subprocess, with the sign-in that subprocess holds. */
853
+ liveSubprocessCredentials(): Array<{ sessionId: string; credential: SubprocessCredential }> {
854
+ return [...this.streamingSessions].map(([sessionId, ss]) => ({ sessionId, credential: ss.credential }));
855
+ }
856
+
828
857
  /**
829
858
  * How long this session's prompt cache is worth holding a subprocess for.
830
859
  *
@@ -1390,6 +1419,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1390
1419
  if (latest) account = latest;
1391
1420
  }
1392
1421
  const queryEnv = this.buildQueryEnv(meta.projectPath, account);
1422
+ const queryCredential = credentialOf(queryEnv, account?.id);
1393
1423
 
1394
1424
  // Pre-flight: warn if no credentials at all (avoids 2-minute silent timeout)
1395
1425
  if (!account) {
@@ -1503,7 +1533,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1503
1533
  // The query's one start line: a warm CLI and the MCP servers belong to it.
1504
1534
  log.info(`query: session=${sessionId} isFirst=${isFirstMessage} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform} accountMode=${!!account} permissionMode=${permissionMode} isBypass=${isBypass}`
1505
1535
  + ` cli=${spare ? `warm ageMs=${spare.ageMs}` : "cold"}${crashRetryCount > 0 ? ` crashRetry=${crashRetryCount}` : ""} mcp=${Object.keys(mcpServers).join(",") || "none"}`);
1506
- this.streamingSessions.set(sessionId, { meta, query: initialQuery, controller: initialCtrl, lastUserContent: initContent, lastUserImages: initImages });
1536
+ this.streamingSessions.set(sessionId, { meta, query: initialQuery, controller: initialCtrl, lastUserContent: initContent, lastUserImages: initImages, credential: queryCredential });
1507
1537
  this.activeQueries.set(sessionId, initialQuery);
1508
1538
  let eventSource: AsyncIterable<any> = initialQuery;
1509
1539
  log.debug(`session=${sessionId} query() created, waiting for first SDK event...`);
@@ -1531,7 +1561,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1531
1561
  prompt: generator,
1532
1562
  options: { ...opts, hooks: queryHooks, canUseTool } as any,
1533
1563
  });
1534
- this.streamingSessions.set(sessionId, { meta, query: rq, controller, lastUserContent: retry.lastUserContent, lastUserImages: retry.lastUserImages });
1564
+ this.streamingSessions.set(sessionId, { meta, query: rq, controller, lastUserContent: retry.lastUserContent, lastUserImages: retry.lastUserImages, credential: credentialOf(env, acc?.id) });
1535
1565
  this.activeQueries.set(sessionId, rq);
1536
1566
  return rq;
1537
1567
  };
@@ -1600,7 +1630,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1600
1630
  prompt: retryGen,
1601
1631
  options: { ...retryOpts, hooks: queryHooks, canUseTool } as any,
1602
1632
  });
1603
- this.streamingSessions.set(sessionId, { meta, query: rq, controller: retryCtrl, lastUserContent: retry1.lastUserContent, lastUserImages: retry1.lastUserImages });
1633
+ this.streamingSessions.set(sessionId, { meta, query: rq, controller: retryCtrl, lastUserContent: retry1.lastUserContent, lastUserImages: retry1.lastUserImages, credential: queryCredential });
1604
1634
  this.activeQueries.set(sessionId, rq);
1605
1635
  eventSource = rq;
1606
1636
  continue retryLoop;
@@ -1686,7 +1716,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1686
1716
  prompt: earlyAuthGen,
1687
1717
  options: { ...retryOpts, hooks: queryHooks, canUseTool } as any,
1688
1718
  });
1689
- this.streamingSessions.set(sessionId, { meta, query: rq, controller: earlyAuthCtrl, lastUserContent: retry2.lastUserContent, lastUserImages: retry2.lastUserImages });
1719
+ this.streamingSessions.set(sessionId, { meta, query: rq, controller: earlyAuthCtrl, lastUserContent: retry2.lastUserContent, lastUserImages: retry2.lastUserImages, credential: credentialOf(retryEnv, account?.id) });
1690
1720
  this.activeQueries.set(sessionId, rq);
1691
1721
  eventSource = rq;
1692
1722
  continue retryLoop;
@@ -1913,7 +1943,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1913
1943
  prompt: authRetryGen,
1914
1944
  options: { ...retryOpts, hooks: queryHooks, canUseTool } as any,
1915
1945
  });
1916
- this.streamingSessions.set(sessionId, { meta, query: rq, controller: authRetryCtrl, lastUserContent: retry3.lastUserContent, lastUserImages: retry3.lastUserImages });
1946
+ this.streamingSessions.set(sessionId, { meta, query: rq, controller: authRetryCtrl, lastUserContent: retry3.lastUserContent, lastUserImages: retry3.lastUserImages, credential: credentialOf(retryEnv, account?.id) });
1917
1947
  this.activeQueries.set(sessionId, rq);
1918
1948
  eventSource = rq;
1919
1949
  continue retryLoop;
@@ -1950,7 +1980,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1950
1980
  prompt: ulRetryGen,
1951
1981
  options: { ...retryOpts, hooks: queryHooks, canUseTool } as any,
1952
1982
  });
1953
- this.streamingSessions.set(sessionId, { meta, query: rq, controller: ulRetryCtrl, lastUserContent: retryU.lastUserContent, lastUserImages: retryU.lastUserImages });
1983
+ this.streamingSessions.set(sessionId, { meta, query: rq, controller: ulRetryCtrl, lastUserContent: retryU.lastUserContent, lastUserImages: retryU.lastUserImages, credential: credentialOf(ulRetryEnv, account?.id) });
1954
1984
  this.activeQueries.set(sessionId, rq);
1955
1985
  eventSource = rq;
1956
1986
  continue retryLoop;
@@ -2171,7 +2201,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
2171
2201
  prompt: authRetryGen2,
2172
2202
  options: { ...retryOpts, hooks: queryHooks, canUseTool } as any,
2173
2203
  });
2174
- this.streamingSessions.set(sessionId, { meta, query: rq, controller: authRetryCtrl2, lastUserContent: retry6.lastUserContent, lastUserImages: retry6.lastUserImages });
2204
+ this.streamingSessions.set(sessionId, { meta, query: rq, controller: authRetryCtrl2, lastUserContent: retry6.lastUserContent, lastUserImages: retry6.lastUserImages, credential: credentialOf(retryEnv, account?.id) });
2175
2205
  this.activeQueries.set(sessionId, rq);
2176
2206
  eventSource = rq;
2177
2207
  continue retryLoop;
@@ -1,6 +1,7 @@
1
1
  import type { ChatEvent } from "../provider.interface.ts";
2
2
  import type { TurnUsage } from "../../shared/turn-usage.ts";
3
3
  import { redactTruncate } from "./codex-redact.ts";
4
+ import { codexAsyncQuestionEvent } from "./codex-user-input.ts";
4
5
  import { changeToToolUse, fileUpdateChanges } from "./codex-patch.ts";
5
6
  import { parseSubagentActivity, subagentToolResult, subagentToolUse } from "./codex-subagent-thread.ts";
6
7
 
@@ -237,6 +238,8 @@ export function mapCodexEvent(notif: Notif, sessionId: string): ChatEvent[] {
237
238
 
238
239
  case "item/completed": {
239
240
  const item = asObj(p.item) as Item;
241
+ const question = codexAsyncQuestionEvent(item);
242
+ if (question) return [question];
240
243
  if (item.type === "contextCompaction") return [{ type: "system", subtype: "compact_done" }];
241
244
  const subagent = parseSubagentActivity(item);
242
245
  // Answers the card opened at `started` — keyed on the thread, because the
@@ -51,6 +51,7 @@ import { getOrFetchUsage, registerUsageSource } from "../../services/provider-us
51
51
  import { codexUsageSource } from "./codex-usage-source.ts";
52
52
  import { AMBIENT_ACCOUNT_KEY } from "../../services/provider-usage/usage-source.ts";
53
53
  import { redactTruncate } from "./codex-redact.ts";
54
+ import { buildUserInputResponse, normalizeCodexQuestions } from "./codex-user-input.ts";
54
55
  import { localizeRollout } from "./codex-rollout-transfer.ts";
55
56
  import { getRolloutMessagesAsync } from "./codex-history-async.ts";
56
57
  import {
@@ -62,7 +63,6 @@ import type {
62
63
  ModelListResponse,
63
64
  ServerRequest,
64
65
  JsonRpcNotification,
65
- ToolRequestUserInputResponse,
66
66
  Thread,
67
67
  UserInput,
68
68
  CodexSkill,
@@ -328,24 +328,6 @@ function approvalToolLabel(method: string, params: unknown): string {
328
328
  return String(p.tool ?? "Tool");
329
329
  }
330
330
 
331
- function buildUserInputResponse(questions: unknown, data: unknown): ToolRequestUserInputResponse {
332
- const answers: ToolRequestUserInputResponse["answers"] = {};
333
- if (data && typeof data === "object" && !Array.isArray(data)) {
334
- for (const [k, v] of Object.entries(data as Record<string, unknown>)) {
335
- answers[k] = { answers: Array.isArray(v) ? v.map(String) : [String(v)] };
336
- }
337
- return { answers };
338
- }
339
- const arr = Array.isArray(data) ? data : data != null ? [data] : [];
340
- const qs = Array.isArray(questions) ? questions : [];
341
- qs.forEach((q, i) => {
342
- const qid = (q && typeof q === "object" && ((q as any).id ?? (q as any).questionId)) || String(i);
343
- const a = arr[i];
344
- answers[qid] = { answers: a != null ? [String(a)] : [] };
345
- });
346
- return { answers };
347
- }
348
-
349
331
  /**
350
332
  * Codex provider — implements AIProvider directly (mirrors claude-agent-sdk),
351
333
  * driving `codex app-server` over JSON-RPC. Per-session live subprocess map;
@@ -1160,7 +1142,7 @@ export class CodexAppServerProvider implements AIProvider {
1160
1142
  const ppmReqId = crypto.randomUUID();
1161
1143
  const questions = (req.params as { questions?: unknown })?.questions;
1162
1144
  live.pendingApprovals.set(ppmReqId, { codexId: req.id, method, questions });
1163
- live.channel.push({ type: "approval_request", requestId: ppmReqId, tool: "AskUserQuestion", input: redactTruncate(req.params) });
1145
+ live.channel.push({ type: "approval_request", requestId: ppmReqId, tool: "AskUserQuestion", input: { questions: normalizeCodexQuestions(questions) } });
1164
1146
  return;
1165
1147
  }
1166
1148
  // permissions/* response is a granted-profile, not a decision → decline.
@@ -1174,7 +1156,7 @@ export class CodexAppServerProvider implements AIProvider {
1174
1156
  if (!pending) continue;
1175
1157
  live.pendingApprovals.delete(requestId);
1176
1158
  if (pending.method === "item/tool/requestUserInput") {
1177
- this.respondOnce(live, pending.codexId, buildUserInputResponse(pending.questions, data));
1159
+ this.respondOnce(live, pending.codexId, buildUserInputResponse(pending.questions, approved ? data : undefined));
1178
1160
  } else if (isApprovalMethod(pending.method)) {
1179
1161
  this.respondOnce(live, pending.codexId, decisionFor(pending.method as ApprovalMethod, approved));
1180
1162
  }
@@ -1,5 +1,6 @@
1
1
  import type { ChatEvent } from "../provider.interface.ts";
2
2
  import { redactTruncate } from "./codex-redact.ts";
3
+ import { codexAsyncQuestionEvent } from "./codex-user-input.ts";
3
4
  import { parseSubagentActivity, type SubagentActivity } from "./codex-subagent-thread.ts";
4
5
  import { changeToToolUse, rolloutFileChanges } from "./codex-patch.ts";
5
6
 
@@ -161,6 +162,8 @@ export function mapRolloutItem(item: unknown): RolloutItemMapping {
161
162
  return text ? { kind: "user", text } : { kind: "ignore" };
162
163
  }
163
164
  if (type === "AgentMessage") {
165
+ const question = codexAsyncQuestionEvent(it);
166
+ if (question) return { kind: "events", events: [question] };
164
167
  const text = textFrom(it.content);
165
168
  return text ? { kind: "assistant", text } : { kind: "ignore" };
166
169
  }
@@ -116,6 +116,7 @@ export function dbToolsMcpEnv(access: DbToolsMcpAccess | undefined): Record<stri
116
116
  export function buildThreadParams(input: ThreadParamsInput): CodexThreadParams {
117
117
  const instructions = input.developerInstructions?.trim();
118
118
  const config = {
119
+ "features.default_mode_request_user_input": true,
119
120
  ...(input.configOverrides?.config ?? {}),
120
121
  ...designMcpConfig(input.designMcp),
121
122
  ...tabToolsMcpConfig(input.tabToolsMcp),
@@ -0,0 +1,41 @@
1
+ import type { ChatEvent } from "../provider.interface.ts";
2
+ import { ASYNC_QUESTION_TOOL, type ChatQuestion } from "../../shared/chat-questions.ts";
3
+ import type { ToolRequestUserInputResponse } from "./codex-protocol.ts";
4
+
5
+ export function normalizeCodexQuestions(value: unknown): ChatQuestion[] {
6
+ if (!Array.isArray(value)) return [];
7
+ return value.flatMap((value, index) => {
8
+ if (!value || typeof value !== "object") return [];
9
+ const question = typeof value.question === "string" ? value.question : value.title;
10
+ if (typeof question !== "string" || !question.trim()) return [];
11
+ const options = Array.isArray(value.options) ? value.options.flatMap((option: unknown) => {
12
+ if (typeof option === "string") return [{ label: option }];
13
+ if (!option || typeof option !== "object" || !("label" in option) || typeof option.label !== "string") return [];
14
+ return [{ label: option.label, ...("description" in option && typeof option.description === "string" ? { description: option.description } : {}) }];
15
+ }) : [];
16
+ return [{ id: typeof value.id === "string" ? value.id : String(index), question,
17
+ ...(typeof value.header === "string" ? { header: value.header } : {}), options }];
18
+ });
19
+ }
20
+
21
+ export function codexAsyncQuestionEvent(item: Record<string, unknown>): ChatEvent | null {
22
+ if (item.type !== "agentMessage" && item.type !== "AgentMessage") return null;
23
+ const questions = normalizeCodexQuestions(item.questions);
24
+ if (typeof item.id !== "string" || !questions.length) return null;
25
+ return { type: "tool_use", tool: ASYNC_QUESTION_TOOL, toolUseId: item.id, input: { questions } };
26
+ }
27
+
28
+ export function buildUserInputResponse(questions: unknown, data: unknown): ToolRequestUserInputResponse {
29
+ const answers: ToolRequestUserInputResponse["answers"] = {};
30
+ if (!data || typeof data !== "object") return { answers };
31
+ normalizeCodexQuestions(questions).forEach((question, index) => {
32
+ const id = question.id!;
33
+ const value = Array.isArray(data) ? data[index]
34
+ : Object.hasOwn(data, id) ? (data as Record<string, unknown>)[id]
35
+ : Object.hasOwn(data, question.question) ? (data as Record<string, unknown>)[question.question] : undefined;
36
+ if (value != null) Object.defineProperty(answers, id, {
37
+ value: { answers: Array.isArray(value) ? value.map(String) : [String(value)] }, enumerable: true,
38
+ });
39
+ });
40
+ return { answers };
41
+ }
@@ -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,19 @@
1
+ export interface ChatQuestion {
2
+ id?: string;
3
+ question: string;
4
+ header?: string;
5
+ options: { label: string; description?: string }[];
6
+ multiSelect?: boolean;
7
+ }
8
+
9
+ export const ASYNC_QUESTION_TOOL = "RequestUserInput";
10
+
11
+ export function questionReplyPrefix(itemId: string): string {
12
+ return `Answers to Codex question ${encodeURIComponent(itemId)}:\n`;
13
+ }
14
+
15
+ export function formatQuestionReply(itemId: string, questions: ChatQuestion[], answers?: Record<string, string>): string {
16
+ return questionReplyPrefix(itemId) + (answers
17
+ ? questions.map((question) => `${question.question}\n${answers[question.id ?? question.question] ?? ""}`).join("\n\n")
18
+ : "Skipped by the user.");
19
+ }
@@ -0,0 +1,24 @@
1
+ import { createContext, useContext } from "react";
2
+ import type { ChatMessage } from "../../../types/chat";
3
+ import { formatQuestionReply, questionReplyPrefix, type ChatQuestion } from "../../../shared/chat-questions";
4
+ import { QuestionCard } from "./question-card";
5
+
6
+ export const AsyncQuestionContext = createContext<{
7
+ messages: ChatMessage[];
8
+ onSend?: (content: string) => void;
9
+ }>({ messages: [] });
10
+
11
+ export function AsyncQuestionCard({ itemId, questions }: { itemId: string; questions: ChatQuestion[] }) {
12
+ const { messages, onSend } = useContext(AsyncQuestionContext);
13
+ const prefix = questionReplyPrefix(itemId);
14
+ const reply = messages.find((message) => message.role === "user" && message.content.startsWith(prefix));
15
+ if (reply || !onSend) return (
16
+ <div className="rounded-lg border border-border bg-surface p-3 text-sm whitespace-pre-wrap">
17
+ {reply ? reply.content.slice(prefix.length) : questions.map((question) => question.question).join("\n")}
18
+ <div className="mt-2 text-xs text-text-secondary">{reply ? "Response sent" : "Connect to answer this question"}</div>
19
+ </div>
20
+ );
21
+ return <QuestionCard questions={questions} autoFocus={false}
22
+ onSubmit={(answers) => onSend(formatQuestionReply(itemId, questions, answers))}
23
+ onSkip={() => onSend(formatQuestionReply(itemId, questions))} />;
24
+ }
@@ -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
+ }
@@ -1,4 +1,5 @@
1
1
  import { useState, useCallback, useRef, useEffect, useMemo } from "react";
2
+ import { AsyncQuestionContext } from "./async-question-card";
2
3
  import { Loader2, Upload, X } from "@/lib/icons";
3
4
  import { toast } from "sonner";
4
5
  import { api, projectUrl } from "@/lib/api-client";
@@ -19,6 +20,7 @@ import { useChatAccountClaim } from "@/hooks/use-chat-account-claim";
19
20
  import { startPrepare, getPrepare, isPrepared, forgetPrepare } from "@/lib/new-chat-prepare-client";
20
21
  import { MessageList } from "./message-list";
21
22
  import { BackgroundCommandBar } from "./background-command-bar";
23
+ import { ChatReconnectBanner } from "./chat-reconnect-banner";
22
24
  import { RunningAgentsBar } from "./running-agents-bar";
23
25
  import { AgentSessionProvider, normalizeProviderId } from "./agent-session-context";
24
26
  import { McpSignInBar } from "@/components/mcp-auth/mcp-sign-in-bar";
@@ -473,6 +475,12 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
473
475
  killBackgroundShell,
474
476
  } = useChat(sessionId, providerId, projectName, handleSessionMigrated, observeAttempt, replyTransportOptions);
475
477
 
478
+ // Shared by the history bar's reload button and the reconnect pill's Retry.
479
+ const reloadChat = useCallback(() => {
480
+ if (!isConnected) reconnect();
481
+ refetchMessages();
482
+ }, [isConnected, reconnect, refetchMessages]);
483
+
476
484
  const sessionChanges = useSessionFileChanges({ projectName, sessionId, messages, isStreaming });
477
485
  const openReview = useCallback((path?: string) => {
478
486
  if (!sessionId) return;
@@ -1283,15 +1291,8 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1283
1291
  </div>
1284
1292
  )}
1285
1293
 
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
- )}
1294
+ {/* Reconnect status — a pill, not an overlay: the chat stays readable and usable */}
1295
+ <ChatReconnectBanner isReconnecting={isReconnecting} onRetry={reloadChat} />
1295
1296
 
1296
1297
  {/* Edit-fork overlay — covers the chat while the new version forks + connects */}
1297
1298
  {editForking && (
@@ -1315,7 +1316,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1315
1316
  <span>Starting conversation...</span>
1316
1317
  </div>
1317
1318
  </div>
1318
- ) : <SessionChangesContext.Provider value={sessionChangesValue}><MessageList
1319
+ ) : <AsyncQuestionContext.Provider value={{ messages: renderedMessages, onSend: isConnected ? (content) => sendMessage(content, { permissionMode }) : undefined }}><SessionChangesContext.Provider value={sessionChangesValue}><MessageList
1319
1320
  messages={renderedMessages}
1320
1321
  onExpandCompact={expandCompact}
1321
1322
  isCompactExpanded={isCompactExpanded}
@@ -1346,7 +1347,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1346
1347
  onDismissMessage={dismissMessage}
1347
1348
  onClearErrors={clearErrors}
1348
1349
  bashPartialOutput={bashPartialOutput}
1349
- /></SessionChangesContext.Provider>}
1350
+ /></SessionChangesContext.Provider></AsyncQuestionContext.Provider>}
1350
1351
 
1351
1352
  {/* Agents still working — pinned here so it is the last thing under the conversation */}
1352
1353
  <RunningAgentsBar
@@ -1399,10 +1400,7 @@ function ChatTabContent({ metadata, tabId, onNewSession, onFork, historyFilter }
1399
1400
  onSelectSession={handleSelectSession}
1400
1401
  historyFilter={historyFilter}
1401
1402
  isConnected={isConnected}
1402
- onReload={() => {
1403
- if (!isConnected) reconnect();
1404
- refetchMessages();
1405
- }}
1403
+ onReload={reloadChat}
1406
1404
  teamActivity={teamActivity}
1407
1405
  teamMessages={teamMessages}
1408
1406
  onTeamOpen={markTeamRead}