@hienlh/ppm 0.23.6 → 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 +84 -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 +29 -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-Dn_wB-o5.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-BA3y-qSH.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-BioxkrQF.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-BSoJ0E6p.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-DHh5QsH9.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-lctWoSmG.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-rbkXsZVR.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-uPO0WgCq.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-CT7mDb0L.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-DANPfMRN.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-BJPkLiEw.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-R4PcAntf.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-DHRoyWcG.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-BKej4mUf.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-BHtYqkYZ.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-DS_XrpTG.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-D8AES1BA.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-DTBG4beI.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-CqtmbeRj.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-Djt6cpNL.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-DNJF8TcF.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-BKoPovy2.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-BjIMi9Wk.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-BNcA6Irq.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-CIRGNCvi.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-CwXpLi3X.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-4swiiDz7.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-BGcdtMMT.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-D0-krtA2.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-C88KphGx.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-Chk9guIU.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/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/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 +16 -3
- package/src/services/compaction-savings.ts +127 -0
- package/src/services/db.service.ts +123 -8
- 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/shared/prompt-cache-idle.ts +197 -0
- package/src/shared/turn-usage.ts +85 -1
- package/src/types/chat.ts +27 -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/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 +5 -1
- 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-DUVO32SQ.js +0 -1
- package/dist/web/assets/accounts-settings-section-DUVO32SQ.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-DUVO32SQ.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-Dn_wB-o5.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-Dn_wB-o5.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-CEbW8jRy.js +0 -1
- package/dist/web/assets/ai-settings-section-CEbW8jRy.js.br +0 -0
- package/dist/web/assets/ai-settings-section-CEbW8jRy.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-D-mwEYFZ.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-tw6TpFb6.js +0 -1
- package/dist/web/assets/appearance-settings-section-tw6TpFb6.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-tw6TpFb6.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-MBuMWp_i.js +0 -1
- package/dist/web/assets/branch-review-tab-MBuMWp_i.js.br +0 -0
- package/dist/web/assets/branch-review-tab-MBuMWp_i.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-DVvnqhw6.js +0 -1
- package/dist/web/assets/chat-tab-DVvnqhw6.js.br +0 -0
- package/dist/web/assets/chat-tab-DVvnqhw6.js.gz +0 -0
- package/dist/web/assets/chat-tab-DaEHva22.js +0 -15
- package/dist/web/assets/chat-tab-DaEHva22.js.br +0 -0
- package/dist/web/assets/chat-tab-DaEHva22.js.gz +0 -0
- package/dist/web/assets/code-editor-Bu9lhTVp.js +0 -7
- package/dist/web/assets/code-editor-Bu9lhTVp.js.br +0 -0
- package/dist/web/assets/code-editor-Bu9lhTVp.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-CbmE5mRX.js +0 -1
- package/dist/web/assets/codex-reset-credit-button-CbmE5mRX.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-CbmE5mRX.js.gz +0 -0
- package/dist/web/assets/conflict-editor-BA3y-qSH.js.br +0 -0
- package/dist/web/assets/conflict-editor-BA3y-qSH.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-BioxkrQF.js.br +0 -0
- package/dist/web/assets/csv-preview-BioxkrQF.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-BSoJ0E6p.js.br +0 -0
- package/dist/web/assets/database-viewer-BSoJ0E6p.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-iU2CviE3.js +0 -33
- package/dist/web/assets/design-tab-iU2CviE3.js.br +0 -0
- package/dist/web/assets/design-tab-iU2CviE3.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-DHh5QsH9.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-DHh5QsH9.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-lctWoSmG.js.br +0 -0
- package/dist/web/assets/diff-viewer-lctWoSmG.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-fPG56hQO.js +0 -1
- package/dist/web/assets/editor-language-service-fPG56hQO.js.br +0 -0
- package/dist/web/assets/editor-language-service-fPG56hQO.js.gz +0 -0
- package/dist/web/assets/explorer-body-BgF7FsxI.js +0 -2
- package/dist/web/assets/explorer-body-BgF7FsxI.js.br +0 -0
- package/dist/web/assets/explorer-body-BgF7FsxI.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-C21w_7lf.js +0 -1
- package/dist/web/assets/explorer-window-content-C21w_7lf.js.br +0 -0
- package/dist/web/assets/explorer-window-content-C21w_7lf.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-rbkXsZVR.js.br +0 -0
- package/dist/web/assets/extension-manager-section-rbkXsZVR.js.gz +0 -0
- package/dist/web/assets/extension-webview-uPO0WgCq.js.br +0 -0
- package/dist/web/assets/extension-webview-uPO0WgCq.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-CT7mDb0L.js.br +0 -0
- package/dist/web/assets/files-settings-section-CT7mDb0L.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-DANPfMRN.js.br +0 -0
- package/dist/web/assets/general-settings-section-DANPfMRN.js.gz +0 -0
- package/dist/web/assets/git-log-panel-BJPkLiEw.js.br +0 -0
- package/dist/web/assets/git-log-panel-BJPkLiEw.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-R4PcAntf.js.br +0 -0
- package/dist/web/assets/glide-data-grid-R4PcAntf.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-BXMdo8w9.js +0 -1
- package/dist/web/assets/group-chat-tab-BXMdo8w9.js.br +0 -0
- package/dist/web/assets/group-chat-tab-BXMdo8w9.js.gz +0 -0
- package/dist/web/assets/index-C8nGqkKN.js +0 -52
- package/dist/web/assets/index-C8nGqkKN.js.br +0 -0
- package/dist/web/assets/index-C8nGqkKN.js.gz +0 -0
- package/dist/web/assets/index-DvClbTQx.css +0 -1
- package/dist/web/assets/index-DvClbTQx.css.br +0 -0
- package/dist/web/assets/index-DvClbTQx.css.gz +0 -0
- package/dist/web/assets/keybindings-store-BGKlQQAv.js +0 -1
- package/dist/web/assets/keyboard-shortcuts-section-BKej4mUf.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-BKej4mUf.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-BHtYqkYZ.js.br +0 -0
- package/dist/web/assets/markdown-renderer-BHtYqkYZ.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-vDNuKjel.js +0 -1
- 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-DtvxS7-r.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-DtvxS7-r.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-DtvxS7-r.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-DS_XrpTG.js.br +0 -0
- package/dist/web/assets/new-design-dialog-DS_XrpTG.js.gz +0 -0
- package/dist/web/assets/notification-store-Dnnj20Ky.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-D8AES1BA.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-D8AES1BA.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-DTBG4beI.js.br +0 -0
- package/dist/web/assets/problems-panel-DTBG4beI.js.gz +0 -0
- package/dist/web/assets/process-table-D_gR2XA6.js +0 -1
- package/dist/web/assets/process-table-D_gR2XA6.js.br +0 -0
- package/dist/web/assets/process-table-D_gR2XA6.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-CqtmbeRj.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-CqtmbeRj.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-Djt6cpNL.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-Djt6cpNL.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BQxhJUkm.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-BQxhJUkm.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BQxhJUkm.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DGnT8RY7.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-view-DGnT8RY7.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DGnT8RY7.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-DNJF8TcF.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-DNJF8TcF.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-Ds0DKmM0.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-Ds0DKmM0.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-Ds0DKmM0.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-BKoPovy2.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-BKoPovy2.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-k2qPgRFe.js +0 -2
- package/dist/web/assets/settings-body-k2qPgRFe.js.br +0 -0
- package/dist/web/assets/settings-body-k2qPgRFe.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-Bb23VdBu.js +0 -1
- package/dist/web/assets/settings-window-content-ClarWusz.js +0 -1
- package/dist/web/assets/sql-query-editor-BjIMi9Wk.js.br +0 -0
- package/dist/web/assets/sql-query-editor-BjIMi9Wk.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-BNcA6Irq.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-BNcA6Irq.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-D0T3ggt2.js +0 -2
- package/dist/web/assets/system-monitor-body-D0T3ggt2.js.br +0 -0
- package/dist/web/assets/system-monitor-body-D0T3ggt2.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-I1Igmoey.js +0 -1
- package/dist/web/assets/system-monitor-window-content-C5SeUv1H.js +0 -1
- package/dist/web/assets/tab-host-window-content-CIRGNCvi.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-CIRGNCvi.js.gz +0 -0
- package/dist/web/assets/team-member-sheet-CwXpLi3X.js.br +0 -0
- package/dist/web/assets/team-member-sheet-CwXpLi3X.js.gz +0 -0
- package/dist/web/assets/team-member-window-content-4swiiDz7.js.br +0 -0
- package/dist/web/assets/team-member-window-content-4swiiDz7.js.gz +0 -0
- package/dist/web/assets/terminal-tab-BGcdtMMT.js.br +0 -0
- package/dist/web/assets/terminal-tab-BGcdtMMT.js.gz +0 -0
- package/dist/web/assets/tool-cards-D0-krtA2.js.br +0 -0
- package/dist/web/assets/tool-cards-D0-krtA2.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-pKaXyGU-.js +0 -16
- package/dist/web/assets/use-inline-blame-pKaXyGU-.js.br +0 -0
- package/dist/web/assets/use-inline-blame-pKaXyGU-.js.gz +0 -0
- package/dist/web/assets/use-lsp-71azTXQz.js +0 -6
- package/dist/web/assets/use-lsp-71azTXQz.js.br +0 -0
- package/dist/web/assets/use-lsp-71azTXQz.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-DxEX-uGo.js +0 -1
- package/dist/web/assets/video-preview-DxEX-uGo.js.br +0 -0
- package/dist/web/assets/video-preview-DxEX-uGo.js.gz +0 -0
- package/dist/web/assets/ws-auth-kXgsX2Js.js +0 -1
- /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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Services page's backend: TWO `systemctl` spawns for a whole scope's list,
|
|
3
|
+
* one more for a unit's details, one `journalctl` for its log.
|
|
4
|
+
*
|
|
5
|
+
* Two traps, both found against a real host and both load-bearing:
|
|
6
|
+
*
|
|
7
|
+
* - A unit name may begin with a dash. The root mount is literally `-.mount`,
|
|
8
|
+
* so every call that takes unit names passes them after `--`, or systemctl
|
|
9
|
+
* reads the name as an option bundle and the whole call fails.
|
|
10
|
+
* - An action without `--no-ask-password` can BLOCK on a polkit prompt nobody
|
|
11
|
+
* can answer, turning "stop this unit" into a wedged request until the
|
|
12
|
+
* timeout. PPM is not root, so a system-scope action is expected to be
|
|
13
|
+
* refused by systemd; it must be refused quickly and reported plainly.
|
|
14
|
+
*/
|
|
15
|
+
import type {
|
|
16
|
+
ServiceAction, ServiceActionResult, ServiceDetails, ServiceInfo, ServiceLogLine,
|
|
17
|
+
ServiceScope, ServicesSnapshot,
|
|
18
|
+
} from "../../types/system-services.ts";
|
|
19
|
+
import type { RunResult, Runner } from "../host-info/spawn-runner.ts";
|
|
20
|
+
import { defaultRunner } from "../host-info/spawn-runner.ts";
|
|
21
|
+
import { realLinuxFs } from "../system-metrics/linux-fs.ts";
|
|
22
|
+
import {
|
|
23
|
+
keepUnit, parseJournalJson, parseListUnits, parseShowBlock, parseShowRecords,
|
|
24
|
+
SHOW_DETAIL_PROPERTIES, SHOW_PROPERTIES, toServiceInfo, type ListUnitsRow,
|
|
25
|
+
} from "./systemd-parse.ts";
|
|
26
|
+
import { checkServiceActionAllowed, serviceRefusals, type ServiceGuardContext } from "./service-guard.ts";
|
|
27
|
+
|
|
28
|
+
export const SYSTEMCTL_TIMEOUT_MS = 10_000;
|
|
29
|
+
/** An action may legitimately take a while; a stop waits for the unit to settle. */
|
|
30
|
+
export const ACTION_TIMEOUT_MS = 20_000;
|
|
31
|
+
export const MAX_LOG_LINES = 200;
|
|
32
|
+
|
|
33
|
+
export const SCOPES: readonly ServiceScope[] = ["system", "user"];
|
|
34
|
+
|
|
35
|
+
export function systemctlArgv(scope: ServiceScope, args: readonly string[]): string[] {
|
|
36
|
+
return scope === "user" ? ["systemctl", "--user", ...args] : ["systemctl", ...args];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** `--unit=<name>` rather than `--unit <name>`: see the dash trap above. */
|
|
40
|
+
export function journalctlArgv(scope: ServiceScope, unit: string, lines: number): string[] {
|
|
41
|
+
const argv = ["journalctl"];
|
|
42
|
+
if (scope === "user") argv.push("--user");
|
|
43
|
+
argv.push(
|
|
44
|
+
`--unit=${unit}`, "--boot", `--lines=${lines}`,
|
|
45
|
+
"--no-pager", "--output=json", "--output-fields=MESSAGE",
|
|
46
|
+
);
|
|
47
|
+
return argv;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SystemdDeps {
|
|
51
|
+
run: Runner;
|
|
52
|
+
guard: ServiceGuardContext;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Attaches the refusals this unit carries, from the same function the action
|
|
56
|
+
* route enforces — so a greyed-out button and a 403 cannot disagree. */
|
|
57
|
+
export function withRefusals(info: ServiceInfo, guard: ServiceGuardContext): ServiceInfo {
|
|
58
|
+
const refused = serviceRefusals(info.unit, info.scope, guard);
|
|
59
|
+
return Object.keys(refused).length > 0 ? { ...info, refused } : info;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function failureText(result: RunResult): string {
|
|
63
|
+
if (result.timedOut) return "timed out";
|
|
64
|
+
return result.stderr.trim() || result.stdout.trim() || `exited ${result.code}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ScopeListing {
|
|
68
|
+
services: ServiceInfo[];
|
|
69
|
+
warnings: string[];
|
|
70
|
+
/** False when this manager did not answer at all (no systemd, no user bus). */
|
|
71
|
+
ok: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function listScope(scope: ServiceScope, deps: SystemdDeps): Promise<ScopeListing> {
|
|
75
|
+
const list = await deps.run(systemctlArgv(scope, [
|
|
76
|
+
"list-units", "--type=service,socket,mount", "--all", "--no-legend", "--plain", "--no-pager",
|
|
77
|
+
]), SYSTEMCTL_TIMEOUT_MS);
|
|
78
|
+
if (list.timedOut || list.code !== 0) {
|
|
79
|
+
return { services: [], warnings: [`${scope} services unavailable: ${failureText(list)}`], ok: false };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const rows = parseListUnits(list.stdout).filter(keepUnit);
|
|
83
|
+
if (rows.length === 0) return { services: [], warnings: [], ok: true };
|
|
84
|
+
|
|
85
|
+
const show = await deps.run(systemctlArgv(scope, [
|
|
86
|
+
"show", `--property=${SHOW_PROPERTIES.join(",")}`, "--no-pager", "--", ...rows.map((r) => r.unit),
|
|
87
|
+
]), SYSTEMCTL_TIMEOUT_MS);
|
|
88
|
+
|
|
89
|
+
const warnings: string[] = [];
|
|
90
|
+
let byId = new Map<string, Record<string, string>>();
|
|
91
|
+
if (show.timedOut || show.code !== 0) {
|
|
92
|
+
// The list still stands: LOAD/ACTIVE/SUB came from `list-units`. Only the
|
|
93
|
+
// unit-file state and the main pid are lost, so the page degrades rather
|
|
94
|
+
// than emptying.
|
|
95
|
+
warnings.push(`${scope} service details unavailable: ${failureText(show)}`);
|
|
96
|
+
} else {
|
|
97
|
+
byId = parseShowRecords(show.stdout);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const services = rows.map((row) => withRefusals(toServiceInfo(row, scope, byId.get(row.unit)), deps.guard));
|
|
101
|
+
return { services, warnings, ok: true };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function collectServices(deps: SystemdDeps): Promise<ServicesSnapshot> {
|
|
105
|
+
const listings = await Promise.all(SCOPES.map((scope) => listScope(scope, deps)));
|
|
106
|
+
return {
|
|
107
|
+
// Either manager answering is enough: a container commonly has the system
|
|
108
|
+
// one and no user session, and a headless server the other way round.
|
|
109
|
+
supported: listings.some((l) => l.ok),
|
|
110
|
+
services: listings.flatMap((l) => l.services),
|
|
111
|
+
warnings: listings.flatMap((l) => l.warnings),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function serviceLogs(
|
|
116
|
+
unit: string,
|
|
117
|
+
scope: ServiceScope,
|
|
118
|
+
deps: SystemdDeps,
|
|
119
|
+
lines: number = MAX_LOG_LINES,
|
|
120
|
+
): Promise<ServiceLogLine[]> {
|
|
121
|
+
const result = await deps.run(journalctlArgv(scope, unit, lines), SYSTEMCTL_TIMEOUT_MS);
|
|
122
|
+
// Not gated on the exit code: journalctl exits non-zero for a unit with no
|
|
123
|
+
// entries this boot while still being perfectly healthy.
|
|
124
|
+
if (result.timedOut || !result.stdout.trim()) return [];
|
|
125
|
+
return parseJournalJson(result.stdout);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Null when the unit does not exist — the route turns that into a 404. */
|
|
129
|
+
export async function serviceDetails(
|
|
130
|
+
unit: string,
|
|
131
|
+
scope: ServiceScope,
|
|
132
|
+
deps: SystemdDeps,
|
|
133
|
+
): Promise<ServiceDetails | null> {
|
|
134
|
+
const show = await deps.run(systemctlArgv(scope, [
|
|
135
|
+
"show", `--property=${SHOW_DETAIL_PROPERTIES.join(",")}`, "--no-pager", "--", unit,
|
|
136
|
+
]), SYSTEMCTL_TIMEOUT_MS);
|
|
137
|
+
if (show.timedOut || show.code !== 0) return null;
|
|
138
|
+
|
|
139
|
+
const record = parseShowBlock(show.stdout);
|
|
140
|
+
// `show` answers for a unit it has never heard of too, with LoadState=not-found
|
|
141
|
+
// and every other field blank. That is a 404, not a unit with no description.
|
|
142
|
+
if (!record.Id || record.LoadState === "not-found") return null;
|
|
143
|
+
|
|
144
|
+
const row: ListUnitsRow = {
|
|
145
|
+
unit: record.Id,
|
|
146
|
+
loadState: record.LoadState ?? "",
|
|
147
|
+
activeState: record.ActiveState ?? "",
|
|
148
|
+
subState: record.SubState ?? "",
|
|
149
|
+
description: record.Description ?? "",
|
|
150
|
+
};
|
|
151
|
+
const info = withRefusals(toServiceInfo(row, scope, record), deps.guard);
|
|
152
|
+
return {
|
|
153
|
+
...info,
|
|
154
|
+
user: record.User || null,
|
|
155
|
+
group: record.Group || null,
|
|
156
|
+
fragmentPath: record.FragmentPath || null,
|
|
157
|
+
logs: await serviceLogs(record.Id, scope, deps),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Thrown when PPM's own guard refuses; the route maps it to 403, not 500. */
|
|
162
|
+
export class ServiceActionRefused extends Error {
|
|
163
|
+
constructor(message: string) {
|
|
164
|
+
super(message);
|
|
165
|
+
this.name = "ServiceActionRefused";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function actionFailureText(result: RunResult, scope: ServiceScope): string {
|
|
170
|
+
const text = failureText(result);
|
|
171
|
+
if (/interactive authentication required/i.test(text)) {
|
|
172
|
+
return scope === "system"
|
|
173
|
+
? "systemd refused: PPM does not run as root, so system units cannot be changed from here"
|
|
174
|
+
: "systemd refused: this action needs an authentication PPM cannot provide";
|
|
175
|
+
}
|
|
176
|
+
return text;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function runServiceAction(
|
|
180
|
+
unit: string,
|
|
181
|
+
scope: ServiceScope,
|
|
182
|
+
action: ServiceAction,
|
|
183
|
+
deps: SystemdDeps,
|
|
184
|
+
): Promise<ServiceActionResult> {
|
|
185
|
+
const verdict = checkServiceActionAllowed(unit, scope, action, deps.guard);
|
|
186
|
+
if (!verdict.allowed) throw new ServiceActionRefused(verdict.reason ?? "Refused");
|
|
187
|
+
|
|
188
|
+
const result = await deps.run(
|
|
189
|
+
systemctlArgv(scope, ["--no-ask-password", action, "--no-pager", "--", unit]),
|
|
190
|
+
ACTION_TIMEOUT_MS,
|
|
191
|
+
);
|
|
192
|
+
if (result.timedOut || result.code !== 0) throw new Error(actionFailureText(result, scope));
|
|
193
|
+
return { unit, scope, action };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** PPM's own cgroup, which is what the guard derives its refusals from. */
|
|
197
|
+
export function readSelfCgroup(): string | null {
|
|
198
|
+
return realLinuxFs.read("/proc/self/cgroup");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function createSystemdServices(run: Runner = defaultRunner): SystemdDeps {
|
|
202
|
+
return { run, guard: { selfCgroup: readSelfCgroup() } };
|
|
203
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parsers for systemd's text output - Mission Center's Services page, read
|
|
3
|
+
* through `systemctl` instead of D-Bus.
|
|
4
|
+
*
|
|
5
|
+
* MC talks to org.freedesktop.systemd1 over zbus. PPM has no D-Bus client in Bun,
|
|
6
|
+
* and shelling out is not the compromise it looks like: `list-units` plus ONE
|
|
7
|
+
* multi-unit `show` covers all 239 units on this host in 6 ms total, against MC's
|
|
8
|
+
* property read per unit. What is lost is change signalling, so the page polls
|
|
9
|
+
* (SERVICES_POLL_INTERVAL_MS) rather than subscribing.
|
|
10
|
+
*
|
|
11
|
+
* Mission Center also reads `list-unit-files` for the enabled flag. That call
|
|
12
|
+
* alone measured 322 ms here and `show` already answers UnitFileState for every
|
|
13
|
+
* unit that has one - the 26 units it leaves empty have no unit file at all, so
|
|
14
|
+
* the file list would not know them either. It is not made.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here spawns anything; every function takes the captured stdout.
|
|
17
|
+
*/
|
|
18
|
+
import type { ServiceInfo, ServiceLogLine, ServiceScope } from "../../types/system-services.ts";
|
|
19
|
+
|
|
20
|
+
/** Mission Center lists exactly these three unit types and ignores the rest. */
|
|
21
|
+
export const LISTED_SUFFIXES: readonly string[] = [".service", ".socket", ".mount"];
|
|
22
|
+
|
|
23
|
+
export function isListedUnit(name: string): boolean {
|
|
24
|
+
return LISTED_SUFFIXES.some((s) => name.endsWith(s));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ListUnitsRow {
|
|
28
|
+
unit: string;
|
|
29
|
+
loadState: string;
|
|
30
|
+
activeState: string;
|
|
31
|
+
subState: string;
|
|
32
|
+
description: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `systemctl list-units --plain --no-legend` gives UNIT LOAD ACTIVE SUB DESCRIPTION.
|
|
37
|
+
*
|
|
38
|
+
* The `--plain` flag is supposed to drop the leading status bullet, but a failed
|
|
39
|
+
* unit is still marked in some versions, so it is stripped defensively - an
|
|
40
|
+
* unstripped bullet would shift every column by one and rename every unit.
|
|
41
|
+
*/
|
|
42
|
+
export function parseListUnits(stdout: string): ListUnitsRow[] {
|
|
43
|
+
const rows: ListUnitsRow[] = [];
|
|
44
|
+
for (const line of stdout.split("\n")) {
|
|
45
|
+
const cleaned = line.trim().replace(/^[●○×→*]\s+/, "");
|
|
46
|
+
if (!cleaned) continue;
|
|
47
|
+
const parts = cleaned.split(/\s+/);
|
|
48
|
+
if (parts.length < 4) continue;
|
|
49
|
+
const [unit, loadState, activeState, subState] = parts as [string, string, string, string];
|
|
50
|
+
rows.push({ unit, loadState, activeState, subState, description: parts.slice(4).join(" ") });
|
|
51
|
+
}
|
|
52
|
+
return rows;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One Key=Value block from `systemctl show`. A value may itself contain "=". */
|
|
56
|
+
export function parseShowBlock(block: string): Record<string, string> {
|
|
57
|
+
const record: Record<string, string> = {};
|
|
58
|
+
for (const line of block.split("\n")) {
|
|
59
|
+
const eq = line.indexOf("=");
|
|
60
|
+
if (eq <= 0) continue;
|
|
61
|
+
record[line.slice(0, eq)] = line.slice(eq + 1);
|
|
62
|
+
}
|
|
63
|
+
return record;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A multi-unit `show` writes one block per unit separated by a blank line. Keyed
|
|
68
|
+
* by Id rather than by position: `show` answers under the unit's CANONICAL name,
|
|
69
|
+
* so asking about an alias comes back under a different one, and a positional
|
|
70
|
+
* read would attach every later unit's figures to the wrong row.
|
|
71
|
+
*/
|
|
72
|
+
export function parseShowRecords(stdout: string): Map<string, Record<string, string>> {
|
|
73
|
+
const byId = new Map<string, Record<string, string>>();
|
|
74
|
+
for (const block of stdout.split(/\n[ \t]*\n/)) {
|
|
75
|
+
const record = parseShowBlock(block);
|
|
76
|
+
if (record.Id) byId.set(record.Id, record);
|
|
77
|
+
}
|
|
78
|
+
return byId;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Properties one `show` call must ask for to fill a ServiceInfo. */
|
|
82
|
+
export const SHOW_PROPERTIES: readonly string[] = [
|
|
83
|
+
"Id", "Description", "LoadState", "ActiveState", "SubState", "UnitFileState", "MainPID",
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
/** Everything a details pane adds on top of a list row. */
|
|
87
|
+
export const SHOW_DETAIL_PROPERTIES: readonly string[] = [
|
|
88
|
+
...SHOW_PROPERTIES, "User", "Group", "FragmentPath",
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Mission Center's three booleans, derived exactly as it does: ActiveState
|
|
93
|
+
* "active" is running, "failed" is failed, and enabled is the unit-file state
|
|
94
|
+
* being the literal string "enabled" - static, indirect, alias, generated and
|
|
95
|
+
* masked all count as not enabled.
|
|
96
|
+
*/
|
|
97
|
+
export function toServiceInfo(
|
|
98
|
+
row: ListUnitsRow,
|
|
99
|
+
scope: ServiceScope,
|
|
100
|
+
show: Record<string, string> | undefined,
|
|
101
|
+
): ServiceInfo {
|
|
102
|
+
const activeState = show?.ActiveState || row.activeState;
|
|
103
|
+
const subState = show?.SubState || row.subState;
|
|
104
|
+
// An empty UnitFileState means the unit has no unit file, which is null rather
|
|
105
|
+
// than a state of "".
|
|
106
|
+
const fileState = show?.UnitFileState ?? "";
|
|
107
|
+
const mainPid = Number(show?.MainPID ?? "0");
|
|
108
|
+
return {
|
|
109
|
+
unit: row.unit,
|
|
110
|
+
scope,
|
|
111
|
+
description: row.description || show?.Description || "",
|
|
112
|
+
activeState,
|
|
113
|
+
subState,
|
|
114
|
+
unitFileState: fileState || null,
|
|
115
|
+
running: activeState === "active",
|
|
116
|
+
failed: activeState === "failed",
|
|
117
|
+
enabled: fileState === "enabled",
|
|
118
|
+
mainPid: Number.isFinite(mainPid) && mainPid > 0 ? mainPid : null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Drops what Mission Center drops: the wrong unit type, and a name systemd knows
|
|
123
|
+
* with no file behind it. Masked units deliberately stay in the list. */
|
|
124
|
+
export function keepUnit(row: ListUnitsRow): boolean {
|
|
125
|
+
return isListedUnit(row.unit) && row.loadState !== "not-found";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* `journalctl -o json`, one JSON object per line.
|
|
130
|
+
*
|
|
131
|
+
* The short-iso form would be shorter to parse and is wrong: a multi-line message
|
|
132
|
+
* - a stack trace, a core dump report - prints as continuation lines with no
|
|
133
|
+
* timestamp, so each would become its own bogus entry. The JSON form keeps one
|
|
134
|
+
* record per entry whatever is in it.
|
|
135
|
+
*/
|
|
136
|
+
export function parseJournalJson(stdout: string): ServiceLogLine[] {
|
|
137
|
+
const lines: ServiceLogLine[] = [];
|
|
138
|
+
for (const raw of stdout.split("\n")) {
|
|
139
|
+
if (!raw.trim()) continue;
|
|
140
|
+
let record: Record<string, unknown>;
|
|
141
|
+
try {
|
|
142
|
+
record = JSON.parse(raw) as Record<string, unknown>;
|
|
143
|
+
} catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const message = journalMessage(record.MESSAGE);
|
|
147
|
+
if (message === null) continue;
|
|
148
|
+
const micros = Number(record.__REALTIME_TIMESTAMP);
|
|
149
|
+
lines.push({ ts: Number.isFinite(micros) ? Math.round(micros / 1000) : 0, message });
|
|
150
|
+
}
|
|
151
|
+
return lines;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** journald keeps a message that is not valid UTF-8 as an array of byte values. */
|
|
155
|
+
export function journalMessage(value: unknown): string | null {
|
|
156
|
+
if (typeof value === "string") return value;
|
|
157
|
+
if (Array.isArray(value)) {
|
|
158
|
+
const bytes = value.filter((n): n is number => typeof n === "number" && n >= 0 && n <= 255);
|
|
159
|
+
return new TextDecoder().decode(Uint8Array.from(bytes));
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a session has sat idle long enough that its prompt cache is gone.
|
|
3
|
+
*
|
|
4
|
+
* The cost of a turn is dominated by the replayed transcript, and that replay is cheap only
|
|
5
|
+
* while the prefix behind it is still cached (see `turn-usage.ts`). The cache is the one
|
|
6
|
+
* part of that with a clock on it: it lapses on its own, silently, some time after the last
|
|
7
|
+
* turn — so the first message of the next morning costs several times what the last message
|
|
8
|
+
* of the night before did, for no reason visible in the conversation.
|
|
9
|
+
*
|
|
10
|
+
* `TurnCostWarning` reports that after the fact, on the turn that paid. This says it
|
|
11
|
+
* *before*, while the user can still decide whether to carry on in this chat or start a
|
|
12
|
+
* cheaper one. Pure and shared so the threshold is the same one the warning already uses.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { PREFIX_WARN_TOKENS } from "./turn-usage.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* What the server knows about a session's cache.
|
|
19
|
+
*
|
|
20
|
+
* `ttlMs` alone is the state of a session that has not completed a turn yet — it is a
|
|
21
|
+
* property of the install, not of the conversation, and sending it early is what lets a
|
|
22
|
+
* long-lived tab arm this notice from its own turns without waiting for a reconnect.
|
|
23
|
+
*/
|
|
24
|
+
export interface PromptCacheState {
|
|
25
|
+
/** This install's cache lifetime: an hour on a subscription, five minutes on an API key. */
|
|
26
|
+
ttlMs: number;
|
|
27
|
+
/** When the last turn completed — the moment the cache was last written. */
|
|
28
|
+
lastTurnEndedAt?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Cached prefix *billed* on the last turn — a running session total, not a transcript size.
|
|
31
|
+
*
|
|
32
|
+
* The SDK sums `modelUsage` across a streaming session, so a long chat reports tens of
|
|
33
|
+
* millions against a context window of one. It is a usable floor for "has this session
|
|
34
|
+
* cached anything worth losing" and nothing more; never show it as a number.
|
|
35
|
+
*/
|
|
36
|
+
billedPrefixTokens?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Context the last turn actually held — the figure that may be displayed.
|
|
39
|
+
*
|
|
40
|
+
* Measured from one API call's own `usage` rather than summed (see
|
|
41
|
+
* `TurnUsage.contextTokens`). Absent for a turn recorded before PPM measured this, so the
|
|
42
|
+
* notice has to read correctly without it.
|
|
43
|
+
*/
|
|
44
|
+
contextTokens?: number;
|
|
45
|
+
/**
|
|
46
|
+
* When this session was compacted, if it has been since its last cache write.
|
|
47
|
+
*
|
|
48
|
+
* A compaction replaces the conversation with a summary, so the cached prefix no longer
|
|
49
|
+
* matches anything that will be sent — the cache is gone regardless of how recently it was
|
|
50
|
+
* written. Cleared again by the first API call after the boundary, which caches the new
|
|
51
|
+
* prefix; it therefore only survives a compaction that nothing followed, which is exactly
|
|
52
|
+
* the `/compact` the user is about to type into.
|
|
53
|
+
*/
|
|
54
|
+
compactedAt?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Why a session's cache is gone. */
|
|
58
|
+
export type ColdReason = "expired" | "compacted";
|
|
59
|
+
|
|
60
|
+
export interface IdleCacheNotice {
|
|
61
|
+
/** How long since the last turn, or since the compaction, for the wording. */
|
|
62
|
+
idleMs: number;
|
|
63
|
+
reason: ColdReason;
|
|
64
|
+
/**
|
|
65
|
+
* Transcript that the next message re-sends, when PPM measured it.
|
|
66
|
+
*
|
|
67
|
+
* Never set for a compaction: what will be re-cached then is the summary the compaction
|
|
68
|
+
* produced, which no API call has reported a size for yet. Claude Code hardcodes the same
|
|
69
|
+
* `undefined` for that branch.
|
|
70
|
+
*/
|
|
71
|
+
contextTokens?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The notice to show, or null for nothing worth saying.
|
|
76
|
+
*
|
|
77
|
+
* Silent in three cases, all of them deliberate. Before the TTL, because the cache really is
|
|
78
|
+
* still warm and a countdown to a cost that has not happened is just noise. Below
|
|
79
|
+
* `PREFIX_WARN_TOKENS`, because a session that has never billed even that much has nothing
|
|
80
|
+
* worth warning about — the same floor the after-the-fact warning uses, so the two cannot
|
|
81
|
+
* disagree about what is worth mentioning. And with no state at all, because "PPM has not
|
|
82
|
+
* measured this" and "this session has nothing cached" must not be reported as the same thing.
|
|
83
|
+
*/
|
|
84
|
+
export function idleCacheNotice(
|
|
85
|
+
state: PromptCacheState | null | undefined,
|
|
86
|
+
now: number,
|
|
87
|
+
): IdleCacheNotice | null {
|
|
88
|
+
const status = promptCacheStatus(state, now);
|
|
89
|
+
if (status.kind !== "cold") return null;
|
|
90
|
+
return {
|
|
91
|
+
idleMs: status.idleMs,
|
|
92
|
+
reason: status.reason,
|
|
93
|
+
...(status.contextTokens != null && { contextTokens: status.contextTokens }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The cache's clock, for a countdown that is on screen the whole time.
|
|
99
|
+
*
|
|
100
|
+
* Same three gates as the notice, deliberately: a chip that turns red at the hour while no
|
|
101
|
+
* banner appears — or the reverse — asks the user to reconcile two readings of one fact.
|
|
102
|
+
* `unknown` means PPM has not measured this session, which must render as nothing rather
|
|
103
|
+
* than as a full or empty timer.
|
|
104
|
+
*/
|
|
105
|
+
export type PromptCacheStatus =
|
|
106
|
+
| { kind: "unknown" }
|
|
107
|
+
| { kind: "warm"; remainingMs: number }
|
|
108
|
+
| { kind: "cold"; reason: ColdReason; idleMs: number; contextTokens?: number };
|
|
109
|
+
|
|
110
|
+
export function promptCacheStatus(
|
|
111
|
+
state: PromptCacheState | null | undefined,
|
|
112
|
+
now: number,
|
|
113
|
+
): PromptCacheStatus {
|
|
114
|
+
if (!state) return { kind: "unknown" };
|
|
115
|
+
|
|
116
|
+
// Before every other question, including whether anything has been measured at all. A
|
|
117
|
+
// compaction does not make the cache *stale*, it makes it inapplicable — the prefix it
|
|
118
|
+
// holds is not the conversation any more — so a turn that finished ten seconds ago is
|
|
119
|
+
// still cold. Ordered exactly as Claude Code orders it, and it also settles the floor
|
|
120
|
+
// below: a session large enough to have been compacted is never a trivial one.
|
|
121
|
+
if (state.compactedAt != null) {
|
|
122
|
+
return { kind: "cold", reason: "compacted", idleMs: Math.max(0, now - state.compactedAt) };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// No completed turn means nothing has been cached, so there is nothing to lose yet.
|
|
126
|
+
if (state.lastTurnEndedAt == null || state.billedPrefixTokens == null) return { kind: "unknown" };
|
|
127
|
+
if (state.billedPrefixTokens < PREFIX_WARN_TOKENS) return { kind: "unknown" };
|
|
128
|
+
|
|
129
|
+
const idleMs = now - state.lastTurnEndedAt;
|
|
130
|
+
// A clock that disagrees between server and browser can make this negative; a turn that
|
|
131
|
+
// just finished is the warmest case there is, so it reads as "not idle" either way.
|
|
132
|
+
if (idleMs < state.ttlMs) {
|
|
133
|
+
// Clamped, so a skewed clock shows a full window rather than one longer than the TTL.
|
|
134
|
+
return { kind: "warm", remainingMs: Math.min(state.ttlMs, state.ttlMs - idleMs) };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
kind: "cold",
|
|
139
|
+
reason: "expired",
|
|
140
|
+
idleMs,
|
|
141
|
+
...(state.contextTokens != null && { contextTokens: state.contextTokens }),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const MINUTE_MS = 60_000;
|
|
146
|
+
const HOUR_MS = 60 * MINUTE_MS;
|
|
147
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* How long the session has been idle, at the coarsest unit that still says something.
|
|
151
|
+
*
|
|
152
|
+
* Minutes are dropped past a day: "2d 7h 13m" is three figures to answer "is this stale",
|
|
153
|
+
* which is the only question being asked.
|
|
154
|
+
*/
|
|
155
|
+
export function formatIdleDuration(ms: number): string {
|
|
156
|
+
if (ms >= DAY_MS) {
|
|
157
|
+
const days = Math.floor(ms / DAY_MS);
|
|
158
|
+
const hours = Math.floor((ms % DAY_MS) / HOUR_MS);
|
|
159
|
+
return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
160
|
+
}
|
|
161
|
+
if (ms >= HOUR_MS) {
|
|
162
|
+
const hours = Math.floor(ms / HOUR_MS);
|
|
163
|
+
const minutes = Math.floor((ms % HOUR_MS) / MINUTE_MS);
|
|
164
|
+
return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
165
|
+
}
|
|
166
|
+
return `${Math.max(1, Math.floor(ms / MINUTE_MS))}m`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Time left on the cache, for the composer's countdown chip.
|
|
171
|
+
*
|
|
172
|
+
* Rounded **up**, so a fresh turn reads the install's whole window (`60m`, not `59m`) and the
|
|
173
|
+
* last partial minute reads `1m` rather than `0m` — a zero here would say expired while the
|
|
174
|
+
* cache is still warm, which is the one reading the chip must never give.
|
|
175
|
+
*
|
|
176
|
+
* Minutes throughout, with no hours branch: the longest window PPM has is the subscription's
|
|
177
|
+
* hour, so the only thing an hours branch could ever render is the full window as `1h` — one
|
|
178
|
+
* more unit for the eye to parse, in the single case the chip is least interesting.
|
|
179
|
+
*/
|
|
180
|
+
export function formatCacheCountdown(ms: number): string {
|
|
181
|
+
return `${Math.max(1, Math.ceil(ms / MINUTE_MS))}m`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A measured context size, at the precision the decision needs.
|
|
186
|
+
*
|
|
187
|
+
* Whole thousands: the user is deciding whether to carry on in this chat or start a fresh
|
|
188
|
+
* one, and no part of that turns on the difference between 58.7k and 59k.
|
|
189
|
+
*/
|
|
190
|
+
export function formatContextTokens(tokens: number): string {
|
|
191
|
+
if (tokens >= 1_000_000) {
|
|
192
|
+
const millions = tokens / 1_000_000;
|
|
193
|
+
return `${millions >= 10 ? Math.round(millions) : millions.toFixed(1)}M`;
|
|
194
|
+
}
|
|
195
|
+
if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}k`;
|
|
196
|
+
return `${tokens}`;
|
|
197
|
+
}
|
package/src/shared/turn-usage.ts
CHANGED
|
@@ -46,6 +46,36 @@ export interface TurnUsage {
|
|
|
46
46
|
*/
|
|
47
47
|
accountId?: string;
|
|
48
48
|
accountLabel?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Live context held at the end of the turn — what the next message actually replays.
|
|
51
|
+
*
|
|
52
|
+
* Not derivable from anything else on this type. Every other field here is summed out of
|
|
53
|
+
* the result's `modelUsage`, which the SDK accumulates across a streaming session and
|
|
54
|
+
* across subagents: one measured session reported 55.9M prefix tokens against a 200k
|
|
55
|
+
* window, and per-turn deltas fare no better (one turn's delta was 4.2M — twenty-one
|
|
56
|
+
* windows — because twenty subagents each replayed their own prefix into the same sum).
|
|
57
|
+
*
|
|
58
|
+
* This comes instead from the `usage` on the turn's last top-level assistant message,
|
|
59
|
+
* which is one API call's own input side and so is a real context size. Absent when no
|
|
60
|
+
* such message carried usage; never substitute a summed figure for it.
|
|
61
|
+
*/
|
|
62
|
+
contextTokens?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Cache window the API reported for this turn, when it reported one.
|
|
65
|
+
*
|
|
66
|
+
* Outranks the provider's credential-shaped guess wherever both exist — see
|
|
67
|
+
* `messageCacheTtl`. Absent on a turn that only read the cache, and on every turn recorded
|
|
68
|
+
* before PPM read this field.
|
|
69
|
+
*/
|
|
70
|
+
cacheTtlMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* When a compaction invalidated this session's cache, if one did and nothing re-cached after.
|
|
73
|
+
*
|
|
74
|
+
* Not a timestamp of "the last compaction" — the provider clears it on the next API call,
|
|
75
|
+
* so an auto-compact mid-turn never reaches here. What survives is a compaction the turn
|
|
76
|
+
* ended on, which is the case where the user's next message pays to rebuild the prefix.
|
|
77
|
+
*/
|
|
78
|
+
compactedAt?: number;
|
|
49
79
|
}
|
|
50
80
|
|
|
51
81
|
/** Total prefix replayed to the API this turn, cached or not. */
|
|
@@ -66,7 +96,7 @@ export function uncachedPrefixTokens(u: TurnUsage): number {
|
|
|
66
96
|
*/
|
|
67
97
|
export function buildTurnUsage(
|
|
68
98
|
modelUsage: Record<string, ModelUsageLike> | undefined,
|
|
69
|
-
opts: { coldReason?: string; accountId?: string; accountLabel?: string } = {},
|
|
99
|
+
opts: { coldReason?: string; accountId?: string; accountLabel?: string; contextTokens?: number; cacheTtlMs?: number; compactedAt?: number } = {},
|
|
70
100
|
): TurnUsage | undefined {
|
|
71
101
|
if (!modelUsage) return undefined;
|
|
72
102
|
const entries = Object.entries(modelUsage);
|
|
@@ -113,9 +143,63 @@ export function buildTurnUsage(
|
|
|
113
143
|
...(opts.coldReason && { coldReason: opts.coldReason }),
|
|
114
144
|
...(opts.accountId && { accountId: opts.accountId }),
|
|
115
145
|
...(opts.accountLabel && { accountLabel: opts.accountLabel }),
|
|
146
|
+
...(opts.contextTokens != null && { contextTokens: opts.contextTokens }),
|
|
147
|
+
...(opts.cacheTtlMs != null && { cacheTtlMs: opts.cacheTtlMs }),
|
|
148
|
+
...(opts.compactedAt != null && { compactedAt: opts.compactedAt }),
|
|
116
149
|
};
|
|
117
150
|
}
|
|
118
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Context size carried by one assistant message's `usage`, or undefined if it carried none.
|
|
154
|
+
*
|
|
155
|
+
* The input side of a single API call: what the model was actually given, whether it arrived
|
|
156
|
+
* fresh or out of the cache. Output tokens are excluded — they are not in the context the
|
|
157
|
+
* *next* call replays until they have been written back as a message, and counting them here
|
|
158
|
+
* would inflate every turn by its own answer.
|
|
159
|
+
*/
|
|
160
|
+
export function messageContextTokens(usage: unknown): number | undefined {
|
|
161
|
+
if (!usage || typeof usage !== "object") return undefined;
|
|
162
|
+
const u = usage as Record<string, unknown>;
|
|
163
|
+
const num = (v: unknown): number => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
164
|
+
const total = num(u.input_tokens) + num(u.cache_read_input_tokens) + num(u.cache_creation_input_tokens);
|
|
165
|
+
// Zero is not a context, it is a frame that carried no input side — a usage object with
|
|
166
|
+
// only output tokens, say. Returning it would render as "re-sends about 0 tokens" on a
|
|
167
|
+
// full session. Matches Claude Code's own reader, which likewise sums all three fields
|
|
168
|
+
// without requiring any one of them and rejects a zero total.
|
|
169
|
+
return total > 0 ? total : undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Which prompt-cache window the API wrote this turn's prefix into. */
|
|
173
|
+
export type PromptCacheTtl = "1h" | "5m";
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The cache lifetime the API itself reports, or undefined when it reported none.
|
|
177
|
+
*
|
|
178
|
+
* PPM otherwise *infers* this from the credential — an `sk-ant-oat` token means a
|
|
179
|
+
* subscription and therefore an hour, anything else five minutes — which is a guess about
|
|
180
|
+
* billing made from the shape of a string. It is wrong for a proxy, for a custom `base_url`,
|
|
181
|
+
* and for a subscription past its usage limits, and being wrong is not cosmetic: the
|
|
182
|
+
* countdown and the idle banner both measure against this, so a session whose cache really
|
|
183
|
+
* lives an hour is declared cold at minute five.
|
|
184
|
+
*
|
|
185
|
+
* `usage.cache_creation` answers it outright. Mirrors Claude Code's own reader, including
|
|
186
|
+
* the order: a prefix written into both windows is reported as the longer one, because that
|
|
187
|
+
* is the one still standing. Returns the discriminant rather than milliseconds so this stays
|
|
188
|
+
* free of the server's constants — `shared/` is imported by the browser bundle.
|
|
189
|
+
*/
|
|
190
|
+
export function messageCacheTtl(usage: unknown): PromptCacheTtl | undefined {
|
|
191
|
+
if (!usage || typeof usage !== "object") return undefined;
|
|
192
|
+
const cc = (usage as { cache_creation?: unknown }).cache_creation;
|
|
193
|
+
if (!cc || typeof cc !== "object") return undefined;
|
|
194
|
+
const c = cc as Record<string, unknown>;
|
|
195
|
+
const num = (v: unknown): number => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
196
|
+
if (num(c.ephemeral_1h_input_tokens) > 0) return "1h";
|
|
197
|
+
if (num(c.ephemeral_5m_input_tokens) > 0) return "5m";
|
|
198
|
+
// A turn that only *read* the cache writes nothing, so it names no window. That is silence,
|
|
199
|
+
// not five minutes: answering the short one here would expire a warm hour-long cache.
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
119
203
|
/**
|
|
120
204
|
* A prefix smaller than this is cheap however it is billed, so a poor hit rate on it is not
|
|
121
205
|
* worth interrupting the user over. Short sessions legitimately start cold.
|