@hienlh/ppm 0.23.5 → 0.23.7
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 +89 -0
- package/CLAUDE.md +21 -1
- package/Dockerfile.test +8 -1
- 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 +30 -1
- package/dist/web/assets/accounts-settings-section-BYWnY9hH.js +1 -0
- package/dist/web/assets/accounts-settings-section-BYWnY9hH.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-BYWnY9hH.js.gz +0 -0
- package/dist/web/assets/{ai-resource-editor-DKcsVDle.js → ai-resource-editor-CY4n_Ccv.js} +1 -1
- package/dist/web/assets/ai-resource-editor-CY4n_Ccv.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-CY4n_Ccv.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-Bs3iUW4j.js +1 -0
- package/dist/web/assets/ai-settings-section-Bs3iUW4j.js.br +0 -0
- package/dist/web/assets/ai-settings-section-Bs3iUW4j.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-DDs11XV4.js +1 -0
- package/dist/web/assets/api-client-CJPnCOG-.js +1 -0
- package/dist/web/assets/{api-client-c8YOpTlT.js → api-client-D-SMbdiH.js} +1 -1
- package/dist/web/assets/api-client-D-SMbdiH.js.br +0 -0
- package/dist/web/assets/api-client-D-SMbdiH.js.gz +0 -0
- package/dist/web/assets/api-settings-BDkkhFMq.js +1 -0
- package/dist/web/assets/{api-settings-BmvNoCbn.js → api-settings-Bv9mLCty.js} +1 -1
- package/dist/web/assets/api-settings-Bv9mLCty.js.br +0 -0
- package/dist/web/assets/api-settings-Bv9mLCty.js.gz +0 -0
- package/dist/web/assets/{api-themes-6fOQJgM9.js → api-themes-3wW-zLzU.js} +1 -1
- package/dist/web/assets/appearance-settings-section-C0BoIBmz.js +1 -0
- package/dist/web/assets/appearance-settings-section-C0BoIBmz.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-C0BoIBmz.js.gz +0 -0
- package/dist/web/assets/apps-panel-DXAFsveZ.js +1 -0
- package/dist/web/assets/apps-panel-DXAFsveZ.js.br +0 -0
- package/dist/web/assets/apps-panel-DXAFsveZ.js.gz +0 -0
- package/dist/web/assets/{audio-preview-vaG2cB-k.js → audio-preview-BfES4Cx5.js} +1 -1
- package/dist/web/assets/audio-preview-BfES4Cx5.js.br +0 -0
- package/dist/web/assets/audio-preview-BfES4Cx5.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-CA8hPSX9.js +1 -0
- package/dist/web/assets/branch-review-tab-CA8hPSX9.js.br +0 -0
- package/dist/web/assets/branch-review-tab-CA8hPSX9.js.gz +0 -0
- package/dist/web/assets/{button-Ci9EUgqd.js → button-DeAyLH9U.js} +1 -1
- package/dist/web/assets/button-DeAyLH9U.js.br +0 -0
- package/dist/web/assets/button-DeAyLH9U.js.gz +0 -0
- package/dist/web/assets/{chat-preparation-cache-BSIQGiWx.js → chat-preparation-cache-CcCd2g7A.js} +1 -1
- package/dist/web/assets/chat-preparation-cache-CcCd2g7A.js.br +0 -0
- package/dist/web/assets/chat-preparation-cache-CcCd2g7A.js.gz +0 -0
- package/dist/web/assets/chat-tab-BWi_bUzA.js +1 -0
- package/dist/web/assets/chat-tab-BWi_bUzA.js.br +0 -0
- package/dist/web/assets/chat-tab-BWi_bUzA.js.gz +0 -0
- package/dist/web/assets/chat-tab-BvJhIaVE.js +15 -0
- package/dist/web/assets/chat-tab-BvJhIaVE.js.br +0 -0
- package/dist/web/assets/chat-tab-BvJhIaVE.js.gz +0 -0
- package/dist/web/assets/code-editor-OJUdLVnn.js +7 -0
- package/dist/web/assets/code-editor-OJUdLVnn.js.br +0 -0
- package/dist/web/assets/code-editor-OJUdLVnn.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js +1 -0
- package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js.gz +0 -0
- package/dist/web/assets/{conflict-editor-qMGJH0Bx.js → conflict-editor-BpKW_vbL.js} +1 -1
- package/dist/web/assets/conflict-editor-BpKW_vbL.js.br +0 -0
- package/dist/web/assets/conflict-editor-BpKW_vbL.js.gz +0 -0
- package/dist/web/assets/context-menu-7OX80HDB.js +1 -0
- package/dist/web/assets/context-menu-7OX80HDB.js.br +0 -0
- package/dist/web/assets/context-menu-7OX80HDB.js.gz +0 -0
- package/dist/web/assets/{csv-preview-D2I742dl.js → csv-preview-COYdTABa.js} +1 -1
- package/dist/web/assets/csv-preview-COYdTABa.js.br +0 -0
- package/dist/web/assets/csv-preview-COYdTABa.js.gz +0 -0
- package/dist/web/assets/{data-grid-overlay-editor-CQRVKr7y.js → data-grid-overlay-editor-CeZoduke.js} +1 -1
- package/dist/web/assets/data-grid-overlay-editor-CeZoduke.js.br +0 -0
- package/dist/web/assets/data-grid-overlay-editor-CeZoduke.js.gz +0 -0
- package/dist/web/assets/{database-viewer-Bz4LjgIa.js → database-viewer-BQcBqCKm.js} +1 -1
- package/dist/web/assets/database-viewer-BQcBqCKm.js.br +0 -0
- package/dist/web/assets/database-viewer-BQcBqCKm.js.gz +0 -0
- package/dist/web/assets/{design-export-client-ChMcMoqW.js → design-export-client-OrnGCSwu.js} +1 -1
- package/dist/web/assets/design-export-client-OrnGCSwu.js.br +0 -0
- package/dist/web/assets/design-export-client-OrnGCSwu.js.gz +0 -0
- package/dist/web/assets/{design-responsive-dialog-DNBhJJLf.js → design-responsive-dialog-vJjQrC4n.js} +1 -1
- package/dist/web/assets/design-responsive-dialog-vJjQrC4n.js.br +0 -0
- package/dist/web/assets/design-responsive-dialog-vJjQrC4n.js.gz +0 -0
- package/dist/web/assets/design-tab-BbmFk38H.js +33 -0
- package/dist/web/assets/design-tab-BbmFk38H.js.br +0 -0
- package/dist/web/assets/design-tab-BbmFk38H.js.gz +0 -0
- package/dist/web/assets/{designs-sidebar-panel-CapP1yXq.js → designs-sidebar-panel-D2kXwB8B.js} +1 -1
- package/dist/web/assets/designs-sidebar-panel-D2kXwB8B.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-D2kXwB8B.js.gz +0 -0
- package/dist/web/assets/dialog-Bocc0Fgb.js +1 -0
- package/dist/web/assets/dialog-Bocc0Fgb.js.br +0 -0
- package/dist/web/assets/dialog-Bocc0Fgb.js.gz +0 -0
- package/dist/web/assets/{diff-viewer-CdXi7cZN.js → diff-viewer-Bj4DT6Mj.js} +1 -1
- package/dist/web/assets/diff-viewer-Bj4DT6Mj.js.br +0 -0
- package/dist/web/assets/diff-viewer-Bj4DT6Mj.js.gz +0 -0
- package/dist/web/assets/{docx-preview-C9XdKEj1.js → docx-preview-C2QqNGRE.js} +1 -1
- package/dist/web/assets/docx-preview-C2QqNGRE.js.br +0 -0
- package/dist/web/assets/docx-preview-C2QqNGRE.js.gz +0 -0
- package/dist/web/assets/editor-language-service-COqB2tuv.js +1 -0
- package/dist/web/assets/editor-language-service-COqB2tuv.js.br +3 -0
- package/dist/web/assets/editor-language-service-COqB2tuv.js.gz +0 -0
- package/dist/web/assets/explorer-body-DGrYajDN.js +2 -0
- package/dist/web/assets/explorer-body-DGrYajDN.js.br +0 -0
- package/dist/web/assets/explorer-body-DGrYajDN.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-DLzkODTP.js +1 -0
- package/dist/web/assets/explorer-window-content-DLzkODTP.js.br +0 -0
- package/dist/web/assets/explorer-window-content-DLzkODTP.js.gz +0 -0
- package/dist/web/assets/{extension-manager-section-B2nZzJ2b.js → extension-manager-section-Bu0qcB2i.js} +1 -1
- package/dist/web/assets/extension-manager-section-Bu0qcB2i.js.br +0 -0
- package/dist/web/assets/extension-manager-section-Bu0qcB2i.js.gz +0 -0
- package/dist/web/assets/{extension-webview-BGoDxkdF.js → extension-webview-G_WJ0N52.js} +1 -1
- package/dist/web/assets/extension-webview-G_WJ0N52.js.br +0 -0
- package/dist/web/assets/extension-webview-G_WJ0N52.js.gz +0 -0
- package/dist/web/assets/{file-icons-gx_FBOXY.js → file-icons-l7fNOmdS.js} +1 -1
- package/dist/web/assets/file-icons-l7fNOmdS.js.br +0 -0
- package/dist/web/assets/file-icons-l7fNOmdS.js.gz +0 -0
- package/dist/web/assets/{file-store-D9KE0kQC.js → file-store-B0e7sLag.js} +1 -1
- package/dist/web/assets/file-store-B0e7sLag.js.br +0 -0
- package/dist/web/assets/file-store-B0e7sLag.js.gz +0 -0
- package/dist/web/assets/{files-settings-section-DC3OzzOl.js → files-settings-section-ChZ_CynX.js} +1 -1
- package/dist/web/assets/files-settings-section-ChZ_CynX.js.br +0 -0
- package/dist/web/assets/files-settings-section-ChZ_CynX.js.gz +0 -0
- package/dist/web/assets/format-bytes-__396eCN.js +1 -0
- package/dist/web/assets/{format-file-meta-mQwA6SuY.js → format-file-meta-o1XBuoKS.js} +1 -1
- package/dist/web/assets/format-file-meta-o1XBuoKS.js.br +0 -0
- package/dist/web/assets/format-file-meta-o1XBuoKS.js.gz +0 -0
- package/dist/web/assets/{fs-api-DL36slbd.js → fs-api-CeHKwx95.js} +1 -1
- package/dist/web/assets/fs-api-CeHKwx95.js.br +0 -0
- package/dist/web/assets/fs-api-CeHKwx95.js.gz +0 -0
- package/dist/web/assets/{general-settings-section-i41W43ER.js → general-settings-section-MSvDEbFa.js} +1 -1
- package/dist/web/assets/general-settings-section-MSvDEbFa.js.br +0 -0
- package/dist/web/assets/general-settings-section-MSvDEbFa.js.gz +0 -0
- package/dist/web/assets/{git-log-panel-DHzPIkJg.js → git-log-panel-gw5_a6j3.js} +1 -1
- package/dist/web/assets/git-log-panel-gw5_a6j3.js.br +0 -0
- package/dist/web/assets/git-log-panel-gw5_a6j3.js.gz +0 -0
- package/dist/web/assets/{git-repo-store-DSypNj19.js → git-repo-store-BpPnah5l.js} +1 -1
- package/dist/web/assets/git-repo-store-BpPnah5l.js.br +0 -0
- package/dist/web/assets/git-repo-store-BpPnah5l.js.gz +0 -0
- package/dist/web/assets/{glide-data-grid-Dj4vrDvj.js → glide-data-grid-Db2Cl26B.js} +5 -5
- package/dist/web/assets/glide-data-grid-Db2Cl26B.js.br +0 -0
- package/dist/web/assets/glide-data-grid-Db2Cl26B.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-B4etmYTR.js +1 -0
- package/dist/web/assets/group-chat-tab-B4etmYTR.js.br +0 -0
- package/dist/web/assets/group-chat-tab-B4etmYTR.js.gz +0 -0
- package/dist/web/assets/{image-preview-CGdZrmLo.js → image-preview-CjNhpfWw.js} +1 -1
- package/dist/web/assets/index-CtAvmybi.js +52 -0
- package/dist/web/assets/index-CtAvmybi.js.br +0 -0
- package/dist/web/assets/index-CtAvmybi.js.gz +0 -0
- package/dist/web/assets/index-DeCJasyy.css +1 -0
- package/dist/web/assets/index-DeCJasyy.css.br +0 -0
- package/dist/web/assets/index-DeCJasyy.css.gz +0 -0
- package/dist/web/assets/{jira-watcher-section-BJ9edGEP.js → jira-watcher-section-CKSI1Alo.js} +1 -1
- package/dist/web/assets/keybindings-store-C4_yyU4u.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-CxdGSNGr.js → keyboard-shortcuts-section-CEaVUc9a.js} +1 -1
- package/dist/web/assets/keyboard-shortcuts-section-CEaVUc9a.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-CEaVUc9a.js.gz +0 -0
- package/dist/web/assets/language-servers-section-ePtEpnDc.js +2 -0
- package/dist/web/assets/language-servers-section-ePtEpnDc.js.br +0 -0
- package/dist/web/assets/language-servers-section-ePtEpnDc.js.gz +0 -0
- package/dist/web/assets/lsp-client-XzmR3Brr.js +1 -0
- package/dist/web/assets/lsp-client-XzmR3Brr.js.br +0 -0
- package/dist/web/assets/lsp-client-XzmR3Brr.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-CTtDap-l.js +1 -0
- package/dist/web/assets/{markdown-renderer-ok96IgnS.js → markdown-renderer-D05fAr57.js} +2 -2
- package/dist/web/assets/markdown-renderer-D05fAr57.js.br +0 -0
- package/dist/web/assets/markdown-renderer-D05fAr57.js.gz +0 -0
- package/dist/web/assets/{media-url-CzUJrREv.js → media-url-C6NumuN5.js} +1 -1
- package/dist/web/assets/metric-chart-canvas-DHJe0VER.js +1 -0
- package/dist/web/assets/metric-chart-canvas-DHJe0VER.js.br +0 -0
- package/dist/web/assets/metric-chart-canvas-DHJe0VER.js.gz +0 -0
- package/dist/web/assets/{mobile-bottom-sheet-CuNi2FRv.js → mobile-bottom-sheet-D4UyOatB.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-D4UyOatB.js.br +0 -0
- package/dist/web/assets/mobile-bottom-sheet-D4UyOatB.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js.gz +0 -0
- package/dist/web/assets/{new-design-dialog-Drkjb_LI.js → new-design-dialog-BgmQopJM.js} +1 -1
- package/dist/web/assets/new-design-dialog-BgmQopJM.js.br +0 -0
- package/dist/web/assets/new-design-dialog-BgmQopJM.js.gz +0 -0
- package/dist/web/assets/notification-store-q5QBklKA.js +1 -0
- package/dist/web/assets/{notifications-settings-section-C05WYqcE.js → notifications-settings-section-BBJ0r9OS.js} +1 -1
- package/dist/web/assets/notifications-settings-section-BBJ0r9OS.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-BBJ0r9OS.js.gz +0 -0
- package/dist/web/assets/{onboarding-entry-CmUqv1kl.js → onboarding-entry-BYOYswsi.js} +1 -1
- package/dist/web/assets/onboarding-entry-BYOYswsi.js.br +0 -0
- package/dist/web/assets/onboarding-entry-BYOYswsi.js.gz +0 -0
- package/dist/web/assets/{onboarding-quick-orientation-DIhWv1CN.js → onboarding-quick-orientation-DPHYFMbA.js} +1 -1
- package/dist/web/assets/onboarding-quick-orientation-DPHYFMbA.js.br +0 -0
- package/dist/web/assets/onboarding-quick-orientation-DPHYFMbA.js.gz +0 -0
- package/dist/web/assets/overview-panel-pkPs44Tv.js +1 -0
- package/dist/web/assets/overview-panel-pkPs44Tv.js.br +0 -0
- package/dist/web/assets/overview-panel-pkPs44Tv.js.gz +0 -0
- package/dist/web/assets/{pdf-preview-qdXmMkOR.js → pdf-preview-c-L_kDAs.js} +1 -1
- package/dist/web/assets/pdf-preview-c-L_kDAs.js.br +0 -0
- package/dist/web/assets/pdf-preview-c-L_kDAs.js.gz +0 -0
- package/dist/web/assets/performance-panel-yPrq0DJV.js +3 -0
- package/dist/web/assets/performance-panel-yPrq0DJV.js.br +0 -0
- package/dist/web/assets/performance-panel-yPrq0DJV.js.gz +0 -0
- package/dist/web/assets/{postgres-viewer-Xvlun_n5.js → postgres-viewer-CnwDCMNd.js} +1 -1
- package/dist/web/assets/postgres-viewer-CnwDCMNd.js.br +0 -0
- package/dist/web/assets/postgres-viewer-CnwDCMNd.js.gz +0 -0
- package/dist/web/assets/{ppmbot-settings-section-C8PFlvaq.js → ppmbot-settings-section-BDXoSIDp.js} +1 -1
- package/dist/web/assets/ppmbot-settings-section-BDXoSIDp.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-BDXoSIDp.js.gz +0 -0
- package/dist/web/assets/{pptx-export-CG6MRNok.js → pptx-export-D0RDY48h.js} +4 -4
- package/dist/web/assets/pptx-export-D0RDY48h.js.br +0 -0
- package/dist/web/assets/pptx-export-D0RDY48h.js.gz +0 -0
- package/dist/web/assets/{problems-panel-CFYO0Ro3.js → problems-panel-BgY8oyo9.js} +1 -1
- package/dist/web/assets/problems-panel-BgY8oyo9.js.br +0 -0
- package/dist/web/assets/problems-panel-BgY8oyo9.js.gz +0 -0
- package/dist/web/assets/process-row-format-CaaMcHzn.js +1 -0
- package/dist/web/assets/process-table-BOwDtXuG.js +1 -0
- package/dist/web/assets/process-table-BOwDtXuG.js.br +0 -0
- package/dist/web/assets/process-table-BOwDtXuG.js.gz +0 -0
- package/dist/web/assets/{project-framework-store-XxFXcUpb.js → project-framework-store-UDEMu28U.js} +1 -1
- package/dist/web/assets/{project-store-C5gJXkvb.js → project-store-DGa31y5B.js} +1 -1
- package/dist/web/assets/project-store-DGa31y5B.js.br +0 -0
- package/dist/web/assets/project-store-DGa31y5B.js.gz +0 -0
- package/dist/web/assets/{proxy-settings-section-BcHIEANE.js → proxy-settings-section-Db6Kc6xG.js} +1 -1
- package/dist/web/assets/proxy-settings-section-Db6Kc6xG.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-Db6Kc6xG.js.gz +0 -0
- package/dist/web/assets/{query-audit-section-UKG_smyt.js → query-audit-section-C0PAXhfT.js} +1 -1
- package/dist/web/assets/query-audit-section-C0PAXhfT.js.br +0 -0
- package/dist/web/assets/query-audit-section-C0PAXhfT.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-clipboard-notice-BGsp0edA.js → remote-desktop-clipboard-notice-DOVtobj_.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-DOVtobj_.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-DOVtobj_.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-CsZCCRJI.js → remote-desktop-readiness-gate-BsfoJzdc.js} +1 -1
- package/dist/web/assets/remote-desktop-readiness-gate-BsfoJzdc.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BsfoJzdc.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js.gz +0 -0
- package/dist/web/assets/{schedules-settings-section-Ci0O2Ng6.js → schedules-settings-section-CvbjQM8z.js} +1 -1
- package/dist/web/assets/schedules-settings-section-CvbjQM8z.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-CvbjQM8z.js.gz +0 -0
- package/dist/web/assets/{scroll-area-XonvWYn_.js → scroll-area-sjOeAfoD.js} +1 -1
- package/dist/web/assets/scroll-area-sjOeAfoD.js.br +0 -0
- package/dist/web/assets/scroll-area-sjOeAfoD.js.gz +0 -0
- package/dist/web/assets/{separator-sQj04Ari.js → separator-9A3PCG1t.js} +1 -1
- package/dist/web/assets/services-panel-CimW47P8.js +1 -0
- package/dist/web/assets/services-panel-CimW47P8.js.br +0 -0
- package/dist/web/assets/services-panel-CimW47P8.js.gz +0 -0
- package/dist/web/assets/settings-body-BTXNtpWL.js +2 -0
- package/dist/web/assets/settings-body-BTXNtpWL.js.br +0 -0
- package/dist/web/assets/settings-body-BTXNtpWL.js.gz +0 -0
- package/dist/web/assets/settings-store-DvftLqzy.js +2 -0
- package/dist/web/assets/settings-store-DvftLqzy.js.br +0 -0
- package/dist/web/assets/settings-store-DvftLqzy.js.gz +0 -0
- package/dist/web/assets/settings-tab-CGhaRXjs.js +1 -0
- package/dist/web/assets/settings-window-content--Uyg_Psr.js +1 -0
- package/dist/web/assets/sortable-header-DUW9T1Hz.js +1 -0
- package/dist/web/assets/sortable-header-DUW9T1Hz.js.br +0 -0
- package/dist/web/assets/sortable-header-DUW9T1Hz.js.gz +0 -0
- package/dist/web/assets/{sql-query-editor-4iCGE4h2.js → sql-query-editor-Dn-cgDrW.js} +1 -1
- package/dist/web/assets/sql-query-editor-Dn-cgDrW.js.br +0 -0
- package/dist/web/assets/sql-query-editor-Dn-cgDrW.js.gz +0 -0
- package/dist/web/assets/{sqlite-viewer-CK6DulYk.js → sqlite-viewer-B0NfaMjq.js} +1 -1
- package/dist/web/assets/sqlite-viewer-B0NfaMjq.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-B0NfaMjq.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-CxCfRQFb.js +2 -0
- package/dist/web/assets/system-monitor-body-CxCfRQFb.js.br +0 -0
- package/dist/web/assets/system-monitor-body-CxCfRQFb.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-Cb3EZUdp.js +1 -0
- package/dist/web/assets/system-monitor-window-content-51CA-2PH.js +1 -0
- package/dist/web/assets/{tab-host-window-content-CWBaWKjA.js → tab-host-window-content-CSrcmKNG.js} +1 -1
- package/dist/web/assets/tab-host-window-content-CSrcmKNG.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-CSrcmKNG.js.gz +0 -0
- package/dist/web/assets/{team-member-sheet-Br6mrxrw.js → team-member-sheet-D4X6nh-x.js} +2 -2
- package/dist/web/assets/team-member-sheet-D4X6nh-x.js.br +0 -0
- package/dist/web/assets/team-member-sheet-D4X6nh-x.js.gz +0 -0
- package/dist/web/assets/{team-member-window-content-CwCy7ZWf.js → team-member-window-content-BxkbGVIL.js} +1 -1
- package/dist/web/assets/team-member-window-content-BxkbGVIL.js.br +0 -0
- package/dist/web/assets/team-member-window-content-BxkbGVIL.js.gz +0 -0
- package/dist/web/assets/{terminal-tab-BW14RpCb.js → terminal-tab-BYo34IqU.js} +3 -3
- package/dist/web/assets/terminal-tab-BYo34IqU.js.br +0 -0
- package/dist/web/assets/terminal-tab-BYo34IqU.js.gz +0 -0
- package/dist/web/assets/{tool-cards-CgOBEUnb.js → tool-cards-DvXjOped.js} +4 -4
- package/dist/web/assets/tool-cards-DvXjOped.js.br +0 -0
- package/dist/web/assets/tool-cards-DvXjOped.js.gz +0 -0
- package/dist/web/assets/{use-blob-url-CmavyEP8.js → use-blob-url-CISBWKG5.js} +1 -1
- package/dist/web/assets/use-inline-blame-Br6mheS4.js +16 -0
- package/dist/web/assets/use-inline-blame-Br6mheS4.js.br +0 -0
- package/dist/web/assets/use-inline-blame-Br6mheS4.js.gz +0 -0
- package/dist/web/assets/use-lsp-BpJmvQ0n.js +6 -0
- package/dist/web/assets/use-lsp-BpJmvQ0n.js.br +0 -0
- package/dist/web/assets/use-lsp-BpJmvQ0n.js.gz +0 -0
- package/dist/web/assets/{use-monaco-theme-rH6epJYA.js → use-monaco-theme-UGOzyIhv.js} +1 -1
- package/dist/web/assets/use-process-kill-CyiiUD1E.js +1 -0
- package/dist/web/assets/use-process-kill-CyiiUD1E.js.br +0 -0
- package/dist/web/assets/use-process-kill-CyiiUD1E.js.gz +0 -0
- package/dist/web/assets/{use-websocket-QwURkjpV.js → use-websocket-Dpl21cHG.js} +1 -1
- package/dist/web/assets/{vendor-ui-BHh12_0v.js → vendor-ui-P9Ym7hCv.js} +1 -1
- package/dist/web/assets/vendor-ui-P9Ym7hCv.js.br +0 -0
- package/dist/web/assets/vendor-ui-P9Ym7hCv.js.gz +0 -0
- package/dist/web/assets/video-preview-D6Jw-IZK.js +1 -0
- package/dist/web/assets/video-preview-D6Jw-IZK.js.br +0 -0
- package/dist/web/assets/video-preview-D6Jw-IZK.js.gz +0 -0
- package/dist/web/assets/voice-settings-section-CcHQDYzN.js +1 -0
- package/dist/web/assets/voice-settings-section-CcHQDYzN.js.br +0 -0
- package/dist/web/assets/voice-settings-section-CcHQDYzN.js.gz +0 -0
- package/dist/web/assets/ws-auth-CqI8-aoA.js +1 -0
- package/dist/web/index.html +20 -20
- package/dist/web/index.html.br +0 -0
- package/dist/web/index.html.gz +0 -0
- package/dist/web/sw.js +1 -1
- package/dist/web/sw.js.br +0 -0
- package/dist/web/sw.js.gz +0 -0
- package/docs/deployment-guide.md +23 -0
- package/package.json +1 -1
- package/release-from-worktree.sh +224 -0
- package/src/cli/commands/config-cmd.ts +12 -0
- package/src/providers/claude-agent-sdk.ts +65 -2
- package/src/providers/codex-app-server/codex-jsonrpc-client.ts +16 -1
- package/src/providers/codex-app-server/codex-provider.ts +39 -7
- package/src/providers/codex-app-server/codex-reset-credit.ts +49 -0
- package/src/providers/provider-probe.ts +63 -0
- package/src/providers/registry.ts +109 -29
- package/src/server/index.ts +9 -0
- package/src/server/middleware/auth.ts +5 -1
- package/src/server/routes/codex-accounts.ts +14 -0
- package/src/server/routes/cross-origin-guard.ts +20 -0
- package/src/server/routes/git.ts +40 -4
- package/src/server/routes/lsp.ts +97 -4
- package/src/server/routes/resources.ts +72 -9
- package/src/server/routes/settings.ts +19 -1
- package/src/server/routes/speech.ts +66 -0
- package/src/server/routes/system-services.ts +76 -0
- package/src/server/routes/tunnel.ts +33 -1
- package/src/server/ws/chat.ts +74 -3
- package/src/services/codex-account-login.ts +26 -2
- package/src/services/codex-reset-credit.service.ts +83 -0
- package/src/services/compaction-savings.ts +127 -0
- package/src/services/db.service.ts +123 -8
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/fs-ops/fs-ops-trash.service.ts +22 -9
- package/src/services/git-refs/for-each-ref.ts +118 -0
- package/src/services/git.service.ts +25 -2
- package/src/services/host-info/spawn-runner.ts +48 -4
- package/src/services/jsonl-transcript-parser.ts +7 -42
- package/src/services/lsp/lsp-install.ts +289 -0
- package/src/services/lsp/lsp-manager.ts +160 -29
- package/src/services/lsp/server-registry.ts +120 -4
- package/src/services/named-tunnel/named-tunnel-config.ts +8 -2
- package/src/services/named-tunnel/named-tunnel-setup.service.ts +4 -0
- package/src/services/read-lines.ts +54 -0
- package/src/services/speech-to-text/whisper-catalog.ts +142 -0
- package/src/services/speech-to-text/whisper-download.ts +113 -0
- package/src/services/speech-to-text/whisper-hallucinations.ts +51 -0
- package/src/services/speech-to-text/whisper-install.service.ts +247 -0
- package/src/services/speech-to-text/whisper-paths.ts +62 -0
- package/src/services/speech-to-text/whisper-transcribe.service.ts +117 -0
- package/src/services/supervisor.ts +94 -11
- package/src/services/system-metrics/cpu-details-linux.ts +252 -0
- package/src/services/system-metrics/cpu-memory-collector.ts +140 -18
- package/src/services/system-metrics/device-collector-linux.ts +31 -0
- package/src/services/system-metrics/device-collector-types.ts +40 -0
- package/src/services/system-metrics/disk-devices-linux.ts +156 -0
- package/src/services/system-metrics/disk-inventory-linux.ts +150 -0
- package/src/services/system-metrics/fans-linux.ts +51 -0
- package/src/services/system-metrics/gpu-api-versions.ts +120 -0
- package/src/services/system-metrics/gpu-devices-linux.ts +370 -0
- package/src/services/system-metrics/gpu-fdinfo-linux.ts +253 -0
- package/src/services/system-metrics/hardware-inventory.ts +50 -0
- package/src/services/system-metrics/linux-fs.ts +43 -0
- package/src/services/system-metrics/memory-darwin.ts +91 -0
- package/src/services/system-metrics/memory-linux.ts +156 -0
- package/src/services/system-metrics/net-devices-linux.ts +114 -0
- package/src/services/system-metrics/net-inventory-linux.ts +100 -0
- package/src/services/system-metrics/partitions-linux.ts +158 -0
- package/src/services/system-metrics/process-collector-linux.ts +58 -6
- package/src/services/system-metrics/process-collector-types.ts +8 -1
- package/src/services/system-metrics/process-details-darwin.ts +166 -0
- package/src/services/system-metrics/process-details-linux.ts +150 -0
- package/src/services/system-metrics/process-grouping.ts +2 -1
- package/src/services/system-metrics/process-rows-builder.ts +2 -0
- package/src/services/system-metrics/process-swap-linux.ts +37 -0
- package/src/services/system-metrics/signal-executor.ts +84 -0
- package/src/services/system-metrics/signal-request-handler.ts +92 -0
- package/src/services/system-metrics/system-metrics-platform.ts +30 -4
- package/src/services/system-metrics/system-metrics-tick.ts +73 -4
- package/src/services/system-metrics/system-metrics.service.ts +87 -2
- package/src/services/system-metrics/udev-db.ts +40 -0
- package/src/services/system-services/app-cgroup-linux.ts +115 -0
- package/src/services/system-services/app-icon-service.ts +31 -0
- package/src/services/system-services/app-icons-linux.ts +124 -0
- package/src/services/system-services/apps-linux.ts +174 -0
- package/src/services/system-services/desktop-entry.ts +103 -0
- package/src/services/system-services/service-guard.ts +124 -0
- package/src/services/system-services/systemd-collector.ts +203 -0
- package/src/services/system-services/systemd-parse.ts +162 -0
- package/src/services/windows-process-tree.ts +351 -351
- package/src/shared/prompt-cache-idle.ts +197 -0
- package/src/shared/turn-usage.ts +85 -1
- package/src/shared/usage-extra.ts +19 -0
- package/src/types/chat.ts +29 -0
- package/src/types/config.ts +14 -0
- package/src/types/git.ts +41 -0
- package/src/types/system-hardware.ts +137 -0
- package/src/types/system-metrics.ts +231 -2
- package/src/types/system-services.ts +73 -0
- package/src/web/components/branch-review/branch-review-tab.tsx +20 -20
- package/src/web/components/branch-review/branch-review-tree-row.tsx +1 -13
- package/src/web/components/chat/chat-tab.tsx +3 -0
- package/src/web/components/chat/codex-usage-panel.tsx +4 -0
- package/src/web/components/chat/compaction-divider.tsx +48 -0
- package/src/web/components/chat/idle-cache-notice.tsx +67 -0
- package/src/web/components/chat/message-input.tsx +48 -13
- package/src/web/components/chat/message-list.tsx +15 -0
- package/src/web/components/chat/prompt-cache-chip.tsx +74 -0
- package/src/web/components/editor/code-editor.tsx +1 -0
- package/src/web/components/editor/editor-language-service.tsx +11 -3
- package/src/web/components/editor/lsp-status.tsx +115 -13
- package/src/web/components/git/branch-picker.tsx +419 -0
- package/src/web/components/git/branch-select.tsx +34 -0
- package/src/web/components/git/create-worktree-dialog.tsx +17 -16
- package/src/web/components/git/git-repo-picker.tsx +28 -40
- package/src/web/components/layout/status-bar.tsx +42 -19
- package/src/web/components/settings/accounts/account-card.tsx +10 -2
- package/src/web/components/settings/accounts/account-usage-extras.tsx +3 -2
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +3 -1
- package/src/web/components/settings/accounts/codex-reset-credit-button.tsx +120 -0
- package/src/web/components/settings/ai-settings-section.tsx +67 -3
- package/src/web/components/settings/appearance-settings-section.tsx +2 -28
- package/src/web/components/settings/language-servers-section.tsx +362 -0
- package/src/web/components/settings/settings-categories.ts +8 -3
- package/src/web/components/settings/settings-section-content.tsx +2 -0
- package/src/web/components/settings/voice-settings-section.tsx +283 -0
- package/src/web/components/system/apps/app-row.tsx +110 -0
- package/src/web/components/system/apps/app-rows.ts +140 -0
- package/src/web/components/system/apps/apps-panel.tsx +129 -0
- package/src/web/components/system/metric-chart-canvas.tsx +79 -3
- package/src/web/components/system/overview-cards/card-shell.tsx +54 -0
- package/src/web/components/system/overview-cards/cpu-card.tsx +11 -6
- package/src/web/components/system/overview-cards/disk-card.tsx +11 -6
- package/src/web/components/system/overview-cards/gpu-card.tsx +16 -6
- package/src/web/components/system/overview-cards/mem-card.tsx +11 -6
- package/src/web/components/system/overview-cards/net-card.tsx +11 -6
- package/src/web/components/system/overview-device-target.ts +69 -0
- package/src/web/components/system/overview-panel.tsx +15 -1
- package/src/web/components/system/performance/cpu-core-grid.tsx +74 -0
- package/src/web/components/system/performance/cpu-detail.tsx +220 -0
- package/src/web/components/system/performance/detail-parts.tsx +149 -0
- package/src/web/components/system/performance/device-list.ts +139 -0
- package/src/web/components/system/performance/disk-detail.tsx +64 -0
- package/src/web/components/system/performance/fans-detail.tsx +35 -0
- package/src/web/components/system/performance/gpu-detail.tsx +116 -0
- package/src/web/components/system/performance/memory-detail.tsx +109 -0
- package/src/web/components/system/performance/nic-detail.tsx +55 -0
- package/src/web/components/system/performance/partition-list.tsx +79 -0
- package/src/web/components/system/performance/performance-panel.tsx +206 -0
- package/src/web/components/system/performance/use-hardware-inventory.ts +48 -0
- package/src/web/components/system/process-columns-grid.ts +5 -3
- package/src/web/components/system/process-details-dialog.tsx +116 -0
- package/src/web/components/system/process-group-row.tsx +6 -1
- package/src/web/components/system/process-row-format.ts +6 -0
- package/src/web/components/system/process-row-menu.tsx +72 -0
- package/src/web/components/system/process-row.tsx +7 -1
- package/src/web/components/system/process-table-model.ts +1 -1
- package/src/web/components/system/process-table-sort.ts +8 -4
- package/src/web/components/system/process-table-toolbar.tsx +10 -2
- package/src/web/components/system/process-table-totals.ts +3 -0
- package/src/web/components/system/process-table.tsx +25 -2
- package/src/web/components/system/services/service-action-confirm.tsx +100 -0
- package/src/web/components/system/services/service-columns.ts +103 -0
- package/src/web/components/system/services/service-details-sheet.tsx +97 -0
- package/src/web/components/system/services/service-resources.ts +105 -0
- package/src/web/components/system/services/service-row.tsx +146 -0
- package/src/web/components/system/services/service-rows.ts +92 -0
- package/src/web/components/system/services/service-sort.ts +90 -0
- package/src/web/components/system/services/services-header.tsx +51 -0
- package/src/web/components/system/services/services-panel.tsx +207 -0
- package/src/web/components/system/services/use-services.ts +92 -0
- package/src/web/components/system/sortable-header.tsx +13 -8
- package/src/web/components/system/sysmon-preferences.tsx +85 -0
- package/src/web/components/system/system-monitor-body.tsx +101 -43
- package/src/web/components/system/system-monitor-window-content.tsx +2 -3
- package/src/web/components/system/use-process-signal.ts +87 -0
- package/src/web/components/tunnels/public-tunnel-switch.tsx +82 -0
- package/src/web/components/tunnels/tunnel-manager-tab.tsx +4 -0
- package/src/web/components/ui/dialog.tsx +14 -1
- package/src/web/components/ui/search-select.tsx +279 -0
- package/src/web/components/ui/start-ellipsis.tsx +18 -0
- package/src/web/hooks/use-chat.ts +28 -0
- package/src/web/hooks/use-global-keybindings.ts +7 -5
- package/src/web/hooks/use-lsp.ts +23 -2
- package/src/web/hooks/use-minute-clock.ts +20 -0
- package/src/web/hooks/use-whisper-voice-input.ts +113 -0
- package/src/web/lib/api-tunnels.ts +19 -0
- package/src/web/lib/branch-select-items.ts +29 -0
- package/src/web/lib/cpu-graph-mode.ts +90 -0
- package/src/web/lib/format-bytes.ts +14 -0
- package/src/web/lib/git-ref-picker.ts +120 -0
- package/src/web/lib/lsp/lsp-client.ts +32 -0
- package/src/web/lib/search-select-rows.ts +55 -0
- package/src/web/lib/temperature.ts +27 -0
- package/src/web/lib/voice-wav.ts +69 -0
- package/src/web/stores/git-status-store.ts +31 -17
- package/src/web/stores/keybindings-store.ts +14 -1
- package/src/web/stores/settings-store.ts +65 -0
- package/src/web/styles/globals.css +11 -0
- package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js +0 -1
- package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-CgG5P3Jm.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-DKcsVDle.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-DKcsVDle.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-D7zJ-hCO.js +0 -1
- package/dist/web/assets/ai-settings-section-D7zJ-hCO.js.br +0 -0
- package/dist/web/assets/ai-settings-section-D7zJ-hCO.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-OPqC2bYG.js +0 -1
- package/dist/web/assets/api-client-BgedDTvo.js +0 -1
- package/dist/web/assets/api-client-c8YOpTlT.js.br +0 -0
- package/dist/web/assets/api-client-c8YOpTlT.js.gz +0 -0
- package/dist/web/assets/api-settings-BmvNoCbn.js.br +0 -0
- package/dist/web/assets/api-settings-BmvNoCbn.js.gz +0 -0
- package/dist/web/assets/api-settings-Bti1-Zn3.js +0 -1
- package/dist/web/assets/appearance-settings-section-DL466x83.js +0 -1
- package/dist/web/assets/appearance-settings-section-DL466x83.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-DL466x83.js.gz +0 -0
- package/dist/web/assets/audio-preview-vaG2cB-k.js.br +0 -0
- package/dist/web/assets/audio-preview-vaG2cB-k.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-Dag86n_R.js +0 -1
- package/dist/web/assets/branch-review-tab-Dag86n_R.js.br +0 -0
- package/dist/web/assets/branch-review-tab-Dag86n_R.js.gz +0 -0
- package/dist/web/assets/button-Ci9EUgqd.js.br +0 -0
- package/dist/web/assets/button-Ci9EUgqd.js.gz +0 -0
- package/dist/web/assets/chat-preparation-cache-BSIQGiWx.js.br +0 -0
- package/dist/web/assets/chat-preparation-cache-BSIQGiWx.js.gz +0 -0
- package/dist/web/assets/chat-tab-CcYZLXls.js +0 -15
- package/dist/web/assets/chat-tab-CcYZLXls.js.br +0 -0
- package/dist/web/assets/chat-tab-CcYZLXls.js.gz +0 -0
- package/dist/web/assets/chat-tab-QFDhzzBZ.js +0 -1
- package/dist/web/assets/chat-tab-QFDhzzBZ.js.br +0 -0
- package/dist/web/assets/chat-tab-QFDhzzBZ.js.gz +0 -0
- package/dist/web/assets/code-editor-K-IN8VwJ.js +0 -7
- package/dist/web/assets/code-editor-K-IN8VwJ.js.br +0 -0
- package/dist/web/assets/code-editor-K-IN8VwJ.js.gz +0 -0
- package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js +0 -1
- package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js.br +0 -0
- package/dist/web/assets/codex-sign-in-again-dialog-C8JvblLG.js.gz +0 -0
- package/dist/web/assets/conflict-editor-qMGJH0Bx.js.br +0 -0
- package/dist/web/assets/conflict-editor-qMGJH0Bx.js.gz +0 -0
- package/dist/web/assets/context-menu-CjYTq8DI.js +0 -1
- package/dist/web/assets/context-menu-CjYTq8DI.js.br +0 -0
- package/dist/web/assets/context-menu-CjYTq8DI.js.gz +0 -0
- package/dist/web/assets/csv-preview-D2I742dl.js.br +0 -0
- package/dist/web/assets/csv-preview-D2I742dl.js.gz +0 -0
- package/dist/web/assets/data-grid-overlay-editor-CQRVKr7y.js.br +0 -0
- package/dist/web/assets/data-grid-overlay-editor-CQRVKr7y.js.gz +0 -0
- package/dist/web/assets/database-viewer-Bz4LjgIa.js.br +0 -0
- package/dist/web/assets/database-viewer-Bz4LjgIa.js.gz +0 -0
- package/dist/web/assets/design-export-client-ChMcMoqW.js.br +0 -0
- package/dist/web/assets/design-export-client-ChMcMoqW.js.gz +0 -0
- package/dist/web/assets/design-responsive-dialog-DNBhJJLf.js.br +0 -0
- package/dist/web/assets/design-responsive-dialog-DNBhJJLf.js.gz +0 -0
- package/dist/web/assets/design-tab-C987MtMR.js +0 -33
- package/dist/web/assets/design-tab-C987MtMR.js.br +0 -0
- package/dist/web/assets/design-tab-C987MtMR.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-CapP1yXq.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-CapP1yXq.js.gz +0 -0
- package/dist/web/assets/dialog-B8clVkyi.js +0 -1
- package/dist/web/assets/dialog-B8clVkyi.js.br +0 -0
- package/dist/web/assets/dialog-B8clVkyi.js.gz +0 -0
- package/dist/web/assets/diff-viewer-CdXi7cZN.js.br +0 -0
- package/dist/web/assets/diff-viewer-CdXi7cZN.js.gz +0 -0
- package/dist/web/assets/docx-preview-C9XdKEj1.js.br +0 -0
- package/dist/web/assets/docx-preview-C9XdKEj1.js.gz +0 -0
- package/dist/web/assets/editor-language-service-Zle_PZV8.js +0 -1
- package/dist/web/assets/editor-language-service-Zle_PZV8.js.br +0 -0
- package/dist/web/assets/editor-language-service-Zle_PZV8.js.gz +0 -0
- package/dist/web/assets/explorer-body-DHMQR1Rk.js +0 -2
- package/dist/web/assets/explorer-body-DHMQR1Rk.js.br +0 -0
- package/dist/web/assets/explorer-body-DHMQR1Rk.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-Df40nQBk.js +0 -1
- package/dist/web/assets/explorer-window-content-Df40nQBk.js.br +0 -0
- package/dist/web/assets/explorer-window-content-Df40nQBk.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-B2nZzJ2b.js.br +0 -0
- package/dist/web/assets/extension-manager-section-B2nZzJ2b.js.gz +0 -0
- package/dist/web/assets/extension-webview-BGoDxkdF.js.br +0 -0
- package/dist/web/assets/extension-webview-BGoDxkdF.js.gz +0 -0
- package/dist/web/assets/file-icons-gx_FBOXY.js.br +0 -0
- package/dist/web/assets/file-icons-gx_FBOXY.js.gz +0 -0
- package/dist/web/assets/file-store-D9KE0kQC.js.br +0 -0
- package/dist/web/assets/file-store-D9KE0kQC.js.gz +0 -0
- package/dist/web/assets/files-settings-section-DC3OzzOl.js.br +0 -0
- package/dist/web/assets/files-settings-section-DC3OzzOl.js.gz +0 -0
- package/dist/web/assets/format-bytes-DmSHAKFe.js +0 -1
- package/dist/web/assets/format-file-meta-mQwA6SuY.js.br +0 -0
- package/dist/web/assets/format-file-meta-mQwA6SuY.js.gz +0 -0
- package/dist/web/assets/fs-api-DL36slbd.js.br +0 -0
- package/dist/web/assets/fs-api-DL36slbd.js.gz +0 -0
- package/dist/web/assets/general-settings-section-i41W43ER.js.br +0 -0
- package/dist/web/assets/general-settings-section-i41W43ER.js.gz +0 -0
- package/dist/web/assets/git-log-panel-DHzPIkJg.js.br +0 -0
- package/dist/web/assets/git-log-panel-DHzPIkJg.js.gz +0 -0
- package/dist/web/assets/git-repo-store-DSypNj19.js.br +0 -0
- package/dist/web/assets/git-repo-store-DSypNj19.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-Dj4vrDvj.js.br +0 -0
- package/dist/web/assets/glide-data-grid-Dj4vrDvj.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-DfwZb-fI.js +0 -1
- package/dist/web/assets/group-chat-tab-DfwZb-fI.js.br +0 -0
- package/dist/web/assets/group-chat-tab-DfwZb-fI.js.gz +0 -0
- package/dist/web/assets/index-DMA-So69.css +0 -1
- package/dist/web/assets/index-DMA-So69.css.br +0 -0
- package/dist/web/assets/index-DMA-So69.css.gz +0 -0
- package/dist/web/assets/index-jG9mLs-J.js +0 -52
- package/dist/web/assets/index-jG9mLs-J.js.br +0 -0
- package/dist/web/assets/index-jG9mLs-J.js.gz +0 -0
- package/dist/web/assets/keybindings-store-BlapkC6O.js +0 -1
- package/dist/web/assets/keyboard-shortcuts-section-CxdGSNGr.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-CxdGSNGr.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-DJ9EVU1B.js +0 -1
- package/dist/web/assets/markdown-renderer-ok96IgnS.js.br +0 -0
- package/dist/web/assets/markdown-renderer-ok96IgnS.js.gz +0 -0
- package/dist/web/assets/mobile-bottom-sheet-CuNi2FRv.js.br +0 -0
- package/dist/web/assets/mobile-bottom-sheet-CuNi2FRv.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-Cc03LUKy.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-Drkjb_LI.js.br +0 -0
- package/dist/web/assets/new-design-dialog-Drkjb_LI.js.gz +0 -0
- package/dist/web/assets/notification-store-DCyxho0O.js +0 -1
- package/dist/web/assets/notifications-settings-section-C05WYqcE.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-C05WYqcE.js.gz +0 -0
- package/dist/web/assets/onboarding-entry-CmUqv1kl.js.br +0 -0
- package/dist/web/assets/onboarding-entry-CmUqv1kl.js.gz +0 -0
- package/dist/web/assets/onboarding-quick-orientation-DIhWv1CN.js.br +0 -0
- package/dist/web/assets/onboarding-quick-orientation-DIhWv1CN.js.gz +0 -0
- package/dist/web/assets/overview-panel-BZAW_nag.js +0 -1
- package/dist/web/assets/overview-panel-BZAW_nag.js.br +0 -0
- package/dist/web/assets/overview-panel-BZAW_nag.js.gz +0 -0
- package/dist/web/assets/pdf-preview-qdXmMkOR.js.br +0 -0
- package/dist/web/assets/pdf-preview-qdXmMkOR.js.gz +0 -0
- package/dist/web/assets/postgres-viewer-Xvlun_n5.js.br +0 -0
- package/dist/web/assets/postgres-viewer-Xvlun_n5.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-C8PFlvaq.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-C8PFlvaq.js.gz +0 -0
- package/dist/web/assets/pptx-export-CG6MRNok.js.br +0 -0
- package/dist/web/assets/pptx-export-CG6MRNok.js.gz +0 -0
- package/dist/web/assets/problems-panel-CFYO0Ro3.js.br +0 -0
- package/dist/web/assets/problems-panel-CFYO0Ro3.js.gz +0 -0
- package/dist/web/assets/process-table-BVeRguFg.js +0 -1
- package/dist/web/assets/process-table-BVeRguFg.js.br +0 -0
- package/dist/web/assets/process-table-BVeRguFg.js.gz +0 -0
- package/dist/web/assets/project-store-C5gJXkvb.js.br +0 -0
- package/dist/web/assets/project-store-C5gJXkvb.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-BcHIEANE.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-BcHIEANE.js.gz +0 -0
- package/dist/web/assets/query-audit-section-UKG_smyt.js.br +0 -0
- package/dist/web/assets/query-audit-section-UKG_smyt.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-BGsp0edA.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-BGsp0edA.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DxkmF8qb.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DMD0M9Jh.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CsZCCRJI.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CsZCCRJI.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-DrHw_vwY.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-Ci0O2Ng6.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-Ci0O2Ng6.js.gz +0 -0
- package/dist/web/assets/scroll-area-XonvWYn_.js.br +0 -0
- package/dist/web/assets/scroll-area-XonvWYn_.js.gz +0 -0
- package/dist/web/assets/settings-body-B6JGOIYb.js +0 -2
- package/dist/web/assets/settings-body-B6JGOIYb.js.br +0 -0
- package/dist/web/assets/settings-body-B6JGOIYb.js.gz +0 -0
- package/dist/web/assets/settings-store-Br5c1efC.js +0 -2
- package/dist/web/assets/settings-store-Br5c1efC.js.br +0 -0
- package/dist/web/assets/settings-store-Br5c1efC.js.gz +0 -0
- package/dist/web/assets/settings-tab-CIQM4fNE.js +0 -1
- package/dist/web/assets/settings-window-content-BGNNz-E5.js +0 -1
- package/dist/web/assets/sql-query-editor-4iCGE4h2.js.br +0 -3
- package/dist/web/assets/sql-query-editor-4iCGE4h2.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-CK6DulYk.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-CK6DulYk.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-J9JtvAWV.js +0 -2
- package/dist/web/assets/system-monitor-body-J9JtvAWV.js.br +0 -0
- package/dist/web/assets/system-monitor-body-J9JtvAWV.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-DjBeRE81.js +0 -1
- package/dist/web/assets/system-monitor-window-content-BT7-ugFx.js +0 -1
- package/dist/web/assets/tab-host-window-content-CWBaWKjA.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-CWBaWKjA.js.gz +0 -0
- package/dist/web/assets/team-member-sheet-Br6mrxrw.js.br +0 -0
- package/dist/web/assets/team-member-sheet-Br6mrxrw.js.gz +0 -0
- package/dist/web/assets/team-member-window-content-CwCy7ZWf.js.br +0 -0
- package/dist/web/assets/team-member-window-content-CwCy7ZWf.js.gz +0 -0
- package/dist/web/assets/terminal-tab-BW14RpCb.js.br +0 -0
- package/dist/web/assets/terminal-tab-BW14RpCb.js.gz +0 -0
- package/dist/web/assets/tool-cards-CgOBEUnb.js.br +0 -0
- package/dist/web/assets/tool-cards-CgOBEUnb.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-Bzjfru4n.js +0 -16
- package/dist/web/assets/use-inline-blame-Bzjfru4n.js.br +0 -0
- package/dist/web/assets/use-inline-blame-Bzjfru4n.js.gz +0 -0
- package/dist/web/assets/use-lsp-hGdjL2hV.js +0 -6
- package/dist/web/assets/use-lsp-hGdjL2hV.js.br +0 -0
- package/dist/web/assets/use-lsp-hGdjL2hV.js.gz +0 -0
- package/dist/web/assets/vendor-ui-BHh12_0v.js.br +0 -0
- package/dist/web/assets/vendor-ui-BHh12_0v.js.gz +0 -0
- package/dist/web/assets/video-preview-P9T-ZVdN.js +0 -1
- package/dist/web/assets/video-preview-P9T-ZVdN.js.br +0 -0
- package/dist/web/assets/video-preview-P9T-ZVdN.js.gz +0 -0
- package/dist/web/assets/ws-auth-kXgsX2Js.js +0 -1
- 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/dist/web/assets/{__vite-browser-external-BpSQWxcq.js → __vite-browser-external-BILfYaAs.js} +0 -0
- /package/dist/web/assets/{run-in-terminal-HIdNgYim.js → run-in-terminal-2pSiup8e.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-DLDtzLib.js → vendor-xterm-DF1JTfsy.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-DLDtzLib.js.br → vendor-xterm-DF1JTfsy.js.br} +0 -0
- /package/dist/web/assets/{vendor-xterm-DLDtzLib.js.gz → vendor-xterm-DF1JTfsy.js.gz} +0 -0
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Whole-machine metrics routes, mounted under `/api/system` behind auth.
|
|
3
3
|
*
|
|
4
|
+
* GET /hardware static device inventory (drives, NICs, GPUs)
|
|
4
5
|
* GET /resources?processes=0|1 latest snapshot for that tier (or null)
|
|
5
6
|
* GET /resources/stream?processes=0|1 SSE: `session` frame, then `snapshot` frames
|
|
6
7
|
* POST /resources/stream/:sid/ping renew the subscriber lease
|
|
7
8
|
* DELETE /resources/stream/:sid drop the subscriber
|
|
8
9
|
* POST /resources/kill guarded kill (JSON + X-PPM-Request header)
|
|
10
|
+
* POST /resources/signal guarded signal, same guard as a kill
|
|
11
|
+
* GET /resources/process/:pid on-demand facts for the Details dialog
|
|
9
12
|
*/
|
|
10
13
|
import { Hono } from "hono";
|
|
11
14
|
import type { MetricsSnapshot, MetricsTier } from "../../types/system-metrics.ts";
|
|
15
|
+
import type { HardwareInventory } from "../../types/system-hardware.ts";
|
|
12
16
|
import { ok, err } from "../../types/api.ts";
|
|
13
17
|
import {
|
|
14
18
|
systemMetricsService, MAX_STREAM_SUBSCRIBERS, type SystemMetricsService,
|
|
15
19
|
} from "../../services/system-metrics/system-metrics.service.ts";
|
|
16
20
|
import { isValidSid } from "../../services/system-metrics/metrics-subscriber-registry.ts";
|
|
21
|
+
import { readHardwareInventory } from "../../services/system-metrics/hardware-inventory.ts";
|
|
22
|
+
import { createAppIconService, type AppIconService } from "../../services/system-services/app-icon-service.ts";
|
|
23
|
+
import { iconMimeType } from "../../services/system-services/app-icons-linux.ts";
|
|
24
|
+
import { crossOriginRefusal } from "./cross-origin-guard.ts";
|
|
17
25
|
|
|
18
26
|
/** Frames a stalled client failed to take before the stream is closed. Metrics
|
|
19
27
|
* are lossy by nature; `ReadableStream`'s queue is not, and 30-60 KB every 2 s
|
|
@@ -22,9 +30,34 @@ export const MAX_DROPPED_FRAMES = 10;
|
|
|
22
30
|
|
|
23
31
|
const tierOf = (raw: string | undefined): MetricsTier => (raw === "1" || raw === "true" ? "full" : "light");
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
|
|
34
|
+
export function createResourceRoutes(
|
|
35
|
+
service: SystemMetricsService = systemMetricsService,
|
|
36
|
+
hardware: () => Promise<HardwareInventory> = readHardwareInventory,
|
|
37
|
+
appIcons: AppIconService = createAppIconService(),
|
|
38
|
+
): Hono {
|
|
26
39
|
const routes = new Hono();
|
|
27
40
|
|
|
41
|
+
// Static facts, fetched once per window and again whenever a snapshot names a
|
|
42
|
+
// device id the client does not know yet — never on the 2 s tick.
|
|
43
|
+
routes.get("/hardware", async (c) => c.json(ok(await hardware())));
|
|
44
|
+
|
|
45
|
+
// The request names an APP, never a path: the only file this can serve is one
|
|
46
|
+
// this host's own desktop entry pointed at, resolved server-side.
|
|
47
|
+
routes.get("/app-icon/:id", async (c) => {
|
|
48
|
+
const iconPath = appIcons.path(c.req.param("id") ?? "");
|
|
49
|
+
if (!iconPath) return c.json(err("No icon for that app"), 404);
|
|
50
|
+
const file = Bun.file(iconPath);
|
|
51
|
+
if (!(await file.exists())) return c.json(err("No icon for that app"), 404);
|
|
52
|
+
return new Response(file, {
|
|
53
|
+
headers: {
|
|
54
|
+
"Content-Type": iconMimeType(iconPath),
|
|
55
|
+
// Icons change only when a package is reinstalled.
|
|
56
|
+
"Cache-Control": "public, max-age=86400",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
28
61
|
routes.get("/resources", (c) => c.json(ok(service.getLatest(tierOf(c.req.query("processes"))))));
|
|
29
62
|
|
|
30
63
|
routes.get("/resources/stream", (c) => {
|
|
@@ -102,15 +135,45 @@ export function createResourceRoutes(service: SystemMetricsService = systemMetri
|
|
|
102
135
|
return c.json(ok({ stopped }));
|
|
103
136
|
});
|
|
104
137
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
return c.json(err("
|
|
138
|
+
// Every field here is already in the process table this caller can stream,
|
|
139
|
+
// except exe/cwd/cgroup — the same trust level, behind the same auth.
|
|
140
|
+
routes.get("/resources/process/:pid", async (c) => {
|
|
141
|
+
const raw = c.req.param("pid") ?? "";
|
|
142
|
+
const pid = Number(raw);
|
|
143
|
+
if (!/^\d+$/.test(raw) || !Number.isInteger(pid) || pid <= 0) {
|
|
144
|
+
return c.json(err("PID must be a positive integer"), 400);
|
|
145
|
+
}
|
|
146
|
+
// Two different claims, and for a while they shared one sentence: a host with
|
|
147
|
+
// no reader answered "no longer running" about processes that were running.
|
|
148
|
+
if (!service.supportsProcessDetails()) {
|
|
149
|
+
return c.json(err("PPM cannot read process details on this host yet"), 501);
|
|
112
150
|
}
|
|
113
|
-
|
|
151
|
+
const details = await service.processDetails(pid);
|
|
152
|
+
if (!details) return c.json(err(`PID ${pid} is no longer running`), 404);
|
|
153
|
+
return c.json(ok(details));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
routes.post("/resources/signal", async (c) => {
|
|
157
|
+
const refusal = crossOriginRefusal(c);
|
|
158
|
+
if (refusal) return c.json(err(refusal), 400);
|
|
159
|
+
let body: unknown;
|
|
160
|
+
try {
|
|
161
|
+
body = await c.req.json();
|
|
162
|
+
} catch {
|
|
163
|
+
return c.json(err("Invalid JSON body"), 400);
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const outcome = await service.signal(body);
|
|
167
|
+
return c.json(outcome.body, outcome.status);
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.error("[SystemMetrics] signal re-query failed:", (e as Error)?.message ?? e);
|
|
170
|
+
return c.json(err("Could not verify the process — try again"), 500);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
routes.post("/resources/kill", async (c) => {
|
|
175
|
+
const refusal = crossOriginRefusal(c);
|
|
176
|
+
if (refusal) return c.json(err(refusal), 400);
|
|
114
177
|
let body: unknown;
|
|
115
178
|
try {
|
|
116
179
|
body = await c.req.json();
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import { ok, err } from "../../types/api.ts";
|
|
16
16
|
import { proxyService } from "../../services/proxy.service.ts";
|
|
17
17
|
import { clearIndexCache } from "../../services/file-list-index.service.ts";
|
|
18
|
-
import { providerRegistry } from "../../providers/registry.ts";
|
|
18
|
+
import { providerRegistry, providerProbeStatuses, retryProviderProbe } from "../../providers/registry.ts";
|
|
19
19
|
|
|
20
20
|
export const settingsRoutes = new Hono();
|
|
21
21
|
|
|
@@ -264,6 +264,24 @@ settingsRoutes.put("/ai", async (c) => {
|
|
|
264
264
|
}
|
|
265
265
|
});
|
|
266
266
|
|
|
267
|
+
/**
|
|
268
|
+
* GET /settings/ai/providers/status — which CLI providers registered, and why one did not.
|
|
269
|
+
*
|
|
270
|
+
* Settings builds its provider tabs from config, which keeps a codex entry
|
|
271
|
+
* forever once it has been written — so the tab is there while chat offers no
|
|
272
|
+
* Codex at all. This is what lets that tab say which of the two it is.
|
|
273
|
+
*/
|
|
274
|
+
settingsRoutes.get("/ai/providers/status", (c) => c.json(ok(providerProbeStatuses())));
|
|
275
|
+
|
|
276
|
+
/** POST /settings/ai/providers/:id/probe — probe now, ahead of the backoff */
|
|
277
|
+
settingsRoutes.post("/ai/providers/:id/probe", async (c) => {
|
|
278
|
+
try {
|
|
279
|
+
return c.json(ok(await retryProviderProbe(c.req.param("id"))));
|
|
280
|
+
} catch (e) {
|
|
281
|
+
return c.json(err((e as Error).message), 400);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
267
285
|
/** GET /settings/ai/providers/:id/models — list models for a provider (global, no project context needed) */
|
|
268
286
|
settingsRoutes.get("/ai/providers/:id/models", async (c) => {
|
|
269
287
|
try {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speech-to-text on the host: install status, the install/uninstall buttons in
|
|
3
|
+
* Settings, and the transcribe door the chat mic posts a WAV to.
|
|
4
|
+
*
|
|
5
|
+
* Mounted under `/api`, so every route here is already behind `authMiddleware`.
|
|
6
|
+
*/
|
|
7
|
+
import { Hono } from "hono";
|
|
8
|
+
import { err, ok } from "../../types/api.ts";
|
|
9
|
+
import {
|
|
10
|
+
getWhisperStatus,
|
|
11
|
+
startWhisperInstall,
|
|
12
|
+
uninstallWhisper,
|
|
13
|
+
} from "../../services/speech-to-text/whisper-install.service.ts";
|
|
14
|
+
import { transcribeWav } from "../../services/speech-to-text/whisper-transcribe.service.ts";
|
|
15
|
+
|
|
16
|
+
export const speechRoutes = new Hono();
|
|
17
|
+
|
|
18
|
+
/** 16 kHz mono 16-bit is 32 KB/s, so this is about 13 minutes of speech. */
|
|
19
|
+
const MAX_AUDIO_BYTES = 25 * 1024 * 1024;
|
|
20
|
+
|
|
21
|
+
const message = (e: unknown) => (e instanceof Error ? e.message : String(e));
|
|
22
|
+
|
|
23
|
+
speechRoutes.get("/status", (c) => c.json(ok(getWhisperStatus())));
|
|
24
|
+
|
|
25
|
+
speechRoutes.post("/install", async (c) => {
|
|
26
|
+
const body = (await c.req.json().catch(() => ({}))) as { model?: string };
|
|
27
|
+
try {
|
|
28
|
+
return c.json(ok(startWhisperInstall(body.model)));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return c.json(err(message(e)), 409);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
speechRoutes.post("/uninstall", (c) => {
|
|
35
|
+
try {
|
|
36
|
+
uninstallWhisper();
|
|
37
|
+
return c.json(ok(getWhisperStatus()));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return c.json(err(message(e)), 409);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* POST /api/speech/transcribe?lang=vi — body is the raw WAV the browser recorded.
|
|
45
|
+
*
|
|
46
|
+
* WAV rather than the browser's native webm/opus: whisper.cpp reads wav, flac,
|
|
47
|
+
* mp3 and Vorbis-ogg only, and converting Opus on the host would mean depending
|
|
48
|
+
* on ffmpeg being installed as well.
|
|
49
|
+
*/
|
|
50
|
+
speechRoutes.post("/transcribe", async (c) => {
|
|
51
|
+
const lang = c.req.query("lang") ?? "vi";
|
|
52
|
+
if (!/^(auto|[a-z]{2})$/.test(lang)) return c.json(err("invalid lang"), 400);
|
|
53
|
+
|
|
54
|
+
const body = new Uint8Array(await c.req.arrayBuffer());
|
|
55
|
+
if (body.byteLength === 0) return c.json(err("empty audio"), 400);
|
|
56
|
+
if (body.byteLength > MAX_AUDIO_BYTES) return c.json(err("audio too long"), 413);
|
|
57
|
+
|
|
58
|
+
const status = getWhisperStatus();
|
|
59
|
+
if (!status.ready) return c.json(err("Whisper is not installed on this host"), 409);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
return c.json(ok(await transcribeWav(body, lang)));
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return c.json(err(message(e)), 500);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Services page routes, mounted under /api/system behind auth.
|
|
3
|
+
*
|
|
4
|
+
* GET /services both scopes in one snapshot
|
|
5
|
+
* GET /services/:scope/:unit details plus this boot's journal
|
|
6
|
+
* POST /services/:scope/:unit/:action guarded start/stop/restart/enable/disable
|
|
7
|
+
*
|
|
8
|
+
* The action route is the only one that changes anything, and it is guarded
|
|
9
|
+
* twice: the cross-origin header pair, then PPM's own refusals (which is the
|
|
10
|
+
* SAME function that produced each row's `refused` map, so a greyed-out button
|
|
11
|
+
* and a 403 always agree).
|
|
12
|
+
*/
|
|
13
|
+
import { Hono } from "hono";
|
|
14
|
+
import { ok, err } from "../../types/api.ts";
|
|
15
|
+
import type { ServiceAction, ServiceScope } from "../../types/system-services.ts";
|
|
16
|
+
import { SERVICE_ACTIONS } from "../../types/system-services.ts";
|
|
17
|
+
import {
|
|
18
|
+
collectServices, createSystemdServices, runServiceAction, serviceDetails,
|
|
19
|
+
ServiceActionRefused, type SystemdDeps,
|
|
20
|
+
} from "../../services/system-services/systemd-collector.ts";
|
|
21
|
+
import { isPlausibleUnitName } from "../../services/system-services/service-guard.ts";
|
|
22
|
+
import { crossOriginRefusal } from "./cross-origin-guard.ts";
|
|
23
|
+
|
|
24
|
+
const SCOPES: readonly string[] = ["system", "user"];
|
|
25
|
+
const isScope = (value: string): value is ServiceScope => SCOPES.includes(value);
|
|
26
|
+
|
|
27
|
+
export function createSystemServiceRoutes(deps: SystemdDeps = createSystemdServices()): Hono {
|
|
28
|
+
const routes = new Hono();
|
|
29
|
+
|
|
30
|
+
routes.get("/services", async (c) => c.json(ok(await collectServices(deps))));
|
|
31
|
+
|
|
32
|
+
routes.get("/services/:scope/:unit", async (c) => {
|
|
33
|
+
const scope = c.req.param("scope") ?? "";
|
|
34
|
+
const unit = c.req.param("unit") ?? "";
|
|
35
|
+
if (!isScope(scope)) return c.json(err("Scope must be system or user"), 400);
|
|
36
|
+
if (!isPlausibleUnitName(unit)) return c.json(err("Not a unit name"), 400);
|
|
37
|
+
const details = await serviceDetails(unit, scope, deps);
|
|
38
|
+
if (!details) return c.json(err(`No unit named ${unit}`), 404);
|
|
39
|
+
return c.json(ok(details));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
routes.post("/services/:scope/:unit/:action", async (c) => {
|
|
43
|
+
const refusal = crossOriginRefusal(c);
|
|
44
|
+
if (refusal) return c.json(err(refusal), 400);
|
|
45
|
+
|
|
46
|
+
const scope = c.req.param("scope") ?? "";
|
|
47
|
+
const unit = c.req.param("unit") ?? "";
|
|
48
|
+
const action = c.req.param("action") ?? "";
|
|
49
|
+
if (!isScope(scope)) return c.json(err("Scope must be system or user"), 400);
|
|
50
|
+
if (!isPlausibleUnitName(unit)) return c.json(err("Not a unit name"), 400);
|
|
51
|
+
if (!SERVICE_ACTIONS.includes(action as ServiceAction)) {
|
|
52
|
+
return c.json(err(`Action must be one of ${SERVICE_ACTIONS.join("|")}`), 400);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Audit line: unit, scope, action and outcome only. No command lines and no
|
|
56
|
+
// journal text — the tail of ~/.ppm/ppm.log is served unauthenticated.
|
|
57
|
+
const prefix = `[SystemServices] ${action} ${scope}/${unit}`;
|
|
58
|
+
try {
|
|
59
|
+
const result = await runServiceAction(unit, scope, action as ServiceAction, deps);
|
|
60
|
+
console.log(`${prefix} -> done`);
|
|
61
|
+
return c.json(ok(result));
|
|
62
|
+
} catch (e) {
|
|
63
|
+
if (e instanceof ServiceActionRefused) {
|
|
64
|
+
console.log(`${prefix} -> refused: ${e.message}`);
|
|
65
|
+
return c.json(err(e.message), 403);
|
|
66
|
+
}
|
|
67
|
+
const message = (e as Error)?.message ?? "Action failed";
|
|
68
|
+
console.log(`${prefix} -> failed: ${message}`);
|
|
69
|
+
return c.json(err(message), 500);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return routes;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const systemServiceRoutes = createSystemServiceRoutes();
|
|
@@ -5,6 +5,7 @@ import { getLocalIp } from "../../lib/network-utils.ts";
|
|
|
5
5
|
import { ok, err } from "../../types/api.ts";
|
|
6
6
|
import { getConfigValue } from "../../services/db.service.ts";
|
|
7
7
|
import { resolveTunnelConfig } from "../../services/named-tunnel/named-tunnel-config.ts";
|
|
8
|
+
import { requestTunnelReload } from "../../services/supervisor-state.ts";
|
|
8
9
|
|
|
9
10
|
export const tunnelRoutes = new Hono();
|
|
10
11
|
|
|
@@ -14,7 +15,31 @@ tunnelRoutes.get("/", (c) => {
|
|
|
14
15
|
const port = configService.get("port") ?? 8080;
|
|
15
16
|
const localIp = getLocalIp();
|
|
16
17
|
const localUrl = localIp ? `http://${localIp}:${port}` : null;
|
|
17
|
-
|
|
18
|
+
// `enabled` is what the user asked for; `active` is what is actually serving.
|
|
19
|
+
// They disagree for the seconds between a toggle and the supervisor acting on
|
|
20
|
+
// it, and whenever a tunnel the switch permits has failed to come up.
|
|
21
|
+
const enabled = resolveTunnelConfig(getConfigValue("tunnel")).enabled;
|
|
22
|
+
return c.json(ok({ active: !!url, url, localUrl, enabled }));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* POST /api/tunnel/enabled — the master switch.
|
|
27
|
+
*
|
|
28
|
+
* Writing the config is only half of it: the supervisor owns the cloudflared
|
|
29
|
+
* process and lives in another process with its own config cache, so the change
|
|
30
|
+
* reaches it as a `retunnel`, which it already knows how to claim atomically.
|
|
31
|
+
* `no-supervisor` is not an error — a dev server started without one still
|
|
32
|
+
* persists the choice for the next real start.
|
|
33
|
+
*/
|
|
34
|
+
tunnelRoutes.post("/enabled", async (c) => {
|
|
35
|
+
const body = await c.req.json().catch(() => null) as { enabled?: unknown } | null;
|
|
36
|
+
if (typeof body?.enabled !== "boolean") {
|
|
37
|
+
return c.json(err("`enabled` must be a boolean"), 400);
|
|
38
|
+
}
|
|
39
|
+
const current = configService.get("tunnel");
|
|
40
|
+
configService.set("tunnel", { ...current, enabled: body.enabled });
|
|
41
|
+
const reload = requestTunnelReload();
|
|
42
|
+
return c.json(ok({ enabled: body.enabled, reload }));
|
|
18
43
|
});
|
|
19
44
|
|
|
20
45
|
/** POST /api/tunnel/start — start tunnel if not already running */
|
|
@@ -26,6 +51,13 @@ tunnelRoutes.post("/start", async (c) => {
|
|
|
26
51
|
// round-robins between the two, so ~half of requests hit whichever origin the
|
|
27
52
|
// stale connector still points at.
|
|
28
53
|
const cfg = resolveTunnelConfig(getConfigValue("tunnel"));
|
|
54
|
+
// The master switch has to hold here too, or it is not one. This route spawns
|
|
55
|
+
// cloudflared from the *server* process, which the supervisor never learns a pid
|
|
56
|
+
// for — so a public URL raised past a switched-off tunnel is also one nothing
|
|
57
|
+
// will reap except the next restart's orphan sweep.
|
|
58
|
+
if (!cfg.enabled) {
|
|
59
|
+
return c.json(err("public tunnel is switched off"), 409);
|
|
60
|
+
}
|
|
29
61
|
if (cfg.mode === "named") {
|
|
30
62
|
return c.json(err("named tunnel is supervisor-managed"), 409);
|
|
31
63
|
}
|
package/src/server/ws/chat.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { providerRegistry } from "../../providers/registry.ts";
|
|
|
3
3
|
import { resolveProjectPath } from "../helpers/resolve-project.ts";
|
|
4
4
|
import { logSessionEvent } from "../../services/session-log.service.ts";
|
|
5
5
|
import { listSessions as sdkListSessions } from "@anthropic-ai/claude-agent-sdk";
|
|
6
|
-
import { getSessionTitle, incrementSessionUnread, clearSessionUnread, getSessionModel, setSessionModel, getSessionProvider, getSessionEffort, setSessionEffort, getSessionThinking, setSessionThinking, setSessionMigratedTo, getSessionDesignSlug, setSessionPermissionMode } from "../../services/db.service.ts";
|
|
6
|
+
import { getSessionTitle, incrementSessionUnread, clearSessionUnread, getSessionModel, setSessionModel, getSessionProvider, getSessionEffort, setSessionEffort, getSessionThinking, setSessionThinking, setSessionMigratedTo, getSessionDesignSlug, setSessionPermissionMode, getLastTurnCacheState } from "../../services/db.service.ts";
|
|
7
7
|
import { VALID_PERMISSION_MODES } from "../../types/config.ts";
|
|
8
8
|
import { VALID_EFFORT_VALUES, THINKING_ADAPTIVE, isThinkingEnabled } from "../../providers/claude-agent-sdk-query-options.ts";
|
|
9
9
|
import type { ChatWsClientMessage, SessionPhase } from "../../types/api.ts";
|
|
@@ -16,7 +16,8 @@ import { resolveSessionDir } from "../../services/subagent-transcript-merger.ts"
|
|
|
16
16
|
import { backgroundShellRegistry } from "../../services/background-shell-registry.ts";
|
|
17
17
|
import { basename } from "node:path";
|
|
18
18
|
import { configService } from "../../services/config.service.ts";
|
|
19
|
-
import { formatTurnUsageLog } from "../../shared/turn-usage.ts";
|
|
19
|
+
import { formatTurnUsageLog, prefixTokens } from "../../shared/turn-usage.ts";
|
|
20
|
+
import type { PromptCacheState } from "../../shared/prompt-cache-idle.ts";
|
|
20
21
|
import { isAsyncAgentLaunchAck, isTerminalAgentStatus } from "../../shared/background-agent-status.ts";
|
|
21
22
|
import { cacheReleaseDelayMs, selectWarmIdleEvictions } from "../../services/subprocess-retention.ts";
|
|
22
23
|
import { needsAuthServerNames } from "../../services/mcp-oauth/mcp-oauth-redirect.ts";
|
|
@@ -131,6 +132,14 @@ interface SessionEntry {
|
|
|
131
132
|
idleSince?: number;
|
|
132
133
|
/** When the last turn completed — the moment this session's prompt cache was last written */
|
|
133
134
|
lastTurnEndedAt?: number;
|
|
135
|
+
/** Transcript replayed to the API on that turn — what re-caching it would cost again */
|
|
136
|
+
lastTurnPrefixTokens?: number;
|
|
137
|
+
/** Context that turn actually held, measured per API call rather than summed */
|
|
138
|
+
lastTurnContextTokens?: number;
|
|
139
|
+
/** Cache window the API reported on that turn, outranking the credential-shaped guess */
|
|
140
|
+
lastTurnCacheTtlMs?: number;
|
|
141
|
+
/** A compaction the last turn ended on, which leaves the cached prefix inapplicable */
|
|
142
|
+
lastTurnCompactedAt?: number;
|
|
134
143
|
/**
|
|
135
144
|
* When the message that opened the current turn reached the server — what "time to first
|
|
136
145
|
* event" is measured from. The consumer loop outlives turns, so its own clock would count
|
|
@@ -235,7 +244,12 @@ function scheduleSubprocessRelease(sessionId: string): void {
|
|
|
235
244
|
const note = "Subprocess released: its prompt cache has expired, so keeping it warm saves nothing";
|
|
236
245
|
// The window is the provider's to state: an API-key install's cache dies at five minutes,
|
|
237
246
|
// so holding the subprocess for an hour there guards nothing and costs ~350MB.
|
|
238
|
-
|
|
247
|
+
// The API's own answer where the turn gave one, the provider's inference otherwise: an
|
|
248
|
+
// install wrongly read as API-key would drop a subprocess at minute five and pay to rebuild
|
|
249
|
+
// a prefix whose cache had fifty-five minutes left. Entry-only, with no DB lookup — a
|
|
250
|
+
// session with no turn in this process has no `lastTurnEndedAt` either, so the delay is 0
|
|
251
|
+
// regardless of the window.
|
|
252
|
+
const ttlMs = entry.lastTurnCacheTtlMs ?? provider.promptCacheTtlMs?.(sessionId);
|
|
239
253
|
const delay = cacheReleaseDelayMs(entry.lastTurnEndedAt, Date.now(), ttlMs);
|
|
240
254
|
if (delay === 0) {
|
|
241
255
|
releaseSubprocess(sessionId, "cache_expired", note);
|
|
@@ -248,6 +262,54 @@ function scheduleSubprocessRelease(sessionId: string): void {
|
|
|
248
262
|
}, delay);
|
|
249
263
|
}
|
|
250
264
|
|
|
265
|
+
/**
|
|
266
|
+
* What a reconnecting client needs to say whether this session's prompt cache is still warm.
|
|
267
|
+
*
|
|
268
|
+
* The three facts are only known here: when the cache was last written, how long this
|
|
269
|
+
* install's caches live, and how much transcript would have to be re-sent. The browser has
|
|
270
|
+
* none of them after a reload — `ChatMessage.usage` is attached from the live `done` event
|
|
271
|
+
* and is not in the transcript — so a tab reopened the next morning would otherwise have no
|
|
272
|
+
* way to warn that the first message of the day is the expensive one.
|
|
273
|
+
*
|
|
274
|
+
* Null until a turn has both completed and reported its usage: with nothing cached there is
|
|
275
|
+
* nothing to lose, and a size PPM cannot measure must not be guessed at.
|
|
276
|
+
*/
|
|
277
|
+
function promptCacheSnapshot(sessionId: string, entry: SessionEntry): PromptCacheState | null {
|
|
278
|
+
const provider = providerRegistry.get(entry.providerId);
|
|
279
|
+
const declaredTtlMs = provider?.promptCacheTtlMs?.(sessionId);
|
|
280
|
+
// A provider with no opinion has no Anthropic prompt cache to warn about. Asked before the
|
|
281
|
+
// measured window is consulted, because this gate is about whether there is a cache at all.
|
|
282
|
+
if (declaredTtlMs == null) return null;
|
|
283
|
+
|
|
284
|
+
// The entry is memory, and memory is the short-lived half of this. `CLEANUP_TIMEOUT_MS`
|
|
285
|
+
// drops it five minutes after the last tab leaves, and a restart drops it at once — both
|
|
286
|
+
// well inside the hour the cache it describes actually lives. So a session reopened later
|
|
287
|
+
// has to answer from `turn_usage`, which recorded the same three facts on every turn.
|
|
288
|
+
// Memory still wins when it has them: it is this process's own turn, with no clock
|
|
289
|
+
// conversion between here and SQLite's UTC text.
|
|
290
|
+
const persisted = entry.lastTurnEndedAt == null ? getLastTurnCacheState(sessionId) : null;
|
|
291
|
+
const lastTurnEndedAt = entry.lastTurnEndedAt ?? persisted?.endedAtMs;
|
|
292
|
+
const billedPrefixTokens = entry.lastTurnPrefixTokens ?? persisted?.prefixTokens;
|
|
293
|
+
const contextTokens = entry.lastTurnContextTokens ?? persisted?.contextTokens;
|
|
294
|
+
// `usage.cache_creation` beats the guess `promptCacheTtlMs` makes from the credential's
|
|
295
|
+
// shape, which is wrong for a proxy, a custom base_url, or a subscription past its limits.
|
|
296
|
+
const ttlMs = entry.lastTurnCacheTtlMs ?? persisted?.cacheTtlMs ?? declaredTtlMs;
|
|
297
|
+
// Memory first again, and here that matters in the other direction: a live entry that has
|
|
298
|
+
// re-cached since holds `undefined`, which must beat the stale compaction still on the
|
|
299
|
+
// row this session's last turn wrote.
|
|
300
|
+
const compactedAt = entry.lastTurnEndedAt != null ? entry.lastTurnCompactedAt : persisted?.compactedAt;
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
ttlMs,
|
|
304
|
+
// Sent even before a turn has completed: the window is the install's, and a tab that
|
|
305
|
+
// stays connected all day needs it to arm the notice from its own turns.
|
|
306
|
+
...(lastTurnEndedAt != null && { lastTurnEndedAt }),
|
|
307
|
+
...(billedPrefixTokens != null && { billedPrefixTokens }),
|
|
308
|
+
...(contextTokens != null && { contextTokens }),
|
|
309
|
+
...(compactedAt != null && { compactedAt }),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
251
313
|
/** Push the current background-shell registry snapshot to a session's clients. */
|
|
252
314
|
function broadcastBackgroundRegistry(sessionId: string): void {
|
|
253
315
|
broadcast(sessionId, {
|
|
@@ -850,6 +912,13 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
850
912
|
// release pending from the disconnect was timed against the previous turn — re-time it
|
|
851
913
|
// or it fires while the cache it was protecting is still fresh.
|
|
852
914
|
entry.lastTurnEndedAt = Date.now();
|
|
915
|
+
if (ev.usage) entry.lastTurnPrefixTokens = prefixTokens(ev.usage);
|
|
916
|
+
if (ev.usage?.contextTokens != null) entry.lastTurnContextTokens = ev.usage.contextTokens;
|
|
917
|
+
if (ev.usage?.cacheTtlMs != null) entry.lastTurnCacheTtlMs = ev.usage.cacheTtlMs;
|
|
918
|
+
// Assigned rather than only-when-present: the provider clears this the moment an API
|
|
919
|
+
// call re-caches, and that clearing is the signal the cache is usable again. Skipping
|
|
920
|
+
// the undefined case would leave a session cold for the rest of its life.
|
|
921
|
+
if (ev.usage) entry.lastTurnCompactedAt = ev.usage.compactedAt;
|
|
853
922
|
if (entry.clients.size === 0) scheduleSubprocessRelease(sessionId);
|
|
854
923
|
|
|
855
924
|
// Fire-and-forget: fetch updated session title (DB title takes priority) + notification
|
|
@@ -1060,6 +1129,7 @@ export const chatWebSocket = {
|
|
|
1060
1129
|
model: resolveSessionModel(sessionId),
|
|
1061
1130
|
effort: resolveSessionEffort(sessionId),
|
|
1062
1131
|
thinking: resolveSessionThinkingEnabled(sessionId),
|
|
1132
|
+
promptCache: promptCacheSnapshot(sessionId, existing),
|
|
1063
1133
|
}));
|
|
1064
1134
|
|
|
1065
1135
|
// If actively streaming, send buffered turn events for reconnect sync
|
|
@@ -1194,6 +1264,7 @@ export const chatWebSocket = {
|
|
|
1194
1264
|
model: resolveSessionModel(sessionId),
|
|
1195
1265
|
effort: resolveSessionEffort(sessionId),
|
|
1196
1266
|
thinking: resolveSessionThinkingEnabled(sessionId),
|
|
1267
|
+
promptCache: promptCacheSnapshot(sessionId, entry),
|
|
1197
1268
|
}));
|
|
1198
1269
|
if (entry.phase !== "idle") {
|
|
1199
1270
|
sendTurnEvents(sessionId, ws);
|
|
@@ -88,6 +88,8 @@ interface ChatGptLogin {
|
|
|
88
88
|
/** True from the completion notification until the account is written. The
|
|
89
89
|
* home must survive this window even if the browser walks away. */
|
|
90
90
|
finalizing: boolean;
|
|
91
|
+
/** Settles on the app-server's account/updated — see finalizeLogin for why it matters. */
|
|
92
|
+
authReloaded: Promise<void>;
|
|
91
93
|
}
|
|
92
94
|
const pendingLogins = new Map<string, ChatGptLogin>();
|
|
93
95
|
|
|
@@ -142,6 +144,14 @@ function settle(id: string, status: Exclude<DeviceLoginStatus, { state: "pending
|
|
|
142
144
|
p.timer = unref(setTimeout(() => { pendingLogins.delete(id); }, RESULT_GRACE_MS));
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
/** Bounded: an app-server that never announces the reload still gets its second read. */
|
|
148
|
+
function waitForAuthReload(p: ChatGptLogin): Promise<void> {
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
const t = unref(setTimeout(resolve, CONTROL_REQUEST_TIMEOUT_MS));
|
|
151
|
+
void p.authReloaded.then(() => { clearTimeout(t); resolve(); });
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
145
155
|
/** Turn the app-server's completion notification into a persisted account. */
|
|
146
156
|
async function finalizeLogin(id: string, notif: CompletionNotification): Promise<void> {
|
|
147
157
|
const p = pendingLogins.get(id);
|
|
@@ -151,7 +161,15 @@ async function finalizeLogin(id: string, notif: CompletionNotification): Promise
|
|
|
151
161
|
// Authorization has completed; account/read has its own bounded timeout.
|
|
152
162
|
clearTimeout(p.timer);
|
|
153
163
|
try {
|
|
154
|
-
|
|
164
|
+
let read = await p.client.request<AccountRead>("account/read", {}, CONTROL_REQUEST_TIMEOUT_MS);
|
|
165
|
+
if (!read?.account) {
|
|
166
|
+
// codex sends account/login/completed BEFORE it reloads the auth the login just wrote,
|
|
167
|
+
// and announces the reload with account/updated (send_chatgpt_login_completion_notifications,
|
|
168
|
+
// rust-v0.154.0). A read that lands in between finds no account, although the sign-in
|
|
169
|
+
// succeeded — so an empty answer waits for the reload and asks once more.
|
|
170
|
+
await waitForAuthReload(p);
|
|
171
|
+
read = await p.client.request<AccountRead>("account/read", {}, CONTROL_REQUEST_TIMEOUT_MS);
|
|
172
|
+
}
|
|
155
173
|
if (!read?.account) throw new Error("login completed but account is empty");
|
|
156
174
|
if (p.status.state !== "pending") return;
|
|
157
175
|
if (p.reloginId) {
|
|
@@ -200,15 +218,21 @@ async function startChatGptLogin(
|
|
|
200
218
|
const home = target ? target.home : codexAccountHome(id);
|
|
201
219
|
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
202
220
|
const client = makeClient();
|
|
221
|
+
// Registered with the completion handler, not after it: the reload can be announced
|
|
222
|
+
// before the first account/read has even answered.
|
|
223
|
+
let markAuthReloaded!: () => void;
|
|
224
|
+
const authReloaded = new Promise<void>((resolve) => { markAuthReloaded = resolve; });
|
|
203
225
|
client.onNotification((n) => {
|
|
204
226
|
if (n.method === "account/login/completed") {
|
|
205
227
|
void finalizeLogin(id, (n.params ?? {}) as CompletionNotification);
|
|
228
|
+
} else if (n.method === "account/updated") {
|
|
229
|
+
markAuthReloaded();
|
|
206
230
|
}
|
|
207
231
|
});
|
|
208
232
|
client.onClose(() => settle(id, { state: "error", error: "login process exited" }));
|
|
209
233
|
const timer = unref(setTimeout(() => settle(id, { state: "error", error: "timed out" }), method === "browser" ? BROWSER_LOGIN_TTL : DEVICE_LOGIN_TTL));
|
|
210
234
|
const pending: ChatGptLogin = {
|
|
211
|
-
method, client, home, label, status: { state: "pending" }, timer, finalizing: false,
|
|
235
|
+
method, client, home, label, status: { state: "pending" }, timer, finalizing: false, authReloaded,
|
|
212
236
|
...(target ? { reloginId: target.id } : {}),
|
|
213
237
|
};
|
|
214
238
|
pendingLogins.set(id, pending);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Use reset" for a Codex account: spend one free rate-limit reset — but only once a limit has
|
|
3
|
+
* actually been reached.
|
|
4
|
+
*
|
|
5
|
+
* The gate lives on the server, not just on the button. A reset is one-use, it moves the
|
|
6
|
+
* weekly reset date (OpenAI: the next weekly reset is counted from when you continue, and the
|
|
7
|
+
* original one is not also granted), and spending it early throws away whatever was left of
|
|
8
|
+
* the window. So the decision is made against a *live* quota read taken right here, never
|
|
9
|
+
* against whatever the card was last shown.
|
|
10
|
+
*/
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { getCodexAccount } from "./codex-account.service.ts";
|
|
13
|
+
import { clearCodexAccountUsageLimit, isCodexAccountUsageLimited } from "./codex-account-cooldown.ts";
|
|
14
|
+
import { refreshUsage } from "./provider-usage/usage-registry.ts";
|
|
15
|
+
import { fetchCodexUsageLive } from "../providers/codex-app-server/codex-usage-fetch.ts";
|
|
16
|
+
import { consumeCodexResetCredit, type ResetCreditOutcome } from "../providers/codex-app-server/codex-reset-credit.ts";
|
|
17
|
+
import { usageLimitReached } from "../shared/usage-extra.ts";
|
|
18
|
+
import type { UsageInfo } from "../types/chat.ts";
|
|
19
|
+
|
|
20
|
+
/** A refusal the caller should show as-is, with the HTTP status that fits it. */
|
|
21
|
+
export class ResetCreditRefusedError extends Error {
|
|
22
|
+
constructor(message: string, readonly status: 404 | 409 | 502) { super(message); }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Accounts with a reset in flight — a double click must not become two attempts. */
|
|
26
|
+
const inFlight = new Set<string>();
|
|
27
|
+
|
|
28
|
+
export interface ResetCreditResult {
|
|
29
|
+
outcome: ResetCreditOutcome;
|
|
30
|
+
/** The account's quota after the attempt, freshly read. */
|
|
31
|
+
usage: UsageInfo;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The ways out to Codex, replaceable so a test can stand in for the app-server.
|
|
36
|
+
*
|
|
37
|
+
* All three of them. `refresh` was reached directly for a while, which left one door open in
|
|
38
|
+
* a seam whose whole point is that there are none: a test that faked the other two still made
|
|
39
|
+
* a real usage read on the way out, and the only sign of it was the call taking whatever the
|
|
40
|
+
* network took — five seconds and a timeout when something earlier in the process had left a
|
|
41
|
+
* connection to wait on.
|
|
42
|
+
*/
|
|
43
|
+
export interface ResetCreditCodexPorts {
|
|
44
|
+
readUsage: (codexHome: string) => Promise<UsageInfo>;
|
|
45
|
+
consume: (codexHome: string, idempotencyKey: string, creditId?: string) => Promise<ResetCreditOutcome>;
|
|
46
|
+
refresh: (accountId: string) => Promise<UsageInfo>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const LIVE_PORTS: ResetCreditCodexPorts = {
|
|
50
|
+
readUsage: fetchCodexUsageLive,
|
|
51
|
+
consume: consumeCodexResetCredit,
|
|
52
|
+
refresh: (accountId) => refreshUsage("codex", accountId),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export async function spendCodexResetCredit(accountId: string, ports: ResetCreditCodexPorts = LIVE_PORTS): Promise<ResetCreditResult> {
|
|
56
|
+
const account = getCodexAccount(accountId);
|
|
57
|
+
if (!account) throw new ResetCreditRefusedError("Account not found", 404);
|
|
58
|
+
if (inFlight.has(accountId)) throw new ResetCreditRefusedError("A reset for this account is already in progress.", 409);
|
|
59
|
+
inFlight.add(accountId);
|
|
60
|
+
try {
|
|
61
|
+
let live: UsageInfo;
|
|
62
|
+
try {
|
|
63
|
+
live = await ports.readUsage(account.home);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// Refuse rather than guess: the only safe answer to "is the limit reached?" is a fresh one.
|
|
66
|
+
throw new ResetCreditRefusedError(`Could not read ${account.label}'s current usage, so no reset was used: ${(e as Error).message}`, 502);
|
|
67
|
+
}
|
|
68
|
+
// Codex refusing a turn for quota is also a reached limit, even if the figures lag it.
|
|
69
|
+
if (!usageLimitReached(live) && !isCodexAccountUsageLimited(accountId)) {
|
|
70
|
+
throw new ResetCreditRefusedError(`${account.label} has not reached a limit yet, so a reset would waste the usage left and move the weekly reset date. It is kept for when you hit the limit.`, 409);
|
|
71
|
+
}
|
|
72
|
+
if ((live.resetCredits?.available ?? 0) === 0) {
|
|
73
|
+
throw new ResetCreditRefusedError(`${account.label} has no free reset left.`, 409);
|
|
74
|
+
}
|
|
75
|
+
// The soonest-expiring credit first: the others outlive it.
|
|
76
|
+
const outcome = await ports.consume(account.home, randomUUID(), live.resetCredits?.nextCreditId);
|
|
77
|
+
if (outcome === "reset") clearCodexAccountUsageLimit(accountId);
|
|
78
|
+
console.log(`[codex] account ${accountId} reset credit → ${outcome}`);
|
|
79
|
+
return { outcome, usage: await ports.refresh(accountId) };
|
|
80
|
+
} finally {
|
|
81
|
+
inFlight.delete(accountId);
|
|
82
|
+
}
|
|
83
|
+
}
|