@hienlh/ppm 0.19.2 → 0.19.3
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 +22 -0
- package/CLAUDE.md +1 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +3 -2
- package/dist/web/assets/accounts-settings-section-LtSVxNNp.js +1 -0
- package/dist/web/assets/ai-resource-editor-BqFT1EUB.js +1 -0
- package/dist/web/assets/ai-settings-section-CY6spbEx.js +1 -0
- package/dist/web/assets/ai-settings-section-Po3K8j-g.js +1 -0
- package/dist/web/assets/{angular-html-BlcrLjUx.js → angular-html-BuHNQIbQ.js} +1 -1
- package/dist/web/assets/{angular-ts-BUjtceAJ.js → angular-ts-DHRWoLc-.js} +1 -1
- package/dist/web/assets/api-client-BogqsFna.js +1 -0
- package/dist/web/assets/api-settings-CTMdz_VF.js +1 -0
- package/dist/web/assets/{api-themes-YsZgf6Xj.js → api-themes-Cknp3Q-P.js} +1 -1
- package/dist/web/assets/{apl-B3g0v1pk.js → apl-C1VSWVei.js} +1 -1
- package/dist/web/assets/appearance-settings-section-L9Z-nej_.js +1 -0
- package/dist/web/assets/architecture-PBZL5I3N-Ca5IpdzP.js +1 -0
- package/dist/web/assets/arrow-left-CyFcsyzD.js +1 -0
- package/dist/web/assets/{astro-FRnyn7cH.js → astro-RX-rChsZ.js} +1 -1
- package/dist/web/assets/{audio-preview-DWTRm4HC.js → audio-preview-CCd1jdje.js} +1 -1
- package/dist/web/assets/{blade-NLdOqUKw.js → blade-CZ6bxMbK.js} +1 -1
- package/dist/web/assets/{pencil-BlIIg6RH.js → brain-KDa9jFRq.js} +1 -1
- package/dist/web/assets/{bsl-CenHZfaU.js → bsl-BhgCXLWk.js} +1 -1
- package/dist/web/assets/{button-Cq8yixjf.js → button-BZtEJcUv.js} +1 -1
- package/dist/web/assets/{cairo-B_WR0aiQ.js → cairo-BrwygPkn.js} +1 -1
- package/dist/web/assets/calendar-clock-C1asT-gk.js +1 -0
- package/dist/web/assets/chat-tab-BIO-VhWT.js +15 -0
- package/dist/web/assets/check-DN75Ucvz.js +1 -0
- package/dist/web/assets/chevron-up-8PbsNiJp.js +1 -0
- package/dist/web/assets/{cobol-DOynhWYj.js → cobol-C5TGW1Ir.js} +1 -1
- package/dist/web/assets/code-editor-B9lVjElM.js +7 -0
- package/dist/web/assets/{coffee-Bn6dbw4P.js → coffee-CFgKlmgI.js} +1 -1
- package/dist/web/assets/{conflict-editor-Cgb-z12D.js → conflict-editor-BoRPwL3L.js} +1 -1
- package/dist/web/assets/context-menu-C13b1uT3.js +1 -0
- package/dist/web/assets/{cpp-CoZR9t8r.js → cpp-BhG5q3-d.js} +1 -1
- package/dist/web/assets/{crystal-BSheONSg.js → crystal-DvuI7_tY.js} +1 -1
- package/dist/web/assets/{csv-preview-BZMHfFH3.js → csv-preview-BpoJqZQi.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-gwPoVSCU.js → data-grid-overlay-editor-DP1xkog4.js} +1 -1
- package/dist/web/assets/database-viewer-I-9cER1L.js +1 -0
- package/dist/web/assets/diff-viewer-_xHZsxvr.js +4 -0
- package/dist/web/assets/{docx-preview-DqVDOMmv.js → docx-preview-CWIDVNZn.js} +1 -1
- package/dist/web/assets/{edge-DTtoslQu.js → edge-BSIEsmlI.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-CXSbSUBX.js → edit-diff-preview-DeHb85DN.js} +1 -1
- package/dist/web/assets/{elixir-CIBCZ3Zi.js → elixir-BF-lrGXM.js} +1 -1
- package/dist/web/assets/{elm-BLmb7K1H.js → elm-8mCsJOeN.js} +1 -1
- package/dist/web/assets/{erb-BBY8ApRd.js → erb-Dw5PUVKa.js} +1 -1
- package/dist/web/assets/{erlang-D5Zb5_P0.js → erlang-BvjBIA6O.js} +1 -1
- package/dist/web/assets/{esm-6F8v1jLL.js → esm-Bt9OwSnU.js} +1 -1
- package/dist/web/assets/explorer-body-DapwsOnp.js +2 -0
- package/dist/web/assets/explorer-window-content-DD6Z15pm.js +1 -0
- package/dist/web/assets/extension-manager-section-Cu9D9Udt.js +1 -0
- package/dist/web/assets/{extension-webview-BSh6KGd7.js → extension-webview-Diz2OzVq.js} +1 -1
- package/dist/web/assets/{plus-BR0qewJI.js → eye-gWwaF4Lg.js} +1 -1
- package/dist/web/assets/eye-off-GjXVuAOn.js +1 -0
- package/dist/web/assets/files-settings-section-B1e7BgKQ.js +1 -0
- package/dist/web/assets/flask-conical-tp8gyZGh.js +1 -0
- package/dist/web/assets/{fortran-fixed-form-CBdwkEUJ.js → fortran-fixed-form-CRjUJe9K.js} +1 -1
- package/dist/web/assets/{fsharp-AT4XdVYH.js → fsharp-CjUIHIHp.js} +1 -1
- package/dist/web/assets/{gdresource-D852fWBR.js → gdresource-x_NUQFrZ.js} +1 -1
- package/dist/web/assets/general-settings-section-DWdHHRDy.js +1 -0
- package/dist/web/assets/{git-commit-DWfWAyxy.js → git-commit-BOpxSbns.js} +1 -1
- package/dist/web/assets/git-log-panel-CMMsvU64.js +1 -0
- package/dist/web/assets/{git-rebase-C6opk8gr.js → git-rebase-BW4xlK7k.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BloZfB0Q.js +1 -0
- package/dist/web/assets/{glide-data-grid-CICFjJon.js → glide-data-grid-pKQuyYFj.js} +8 -8
- package/dist/web/assets/{glimmer-js-DLi5mWYO.js → glimmer-js-CT9jrmFj.js} +1 -1
- package/dist/web/assets/{glimmer-ts-D-scFX30.js → glimmer-ts-CjQT1lxK.js} +1 -1
- package/dist/web/assets/{glsl-dbQzF4iA.js → glsl-DsFJvtg5.js} +1 -1
- package/dist/web/assets/{graphql-D1m5Wen2.js → graphql-Dhlnmkn_.js} +1 -1
- package/dist/web/assets/group-chat-tab-U5u0Loii.js +1 -0
- package/dist/web/assets/{hack-CqYmpO7u.js → hack-BKHbsJ7u.js} +1 -1
- package/dist/web/assets/{haml-BXzKWEag.js → haml-C1Ih25Y2.js} +1 -1
- package/dist/web/assets/{handlebars-DXnrtHtq.js → handlebars-CaFitvmC.js} +1 -1
- package/dist/web/assets/{html-DhgQJ-xL.js → html-BwpKIE6V.js} +1 -1
- package/dist/web/assets/{html-derivative-xZRft-YE.js → html-derivative-uuTEquZU.js} +1 -1
- package/dist/web/assets/{http-B53mSj14.js → http-Cv4Pg5cR.js} +1 -1
- package/dist/web/assets/{hurl-CsomVy04.js → hurl-DubkI6TG.js} +1 -1
- package/dist/web/assets/{hxml-LffjWwKA.js → hxml-B9OVZFAB.js} +1 -1
- package/dist/web/assets/{image-preview-CNA6XMNS.js → image-preview-DRbZ0FA2.js} +1 -1
- package/dist/web/assets/index--KWVW-Ga.js +52 -0
- package/dist/web/assets/index-CvGx7wBQ.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-c0W8wp5o.js +1 -0
- package/dist/web/assets/{jinja-CCHURl7V.js → jinja-BHb1jyl6.js} +1 -1
- package/dist/web/assets/jira-watcher-section-uA5oyB8s.js +1 -0
- package/dist/web/assets/{jison-7qBd8dnR.js → jison-CtpfXDzp.js} +1 -1
- package/dist/web/assets/{julia-BY9pnTcy.js → julia-TYjgpazU.js} +1 -1
- package/dist/web/assets/{just-D7wRSNRr.js → just-D27bzZuW.js} +1 -1
- package/dist/web/assets/key-round-CQpDrDxg.js +1 -0
- package/dist/web/assets/keybindings-store-Ccf0etMJ.js +1 -0
- package/dist/web/assets/keyboard-shortcuts-section-B7IxJHiI.js +1 -0
- package/dist/web/assets/{latex-RgsCae55.js → latex-D_MzopTn.js} +1 -1
- package/dist/web/assets/{liquid-CnDFzvOZ.js → liquid-BG_FG0Pe.js} +1 -1
- package/dist/web/assets/{lua-0lj5W8sm.js → lua-Bl29oC2L.js} +1 -1
- package/dist/web/assets/markdown-renderer-CY1epGRc.js +1 -0
- package/dist/web/assets/{markdown-renderer-dbLIvU2M.js → markdown-renderer-DDcIGQEd.js} +3 -3
- package/dist/web/assets/{marko-CQgqAhbV.js → marko-D-HI7dbm.js} +1 -1
- package/dist/web/assets/{mdc-BkIcTiIy.js → mdc-y444JQTQ.js} +1 -1
- package/dist/web/assets/{media-url-DDciwvLU.js → media-url-VzecHk9g.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-pjcHKorA.js → mobile-bottom-sheet-BD5b7KJF.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CqkwbCRl.js +1 -0
- package/dist/web/assets/{nextflow-DvIW68DS.js → nextflow-C5sKcldC.js} +1 -1
- package/dist/web/assets/{nginx-O-hft0gG.js → nginx-bXnEC3wm.js} +1 -1
- package/dist/web/assets/{nim-BxrR4HwQ.js → nim-pevhf4WD.js} +1 -1
- package/dist/web/assets/notification-store-BNSYk3E7.js +1 -0
- package/dist/web/assets/notifications-settings-section-v_628h4B.js +1 -0
- package/dist/web/assets/{number-overlay-editor-BoLRYuZT.js → number-overlay-editor-B7WINIW4.js} +1 -1
- package/dist/web/assets/{overview-panel-l3g1gT5e.js → overview-panel-mhN0qw6Q.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-D_BoHr_M.js +1 -0
- package/dist/web/assets/panel-store-BRrnDO_i.js +1 -0
- package/dist/web/assets/pdf-preview-CI3q1MnH.js +1 -0
- package/dist/web/assets/{perl-Da-uNqmL.js → perl-B1KUtMnW.js} +1 -1
- package/dist/web/assets/{php-JMu45mEx.js → php-BGmVZBAJ.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-BaLfuEf8.js +1 -0
- package/dist/web/assets/{postgres-viewer-DstZwUai.js → postgres-viewer-jaR0FYJ_.js} +3 -3
- package/dist/web/assets/power-CriQkeK5.js +1 -0
- package/dist/web/assets/ppmbot-settings-section-BET4VmKb.js +1 -0
- package/dist/web/assets/{process-table-kmQ2-8qJ.js → process-table-D1r-Gupi.js} +1 -1
- package/dist/web/assets/{project-store-DTUCS65m.js → project-store-BuY7rxn_.js} +1 -1
- package/dist/web/assets/proxy-settings-section-0DvNh1B-.js +3 -0
- package/dist/web/assets/{pug-CLzyQhia.js → pug-BfN8CpEH.js} +1 -1
- package/dist/web/assets/{qml-BHXfGB1l.js → qml-D6fRdLZt.js} +1 -1
- package/dist/web/assets/query-audit-section-BA5rT6Sh.js +1 -0
- package/dist/web/assets/radar-KQ55EAFF-DBIZEaIU.js +1 -0
- package/dist/web/assets/{razor-BqetJLN3.js → razor-fg-9AScR.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-CPa3XElX.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-De_a4skd.js +1 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CPrDIv2d.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-CG1StfCE.js +1 -0
- package/dist/web/assets/{rst-iQdJZ0BP.js → rst-BZgx076b.js} +1 -1
- package/dist/web/assets/{ruby-CNwzZGdH.js → ruby-iUkWDcLo.js} +1 -1
- package/dist/web/assets/{run-in-terminal-CBXmVGWa.js → run-in-terminal-bKRwuO2z.js} +1 -1
- package/dist/web/assets/{sas-BpBIT2eR.js → sas-CLXO0lZ6.js} +1 -1
- package/dist/web/assets/schedules-settings-section-B2f7V7Xr.js +1 -0
- package/dist/web/assets/{scss-D1MotuEp.js → scss-DQqteGaG.js} +1 -1
- package/dist/web/assets/send-Bb0jtcR6.js +1 -0
- package/dist/web/assets/{send-to-chat-DJAdlvJ8.js → send-to-chat-S8LCNX7V.js} +1 -1
- package/dist/web/assets/separator-DqGg3f9c.js +1 -0
- package/dist/web/assets/settings-body-omv04jbb.js +2 -0
- package/dist/web/assets/settings-store-Brjd0QnO.js +2 -0
- package/dist/web/assets/settings-tab-NdnBTqyj.js +1 -0
- package/dist/web/assets/settings-window-content-Chou1JGa.js +1 -0
- package/dist/web/assets/{shaderlab-BNqmcPQi.js → shaderlab-CeMTqagR.js} +1 -1
- package/dist/web/assets/{shellsession-BBLOVOO-.js → shellsession-BAkquZAV.js} +1 -1
- package/dist/web/assets/{soy-BpTGo9uy.js → soy-Bxm9Ez55.js} +1 -1
- package/dist/web/assets/{sparql-D7A3yKob.js → sparql-C1phwdLc.js} +1 -1
- package/dist/web/assets/sql-query-editor-Dk9y3jqy.js +1 -0
- package/dist/web/assets/sqlite-viewer-4xFhkLYy.js +1 -0
- package/dist/web/assets/{stata-B7N56ptS.js → stata-DGNMQdEb.js} +1 -1
- package/dist/web/assets/{surrealql-CTmnD-lv.js → surrealql-ByVrV320.js} +1 -1
- package/dist/web/assets/{svelte-BppwY9TR.js → svelte-CNWjIMhl.js} +1 -1
- package/dist/web/assets/system-monitor-body-B_1iMVOI.js +2 -0
- package/dist/web/assets/system-monitor-tab-B2j9mWBW.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-B9Y98UW6.js → system-monitor-window-content-BOjq53uU.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-C1j8zr68.js → tab-host-window-content-CW_dlYlW.js} +1 -1
- package/dist/web/assets/{tab-store-DzYSsw5a.js → tab-store-PZsP5ZwG.js} +1 -1
- package/dist/web/assets/team-member-sheet-CkR4qO7V.js +2 -0
- package/dist/web/assets/team-member-window-content-CmF5ia3n.js +1 -0
- package/dist/web/assets/{templ-CvTMKKra.js → templ-Cf0gQL4I.js} +1 -1
- package/dist/web/assets/{terminal-tab-B9I_HlES.js → terminal-tab-HXZkicIc.js} +3 -3
- package/dist/web/assets/{tex--c4POEUE.js → tex-BTIqPw_R.js} +1 -1
- package/dist/web/assets/tool-cards-jA1BTLZj.js +9 -0
- package/dist/web/assets/treemap-KZPCXAKY-DFiG9UYW.js +1 -0
- package/dist/web/assets/{ts-tags--kx7D-Hw.js → ts-tags-B8dN_Yf6.js} +1 -1
- package/dist/web/assets/{twig-UG9uMHGd.js → twig-BgemonbJ.js} +1 -1
- package/dist/web/assets/use-accounts-data-BI_5qNvr.js +1 -0
- package/dist/web/assets/{use-blob-url-Bdxopwwc.js → use-blob-url-Gfi69PtP.js} +1 -1
- package/dist/web/assets/use-monaco-theme-BcQqKVbE.js +1 -0
- package/dist/web/assets/{use-open-team-member-D9EOB8-i.js → use-open-team-member-CruUTqzD.js} +1 -1
- package/dist/web/assets/use-remote-desktop-display-choice-DY5BWRTo.js +1 -0
- package/dist/web/assets/{use-websocket-xOOwBdwP.js → use-websocket-DetaY8OI.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-0Nt-ew76.js → vendor-mermaid-BKWscKt0.js} +1 -1
- package/dist/web/assets/video-preview-C0uH2228.js +1 -0
- package/dist/web/assets/{vue-jjTomlbt.js → vue-D3D4lbXz.js} +1 -1
- package/dist/web/assets/{vue-html-8KEW6-I9.js → vue-html-DYEQFAiK.js} +1 -1
- package/dist/web/assets/{vue-vine-C5tohCkU.js → vue-vine-D0OPNscN.js} +1 -1
- package/dist/web/assets/{window-pip-registry-FdZdGF7h.js → window-pip-registry-BEN0QNSW.js} +1 -1
- package/dist/web/assets/window-store-DGKf9rPG.js +1 -0
- package/dist/web/assets/ws-auth-Dawx17PM.js +1 -0
- package/dist/web/assets/{xml-ByFFosqL.js → xml-DHaWgpya.js} +1 -1
- package/dist/web/assets/{xsl-CZUwiLkj.js → xsl-DVRN8JsA.js} +1 -1
- package/dist/web/index.html +32 -55
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +9 -5
- package/docs/lessons-learned.md +11 -0
- package/package.json +2 -2
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- package/src/providers/codex-app-server/codex-event-mapper.ts +70 -5
- package/src/providers/codex-app-server/codex-history.ts +24 -0
- package/src/providers/codex-app-server/codex-jsonrpc-client.ts +39 -2
- package/src/providers/codex-app-server/codex-protocol.ts +24 -0
- package/src/providers/codex-app-server/codex-provider.ts +159 -17
- package/src/providers/codex-app-server/codex-rollout-items.ts +149 -0
- package/src/providers/codex-app-server/codex-skill-parser.ts +63 -0
- package/src/providers/codex-app-server/codex-usage-fetch.ts +22 -20
- package/src/providers/codex-app-server/codex-usage-source.ts +37 -0
- package/src/server/routes/chat.ts +67 -9
- package/src/server/routes/codex-accounts.ts +9 -5
- package/src/server/ws/chat.ts +46 -1
- package/src/services/claude-usage-source.ts +40 -0
- package/src/services/claude-usage.service.ts +77 -146
- package/src/services/codex-account-login.ts +123 -55
- package/src/services/codex-account.service.ts +27 -4
- package/src/services/db.service.ts +161 -20
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/fs-credential-path-guard.ts +35 -8
- package/src/services/fs-path-guard.service.ts +1 -0
- package/src/services/postgres.service.ts +144 -72
- package/src/services/provider-usage/index.ts +63 -0
- package/src/services/provider-usage/register-usage-sources.ts +18 -0
- package/src/services/provider-usage/usage-memory-cache.ts +71 -0
- package/src/services/provider-usage/usage-registry.ts +157 -0
- package/src/services/provider-usage/usage-scheduler.ts +117 -0
- package/src/services/provider-usage/usage-snapshot-store.ts +126 -0
- package/src/services/provider-usage/usage-source.ts +82 -0
- package/src/services/slash-discovery/builtin-commands.ts +13 -0
- package/src/services/slash-discovery/codex-skill-items.ts +57 -0
- package/src/services/slash-discovery/provider-skill-sigil.ts +19 -0
- package/src/services/slash-discovery/types.ts +19 -0
- package/src/services/windows-process-tree.ts +351 -351
- package/src/types/chat.ts +7 -1
- package/src/web/app.tsx +1 -7
- package/src/web/components/chat/chat-history-bar.tsx +10 -1
- package/src/web/components/chat/chat-tab.tsx +17 -1
- package/src/web/components/chat/message-input.tsx +11 -4
- package/src/web/components/chat/message-list.tsx +14 -0
- package/src/web/components/chat/slash-command-picker.tsx +22 -3
- package/src/web/components/chat/tool-cards.tsx +47 -4
- package/src/web/components/chat/usage-badge.tsx +43 -447
- package/src/web/components/floating-window/window-content-registry.ts +2 -0
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/layout/command-palette.tsx +2 -2
- package/src/web/components/layout/mobile-drawer.tsx +5 -4
- package/src/web/components/layout/mobile-nav.tsx +5 -1
- package/src/web/components/layout/nav-section-rail.tsx +2 -1
- package/src/web/components/layout/project-bottom-sheet.tsx +3 -9
- package/src/web/components/layout/sidebar.tsx +0 -2
- package/src/web/components/layout/tab-pop-out-menu-item.tsx +3 -2
- package/src/web/components/settings/accounts/account-add-dialog.tsx +155 -0
- package/src/web/components/settings/accounts/account-backup-format.ts +19 -0
- package/src/web/components/settings/accounts/account-bucket-row.tsx +55 -0
- package/src/web/components/settings/accounts/account-card.tsx +127 -0
- package/src/web/components/settings/accounts/account-delete-confirm.tsx +62 -0
- package/src/web/components/settings/accounts/account-export-dialog.tsx +154 -0
- package/src/web/components/settings/accounts/account-export-simulation.tsx +196 -0
- package/src/web/components/settings/accounts/account-import-dialog.tsx +72 -0
- package/src/web/components/settings/accounts/account-profile-panel.tsx +52 -0
- package/src/web/components/{chat → settings/accounts}/account-rotation-settings.tsx +1 -1
- package/src/web/components/settings/accounts/account-token-test-dialog.tsx +147 -0
- package/src/web/components/settings/accounts/account-usage-format.ts +92 -0
- package/src/web/components/{chat/usage-pattern-chart.tsx → settings/accounts/account-usage-pattern-chart.tsx} +1 -1
- package/src/web/components/settings/accounts/accounts-pane-header.tsx +93 -0
- package/src/web/components/settings/accounts/accounts-settings-section.tsx +80 -0
- package/src/web/components/settings/accounts/claude-accounts-section.tsx +168 -0
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +137 -0
- package/src/web/components/settings/accounts/codex-add-account-dialog.tsx +130 -0
- package/src/web/components/settings/accounts/codex-backup-dialog.tsx +101 -0
- package/src/web/components/settings/accounts/codex-rotation-dialog.tsx +65 -0
- package/src/web/components/settings/accounts/use-accounts-data.ts +81 -0
- package/src/web/components/settings/accounts/use-codex-accounts.ts +151 -0
- package/src/web/components/settings/ai-settings-section.tsx +14 -4
- package/src/web/components/settings/appearance-settings-section.tsx +125 -0
- package/src/web/components/settings/general-settings-section.tsx +87 -0
- package/src/web/components/settings/jira-watcher-section.tsx +34 -0
- package/src/web/components/settings/notifications-settings-section.tsx +37 -0
- package/src/web/components/settings/open-settings.ts +42 -0
- package/src/web/components/settings/resolve-open-settings-action.ts +45 -0
- package/src/web/components/settings/settings-body.tsx +110 -0
- package/src/web/components/settings/settings-categories.ts +102 -0
- package/src/web/components/settings/settings-category-list.tsx +56 -0
- package/src/web/components/settings/settings-category-rail.tsx +62 -0
- package/src/web/components/settings/settings-section-content.tsx +43 -0
- package/src/web/components/settings/settings-tab.tsx +15 -329
- package/src/web/components/settings/settings-window-content.tsx +24 -0
- package/src/web/hooks/use-chat.ts +38 -1
- package/src/web/hooks/use-global-keybindings.ts +3 -4
- package/src/web/hooks/use-url-sync.ts +30 -0
- package/src/web/hooks/use-usage.ts +23 -11
- package/src/web/lib/sidebar-tabs/tab-registry.ts +9 -9
- package/src/web/lib/slash-items-cache.ts +27 -7
- package/src/web/stores/panel-utils.ts +11 -0
- package/src/web/stores/settings-store.ts +4 -2
- package/src/web/stores/window-panel-actions.ts +1 -3
- package/src/web/stores/window-panel-persistence.ts +9 -4
- package/dist/web/assets/adaptive-context-menu-BbuRVTLn.js +0 -1
- package/dist/web/assets/ai-resource-editor-BCCOUzox.js +0 -1
- package/dist/web/assets/ai-settings-section-BEM1z7kp.js +0 -1
- package/dist/web/assets/api-client-D1CePgQ4.js +0 -1
- package/dist/web/assets/api-settings-CNtKkHqW.js +0 -1
- package/dist/web/assets/api-settings-Cubuqjsu.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-6konx-J1.js +0 -1
- package/dist/web/assets/chat-tab-CmXGsJGc.js +0 -15
- package/dist/web/assets/clipboard-B-gwJ8DF.js +0 -1
- package/dist/web/assets/code-editor-DtHXzNZ1.js +0 -7
- package/dist/web/assets/context-menu-DfWZAxQy.js +0 -1
- package/dist/web/assets/database-viewer-GVaQVMdb.js +0 -1
- package/dist/web/assets/dialog-BSfuiTkJ.js +0 -1
- package/dist/web/assets/diff-viewer-BG5_fH1E.js +0 -4
- package/dist/web/assets/explorer-body-BPFnjxYj.js +0 -2
- package/dist/web/assets/explorer-window-content-Da1T9uM4.js +0 -1
- package/dist/web/assets/extension-store-Bhu--fPX.js +0 -1
- package/dist/web/assets/external-link-B6Hl0U4X.js +0 -1
- package/dist/web/assets/eye-off-CUdzgmX8.js +0 -1
- package/dist/web/assets/file-store-6HW2Lfl4.js +0 -1
- package/dist/web/assets/git-log-panel-DNC9fV0M.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-ikUwsekd.js +0 -1
- package/dist/web/assets/group-chat-tab-DbUkcOaJ.js +0 -1
- package/dist/web/assets/index-DTqbMiVc.js +0 -52
- package/dist/web/assets/index-SFFv2-VM.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BAgmboBp.js +0 -1
- package/dist/web/assets/input-D0ol5jW0.js +0 -1
- package/dist/web/assets/keybindings-store-BGkB72gM.js +0 -1
- package/dist/web/assets/keybindings-store-Bq6VWl5X.js +0 -1
- package/dist/web/assets/lock-CNxUGpTk.js +0 -1
- package/dist/web/assets/markdown-renderer-DEAVpo0C.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-CD_kl-jA.js +0 -1
- package/dist/web/assets/monitor-smartphone-DLZ6Q05s.js +0 -1
- package/dist/web/assets/notification-store-Dgbsw9p9.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-RVlbbDRS.js +0 -1
- package/dist/web/assets/panel-store-UOV8wrfR.js +0 -1
- package/dist/web/assets/pdf-preview-B2CJ_EKv.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-ZWb4izpk.js +0 -1
- package/dist/web/assets/play-DpEEBJTT.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-bsP5x1Bx.js +0 -1
- package/dist/web/assets/refresh-cw-BQU5g2CY.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-KKTRNjOL.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-view-C1ST4-Fm.js +0 -1
- package/dist/web/assets/remote-desktop-readiness-gate-BH3BE5wA.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-BaZJQt_N.js +0 -1
- package/dist/web/assets/rotate-ccw-CpjRo90J.js +0 -1
- package/dist/web/assets/scroll-area-lc0ZHORr.js +0 -1
- package/dist/web/assets/send-C_sLFt56.js +0 -1
- package/dist/web/assets/settings-store-zbPkG7o-.js +0 -2
- package/dist/web/assets/settings-tab-Cn-AqUP_.js +0 -1
- package/dist/web/assets/settings-tab-xyRpt2gq.js +0 -3
- package/dist/web/assets/shallow-CmPMAPte.js +0 -1
- package/dist/web/assets/sql-query-editor-B86NiZHI.js +0 -1
- package/dist/web/assets/sqlite-viewer-DB775F6C.js +0 -1
- package/dist/web/assets/switch-BmjKoIpR.js +0 -1
- package/dist/web/assets/system-monitor-body-BDxHQvy9.js +0 -2
- package/dist/web/assets/system-monitor-tab-BoTHLx6B.js +0 -1
- package/dist/web/assets/team-member-sheet-Cxz6h5Ne.js +0 -2
- package/dist/web/assets/team-member-window-content-CUSuRHCB.js +0 -1
- package/dist/web/assets/tool-cards-D3KSzd2K.js +0 -8
- package/dist/web/assets/trash-2-By4VMbC4.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-Dh1oxNdQ.js +0 -1
- package/dist/web/assets/triangle-alert-CUTURFbG.js +0 -1
- package/dist/web/assets/upload-BNlX8D07.js +0 -1
- package/dist/web/assets/use-monaco-theme-CVCye2kq.js +0 -1
- package/dist/web/assets/use-remote-desktop-display-choice-neUqyE3p.js +0 -1
- package/dist/web/assets/video-preview-DvpgBDhM.js +0 -1
- package/dist/web/assets/window-store-D0x_fxr0.js +0 -1
- package/src/web/components/chat/account-dialogs.tsx +0 -384
- package/src/web/components/settings/accounts-settings-section.tsx +0 -1090
- package/src/web/components/settings/codex-accounts-section.tsx +0 -198
- package/src/web/components/settings/telegram-settings-section.tsx +0 -113
- /package/dist/web/assets/{abap-C5DFF0s3.js → abap-h3QyH8MW.js} +0 -0
- /package/dist/web/assets/{actionscript-3-B0qyRNbc.js → actionscript-3-mkxkq-FN.js} +0 -0
- /package/dist/web/assets/{ada-bAJzplug.js → ada-DhISK_EH.js} +0 -0
- /package/dist/web/assets/{andromeeda-DUCpISy6.js → andromeeda-DnlWWesX.js} +0 -0
- /package/dist/web/assets/{apache-BhAr84Z_.js → apache-P5AK3sfA.js} +0 -0
- /package/dist/web/assets/{apex-CRv8YnNq.js → apex-CJUPehg2.js} +0 -0
- /package/dist/web/assets/{api-client-BzWM7XPR.js → api-client-oY1tC3tM.js} +0 -0
- /package/dist/web/assets/{applescript-CD-Cw8bt.js → applescript-Dx6GGbay.js} +0 -0
- /package/dist/web/assets/{apply-theme-DJ794zSQ.js → apply-theme-DxTXOAcV.js} +0 -0
- /package/dist/web/assets/{ara-BUzeQYoG.js → ara-95Kx3qKb.js} +0 -0
- /package/dist/web/assets/{asciidoc-CT2hTg-v.js → asciidoc-Dp0RbIej.js} +0 -0
- /package/dist/web/assets/{asm-CAu35JeY.js → asm-C82coUjU.js} +0 -0
- /package/dist/web/assets/{aurora-x-BeFJoIA1.js → aurora-x-BFc3zeop.js} +0 -0
- /package/dist/web/assets/{awk-Vau-DrGN.js → awk-ChjC0icr.js} +0 -0
- /package/dist/web/assets/{ayu-dark-CLe-OTsQ.js → ayu-dark-DYfUf_PB.js} +0 -0
- /package/dist/web/assets/{ayu-light-CJbQfu4M.js → ayu-light-Bh6O0plO.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-GWaKwdrx.js → ayu-mirage-DlNhHTGq.js} +0 -0
- /package/dist/web/assets/{ballerina-DWu0USI5.js → ballerina-Ceoll3Sk.js} +0 -0
- /package/dist/web/assets/{bat-_07Huw7E.js → bat-C7QS6Lfq.js} +0 -0
- /package/dist/web/assets/{beancount-EcvogrcF.js → beancount-5NMEAZgs.js} +0 -0
- /package/dist/web/assets/{berry-BBB87SY7.js → berry-BBL83Vbk.js} +0 -0
- /package/dist/web/assets/{bibtex-DbueS87A.js → bibtex-L7rPkPSG.js} +0 -0
- /package/dist/web/assets/{bicep-tcmXnUo4.js → bicep-BOwHIINh.js} +0 -0
- /package/dist/web/assets/{bird2-B2qwilIR.js → bird2-CWLsMom2.js} +0 -0
- /package/dist/web/assets/{c-Z58oivSt.js → c-CD4Ku5Cr.js} +0 -0
- /package/dist/web/assets/{c3-U40cGT9p.js → c3-CpMl-w3G.js} +0 -0
- /package/dist/web/assets/{cadence-BFcD-LyM.js → cadence-CvgF6ehN.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-CdCK2c2a.js → can-open-in-ppm-j3jh7fUo.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-RkC9AX6J.js → catppuccin-frappe-eo_dTm3F.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-Bsi4l-nO.js → catppuccin-latte-DoiMCU_7.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B8JIdTW-.js → catppuccin-macchiato-UQxsK2Vh.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-oH8wwrDj.js → catppuccin-mocha-J0i-8RNy.js} +0 -0
- /package/dist/web/assets/{clarity-C3WNeRCR.js → clarity-DXYM8-SA.js} +0 -0
- /package/dist/web/assets/{clojure-CFoAz7u9.js → clojure-DzamsL69.js} +0 -0
- /package/dist/web/assets/{cmake-Bcsqp9yR.js → cmake-CR6RP_T_.js} +0 -0
- /package/dist/web/assets/{codeowners-DC2Z7heP.js → codeowners-D1s6-IyY.js} +0 -0
- /package/dist/web/assets/{codeql-BfG68Al5.js → codeql-DAqNWMkS.js} +0 -0
- /package/dist/web/assets/{common-lisp-Z5P-lKQe.js → common-lisp-8e-KtitB.js} +0 -0
- /package/dist/web/assets/{coq-BJslqrnv.js → coq-UEq0WUcO.js} +0 -0
- /package/dist/web/assets/{csharp-DhhZTOTs.js → csharp-wxJ0TuCm.js} +0 -0
- /package/dist/web/assets/{css-DiFIpMfE.js → css-Cerub-yB.js} +0 -0
- /package/dist/web/assets/{csv-DViLbHxI.js → csv-Bh9_WD_h.js} +0 -0
- /package/dist/web/assets/{csv-parser-B2v8MoUy.js → csv-parser-BPIDt6ni.js} +0 -0
- /package/dist/web/assets/{cue-CoG3mg2I.js → cue-rzM1ztqU.js} +0 -0
- /package/dist/web/assets/{cypher-jTxC8NAB.js → cypher-DGaTQ8m0.js} +0 -0
- /package/dist/web/assets/{d-6hKIUAbU.js → d-DbkZGuhB.js} +0 -0
- /package/dist/web/assets/{dark-plus-D_-np2Sr.js → dark-plus-BO2bzZcA.js} +0 -0
- /package/dist/web/assets/{dart-CVpxLpVn.js → dart-BDhCoqVg.js} +0 -0
- /package/dist/web/assets/{data-grid-types-Dq_H1IPa.js → data-grid-types-CdAg-4W1.js} +0 -0
- /package/dist/web/assets/{dax-BPqJ8qIY.js → dax-Bh03stWh.js} +0 -0
- /package/dist/web/assets/{desktop-u_y5_Hr5.js → desktop-BOdr_fwq.js} +0 -0
- /package/dist/web/assets/{diff-Duj1XMrd.js → diff-Bb_gTsS2.js} +0 -0
- /package/dist/web/assets/{dist-CEZ4plgH.js → dist-BUMDu0av.js} +0 -0
- /package/dist/web/assets/{dist-aDSxTR_v.js → dist-DonmFWG8.js} +0 -0
- /package/dist/web/assets/{docker-8UGSCc7N.js → docker-BDsTF5ce.js} +0 -0
- /package/dist/web/assets/{dotenv-DVnj8kyR.js → dotenv-CbCWp9Ti.js} +0 -0
- /package/dist/web/assets/{dracula-D0cEmSg7.js → dracula-Cd1fxvoX.js} +0 -0
- /package/dist/web/assets/{dracula-soft-DfGS5Yfr.js → dracula-soft-B7eXkfXG.js} +0 -0
- /package/dist/web/assets/{dream-maker-qG8kMGTF.js → dream-maker-XlzuS21R.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-iVqyMLyT.js → emacs-lisp-CXV1Ki_7.js} +0 -0
- /package/dist/web/assets/{everforest-dark-Cruygd5U.js → everforest-dark-DuqR60Dg.js} +0 -0
- /package/dist/web/assets/{everforest-light-BiJa8RHL.js → everforest-light-q4BPnenD.js} +0 -0
- /package/dist/web/assets/{fennel-H6XSEIoz.js → fennel-B-e0TX2Q.js} +0 -0
- /package/dist/web/assets/{fish-ho_7CxX6.js → fish-Bzb6-TtF.js} +0 -0
- /package/dist/web/assets/{fluent-1l5ssJTF.js → fluent-B-szH2-f.js} +0 -0
- /package/dist/web/assets/{folder-symlink-Dx1hOk8X.js → folder-symlink-fQRoG_rV.js} +0 -0
- /package/dist/web/assets/{format-bytes-gQw5wzqa.js → format-bytes-CKXk9k2d.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-Bo1x233A.js → fortran-free-form-OS6SGYC0.js} +0 -0
- /package/dist/web/assets/{gdscript-DffFMp_k.js → gdscript-FYq4OP2d.js} +0 -0
- /package/dist/web/assets/{gdshader-CvnpAiAF.js → gdshader-BpG6P8rw.js} +0 -0
- /package/dist/web/assets/{genie-2eOvAEzW.js → genie-BS9RLAna.js} +0 -0
- /package/dist/web/assets/{gherkin-DL-5OUlV.js → gherkin-B_9FdtGs.js} +0 -0
- /package/dist/web/assets/{github-dark-DrTZprgG.js → github-dark-cfaIlnBV.js} +0 -0
- /package/dist/web/assets/{github-dark-default-_mDH8IMP.js → github-dark-default-B2Wv65He.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-C48hJuLn.js → github-dark-dimmed-CtM9X76s.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-BQ-zGGNe.js → github-dark-high-contrast-DLHyk0tG.js} +0 -0
- /package/dist/web/assets/{github-light-BYc90Yx8.js → github-light-DWEI10iG.js} +0 -0
- /package/dist/web/assets/{github-light-default-Daecj4J5.js → github-light-default-DcJkK1YN.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DoafCbsU.js → github-light-high-contrast-Bcx9iKDt.js} +0 -0
- /package/dist/web/assets/{gleam-hUWWjmnp.js → gleam-DVpiM6b2.js} +0 -0
- /package/dist/web/assets/{gn-7PJ3R70o.js → gn-B80DNc0i.js} +0 -0
- /package/dist/web/assets/{gnuplot-V-Pxck7b.js → gnuplot-D9i3MaT-.js} +0 -0
- /package/dist/web/assets/{go-ARF6DprD.js → go-CwCBf_Pt.js} +0 -0
- /package/dist/web/assets/{groovy-CKGBIPOz.js → groovy-D7mEfwmW.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-Cm9wqdI3.js → gruvbox-dark-hard-C4R4MOoH.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Cmsd6dG8.js → gruvbox-dark-medium-CZnFC0s8.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-BBGlGGCz.js → gruvbox-dark-soft-DFcJlEBl.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-BCR7KmqG.js → gruvbox-light-hard-B6IF_1L-.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-CP32bga8.js → gruvbox-light-medium-C19AZJv2.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-qjxk8ps6.js → gruvbox-light-soft-BAnpDm9B.js} +0 -0
- /package/dist/web/assets/{haskell-C5DzwGRa.js → haskell-CsXSYXRG.js} +0 -0
- /package/dist/web/assets/{haxe-C8f_jCVv.js → haxe-B1uyiI9t.js} +0 -0
- /package/dist/web/assets/{hcl-DetJkf6E.js → hcl-DlYKn5q9.js} +0 -0
- /package/dist/web/assets/{hjson-DeZhwIdo.js → hjson-C_0VMGJJ.js} +0 -0
- /package/dist/web/assets/{hlsl-SMIDLRyX.js → hlsl-zt2PEzbg.js} +0 -0
- /package/dist/web/assets/{horizon-BMAVDxMx.js → horizon-6wQfyOK0.js} +0 -0
- /package/dist/web/assets/{horizon-bright-3BaV_VGD.js → horizon-bright-CqI2qTim.js} +0 -0
- /package/dist/web/assets/{houston-BWBbEPvb.js → houston-BljpHDoG.js} +0 -0
- /package/dist/web/assets/{hy-DcsmE-Nm.js → hy-XzuL55dw.js} +0 -0
- /package/dist/web/assets/{image-gallery-Bqb-wjNw.js → image-gallery-Cj8nd4xW.js} +0 -0
- /package/dist/web/assets/{imba-CnkgnYMf.js → imba-CqhhjBrP.js} +0 -0
- /package/dist/web/assets/{ini-BnvTdau7.js → ini-CiFDi6cY.js} +0 -0
- /package/dist/web/assets/{java-BLFACXat.js → java-OsQgTL5-.js} +0 -0
- /package/dist/web/assets/{javascript-BKcFWENk.js → javascript-qWAOScxl.js} +0 -0
- /package/dist/web/assets/{json-3AJtJADf.js → json-RiAKsaD1.js} +0 -0
- /package/dist/web/assets/{json5-DLyDVlwe.js → json5-BeCnS3H2.js} +0 -0
- /package/dist/web/assets/{jsonc-B5DgF1jW.js → jsonc-CbTW1Mka.js} +0 -0
- /package/dist/web/assets/{jsonl-CV_O71a2.js → jsonl-BLSi_669.js} +0 -0
- /package/dist/web/assets/{jsonnet-DZfPLgYz.js → jsonnet-C3xAggou.js} +0 -0
- /package/dist/web/assets/{jssm-Cr1zYOsY.js → jssm-CLdiagC9.js} +0 -0
- /package/dist/web/assets/{jsx-VN-JS2_q.js → jsx-lYE1lb50.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-D9CcT3xg.js → kanagawa-dragon-Cd1vcrpD.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-DLXsB8fr.js → kanagawa-lotus-Dw3QKtk9.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-weDs3Wjz.js → kanagawa-wave-DIq5Zv5G.js} +0 -0
- /package/dist/web/assets/{kdl-0XF56jnT.js → kdl-BfDV0825.js} +0 -0
- /package/dist/web/assets/{keyboard-DBBaoQSu.js → keyboard-DiOYrs6C.js} +0 -0
- /package/dist/web/assets/{kotlin-BPkH9vzk.js → kotlin-DONxR4wD.js} +0 -0
- /package/dist/web/assets/{kusto-DMVpADb9.js → kusto-BDP63Fh5.js} +0 -0
- /package/dist/web/assets/{laserwave-Cl6T2cGP.js → laserwave-BeBrWApL.js} +0 -0
- /package/dist/web/assets/{lean-BvwzQGl1.js → lean-DOZc0N1A.js} +0 -0
- /package/dist/web/assets/{less-DI_03xkC.js → less-C3H9NlDv.js} +0 -0
- /package/dist/web/assets/{lib-v9x_qXgI.js → lib-xrSJ4EZX.js} +0 -0
- /package/dist/web/assets/{libesm-dcbw4_18.js → libesm-DLBE59ul.js} +0 -0
- /package/dist/web/assets/{light-plus-hIiIsQN_.js → light-plus-C_kTL608.js} +0 -0
- /package/dist/web/assets/{llvm-B5hjjSWc.js → llvm-Dsp27U3P.js} +0 -0
- /package/dist/web/assets/{loader-circle-DouKgIXy.js → loader-circle-DPMrnLQ2.js} +0 -0
- /package/dist/web/assets/{log-D28raQiE.js → log-BywQaTCy.js} +0 -0
- /package/dist/web/assets/{logo-6Cdd4ASb.js → logo-BirJgciO.js} +0 -0
- /package/dist/web/assets/{luau-CK9nKpOX.js → luau-Btrz5kJB.js} +0 -0
- /package/dist/web/assets/{make-CVnxKCB6.js → make-Kl7nFQrS.js} +0 -0
- /package/dist/web/assets/{markdown-B9mMQzD5.js → markdown-qsC4P8S-.js} +0 -0
- /package/dist/web/assets/{material-theme-BZSs2o0j.js → material-theme-CC__ui3p.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BeAQBzQ_.js → material-theme-darker-D4FxG2FZ.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BAe0IXcs.js → material-theme-lighter-zGqiWcxn.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CiPIf0Np.js → material-theme-ocean-CZIdTTB1.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-DJBb4_XN.js → material-theme-palenight-CEIs0GcK.js} +0 -0
- /package/dist/web/assets/{matlab-CY0dbp_s.js → matlab-Cf0aUjN9.js} +0 -0
- /package/dist/web/assets/{mdx-DU9zbF7h.js → mdx-CIsXjH86.js} +0 -0
- /package/dist/web/assets/{mermaid-UmmjGwdh.js → mermaid-B06WO9Qc.js} +0 -0
- /package/dist/web/assets/{min-dark-C59GkzOm.js → min-dark-z8fyp0FT.js} +0 -0
- /package/dist/web/assets/{min-light-B9QglrdQ.js → min-light-Dr-qesbW.js} +0 -0
- /package/dist/web/assets/{mipsasm-Dc6Jsr_D.js → mipsasm-CWHzYLcE.js} +0 -0
- /package/dist/web/assets/{mojo-D5ij-nGW.js → mojo-D9zXrKmv.js} +0 -0
- /package/dist/web/assets/{monitor-CxPV_fQI.js → monitor-C835ucWH.js} +0 -0
- /package/dist/web/assets/{monokai-QAtE_yCP.js → monokai-4b-AKOpX.js} +0 -0
- /package/dist/web/assets/{moonbit-BMtIdiTL.js → moonbit-Dl4dhOZJ.js} +0 -0
- /package/dist/web/assets/{move-BB_jMHYZ.js → move-DTYKJmXy.js} +0 -0
- /package/dist/web/assets/{narrat-BvvoKGVn.js → narrat-DnvXOS-o.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CqxtAsnF.js → nextflow-groovy-Di8QnEXg.js} +0 -0
- /package/dist/web/assets/{night-owl-CLoE9kjt.js → night-owl-Czh0yq-H.js} +0 -0
- /package/dist/web/assets/{night-owl-light-BwIFT5Xd.js → night-owl-light-D_OpZqOR.js} +0 -0
- /package/dist/web/assets/{nix-BKlnVT9r.js → nix-2MvcjpK3.js} +0 -0
- /package/dist/web/assets/{nord-B7R1KRAK.js → nord-CG_r4cyk.js} +0 -0
- /package/dist/web/assets/{nushell-CzUme-NJ.js → nushell-DINskMcU.js} +0 -0
- /package/dist/web/assets/{objective-c-D-UKKiOc.js → objective-c-Dv7BIicR.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BucWn4Fx.js → objective-cpp-CpqsCxPb.js} +0 -0
- /package/dist/web/assets/{ocaml-EJCNXMGo.js → ocaml-DwTsYSmW.js} +0 -0
- /package/dist/web/assets/{odin-CEjnRU_3.js → odin-G-4O4k4t.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-Bv469NX5.js → one-dark-pro-13Pv5SjI.js} +0 -0
- /package/dist/web/assets/{one-light-BlzO5RWI.js → one-light-Bi2e86mS.js} +0 -0
- /package/dist/web/assets/{openscad-Czl6alLw.js → openscad-D0WsOTij.js} +0 -0
- /package/dist/web/assets/{pascal-BI9fLvG4.js → pascal-DmDStuXr.js} +0 -0
- /package/dist/web/assets/{pkl-ChQLsyjU.js → pkl-Bl6Qjtuo.js} +0 -0
- /package/dist/web/assets/{plastic-DGL3mtdL.js → plastic-UeSVSjTI.js} +0 -0
- /package/dist/web/assets/{plsql-BJDjqcWp.js → plsql-DreFC689.js} +0 -0
- /package/dist/web/assets/{po-DK5O3OpF.js → po-ZIjccxsX.js} +0 -0
- /package/dist/web/assets/{poimandres-CnuqZT4T.js → poimandres-CvQHJPrI.js} +0 -0
- /package/dist/web/assets/{polar-DCsUxSsQ.js → polar-BrMB0Kgh.js} +0 -0
- /package/dist/web/assets/{portal-container-context-D-495m5C.js → portal-container-context-a399Hd9F.js} +0 -0
- /package/dist/web/assets/{postcss-8UjdFmis.js → postcss-pgUsil1T.js} +0 -0
- /package/dist/web/assets/{powerquery-CAp0CGQj.js → powerquery-CTsZkCg_.js} +0 -0
- /package/dist/web/assets/{powershell-CbBsKZJP.js → powershell-CcwVuBAp.js} +0 -0
- /package/dist/web/assets/{prisma-BnNpXCJN.js → prisma-spjnAI5W.js} +0 -0
- /package/dist/web/assets/{prolog-CC-l9E8Y.js → prolog-ChCkZpDO.js} +0 -0
- /package/dist/web/assets/{proto-CV_1k2rL.js → proto-wVGqK4gB.js} +0 -0
- /package/dist/web/assets/{puppet-2D8HU2bv.js → puppet-CED7iwPQ.js} +0 -0
- /package/dist/web/assets/{purescript-BEJAORgU.js → purescript-u9lGiQKV.js} +0 -0
- /package/dist/web/assets/{python-BBdbb7nn.js → python-DeqTx6ef.js} +0 -0
- /package/dist/web/assets/{qmldir-D5ZYI4Ie.js → qmldir-BH1uoPrX.js} +0 -0
- /package/dist/web/assets/{qss-BOJVGyXE.js → qss-CUVKA_13.js} +0 -0
- /package/dist/web/assets/{r-B5dlEtkj.js → r-BqdPzqYH.js} +0 -0
- /package/dist/web/assets/{racket-BUAEygJ6.js → racket-C2eTgPN3.js} +0 -0
- /package/dist/web/assets/{raku-BG0Ss0ID.js → raku-uAay1sWx.js} +0 -0
- /package/dist/web/assets/{react-B549sK_T.js → react-X4jUntEf.js} +0 -0
- /package/dist/web/assets/{red-C5yyQbtG.js → red-DTZP1ZfC.js} +0 -0
- /package/dist/web/assets/{reg-DHoBWtLf.js → reg-Cdk3YsxY.js} +0 -0
- /package/dist/web/assets/{regexp-DDtJrlKv.js → regexp-Bpa2aS7K.js} +0 -0
- /package/dist/web/assets/{rel-CR6WJDRD.js → rel-bNsb1-3L.js} +0 -0
- /package/dist/web/assets/{resolve-theme-4fexHWl6.js → resolve-theme-DQk_YcsT.js} +0 -0
- /package/dist/web/assets/{riscv-2V9tC-nx.js → riscv-DSaubByW.js} +0 -0
- /package/dist/web/assets/{ron-QLTmqGUw.js → ron-DbQzYM6i.js} +0 -0
- /package/dist/web/assets/{rose-pine-be0__X3A.js → rose-pine-C7RsprTu.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-n_WQJwsW.js → rose-pine-dawn-VP976YzL.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-t2aLtzgB.js → rose-pine-moon-XHZNhOqd.js} +0 -0
- /package/dist/web/assets/{rosmsg-Ce1JD8vt.js → rosmsg-BVQlx0Lj.js} +0 -0
- /package/dist/web/assets/{rust-5fHH-ZoW.js → rust-VHaQ3rh3.js} +0 -0
- /package/dist/web/assets/{sass-DPmGFiOJ.js → sass-CO-kMkwD.js} +0 -0
- /package/dist/web/assets/{save-DYLiVzU8.js → save-CipGrG1k.js} +0 -0
- /package/dist/web/assets/{scala-BRMVFbZF.js → scala-cY0GXkiz.js} +0 -0
- /package/dist/web/assets/{scheme-BMSJWevF.js → scheme-D4LNZE0X.js} +0 -0
- /package/dist/web/assets/{sdbl-DTZ-pEHI.js → sdbl-Bh2Fd2cn.js} +0 -0
- /package/dist/web/assets/{shellscript-6-9yg5Cz.js → shellscript-KKclmCnt.js} +0 -0
- /package/dist/web/assets/{shield-check-CUI36aSc.js → shield-check-BsG3LV8i.js} +0 -0
- /package/dist/web/assets/{shield-off-GdkkVhb3.js → shield-off-7jKWpJBD.js} +0 -0
- /package/dist/web/assets/{slack-dark-DKJgiR7e.js → slack-dark-D6YQ-fqf.js} +0 -0
- /package/dist/web/assets/{slack-ochin-WjehVgyK.js → slack-ochin-CDf66CDj.js} +0 -0
- /package/dist/web/assets/{smalltalk-BChf4SN1.js → smalltalk-D2OH_QIl.js} +0 -0
- /package/dist/web/assets/{snazzy-light-C1WAwOx6.js → snazzy-light-CQeF8G1K.js} +0 -0
- /package/dist/web/assets/{solarized-dark-BxREZrGx.js → solarized-dark-LTPf1iLZ.js} +0 -0
- /package/dist/web/assets/{solarized-light-DG29B1pR.js → solarized-light-Ose1n88k.js} +0 -0
- /package/dist/web/assets/{solidity-bDbtGwdR.js → solidity-lFuOMHki.js} +0 -0
- /package/dist/web/assets/{splunk-Ceqx5njI.js → splunk-Bov-sB5S.js} +0 -0
- /package/dist/web/assets/{sql-OwMEKbz8.js → sql-BKyZ6wmk.js} +0 -0
- /package/dist/web/assets/{ssh-config-WAR3lu2k.js → ssh-config-CqJoMGMb.js} +0 -0
- /package/dist/web/assets/{stylus-Cjmoz3d8.js → stylus-Bf5pWRLI.js} +0 -0
- /package/dist/web/assets/{swift-S_ZSBG1q.js → swift-D5d1hn_u.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DiUeWMSh.js → synthwave-84-B4c6YAgO.js} +0 -0
- /package/dist/web/assets/{system-verilog-RRjqPjUV.js → system-verilog-D7fXyDsI.js} +0 -0
- /package/dist/web/assets/{systemd-BD3SkXoU.js → systemd-oNagzlBK.js} +0 -0
- /package/dist/web/assets/{table-D5Q4ap4W.js → table-CRJsmfeg.js} +0 -0
- /package/dist/web/assets/{talonscript-DWefISOm.js → talonscript-C6Nqm9Dm.js} +0 -0
- /package/dist/web/assets/{tasl-DCjSS-09.js → tasl-CaWy-cBX.js} +0 -0
- /package/dist/web/assets/{tcl-DTYqT_66.js → tcl-C435P5Mq.js} +0 -0
- /package/dist/web/assets/{terraform-CkbP5QnR.js → terraform-CVDxqdPf.js} +0 -0
- /package/dist/web/assets/{text-wrap-DBvQD3kc.js → text-wrap-oiekm5SB.js} +0 -0
- /package/dist/web/assets/{tokyo-night-qVSqO9xy.js → tokyo-night-Uhs17tQ3.js} +0 -0
- /package/dist/web/assets/{toml-D9F5Osqs.js → toml-j6fPOJ6P.js} +0 -0
- /package/dist/web/assets/{tsv-C5tpHoxm.js → tsv-h78O6xrL.js} +0 -0
- /package/dist/web/assets/{tsx-CMWkGtGm.js → tsx-Bol-vCrQ.js} +0 -0
- /package/dist/web/assets/{turtle-Cn4z3xOi.js → turtle-Dw8eFf04.js} +0 -0
- /package/dist/web/assets/{typescript-DSyAEWsn.js → typescript-Dtz7DPci.js} +0 -0
- /package/dist/web/assets/{typespec-B93Qap7y.js → typespec-BIOQ7dMP.js} +0 -0
- /package/dist/web/assets/{typst-DtlOmFlX.js → typst-CoaGMjzu.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-B9UZNwSm.js → use-is-mobile-CYW3FJFE.js} +0 -0
- /package/dist/web/assets/{use-visual-viewport-Sfb6CZ80.js → use-visual-viewport-rnvuooQZ.js} +0 -0
- /package/dist/web/assets/{utils-QHe_Q6UF.js → utils-C9OjCrou.js} +0 -0
- /package/dist/web/assets/{v-Dfzf-szS.js → v-BoqFdpJ7.js} +0 -0
- /package/dist/web/assets/{vala-CyiQZQ9f.js → vala-COBpNnCC.js} +0 -0
- /package/dist/web/assets/{vb-C7DGSldd.js → vb-DxI86znh.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-BHKF_Nsf.js → vendor-xterm-BaMOj65g.js} +0 -0
- /package/dist/web/assets/{verilog-kF-hClY2.js → verilog-DcTAamuQ.js} +0 -0
- /package/dist/web/assets/{vesper-BmKcADuz.js → vesper-DO298JSv.js} +0 -0
- /package/dist/web/assets/{vhdl-CDnb16KJ.js → vhdl-Dq-bDVSN.js} +0 -0
- /package/dist/web/assets/{viml-B8cs6Ady.js → viml-zHTRWuQh.js} +0 -0
- /package/dist/web/assets/{vitesse-black-BR6o9Gto.js → vitesse-black-jlYMJCBf.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-Dye14-KB.js → vitesse-dark-DS0sUlSZ.js} +0 -0
- /package/dist/web/assets/{vitesse-light-BLpswIxf.js → vitesse-light-HSeZ2YhG.js} +0 -0
- /package/dist/web/assets/{vyper-DU51Z97W.js → vyper-BQfKVO3M.js} +0 -0
- /package/dist/web/assets/{wasm-ChtccNfv.js → wasm-B1xp9bLp.js} +0 -0
- /package/dist/web/assets/{wasm-CVqlJAR2.js → wasm-B6mCuI8h.js} +0 -0
- /package/dist/web/assets/{wenyan-DhCa3hCn.js → wenyan-CfpS05sP.js} +0 -0
- /package/dist/web/assets/{wgsl-Bj1Ec4vv.js → wgsl-14GDsWRD.js} +0 -0
- /package/dist/web/assets/{wikitext-C90DHNa0.js → wikitext-Ccbq5EVX.js} +0 -0
- /package/dist/web/assets/{wit-DUgbOD7Z.js → wit-C19hb3R5.js} +0 -0
- /package/dist/web/assets/{wolfram-BgwUqD1I.js → wolfram-B-ZSwGwP.js} +0 -0
- /package/dist/web/assets/{x-DiOUR_mp.js → x-YDu4m2Iq.js} +0 -0
- /package/dist/web/assets/{yaml-DEQulj-8.js → yaml-mw_JUeio.js} +0 -0
- /package/dist/web/assets/{zenscript-C7Jp7TgT.js → zenscript-WbzDRjvF.js} +0 -0
- /package/dist/web/assets/{zig-9x4qXYLg.js → zig-F0vvImTJ.js} +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { UsageInfo } from "../../types/chat.ts";
|
|
2
|
+
import {
|
|
3
|
+
AMBIENT_ACCOUNT_KEY,
|
|
4
|
+
FETCH_TIMEOUT_MS,
|
|
5
|
+
type ProviderUsageSource,
|
|
6
|
+
} from "./usage-source.ts";
|
|
7
|
+
import {
|
|
8
|
+
getCachedUsageEntry,
|
|
9
|
+
isRecentlyFailed,
|
|
10
|
+
setCachedFailure,
|
|
11
|
+
setCachedUsage,
|
|
12
|
+
invalidateCachedUsage,
|
|
13
|
+
} from "./usage-memory-cache.ts";
|
|
14
|
+
import { readStoredUsage, writeStoredUsage } from "./usage-snapshot-store.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Registry of provider usage sources, and the read/refresh path over them.
|
|
18
|
+
*
|
|
19
|
+
* The rule the whole layer exists to enforce: **reads never wait on a provider.**
|
|
20
|
+
* `getUsage` is synchronous and answers from memory, falling back to the stored
|
|
21
|
+
* snapshot. Anything that talks to a provider happens on the background sweep or
|
|
22
|
+
* behind an explicit refresh, both of which are bounded by a timeout.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const sources = new Map<string, ProviderUsageSource>();
|
|
26
|
+
|
|
27
|
+
export function registerUsageSource(source: ProviderUsageSource): void {
|
|
28
|
+
sources.set(source.providerId, source);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getUsageSource(providerId: string): ProviderUsageSource | undefined {
|
|
32
|
+
return sources.get(providerId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function listUsageSources(): ProviderUsageSource[] {
|
|
36
|
+
return [...sources.values()];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Test seam: drop every registered source. */
|
|
40
|
+
export function _clearUsageSources(): void {
|
|
41
|
+
sources.clear();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Latest known usage for one account, without ever touching the provider.
|
|
46
|
+
*
|
|
47
|
+
* Memory first, then the stored snapshot — which is what makes a percentage
|
|
48
|
+
* appear immediately after a restart, before the first sweep lands. Returns an
|
|
49
|
+
* empty object when nothing has ever been recorded; the caller cannot tell that
|
|
50
|
+
* apart from "no limits reported", and does not need to.
|
|
51
|
+
*/
|
|
52
|
+
export function getUsage(providerId: string, accountId = AMBIENT_ACCOUNT_KEY): UsageInfo {
|
|
53
|
+
const cached = getCachedUsageEntry(providerId, accountId);
|
|
54
|
+
if (cached) return cached;
|
|
55
|
+
const stored = readStoredUsage(providerId, accountId);
|
|
56
|
+
if (stored) {
|
|
57
|
+
// Promote into memory so the next read skips the query, but keep the
|
|
58
|
+
// provider's own freshness clock: this value is as old as it ever was.
|
|
59
|
+
setCachedUsage(providerId, accountId, stored);
|
|
60
|
+
return stored;
|
|
61
|
+
}
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Reject if `promise` has not settled within `ms`, naming what timed out. */
|
|
66
|
+
function withTimeout<T>(promise: Promise<T>, ms: number, what: string): Promise<T> {
|
|
67
|
+
return new Promise<T>((resolve, reject) => {
|
|
68
|
+
const timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms}ms`)), ms);
|
|
69
|
+
promise.then(
|
|
70
|
+
(v) => { clearTimeout(timer); resolve(v); },
|
|
71
|
+
(e) => { clearTimeout(timer); reject(e); },
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Concurrent refreshes of the same account share one in-flight fetch.
|
|
78
|
+
*
|
|
79
|
+
* Each attempt carries a token so its cleanup can tell "I am still the current
|
|
80
|
+
* attempt" from "I was abandoned and something newer took my place".
|
|
81
|
+
*/
|
|
82
|
+
const inflight = new Map<string, { token: object; promise: Promise<UsageInfo> }>();
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Fetch one account live, then cache and store it.
|
|
86
|
+
*
|
|
87
|
+
* Failures are swallowed into the last known value rather than propagated: the
|
|
88
|
+
* callers are a background sweep and a toolbar refresh, and neither has anything
|
|
89
|
+
* useful to do with the error beyond showing what it already had. The failure is
|
|
90
|
+
* still remembered so it does not turn into a retry per request.
|
|
91
|
+
*/
|
|
92
|
+
export async function refreshUsage(
|
|
93
|
+
providerId: string,
|
|
94
|
+
accountId = AMBIENT_ACCOUNT_KEY,
|
|
95
|
+
): Promise<UsageInfo> {
|
|
96
|
+
const source = sources.get(providerId);
|
|
97
|
+
if (!source) return {};
|
|
98
|
+
|
|
99
|
+
const key = `${providerId}\0${accountId}`;
|
|
100
|
+
const existing = inflight.get(key);
|
|
101
|
+
if (existing) return existing.promise;
|
|
102
|
+
|
|
103
|
+
// Identity for this attempt. Comparing tokens rather than the promise avoids
|
|
104
|
+
// the closure having to reference the very promise it is producing.
|
|
105
|
+
const token = {};
|
|
106
|
+
const run = (async () => {
|
|
107
|
+
try {
|
|
108
|
+
const usage = await withTimeout(
|
|
109
|
+
source.fetch(accountId),
|
|
110
|
+
source.fetchTimeoutMs ?? FETCH_TIMEOUT_MS,
|
|
111
|
+
`${providerId} usage fetch`,
|
|
112
|
+
);
|
|
113
|
+
setCachedUsage(providerId, accountId, usage);
|
|
114
|
+
writeStoredUsage(providerId, accountId, usage);
|
|
115
|
+
return usage;
|
|
116
|
+
} catch (e) {
|
|
117
|
+
setCachedFailure(providerId, accountId);
|
|
118
|
+
console.error(`[usage] ${providerId}/${accountId || "ambient"}:`, (e as Error).message);
|
|
119
|
+
return readStoredUsage(providerId, accountId) ?? {};
|
|
120
|
+
} finally {
|
|
121
|
+
// Only clear if this is still the current attempt. An abandoned fetch
|
|
122
|
+
// that finishes late must not evict the entry a newer one just installed,
|
|
123
|
+
// or that newer caller would be silently un-deduped.
|
|
124
|
+
if (inflight.get(key)?.token === token) inflight.delete(key);
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
127
|
+
|
|
128
|
+
inflight.set(key, { token, promise: run });
|
|
129
|
+
return run;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Test seam: forget every in-flight fetch so the next call starts a fresh one. */
|
|
133
|
+
export function _clearInflightUsage(): void {
|
|
134
|
+
inflight.clear();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Read, fetching only if nothing usable is known yet.
|
|
139
|
+
*
|
|
140
|
+
* Used by the request path so a cold start still produces a number, while a
|
|
141
|
+
* remembered failure short-circuits to the stored value instead of re-attempting
|
|
142
|
+
* a fetch on every request.
|
|
143
|
+
*/
|
|
144
|
+
export async function getOrFetchUsage(
|
|
145
|
+
providerId: string,
|
|
146
|
+
accountId = AMBIENT_ACCOUNT_KEY,
|
|
147
|
+
): Promise<UsageInfo> {
|
|
148
|
+
const known = getUsage(providerId, accountId);
|
|
149
|
+
if (Object.keys(known).length > 0) return known;
|
|
150
|
+
if (isRecentlyFailed(providerId, accountId)) return known;
|
|
151
|
+
return refreshUsage(providerId, accountId);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Force the next read of this provider (or one account) to go back to the source. */
|
|
155
|
+
export function invalidateUsage(providerId: string, accountId?: string): void {
|
|
156
|
+
invalidateCachedUsage(providerId, accountId);
|
|
157
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCOUNT_STAGGER_MS,
|
|
3
|
+
AMBIENT_ACCOUNT_KEY,
|
|
4
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
5
|
+
type ProviderUsageSource,
|
|
6
|
+
} from "./usage-source.ts";
|
|
7
|
+
import { listUsageSources, refreshUsage } from "./usage-registry.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The one background loop that keeps every provider's quota warm.
|
|
11
|
+
*
|
|
12
|
+
* Sweeping in the background is what makes reads instant: by the time the
|
|
13
|
+
* toolbar asks, the value is already in memory. Both providers run on the same
|
|
14
|
+
* cadence and through the same code, so a new provider gets this by registering
|
|
15
|
+
* a source — there is no second timer to write.
|
|
16
|
+
*
|
|
17
|
+
* Survives `bun --hot` reloads through globalThis: module state resets on
|
|
18
|
+
* reload, so a module-level timer would leak a new interval per reload while
|
|
19
|
+
* the old one kept firing.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const HOT_KEY = "__PPM_PROVIDER_USAGE_POLL__" as const;
|
|
23
|
+
|
|
24
|
+
interface HotState {
|
|
25
|
+
timers: Map<string, ReturnType<typeof setTimeout>>;
|
|
26
|
+
inflight: Map<string, Promise<void>>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const hot: HotState = ((globalThis as Record<string, unknown>)[HOT_KEY] ??= {
|
|
30
|
+
timers: new Map(),
|
|
31
|
+
inflight: new Map(),
|
|
32
|
+
}) as HotState;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One pass over a source's accounts.
|
|
36
|
+
*
|
|
37
|
+
* Accounts are walked in series with a gap between them: a sweep is maintenance,
|
|
38
|
+
* not something worth spending a burst of subprocesses or API calls on. Each
|
|
39
|
+
* account is independent — `refreshUsage` never rejects, so a failing account
|
|
40
|
+
* does not cut the sweep short for the ones after it.
|
|
41
|
+
*/
|
|
42
|
+
async function sweepSource(source: ProviderUsageSource): Promise<void> {
|
|
43
|
+
const accountIds = source.listAccountIds();
|
|
44
|
+
// No account store configured: the provider reads one ambient login instead.
|
|
45
|
+
const targets = accountIds.length > 0 ? accountIds : [AMBIENT_ACCOUNT_KEY];
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < targets.length; i++) {
|
|
48
|
+
const accountId = targets[i]!;
|
|
49
|
+
try {
|
|
50
|
+
if (await source.shouldSkip?.(accountId)) continue;
|
|
51
|
+
} catch {
|
|
52
|
+
continue; // A source that cannot decide is treated as "skip", not "fetch".
|
|
53
|
+
}
|
|
54
|
+
await refreshUsage(source.providerId, accountId);
|
|
55
|
+
if (i < targets.length - 1) await new Promise((r) => setTimeout(r, ACCOUNT_STAGGER_MS));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Sweep one source now; concurrent callers share the in-flight pass. */
|
|
60
|
+
export function sweepUsageSource(source: ProviderUsageSource): Promise<void> {
|
|
61
|
+
const existing = hot.inflight.get(source.providerId);
|
|
62
|
+
if (existing) return existing;
|
|
63
|
+
|
|
64
|
+
const pass = sweepSource(source)
|
|
65
|
+
.catch((e) => console.error(`[usage] sweep ${source.providerId}:`, (e as Error).message))
|
|
66
|
+
.finally(() => {
|
|
67
|
+
// Only clear if still the current pass, so a stale finally cannot drop a newer one.
|
|
68
|
+
if (hot.inflight.get(source.providerId) === pass) hot.inflight.delete(source.providerId);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
hot.inflight.set(source.providerId, pass);
|
|
72
|
+
return pass;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Start the background loop for every registered source.
|
|
77
|
+
*
|
|
78
|
+
* Each source is swept once immediately — a fresh server should not show blank
|
|
79
|
+
* percentages for a whole interval — and then rescheduled after each pass
|
|
80
|
+
* completes rather than on a fixed interval, so a slow sweep cannot overlap the
|
|
81
|
+
* next one.
|
|
82
|
+
*/
|
|
83
|
+
export function startProviderUsagePolling(): void {
|
|
84
|
+
stopProviderUsagePolling();
|
|
85
|
+
for (const source of listUsageSources()) {
|
|
86
|
+
const interval = source.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
87
|
+
const scheduleNext = () => {
|
|
88
|
+
hot.timers.set(source.providerId, setTimeout(async () => {
|
|
89
|
+
await sweepUsageSource(source);
|
|
90
|
+
scheduleNext();
|
|
91
|
+
}, interval));
|
|
92
|
+
};
|
|
93
|
+
sweepUsageSource(source).then(scheduleNext, scheduleNext);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function stopProviderUsagePolling(): void {
|
|
98
|
+
for (const timer of hot.timers.values()) clearTimeout(timer);
|
|
99
|
+
hot.timers.clear();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Abandon any in-flight sweep so the next one starts fresh.
|
|
104
|
+
*
|
|
105
|
+
* Needed when a sweep is known to be stuck: without it the hung pass is handed
|
|
106
|
+
* to every later caller, and a provider that stops answering blocks its own
|
|
107
|
+
* refresh indefinitely. The guard in {@link sweepUsageSource} means the
|
|
108
|
+
* abandoned pass cannot evict its replacement when it eventually settles.
|
|
109
|
+
*/
|
|
110
|
+
export function _clearInflightSweeps(): void {
|
|
111
|
+
hot.inflight.clear();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Test seam: true while any source has a scheduled pass. */
|
|
115
|
+
export function _isUsagePollingActive(): boolean {
|
|
116
|
+
return hot.timers.size > 0;
|
|
117
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { UsageInfo, LimitBucket } from "../../types/chat.ts";
|
|
2
|
+
import {
|
|
3
|
+
insertLimitSnapshot,
|
|
4
|
+
getLatestLimitSnapshot,
|
|
5
|
+
getLatestSnapshotForAccount,
|
|
6
|
+
touchSnapshotTimestamp,
|
|
7
|
+
cleanupOldLimitSnapshots,
|
|
8
|
+
type LimitSnapshotRow,
|
|
9
|
+
} from "../db.service.ts";
|
|
10
|
+
import { AMBIENT_ACCOUNT_KEY } from "./usage-source.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The durable half of the usage layer: turning a provider's `UsageInfo` into a
|
|
14
|
+
* snapshot row and back, for any provider.
|
|
15
|
+
*
|
|
16
|
+
* Persistence is what lets the toolbar show a number the moment the server comes
|
|
17
|
+
* back up, before the first background sweep has finished. Without it a restart
|
|
18
|
+
* blanks every percentage for as long as the first fetch takes.
|
|
19
|
+
*
|
|
20
|
+
* A row is only written when something actually moved. Otherwise the newest row
|
|
21
|
+
* has its timestamp touched, so "last fetched" stays honest without growing a
|
|
22
|
+
* row per account per sweep forever.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Recompute a bucket's countdown against the current clock. */
|
|
26
|
+
function toBucket(util: number, resetsAt: string, windowHours: number): LimitBucket {
|
|
27
|
+
const diff = resetsAt ? new Date(resetsAt).getTime() - Date.now() : 0;
|
|
28
|
+
const totalMins = diff > 0 ? Math.ceil(diff / 60_000) : 0;
|
|
29
|
+
return {
|
|
30
|
+
utilization: util,
|
|
31
|
+
resetsAt,
|
|
32
|
+
resetsInMinutes: windowHours <= 5 ? totalMins : null,
|
|
33
|
+
resetsInHours: windowHours > 5 ? Math.round((totalMins / 60) * 100) / 100 : null,
|
|
34
|
+
windowHours,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Snapshot row → UsageInfo, with the flat mirror fields the chat toolbar reads. */
|
|
39
|
+
export function snapshotToUsage(row: LimitSnapshotRow): UsageInfo & { lastFetchedAt?: string } {
|
|
40
|
+
// SQLite's datetime('now') is UTC but carries no Z, which JS would read as local time.
|
|
41
|
+
const iso = row.recorded_at.endsWith("Z")
|
|
42
|
+
? row.recorded_at
|
|
43
|
+
: row.recorded_at.replace(" ", "T") + "Z";
|
|
44
|
+
const usage: UsageInfo & { lastFetchedAt?: string } = { lastFetchedAt: iso };
|
|
45
|
+
if (row.five_hour_util != null) {
|
|
46
|
+
usage.session = toBucket(row.five_hour_util, row.five_hour_resets_at ?? "", 5);
|
|
47
|
+
usage.fiveHour = row.five_hour_util;
|
|
48
|
+
if (row.five_hour_resets_at) usage.fiveHourResetsAt = row.five_hour_resets_at;
|
|
49
|
+
}
|
|
50
|
+
if (row.weekly_util != null) {
|
|
51
|
+
usage.weekly = toBucket(row.weekly_util, row.weekly_resets_at ?? "", 168);
|
|
52
|
+
usage.sevenDay = row.weekly_util;
|
|
53
|
+
if (row.weekly_resets_at) usage.sevenDayResetsAt = row.weekly_resets_at;
|
|
54
|
+
}
|
|
55
|
+
if (row.weekly_opus_util != null) {
|
|
56
|
+
usage.weeklyOpus = toBucket(row.weekly_opus_util, row.weekly_opus_resets_at ?? "", 168);
|
|
57
|
+
}
|
|
58
|
+
if (row.weekly_sonnet_util != null) {
|
|
59
|
+
usage.weeklySonnet = toBucket(row.weekly_sonnet_util, row.weekly_sonnet_resets_at ?? "", 168);
|
|
60
|
+
}
|
|
61
|
+
return usage;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Read the newest stored snapshot for one provider account, or undefined. */
|
|
65
|
+
export function readStoredUsage(
|
|
66
|
+
providerId: string,
|
|
67
|
+
accountId: string,
|
|
68
|
+
): (UsageInfo & { lastFetchedAt?: string }) | undefined {
|
|
69
|
+
const row = accountId === AMBIENT_ACCOUNT_KEY
|
|
70
|
+
? getLatestLimitSnapshot(providerId)
|
|
71
|
+
: getLatestSnapshotForAccount(accountId, providerId);
|
|
72
|
+
return row ? snapshotToUsage(row) : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* True when any stored bucket differs from the freshly fetched one.
|
|
77
|
+
*
|
|
78
|
+
* Utilizations compare with a tolerance rather than exactly: they are floats
|
|
79
|
+
* that round-trip through SQLite, and treating a last-digit wobble as a change
|
|
80
|
+
* would write a row on every single sweep.
|
|
81
|
+
*/
|
|
82
|
+
function hasChanged(usage: UsageInfo, last: LimitSnapshotRow | null): boolean {
|
|
83
|
+
if (!last) return true;
|
|
84
|
+
const moved = (fresh: number | null | undefined, stored: number | null) =>
|
|
85
|
+
fresh != null && (stored == null || Math.abs(fresh - stored) > 0.001);
|
|
86
|
+
if (moved(usage.session?.utilization, last.five_hour_util)) return true;
|
|
87
|
+
if (moved(usage.weekly?.utilization, last.weekly_util)) return true;
|
|
88
|
+
if (moved(usage.weeklyOpus?.utilization, last.weekly_opus_util)) return true;
|
|
89
|
+
if (moved(usage.weeklySonnet?.utilization, last.weekly_sonnet_util)) return true;
|
|
90
|
+
if (usage.session?.resetsAt && usage.session.resetsAt !== (last.five_hour_resets_at ?? "")) return true;
|
|
91
|
+
if (usage.weekly?.resetsAt && usage.weekly.resetsAt !== (last.weekly_resets_at ?? "")) return true;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Persist a freshly fetched value, writing a row only when a bucket moved.
|
|
97
|
+
*
|
|
98
|
+
* An account with nothing to report (every bucket absent) is not written at all:
|
|
99
|
+
* a row of nulls is indistinguishable from "never fetched" on read, so storing
|
|
100
|
+
* one buys nothing and only makes the history noisier.
|
|
101
|
+
*/
|
|
102
|
+
export function writeStoredUsage(providerId: string, accountId: string, usage: UsageInfo): void {
|
|
103
|
+
const hasAnyBucket = usage.session != null || usage.weekly != null
|
|
104
|
+
|| usage.weeklyOpus != null || usage.weeklySonnet != null;
|
|
105
|
+
if (!hasAnyBucket) return;
|
|
106
|
+
|
|
107
|
+
const id = accountId === AMBIENT_ACCOUNT_KEY ? null : accountId;
|
|
108
|
+
const last = id ? getLatestSnapshotForAccount(id, providerId) : getLatestLimitSnapshot(providerId);
|
|
109
|
+
if (!hasChanged(usage, last)) {
|
|
110
|
+
if (id) touchSnapshotTimestamp(id, providerId);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
insertLimitSnapshot({
|
|
114
|
+
provider: providerId,
|
|
115
|
+
account_id: id,
|
|
116
|
+
five_hour_util: usage.session?.utilization ?? null,
|
|
117
|
+
five_hour_resets_at: usage.session?.resetsAt ?? null,
|
|
118
|
+
weekly_util: usage.weekly?.utilization ?? null,
|
|
119
|
+
weekly_resets_at: usage.weekly?.resetsAt ?? null,
|
|
120
|
+
weekly_opus_util: usage.weeklyOpus?.utilization ?? null,
|
|
121
|
+
weekly_opus_resets_at: usage.weeklyOpus?.resetsAt ?? null,
|
|
122
|
+
weekly_sonnet_util: usage.weeklySonnet?.utilization ?? null,
|
|
123
|
+
weekly_sonnet_resets_at: usage.weeklySonnet?.resetsAt ?? null,
|
|
124
|
+
});
|
|
125
|
+
cleanupOldLimitSnapshots();
|
|
126
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { UsageInfo } from "../../types/chat.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What one provider must supply for its quota to be polled, cached, and stored
|
|
5
|
+
* by the shared usage layer.
|
|
6
|
+
*
|
|
7
|
+
* Everything scheduling-shaped — the background timer, the fetch timeout, the
|
|
8
|
+
* per-account cache, negative caching, staggering, in-flight de-duplication,
|
|
9
|
+
* writing to and reading back from the snapshot store — belongs to that layer,
|
|
10
|
+
* not here. A new provider implements the two required methods and inherits all
|
|
11
|
+
* of it; nothing in this interface knows about HTTP, JSON-RPC, or subprocesses.
|
|
12
|
+
*/
|
|
13
|
+
export interface ProviderUsageSource {
|
|
14
|
+
/** Matches the AIProvider id, and is what a snapshot row is scoped by. */
|
|
15
|
+
readonly providerId: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Accounts whose quota should be kept warm.
|
|
19
|
+
*
|
|
20
|
+
* An empty array is meaningful: it means this provider has no account store
|
|
21
|
+
* configured and reads a single ambient login instead, which the layer keys
|
|
22
|
+
* under {@link AMBIENT_ACCOUNT_KEY}.
|
|
23
|
+
*/
|
|
24
|
+
listAccountIds(): string[];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read one account's quota from the provider, live.
|
|
28
|
+
*
|
|
29
|
+
* Throw or reject on failure; do NOT return an empty object to signal one.
|
|
30
|
+
* The layer distinguishes the two: a rejection is cached briefly so a broken
|
|
31
|
+
* account cannot be retried on every request, while a resolved empty object
|
|
32
|
+
* is stored as a genuine "this account has no limits to report".
|
|
33
|
+
*
|
|
34
|
+
* `accountId` is {@link AMBIENT_ACCOUNT_KEY} when the provider has no
|
|
35
|
+
* account store.
|
|
36
|
+
*/
|
|
37
|
+
fetch(accountId: string): Promise<UsageInfo>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Skip this account on a background sweep without treating it as a failure —
|
|
41
|
+
* an expired token, a cooldown after a rate-limit, a login that cannot
|
|
42
|
+
* refresh. The last stored value stays readable.
|
|
43
|
+
*/
|
|
44
|
+
shouldSkip?(accountId: string): boolean | Promise<boolean>;
|
|
45
|
+
|
|
46
|
+
/** Poll period override. Defaults to {@link DEFAULT_POLL_INTERVAL_MS}. */
|
|
47
|
+
readonly pollIntervalMs?: number;
|
|
48
|
+
|
|
49
|
+
/** Per-fetch ceiling override. Defaults to {@link FETCH_TIMEOUT_MS}. */
|
|
50
|
+
readonly fetchTimeoutMs?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Cache/store key for a provider that reads one ambient login, not an account list. */
|
|
54
|
+
export const AMBIENT_ACCOUNT_KEY = "";
|
|
55
|
+
|
|
56
|
+
/** Both providers refresh on the same cadence; a source may override it. */
|
|
57
|
+
export const DEFAULT_POLL_INTERVAL_MS = 300_000; // 5 min
|
|
58
|
+
|
|
59
|
+
/** Gap between accounts inside one sweep, so a sweep is not a burst. */
|
|
60
|
+
export const ACCOUNT_STAGGER_MS = 1_000;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Ceiling on a single `fetch`. Codex spawns an app-server to answer, and that
|
|
64
|
+
* subprocess has been observed to accept the spawn and then never reply — with
|
|
65
|
+
* no ceiling the sweep waits forever, the HTTP route behind it never answers,
|
|
66
|
+
* and the subprocess is never closed. Claude's HTTP call has its own timeout
|
|
67
|
+
* but is covered here too, so one hung account cannot stall the whole sweep.
|
|
68
|
+
*/
|
|
69
|
+
export const FETCH_TIMEOUT_MS = 20_000;
|
|
70
|
+
|
|
71
|
+
/** How long a resolved value is served from memory before a re-read. */
|
|
72
|
+
export const USAGE_CACHE_TTL_MS = 300_000; // 5 min — matches the poll period
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* How long a FAILURE is remembered.
|
|
76
|
+
*
|
|
77
|
+
* Short, because the cause is usually transient (a login being refreshed, a
|
|
78
|
+
* subprocess losing a race). Non-zero, because without it every request that
|
|
79
|
+
* misses re-attempts a fetch: for codex that meant spawning another app-server
|
|
80
|
+
* per request, and the ones that hung were never reaped.
|
|
81
|
+
*/
|
|
82
|
+
export const USAGE_FAILURE_TTL_MS = 30_000;
|
|
@@ -42,6 +42,19 @@ export function getBuiltinSlashItems(): SlashItem[] {
|
|
|
42
42
|
}));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Built-ins that work no matter which provider the tab is on.
|
|
47
|
+
*
|
|
48
|
+
* `handler: "sdk"` commands (`/compact`, `/cost`, `/model`, …) are passed
|
|
49
|
+
* straight through to the Claude SDK, so they do nothing in a session driven by
|
|
50
|
+
* another runtime. The `ppm` and `client` ones are intercepted before any
|
|
51
|
+
* provider sees them — by the PPM server and the web UI respectively — so they
|
|
52
|
+
* belong in every picker.
|
|
53
|
+
*/
|
|
54
|
+
export function getHostBuiltinSlashItems(): SlashItem[] {
|
|
55
|
+
return getBuiltinSlashItems().filter((item) => item.handler !== "sdk");
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
/** Look up a builtin command by name (or alias) */
|
|
46
59
|
export function getBuiltinByName(name: string): BuiltinSlashCommand | undefined {
|
|
47
60
|
const lower = name.toLowerCase();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
import type { CodexSkill } from "../../providers/codex-app-server/codex-protocol.ts";
|
|
4
|
+
import type { SlashItem } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
/** Icons above this are not worth inlining into every picker response. */
|
|
7
|
+
const MAX_ICON_BYTES = 32 * 1024;
|
|
8
|
+
|
|
9
|
+
const ICON_MIME: Record<string, string> = {
|
|
10
|
+
".svg": "image/svg+xml",
|
|
11
|
+
".png": "image/png",
|
|
12
|
+
".webp": "image/webp",
|
|
13
|
+
".jpg": "image/jpeg",
|
|
14
|
+
".jpeg": "image/jpeg",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Map the skills codex reported for a session into picker items.
|
|
19
|
+
*
|
|
20
|
+
* These are deliberately NOT merged with the disk-discovered Claude-side list.
|
|
21
|
+
* A codex session cannot run a Claude skill and vice versa, so mixing them
|
|
22
|
+
* offers the user entries that do nothing.
|
|
23
|
+
*
|
|
24
|
+
* Every item is marked `$`: codex activates a skill from a `$name` mention in
|
|
25
|
+
* the prompt, not from a leading slash.
|
|
26
|
+
*/
|
|
27
|
+
export function codexSkillsToSlashItems(skills: CodexSkill[]): SlashItem[] {
|
|
28
|
+
return skills.map((s) => ({
|
|
29
|
+
type: "skill" as const,
|
|
30
|
+
name: s.name,
|
|
31
|
+
description: s.interface?.shortDescription || s.description || "",
|
|
32
|
+
// "system" is codex's own bundled set; anything else the user put there.
|
|
33
|
+
scope: s.scope === "system" ? ("bundled" as const) : ("user" as const),
|
|
34
|
+
invokeSigil: "$" as const,
|
|
35
|
+
displayName: s.interface?.displayName,
|
|
36
|
+
iconDataUri: readIconDataUri(s.interface?.iconSmall),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Read a skill icon into a data URI, or return undefined for anything that is
|
|
42
|
+
* missing, oversized, or of an unknown type.
|
|
43
|
+
*
|
|
44
|
+
* Never throws: a skill with a broken icon path still belongs in the picker,
|
|
45
|
+
* just without a picture.
|
|
46
|
+
*/
|
|
47
|
+
function readIconDataUri(path: string | undefined): string | undefined {
|
|
48
|
+
if (!path) return undefined;
|
|
49
|
+
const mime = ICON_MIME[extname(path).toLowerCase()];
|
|
50
|
+
if (!mime) return undefined;
|
|
51
|
+
try {
|
|
52
|
+
if (statSync(path).size > MAX_ICON_BYTES) return undefined;
|
|
53
|
+
return `data:${mime};base64,${readFileSync(path).toString("base64")}`;
|
|
54
|
+
} catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swap the leading `/` of a typed command for the sigil the target runtime
|
|
3
|
+
* actually recognises.
|
|
4
|
+
*
|
|
5
|
+
* PPM's picker is slash-driven everywhere, but a provider may resolve its own
|
|
6
|
+
* skills differently — codex activates one from a `$name` mention in the prompt
|
|
7
|
+
* and treats `/name` as ordinary text, so a picked skill would appear to be
|
|
8
|
+
* ignored. Only the sigil changes; arguments after the name are untouched.
|
|
9
|
+
*
|
|
10
|
+
* The rewrite is deliberately conservative: the first word must match a skill
|
|
11
|
+
* the runtime reported, so a slash command the runtime does not own (a PPM
|
|
12
|
+
* built-in, a typo, a bare `/`) is returned exactly as typed.
|
|
13
|
+
*/
|
|
14
|
+
export function applySkillSigil(content: string, skillNames: Set<string>, sigil: string): string {
|
|
15
|
+
const match = content.match(/^\/(\S+)/);
|
|
16
|
+
const name = match?.[1];
|
|
17
|
+
if (!name || !skillNames.has(name)) return content;
|
|
18
|
+
return sigil + content.slice(1);
|
|
19
|
+
}
|
|
@@ -35,6 +35,25 @@ export interface SlashItem {
|
|
|
35
35
|
tools?: string[];
|
|
36
36
|
/** Built-in only: which layer executes the command */
|
|
37
37
|
handler?: SlashHandler;
|
|
38
|
+
/**
|
|
39
|
+
* Sigil the runtime expects when the item is invoked. Absent means `/`, which
|
|
40
|
+
* is every Claude-side skill and command. Codex resolves its skills from a
|
|
41
|
+
* `$name` mention inside the prompt instead, so an item carrying `"$"` must be
|
|
42
|
+
* sent with that prefix or codex treats it as ordinary prose.
|
|
43
|
+
*/
|
|
44
|
+
invokeSigil?: "/" | "$";
|
|
45
|
+
/** Pretty label the runtime supplies for itself, when it does (codex system skills). */
|
|
46
|
+
displayName?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Runtime-supplied icon, inlined as a data URI rather than a URL.
|
|
49
|
+
*
|
|
50
|
+
* The source files sit inside the codex account home, which the credential
|
|
51
|
+
* path guard refuses on every generic file route — correctly, since
|
|
52
|
+
* `auth.json` is a sibling. Inlining keeps that door shut: the icon never
|
|
53
|
+
* needs a servable path, and the client never sends one back. They are small
|
|
54
|
+
* enough for this to be cheap (under 3 KB each).
|
|
55
|
+
*/
|
|
56
|
+
iconDataUri?: string;
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
export interface SkillRoot {
|