@hienlh/ppm 0.23.0 → 0.23.2

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 (382) hide show
  1. package/CHANGELOG.md +15 -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-Cs7BC5O1.js +1 -0
  6. package/dist/web/assets/accounts-settings-section-Cs7BC5O1.js.br +0 -0
  7. package/dist/web/assets/accounts-settings-section-Cs7BC5O1.js.gz +0 -0
  8. package/dist/web/assets/{ai-resource-editor-DoCiQDKY.js → ai-resource-editor-TCREp5Ml.js} +1 -1
  9. package/dist/web/assets/ai-resource-editor-TCREp5Ml.js.br +0 -0
  10. package/dist/web/assets/ai-resource-editor-TCREp5Ml.js.gz +0 -0
  11. package/dist/web/assets/{ai-settings-section-0x6GFJ56.js → ai-settings-section-B7Un4KiW.js} +1 -1
  12. package/dist/web/assets/ai-settings-section-B7Un4KiW.js.br +0 -0
  13. package/dist/web/assets/ai-settings-section-B7Un4KiW.js.gz +0 -0
  14. package/dist/web/assets/{ai-settings-section-Cp6jcnTn.js → ai-settings-section-BlKlcihJ.js} +1 -1
  15. package/dist/web/assets/{appearance-settings-section-mRiGj3_g.js → appearance-settings-section-DACtiDor.js} +1 -1
  16. package/dist/web/assets/appearance-settings-section-DACtiDor.js.br +0 -0
  17. package/dist/web/assets/appearance-settings-section-DACtiDor.js.gz +0 -0
  18. package/dist/web/assets/{audio-preview-CO7Ia2N5.js → audio-preview-DXtE1Lpz.js} +1 -1
  19. package/dist/web/assets/audio-preview-DXtE1Lpz.js.br +0 -0
  20. package/dist/web/assets/audio-preview-DXtE1Lpz.js.gz +0 -0
  21. package/dist/web/assets/{branch-review-tab-F3CALiEa.js → branch-review-tab-vHJuFXtB.js} +1 -1
  22. package/dist/web/assets/branch-review-tab-vHJuFXtB.js.br +0 -0
  23. package/dist/web/assets/branch-review-tab-vHJuFXtB.js.gz +0 -0
  24. package/dist/web/assets/chat-tab-Ba3NvUgt.js +1 -0
  25. package/dist/web/assets/chat-tab-Ba3NvUgt.js.br +0 -0
  26. package/dist/web/assets/chat-tab-Ba3NvUgt.js.gz +0 -0
  27. package/dist/web/assets/chat-tab-DeISufm0.js +15 -0
  28. package/dist/web/assets/chat-tab-DeISufm0.js.br +0 -0
  29. package/dist/web/assets/chat-tab-DeISufm0.js.gz +0 -0
  30. package/dist/web/assets/{code-editor-FsR6KpZa.js → code-editor-BCwiLVyc.js} +3 -3
  31. package/dist/web/assets/code-editor-BCwiLVyc.js.br +0 -0
  32. package/dist/web/assets/code-editor-BCwiLVyc.js.gz +0 -0
  33. package/dist/web/assets/codex-sign-in-again-dialog-DcIOYhAe.js +1 -0
  34. package/dist/web/assets/codex-sign-in-again-dialog-DcIOYhAe.js.br +0 -0
  35. package/dist/web/assets/codex-sign-in-again-dialog-DcIOYhAe.js.gz +0 -0
  36. package/dist/web/assets/{conflict-editor-GGqIz6tt.js → conflict-editor-0YEDQ--c.js} +1 -1
  37. package/dist/web/assets/conflict-editor-0YEDQ--c.js.br +0 -0
  38. package/dist/web/assets/conflict-editor-0YEDQ--c.js.gz +0 -0
  39. package/dist/web/assets/{context-menu-CVO3HtIt.js → context-menu-CjYTq8DI.js} +1 -1
  40. package/dist/web/assets/context-menu-CjYTq8DI.js.br +0 -0
  41. package/dist/web/assets/context-menu-CjYTq8DI.js.gz +0 -0
  42. package/dist/web/assets/{csv-preview-ycnxJ_6J.js → csv-preview-CYxznB1I.js} +1 -1
  43. package/dist/web/assets/csv-preview-CYxznB1I.js.br +0 -0
  44. package/dist/web/assets/csv-preview-CYxznB1I.js.gz +0 -0
  45. package/dist/web/assets/{database-viewer-y6URJv8N.js → database-viewer-eP3gCxGb.js} +1 -1
  46. package/dist/web/assets/database-viewer-eP3gCxGb.js.br +0 -0
  47. package/dist/web/assets/database-viewer-eP3gCxGb.js.gz +0 -0
  48. package/dist/web/assets/{design-responsive-dialog-D0QYfX7v.js → design-responsive-dialog-CcJETHO2.js} +1 -1
  49. package/dist/web/assets/design-responsive-dialog-CcJETHO2.js.br +0 -0
  50. package/dist/web/assets/design-responsive-dialog-CcJETHO2.js.gz +0 -0
  51. package/dist/web/assets/{design-tab-DgCTcwot.js → design-tab-B7aVMMfj.js} +1 -1
  52. package/dist/web/assets/design-tab-B7aVMMfj.js.br +0 -0
  53. package/dist/web/assets/design-tab-B7aVMMfj.js.gz +0 -0
  54. package/dist/web/assets/{designs-sidebar-panel-Dtuph5jh.js → designs-sidebar-panel-8KI79o1u.js} +1 -1
  55. package/dist/web/assets/designs-sidebar-panel-8KI79o1u.js.br +0 -0
  56. package/dist/web/assets/designs-sidebar-panel-8KI79o1u.js.gz +0 -0
  57. package/dist/web/assets/{dialog-CaShS4f5.js → dialog-tzBRkGeW.js} +1 -1
  58. package/dist/web/assets/dialog-tzBRkGeW.js.br +0 -0
  59. package/dist/web/assets/dialog-tzBRkGeW.js.gz +0 -0
  60. package/dist/web/assets/{diff-viewer-ciBkExM7.js → diff-viewer-B_K7VQup.js} +1 -1
  61. package/dist/web/assets/diff-viewer-B_K7VQup.js.br +0 -0
  62. package/dist/web/assets/diff-viewer-B_K7VQup.js.gz +0 -0
  63. package/dist/web/assets/{docx-preview-C8K6QzOW.js → docx-preview-C-UKGMXR.js} +1 -1
  64. package/dist/web/assets/docx-preview-C-UKGMXR.js.br +0 -0
  65. package/dist/web/assets/docx-preview-C-UKGMXR.js.gz +0 -0
  66. package/dist/web/assets/{editor-language-service-CkoaAHjF.js → editor-language-service-DYBxZX66.js} +1 -1
  67. package/dist/web/assets/editor-language-service-DYBxZX66.js.br +0 -0
  68. package/dist/web/assets/editor-language-service-DYBxZX66.js.gz +0 -0
  69. package/dist/web/assets/{explorer-body-C5gXPGOl.js → explorer-body-C0syNltJ.js} +1 -1
  70. package/dist/web/assets/explorer-body-C0syNltJ.js.br +0 -0
  71. package/dist/web/assets/explorer-body-C0syNltJ.js.gz +0 -0
  72. package/dist/web/assets/{explorer-window-content-8EBl9Vkx.js → explorer-window-content-CfIomwTq.js} +1 -1
  73. package/dist/web/assets/explorer-window-content-CfIomwTq.js.br +0 -0
  74. package/dist/web/assets/explorer-window-content-CfIomwTq.js.gz +0 -0
  75. package/dist/web/assets/{extension-manager-section-DOwZYx8O.js → extension-manager-section-CL7pl3re.js} +1 -1
  76. package/dist/web/assets/extension-manager-section-CL7pl3re.js.br +0 -0
  77. package/dist/web/assets/extension-manager-section-CL7pl3re.js.gz +0 -0
  78. package/dist/web/assets/{extension-webview-CdEva8pD.js → extension-webview-FHTB0jh1.js} +1 -1
  79. package/dist/web/assets/extension-webview-FHTB0jh1.js.br +0 -0
  80. package/dist/web/assets/extension-webview-FHTB0jh1.js.gz +0 -0
  81. package/dist/web/assets/{files-settings-section-DVemQIZ2.js → files-settings-section-C574RfCl.js} +1 -1
  82. package/dist/web/assets/files-settings-section-C574RfCl.js.br +0 -0
  83. package/dist/web/assets/files-settings-section-C574RfCl.js.gz +0 -0
  84. package/dist/web/assets/{general-settings-section-DiY_5C93.js → general-settings-section--DSUTrEP.js} +1 -1
  85. package/dist/web/assets/general-settings-section--DSUTrEP.js.br +0 -0
  86. package/dist/web/assets/general-settings-section--DSUTrEP.js.gz +0 -0
  87. package/dist/web/assets/{git-log-panel-Cq0WohOO.js → git-log-panel-BzwEbRwN.js} +1 -1
  88. package/dist/web/assets/git-log-panel-BzwEbRwN.js.br +0 -0
  89. package/dist/web/assets/git-log-panel-BzwEbRwN.js.gz +0 -0
  90. package/dist/web/assets/{glide-data-grid-DG9AnCmP.js → glide-data-grid-CUNWBFfN.js} +1 -1
  91. package/dist/web/assets/glide-data-grid-CUNWBFfN.js.br +0 -0
  92. package/dist/web/assets/glide-data-grid-CUNWBFfN.js.gz +0 -0
  93. package/dist/web/assets/{group-chat-tab-DxbDrFJl.js → group-chat-tab-Dp1p4Jdu.js} +1 -1
  94. package/dist/web/assets/group-chat-tab-Dp1p4Jdu.js.br +0 -0
  95. package/dist/web/assets/group-chat-tab-Dp1p4Jdu.js.gz +0 -0
  96. package/dist/web/assets/icons-CO_II6D-.js +1 -0
  97. package/dist/web/assets/icons-CO_II6D-.js.br +0 -0
  98. package/dist/web/assets/icons-CO_II6D-.js.gz +0 -0
  99. package/dist/web/assets/{image-preview-eAv91-an.js → image-preview-78Fguk1f.js} +1 -1
  100. package/dist/web/assets/{index-BVKT5UCu.js → index-CvUGUp7d.js} +6 -6
  101. package/dist/web/assets/index-CvUGUp7d.js.br +0 -0
  102. package/dist/web/assets/index-CvUGUp7d.js.gz +0 -0
  103. package/dist/web/assets/index-DMA-So69.css +1 -0
  104. package/dist/web/assets/index-DMA-So69.css.br +0 -0
  105. package/dist/web/assets/index-DMA-So69.css.gz +0 -0
  106. package/dist/web/assets/{jira-watcher-section-Cr8gSVGY.js → jira-watcher-section-DoXl0GAq.js} +1 -1
  107. package/dist/web/assets/jira-watcher-section-DoXl0GAq.js.br +0 -0
  108. package/dist/web/assets/jira-watcher-section-DoXl0GAq.js.gz +0 -0
  109. package/dist/web/assets/{keybindings-store-Ch1mWz0q.js → keybindings-store-BqGm9AgI.js} +1 -1
  110. package/dist/web/assets/{keyboard-shortcuts-section-BvC6m_C2.js → keyboard-shortcuts-section-BogwnAdr.js} +1 -1
  111. package/dist/web/assets/keyboard-shortcuts-section-BogwnAdr.js.br +0 -0
  112. package/dist/web/assets/keyboard-shortcuts-section-BogwnAdr.js.gz +0 -0
  113. package/dist/web/assets/{markdown-renderer-56wMzGkr.js → markdown-renderer-C33dsNJ_.js} +1 -1
  114. package/dist/web/assets/{markdown-renderer-8JlfrfnD.js → markdown-renderer-CZiECUJp.js} +1 -1
  115. package/dist/web/assets/markdown-renderer-CZiECUJp.js.br +0 -0
  116. package/dist/web/assets/markdown-renderer-CZiECUJp.js.gz +0 -0
  117. package/dist/web/assets/{mobile-explorer-sheet-cJfocgE7.js → mobile-explorer-sheet-BOgo9tbK.js} +1 -1
  118. package/dist/web/assets/mobile-explorer-sheet-BOgo9tbK.js.br +0 -0
  119. package/dist/web/assets/mobile-explorer-sheet-BOgo9tbK.js.gz +0 -0
  120. package/dist/web/assets/{new-design-dialog-BO_D3nex.js → new-design-dialog-DcoejI1s.js} +1 -1
  121. package/dist/web/assets/new-design-dialog-DcoejI1s.js.br +0 -0
  122. package/dist/web/assets/new-design-dialog-DcoejI1s.js.gz +0 -0
  123. package/dist/web/assets/{notification-store-DNWazI7j.js → notification-store-D88Y9kF_.js} +1 -1
  124. package/dist/web/assets/{notifications-settings-section-DaMh3khs.js → notifications-settings-section-Bfxqz65i.js} +1 -1
  125. package/dist/web/assets/notifications-settings-section-Bfxqz65i.js.br +0 -0
  126. package/dist/web/assets/notifications-settings-section-Bfxqz65i.js.gz +0 -0
  127. package/dist/web/assets/{onboarding-entry-CwkNJ69U.js → onboarding-entry-CcvySZ_l.js} +1 -1
  128. package/dist/web/assets/onboarding-entry-CcvySZ_l.js.br +0 -0
  129. package/dist/web/assets/onboarding-entry-CcvySZ_l.js.gz +0 -0
  130. package/dist/web/assets/{onboarding-quick-orientation-u7yM68d3.js → onboarding-quick-orientation-CT4PylG0.js} +1 -1
  131. package/dist/web/assets/onboarding-quick-orientation-CT4PylG0.js.br +0 -0
  132. package/dist/web/assets/onboarding-quick-orientation-CT4PylG0.js.gz +0 -0
  133. package/dist/web/assets/{pdf-preview-CaxTidh0.js → pdf-preview-BWim6mu1.js} +1 -1
  134. package/dist/web/assets/pdf-preview-BWim6mu1.js.br +0 -0
  135. package/dist/web/assets/pdf-preview-BWim6mu1.js.gz +0 -0
  136. package/dist/web/assets/{postgres-viewer-CJOOLdgO.js → postgres-viewer-HwlL1UTJ.js} +1 -1
  137. package/dist/web/assets/postgres-viewer-HwlL1UTJ.js.br +0 -0
  138. package/dist/web/assets/postgres-viewer-HwlL1UTJ.js.gz +0 -0
  139. package/dist/web/assets/{ppmbot-settings-section-rNBdWG7F.js → ppmbot-settings-section-DF0kqdyO.js} +1 -1
  140. package/dist/web/assets/ppmbot-settings-section-DF0kqdyO.js.br +0 -0
  141. package/dist/web/assets/ppmbot-settings-section-DF0kqdyO.js.gz +0 -0
  142. package/dist/web/assets/{problems-panel-BTmOGMcL.js → problems-panel-DRcq5ChN.js} +1 -1
  143. package/dist/web/assets/problems-panel-DRcq5ChN.js.br +0 -0
  144. package/dist/web/assets/problems-panel-DRcq5ChN.js.gz +0 -0
  145. package/dist/web/assets/{process-table-Bc9WEBsS.js → process-table-Ds0S_YK-.js} +1 -1
  146. package/dist/web/assets/process-table-Ds0S_YK-.js.br +0 -0
  147. package/dist/web/assets/process-table-Ds0S_YK-.js.gz +0 -0
  148. package/dist/web/assets/{proxy-settings-section-Bo-FPk6Y.js → proxy-settings-section-Q-ttqVOa.js} +1 -1
  149. package/dist/web/assets/proxy-settings-section-Q-ttqVOa.js.br +0 -0
  150. package/dist/web/assets/proxy-settings-section-Q-ttqVOa.js.gz +0 -0
  151. package/dist/web/assets/{query-audit-section-CQfzKAf9.js → query-audit-section-Cn11xqgz.js} +1 -1
  152. package/dist/web/assets/query-audit-section-Cn11xqgz.js.br +0 -0
  153. package/dist/web/assets/query-audit-section-Cn11xqgz.js.gz +0 -0
  154. package/dist/web/assets/{remote-desktop-clipboard-notice-DgeuoZ4n.js → remote-desktop-clipboard-notice-CvPPtrRq.js} +1 -1
  155. package/dist/web/assets/remote-desktop-clipboard-notice-CvPPtrRq.js.br +0 -0
  156. package/dist/web/assets/remote-desktop-clipboard-notice-CvPPtrRq.js.gz +0 -0
  157. package/dist/web/assets/{remote-desktop-mobile-sheet-BpP7jI0p.js → remote-desktop-mobile-sheet-B-TT9Fdt.js} +2 -2
  158. package/dist/web/assets/remote-desktop-mobile-sheet-B-TT9Fdt.js.br +0 -0
  159. package/dist/web/assets/remote-desktop-mobile-sheet-B-TT9Fdt.js.gz +0 -0
  160. package/dist/web/assets/{remote-desktop-mobile-view-Byn0lFCF.js → remote-desktop-mobile-view-hUnYxXDk.js} +1 -1
  161. package/dist/web/assets/remote-desktop-mobile-view-hUnYxXDk.js.br +0 -0
  162. package/dist/web/assets/remote-desktop-mobile-view-hUnYxXDk.js.gz +0 -0
  163. package/dist/web/assets/{remote-desktop-readiness-gate-B2mXZ9vo.js → remote-desktop-readiness-gate-CUmaeiX9.js} +1 -1
  164. package/dist/web/assets/remote-desktop-readiness-gate-CUmaeiX9.js.br +0 -0
  165. package/dist/web/assets/remote-desktop-readiness-gate-CUmaeiX9.js.gz +0 -0
  166. package/dist/web/assets/{remote-desktop-window-content-CWt3VVjm.js → remote-desktop-window-content-DEPUOOGw.js} +1 -1
  167. package/dist/web/assets/remote-desktop-window-content-DEPUOOGw.js.br +0 -0
  168. package/dist/web/assets/remote-desktop-window-content-DEPUOOGw.js.gz +0 -0
  169. package/dist/web/assets/{schedules-settings-section-Bx9Rm-NB.js → schedules-settings-section-Bv_wrzDp.js} +1 -1
  170. package/dist/web/assets/schedules-settings-section-Bv_wrzDp.js.br +0 -0
  171. package/dist/web/assets/schedules-settings-section-Bv_wrzDp.js.gz +0 -0
  172. package/dist/web/assets/{settings-body-DU3LZkOy.js → settings-body-C5_chEpq.js} +2 -2
  173. package/dist/web/assets/settings-body-C5_chEpq.js.br +0 -0
  174. package/dist/web/assets/settings-body-C5_chEpq.js.gz +0 -0
  175. package/dist/web/assets/{settings-tab-Q5Ai_XB7.js → settings-tab-CogpEbq5.js} +1 -1
  176. package/dist/web/assets/{settings-window-content-DNg-jjm4.js → settings-window-content-BNRirQDZ.js} +1 -1
  177. package/dist/web/assets/{sql-query-editor-BMNsmnfi.js → sql-query-editor-Bg3lOrmt.js} +1 -1
  178. package/dist/web/assets/sql-query-editor-Bg3lOrmt.js.br +0 -0
  179. package/dist/web/assets/sql-query-editor-Bg3lOrmt.js.gz +0 -0
  180. package/dist/web/assets/{sqlite-viewer-DBdxQgCD.js → sqlite-viewer-DMKtDkI6.js} +1 -1
  181. package/dist/web/assets/sqlite-viewer-DMKtDkI6.js.br +0 -0
  182. package/dist/web/assets/sqlite-viewer-DMKtDkI6.js.gz +0 -0
  183. package/dist/web/assets/{system-monitor-body-BEuqhoiF.js → system-monitor-body-Dst39LA7.js} +2 -2
  184. package/dist/web/assets/system-monitor-body-Dst39LA7.js.br +0 -0
  185. package/dist/web/assets/system-monitor-body-Dst39LA7.js.gz +0 -0
  186. package/dist/web/assets/system-monitor-tab-CEwME1_C.js +1 -0
  187. package/dist/web/assets/{system-monitor-window-content-Dr6EgqpJ.js → system-monitor-window-content-DQRBjn9z.js} +1 -1
  188. package/dist/web/assets/{tab-host-window-content-BhS39QWy.js → tab-host-window-content-Dlunzrfh.js} +1 -1
  189. package/dist/web/assets/tab-host-window-content-Dlunzrfh.js.br +0 -0
  190. package/dist/web/assets/tab-host-window-content-Dlunzrfh.js.gz +0 -0
  191. package/dist/web/assets/{team-member-sheet-C5-PeHDb.js → team-member-sheet-CPZGU9j5.js} +2 -2
  192. package/dist/web/assets/team-member-sheet-CPZGU9j5.js.br +0 -0
  193. package/dist/web/assets/team-member-sheet-CPZGU9j5.js.gz +0 -0
  194. package/dist/web/assets/{team-member-window-content-CZjHuvdw.js → team-member-window-content-CwI9JD_H.js} +1 -1
  195. package/dist/web/assets/{team-member-window-content-CZjHuvdw.js.br → team-member-window-content-CwI9JD_H.js.br} +0 -0
  196. package/dist/web/assets/team-member-window-content-CwI9JD_H.js.gz +0 -0
  197. package/dist/web/assets/{terminal-tab-B0w7Jsbs.js → terminal-tab-BcQXleSa.js} +1 -1
  198. package/dist/web/assets/terminal-tab-BcQXleSa.js.br +0 -0
  199. package/dist/web/assets/terminal-tab-BcQXleSa.js.gz +0 -0
  200. package/dist/web/assets/{tool-cards-CmdjXi2c.js → tool-cards-DsQdMorI.js} +2 -2
  201. package/dist/web/assets/tool-cards-DsQdMorI.js.br +0 -0
  202. package/dist/web/assets/tool-cards-DsQdMorI.js.gz +0 -0
  203. package/dist/web/assets/{use-inline-blame-BSb807l9.js → use-inline-blame--fS-pzzX.js} +1 -1
  204. package/dist/web/assets/use-inline-blame--fS-pzzX.js.br +0 -0
  205. package/dist/web/assets/use-inline-blame--fS-pzzX.js.gz +0 -0
  206. package/dist/web/assets/{use-lsp-DhBjkGWr.js → use-lsp-DK26Eter.js} +1 -1
  207. package/dist/web/assets/use-lsp-DK26Eter.js.br +0 -0
  208. package/dist/web/assets/use-lsp-DK26Eter.js.gz +0 -0
  209. package/dist/web/assets/{use-monaco-theme-CNEk66US.js → use-monaco-theme-BVatH9yZ.js} +1 -1
  210. package/dist/web/assets/{use-websocket-Bki-B4fj.js → use-websocket-C1mloX0R.js} +1 -1
  211. package/dist/web/assets/{video-preview-T1SMFp03.js → video-preview-C48Go7JZ.js} +1 -1
  212. package/dist/web/assets/video-preview-C48Go7JZ.js.br +0 -0
  213. package/dist/web/assets/video-preview-C48Go7JZ.js.gz +0 -0
  214. package/dist/web/index.html +6 -6
  215. package/dist/web/index.html.br +0 -0
  216. package/dist/web/index.html.gz +0 -0
  217. package/dist/web/sw.js +1 -1
  218. package/dist/web/sw.js.br +0 -0
  219. package/dist/web/sw.js.gz +0 -0
  220. package/package.json +1 -1
  221. package/src/providers/codex-app-server/codex-auth-failure.ts +51 -0
  222. package/src/providers/codex-app-server/codex-event-mapper.ts +8 -0
  223. package/src/providers/codex-app-server/codex-provider.ts +97 -19
  224. package/src/providers/codex-app-server/codex-usage-source.ts +14 -1
  225. package/src/server/routes/codex-accounts.ts +18 -6
  226. package/src/services/codex-account-auth-state.ts +58 -0
  227. package/src/services/codex-account-login.ts +42 -9
  228. package/src/services/codex-account.service.ts +42 -7
  229. package/src/services/proxy-agent-anthropic-bridge.ts +2 -2
  230. package/src/services/proxy-agent-bridge.ts +2 -2
  231. package/src/services/proxy-agent-turn.ts +80 -20
  232. package/src/services/proxy-image-bridge.ts +3 -3
  233. package/src/services/proxy.service.ts +1 -1
  234. package/src/web/components/chat/codex-usage-panel.tsx +13 -1
  235. package/src/web/components/chat/usage-badge.tsx +14 -0
  236. package/src/web/components/settings/accounts/account-add-dialog.tsx +10 -3
  237. package/src/web/components/settings/accounts/account-card.tsx +15 -4
  238. package/src/web/components/settings/accounts/account-hint.tsx +19 -2
  239. package/src/web/components/settings/accounts/claude-accounts-section.tsx +10 -2
  240. package/src/web/components/settings/accounts/codex-accounts-section.tsx +18 -2
  241. package/src/web/components/settings/accounts/codex-add-account-dialog.tsx +50 -33
  242. package/src/web/components/settings/accounts/codex-sign-in-again-dialog.tsx +47 -0
  243. package/src/web/components/settings/accounts/use-codex-accounts.ts +18 -4
  244. package/src/web/lib/fluent-icon.tsx +38 -2
  245. package/dist/web/assets/accounts-settings-section-CVJ7hQom.js +0 -1
  246. package/dist/web/assets/accounts-settings-section-CVJ7hQom.js.br +0 -0
  247. package/dist/web/assets/accounts-settings-section-CVJ7hQom.js.gz +0 -0
  248. package/dist/web/assets/ai-resource-editor-DoCiQDKY.js.br +0 -0
  249. package/dist/web/assets/ai-resource-editor-DoCiQDKY.js.gz +0 -0
  250. package/dist/web/assets/ai-settings-section-0x6GFJ56.js.br +0 -0
  251. package/dist/web/assets/ai-settings-section-0x6GFJ56.js.gz +0 -0
  252. package/dist/web/assets/appearance-settings-section-mRiGj3_g.js.br +0 -0
  253. package/dist/web/assets/appearance-settings-section-mRiGj3_g.js.gz +0 -0
  254. package/dist/web/assets/audio-preview-CO7Ia2N5.js.br +0 -0
  255. package/dist/web/assets/audio-preview-CO7Ia2N5.js.gz +0 -0
  256. package/dist/web/assets/branch-review-tab-F3CALiEa.js.br +0 -0
  257. package/dist/web/assets/branch-review-tab-F3CALiEa.js.gz +0 -0
  258. package/dist/web/assets/chat-tab-D6BFnkCj.js +0 -1
  259. package/dist/web/assets/chat-tab-D6BFnkCj.js.br +0 -0
  260. package/dist/web/assets/chat-tab-D6BFnkCj.js.gz +0 -0
  261. package/dist/web/assets/chat-tab-IXHMUKMW.js +0 -15
  262. package/dist/web/assets/chat-tab-IXHMUKMW.js.br +0 -0
  263. package/dist/web/assets/chat-tab-IXHMUKMW.js.gz +0 -0
  264. package/dist/web/assets/code-editor-FsR6KpZa.js.br +0 -0
  265. package/dist/web/assets/code-editor-FsR6KpZa.js.gz +0 -0
  266. package/dist/web/assets/codex-daily-guard-Bn8jfcei.js +0 -1
  267. package/dist/web/assets/codex-daily-guard-Bn8jfcei.js.br +0 -0
  268. package/dist/web/assets/codex-daily-guard-Bn8jfcei.js.gz +0 -0
  269. package/dist/web/assets/conflict-editor-GGqIz6tt.js.br +0 -0
  270. package/dist/web/assets/conflict-editor-GGqIz6tt.js.gz +0 -0
  271. package/dist/web/assets/context-menu-CVO3HtIt.js.br +0 -0
  272. package/dist/web/assets/context-menu-CVO3HtIt.js.gz +0 -0
  273. package/dist/web/assets/csv-preview-ycnxJ_6J.js.br +0 -0
  274. package/dist/web/assets/csv-preview-ycnxJ_6J.js.gz +0 -0
  275. package/dist/web/assets/database-viewer-y6URJv8N.js.br +0 -0
  276. package/dist/web/assets/database-viewer-y6URJv8N.js.gz +0 -0
  277. package/dist/web/assets/design-responsive-dialog-D0QYfX7v.js.br +0 -0
  278. package/dist/web/assets/design-responsive-dialog-D0QYfX7v.js.gz +0 -0
  279. package/dist/web/assets/design-tab-DgCTcwot.js.br +0 -0
  280. package/dist/web/assets/design-tab-DgCTcwot.js.gz +0 -0
  281. package/dist/web/assets/designs-sidebar-panel-Dtuph5jh.js.br +0 -0
  282. package/dist/web/assets/designs-sidebar-panel-Dtuph5jh.js.gz +0 -0
  283. package/dist/web/assets/dialog-CaShS4f5.js.br +0 -0
  284. package/dist/web/assets/dialog-CaShS4f5.js.gz +0 -0
  285. package/dist/web/assets/diff-viewer-ciBkExM7.js.br +0 -0
  286. package/dist/web/assets/diff-viewer-ciBkExM7.js.gz +0 -0
  287. package/dist/web/assets/docx-preview-C8K6QzOW.js.br +0 -0
  288. package/dist/web/assets/docx-preview-C8K6QzOW.js.gz +0 -0
  289. package/dist/web/assets/editor-language-service-CkoaAHjF.js.br +0 -0
  290. package/dist/web/assets/editor-language-service-CkoaAHjF.js.gz +0 -0
  291. package/dist/web/assets/explorer-body-C5gXPGOl.js.br +0 -0
  292. package/dist/web/assets/explorer-body-C5gXPGOl.js.gz +0 -0
  293. package/dist/web/assets/explorer-window-content-8EBl9Vkx.js.br +0 -0
  294. package/dist/web/assets/explorer-window-content-8EBl9Vkx.js.gz +0 -0
  295. package/dist/web/assets/extension-manager-section-DOwZYx8O.js.br +0 -0
  296. package/dist/web/assets/extension-manager-section-DOwZYx8O.js.gz +0 -0
  297. package/dist/web/assets/extension-webview-CdEva8pD.js.br +0 -0
  298. package/dist/web/assets/extension-webview-CdEva8pD.js.gz +0 -0
  299. package/dist/web/assets/files-settings-section-DVemQIZ2.js.br +0 -0
  300. package/dist/web/assets/files-settings-section-DVemQIZ2.js.gz +0 -0
  301. package/dist/web/assets/general-settings-section-DiY_5C93.js.br +0 -0
  302. package/dist/web/assets/general-settings-section-DiY_5C93.js.gz +0 -0
  303. package/dist/web/assets/git-log-panel-Cq0WohOO.js.br +0 -0
  304. package/dist/web/assets/git-log-panel-Cq0WohOO.js.gz +0 -0
  305. package/dist/web/assets/glide-data-grid-DG9AnCmP.js.br +0 -0
  306. package/dist/web/assets/glide-data-grid-DG9AnCmP.js.gz +0 -0
  307. package/dist/web/assets/group-chat-tab-DxbDrFJl.js.br +0 -0
  308. package/dist/web/assets/group-chat-tab-DxbDrFJl.js.gz +0 -0
  309. package/dist/web/assets/icons-C9ZwXgel.js +0 -1
  310. package/dist/web/assets/icons-C9ZwXgel.js.br +0 -0
  311. package/dist/web/assets/icons-C9ZwXgel.js.gz +0 -0
  312. package/dist/web/assets/index-BVKT5UCu.js.br +0 -0
  313. package/dist/web/assets/index-BVKT5UCu.js.gz +0 -0
  314. package/dist/web/assets/index-CzYrKDUF.css +0 -1
  315. package/dist/web/assets/index-CzYrKDUF.css.br +0 -0
  316. package/dist/web/assets/index-CzYrKDUF.css.gz +0 -0
  317. package/dist/web/assets/jira-watcher-section-Cr8gSVGY.js.br +0 -0
  318. package/dist/web/assets/jira-watcher-section-Cr8gSVGY.js.gz +0 -0
  319. package/dist/web/assets/keyboard-shortcuts-section-BvC6m_C2.js.br +0 -0
  320. package/dist/web/assets/keyboard-shortcuts-section-BvC6m_C2.js.gz +0 -0
  321. package/dist/web/assets/markdown-renderer-8JlfrfnD.js.br +0 -0
  322. package/dist/web/assets/markdown-renderer-8JlfrfnD.js.gz +0 -0
  323. package/dist/web/assets/mobile-explorer-sheet-cJfocgE7.js.br +0 -0
  324. package/dist/web/assets/mobile-explorer-sheet-cJfocgE7.js.gz +0 -0
  325. package/dist/web/assets/new-design-dialog-BO_D3nex.js.br +0 -0
  326. package/dist/web/assets/new-design-dialog-BO_D3nex.js.gz +0 -0
  327. package/dist/web/assets/notifications-settings-section-DaMh3khs.js.br +0 -0
  328. package/dist/web/assets/notifications-settings-section-DaMh3khs.js.gz +0 -0
  329. package/dist/web/assets/onboarding-entry-CwkNJ69U.js.br +0 -0
  330. package/dist/web/assets/onboarding-entry-CwkNJ69U.js.gz +0 -0
  331. package/dist/web/assets/onboarding-quick-orientation-u7yM68d3.js.br +0 -0
  332. package/dist/web/assets/onboarding-quick-orientation-u7yM68d3.js.gz +0 -0
  333. package/dist/web/assets/pdf-preview-CaxTidh0.js.br +0 -0
  334. package/dist/web/assets/pdf-preview-CaxTidh0.js.gz +0 -0
  335. package/dist/web/assets/postgres-viewer-CJOOLdgO.js.br +0 -0
  336. package/dist/web/assets/postgres-viewer-CJOOLdgO.js.gz +0 -0
  337. package/dist/web/assets/ppmbot-settings-section-rNBdWG7F.js.br +0 -0
  338. package/dist/web/assets/ppmbot-settings-section-rNBdWG7F.js.gz +0 -0
  339. package/dist/web/assets/problems-panel-BTmOGMcL.js.br +0 -0
  340. package/dist/web/assets/problems-panel-BTmOGMcL.js.gz +0 -0
  341. package/dist/web/assets/process-table-Bc9WEBsS.js.br +0 -0
  342. package/dist/web/assets/process-table-Bc9WEBsS.js.gz +0 -0
  343. package/dist/web/assets/proxy-settings-section-Bo-FPk6Y.js.br +0 -0
  344. package/dist/web/assets/proxy-settings-section-Bo-FPk6Y.js.gz +0 -0
  345. package/dist/web/assets/query-audit-section-CQfzKAf9.js.br +0 -0
  346. package/dist/web/assets/query-audit-section-CQfzKAf9.js.gz +0 -0
  347. package/dist/web/assets/remote-desktop-clipboard-notice-DgeuoZ4n.js.br +0 -0
  348. package/dist/web/assets/remote-desktop-clipboard-notice-DgeuoZ4n.js.gz +0 -0
  349. package/dist/web/assets/remote-desktop-mobile-sheet-BpP7jI0p.js.br +0 -0
  350. package/dist/web/assets/remote-desktop-mobile-sheet-BpP7jI0p.js.gz +0 -0
  351. package/dist/web/assets/remote-desktop-mobile-view-Byn0lFCF.js.br +0 -0
  352. package/dist/web/assets/remote-desktop-mobile-view-Byn0lFCF.js.gz +0 -0
  353. package/dist/web/assets/remote-desktop-readiness-gate-B2mXZ9vo.js.br +0 -0
  354. package/dist/web/assets/remote-desktop-readiness-gate-B2mXZ9vo.js.gz +0 -0
  355. package/dist/web/assets/remote-desktop-window-content-CWt3VVjm.js.br +0 -0
  356. package/dist/web/assets/remote-desktop-window-content-CWt3VVjm.js.gz +0 -0
  357. package/dist/web/assets/schedules-settings-section-Bx9Rm-NB.js.br +0 -0
  358. package/dist/web/assets/schedules-settings-section-Bx9Rm-NB.js.gz +0 -0
  359. package/dist/web/assets/settings-body-DU3LZkOy.js.br +0 -0
  360. package/dist/web/assets/settings-body-DU3LZkOy.js.gz +0 -0
  361. package/dist/web/assets/sql-query-editor-BMNsmnfi.js.br +0 -0
  362. package/dist/web/assets/sql-query-editor-BMNsmnfi.js.gz +0 -0
  363. package/dist/web/assets/sqlite-viewer-DBdxQgCD.js.br +0 -0
  364. package/dist/web/assets/sqlite-viewer-DBdxQgCD.js.gz +0 -0
  365. package/dist/web/assets/system-monitor-body-BEuqhoiF.js.br +0 -0
  366. package/dist/web/assets/system-monitor-body-BEuqhoiF.js.gz +0 -0
  367. package/dist/web/assets/system-monitor-tab-BAU1kzbY.js +0 -1
  368. package/dist/web/assets/tab-host-window-content-BhS39QWy.js.br +0 -0
  369. package/dist/web/assets/tab-host-window-content-BhS39QWy.js.gz +0 -0
  370. package/dist/web/assets/team-member-sheet-C5-PeHDb.js.br +0 -0
  371. package/dist/web/assets/team-member-sheet-C5-PeHDb.js.gz +0 -0
  372. package/dist/web/assets/team-member-window-content-CZjHuvdw.js.gz +0 -0
  373. package/dist/web/assets/terminal-tab-B0w7Jsbs.js.br +0 -0
  374. package/dist/web/assets/terminal-tab-B0w7Jsbs.js.gz +0 -0
  375. package/dist/web/assets/tool-cards-CmdjXi2c.js.br +0 -0
  376. package/dist/web/assets/tool-cards-CmdjXi2c.js.gz +0 -0
  377. package/dist/web/assets/use-inline-blame-BSb807l9.js.br +0 -0
  378. package/dist/web/assets/use-inline-blame-BSb807l9.js.gz +0 -0
  379. package/dist/web/assets/use-lsp-DhBjkGWr.js.br +0 -0
  380. package/dist/web/assets/use-lsp-DhBjkGWr.js.gz +0 -0
  381. package/dist/web/assets/video-preview-T1SMFp03.js.br +0 -0
  382. package/dist/web/assets/video-preview-T1SMFp03.js.gz +0 -0
