@hienlh/ppm 0.19.12 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/CLAUDE.md +8 -1
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +4 -1
- package/dist/web/assets/accounts-settings-section-BQyru31C.js +1 -0
- package/dist/web/assets/{ai-resource-editor-CqL-X2zu.js → ai-resource-editor-BgYIPGVP.js} +1 -1
- package/dist/web/assets/{ai-settings-section-CcqokdzF.js → ai-settings-section-Cywmtnps.js} +1 -1
- package/dist/web/assets/ai-settings-section-iBRCQvBR.js +1 -0
- package/dist/web/assets/{angular-html-C8nsn2VC.js → angular-html-BtJLNcMw.js} +1 -1
- package/dist/web/assets/{angular-ts-Caw9nRCH.js → angular-ts-CQiu5F_W.js} +1 -1
- package/dist/web/assets/api-client-QfreGirP.js +1 -0
- package/dist/web/assets/api-settings-D18Q4Ocp.js +1 -0
- package/dist/web/assets/{api-themes-BoOJPqwM.js → api-themes-sSv-6OLk.js} +1 -1
- package/dist/web/assets/{apl-Dhu1LORF.js → apl-CKZvgs-w.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-2hZBnpPS.js → appearance-settings-section-mhu9T6Fk.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DfR8k8Bc.js +1 -0
- package/dist/web/assets/{astro-DwkX9sLZ.js → astro-D0b8ssLD.js} +1 -1
- package/dist/web/assets/{audio-preview-CkGZO8Uy.js → audio-preview-B4E5_7TW.js} +1 -1
- package/dist/web/assets/{blade-D1fC9Osx.js → blade-DOIpINLw.js} +1 -1
- package/dist/web/assets/{bsl-pur-PRip.js → bsl-CJ7_JlQ8.js} +1 -1
- package/dist/web/assets/{button-2WCAS8dI.js → button-DX32ffge.js} +1 -1
- package/dist/web/assets/{cairo-DDJs9amR.js → cairo-5CpV2T1P.js} +1 -1
- package/dist/web/assets/chat-tab-Bs9OD6ss.js +15 -0
- package/dist/web/assets/{cobol-Bo0usdoM.js → cobol-BreMVrtc.js} +1 -1
- package/dist/web/assets/code-editor-Cv_pzHo7.js +7 -0
- package/dist/web/assets/{coffee-DBBsbX1e.js → coffee-D7DoKh5b.js} +1 -1
- package/dist/web/assets/{conflict-editor-NqFAJP47.js → conflict-editor-BnWaMSEa.js} +1 -1
- package/dist/web/assets/{context-menu-C13b1uT3.js → context-menu-CJL8L98s.js} +1 -1
- package/dist/web/assets/{cpp-6CTO78A-.js → cpp-BnWeWAoE.js} +1 -1
- package/dist/web/assets/{crystal-DAlvpAP7.js → crystal-D2pJbFUn.js} +1 -1
- package/dist/web/assets/{csv-preview-Dvriy18_.js → csv-preview-DG4YAabr.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-iRrpAV0e.js → data-grid-overlay-editor-CbBkhhJa.js} +1 -1
- package/dist/web/assets/{database-viewer-Bs2RMm5a.js → database-viewer-B-wA2qT-.js} +1 -1
- package/dist/web/assets/diff-viewer-DRtzdfXk.js +4 -0
- package/dist/web/assets/{docx-preview-Bgw838ry.js → docx-preview-Xy7SZ7Vf.js} +1 -1
- package/dist/web/assets/{edge-D0tZrpC7.js → edge-aqGMtbY3.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-DUgmjNre.js → edit-diff-preview-6tzdk7Pn.js} +1 -1
- package/dist/web/assets/{elixir-C1E6-o4G.js → elixir-CsYMBLc5.js} +1 -1
- package/dist/web/assets/{elm-BKyTfeQX.js → elm-BU8C2gFp.js} +1 -1
- package/dist/web/assets/{erb-Cdnr4BMI.js → erb-Z_E49ab8.js} +1 -1
- package/dist/web/assets/{erlang-N1237mFq.js → erlang-BRqOjXH9.js} +1 -1
- package/dist/web/assets/{esm-ClNnmD9t.js → esm-B-vXxHUd.js} +1 -1
- package/dist/web/assets/{explorer-body-DfWunSf9.js → explorer-body-uh8oJp6J.js} +2 -2
- package/dist/web/assets/explorer-window-content-DLn_4egF.js +1 -0
- package/dist/web/assets/{extension-manager-section-Zc9yag2c.js → extension-manager-section-C_ZGYvWf.js} +1 -1
- package/dist/web/assets/{extension-webview-B5FayDPz.js → extension-webview-DbFWxJpj.js} +1 -1
- package/dist/web/assets/{files-settings-section-CxAtO3GL.js → files-settings-section-BrUbJglA.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-D_f9tVv0.js → fortran-fixed-form-CWbxmDGR.js} +1 -1
- package/dist/web/assets/{fsharp-DEJmdhc2.js → fsharp-DKMYbq6I.js} +1 -1
- package/dist/web/assets/{gdresource-Dmmrx2gP.js → gdresource-B3yFau4V.js} +1 -1
- package/dist/web/assets/{general-settings-section-9DfKrBns.js → general-settings-section-B9ZPZVso.js} +1 -1
- package/dist/web/assets/{git-commit-qwJ867ig.js → git-commit-BW0p3Ekk.js} +1 -1
- package/dist/web/assets/{git-log-panel-BsmDZurR.js → git-log-panel-C0jnhmgC.js} +1 -1
- package/dist/web/assets/{git-rebase-Dkv8Zw86.js → git-rebase-DrvmQCS7.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CPd78aaT.js +1 -0
- package/dist/web/assets/{glide-data-grid-BuYwXIoS.js → glide-data-grid-CZVKIum-.js} +6 -6
- package/dist/web/assets/{glimmer-js-CcrXn8lQ.js → glimmer-js-BY1XApJJ.js} +1 -1
- package/dist/web/assets/{glimmer-ts-CLab3lU7.js → glimmer-ts-DPZtxF8N.js} +1 -1
- package/dist/web/assets/{glsl-DXhkG9dU.js → glsl-DZ8G_Hvf.js} +1 -1
- package/dist/web/assets/{graphql-Cuqsx8r3.js → graphql-Ck06uCZy.js} +1 -1
- package/dist/web/assets/group-chat-tab-DnPtmbPc.js +1 -0
- package/dist/web/assets/{hack-C_CBVdBc.js → hack-pLogDwAs.js} +1 -1
- package/dist/web/assets/{haml-CBWfzZpv.js → haml-CBqeCQ2A.js} +1 -1
- package/dist/web/assets/{handlebars-4e8OR-VI.js → handlebars-B66JmVtp.js} +1 -1
- package/dist/web/assets/{html-C5X4urn_.js → html-Dk4wmfwC.js} +1 -1
- package/dist/web/assets/{html-derivative-nH-jXfsL.js → html-derivative-CJWqfN51.js} +1 -1
- package/dist/web/assets/{http-CQgaG7eu.js → http-CexhotTh.js} +1 -1
- package/dist/web/assets/{hurl-B8gidJhu.js → hurl-BScKFk3Z.js} +1 -1
- package/dist/web/assets/{hxml-BAK94GX8.js → hxml-DAle7vtu.js} +1 -1
- package/dist/web/assets/{image-preview-1kGj4W9R.js → image-preview-fuU4h2nr.js} +1 -1
- package/dist/web/assets/index-CHQAPZIX.js +52 -0
- package/dist/web/assets/index-CoaVwAF6.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BciNds6R.js +1 -0
- package/dist/web/assets/{jinja-BuAjvj8M.js → jinja-yHpyD80t.js} +1 -1
- package/dist/web/assets/{jira-watcher-section-rJVz40wR.js → jira-watcher-section-Bjldyhru.js} +1 -1
- package/dist/web/assets/{jison-Dhhs26-L.js → jison-BY6WLJVu.js} +1 -1
- package/dist/web/assets/{julia-gZavIyll.js → julia-Dd_iYQDT.js} +1 -1
- package/dist/web/assets/{just-DDHrFReO.js → just-BTuUKpxF.js} +1 -1
- package/dist/web/assets/keybindings-store-DBVXZKp8.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-Dv4e3M8h.js → keyboard-shortcuts-section-D7jaCPrv.js} +1 -1
- package/dist/web/assets/{latex-BdGZa8ov.js → latex-Dt3OUlMZ.js} +1 -1
- package/dist/web/assets/{liquid-urFfd9Fr.js → liquid-BxPJU2ix.js} +1 -1
- package/dist/web/assets/{lua-CcS6tnB5.js → lua-DEGIvn7d.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CEloCR06.js → markdown-renderer-B4B5oxQS.js} +3 -3
- package/dist/web/assets/markdown-renderer-DwHh-jd_.js +1 -0
- package/dist/web/assets/{marko-CSQqk4oG.js → marko-BDxwBJur.js} +1 -1
- package/dist/web/assets/{mdc-CYIe80m_.js → mdc-B8aXPTg7.js} +1 -1
- package/dist/web/assets/{media-url-DBmHJRft.js → media-url-BMeBaB0q.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-B3uDlD79.js → mobile-bottom-sheet-CmySDB6I.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-JJxZYWkV.js +1 -0
- package/dist/web/assets/{nextflow-C-W8vX8I.js → nextflow-DBVZnnT_.js} +1 -1
- package/dist/web/assets/{nginx-COGi5you.js → nginx-Crpyd-Do.js} +1 -1
- package/dist/web/assets/{nim-CPitYGel.js → nim-BSqTBYDj.js} +1 -1
- package/dist/web/assets/notification-store-CCWNUHsS.js +1 -0
- package/dist/web/assets/{notifications-settings-section-B2GkfB0l.js → notifications-settings-section-DbbX7-Qp.js} +1 -1
- package/dist/web/assets/{number-overlay-editor-C_eD-Bdr.js → number-overlay-editor-DRjTIcPH.js} +1 -1
- package/dist/web/assets/{overview-panel-Cm2HRtEz.js → overview-panel-DiiA3B_Z.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-ClD3l2Bs.js +1 -0
- package/dist/web/assets/{panel-store-BRrnDO_i.js → panel-store-BmdcSBNb.js} +1 -1
- package/dist/web/assets/{pdf-preview-Dmz8jkZZ.js → pdf-preview-DxNjWa1v.js} +1 -1
- package/dist/web/assets/{perl-B0yW3hbb.js → perl-BU0kbB-K.js} +1 -1
- package/dist/web/assets/{php-BNMYN5Jp.js → php-D9n1ikHN.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-PU6kD0rW.js +1 -0
- package/dist/web/assets/{postgres-viewer-BtZbfC3f.js → postgres-viewer-BScvk5ev.js} +3 -3
- package/dist/web/assets/{ppmbot-settings-section-OYfVG5-l.js → ppmbot-settings-section-jFA9Ncgo.js} +1 -1
- package/dist/web/assets/{process-table-DRu3NyO_.js → process-table-BexeYWQm.js} +1 -1
- package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-wWZ6bjwq.js} +1 -1
- package/dist/web/assets/{proxy-settings-section-xMbrcyr8.js → proxy-settings-section-C_seKq56.js} +1 -1
- package/dist/web/assets/{pug-CP5fxsxD.js → pug-BOpaczDr.js} +1 -1
- package/dist/web/assets/{qml-CHGpQosY.js → qml-B-cQXhrg.js} +1 -1
- package/dist/web/assets/{query-audit-section-CC5Mf6qp.js → query-audit-section-BWXJmPDV.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-4LzjL0Zt.js +1 -0
- package/dist/web/assets/{razor-Cfe0AYo4.js → razor-DFB0pNCh.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-B--PeL5i.js +2 -0
- package/dist/web/assets/{remote-desktop-mobile-view-B4BSHIxU.js → remote-desktop-mobile-view-jgn8nmQa.js} +1 -1
- package/dist/web/assets/{remote-desktop-readiness-gate-C0d0PSX4.js → remote-desktop-readiness-gate-CjDth-W_.js} +1 -1
- package/dist/web/assets/{remote-desktop-window-content-DjeCZzqq.js → remote-desktop-window-content-CrHAUA03.js} +1 -1
- package/dist/web/assets/{rst-DxSSGshb.js → rst-DAXedsgm.js} +1 -1
- package/dist/web/assets/{ruby-rbK5SsJa.js → ruby-BCO4polC.js} +1 -1
- package/dist/web/assets/{run-in-terminal-DHAvC1AG.js → run-in-terminal-wHKwAnWP.js} +1 -1
- package/dist/web/assets/{sas-DDM28fbb.js → sas-DfB5dcFC.js} +1 -1
- package/dist/web/assets/schedules-settings-section-DMFn3mYL.js +1 -0
- package/dist/web/assets/{scss-TDl-6ro4.js → scss-BknIVgn7.js} +1 -1
- package/dist/web/assets/{send-to-chat-BOyUWNKw.js → send-to-chat-BI7-fFpG.js} +1 -1
- package/dist/web/assets/{separator-BzGoTlR7.js → separator-W_hJcXPk.js} +1 -1
- package/dist/web/assets/{settings-body-CXTQwhbM.js → settings-body-Dz2vPTEP.js} +2 -2
- package/dist/web/assets/settings-store-CzLnTR8p.js +2 -0
- package/dist/web/assets/settings-tab-B5QbsvS4.js +1 -0
- package/dist/web/assets/settings-window-content-iM2bPoX5.js +1 -0
- package/dist/web/assets/{shaderlab-CCvmDgGS.js → shaderlab-CPu31TpY.js} +1 -1
- package/dist/web/assets/{shellsession-BzKS9yKh.js → shellsession-CFX4c_TM.js} +1 -1
- package/dist/web/assets/{soy-QfO35nDh.js → soy-rz9hilgm.js} +1 -1
- package/dist/web/assets/{sparql-D29RDfp2.js → sparql-Bo8ZDeqE.js} +1 -1
- package/dist/web/assets/{sql-query-editor-B-VwfyTf.js → sql-query-editor-C0mGgLfX.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-CpUhDEhN.js → sqlite-viewer-Bs3RKf1X.js} +1 -1
- package/dist/web/assets/{stata-Be6j_SNv.js → stata-B5J-QMyE.js} +1 -1
- package/dist/web/assets/{streaming-store-9_9j70zt.js → streaming-store-9XVmAMfV.js} +1 -1
- package/dist/web/assets/{surrealql-CykFjgNG.js → surrealql-Caw1ADnG.js} +1 -1
- package/dist/web/assets/{svelte-CdBF8z4-.js → svelte-D3oMVcxz.js} +1 -1
- package/dist/web/assets/{system-monitor-body-DzWXd1wM.js → system-monitor-body-DvUmEdNe.js} +2 -2
- package/dist/web/assets/system-monitor-tab-CHZ5F_ZE.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-DIIP3dff.js → system-monitor-window-content-DjzqoeCN.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-CLdcz84K.js → tab-host-window-content-DKeAr5mD.js} +1 -1
- package/dist/web/assets/{tab-store-PZsP5ZwG.js → tab-store-1xYJb0gM.js} +1 -1
- package/dist/web/assets/team-member-sheet-Cl9ZB3dl.js +2 -0
- package/dist/web/assets/team-member-window-content-DfP7WdFD.js +1 -0
- package/dist/web/assets/{templ-BVHE2CNb.js → templ-BAdNyIOL.js} +1 -1
- package/dist/web/assets/{terminal-tab-BeKLpaPM.js → terminal-tab-J1Fqa_rm.js} +2 -2
- package/dist/web/assets/{tex-C8XwrbBw.js → tex-IQ8mELOR.js} +1 -1
- package/dist/web/assets/tool-cards-DQghSuKX.js +9 -0
- package/dist/web/assets/treemap-KZPCXAKY-DiumLNMg.js +1 -0
- package/dist/web/assets/{ts-tags-BIHk5AM0.js → ts-tags-D1DDO2jl.js} +1 -1
- package/dist/web/assets/{twig-YcE6Q2Ox.js → twig-C8Fd5RvV.js} +1 -1
- package/dist/web/assets/use-accounts-data-KWcMgRlc.js +1 -0
- package/dist/web/assets/{use-blob-url-De-ZtcpX.js → use-blob-url-CrwoZJUJ.js} +1 -1
- package/dist/web/assets/use-inline-blame-C1G-_NjC.js +8 -0
- package/dist/web/assets/{use-monaco-theme-Kj2OGDlm.js → use-monaco-theme-aa4-wjKQ.js} +1 -1
- package/dist/web/assets/{use-open-team-member-DWhhWdmJ.js → use-open-team-member-CSUU7Z8N.js} +1 -1
- package/dist/web/assets/{use-remote-desktop-display-choice-8AkPpM_0.js → use-remote-desktop-display-choice-B-TpQ-Ua.js} +1 -1
- package/dist/web/assets/{use-websocket-kR1omKO1.js → use-websocket-BBslmv3D.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-CKKtYuTV.js → vendor-mermaid-CfY1-PkA.js} +1 -1
- package/dist/web/assets/{video-preview-BdV7LjbZ.js → video-preview-Bd0lcK3d.js} +1 -1
- package/dist/web/assets/{vue-q7m01V0h.js → vue-EvccXlkP.js} +1 -1
- package/dist/web/assets/{vue-html-D1y9feOj.js → vue-html-Dg0xUJCh.js} +1 -1
- package/dist/web/assets/{vue-vine-DrIGs03v.js → vue-vine-BdrMg72k.js} +1 -1
- package/dist/web/assets/{window-pip-registry-CSLhJfpL.js → window-pip-registry-CrCYYTTf.js} +1 -1
- package/dist/web/assets/{window-store-DGKf9rPG.js → window-store-D0HbQ1pM.js} +1 -1
- package/dist/web/assets/ws-auth-D111jj32.js +1 -0
- package/dist/web/assets/{xml-oyvc1920.js → xml-MmDclzxK.js} +1 -1
- package/dist/web/assets/{xsl-BbIGdNPF.js → xsl-DQEFs6Dd.js} +1 -1
- package/dist/web/index.html +23 -23
- package/dist/web/sw.js +1 -1
- package/docs/extension-development-guide.md +47 -0
- package/docs/project-roadmap.md +3 -1
- package/package.json +2 -2
- package/packages/ext-git-graph/package.json +23 -5
- package/packages/ext-git-graph/src/blame-parser.test.ts +154 -0
- package/packages/ext-git-graph/src/blame-parser.ts +148 -0
- package/packages/ext-git-graph/src/blame-view.ts +389 -0
- package/packages/ext-git-graph/src/commit-search.test.ts +118 -0
- package/packages/ext-git-graph/src/commit-search.ts +102 -0
- package/packages/ext-git-graph/src/compare-args.test.ts +111 -0
- package/packages/ext-git-graph/src/compare-args.ts +109 -0
- package/packages/ext-git-graph/src/compare-view.ts +343 -0
- package/packages/ext-git-graph/src/extension-integration.test.ts +6 -3
- package/packages/ext-git-graph/src/extension.ts +202 -249
- package/packages/ext-git-graph/src/file-history-parser.test.ts +102 -0
- package/packages/ext-git-graph/src/file-history-parser.ts +89 -0
- package/packages/ext-git-graph/src/file-history-view.ts +386 -0
- package/packages/ext-git-graph/src/git-exec.ts +122 -0
- package/packages/ext-git-graph/src/git-state.ts +81 -0
- package/packages/ext-git-graph/src/panel-nav.ts +65 -0
- package/packages/ext-git-graph/src/panel-registry.ts +72 -0
- package/packages/ext-git-graph/src/ppm-api.ts +48 -0
- package/packages/ext-git-graph/src/rebase-todo.test.ts +196 -0
- package/packages/ext-git-graph/src/rebase-todo.ts +119 -0
- package/packages/ext-git-graph/src/rebase-view.ts +483 -0
- package/packages/ext-git-graph/src/reflog-parser.test.ts +93 -0
- package/packages/ext-git-graph/src/reflog-parser.ts +86 -0
- package/packages/ext-git-graph/src/reflog-view.ts +375 -0
- package/packages/ext-git-graph/src/register-view-command.ts +47 -0
- package/packages/ext-git-graph/src/submodule-parser.test.ts +79 -0
- package/packages/ext-git-graph/src/submodule-parser.ts +77 -0
- package/packages/ext-git-graph/src/types.ts +10 -1
- package/packages/ext-git-graph/src/webview-html.ts +438 -3
- package/packages/ext-git-graph/src/webview-shell.ts +236 -0
- package/src/providers/claude-agent-sdk.ts +7 -0
- package/src/providers/codex-app-server/codex-provider.ts +89 -1
- package/src/server/index.ts +1 -1
- package/src/server/routes/accounts.ts +25 -1
- package/src/server/routes/chat.ts +29 -1
- package/src/server/routes/codex-accounts.ts +41 -1
- package/src/server/routes/git.ts +97 -0
- package/src/server/routes/settings.ts +1 -0
- package/src/server/routes/teams.ts +14 -3
- package/src/services/account-selector.service.ts +125 -20
- package/src/services/account.service.ts +176 -54
- package/src/services/autostart-generator.ts +12 -1
- package/src/services/codex-account.service.ts +87 -13
- package/src/services/db.service.ts +58 -3
- package/src/services/extension.service.ts +55 -5
- package/src/services/git-blame/blame-porcelain.ts +89 -0
- package/src/services/git-blame/git-blame.service.ts +90 -0
- package/src/services/git-hunks/git-hunks.service.ts +264 -0
- package/src/services/git-hunks/unified-diff.ts +359 -0
- package/src/services/picked-account-binding.ts +57 -0
- package/src/services/team-member-activity/member-transcript-tail.ts +64 -0
- package/src/shared/blame.ts +79 -0
- package/src/web/components/chat/chat-history-bar.tsx +34 -5
- package/src/web/components/chat/chat-tab.tsx +120 -3
- package/src/web/components/chat/codex-usage-panel.tsx +132 -14
- package/src/web/components/chat/team-member-window-content.tsx +8 -39
- package/src/web/components/chat/usage-badge.tsx +176 -52
- package/src/web/components/editor/code-editor.tsx +33 -1
- package/src/web/components/editor/diff-viewer.tsx +83 -8
- package/src/web/components/editor/editor-toolbar.tsx +25 -1
- package/src/web/components/git/git-status-panel.tsx +46 -0
- package/src/web/components/git/hunk-selection.ts +132 -0
- package/src/web/components/git/hunk-stage-dialog.tsx +419 -0
- package/src/web/components/settings/accounts/account-bucket-row.tsx +19 -3
- package/src/web/components/settings/accounts/account-card.tsx +153 -33
- package/src/web/components/settings/accounts/account-hint.tsx +33 -0
- package/src/web/components/settings/accounts/account-usage-format.ts +26 -0
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +24 -9
- package/src/web/components/settings/accounts/codex-usage-rows.tsx +2 -2
- package/src/web/components/settings/accounts/use-accounts-data.ts +32 -2
- package/src/web/components/settings/accounts/use-codex-accounts.ts +24 -2
- package/src/web/hooks/use-chat.ts +27 -0
- package/src/web/hooks/use-inline-blame.ts +175 -0
- package/src/web/hooks/use-member-transcript-tail.ts +95 -0
- package/src/web/lib/api-settings.ts +24 -0
- package/src/web/stores/settings-store.ts +12 -0
- package/dist/web/assets/accounts-settings-section-Cd1Nh04o.js +0 -1
- package/dist/web/assets/ai-settings-section-OaYGT5OF.js +0 -1
- package/dist/web/assets/api-client-BogqsFna.js +0 -1
- package/dist/web/assets/api-settings-BVfemzZW.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-BZBxtErN.js +0 -1
- package/dist/web/assets/chat-tab-Cc1W2pTu.js +0 -15
- package/dist/web/assets/code-editor-gLRyCPWE.js +0 -7
- package/dist/web/assets/diff-viewer-ix1Ko0xk.js +0 -4
- package/dist/web/assets/explorer-window-content-B7HmteDI.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BHxJbXfV.js +0 -1
- package/dist/web/assets/group-chat-tab-DmZ1vLk-.js +0 -1
- package/dist/web/assets/index-N-pnuwFP.css +0 -2
- package/dist/web/assets/index-yV5RgBDh.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-DI4p3vbH.js +0 -1
- package/dist/web/assets/keybindings-store-88_Zkn5F.js +0 -1
- package/dist/web/assets/markdown-renderer-UhszMWSn.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-D9cqcU3X.js +0 -1
- package/dist/web/assets/notification-store-B6X6RYqQ.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-YNQWTi_-.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-E3Wiw76n.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-BPQJUKON.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-DefltAPl.js +0 -2
- package/dist/web/assets/schedules-settings-section-D76Ob6tI.js +0 -1
- package/dist/web/assets/settings-store-DWUx3865.js +0 -2
- package/dist/web/assets/settings-tab-DToHQXfC.js +0 -1
- package/dist/web/assets/settings-window-content-B2rlq-Dv.js +0 -1
- package/dist/web/assets/system-monitor-tab-CuDpwAZU.js +0 -1
- package/dist/web/assets/team-member-sheet-DLrT_Kxo.js +0 -2
- package/dist/web/assets/team-member-window-content-S8cdLcg3.js +0 -1
- package/dist/web/assets/tool-cards-CLxrQlUs.js +0 -9
- package/dist/web/assets/treemap-KZPCXAKY-C2CvKBQR.js +0 -1
- package/dist/web/assets/use-accounts-data-snYPJED_.js +0 -1
- package/dist/web/assets/ws-auth-ZSZkAT6p.js +0 -1
- /package/dist/web/assets/{abap-Cq9nYQyd.js → abap-D-o-bvtI.js} +0 -0
- /package/dist/web/assets/{actionscript-3-DL8rUIy0.js → actionscript-3-D2tIzAm8.js} +0 -0
- /package/dist/web/assets/{ada-BY8TLuhY.js → ada-DxatBcxS.js} +0 -0
- /package/dist/web/assets/{andromeeda-CyVfHsS0.js → andromeeda-D0LK9YJ3.js} +0 -0
- /package/dist/web/assets/{apache-DTQUy73x.js → apache-B7uMPNiW.js} +0 -0
- /package/dist/web/assets/{apex-CiSZngrZ.js → apex-B7GiXrAr.js} +0 -0
- /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-CEYCBt_0.js} +0 -0
- /package/dist/web/assets/{applescript-CzuRDd3h.js → applescript-D_SIFkKe.js} +0 -0
- /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-BVXAUYoN.js} +0 -0
- /package/dist/web/assets/{ara-CSFV93kR.js → ara-C3iECgdg.js} +0 -0
- /package/dist/web/assets/{asciidoc-hOQNnXhz.js → asciidoc-gc8z_MMY.js} +0 -0
- /package/dist/web/assets/{asm-DdG53NGR.js → asm-27qeoPOI.js} +0 -0
- /package/dist/web/assets/{aurora-x-DJ7ORZG6.js → aurora-x-C5J9fWe1.js} +0 -0
- /package/dist/web/assets/{awk-B3FFpX_J.js → awk-B7GBzF92.js} +0 -0
- /package/dist/web/assets/{ayu-dark-D_P1CfTe.js → ayu-dark-XlnQhuLL.js} +0 -0
- /package/dist/web/assets/{ayu-light-Cz3YLUqH.js → ayu-light-DKLLilkU.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-JcMq5tgJ.js → ayu-mirage-Dv-XOprJ.js} +0 -0
- /package/dist/web/assets/{ballerina-7Hs4Rfpk.js → ballerina-DACRT-sT.js} +0 -0
- /package/dist/web/assets/{bat-COgQdnFA.js → bat-BlQCq2XE.js} +0 -0
- /package/dist/web/assets/{beancount-BJ3CutBG.js → beancount-DYDztRI0.js} +0 -0
- /package/dist/web/assets/{berry-D7so2Cui.js → berry-BE5YKqvx.js} +0 -0
- /package/dist/web/assets/{bibtex-Ct3acHa1.js → bibtex-CAm7mgEm.js} +0 -0
- /package/dist/web/assets/{bicep-jXDwCQcm.js → bicep-BKgAbfVa.js} +0 -0
- /package/dist/web/assets/{bird2-CjLD2cna.js → bird2-yrBNbW1G.js} +0 -0
- /package/dist/web/assets/{c-CdgZojnL.js → c-txZFr4RM.js} +0 -0
- /package/dist/web/assets/{c3-DtpZYego.js → c3-D_DrzBUd.js} +0 -0
- /package/dist/web/assets/{cadence-CPLkixoh.js → cadence-CUFCEgG9.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-BntPywsy.js → can-open-in-ppm-_yqa69dV.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Xt0_qJa9.js → catppuccin-frappe-Dn32MuhA.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CJnbLec5.js → catppuccin-latte-Dewo7yDf.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-BPHP1wYB.js → catppuccin-macchiato-D8WOWZeo.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-ssWJodGc.js → catppuccin-mocha-b0md48XM.js} +0 -0
- /package/dist/web/assets/{clarity-DB0408F6.js → clarity-Ct3UqLBj.js} +0 -0
- /package/dist/web/assets/{clojure-dpRPlAO4.js → clojure-CwP5aPEo.js} +0 -0
- /package/dist/web/assets/{cmake-r2VkeX9y.js → cmake-D5RF49e5.js} +0 -0
- /package/dist/web/assets/{codeowners-v5nQqHiB.js → codeowners-Cl5897L_.js} +0 -0
- /package/dist/web/assets/{codeql-BSSEhKRR.js → codeql-BKh1RCg0.js} +0 -0
- /package/dist/web/assets/{common-lisp-BXb2MMMA.js → common-lisp-BhHuD6t0.js} +0 -0
- /package/dist/web/assets/{coq-BjBNi73X.js → coq-Dco3qwD8.js} +0 -0
- /package/dist/web/assets/{csharp-CdPjZRbV.js → csharp-Dpjw9ZA9.js} +0 -0
- /package/dist/web/assets/{css-BuxbQOHL.js → css-CPe4cabH.js} +0 -0
- /package/dist/web/assets/{csv-Dj-eoZwH.js → csv-BAcTN8Kt.js} +0 -0
- /package/dist/web/assets/{csv-parser-CBbALsFU.js → csv-parser-DbxGKdMG.js} +0 -0
- /package/dist/web/assets/{cue-Dz73VksN.js → cue-Cr6y60TA.js} +0 -0
- /package/dist/web/assets/{cypher-CkVkDywZ.js → cypher-DWtqXXFP.js} +0 -0
- /package/dist/web/assets/{d-oU51Pbpf.js → d-BASiTZk4.js} +0 -0
- /package/dist/web/assets/{dark-plus-DcwvjjfO.js → dark-plus-56kmnrcl.js} +0 -0
- /package/dist/web/assets/{dart-DGvu8mcX.js → dart-C5mZnjoE.js} +0 -0
- /package/dist/web/assets/{data-grid-types-B6kUrF-M.js → data-grid-types-DeY60CLU.js} +0 -0
- /package/dist/web/assets/{dax-BmbjKQ90.js → dax-B4I2e7d8.js} +0 -0
- /package/dist/web/assets/{desktop-IRBSiFfh.js → desktop-DekGg0BT.js} +0 -0
- /package/dist/web/assets/{diff-Bdik2JR8.js → diff-CWnJBd73.js} +0 -0
- /package/dist/web/assets/{dist-Bv4zQpS1.js → dist-B0J2eYBz.js} +0 -0
- /package/dist/web/assets/{dist-C8YPBIOb.js → dist-BKOM_AoT.js} +0 -0
- /package/dist/web/assets/{docker-Bgdl0H8I.js → docker-DevchDxK.js} +0 -0
- /package/dist/web/assets/{dotenv-COe5NdnA.js → dotenv-DCvtdaGJ.js} +0 -0
- /package/dist/web/assets/{dracula-DjURaSAa.js → dracula-8U6Vkk3l.js} +0 -0
- /package/dist/web/assets/{dracula-soft-Do7d0bo-.js → dracula-soft-Bfeew5Wg.js} +0 -0
- /package/dist/web/assets/{dream-maker-DYwQvJIj.js → dream-maker-DDTJVuoy.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-BXjlHyDW.js → emacs-lisp-CEHJqI4m.js} +0 -0
- /package/dist/web/assets/{everforest-dark-h8Q2loiX.js → everforest-dark-CHHLt7cq.js} +0 -0
- /package/dist/web/assets/{everforest-light-CdsJiL-9.js → everforest-light-TcTbK1sM.js} +0 -0
- /package/dist/web/assets/{fennel-D0CBtOvY.js → fennel-Bh4vsA1A.js} +0 -0
- /package/dist/web/assets/{fish-DzayfrMj.js → fish-D0T0fILG.js} +0 -0
- /package/dist/web/assets/{fluent-CatjkUfx.js → fluent-CgXDXdyy.js} +0 -0
- /package/dist/web/assets/{format-bytes-BulRSULX.js → format-bytes-Bc6SqF7M.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-DM04ud0b.js → fortran-free-form-CUg3Xhz4.js} +0 -0
- /package/dist/web/assets/{gdscript-BLN5iZwf.js → gdscript-Cx056p_k.js} +0 -0
- /package/dist/web/assets/{gdshader-BK-1iZQs.js → gdshader-Dj1qP4I5.js} +0 -0
- /package/dist/web/assets/{genie-BGPOFWDN.js → genie-SBt15wn7.js} +0 -0
- /package/dist/web/assets/{gherkin-CdVOJ_JY.js → gherkin-bTJiHIrB.js} +0 -0
- /package/dist/web/assets/{github-dark-B_xW8TaY.js → github-dark-BOPhyugs.js} +0 -0
- /package/dist/web/assets/{github-dark-default-h_iKq7CX.js → github-dark-default-Cktqe6NF.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-nKDYE62T.js → github-dark-dimmed-CeWfII_B.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-l8I771QT.js → github-dark-high-contrast-CTx8LpdV.js} +0 -0
- /package/dist/web/assets/{github-light-DNhjkg8Z.js → github-light-DVsO5Un1.js} +0 -0
- /package/dist/web/assets/{github-light-default-DQ06uS3g.js → github-light-default-BiyPxicr.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-qc2sZCVN.js → github-light-high-contrast-BPlnke0a.js} +0 -0
- /package/dist/web/assets/{gleam-DPPatkNH.js → gleam-CWSxvD0C.js} +0 -0
- /package/dist/web/assets/{gn-BnzGQTy5.js → gn-D7NUVJuv.js} +0 -0
- /package/dist/web/assets/{gnuplot-Ct1leoo3.js → gnuplot-c5uXmJKc.js} +0 -0
- /package/dist/web/assets/{go-qysCdbrn.js → go-CRsHi4Cw.js} +0 -0
- /package/dist/web/assets/{groovy-BkWIdrRQ.js → groovy-DpV0RA5r.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-L6Iiybbl.js → gruvbox-dark-hard-DD9fguUN.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-DbD3yl0b.js → gruvbox-dark-medium-BvcwpYJ-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-Ban2cHK3.js → gruvbox-dark-soft-48dCxX9M.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-CK_SjS3e.js → gruvbox-light-hard-CjJ994Ip.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-Bjgvhc2w.js → gruvbox-light-medium-DQJQZ_rV.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-EWnyYGUu.js → gruvbox-light-soft-AqsC9mKw.js} +0 -0
- /package/dist/web/assets/{haskell-rT1N9nYr.js → haskell-B5V7yvmh.js} +0 -0
- /package/dist/web/assets/{haxe-Bbc91axo.js → haxe-LWTKfPKr.js} +0 -0
- /package/dist/web/assets/{hcl-D-3qJA4v.js → hcl-QmNZdAtI.js} +0 -0
- /package/dist/web/assets/{hjson-DWtewcyQ.js → hjson-CboJsAzK.js} +0 -0
- /package/dist/web/assets/{hlsl-C7t6hCrO.js → hlsl-Bd-DO_WL.js} +0 -0
- /package/dist/web/assets/{horizon-zJAGUQ3j.js → horizon-CU8Bi_ss.js} +0 -0
- /package/dist/web/assets/{horizon-bright-CRNyrJy8.js → horizon-bright-iSQ3_3jP.js} +0 -0
- /package/dist/web/assets/{houston-BFKa4W1-.js → houston-CTXv3Puz.js} +0 -0
- /package/dist/web/assets/{hy-W1hNjMBM.js → hy-Cd4hTidD.js} +0 -0
- /package/dist/web/assets/{image-gallery-BycaVWOu.js → image-gallery-J8h8IY0c.js} +0 -0
- /package/dist/web/assets/{imba-CPphaV-V.js → imba-BipCHEY2.js} +0 -0
- /package/dist/web/assets/{ini-CayaZ4vW.js → ini-Dd1KkMrB.js} +0 -0
- /package/dist/web/assets/{java-BPAE_9Ej.js → java-rGr0OQD8.js} +0 -0
- /package/dist/web/assets/{javascript-C5SKIVqe.js → javascript-DHhCH2TN.js} +0 -0
- /package/dist/web/assets/{json-C-0EUYmu.js → json-CMMLBCUf.js} +0 -0
- /package/dist/web/assets/{json5-BqVetEZL.js → json5-mPOwTOsB.js} +0 -0
- /package/dist/web/assets/{jsonc-DnjIzkUB.js → jsonc-Bie0E6D3.js} +0 -0
- /package/dist/web/assets/{jsonl-rbV-3EqO.js → jsonl-CpqHmtC1.js} +0 -0
- /package/dist/web/assets/{jsonnet-9hvXw4qr.js → jsonnet-D21Ms51e.js} +0 -0
- /package/dist/web/assets/{jssm-1ifXyIhu.js → jssm-BXnl1nur.js} +0 -0
- /package/dist/web/assets/{jsx-hPumywTY.js → jsx-Chnii_vB.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BCPjmoMO.js → kanagawa-dragon-DhxR0UjL.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-DN2VNjy7.js → kanagawa-lotus-fupRg5wp.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-B8RZEsgc.js → kanagawa-wave-DSWbRS9M.js} +0 -0
- /package/dist/web/assets/{kdl-CkvjWQIa.js → kdl-DirGHqUy.js} +0 -0
- /package/dist/web/assets/{kotlin-D8cnRi5o.js → kotlin-DkhUVMDC.js} +0 -0
- /package/dist/web/assets/{kusto-CmeeBEJG.js → kusto-DU7OpovF.js} +0 -0
- /package/dist/web/assets/{laserwave--rtV8Mv9.js → laserwave-iLKb1ZCT.js} +0 -0
- /package/dist/web/assets/{lean-CS-Pyi42.js → lean-Cuvh_QAP.js} +0 -0
- /package/dist/web/assets/{less-Ddi_7qGm.js → less-B5tQZOdI.js} +0 -0
- /package/dist/web/assets/{lib-DIrxSyLb.js → lib-el9rA30D.js} +0 -0
- /package/dist/web/assets/{libesm-CYLrAeW3.js → libesm-CdttTIAd.js} +0 -0
- /package/dist/web/assets/{light-plus-CrEjpkkX.js → light-plus-DiHU2KL5.js} +0 -0
- /package/dist/web/assets/{llvm-Cm7ZXmAO.js → llvm-sCBTKCKN.js} +0 -0
- /package/dist/web/assets/{log-lW2sYxtp.js → log-DtyR34Ie.js} +0 -0
- /package/dist/web/assets/{logo-B-PiRb53.js → logo-CP91aQyG.js} +0 -0
- /package/dist/web/assets/{luau-8St0pC-R.js → luau-nfGo2ulP.js} +0 -0
- /package/dist/web/assets/{make-DbNdhH54.js → make-XbDn9GHe.js} +0 -0
- /package/dist/web/assets/{markdown-CYocYSGd.js → markdown-BB1_3xLr.js} +0 -0
- /package/dist/web/assets/{material-theme-CVBF1Cbi.js → material-theme-CIx_sxAy.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-HFrlNZD8.js → material-theme-darker-DsQeueDD.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BM-uz2ft.js → material-theme-lighter-x_lMi74j.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-PbvUez8m.js → material-theme-ocean-Bkgh25P8.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-B22Ck6qd.js → material-theme-palenight-CoOjdbti.js} +0 -0
- /package/dist/web/assets/{matlab-GFFNzucw.js → matlab-D6xZas_8.js} +0 -0
- /package/dist/web/assets/{mdx-BVnJzMDx.js → mdx-Cl5_YNoz.js} +0 -0
- /package/dist/web/assets/{mermaid-BlCmSTCM.js → mermaid-8Eow4jz_.js} +0 -0
- /package/dist/web/assets/{min-dark-B0PwlAfC.js → min-dark-D4DhjgRE.js} +0 -0
- /package/dist/web/assets/{min-light-C_H2ABKu.js → min-light-DzAckd0t.js} +0 -0
- /package/dist/web/assets/{mipsasm-C3rf2fo6.js → mipsasm-zI_zu0tj.js} +0 -0
- /package/dist/web/assets/{mojo-CDRuzM3o.js → mojo-DQnGGPHf.js} +0 -0
- /package/dist/web/assets/{monokai-KiebNy_Y.js → monokai-CrFoD2VD.js} +0 -0
- /package/dist/web/assets/{moonbit-CdVUHoWl.js → moonbit-DdphIGs6.js} +0 -0
- /package/dist/web/assets/{move-Dq9iKZRY.js → move-BUlkn0xW.js} +0 -0
- /package/dist/web/assets/{narrat-7y5L5sGM.js → narrat-CbOBdtCn.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-Cwcbkcrx.js → nextflow-groovy-CeVLUTuQ.js} +0 -0
- /package/dist/web/assets/{night-owl-TBgLPzj4.js → night-owl-Bm4LhUR1.js} +0 -0
- /package/dist/web/assets/{night-owl-light-Dehb9My2.js → night-owl-light-DtcBiMEf.js} +0 -0
- /package/dist/web/assets/{nix-BKj-gzoX.js → nix-DrdwQ70g.js} +0 -0
- /package/dist/web/assets/{nord-BF_r8PeX.js → nord-B0Head1v.js} +0 -0
- /package/dist/web/assets/{nushell-CeAjk9_E.js → nushell-CmUdltKn.js} +0 -0
- /package/dist/web/assets/{objective-c-Xl1OMcDp.js → objective-c-BGDR33XN.js} +0 -0
- /package/dist/web/assets/{objective-cpp-C4ZgzrRq.js → objective-cpp-Buc1Zhd0.js} +0 -0
- /package/dist/web/assets/{ocaml-CGj8IYTq.js → ocaml-xdzl9rRD.js} +0 -0
- /package/dist/web/assets/{odin-C4veqvW2.js → odin-DiUFngnd.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-gAcWejsV.js → one-dark-pro-BURxmIp3.js} +0 -0
- /package/dist/web/assets/{one-light-B7E-UUXy.js → one-light-BMRMInAj.js} +0 -0
- /package/dist/web/assets/{openscad-C-wN9BEv.js → openscad-DAgLA5J3.js} +0 -0
- /package/dist/web/assets/{pascal-C8WBtxMs.js → pascal-CoDZxcRB.js} +0 -0
- /package/dist/web/assets/{pkl-BBfF4G2A.js → pkl-DzKM6BuM.js} +0 -0
- /package/dist/web/assets/{plastic-DFOAb89P.js → plastic-vF15LmNS.js} +0 -0
- /package/dist/web/assets/{plsql-BS2CEMMV.js → plsql-C1v07teA.js} +0 -0
- /package/dist/web/assets/{po-BKJDtPmy.js → po-CLLC8BuA.js} +0 -0
- /package/dist/web/assets/{poimandres-Cg3hL13e.js → poimandres-Uq0xysxM.js} +0 -0
- /package/dist/web/assets/{polar-B9ebiXUU.js → polar-2-luY1Pc.js} +0 -0
- /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DKymYOY-.js} +0 -0
- /package/dist/web/assets/{postcss-B5REwNY_.js → postcss-lbFhe0rz.js} +0 -0
- /package/dist/web/assets/{powerquery-stc0Gzkf.js → powerquery-RL8O9Wie.js} +0 -0
- /package/dist/web/assets/{powershell-jgd0ahJN.js → powershell-upFWhb-b.js} +0 -0
- /package/dist/web/assets/{prisma-CWuZjRhI.js → prisma-DBtYaWFk.js} +0 -0
- /package/dist/web/assets/{prolog-D5zNFE63.js → prolog-D8jSMykJ.js} +0 -0
- /package/dist/web/assets/{proto-B-6RrU7n.js → proto-BM4eadF7.js} +0 -0
- /package/dist/web/assets/{puppet-DONP8TDR.js → puppet-BQDQ3Snu.js} +0 -0
- /package/dist/web/assets/{purescript-DDJITf-A.js → purescript-Cj2aTX4s.js} +0 -0
- /package/dist/web/assets/{python-bmWy9j1-.js → python-BYpqq6u4.js} +0 -0
- /package/dist/web/assets/{qmldir-CKDwMt7X.js → qmldir-4uiTcwAu.js} +0 -0
- /package/dist/web/assets/{qss-DptQQzBn.js → qss-D5xB-Mqo.js} +0 -0
- /package/dist/web/assets/{r-BorKpJ8r.js → r-P6Ujy8rf.js} +0 -0
- /package/dist/web/assets/{racket-BaHpYVsZ.js → racket-COKgCFuM.js} +0 -0
- /package/dist/web/assets/{raku-DFJIV4k_.js → raku-z14Q9i7K.js} +0 -0
- /package/dist/web/assets/{react-X4jUntEf.js → react-D1Yg256j.js} +0 -0
- /package/dist/web/assets/{red-70oJGrkb.js → red-ObAvgvQ5.js} +0 -0
- /package/dist/web/assets/{reg-C4g235Dv.js → reg-Qv4f0JFA.js} +0 -0
- /package/dist/web/assets/{regexp-Cw1UVmti.js → regexp-CEOVGIzy.js} +0 -0
- /package/dist/web/assets/{rel-DeLhIz5L.js → rel-cWOyTUwd.js} +0 -0
- /package/dist/web/assets/{resolve-theme-b_yGlUFQ.js → resolve-theme-CMp5xxCW.js} +0 -0
- /package/dist/web/assets/{riscv-ChnTiBRK.js → riscv-BZDwx-lv.js} +0 -0
- /package/dist/web/assets/{ron-C5IWeqDn.js → ron-BpjosPS0.js} +0 -0
- /package/dist/web/assets/{rose-pine-DCR0ohYW.js → rose-pine-DhxfJWcm.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-DvGZs1PR.js → rose-pine-dawn-BbGt9Wy_.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-BWACHjeS.js → rose-pine-moon-dj4Zxgik.js} +0 -0
- /package/dist/web/assets/{rosmsg-CQlIVazE.js → rosmsg-EciOaHo2.js} +0 -0
- /package/dist/web/assets/{rust-DGGVzWI8.js → rust-C_eH59O_.js} +0 -0
- /package/dist/web/assets/{sass-bj1C0rFI.js → sass-Dos3R5lL.js} +0 -0
- /package/dist/web/assets/{scala-MU1tLKDi.js → scala-8lVKsTvf.js} +0 -0
- /package/dist/web/assets/{scheme-CeGXtv7t.js → scheme-Bd3VdaZj.js} +0 -0
- /package/dist/web/assets/{sdbl-D0HLSGKp.js → sdbl-C4Yrjlgh.js} +0 -0
- /package/dist/web/assets/{shellscript-DkYiDvh1.js → shellscript-OFZbPKZH.js} +0 -0
- /package/dist/web/assets/{slack-dark-YqeHiGWH.js → slack-dark-aL6vlxZs.js} +0 -0
- /package/dist/web/assets/{slack-ochin-9Oe0DljJ.js → slack-ochin-BKt1E7sL.js} +0 -0
- /package/dist/web/assets/{smalltalk-CaaxedR7.js → smalltalk-D6qVBL1U.js} +0 -0
- /package/dist/web/assets/{snazzy-light-C8s2FHNC.js → snazzy-light-BrWerT0t.js} +0 -0
- /package/dist/web/assets/{solarized-dark-0ryY6oN3.js → solarized-dark-CzxTJgVQ.js} +0 -0
- /package/dist/web/assets/{solarized-light-bltgZeNh.js → solarized-light-BecaDAnG.js} +0 -0
- /package/dist/web/assets/{solidity-DOf5HmLZ.js → solidity-C9t7pZ84.js} +0 -0
- /package/dist/web/assets/{splunk-D1u4WHDW.js → splunk-CtxfFKtF.js} +0 -0
- /package/dist/web/assets/{sql-G8uQZljX.js → sql-7FE_Duz0.js} +0 -0
- /package/dist/web/assets/{ssh-config-rbXvly7Q.js → ssh-config-3Fr37ACC.js} +0 -0
- /package/dist/web/assets/{stylus-Cii1iADy.js → stylus-BP4mE1sO.js} +0 -0
- /package/dist/web/assets/{swift-DpIgonN4.js → swift-0uqTFouD.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DjGLzU2b.js → synthwave-84-DQaNM3DI.js} +0 -0
- /package/dist/web/assets/{system-verilog-_Z0M0Hs1.js → system-verilog-DUIVrZHZ.js} +0 -0
- /package/dist/web/assets/{systemd-BhyDCh9P.js → systemd-CtNo_WIh.js} +0 -0
- /package/dist/web/assets/{talonscript-CNchgCRD.js → talonscript-B0HXaCVT.js} +0 -0
- /package/dist/web/assets/{tasl-m--W7Pyr.js → tasl-Cb7RAl11.js} +0 -0
- /package/dist/web/assets/{tcl-gY-M_2BC.js → tcl-B79aC6l5.js} +0 -0
- /package/dist/web/assets/{terraform-CB6CzMwo.js → terraform-75jJDjpW.js} +0 -0
- /package/dist/web/assets/{tokyo-night-COh9oRYi.js → tokyo-night-Cz9Uh9qb.js} +0 -0
- /package/dist/web/assets/{toml-Bg2bsY-v.js → toml-CD6y9A-z.js} +0 -0
- /package/dist/web/assets/{tsv-DceGDiJC.js → tsv-CXJMbBrb.js} +0 -0
- /package/dist/web/assets/{tsx-EccDYa7i.js → tsx-BuYN0I2t.js} +0 -0
- /package/dist/web/assets/{turtle-C_l2JhvX.js → turtle-DWH-Rjse.js} +0 -0
- /package/dist/web/assets/{typescript-C1ytmMcr.js → typescript-BPd-hsrR.js} +0 -0
- /package/dist/web/assets/{typespec-De2ztzG5.js → typespec-B0wvVykx.js} +0 -0
- /package/dist/web/assets/{typst-DNcS7gSr.js → typst-DWG2c48-.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-CYW3FJFE.js → use-is-mobile-D1weanbj.js} +0 -0
- /package/dist/web/assets/{use-visual-viewport-D9O48HQt.js → use-visual-viewport-Bfl3Bxxb.js} +0 -0
- /package/dist/web/assets/{utils-C9OjCrou.js → utils-IrnQujwR.js} +0 -0
- /package/dist/web/assets/{v-DSMUsdMO.js → v-Ch6E4Egd.js} +0 -0
- /package/dist/web/assets/{vala-BEazLpkO.js → vala-CGVKwbPx.js} +0 -0
- /package/dist/web/assets/{vb-CV9pEJzW.js → vb-BIEbSxyT.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-cuGQmUjN.js → vendor-xterm-YQlzIWv2.js} +0 -0
- /package/dist/web/assets/{verilog-DhXkLVMk.js → verilog-A-3pYPad.js} +0 -0
- /package/dist/web/assets/{vesper-n4IltYq-.js → vesper-CK1naZAV.js} +0 -0
- /package/dist/web/assets/{vhdl-CePjFbvs.js → vhdl-CVawmgMg.js} +0 -0
- /package/dist/web/assets/{viml-ChndFsm5.js → viml-CiWjaqBY.js} +0 -0
- /package/dist/web/assets/{vitesse-black-BytVdvhC.js → vitesse-black-PYXW4-BO.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-83Wech0N.js → vitesse-dark-BU5s4AuG.js} +0 -0
- /package/dist/web/assets/{vitesse-light-Xg4wbeMO.js → vitesse-light-DgkIT-N7.js} +0 -0
- /package/dist/web/assets/{vyper-eW1NC3FG.js → vyper-D8cRDPp3.js} +0 -0
- /package/dist/web/assets/{wasm-BucxKwBZ.js → wasm-C02wlRbP.js} +0 -0
- /package/dist/web/assets/{wasm-Bhucqu1z.js → wasm-ClFJgzmT.js} +0 -0
- /package/dist/web/assets/{wenyan-BJFBuXuH.js → wenyan-B8plxIr8.js} +0 -0
- /package/dist/web/assets/{wgsl-6z4FUyMK.js → wgsl-CVujtm3V.js} +0 -0
- /package/dist/web/assets/{wikitext-BoDU6teJ.js → wikitext-CmcG9B69.js} +0 -0
- /package/dist/web/assets/{wit-DerMPpgt.js → wit--gl2Jium.js} +0 -0
- /package/dist/web/assets/{wolfram-D78v0Ap-.js → wolfram-6T_1D-NS.js} +0 -0
- /package/dist/web/assets/{x-YDu4m2Iq.js → x-CxoBA0po.js} +0 -0
- /package/dist/web/assets/{yaml-C4TIgIw4.js → yaml-CIVV9uA1.js} +0 -0
- /package/dist/web/assets/{zenscript-BydW-dLC.js → zenscript-CoT4yNdA.js} +0 -0
- /package/dist/web/assets/{zig-b9lTqL5E.js → zig-BWs2Qo3G.js} +0 -0
|
@@ -23,6 +23,12 @@ export interface Account {
|
|
|
23
23
|
lastUsedAt: number | null;
|
|
24
24
|
profileData: OAuthProfileData | null;
|
|
25
25
|
createdAt: number;
|
|
26
|
+
/** When the current OAuth grant was issued. Null for rows that predate the column. */
|
|
27
|
+
grantedAt: number | null;
|
|
28
|
+
/** Refresh-token expiry as reported by the token endpoint, when it reports one. */
|
|
29
|
+
refreshExpiresAt: number | null;
|
|
30
|
+
/** The OAuth server rejected the refresh token; nothing but a fresh sign-in clears it. */
|
|
31
|
+
reauthRequired: boolean;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
export interface AccountWithTokens extends Account {
|
|
@@ -69,6 +75,72 @@ const OAUTH_TOKEN_URL = "https://api.anthropic.com/v1/oauth/token";
|
|
|
69
75
|
const OAUTH_SCOPE = "org:create_api_key user:profile user:inference";
|
|
70
76
|
const OAUTH_PLATFORM_REDIRECT = "https://platform.claude.com/oauth/code/callback";
|
|
71
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Anthropic invalidates the entire refresh-token family this long after the original
|
|
80
|
+
* grant, whether or not the token kept rotating in between. Measured twice on a live
|
|
81
|
+
* install: developers@ signed in 2026-08-11 and was rejected 2026-09-08 (28d 2h);
|
|
82
|
+
* victor@ signed in 2026-08-18 and was rejected 2026-09-15 (27d 15h). Both died on the
|
|
83
|
+
* first use of a token minted hours earlier, so this is grant age, not token age.
|
|
84
|
+
*
|
|
85
|
+
* Only a fallback: if the token endpoint reports the refresh token's own expiry we use
|
|
86
|
+
* that instead, because a guessed constant silently rots if Anthropic changes it.
|
|
87
|
+
*/
|
|
88
|
+
const GRANT_LIFETIME_S = 28 * 86400;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* How close to expiry a token may be before use forces a refresh. Claude Code uses
|
|
92
|
+
* 5 minutes (`g0e = 300000` in its bundle) and refreshes at the point of use rather
|
|
93
|
+
* than on a timer; PPM now does the same.
|
|
94
|
+
*
|
|
95
|
+
* A buffer this small can hand a long turn a token that lapses mid-run. That is
|
|
96
|
+
* survivable here, and only here, because the 401 path in claude-agent-sdk.ts force-
|
|
97
|
+
* refreshes and retries the turn instead of failing it. Shrink that safety net and this
|
|
98
|
+
* number has to grow again.
|
|
99
|
+
*/
|
|
100
|
+
const PREFLIGHT_REFRESH_BUFFER_S = 300;
|
|
101
|
+
|
|
102
|
+
/** Response fields we already understand; anything else is worth learning about. */
|
|
103
|
+
const KNOWN_TOKEN_FIELDS = new Set([
|
|
104
|
+
"access_token", "refresh_token", "expires_in", "token_type", "scope", "account", "organization",
|
|
105
|
+
"refresh_token_expires_in", "refresh_expires_in", "refresh_token_expires_at", "refresh_expires_at",
|
|
106
|
+
]);
|
|
107
|
+
const loggedUnknownTokenFields = new Set<string>();
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Log response fields we do not consume, once per field name.
|
|
111
|
+
*
|
|
112
|
+
* The refresh-token expiry is the one number that would let PPM warn before a grant
|
|
113
|
+
* dies, and Claude Code stores it (`claudeAiOauth.refreshTokenExpiresAt`), but its own
|
|
114
|
+
* wire format is not public. Rather than hard-code a guess, read whichever field shows
|
|
115
|
+
* up and surface the rest so an unknown name is a log line instead of a silent miss.
|
|
116
|
+
*/
|
|
117
|
+
function noteUnknownTokenFields(data: Record<string, unknown>, context: string): void {
|
|
118
|
+
for (const key of Object.keys(data)) {
|
|
119
|
+
if (KNOWN_TOKEN_FIELDS.has(key) || loggedUnknownTokenFields.has(key)) continue;
|
|
120
|
+
loggedUnknownTokenFields.add(key);
|
|
121
|
+
const value = data[key];
|
|
122
|
+
const shown = typeof value === "string" && /token|secret|key/i.test(key)
|
|
123
|
+
? `<redacted len=${value.length}>`
|
|
124
|
+
: JSON.stringify(value);
|
|
125
|
+
console.log(`[accounts] ${context} response carries unhandled field "${key}" = ${shown}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Pull the refresh token's own expiry out of a token response, in whatever shape it arrives. */
|
|
130
|
+
function readRefreshExpiry(data: Record<string, unknown>): number | null {
|
|
131
|
+
const nowS = Math.floor(Date.now() / 1000);
|
|
132
|
+
for (const key of ["refresh_token_expires_in", "refresh_expires_in"]) {
|
|
133
|
+
const v = data[key];
|
|
134
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) return nowS + Math.floor(v);
|
|
135
|
+
}
|
|
136
|
+
for (const key of ["refresh_token_expires_at", "refresh_expires_at"]) {
|
|
137
|
+
const v = data[key];
|
|
138
|
+
// Milliseconds if it is far past any plausible epoch-seconds value.
|
|
139
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) return v > 1e11 ? Math.floor(v / 1000) : Math.floor(v);
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
72
144
|
// Survive Bun --hot reloads: persist timer ref across module re-evaluations
|
|
73
145
|
const ACCT_HOT_KEY = "__PPM_ACCT_REFRESH__" as const;
|
|
74
146
|
const acctHotState = ((globalThis as any)[ACCT_HOT_KEY] ??= {
|
|
@@ -97,9 +169,25 @@ class AccountService {
|
|
|
97
169
|
lastUsedAt: row.last_used_at,
|
|
98
170
|
profileData,
|
|
99
171
|
createdAt: row.created_at,
|
|
172
|
+
grantedAt: row.granted_at,
|
|
173
|
+
refreshExpiresAt: row.refresh_expires_at,
|
|
174
|
+
reauthRequired: row.reauth_required === 1,
|
|
100
175
|
};
|
|
101
176
|
}
|
|
102
177
|
|
|
178
|
+
/**
|
|
179
|
+
* When this account's refresh token family is expected to die, or null if unknowable.
|
|
180
|
+
*
|
|
181
|
+
* Prefers whatever the server reported. Falls back to grant age, which is only
|
|
182
|
+
* available for accounts signed in since PPM started recording it — an older row
|
|
183
|
+
* reads as "unknown" rather than being given a fabricated date.
|
|
184
|
+
*/
|
|
185
|
+
grantExpiresAt(acc: Account): number | null {
|
|
186
|
+
if (acc.refreshExpiresAt) return acc.refreshExpiresAt;
|
|
187
|
+
if (acc.grantedAt) return acc.grantedAt + GRANT_LIFETIME_S;
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
103
191
|
private toAccountWithTokens(row: AccountRow): AccountWithTokens {
|
|
104
192
|
return {
|
|
105
193
|
...this.toAccount(row),
|
|
@@ -144,18 +232,25 @@ class AccountService {
|
|
|
144
232
|
* the two because its callers are background loops with nobody to tell; the one caller
|
|
145
233
|
* that has a user in front of it (`PATCH /api/accounts/:id`) uses this.
|
|
146
234
|
*/
|
|
147
|
-
async ensureFreshTokenChecked(
|
|
235
|
+
async ensureFreshTokenChecked(
|
|
236
|
+
id: string,
|
|
237
|
+
opts?: { retryRejected?: boolean },
|
|
238
|
+
): Promise<{ account: AccountWithTokens | null; rejected: boolean }> {
|
|
148
239
|
const acc = this.getWithTokens(id);
|
|
149
240
|
if (!acc) return { account: null, rejected: false };
|
|
150
241
|
// Only OAuth tokens need refresh
|
|
151
242
|
if (!acc.accessToken.startsWith("sk-ant-oat")) return { account: acc, rejected: false };
|
|
152
243
|
if (!acc.expiresAt) return { account: acc, rejected: false };
|
|
244
|
+
// A grant the server already rejected cannot come back on its own, and this method is
|
|
245
|
+
// on the usage poller's five-minute path. Callers acting on a deliberate user gesture
|
|
246
|
+
// (enable, test token) pass retryRejected so a false positive is always one click from
|
|
247
|
+
// being re-tested.
|
|
248
|
+
if (acc.reauthRequired && !opts?.retryRejected) return { account: null, rejected: true };
|
|
153
249
|
const nowS = Math.floor(Date.now() / 1000);
|
|
154
|
-
|
|
155
|
-
if (acc.expiresAt - nowS > REFRESH_BUFFER_S) return { account: acc, rejected: false }; // still fresh
|
|
250
|
+
if (acc.expiresAt - nowS > PREFLIGHT_REFRESH_BUFFER_S) return { account: acc, rejected: false }; // still fresh
|
|
156
251
|
try {
|
|
157
|
-
console.log(`[accounts] Pre-flight refresh for ${acc.email ?? id} (expires in ${acc.expiresAt - nowS}s, buffer=${
|
|
158
|
-
await this.refreshAccessToken(id, false, false,
|
|
252
|
+
console.log(`[accounts] Pre-flight refresh for ${acc.email ?? id} (expires in ${acc.expiresAt - nowS}s, buffer=${PREFLIGHT_REFRESH_BUFFER_S}s)`);
|
|
253
|
+
await this.refreshAccessToken(id, false, false, PREFLIGHT_REFRESH_BUFFER_S);
|
|
159
254
|
return { account: this.getWithTokens(id), rejected: false };
|
|
160
255
|
} catch (e) {
|
|
161
256
|
const msg = (e as Error).message ?? String(e);
|
|
@@ -186,13 +281,24 @@ class AccountService {
|
|
|
186
281
|
accessToken: string;
|
|
187
282
|
refreshToken: string;
|
|
188
283
|
expiresAt: number;
|
|
284
|
+
refreshExpiresAt?: number | null;
|
|
189
285
|
label?: string;
|
|
190
286
|
profileData?: OAuthProfileData;
|
|
191
287
|
}): Account {
|
|
288
|
+
const grantedAt = Math.floor(Date.now() / 1000);
|
|
192
289
|
// Check for duplicate — update existing account tokens instead of creating new
|
|
193
290
|
const dup = this.findDuplicate(params.email, params.profileData);
|
|
194
291
|
if (dup) {
|
|
195
292
|
this.updateTokens(dup.id, params.accessToken, params.refreshToken, params.expiresAt);
|
|
293
|
+
// Signing in again starts a brand new grant, so the 28-day clock restarts here.
|
|
294
|
+
// This is the branch that matters in practice: re-authenticating an account that
|
|
295
|
+
// already exists is how every expiry is recovered from, and it is exactly where
|
|
296
|
+
// created_at stays frozen at the row's original insert.
|
|
297
|
+
updateAccount(dup.id, {
|
|
298
|
+
granted_at: grantedAt,
|
|
299
|
+
refresh_expires_at: params.refreshExpiresAt ?? null,
|
|
300
|
+
reauth_required: 0,
|
|
301
|
+
});
|
|
196
302
|
if (params.profileData) {
|
|
197
303
|
updateAccount(dup.id, { profile_json: JSON.stringify(params.profileData) });
|
|
198
304
|
}
|
|
@@ -217,6 +323,10 @@ class AccountService {
|
|
|
217
323
|
total_requests: 0,
|
|
218
324
|
last_used_at: null,
|
|
219
325
|
profile_json: params.profileData ? JSON.stringify(params.profileData) : null,
|
|
326
|
+
granted_at: grantedAt,
|
|
327
|
+
refresh_expires_at: params.refreshExpiresAt ?? null,
|
|
328
|
+
reauth_required: 0,
|
|
329
|
+
last_refresh_attempt_at: null,
|
|
220
330
|
});
|
|
221
331
|
return this.toAccount(getAccountById(id)!);
|
|
222
332
|
}
|
|
@@ -347,6 +457,11 @@ class AccountService {
|
|
|
347
457
|
total_requests: 0,
|
|
348
458
|
last_used_at: null,
|
|
349
459
|
profile_json: info.profileData ? JSON.stringify(info.profileData) : null,
|
|
460
|
+
// An API key is not an OAuth grant: it has no family to expire and no sign-in to date.
|
|
461
|
+
granted_at: null,
|
|
462
|
+
refresh_expires_at: null,
|
|
463
|
+
reauth_required: 0,
|
|
464
|
+
last_refresh_attempt_at: null,
|
|
350
465
|
});
|
|
351
466
|
return this.toAccount(getAccountById(id)!);
|
|
352
467
|
}
|
|
@@ -366,6 +481,10 @@ class AccountService {
|
|
|
366
481
|
// The cooldown clears either way — a fresh token is what ends one, and leaving the
|
|
367
482
|
// timestamp on a parked account would resurrect it the moment the park is lifted.
|
|
368
483
|
cooldown_until: null,
|
|
484
|
+
// Holding live tokens is the only thing that clears a re-auth demand. Every door
|
|
485
|
+
// that lands here — sign-in, import, a successful refresh — has just proved the
|
|
486
|
+
// credential works, which is exactly the condition the flag was tracking.
|
|
487
|
+
reauth_required: 0,
|
|
369
488
|
...(parked ? {} : { status: "active" as const }),
|
|
370
489
|
});
|
|
371
490
|
}
|
|
@@ -515,6 +634,7 @@ class AccountService {
|
|
|
515
634
|
accessToken: tokens.accessToken,
|
|
516
635
|
refreshToken: tokens.refreshToken,
|
|
517
636
|
expiresAt: tokens.expiresAt,
|
|
637
|
+
refreshExpiresAt: tokens.refreshExpiresAt,
|
|
518
638
|
label,
|
|
519
639
|
profileData,
|
|
520
640
|
});
|
|
@@ -533,6 +653,7 @@ class AccountService {
|
|
|
533
653
|
accessToken: tokens.accessToken,
|
|
534
654
|
refreshToken: tokens.refreshToken,
|
|
535
655
|
expiresAt: tokens.expiresAt,
|
|
656
|
+
refreshExpiresAt: tokens.refreshExpiresAt,
|
|
536
657
|
profileData,
|
|
537
658
|
});
|
|
538
659
|
}
|
|
@@ -541,6 +662,7 @@ class AccountService {
|
|
|
541
662
|
accessToken: string;
|
|
542
663
|
refreshToken: string;
|
|
543
664
|
expiresAt: number;
|
|
665
|
+
refreshExpiresAt: number | null;
|
|
544
666
|
email: string;
|
|
545
667
|
}> {
|
|
546
668
|
const body: Record<string, string> = {
|
|
@@ -560,16 +682,18 @@ class AccountService {
|
|
|
560
682
|
const text = await res.text();
|
|
561
683
|
throw new Error(`OAuth token exchange failed: ${res.status} ${text}`);
|
|
562
684
|
}
|
|
563
|
-
const data = await res.json() as {
|
|
685
|
+
const data = await res.json() as Record<string, unknown> & {
|
|
564
686
|
access_token: string;
|
|
565
687
|
refresh_token: string;
|
|
566
688
|
expires_in: number;
|
|
567
689
|
account?: { email_address?: string };
|
|
568
690
|
};
|
|
691
|
+
noteUnknownTokenFields(data, "authorization_code");
|
|
569
692
|
return {
|
|
570
693
|
accessToken: data.access_token,
|
|
571
694
|
refreshToken: data.refresh_token,
|
|
572
695
|
expiresAt: Math.floor(Date.now() / 1000) + data.expires_in,
|
|
696
|
+
refreshExpiresAt: readRefreshExpiry(data),
|
|
573
697
|
email: data.account?.email_address ?? "",
|
|
574
698
|
};
|
|
575
699
|
}
|
|
@@ -606,15 +730,18 @@ class AccountService {
|
|
|
606
730
|
|
|
607
731
|
/**
|
|
608
732
|
* POST the refresh grant, retrying transient failures with backoff.
|
|
609
|
-
* A network blip (e.g. waking from sleep) must not cost a whole refresh cycle: this
|
|
610
|
-
* runs at or past access-token expiry, so a dropped attempt leaves the account unable
|
|
611
|
-
* to serve a turn until the next sweep.
|
|
612
733
|
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
734
|
+
* Retrying matters more now than it used to. This runs at the point of use, inside a
|
|
735
|
+
* five-minute window before the token is needed, so a network blip that burns the
|
|
736
|
+
* attempt costs the turn rather than merely deferring to the next sweep — there is no
|
|
737
|
+
* next sweep.
|
|
738
|
+
*
|
|
739
|
+
* An earlier version of this comment claimed refresh tokens outlive their access tokens
|
|
740
|
+
* by days, citing 324 refreshes with zero `invalid_grant`. That held only inside one
|
|
741
|
+
* grant's lifetime. Across grants it is wrong in the way that matters: the family is
|
|
742
|
+
* revoked on a fixed schedule from sign-in (GRANT_LIFETIME_S), and when it goes, the
|
|
743
|
+
* refresh token is rejected on its first use while the access token minted alongside it
|
|
744
|
+
* is still valid. No amount of retrying recovers that — only a new sign-in does.
|
|
618
745
|
*/
|
|
619
746
|
private async postRefreshGrant(refreshToken: string, label: string): Promise<Response> {
|
|
620
747
|
const backoffMs = [2_000, 6_000];
|
|
@@ -661,11 +788,13 @@ class AccountService {
|
|
|
661
788
|
console.log(`[accounts] Token for ${account.email ?? accountId} is already fresh (expires in ${account.expiresAt - nowS}s, threshold=${freshThresholdS}s) — skipping OAuth refresh`);
|
|
662
789
|
return;
|
|
663
790
|
}
|
|
791
|
+
updateAccount(accountId, { last_refresh_attempt_at: nowS });
|
|
664
792
|
const res = await this.postRefreshGrant(account.refreshToken, account.email ?? accountId);
|
|
665
793
|
if (!res.ok) {
|
|
666
794
|
const errorBody = await res.text().catch(() => "");
|
|
667
795
|
console.error(`[accounts] Refresh failed for ${accountId}: ${res.status} ${errorBody}`);
|
|
668
|
-
|
|
796
|
+
const rejected = errorBody.includes("invalid_grant") || errorBody.includes("invalid_request");
|
|
797
|
+
if (rejected) {
|
|
669
798
|
// Another session/process may have refreshed (and rotated) the token between our read
|
|
670
799
|
// and this OAuth call, making this failure stale. Detect that by comparing against the
|
|
671
800
|
// expiry we read — testing "is it fresh" instead would swallow genuine rejections
|
|
@@ -680,24 +809,35 @@ class AccountService {
|
|
|
680
809
|
// permanently with no recovery path (esp. for parked/disabled accounts). Preserve
|
|
681
810
|
// it so re-enable / re-import / re-sync can restore access.
|
|
682
811
|
console.warn(`[accounts] Refresh token rejected for ${account.email ?? accountId} — preserving token for recovery (not clearing)`);
|
|
812
|
+
// Anthropic ends the refresh-token family a fixed time after the grant, so a
|
|
813
|
+
// rejection here is terminal: every subsequent attempt returns the same 400.
|
|
814
|
+
// Recording it stops the retry loop that otherwise runs every few minutes for
|
|
815
|
+
// days — one live install logged 17,510 of them — and gives the UI something to
|
|
816
|
+
// show besides an account that still claims to be active.
|
|
817
|
+
updateAccount(accountId, { reauth_required: 1 });
|
|
683
818
|
}
|
|
684
819
|
if (disableOnFail) {
|
|
685
820
|
this.setDisabled(accountId);
|
|
686
821
|
}
|
|
687
822
|
throw new Error(`Token refresh failed for account ${accountId}: ${res.status} ${errorBody}`);
|
|
688
823
|
}
|
|
689
|
-
const data = await res.json() as {
|
|
824
|
+
const data = await res.json() as Record<string, unknown> & {
|
|
690
825
|
access_token: string;
|
|
691
826
|
refresh_token?: string;
|
|
692
827
|
expires_in: number;
|
|
693
828
|
};
|
|
694
|
-
|
|
829
|
+
noteUnknownTokenFields(data, "refresh_token");
|
|
830
|
+
const refreshExpiresAt = readRefreshExpiry(data);
|
|
831
|
+
console.log(`[accounts] Token refreshed for ${account.email ?? accountId} (expires_in=${data.expires_in}s, new_refresh=${!!data.refresh_token}, refresh_expires_at=${refreshExpiresAt ?? "not reported"})`);
|
|
695
832
|
this.updateTokens(
|
|
696
833
|
accountId,
|
|
697
834
|
data.access_token,
|
|
698
835
|
data.refresh_token ?? account.refreshToken,
|
|
699
836
|
Math.floor(Date.now() / 1000) + data.expires_in,
|
|
700
837
|
);
|
|
838
|
+
// Only overwrite when the server said something. A rotation that reports nothing
|
|
839
|
+
// must not erase an expiry an earlier response did report.
|
|
840
|
+
if (refreshExpiresAt) updateAccount(accountId, { refresh_expires_at: refreshExpiresAt });
|
|
701
841
|
}
|
|
702
842
|
|
|
703
843
|
// ---------------------------------------------------------------------------
|
|
@@ -816,6 +956,12 @@ class AccountService {
|
|
|
816
956
|
total_requests: row.total_requests ?? 0,
|
|
817
957
|
last_used_at: row.last_used_at,
|
|
818
958
|
profile_json: row.profile_json ?? null,
|
|
959
|
+
// A backup written before these columns existed has none of this; null means
|
|
960
|
+
// "unknown", which is what the import genuinely knows.
|
|
961
|
+
granted_at: row.granted_at ?? null,
|
|
962
|
+
refresh_expires_at: row.refresh_expires_at ?? null,
|
|
963
|
+
reauth_required: 0,
|
|
964
|
+
last_refresh_attempt_at: null,
|
|
819
965
|
});
|
|
820
966
|
imported++;
|
|
821
967
|
if (hasRefresh && importedStatus !== "disabled") fullTransferIds.push(row.id);
|
|
@@ -846,43 +992,21 @@ class AccountService {
|
|
|
846
992
|
// Auto-refresh background timer
|
|
847
993
|
// ---------------------------------------------------------------------------
|
|
848
994
|
|
|
849
|
-
|
|
995
|
+
startAccountMaintenance(): void {
|
|
850
996
|
if (acctHotState.refreshTimer) return;
|
|
851
997
|
const CHECK_INTERVAL_MS = 5 * 60_000;
|
|
852
|
-
// Refresh a full hour early: Anthropic's refresh token lapses soon after the access
|
|
853
|
-
// token expires, so waiting until the last minutes leaves no room to recover from a
|
|
854
|
-
// network outage. An hour of headroom gives ~12 retry ticks before the token is lost.
|
|
855
|
-
const REFRESH_BUFFER_S = 60 * 60;
|
|
856
998
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
// skip parked accounts — deliberately, because a token nothing ever refreshes does
|
|
869
|
-
// eventually die. So while polling is on, a parked account's token still rotates
|
|
870
|
-
// here-or-there; this skip only declines to be the one that does it. Enabling the
|
|
871
|
-
// account proves the token either way (`PATCH /api/accounts/:id`).
|
|
872
|
-
if (acc.status === "disabled") continue;
|
|
873
|
-
if (!acc.expiresAt) continue;
|
|
874
|
-
if (acc.expiresAt - nowS > REFRESH_BUFFER_S) continue;
|
|
875
|
-
// Skip temporary accounts (no refresh token) — they can't be refreshed
|
|
876
|
-
const withTokens = this.getWithTokens(acc.id);
|
|
877
|
-
if (!withTokens?.refreshToken) continue;
|
|
878
|
-
console.log(`[accounts] Auto-refreshing token for ${acc.email ?? acc.id}`);
|
|
879
|
-
try {
|
|
880
|
-
await this.refreshAccessToken(acc.id, false, false, REFRESH_BUFFER_S);
|
|
881
|
-
} catch (e) {
|
|
882
|
-
console.error(`[accounts] Auto-refresh failed for ${acc.id}: ${(e as Error).message ?? e}`);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
};
|
|
999
|
+
// There is deliberately no token-refresh sweep here any more.
|
|
1000
|
+
//
|
|
1001
|
+
// It used to refresh every account an hour before expiry, which rotated an idle
|
|
1002
|
+
// account's token three or four times a day for nothing. That bought no safety: the
|
|
1003
|
+
// refresh-token family dies on grant age (see GRANT_LIFETIME_S), not on idleness, so
|
|
1004
|
+
// keeping a parked token warm never extended its life — it only widened the window in
|
|
1005
|
+
// which two holders of the same credential could rotate over each other.
|
|
1006
|
+
//
|
|
1007
|
+
// Claude Code refreshes where the token is consumed and nowhere else, and PPM now
|
|
1008
|
+
// matches: ensureFreshTokenChecked() on the turn path, the proxy, and the usage
|
|
1009
|
+
// poller. What survives on this timer is the one job no request path covers.
|
|
886
1010
|
|
|
887
1011
|
// Cleanup: auto-delete expired temporary accounts (no refresh token) after 7 days
|
|
888
1012
|
const TEMP_EXPIRY_DAYS = 7;
|
|
@@ -902,10 +1026,8 @@ class AccountService {
|
|
|
902
1026
|
};
|
|
903
1027
|
|
|
904
1028
|
// Run immediately on startup, then every 5 minutes
|
|
905
|
-
refreshExpiring().catch(() => {});
|
|
906
1029
|
cleanupExpiredTemporary();
|
|
907
1030
|
acctHotState.refreshTimer = setInterval(() => {
|
|
908
|
-
refreshExpiring().catch(() => {});
|
|
909
1031
|
cleanupExpiredTemporary();
|
|
910
1032
|
}, CHECK_INTERVAL_MS);
|
|
911
1033
|
|
|
@@ -914,7 +1036,7 @@ class AccountService {
|
|
|
914
1036
|
}
|
|
915
1037
|
}
|
|
916
1038
|
|
|
917
|
-
|
|
1039
|
+
stopAccountMaintenance(): void {
|
|
918
1040
|
if (acctHotState.refreshTimer) {
|
|
919
1041
|
clearInterval(acctHotState.refreshTimer);
|
|
920
1042
|
acctHotState.refreshTimer = null;
|
|
@@ -140,7 +140,18 @@ export function getServicePath(): string {
|
|
|
140
140
|
export function generateSystemdService(config: AutoStartConfig): string {
|
|
141
141
|
const cmd = buildExecCommand(config);
|
|
142
142
|
const execStart = cmd.map(shellEscape).join(" ");
|
|
143
|
-
|
|
143
|
+
// Bun is a runtime dependency even when PPM itself is a compiled binary: the extension
|
|
144
|
+
// installer spawns bare `bun add` / `bun remove` (extension-installer.ts), and a systemd
|
|
145
|
+
// user unit inherits a PATH that does not include ~/.bun/bin. Skipping this line whenever
|
|
146
|
+
// PPM was not started *by* bun left extension install/search failing with
|
|
147
|
+
// `Executable not found in $PATH: "bun"`. The launchd plist already carries a PATH through
|
|
148
|
+
// for the same reason.
|
|
149
|
+
let bunDir = "";
|
|
150
|
+
try {
|
|
151
|
+
bunDir = resolve(resolveBunPath(), "..");
|
|
152
|
+
} catch {
|
|
153
|
+
// No bun installed — there is no directory to add, and the feature that needs it says so.
|
|
154
|
+
}
|
|
144
155
|
|
|
145
156
|
// Build PATH with bun directory prepended
|
|
146
157
|
const envPath = bunDir
|
|
@@ -21,12 +21,16 @@ export type CodexStrategy = "round-robin" | "fill-first" | "lowest-usage";
|
|
|
21
21
|
|
|
22
22
|
export type CodexAccountType = "apiKey" | "chatgpt";
|
|
23
23
|
|
|
24
|
+
/** Two states only — nothing here parks an account in cooldown the way Claude's rotation does. */
|
|
25
|
+
export type CodexAccountStatus = "active" | "disabled";
|
|
26
|
+
|
|
24
27
|
export interface CodexAccount {
|
|
25
28
|
id: string;
|
|
26
29
|
label: string;
|
|
27
30
|
type: CodexAccountType;
|
|
28
31
|
home: string;
|
|
29
32
|
planType?: string | null;
|
|
33
|
+
status: CodexAccountStatus;
|
|
30
34
|
addedAt: string;
|
|
31
35
|
}
|
|
32
36
|
|
|
@@ -38,10 +42,21 @@ export type CodexCreds =
|
|
|
38
42
|
interface Row {
|
|
39
43
|
id: string; label: string | null; type: string; home: string;
|
|
40
44
|
plan_type: string | null; creds_enc: string | null; added_at: string;
|
|
45
|
+
status?: string | null;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
function rowToAccount(r: Row): CodexAccount {
|
|
44
|
-
return {
|
|
49
|
+
return {
|
|
50
|
+
id: r.id,
|
|
51
|
+
label: r.label ?? r.id.slice(0, 8),
|
|
52
|
+
type: r.type as CodexAccountType,
|
|
53
|
+
home: r.home,
|
|
54
|
+
planType: r.plan_type,
|
|
55
|
+
// A row written before the column existed reads as null; treat that as enabled, which
|
|
56
|
+
// is what it effectively was.
|
|
57
|
+
status: r.status === "disabled" ? "disabled" : "active",
|
|
58
|
+
addedAt: r.added_at,
|
|
59
|
+
};
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
/** CODEX_HOME directory for an account id. */
|
|
@@ -102,6 +117,24 @@ export function updateCodexAccountMeta(id: string, meta: { label?: string; planT
|
|
|
102
117
|
getDb().query(`UPDATE codex_accounts SET ${sets.join(", ")} WHERE id = ?`).run(...vals as any[]);
|
|
103
118
|
}
|
|
104
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Switch an account on or off.
|
|
122
|
+
*
|
|
123
|
+
* Deliberately no token proof on the way in, unlike the Claude path: that check exists
|
|
124
|
+
* because an Anthropic OAuth account can hold a refresh token the server has to exercise
|
|
125
|
+
* before trusting. A Codex account is a CODEX_HOME directory the app-server authenticates
|
|
126
|
+
* against when it spawns, so there is nothing here to prove in advance — copying that check
|
|
127
|
+
* across would buy a slow button and no safety.
|
|
128
|
+
*
|
|
129
|
+
* Disabling never touches credentials. Off and removed are different things, and the account
|
|
130
|
+
* has to come back exactly as it was.
|
|
131
|
+
*/
|
|
132
|
+
export function setCodexAccountStatus(id: string, status: CodexAccountStatus): CodexAccount | null {
|
|
133
|
+
if (!getCodexAccount(id)) return null;
|
|
134
|
+
getDb().query("UPDATE codex_accounts SET status = ? WHERE id = ?").run(status, id);
|
|
135
|
+
return getCodexAccount(id);
|
|
136
|
+
}
|
|
137
|
+
|
|
105
138
|
/** Remove the account row and its CODEX_HOME dir. */
|
|
106
139
|
export function removeCodexAccount(id: string): void {
|
|
107
140
|
const acct = getCodexAccount(id);
|
|
@@ -126,16 +159,48 @@ export function setCodexStrategy(strategy: CodexStrategy): void {
|
|
|
126
159
|
configService.save();
|
|
127
160
|
}
|
|
128
161
|
|
|
162
|
+
/** Five-hour utilisation at or above which an account is skipped while others have room. */
|
|
163
|
+
const FIVE_HOUR_SKIP_THRESHOLD = 0.95;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Drop accounts with no five-hour room left, unless that would leave nothing.
|
|
167
|
+
*
|
|
168
|
+
* Soft on purpose, mirroring the Claude selector: a throttled turn beats no turn, so when
|
|
169
|
+
* every account is at its cap the caller still gets one back. `usageOf` is supplied by the
|
|
170
|
+
* caller rather than fetched here — usage lives behind an async provider layer, and pulling
|
|
171
|
+
* it inside a synchronous pick would put a network round-trip on every account selection.
|
|
172
|
+
* With no `usageOf` there is nothing to judge and every account stays a candidate.
|
|
173
|
+
*/
|
|
174
|
+
function withFiveHourRoom(
|
|
175
|
+
accts: CodexAccount[],
|
|
176
|
+
usageOf?: (id: string) => number,
|
|
177
|
+
): CodexAccount[] {
|
|
178
|
+
if (!usageOf) return accts;
|
|
179
|
+
const room = accts.filter((a) => {
|
|
180
|
+
const util = usageOf(a.id);
|
|
181
|
+
return !Number.isFinite(util) ? true : util < FIVE_HOUR_SKIP_THRESHOLD;
|
|
182
|
+
});
|
|
183
|
+
return room.length > 0 ? room : accts;
|
|
184
|
+
}
|
|
185
|
+
|
|
129
186
|
/**
|
|
130
187
|
* Pick an account by strategy. `usageOf` (optional, from P4) enables lowest-usage;
|
|
131
188
|
* without it lowest-usage falls back to round-robin.
|
|
189
|
+
*
|
|
190
|
+
* `usageOf` also drives the five-hour skip, so a caller that supplies it gets an account
|
|
191
|
+
* with room rather than merely the least-used one among several that are all capped.
|
|
132
192
|
*/
|
|
133
193
|
export function selectCodexAccount(opts?: { strategy?: CodexStrategy; usageOf?: (id: string) => number }): CodexAccount | null {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
194
|
+
// Disabled is a hard exclusion with no fallback, unlike the five-hour skip below. Being
|
|
195
|
+
// capped means a slower turn; being switched off is the user saying don't use this, and
|
|
196
|
+
// handing it back anyway because nothing else was left would ignore them.
|
|
197
|
+
const all = listCodexAccounts().filter((a) => a.status !== "disabled");
|
|
198
|
+
if (all.length === 0) return null;
|
|
199
|
+
if (all.length === 1) return all[0]!;
|
|
137
200
|
const strategy = opts?.strategy ?? getCodexStrategy();
|
|
138
201
|
const usageOf = opts?.usageOf;
|
|
202
|
+
const accts = withFiveHourRoom(all, usageOf);
|
|
203
|
+
if (accts.length === 1) return accts[0]!;
|
|
139
204
|
if (strategy === "fill-first") return accts[0]!;
|
|
140
205
|
if (strategy === "lowest-usage" && usageOf) {
|
|
141
206
|
return accts.reduce((best, a) => (usageOf(a.id) < usageOf(best.id) ? a : best), accts[0]!);
|
|
@@ -160,7 +225,9 @@ export function selectCodexAccount(opts?: { strategy?: CodexStrategy; usageOf?:
|
|
|
160
225
|
* null rather than guessing at one.
|
|
161
226
|
*/
|
|
162
227
|
export function peekCodexAccount(): CodexAccount | null {
|
|
163
|
-
|
|
228
|
+
// Status is the one thing this can filter on without going async, so it does. Usage lives
|
|
229
|
+
// behind a fetch and stays out of reach here; the consuming pick endpoint reads it instead.
|
|
230
|
+
const accts = listCodexAccounts().filter((a) => a.status !== "disabled");
|
|
164
231
|
if (accts.length === 0) return null;
|
|
165
232
|
if (accts.length === 1) return accts[0]!;
|
|
166
233
|
return getCodexStrategy() === "fill-first" ? accts[0]! : null;
|
|
@@ -171,14 +238,21 @@ export async function resolveCodexAccountForSession(sessionId?: string): Promise
|
|
|
171
238
|
// Session-less callers (the model list) have nothing sticky to honour and fall
|
|
172
239
|
// straight through to the configured strategy.
|
|
173
240
|
const sticky = sessionId ? getSessionCodexAccount(sessionId) : null;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
241
|
+
// A binding is held for the prompt cache, not honoured unconditionally: an account the
|
|
242
|
+
// user has switched off has to let go of the sessions sitting on it, or turning it off
|
|
243
|
+
// would do nothing for exactly the conversations already using it.
|
|
244
|
+
if (sticky) { const a = getCodexAccount(sticky); if (a && a.status !== "disabled") return a; }
|
|
245
|
+
if (listCodexAccounts().filter((a) => a.status !== "disabled").length <= 1) return selectCodexAccount();
|
|
246
|
+
// Usage is read for every strategy, not just lowest-usage: it is also what lets the
|
|
247
|
+
// selector skip an account with no five-hour room left. Reading it only for lowest-usage
|
|
248
|
+
// left that skip inert under round-robin, which is the default — sessions kept landing on
|
|
249
|
+
// accounts that were already capped. The read is served from the shared usage cache, so
|
|
250
|
+
// this costs a lookup rather than a request per session start.
|
|
251
|
+
//
|
|
252
|
+
// A failed usage fetch (broken/expired account) yields {} → treat as +Infinity so
|
|
253
|
+
// lowest-usage de-prioritizes it instead of preferring it as "0% used".
|
|
254
|
+
const usages = await getAllCodexUsages();
|
|
255
|
+
return selectCodexAccount({ usageOf: (id) => usages[id]?.fiveHour ?? Number.POSITIVE_INFINITY });
|
|
182
256
|
}
|
|
183
257
|
|
|
184
258
|
// ── Usage (per account) ──
|