package/dist/web/sw.js CHANGED
@@ -1 +1 @@
1
- try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}var B=`-precache-`,V=async(e,t=B)=>{let n=(await self.caches.keys()).filter(n=>n.includes(t)&&n.includes(self.registration.scope)&&n!==e);return await Promise.all(n.map(e=>self.caches.delete(e))),n};function H(){self.addEventListener(`activate`,(e=>{let t=a.getPrecacheName();e.waitUntil(V(t).then(e=>{}))}))}function U(e){O().precache(e)}function W(e,t){U(e),z(t)}var G=class extends w{async _handle(e,n){let r=await n.cacheMatch(e),i;if(!r)try{r=await n.fetchAndCachePut(e)}catch(e){e instanceof Error&&(i=e)}if(!r)throw new t(`no-response`,{url:e.url,error:i});return r}};W([{"revision":"42b8694150f4feff73ca093c4abcfb3e","url":"index.html"},{"revision":"26dccd02a2ef7522892015154f5e3680","url":"manifest.webmanifest"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":null,"url":"assets/index-CzYrKDUF.css"},{"revision":null,"url":"assets/index-BVKT5UCu.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"26dccd02a2ef7522892015154f5e3680","url":"manifest.webmanifest"}]),H();var K=e=>(e.headers.get(`content-type`)??``).startsWith(`text/html`);F(({url:e})=>e.pathname.startsWith(`/assets/`),new G({cacheName:`ppm-assets`,plugins:[{cacheWillUpdate:async({response:e})=>K(e)?null:e,cachedResponseWillBeUsed:async({cachedResponse:e})=>e&&K(e)?null:e}]})),self.skipWaiting(),self.addEventListener(`activate`,()=>{self.clients.claim()});
1
+ try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}var B=`-precache-`,V=async(e,t=B)=>{let n=(await self.caches.keys()).filter(n=>n.includes(t)&&n.includes(self.registration.scope)&&n!==e);return await Promise.all(n.map(e=>self.caches.delete(e))),n};function H(){self.addEventListener(`activate`,(e=>{let t=a.getPrecacheName();e.waitUntil(V(t).then(e=>{}))}))}function U(e){O().precache(e)}function W(e,t){U(e),z(t)}var G=class extends w{async _handle(e,n){let r=await n.cacheMatch(e),i;if(!r)try{r=await n.fetchAndCachePut(e)}catch(e){e instanceof Error&&(i=e)}if(!r)throw new t(`no-response`,{url:e.url,error:i});return r}};W([{"revision":"e44b0ffb84bec4e318fe35cd8c1f1ddc","url":"index.html"},{"revision":"26dccd02a2ef7522892015154f5e3680","url":"manifest.webmanifest"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":null,"url":"assets/index-DMA-So69.css"},{"revision":null,"url":"assets/index-CvUGUp7d.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"26dccd02a2ef7522892015154f5e3680","url":"manifest.webmanifest"}]),H();var K=e=>(e.headers.get(`content-type`)??``).startsWith(`text/html`);F(({url:e})=>e.pathname.startsWith(`/assets/`),new G({cacheName:`ppm-assets`,plugins:[{cacheWillUpdate:async({response:e})=>K(e)?null:e,cachedResponseWillBeUsed:async({cachedResponse:e})=>e&&K(e)?null:e}]})),self.skipWaiting(),self.addEventListener(`activate`,()=>{self.clients.claim()});
package/dist/web/sw.js.br CHANGED
Binary file
package/dist/web/sw.js.gz CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.23.0",
3
+ "version": "0.23.2",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Recognising a Codex account that is no longer signed in.
3
+ *
4
+ * A revoked or expired ChatGPT login does not stop the app-server from starting or
5
+ * from opening a thread — both succeed. It is the model request that fails, and codex
6
+ * then spends ~24s in its own reconnect loop ("Reconnecting... 1/5" twice over) before
7
+ * it gives up. Each of those notices carries the real cause in `additionalDetails`
8
+ * (`workspace routing discovery unauthorized (401)`), so the first one is already
9
+ * enough to know that waiting will not help.
10
+ *
11
+ * The same account's quota read fails with `401 Unauthorized ... "code": "token_revoked"`,
12
+ * which is the other place this is recognised.
13
+ *
14
+ * Narrow on purpose, like the usage-limit patterns: a false positive takes a working
15
+ * account out of rotation. A bare "401" is not enough — it has to come with the word
16
+ * the status means, or with codex's own sign-in wording.
17
+ */
18
+
19
+ const AUTH_FAILURE_PATTERNS = [
20
+ /\b401\b[^\n]{0,40}unauthori[sz]ed/i,
21
+ /unauthori[sz]ed[^\n]{0,40}\b401\b/i,
22
+ /\btoken_revoked\b/i,
23
+ /invalidated\s+oauth\s+token/i,
24
+ /refresh\s+token\s+(?:was|has\s+been)\s+(?:revoked|invalidated|already\s+used)/i,
25
+ /(?:log|sign)\s+(?:out\s+and\s+sign\s+)?in\s+again/i,
26
+ ];
27
+
28
+ /** Whether this error text means the account has to be signed in again. */
29
+ export function isCodexAuthFailure(message: string): boolean {
30
+ return AUTH_FAILURE_PATTERNS.some((re) => re.test(message));
31
+ }
32
+
33
+ /**
34
+ * Everything a codex `error` notification says about its cause, as one string.
35
+ *
36
+ * The headline of a retry notice is just "Reconnecting... 2/5"; the reason sits in
37
+ * `additionalDetails`, so matching the message alone would never see it.
38
+ */
39
+ export function codexErrorText(params: unknown): string {
40
+ const p = (params && typeof params === "object" ? params : {}) as Record<string, unknown>;
41
+ const err = (p.error && typeof p.error === "object" ? p.error : {}) as Record<string, unknown>;
42
+ return [err.message, err.additionalDetails, p.message]
43
+ .filter((s): s is string => typeof s === "string" && s.length > 0)
44
+ .join(" — ");
45
+ }
46
+
47
+ /** The message shown when a turn cannot run because its account is signed out. */
48
+ export function codexSignedOutMessage(accountLabel?: string | null): string {
49
+ const who = accountLabel ? `Codex account ${accountLabel}` : "The Codex account";
50
+ return `${who} is signed out (its login was revoked or expired). Press "Sign in again" on its card in Settings → Accounts → Codex.`;
51
+ }
@@ -270,6 +270,14 @@ export function mapCodexEvent(notif: Notif, sessionId: string): ChatEvent[] {
270
270
  const message = typeof err.message === "string" ? err.message
271
271
  : typeof p.message === "string" ? p.message
272
272
  : "codex error";
273
+ // `willRetry: true` is codex narrating its own retry loop ("Reconnecting...
274
+ // 2/5"), not a failure: the turn is still running and ends with either an
275
+ // answer or a final `willRetry: false` error. Reporting each one as an error
276
+ // put a card per attempt in the chat and made the proxy give up on a turn
277
+ // at the first reconnect.
278
+ if (p.willRetry === true) {
279
+ return [{ type: "status_update", phase: "retrying", message: redactTruncate(message, 256) }];
280
+ }
273
281
  return [{ type: "error", message: redactTruncate(message, 1024) }];
274
282
  }
275
283
 
@@ -26,6 +26,10 @@ import {
26
26
  import { dailyGuardMessage, dailyGuardState } from "../../shared/codex-daily-guard.ts";
27
27
  import { isCodexAccountUsageLimited, markCodexAccountUsageLimited } from "../../services/codex-account-cooldown.ts";
28
28
  import { isCodexUsageLimit, codexErrorMessage, parseCodexUsageLimitReset } from "./codex-usage-limit.ts";
29
+ import { isCodexAuthFailure, codexErrorText, codexSignedOutMessage } from "./codex-auth-failure.ts";
30
+ import {
31
+ isCodexAccountAuthFailed, markCodexAccountAuthFailed, clearCodexAccountAuthFailure,
32
+ } from "../../services/codex-account-auth-state.ts";
29
33
  import { killProcessTree } from "../../services/windows-process-tree.ts";
30
34
  import { homedir } from "node:os";
31
35
  import { join } from "node:path";
@@ -181,8 +185,16 @@ interface LiveSession {
181
185
  /** The turn currently in flight, kept so a rotation can send it again on the new
182
186
  * account. Cleared when the turn ends — there is then nothing to replay. */
183
187
  lastTurnInput?: { message: string; opts?: SendMessageOpts };
188
+ /** The running turn has already been reported as failed (its account is signed out) and
189
+ * interrupted. Codex keeps narrating it — more retry notices, a final error, then
190
+ * `turn/completed` — none of which may reach the caller a second time. Cleared by that
191
+ * `turn/completed`, which is also when the queue may move on. */
192
+ discardingTurn?: boolean;
184
193
  }
185
194
 
195
+ /** Why a turn is being moved to another account. */
196
+ type RotationKind = "usage" | "auth";
197
+
186
198
  /** A follow-up held back because a turn was already running. */
187
199
  interface QueuedTurn {
188
200
  message: string;
@@ -516,7 +528,15 @@ export class CodexAppServerProvider implements AIProvider {
516
528
  // A quota refusal can come back as the rejection of the request itself rather than
517
529
  // as an `error` notification, and it is the same situation either way — move the
518
530
  // turn to an account that still has room instead of showing the refusal.
519
- if (isCodexUsageLimit(reason) && this.beginRotation(live, reason)) return;
531
+ if (isCodexUsageLimit(reason) && this.beginRotation(live, reason, "usage")) return;
532
+ if (isCodexAuthFailure(reason)) {
533
+ this.markSessionAccountSignedOut(live, reason);
534
+ if (this.beginRotation(live, reason, "auth")) return;
535
+ live.channel.push({ type: "error", message: codexSignedOutMessage(this.sessionAccountLabel(live)) });
536
+ live.channel.push({ type: "done", sessionId: live.threadId ?? "", resultSubtype: "error_during_execution" });
537
+ this.endTurn(live);
538
+ return;
539
+ }
520
540
  if (!live.client.isClosed) live.channel.push({ type: "error", message: redactTruncate(reason, 256) });
521
541
  // No turn is running, so nothing will report `turn/completed` — release the
522
542
  // queue here or every later follow-up waits on a turn that never existed.
@@ -556,6 +576,7 @@ export class CodexAppServerProvider implements AIProvider {
556
576
  live.turnInFlight = false;
557
577
  live.activeTurnId = null;
558
578
  live.interruptRequested = false;
579
+ live.discardingTurn = false;
559
580
  live.lastTurnInput = undefined;
560
581
  const next = live.pendingTurns.shift();
561
582
  if (next) void this.startTurn(live, next.message, next.opts);
@@ -575,29 +596,64 @@ export class CodexAppServerProvider implements AIProvider {
575
596
  * user untouched. That is the honest outcome: an error saying the quota is spent is worth
576
597
  * more than a silent retry on the account that just said so.
577
598
  */
578
- private beginRotation(live: LiveSession, reason: string): boolean {
599
+ private beginRotation(live: LiveSession, reason: string, kind: RotationKind): boolean {
579
600
  const threadId = live.threadId;
580
601
  if (!threadId || live.rotating) return false;
581
602
  const currentId = getSessionCodexAccount(threadId);
582
603
  const candidates = listCodexAccounts().filter(
583
- (a) => a.status !== "disabled" && a.id !== currentId && !isCodexAccountUsageLimited(a.id),
604
+ (a) => a.status !== "disabled" && a.id !== currentId
605
+ && !isCodexAccountUsageLimited(a.id) && !isCodexAccountAuthFailed(a.id),
584
606
  );
585
607
  if (candidates.length === 0) return false;
586
608
  live.rotating = true;
587
- void this.rotateAccount(live, threadId, currentId, reason);
609
+ void this.rotateAccount(live, threadId, currentId, reason, kind);
588
610
  return true;
589
611
  }
590
612
 
591
- /** Park the spent account, move the session onto a fresh one, and send the turn again. */
613
+ /** Mark the account serving this session as signed out, if it is a managed one. */
614
+ private markSessionAccountSignedOut(live: LiveSession, reason: string): void {
615
+ const accountId = live.threadId ? getSessionCodexAccount(live.threadId) : null;
616
+ if (accountId) markCodexAccountAuthFailed(accountId, redactTruncate(reason, 256));
617
+ }
618
+
619
+ private sessionAccountLabel(live: LiveSession): string | null {
620
+ const accountId = live.threadId ? getSessionCodexAccount(live.threadId) : null;
621
+ return accountId ? getCodexAccount(accountId)?.label ?? null : null;
622
+ }
623
+
624
+ /**
625
+ * The running turn's account turned out to be signed out.
626
+ *
627
+ * Recognised on codex's FIRST retry notice rather than its final error: the notice already
628
+ * names the 401, and waiting out the rest of the loop is ~24s of a subprocess and a request
629
+ * held open for an answer that cannot come. Moves the turn to another account when one can
630
+ * take it; otherwise reports it and interrupts the turn, discarding what codex still says
631
+ * about it.
632
+ */
633
+ private handleAuthFailure(live: LiveSession, reason: string): void {
634
+ this.markSessionAccountSignedOut(live, reason);
635
+ if (this.beginRotation(live, reason, "auth")) return;
636
+ live.discardingTurn = true;
637
+ this.interruptActiveTurn(live);
638
+ live.channel.push({ type: "error", message: codexSignedOutMessage(this.sessionAccountLabel(live)) });
639
+ live.channel.push({ type: "done", sessionId: live.threadId ?? "", resultSubtype: "error_during_execution" });
640
+ }
641
+
642
+ /** Bench the refused account, move the session onto a fresh one, and send the turn again. */
592
643
  private async rotateAccount(
593
644
  live: LiveSession,
594
645
  threadId: string,
595
646
  currentId: string | null,
596
647
  reason: string,
648
+ kind: RotationKind,
597
649
  ): Promise<void> {
598
650
  const pending = live.lastTurnInput;
599
- const reset = parseCodexUsageLimitReset(reason);
600
- if (currentId) markCodexAccountUsageLimited(currentId, reset?.atMs);
651
+ const reset = kind === "usage" ? parseCodexUsageLimitReset(reason) : null;
652
+ // A signed-out account was already marked by the caller; only a quota refusal parks here.
653
+ if (currentId && kind === "usage") markCodexAccountUsageLimited(currentId, reset?.atMs);
654
+ const giveUp = (): void => this.abandonRotation(live, kind === "auth"
655
+ ? codexSignedOutMessage(currentId ? getCodexAccount(currentId)?.label : null)
656
+ : this.usageLimitMessage(reason, reset?.text));
601
657
 
602
658
  let next: CodexAccount | null = null;
603
659
  try {
@@ -612,12 +668,12 @@ export class CodexAppServerProvider implements AIProvider {
612
668
  next = selectCodexAccount(currentId ? { exclude: [currentId] } : undefined);
613
669
  }
614
670
 
615
- if (!next) { this.abandonRotation(live, reason, reset?.text); return; }
671
+ if (!next) { giveUp(); return; }
616
672
 
617
- console.warn(`[codex] session=${threadId} usage limit — switching to ${next.id} (${next.label})`);
673
+ console.warn(`[codex] session=${threadId} ${kind === "auth" ? "account signed out" : "usage limit"} — switching to ${next.id} (${next.label})`);
618
674
  live.channel.push({
619
675
  type: "account_retry",
620
- reason: "Usage limit reached — switching account",
676
+ reason: kind === "auth" ? "Account signed out — switching account" : "Usage limit reached — switching account",
621
677
  accountId: next.id,
622
678
  accountLabel: next.label,
623
679
  });
@@ -626,7 +682,7 @@ export class CodexAppServerProvider implements AIProvider {
626
682
  await this.respawnOn(live, threadId, next);
627
683
  } catch (e) {
628
684
  console.error(`[codex] session=${threadId} rotation to ${next.id} failed: ${redactTruncate((e as Error)?.message, 200)}`);
629
- this.abandonRotation(live, reason, reset?.text);
685
+ giveUp();
630
686
  return;
631
687
  }
632
688
 
@@ -639,14 +695,15 @@ export class CodexAppServerProvider implements AIProvider {
639
695
  else this.endTurn(live);
640
696
  }
641
697
 
698
+ private usageLimitMessage(reason: string, resetText?: string): string {
699
+ const suffix = resetText ? ` Try again ${resetText}.` : "";
700
+ return `${redactTruncate(reason, 512)}${suffix} Add another account in Settings → Accounts, or wait for the reset.`;
701
+ }
702
+
642
703
  /** No account could take the turn — report the refusal and close the turn out. */
643
- private abandonRotation(live: LiveSession, reason: string, resetText?: string): void {
704
+ private abandonRotation(live: LiveSession, message: string): void {
644
705
  live.rotating = false;
645
- const suffix = resetText ? ` Try again ${resetText}.` : "";
646
- live.channel.push({
647
- type: "error",
648
- message: `${redactTruncate(reason, 512)}${suffix} Add another account in Settings → Accounts, or wait for the reset.`,
649
- });
706
+ live.channel.push({ type: "error", message });
650
707
  live.channel.push({ type: "done", sessionId: live.threadId ?? "", resultSubtype: "error_during_execution" });
651
708
  this.endTurn(live);
652
709
  }
@@ -825,14 +882,35 @@ export class CodexAppServerProvider implements AIProvider {
825
882
  private handleNotification(live: LiveSession, notif: JsonRpcNotification): void {
826
883
  // A quota refusal is an ordinary `error` notification, so it has to be recognised
827
884
  // before the mapper turns it into a plain error card in front of the user.
828
- if (notif.method === "error" && !live.rotating) {
885
+ if (notif.method === "error" && !live.rotating && !live.discardingTurn) {
886
+ // Retry notices count too: the headline says only "Reconnecting... N/5", but their
887
+ // details name the 401, and waiting for the final error costs the whole retry loop.
888
+ const authText = codexErrorText(notif.params);
889
+ if (isCodexAuthFailure(authText)) { this.handleAuthFailure(live, authText); return; }
829
890
  const reason = codexErrorMessage(notif.params);
830
- if (isCodexUsageLimit(reason) && this.beginRotation(live, reason)) return;
891
+ if (isCodexUsageLimit(reason) && this.beginRotation(live, reason, "usage")) return;
831
892
  }
832
893
  // Mid-rotation the subprocess behind this session is being replaced. Anything it still
833
894
  // emits describes the turn that was refused — including its `turn/completed`, which
834
895
  // would close the turn the caller is about to be given a second time.
835
896
  if (live.rotating) return;
897
+ // A turn already reported as failed: swallow the rest of it, and let the queue move on
898
+ // only once codex confirms it is over.
899
+ if (live.discardingTurn) {
900
+ if (notif.method === "turn/completed") {
901
+ live.discardingTurn = false;
902
+ live.currentAssistant = "";
903
+ live.currentEvents = [];
904
+ this.endTurn(live);
905
+ }
906
+ return;
907
+ }
908
+ if (notif.method === "turn/completed"
909
+ && (notif.params as { turn?: { status?: string } })?.turn?.status === "completed" && live.threadId) {
910
+ // An answered turn is proof the login works, whatever was observed before.
911
+ const accountId = getSessionCodexAccount(live.threadId);
912
+ if (accountId) clearCodexAccountAuthFailure(accountId);
913
+ }
836
914
  if (notif.method === "item/agentMessage/delta") {
837
915
  const d = (notif.params as { delta?: string })?.delta;
838
916
  if (typeof d === "string") live.currentAssistant += d;
@@ -3,6 +3,8 @@ import type { ProviderUsageSource } from "../../services/provider-usage/usage-so
3
3
  import { AMBIENT_ACCOUNT_KEY } from "../../services/provider-usage/usage-source.ts";
4
4
  import { listCodexAccounts, getCodexAccount } from "../../services/codex-account.service.ts";
5
5
  import { fetchCodexUsageLive } from "./codex-usage-fetch.ts";
6
+ import { isCodexAuthFailure } from "./codex-auth-failure.ts";
7
+ import { markCodexAccountAuthFailed, clearCodexAccountAuthFailure } from "../../services/codex-account-auth-state.ts";
6
8
 
7
9
  /**
8
10
  * Codex's plug into the shared usage layer.
@@ -29,7 +31,18 @@ export const codexUsageSource: ProviderUsageSource = {
29
31
  // object would be persisted as a real reading for an account that is gone.
30
32
  if (!account) throw new Error(`codex account ${accountId} no longer exists`);
31
33
 
32
- const usage = await fetchCodexUsageLive(account.home);
34
+ let usage: UsageInfo;
35
+ try {
36
+ usage = await fetchCodexUsageLive(account.home);
37
+ } catch (e) {
38
+ // The sweep is the one thing that reads every account on a timer, so it is what keeps
39
+ // a signed-out account benched between turns — and what notices it first, before any
40
+ // turn is spent on it.
41
+ const message = (e as Error)?.message ?? String(e);
42
+ if (isCodexAuthFailure(message)) markCodexAccountAuthFailed(account.id, message.slice(0, 256));
43
+ throw e;
44
+ }
45
+ clearCodexAccountAuthFailure(account.id);
33
46
  // The rate-limit payload carries the plan name, which the parser puts in
34
47
  // activeAccountLabel; the account's own label is the truthful one.
35
48
  return { ...usage, activeAccountId: account.id, activeAccountLabel: account.label };
@@ -3,11 +3,20 @@ import { ok, err } from "../../types/api.ts";
3
3
  import { listCodexAccounts, removeCodexAccount, getAllCodexUsages, getCodexStrategy, setCodexStrategy, selectCodexAccount, setCodexAccountStatus, setCodexDailyGuard, codexUsageLevel, type CodexStrategy } from "../../services/codex-account.service.ts";
4
4
  import { addApiKeyAccount, startDeviceLogin, getDeviceLoginStatus, cancelDeviceLogin, startBrowserLogin, submitBrowserCallback, getBrowserLoginStatus, cancelBrowserLogin } from "../../services/codex-account-login.ts";
5
5
  import { exportCodexEncrypted, importCodexEncrypted } from "../../services/codex-account-portability.ts";
6
+ import { isCodexAccountAuthFailed } from "../../services/codex-account-auth-state.ts";
6
7
 
7
8
  /** Codex multi-account management. Mounted under /api/codex-accounts (auth-guarded). */
8
9
  export const codexAccountsRoutes = new Hono();
9
10
 
10
- codexAccountsRoutes.get("/", (c) => c.json(ok({ accounts: listCodexAccounts(), strategy: getCodexStrategy() })));
11
+ /**
12
+ * `signedOut` is the server's live observation (a revoked/expired login seen on a turn or a
13
+ * quota read), not a stored field. The usage endpoint keeps serving the last good reading
14
+ * for such an account, so without it the card looked healthy while every turn failed.
15
+ */
16
+ codexAccountsRoutes.get("/", (c) => c.json(ok({
17
+ accounts: listCodexAccounts().map((a) => ({ ...a, signedOut: isCodexAccountAuthFailed(a.id) })),
18
+ strategy: getCodexStrategy(),
19
+ })));
11
20
 
12
21
  /** Per-account quota map { [accountId]: UsageInfo }. */
13
22
  codexAccountsRoutes.get("/usage", async (c) => c.json(ok(await getAllCodexUsages())));
@@ -73,10 +82,12 @@ codexAccountsRoutes.post("/api-key", async (c) => {
73
82
  catch (e) { return c.json(err((e as Error).message), 400); }
74
83
  });
75
84
 
76
- /** Begin ChatGPT device-code login → returns { id, userCode, verificationUrl }. */
85
+ /** Begin ChatGPT device-code login → returns { id, userCode, verificationUrl }.
86
+ * With `accountId`, signs that existing account in again instead of adding one. */
77
87
  codexAccountsRoutes.post("/device-login", async (c) => {
78
- const body = await c.req.json<{ label?: string }>().catch(() => ({} as { label?: string }));
79
- try { return c.json(ok(await startDeviceLogin(body.label))); }
88
+ const body = await c.req.json<{ label?: string; accountId?: unknown }>().catch(() => ({} as { label?: string; accountId?: unknown }));
89
+ if (body.accountId !== undefined && typeof body.accountId !== "string") return c.json(err("accountId must be a string"), 400);
90
+ try { return c.json(ok(await startDeviceLogin(body.label, undefined, body.accountId))); }
80
91
  catch (e) { return c.json(err((e as Error).message), 400); }
81
92
  });
82
93
 
@@ -92,10 +103,11 @@ codexAccountsRoutes.delete("/device-login/:id", (c) => {
92
103
 
93
104
  /** Browser OAuth uses the same durable polling as device-code login. */
94
105
  codexAccountsRoutes.post("/browser-login", async (c) => {
95
- const body = await c.req.json<{ label?: string }>().catch(() => ({} as { label?: string }));
106
+ const body = await c.req.json<{ label?: string; accountId?: unknown }>().catch(() => ({} as { label?: string; accountId?: unknown }));
96
107
  if (body.label !== undefined && typeof body.label !== "string") return c.json(err("label must be a string"), 400);
108
+ if (body.accountId !== undefined && typeof body.accountId !== "string") return c.json(err("accountId must be a string"), 400);
97
109
  c.header("Cache-Control", "no-store");
98
- try { return c.json(ok(await startBrowserLogin(body.label))); }
110
+ try { return c.json(ok(await startBrowserLogin(body.label, undefined, body.accountId))); }
99
111
  catch (e) { return c.json(err((e as Error).message), 400); }
100
112
  });
101
113
 
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Codex accounts the server has seen refused for a revoked or expired login.
3
+ *
4
+ * Sibling of `codex-account-cooldown.ts`, and in memory for the same reason: this is the
5
+ * server's own observation, not the user's on/off switch, and writing it into the account
6
+ * row would show an account the user never touched as changed in Settings.
7
+ *
8
+ * Unlike a quota park this is a hard exclusion from selection. A spent quota still answers
9
+ * slowly; a signed-out account never answers at all, and every turn handed to it costs a
10
+ * subprocess and ~24s of codex retrying before it says so. That is what let a burst of
11
+ * proxy requests pin the server until the supervisor killed it.
12
+ *
13
+ * The mark lapses on its own so a misread error cannot bench an account forever, and the
14
+ * usage sweep (every 5 minutes) renews it for as long as the account really is signed out.
15
+ * A successful usage read, a completed turn or a fresh sign-in clears it at once.
16
+ */
17
+
18
+ const MARK_TTL_MS = 15 * 60 * 1000;
19
+
20
+ interface AuthFailure {
21
+ until: number;
22
+ reason: string;
23
+ since: number;
24
+ }
25
+
26
+ const failures = new Map<string, AuthFailure>();
27
+
28
+ /** Record that this account's login was refused. */
29
+ export function markCodexAccountAuthFailed(accountId: string, reason: string): void {
30
+ const now = Date.now();
31
+ const prev = failures.get(accountId);
32
+ const fresh = !prev || prev.until <= now;
33
+ failures.set(accountId, { until: now + MARK_TTL_MS, reason, since: fresh ? now : prev.since });
34
+ if (fresh) console.warn(`[codex] account ${accountId} is signed out — skipping it until it signs in again`);
35
+ }
36
+
37
+ /** Whether the account is currently known to be signed out. Lapsed marks drop as read. */
38
+ export function isCodexAccountAuthFailed(accountId: string): boolean {
39
+ const f = failures.get(accountId);
40
+ if (!f) return false;
41
+ if (f.until <= Date.now()) { failures.delete(accountId); return false; }
42
+ return true;
43
+ }
44
+
45
+ /** When the account was first seen signed out, or null if it is not. */
46
+ export function codexAccountAuthFailedSince(accountId: string): number | null {
47
+ return isCodexAccountAuthFailed(accountId) ? failures.get(accountId)!.since : null;
48
+ }
49
+
50
+ /** The account answered normally again — drop the mark. */
51
+ export function clearCodexAccountAuthFailure(accountId: string): void {
52
+ if (failures.delete(accountId)) console.log(`[codex] account ${accountId} is signed in again`);
53
+ }
54
+
55
+ /** Test seam: forget every mark. */
56
+ export function _resetCodexAuthFailuresForTesting(): void {
57
+ failures.clear();
58
+ }
@@ -17,7 +17,9 @@ import { randomUUID } from "node:crypto";
17
17
  import { mkdirSync, rmSync } from "node:fs";
18
18
  import { buildCodexCallback, buildCodexSuccessCallback, checkCodexLoginPort, parseBrowserLogin, type BrowserLoginCallback } from "./codex-login-callback.ts";
19
19
  import { CodexJsonRpcClient, CONTROL_REQUEST_TIMEOUT_MS } from "../providers/codex-app-server/codex-jsonrpc-client.ts";
20
- import { codexAccountHome, createCodexAccount, type CodexAccount } from "./codex-account.service.ts";
20
+ import { codexAccountHome, createCodexAccount, getCodexAccount, updateCodexAccountMeta, type CodexAccount } from "./codex-account.service.ts";
21
+ import { clearCodexAccountAuthFailure } from "./codex-account-auth-state.ts";
22
+ import { refreshUsage } from "./provider-usage/usage-registry.ts";
21
23
 
22
24
  const CLIENT_INFO = { name: "ppm", title: "PPM", version: "0.0.0" };
23
25
  const CAPABILITIES = { experimentalApi: true, requestAttestation: false, optOutNotificationMethods: null };
@@ -78,6 +80,9 @@ interface ChatGptLogin {
78
80
  browser?: BrowserLoginCallback;
79
81
  callbackAbort?: AbortController;
80
82
  client: LoginClient; home: string; label?: string;
83
+ /** Signing an existing account in again: the login runs in that account's own home and
84
+ * finalises by updating its row instead of creating one. */
85
+ reloginId?: string;
81
86
  status: DeviceLoginStatus;
82
87
  timer: ReturnType<typeof setTimeout>;
83
88
  /** True from the completion notification until the account is written. The
@@ -116,7 +121,8 @@ function disposeLogin(id: string, rmHome: boolean): void {
116
121
  clearTimeout(p.timer);
117
122
  p.callbackAbort?.abort();
118
123
  try { p.client.close(); } catch { /* ignore */ }
119
- if (rmHome) { try { rmSync(p.home, { recursive: true, force: true }); } catch { /* ignore */ } }
124
+ // A re-login borrows a live account's home; abandoning it must never delete that.
125
+ if (rmHome && !p.reloginId) { try { rmSync(p.home, { recursive: true, force: true }); } catch { /* ignore */ } }
120
126
  }
121
127
 
122
128
  /** Record the outcome and stop the app-server. First settle wins, so the close
@@ -131,7 +137,8 @@ function settle(id: string, status: Exclude<DeviceLoginStatus, { state: "pending
131
137
  try { p.client.close(); } catch { /* ignore */ }
132
138
  // A failure leaves no account owning the dir, so the half-built home goes now.
133
139
  // A success must keep it: createCodexAccount adopted it as the account's home.
134
- if (status.state === "error") { try { rmSync(p.home, { recursive: true, force: true }); } catch { /* ignore */ } }
140
+ // A failed re-login leaves the account's home alone — it still belongs to the account.
141
+ if (status.state === "error" && !p.reloginId) { try { rmSync(p.home, { recursive: true, force: true }); } catch { /* ignore */ } }
135
142
  p.timer = unref(setTimeout(() => { pendingLogins.delete(id); }, RESULT_GRACE_MS));
136
143
  }
137
144
 
@@ -147,6 +154,21 @@ async function finalizeLogin(id: string, notif: CompletionNotification): Promise
147
154
  const read = await p.client.request<AccountRead>("account/read", {}, CONTROL_REQUEST_TIMEOUT_MS);
148
155
  if (!read?.account) throw new Error("login completed but account is empty");
149
156
  if (p.status.state !== "pending") return;
157
+ if (p.reloginId) {
158
+ const existing = getCodexAccount(p.reloginId);
159
+ if (!existing) throw new Error("That account was removed while signing in. Add it again.");
160
+ updateCodexAccountMeta(existing.id, {
161
+ // The label is the user's name for this account ("Work"), not a copy of the email.
162
+ label: p.label || existing.label,
163
+ planType: read.account.planType ?? existing.planType ?? null,
164
+ });
165
+ // The fresh login is the proof: stop skipping the account and read its quota now,
166
+ // rather than showing the stale reading until the next five-minute sweep.
167
+ clearCodexAccountAuthFailure(existing.id);
168
+ void refreshUsage("codex", existing.id);
169
+ settle(id, { state: "done", account: getCodexAccount(existing.id)! });
170
+ return;
171
+ }
150
172
  const account = createCodexAccount({
151
173
  id, label: p.label || read.account.email || "ChatGPT", type: "chatgpt",
152
174
  planType: read.account.planType ?? null,
@@ -163,12 +185,19 @@ async function startChatGptLogin(
163
185
  method: "device" | "browser",
164
186
  label: string | undefined,
165
187
  makeClient: () => LoginClient,
188
+ reloginId?: string,
166
189
  ): Promise<ChatGptLoginStart> {
167
190
  if (method === "browser" && hasPendingBrowserLogin()) {
168
191
  throw new Error("A browser login is already in progress. Finish or cancel it first.");
169
192
  }
193
+ // Re-login signs the SAME account in again, in its own CODEX_HOME: the id, the sessions
194
+ // bound to it, its transcripts and its settings all stay. Removing and re-adding was the
195
+ // only way before, and it cost every one of those.
196
+ const target = reloginId ? getCodexAccount(reloginId) : null;
197
+ if (reloginId && !target) throw new Error("Account not found");
198
+ if (target && target.type !== "chatgpt") throw new Error("Only a ChatGPT sign-in can be renewed; replace an API key by adding it again.");
170
199
  const id = randomUUID();
171
- const home = codexAccountHome(id);
200
+ const home = target ? target.home : codexAccountHome(id);
172
201
  mkdirSync(home, { recursive: true, mode: 0o700 });
173
202
  const client = makeClient();
174
203
  client.onNotification((n) => {
@@ -178,7 +207,10 @@ async function startChatGptLogin(
178
207
  });
179
208
  client.onClose(() => settle(id, { state: "error", error: "login process exited" }));
180
209
  const timer = unref(setTimeout(() => settle(id, { state: "error", error: "timed out" }), method === "browser" ? BROWSER_LOGIN_TTL : DEVICE_LOGIN_TTL));
181
- const pending: ChatGptLogin = { method, client, home, label, status: { state: "pending" }, timer, finalizing: false };
210
+ const pending: ChatGptLogin = {
211
+ method, client, home, label, status: { state: "pending" }, timer, finalizing: false,
212
+ ...(target ? { reloginId: target.id } : {}),
213
+ };
182
214
  pendingLogins.set(id, pending);
183
215
  try {
184
216
  if (method === "browser") await checkCodexLoginPort();
@@ -195,13 +227,14 @@ async function startChatGptLogin(
195
227
  }
196
228
  }
197
229
 
198
- export async function startDeviceLogin(label?: string, makeClient: () => LoginClient = () => new CodexJsonRpcClient()) {
199
- const { id, userCode, verificationUrl } = await startChatGptLogin("device", label, makeClient);
230
+ /** `reloginId` signs that existing ChatGPT account in again instead of adding a new one. */
231
+ export async function startDeviceLogin(label?: string, makeClient: () => LoginClient = () => new CodexJsonRpcClient(), reloginId?: string) {
232
+ const { id, userCode, verificationUrl } = await startChatGptLogin("device", label, makeClient, reloginId);
200
233
  return { id, userCode, verificationUrl };
201
234
  }
202
235
 
203
- export async function startBrowserLogin(label?: string, makeClient: () => LoginClient = () => new CodexJsonRpcClient()) {
204
- const { id, authUrl } = await startChatGptLogin("browser", label, makeClient);
236
+ export async function startBrowserLogin(label?: string, makeClient: () => LoginClient = () => new CodexJsonRpcClient(), reloginId?: string) {
237
+ const { id, authUrl } = await startChatGptLogin("browser", label, makeClient, reloginId);
205
238
  return { id, authUrl };
206
239
  }
207
240